chore(docs): cleanup + restructuring

This commit is contained in:
2026-07-04 11:41:11 +00:00
parent e58234eaf9
commit 31e11e7969
18 changed files with 0 additions and 475 deletions

View File

@@ -1,131 +0,0 @@
---
topic: decision-guide
source_keys:
- context7-websites-code-claude
- context7-websites-github-en-copilot
- copilot-comparing-cli-features
- copilot-customization-cheat-sheet
- claude-code-plugins-reference
- claude-code-whats-new-2026-w14
---
## Master Decision Table
From the official Copilot CLI comparing-features guide:
| Requirement | Component |
|---|---|
| Copilot should always follow repository conventions | Custom instructions / `CLAUDE.md` |
| Repeatable on-demand workflow | Skill |
| Consistent prompting / broad standards | Custom instructions (not hooks) |
| Guardrails, policy, or automation around tool / session events | Hook |
| External service tool access | MCP server |
| Specialist operation with constrained toolset | Custom agent |
| Complex multi-step task delegation | Subagent (spawned automatically) |
| Package functionality for distribution | Plugin |
---
## Gap 1: MCP Servers vs Skills vs Agents
These three are often confused because all involve extending what Claude can do. The distinction is runtime model:
| | Skill | Agent | MCP Server |
|---|---|---|---|
| Runtime | No process — Claude reads instructions | Claude Code spawns a subagent session | Long-running external process |
| What it adds | Workflow instructions / prompt guidance | A scoped collaborator with its own model and tools | Callable tool functions |
| Who invokes | Claude (by reasoning) or user (`/name`) | Claude (by delegation) or user (`@agent-name`) | Claude (by reasoning, like any tool) |
| Isolation | None — runs in the main conversation | Full — own context window, system prompt, tool list | Full — separate process |
| Best for | Multi-step workflows Claude follows | Domain-focused tasks requiring extended reasoning or parallelism | External APIs, databases, real-time data, persistent services |
**Decision questions:**
1. Does this require a new callable function (e.g. "query database", "fetch issues", "run browser test")? → **MCP server.** MCP adds new tool verbs Claude can call, like built-ins.
2. Does this require a specialist persona with a restricted tool set and its own context? → **Agent.** Use when a task needs domain focus, turn budgeting, or tool isolation that would pollute the main conversation.
3. Does this require prompt guidance and workflow instructions Claude reasons about and follows? → **Skill.** Skills are the lightest option — no process, no isolation overhead, no daemon. Prefer skills when the capability is instructional rather than procedural-external.
**Anti-patterns:**
- Do not put an API call inside a skill body and expect it to work reliably — skills are instruction sets, not execution environments. Wrap the API in an MCP server tool instead.
- Do not use an agent when a skill suffices. Agents have overhead (isolated context, spawn cost). Use them only when isolation or domain specialization is the point.
- Do not use MCP for self-contained multi-step workflows that don't touch external systems — a skill handles that more cheaply.
---
## Gap 2: Hooks vs Skills
The official Copilot guide states it directly: "Avoid hooks when you primarily need consistent prompting (use skills), persistent preferences (use custom instructions), new external capabilities (use MCP servers)."
| | Skill | Hook |
|---|---|---|
| Trigger | Claude's reasoning or user slash command | Automatic — fires on matching lifecycle event |
| Format | Markdown prompt/instructions | JSON event matcher + shell/HTTP command |
| Can block tool calls | No | Yes (`PreToolUse`, `UserPromptExpansion`) |
| Can modify tool input/output | No | Yes (`updatedInput`, `updatedToolOutput`) |
| Runs deterministically | No — Claude decides | Yes — fires every time the event matches |
| Reasoning involved | Yes | None |
| Best for | Repeatable workflows Claude follows, reusable output formatting, optional task guidance | Security enforcement, context injection, post-processing, auditing, session automation |
**Decision questions:**
1. Must this execute at a specific lifecycle point regardless of what Claude plans to do? → **Hook.** Hooks fire unconditionally on event match — Claude cannot suppress them.
2. Must this block a tool call before it executes? → **Hook (`PreToolUse`).** Skills cannot block tool calls.
3. Is this a reusable workflow with multiple steps Claude should reason about? → **Skill.** Skills let Claude adapt to context; hooks execute blindly.
4. Does this inject dynamic per-session context (current branch, open tickets, environment state)? → **Hook (`SessionStart`).** Hooks get this information from the environment without any model inference cost.
5. Must this run after every file edit, every session end, or every prompt submission? → **Hook.** Skills run when Claude judges them relevant; hooks run every time.
**Examples by category:**
| Use case | Component | Reason |
|---|---|---|
| Auto-lint after every Write/Edit | Hook (PostToolUse) | Guaranteed execution, no reasoning |
| Block `rm -rf` commands | Hook (PreToolUse) | Synchronous enforcement before execution |
| Inject current git branch at session start | Hook (SessionStart) | Dynamic env context, zero model cost |
| Guide Claude through a release note workflow | Skill | Multi-step reasoning, optional invocation |
| Scan for secrets after file write | Hook (PostToolUse) | Must always run, cannot be skipped |
| Repeatable output formatting template | Skill | Claude reads and applies; no auto-trigger needed |
| Archive transcript when session ends | Hook (SessionEnd) | Session lifecycle, not reasoning-driven |
| Validate subagent output before returning | Hook (SubagentStop) | Lifecycle control point |
| Enforce ticket ID in commit messages | Hook (PreToolUse, matcher: Bash) | Policy enforcement before tool runs |
---
## Gap 3: `bin/` vs `scripts/` in a Skill
Both are ways to ship executable code alongside a plugin, but they differ in scope and invocation:
| | `bin/` (plugin root) | `scripts/` (inside `skills/<name>/`) |
|---|---|---|
| Scope | All Bash tool calls session-wide while plugin is enabled | That skill's content only |
| Invocation | Bare command name (`my-tool`) | Full path via `${CLAUDE_PLUGIN_ROOT}/skills/<name>/scripts/my-script.sh` |
| Shared? | Yes — any skill, agent, or hook can call it | No — local to the skill |
| PATH injection | Yes — added to Bash tool's PATH | No |
| Purpose | Shared CLI helpers used by multiple components | Skill-specific support scripts |
**Decision rule:**
- Use `bin/` when a utility is called by more than one component (two skills, a skill and a hook, etc.), or when you want Claude to be able to invoke it as a bare command in Bash without knowing the plugin's install path.
- Use `scripts/` when a helper is only needed by one skill and should be kept self-contained. This also supports the cache-isolation guarantee — the skill directory is fully portable.
**Anti-pattern:** Do not reference `scripts/` files from a hook or a different skill using a relative path — those paths break in plugin cache mode. Either put the shared file in `bin/`, or duplicate it into each skill's `scripts/` directory.
---
## Surface Support (Copilot)
Some components are not available on all surfaces. Verify availability before building:
| Component | VS Code | JetBrains | GitHub.com | Copilot CLI |
|---|:---:|:---:|:---:|:---:|
| Skills | yes | yes | yes | yes |
| Custom agents | yes | preview | yes | yes |
| Hooks | preview | no | yes | yes |
| MCP servers | yes | yes | yes | yes |
| Subagents | yes | preview | no | yes |
Hooks have the most limited surface support. If a behavior must work in JetBrains or non-CLI contexts, prefer a skill or MCP approach.

View File

@@ -1,191 +0,0 @@
---
topic: hooks
source_keys:
- claude-code-hooks
- claude-code-plugins-reference
- copilot-hooks
- copilot-customization-cheat-sheet
- context7-websites-code-claude
- context7-websites-github-en-copilot
---
## What Hooks Are
Hooks are user-defined shell commands or HTTP endpoints that execute deterministically at specific points in an agent's lifecycle. No model reasoning is involved — the hook fires unconditionally whenever its configured event matches, regardless of what Claude intended to do.
This is the defining property: hooks enforce invariants outside Claude's reasoning path. They are not invoked by Claude; they are triggered by the runtime on event occurrence.
---
## Lifecycle Events
### Claude Code
**Session-level** (once per session):
| Event | When | Can block? |
|---|---|---|
| `SessionStart` | Session begins or resumes | No |
| `Setup` | `--init-only` / `--init` / `--maintenance` mode | No |
| `SessionEnd` | Session terminates | No |
**Per-turn** (once per user prompt):
| Event | When | Can block? |
|---|---|---|
| `UserPromptSubmit` | User submits a prompt, before Claude processes it | Yes (exit 2) |
| `UserPromptExpansion` | A slash command expands into a prompt | Yes |
| `Stop` | Claude finishes responding | Yes |
| `StopFailure` | Turn ends due to API error | No |
**Agentic loop** (per tool call):
| Event | When | Can block? |
|---|---|---|
| `PreToolUse` | Before a tool call executes | Yes |
| `PermissionRequest` | Permission dialog appears | Yes |
| `PermissionDenied` | Tool denied by auto-mode classifier | No (can retry) |
| `PostToolUse` | After a tool call succeeds | Yes (further turns) |
| `PostToolUseFailure` | After a tool call fails | No |
| `PostToolBatch` | After a batch of parallel tool calls resolves | Yes |
**Subagent / team events**:
| Event | When | Can block? |
|---|---|---|
| `SubagentStart` | Subagent spawns | No |
| `SubagentStop` | Subagent finishes | Yes |
| `TeammateIdle` | Agent team teammate about to go idle | Yes |
**Task management**:
| Event | When | Can block? |
|---|---|---|
| `TaskCreated` | Task being created via `TaskCreate` | Yes |
| `TaskCompleted` | Task being marked complete | Yes |
**File / config (async)**:
| Event | When | Can block? |
|---|---|---|
| `FileChanged` | Watched file changes on disk | No |
| `ConfigChange` | Config file changes during session | Yes |
| `CwdChanged` | Working directory changes | No |
| `InstructionsLoaded` | `CLAUDE.md` or `.claude/rules/*.md` loaded | No |
**Context compaction**:
| Event | When | Can block? |
|---|---|---|
| `PreCompact` | Before context compaction | Yes |
| `PostCompact` | After compaction completes | No |
**MCP elicitation**:
| Event | When | Can block? |
|---|---|---|
| `Elicitation` | MCP server requests user input during a tool call | Yes |
| `ElicitationResult` | After user responds to MCP elicitation | Yes |
**Worktree**:
| Event | When | Can block? |
|---|---|---|
| `WorktreeCreate` | Worktree being created | Yes |
| `WorktreeRemove` | Worktree being removed | No |
### Copilot CLI
Copilot CLI uses a subset: `sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`, `postToolUse`, `errorOccurred`, `agentStop`.
---
## Inputs
Every hook receives a JSON object on stdin with these common fields:
```json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"agent_id": "optional-subagent-id",
"agent_type": "optional-agent-name"
}
```
Tool events additionally include `tool_name` and `tool_input`. `UserPromptSubmit` adds `prompt` and `messages_in_context`. `SessionStart` adds `source` (`startup`, `resume`, `clear`, `compact`), and optionally `model` and `session_title`. `FileChanged` adds `file_path` and `change_type`.
---
## Outputs
**Exit codes:**
- `0` — success; Claude Code parses stdout as JSON for structured control
- `2` — blocking error; stderr sent to Claude; blocks the tool call on PreToolUse
- Other — non-blocking error; first line of stderr shown in transcript; execution continues
**Universal JSON output fields (exit 0 only):**
| Field | Effect |
|---|---|
| `continue: false` | Stops the current turn with `stopReason` message |
| `suppressOutput: true` | Hides hook output from the UI |
| `systemMessage` | Warning shown to the user in the UI |
**Event-specific output via `hookSpecificOutput`:**
`PreToolUse` — can allow, deny, or modify:
- `permissionDecision: "allow"` / `"deny"` with `permissionDecisionReason`
- `updatedInput` — replace the tool's input before it executes
- `additionalContext` — inject text into Claude's context
`PostToolUse` — can block further turns or replace what Claude sees:
- `decision: "block"` with `reason`
- `updatedToolOutput` — replace the tool result text Claude receives
- `additionalContext`
`Stop` / `SubagentStop` — can block turn completion:
- `decision: "block"` with `reason` (causes Claude to continue the agentic loop)
`SessionStart` — richest output; can inject context, set title, watch paths, reload skills:
- `additionalContext` — dynamic context added to Claude's context at session start
- `watchPaths` — file paths to monitor for `FileChanged` events
- `reloadSkills: true` — force skill reload
- `sessionTitle` — set or auto-generate the session title
Plain stdout behavior by event: for `SessionStart`, `Setup`, `SubagentStart`, `UserPromptSubmit`, and `UserPromptExpansion`, plain text stdout is added as context visible to the model. For all other events, plain text goes to the debug log only — use `hookSpecificOutput.additionalContext` instead.
---
## Common Patterns
**Block a dangerous command (PreToolUse):**
```json
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "rm -rf is blocked by policy"
}
}
```
**Inject git state at session start (SessionStart):**
```bash
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"Branch: $(git branch --show-current)\nOpen issues: $(gh issue list --limit 5 --json number,title)\"}}"
```
**Auto-lint after edit (PostToolUse, matcher: `Edit|Write`):**
Exit 0 to continue, or exit 2 with lint errors in stderr to block the turn.
**Validate subagent output before returning (SubagentStop):**
Exit 2 if validation fails; Claude will retry the subagent call.
---
## Plugin Scope Restriction
Plugin-shipped hooks (in `hooks/hooks.json` inside a plugin directory) are silently ignored when the agent definition is loaded from a plugin's `agents/` directory. Per-agent hooks only work when the agent file is at `.claude/agents/` or `~/.claude/agents/`. This is a known limitation — copy the agent file to project or user scope to use per-agent hooks.

