Scopes the second of E's remaining pieces: migration-collision preflight,
keep-ours merge driver for generated files, and the single sanctioned
origin/development-into-feature-branch merge. Agents and F's integrations
stay out of scope, per the roadmap's own subsystem split.
4 tasks: --qc boot flag + QC_BOOT_ENV, pipeline template JSON, the ported
skill text (Stages 0-14, integrations hardcoded off pending F), and a dry
run + docs. Corrected the pipeline-template task against the real node
schema (id/label/icon/gate/aliases, not the detect.stage sketch) and the
real getPipeline never-throws behavior during self-review.
Scopes the first of E's four independent pieces: --qc boot flag +
QC_BOOT_ENV, the pipeline template, and the ported skill text. Agents,
sync-base, and F's integrations are named but deliberately out of scope,
per the roadmap's own subsystem split.
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.
docs/API.md's Locks section described a timeoutMs request param, a
408-timeout response, and field names (acquiredAt/acquiredMs) that
don't exist anywhere in the actual routes.js — the server is single-shot
and never blocks; timing out is a CLI-only concept. Also fixes the
section being spliced into the middle of the pre-existing Sessions
heading and its content.
docs/LANES.md said the owner file stores an epoch in milliseconds
(it's seconds), that the CLI polls every ~1s (it's ~2s), that the lane
card shows a "waiting" state (no such server-side concept exists, only
who currently holds), and included a fabricated "manually transfer a
lock's holder identity" procedure that also contradicted the
never-touch-the-lock-directory etiquette rule stated right above it.
The lock-indicator commit's JSON edit re-serialized both files through
something that escaped every non-ASCII character (—, …, ·, ▶, ■) as
\uXXXX, touching ~180 unrelated lines for a two-key addition. No
functional change (JSON \u escapes are semantically identical) — just
restoring the literal-character convention every other locale file uses.
Help text for "lanes up|down" implied --no-build applied to both;
it's only read by up. Also commits the A3 profile-scaffolding design
spec and implementation plan that weren't yet in git.
Client-side rendering for the per-lane runtime facts (slot, ports,
database, Redis index, service liveness) added in the server-side
A1/A2 work, plus the doc updates (README, CLAUDE.md, docs/API.md,
client/server READMEs) describing the new profile.env keys, hook
environment contract, and REST endpoints.
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.
The sandbox's ambient FORCE_COLOR=3 was inherited by every ccam-cli.test.js
child process spawn, defeating the CLI's own "colors off when piped" default
and breaking every plain-text output assertion (help text, piped output,
offline-mode rendering).
Adds a working Dark/Light toggle (next to the language switcher, same row
as EN/VI) and re-themes the whole dashboard, not just the handful of
components that already used semantic tokens.
- Tailwind darkMode:"class" + CSS-variable color tokens (client/src/index.css,
tailwind.config.js): surface.0-5, border/border-light, accent/accent-hover,
fg.primary/secondary/muted, status.success/danger/warning. One class flip
on <html> re-themes everything — no per-element dark: variant pairs.
- useTheme() hook: localStorage-persisted, defaults to dark, no
prefers-color-scheme fallback (client/src/hooks/useTheme.ts).
- Mechanical, table-driven migration (scripts/migrate-color-tokens.mjs,
scripts/tokenize-status-colors.mjs, scripts/darken-status-colors.mjs) of
every raw neutral/gray/slate + emerald/red/amber Tailwind utility across
client/src onto the new tokens, so every badge/button/component pulls the
same shade per status/role instead of each picking its own.
- Palette values are the literal Radix Colors (radix-ui.com/colors) scale
constants — slate/blue/green/red/amber steps 1-12 — adopted after three
rounds of hand-picked values that kept overshooting (flat, then too dark,
then glaring); see docs/superpowers/specs/2026-07-31-color-redesign-
dark-light-mode-design.md for the full history and role mapping.
- PipelineMap: done/current/failed/passed-no-evidence/detected share one
visual language (border + text + translucent wash of the same status
color); `current` alone stays a solid accent fill, the one state that
gets to look bolder ("you are here").
- LaneCard: removed the stage/kind/auto-stage chips that duplicated the
Workspace lane-detail header already showing them.
Categorical/decorative hues (violet, indigo, cyan, teal, sky, rose, pink,
orange, yellow, and blue where it plays a role-coloring part e.g. message
bubbles) are deliberately out of scope — collapsing those onto shared
tokens would erase the distinction between different kinds of thing, not
a status.
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.
Two lane-card symptoms, one source: touchLaneFromHook.
needs_action was stamped on every Notification hook, including Claude
Code's bare idle nudge ("Claude is waiting for your input"), which fires
~60s AFTER Stop. Nothing later arrives to clear it, so the card kept a
permanent amber "needs you" while the user was simply not typing. The
nudge now clears the flag instead of raising it - it proves the CLI is
parked at an idle prompt. Permission/AskUserQuestion messages are
unchanged.
lane.status only moved through the dashboard run lifecycle (run_id), so
a lane driven by `claude` in a terminal read "idle" for the whole time
Claude was working in it. For lanes the dashboard did not launch, the
turn hooks now mirror it: UserPromptSubmit/PreToolUse/PostToolUse ->
running, Stop/SessionEnd/idle-nudge -> idle. SubagentStop is excluded (a
subagent finishing is not the end of the turn) and provisioning/failed
are never stomped.
Side effect: classifyLiveness treats status=running as "expect live", so
a turn with no hook for more than LANE_DEAD_SEC (300s) now shows the dead
dot. That is the detector working as designed; raise LANE_DEAD_SEC if a
long single Bash trips it.
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.