feat(plugin): ship the agents from a single root agents/ directory

Move .claude/agents/ and server/data/agent-templates/ship-feature-lane/
into one agents/ at the repo root — which IS the plugin root, so all five
agents now ship with the ccam plugin as ccam:<name>, the same way skills
already do.

The two pipeline agents were byte-identical copies in both places; that
duplication is gone. `ccam lanes agents install` now reads them straight
from agents/, so the per-lane copy in <lane>/.claude/agents/ still gives
the bare `subagent_type: qc-local` the ship-feature-lane skill prefers.
Skill and docs record both names: bare inside a lane, ccam: as fallback.
This commit is contained in:
2026-08-21 17:30:04 +07:00
parent b7c573f8d9
commit 510f015fdf
13 changed files with 27 additions and 19 deletions
+2 -1
View File
@@ -1561,10 +1561,11 @@ graph TD
- feature shipping
- MCP operations
- live issue debugging
- `.claude/agents/` provides specialized review workers:
- `agents/` provides specialized review workers (shipped as the `ccam` plugin's agents):
- backend reviewer
- frontend reviewer
- MCP reviewer
- qc-local, senior-gate-reviewer (the ship-feature-lane pipeline's QC and publish gate)
- `plugins/` provides distributable plugin marketplace (see [Plugin Marketplace](#plugin-marketplace)):
- ccam-analytics (session reports, cost analysis, usage trends, productivity scoring)
- ccam-productivity (standups, weekly reports, sprint summaries, workflow optimization)
+2 -2
View File
@@ -10,7 +10,7 @@
- `client/`: React + Vite UI.
- `scripts/`: hook installer/handler, import, seed, cleanup utilities. (Update detection lives server-side in `server/lib/update-check.js`. `POST /api/updates/apply` (only when the checkout is fast-forwardable) pulls, rebuilds, and self-restarts via `server/lib/self-restart.js` + the detached `scripts/restart-helper.js`; otherwise users run the printed manual command, surfaced in the UI and by `ccam update-check`.)
- `mcp/`: local MCP server exposing dashboard operations as tools. **`mcp/build/` is committed on purpose** — plugin MCP servers start before any bootstrap could build them; `scripts/check-mcp-build.js` (content hash in `mcp/build/.srchash`, run by pre-commit and `/ccam-doctor`) keeps it honest. Rebuild with `npm run mcp:build`, never hand-edit `mcp/build/`.
- `skills/`: this repo's skills. Because the repo root **is** the `ccam` plugin root, this is also the plugin's skills directory — every skill here ships to anyone who installs the plugin, addressed as `/ccam:<name>` (bare `/<name>` works as an alias). Put a skill here only if it should reach plugin users; `ccam skills install` remains a fallback that copies `ship-feature-lane` into `~/.claude/skills/` for plain checkouts. Agents deliberately do NOT live at the root: `.claude/agents/` stays repo-local and lane agents are installed per-lane by `ccam lanes agents install`, because a plugin agent would be renamed `ccam:qc-local` and break `subagent_type: qc-local`.
- `skills/`: this repo's skills. Because the repo root **is** the `ccam` plugin root, this is also the plugin's skills directory — every skill here ships to anyone who installs the plugin, addressed as `/ccam:<name>` (bare `/<name>` works as an alias). Put a skill here only if it should reach plugin users; `ccam skills install` remains a fallback that copies `ship-feature-lane` into `~/.claude/skills/` for plain checkouts. - `agents/`: the same story for subagents — plugin root, so all five (`qc-local`, `senior-gate-reviewer`, `backend-reviewer`, `frontend-reviewer`, `mcp-reviewer`) ship as `ccam:<name>`. This is also the source `ccam lanes agents install` copies the two pipeline agents from into a lane's `.claude/agents/`, which is what keeps the bare `subagent_type: qc-local` working there. One file per agent, never a second copy.
- `.claude-plugin/`: the marketplace plus the root `ccam` plugin manifest (`"source": "./"` — the whole repo is the plugin). Its hooks are inline in `plugin.json`; its commands live in `plugins/ccam/commands/`, which is NOT a subdirectory plugin. `scripts/plugin-bootstrap.js` runs from `SessionStart` and owns the writable runtime under `~/.claude/agent-dashboard/runtime/` — it never writes into the plugin cache, which Claude Code replaces on every update. See `docs/PLUGINS.md`.
## Lanes
@@ -73,5 +73,5 @@ See `docs/LANES.md` for full guide: stage reporting, custom templates, lane acti
- For larger tasks, propose/check a short plan before broad edits.
- Use file-specific rules in `.claude/rules/` when working in scoped areas.
- Use project skills from `skills/` for repeatable workflows (they ship with the `ccam` plugin: `/ccam:<name>`, or the bare `/<name>` alias).
- Use `.claude/agents/` subagents for focused review or investigation passes.
- Use `agents/` subagents for focused review or investigation passes (they ship with the plugin: `ccam:backend-reviewer`, etc.).
- **Declare lane stage even in plain chat, not just inside skills.** `ccam stage` (see `docs/LANES.md` § Reporting a stage) is a reporting command, not a skill-only ritual — any Claude session working inside an adopted lane's cwd should call it on real stage transitions (starting to plan, starting to implement, running tests, opening the PR, etc.), whether or not a skill is driving. Tool-event detection (`server/lib/stage-detect.js`) only ever paints the amber "detected" badge, never the blue `current` ring — a lane worked entirely through plain chat with no `ccam stage` calls will show a stale `current` stage no matter how much real work happens. Skip it only when `ccam stage` reports no lane owns the cwd (not adopted).
+1 -1
View File
@@ -238,7 +238,7 @@ This repository includes extension packs for both Claude Code and Codex.
- `CLAUDE.md`
- `.claude/rules/`
- `skills/` (shipped as the `ccam` plugin's skills)
- `.claude/agents/`
- `agents/` (shipped as the `ccam` plugin's agents)
- Codex project packs live under `.codex/`:
- `AGENTS.md`
- `.codex/rules/`
+1 -1
View File
@@ -169,7 +169,7 @@ This repository ships extension files for both agent ecosystems:
- `CLAUDE.md`
- `.claude/rules/*`
- `skills/*` (the `ccam` plugin's own skills)
- `.claude/agents/*`
- `agents/*` (the `ccam` plugin's own agents)
- Codex:
- `AGENTS.md`
- `.codex/config.toml`
+3 -3
View File
@@ -1086,7 +1086,7 @@ ccam skills install
Copies `skills/ship-feature-lane/` into `~/.claude/skills/ship-feature-lane/` (overwrites on reinstall — run it again after pulling an update to the skill), which restores the bare `/ship-feature-lane` name. One-time per machine, same as `ccam lanes agents install` is one-time per lane.
**Agents are the deliberate exception.** `qc-local` and `senior-gate-reviewer` are *not* shipped as plugin agents: a plugin agent is addressed as `ccam:qc-local`, and the skill dispatches them by the bare `subagent_type: qc-local`. `ccam lanes agents install` writes them into the lane's own `.claude/agents/`, which is also the highest-priority agent source — so the lane's copy wins over anything global.
**Agents come from the same plugin, under two names.** `qc-local` and `senior-gate-reviewer` live in `agents/` at the repo root, so the plugin exposes them as `ccam:qc-local` / `ccam:senior-gate-reviewer`. `ccam lanes agents install` copies those same files into the lane's own `.claude/agents/` — the highest-priority agent source — which is what makes the bare `subagent_type: qc-local` the skill prefers resolve inside the lane. Either name reaches the same definition; the skill falls back to the `ccam:` form when the lane copy is missing.
### Invoking the skill
@@ -1155,13 +1155,13 @@ GENERATED_MERGE_PATHS="api/openapi.json api/client.ts"
### Installing the QC/gate agents: agents install
Stage 6 (browser QC) and Stage 7 (senior GO/NO-GO gate) run as subagents — `qc-local` and `senior-gate-reviewer` — that must exist in the lane's own `.claude/agents/` before the skill can launch them:
Stage 6 (browser QC) and Stage 7 (senior GO/NO-GO gate) run as subagents — `qc-local` and `senior-gate-reviewer`. The plugin already ships them as `ccam:qc-local` / `ccam:senior-gate-reviewer`; installing them into the lane's own `.claude/agents/` is what makes the bare names work there:
```bash
ccam lanes agents install
```
Writes both agent templates into `<lane>/.claude/agents/` and adds that directory to the lane's local `.git/info/exclude` (never the tracked `.gitignore` — this is a per-clone runtime concern, not an app-repo change). Idempotent and never automatic, the same shape as `ccam lanes proof-link` — a session installs the agents explicitly, once, before a lane's first run through Stage 6/7 (or after a CCAM upgrade ships updated templates — reinstalling overwrites, it doesn't merge).
Writes both agent definitions (from the repo's own `agents/`, the same files the plugin ships) into `<lane>/.claude/agents/` and adds that directory to the lane's local `.git/info/exclude` (never the tracked `.gitignore` — this is a per-clone runtime concern, not an app-repo change). Idempotent and never automatic, the same shape as `ccam lanes proof-link` — a session installs the agents explicitly, once, before a lane's first run through Stage 6/7 (or after a CCAM upgrade ships updated templates — reinstalling overwrites, it doesn't merge).
**Credentials are not embedded.** Unlike Shipyard's original per-lane agent generation, these templates carry no QA account credentials — this repo has no seeded-QA-account system yet. `qc-local` degrades gracefully: if it hits a login page with no credentials block present, it reports `LOCAL-QC: FAIL — login required, no seed-account mechanism configured for this profile` rather than guessing.
+10 -6
View File
@@ -89,6 +89,7 @@ checkout unnecessary.
| The dashboard UI (`/run` and every other client route) | built into the runtime dir by the bootstrap, so it works the moment `claude` starts |
| `/ccam-doctor`, `/ccam-update`, `/ccam-open` | `plugins/ccam/commands/` |
| The skills (`ship-feature-lane`, `ship-feature`, `repo-onboarding`, `debug-live-issue`, `mcp-operations`, `update-project-docs`, `file-headers`) | `skills/` at the repo root — which IS the plugin root, so they are auto-discovered as plugin skills |
| The agents (`qc-local`, `senior-gate-reviewer`, `backend-reviewer`, `frontend-reviewer`, `mcp-reviewer`) | `agents/` at the repo root, auto-discovered the same way — exposed as `ccam:<name>` |
Plugin skills are namespaced: they are invoked as `/ccam:<name>`, with the bare
`/<name>` working as an alias unless another skill or command claims that name.
@@ -97,12 +98,15 @@ latter only loads for a session whose cwd is inside this checkout, which is
useless for a lane working in a different repo. `ccam skills install` survives
as the fallback for a plain checkout without the plugin.
**Agents are deliberately not shipped this way.** `.claude/agents/` stays
repo-local, and the lane agents (`qc-local`, `senior-gate-reviewer`) are
installed per-lane by `ccam lanes agents install`. A plugin agent would be
addressed as `ccam:qc-local`, breaking the `subagent_type: qc-local` references
in the pipeline skill; the per-lane copy also lands in the highest-priority
agent source.
**The agents ship the same way, from the same files.** `agents/` at the repo
root holds all five (`qc-local`, `senior-gate-reviewer`, `backend-reviewer`,
`frontend-reviewer`, `mcp-reviewer`), so the plugin exposes them as
`ccam:<name>`. `ccam lanes agents install` copies `qc-local` +
`senior-gate-reviewer` from that same directory into a lane's own
`.claude/agents/` — the highest-priority agent source — which is what makes the
bare `subagent_type: qc-local` in the pipeline skill resolve there. One source
file, two ways to reach it; the skill names the bare form first and falls back
to the `ccam:` form.
Because the plugin ships the hooks itself, `npm run install-hooks` is not needed
for plugin users — and must not be run alongside it. Events carry no id, so two
+5 -2
View File
@@ -1,7 +1,10 @@
/**
* @file Installs the ship-feature-lane pipeline's agent templates
* (qc-local, senior-gate-reviewer) into a lane's own .claude/agents/, so a
* driving session can launch them by subagent_type. Plain file I/O except
* driving session can launch them by the BARE subagent_type name. Source is
* the repo's own `agents/` — the same files the ccam plugin ships, where
* they resolve as `ccam:qc-local`; installing them into the lane is what
* makes the unprefixed name work there too. Plain file I/O except
* for one git call — resolving where a worktree lane's shared info/exclude
* actually lives, the same git-dir/git-common-dir distinction E2's
* lane-sync.js had to get right for info/attributes.
@@ -12,7 +15,7 @@ const fs = require("node:fs");
const path = require("node:path");
const { git } = require("./worktree");
const TEMPLATE_DIR = path.join(__dirname, "..", "data", "agent-templates", "ship-feature-lane");
const TEMPLATE_DIR = path.join(__dirname, "..", "..", "agents");
const TEMPLATE_FILES = ["qc-local.md", "senior-gate-reviewer.md"];
const EXCLUDE_LINE = ".claude/agents/";
+3 -3
View File
@@ -36,7 +36,7 @@ Then check your current position:
Resume from the stage shown. If state says `stage=X status=running`, you were mid-stage X when context compacted — re-run that stage from the top (all `ccam` commands are idempotent).
- **MCP preflight (fail fast):** if this is the lane's first run, or a required Playwright MCP's `browser_*` tools aren't available in this session, run `ccam lanes mcp sync` — it relocates the source repo's already-configured MCP servers into this lane's `.mcp.json`. If the source repo has none configured (`ENOMCPCONFIG`), tell the human to configure `.mcp.json` manually for the source repo first. Either way, restart the session after syncing so the new config loads — `playwright` and a local-QC MCP are always required for Stage 3/6. Catching this at Stage 0 costs a minute; catching it at Stage 13 strands a merged feature unverified.
- **Agents preflight:** if this is the lane's first run through this pipeline, run `ccam lanes agents install` once — it writes the `qc-local` and `senior-gate-reviewer` agent definitions into the lane's own `.claude/agents/` (idempotent; re-running just overwrites with the current templates). Stage 6/7 launch these by `subagent_type`; skipping this leaves those stages unable to find the agent.
- **Agents preflight:** if this is the lane's first run through this pipeline, run `ccam lanes agents install` once — it writes the `qc-local` and `senior-gate-reviewer` agent definitions into the lane's own `.claude/agents/` (idempotent; re-running just overwrites with the current definitions). That copy is what makes the bare `subagent_type: qc-local` resolve inside the lane. If the install isn't possible (not a git repo, or the CLI is unavailable), the `ccam` plugin ships the same two agents, addressed as `ccam:qc-local` / `ccam:senior-gate-reviewer` — use those names instead. Only if NEITHER exists are Stages 6/7 unable to launch.
## Hard rules
@@ -133,12 +133,12 @@ The e2e hook doesn't run migrations itself — it tests the already-running stac
### 6 — Browser QC via the qc-local agent
- **Test-only fast-path:** on a fix-loop re-entry whose change is ENTIRELY test files (see the fix-loop rule), SKIP this stage — the app's runtime UI is unchanged — and record `--evidence "QC skipped: test-only change"`. Otherwise run it:
- `ccam stage qc --status running`, then launch the **qc-local** agent (Agent tool, `subagent_type: qc-local` — FOREGROUND, fresh context, **never `fork`**; it gates the pipeline; the Setup section's agents preflight already installed it). Give it: the lane's working directory, the feature slug, the feature title, the acceptance points (lane spec), and the **QC Plan** (lane spec, Stage 5) as the authoritative scope to execute against — and NOTHING about how you built it. No "the button is at the top-right", no "this part already works", no known-issue list: a QC pass that starts from your mental model only re-checks what you already believed. It owns the whole local browser QC and proof capture (`ccam lanes proof-link` first, then screenshots land under the proof gallery automatically). It runs against the lane's feature-branch stack from Stage 3. Do NOT drive the browser yourself at this stage.
- `ccam stage qc --status running`, then launch the **qc-local** agent (Agent tool, `subagent_type: qc-local`, or `ccam:qc-local` if the lane copy isn't installed — FOREGROUND, fresh context, **never `fork`**; it gates the pipeline; the Setup section's agents preflight covers both names). Give it: the lane's working directory, the feature slug, the feature title, the acceptance points (lane spec), and the **QC Plan** (lane spec, Stage 5) as the authoritative scope to execute against — and NOTHING about how you built it. No "the button is at the top-right", no "this part already works", no known-issue list: a QC pass that starts from your mental model only re-checks what you already believed. It owns the whole local browser QC and proof capture (`ccam lanes proof-link` first, then screenshots land under the proof gallery automatically). It runs against the lane's feature-branch stack from Stage 3. Do NOT drive the browser yourself at this stage.
- Parse its last line: `LOCAL-QC: PASS` → continue. `LOCAL-QC: FAIL — <reasons>` → fix on the feature branch → re-run from Stage 2. Keep its report — it is the feature user-flow review for the senior gate.
- **Fold back discoveries:** if its report lists scenarios it found that weren't in the plan (its "Scenarios discovered during QC" section), add them to the `## QC Plan` in-scope list in the lane spec.
### 7 — Senior GO/NO-GO gate *(authorizes the publish)*
- Launch the **senior-gate-reviewer** agent (Agent tool, `subagent_type: senior-gate-reviewer` — FOREGROUND, fresh context, **never `fork`**; installed the same way as `qc-local`, see Stage 6). Give it: the lane's working directory, the requirement + Stage-0 answers (the lane spec file), the feature branch, the Stage-4 code-review findings + resolutions **as the other agents wrote them** (verbatim/by path — never your paraphrase of why each was fine), the Stage-6 `qc-local` report (the user-flow review), and the raw results of gates/e2e/QC. The agent inspects the local diff with `git diff origin/development...feat/<slug>` — no open PR is required (and none exists yet).
- Launch the **senior-gate-reviewer** agent (Agent tool, `subagent_type: senior-gate-reviewer`, or `ccam:senior-gate-reviewer` if the lane copy isn't installed — FOREGROUND, fresh context, **never `fork`**; installed the same way as `qc-local`, see Stage 6). Give it: the lane's working directory, the requirement + Stage-0 answers (the lane spec file), the feature branch, the Stage-4 code-review findings + resolutions **as the other agents wrote them** (verbatim/by path — never your paraphrase of why each was fine), the Stage-6 `qc-local` report (the user-flow review), and the raw results of gates/e2e/QC. The agent inspects the local diff with `git diff origin/development...feat/<slug>` — no open PR is required (and none exists yet).
- Parse its final line:
- `VERDICT: GO` → proceed to Stage 8.
- `VERDICT: NO-GO — <fixes>` → `ccam stage gate-blocked --evidence "NO-GO — <reason>"` (an alias of `gate`, so the map holds while the lane's stage name says the gate refused), then fix on the feature branch and re-run **from Stage 2**. No attempt cap — the loop re-enters; the dashboard's time-on-stage surfaces a lane stuck cycling so the human can step in. Set `--status blocked` only for a genuine blocker you can't resolve.