View File

@@ -1,89 +0,0 @@
---
topic: overview
source_keys:
- context7-websites-code-claude
- context7-websites-github-en-copilot
- claude-code-plugins-reference
- copilot-comparing-cli-features
---
## What Plugin Components Are
A plugin bundles multiple component types. Each has a distinct runtime role. Understanding which role a problem requires is the first decision.
The two primary axes are:
**Axis 1 — Who decides to invoke it?**
| Claude reasons about invoking | Fires automatically / deterministically |
|---|---|
| Skills | Hooks |
| Agents | Monitors (background watchers) |
| MCP server tools (Claude picks when to call) | MCP server process (always running when plugin enabled) |
**Axis 2 — What does it add?**
| Adds instructions / workflow guidance | Adds callable capabilities | Delegates work |
|---|---|---|
| Skills | MCP servers (new tool verbs) | Agents (subagent sessions) |
| Custom instructions | Hooks (side-effects, enforcement) | Subagents (parallel subtasks) |
---
## Component Roles
### Skills (`skills/`)
Markdown instruction files. Claude reads them and reasons about whether and how to follow them. No background process. No automatic trigger. Claude loads a skill when it judges the task matches — or the user invokes via slash command.
Skills are prompt guidance, not programs. They extend what Claude knows how to do.
### Agents (`agents/`)
Specialized subagent sessions with their own system prompt, tool restrictions, model choice, and turn budget. Claude spawns them when it needs to delegate a task requiring extended reasoning or domain focus. They run in isolated context windows — they do not inherit the parent conversation.
Agents extend what Claude can delegate to, not what it knows itself.
### MCP Servers (`.mcp.json`)
External processes that expose callable tool functions via the Model Context Protocol. They start automatically when the plugin is enabled and run as background services. Claude calls their tools the same way it calls built-ins (Read, Bash, etc.) — but decides when to call them based on context.
MCP servers extend the tool set Claude can call.
### Hooks (`hooks/hooks.json`)
Shell commands or HTTP endpoints that fire deterministically at specific lifecycle events. No model reasoning involved — the hook runs every time its event fires, regardless of what Claude intends. Hooks can block tool calls, modify inputs/outputs, inject context, and enforce policies.
Hooks enforce what happens automatically, outside of Claude's reasoning.
### `bin/` (plugin root)
Executables placed here are added to the Bash tool's `PATH` for the duration of the session while the plugin is enabled. Claude — or any hook, skill script, or agent — can invoke them as bare commands without absolute paths. Scoped to all Bash calls session-wide.
`bin/` is shared infrastructure for the plugin's other components.
### `scripts/` (inside a skill directory)
Local support files for one skill. Not on PATH. Referenced by absolute path via `${CLAUDE_PLUGIN_ROOT}` in hook commands or skill content. Scoped to the single skill, not available to other components.
### Monitors (`monitors/monitors.json`)
Background shell processes that run continuously and stream their stdout to Claude as notifications. Use for watching logs, polling external status, or reacting to filesystem changes in real time.
### `settings.json` (plugin root)
Default settings applied when the plugin is enabled. Currently supports `agent` (activate a named agent as the main session thread) and `subagentStatusLine`. Project and user settings override plugin settings on collision.
---
## Cross-Provider Portability
`SKILL.md` is portable — the same file works in Claude Code and Copilot CLI. All other component formats are provider-specific:
| Component | Claude Code format | Copilot CLI format |
|---|---|---|
| Skills | `skills/<name>/SKILL.md` | `skills/<name>/SKILL.md` (identical) |
| Agents | `.md` with YAML frontmatter | `.agent.md` with YAML frontmatter |
| Hooks | `hooks/hooks.json` | `hooks.json` (same schema) |
| MCP servers | `.mcp.json` | `.mcp.json` (same schema) |
| Manifest | `.claude-plugin/plugin.json` | `plugin.json` at root |

View File

@@ -1,64 +0,0 @@
# Sources
## context7-websites-code-claude
- **URL:** context7:/websites/code_claude
- **Description:** Official Claude Code documentation via Context7 — plugin architecture, component types, bin/ directory, hooks, MCP, agent SDK
- **Contributing files:** overview.md, decision-guide.md, hooks.md
- **Status:** `extracted`
## context7-websites-github-en-copilot
- **URL:** context7:/websites/github_en_copilot
- **Description:** Official GitHub Copilot documentation via Context7 — CLI plugin system, component comparison, hooks, MCP, custom agents
- **Contributing files:** overview.md, decision-guide.md, hooks.md
- **Status:** `extracted`
## copilot-comparing-cli-features
- **URL:** https://docs.github.com/en/copilot/concepts/agents/copilot-cli/comparing-cli-features
- **Description:** Copilot CLI component comparison guide — when to use custom instructions, skills, tools, MCP servers, hooks, subagents, custom agents, and plugins; includes the "Putting it together" decision table and "When shouldn't you use hooks?" section
- **Contributing files:** overview.md, decision-guide.md
- **Status:** `extracted`
## copilot-customization-cheat-sheet
- **URL:** https://docs.github.com/en/copilot/reference/customization-cheat-sheet
- **Description:** Copilot customization cheat sheet — full comparison table covering trigger mechanism, best-for scenarios, and IDE surface support matrix for all component types
- **Contributing files:** decision-guide.md
- **Status:** `extracted`
## copilot-hooks
- **URL:** https://docs.github.com/en/copilot/concepts/agents/hooks
- **Description:** Copilot hooks documentation — lifecycle events, hook configuration, example hook JSON for sessionStart, userPromptSubmitted, preToolUse, postToolUse, sessionEnd
- **Contributing files:** hooks.md
- **Status:** `extracted`
## claude-code-plugins-reference
- **URL:** https://code.claude.com/docs/en/plugins-reference
- **Description:** Claude Code plugins reference — full component directory layout, skills vs commands, agents frontmatter, hooks event list, MCP server config, bin/ directory, monitors, settings.json; includes per-component when-to-use guidance
- **Contributing files:** overview.md, decision-guide.md, hooks.md
- **Status:** `extracted`
## claude-code-whats-new-2026-w14
- **URL:** https://code.claude.com/docs/en/whats-new/2026-w14
- **Description:** Claude Code v2.1.91 changelog — bin/ directory PATH injection feature introduction and structure example
- **Contributing files:** decision-guide.md
- **Status:** `extracted`
## claude-code-hooks
- **URL:** https://code.claude.com/docs/en/hooks
- **Description:** Claude Code hooks deep reference — full lifecycle event table, stdin JSON schema, exit code semantics, hookSpecificOutput per event, hooks vs skills comparison table, hooks vs MCP servers vs agents
- **Contributing files:** hooks.md, decision-guide.md
- **Status:** `extracted`
## claude-code-agent-sdk-plugins
- **URL:** https://code.claude.com/docs/en/agent-sdk/plugins
- **Description:** Claude Code Agent SDK plugin documentation — plugin directory structure, skills vs commands distinction, component auto-discovery
- **Contributing files:** overview.md
- **Status:** `extracted`

View File

