Releases: kvdm-co-pilot/create-cmp
Release list
v0.24.0 — Rule 0 ships to adopters
[0.24.0] - 2026-09-04
The Rule 0 instrument now ships. Four files in every generated project pointed adopters at
scripts/framework-check.mjs — a path that exists only inside the create-cmp repo. The tool it
named has never been in an adopter's tree, so the fast way to calibrate a gate was advertised
and withheld, and the expensive way was the only one available.
Added
qa/framework-check.mjs— Rule 0, in your own tree. Proves the lane returns a fast
deterministic PASS and FAIL through the real machinery: runner, receipt, Stop hook. No Gradle,
no device, no network. Measured on a fresh scaffold: 7 plants, 1.9 s.- Plants are derived from the tree, not hardcoded — a project with no specs, no flows or
no Kotlin tests still gets the region plants, and every plant it cannot make is reported
with its reason. It refuses rather than printing PASS over an empty plant list. - Restores everything it touched, including the receipt and the README badge — a smoke
receipt is refused as done-evidence, so leaving one in place would overwrite a real L1/L2
receipt with one that proves nothing. Empty directories it created are removed too. - Refuses to start when a file it plants into has uncommitted changes.
--budget-ms(default 5 s/cycle) reports any calibration cycle slower than Rule 1's stated
"seconds each" — the bound was prose, and prose does not refuse.
- Plants are derived from the tree, not hardcoded — a project with no specs, no flows or
qa/lib/framework-check.mjs— the plant selection and run assessment as pure functions,
unit-tested without a scaffold (27 new tests; suite 1306 → 1333).scripts/framework-check.mjsproves the shipped twin. A sixth leg runs
qa/framework-check.mjsinside the scratch app it already stamps and asserts the tree is
byte-identical afterwards — the tool we hand adopters is proven on the tree as shipped, not
merely shipped. (Its first version comparedgit statusin a directory that is not a repo and
therefore compared nothing; it hashes the tree now, build output excluded.)
Added
qa/lib/agent-hold.mjs+qa/plan.mjs --hold/--beat/--release— liveness for a working
agent. Two failures reported from payment-blueprint on 2026-09-04 were the same missing
fact. (a) There was no cheap way to answer "is the agent working or wedged?", so the question
was answered by filesystem archaeology — twice with broken instruments (afindthat excluded
build/, the only directory a proof run writes to; then afind -newermtthat reported zero
writes in 45 minutes whilels -lTshowed one at 10) — and a healthy agent was killed
mid-proof. (b) The Stop hook fired ~15 times in one evening while the correct action every
time was to wait, because it cannot tell a receipt stale from laziness from one stale because
a subagent is mid-commit on a half-adopted port.- A hold changes the ADVICE, never the verdict. The hook still refuses — a file an agent
writes about itself must never be able to end a turn. It follows the precedent already set
for a lane in flight: same refusal, different instruction. - It explains only the two refusals a working agent legitimately causes — no receipt yet,
and a tree that moved under a PASSing receipt. A FAIL, a forgery, a skipped device tier or
an unreadable surface are never explained by a hold, because there the hold is not the cause
and offering it would send the reader to wait on an agent when a test is red. - Heartbeat older than 5 min reads as a crashed writer; a hold older than the 45 min ceiling
reads as a wedge and the alarm returns — a heartbeat proves a process is alive, never that
it is progressing. - Ephemeral like its siblings: gitignored and excluded from the receipt's hashed input surface,
so a note about who is typing can never invalidate a receipt. - 19 tests, including the whole safety asymmetry (suite 1333 → 1352).
- A hold changes the ADVICE, never the verdict. The hook still refuses — a file an agent
- The orchestrator agent now claims, beats and releases the hold, and owes a visible line per
subagent start and finish.
Fixed
- Signing N artifacts costs one receipt invalidation, not N. A signature changes
qa/approvals.json'sstatus, and the approvals gate readsstatus— so a signature
legitimately moves the receipt's input hash. Signed one at a time after a green lane, that
means N signatures kill the receipt N times and cost N lane re-runs and N bookkeeping commits.
Measured in payment-blueprint's log (2026-09-04, 00:12–03:54): 21 commits, of which 7 were
code, 6 docs and 8 pure bookkeeping ("sign the approval", "bind the green receipt"); every
code change cost two to three commits and fired the Stop hook after each. It compounds as a
repo accumulates governed artifacts, which is what makes a session start fast and grind later.node qa/approve.mjs <a> <b> <c> --as "…"signs them in one write — the idiom
--reopen-featurealready established ("one recorded change, not N commands").- The CLI states the other half with its reason attached: sign before the final lane run,
so the receipt you keep already covers the signatures. - An unrecognised flag is still refused by name rather than filtered out silently.
Changed
- GATE-RULES Rule 1 names its instrument at the point of authoring. Rule 1 states a
property — this gate fails, by name, on its own violation — not a procedure to perform by
hand, and it now says so where a gate author reads it rather than inside a Rule 0 aside.
Measured cost of the procedural reading, three occurrences in one adoption: a wave briefed to
plant →./gradlew→ confirm red → revert → build, at 30–60 s per cycle, ~38 minutes with
nothing committed. It had violated Rule 1's own "seconds each" from the first cycle, and
nothing noticed, because a bound in prose cannot refuse. A plant kept in the instrument is run
by everyone forever in milliseconds; a plant performed by hand is run once and lost. - The orchestrator agent has standing to refuse an expensive instruction. It could already
escalate before changing a number; it now costs a mandated mechanism the same way —
instances × seconds-per-instance against the stage budget — names a cheaper instrument if one
exists, and answers "what in this repo already does this, and why is it insufficient?" before
briefing any mechanism. It also owes visible progress: a line per subagent start and finish, a
status upward for anything past ~5 minutes with no output, and proof wall-clock as its own
report line. A silent agent is indistinguishable from a wedged one. - The four dangling
scripts/framework-check.mjsreferences in shipped files now name
qa/framework-check.mjs(verify.mjs,lib/steps-cmp.mjs,lib/evidence-badge.mjs,
docs/USAGE.md).
v0.23.0
Two spine fixes from payment-blueprint's adoption: the locked region now covers a lane's own
tests and the declarations it reads. No migration — a Compose app has neither qa/test/ nor a
surface declaration by default, so its region, lock and receipts are unchanged.
Changed
- The lock region covers the lane's own tests.
qa/test/**(every.mjs, recursively) is
part of the machine-owned region when a project has it, so an adopter's suite that proves its
gates is locked with the gates. Found by payment-blueprint, which could not vendor
harness-lock.mjswithout narrowing its integrity claim. A Compose app has noqa/test; its
region and lock are unchanged. Pack tests now pin unique step names per profile (an aliasing
factory passes a null check) and that the smoke profile stays pure-Node. - The lock region covers the declarations the lane reads:
qa/verified-surface.jsonand
qa/harness-manifest.json. payment-blueprint's planted proof: one entry removed from the
surface declaration un-attested 203 files while every checker stayed intact and the next
receipt validated over the smaller surface. An edited definition of what the checker looks at
is the same attack as an edited checker. State a lane writes (ledgers, journal, evidence) stays
out. The framework check now plants a narrowed declaration and assertsharnessIntegrity
names it (7 plants).
v0.22.0
The device tier always runs, every Maestro flow runs, a device journey per feature is a lane
verdict, mutation testing is gone, and every skipped-test guard is read by a planted failure
before release. No migration: an app with no emulator attached will now boot one (set
CMP_AVD if the lane cannot choose; CMP_DEVICE=none is the visible opt-out and is never
done-evidence), and a feature with a screen and a spec now needs one clause proven by a flow.
Removed
- Every mention of mutation testing. Karel, 2026-09-03: overkill; it broke development
completely and caused the last days' issues; tests are written from the spec and that is the
guard. Derived before the decision: the step had never returned a verdict, one module measured
zero against a 60 % threshold, and a run pinned a shared machine at load 70–80 for 25 minutes.
Nothing of it remains in the pack, the docs of record, the proposals or the adoption example.
Added — every skipped-test guard is read, on a real scaffold
scripts/framework-check.mjsis a planted-failure matrix. On a freshly stamped app it
plants, one at a time, an orphaned citation, an unbound citation (aSPEC:tag on a class
with no test in the binding window), a[tier: e2e]clause cited only from the JVM, a real
feature whose flow stops citing its clause, a citation in a nested flow the lane never runs,
and one edited byte in the machine-owned region — and asserts each FAILs BY NAME, that the
Stop hook refuses a FAIL receipt, refuses the forgery (verdict flipped to PASS over a failed
harnessIntegrity row), and refuses a receipt whose device tier was skipped for an
environmental reason; then reverts everything and asserts PASS. Six plants, under two
seconds, no Gradle, no device. This is Rule 0 and Rule 1 for every guard that catches a
skipped or fake test, run before any release.
Changed — the device tier always runs
- The lane boots its own headless emulator. With no device attached, the first device
step boots an AVD headless (CMP_AVD, else the doctor'scmp_pixel, else the only one),
bounded at 4 minutes, and the lane shuts it down on exit (CMP_KEEP_DEVICE=1keeps it). A
device that cannot be provisioned is an ERROR row and the lane FAILs.CMP_DEVICE=noneis
the one explicit opt-out;qa/receipt-check.mjsrefuses a receipt whose device tier was
skipped for an environmental reason as done-evidence. Until now every device row SKIPped
"no device" on every receipt anyone looked at, and 0.21.0 shipped on a fleet check with
the whole tier skipped. - Every Maestro flow runs. e2eSmoke ran
smoke.yamlby name while spec coverage counted
citations from any yaml underqa/e2e— four hand-written per-feature flows on the
showcase satisfied clauses without ever executing. The directory now runs in one Maestro
session; the receipt row lists each flow's result; the executed list and the coverage scan
read the same list, so a citation can only come from a flow that ran. - A flow per feature.
qa/scaffold-feature.mjsstampsqa/e2e/<feature>.yaml, a passing
skeleton naming the screen id and clauses to prove; it cites nothing until it is the
journey.[tier: e2e]clauses fail coverage until a flow cites them. - A device journey per feature is a lane verdict. New pure-Node gate
e2eCoverage(every
profile that runs the pure tier): a feature with a screen AND a spec must have at least one
live clause cited from a flow the lane runs; FAIL names the feature and the flow file to
write. A screen without a spec is a placeholder (reported, not failed); a screen declared
{ "unrouted": true }in its brief is exempt. Feature-brief doneness gains the same
conjunct: ascreens: truebrief is not done until one of its clauses is cited from
qa/e2e/. The template smoke flow now proves HOME-02 on device (the first item renders), so
a fresh scaffold passes; a freshly stamped feature FAILs until its flow is the journey —
that is the point ("is anything forcing e2e tests to be written per feature?" — now yes). - The post-run ANR/crash sweep is scoped to the app under test. The first self-booted lane
went red on an ANR in another app that lived on the emulator. Incidents in other packages
are not this lane's failure; with no app id known the sweep stays unscoped and says so.
Fixed — from the showcase's 0.21.0 upgrade report
-
The template
.gitignorecarries the Android signing-key ignores (keystore.properties,
keystore/,*.jks,*.keystore). Three consecutive upgrades produced a.gitignore
sidecar without them; an agent taking the sidecar would leave the keystore onegit add -A
from a public repo. The upgrade now also names what taking a sidecar would drop. -
Receipts name the lane in words.
harnessIntegrityprints the region digest beside the
version (@create-cmp/harness 0.17.0 (region b56cd4f8) — 45 files verified): two lanes with
the same package version and different content are told apart on the receipt, not only in
the lock. The harness package is 0.17.0. -
Stale
*.bak-upgradefiles from earlier upgrades are removed before a new upgrade writes
its own; nothing cleaned them and one set accumulated per upgrade. -
scripts/fleet-check.mjsdefaults to--min-level L2; the template CI workflow sets
CMP_DEVICE=noneexplicitly on its hosted runner.
v0.21.0
The console follows the project's layout, and four spine defects the first non-Compose
adopter found are closed. No migration: every change is additive or a bug fix, and a Compose
app with no manifest behaves exactly as before.
Fixed
--fastfell open to the full suite after the first lane run — in every Compose app.
qa/flight-recorder.jsonlis appended by every run and committed, so it sat in the
changed set as a modified file underqa/, which is the "harness itself" escape hatch.
It is now a lane output (affected-tests.mjs), pinned by a planted test. Derived on a
fresh scaffold before the fix: the journal in the changed set, filter modeall.- A smoke run rewrote the README's evidence badge from a true L1 to "rung unrecorded".
Smoke and nightly receipts are refused as done-evidence and now leave the badge alone,
like--fast. - The evidence ladder was the Compose pack's step names inside the spine. Vendored into
a backend it graded the strongest run L0, L1 unreachable by construction. The ladder is now
the pack's (createCmpStepsreturnsevidenceLadder;evidenceLeveltakesladder); a
pack that declares none earns no rung. Compose apps: unchanged rungs. - The release gate itself failed on timing. The bundle-boot test slept 700 ms + 1800 ms and
read whatever had arrived; undernpm publishon a loaded machine the bundle answered late
andprepublishOnlywent red on a healthy bundle. It now waits for the response, bounded
at 20 s — the bound is the assertion, never a fixed sleep. - A new top-level directory was silently unattested by the surface allowlist. The
receipt now carriesinputs.undeclaredand the lane prints it — a report, never a gate,
because the Compose default deliberately omitsdocs/, the README and the wrapper.
Added
- Project manifest for the console —
qa/harness-manifest.jsondeclares where a project
keeps its receipt, architecture document, spec directory and citation roots (and which step
packs it composes). The console read one layout — a create-cmp Compose app's — as constants,
so the first spine adopter with its own (payment-blueprint:qa/evidence/receipt.json,
ARCHITECTURE.mdat the root, Kotlin underservices/) got an Evidence pane, verdict history,
audit trail, digest and Architecture page that said "not found" about files that existed. The
layout is now resolved per project exactly like the verified surface: Compose default, manifest
override, and a present-but-malformed manifest is refused with every problem named rather than
silently defaulted.--statusreports the layout in use; the rail names the manifest. - Layer-tagged receipt rows — a step pack may tag a step function with the layer it proves
(fn.layer = "backend"); the runner stamps it onto the receipt row and the Evidence page
groups steps per layer with a per-layer tally. Backward compatible: untagged rows and receipts
render exactly as before. The Compose pack tagsspine/compose/device. - Specs page bridges to the project's own scanner — when
qa/lib/spec-coverage.mjsexports
scanSpecClauses/scanCitations, the console renders their reading (the project's clause
grammar and citation-binding rule), the same "bridge, never fork" stance it takes for
approvals.mjs. The console's own scan remains the fallback. - The receipt bridge reads a flat receipt schema (
inputsHash/gitSha/timestamp) alongside
the nestedcmp-evidence/1one.
Changed
- Approvals-bridge refusals for a library without
reopenArtifact/acceptFeatureno longer
assume an "older scaffold": a step pack that does not govern reopen or briefs is the other,
named, possibility.
v0.20.0 — lane vouching (strict), approver identity, citation binding
Three gates that read as green while holding nothing, closed. Each is breaking by design
— every one closes a hole that currently reports PASS — and each has a one-command migration.
Read the migration before upgrading.
Migration
Do these once, in this order, after upgrading the harness (create-cmp upgrade --harness):
- If your app predates harness locks (no
qa/harness.lock.json): the upgrade writes one.
Without it,harnessIntegritySKIPs, and 0.20.0 refuses a receipt whose lane did not vouch
for itself — strict, decided 2026-09-03: a lane that could not check its own integrity is not
trusted. Apps generated since locks existed, and any app already upgraded, are unaffected. - Re-approve each governed artifact with a signer:
node qa/approve.mjs <artifact> --as "Name <email>". Every existing approved row lacksapprovedBy; the gate FAILs naming each
until it carries one. Hashes, statuses and the journal are untouched — only the signer is
added. The studio console asks once per session and signs with what you type. - Move any
// SPEC:citation onto the test it claims. A tag now counts only when a test
declaration follows within five meaningful lines, and never when it sits on a type
declaration.specCoveragemay go red naming clauses that were never really covered — each
is a gap the old rule hid, not a regression. - Run the full lane once so the receipt carries the new rows.
Added
- Approvals record who signed (
approvedBy).approveArtifactrefuses without one and the
refusal names the flag;approve.mjsrequires--as "Name <email>"; the express lane carries
the signer through to each artifact; the console asks once per session (sessionStorage — a
signature is a deliberate act by whoever is at the keyboard now, not a name a browser
remembers). Deliberately NOT defaulted from git config: that is whatever the machine says, and
an agent on a developer's laptop would sign with that developer's name.--reopentakes no
signer — it walks a signature back. - A citation must sit on a test.
scanCitationscounts a// SPEC:tag only when a test
declaration follows withinBINDING_WINDOW(5) non-blank lines; block comments are skipped;
a tag whose first meaningful line declares a type is refused structurally — the real drift
that motivated this sat onclass PaymentWorkerTestabove a genuine@Test. Maestro flows
are exempt (a flow file is its own test). This also closes the hole under 0.19.0's
[tier: device]gate, where a device-tier citation could be a bare comment. The rule caught
the template's ownARCH-04tag, and the fixtures written to prove the tier gate. - A PASS must be vouched by the lane's own rows (
checkLaneVouching, in
@create-cmp/receipts). A receipt whose verdict is PASS is refused if any row is FAIL or
ERROR, if it has noharnessIntegrityrow, or if that row is not PASS — the receipt is
necessarily excluded from its own inputs hash, sosteps[]is the only thing between the
gate and a text editor.
Fixed
VERIFIED_SURFACEwas hardcoded in the spine. A repo whose code lives outsidecomposeApp/
that vendored it had its verified surface silently shrink — the published 0.19.0 spine, run on
a nested project, returned the sha256 of the empty string as a valid-looking digest attesting
zero files. The surface now resolves per project fromqa/verified-surface.json; the CMP
default is unchanged (the showcase hashes 485 files to the same digest before and after); a
malformed or empty declaration is refused; a surface matching nothing throws, and the Stop
hook reports that as a refusal with its reason rather than a stack trace.- The lane marker narrated eight steps as
null(memoized wrappers andgradleTestStep's
returned functions had no runtime name), giving each the default deadline. Named.
v0.19.0 — evidence economics
Added
- Evidence economics — the loop made cheap, visible and honest (
docs/EVIDENCE-ECONOMICS-PLAN.md,
the doc of record across sessions; causes and named practices in
docs/proposals/evidence-economics.md). Every slice landed with a planted-failure proof.- A fourth verdict,
ERROR, and a deadline on every step (qa/lib/step-outcomes.mjs).
"Could not run" — zero tests executed, a deadline passed, a throw — is no longer reported as
a behaviour failure: it never accuses the change, earns no evidence rung, does not count as
executed for receipt plausibility, is never cached, wears its own mark (⊘), and still
FAILs the lane. Every subprocess inherits a per-step deadline derived from the step's own
measured history (×3, floor 5 min, ceiling 30); a throw inside a step becomes one row
instead of crashing the lane. - A clause can declare the tier that can observe it —
- **MOTION-13** [tier: device] — ….
specCoverageFAILs when no citing test comes from a tier that could see the promise; a
desktop Compose test citing a process-lifecycle claim is the planted red. App/process
lifecycle joins the platform-behaviour list in the generatedCLAUDE.md. - Tiers that have never run are named. The lane reads its own journal and prints
e2eSmoke — skipped in all 37 recorded full runs; a SKIP is non-fatal, "never" is no longer
invisible. - The lane says what it costs. Last, typical and worst full-run cost (measured over ≥3
runs) replace the single last-run figure that a Gradle cache hit made a lie. - A pulse during long steps (
qa/lib/lane-narrator.mjs) — a separate process, because the
steps are synchronous and no in-process timer can fire during a Gradle call. Quiet under
20 s, one stderr line every 30 s, "longer than usual" past 1.5×. --profile nightly, with the determinism probe forced on; every receipt names its
stage(scaffold / change / merge / nightly / release) and a nightly receipt is refused as
done-evidence like--fast.- The build stage is observed, not merely declared. Files written since the current
request move the Drive strip and the inject's chain (observed: 12 files written since the request · last 4s ago, or a named stall) — with no declared plan required. - The lane is a spine plus a step pack.
qa/lib/lane-runner.mjs(marker narration,
deadlines, the pulse, the catch, the verdict) andqa/lib/steps-cmp.mjs(every CMP step
behindcreateCmpSteps(ctx));qa/verify.mjs1,835 → 641 lines. A Kotlin backend keeps the
spine and supplies its own pack —packages/harness/README.mddocuments the swap with
payment-blueprint as the worked example.
- A fourth verdict,
- The studio console renews itself (
docs/features/studio-self-renewal.md).bin/console.mjs
supervises a worker; when the console's own sources change and nothing is in flight, the worker
exits 75 and is respawned with the new code (budgeted, source-mode only), the page reloads once,
and the registry record carriesbuildStaleso the per-prompt inject and the statusline stop
reporting a clean bill of health for a console drawing from old code. - The studio console opens on
qa/alone. Screens, preview and the live device are a
capability, not a gate: a repo with nocomposeApp/gets Drive, walks, approvals, evidence,
comments and the chain. Proven live on a Kotlin backend repo.
Changed
releaseBuildruns after the cheap tier (unit, conformance, goldens, a11y) inlocal,ci
andrelease— a red unit test is reported in seconds, not after R8 finishes.- The Stop hook says WAIT at a running lane instead of "run the lane" — the refusal stands;
only the instruction changes, naming the running step from the marker. - A feature reopen walks back only what it amends — the brief, its declared spec(s), and its
design whenscreens: true. Declaredtouchesstay signed and are reported as still signed;
the hash demands a fresh signature only if the change actually moves them (twelve-for-zero
becomes two-for-two). - The retrospective's skip grouping keys on the reason's first line — seven near-identical
approvals rows become one. androidChecksthat executed zero tests reports "DID NOT EXECUTE — not accusing it" with
the rerun command, instead of "an on-device behavior claim is broken".
Fixed
- Memoized step wrappers and
gradleTestStep's returned functions had no runtime name, so the
lane marker narrated eight steps asnulland gave them the default deadline. Both now carry
explicit names.
Added
grill-meskill + the grill step in the workflow — the decide step's opening act
(docs/features/grill-me.md). Before the intent interview at genesis and before the brief on
every brief-lane request, the agent reads what the repo already answers (signed briefs and
specs are closed — cited, never re-asked), then asks the frontier of unsettled decisions in
numbered rounds of ≤5, each with why it matters and a recommended answer, and waits; it stops
when no answer would change the work. No new artifact: settled answers become the brief's
Decisions with their why, the human's own calls its Open decisions, and the signature closes
them. Never grills the direct lane, a bug fix, or a spike. Stated on every entry surface —
CHANGE-FLOW-DESIGN.md§3,GENESIS-FLOW-DESIGN.mdstep 0, the generatedCLAUDE.md,
cmp-new§1 — and pinned to agreement bytest/grill-me.test.mjs. The plugin now ships
11 skills.
v0.18.0 — walk legibility + studio drive mode
Two approved feature waves. Harness → 0.16.0.
Walk legibility
The walk speaks human, the console carries it, and the console is always there.
- Briefs name their specs —
"specs": ["catalog", "entry-editing"]in thecmp:featureblock, the**Spec:**paragraph as fallback, filename as default. One pairing function feeds the board, coverage, the walk and the console. Closes a real defect: a brief whose promises live in two specs used to put a standing false instruction on the primary surface, inviting a second definition of already-signed behavior. - Chat is a walk surface — every reply opens with a machinery-authored header line (the statusline's own string, delivered by the per-prompt hook and pasted verbatim). It cannot drift, and unlike the statusline it persists in the transcript.
- Plain words at the boundary — stage glosses and artifact translation everywhere a human reads, including the Stop hook, which now opens
■ Prove — not done: …. Ledger ids never change. - Time is part of position — the lane's cost is read from its own flight recorder ("98s last full run"), never estimated.
- The console leads every call to action — your-turn cards point at the studio first; the In-flight card carries the full promise list with per-promise state, the measured lane cost, the signature button on the row, and arrivals as now/after buttons.
- The console is a resident —
previewnow spawns a detached console that survives MCP respawns; connecting the MCP inside a create-cmp app ensures one at session start; a crashed console is loud on four surfaces (statusline· console down, doctor'sconsole-livenessfinding, the injected prompt, and the tool result).
Studio drive mode
Third report of the same complexity problem, fixed by demotion rather than another aggregation layer.
- Drive replaces Overview as the driving surface: the live chain, the walk cards, the queue with its buttons — with the page's own digest and history folded away.
- Pages answer first — every mirror section renders its verdict, with the complete derived corpus behind one "Read the full …" disclosure. A section the queue points at greets open, and "take me there" opens the folds on the way. The spec-mirror principle is untouched; only default expansion changed.
- The live chain —
Request: <the ask>·✓ 1 → ◉ 2 → ○ 3·now: step 2 of 3, in the studio, the injected prompt, andqa/walk-status.mjs. Three labeled provenance tiers: the request is recorded mechanically from the human's own prompt; the steps are agent-declared through the newnode qa/plan.mjsand always carry their age; the lane/render markers say what is actually running and override. The chain gates nothing — the walk stays the truth — and its ephemeral files are excluded from the receipt's hashed inputs, so a prompt can never invalidate a receipt.
Release evidence
- Fleet check PASS — a scratch app stamped from this tree and its full lane run;
harnessIntegrity: PASS (@create-cmp/harness 0.16.0 — 40 files verified). - Engine suite 1148/1148; inspector suite 556/556; plugin bundle rebuilt and verified current.
- Evidence rung: L1 desktop. No device was attached for this release, so
e2eSmoke,androidChecksandtokenDriftSKIPped — v0.17.1 shipped with L2 device proof and this one does not. The changed surface is Node-side (lane libraries and console rendering) and CI's stamp +assembleDebuglane passed on the merge commit, so the risk is low, but the rung is genuinely lower and is recorded rather than glossed.
Full changelog: v0.17.1...v0.18.0
v0.17.1
Fixed
-
The walk can no longer be installed and silently unwired.
qa/walk-status.mjsis
lane code, replaced wholesale onupgrade --harness; its two surfaces are not — the
statusLineand theUserPromptSubmithook live in.claude/settings.json, which is
app-owned and, correctly, never clobbered. So an app that hand-edited its settings could
take the machinery and lose the wiring, and the failure mode was silence — the exact
thing the walk exists to end, in the one shape nothing in the system could notice.
create-cmp doctornow carries awalk-wiringfinding: warn when the script is
installed but settings.json does not invoke it, naming which half is missing, healable
with--fixfrom the engine's own template (read at fix time, so the heal cannot drift
from what a fresh scaffold gets). The heal only ever claims an unclaimed slot — an app
that set its own status line keeps it, an existingUserPromptSubmitis appended to
rather than replaced, settings it cannot parse are never written over, and a second run
is a no-op. -
Minimal mode no longer ships a status line pointing at a lane it deletes. 0.17.0 added
a top-levelstatusLineto the template — a command surfaceclassifyHooknever saw,
because it is not a hook.minimalHookSettingstherefore passed it through untouched,
giving a minimal scaffold a status line readingqa/walk-status.mjs, a file minimal
removes. The command is guarded (test -f … || true), so it printed nothing rather than
erroring: dead config that fails silently, which is precisely why it survived review. The
lane-reference rule now applies to every settings surface that carries a command, not only
tohooks. -
The Node 18 lane was red on main across two releases, and it was telling the truth.
package.jsondeclaresengines: ">=18"and the CI matrix tests 18, but
scripts/check-plugin-sync.mjsusedimport.meta.dirname(Node 20.11+) — so the
plugin-drift detector was dead on load for anyone on the declared floor, and its test
file took the whole Node 18 job down with it. Both sites now resolve the long way
(fileURLToPath(import.meta.url)). A newtest/node-floor.test.mjsscans every shipped
.mjsfor APIs newer than the declared floor, so the next violation fails locally rather
than becoming background noise in a job nobody reads.
Note
upgrade --harness was not at fault here and needed no change: .claude/settings.json
lands in the sweep's applied bucket, so an app that never touched its settings receives the
walk wiring along with the machinery. Verified against the showcase (0.14.1 → 0.17.0).
No change to @create-cmp/harness (0.15.0) or @create-cmp/inspector (0.8.0) — both
version independently and neither was touched.
v0.17.0 — the walk, and a front door that signs
Ships @create-cmp/inspector 0.8.0 (the console) and @create-cmp/harness 0.15.0
(the walk lands in every generated project's lane).
Added
- The walk — you always know what it's doing and whose turn it is
(docs/features/walk-status.md; born from the navigation-ia session: 20+ prompts, three
interleaved walks, no shared map). Every governed change is now a walk through six
stages spoken in human vocabulary — Decide · Design · Contract · Build · Prove ·
Sign-off — with spec clauses rendered as promises ("keeping promise 5 of 7", in
the clause's own words). It is a PROJECTION of the existing feature-board derivation
(phase + nextStep), never new state. Surfaces, layered by reliability:qa/walk-status.mjs
(cards ·--statusline·--inject·--json, fail-open by contract), the template's
statusLine(always-visible position, no agent involved), aUserPromptSubmithook that
re-anchors the agent with derived position + protocol on EVERY prompt (decay-proof:
re-told, not remembered), and the contract's walk protocol (upfront itinerary with
"stops for you: N", quiet one-line transitions, loud stop cards at human gates).
Work no open walk accounts for renders as ▲ ARRIVED, UNPLANNED with the journal's
reason — interleaving becomes an explicit choice, never an ambush. Deliberately NOT
a second Stop hook: Stop stays the receipt gate alone (test-pinned); the statusline
does the footer's job better. - The front door signs. Every row in Overview's "What needs you" queue now carries its
own control — Approve / Re-approve / Accept — with read it first beside it.
This supersedes the front door's original "names the act, never signs" rule: what that
rule protected (never sign what you have not read) is served by the evidence already on
the row plus one-click read-through, not by a forced round trip. It is not a second
path — the rows emit the same.approve-btn/.feature-accept-btncontract the owning
sections do, so one approve endpoint, one accept endpoint, one ledger.
Fixed
- A refusal now surfaces on the panel the click came from. The approve/accept error
path had exactly two boxes, on the Approvals and Features panels; a refusal triggered
anywhere else wrote its reason into a hidden element on a tab the human was not looking
at — a silent failure. Errors resolve to the clicking panel's.sig-errorbox, with the
original ids as fallback.
v0.16.0 — the studio console's front door
Ships @create-cmp/inspector 0.7.0 — the console lives in that package, so the
front door is released there and picked up by create-cmp-cli 0.16.0.
Added
- The studio console has a front door. The console had nine peer sections and no
hierarchy of attention above them, so the returning owner's three questions were
answered in four places: the queue as a rail widget pointing at rows scattered across
other tabs, "what changed since I last looked" behind a tab you had to know to open,
and the lane verdict behind another. The new Overview section (the default landing
tab) answers all three in the order they get asked — what needs you (the human queue,
each act carrying the evidence to judge its urgency: a drifted artifact's
changed/still-signed file split, a proven feature's clause tally and receipt) · what
changed (the digest, retired as a tab into this page) · is it proven (lane glyph,
verdict, rung, signing tally). It derives nothing of its own and grows no signature
control — sign-where-you-read is unchanged, and it jumps you to the section that owns
each act. The rail strip collapses to a pointer in the same move: it keeps the counts
(status visible on every tab) and hands off with "N things need you → Overview", so the
rail says that something waits and the front door says what. - Evidence renders the test tally the receipt already carried.
qa/verify.mjswrites
each test step's JUnit summary intodetails; the receipt bridge was stripping it, so
the release-readiness report could only sayunitTests PASSwhere
214 tests · 0 failed · 3 skippedwas sitting on disk. The step's ownnote(which
affected-test filter ran, when a CACHED verdict was earned) renders too. - The digest's commits name their files (
--name-status). Non-governed changes — the
data layer, the version catalog, build files — have no approval row and therefore had no
console surface at all: proven by the lane, reported to no one.
Fixed
@create-cmp/inspector0.6.2 — themcpNamefield the MCP registry's ownership
check requires. The registry verifies that a server's published npm package carries
"mcpName"equal to theserver.jsonname it is submitted under; that field was
prepared in the launch runbook's patch but never landed, and 0.6.1 published without
it. npm versions are immutable, so the field arrives as 0.6.2 rather than a
re-publish. The package stays independently versioned (GROUND-TRUTH.md's
"deliberately independent" note is unchanged) — this is the anti-impersonation
handshake, not a version-policy change.