Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ The Registries tab pulls skills and plugins from external catalogs. Need a priva
- One MCP server per agent. Tools load on demand, so the context window stays small.
- Any CLI command can be wrapped as an MCP tool the agent (and `capa sh`) can call.
- Rules go to each provider's native location: Cursor `.cursor/rules/`, Windsurf `.windsurf/rules/`, Copilot's instructions file, or a managed marker block in `AGENTS.md` / `CLAUDE.md` for providers without a rules directory. Glob scoping works.
- Lifecycle hooks for providers that support them (Claude Code, Cursor, Codex, Gemini CLI). Declare canonical events like `beforeShell` or `afterFileEdit` once and capa translates them into each provider's hook config — `.claude/settings.json`, `.cursor/hooks.json`, `.codex/config.toml`, `.gemini/settings.json` — using `capa:<id>` tags so user-authored entries are never touched. Providers without hook support emit a warning and skip.
- Sub-agents get their own filtered MCP endpoint that exposes only the tools the specialist actually needs.
- Skills and plugins are browsable from `capa add` and the web UI. Add a registry with `capa registry add owner/repo@my-adapter` (GitHub/GitLab/HTTPS sources, slug auto-derived, adapter validated before install) and it shows up too.
- `capabilities.lock` records resolved commit SHAs. A SHA-keyed content cache makes repeat installs near instant.
Expand Down
42 changes: 33 additions & 9 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ flowchart TD
Unregister --> Subagents

Subagents[install-subagents<br/>purge stale entries,<br/>unregister removed agents,<br/>register current agents +<br/>write subagent files + snippets]
Subagents --> Creds
Subagents --> PruneHooks

PruneHooks[prune-orphan-hooks<br/>delete capa-tagged hook entries<br/>no longer in capabilities,<br/>rm materialised scripts under<br/>~/.capa/hooks/<projectId>/] --> InstallHooks

InstallHooks{capabilities.hooks<br/>set?}
InstallHooks -- yes --> WriteHooks[install-hooks<br/>resolve sources to<br/>~/.capa/hooks/<projectId>/,<br/>upsert capa:&lt;id&gt; entries in<br/>provider hooks config]
InstallHooks -- no --> Creds
WriteHooks --> Creds