@@ -1,693 +0,0 @@
---
topic: api-reference
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Gitea MCP — Tool Reference
Full parameter schemas and response shapes for all MCP tools. Tools marked `[W]` are write operations suppressed in `--read-only` mode. Parameters listed as `required*` are conditionally required depending on the `method` value.
---
## Issues
### `list_issues` [R]
List repository issues or pull requests.
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `state` (string, optional, default: `"all"`) — `"open"`, `"closed"`, `"all"`
- `type` (string, optional) — `"issues"` or `"pulls"` to filter; omit for both
- `labels` (array of strings, optional) — filter by label names
- `milestones` (array, optional) — filter by milestone name or ID
- `since` (string, optional) — ISO 8601; issues updated after this time
- `before` (string, optional) — ISO 8601; issues updated before this time
- `page` (number, optional, default: 1)
- `per_page` (number, optional, default: 30)
**Response shape (list item):**
```
number, title, state, html_url, user (login), comments, created_at, updated_at
labels? ([]string), milestone? ({id, title}), ref?, deadline?
```
Body and `closed_at` are omitted from list responses.
---
### `issue_read` [R]
Read a single issue's details, comments, or label list.
**Parameters:**
- `method` (string, required) — `"get"` | `"get_comments"` | `"get_labels"`
- `owner` (string, required)
- `repo` (string, required)
- `issue_number` (number, required)
**Response shapes:**
`get` — full issue:
```
number, title, body, state, html_url, user, labels ([]string),
comments, created_at, updated_at, closed_at
assignees? ([]string), milestone? ({id, title}), ref?, deadline?,
is_pull? (true — present only when backed by a PR)
```
`get_comments` — array of:
```
id (int64), body, user, html_url, created_at, updated_at
```
`get_labels` — array of label objects from the Gitea API (not slimmed, full label objects including id, name, color).
---
### `issue_write` [W]
Create or mutate an issue, its comments, or its labels.
**Parameters:**
- `method` (string, required) — one of:
`"create"` | `"update"` | `"add_comment"` | `"edit_comment"` | `"add_labels"` | `"remove_label"` | `"replace_labels"` | `"clear_labels"`
- `owner` (string, required)
- `repo` (string, required)
- `issue_number` (number, required for all except `"create"`)
- `title` (string, required for `"create"`)
- `body` (string, required for `"create"`, `"add_comment"`, `"edit_comment"`)
- `assignees` (array of strings, optional) — login names
- `milestone` (number, optional) — milestone ID (not title, not number)
- `state` (string, optional) — `"open"` | `"closed"` | `"all"` (for `"update"`)
- `commentID` (number, optional, required for `"edit_comment"`)
- `labels` (array of numbers, optional) — label IDs (not names) for add/replace ops
- `label_id` (number, optional, required for `"remove_label"`)
- `ref` (string, optional) — branch association
- `deadline` (string, optional) — ISO 8601
- `remove_deadline` (boolean, optional)
**Critical:** `labels` takes IDs (numbers), not names. Must resolve label names to IDs via `label_read` before labeling.
---
### `search_issues` [R]
Search issues and PRs across repositories.
**Parameters:**
- `query` (string, required)
- `state` (string, optional) — `"open"` | `"closed"` | `"all"`
- `type` (string, optional) — `"issues"` | `"pulls"`
- `labels` (string, optional) — comma-separated label names
- `owner` (string, optional) — restrict to owner
- `page` (number, optional, default: 1)
- `per_page` (number, optional, default: 30)
---
## Pull Requests
### `list_pull_requests` [R]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `state` (string, optional, default: `"all"`) — `"open"` | `"closed"` | `"all"`
- `sort` (string, optional, default: `"recentupdate"`) — `"oldest"` | `"recentupdate"` | `"leastupdate"` | `"mostcomment"` | `"leastcomment"` | `"priority"`
- `milestone` (number, optional) — milestone ID filter
- `page` (number, optional, default: 1)
- `per_page` (number, optional, default: 30)
**Response shape (list item — heavily trimmed):**
```
number, title, state, draft, merged, html_url, user, created_at, updated_at
head? (ref string only), base? (ref string only), labels? ([]string)
```
Body, mergeable, comments, closed_at, and diff stats are omitted from list responses. Head/base are bare ref strings in the list, not objects.
---
### `pull_request_read` [R]
**Parameters:**
- `method` (string, required) — `"get"` | `"get_diff"` | `"get_files"` | `"get_status"` | `"get_reviews"` | `"get_review"` | `"get_review_comments"`
- `owner` (string, required)
- `repo` (string, required)
- `pull_number` (number, required)
- `review_id` (number, optional, required for `"get_review"` and `"get_review_comments"`)
- `binary` (boolean, optional) — include binary diff
- `page` (number, optional, default: 1)
- `per_page` (number, optional, default: 30)
**Response shapes:**
`get` — full PR:
```
number, title, body, state, draft, merged, mergeable, html_url, user,
labels ([]string), comments, created_at, updated_at, closed_at
head? ({ref, sha, repo?: {full_name, description}}),
base? ({ref, sha, repo?: {full_name, description}}),
additions?, deletions?, changed_files?,
merged_at?, merge_commit_sha?, merged_by?,
assignees? ([]string),
milestone? (string — title only, NOT an object),
review_scomments? (int — note: misspelled key in source, not review_comments)
```
`get_diff` — raw diff text
`get_files` — list of changed file objects
`get_status` — combined commit status for the PR head commit
`get_reviews` — array of review objects:
```
id, state, body, user (login), comments_count, submitted_at, html_url,
stale (bool), official (bool), dismissed (bool)
```
`get_review_comments` — array of inline review comments:
```
id, body, path, position (new line), old_position, diff_hunk,
user (login), html_url, created_at, updated_at
```
---
### `pull_request_write` [W]
**Parameters:**
- `method` (string, required) — `"create"` | `"update"` | `"close"` | `"reopen"` | `"merge"` | `"update_branch"` | `"add_reviewers"` | `"remove_reviewers"`
- `owner` (string, required)
- `repo` (string, required)
- `pull_number` (number, required except for `"create"`)
- `title` (string, required for `"create"`, optional for `"update"` and `"merge"`)
- `body` (string, required for `"create"`, optional for `"update"`)
- `head` (string, required for `"create"`) — source branch; use `owner:branch` for cross-repo
- `base` (string, required for `"create"`) — target branch
- `assignee` (string, optional)
- `assignees` (array of strings, optional)
- `milestone` (number, optional) — milestone ID
- `state` (string, optional) — `"open"` | `"closed"`
- `allow_maintainer_edit` (boolean, optional)
- `labels` (array of numbers, optional) — label IDs
- `deadline` (string, optional) — ISO 8601
- `remove_deadline` (boolean, optional)
- `merge_style` (string, optional, default: `"merge"`) — `"merge"` | `"rebase"` | `"rebase-merge"` | `"squash"` | `"fast-forward-only"`
- `message` (string, optional) — merge commit message or review dismissal reason
- `delete_branch` (boolean, optional) — delete head branch after merge
- `force_merge` (boolean, optional) — merge even if checks fail
- `merge_when_checks_succeed` (boolean, optional)
- `head_commit_id` (string, optional) — expected head SHA for conflict detection
- `reviewers` (array of strings, optional) — login names
- `team_reviewers` (array of strings, optional)
- `draft` (boolean, optional) — marks PR as draft by prefixing title with `"WIP:"`
---
### `pull_request_review_write` [W]
**Parameters:**
- `method` (string, required) — `"create"` | `"submit"` | `"delete"` | `"dismiss"`
- `owner` (string, required)
- `repo` (string, required)
- `pull_number` (number, required)
- `review_id` (number, optional, required except for `"create"`)
- `state` (string, optional) — `"APPROVED"` | `"REQUEST_CHANGES"` | `"COMMENT"` | `"PENDING"`
- `body` (string, optional)
- `commit_id` (string, optional) — for `"create"`; anchors inline comments to a commit
- `message` (string, optional) — dismissal reason for `"dismiss"`
- `comments` (array, optional) — inline comments for `"create"`:
each object: `{path, body, old_line_num, new_line_num}`
---
## Labels
### `label_read` [R]
**Parameters:**
- `method` (string, required) — `"list_repo_labels"` | `"get_repo_label"` | `"list_org_labels"`
- `owner` (string, optional, required for repo methods)
- `repo` (string, optional, required for repo methods)
- `org` (string, optional, required for org methods)
- `id` (number, optional, required for `"get_repo_label"`)
- `page` (number, optional, default: 1)
- `per_page` (number, optional, default: 30)
**Response:** Full label objects including `id`, `name`, `color`, `description`, `exclusive`, `is_archived`. Use this to map label names to IDs before write operations.
---
### `label_write` [W]
**Parameters:**
- `method` (string, required) — `"create_repo_label"` | `"edit_repo_label"` | `"delete_repo_label"` | `"create_org_label"` | `"edit_org_label"` | `"delete_org_label"`
- `owner` (string, optional, required for repo methods)
- `repo` (string, optional, required for repo methods)
- `org` (string, optional, required for org methods)
- `id` (number, optional, required for edit/delete)
- `name` (string, optional, required for create)
- `color` (string, optional, required for create) — hex format `#RRGGBB`
- `description` (string, optional)
- `exclusive` (boolean, optional) — org labels only; makes label mutually exclusive within a group
- `is_archived` (boolean, optional) — repo labels only
---
## Milestones
### `milestone_read` [R]
**Parameters:**
- `method` (string, required) — `"get"` | `"list"`
- `owner` (string, required)
- `repo` (string, required)
- `id` (number, optional, required for `"get"`) — milestone ID
- `state` (string, optional, default: `"all"`) — `"open"` | `"closed"` | `"all"`
- `name` (string, optional) — filter by title for `"list"`
- `page` (number, optional, default: 1)
- `per_page` (number, optional, default: 30)
**Response:** Full milestone objects including `id`, `title`, `description`, `state`, `due_on`, `closed_at`, `open_issues`, `closed_issues`.
---
### `milestone_write` [W]
**Parameters:**
- `method` (string, required) — `"create"` | `"update"` | `"edit"` | `"delete"`
(`"update"` and `"edit"` are aliases for the same operation)
- `owner` (string, required)
- `repo` (string, required)
- `id` (number, optional, required for update/delete)
- `title` (string, optional, required for create)
- `description` (string, optional)
- `due_on` (string, optional) — ISO 8601 date
- `state` (string, optional) — `"open"` | `"closed"`
---
## Branches
### `list_branches` [R]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `page` (number, optional, default: 1)
- `per_page` (number, optional, default: 30)
**Response shape per branch:**
```
name (string), protected (bool), commit_sha? (string — present when Commit != nil)
```
---
### `create_branch` [W]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `branch` (string, required) — new branch name
- `old_branch` (string, optional) — source branch; defaults to repo default branch
---
### `delete_branch` [W]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `branch` (string, required)
---
## Commits
### `list_commits` [R]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `sha` (string, optional) — starting SHA or branch name
- `path` (string, optional) — filter commits touching this path
- `page` (number, optional, default: 1, min: 1)
- `per_page` (number, optional, default: 30, min: 1)
**Response shape per commit:**
```
sha, html_url, created
message? (string — present when RepoCommit != nil),
author? ({name, email, date} — present when RepoCommit.Author != nil)
```
---
### `get_commit` [R]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `sha` (string, required)
**Response:** Same shape as list commit but always includes full detail.
---
## Files
### `get_file_contents` [R]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `ref` (string, required) — branch name, tag, or commit SHA
- `path` (string, required)
- `withLines` (boolean, optional) — return content with line numbers
**Response:**
```
name, path, sha, type, size
content? (string, base64-encoded), encoding?, html_url?, download_url?
```
The `sha` in the response is required when updating or deleting this file.
---
### `create_or_update_file` [W]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `path` (string, required)
- `content` (string, required) — base64-encoded file content
- `message` (string, required) — commit message
- `branch_name` (string, required) — target branch
- `sha` (string, optional, required when updating an existing file) — current file SHA
- `new_branch_name` (string, optional) — create a new branch during the operation
To update an existing file: must provide the file's current `sha` (get it from `get_file_contents` first). Without `sha`, Gitea treats the operation as a create and returns 409 if the file already exists.
---
### `delete_file` [W]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `path` (string, required)
- `message` (string, required) — commit message
- `branch_name` (string, required)
- `sha` (string, required) — current file SHA; must match the server's current SHA
---
### `get_dir_contents` [R]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `ref` (string, required)
- `path` (string, required)
**Response:** Array of directory entry objects:
```
name, path, type, size
```
No sha, no content, no URLs in directory listings.
---
### `get_repository_tree` [R]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `tree_sha` (string, required) — SHA, branch, or tag
- `recursive` (boolean, optional) — recurse into subdirectories
- `page` (number, optional, default: 1)
- `per_page` (number, optional, default: 30)
**Response:**
```
{sha, truncated (bool), total_count (int), tree: [{path, mode, type, size, sha}, ...]}
```
---
## Releases and Tags
### `list_releases` [R]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `is_draft` (boolean, optional)
- `is_pre_release` (boolean, optional)
- `page` (number, optional, default: 1, min: 1)
- `per_page` (number, optional, default: 20, min: 1)
**Response shape per release:**
```
id, tag_name, target (commitish), title, body (from Note field),
draft, prerelease, html_url, author (login), created_at, published_at
```
---
### `create_release` [W]
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `tag_name` (string, required)
- `target` (string, required) — branch, tag, or commit SHA
- `title` (string, required)
- `is_draft` (boolean, optional)
- `is_pre_release` (boolean, optional)
- `body` (string, optional) — release notes
---
### `get_release` / `get_latest_release` [R]
`get_release` requires `id` (number). `get_latest_release` takes only `owner` and `repo`.
---
### `delete_release` [W]
- `owner`, `repo`, `id` (number) — release numeric ID from list/get response.
---
### Tag tools
**`list_tags`** — `owner`, `repo`, `page`, `per_page` (default 20). Response per tag: `name`, `commit_sha?`. Message is dropped from list responses.
**`get_tag`** — `owner`, `repo`, `tag_name`. Full response: `name`, `message`, `commit_sha?`.
**`create_tag`** [W] — `owner`, `repo`, `tag_name` (required), `target` (commitish, optional), `message` (optional).
**`delete_tag`** [W] — `owner`, `repo`, `tag_name`.
---
## Search
### `search_repos` [R]
- `query` (string, required)
- `keywordIsTopic` (boolean, optional)
- `keywordInDescription` (boolean, optional)
- `ownerID` (number, optional) — filter by numeric user/org ID
- `isPrivate` (boolean, optional)
- `isArchived` (boolean, optional)
- `sort`, `order` (string, optional)
- `page`, `per_page` (default 30)
`search_repos` is the workaround for listing a user's repos when `read:user` scope is unavailable. Requires knowing the numeric `ownerID`.
---
### `search_users` [R]
- `query` (string, required)
- `page`, `per_page` (default 30)
---
### `search_org_teams` [R]
- `org` (string, required)
- `query` (string, required)
- `includeDescription` (boolean, optional)
- `page`, `per_page` (default 30)
---
## Repository Management
### `create_repo` [W]
- `name` (string, required)
- `description`, `private`, `issue_labels`, `auto_init`, `template`, `gitignores`, `license`, `readme`, `default_branch`, `trust_model`, `object_format_name` (all optional)
- `organization` (string, optional) — creates under org; defaults to personal account
- `trust_model` — `"default"` | `"collaborator"` | `"committer"` | `"collaboratorcommitter"`
- `object_format_name` — `"sha1"` | `"sha256"`
### `fork_repo` [W]
- `user` (string, required) — source repo owner
- `repo` (string, required) — source repo name
- `organization` (string, optional) — target org
- `name` (string, optional) — fork name
### `list_my_repos` [R]
Requires `read:user` scope. Without it, use `search_repos`.
- `page`, `per_page` (default 30, min 1)
### `list_org_repos` [R]
- `org` (string, required)
- `page`, `per_page` (default 100, min 1)
---
## User
### `get_me` [R]
No parameters. Returns current authenticated user. Requires `read:user` scope.
### `get_user_orgs` [R]
- `page`, `per_page` (default 30)
Requires `read:user` scope.
---
## Actions (CI)
### `actions_config_read` [R]
- `method` — `"list_repo_secrets"` | `"list_org_secrets"` | `"list_repo_variables"` | `"get_repo_variable"` | `"list_org_variables"` | `"get_org_variable"`
- `owner`, `repo` (for repo methods), `org` (for org methods)
- `name` (for get methods)
- `page`, `per_page` (default 30, min 1)
### `actions_config_write` [W]
- `method` — `"upsert_repo_secret"` | `"delete_repo_secret"` | `"upsert_org_secret"` | `"delete_org_secret"` | `"create_repo_variable"` | `"update_repo_variable"` | `"delete_repo_variable"` | `"create_org_variable"` | `"update_org_variable"` | `"delete_org_variable"`
- `name` (secret/variable name), `data` (secret value), `value` (variable value), `description` (optional)
### `actions_run_read` [R]
- `method` — `"list_workflows"` | `"get_workflow"` | `"list_runs"` | `"get_run"` | `"list_jobs"` | `"list_run_jobs"` | `"get_job_log_preview"` | `"download_job_log"`
- `owner`, `repo` (required)
- `workflow_id` (string) — workflow ID or filename
- `run_id`, `job_id` (numbers) — for run/job-specific methods
- `status` — filter for list methods
- `tail_lines` (default 200, min 1), `max_bytes` (default 65536, min 1024) — for log methods
- `output_path` — for `"download_job_log"`
- `page`, `per_page` (default 30, min 1)
### `actions_run_write` [W]
- `method` — `"dispatch_workflow"` | `"cancel_run"` | `"rerun_run"`
- `owner`, `repo` (required)
- `workflow_id` (for dispatch), `ref` (branch/tag for dispatch), `inputs` (object for dispatch)
- `run_id` (for cancel/rerun)
---
## Notifications
### `notification_read` [R]
- `method` — `"list"` | `"get"`
- `owner`, `repo` (optional, scopes to repo)
- `id` (thread ID for `"get"`)
- `status` — `"unread"` | `"read"` | `"pinned"`
- `subject_type` — `"Issue"` | `"Pull"` | `"Commit"` | `"Repository"`
- `since`, `before` (ISO 8601)
- `page`, `per_page` (default 30)
### `notification_write` [W]
- `method` — `"mark_read"` | `"mark_all_read"`
- `id` (thread ID for `"mark_read"`)
- `owner`, `repo` (optional scope)
- `last_read_at` (ISO 8601, defaults to now)
---
## Time Tracking
### `timetracking_read` [R]
- `method` — `"list_issue_times"` | `"list_repo_times"` | `"get_my_stopwatches"` | `"get_my_times"`
- `owner`, `repo` (for list methods)
- `issue_number` (for `"list_issue_times"`)
- `page`, `per_page` (default 30)
### `timetracking_write` [W]
- `method` — `"start_stopwatch"` | `"stop_stopwatch"` | `"delete_stopwatch"` | `"add_time"` | `"delete_time"`
- `owner`, `repo`, `issue_number` (optional depending on method)
- `time` (seconds, for `"add_time"`)
- `id` (entry ID, for `"delete_time"`)
---
## Packages
### `package_read` [R]
- `method` — `"list"` | `"list_versions"` | `"get"`
- `owner` (user or org, required)
- `type` (package type: `"container"` | `"npm"` | `"maven"` | `"pypi"` | `"cargo"` | `"generic"`) — required except for `"list"`
- `name` (package name, slashes auto-encoded) — required except for `"list"`
- `version` — for `"get"`
- `q` (search query)
- `page`, `per_page` (default 30, min 1)
### `package_write` [W]
- `method` — `"delete"` (only operation; irreversible)
- `owner`, `type`, `name`, `version` (all required)
---
## Wiki
### `wiki_read` [R]
- `method` — `"list"` | `"get"` | `"get_revisions"`
- `owner`, `repo` (required)
- `pageName` (for `"get"` and `"get_revisions"`)
### `wiki_write` [W]
- `method` — `"create"` | `"update"` | `"delete"`
- `owner`, `repo` (required)
- `pageName` (required for update/delete)
- `title` (required for create)
- `content` (for create/update)
- `message` (commit message)
---
## Version
### `get_gitea_mcp_server_version` [R]
No parameters. Returns the running server version string.

