A lane's pipeline map only ever moved when a skill remembered to call
`ccam stage`, and the ship-feature template shipped with no detection rules
at all — so a lane driven by Superpowers skills sat at whatever stage it
last declared, and the `gates` node was never declared by anything.
Detection (`detect` rules on each node) now covers the Superpowers skill
invocations and the `ccam`/`gh` commands the ship-feature-lane skill
actually runs. It stays a safety net, not the mechanism: forward-only,
never `done`, never overriding a declaration. Two rules were deliberately
left out — `git diff` on `review` (this repo's own tests record it pinning
a lane at `review` on a real session) and anything on `merged`/`done`.
Stage vocabulary grows to 50 names over the same 16 nodes, following
Shipyard's PHASES shape: sub-states like `migration-collision`,
`e2e-scoped` and `gate-blocked` say WHY a lane sits on a node without the
map growing a node per reason. Every alias has a source — the skill
declares it, `default.json` uses it, or Shipyard's PHASES lists it.
Two silent failures fixed along the way:
- `lane.stages` is keyed by the raw declared string, so a stage declared
under an alias lost its `--evidence` and rendered amber instead of
green. `stageRecords` resolves each key onto its node.
- `ccam stage <typo>` stored fine and then rendered nowhere. It now warns
on stderr while still exiting 0.
`ccam lanes pipeline` closes the gap that made all of this invisible: a
lane could only be assigned a template at creation, and no screen in the
web UI offers the choice, so every lane added from "+ Add lane" was stuck
on `default`'s 8 nodes. An unknown template id is now refused rather than
silently falling back to `default` on read.
Also merges the repo's own `ship-feature` skill into the Superpowers
workflow: it delegates planning/TDD/review/verification instead of
restating them, and declares a stage at each phase.
- Repo mode adopts a directory as-is via /lanes/ensure (no worktree, no
branch fields) - the right choice for a main repo you want stage
detection on. Worktree mode (default) keeps the existing provisioning
flow but now requires a manually-typed branch name instead of deriving
one from the title.
- POST /lanes/worktree accepts an optional `branch`, validated via
`git check-ref-format --branch`; omitting it preserves the CLI's
existing auto-derived-branch behavior.
- New GET /lanes/browse lists a directory's immediate subdirectories,
backing a small folder-browse modal on both path fields - browsers
cannot expose an absolute path from a native picker, so this is
server-backed instead, consistent with the tool's local-first model.
Ports the two pieces of Shipyard's lane-gc.sh that match CCAM's actual
architecture: kill Playwright MCP processes reparented to pid 1 (owning
session died), cap hook logs over 10MB back to their last 2MB in place.
Drops auto-removing stale worktrees by age (conflicts with the
never-automatic-destroy rule), state archiving, and scratch-debris
sweep (different storage architecture / files CCAM doesn't generate) —
see docs/superpowers/specs/2026-08-05-lane-gc-design.md.
Root cause: the catch block only fell through to the auto-commit path when
`unmergedFiles().length && MERGE_HEAD exists` — but a merge rerere resolved
completely has ZERO unmerged files (git already staged the resolution), so
that guard was always false and the raw git error was rethrown instead.
Found via an audit against the Shipyard source this was ported from.
Fixed by checking MERGE_HEAD first (unconditionally — its absence means the
merge never started, a real failure), then branching on whether any files
are still unmerged. Added a real rerere fixture test (teach a resolution,
recreate the identical conflict, confirm mergeSync auto-commits) — the
existing test suite had no coverage for this path.
archiveActiveFeature was overwriting a feature's own title with the
LIVE lane's title on every clear/switch — a --title set on activation
silently disappeared. activateFeature restored stage/status/gate/CI/
stages/notes onto the live lane when switching back to a past feature,
but never links, so they vanished on reactivation. Both are fixed, with
a regression test for each (full activate/archive/reactivate round trip
for links).
Also fixes server/routes/lanes.js: the doc comment explaining GET
/:id/git's rationale had been left sitting above the newly-inserted
/:id/features routes instead of its own route.
docs/API.md's Lane features section described fields and behavior that
don't exist in the real routes (an "active" boolean, a POST response
containing "archivedPrevious", a "409 ESTALE" concurrency response) —
rewritten to match the actual request/response shapes exactly.
Gives each lane its own slot-derived runtime (ports, detached process
lifecycle, profile-driven hooks) and its own database/Redis logical
index/.env file, so two lanes running the same repo's stack at once no
longer share state. Machine-level DB/Redis credentials live at
~/.ccam/secrets.env (mode 0600, never returned by any route); a hook's
output is redacted of that password (raw and URL-encoded forms) before
it reaches a log file or the lane_hook_output websocket broadcast.
Wired into provision/up/reset/remove; reset accepts --keep-db to skip
the drop/recreate/migrate/reseed block entirely.
setStage() left detected_stage/signal/at untouched across a real transition,
so a prior task's leftover inference (e.g. `tests` from earlier work) both
misrepresented a fresh task's progress and — because recordDetection is
forward-only — silently rejected every real detection behind it until the
old one aged past DETECTION_TTL_MS. A real stage change now clears the
detection columns; a same-stage heartbeat leaves a live detection alone.
Also excludes `.superpowers/` from the `implement` node's Write detect rule
- brainstorm-companion scratch files were being counted as implementation
work.
fix(client): disable Node's default --experimental-webstorage in tests
Node 25 enables --experimental-webstorage by default, which defines a
broken global `localStorage` (no backing file configured) ahead of jsdom's
own full polyfill. Every test touching real localStorage failed with
"localStorage.clear/getItem is not a function" — not flaky, not test-specific,
just this one Node default. NODE_OPTIONS=--no-experimental-webstorage on the
test scripts lets jsdom's polyfill take over as before.
Internal SmartGift build of a Claude Code monitoring dashboard.
Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.
Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.
Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.