8a82895c65
Adds a root `ccam` plugin (`.claude-plugin/plugin.json`, `"source": "./"`) so
`/plugin marketplace add` + `/plugin install ccam@...` is enough on a machine
with nothing but Claude Code: no clone, no npm run setup, no manual npm start.
- scripts/plugin-bootstrap.js: SessionStart hook. Fast-path exit, Node >=22.5
gate (node:sqlite), mkdir lock with stale reclaim, deps installed into
~/.claude/agent-dashboard/runtime/ (never the plugin cache), legacy
checkout-hook cleanup (backed up), ~/.local/bin/ccam launcher, eager UI
build so client routes like /run work immediately, detached server spawn.
- scripts/plugin-open.js, scripts/plugin-doctor.js: /ccam-open, /ccam-doctor.
- server/index.js: DASHBOARD_CLIENT_DIST override (plugin cache is read-only).
- mcp/build/ is committed (plugin MCP servers start before any bootstrap could
build them) and kept honest by scripts/check-mcp-build.js (content hash,
not mtime), enforced by pre-commit when mcp/src changes.
- plugins/ccam-dashboard/.mcp.json moved under plugins/ccam/ with a working
${CLAUDE_PLUGIN_ROOT} path (the old relative path never resolved from a
marketplace-cached subdir).
- Docs: README, INSTALL, SETUP, ARCHITECTURE, CLAUDE.md, docs/PLUGINS.md,
docs/MCP.md, docs/CLI.md, docs/HOOKS.md.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
9.8 KiB
9.8 KiB
Claude Code Working Guide
Project mission
- Maintain a reliable local-first dashboard for Claude Code session monitoring.
- Preserve real-time behavior (hooks -> API -> SQLite -> WebSocket -> UI).
- Keep MCP integration production-ready for local use (
mcp/).
Repo map
server/: Express API, hook ingestion, SQLite access, websocket broadcast (includes optional git upstream checks androutes/updates.js, pluslib/workflow-ingest.jswhich ingests on-disk Workflow-tool run journals — fleets that emit no hooks).client/: React + Vite UI.scripts/: hook installer/handler, import, seed, cleanup utilities. (Update detection lives server-side inserver/lib/update-check.js; the dashboard never restarts itself — users run the printed command, surfaced in the UI and byccam update-check.)mcp/: local MCP server exposing dashboard operations as tools.mcp/build/is committed on purpose — plugin MCP servers start before any bootstrap could build them;scripts/check-mcp-build.js(content hash inmcp/build/.srchash, run by pre-commit and/ccam-doctor) keeps it honest. Rebuild withnpm run mcp:build, never hand-editmcp/build/..claude-plugin/: the marketplace plus the rootccamplugin manifest ("source": "./"— the whole repo is the plugin). Its hooks are inline inplugin.json; its commands live inplugins/ccam/commands/, which is NOT a subdirectory plugin.scripts/plugin-bootstrap.jsruns fromSessionStartand owns the writable runtime under~/.claude/agent-dashboard/runtime/— it never writes into the plugin cache, which Claude Code replaces on every update. Seedocs/PLUGINS.md.
Lanes
A lane is a durable unit of parallel agent work — one working directory, many Claude Code sessions over time. Lanes are keyed by cwd, not session_id, so they survive session restarts and allow the dashboard to show a pipeline that persists across runs.
Key points:
- Sessions are bound to lanes by longest path-boundary prefix match on their
cwd(set in hook data fromclaudeCLI). - A lane's stage is declared by the driving Claude session (via
ccam stage <stage> [--evidence "..."]), not inferred by the dashboard. - The console never writes a lane's stage. Declared stages come from
ccam stage(a skill calling the CLI command), and inferred stages come from tool-event detection only. The UI's run console and the Workspace page never callPOST /api/lanes/:id/stage— that boundary exists because the console observes tool calls but cannot see their outcomes. A lane's stage is the session's truth about progress; the console's observation of tool execution would not be proof of completion. - A detection expires, an evidence rule does not.
recordDetectionskips its forward-only comparison oncedetected_atis older thanDETECTION_TTL_MS(default 5 min), so a lane can move backwards between work sessions. That window changes only WHICH detection is current — it never relaxes declared-wins (an agent's own claim has no expiry) and never lets an inferred node renderdone. - Working-copy facts live at
GET /api/lanes/:id/git, never insideGET /api/lanes. That endpoint shells out to git three times; the lane list is polled and re-broadcast on every hook. A cwd that is not a readable repo returns{available:false}with HTTP 200 — a normal state, not a fault. Cards fetch it themselves every 30s and fail silently. - The Workspace console collapses with CSS, never by unmounting. Unmounting
RunConsoledisposes the run subscription and drops a live run's rendered history. - CCAM does not orchestrate: no chaining, no queue, no retry logic, no gate evaluation. The session in control makes all decisions; the dashboard records the claimed stage and shows evidence. The runtime layer (
ccam lanes up|down|hook) does not change this — it offers primitives a session calls; nothing in the dashboard sequences them. - The runtime never writes
stage,statusornotes— onlyslotandports.status=runningmeans an AGENT is working, not that a server is listening; merging the two would corruptclassifyLiveness. Boot failures live inLANES_ROOT/.state/lane<slot>/last-error.jsonand surface viaGET /api/lanes/:id/runtime. Same boundary as the console-never-writes-stage rule above. - A lane's stack is up or down as a computed fact, never a stored one.
runtimeFactsre-derives it from pid files and port probes on every read. A process dies to OOM, a straykill, a reboot — caching a truth CCAM does not control buys ghost state. Slot allocation is the opposite (fully controlled, must be race-free), so that one does live in the DB, underwithLaneLockplus a partial unique index. - A profile's
profile.envis parsed, never sourced. Hooks are executed deliberately; config is only read. Sourcing arbitrary shell from a user's repository into the dashboard process would be a code-execution path. Hook names always come from the fixed allowlist inserver/lib/lane-profile.js, never from a request. - Pipeline templates are JSON files (
server/data/pipelines/) with node definitions; custom templates override built-ins whenDASHBOARD_PIPELINES_DIRis set. - Nodes render in five states:
failed(rejected),current(now),done(with evidence),passed-no-evidence(claimed or skipped, amber),pending(not reached). - Liveness: a silent watcher (stage matching
/watch|poll/) is dead afterLANE_DEAD_SECseconds (default 300); a silent idle lane is at rest, not dead. - Never
rm -rfa lane. Destructive lane operations (reset,remove,purge) go throughserver/lib/worktree.js/server/lib/lanes.js, never a raw filesystem delete — that's what keeps the three-check destroy guard and the DB bookkeeping in lockstep. - Never build a git command as a shell string. Lane git operations use
execFile("git", [...args])with an explicit argument array (seeserver/lib/worktree.js'sgit()helper), never a concatenated/interpolated string passed to a shell. - Adopted lanes are not destroyable. A lane with
kind === "adopted"may never have its worktree reset or removed; "removing" one only drops the dashboard's own record of it. This is enforced in code (assertDestroyable's first check) — do not add a path that bypasses it.
See docs/LANES.md for full guide: stage reporting, custom templates, lane actions, and the five node states.
Non-negotiable engineering rules
- Preserve existing behavior unless explicitly asked to change it.
- Prefer minimal, reversible diffs.
- Never silently weaken safety controls around destructive actions.
- Keep docs updated when behavior, commands, file locations, or workflows change — apply the
update-project-docsskill automatically at the end of every change-set that alters behavior, config, interfaces, events, schema, CLI commands, or features (do not wait to be asked). - Every applicable source file you create or update (
.js/.ts/.tsx/.cjs/.mjs/.py/.sh/.css) must start with the copyright/authorship header — file overview + the exact line@author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>. Formats and audit script:.claude/skills/file-headers/(verify withbash .claude/skills/file-headers/scripts/check-headers.sh). This binds every coding agent (Claude Code, Codex, or others).
Commands you should know
- Setup:
npm run setup - Dev:
npm run dev - Prod build/start:
npm run buildthennpm start - Server tests:
npm run test:server - Client tests:
npm run test:client - MCP install/build/start:
npm run mcp:install,npm run mcp:build,npm run mcp:start - MCP build freshness:
npm run mcp:check-build(must pass whenevermcp/srcchanges) - MCP typecheck:
npm run mcp:typecheck - CLI (after setup):
ccam <command>— terminal access to the full dashboard surface (bin/ccam.js;ccam helplists commands)
Testing and verification policy
- Backend changes: run
npm run test:serverbefore finishing. - Frontend changes: run
npm run test:clientwhen relevant. This includes per-screen render snapshots (client/src/pages/__tests__/screens.snapshot.test.tsx). If a UI change is intentional, review the snapshot diff and regenerate baselines withcd client && npx vitest run -u; never blindly update snapshots to make tests pass. - MCP changes: run
npm run mcp:typecheckandnpm run mcp:build. - If you cannot run a verification step, state exactly what was not run and why.
Change guidelines by area
- API routes: preserve response shapes unless change is requested and documented.
- Database: avoid schema changes without migration-safe logic.
- Hooks: keep fail-safe and non-blocking behavior.
- WebSocket: keep message types stable and backward-compatible.
- Documentation: include exact commands and paths; keep markdown examples runnable.
Agent behavior
- Explore first, then implement.
- For larger tasks, propose/check a short plan before broad edits.
- Use file-specific rules in
.claude/rules/when working in scoped areas. - Use project skills from
.claude/skills/for repeatable workflows. - Use
.claude/agents/subagents for focused review or investigation passes. - Declare lane stage even in plain chat, not just inside skills.
ccam stage(seedocs/LANES.md§ Reporting a stage) is a reporting command, not a skill-only ritual — any Claude session working inside an adopted lane's cwd should call it on real stage transitions (starting to plan, starting to implement, running tests, opening the PR, etc.), whether or not a skill is driving. Tool-event detection (server/lib/stage-detect.js) only ever paints the amber "detected" badge, never the bluecurrentring — a lane worked entirely through plain chat with noccam stagecalls will show a stalecurrentstage no matter how much real work happens. Skip it only whenccam stagereports no lane owns the cwd (not adopted).