View File

@@ -1,87 +0,0 @@
---
topic: data-model
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Gitea Data Model
How issues, PRs, labels, milestones, and branches relate to each other — and the representation quirks that affect how you call tools.
## Issues and PRs share a number space
Issues and pull requests are the same entity type in Gitea's data model. They share a single sequential counter per repository: if issue #3 exists, there cannot be a PR #3 in the same repo. The `list_issues` tool returns both unless you pass `type: "issues"` or `type: "pulls"` to filter. A single-item `issue_read` response includes `is_pull: true` when the issue is backed by a pull request.
This matters for cross-linking: when you refer to `#5`, it might be either an issue or a PR. Use `issue_read method: "get"` and check `is_pull` to determine the type before deciding how to handle it.
## Label identity: names vs IDs
Labels have two identifiers that appear in different contexts:
- **Name** (string) — what appears in `issue_read` and `list_issues` responses; `labels: ["bug", "wontfix"]`
- **ID** (number) — what `issue_write` requires when applying labels; `labels: [3, 7]`
This mismatch is the single most common source of errors when managing labels. The MCP server slims label data in issue/PR responses to name-only strings. To apply labels to an issue or PR, you must first call `label_read method: "list_repo_labels"` to get the full label list (including IDs), then extract the IDs for the labels you want to apply.
The only tool that returns full label objects (including ID, color, description) is `label_read`.
## Milestone references
Milestones are repo-scoped and have both an **ID** and a sequential **number** within the repo. The tools use the numeric **ID** for all references:
- `issue_write` takes `milestone: <id>` (not a title, not a number)
- `milestone_read method: "get"` takes `id: <id>`
Milestone representations differ between issue and PR responses:
- In `issue_read` responses: `milestone: {id, title}` — an object with both fields
- In `pull_request_read` responses: `milestone: "title string"` — the title only, no ID
This inconsistency means you cannot extract a milestone's ID from a PR response. To get the milestone ID from a PR, you must look up milestones via `milestone_read method: "list"` and match by title.
## Label scope: repo vs org
Labels exist at two scopes:
- **Repo labels** — the default; scoped to a single repository; managed via `label_read/write` with `owner` + `repo`
- **Org labels** — scoped to an organization; shared across repos in the org; managed via `label_read/write` with `org`
Org labels have an additional `exclusive` boolean. When `exclusive: true`, applying one label in an exclusive group removes all other exclusive labels from the same group on the issue. This is Gitea's equivalent of a single-select category field.
When listing labels for labeling purposes, repo labels and org labels are listed separately. Issues in a repo can carry both.
## PR branches and cross-repo forks
For a PR within the same repository, `head` is just a branch name. For cross-repo PRs (from a fork), `head` uses the format `owner:branch`.
In `pull_request_write method: "create"`:
- `head` — the source (feature branch or fork branch)
- `base` — the merge target (usually `main` or `master`)
In `pull_request_read method: "get"` responses, `head` and `base` are full objects: `{ref, sha, repo: {full_name, description}}`. In `list_pull_requests` responses, they are bare ref strings.
## File SHA requirement
File operations in Gitea use SHA-based optimistic concurrency. To update or delete an existing file, you must provide the file's current `sha` — a content-addressed identifier returned by `get_file_contents`. Without it:
- `create_or_update_file` without `sha` is treated as a create; Gitea returns 409 if the file exists
- `delete_file` without `sha` returns a validation error
The workflow is always: `get_file_contents` → extract `sha` → pass to `create_or_update_file` or `delete_file`.
## Commit association on issues
Issues optionally carry a `ref` field — a branch name that associates the issue with ongoing work. This is set via `issue_write method: "create"` or `"update"` with the `ref` parameter. It is informational only; it does not create a branch or affect PR linking.
## PR review states
PR reviews move through a state machine:
1. Create a review in `"PENDING"` state (drafting inline comments)
2. Submit the review with a state: `"APPROVED"`, `"REQUEST_CHANGES"`, or `"COMMENT"`
3. A submitted review can be dismissed (not deleted)
The `stale` boolean on a review indicates the PR was pushed to after the review was submitted, making the review potentially outdated.
## Pagination and truncation
Repository tree (`get_repository_tree`) returns a `truncated: bool` field alongside `total_count`. When `truncated` is true, not all tree entries fit in one response — use `page` + `per_page` to paginate.
All list endpoints use cursor-less offset pagination (`page` integer). There is no `next_cursor` or `Link` header exposed through the MCP tools — iterate by incrementing `page` until you get fewer results than `per_page`.

View File