Creds{configureResult<br/>needs creds?}
Creds -- yes --> OpenBrowser[open-credential-setup<br/>open web UI for missing<br/>variables or OAuth2 connect]
Expand All @@ -120,6 +127,8 @@ table below is a quick map of what each task reads/writes.
| `install-agent-instructions` | `install-agent-instructions.ts` | `capabilities.agents` | `AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`, … |
| `prune-orphan-rules` | `prune-orphan-rules.ts` | DB managed-files, current rule IDs | rm rule files/marker blocks |
| `install-rules` | `install-rules.ts` | `capabilities.rules`, snapshot cache | per-provider rules dir **or** marker blocks in instructions file |
| `prune-orphan-hooks` | `prune-orphan-hooks.ts` | DB `managed_hooks`, current hook IDs | rm capa-tagged hook entries from provider configs, rm scripts in `~/.capa/hooks/<projectId>/` |
| `install-hooks` | `install-hooks.ts` | `capabilities.hooks`, snapshot cache, lockfile | provider hook config (`.claude/settings.json`, `.cursor/hooks.json`, `.codex/config.toml`, `.gemini/settings.json`), `~/.capa/hooks/<projectId>/<hookId>` script files, `managed_hooks` rows, lock entries |
| `configure-tools` | `configure-tools.ts` | merged capabilities | POSTs to `/api/projects/:id/configure`, stores `configureResult` |
| `register-mcp-server` | `register-mcp-server.ts` | provider registry, mcpUrl | provider MCP config (`.cursor/mcp.json`, `.mcp.json`, …) |
| `install-subagents` | `install-subagents.ts` | `capabilities.subagents`, DB sub-agents | sub-agent files, sub-agent MCP entries, instructions snippets |
Expand All @@ -141,10 +150,11 @@ flowchart TD
Resolve --> RmManaged[1. Remove managed files<br/>iterate db.getManagedFiles]
RmManaged --> CleanInstr[2. Clean agent instructions<br/>strip every capa:* marker block<br/>from AGENTS.md / CLAUDE.md / …]
CleanInstr --> CleanRules[3. Clean rules<br/>rm rule files in provider dirs<br/>+ rule marker blocks]
CleanRules --> RmLock[4. Remove capabilities.lock]
RmLock --> UnregSub[5. Unregister sub-agents<br/>rm MCP entries +<br/>instruction snippets]
UnregSub --> UnregMcp[6. Unregister capa MCP<br/>delete capa entry from each<br/>provider's MCP config]
UnregMcp --> RmProject[7. Remove project row<br/>db.deleteProject]
CleanRules --> CleanHooks[4. Clean hooks<br/>rm capa:* hook entries from<br/>provider hooks config +<br/>rm ~/.capa/hooks/&lt;projectId&gt;/]
CleanHooks --> RmLock[5. Remove capabilities.lock]
RmLock --> UnregSub[6. Unregister sub-agents<br/>rm MCP entries +<br/>instruction snippets]
UnregSub --> UnregMcp[7. Unregister capa MCP<br/>delete capa entry from each<br/>provider's MCP config]
UnregMcp --> RmProject[8. Remove project row<br/>db.deleteProject]
RmProject --> Done([Cleanup complete])
```

Expand All @@ -170,11 +180,25 @@ flowchart TD
delete on the next install when entries disappear from the capabilities
file. `capa clean` iterates the same table.

- **Managed-hooks table**. `managed_hooks(project_id, provider_id, hook_id,
config_path, locator, script_path)` tracks every hook entry capa wrote
into a shared provider config (`.claude/settings.json`,
`.cursor/hooks.json`, `.codex/config.toml`, `.gemini/settings.json`). The
`locator` is a JSON pointer (or TOML path) into the file so prune/clean
can edit a single entry surgically without disturbing user-authored
ones. `script_path` points at the materialised body under
`~/.capa/hooks/<projectId>/<hookId>` for `inline` / `remote` /
`github` / `gitlab` sources, and is `NULL` for inline-command hooks
and `source: { type: local }` hooks (which reference the user's file
in place and must never be deleted on clean). `prune-orphan-hooks`,
`install-hooks`, and `capa clean` all read and mutate this table.

- **Lockfile**. `capabilities.lock` pins resolved commit SHAs for every
`github`/`gitlab` skill and plugin. The lockfile is built incrementally
during install (`ctx.lockBuilder.upsertSkill/upsertPlugin`) and pruned to the
current set of IDs at the end. `--no-cache` disables both lockfile lookups
and the on-disk snapshot cache.
`github`/`gitlab` skill, plugin, and hook source. The lockfile is built
incrementally during install (`ctx.lockBuilder.upsertSkill / upsertPlugin
/ upsertHook`) and pruned to the current set of IDs at the end.
`--no-cache` disables both lockfile lookups and the on-disk snapshot
cache.

---

Expand Down
34 changes: 34 additions & 0 deletions docs/providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,40 @@ included for completeness but lack any project-local write paths.
| **[Windsurf (`windsurf`)](./windsurf.md)** | `.windsurf/skills/` | — | — | `.windsurf/rules/*.md` (yaml: `description`, `globs`, `trigger: always_on \| model_decision`) | — |
| [Zencoder (`zencoder`)](./zencoder.md) | `.zencoder/skills/` | — *(UI-managed; rules format unverified)* | — | — | — |

### Hooks integration

Hooks are intentionally absent from the matrix above because only a small
slice of providers wire them up. The four providers below have a `hooks`
integration in `registry.ts` today — capa edits the file in-place using
the `name = "capa:<id>"` tag (TOML providers like Codex use the same
field — appended as an opaque key Codex's deserialiser ignores) so it
can update or remove its own entries without touching user-authored
ones. Every other provider triggers a one-shot warning and skips; `capa
install` never fails because of an unsupported hook target.

| Provider | Config file | Shape |
| --- | --- | --- |
| **[Claude Code (`claude-code`)](./claude-code.md)** | `.claude/settings.json` → `hooks` | JSON map (event → `[{ matcher, hooks: [...] }]`) |
| **[Codex (`codex`)](./codex.md)** | `.codex/config.toml` → `[hooks]` | Matcher-grouped Claude-style envelope, serialised as TOML, `name: capa:<id>` tag |
| **[Cursor (`cursor`)](./cursor.md)** | `.cursor/hooks.json` (standalone) | `{ version: 1, hooks: { ... } }` envelope |
| **[Gemini CLI (`gemini-cli`)](./gemini-cli.md)** | `.gemini/settings.json` → `hooks` | JSON map (claude-style) |

Materialised hook scripts (when the YAML uses `source: { type: inline /
remote / github / gitlab }`) live under `~/.capa/hooks/<projectId>/<hookId>`
rather than in the project. `source: { type: local }` is special: the
script already exists in the project, so capa references it in place via
its absolute path — no copy under `~/.capa`, `chmod` is the user's
responsibility, edits take effect without re-running `capa install`, and
`capa clean` never deletes it. The `managed_hooks` SQLite table tracks
`(projectId, providerId, hookId, configPath, locator, scriptPath)` so
prune and clean can edit a single entry surgically; `scriptPath` is null
for inline-command hooks and for `local`-source hooks.

See [`docs/README.md`](../README.md#installation-pipeline) for how
`prune-orphan-hooks` and `install-hooks` slot into the install pipeline,
and the per-provider pages above for citations to each provider's hooks
documentation.

### Cross-cutting notes

- **`AGENTS.md` is universal.** Every install run touches `AGENTS.md`
Expand Down
15 changes: 14 additions & 1 deletion docs/providers/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,24 @@ Source-of-truth definition: [`src/shared/providers/registry.ts → claude-code`]
| Instructions | `CLAUDE.md` | Universal marker blocks; `AGENTS.md` also written if any other provider is active. |
| Rules | `.claude/rules/<id>.md` | YAML frontmatter — capa's `appliesTo` maps to `paths`. A file with no `paths` is loaded unconditionally. |
| Sub-agents | `.claude/agents/<id>.md` | Markdown + frontmatter (`name`, `description`, `model: inherit`). Also folds a `sub-agent:<id>` snippet into `CLAUDE.md`. |
| Hooks | `.claude/settings.json` → `hooks` | JSON map keyed by event (`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, `SessionEnd`, `Stop`, `SubagentStop`, `PreCompact`, `Notification`). Capa upserts `[{ matcher, hooks: [{ name: "capa:<id>", type, command, timeout }] }]` and only touches the entries it tagged. |
| Plugin manifests | `.claude-plugin/plugin.json` (`pluginProviderId: claude`) | Parsed by `parseClaudeManifest`. Hoisted to front of plugin search order — see [plugin docs](../README.md#plugin-discovery-and-unpack). |

## Hooks event mapping

Capa translates canonical events to Claude's hook event names:
`beforeTool → PreToolUse`, `afterTool → PostToolUse`,
`userPromptSubmit → UserPromptSubmit`, `sessionStart → SessionStart`,
`sessionEnd → SessionEnd`, `stop → Stop`, `subagentStop → SubagentStop`,
`preCompact → PreCompact`. `beforeShell` / `afterShell` re-use
`PreToolUse` / `PostToolUse` with an automatic `matcher: Bash` so the
hook only fires for shell tool invocations.

## Sources

- Memory & rules organisation: <https://code.claude.com/docs/en/memory>
- `.claude/rules/`: <https://code.claude.com/docs/en/memory#organize-rules-with-claude/rules/>
- Hooks reference: <https://docs.claude.com/en/docs/claude-code/hooks>
- Hooks settings (`.claude/settings.json` → `hooks`): <https://docs.claude.com/en/docs/claude-code/settings>

Last verified: 2026-05-23
Last verified: 2026-05-24
50 changes: 49 additions & 1 deletion docs/providers/codex.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,58 @@ Source-of-truth definition: [`src/shared/providers/registry.ts → codex`](../..
| Instructions | `AGENTS.md` | — |
| Rules | folded into `AGENTS.md` | No project-local rules directory; capa writes marker blocks into the instructions file. |
| Sub-agents | `.codex/agents/<id>.toml` | TOML format; body goes into the `developer_instructions` field. |
| Hooks | `.codex/config.toml` → `[hooks]` | Matcher-grouped Claude-style layout (`[[hooks.<Event>]]` + nested `[[hooks.<Event>.hooks]]`), serialised as TOML. Capa appends an opaque `name = "capa:<hookId>"` field on entries it owns; Codex's TOML deserialiser ignores unknown fields, so the tag round-trips cleanly and capa uses it for surgical updates without disturbing user-authored entries. |
| Plugin manifests | — | Not declared; Codex consumes plugins via the same Claude/Cursor manifest paths handled elsewhere. |

## Hooks event mapping

Codex uses Claude-style event names plus a tool-name matcher; built-in
tools include `Bash` and `apply_patch`, and MCP tools follow the
`mcp__server__tool` pattern. Canonical → Codex:
`sessionStart → SessionStart`, `userPromptSubmit → UserPromptSubmit`,
`beforeTool → PreToolUse`, `afterTool → PostToolUse`,
`beforeShell → PreToolUse` + `matcher: Bash`,
`afterShell → PostToolUse` + `matcher: Bash`,
`afterFileEdit → PostToolUse` + `matcher: apply_patch`,
`beforeMcpCall → PreToolUse` + `matcher: mcp__`,
`afterMcpCall → PostToolUse` + `matcher: mcp__`,
`subagentStart → SubagentStart`, `subagentStop → SubagentStop`,
`preCompact → PreCompact`, `stop → Stop`. Codex does not expose a
`sessionEnd` or `beforeFileRead` equivalent — those canonical hooks are
skipped on Codex with a one-shot warning. Codex-specific events (e.g.
`PermissionRequest`, `PostCompact`) can be targeted directly with
`on: codex:<EventName>`.

## TOML layout

Codex's hook config uses the same matcher-grouped envelope Claude uses,
serialised as TOML's nested array of tables. A capa-managed
`beforeShell` hook lands as:

```toml
[[hooks.PreToolUse]]
matcher = "Bash"

[[hooks.PreToolUse.hooks]]
type = "command"
command = "/abs/path/to/script"
name = "capa:audit-shell"
timeout = 5
```

The `name` field is capa's opaque entry tag (`capa:<hookId>`). Codex's
deserialiser (see
[`codex-rs/config/src/hook_config.rs`](https://github.com/openai/codex/blob/main/codex-rs/config/src/hook_config.rs))
does not use `#[serde(deny_unknown_fields)]` on `MatcherGroup` or
`HookHandlerConfig`, so the tag is silently ignored at runtime but
preserved across writes — capa relies on it to find and update or
remove its own entries without touching user-authored siblings in the
same matcher group.

## Sources

- Codex repo & config docs: <https://github.com/openai/codex>
- Codex hooks guide: <https://developers.openai.com/codex/hooks>
- Codex hooks deserialiser: <https://github.com/openai/codex/blob/main/codex-rs/config/src/hook_config.rs>

Last verified: 2026-05-23
Last verified: 2026-05-24
18 changes: 17 additions & 1 deletion docs/providers/cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,26 @@ Source-of-truth definition: [`src/shared/providers/registry.ts → cursor`](../.
| Instructions | `AGENTS.md` | — |
| Rules | `.cursor/rules/<id>.mdc` | YAML frontmatter: `description`, `globs` (from capa's `appliesTo`), `alwaysApply`. |
| Sub-agents | `.cursor/agents/<id>.md` | Markdown + frontmatter (`model`, `readonly`, `is_background`). |
| Hooks | `.cursor/hooks.json` (standalone) | `{ version: 1, hooks: { <eventName>: [ { name: "capa:<id>", command, … } ] } }` envelope. Supports both command-based hooks (`command`) and prompt-based, LLM-evaluated hooks (`type: "prompt"` + `prompt`). Cursor lets a hook fail-close on a non-zero exit (`failClosed: true`). |
| Plugin manifests | `.cursor-plugin/plugin.json` (`pluginProviderId: cursor`) | Parsed by `parseCursorManifest` — see [plugin docs](../README.md#plugin-discovery-and-unpack). |

## Hooks event mapping

Canonical → Cursor: `sessionStart → sessionStart`, `sessionEnd → sessionEnd`,
`beforeTool → preToolUse`, `afterTool → postToolUse`,
`afterToolFailure → postToolUseFailure`, `beforeShell → beforeShellExecution`,
`afterShell → afterShellExecution`, `beforeFileRead → beforeReadFile`,
`afterFileEdit → afterFileEdit`, `beforeMcpCall → beforeMCPExecution`,
`afterMcpCall → afterMCPExecution`, `userPromptSubmit → beforeSubmitPrompt`,
`subagentStart → subagentStart`, `subagentStop → subagentStop`,
`preCompact → preCompact`, `stop → stop`. Cursor-only events (e.g.
`afterAgentResponse`, `workspaceOpen`, `beforeTabFileRead`) can be
targeted directly with `on: cursor:<eventName>` to bypass the canonical
map.

## Sources

- Cursor docs: <https://docs.cursor.com/>
- Cursor hooks reference: <https://cursor.com/docs/agent/hooks>

Last verified: 2026-05-23
Last verified: 2026-05-24
19 changes: 18 additions & 1 deletion docs/providers/gemini-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,24 @@ Source-of-truth definition: [`src/shared/providers/registry.ts → gemini-cli`](
| Instructions | `AGENTS.md` | Supported via configurable `context.fileName`. |
| Rules | folded into `AGENTS.md` | No project-local rules directory; rules become marker blocks. |
| Sub-agents | `.gemini/agents/<id>.md` | Markdown + YAML frontmatter; `name` / `description` required. |
| Hooks | `.gemini/settings.json` → `hooks` | JSON map; Gemini reuses the Claude shape, so capa upserts `[{ matcher, hooks: [{ name: "capa:<id>", … }] }]` and only manages its own tagged entries. |
| Plugin manifests | — | Not declared. |

## Hooks event mapping

Canonical → Gemini: `sessionStart → SessionStart`, `sessionEnd → SessionEnd`,
`userPromptSubmit → BeforeAgent`, `beforeTool → BeforeTool`,
`afterTool → AfterTool`, `beforeShell → BeforeTool` + `matcher: run_shell_command`,
`afterShell → AfterTool` + `matcher: run_shell_command`,
`beforeFileRead → BeforeTool` + `matcher: read_file`,
`afterFileEdit → AfterTool` + `matcher: write_file|replace|edit_file`,
`beforeMcpCall → BeforeTool` + `matcher: mcp_.*`,
`afterMcpCall → AfterTool` + `matcher: mcp_.*`, `preCompact → PreCompress`.
Gemini does not expose a `Stop` equivalent, so canonical `stop` hooks are
skipped on this provider with a one-shot warning. Gemini-only events
(e.g. `BeforeToolSelection`, `BeforeModel`, `AfterModel`, `Notification`)
can be targeted directly with `on: gemini-cli:<EventName>`.

## Caveats

- Capa registers as `httpUrl` (streamable HTTP), not `url` (SSE). Don't
Expand All @@ -25,5 +41,6 @@ Source-of-truth definition: [`src/shared/providers/registry.ts → gemini-cli`](
## Sources

- Gemini CLI repo: <https://github.com/google-gemini/gemini-cli>
- Gemini CLI hooks (`.gemini/settings.json` → `hooks`): <https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/configuration.md#hooks>

Last verified: 2026-05-23
Last verified: 2026-05-24
Loading
Loading