Project Design — CADENCE
Name: CADENCE — named for the rhythm of its core DRAFT → BUILD → SETTLE loop. Locked as of Phase 12 /
v0.2.0-rc.1(2026-05-14). The backronym “Coordinated AI-Driven Engineering with Notifications and Customizable Execution” was retired 2026-06-02 as a forced fit (it over-billed the minoranomaly-notifyfeature); the word CADENCE itself is the keeper. Historical KEEL phase artifacts under.keel/phases/remain by design (transition narrative).
Living design document. Captures intent, decisions, deferrals. Lives at repo root, outside
.cadence/, so the tool isn’t planning itself with itself.
Last updated: 2026-07-06
1. What this project is
Section titled “1. What this project is”A customizable, AI-assisted development framework that lets a user dial in how much they want to drive vs. how much the AI drives — without giving up the quality gates that make AI-generated work trustworthy.
Inspired by GSD (Get Shit Done). Intended as a faster, more efficient, more customizable alternative to GSD — not a lighter one. The gates exist; the user picks which ones fire.
Current architecture (as of v1.60.0): one core engine, reached through
three surface categories — the host-agnostic cadence CLI, host
adapters (Claude Code and Codex, D5/D9) that wire the engine into a specific
coding agent’s lifecycle hooks, and the MCP server (cadence mcp serve,
D11) for any MCP-capable host. See the README’s surface-model
section and
docs/host-adapters.md for the current, code-verified
shape; this document tracks decisions, not the live package inventory.
2. What this project is NOT
Section titled “2. What this project is NOT”- Historical (pre-Phase 60, v1): “Not a host-agnostic framework at any cost. v1 is Claude Code only. Multi-host is a v1.x/v2 concern.” Current state: that anti-goal has been superseded by D5/D9 — Cadence now ships two host adapters (Claude Code, Codex) plus an MCP server. The spirit endures as: not a host-adapter-sprawl project. Cadence keeps one engine and prefers MCP as the generic host-interoperability path; bespoke host adapters exist only where host hooks add value that MCP cannot provide, such as ambient edit-time gates (boundary checks, live anomaly detection). Codex is a shipped conformance consumer of the host-adapter contract, not a speculative one (D9). MCP is supported for the imperative loop only — it does not provide ambient edit-time gates absent host hooks (D11).
- Not an autopilot. Even the most hands-off profile must surface anomalies.
- Not a structural-only verifier. “Tasks marked DONE” ≠ “AC delivered.” Behavioral verification is mandatory.
- Not a slow framework. The point is GSD’s discipline without GSD’s wall-clock cost.
3. Design center — three pillars
Section titled “3. Design center — three pillars”3.1 Profiles (user-involvement axis)
Section titled “3.1 Profiles (user-involvement axis)”Three modes, user-selected per project or per phase:
| Profile | Posture | Gates user must approve |
|---|---|---|
| strict | Full control. Every step is a checkpoint. | DRAFT review · plan review · per-task verify · settle verify |
| standard | Major-step gating. | DRAFT approve · settle verify |
| auto | Hands-off; the AI drives. | None by default — but anomalies pause + notify |
KEEL’s original behavior was auto without the notify part — closing that gap was the first priority, shipped as anomaly-notify (Phase 17).
3.2 Behavioral verification (hybrid)
Section titled “3.2 Behavioral verification (hybrid)”settle must answer “does the built code actually do what the AC promised in plain English?” — not just “are tasks marked DONE?”
Hybrid implementation:
-
Default — structural (current
--auto) + test-coverage proof. Each AC must have ≥1 test that references it.- Convention (locked in Phase 14): the AC id token (
AC-N) must appear somewhere in a test file’s contents. Typical placement: insidedescribe()orit()strings, but any occurrence (even a comment) counts. The gate is binary per AC: at-least-one-linked-test or refuse. - Scanner: walks
verification.testGlobsfrom.cadence/config.json(defaults:packages/**/*.test.ts(x)). Whole-file text search via/\bAC-\d+\b/g; per-file deduplication. - Bypass per-invocation:
cadence settle run --allow-missing-coverageskips the check entirely. Explicit--ac AC-1=pass:noteoverrides bypass the gate for that AC only.
- Convention (locked in Phase 14): the AC id token (
-
--deep— spawn an independent verifier (Phase 15, shipped). Three providers viaconfig.verifier.provider:mock(default, deterministic linked-test rule, offline),anthropic(opt-in viaANTHROPIC_API_KEY; usesmessages.parse()with a Zod schema for per-AC verdicts; system prompt is prompt-cached), orlocal(OpenAI-compatible/v1/chat/completions, e.g. Ollama; requiresCADENCE_LOCAL_BASE_URL+CADENCE_LOCAL_MODEL; per-gatemodelconfig key overrides env; falls back tomockwith a stderr warning if base URL or model is unset). Refuses to settle on any non-overridden AC the verifier markspass=falseunless--force. Transport failures gated by--allow-verifier-failure. Per-AC results recorded intoSUMMARY.json deepVerify.Activation (v1.22, no new D-number — additive over this decision). The provider model above ships with
mockas the default, so the “refuses to settle unverified work” guarantee is not live out of the box.cadence activateis the guided on-ramp: it writesverifier.provider(the deep-verify seam by default,--allfor every seam), validates the key with a minimal live anthropic ping, and never persists the key (only the provider name). Acadence doctorverification-readinesscheck + pointers fromquickstart/config explain/initmake the mock-vs-real state diagnosable and the verb discoverable. This is an activation/legibility layer — it does not change verdict logic, the gate set, or the six-seam provider model. -
--interactive— shipped in Phase 16. Walks each AC sequentially with its given/when/then text + linked test refs + touched files, prompts the user forpass | fail | skipplus an optional note. Pass/fail verdicts win over structural/deep derivation; skip falls through. Per-AC results recorded intoSUMMARY.json interactiveVerify. Refuses on TTY-less invocations unless--no-interactivebypasses; tests drive the walker viaCADENCE_PROMPTER_SCRIPTenv var seam.
3.3 Anomaly notification (for auto profile) — shipped (Phase 17 + 17.2)
Section titled “3.3 Anomaly notification (for auto profile) — shipped (Phase 17 + 17.2)”Two emission surfaces share one Notifier transport:
- Settle-side (Phase 17.1) —
collectAnomalies(...)walks the settle context and dispatches a batch throughselectNotifier(config)at SETTLE close. - Hook-side (Phase 17.2) —
handlePreToolEditdetectsfiles-outside-boundaryat edit time and dispatches one event per outside path as the edit is about to happen. Detection-only — the hook never refuses the edit. - Coherence-side (Phase 23.2) —
cadence draft checkandcadence draft approvedispatch onecoherence-warnevent per warn-severity coherence issue. Block-severity issues already refuse loudly; warns are the soft signal the auto profile needs to know about.
All three surfaces gate on 'anomaly-notify' being in the effective gate set (auto + standard×{standard,complex} cells). Each event carries ts: ISO8601 (offset-aware, emitter-stamped via new Date().toISOString() — Phase 17.3). Seven event types:
| Type | When |
|---|---|
ac-blocked |
A task ended BLOCKED |
ac-needs-context |
A task ended NEEDS_CONTEXT |
coverage-bypassed |
--allow-missing-coverage flipped an active test-coverage gate |
files-outside-boundary |
A touched file is not in any task’s declared files: list (settle-time reconciliation OR hook-time per-edit detection) |
verifier-failure |
The --deep verifier transport itself failed |
force-used |
--force bypassed at least one failing structural / deep / interactive verdict |
coherence-warn |
A severity: 'warn' coherence issue fired from draft check or draft approve (context.source distinguishes which) |
loop-violation |
A LoopViolationError was thrown — context.expected vs context.actual record the mismatch + context.source identifies the command |
per-task-fail |
The per-task verifier returned refuse at cadence build task <id> --status=DONE (Phase 24.2). context.bypassed is true when --allow-per-task-failure recorded DONE anyway; false when DONE was blocked |
code-review-high |
The code-review verifier returned a HIGH-severity finding at settle time (Phase 24.3). One event per HIGH finding; context.file, optional context.line, context.message, context.provider, context.bypassed (always true at emission — refused settles exit before this emission point) |
Transports (.cadence/config.json: notify.transport):
stderr(default) — one line per event:cadence anomaly [severity] type: messagefile— NDJSON appended tonotify.file(defaults to.cadence/anomalies.log); operator owns rotationnone— drop on the floorwebhook— POST{events: AnomalyEvent[]}JSON tonotify.webhook.url. Generic bridge primitive (Slack/Discord incoming, Zapier/n8n catch, continuity-runtime ingester, …). Optionalheaders(for Authorization) andtimeoutMs(default 5000). Failure (non-2xx / network / timeout) degrades to one stderr warning — the URL itself is never logged (may carry a secret).
Notifier failures degrade to a single stderr warning and never block settle (or the hook). New transports plug in via the Notifier interface — no slack/webhook bridge is built in.
Read recorded events via cadence status anomalies [--since <iso>] [--type <t>] [--limit <n>] — parses .cadence/anomalies.log newest-first, skips malformed lines (count reported on stderr), supports filter by AnomalyType and an inclusive ts >= --since boundary (Phase 17.3 lit this up — events now stamp ts at emission time on both surfaces).
4. Phase model — LOCKED
Section titled “4. Phase model — LOCKED”Two axes: tier (phase size) × profile (user-involvement). Verification level is implicit per cell with explicit override flags.
4.1 Gate universe
Section titled “4.1 Gate universe”| Cost | Gate |
|---|---|
| Free (always fire) | Coherence check · structural verifier (enforcement wired Phase 39.2) · build/test must pass (enforcement wired Phase 39.2; config-gated on verification.testCommand) |
| Cheap | DRAFT-read mtime check (Phase 23.1) · test-coverage proof per AC (Phase 14) · anomaly notify (Phase 17) |
| Medium | Approve gate (manual click) (Phase 24.1) · per-task verify (Phase 24.2) · code review agent (Phase 24.3) |
| Expensive | Independent verifier agent (--deep) · interactive AC verdict (--interactive) · plan review (Phase 25.1) · security audit (Phase 25.2) |
Required-skill enforcement (Phase 34.1) is intentionally not a matrix cell. A phase declares
requiredSkills(DRAFT frontmatter ∪config.skillAudit.required);settle runrefuses on a shortfall (or--allow-skill-audit-miss) and emits askill-audit-missanomaly unconditionally — distinct from theanomaly-notify-gated anomalies, since declaring skills is the opt-in and strict cells (which lackanomaly-notify) must still leave an audit trail. Closes ROADMAP open-question 23.4.
Plan-review convergence (Phase 35.1) —
plan-review(Expensive, Phase 25.1) is no longer one-shot: atdraft approveit tracks attempts in the<id>-PLAN-REVIEW.jsonsidecar and, afterconfig.convergence.maxAttempts(default 3) failing attempts, hard-escalates with an unconditionalplan-review-unconvergedanomaly (un-gated likeskill-audit-miss, since plan-review’s only cell — strict×complex — lacksanomaly-notify). Same gate cell; convergence changes how it fails, not whether it fires. ThenextConvergenceprimitive is reusable (survey #4’s settle-gate attach-point).
Spec stage (Phase 36.1) — a pre-DRAFT
SPECloop position (cadence spec new/check/approve).spec approveruns a convergent spec-review gate (own<id>-SPEC-REVIEW.jsonsidecar) reusing the Phase 35.1nextConvergenceprimitive verbatim; escalation emits an unconditionalspec-review-unconvergedanomaly; override--allow-spec-review-failure(bypasses any fail). Opt-in by use (no matrix cell); host-agnostic (cadence scaffolds+validates, the agent/human authorsSPEC.md). The SPEC→DRAFT content auto-seed is delivered (Phase 38.1, #1b):draft newreads the sibling same-idAPPROVEDSPEC and pre-fills the DRAFT Objective + ACs (lossless, via a purerenderDraftBody; byte-identical legacy scaffold otherwise).
Code-review convergence (Phase 37.1) —
code-review(Expensive, Phase 24.3; cells strict×standard, strict×complex, standard×complex) is no longer one-shot: atsettle runit tracks attempts in the<id>-CODE-REVIEW.jsonsidecar and, afterconfig.convergence.maxAttempts(default 3, the shared Phase 35.1 knob) failing attempts, hard-escalates with an unconditionalcode-review-unconvergedanomaly (un-gated likeskill-audit-miss/plan-review-unconverged, since code-review’s strict cells lackanomaly-notify). The siblingcode-review-highanomaly keeps its Phase 24.3anomaly-notifyguard. Same gate cells; convergence changes how it fails, not whether it fires;--force/--allow-code-review-failurestill bypass any fail. Reuses thenextConvergenceprimitive (third attach-point after plan-review #2 and spec-review #1) — the final v1.2 feature-expansion item.
UI-SPEC gate (Phase 205, rec-20260711-004) — an opt-in
<id>-UI-SPEC.mdsibling to SPEC (cadence spec new --ui), with per-component Layout & Tokens/Precedent References nested under each### <Component>so the convergentui-spec-reviewgate (reusing the Phase 35.1nextConvergenceprimitive, own<id>-UI-SPEC-REVIEW.jsonsidecar, ownui-spec-review-unconvergedanomaly, own--allow-ui-spec-review-failurebypass) can attribute a finding to the specific under-specified component. Runs inside the existingcadence spec approvecommand — no new command, no new loop position.draft newseeds an approved UI-SPEC’s content into a new## UI ContractDRAFT section (bold-text rendering, no nested headings) between Acceptance Criteria and Tasks.
4.2 Default gates per cell (deltas only; free gates always fire)
Section titled “4.2 Default gates per cell (deltas only; free gates always fire)”| quick-fix | standard | complex | |
|---|---|---|---|
| strict | DRAFT-read · approve · test-coverage · interactive settle | + per-task verify · code review | + plan review · security audit · interactive per-AC |
| standard | test-coverage | + DRAFT-read · approve · anomaly notify | + code review · verifier agent (--deep baked in) |
| auto | anomaly notify | + test-coverage · anomaly notify | CAP — soft refuse, override with --allow-auto-complex |
4.3 Locked decisions on the matrix
Section titled “4.3 Locked decisions on the matrix”| # | Question | Decision |
|---|---|---|
| M1 | Verification: own axis or implicit? | Implicit defaults per cell + explicit --deep / --interactive flags for override. Two axes to teach; flags for edge cases. |
| M2 | Cap shape for auto+complex? | Soft cap. Refuse by default; override with --allow-auto-complex. Tighten to notification-target cap once continuity-runtime ships. Shipped — Phase 21.1 (both cadence settle run and cadence draft approve refuse the cell without the flag). |
| M3 | Who picks tier? | AI proposes tier with rationale in DRAFT. Coherence check verifies against touched-files count + AC count. User can override. Catches the AI-lowballs-to-skip-gates failure mode. |
| M4 | Profile scope — per-project or per-phase? | Project default in config + per-phase override in DRAFT frontmatter. Solo user sets auto once; bumps sensitive phases to strict as needed. |
4.4 Cap rationale
Section titled “4.4 Cap rationale”auto + complex is the runaway-LLM scenario: high blast radius + zero supervision. Soft cap (M2) names the risk without removing autonomy. Once the user’s continuity-runtime project ships, the cap can tighten to “requires a working notification target” — making “auto” safe by construction.
5. Decisions locked this session
Section titled “5. Decisions locked this session”| # | Decision | Rationale |
|---|---|---|
| D1 | Project = GSD done better, not GSD-lite | Quality gates exist; speed comes from customization, not omission |
| D2 | Three user-involvement profiles (strict/standard/auto) | Captures the persona spread in real use |
| D3 | Behavioral verification mandatory, hybrid design | Structural-only is unacceptable |
| D4 | Anomaly-notify required for auto profile | Hands-off ≠ unsupervised |
| D5 | Primary bespoke host = Claude Code; Codex adapter restored as a shipped conformance consumer | Claude Code remains the reference integration for ambient edit-time gates. MCP is the preferred path for most new hosts, but the repo now ships @thomas-powers-jr/cadence-host-codex; docs and package reality must stay aligned. |
| D6 | Top-level planning doc (this file) lives outside .cadence/ |
Avoid the tool planning its own rewrite with itself |
| D7 | Name = CADENCE | Locked; backronym refinable |
| D8 | Tier × profile matrix locked (Section 4) | M1–M4 settled |
| D9 | Codex disposition amended = restored adapter, no broad HostCapabilities revival | Historical archive/collapse remains part of the transition record, but current main ships packages/host-codex/ as the second host-adapter contract consumer. |
| D10 | Session handoff = two engine commands (handoff/resume), not a loop phase (Phase 46) |
Promotes an unowned convention (the reserved .cadence/handoff/ dir + state.session.lastHandoff field) to first-class, host-agnostic commands. resume is read-only by design — live state.json stays authoritative, so a stale doc can never overwrite machine state. cadence handoff is core’s first read-only git shell-out (packages/core/src/handoff/git-facts.ts, via execFile with fixed arg arrays, never a shell) — a deliberate new dependency direction, best-effort and non-throwing. lastHandoff stamping is default-on (so resume finds the freshest doc reliably), opt-out via --no-stamp. Cross-branch handoff discovery is an explicit non-goal — a host/skill concern, not the engine’s. |
| D11 | MCP is a supported surface category on the single engine (cadence mcp serve, Phase 58) |
(Historical framing: “CLI · Claude-Code hooks · MCP — one engine, three ways to drive it,” written when Claude Code was the only host adapter. Current-state clarification: the three surface categories are CLI, host adapters (now Claude Code + Codex, D9), and MCP — MCP remains its own category, not renumbered by Codex joining the host-adapters category.) MCP exposes the imperative loop only (a curated read+write tool set over stdio); it wraps the same service functions the CLI renders, so command-boundary gates (coherence, the settle gate stack, spec-review) run unchanged. Ambient edit-time gates (the pre-tool-edit boundary check) require host hooks and are therefore unavailable over MCP — the surface degrades gracefully, it does not run ungated. This is one surface, not multi-host adapter pluralism — the bespoke Claude-Code adapter stays the reference integration for ambient gating, and the D5-era anti-goal (“no multi-host complexity before single-host is solid”) still holds. The MCP SDK is a lazy-loaded core dependency: ordinary CLI commands never load it. stdio-only by design (no HTTP/remote/auth/multi-tenancy — a future additive call). Deepened in v1.16 (phases 75–78, additive — no new D-number): the surface gains the two remaining MCP primitives — Resources (.cadence/ artifacts under a read-on-demand cadence:// scheme; no subscriptions) and Prompts (guided workflows incl. the cadence-scout dialogue, sourced from a shared cadence-types guidance module so the slash commands and MCP share one source of truth) — plus five tool-parity additions (handoff/resume/recommendation_add/recommendation_promote/doctor) and a cadence mcp install zero-config helper that non-destructively writes/merges .mcp.json. Still imperative-surface-only, still stdio, still no ambient gates. |
| D12 | deep-verify reads the actual diff (Phase 70, 2026-06-06) |
The gate previously sent diff: '' to the AI verifier, so “deep verification” judged ACs on test-linkage + filenames only — structurally blind to the implementation, even with a real provider whose prompt demands it judge “the supplied diff.” The gate now feeds the verifier the real phase diff (the memoized git diff HEAD already shared with code-review), bounded by verifier.diffCapBytes (default 256KB) and truncated with an explicit marker when oversized. Run-level provenance (deepVerifyMeta: diffProvided/diffBytes/truncated/filesCount/provider/model) is written to the SUMMARY so a verdict is auditable. The mock-fallback banner (Phase 71) now fires on the gate’s real firing condition (--deep or gate-set membership), not just --deep, so a standard × complex settle never runs mock verification silently. |
6. Decisions deferred
Section titled “6. Decisions deferred”| # | Item | Why deferred |
|---|---|---|
| Resolved — see Section 4 | ||
Resolved — Phase 18.1. Slash commands, settings, root package.json, state metadata, testkit fixture, install.ts legacy-eviction all on the cadence side; intentional history (DESIGN.md §8 rejected-names table, README Phase 12 banner) preserved. Backronym wording refinement is parked — the word is the keeper. |
||
Amended by current package reality. The earlier archive + collapse decision is historical; packages/host-codex/ now ships as a supported adapter and conformance consumer. |
||
Resolved — Phase 17 + 17.2 + 17.3 + 19.1. Four transports shipped: stderr (default) / file (NDJSON) / none / webhook (POST JSON to any URL). Generic webhook primitive avoids baking a specific bridge into cadence — continuity-runtime / Slack / Discord / Zapier / n8n / etc. all hang off the same contract. |
||
Resolved — Phase 14. AC id token (AC-N) anywhere in a test file’s contents; binary per-AC; scanner walks verification.testGlobs from .cadence/config.json (default packages/**/*.test.ts(x)). |
||
--deep) |
Resolved — Phase 15. Two providers: mock (deterministic, offline, linked-test rule) + anthropic (opt-in via ANTHROPIC_API_KEY, prompt-cached system prompt, Zod-typed per-AC verdicts via messages.parse()). |
7. Anti-goals
Section titled “7. Anti-goals”- ❌ Adding back every GSD gate (defeats the speed goal).
- ❌ Removing quality gates to chase speed (defeats the quality goal).
- ❌ Making the user read a 4-page DRAFT before every phase (defeats the auto profile).
- ❌ Trusting “tasks DONE” as proof of “AC delivered” (defeats the verification goal).
- ❌ Adding multi-host complexity before single-host is solid.
8. Name — LOCKED: CADENCE
Section titled “8. Name — LOCKED: CADENCE”8.1 Retired backronym (dropped 2026-06-02)
Section titled “8.1 Retired backronym (dropped 2026-06-02)”The launch placeholder backronym, kept here as a record. It is no longer used
in the README, docs/, or the cadence --help banner — it over-billed
Notifications (the minor anomaly-notify safety floor) relative to its real
weight, so it was dropped as a forced fit. The word CADENCE — the rhythm of the
loop — stands on its own (see §8.2).
CADENCE — Coordinated AI-Driven Engineering with Notifications and Customizable Execution
- Coordinated — the loop has structure; AI and human stay in sync
- AI-Driven — the AI does the typing
- Engineering — software engineering, not freeform chat
- Notifications — anomaly notify, the safety floor
- Customizable Execution — the three-profile system; user picks the gates
8.2 Why CADENCE works
Section titled “8.2 Why CADENCE works”- Rhythmic; suggests pace + repetition without forcing the loop framing
- Verb-friendly: “run cadence,” “cadence shipped this,” “open cadence”
- Tool-agnostic: doesn’t lock to nautical, doesn’t lock to one host
- Acronym = pure bonus; the word stands alone if the backronym ever shifts
8.3 Rejected / set-aside
Section titled “8.3 Rejected / set-aside”| Name | Status | Reason |
|---|---|---|
| KEEL | retired (will rename) | Acronym overfits v0 loop-only design |
| AEGIS | strong runner-up | Slightly heavier connotation (military/protection); CADENCE feels truer to the rhythm of the work |
| HELM | strong runner-up | Tighter acronym fit but narrower metaphor; CADENCE generalizes better |
9. Cost of the rework
Section titled “9. Cost of the rework”Phases already shipped on KEEL that the new design changes:
- Phase 02 (Codex host) + Phase 04 (HostCapabilities) + Phase 09 (Codex skill codegen) — multi-host work. ~3 phases of effort to deprecate/archive in v1.
- Phase 06 (
settle --auto) — structural verifier. Replaced/wrapped by hybrid behavioral verifier. - Phase 10 (smoke-test fixes) — these stay, they’re orthogonal.
Roughly: 4 phases of work needs revisit. Not all is throwaway — schemas, state engine, hook dispatcher, slash-command codegen for Claude Code are all kept.
10. Next concrete steps
Section titled “10. Next concrete steps”Lock the name (CADENCE).✓Phase tier × profile cap matrix.✓ (Section 4)Codex disposition (F3).✓ (restored adapter, no broad HostCapabilities revival)Archive codex + collapse HostCapabilities✓ (Phase 11)Rename rollout✓ (Phase 12 /v0.2.0-rc.1)- Plan + build the verifier hybrid — in progress.
Phase 13 — Profile system foundation✓Phase 14 — Test-coverage proof default verifier✓Phase 15 —✓--deepindependent verifier agentPhase 16 —✓--interactivehuman-verdict modePhase 17 — Anomaly notify transport✓Phase 17.2 — Hook-side detection +✓status anomaliesreaderPhase 17.3 —✓AnomalyEvent.ts+ live--sincefilter
Phase 18.1 — F2 physical rename rollout✓Phase 19.1 — F4 webhook transport✓Phase 21.1 — auto × complex soft cap (M2)✓Phase 23.1 — draft-read mtime gate✓Phase 23.2 — coherence-warn anomaly emission✓Phase 23.3 — loop-violation anomaly emission✓Phase 23.4 — skillAudit wiring (✓invokedtracking; tokenUtilization deferred)Phase 24.1 — manual approve gate (interactive Y/N at✓draft approve)Phase 24.2 — per-task verifier agent (gate at✓build task --status=DONE)Phase 24.3 — code-review verifier agent (gate at✓ (closes v0.5.0 medium-gate milestone)settle run)Phase 25.1 — plan-review verifier agent (gate at✓draft approve, strict×complex)Phase 25.2 — security-audit verifier agent (gate at✓ (closes v0.6.0 expensive-gate milestone)settle run, strict×complex)Phase 26.1 —✓cadence initUX polish (name prompt, gate-profile heuristic, post-init summary)Phase 26.2 —✓CLAUDE.mdscaffold (managed-marker,--claude-mdregenerate)Phase 26.3 —✓ (closes v0.7.0 operator-ergonomics milestone)status anomalies --tail/--followPhase 27.1 — GitHub Actions tests-on-PR + Dependabot + lint fixes✓ (closes v0.8.0 CI milestone)Phase 28.1 — v1.0.0 release (version bump 0.3.0 → 1.0.0, CHANGELOG cut, annotated tag)✓ (v1.0.0 — roadmap complete)Phase 29.4 (F2 pulled forward) —✓ (v1.1 publish-blocker from 29.1 shakedown)initlayout-detectedtestGlobs:packages/→ workspace glob, else**/*.test.ts(x); summary reports detected layoutPhase 29.6 — 29.1 doc/ux remediation: F1 (✓ (29.2/29.3 resource-blocked; 30.1 publish gate cleared of open 29.1 defects)--localinstall warning), F6 (init non-TTY-approve hint + README), F4 (preset/gate-profile summary disambiguation) + consolidated29-04-REMEDIATION.mdledgerPhase 30.1 — local LLM provider (OpenAI-compatible /v1/chat/completions; CADENCE_LOCAL_BASE_URL/MODEL; warn+mock fallback)✓Phase 30.2 — build-per-task spawn-CLI test block-timeout (2nd parallel-load flake after 29.5; v1.2 test-infra lane still deferred)✓Phase 29.2 — expensive-gate live exercise on Ollama✓qwen3-coder:30b(zero cloud spend; ROADMAP anthropic→local divergence documented);29-02-EXPENSIVE.mdPhase 29.7 — 29.2 remediation: G1 (deep-verify local prompt id-binding + 2 retries), G2 (failed-provider stamp), G3 (plan-review pass-time artifact); G4 withdrawn✓ (all 5 gates live-verified on local)Phase 29.3 — interactive/approve TTY exercise (human-driven;✓29-03-TTY.md)Phase 29.8 — 29.3 remediation: T2 (approve-prompt feedback), T3 (✓ (all 3 shakedowns now closed; publish gate clear of open shakedown defects)build taskid validation), T4 (interactive skip falls through to structural derivation)Phase 31.1 — user-guide docs/ tree (quickstart/concepts/cli/claude-code/providers + reference) + command-drift guard + slimmed README✓Phase 32.1 — test-infra flake root-fix: shared✓vitest.shared.tsbase (testTimeout/hookTimeout/maxForks) +tempRepormdir retry + revert 29.5/30.2 per-test timeout band-aids (pulled the ROADMAP v1.2 test-infra lane forward; 3rd parallel-load recurrence)Phase 33.1 (ROADMAP “Phase 30.1”) — publish pipeline reversible proof: metadata hardening (license/publishConfig/repository, per-pkg LICENSE/README),✓scripts/publish-proof.mjs(ephemeral verdaccio real publish + clean-install, noworkspace:leak, both bins run, Windows-safe teardown), public--dry-run+ tarball-clean (types 39 / core 240 / host 36 files, no src/tests/.cadence);@thomas-powers-jr/cadence-testkitprivate; real public publish / provenance /release.yml/ changesets deferred to a named v1.2 public-release milestonePhase 34.1 (closes ROADMAP open-question 23.4) — required-skill enforcement:✓DraftZ.requiredSkills∪config.skillAudit.required→ effective set written tostate.skillAudit.required; settle-time check (declaration = opt-in, NOT a gate-matrix cell), inert when empty, skip+warn when telemetry off, else refuse + unconditionalskill-audit-missanomaly unless--allow-skill-audit-missPhase 35.1 (v1.2 feature-expansion #2) — review-convergence loop primitive: pure✓nextConvergence(reusable by #4);plan-review@approve now bounded — attempts/history in the<id>-PLAN-REVIEW.jsonsidecar, reloop on fail, hard-escalate atconfig.convergence.maxAttempts(default 3) with an unconditionalplan-review-unconvergedanomaly, override = existing--allow-plan-review-failure. No state.json / gate-matrix changePhase 36.1 (v1.2 feature-expansion #1) — brainstorm→spec stage: new✓SPECloop position +<id>-SPEC.mdartifact +cadence spec new/check/approve;spec approveruns a convergent spec-review gate reusing Phase 35.1nextConvergence+ sidecar/history + unconditionalspec-review-unconvergedanomaly +--allow-spec-review-failure(35.1 flag semantics). Host-agnostic (scaffold+validate); SPEC→DRAFT auto-seed deferred (#1b)Phase 37.1 (v1.2 feature-expansion #4, final) — code-review convergence at settle: the Phase 24.3✓code-reviewgate is no longer one-shot. It reuses the Phase 35.1nextConvergenceprimitive verbatim — attempts + append-onlyhistoryin a new<id>-CODE-REVIEW.jsonsidecar (pass := no HIGH; legacy/absent → 0), reloop on HIGH, hard-escalate atconfig.convergence.maxAttempts(default 3, shared knob) with a new unconditionalcode-review-unconvergedanomaly. The Phase 24.3--force/--allow-code-review-failurebypass contract is preserved verbatim (bypasses any fail; existing settle-code-review tests green). No state.json / gate-matrix changePhase 38.1 (v1.2 feature-expansion #1b — closes #1) — SPEC→DRAFT auto-seed:✓cadence draft newreads the sibling same-idAPPROVED<id>-SPEC.mdand pre-fills the DRAFT Objective + ACs (lossless incl. AC name) via a purerenderDraftBody; byte-identical legacy scaffold when no/non-APPROVED/unparseable SPEC; warn+empty fallback, never refuses. AdditiveAcceptanceCriterionZ.name(back-compat default; spec-parser & draft-parser populate it from the AC head); no state.json/config/gate changePhase 46 — session handoff/resume: two host-agnostic engine commands (✓cadence handoffwrite +cadence resumeread-only replay) promote the previously-unowned.cadence/handoff/convention to first-class. The SESSION doc has a machine-filled zone (loop + read-only git facts + reusedrunContext('handoff')packet, correct by construction) and an empty narrative zone.handoffstampsstate.session.lastHandoffby default (--no-stampopts out); refuses to clobber an existing same-day doc (exit 2) without--force.resumemutates nothing (state.json byte-unchanged), prefers thelastHandoffpointer then globsSESSION-*.md, and prints a drift note when the doc’s loop position ≠ live state.git-facts.tsis core’s first read-only git shell-out (execFile, fixed arg arrays); cross-branch discovery left to the host/skill layer (non-goal). +2 Claude Code slash commands (9 → 11). See D10Phase 58 — MCP server surface (✓cadence mcp serve): a third surface on the single engine (CLI · Claude-Code hooks · MCP) so any MCP-capable host can drive the loop over stdio with no bespoke adapter. The 10 curated commands (progress/status/recommendread;draft new/check/approve,build task,settle,spec new/approvewrite) had their logic factored into pure*Service(repoRoot, args, io)functions underpackages/core/src/services/; the CLI action wiresioto the process streams (byte-identical output — all prior tests green), the MCP tool handler wires it to buffers and serializes structureddata. Excludesinit/config/doctor/install/handoff/resume. Command-boundary gates run unchanged; ambient edit-time gates need host hooks (unavailable over MCP).@modelcontextprotocol/sdkis a lazy-loadedcoredep (proven off the CLI hot path via amodule.registerload probe). MCP tests use the SDK in-memory transport against a testkit ephemeral repo. No new package;coreminor bump. See D11
Sequencing rationale: remove dead surface before rename (smaller rename); rename before verifier (verifier born in correct namespace).
Publish pipeline (v1.1 — reversible proof)
Section titled “Publish pipeline (v1.1 — reversible proof)”Three packages publish: @thomas-powers-jr/cadence-core, @thomas-powers-jr/cadence-types, @thomas-powers-jr/cadence-host-claude-code. @thomas-powers-jr/cadence-testkit is private (dev-only test tooling — nothing runtime-depends on it). Each publishable package carries license:"MIT", publishConfig.access:"public", a repository block, and its own LICENSE/README. The path is proven reversibly by scripts/publish-proof.mjs: it stands up an ephemeral local verdaccio (anonymous publish, npmjs uplink for transitive deps), runs a real pnpm publish of the three (which rewrites workspace:* → the concrete version — raw npm publish would not), installs them into a clean dir, asserts no workspace: survives and both bins (cadence, cadence-host-claude-code) execute, then tears the verdaccio process tree + all OS-temp down unconditionally. A public pnpm publish --dry-run + npm pack --json inspection prove the public-npm shape (tarballs = dist/bin/package.json/LICENSE/README only). No non-localhost registry is ever contacted for publish.
Deliberately deferred to a named v1.4 “Public release” milestone (see ROADMAP): the real public-npm publish, npm provenance (requires the source repo be public — a conscious repo-visibility decision), .github/workflows/release.yml gated on ci-success, changesets adoption, and re-deciding whether @thomas-powers-jr/cadence-testkit ever publishes. The v1.1 boundary is “prove the path without irreversible action.”
11. Telemetry
Section titled “11. Telemetry”state.skillAudit.invoked: string[] records which cadence skills the user has invoked during the active session (Phase 23.4). Wiring:
- Claude Code’s
Skilltool firesPostToolUsewithtool_name === 'Skill'andtool_input.skill === '<name>'. - The host shim maps it to the
'skill-invoke'abstract event and forwards the skill name viactx.raw.skill. - The dispatcher routes
'skill-invoke'tohandleSkillInvokewhich appends tostate.skillAudit.invokedwhenconfig.telemetry.skillInvocations === true. - Dedup: a skill is recorded at most once per session. Cap: array is bounded at 100 entries with FIFO eviction.
cadence status --jsonsurfaces the populated array.
tokenUtilization real-signal wiring is deferred — host payload shape varies across Claude Code versions; a separate phase scopes the investigation. Current behavior: handleUserPrompt increments by +0.01 per user-prompt event (proxy, not a real signal).
state.skillAudit.required[] semantics (the list of skills the user expects to invoke each session, with a skill-audit-miss anomaly when one is absent at SessionStop) are also deferred.
12. Observability (operational logging)
Section titled “12. Observability (operational logging)”Operational logging is distinct from §11 telemetry: telemetry tracks user behavior
(skillAudit), whereas observability is operator-facing diagnostics for why CADENCE itself did
something. Shipped in the v1.17 milestone (phases 80–82, the Post-v1.0 “structured logging” vector).
Locked decisions:
- Zero runtime dependency. A homegrown logger in
cadence-core(src/logging/), with theLogLevel/LogFormat/LogRecordtypes incadence-types. Nopino/debug— fits the lean-deps- verifiable-claims bar and works identically across CLI, subprocess-hook, and MCP-stdio runtimes.
- Default-off. Default level is
silent; the logger emits nothing unlessCADENCE_LOG_LEVELorconfig.logging.levelraises it. Existing output and golden fixtures are unaffected by construction. - stderr-only. Records are written exclusively to stderr — load-bearing because
cadence mcp serveowns stdout as the MCP protocol channel and--jsonoutput must stay clean on stdout. - Additive instrumentation. Three seams emit via
getLogger().child({ seam }):gate(settle gate decisions),hook(lifecycle event dispatch),verify(AI verifier calls, incl. token usage). No diagnosticconsole.*existed at these seams to migrate; thehost.ts/hookcontext payload remains the intentional stdout contract. Secret material (verifier auth headers, API keys, webhook URLs) is never logged. - Control precedence: env > config > default.
CADENCE_LOG_LEVEL/CADENCE_LOG_FORMAToverrideconfig.logging.{level,format}, which override thesilent/ TTY-derived defaults.
Deferred (still Post-v1.0): state-transition logging, OpenTelemetry / OTLP export (the logger leaves a clean extension point for an exporter without re-plumbing call sites), and an audit NDJSON sink.
13. Worktree safety (phase-collision guard)
Section titled “13. Worktree safety (phase-collision guard)”CADENCE’s loop state is file-based and lives in the working tree, and git worktrees each hold a
private .cadence/. Two worktrees branched from one commit independently conclude “phase N is next”;
with different slugs (30-auth vs 30-cache) git silently merges both in — two phase Ns, no
conflict marker. Shipped in the v1.18 milestone (phases 83–84).
Locked decisions:
- Observe ground truth, not a reservation registry (Approach A over B). Phase-number uniqueness
is enforced by observing sibling worktrees (
git worktree list) + the upstream ref (origin/<integrationRef>), not by reserving numbers in a parallel allocation registry. The worktree list IS the registry; the phase dirs ARE the claims. Rationale: no parallel state to drift out of sync, and it degrades gracefully offline / in a non-git checkout. A reservation registry (reserve a number before the dir exists) was considered and rejected as the wrong weight for the problem — the scaffold guard + settle backstop cover the collision without it. - Refuse + suggest, never auto-renumber. On collision the guard fails loud, names what is taken
and where, and suggests the next free number (
max(observed) + 1, monotonic — not lowest-gap). It never silently changes the number you asked for.--allow-phase-collisionbypasses per run. - Scaffold-time primary, settle backstop secondary. Refuse at
spec new/draft newbefore work begins; re-check atsettle runto catch a scaffold-race. The backstop is asettleServiceprecondition, not a profile×tier gate-matrix cell — it is a cross-cutting safety check. - Self is identified by source, not number. The collision authority is the
sibling+upstreamsources. Thelocalsource (this worktree’s own dirs) is excluded from conflict matching — at scaffold time the dir is being created; at settle the active phase is local. (Self and a genuine same-number sibling share the number, so a number-based self-exclusion would also hide the sibling — exclusion must be by source.) Local still feeds thenext freecomputation. - Best-effort, default-on, additive. Any git/fs failure on a source contributes nothing and never
throws; the only hard failure is an actual detected collision. Pure
detectPhaseCollision+ impuregatherOccupancymirror the repo’s pure-seam split. The existing local same-directoryexistsSyncrefusal is untouched and is never bypassed by the flag.
Polished in the v1.19 milestone (phases 85–87) — two of the three v1.18 follow-ups, shipped as
pure additions on the same gatherOccupancy + detectPhaseCollision primitive:
cadence doctorcross-worktree phase-usage line (phase 85). A read-onlyworktree-phasescheck surfaces phase numbers claimed by sibling worktrees andwarnings when one collides with a local number (the silent-dual-merge precondition), naming the conflict + the next free number. Collisions are sibling-vs-local only — upstream is the merged baseline (every local phase is also onorigin/<ref>once merged), so it is not a standing warning, though it still feeds the suggested next free number. Best-effort: degrades tookoffline / non-git.- Proactive next-free allocation in
progress/recommend(phase 86). The IDLE “next” suggestion fills inmax(observed) + 1over local + sibling + upstream claims, so the operator’s first pick already clears what the guard would otherwise refuse. The purenextActiontakes the number as a hint; the impure service layer resolves it best-effort and falls back to the literal placeholder on any failure — it never blocksprogress.
Lowest-gap (vs max + 1) numbering was evaluated and dropped (not deferred). It would reverse
the locked “max + 1, monotonic — not lowest-gap” decision above to solve no demonstrated problem:
monotonic numbers stay chronological and never resurrect a number history intentionally skipped
(.keel/, archived dirs). Even an opt-in phaseGuard.numbering knob was judged YAGNI — permanent
config surface for a mode unlikely to be used. nextFree stays max(observed) + 1. If gap-reuse is
ever genuinely wanted it remains a clean additive follow-up; dropping it now burns no bridge.
The v1.38 cross-worktree-handoff-discovery milestone (phases 142–144) is a sibling application of
this section’s locked decision, not a new one — the same “observe ground truth, not a reservation
registry” philosophy, extended from phase numbers to handoff docs. Two independent designs were
weighed for surfacing a sibling worktree’s resumable session in cadence resume: a live scan, and a
shared index cached in the repo’s common .git directory. Live scan won — even the index design’s own
analysis concluded an index is over-engineering at this scale (single-digit worktrees, sub-100ms scan
cost) and trades in a worse failure mode than the one it optimizes: a lost or never-written index entry
makes a real handoff invisible, whereas a live scan can only ever be as stale as “right now.” No index
file anywhere: every cadence resume call freshly discovers sibling worktrees via git worktree list --porcelain (the discovery plumbing this section already established, now extracted to
packages/core/src/git/worktrees.ts) and freshly reads each sibling’s own .cadence/handoff/ directory.
The default UX stays conservative for the same reason --allow-phase-collision stays opt-in above: bare
cadence resume resumes the local candidate exactly as before, plus a one-line stderr nudge when
siblings have resumable handoffs — never an auto-picker — with a resume.autoList config field for
operators who want the picker to open automatically. This deepens §13; no new D-number.
14. Config legibility (cadence config explain)
Section titled “14. Config legibility (cadence config explain)”The config surface grew to 22 top-level keys with six near-identical provider blocks and several
invisible interactions (profile × tier → gate set, hooks in config vs. hooks registered in
.claude/settings.json, provider → silent-mock-fallback). docs/reference/config.md is a faithful
but large reference dump — not a guide. The first adoption complaint was simply “I can’t tell what my
config does.” Shipped in the v1.21 config-legibility milestone (phases 91–92) as an additive,
read-only CLI surface — no schema change, no new D-number.
cadence config explain [field] [--all] [--json] renders the active config in plain language:
profile/enforcement meanings, the concrete gate set per tier (reusing gatesFor — the same matrix the
engine runs, so the explanation can’t drift from behavior), the six provider blocks collapsed into one
table, and config-semantic warnings (provider-set-without-key → silent mock; hook-enabled-but-
adapter-absent; auto × complex soft cap). The pure core (buildExplanation + renderText/renderJson,
phase 91) takes all external facts via an injected ExplainContext; the impure gather (phase 92) reads
state.json, the env, and host-install state best-effort, never throwing.
It is complementary to the doctor commands, not a replacement: cadence config doctor flags
conflicting config pairs, cadence doctor runs the structural health checks, and config explain
describes + surfaces semantic foot-guns, pointing at both. The three share detection logic where they
overlap (the host-hooks-installed predicate was extracted to one helper in phase 92) so their answers
stay consistent.
Slice C — cadence config edit: a guided zero-dep wizard over the curated keys, closing the explain→edit loop (additive; no new D-number).
Slice D — cadence quickstart: a read-only, never-failing, state-aware front door that reuses progress’s next-action post-init (additive; no new D-number).
Deferred (remaining slices of the same effort): deepening cadence explain with concept
cross-links (B). Explaining an invalid
config field-by-field, per-field docs deep-links, and a diff-vs-defaults view were judged out of scope (YAGNI).