@@ -1,193 +0,0 @@
---
topic: examples
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Gitea MCP — Common Workflow Patterns
Canonical call sequences for the operations most likely to appear in a Gitea-managing skill.
## Create an issue with labels and a milestone
Labels and milestones must be referenced by numeric ID in write operations. Resolve them first.
```
1. label_read method: "list_repo_labels" owner: "Defame1297" repo: "holocron"
→ returns [{id: 3, name: "bug"}, {id: 7, name: "enhancement"}, ...]
2. milestone_read method: "list" owner: "Defame1297" repo: "holocron"
→ returns [{id: 1, title: "v1.0"}, ...]
3. issue_write method: "create"
owner: "Defame1297" repo: "holocron"
title: "Fix the widget"
body: "Description of the problem"
labels: [3] ← IDs, not names
milestone: 1 ← milestone ID
assignees: ["alice"]
```
## Apply labels to an existing issue
```
1. label_read method: "list_repo_labels" owner: ... repo: ...
→ map name → id for the labels you want
2. issue_write method: "add_labels"
owner: ... repo: ...
issue_number: 42
labels: [3, 7] ← IDs
```
To replace all labels atomically (remove existing, set new):
```
issue_write method: "replace_labels"
issue_number: 42
labels: [3, 7]
```
To remove a single label:
```
issue_write method: "remove_label"
issue_number: 42
label_id: 3 ← singular, not the array form
```
## Create a feature branch, push a file, open a PR
```
1. create_branch
owner: "Defame1297" repo: "holocron"
branch: "feat/my-feature"
old_branch: "main" ← defaults to repo default if omitted
2. get_file_contents (only needed if updating an existing file)
owner: ... repo: ... ref: "feat/my-feature" path: "README.md"
→ note the sha field
3. create_or_update_file
owner: ... repo: ...
path: "README.md"
content: "<base64-encoded content>"
message: "feat: update README"
branch_name: "feat/my-feature"
sha: "<sha from step 2>" ← required when updating; omit only for new files
4. pull_request_write method: "create"
owner: ... repo: ...
title: "feat: my feature"
body: "Description of changes"
head: "feat/my-feature"
base: "main"
labels: [7] ← label IDs, if desired
milestone: 1
reviewers: ["alice"]
```
## Merge a PR and clean up
```
1. pull_request_read method: "get_status"
owner: ... repo: ... pull_number: 12
→ check that status is passing before merge
2. pull_request_write method: "merge"
owner: ... repo: ... pull_number: 12
merge_style: "squash" ← or "merge", "rebase", etc.
message: "feat: my feature (#12)"
delete_branch: true ← clean up the feature branch post-merge
```
## Close an issue when a PR merges
Issues are not automatically closed when a PR merges in Gitea (unlike GitHub). Close them explicitly after merge:
```
issue_write method: "update"
owner: ... repo: ...
issue_number: 5
state: "closed"
```
## Create and manage a milestone
```
1. milestone_write method: "create"
owner: ... repo: ...
title: "v1.0"
description: "First stable release"
due_on: "2025-03-01T00:00:00Z"
2. Assign issues to it:
issue_write method: "update"
issue_number: 42
milestone: <id from step 1>
3. Close it when done:
milestone_write method: "update"
id: <milestone id>
state: "closed"
```
## List open PRs linked to a milestone
```
list_pull_requests
owner: ... repo: ...
state: "open"
milestone: <milestone id>
```
Note: `list_pull_requests` response items carry `milestone` as a bare title string, not an object. You cannot filter by milestone ID from the PR list response alone — pass the milestone ID as a query parameter instead.
## Resolve label name → ID without listing all labels
There is no direct lookup-by-name endpoint exposed through the MCP tools. The pattern is always:
```
label_read method: "list_repo_labels" per_page: 50
→ scan results for the target name → extract id
```
If you have more than 50 labels, paginate until found.
## Submit a code review
```
1. pull_request_review_write method: "create"
pull_number: 12
state: "PENDING"
commit_id: "<head SHA from PR get response>"
comments: [
{path: "src/foo.go", body: "Consider extracting this", new_line_num: 42}
]
→ returns review_id
2. pull_request_review_write method: "submit"
pull_number: 12
review_id: <from step 1>
state: "REQUEST_CHANGES"
body: "A few nits, see inline comments"
```
## Update a file when you don't know the current SHA
SHA is mandatory for file updates. If you skipped storing it:
```
get_file_contents
owner: ... repo: ...
ref: "main"
path: "the/file.md"
→ extract sha from response
create_or_update_file
...
sha: <extracted sha>
```
Do not guess or omit the SHA — the request will fail or create a duplicate.

View File

@@ -1,65 +0,0 @@
---
topic: overview
source_keys:
- gitea-mcp-repo
---
# Gitea MCP Server — Overview
The Gitea MCP server (gitea-mcp v1.3.0) wraps the Gitea REST API and exposes it as 55 MCP tools. It runs as a stdio process and is launched with `go run gitea.com/gitea/gitea-mcp@latest -t stdio`. The skill author interacts with it entirely through MCP tool calls — no direct HTTP or shell access is required.
## Tool registry architecture
Tools are split into two registries at startup: 31 read-only tools and 24 write tools. The `--read-only` flag suppresses all write tools; the `--tools` flag accepts a comma-separated allowlist and filters out any tool not named. In normal use (no flags), all 55 tools are available.
Many tools use a **method dispatch** pattern: a single MCP tool exposes multiple operations through a required `method` enum parameter. For example, `issue_write` handles `create`, `update`, `add_comment`, `edit_comment`, `add_labels`, `remove_label`, `replace_labels`, and `clear_labels`. This keeps the tool surface smaller while multiplexing related mutations. The `method` parameter is always required for dispatch tools and must be one of the documented enum values.
## Naming and parameter conventions
All repo-scoped tools require `owner` (string, the Gitea username or org) and `repo` (string, the repository name). These are always required and positional — never omit them.
Pagination is consistent across list tools: `page` (default 1) and `per_page` (default 30 for most tools, 20 for releases, 100 for org repos). Results are not automatically paginated — the caller must iterate pages.
Timestamps use ISO 8601 format (`2024-01-15T10:00:00Z`) for both input parameters (`since`, `before`, `deadline`, `due_on`) and response fields.
## Token scopes and tool availability
Three token scopes gate different tool groups:
- `write:issue` — enables issue, comment, label, and milestone operations (read and write)
- `write:repository` — enables PR, branch, file, release, and tag operations (read and write, because Gitea gates reads behind write scope for these)
- `read:user` — required for `get_me`, `list_my_repos`, `get_user_orgs`; absent from the default token config
Tools that require `read:user` but are called without it return an error or empty result. The workaround for repo discovery without `read:user` is `search_repos` with an `ownerID` filter, though `ownerID` requires knowing the numeric user ID in advance.
## Response shape philosophy
The MCP server returns **slimmed** response objects, not the full Gitea API JSON. Key simplifications:
- User fields are always a bare login string, never a user object
- Label fields are always a flat array of name strings (`["bug", "enhancement"]`), never label objects
- List responses drop `body` (description text) and some metadata fields to reduce token payload
- Single-item responses (get by ID or number) include full detail including body
This means you cannot get a user's display name or label colors from issue/PR responses — only the login or label name.
## Domain grouping
Tools are organized into these functional domains, each covered in detail in `api-reference.md`:
- **Issues** — `list_issues`, `search_issues`, `issue_read`, `issue_write`
- **Pull Requests** — `list_pull_requests`, `pull_request_read`, `pull_request_write`, `pull_request_review_write`
- **Labels** — `label_read`, `label_write`
- **Milestones** — `milestone_read`, `milestone_write`
- **Branches** — `list_branches`, `create_branch`, `delete_branch`
- **Files** — `get_file_contents`, `get_dir_contents`, `create_or_update_file`, `delete_file`, `get_repository_tree`
- **Commits** — `list_commits`, `get_commit`
- **Releases / Tags** — `list_releases`, `get_release`, `get_latest_release`, `create_release`, `delete_release`, `list_tags`, `get_tag`, `create_tag`, `delete_tag`
- **Search** — `search_repos`, `search_issues`, `search_users`, `search_org_teams`
- **Repos** — `create_repo`, `fork_repo`, `list_my_repos`, `list_org_repos`
- **User / Orgs** — `get_me`, `get_user_orgs`
- **Actions (CI)** — `actions_config_read`, `actions_config_write`, `actions_run_read`, `actions_run_write`
- **Notifications** — `notification_read`, `notification_write`
- **Time Tracking** — `timetracking_read`, `timetracking_write`
- **Packages** — `package_read`, `package_write`
- **Wiki** — `wiki_read`, `wiki_write`

View File

