refactor!: rebrand CCAM to AgentHub
Clean break, no compatibility alias. Renames the product name, the CLI binary (`ccam` -> `agenthub`), the env var prefix (`CCAM_` -> `AGENTHUB_`), the Prometheus metric namespace (`ccam_*` -> `agenthub_*`), the tmux session prefix (`ccam-lane-<id>` -> `agenthub-lane-<id>`), the per-repo runtime profile directory (`.ccam/profile` -> `.agenthub/profile`), the lanes state root (`~/.claude/ccam-lanes` -> `~/.claude/agenthub-lanes`), the bundled plugins (`plugins/ccam*` -> `plugins/agenthub*`) and their slash commands (`/ccam-doctor` -> `/agenthub-doctor`, `/ccam:*` -> `/agenthub:*`). Because there is no alias, an existing install has to be updated by hand: rename its `.ccam/profile` directory and any `CCAM_*` variables, and reinstall the plugin under its new name. Two other name families are deliberately left alone here, since they are not just strings: the `Claude-Code-Monitor` repository/marketplace name, and the `agent-dashboard` npm package plus its `~/.claude/agent-dashboard` runtime directory, which holds the live database and needs a migration rather than a rename. Verified: server 1406/1406, client 459/459, `grep -i ccam` clean across tracked files, `openapi.yaml` regenerated (95 paths, unchanged count).
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
## Problem
|
||||
|
||||
A lane's stage only moves when the driving agent calls `ccam stage <name>`. Every un-instrumented session — which is most of them — sits at `idle` forever while its agent works, so the pipeline map shows nothing. The dashboard already receives every tool call the agent makes; it just never reads them.
|
||||
A lane's stage only moves when the driving agent calls `agenthub stage <name>`. Every un-instrumented session — which is most of them — sits at `idle` forever while its agent works, so the pipeline map shows nothing. The dashboard already receives every tool call the agent makes; it just never reads them.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -12,7 +12,7 @@ Infer a lane's stage from the hook stream it already ingests, and show it **with
|
||||
|
||||
## The rule that shapes everything
|
||||
|
||||
**Inference never renders green.** A node the dashboard inferred reaches `passed-no-evidence` (amber) at most; `done` requires a declared stage carrying evidence via `ccam stage --evidence`. If inference could paint a node green, the amber/green distinction — the reason this feature exists — would be worthless.
|
||||
**Inference never renders green.** A node the dashboard inferred reaches `passed-no-evidence` (amber) at most; `done` requires a declared stage carrying evidence via `agenthub stage --evidence`. If inference could paint a node green, the amber/green distinction — the reason this feature exists — would be worthless.
|
||||
|
||||
Declared always outranks detected. A lane that has declared `review` ignores a detection for `implement`.
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ Four small pieces, each independently useful:
|
||||
|
||||
## What the console must not do
|
||||
|
||||
**It never touches the lane's stage.** Typing `/code-review` in the UI does not move the lane to `review`; only `ccam stage` declares, and only detection (sub-project B) infers. The console is a window onto a process, not a driver of the pipeline. Keeping that boundary is what stops the pipeline from becoming a lie.
|
||||
**It never touches the lane's stage.** Typing `/code-review` in the UI does not move the lane to `review`; only `agenthub stage` declares, and only detection (sub-project B) infers. The console is a window onto a process, not a driver of the pipeline. Keeping that boundary is what stops the pipeline from becoming a lie.
|
||||
|
||||
## Risks and how they are contained
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
## Problem
|
||||
|
||||
A lane today is a pointer at a directory that already exists. Two agents working in parallel therefore work in the *same* checkout and collide — the exact failure Shipyard solves by giving every lane its own clone. CCAM has no provisioning at all: no way to create a lane's working copy, no way to reset it between features, no way to remove it, and no way to reclaim the database a finished lane leaves behind (a real install reached 121 MB).
|
||||
A lane today is a pointer at a directory that already exists. Two agents working in parallel therefore work in the *same* checkout and collide — the exact failure Shipyard solves by giving every lane its own clone. AgentHub has no provisioning at all: no way to create a lane's working copy, no way to reset it between features, no way to remove it, and no way to reclaim the database a finished lane leaves behind (a real install reached 121 MB).
|
||||
|
||||
## Goals
|
||||
|
||||
- A lane can own a **git worktree** that CCAM creates, resets and removes.
|
||||
- The lifecycle verbs mirror Shipyard's, because that vocabulary is proven: `add`, `clear`, `reset`, `remove`, plus `purge` as CCAM's analogue of Shipyard's per-lane `dropdb`.
|
||||
- A lane can own a **git worktree** that AgentHub creates, resets and removes.
|
||||
- The lifecycle verbs mirror Shipyard's, because that vocabulary is proven: `add`, `clear`, `reset`, `remove`, plus `purge` as AgentHub's analogue of Shipyard's per-lane `dropdb`.
|
||||
- Every destructive action is confirmed **against counted facts**, not adjectives.
|
||||
- Directories the user already had must be impossible for CCAM to destroy.
|
||||
- Directories the user already had must be impossible for AgentHub to destroy.
|
||||
|
||||
## Non-goals
|
||||
|
||||
@@ -33,9 +33,9 @@ Additive columns on `lanes`, each guarded by the repo's `try { SELECT col } catc
|
||||
|
||||
`branch`, `cwd`, `stage`, `stages` and the rest keep their current meaning. `cwd` stays `UNIQUE`.
|
||||
|
||||
Environment: `LANES_ROOT` (default `~/.claude/ccam-lanes`), `LANE_BASE_BRANCH`, `LANE_BRANCH_PREFIX` (default `feat/`).
|
||||
Environment: `LANES_ROOT` (default `~/.claude/agenthub-lanes`), `LANE_BASE_BRANCH`, `LANE_BRANCH_PREFIX` (default `feat/`).
|
||||
|
||||
Layout: worktree at `$LANES_ROOT/<repo-basename>__<slug>`, branch `<prefix><slug>`. Numbered `lane1..lane9` slots were considered and rejected — CCAM is multi-repo, and slot numbers carry no meaning without Shipyard's per-lane ports.
|
||||
Layout: worktree at `$LANES_ROOT/<repo-basename>__<slug>`, branch `<prefix><slug>`. Numbered `lane1..lane9` slots were considered and rejected — AgentHub is multi-repo, and slot numbers carry no meaning without Shipyard's per-lane ports.
|
||||
|
||||
## Safety model
|
||||
|
||||
@@ -49,7 +49,7 @@ A `managed` lane may be destroyed only when **all three** independent checks pas
|
||||
|
||||
Shipyard gets away with one check (`case "$DIR" in */lane$N`) because its directory names are fixed. Dropping numbered slots costs that guarantee, so three cheaper checks replace it. Every destructive function in `server/lib/worktree.js` re-runs the three checks itself rather than trusting its caller.
|
||||
|
||||
CCAM never runs `rm -rf` on a lane. Removal goes through `git worktree remove`; if git refuses, the error surfaces unchanged.
|
||||
AgentHub never runs `rm -rf` on a lane. Removal goes through `git worktree remove`; if git refuses, the error surfaces unchanged.
|
||||
|
||||
## Verbs
|
||||
|
||||
@@ -110,7 +110,7 @@ SQLite does not shrink on `DELETE`. `purge` runs `DELETE` plus `PRAGMA optimize`
|
||||
|
||||
**API:** `POST /api/lanes/worktree` (add), `GET /api/lanes/:id/preflight`, and `reset` / `purge` joining the existing `POST /api/lanes/:id/:action` set, all behind the existing same-origin guard.
|
||||
|
||||
**CLI:** `ccam lanes add --repo <path> [--title <t>] [--base <branch>]`, `ccam lanes reset|remove|purge <id> [--force]`.
|
||||
**CLI:** `agenthub lanes add --repo <path> [--title <t>] [--base <branch>]`, `agenthub lanes reset|remove|purge <id> [--force]`.
|
||||
|
||||
**UI:** a `managed` / `adopted` badge on the lane card; destructive buttons rendered only for `managed`; the existing `ConfirmModal` showing the preflight table.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Two facts also make lanes look emptier than they are:
|
||||
|
||||
## Goal
|
||||
|
||||
The reference screen's legibility, on CCAM's real data: a lane's state readable
|
||||
The reference screen's legibility, on AgentHub's real data: a lane's state readable
|
||||
from across the room, the pipeline large enough to trace, and the console present
|
||||
but out of the way until wanted.
|
||||
|
||||
@@ -37,7 +37,7 @@ but out of the way until wanted.
|
||||
- **The console collapses.** It keeps every capability from A; it starts
|
||||
collapsed and opens for the selected lane. Watching lanes is the default
|
||||
posture, driving one is the exception.
|
||||
- **Only real data.** No placeholder tiles for facts CCAM does not have
|
||||
- **Only real data.** No placeholder tiles for facts AgentHub does not have
|
||||
(tickets, preview ports, per-lane credentials). Branch/commit/CI are added
|
||||
because they can be read for real — see below.
|
||||
- **Detection expires.** A detection older than a TTL stops holding the floor.
|
||||
@@ -59,7 +59,7 @@ A wired it. The console is unchanged behind its new disclosure — no prop of
|
||||
|
||||
## The card
|
||||
|
||||
Reference layout, CCAM's fields, nothing invented:
|
||||
Reference layout, AgentHub's fields, nothing invented:
|
||||
|
||||
| Row | Content | Source |
|
||||
|---|---|---|
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Lane profile scaffolding (A3, v1: Node preset) — design
|
||||
|
||||
**Source of truth for "Shipyard":** `~/MyDrive/Projects/ResearchAndDevelopment/AgentWorkflow/`. This is the third phase of the Shipyard-parity roadmap (`docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md`, phase A3), scoped down to exactly the piece that phase's own text asked for: **detect a Node.js project and scaffold a working `.ccam/profile/`**, so adopting a repo becomes one command instead of hand-writing nine hook scripts. Python/Go/Ruby detection, named in the long-range plan, is explicitly **out of scope** for this design — see Non-goals.
|
||||
**Source of truth for "Shipyard":** `~/MyDrive/Projects/ResearchAndDevelopment/AgentWorkflow/`. This is the third phase of the Shipyard-parity roadmap (`docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md`, phase A3), scoped down to exactly the piece that phase's own text asked for: **detect a Node.js project and scaffold a working `.agenthub/profile/`**, so adopting a repo becomes one command instead of hand-writing nine hook scripts. Python/Go/Ruby detection, named in the long-range plan, is explicitly **out of scope** for this design — see Non-goals.
|
||||
|
||||
## Problem
|
||||
|
||||
Today, using CCAM's runtime/data-isolation features (A1: slots+ports, A2: database/Redis/`.env`) on a new repo means a human reads `docs/LANES.md` and hand-writes `.ccam/profile/profile.env` plus every hook script from scratch. That's the adoption barrier this removes.
|
||||
Today, using AgentHub's runtime/data-isolation features (A1: slots+ports, A2: database/Redis/`.env`) on a new repo means a human reads `docs/LANES.md` and hand-writes `.agenthub/profile/profile.env` plus every hook script from scratch. That's the adoption barrier this removes.
|
||||
|
||||
## Scope (v1)
|
||||
|
||||
@@ -19,7 +19,7 @@ Anything else (no package.json anywhere, or a `backend/` without a matching `fro
|
||||
|
||||
- Detecting Python (Django/FastAPI), Go, Ruby, or any non-Node stack. That is real, planned work (see the parent plan's A3 section) but a separate preset, added later, informed by whatever this Node preset's implementation actually looks like once it exists.
|
||||
- Detecting ORMs/migration tools (Prisma, Knex, TypeORM, Sequelize, alembic, …). `migrate`/`seed` are always scaffolded as an explicit TODO stub when a database is detected — never a guessed command.
|
||||
- pnpm/yarn/npm **workspaces** or turborepo-style (`apps/`, `packages/`) monorepo layouts. Only the flat `backend/` + `frontend/` layout already documented as CCAM's convention (`docs/LANES.md`'s `BACKEND_DIR`/`FRONTEND_DIR` example) is detected.
|
||||
- pnpm/yarn/npm **workspaces** or turborepo-style (`apps/`, `packages/`) monorepo layouts. Only the flat `backend/` + `frontend/` layout already documented as AgentHub's convention (`docs/LANES.md`'s `BACKEND_DIR`/`FRONTEND_DIR` example) is detected.
|
||||
- Any HTTP API route. `profile init`/`profile check` are CLI-only, local filesystem actions against the *source* repo — no dashboard surface needs them.
|
||||
- A `--fix` mode for `profile check`. It is read-only/diagnostic only.
|
||||
|
||||
@@ -30,17 +30,17 @@ One new library module, **`server/lib/lane-detect.js`**, plus two new CLI subcom
|
||||
```
|
||||
server/lib/lane-detect.js
|
||||
detectNode(repoPath) -> { layout: "monorepo"|"single-service", ... facts } | null
|
||||
scaffoldProfile(repoPath, facts, { force }) -> writes .ccam/profile/**, returns { written: [...], todos: [...] }
|
||||
scaffoldProfile(repoPath, facts, { force }) -> writes .agenthub/profile/**, returns { written: [...], todos: [...] }
|
||||
checkProfile(dir) -> { ok: boolean, errors: [...], warnings: [...] }
|
||||
```
|
||||
|
||||
`bin/ccam.js` gains:
|
||||
`bin/agenthub.js` gains:
|
||||
```
|
||||
ccam lanes profile init <repo> [--force]
|
||||
ccam lanes profile check [<path>] # defaults to cwd
|
||||
agenthub lanes profile init <repo> [--force]
|
||||
agenthub lanes profile check [<path>] # defaults to cwd
|
||||
```
|
||||
|
||||
Both are pure local actions on whatever machine runs the CLI — no dashboard server round trip needed for the write itself (unlike `lanes add --repo`, this never creates a worktree or a lane row; it just prepares a repo so that a *later* `lanes add --repo` on it gets a working profile for free, because `resolveProfile` already reads `.ccam/profile/` from the lane's own working copy or its source repo — nothing downstream needs to change).
|
||||
Both are pure local actions on whatever machine runs the CLI — no dashboard server round trip needed for the write itself (unlike `lanes add --repo`, this never creates a worktree or a lane row; it just prepares a repo so that a *later* `lanes add --repo` on it gets a working profile for free, because `resolveProfile` already reads `.agenthub/profile/` from the lane's own working copy or its source repo — nothing downstream needs to change).
|
||||
|
||||
## Detection rules (`detectNode`)
|
||||
|
||||
@@ -77,7 +77,7 @@ Reads only; never executes `npm`, never shells out.
|
||||
|
||||
### `scaffoldProfile`
|
||||
|
||||
Writes `<repo>/.ccam/profile/profile.env` and `<repo>/.ccam/profile/hooks/*.sh` (mode `0755`) from the facts `detectNode` returned. Refuses if `.ccam/profile/profile.env` already exists, unless `--force`. Returns which files it wrote and which lines are TODO placeholders, so the CLI can print a clear "N things need your attention" summary instead of silently declaring success.
|
||||
Writes `<repo>/.agenthub/profile/profile.env` and `<repo>/.agenthub/profile/hooks/*.sh` (mode `0755`) from the facts `detectNode` returned. Refuses if `.agenthub/profile/profile.env` already exists, unless `--force`. Returns which files it wrote and which lines are TODO placeholders, so the CLI can print a clear "N things need your attention" summary instead of silently declaring success.
|
||||
|
||||
### `checkProfile` (validator, read-only)
|
||||
|
||||
@@ -87,7 +87,7 @@ Given a directory (a repo root, or a lane's cwd — the same two places `resolve
|
||||
- Every hook referenced (`bootstrap`, `boot`, `health`, plus `db-create`/`db-drop` when `DB_PREFIX` is set) exists on disk and is executable (`fs.statSync(...).mode & 0o111`).
|
||||
- No literal string `TODO:` remains anywhere in `profile.env` or any hook file's contents. **This is expected to fail immediately after `profile init` scaffolds a repo whose detection left any TODO (no start/dev script, or a database with unconfigured migrate/seed) — that is the intended signal to finish the manual step, not a bug.**
|
||||
- If `DB_PREFIX` is set: `db-create.sh`/`db-drop.sh` both exist (redundant with the hook check above, but reported as its own line so the message reads as "database configuration" rather than a generic missing-hook error).
|
||||
- If `DB_PREFIX` is set and `~/.ccam/secrets.env` (`server/lib/secrets.js:SECRETS_PATH`) does not exist: a **warning** (not a failure) — "no secrets.env; database will use built-in defaults until you create one."
|
||||
- If `DB_PREFIX` is set and `~/.agenthub/secrets.env` (`server/lib/secrets.js:SECRETS_PATH`) does not exist: a **warning** (not a failure) — "no secrets.env; database will use built-in defaults until you create one."
|
||||
- Every declared `PORT_BASE_<name>` port is currently free (`server/lib/ports.js:isListening`, reused as-is).
|
||||
|
||||
Exits non-zero and prints every failure found (not just the first) when any of the hard checks fail; warnings never affect the exit code.
|
||||
@@ -95,20 +95,20 @@ Exits non-zero and prints every failure found (not just the first) when any of t
|
||||
## CLI
|
||||
|
||||
```
|
||||
ccam lanes profile init <repo> [--force]
|
||||
agenthub lanes profile init <repo> [--force]
|
||||
```
|
||||
- `<repo>` must be an absolute, existing, git-repository path (same validation `POST /api/lanes/worktree` already applies to `sourceRepo`).
|
||||
- On success: prints what was scaffolded, and — if anything was left as a TODO — a clear "N item(s) need manual attention before this profile is usable: <list>" block, plus the suggested next command (`ccam lanes profile check <repo>`).
|
||||
- On success: prints what was scaffolded, and — if anything was left as a TODO — a clear "N item(s) need manual attention before this profile is usable: <list>" block, plus the suggested next command (`agenthub lanes profile check <repo>`).
|
||||
- On "nothing detected": a clear, actionable refusal — *not* a bare error:
|
||||
```
|
||||
✖ No detectable Node.js project at <repo> (looked for backend/package.json +
|
||||
frontend/package.json, or a root package.json).
|
||||
Auto-scaffolding currently supports Node.js repos in that layout only.
|
||||
Write .ccam/profile/ by hand — see docs/LANES.md.
|
||||
Write .agenthub/profile/ by hand — see docs/LANES.md.
|
||||
```
|
||||
|
||||
```
|
||||
ccam lanes profile check [<path>]
|
||||
agenthub lanes profile check [<path>]
|
||||
```
|
||||
- `<path>` defaults to `process.cwd()`. Deliberately **not** lane-id/cwd-resolved the way `lanes up`/`lanes logs`/etc. are — `profile check` is meant to run against a bare repo right after `profile init`, before any lane/worktree exists for it.
|
||||
- Prints every failure and warning found; exits 0 only when there are zero failures (warnings are fine).
|
||||
|
||||
@@ -22,7 +22,7 @@ isolation — goes first because it blocks the rest, and splits into three:
|
||||
- **A1 (this spec)** — a lane can boot its stack on isolated ports and directories,
|
||||
and the stack survives a dashboard restart. Profiles are hand-written.
|
||||
- **A2** — data isolation: `.env` seeding, database create/drop/migrate/seed, Redis index.
|
||||
- **A3** — stack detection + `ccam lane profile init` scaffolding, designed **from**
|
||||
- **A3** — stack detection + `agenthub lane profile init` scaffolding, designed **from**
|
||||
the real profiles written during A1/A2.
|
||||
|
||||
The split point matters. Shipyard was genericized but its `profiles/` is still
|
||||
@@ -30,14 +30,14 @@ empty (`PROFILE=_template`, `SOURCE_REPO=""`, `state/` empty) — a clean seam t
|
||||
never ran against a real app. Designing presets before a real profile exists
|
||||
repeats exactly that.
|
||||
|
||||
**A1 done means:** `ccam lane up` boots a lane's stack on ports no other lane uses,
|
||||
and `ccam update` (or any dashboard restart) leaves running stacks alone.
|
||||
**A1 done means:** `agenthub lane up` boots a lane's stack on ports no other lane uses,
|
||||
and `agenthub update` (or any dashboard restart) leaves running stacks alone.
|
||||
|
||||
## Decisions
|
||||
|
||||
| | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Profile location | `<repo>/.ccam/profile/` | CCAM is multi-repo (lanes keyed by `cwd`); one global `PROFILE` is wrong the moment two repos have lanes. A profile that lives in the repo is already present in a fresh worktree. |
|
||||
| Profile location | `<repo>/.agenthub/profile/` | AgentHub is multi-repo (lanes keyed by `cwd`); one global `PROFILE` is wrong the moment two repos have lanes. A profile that lives in the repo is already present in a fresh worktree. |
|
||||
| Engine | Node, `server/lib/` | `execFile` with argument arrays (repo rule: never build a git/shell command as a string). Shares the SQLite DB and the websocket. Hooks stay shell scripts; the engine spawns them. |
|
||||
| Resource model | Declared per repo | `PORTS="api fe worker"`, `LANE_DIRS="…"`. An app with no database declares none, so A2 generates no database hooks — no dead code. |
|
||||
| Process ownership | Fully detached | pid files outside the worktree; a dashboard restart does not touch a running stack. |
|
||||
@@ -48,11 +48,11 @@ and `ccam update` (or any dashboard restart) leaves running stacks alone.
|
||||
## Invariants
|
||||
|
||||
1. **The runtime never writes `stage`, `status`, or `notes`** — only `slot` and
|
||||
`ports`. In CCAM `status=running` means *an agent is working*, not *a stack is
|
||||
`ports`. In AgentHub `status=running` means *an agent is working*, not *a stack is
|
||||
up*; conflating the two breaks `classifyLiveness`. Boot failures surface through
|
||||
`GET /runtime` and the hook log. Same reasoning as "the console never writes a
|
||||
lane's stage".
|
||||
2. **CCAM still does not orchestrate.** A1 adds *primitives* (`up`/`down`/`hook`)
|
||||
2. **AgentHub still does not orchestrate.** A1 adds *primitives* (`up`/`down`/`hook`)
|
||||
that a session calls. The dashboard chains nothing, evaluates no gate, retries nothing.
|
||||
3. **No shell command built as a string.** `execFile` + argv arrays everywhere.
|
||||
Hooks are the deliberate exception — they *are* scripts, spawned as
|
||||
@@ -89,7 +89,7 @@ No column records "is up". Deliberate.
|
||||
|---|---|
|
||||
| `server/lib/ports.js` | `isListening(port)` via a short-timeout `net.connect`; `listenerPids(port)` via `lsof`, falling back to `ss`, returning `[]` with a one-time warning when neither exists. |
|
||||
| `server/lib/lane-slots.js` | `allocateSlot()` — lowest free in `[1, LANE_MAX_SLOTS]` (default 9), called inside the existing `withLaneLock`, with the unique index as the backstop. `releaseSlot()` on `remove` only — **not** on `reset`, or a lane's ports and database move under a running session. `resolvePorts(slot, profile)` — prefers `PORT_BASE_<name> + slot`, then `+100`, `+200`… (the last digit stays the slot, so the number still reads as "lane 3"), up to 10 tries before `EPORTBUSY` naming the occupying pid. |
|
||||
| `server/lib/lane-profile.js` | `resolveProfile(lane)` — looks in `lane.cwd/.ccam/profile/` **first**, `lane.source_repo/.ccam/profile/` second, because the profile lives in the repo and a branch that edits its hooks must run its own. `runHook(lane, name, args, {onLine})` — fixed hook-name allowlist, `bash <hook>` with an argv array, Shipyard's env contract verbatim so its profiles port unchanged (`LANE` = the **slot**, `LANE_DIR`, `RUN_DIR`, `LOG_DIR`, `PROFILE_DIR`, `SOURCE_REPO`, `<NAME>_PORT` per declared port), `harness_spawn` exported as a bash function writing the same `$RUN_DIR/<name>.pid` + `$LOG_DIR/<name>.log`, the `GIT_*` scrub from `worktree.js:git()`, and output streamed both to the log file and to a new `lane_hook_output` websocket message. |
|
||||
| `server/lib/lane-profile.js` | `resolveProfile(lane)` — looks in `lane.cwd/.agenthub/profile/` **first**, `lane.source_repo/.agenthub/profile/` second, because the profile lives in the repo and a branch that edits its hooks must run its own. `runHook(lane, name, args, {onLine})` — fixed hook-name allowlist, `bash <hook>` with an argv array, Shipyard's env contract verbatim so its profiles port unchanged (`LANE` = the **slot**, `LANE_DIR`, `RUN_DIR`, `LOG_DIR`, `PROFILE_DIR`, `SOURCE_REPO`, `<NAME>_PORT` per declared port), `harness_spawn` exported as a bash function writing the same `$RUN_DIR/<name>.pid` + `$LOG_DIR/<name>.log`, the `GIT_*` scrub from `worktree.js:git()`, and output streamed both to the log file and to a new `lane_hook_output` websocket message. |
|
||||
| `server/lib/lane-runtime.js` | `upLane` (defensive down → mkdir run/log/`LANE_DIRS` → `boot` → `health`; no `bootstrap`, which belongs to provision/reset as in `lane-up.sh`), `downLane` (recursive pid-tree kill from the pid files — ported from `lane-down.sh:kill_tree`, which exists because detached uvicorn/celery prefork children were being missed — then a port-listener backstop; idempotent, sleep-free), `runtimeFacts` (**computed**: `kill -0` per pid file, `isListening` per port, plus `last-error.json`). |
|
||||
|
||||
State lives at `LANES_ROOT/.state/lane<slot>/{run,logs}/`, **outside** the
|
||||
@@ -123,7 +123,7 @@ argv, never joined.
|
||||
|
||||
| Condition | Code | Behaviour |
|
||||
|---|---|---|
|
||||
| No `.ccam/profile/` | `ENOPROFILE` | 400, naming both paths searched |
|
||||
| No `.agenthub/profile/` | `ENOPROFILE` | 400, naming both paths searched |
|
||||
| Unknown hook | `ENOHOOK` | 400 |
|
||||
| Hook exits non-zero | — | `lane_hook_output` + `last-error.json`; reflected by `/runtime` |
|
||||
| Every candidate port busy | `EPORTBUSY` | names the occupying pid |
|
||||
@@ -154,8 +154,8 @@ Covered:
|
||||
the allowlist returns 400 and spawns nothing.
|
||||
- **Env** — with `GIT_DIR` set on the parent, the hook does not see it.
|
||||
|
||||
Manual: create a lane on ccam-lanes itself, write a three-line profile,
|
||||
`ccam lane up`, open the port, restart the server, confirm the stack survived.
|
||||
Manual: create a lane on agenthub-lanes itself, write a three-line profile,
|
||||
`agenthub lane up`, open the port, restart the server, confirm the stack survived.
|
||||
|
||||
## Out of scope
|
||||
|
||||
|
||||
@@ -5,20 +5,20 @@
|
||||
|
||||
## Problem
|
||||
|
||||
Shipyard's `ship-feature/SKILL.md` (`~/MyDrive/Projects/ResearchAndDevelopment/AgentWorkflow/claude/skills/ship-feature/SKILL.md`, 208 lines, 16 stages) is the driving skill that runs a lane end-to-end: intake → TDD implement → gates → e2e → review → QC → senior gate → publish PR → watch → post-merge verify → done. CCAM has no equivalent yet. E (per the roadmap) is four independent pieces — this spec covers only the first: the pipeline template + the skill text itself. Agents (qc-local, senior-gate-reviewer, ticketer, dev-qc, pr-reviewer) and `ccam lanes sync-base` are separate, later tasks; F's integrations (tracker, dev-QC, CI-wait) are later still.
|
||||
Shipyard's `ship-feature/SKILL.md` (`~/MyDrive/Projects/ResearchAndDevelopment/AgentWorkflow/claude/skills/ship-feature/SKILL.md`, 208 lines, 16 stages) is the driving skill that runs a lane end-to-end: intake → TDD implement → gates → e2e → review → QC → senior gate → publish PR → watch → post-merge verify → done. AgentHub has no equivalent yet. E (per the roadmap) is four independent pieces — this spec covers only the first: the pipeline template + the skill text itself. Agents (qc-local, senior-gate-reviewer, ticketer, dev-qc, pr-reviewer) and `agenthub lanes sync-base` are separate, later tasks; F's integrations (tracker, dev-QC, CI-wait) are later still.
|
||||
|
||||
## Scope
|
||||
|
||||
**In scope:**
|
||||
1. A backend gap the skill text requires to be truthful: `ccam lanes up --qc`, backed by a new `QC_BOOT_ENV` profile declaration.
|
||||
1. A backend gap the skill text requires to be truthful: `agenthub lanes up --qc`, backed by a new `QC_BOOT_ENV` profile declaration.
|
||||
2. `server/data/pipelines/ship-feature.json` — the node/stage definitions the dashboard renders against.
|
||||
3. `.claude/skills/ship-feature-lane/SKILL.md` — the ported skill, every Shipyard `bin/…` call rewritten to its CCAM equivalent.
|
||||
3. `.claude/skills/ship-feature-lane/SKILL.md` — the ported skill, every Shipyard `bin/…` call rewritten to its AgentHub equivalent.
|
||||
|
||||
**Out of scope (later tasks, referenced by name in the ported skill but not built here):**
|
||||
- `ccam lanes sync-base` (the migration-collision + merge-driver command Stage 2/8/12 call).
|
||||
- `ccam lanes mcp sync` (F) — Stage 0's MCP preflight becomes a manual check + a message to the human, not an auto-sync call.
|
||||
- `ccam lanes integration <name>` / `.ccam/profile/integrations.env` (F) — tracker/dev_qc/ci_wait are **hardcoded off** in this port; Stage 9 (ticket) and the dev-QC half of Stage 13 unconditionally skip; Stage 10's CI watch unconditionally uses the `gh pr checks` fallback the original skill already documents for "helper not configured" (no conditional branch needed — there is nothing to branch on yet).
|
||||
- `ccam ci` (F, `ci-job.sh` port) — same reasoning; Stage 10/13's CI-triage logic that depends on it is dropped to the fallback path.
|
||||
- `agenthub lanes sync-base` (the migration-collision + merge-driver command Stage 2/8/12 call).
|
||||
- `agenthub lanes mcp sync` (F) — Stage 0's MCP preflight becomes a manual check + a message to the human, not an auto-sync call.
|
||||
- `agenthub lanes integration <name>` / `.agenthub/profile/integrations.env` (F) — tracker/dev_qc/ci_wait are **hardcoded off** in this port; Stage 9 (ticket) and the dev-QC half of Stage 13 unconditionally skip; Stage 10's CI watch unconditionally uses the `gh pr checks` fallback the original skill already documents for "helper not configured" (no conditional branch needed — there is nothing to branch on yet).
|
||||
- `agenthub ci` (F, `ci-job.sh` port) — same reasoning; Stage 10/13's CI-triage logic that depends on it is dropped to the fallback path.
|
||||
- The five agents — the skill references them by `subagent_type` at Stages 6/7/9/13/12(pr-reviewer callout), but none exist yet. This spec's own verification stops where an agent invocation would occur.
|
||||
|
||||
## Decisions
|
||||
@@ -26,26 +26,26 @@ Shipyard's `ship-feature/SKILL.md` (`~/MyDrive/Projects/ResearchAndDevelopment/A
|
||||
| | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| `--qc` boot flag | New `QC_BOOT_ENV` profile declaration (space-separated `KEY=value` pairs, same shape as Shipyard's), injected into the boot hook's environment only when `up` is called with `qc: true` | The skill calls `lane-up.sh "$N" --qc` in Stage 3 to get a deterministic (mocked/stubbed) stack for QC. Without a real flag this line in the ported skill would be false documentation — violates this repo's docs rule (executable, aligned with actual scripts). A profile that never declares `QC_BOOT_ENV` gets no extra env (dead code doesn't run), same pattern every other A2 declaration in `DEFAULTS` already follows. |
|
||||
| `state.sh "$N" init` (open a clean feature slot before Stage 0's Q&A) | Dropped — replaced by a plain `ccam feature activate <slug>` at Stage 1 | Task B's `activateFeature` already archives the outgoing active feature automatically on switch; Shipyard needed a separate `init` step because it had no DB-backed archive. Porting `init` as a distinct call would be redundant with what `activate` already does. |
|
||||
| Integration toggles (tracker/dev_qc/ci_wait) | Hardcoded off, not a runtime check | F doesn't exist yet. The alternative (a stub `ccam lanes integration` that always returns off) adds surface area for no behavioral difference today; the skill's own text already documents the "toggle off" behavior for each stage, so hardcoding is truthful, not a shortcut. When F ships, this becomes a follow-up edit to swap hardcoded skips for real checks — an explicit, later, reviewable diff. |
|
||||
| Hook names for `lane-ci-gate.sh` / `lane-e2e.sh` | `ccam lanes hook ci-gate` / `ccam lanes hook e2e` | Both names are **already** in `server/lib/lane-profile.js`'s `HOOKS` allowlist, added during A1 specifically "because... the driving skill needs that surface stable" — this spec is that driving skill. No backend change needed for these two. |
|
||||
| `.harness-lane` marker / `lane-assign.sh` | Dropped entirely | CCAM already resolves a lane from `cwd` (path-boundary prefix match, documented in this repo's CLAUDE.md) — the marker file and the assign step exist in Shipyard only because it has no such resolution. |
|
||||
| `@@HARNESS_ROOT@@` / `@@DEV_QC_MCP@@` / `@@TRACKER_MCP@@` placeholders | Dropped | Install-time templating that doesn't exist in CCAM; every reference becomes the literal CCAM command or, for the two MCP placeholders, is deleted along with the (out-of-scope) integrations that used them. |
|
||||
| `state.sh "$N" init` (open a clean feature slot before Stage 0's Q&A) | Dropped — replaced by a plain `agenthub feature activate <slug>` at Stage 1 | Task B's `activateFeature` already archives the outgoing active feature automatically on switch; Shipyard needed a separate `init` step because it had no DB-backed archive. Porting `init` as a distinct call would be redundant with what `activate` already does. |
|
||||
| Integration toggles (tracker/dev_qc/ci_wait) | Hardcoded off, not a runtime check | F doesn't exist yet. The alternative (a stub `agenthub lanes integration` that always returns off) adds surface area for no behavioral difference today; the skill's own text already documents the "toggle off" behavior for each stage, so hardcoding is truthful, not a shortcut. When F ships, this becomes a follow-up edit to swap hardcoded skips for real checks — an explicit, later, reviewable diff. |
|
||||
| Hook names for `lane-ci-gate.sh` / `lane-e2e.sh` | `agenthub lanes hook ci-gate` / `agenthub lanes hook e2e` | Both names are **already** in `server/lib/lane-profile.js`'s `HOOKS` allowlist, added during A1 specifically "because... the driving skill needs that surface stable" — this spec is that driving skill. No backend change needed for these two. |
|
||||
| `.harness-lane` marker / `lane-assign.sh` | Dropped entirely | AgentHub already resolves a lane from `cwd` (path-boundary prefix match, documented in this repo's CLAUDE.md) — the marker file and the assign step exist in Shipyard only because it has no such resolution. |
|
||||
| `@@HARNESS_ROOT@@` / `@@DEV_QC_MCP@@` / `@@TRACKER_MCP@@` placeholders | Dropped | Install-time templating that doesn't exist in AgentHub; every reference becomes the literal AgentHub command or, for the two MCP placeholders, is deleted along with the (out-of-scope) integrations that used them. |
|
||||
|
||||
## Command mapping
|
||||
|
||||
| Shipyard | CCAM | Status |
|
||||
| Shipyard | AgentHub | Status |
|
||||
|---|---|---|
|
||||
| `state.sh "$N" set stage=X status=Y` | `ccam stage X --status Y` | exists |
|
||||
| `state.sh "$N" activate feat/<slug>` | `ccam feature activate <slug>` | exists (Task B) |
|
||||
| `state.sh "$N" set stage=X status=Y` | `agenthub stage X --status Y` | exists |
|
||||
| `state.sh "$N" activate feat/<slug>` | `agenthub feature activate <slug>` | exists (Task B) |
|
||||
| `state.sh "$N" init` | (dropped — see Decisions) | n/a |
|
||||
| `lane-ci-gate.sh "$N"` | `ccam lanes hook ci-gate` | exists (hook allowlisted) |
|
||||
| `lane-up.sh "$N" --qc` | `ccam lanes up --qc` | **new in this task** |
|
||||
| `lane-e2e.sh "$N" [-- specs]` | `ccam lanes hook e2e [-- specs]` | exists (hook allowlisted) |
|
||||
| `lane-sync-dev.sh …` | `ccam lanes sync-base …` | later task (referenced, not built) |
|
||||
| `lane-mcp-sync.sh "$N"` | `ccam lanes mcp sync` | F (referenced as manual fallback) |
|
||||
| `lane-ci-gate.sh "$N"` | `agenthub lanes hook ci-gate` | exists (hook allowlisted) |
|
||||
| `lane-up.sh "$N" --qc` | `agenthub lanes up --qc` | **new in this task** |
|
||||
| `lane-e2e.sh "$N" [-- specs]` | `agenthub lanes hook e2e [-- specs]` | exists (hook allowlisted) |
|
||||
| `lane-sync-dev.sh …` | `agenthub lanes sync-base …` | later task (referenced, not built) |
|
||||
| `lane-mcp-sync.sh "$N"` | `agenthub lanes mcp sync` | F (referenced as manual fallback) |
|
||||
| `lane-env.sh "$N" --check X` | (hardcoded off) | F (deferred) |
|
||||
| `ci-job.sh status\|failures\|rerun\|cancel` | `ccam ci …` | F (deferred; skill uses its own `gh`-only fallback) |
|
||||
| `ci-job.sh status\|failures\|rerun\|cancel` | `agenthub ci …` | F (deferred; skill uses its own `gh`-only fallback) |
|
||||
|
||||
## Design
|
||||
|
||||
@@ -53,23 +53,23 @@ Shipyard's `ship-feature/SKILL.md` (`~/MyDrive/Projects/ResearchAndDevelopment/A
|
||||
|
||||
- `QC_BOOT_ENV` added to `server/lib/lane-profile.js`'s `DEFAULTS` (empty string default — off).
|
||||
- `POST /:id/up` accepts `{build, qc}` in its body (currently only reads `build`); `qc: true` is threaded through to whatever assembles the boot hook's environment (read the actual `upLane`/hook-invocation code path in `server/lib/lane-runtime.js` before implementing — this spec names the profile declaration and the route contract, not the exact internal plumbing function).
|
||||
- `ccam lanes up [--no-build] [--qc]` in `bin/ccam.js` — `--qc` sets `{qc: true}` in the POST body alongside the existing `--no-build` → `{build: false}` handling.
|
||||
- `agenthub lanes up [--no-build] [--qc]` in `bin/agenthub.js` — `--qc` sets `{qc: true}` in the POST body alongside the existing `--no-build` → `{build: false}` handling.
|
||||
- `QC_BOOT_ENV` format: space-separated `KEY=value` pairs, parsed the same way this file already parses `PORTS`/`LANE_DIRS` (space-separated lists) — not a new parsing convention.
|
||||
|
||||
### Pipeline template
|
||||
|
||||
`server/data/pipelines/ship-feature.json` — one node per skill stage (0 through 15, using the skill's own stage names: `intake`, `plan`, `implementing`, `gates`, `e2e-feature`, `e2e-feature-passed`, `review`, `qc-plan`, `qc`, `gate`, `publishing`, `pr-open`, `reported`, `watching-pr`, `merged`, `done`), same node-definition shape `default.json` already uses (label, `detect` rules matching the `stage=` values `ccam stage` sets). `default.json` untouched. A lane opts into this template via its `pipeline` field, same mechanism every other custom template already uses.
|
||||
`server/data/pipelines/ship-feature.json` — one node per skill stage (0 through 15, using the skill's own stage names: `intake`, `plan`, `implementing`, `gates`, `e2e-feature`, `e2e-feature-passed`, `review`, `qc-plan`, `qc`, `gate`, `publishing`, `pr-open`, `reported`, `watching-pr`, `merged`, `done`), same node-definition shape `default.json` already uses (label, `detect` rules matching the `stage=` values `agenthub stage` sets). `default.json` untouched. A lane opts into this template via its `pipeline` field, same mechanism every other custom template already uses.
|
||||
|
||||
### Skill
|
||||
|
||||
`.claude/skills/ship-feature-lane/SKILL.md` — the full port. Frontmatter, hard rules, and all 16 stages carried over with the Command mapping table's substitutions applied line-by-line. Every hard-won rule (turn-liveness, no-retry-cap/phase-clock, one-driver-per-MCP, never-push-base, the fix-loop's two fast-path exceptions, cross-lane etiquette) is kept verbatim in spirit, with `lane-lock.sh` etiquette re-pointed at D's `ccam lock` CLI and named locks.
|
||||
`.claude/skills/ship-feature-lane/SKILL.md` — the full port. Frontmatter, hard rules, and all 16 stages carried over with the Command mapping table's substitutions applied line-by-line. Every hard-won rule (turn-liveness, no-retry-cap/phase-clock, one-driver-per-MCP, never-push-base, the fix-loop's two fast-path exceptions, cross-lane etiquette) is kept verbatim in spirit, with `lane-lock.sh` etiquette re-pointed at D's `agenthub lock` CLI and named locks.
|
||||
|
||||
## Invariants
|
||||
|
||||
1. **CCAM still does not orchestrate.** The skill runs in the session and calls `ccam` commands one at a time; nothing in this task adds chaining, gate evaluation, or retry logic to the dashboard itself.
|
||||
1. **AgentHub still does not orchestrate.** The skill runs in the session and calls `agenthub` commands one at a time; nothing in this task adds chaining, gate evaluation, or retry logic to the dashboard itself.
|
||||
2. **Every command the skill text names must actually exist and behave as documented** — the docs-markdown rule (executable examples, aligned with actual scripts) applies to a skill file exactly as it applies to `docs/*.md`.
|
||||
3. **A profile that never declares `QC_BOOT_ENV` sees no behavior change** — `--qc` degrades to a plain boot when the declaration is absent, same as every other optional A2 declaration.
|
||||
|
||||
## Verify
|
||||
|
||||
A real dry run on a scratch repo with a profile, walking Stage 0 through Stage 2 (intake → plan → `ccam lanes hook ci-gate`) — the furthest the pipeline can go without the not-yet-built agents. Confirms every `ccam` command named in stages 0–2 exists and behaves as documented. `ccam lanes up --qc` gets its own unit/integration test verifying `QC_BOOT_ENV` reaches the boot hook's environment when declared, and is a no-op when absent. Full Stage 0→14 verification is the agents-port task's responsibility, once qc-local/senior-gate-reviewer exist.
|
||||
A real dry run on a scratch repo with a profile, walking Stage 0 through Stage 2 (intake → plan → `agenthub lanes hook ci-gate`) — the furthest the pipeline can go without the not-yet-built agents. Confirms every `agenthub` command named in stages 0–2 exists and behaves as documented. `agenthub lanes up --qc` gets its own unit/integration test verifying `QC_BOOT_ENV` reaches the boot hook's environment when declared, and is a no-op when absent. Full Stage 0→14 verification is the agents-port task's responsibility, once qc-local/senior-gate-reviewer exist.
|
||||
|
||||
@@ -12,39 +12,39 @@
|
||||
**In scope:**
|
||||
1. `server/data/agent-templates/ship-feature-lane/qc-local.md` — the ported browser-QC agent template.
|
||||
2. `server/data/agent-templates/ship-feature-lane/senior-gate-reviewer.md` — the ported publish-gate agent template.
|
||||
3. `ccam lanes agents install [<id>]` — copies both templates into `<lane>/.claude/agents/`, git-excluded.
|
||||
3. `agenthub lanes agents install [<id>]` — copies both templates into `<lane>/.claude/agents/`, git-excluded.
|
||||
4. `docs/LANES.md` + roadmap progress line.
|
||||
|
||||
**Out of scope (later, when their dependencies exist):**
|
||||
- **`ticketer.md`, `dev-qc.md`** — both are invoked only by Stages 9 and 13, which are hardcoded-skipped pending F's tracker/dev-QC integrations. Porting them now would ship agent files nothing calls.
|
||||
- **`pr-reviewer.md`** — not referenced anywhere in this repo's `SKILL.md` (the CCAM port dropped the reference during E1; confirmed by grep). Nothing to unblock.
|
||||
- **Per-lane embedded credentials** — Shipyard's installer injects a real QA account's email/password directly into `qc-local.md`'s prompt text, sourced from a per-lane seeded account (`.harness-qa.env`) that has no CCAM equivalent (no seed-account system, no matching `secrets.js` category). This spec ports the agent's *fallback* path (no embedded creds → report `FAIL` with a clear reason) rather than inventing a seed-account system to unblock a credentials feature nobody asked for yet.
|
||||
- **MCP auto-provisioning** (`ccam lanes mcp sync`) — F, not built. `qc-local.md` keeps referencing the `playwright-qa-local` MCP server name by convention; `SKILL.md`'s Stage 0 already documents the manual-`.mcp.json` fallback this depends on.
|
||||
- **`review-checks.md` reader command** — `senior-gate-reviewer.md` reads the profile's optional `review-checks.md` directly with the Read tool (a plain file, no CLI needed), not through a new `ccam lanes profile cat` command.
|
||||
- **`pr-reviewer.md`** — not referenced anywhere in this repo's `SKILL.md` (the AgentHub port dropped the reference during E1; confirmed by grep). Nothing to unblock.
|
||||
- **Per-lane embedded credentials** — Shipyard's installer injects a real QA account's email/password directly into `qc-local.md`'s prompt text, sourced from a per-lane seeded account (`.harness-qa.env`) that has no AgentHub equivalent (no seed-account system, no matching `secrets.js` category). This spec ports the agent's *fallback* path (no embedded creds → report `FAIL` with a clear reason) rather than inventing a seed-account system to unblock a credentials feature nobody asked for yet.
|
||||
- **MCP auto-provisioning** (`agenthub lanes mcp sync`) — F, not built. `qc-local.md` keeps referencing the `playwright-qa-local` MCP server name by convention; `SKILL.md`'s Stage 0 already documents the manual-`.mcp.json` fallback this depends on.
|
||||
- **`review-checks.md` reader command** — `senior-gate-reviewer.md` reads the profile's optional `review-checks.md` directly with the Read tool (a plain file, no CLI needed), not through a new `agenthub lanes profile cat` command.
|
||||
|
||||
## Decisions
|
||||
|
||||
| | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Template location | `server/data/agent-templates/ship-feature-lane/*.md` | Parallel to `server/data/pipelines/*.json` (E1's pattern) — data files, not source. **Not** `ccam-lanes/.claude/agents/`, which already holds this repo's own dev-facing agents (`backend-reviewer`, `frontend-reviewer`, `mcp-reviewer`); mixing a lane's runtime-installed agents into that directory would be a name-collision risk and conflate two different agent populations. |
|
||||
| Heartbeat calls (`state.sh set` between QC scenarios) | Dropped entirely | Shipyard dispatches these agents as a separate background-capable process, so a stalled multi-minute run needs an explicit touch to avoid a false "stalled" flag. CCAM's `qc-local`/`senior-gate-reviewer` run as a synchronous Agent-tool call inside the driving session's own turn — the turn is blocked on the subagent's return, so the session is already "alive" by definition for the whole run. No CCAM heartbeat primitive exists for a subagent to call anyway (this repo's own rule: only `ccam stage` writes progress, and these agents must never call it). |
|
||||
| Auto pre-seed login (`lane-qa-login.sh`, `lane-reset.sh`) | Dropped; `qc-local.md`'s login fallback becomes an honest failure, not a script call | CCAM has no seeded-QA-account system these scripts assumed. Calling a command that doesn't exist would be the same "docs must be executable" violation E1's Decisions table already ruled against for hardcoded-off integrations — this spec's version reports `LOCAL-QC: FAIL — login required, no seed-account mechanism configured` instead. |
|
||||
| `lane-sync-dev.sh --check` reference (senior-gate-reviewer's merge-safety check) | → `ccam lanes sync-base --check <branch>` | Direct 1:1 mapping — E2 already built and shipped this command with the exact contract (exit 5 collision, `DEV_DELTA`/`DEV_OVERLAP` output) the agent's text already describes. |
|
||||
| `@@HARNESS_ROOT@@` / `.harness-lane` | Dropped | Same reasoning as E1's skill port — CCAM resolves a lane from `cwd` directly. |
|
||||
| Install command semantics | `ccam lanes agents install [<id>]`, idempotent (overwrite-on-reinstall, not merge) | Matches the "explicit, never automatic" shape this repo already uses for `proof-link` and the E2 merge-driver setup — a session calls it when it wants updated templates, the dashboard never calls it for them. |
|
||||
| Template location | `server/data/agent-templates/ship-feature-lane/*.md` | Parallel to `server/data/pipelines/*.json` (E1's pattern) — data files, not source. **Not** `agenthub-lanes/.claude/agents/`, which already holds this repo's own dev-facing agents (`backend-reviewer`, `frontend-reviewer`, `mcp-reviewer`); mixing a lane's runtime-installed agents into that directory would be a name-collision risk and conflate two different agent populations. |
|
||||
| Heartbeat calls (`state.sh set` between QC scenarios) | Dropped entirely | Shipyard dispatches these agents as a separate background-capable process, so a stalled multi-minute run needs an explicit touch to avoid a false "stalled" flag. AgentHub's `qc-local`/`senior-gate-reviewer` run as a synchronous Agent-tool call inside the driving session's own turn — the turn is blocked on the subagent's return, so the session is already "alive" by definition for the whole run. No AgentHub heartbeat primitive exists for a subagent to call anyway (this repo's own rule: only `agenthub stage` writes progress, and these agents must never call it). |
|
||||
| Auto pre-seed login (`lane-qa-login.sh`, `lane-reset.sh`) | Dropped; `qc-local.md`'s login fallback becomes an honest failure, not a script call | AgentHub has no seeded-QA-account system these scripts assumed. Calling a command that doesn't exist would be the same "docs must be executable" violation E1's Decisions table already ruled against for hardcoded-off integrations — this spec's version reports `LOCAL-QC: FAIL — login required, no seed-account mechanism configured` instead. |
|
||||
| `lane-sync-dev.sh --check` reference (senior-gate-reviewer's merge-safety check) | → `agenthub lanes sync-base --check <branch>` | Direct 1:1 mapping — E2 already built and shipped this command with the exact contract (exit 5 collision, `DEV_DELTA`/`DEV_OVERLAP` output) the agent's text already describes. |
|
||||
| `@@HARNESS_ROOT@@` / `.harness-lane` | Dropped | Same reasoning as E1's skill port — AgentHub resolves a lane from `cwd` directly. |
|
||||
| Install command semantics | `agenthub lanes agents install [<id>]`, idempotent (overwrite-on-reinstall, not merge) | Matches the "explicit, never automatic" shape this repo already uses for `proof-link` and the E2 merge-driver setup — a session calls it when it wants updated templates, the dashboard never calls it for them. |
|
||||
| `.claude/agents/` git-exclusion | Appended to `<lane>/.git/info/exclude` (clone-local, not the tracked `.gitignore`) | Same mechanism `write_lane_markers` used in Shipyard and the same file this repo's proof-link machinery already touches — a lane-runtime concern never becomes a change to the app repo's own tracked ignore rules. |
|
||||
|
||||
## Command mapping
|
||||
|
||||
| Shipyard | CCAM | Status |
|
||||
| Shipyard | AgentHub | Status |
|
||||
|---|---|---|
|
||||
| `lane-agents-install.sh <N>` | `ccam lanes agents install [<id>]` | **new in this task** |
|
||||
| `lane-agents-install.sh <N>` | `agenthub lanes agents install [<id>]` | **new in this task** |
|
||||
| `state.sh "$N" set` (mid-run heartbeat) | dropped — see Decisions | n/a |
|
||||
| `lane-qa-login.sh "$N" local` | dropped — see Decisions | n/a |
|
||||
| `lane-reset.sh "$N"` (reseed on login failure) | dropped — see Decisions | n/a |
|
||||
| `lane-sync-dev.sh "$N" --check <branch>` (senior-gate-reviewer's merge-safety check) | `ccam lanes sync-base --check <branch>` | exists (E2) |
|
||||
| `lane-up.sh "$N" --no-build` (revive a down stack) | `ccam lanes up --no-build` | exists (E1 runtime, pre-existing flag) |
|
||||
| `profile-cat.sh review-checks.md` | Read tool on `<lane>/.ccam/profile/review-checks.md` directly | no command needed |
|
||||
| `lane-sync-dev.sh "$N" --check <branch>` (senior-gate-reviewer's merge-safety check) | `agenthub lanes sync-base --check <branch>` | exists (E2) |
|
||||
| `lane-up.sh "$N" --no-build` (revive a down stack) | `agenthub lanes up --no-build` | exists (E1 runtime, pre-existing flag) |
|
||||
| `profile-cat.sh review-checks.md` | Read tool on `<lane>/.agenthub/profile/review-checks.md` directly | no command needed |
|
||||
| `"$HARNESS/bin/lane-mcp-sync.sh"` (implied MCP setup) | manual `.mcp.json` — `SKILL.md` Stage 0 already documents this fallback | F (deferred) |
|
||||
|
||||
## Design
|
||||
@@ -54,17 +54,17 @@
|
||||
Both files are near-verbatim ports of the Shipyard source with the Command-mapping substitutions applied and the out-of-scope credential/heartbeat/login sections rewritten per the Decisions table:
|
||||
|
||||
- **`qc-local.md`** — frontmatter (`name`, `description`, `model: opus`, `color: green`) unchanged in shape. Body: drops the heartbeat loop and the `@@HARNESS_ROOT@@`/`.harness-lane` setup; the "Account / login" section keeps the "if a credentials block is embedded at the top of this agent, use it" path (future-proofed for when a seed-account system exists) but replaces the auto-login-script fallback with an honest `FAIL`. Proof convention (`proof/<feature-slug>/qc-local/<NN>-<what>.png`) is unchanged — it already matches this repo's proof gallery (C) verbatim.
|
||||
- **`senior-gate-reviewer.md`** — frontmatter unchanged (`model: opus`, `color: red`). Body: the merge-safety check's command becomes `ccam lanes sync-base --check <branch>`; the stack-specific-checks step reads `review-checks.md` with the Read tool if present, otherwise falls back to "derive the equivalents yourself" exactly as the original already instructs when that file is empty. Nothing else changes — this agent never touched credentials or heartbeats to begin with.
|
||||
- **`senior-gate-reviewer.md`** — frontmatter unchanged (`model: opus`, `color: red`). Body: the merge-safety check's command becomes `agenthub lanes sync-base --check <branch>`; the stack-specific-checks step reads `review-checks.md` with the Read tool if present, otherwise falls back to "derive the equivalents yourself" exactly as the original already instructs when that file is empty. Nothing else changes — this agent never touched credentials or heartbeats to begin with.
|
||||
|
||||
### `ccam lanes agents install`
|
||||
### `agenthub lanes agents install`
|
||||
|
||||
- `POST /api/lanes/:id/agents/install` — reads both templates from `server/data/agent-templates/ship-feature-lane/`, writes them to `<lane>/.claude/agents/{qc-local.md,senior-gate-reviewer.md}` (creating the directory if needed), appends `.claude/agents/` to `<lane>/.git/info/exclude` if not already present (same idempotent read-existing-then-append pattern E2's `setupMergeDriver` already established for `.git/info/attributes`). Returns `{installed: ["qc-local.md", "senior-gate-reviewer.md"]}`.
|
||||
- No templating/placeholder substitution needed at install time — both files are already CCAM-final text (all substitutions happened once, at port time, into the template files themselves). This is a deliberate simplification from Shipyard's Python templating step, which existed only to inject per-lane credentials and MCP names this scope doesn't have.
|
||||
- CLI: `ccam lanes agents install [<id>]`, same `resolveLaneArg` pattern as `lanes proof-link`.
|
||||
- No templating/placeholder substitution needed at install time — both files are already AgentHub-final text (all substitutions happened once, at port time, into the template files themselves). This is a deliberate simplification from Shipyard's Python templating step, which existed only to inject per-lane credentials and MCP names this scope doesn't have.
|
||||
- CLI: `agenthub lanes agents install [<id>]`, same `resolveLaneArg` pattern as `lanes proof-link`.
|
||||
|
||||
## Invariants
|
||||
|
||||
1. **CCAM still does not orchestrate.** `agents install` copies static files and returns; nothing about it triggers, gates, or sequences pipeline stages.
|
||||
1. **AgentHub still does not orchestrate.** `agents install` copies static files and returns; nothing about it triggers, gates, or sequences pipeline stages.
|
||||
2. **Neither agent ever writes `stage`/`status`/`notes`.** Both already say so explicitly in their own prompt text (ported verbatim); this spec doesn't change that boundary.
|
||||
3. **A lane that never runs `agents install` sees no behavior change** — Stage 6/7 remain `blocked` exactly as today until a session explicitly installs the agents, same "explicit, never automatic" shape as `proof-link`.
|
||||
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
# F3a — `ccam lanes integration <name>` toggle reader
|
||||
# F3a — `agenthub lanes integration <name>` toggle reader
|
||||
|
||||
**Status:** approved 2026-08-05. Roadmap it belongs to:
|
||||
`docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md` (subsystem **F**).
|
||||
|
||||
## Problem
|
||||
|
||||
`SKILL.md`'s Setup section hardcodes tracker/dev-QC/CI-wait as permanently off ("treat all three as permanently off for this run") because F's `integrations.env` toggle system doesn't exist in CCAM yet. Shipyard's source, `lane-env.sh --check NAME`, is the reference primitive: read one `<NAME>_ENABLED` flag from a profile's `integrations.env`, exit 0/1.
|
||||
`SKILL.md`'s Setup section hardcodes tracker/dev-QC/CI-wait as permanently off ("treat all three as permanently off for this run") because F's `integrations.env` toggle system doesn't exist in AgentHub yet. Shipyard's source, `lane-env.sh --check NAME`, is the reference primitive: read one `<NAME>_ENABLED` flag from a profile's `integrations.env`, exit 0/1.
|
||||
|
||||
This is a narrow slice of F, not all of F3. `ticketer` (the agent that would actually consume `TRACKER_ENABLED`) is explicitly deferred — no tracker MCP is configured for any lane yet, and CCAM has no per-lane credential source (same gap E3 already documented for `qc-local`). This task only builds the toggle-reading primitive itself, so `SKILL.md`'s hardcoded-off text can become a real check instead of a permanent assumption, once something exists to check.
|
||||
This is a narrow slice of F, not all of F3. `ticketer` (the agent that would actually consume `TRACKER_ENABLED`) is explicitly deferred — no tracker MCP is configured for any lane yet, and AgentHub has no per-lane credential source (same gap E3 already documented for `qc-local`). This task only builds the toggle-reading primitive itself, so `SKILL.md`'s hardcoded-off text can become a real check instead of a permanent assumption, once something exists to check.
|
||||
|
||||
## Scope
|
||||
|
||||
**In scope:**
|
||||
1. `<repo>/.ccam/profile/integrations.env` — a new, optional profile file (same directory as `profile.env`), parsed with the EXISTING `parseEnvFile` helper in `server/lib/lane-profile.js` (comments/quotes/`export` already handled — no new parser).
|
||||
1. `<repo>/.agenthub/profile/integrations.env` — a new, optional profile file (same directory as `profile.env`), parsed with the EXISTING `parseEnvFile` helper in `server/lib/lane-profile.js` (comments/quotes/`export` already handled — no new parser).
|
||||
2. `isIntegrationEnabled(lane, name)` in `server/lib/lane-profile.js` — reads `integrations.env` (falling back `lane.cwd` → `lane.source_repo`, same two-location search `resolveProfile` already does for `profile.env`), returns `true` iff `<NAME_UPPER>_ENABLED === "1"`. A missing file or missing key is `false` — off by default, consistent with A2's declarations.
|
||||
3. `GET /api/lanes/:id/integrations/:name` route — `{enabled: boolean}`.
|
||||
4. `ccam lanes integration <name> [<id>]` CLI — exit `0` when enabled, `1` when disabled (the exit-code contract Shipyard's `--check` already established, and what a skill's `&&`-gated stage-skip logic needs).
|
||||
4. `agenthub lanes integration <name> [<id>]` CLI — exit `0` when enabled, `1` when disabled (the exit-code contract Shipyard's `--check` already established, and what a skill's `&&`-gated stage-skip logic needs).
|
||||
|
||||
**Explicitly out of scope:**
|
||||
- `ticketer.md` / `dev-qc.md` agents — deferred pending a tracker/dev-QC MCP and credential source.
|
||||
- Every other field `integrations.env` can declare (`TRACKER_URL`, `TRACKER_PROJECT`, `DEV_SITE_URL`, `CI_REPO`, etc.) — only the `<NAME>_ENABLED` flags matter for this primitive; the rest is read by whatever later task actually consumes a specific integration (F3/F4's real agent work), not by this generic toggle check.
|
||||
- Surfacing toggle state in `GET /api/lanes/:id/runtime` — that endpoint is polled and broadcast on every hook; adding fields nothing consumes yet is premature. `GET /:id/integrations/:name` is its own lightweight endpoint instead.
|
||||
- `lane-env.sh`'s default (no `--check`) mode — dumping the full per-lane runtime env as `export` lines exists in Shipyard because bash/zsh sourcing has a HARNESS_ROOT resolution footgun CCAM's cwd-based lane resolution doesn't have. Nothing in CCAM needs this.
|
||||
- `lane-env.sh`'s default (no `--check`) mode — dumping the full per-lane runtime env as `export` lines exists in Shipyard because bash/zsh sourcing has a HARNESS_ROOT resolution footgun AgentHub's cwd-based lane resolution doesn't have. Nothing in AgentHub needs this.
|
||||
|
||||
## Decisions
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# F3c — `ccam lanes gc`: orphan MCP reap + log capping
|
||||
# F3c — `agenthub lanes gc`: orphan MCP reap + log capping
|
||||
|
||||
**Status:** approved 2026-08-05. Roadmap it belongs to: E (found auditing E1-F3b for Shipyard parity).
|
||||
|
||||
## Problem
|
||||
|
||||
Shipyard's `lane-gc.sh` does 5 things. Two match CCAM's actual architecture; three don't (see Decisions). No CCAM equivalent exists for either of the two that do.
|
||||
Shipyard's `lane-gc.sh` does 5 things. Two match AgentHub's actual architecture; three don't (see Decisions). No AgentHub equivalent exists for either of the two that do.
|
||||
|
||||
## Scope
|
||||
|
||||
@@ -13,15 +13,15 @@ Shipyard's `lane-gc.sh` does 5 things. Two match CCAM's actual architecture; thr
|
||||
2. **Cap oversized hook logs.** `LANES_ROOT/.state/lane<slot>/logs/*.log` (written by `runHook`) grow unbounded across many boots/hooks; cap anything over 10MB to its last 2MB, in place (write-then-replace, not a truncate-while-open, so a concurrent writer's fd stays valid).
|
||||
|
||||
**Explicitly out of scope (architecture mismatch, not an oversight):**
|
||||
- **Auto-removing stale worktrees by age.** This is the piece that actually conflicts with a hard CCAM rule: "Never `rm -rf` a lane... destructive lane operations go through `worktree.js`/`lanes.js`, never a raw filesystem delete." An age-based automatic worktree removal is exactly the kind of automatic destructive action this repo's own CLAUDE.md forbids. `ccam lanes reset|remove|purge` already exist for a human to call explicitly.
|
||||
- **Feature-state archiving.** Shipyard's version moves flat JSON state files to an archive dir. CCAM's lane/feature state lives in the SQLite DB (`lane_features` table, from subsystem B) — there's no flat-file equivalent to archive.
|
||||
- **Scratch-debris sweep** (`scratch-lane*.log`, `manual-*.png`, `state.bak.*`). These are artifacts of Shipyard's own scripts (`lane-add.sh` writing `scratch-lane*.log`, manual QA screenshots named that way, `state.sh`'s own backup rotation). CCAM doesn't generate any of these — nothing to sweep.
|
||||
- **Auto-removing stale worktrees by age.** This is the piece that actually conflicts with a hard AgentHub rule: "Never `rm -rf` a lane... destructive lane operations go through `worktree.js`/`lanes.js`, never a raw filesystem delete." An age-based automatic worktree removal is exactly the kind of automatic destructive action this repo's own CLAUDE.md forbids. `agenthub lanes reset|remove|purge` already exist for a human to call explicitly.
|
||||
- **Feature-state archiving.** Shipyard's version moves flat JSON state files to an archive dir. AgentHub's lane/feature state lives in the SQLite DB (`lane_features` table, from subsystem B) — there's no flat-file equivalent to archive.
|
||||
- **Scratch-debris sweep** (`scratch-lane*.log`, `manual-*.png`, `state.bak.*`). These are artifacts of Shipyard's own scripts (`lane-add.sh` writing `scratch-lane*.log`, manual QA screenshots named that way, `state.sh`'s own backup rotation). AgentHub doesn't generate any of these — nothing to sweep.
|
||||
|
||||
## Design
|
||||
|
||||
`ccam lanes gc [--dry-run]` — no lane argument; this scans the whole machine's CCAM footprint, not one lane. Pure local operation (process listing + file I/O), no HTTP route — same shape as `ccam skills install`.
|
||||
`agenthub lanes gc [--dry-run]` — no lane argument; this scans the whole machine's AgentHub footprint, not one lane. Pure local operation (process listing + file I/O), no HTTP route — same shape as `agenthub skills install`.
|
||||
|
||||
- **Orphan reap:** `pgrep -f "<LANES_ROOT>.*\.playwright-mcp"` to find candidate pids (scoped to CCAM's own `LANES_ROOT`, same safety scoping Shipyard's own pattern used — an adopted lane's `cwd` living outside `LANES_ROOT` is a known gap, same one Shipyard's own numbered-lane-dir pattern had). For each match, `ps -o ppid= -p <pid>`; `ppid === 1` → orphaned, kill its process tree (`pgrep -P` recursively, then `SIGKILL` each, leaves not children first — same shape as Shipyard's `kill_tree`).
|
||||
- **Orphan reap:** `pgrep -f "<LANES_ROOT>.*\.playwright-mcp"` to find candidate pids (scoped to AgentHub's own `LANES_ROOT`, same safety scoping Shipyard's own pattern used — an adopted lane's `cwd` living outside `LANES_ROOT` is a known gap, same one Shipyard's own numbered-lane-dir pattern had). For each match, `ps -o ppid= -p <pid>`; `ppid === 1` → orphaned, kill its process tree (`pgrep -P` recursively, then `SIGKILL` each, leaves not children first — same shape as Shipyard's `kill_tree`).
|
||||
- **Log cap:** walk `LANES_ROOT/.state/lane*/logs/*.log`; for any file over 10MB, read its last 2MB and rewrite the file in place.
|
||||
- `--dry-run` prints what would happen without doing it (same flag Shipyard's version has).
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# F1 — `ccam lanes mcp sync`
|
||||
# F1 — `agenthub lanes mcp sync`
|
||||
|
||||
**Status:** approved 2026-08-05. Roadmap it belongs to:
|
||||
`docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md` (subsystem **F**).
|
||||
@@ -13,12 +13,12 @@
|
||||
1. `server/lib/lane-mcp.js` — reads the lane's `source_repo`'s already-configured MCP servers from `~/.claude.json`, relocates any absolute path under `source_repo` to the lane's own `cwd`, pins a `playwright`-family server's `--output-dir` to the lane's `.playwright-mcp`, writes `<lane.cwd>/.mcp.json`.
|
||||
2. Seeding the lane's `.playwright-mcp/profiles/*` Chromium profile directories from the source repo's own (preserves saved MCP browser logins — a real QA-account login only needs to happen once per machine, not once per lane).
|
||||
3. `.mcp.json` added to the lane's local `.git/info/exclude`.
|
||||
4. `ccam lanes mcp sync [<id>]` CLI + `POST /api/lanes/:id/mcp/sync` route.
|
||||
4. `agenthub lanes mcp sync [<id>]` CLI + `POST /api/lanes/:id/mcp/sync` route.
|
||||
5. `SKILL.md` Setup section edit: replace the manual-fallback text with the real command.
|
||||
6. `docs/LANES.md` + roadmap progress line.
|
||||
|
||||
**Deliberately out of scope (deviates from Shipyard, by explicit decision — not an oversight):**
|
||||
- **Permission/auto-approval writing.** Shipyard's script also writes broad `allow` rules into `<lane>/.claude/settings.local.json` — `enableAllProjectMcpServers`, an `mcp__<server>` rule per synced server, a `gh pr comment/review/create/edit/view/diff/list/checks/status` + `git push` allow-list, and an `autoMode.allow` bypass-style rule that names the `ticketer`/`pr-reviewer` sub-agents specifically. CCAM has no existing pattern anywhere of a backend process writing permission grants on a session's behalf — every other primitive in this codebase (`proof-link`, `agents install`, `sync-base`) is explicit-and-inert until a session calls it, never a permission change. `pr-reviewer` isn't ported (no driving skill exists for it — confirmed during F1's own scoping conversation) and `ticketer` isn't built yet (F3), so the auto-mode rule would reference agents that don't exist. A human approving `gh pr comment`/`git push` through the normal permission prompt, same as any other command, is the correct default until a real need for automation is demonstrated.
|
||||
- **Permission/auto-approval writing.** Shipyard's script also writes broad `allow` rules into `<lane>/.claude/settings.local.json` — `enableAllProjectMcpServers`, an `mcp__<server>` rule per synced server, a `gh pr comment/review/create/edit/view/diff/list/checks/status` + `git push` allow-list, and an `autoMode.allow` bypass-style rule that names the `ticketer`/`pr-reviewer` sub-agents specifically. AgentHub has no existing pattern anywhere of a backend process writing permission grants on a session's behalf — every other primitive in this codebase (`proof-link`, `agents install`, `sync-base`) is explicit-and-inert until a session calls it, never a permission change. `pr-reviewer` isn't ported (no driving skill exists for it — confirmed during F1's own scoping conversation) and `ticketer` isn't built yet (F3), so the auto-mode rule would reference agents that don't exist. A human approving `gh pr comment`/`git push` through the normal permission prompt, same as any other command, is the correct default until a real need for automation is demonstrated.
|
||||
- **The `mcp-node-exec.sh` node-version wrapper.** Shipyard's script routes `npx`/`node`-based MCP servers through a wrapper that resolves the newest Node ≥ 18 at spawn time, working around an nvm/PATH resolution bug specific to that environment. Not a problem this repo has evidence of; add it later if it actually bites someone (YAGNI).
|
||||
- **Merging with an existing `.mcp.json`.** This is a full overwrite from the source repo's current MCP config, same "reinstall, not merge" shape E3's `agents install` already established — not a per-server incremental sync.
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
| Missing config | Throws `ENOMCPCONFIG` with a clear message pointing at `SKILL.md`'s Setup section, not a silent no-op | A lane with zero MCP servers synced would silently break Stage 3/6 later, at a much less debuggable point. Fail loud, fail early, matching `requireProfile`'s existing shape for a missing profile. |
|
||||
| Overwrite semantics | Full overwrite of `.mcp.json` on every call, profiles seeded only if the destination directory doesn't already exist | Matches E3's "reinstall, not merge" for `.mcp.json` (config should always reflect the source repo's current state) but "never clobber a working login" for profiles (an existing profile dir means a session already logged in there — copying over it would lose that). |
|
||||
| `--output-dir` pinning | Only added to a server whose args reference `@playwright/mcp` and don't already declare `--output-dir` | Direct port of Shipyard's own conditional — this is what makes proof screenshots land in the lane's own canonical `.playwright-mcp/proof` (the same path the proof gallery, C, already reads) instead of wherever the server's cwd happens to be. |
|
||||
| Permission/auto-approval writes | Dropped entirely (see Scope) | No existing CCAM pattern for a backend command granting permissions; the two agents the original rules named aren't both in scope (`pr-reviewer` isn't ported at all, `ticketer` is a later task). |
|
||||
| Permission/auto-approval writes | Dropped entirely (see Scope) | No existing AgentHub pattern for a backend command granting permissions; the two agents the original rules named aren't both in scope (`pr-reviewer` isn't ported at all, `ticketer` is a later task). |
|
||||
|
||||
## Command mapping
|
||||
|
||||
| Shipyard | CCAM | Status |
|
||||
| Shipyard | AgentHub | Status |
|
||||
|---|---|---|
|
||||
| `lane-mcp-sync.sh <N>` | `ccam lanes mcp sync [<id>]` | **new in this task** |
|
||||
| `ensure_proof_link "$DIR"` (called at the top of `sync_lane`) | not called — proof-link is `ccam lanes proof-link`, a separate explicit primitive this repo already has; mcp-sync doesn't need to chain into it | n/a (already exists, not this task's job to invoke) |
|
||||
| `lane-mcp-sync.sh <N>` | `agenthub lanes mcp sync [<id>]` | **new in this task** |
|
||||
| `ensure_proof_link "$DIR"` (called at the top of `sync_lane`) | not called — proof-link is `agenthub lanes proof-link`, a separate explicit primitive this repo already has; mcp-sync doesn't need to chain into it | n/a (already exists, not this task's job to invoke) |
|
||||
| Chromium profile seeding | ported directly, same logic | **new in this task** |
|
||||
| `.mcp.json` write + relocate + `--output-dir` pin | ported directly, same logic | **new in this task** |
|
||||
| `settings.local.json` permission/autoMode writes | dropped | n/a (see Scope) |
|
||||
@@ -55,11 +55,11 @@
|
||||
|
||||
### Route + CLI
|
||||
|
||||
`POST /api/lanes/:id/mcp/sync` — synchronous (JSON parse + a few file copies, not a long-running process), 200 with the `syncMcp` result, 400 `ENOMCPCONFIG` when the source repo has no MCP servers configured. `ccam lanes mcp sync [<id>]` — same `resolveLaneArg` pattern as `lanes agents install`.
|
||||
`POST /api/lanes/:id/mcp/sync` — synchronous (JSON parse + a few file copies, not a long-running process), 200 with the `syncMcp` result, 400 `ENOMCPCONFIG` when the source repo has no MCP servers configured. `agenthub lanes mcp sync [<id>]` — same `resolveLaneArg` pattern as `lanes agents install`.
|
||||
|
||||
## Invariants
|
||||
|
||||
1. **CCAM still does not orchestrate; this doesn't add a new automation surface either.** `mcp sync` copies static config and files; it never grants a permission, launches a process, or triggers anything.
|
||||
1. **AgentHub still does not orchestrate; this doesn't add a new automation surface either.** `mcp sync` copies static config and files; it never grants a permission, launches a process, or triggers anything.
|
||||
2. **Never a silent partial state.** A lane with no source-repo MCP config gets a clear error, not an empty-but-present `.mcp.json`.
|
||||
3. **A lane's existing browser-profile login is never overwritten**, only ever seeded once.
|
||||
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
# F3b — `ccam skills install`: making `/ship-feature-lane` actually invokable
|
||||
# F3b — `agenthub skills install`: making `/ship-feature-lane` actually invokable
|
||||
|
||||
**Status:** approved 2026-08-05. Roadmap it belongs to:
|
||||
`docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md` (subsystem **E**, a gap found auditing E1-F3a for Shipyard parity).
|
||||
|
||||
## Problem
|
||||
|
||||
`.claude/skills/ship-feature-lane/SKILL.md`'s own description says "Invoke inside a lane's working directory" — but Claude Code only auto-discovers a `.claude/skills/` directory when the session's cwd is inside the repo that OWNS that directory. The skill lives only at `ccam-lanes/.claude/skills/ship-feature-lane/SKILL.md`; confirmed empty: `~/.claude/skills/` has no `ship-feature-lane` entry. Every piece built across E1/E2/E3/F1/F3a is unreachable from its actual intended invocation context — a session inside some OTHER lane's repo, not inside ccam-lanes itself.
|
||||
`.claude/skills/ship-feature-lane/SKILL.md`'s own description says "Invoke inside a lane's working directory" — but Claude Code only auto-discovers a `.claude/skills/` directory when the session's cwd is inside the repo that OWNS that directory. The skill lives only at `agenthub-lanes/.claude/skills/ship-feature-lane/SKILL.md`; confirmed empty: `~/.claude/skills/` has no `ship-feature-lane` entry. Every piece built across E1/E2/E3/F1/F3a is unreachable from its actual intended invocation context — a session inside some OTHER lane's repo, not inside agenthub-lanes itself.
|
||||
|
||||
Shipyard's own `install-claude-assets.sh` solves exactly this: it copies bundled skills into `~/.claude/skills/` (user-level, global) so every project's session sees them.
|
||||
|
||||
## Scope
|
||||
|
||||
**In scope:**
|
||||
- `ccam skills install` — copies `<ccam-lanes repo>/.claude/skills/ship-feature-lane/` to `~/.claude/skills/ship-feature-lane/`, overwriting on reinstall (same "reinstall, not merge" shape as `ccam lanes agents install`).
|
||||
- `agenthub skills install` — copies `<agenthub-lanes repo>/.claude/skills/ship-feature-lane/` to `~/.claude/skills/ship-feature-lane/`, overwriting on reinstall (same "reinstall, not merge" shape as `agenthub lanes agents install`).
|
||||
- `docs/LANES.md` note.
|
||||
|
||||
**Out of scope:**
|
||||
- Changing `ccam lanes agents install`'s existing per-lane `.claude/agents/` mechanism. That one's already reachable correctly (Claude Code discovers `.claude/agents/` from the lane's own cwd, which IS where a driving session runs) — this gap is specific to the SKILL, not the agents. No architecture change needed there.
|
||||
- Changing `agenthub lanes agents install`'s existing per-lane `.claude/agents/` mechanism. That one's already reachable correctly (Claude Code discovers `.claude/agents/` from the lane's own cwd, which IS where a driving session runs) — this gap is specific to the SKILL, not the agents. No architecture change needed there.
|
||||
- Auto-running this from `npm run setup` or any other command. Explicit, like every other install primitive in this codebase (`lanes agents install`, `lanes mcp sync`, `lanes proof-link`) — a human runs it once per machine.
|
||||
- Any other bundled skill Shipyard ships (`/review-prs`, `/harness-adapt`) — CCAM has no equivalent of either; out of scope by the same reasoning `pr-reviewer` was dropped (no driving skill to attach to).
|
||||
- Any other bundled skill Shipyard ships (`/review-prs`, `/harness-adapt`) — AgentHub has no equivalent of either; out of scope by the same reasoning `pr-reviewer` was dropped (no driving skill to attach to).
|
||||
|
||||
## Design
|
||||
|
||||
`bin/ccam.js` already has `REPO_ROOT` (`path.resolve(path.dirname(fs.realpathSync(__filename)), "..")`) — the exact self-location primitive Shipyard needed `HARNESS_ROOT` for, already present because `ccam` is npm-linked. No new resolution logic needed.
|
||||
`bin/agenthub.js` already has `REPO_ROOT` (`path.resolve(path.dirname(fs.realpathSync(__filename)), "..")`) — the exact self-location primitive Shipyard needed `HARNESS_ROOT` for, already present because `agenthub` is npm-linked. No new resolution logic needed.
|
||||
|
||||
- `cmdSkillsInstall()` in `bin/ccam.js` (no HTTP round-trip — this has nothing to do with any lane or the dashboard's DB, purely a local file copy from the CLI's own checkout to the user's home directory; matches the existing local-only pattern `cmdLanesProfileInit` already uses via `REPO_ROOT`).
|
||||
- `cmdSkillsInstall()` in `bin/agenthub.js` (no HTTP round-trip — this has nothing to do with any lane or the dashboard's DB, purely a local file copy from the CLI's own checkout to the user's home directory; matches the existing local-only pattern `cmdLanesProfileInit` already uses via `REPO_ROOT`).
|
||||
- Copies `path.join(REPO_ROOT, ".claude", "skills", "ship-feature-lane")` → `path.join(os.homedir(), ".claude", "skills", "ship-feature-lane")` via `fs.cpSync(..., {recursive: true, force: true})` (force: true = overwrite existing files, matching reinstall semantics).
|
||||
- Command: `ccam skills install`, no lane argument (nothing lane-specific here).
|
||||
- Command: `agenthub skills install`, no lane argument (nothing lane-specific here).
|
||||
|
||||
## Verify
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# E2 — `ccam lanes sync-base`
|
||||
# E2 — `agenthub lanes sync-base`
|
||||
|
||||
**Status:** approved 2026-08-05. Roadmap it belongs to:
|
||||
`docs/superpowers/plans/2026-08-03-shipyard-parity-lanes.md` (subsystem **E**).
|
||||
|
||||
## Problem
|
||||
|
||||
`.claude/skills/ship-feature-lane/SKILL.md` (ported in E1) names `ccam lanes sync-base` at Stages 2, 8 and 12 by its intended contract, but the command doesn't exist — Stage 2 hardcodes "preflight unavailable, skipped" and Stage 12 falls back to a bare `git merge`. Shipyard's source, `lane-sync-dev.sh` (`~/MyDrive/Projects/ResearchAndDevelopment/AgentWorkflow/bin/lane-sync-dev.sh`, 173 lines, plus its test `tests/test_sync_dev.sh`), is the reference: the ONE sanctioned merge in the pipeline (`origin/development` INTO the feature branch), with a migration-number collision preflight and a keep-ours merge driver for generated files. E (per the roadmap) has three pieces left after E1: agents, sync-base, F's integrations. This spec covers sync-base only.
|
||||
`.claude/skills/ship-feature-lane/SKILL.md` (ported in E1) names `agenthub lanes sync-base` at Stages 2, 8 and 12 by its intended contract, but the command doesn't exist — Stage 2 hardcodes "preflight unavailable, skipped" and Stage 12 falls back to a bare `git merge`. Shipyard's source, `lane-sync-dev.sh` (`~/MyDrive/Projects/ResearchAndDevelopment/AgentWorkflow/bin/lane-sync-dev.sh`, 173 lines, plus its test `tests/test_sync_dev.sh`), is the reference: the ONE sanctioned merge in the pipeline (`origin/development` INTO the feature branch), with a migration-number collision preflight and a keep-ours merge driver for generated files. E (per the roadmap) has three pieces left after E1: agents, sync-base, F's integrations. This spec covers sync-base only.
|
||||
|
||||
## Scope
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
1. Two new profile declarations: `MIGRATIONS_DIR`, `GENERATED_MERGE_PATHS` (empty = off, same `DEFAULTS` pattern as every other optional A2 declaration).
|
||||
2. `server/lib/lane-sync.js` — the git-only core: collision check, dev-delta report, merge, continue-after-conflict.
|
||||
3. `POST /api/lanes/:id/sync-base` route.
|
||||
4. `ccam lanes sync-base [--check|--continue] [branch] [<id>]` CLI subcommand.
|
||||
5. `.claude/skills/ship-feature-lane/SKILL.md` — Stage 2/8/12 edits: drop the "if it exists yet" conditionals, add the exit-4/`--continue` mechanics (currently absent from the CCAM port; Shipyard's original `ship-feature/SKILL.md` Stage 12 has the text to carry over).
|
||||
4. `agenthub lanes sync-base [--check|--continue] [branch] [<id>]` CLI subcommand.
|
||||
5. `.claude/skills/ship-feature-lane/SKILL.md` — Stage 2/8/12 edits: drop the "if it exists yet" conditionals, add the exit-4/`--continue` mechanics (currently absent from the AgentHub port; Shipyard's original `ship-feature/SKILL.md` Stage 12 has the text to carry over).
|
||||
6. `docs/LANES.md` + roadmap progress line.
|
||||
|
||||
**Out of scope:**
|
||||
@@ -27,19 +27,19 @@
|
||||
| | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Route timing model | Synchronous — the route runs the git operation and returns JSON directly, no `202` + websocket broadcast | Unlike `ci-gate`/`e2e` (test suites, minutes), a fetch + collision-check + merge is a few seconds of git work. Matches the existing `GET /:id/git` pattern (working-copy facts, synchronous), not the `hook` pattern. |
|
||||
| Merge-driver setup timing | Lazy, inside `mergeSync` itself, idempotent every call | Matches this repo's own established precedent: proof-link is "idempotent, never automatic" rather than a provision-time step. No other CCAM lane-creation path (`addWorktree`/`provisionLane`) currently touches git config or `.git/info/*`; adding one there for this alone would be a new concern in an unrelated file for a need only sync-base has. |
|
||||
| Stage/status writes | None. `sync-base` returns a structured result (`code`, `collisions`, `devDelta`, `overlap`, `conflictedFiles`); it never calls anything that sets a lane's `stage`, `status`, or `notes` | This repo's own rule: "declared stages come from `ccam stage`... The console never writes a lane's stage" and "the runtime never writes stage, status or notes." Shipyard's `state.sh set` heartbeat/notes calls inside `lane-sync-dev.sh` are dropped for the same reason `--qc`'s design dropped `state.sh init` in E1 — CCAM has a narrower, already-established boundary for who writes stage, and it's not this command. The skill is responsible for calling `ccam stage ... --status blocked` itself on a collision or conflict, same as any other gate failure. |
|
||||
| Merge-driver setup timing | Lazy, inside `mergeSync` itself, idempotent every call | Matches this repo's own established precedent: proof-link is "idempotent, never automatic" rather than a provision-time step. No other AgentHub lane-creation path (`addWorktree`/`provisionLane`) currently touches git config or `.git/info/*`; adding one there for this alone would be a new concern in an unrelated file for a need only sync-base has. |
|
||||
| Stage/status writes | None. `sync-base` returns a structured result (`code`, `collisions`, `devDelta`, `overlap`, `conflictedFiles`); it never calls anything that sets a lane's `stage`, `status`, or `notes` | This repo's own rule: "declared stages come from `agenthub stage`... The console never writes a lane's stage" and "the runtime never writes stage, status or notes." Shipyard's `state.sh set` heartbeat/notes calls inside `lane-sync-dev.sh` are dropped for the same reason `--qc`'s design dropped `state.sh init` in E1 — AgentHub has a narrower, already-established boundary for who writes stage, and it's not this command. The skill is responsible for calling `agenthub stage ... --status blocked` itself on a collision or conflict, same as any other gate failure. |
|
||||
| Exit codes | Preserved verbatim: `0` clean, `4` conflict (left in place), `5` migration collision | `SKILL.md` already documents these two non-zero codes by name (written during E1, before the command existed) — the contract predates this implementation and this spec fulfills it rather than inventing a new one. |
|
||||
| `git` execution | Reuse `worktree.js`'s `git(cwd, args)` helper (export it — not currently exported) | This repo's binding rule: never build a git command as a shell string; `worktree.js` already has the one approved execFile wrapper. A second implementation in `lane-sync.js` would duplicate it. |
|
||||
| State for `--continue` | None — read straight from the lane's own git state (`git ls-files -u`, presence of `.git/MERGE_HEAD`), exactly like the source script | Stateless by construction: the working copy IS the source of truth for "is this conflict resolved yet," so a separate DB flag would just be a second place to get out of sync with the first. |
|
||||
|
||||
## Command mapping
|
||||
|
||||
| Shipyard | CCAM | Status |
|
||||
| Shipyard | AgentHub | Status |
|
||||
|---|---|---|
|
||||
| `lane-sync-dev.sh <N> --check [branch]` | `ccam lanes sync-base --check [branch] [<id>]` | **new in this task** |
|
||||
| `lane-sync-dev.sh <N> [branch]` | `ccam lanes sync-base [branch] [<id>]` | **new in this task** |
|
||||
| `lane-sync-dev.sh <N> --continue [branch]` | `ccam lanes sync-base --continue [branch] [<id>]` | **new in this task** |
|
||||
| `lane-sync-dev.sh <N> --check [branch]` | `agenthub lanes sync-base --check [branch] [<id>]` | **new in this task** |
|
||||
| `lane-sync-dev.sh <N> [branch]` | `agenthub lanes sync-base [branch] [<id>]` | **new in this task** |
|
||||
| `lane-sync-dev.sh <N> --continue [branch]` | `agenthub lanes sync-base --continue [branch] [<id>]` | **new in this task** |
|
||||
| `state.sh "$N" set stage=migration-collision ...` (on exit 5) | dropped — see Decisions | n/a |
|
||||
| `state.sh "$N" set stage=sync-conflict ...` (on exit 4) | dropped — see Decisions | n/a |
|
||||
| `write_lane_markers` (push guards, `.harness-lane` exclude, proof-link) | dropped — out of scope, unrelated to sync | n/a |
|
||||
@@ -59,7 +59,7 @@
|
||||
Three exported async functions, each taking `(lane, profile, branch)`:
|
||||
|
||||
- **`checkSync(lane, profile, branch)`** — validates `branch` isn't `development`/`main` and exists; `git fetch origin --prune`; runs the collision check (below); if a collision is found, returns `{code: 5, collisions: [{file, num, clash, suggestion}]}` without going further. Otherwise computes `devDelta` (files changed on `origin/development` since the merge-base with `branch`, filtered against `GENERATED_MERGE_PATHS`) and `overlap` (the subset of that delta touching `branch`'s own changed files), returns `{code: 0, devDelta, overlap}`.
|
||||
- **`mergeSync(lane, profile, branch)`** — same validation + fetch; installs the keep-ours merge driver for every `GENERATED_MERGE_PATHS` entry via `git config merge.ccam-generated.driver true` + a line per path in `.git/info/attributes` (both local-only, idempotent — `grep -qxF` before appending, exactly like the source's guard); runs the collision check (exit 5 short-circuits, nothing merged); checks out `branch`; runs `git merge --no-edit origin/development`. A clean merge or a fully rerere-auto-resolved one falls through to the regen fold-in (below) and returns `{code: 0}`. A real conflict is left in place — returns `{code: 4, conflictedFiles: [...]}` from `git diff --name-only --diff-filter=U`.
|
||||
- **`mergeSync(lane, profile, branch)`** — same validation + fetch; installs the keep-ours merge driver for every `GENERATED_MERGE_PATHS` entry via `git config merge.agenthub-generated.driver true` + a line per path in `.git/info/attributes` (both local-only, idempotent — `grep -qxF` before appending, exactly like the source's guard); runs the collision check (exit 5 short-circuits, nothing merged); checks out `branch`; runs `git merge --no-edit origin/development`. A clean merge or a fully rerere-auto-resolved one falls through to the regen fold-in (below) and returns `{code: 0}`. A real conflict is left in place — returns `{code: 4, conflictedFiles: [...]}` from `git diff --name-only --diff-filter=U`.
|
||||
- **`continueSync(lane, profile, branch)`** — asserts current branch equals `branch`, no unmerged paths (`git ls-files -u` empty), and no `.git/MERGE_HEAD` (already committed) — any violation is a thrown error with a clear message, not a silent no-op. Then runs the regen fold-in and returns `{code: 0}`.
|
||||
|
||||
**Collision check** (shared helper, not exported): if `MIGRATIONS_DIR` is unset, no-op. Otherwise diff `origin/development...branch` for added files under `MIGRATIONS_DIR`, list `origin/development`'s existing `NNN_*` files in that dir, and for each added file whose number already exists upstream, compute the next free number and report it — same arithmetic as the source script (`10#${maxnum:-0} + 1`, incrementing per additional clash in the same batch).
|
||||
@@ -72,17 +72,17 @@ Three exported async functions, each taking `(lane, profile, branch)`:
|
||||
|
||||
### CLI
|
||||
|
||||
`ccam lanes sync-base [--check|--continue] [branch] [<id>]` in `bin/ccam.js`, same flag-parsing style as `lanes up`. Maps the response `code` to `process.exitCode` (0/4/5) so a driving skill's `$?`/exit-code check works exactly as `SKILL.md` already documents. On `code: 5`, prints each collision's suggested rename; on `code: 4`, prints the conflicted file list plus the exact `--continue` follow-up command; on `code: 0`, prints `DEV_DELTA:`/`DEV_OVERLAP:` (check mode) or a plain success line (merge/continue mode) — same output shape as the source script, since the skill's stage text already expects to read `DEV_DELTA:`/`DEV_OVERLAP:` lines.
|
||||
`agenthub lanes sync-base [--check|--continue] [branch] [<id>]` in `bin/agenthub.js`, same flag-parsing style as `lanes up`. Maps the response `code` to `process.exitCode` (0/4/5) so a driving skill's `$?`/exit-code check works exactly as `SKILL.md` already documents. On `code: 5`, prints each collision's suggested rename; on `code: 4`, prints the conflicted file list plus the exact `--continue` follow-up command; on `code: 0`, prints `DEV_DELTA:`/`DEV_OVERLAP:` (check mode) or a plain success line (merge/continue mode) — same output shape as the source script, since the skill's stage text already expects to read `DEV_DELTA:`/`DEV_OVERLAP:` lines.
|
||||
|
||||
### `SKILL.md` edits
|
||||
|
||||
- Stage 2: replace the "later task, not yet built" paragraph with the real command + its exit-5 handling (rename, re-run gates) — text already half-written in E1's port, just needs the conditional removed.
|
||||
- Stage 8: same — "re-run the preflight if it exists by now" becomes unconditional.
|
||||
- Stage 12: replace the "if it exists / if it doesn't" branch with the unconditional `ccam lanes sync-base feat/<slug>` call, and add the exit-4 handling this repo's port currently lacks: resolve conflicts, `git add` only the resolved files, `git commit --no-edit`, then `ccam lanes sync-base --continue feat/<slug>` (folds regenerated artifacts in) — carried over from Shipyard's original `ship-feature/SKILL.md` Stage 12, which already has this exact text.
|
||||
- Stage 12: replace the "if it exists / if it doesn't" branch with the unconditional `agenthub lanes sync-base feat/<slug>` call, and add the exit-4 handling this repo's port currently lacks: resolve conflicts, `git add` only the resolved files, `git commit --no-edit`, then `agenthub lanes sync-base --continue feat/<slug>` (folds regenerated artifacts in) — carried over from Shipyard's original `ship-feature/SKILL.md` Stage 12, which already has this exact text.
|
||||
|
||||
## Invariants
|
||||
|
||||
1. **CCAM still does not orchestrate.** `sync-base` performs one git operation per call and returns; it never chains into gate re-runs or stage transitions itself — the skill decides what happens next.
|
||||
1. **AgentHub still does not orchestrate.** `sync-base` performs one git operation per call and returns; it never chains into gate re-runs or stage transitions itself — the skill decides what happens next.
|
||||
2. **A profile that never declares `MIGRATIONS_DIR` or `GENERATED_MERGE_PATHS` sees no behavior change** — collision checking and the merge driver both degrade to no-ops, same as every other optional declaration.
|
||||
3. **Never a shell-string git command.** Every git invocation in `lane-sync.js` goes through the shared `git(cwd, args)` execFile helper.
|
||||
4. **`sync-base` never writes `stage`/`status`/`notes`.** Same boundary as the runtime and the hook routes.
|
||||
|
||||
@@ -13,7 +13,7 @@ F4 surfaced `agents install`/`mcp sync`/profile-init as per-lane manual buttons,
|
||||
2. `AddLaneModal.tsx`: after `api.lanes.worktree()` succeeds, fire `profileInit`, `agentsInstall`, and `mcpSync` in parallel (`Promise.allSettled` — all three are independent of each other: `agentsInstall`/`mcpSync` only need `.git`, not a profile). Each outcome (success/failure/skipped) is collected and shown as a short per-step result list before the modal closes, instead of closing silently the instant the lane record exists.
|
||||
|
||||
**Explicitly out of scope:**
|
||||
- `ccam skills install` — machine-wide, one-time-per-machine, not lane-specific. Running it every time someone adds a lane would be redundant (idempotent, but pointless) and conceptually wrong (it has nothing to do with THIS lane). Stays a manual Settings-page action.
|
||||
- `agenthub skills install` — machine-wide, one-time-per-machine, not lane-specific. Running it every time someone adds a lane would be redundant (idempotent, but pointless) and conceptually wrong (it has nothing to do with THIS lane). Stays a manual Settings-page action.
|
||||
- Blocking lane creation on any of the three setup steps failing. A lane with a non-Node.js repo, no MCP servers configured, or a git quirk should still exist and be usable — setup is best-effort enrichment, not a precondition.
|
||||
- Adopting an EXISTING folder (a second "add lane" mode) — `AddLaneModal` only does managed-worktree creation today; that's unchanged. This auto-setup applies to whatever lane the existing flow just created, regardless of how.
|
||||
|
||||
|
||||
@@ -4,30 +4,30 @@
|
||||
|
||||
## Problem
|
||||
|
||||
E1 through F3c (this session) built six CLI-only primitives — `ccam lanes sync-base`, `ccam lanes agents install`, `ccam lanes mcp sync`, `ccam lanes integration <name>`, `ccam lanes gc`, `ccam skills install` — with no UI surface. The user wants the lane-scoped, day-to-day-useful ones clickable from the dashboard instead of terminal-only.
|
||||
E1 through F3c (this session) built six CLI-only primitives — `agenthub lanes sync-base`, `agenthub lanes agents install`, `agenthub lanes mcp sync`, `agenthub lanes integration <name>`, `agenthub lanes gc`, `agenthub skills install` — with no UI surface. The user wants the lane-scoped, day-to-day-useful ones clickable from the dashboard instead of terminal-only.
|
||||
|
||||
## Scope
|
||||
|
||||
**In scope — LaneCard.tsx (per-lane, gated on `runtime?.available`, same condition the existing up/down button already uses since all four need a `.ccam/profile`):**
|
||||
**In scope — LaneCard.tsx (per-lane, gated on `runtime?.available`, same condition the existing up/down button already uses since all four need a `.agenthub/profile`):**
|
||||
1. **"Install agents" button** → `POST /lanes/:id/agents/install` (exists). Shows the installed file list on success.
|
||||
2. **"Sync MCP" button** → `POST /lanes/:id/mcp/sync` (exists). Shows synced servers + seeded profiles.
|
||||
3. **Integration status badges** (tracker / dev_qc / ci_wait), read-only, on/off — reads `GET /lanes/:id/integrations/:name` (exists) for each of the three fixed names.
|
||||
4. **"Check dev sync" button** → `POST /lanes/:id/sync-base` with `{mode: "check"}` (exists — the route already supports `mode`). Shows `DEV_DELTA`/`DEV_OVERLAP` or a collision list. **Read-only preflight only — no merge button.** Merging is a deliberate, evidence-recording action the driving skill/session takes (`ccam lanes sync-base` bare, or `--continue`); a UI click is the wrong place for a git merge with fix-loop implications. This matches the project's own boundary: "the console never writes a lane's stage" — extending that spirit, the console doesn't perform git merges either.
|
||||
4. **"Check dev sync" button** → `POST /lanes/:id/sync-base` with `{mode: "check"}` (exists — the route already supports `mode`). Shows `DEV_DELTA`/`DEV_OVERLAP` or a collision list. **Read-only preflight only — no merge button.** Merging is a deliberate, evidence-recording action the driving skill/session takes (`agenthub lanes sync-base` bare, or `--continue`); a UI click is the wrong place for a git merge with fix-loop implications. This matches the project's own boundary: "the console never writes a lane's stage" — extending that spirit, the console doesn't perform git merges either.
|
||||
|
||||
**In scope — Settings.tsx (machine-wide, new "Lanes" section near the existing Hooks section — modeled on "Reinstall Hooks"/"Cleanup"):**
|
||||
5. **"Install ship-feature-lane skill" button** → new route `POST /api/skills/install` (doesn't exist yet — needs building; wraps the same `fs.cpSync` logic `ccam skills install`'s CLI already has, factored into `server/lib/lane-agents.js` or a new tiny `server/lib/skills-install.js` so the CLI and route share one implementation instead of duplicating the copy logic).
|
||||
5. **"Install ship-feature-lane skill" button** → new route `POST /api/skills/install` (doesn't exist yet — needs building; wraps the same `fs.cpSync` logic `agenthub skills install`'s CLI already has, factored into `server/lib/lane-agents.js` or a new tiny `server/lib/skills-install.js` so the CLI and route share one implementation instead of duplicating the copy logic).
|
||||
6. **"Run housekeeping" button, with a "dry run" checkbox** → new route `POST /api/lanes/gc` (doesn't exist yet — wraps `server/lib/lane-gc.js`'s `reapOrphanMcp`/`capOversizedLogs`, already built and tested in F3c). Shows reaped pids + capped log paths.
|
||||
|
||||
**Explicitly out of scope:**
|
||||
- A merge button for `sync-base` (see #4's reasoning).
|
||||
- Any UI for `ccam lanes sync-base --continue` (conflict resolution is a session/skill action with real judgment calls about which side to keep — not a button).
|
||||
- Refactoring the CLI (`bin/ccam.js`) to call the new HTTP routes instead of its current direct `require()` of the lib modules — the CLI's local-only shape (no server round-trip needed when you're already on the machine) stays as-is; only the browser needs an HTTP path, so the routes are additive, not a replacement for the CLI's existing implementation.
|
||||
- Any UI for `agenthub lanes sync-base --continue` (conflict resolution is a session/skill action with real judgment calls about which side to keep — not a button).
|
||||
- Refactoring the CLI (`bin/agenthub.js`) to call the new HTTP routes instead of its current direct `require()` of the lib modules — the CLI's local-only shape (no server round-trip needed when you're already on the machine) stays as-is; only the browser needs an HTTP path, so the routes are additive, not a replacement for the CLI's existing implementation.
|
||||
|
||||
## Design
|
||||
|
||||
### Two new backend routes
|
||||
|
||||
- `POST /api/skills/install` — same `sameOriginGuard` as every other mutating route. Extracts the copy logic (`REPO_ROOT/.claude/skills/ship-feature-lane` → `~/.claude/skills/ship-feature-lane`, `fs.cpSync` with `force: true`) out of `bin/ccam.js`'s `cmdSkillsInstall` into a small exported function in `server/lib/skills-install.js`, so `bin/ccam.js` and the route both call the same code (no duplication). Returns `{installed: true, path: string}`.
|
||||
- `POST /api/skills/install` — same `sameOriginGuard` as every other mutating route. Extracts the copy logic (`REPO_ROOT/.claude/skills/ship-feature-lane` → `~/.claude/skills/ship-feature-lane`, `fs.cpSync` with `force: true`) out of `bin/agenthub.js`'s `cmdSkillsInstall` into a small exported function in `server/lib/skills-install.js`, so `bin/agenthub.js` and the route both call the same code (no duplication). Returns `{installed: true, path: string}`.
|
||||
- `POST /api/lanes/gc` — same guard. Body `{dryRun?: boolean}`. Calls `reapOrphanMcp`/`capOversizedLogs` from the already-built `server/lib/lane-gc.js` (no changes needed there — Task 2 of F3c already exports both). Returns `{reaped: number[], capped: {path: string, sizeBefore: number}[]}`.
|
||||
|
||||
Both routes are machine-wide (no `:id`), registered at the router's top level (not under `/lanes/:id/...`) — `/api/skills/install` sits alongside other non-lane-scoped routes; `/api/lanes/gc` sits in the lanes router but before any `:id` param route (order matters: Express would otherwise try to resolve `gc` as a lane id on a route like `/lanes/:id`).
|
||||
@@ -60,7 +60,7 @@ gc: (body: { dryRun?: boolean } = {}) =>
|
||||
|
||||
### LaneCard.tsx
|
||||
|
||||
Four additions, all gated on `runtime?.available` (same condition the up/down button already uses — every one of these four needs a `.ccam/profile`):
|
||||
Four additions, all gated on `runtime?.available` (same condition the up/down button already uses — every one of these four needs a `.agenthub/profile`):
|
||||
|
||||
- **Install agents / Sync MCP buttons**: same visual pattern as the up/down toggle (`rounded px-2 py-1 text-fg-secondary transition-colors hover:bg-surface-2 disabled:opacity-50`), a `useState<"agents" | "mcp" | "sync-check" | null>` busy flag (one shared state covering all three new button actions plus check, mirroring `runtimeBusy`'s shape), result shown as a transient inline line below the button (reuse the `laneActionError`-style banner pattern, but for success text too — a new `actionResult: {label: string; message: string} | null` state scoped to LaneCard).
|
||||
- **Integration badges**: a small row of three pill-shaped badges (`tracker` / `dev_qc` / `ci_wait`), fetched once when the card mounts and `runtime.available` is true (parallel `Promise.all` of the three `api.lanes.integration` calls, same effect-on-mount shape the existing `git`/`runtime` fetches already use), green/gray styling for on/off, no click handler — read-only.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Plugin-first install — CCAM usable straight from a Claude Code plugin install
|
||||
# Plugin-first install — AgentHub usable straight from a Claude Code plugin install
|
||||
|
||||
Date: 2026-08-10
|
||||
Status: approved (design)
|
||||
@@ -6,12 +6,12 @@ Status: approved (design)
|
||||
## Goal
|
||||
|
||||
On a machine with nothing but Claude Code installed, these two commands must
|
||||
leave the user with a working CCAM — hooks flowing, server up, dashboard
|
||||
reachable, `ccam` CLI on PATH, MCP tools connected:
|
||||
leave the user with a working AgentHub — hooks flowing, server up, dashboard
|
||||
reachable, `agenthub` CLI on PATH, MCP tools connected:
|
||||
|
||||
```
|
||||
/plugin marketplace add <ccam repo>
|
||||
/plugin install ccam@claude-code-agent-monitor-plugins
|
||||
/plugin marketplace add <agenthub repo>
|
||||
/plugin install agenthub@claude-code-agent-monitor-plugins
|
||||
```
|
||||
|
||||
No repo clone, no `npm run setup`, no `npm run install-hooks`, no manual
|
||||
@@ -19,8 +19,8 @@ No repo clone, no `npm run setup`, no `npm run install-hooks`, no manual
|
||||
|
||||
## Why the current state does not do this
|
||||
|
||||
- The only plugin with a server connection, `ccam-dashboard`, declares its MCP
|
||||
server as `../../mcp/build/index.js` (`plugins/ccam-dashboard/.mcp.json`).
|
||||
- The only plugin with a server connection, `agenthub-dashboard`, declares its MCP
|
||||
server as `../../mcp/build/index.js` (`plugins/agenthub-dashboard/.mcp.json`).
|
||||
Plugins installed from a marketplace subdir are cached as **that subdir
|
||||
only** — verified against `~/.claude/plugins/cache/claude-plugins-official/superpowers/6.2.0/`,
|
||||
which contains the plugin directory's contents and nothing above it. The
|
||||
@@ -32,14 +32,14 @@ No repo clone, no `npm run setup`, no `npm run install-hooks`, no manual
|
||||
|
||||
## Approach: declare the whole repo as one plugin
|
||||
|
||||
Add a **root-level** plugin named `ccam` with `"source": "."` in
|
||||
Add a **root-level** plugin named `agenthub` with `"source": "."` in
|
||||
`.claude-plugin/marketplace.json`. Claude Code then caches the *entire repo*
|
||||
into `~/.claude/plugins/cache/<marketplace>/ccam/<version>/` — verified against
|
||||
into `~/.claude/plugins/cache/<marketplace>/agenthub/<version>/` — verified against
|
||||
`~/.claude/plugins/cache/caveman/`, whose cached tree contains the full source
|
||||
repo (`src/`, `tests/`, `benchmarks/`), because that marketplace declares its
|
||||
plugin with `"source": "./"`.
|
||||
|
||||
So `server/`, `client/`, `mcp/`, `scripts/` and `bin/ccam.js` are all present
|
||||
So `server/`, `client/`, `mcp/`, `scripts/` and `bin/agenthub.js` are all present
|
||||
under `${CLAUDE_PLUGIN_ROOT}` the moment the plugin is installed. No second
|
||||
clone, no credentials beyond the marketplace add itself, no committed copy of
|
||||
the server, no dependency on undocumented cache internals beyond the documented
|
||||
@@ -61,7 +61,7 @@ gains no new top-level directories.
|
||||
`.claude-plugin/marketplace.json` gains one entry:
|
||||
|
||||
```json
|
||||
{ "name": "ccam", "source": ".", "description": "...", "tags": [...] }
|
||||
{ "name": "agenthub", "source": ".", "description": "...", "tags": [...] }
|
||||
```
|
||||
|
||||
### 2. Hooks — inline in `plugin.json`
|
||||
@@ -95,7 +95,7 @@ Runs from the `SessionStart` hook. Never blocks a session.
|
||||
|
||||
Runtime state lives under `~/.claude/agent-dashboard/runtime/`, reusing the
|
||||
existing data-dir convention (`server/lib/claude-home.js:37`, where the SQLite
|
||||
DB already lives) rather than inventing a new directory. `/.ccam/` at the repo
|
||||
DB already lives) rather than inventing a new directory. `/.agenthub/` at the repo
|
||||
root is an unrelated lane profile and must not be confused with it.
|
||||
|
||||
Sequence:
|
||||
@@ -118,13 +118,13 @@ Sequence:
|
||||
fresh version directory, discarding anything installed into the old one.
|
||||
`--ignore-scripts` is required because `scripts/postinstall.js` otherwise
|
||||
runs a full `client/` install, pulling the whole Vite dev toolchain.
|
||||
5. **Legacy hook cleanup.** If `~/.claude/settings.json` still carries CCAM hook
|
||||
5. **Legacy hook cleanup.** If `~/.claude/settings.json` still carries AgentHub hook
|
||||
entries from a previous `npm run install-hooks`, remove them and print one
|
||||
line. Without this, every event is POSTed twice and token/cost figures
|
||||
double. Detection reuses `isOurEntry()` from `scripts/install-hooks.js`.
|
||||
6. **CLI on PATH.** Symlink `bin/ccam.js` to `~/.local/bin/ccam`. Skills and
|
||||
commands throughout the repo (`ccam stage`, `ship-feature-lane`) shell out
|
||||
to `ccam` and fail without it.
|
||||
6. **CLI on PATH.** Symlink `bin/agenthub.js` to `~/.local/bin/agenthub`. Skills and
|
||||
commands throughout the repo (`agenthub stage`, `ship-feature-lane`) shell out
|
||||
to `agenthub` and fail without it.
|
||||
7. **Start server.** Spawn `server/index.js` detached, `stdio: "ignore"`,
|
||||
`unref()`, with `NODE_PATH` pointed at `runtime/node_modules`. The hook
|
||||
returns immediately; the first install (1–3 minutes) proceeds in the
|
||||
@@ -132,7 +132,7 @@ Sequence:
|
||||
8. **Record state.** Write `runtime/state.json`.
|
||||
|
||||
The client UI bundle is *not* built here. Hooks, the API and MCP do not need
|
||||
it; it is built lazily by `/ccam-open`.
|
||||
it; it is built lazily by `/agenthub-open`.
|
||||
|
||||
### 4. Client dist relocation — `server/index.js`
|
||||
|
||||
@@ -143,7 +143,7 @@ checkout behavior is unchanged.
|
||||
|
||||
The bootstrap always starts the server with `DASHBOARD_CLIENT_DIST` set to
|
||||
`runtime/client-dist/`, whether or not a bundle is there yet — a missing
|
||||
directory serves the API fine and only the UI route 404s. `/ccam-open` builds
|
||||
directory serves the API fine and only the UI route 404s. `/agenthub-open` builds
|
||||
into that same directory, so no server restart is needed to pick the bundle up.
|
||||
|
||||
### 5. MCP — commit `mcp/build/`
|
||||
@@ -152,27 +152,27 @@ Claude Code starts a plugin's MCP servers as the session opens and offers no
|
||||
"not ready yet, retry" state, so an async bootstrap cannot win that race.
|
||||
`mcp/build/` is therefore committed (removed from `.gitignore` and `mcp/.gitignore`)
|
||||
and the root `.mcp.json` points at `${CLAUDE_PLUGIN_ROOT}/mcp/build/index.js`
|
||||
with `CCAM_DASHBOARD_URL` defaulted to `http://localhost:4820`.
|
||||
with `AGENTHUB_DASHBOARD_URL` defaulted to `http://localhost:4820`.
|
||||
|
||||
Cost: the build artifact must stay in sync with `mcp/src`. A pre-commit check
|
||||
fails when `mcp/src` is newer than `mcp/build`.
|
||||
|
||||
The existing `plugins/ccam-dashboard/.mcp.json` keeps its broken relative path
|
||||
The existing `plugins/agenthub-dashboard/.mcp.json` keeps its broken relative path
|
||||
fixed to `${CLAUDE_PLUGIN_ROOT}` form as part of this work, so the subdir plugin
|
||||
is not left in a knowingly broken state.
|
||||
|
||||
### 6. Commands — `plugins/ccam/commands/`
|
||||
### 6. Commands — `plugins/agenthub/commands/`
|
||||
|
||||
The plugin root is the repo root, so `plugin.json` declares an explicit
|
||||
`"commands": "./plugins/ccam/commands"` path. This keeps the new command files
|
||||
`"commands": "./plugins/agenthub/commands"` path. This keeps the new command files
|
||||
inside `plugins/`, next to the ten existing plugins, instead of adding a
|
||||
top-level `commands/` directory to the repo root.
|
||||
|
||||
- `/ccam-doctor` — bootstrap state, Node version, server liveness, DB path,
|
||||
- `/agenthub-doctor` — bootstrap state, Node version, server liveness, DB path,
|
||||
duplicate-hook detection, CLI symlink, MCP build freshness.
|
||||
- `/ccam-update` — refresh dependencies and restart the server after a plugin
|
||||
- `/agenthub-update` — refresh dependencies and restart the server after a plugin
|
||||
update.
|
||||
- `/ccam-open` — lazily build the client bundle if missing, then open the
|
||||
- `/agenthub-open` — lazily build the client bundle if missing, then open the
|
||||
dashboard.
|
||||
|
||||
## Data and lifecycle
|
||||
@@ -196,7 +196,7 @@ deduplicates hook ingest to a single port.
|
||||
by full detachment (`detached`, `stdio: "ignore"`, `unref()`) and an
|
||||
immediate hook return. Verified by timing the hook on a cold `HOME`.
|
||||
- *Duplicate hooks double-count cost.* Events carry no id, so ingest cannot
|
||||
deduplicate. Mitigated by step 5 of the bootstrap plus a `/ccam-doctor` check.
|
||||
deduplicate. Mitigated by step 5 of the bootstrap plus a `/agenthub-doctor` check.
|
||||
- *MCP unavailable at session start.* Resolved by committing `mcp/build/`.
|
||||
|
||||
**Medium severity**
|
||||
@@ -204,8 +204,8 @@ deduplicates hook ingest to a single port.
|
||||
- *Two sessions spawn two servers* → `EADDRINUSE`. Mitigated by the lock, with
|
||||
a stale-lock timeout so a crashed bootstrap does not wedge every later
|
||||
session.
|
||||
- *`ccam` not on PATH* when `~/.local/bin` is absent from the user's PATH.
|
||||
`/ccam-doctor` reports it and prints the line to add.
|
||||
- *`agenthub` not on PATH* when `~/.local/bin` is absent from the user's PATH.
|
||||
`/agenthub-doctor` reports it and prints the line to add.
|
||||
- *Windows.* `npm.cmd` resolution (already handled in `scripts/postinstall.js`
|
||||
via `shell: true`), detached spawn semantics, and `mkdir` locking are the
|
||||
least-tested paths.
|
||||
@@ -222,7 +222,7 @@ deduplicates hook ingest to a single port.
|
||||
## Verification
|
||||
|
||||
1. Install the local marketplace and confirm
|
||||
`~/.claude/plugins/cache/<mp>/ccam/*/server/` exists — this gates everything
|
||||
`~/.claude/plugins/cache/<mp>/agenthub/*/server/` exists — this gates everything
|
||||
else.
|
||||
2. `npm run test:server` for the `DASHBOARD_CLIENT_DIST` override.
|
||||
3. Unit tests for bootstrap: fast path, stale-lock reclaim, Node version gate,
|
||||
|
||||
@@ -54,14 +54,14 @@ sync — they were never coupled to the run mechanism in the first place.
|
||||
|
||||
**In scope:**
|
||||
- tmux session lifecycle (create/attach/resume/kill) per lane, named
|
||||
`ccam-lane-<id>`.
|
||||
`agenthub-lane-<id>`.
|
||||
- A dedicated WebSocket path streaming a real PTY (`node-pty` running
|
||||
`tmux attach-session`) to the browser, rendered with `@xterm/xterm`.
|
||||
- Replacing `RunConsole.tsx` with a `TerminalView.tsx` component.
|
||||
- Reusing (not rebuilding) `RunSetup.tsx`'s cwd/model/permission-mode/effort
|
||||
pickers and `RunHistory.tsx`/`ActiveRunsSwitcher`'s multi-run list, both
|
||||
adjusted to the new data source.
|
||||
- `ccam lanes shell` — CLI convenience to attach a real terminal to the same
|
||||
- `agenthub lanes shell` — CLI convenience to attach a real terminal to the same
|
||||
named tmux session.
|
||||
- `dashboard_runs` migration: drop `mode` (headless/conversation no longer
|
||||
applies — a live pane is always interactive), add `tmux_session`.
|
||||
@@ -81,11 +81,11 @@ sync — they were never coupled to the run mechanism in the first place.
|
||||
|
||||
### Session naming and lifecycle
|
||||
|
||||
One tmux session per lane, name `ccam-lane-<lane.id>` — stable, collision-free
|
||||
One tmux session per lane, name `agenthub-lane-<lane.id>` — stable, collision-free
|
||||
(numeric lane id, not a user-editable slug).
|
||||
|
||||
- **Start:** `tmux has-session -t ccam-lane-<id>` (exit code only, no
|
||||
output). If absent: `tmux new-session -d -s ccam-lane-<id> -c <lane.cwd> --
|
||||
- **Start:** `tmux has-session -t agenthub-lane-<id>` (exit code only, no
|
||||
output). If absent: `tmux new-session -d -s agenthub-lane-<id> -c <lane.cwd> --
|
||||
claude <argv...> [initialPrompt]` — `claude` runs directly as the pane's
|
||||
command (nothing is "typed"). `argv` carries `--model`, `--permission-mode`,
|
||||
`--effort` as today; an optional initial prompt is passed as a trailing
|
||||
@@ -97,14 +97,14 @@ One tmux session per lane, name `ccam-lane-<lane.id>` — stable, collision-free
|
||||
already-live server instead of double-binding; see `server/index.js`'s
|
||||
port-adoption logic) applied to tmux sessions.
|
||||
- **Resume:** identical, `argv` includes `--resume <session_id>`.
|
||||
- **Kill:** `tmux kill-session -t ccam-lane-<id>`. This sends SIGHUP to the
|
||||
- **Kill:** `tmux kill-session -t agenthub-lane-<id>`. This sends SIGHUP to the
|
||||
pane's process group. Whether `claude` treats that as a clean shutdown
|
||||
(firing `SessionEnd`) is unverified — flag for implementation to check; if
|
||||
not, this repo's existing dead-session liveness reap (`server/lib/
|
||||
session-liveness.js`, referenced in `CLAUDE.md`) is the safety net that
|
||||
already exists for exactly this kind of gap, no new code needed.
|
||||
- **List (`GET /api/run`):** no longer a Map read. Runs `tmux list-sessions
|
||||
-F '#{session_name}'`, filters the `ccam-lane-` prefix, and joins against
|
||||
-F '#{session_name}'`, filters the `agenthub-lane-` prefix, and joins against
|
||||
`dashboard_runs` rows (started_at, model, lane_id, …) for display. This
|
||||
makes "which runs are active" a **computed fact from tmux state**, not
|
||||
cached server memory — the same principle this repo already applies to
|
||||
@@ -136,7 +136,7 @@ A second server is added for the PTY stream, same `verifyClient` auth guard
|
||||
(Host allowlist + `DASHBOARD_TOKEN`) reused as-is:
|
||||
|
||||
- Path: `/ws-pty/:runId` (runId = tmux session name, validated against the
|
||||
`ccam-lane-<numeric id>` pattern before any tmux command touches it — this
|
||||
`agenthub-lane-<numeric id>` pattern before any tmux command touches it — this
|
||||
is the trust boundary: without validation, a WS client could name an
|
||||
arbitrary tmux session on the host and attach to something unrelated to
|
||||
this dashboard).
|
||||
@@ -179,13 +179,13 @@ A second server is added for the PTY stream, same `verifyClient` auth guard
|
||||
separate code path — expected to be unaffected, but verify before
|
||||
deleting).
|
||||
|
||||
### `ccam lanes shell`
|
||||
### `agenthub lanes shell`
|
||||
|
||||
`bin/ccam.js`, same dispatch pattern as the other `lanes` subcommands
|
||||
`bin/agenthub.js`, same dispatch pattern as the other `lanes` subcommands
|
||||
(`rest[0] === "shell"` → `cmdLanesShell`). Resolves the lane from `cwd` the
|
||||
same way `ccam stage` does, computes `ccam-lane-<id>`, and `execve`s (replace
|
||||
same way `agenthub stage` does, computes `agenthub-lane-<id>`, and `execve`s (replace
|
||||
the current process image, not a child — so Ctrl-C/signals behave like a
|
||||
normal terminal command) `tmux new-session -A -s ccam-lane-<id> -c
|
||||
normal terminal command) `tmux new-session -A -s agenthub-lane-<id> -c
|
||||
<lane.cwd>`. `-A` creates-or-attaches, so this is the exact same idempotent
|
||||
behavior as clicking Start on the dashboard. The user types `claude`
|
||||
themselves inside — this command's only job is getting them into the right
|
||||
@@ -236,7 +236,7 @@ invocation regardless of transport.
|
||||
`npm run test:server`, `npm run test:client` (including the regenerated
|
||||
snapshot), and a manual click-through: install `tmux` locally, Start a run
|
||||
from Workspace, confirm the real Claude Code TUI renders in the browser,
|
||||
type in the browser and confirm it reaches the pane, run `ccam lanes shell`
|
||||
type in the browser and confirm it reaches the pane, run `agenthub lanes shell`
|
||||
from a real terminal for the same lane and confirm it drops into the exact
|
||||
same live session, kill from the dashboard and confirm the tmux session and
|
||||
row both clear.
|
||||
|
||||
@@ -51,7 +51,7 @@ today.
|
||||
## Persistence
|
||||
|
||||
The chosen layout mode and each pane's selected `laneId` are saved to
|
||||
`localStorage` (e.g. key `ccam.workspace.splitView`) and restored on next
|
||||
`localStorage` (e.g. key `agenthub.workspace.splitView`) and restored on next
|
||||
visit to Workspace. If a persisted lane no longer exists, that pane falls
|
||||
back to unselected (dropdown placeholder).
|
||||
|
||||
|
||||
+18
-18
@@ -1,4 +1,4 @@
|
||||
# `ccam ci status` — unblock Stage 13's CI-wait half
|
||||
# `agenthub ci status` — unblock Stage 13's CI-wait half
|
||||
|
||||
**Status:** approved 2026-08-25.
|
||||
|
||||
@@ -9,10 +9,10 @@ skipped. Two integrations gate it: dev-CI-wait (watch CI on `development`
|
||||
after a PR merges, before QCing the deployed site) and dev-QC (QC the
|
||||
deployed site). Neither exists yet.
|
||||
|
||||
This spec covers only the CI-wait half. `ccam lanes integration ci_wait`
|
||||
This spec covers only the CI-wait half. `agenthub lanes integration ci_wait`
|
||||
already exists (`server/lib/lane-profile.js:isIntegrationEnabled`,
|
||||
`bin/ccam.js:cmdLanesIntegration`) and reports whether a profile *wants* the
|
||||
integration on — but nothing acts on it. `ccam ci` doesn't exist
|
||||
`bin/agenthub.js:cmdLanesIntegration`) and reports whether a profile *wants* the
|
||||
integration on — but nothing acts on it. `agenthub ci` doesn't exist
|
||||
(`skills/ship-feature-lane/SKILL.md:167` notes this explicitly at Stage 10,
|
||||
where the skill falls back to raw `gh pr checks`/`gh run view`).
|
||||
|
||||
@@ -22,21 +22,21 @@ here. Stage 13's dev-QC half stays noted as unavailable until that lands.
|
||||
## Approach
|
||||
|
||||
**A read-only status snapshot, not a wait loop.** Per the project's
|
||||
orchestration doctrine (`docs/LANES.md` § "Orchestration: what CCAM does NOT
|
||||
do" — no chaining, no queue, no retry logic), `ccam ci status` reports a
|
||||
orchestration doctrine (`docs/LANES.md` § "Orchestration: what AgentHub does NOT
|
||||
do" — no chaining, no queue, no retry logic), `agenthub ci status` reports a
|
||||
fact once; it does not poll, block, or retry. The driving skill owns the
|
||||
poll loop and the fix/escalate decision, the same shape Stage 12 already
|
||||
uses for watching a PR (`sleep 300` backgrounded, re-invoked per iteration).
|
||||
|
||||
This also means `ccam ci status` needs no flake classification. Stage 10
|
||||
This also means `agenthub ci status` needs no flake classification. Stage 10
|
||||
already does that triage (infra/flake signature vs. real failure) reading
|
||||
`gh` output directly — Stage 13 reuses the same triage, now reading this
|
||||
command's structured JSON instead of raw `gh`.
|
||||
|
||||
**Server-backed, following the `sync-base` pattern**, not a CLI-local `gh`
|
||||
spawn. Every existing `ccam` command is a thin HTTP client — no command in
|
||||
`bin/ccam.js` spawns a child process directly (`worktree.js`'s `git()`
|
||||
helper is the only child-process boundary CCAM has today, always
|
||||
spawn. Every existing `agenthub` command is a thin HTTP client — no command in
|
||||
`bin/agenthub.js` spawns a child process directly (`worktree.js`'s `git()`
|
||||
helper is the only child-process boundary AgentHub has today, always
|
||||
server-side). A new `server/lib/gh.js` mirrors that helper:
|
||||
|
||||
```js
|
||||
@@ -56,12 +56,12 @@ New route: `GET /api/lanes/:id/ci/status?ref=<branch-or-sha>`
|
||||
yet, not an error).
|
||||
- `gh` missing or not authenticated → `{ error: { message } }`, HTTP 500.
|
||||
|
||||
New CLI: `ccam lanes ci status <branch-or-sha> [<lane-id>]`
|
||||
New CLI: `agenthub lanes ci status <branch-or-sha> [<lane-id>]`
|
||||
|
||||
- GETs the route above, prints the JSON, sets `process.exitCode`.
|
||||
- Resolves the lane the same way every other `ccam lanes <verb>` command
|
||||
- Resolves the lane the same way every other `agenthub lanes <verb>` command
|
||||
does (`resolveLaneArg`), so it works with an implicit lane-from-cwd or an
|
||||
explicit id, matching `ccam lanes integration`.
|
||||
explicit id, matching `agenthub lanes integration`.
|
||||
|
||||
## Response shape
|
||||
|
||||
@@ -97,7 +97,7 @@ note with a poll loop mirroring Stage 12's:
|
||||
|
||||
- Get the merge SHA: `gh pr view <pr_url> --json mergeCommit -q .mergeCommit.oid`.
|
||||
- Loop (`sleep 300`, `run_in_background: true`, bump heartbeat each
|
||||
iteration): `ccam lanes ci status <sha>`.
|
||||
iteration): `agenthub lanes ci status <sha>`.
|
||||
- `pending` → keep waiting.
|
||||
- `failure` → triage like Stage 10 (`gh run view` for job logs on the
|
||||
same run id returned in `checks[].url`), fix + re-enter from Stage 2,
|
||||
@@ -107,14 +107,14 @@ note with a poll loop mirroring Stage 12's:
|
||||
that CI-wait specifically passed.
|
||||
- Update `docs/LANES.md`: the "Stages currently hardcoded skipped" list
|
||||
(Stage 13 becomes "CI-wait works; dev-QC still off"), the
|
||||
`ccam lanes integration` section's claim that `ci_wait`'s consumer isn't
|
||||
built, and add `ccam ci status` to the command reference.
|
||||
`agenthub lanes integration` section's claim that `ci_wait`'s consumer isn't
|
||||
built, and add `agenthub ci status` to the command reference.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- No flake auto-classification or auto-rerun inside `ccam ci status` —
|
||||
- No flake auto-classification or auto-rerun inside `agenthub ci status` —
|
||||
stays a dumb fact-reporter; the skill keeps owning that judgment, per
|
||||
CCAM's no-retry-logic rule.
|
||||
AgentHub's no-retry-logic rule.
|
||||
- No change to Stage 10 (PR CI watch) — it keeps using raw `gh` directly.
|
||||
Reusing this command there is a plausible future cleanup, not required
|
||||
now.
|
||||
@@ -14,7 +14,7 @@ same tmux-backed terminal console, same run history.
|
||||
1. Scope: run Codex in the lane's tmux pane. No ingestion of `~/.codex` history.
|
||||
2. The tool belongs to the **lane**, not to the run: one lane = one tool, picked
|
||||
in the "Add lane" modal, not changeable afterwards. One tmux session per lane
|
||||
stays `ccam-lane-<id>`.
|
||||
stays `agenthub-lane-<id>`.
|
||||
3. Codex run controls: model, sandbox, ask-for-approval, reasoning effort.
|
||||
4. Codex runs persist in `dashboard_runs` (new `tool` column) so Active runs and
|
||||
Run history show them next to Claude runs.
|
||||
@@ -97,7 +97,7 @@ Codex does not emit this dashboard's Claude Code hooks, so for a Codex lane:
|
||||
|
||||
- Sessions, Analytics, and cost stay empty — nothing ingests Codex usage.
|
||||
- `server/lib/stage-detect.js` never paints the amber detected badge.
|
||||
- Pipeline stages still work when the agent calls `ccam stage` itself (the repo
|
||||
- Pipeline stages still work when the agent calls `agenthub stage` itself (the repo
|
||||
already ships `AGENTS.md` and `.codex/`).
|
||||
- Lane liveness for Codex lanes comes from the tmux pane plus `dashboard_runs`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user