@@ -1,22 +0,0 @@
# Sources
## gitea-mcp-repo
- **URL:** https://gitea.com/gitea/gitea-mcp
- **Description:** Official gitea-mcp repository (v1.3.0); operation/*.go source files documenting all 55 MCP tools, their parameters, and CLI flags
- **Contributing files:** overview.md, api-reference.md, data-model.md, examples.md, troubleshooting.md
- **Status:** `extracted`
## gitea-mcp-slim-go
- **URL:** https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/issue/slim.go, https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/pull/slim.go, https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/repo/slim.go
- **Description:** Slim response shape structs from gitea-mcp source; defines exactly which fields the MCP server returns for issues, PRs, branches, commits, tags, releases, and files
- **Contributing files:** api-reference.md, data-model.md, examples.md, troubleshooting.md
- **Status:** `extracted`
## gitea-api-docs
- **URL:** https://docs.gitea.com/api/1.20/
- **Description:** Gitea REST API swagger documentation covering underlying endpoints for issues, PRs, labels, milestones, and branches
- **Contributing files:** (see notes below)
- **Status:** `no content extracted` — source fetch timed out; all reference content derived from gitea-mcp source files which are authoritative for MCP tool usage

View File

@@ -1,96 +0,0 @@
---
topic: troubleshooting
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Gitea MCP — Troubleshooting
Known gotchas, source-level bugs, and error patterns for skill authors.
## Source-level typo: `review_scomments`
The `pull_request_read method: "get"` response includes a field called `review_scomments` (not `review_comments`). This is a misspelling in the slim.go source code of gitea-mcp v1.3.0. Do not expect `review_comments` to be present — the field is `review_scomments`. This is the count of inline review comments.
## Label ID vs name confusion
`issue_write` (methods: `add_labels`, `replace_labels`) takes `labels` as an array of **numbers** (IDs). Issue and PR read responses return labels as an array of **name strings**. These are never interchangeable.
If you pass name strings to `labels`, the call will either fail validation or silently apply no labels. Always resolve names to IDs first via `label_read method: "list_repo_labels"`.
## Milestone representation inconsistency
The `milestone` field in responses differs by entity type:
- `issue_read` response: `milestone: {id: 1, title: "v1.0"}` — object
- `pull_request_read` response: `milestone: "v1.0"` — string (title only)
You cannot get a milestone's ID from a PR response. If you need the ID, call `milestone_read method: "list"` and match by title.
## File update requires current SHA
`create_or_update_file` and `delete_file` both require the file's current content SHA. Without it:
- Updating: Gitea treats the call as a create. If the file exists, returns HTTP 409 (Conflict).
- Deleting: returns HTTP 422 (Unprocessable entity) — the SHA is a required field.
Always call `get_file_contents` first to retrieve the SHA. The SHA is in the `sha` field of the response (not `content.sha` — it's top-level).
## `list_my_repos` requires `read:user` scope
With only `write:issue` and `write:repository` scopes, `list_my_repos` returns an error. Use `search_repos` with an `ownerID` filter instead. The `ownerID` is the numeric user ID — you cannot get it from `get_me` either (same `read:user` requirement). If ownerID is unknown, use `search_repos` with the username in the query.
## `get_me` requires `read:user` scope
There is no way to discover the current user's identity (login, ID) with `write:issue` + `write:repository` scopes only. Hardcode the owner/username in the skill or require it as an input parameter.
## Method dispatch: `"update"` vs `"edit"` on milestones
`milestone_write` accepts both `"update"` and `"edit"` as method values for updating a milestone — they map to the same operation. Use `"update"` for consistency with the issue and PR tools.
## Draft PR behavior
`pull_request_write method: "create"` with `draft: true` implements draft by prepending `"WIP:"` to the PR title (not via a dedicated Gitea API flag). This means:
- The title returned by the API will have the `"WIP:"` prefix
- To un-draft, use `"update"` and pass the title without the prefix
This differs from GitHub's draft PR mechanism; draft state is not a first-class boolean field.
## `delete_release` takes numeric ID, not tag name
`delete_release` requires the numeric `id` from the release object, not the `tag_name` string. Call `list_releases` or `get_release` first to get the numeric ID.
## Cross-repo PR `head` format
For PRs originating from a fork, `head` must be `"fork-owner:branch-name"`. Using just the branch name will result in Gitea looking for the branch in the base repo and failing with 422.
## Pagination is not automatic
List tools return one page at a time. There is no cursor, `Link` header, or auto-pagination in the MCP layer. When you need complete results (e.g., all labels to build a name→ID map), iterate `page: 1`, `page: 2`, etc. until the result count is less than `per_page`.
## HTTP error patterns
The MCP server surfaces HTTP error codes from the Gitea REST API:
| Code | Meaning in this context |
|---|---|
| 401 | Token is invalid, expired, or missing entirely |
| 403 | Token lacks the required scope for this operation |
| 404 | Resource not found — also returned by some endpoints when scope is insufficient |
| 409 | Conflict — file already exists (create without SHA), branch already exists, duplicate PR |
| 422 | Unprocessable entity — missing required field (e.g. SHA on file ops), invalid enum value |
| 500 | Gitea server error — usually transient |
404 can mask a permissions error: some Gitea endpoints return 404 instead of 403 when the token has insufficient scope, to avoid leaking resource existence.
## `list_issues` with `milestones` filter
The `milestones` parameter on `list_issues` accepts milestone names or IDs as an array. Using IDs is more reliable — milestone names are mutable. Always prefer filtering by milestone ID when programmatically filtering.
## `per_page` defaults vary
Not all endpoints share the same default `per_page`:
- Most tools: 30
- `list_releases`, `list_tags`: 20
- `list_org_repos`: 100
When building result-count-aware logic, do not assume 30 — check the tool's documented default.

View File

@@ -1,152 +0,0 @@
---
topic: cli-reference
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## Exit codes
| Code | Meaning |
|------|---------|
| 0 | All hooks passed |
| 1 | Hook(s) failed or files were modified |
| 3 | Unexpected internal error |
| 130 | Interrupted (Ctrl+C) |
## `pre-commit run`
Run hooks against files.
```bash
pre-commit run # staged files only (normal commit flow)
pre-commit run --all-files # entire working tree
pre-commit run check-yaml # single hook by ID
pre-commit run --files path/to/file.py # specific files
pre-commit run --from-ref HEAD~3 --to-ref HEAD # diff range (CI use)
pre-commit run --hook-stage pre-push # specific lifecycle stage
pre-commit run --show-diff-on-failure # print diff when hook fails
pre-commit run --verbose # always show hook output
```
For CI, prefer `--from-ref`/`--to-ref` over `--all-files` on large repos. Cache `$PRE_COMMIT_HOME` keyed on `.pre-commit-config.yaml` hash.
## `pre-commit install`
Wire pre-commit into `.git/hooks/`.
```bash
pre-commit install # default: pre-commit stage
pre-commit install -f # overwrite existing hooks
pre-commit install --install-hooks # pre-create all environments now
pre-commit install -t pre-commit -t pre-push # multiple stages
pre-commit install --allow-missing-config # silently skip if no config file
```
Must be run once per clone. Re-run after changing `default_install_hook_types`.
## `pre-commit uninstall`
```bash
pre-commit uninstall
pre-commit uninstall -t pre-push
```
## `pre-commit autoupdate`
Update `rev` values in `.pre-commit-config.yaml` to the latest tag.
```bash
pre-commit autoupdate # latest tag for all repos
pre-commit autoupdate --bleeding-edge # use default branch HEAD
pre-commit autoupdate --freeze # pin to commit SHA (reproducible)
pre-commit autoupdate --repo https://github.com/pre-commit/pre-commit-hooks # single repo
pre-commit autoupdate -j 4 # parallel fetches (v3.3.0+)
```
This modifies `.pre-commit-config.yaml` in-place. A skill updating an existing config should call this rather than manually editing `rev` values.
## `pre-commit validate-config`
Validate `.pre-commit-config.yaml` schema. Non-zero exit means the file is invalid.
```bash
pre-commit validate-config
pre-commit validate-config path/to/other-config.yaml
```
A skill must call this after writing or modifying a config before considering the task complete.
## `pre-commit validate-manifest`
Validate a `.pre-commit-hooks.yaml` hook definition file.
```bash
pre-commit validate-manifest .pre-commit-hooks.yaml
```
## `pre-commit try-repo`
Test a hook repo without adding it to the config.
```bash
pre-commit try-repo https://github.com/pre-commit/pre-commit-hooks
pre-commit try-repo ../local-hook-repo --all-files --verbose
pre-commit try-repo ../hook-repo specific-hook-id --verbose
```
Supports all `run` options. Use this in a skill to smoke-test a hook before writing it to config.
## `pre-commit sample-config`
Print a minimal starter config to stdout.
```bash
pre-commit sample-config > .pre-commit-config.yaml
```
## `pre-commit install-hooks`
Pre-create all hook environments without running any hooks.
```bash
pre-commit install-hooks
```
## `pre-commit gc`
Remove unused cached hook environments (safe to run at any time).
```bash
pre-commit gc
```
## `pre-commit clean`
Wipe all cached environments. Forces full rebuild on next run.
```bash
pre-commit clean
```
## `SKIP` environment variable
Skip specific hooks by ID without modifying config. Comma-separated, exact IDs, no spaces.
```bash
SKIP=flake8,check-yaml git commit -m "wip"
```
## CI recipe
```yaml
# GitHub Actions
- name: Cache pre-commit envs
uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }}
- name: Run pre-commit
run: pre-commit run --from-ref ${{ github.event.pull_request.base.sha }} --to-ref HEAD
```

View File

@@ -1,206 +0,0 @@
---
topic: configuration
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## `.pre-commit-config.yaml` structure
### Top-level keys
| Key | Type | Default | Purpose |
|-----|------|---------|---------|
| `repos` | List | required | List of repo blocks |
| `default_install_hook_types` | List | `[pre-commit]` | Hook types installed when `pre-commit install` is run without `-t` |
| `default_language_version` | Dict | `{}` | Maps language name → version string; overrides per-hook defaults |
| `default_stages` | List | all stages | Applied to every hook unless the hook specifies `stages` |
| `files` | Regex string | `''` | Global include pattern applied before hook-level filters |
| `exclude` | Regex string | `^$` | Global exclude pattern |
| `fail_fast` | Boolean | `false` | Stop after the first failing hook |
| `minimum_pre_commit_version` | String | `'0'` | Minimum pre-commit version required to use this config |
### Repo block keys
| Key | Required | Description |
|-----|----------|-------------|
| `repo` | Yes | Git URL, or the special values `local` or `meta` |
| `rev` | Yes (not for `local`/`meta`) | Tag or SHA — must be immutable |
| `hooks` | Yes | List of hook override blocks |
### Hook override block keys
These keys appear under `hooks:` inside a repo block. All are optional overrides of the hook's upstream manifest values.
| Key | Type | Description |
|-----|------|-------------|
| `id` | String (required) | Hook identifier — must match an `id` in the repo's `.pre-commit-hooks.yaml` |
| `alias` | String | Extra name for targeting with `pre-commit run <alias>` |
| `name` | String | Override the display name |
| `language_version` | String | Override language version |
| `files` | Regex string | Override file include pattern (appended to upstream with AND logic) |
| `exclude` | Regex string | Override file exclude pattern |
| `types` | List | AND-logic type filter (all tags must match) |
| `types_or` | List | OR-logic type filter (any tag must match) |
| `exclude_types` | List | Exclude files matching these types |
| `args` | List | Additional CLI arguments prepended before filenames |
| `stages` | List | Which git stages trigger this hook |
| `additional_dependencies` | List | Extra packages to install into hook environment |
| `always_run` | Boolean | Run even if no files match the filter |
| `verbose` | Boolean | Always print output (not only on failure) |
| `log_file` | String | Write output to this file path on failure |
### Complete annotated example
```yaml
minimum_pre_commit_version: '3.0.0'
fail_fast: false
default_language_version:
python: python3.11
default_stages: [pre-commit, pre-push]
exclude: ^vendor/
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: pretty-format-json
args: [--autofix]
- id: trailing-whitespace
exclude: ^tests/fixtures/
- repo: local
hooks:
- id: validate-manifest
name: Validate marketplace manifest
entry: python scripts/validate_manifest.py
language: python
files: ^\.claude-plugin/marketplace\.json$
always_run: false
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
```
### Multi-line exclude pattern (readable for long lists)
```yaml
- id: my-hook
exclude: |
(?x)^(
path/to/file1.py|
path/to/file2.py|
path/to/generated/.*
)$
```
## Stages
Valid `stages` values: `pre-commit`, `pre-push`, `commit-msg`, `prepare-commit-msg`, `post-checkout`, `post-commit`, `post-merge`, `post-rewrite`, `pre-merge-commit`, `pre-rebase`, `manual`.
The `manual` stage only runs when explicitly invoked: `pre-commit run --hook-stage manual <hookid>`.
To install hooks for non-default stages, pass them to install:
```bash
pre-commit install -t pre-commit -t pre-push -t commit-msg
```
Or declare them in config so they install automatically:
```yaml
default_install_hook_types: [pre-commit, pre-push, commit-msg]
```
## `types` vs `types_or` vs `files`
- `types: [json, text]` — file must carry ALL listed tags (AND)
- `types_or: [javascript, typescript]` — file must carry ANY listed tag (OR)
- `files: \.py$` — regex applied via `re.search()` (not full match) on the file path
- `exclude: \.generated\.py$` — regex excludes matching paths after `files` matches
Both `types` and `files` filters must pass for a file to be processed. Use `identify-cli <file>` to see what tags a file has.
## Local hooks (`repo: local`)
No external repository required. Required fields: `id`, `name`, `language`, `entry`.
```yaml
- repo: local
hooks:
# System tool already installed (don't let pre-commit manage env)
- id: shellcheck
name: shellcheck
entry: shellcheck
language: unsupported # formerly "system"
types: [shell]
# Script in the repo
- id: run-tests
name: Run test suite
entry: bash tests/run-tests.sh
language: unsupported_script # formerly "script"
pass_filenames: false
always_run: true
stages: [pre-push]
# Always-fail guard (lightweight, no env needed)
- id: no-dotenv
name: No .env files
entry: .env files must not be committed
language: fail
files: \.env$
# Python with managed dependencies
- id: my-checker
name: My Python Checker
entry: python -m mymodule.checker
language: python
additional_dependencies: [requests==2.28.0]
types: [python]
```
### Language choices for local hooks
| Language | Description |
|----------|-------------|
| `unsupported` / `system` | Runs executable from system PATH — pre-commit does not manage environment |
| `unsupported_script` / `script` | Runs a script path relative to repo root |
| `fail` | Always fails; `entry` text becomes the error message — good for forbidden file patterns |
| `python` | Creates isolated venv; `additional_dependencies` are pip-installed |
| `node` | Creates isolated node env |
| `ruby`, `golang`, `rust`, `docker`, `docker_image` | Language-specific isolated envs |
### `pass_filenames` behavior
`true` (default): `entry arg1 arg2 file1 file2 file3`
`false`: `entry arg1 arg2` — hook gets no filenames; use for repo-wide or stateful checks.
## Meta hooks (`repo: meta`)
```yaml
- repo: meta
hooks:
- id: check-hooks-apply # each hook must match ≥1 file — catches dead hooks
- id: check-useless-excludes # each exclude must exclude ≥1 file — catches dead excludes
- id: identity # debug: prints every filename passed to pre-commit
```
## Hazmat helpers (v4.5.0+)
Entry-point prefixes for edge cases:
```yaml
# Change directory before running (monorepo)
entry: pre-commit hazmat cd subdir my-bin --
# Treat non-zero exit as warning instead of failure
entry: pre-commit hazmat ignore-exit-code my-bin --
verbose: true
# Run hook once per file (not batched)
entry: pre-commit hazmat n1 my-bin --
```

View File

@@ -1,220 +0,0 @@
---
topic: examples
source_keys:
- context7-pre-commit-com
- pre-commit-com
- context7-pre-commit-hooks
---
## Starter config
```bash
pre-commit sample-config > .pre-commit-config.yaml
pre-commit validate-config
pre-commit install
pre-commit run --all-files
```
## Common config patterns
### File hygiene only
```yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-merge-conflict
- id: detect-private-key
```
### With auto-formatting
```yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: pretty-format-json
args: [--autofix]
- id: mixed-line-ending
args: [--fix=lf]
```
### With branch protection and secret scanning
```yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: detect-private-key
- id: detect-aws-credentials
- id: no-commit-to-branch
args: [--branch, main, --branch, master]
- id: check-added-large-files
args: [--maxkb=500]
```
### Multi-stage config (pre-commit + pre-push + commit-msg)
```yaml
default_install_hook_types: [pre-commit, pre-push, commit-msg]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
stages: [pre-commit]
- id: check-yaml
stages: [pre-commit]
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v2.4.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
- repo: local
hooks:
- id: run-tests
name: Run test suite
entry: bash tests/run-tests.sh
language: unsupported_script
pass_filenames: false
always_run: true
stages: [pre-push]
```
### Kubernetes-style YAML (needs --unsafe for custom tags)
```yaml
- id: check-yaml
args: ['--unsafe']
exclude: ^helm/templates/
```
### Monorepo with subdirectory scope
```yaml
- repo: local
hooks:
- id: validate-frontend
name: Validate frontend
entry: pre-commit hazmat cd frontend npm run lint --
language: unsupported
files: ^frontend/
pass_filenames: false
```
## Testing and validation workflow
```bash
# After writing a new config:
pre-commit validate-config
# Test all hooks against entire repo:
pre-commit run --all-files
# Test a single hook:
pre-commit run check-yaml --all-files
# Test a hook repo before adding it to config:
pre-commit try-repo https://github.com/psf/black --all-files
# Update all rev pins to latest tags:
pre-commit autoupdate
# Freeze revs to commit SHAs for reproducibility:
pre-commit autoupdate --freeze
# Skip a hook for one commit:
SKIP=check-yaml git commit -m "add yaml with custom tags"
# Run manually against a diff (CI):
pre-commit run --from-ref origin/main --to-ref HEAD
```
## Local hook patterns
### Forbidden file guard (cheapest possible hook)
```yaml
- repo: local
hooks:
- id: no-env-files
name: No .env files
entry: .env files must not be committed — use environment variables
language: fail
files: \.env(\..+)?$
```
### Validate a generated file
```yaml
- repo: local
hooks:
- id: validate-manifest
name: Validate plugin manifest
entry: bash scripts/check-manifests.sh
language: unsupported_script
files: ^\.claude-plugin/
pass_filenames: false
always_run: false
```
### Run full test suite pre-push
```yaml
- repo: local
hooks:
- id: run-tests
name: Run test suite
entry: bash tests/run-tests.sh
language: unsupported_script
pass_filenames: false
always_run: true
stages: [pre-push]
```
### Validate SKILL.md frontmatter (inline bash, as used in this repo)
```yaml
- repo: local
hooks:
- id: skill-frontmatter
name: SKILL.md frontmatter validation
entry: bash
language: system
files: 'SKILL\.md$'
args:
- -c
- |
for f in "$@"; do
if [[ -f "$f" ]]; then
if ! grep -q "^name:" "$f" || ! grep -q "^description:" "$f"; then
echo "ERROR: $f missing required frontmatter (name: and description:)"
exit 1
fi
fi
done
```
## Adding meta-validation
Add after all other repos to catch dead hooks/excludes:
```yaml
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
```

View File

@@ -1,156 +0,0 @@
---
topic: hook-authoring
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## What this covers
How to author a hook that lives in its own git repo (shareable), vs. a local hook that lives in the consuming repo. A pre-commit skill/agent needs to know both: it may create local hooks inline in the config, or scaffold a proper hook repo.
## Hook definition file: `.pre-commit-hooks.yaml`
Required in any git repo that others consume as a pre-commit hook source. Lives at the repo root.
```yaml
- id: my-hook
name: My Hook
description: One-line description shown in pre-commit output.
entry: my-hook-script # executable name on PATH, or path relative to repo root
language: python # controls how pre-commit installs the environment
types: [text] # file type filter (AND logic)
files: '' # regex filter on file path (re.search)
exclude: ^$ # regex exclusion on file path
args: [] # default arguments
pass_filenames: true # append matched filenames after args
always_run: false # run even with 0 matched files
require_serial: false # run in parallel by default
fail_fast: false # stop other hooks if this fails
verbose: false # always print stdout/stderr (not only on failure)
stages: [pre-commit] # git lifecycle stages that trigger this hook
additional_dependencies: [] # packages installed into hook environment
minimum_pre_commit_version: '0'
language_version: default # e.g. 'python3.11', 'node18'
```
Validate with: `pre-commit validate-manifest .pre-commit-hooks.yaml`
## Language types and what they control
| Language | Environment | When to use |
|----------|-------------|-------------|
| `python` | Isolated venv | Pure Python hook; `additional_dependencies` are pip packages |
| `node` | Isolated node_modules | JS/TS hook; `additional_dependencies` are npm packages |
| `golang` | Builds from source | Go hook; `additional_dependencies` are Go module paths |
| `ruby` | Isolated gem env | Ruby hook; `additional_dependencies` are gems |
| `rust` | Cargo build | Rust hook |
| `docker` | Docker image built from `entry` | Use when no other language fits |
| `docker_image` | Pulls Docker image by `entry` | When image is pre-built |
| `unsupported` / `system` | No environment — runs from system PATH | When tool is pre-installed on host |
| `unsupported_script` / `script` | No environment — runs repo-relative path | Scripts committed to the hook repo |
| `fail` | No environment — always exits 1 | Forbidden file guards |
| `conda` | Conda environment | Conda-native hooks |
| `coursier` | Coursier (Scala/JVM) | JVM hooks |
## `entry` field
`entry` is the executable invoked. It is resolved differently by language:
- `python`/`node`/etc.: the installed script name (what's in `scripts:` in `setup.cfg` or `package.json`)
- `system`/`unsupported`: resolved from system `PATH`
- `script`/`unsupported_script`: path relative to the hook repo root
- `fail`: the `entry` string is printed as the error message
Arguments in `entry` are supported: `entry: python -m mymodule.cli` works.
## `pass_filenames` and argument ordering
When `pass_filenames: true` (default), pre-commit calls:
```
entry <args from manifest> <args from config override> <matched file1> <file2> ...
```
When `pass_filenames: false`, pre-commit calls:
```
entry <args from manifest> <args from config override>
```
Use `pass_filenames: false` for:
- Hooks that check the repo state as a whole (test runners, manifest validators)
- Hooks whose tool only accepts one file at a time (combine with `require_serial: true` or use `pre-commit hazmat n1`)
## `require_serial`
Default is `false` — pre-commit batches files and runs hook processes in parallel. Set `true` when:
- The hook reads/writes shared state (a database, a lock file)
- The tool cannot handle concurrent invocations
- The tool must process all files in a single process call but `pass_filenames: false` is not appropriate
## Testing a hook during development
```bash
# Test without adding to any consuming repo's config:
pre-commit try-repo . --all-files --verbose
pre-commit try-repo . specific-hook-id --verbose
# Test on a specific file:
pre-commit try-repo . my-hook --files path/to/file.py --verbose
```
`try-repo .` runs from the hook repo's own directory. Use a path to the hook repo from the consuming repo.
## Minimum viable hook repo structure
```
my-hook-repo/
.pre-commit-hooks.yaml # hook manifest
hooks/
my_hook.py # hook script
setup.cfg # (if python) declares scripts entry point
pyproject.toml # (if python) build config
```
`setup.cfg` entry point example:
```ini
[options.entry_points]
console_scripts =
my-hook = hooks.my_hook:main
```
## Local hooks (no separate repo)
For hooks that belong to the consuming repo and don't need to be shared:
```yaml
- repo: local
hooks:
- id: my-local-check
name: My local check
entry: ./scripts/check.sh
language: unsupported_script
pass_filenames: false
always_run: true
```
No `.pre-commit-hooks.yaml` needed. All fields that would normally come from the manifest must be declared inline in the config.
## Inline bash hook (entry splits args)
```yaml
- repo: local
hooks:
- id: validate-frontmatter
name: Validate frontmatter
entry: bash
language: system
files: 'SKILL\.md$'
args:
- -c
- |
for f in "$@"; do
grep -q "^name:" "$f" || { echo "Missing name: in $f"; exit 1; }
done
pass_filenames: true
```
Note: `entry: bash` + `args: [-c, <script>]` means pre-commit calls `bash -c <script> -- file1 file2`. The `--` and `$@` pattern is important — without it, filenames are not available inside the script.

View File

@@ -1,174 +0,0 @@
---
topic: hooks-reference
source_keys:
- context7-pre-commit-hooks
- pre-commit-hooks-github
---
## pre-commit-hooks (official collection)
Repo: `https://github.com/pre-commit/pre-commit-hooks`
Latest version: `v6.0.0`
Pinned in this repo: `v4.5.0` — consider running `pre-commit autoupdate`
```yaml
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: <hook-id>
```
---
## File syntax / content checks
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `check-ast` | Validates Python files parse as valid AST | — |
| `check-json` | Validates JSON parses correctly | — |
| `check-toml` | Validates TOML parses correctly | — |
| `check-xml` | Validates XML parses correctly | — |
| `check-yaml` | Validates YAML parses correctly | `--allow-multiple-documents`, `--unsafe` (syntax-only, enables custom tags) |
| `check-merge-conflict` | Detects unresolved merge markers (`<<<<<<<`) | `--assume-in-merge` |
---
## Filesystem / naming / encoding checks
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `check-added-large-files` | Blocks files over size threshold | `--maxkb=N` (default 500), `--enforce-all` |
| `check-case-conflict` | Detects filenames differing only by case (macOS/Windows hazard) | — |
| `check-executables-have-shebangs` | Ensures executable files have a shebang | — |
| `check-shebang-scripts-are-executable` | Ensures files with shebangs are executable | — |
| `check-illegal-windows-names` | Detects filenames illegal on Windows | — |
| `check-symlinks` | Detects broken symlinks | — |
| `destroyed-symlinks` | Catches symlinks converted to regular files (common on Windows) | — |
| `fix-byte-order-marker` | Removes UTF-8 BOM | — |
---
## Security
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `detect-private-key` | Blocks PEM private key material | — |
| `detect-aws-credentials` | Blocks AWS credentials (reads `~/.aws/credentials`) | `--credentials-file PATH` (repeatable), `--allow-missing-credentials` |
---
## Git-related
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `no-commit-to-branch` | Blocks commits to protected branches | `-b`/`--branch NAME` (repeatable, default: `main`+`master`), `-p`/`--pattern REGEX` |
| `check-vcs-permalinks` | Ensures GitHub links are SHAs not branch refs | `--additional-github-domain DOMAIN` |
| `forbid-new-submodules` | Blocks adding new git submodules | — |
| `forbid-submodules` | Blocks any submodule in the repo | — |
Note: `no-commit-to-branch` runs with `always_run: true` by default — it ignores `files`/`types` filters.
---
## Whitespace / line-ending fixers
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `end-of-file-fixer` | Ensures files end with exactly one newline | — |
| `trailing-whitespace` | Removes trailing whitespace | `--markdown-linebreak-ext=md` (preserve ` ` line breaks), `--chars` |
| `mixed-line-ending` | Normalises line endings | `--fix=auto` (default), `--fix=lf`, `--fix=crlf`, `--fix=no` (check only) |
---
## Formatters / sorters
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `pretty-format-json` | Formats JSON; fails if not already formatted | `--autofix` (fix in place), `--indent N`, `--no-sort-keys`, `--no-ensure-ascii`, `--top-keys k1,k2` |
| `requirements-txt-fixer` | Sorts and deduplicates `requirements.txt` and `constraints.txt` | — |
| `file-contents-sorter` | Sorts lines in user-specified files alphabetically | `--ignore-case`, `--unique`; no files matched by default — must set `files:` |
| `sort-simple-yaml` | Sorts top-level keys in simple YAML files | No files matched by default — must set `files:` |
---
## Python-specific
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `check-builtin-literals` | Requires literal syntax for empty built-ins (`[]` not `list()`) | `--ignore=type1,type2`, `--no-allow-dict-kwargs` |
| `debug-statements` | Detects `import pdb`, `breakpoint()`, etc. | — |
| `double-quote-string-fixer` | Converts double-quoted strings to single-quoted | — |
| `name-tests-test` | Enforces test file naming convention | `--pytest` (default: `.*_test.py`), `--pytest-test-first` (`test_.*.py`), `--django`/`--unittest` |
---
## Deprecated hooks
| Hook ID | Replacement |
|---------|------------|
| `check-byte-order-marker` | Use `fix-byte-order-marker` |
| `fix-encoding-pragma` | Use `pyupgrade` |
| `check-docstring-first` | Deprecated without replacement |
---
## Argument examples
### `check-yaml` with Kubernetes manifests
```yaml
- id: check-yaml
args: ['--unsafe'] # needed for !Tag syntax used by k8s/Helm
```
### `no-commit-to-branch`
```yaml
- id: no-commit-to-branch
args: [--branch, main, --branch, master, --branch, production]
```
### `pretty-format-json` auto-fix
```yaml
- id: pretty-format-json
args: [--autofix, --indent, '2', --no-sort-keys]
```
### `sort-simple-yaml` opt-in (no files matched by default)
```yaml
- id: sort-simple-yaml
files: ^config/simple/
```
### `trailing-whitespace` preserving Markdown line breaks
```yaml
- id: trailing-whitespace
args: ['--markdown-linebreak-ext=md']
```
---
## Hooks in this repo (`.pre-commit-config.yaml`)
From `pre-commit/pre-commit-hooks@v4.5.0` (latest is `v6.0.0`):
| Hook | Stage | Notes |
|------|-------|-------|
| `end-of-file-fixer` | pre-commit | |
| `check-json` | pre-commit | |
| `pretty-format-json` | pre-commit | No `--autofix` — fails on unformatted JSON, does not fix |
| `check-yaml` | pre-commit | |
| `trailing-whitespace` | pre-commit | |
Local hooks in this repo:
| Hook | Stage | Entry |
|------|-------|-------|
| `run-tests` | pre-push | `bash tests/run-tests.sh` |
| `check-manifests` | pre-push | `bash scripts/check-manifests.sh` |
| `validate-plugins` | pre-push | `claude plugin validate --strict` per plugin dir |
| `validate-marketplace` | pre-push | `claude plugin validate --strict .claude-plugin/marketplace.json` |
| `skill-frontmatter` | pre-commit | Validates SKILL.md has `name:` and `description:` |

View File

@@ -1,49 +0,0 @@
---
topic: overview
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## What pre-commit is
Pre-commit is a framework for managing and executing git hooks. Hooks run automatically at specific git lifecycle points (pre-commit, pre-push, commit-msg, etc.) against staged or changed files. Each hook is pulled from a versioned external git repo; pre-commit clones and caches it in `~/.cache/pre-commit` (or `$PRE_COMMIT_HOME`) and manages isolated execution environments per hook. No system-wide language runtimes are required for most hooks.
## Key concepts
**Config file:** `.pre-commit-config.yaml` at the repo root. This is the single source of truth for all hooks.
**Hook repos vs local hooks:** Most hooks live in external git repos (pinned by `rev`). Local hooks (`repo: local`) live in the same repo and run system tools or scripts directly.
**`rev` must be immutable:** Always use a tag or commit SHA, never a branch. `autoupdate` will not work correctly with branches.
**File targeting:** Hooks receive only the files that match their `files` regex AND `types` filter. `pre-commit run --all-files` bypasses staging and runs against all files in the tree.
**Staged files only by default:** When run via git hooks, pre-commit passes only staged files. This means fixers (e.g. `trailing-whitespace`) modify files but the commit is blocked — the user must re-stage and recommit.
**`PRE_COMMIT=1`** is set in the environment whenever a hook is executing (since v2.5.0). Hooks can use this to detect they are running under pre-commit.
**`identify` library** determines file types. Inspect what tags a file has with `identify-cli <file>`. Types include: `file`, `text`, `binary`, `executable`, `python`, `json`, `yaml`, `shell`, `javascript`, `typescript`, `markdown`, `toml`, `xml`, etc.
## Mental model for a skill/agent
A pre-commit agent operates on three artefacts:
1. `.pre-commit-config.yaml` — the config it creates or modifies
2. `.pre-commit-hooks.yaml` — a hook manifest if the agent is also authoring hooks in the repo
3. The git hooks in `.git/hooks/` — installed by `pre-commit install`
The safe workflow for a skill:
1. Write or modify `.pre-commit-config.yaml`
2. Run `pre-commit validate-config` — abort if non-zero
3. Run `pre-commit run --all-files` — surface hook failures
4. Run `pre-commit autoupdate` if updating `rev` values
5. Run `pre-commit install` once to wire hooks into git
## Cache and environment
Default cache: `~/.cache/pre-commit` or `$XDG_CACHE_HOME/pre-commit`.
Override: `export PRE_COMMIT_HOME=/path/to/cache`.
Pre-create all environments: `pre-commit install-hooks`.
Wipe and rebuild: `pre-commit clean`.
Remove unused only: `pre-commit gc`.

View File

@@ -1,29 +0,0 @@
# Sources
## context7-pre-commit-com
- **URL:** context7:/pre-commit/pre-commit.com
- **Description:** Official pre-commit.com documentation — installation, configuration schema, CLI reference, hook authoring, advanced features, troubleshooting
- **Contributing files:** overview.md, configuration.md, cli-reference.md, hook-authoring.md, examples.md, troubleshooting.md
- **Status:** `extracted`
## context7-pre-commit-hooks
- **URL:** context7:/pre-commit/pre-commit-hooks
- **Description:** Official pre-commit-hooks collection — all available hook IDs with options and examples
- **Contributing files:** hooks-reference.md, examples.md
- **Status:** `extracted`
## pre-commit-com
- **URL:** https://pre-commit.com/
- **Description:** Pre-commit framework homepage — full docs covering install, config, CLI, hook authoring, stages, local hooks, meta hooks, hazmat helpers, CI integration
- **Contributing files:** overview.md, configuration.md, cli-reference.md, hook-authoring.md, examples.md, troubleshooting.md
- **Status:** `extracted`
## pre-commit-hooks-github
- **URL:** https://raw.githubusercontent.com/pre-commit/pre-commit-hooks/main/README.md
- **Description:** Official pre-commit-hooks README — complete hook listing with all args, categories, deprecated hooks, and latest version (v6.0.0)
- **Contributing files:** hooks-reference.md
- **Status:** `extracted`

View File

@@ -1,110 +0,0 @@
---
topic: troubleshooting
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## Common issues
### Hooks don't run on `git commit`
`pre-commit install` was never run in this clone. Run it. Git hooks are per-clone — they are not committed.
### Hook modified files but commit was blocked
Expected behavior. The hook fixed files, so the staged version is now stale. Re-stage the modified files and commit again.
```bash
git add -u
git commit -m "same message"
```
### `SKIP` not working
The value must be the exact `id` field from the hook definition. Comma-separated, no spaces.
```bash
SKIP=check-yaml,trailing-whitespace git commit -m "msg" # correct
SKIP=check-yaml, trailing-whitespace git commit -m "msg" # wrong — space after comma
```
### Hook runs but matches wrong files (or no files)
`files:` uses `re.search()`, not a full-string match. `\.py$` matches any path ending in `.py`. To match only repo root: `^[^/]+\.py$`.
Use `identify-cli <filename>` to see exactly what type tags a file has, then verify your `types:` filter.
### Hook environment is stale or broken
```bash
pre-commit clean # wipe all environments
pre-commit install-hooks # rebuild everything
```
Or clean just a specific repo:
```bash
pre-commit gc # remove only unused environments
```
### `rev` is a branch name — `autoupdate` broke it
Branch refs are mutable; pre-commit resolves them at install time and then they drift. Always use a tag or commit SHA. Fix:
```bash
pre-commit autoupdate # finds the latest tag and rewrites rev in place
```
### `validate-config` returns an error
Schema violation in the YAML. Common causes:
- Missing `id` under a hook block
- Missing `rev` under a non-local repo block
- `repo: local` hook missing required `language` or `entry` fields
- Indentation error (YAML parsed but pre-commit schema rejected it)
### `check-hooks-apply` fails
A hook's `files`/`types` filter matches zero files in the repo. Either broaden the filter or remove the hook. This is a sign the hook is dead weight.
### `check-useless-excludes` fails
An `exclude` pattern matches no files. Remove or fix it.
### SSH cloning fails in CI
Export `SSH_AUTH_SOCK` in the CI environment, or use HTTPS URLs for hook repos.
### HTTP proxy needed
```bash
export http_proxy=http://proxy.example.com:3128
export https_proxy=http://proxy.example.com:3128
export no_proxy=localhost,127.0.0.1
```
### Hook too slow in CI
- Check `require_serial: false` (default) — hooks run in parallel by default
- Cache `$PRE_COMMIT_HOME` keyed on the hash of `.pre-commit-config.yaml`
- Use `--from-ref`/`--to-ref` instead of `--all-files` to only check changed files
### `language: system` deprecated warning
Renamed to `language: unsupported`. Old name still works as an alias but triggers a deprecation warning on newer versions.
### `pre-commit install -f` wiped my existing hooks
`-f` overwrites `.git/hooks/pre-commit` unconditionally. Without `-f`, pre-commit migrates the existing hook so both run. Only use `-f` deliberately.
### `pretty-format-json` fails but doesn't fix
`pretty-format-json` only fixes in-place when `args: [--autofix]` is passed. Without it, the hook just fails. Add `--autofix` to have it modify the file (the commit will then be blocked until you re-stage).
## Skill/agent-specific gotchas
- Always call `pre-commit validate-config` after writing or modifying config — do not assume valid YAML is valid pre-commit schema.
- `pre-commit autoupdate` modifies the config file in-place. If a skill calls it, re-read the file to get updated `rev` values for display/logging.
- Local hooks with `language: unsupported_script` require the `entry` script to be executable. If the skill creates the script, `chmod +x` it.
- The `stages` key in a hook override must match what was set in `default_install_hook_types` (or the `-t` flags passed to `pre-commit install`), otherwise the hook will never run.
- `always_run: true` combined with `pass_filenames: false` is the correct pattern for repo-wide validators (test suites, manifest checks) that do not operate on individual files.