create-cmp-cli 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,17 @@
10
10
  // node qa/plan.mjs --done # close the chain
11
11
  // node qa/plan.mjs --clear # a landed request leaves no stale windshield
12
12
  //
13
+ // node qa/plan.mjs --hold "adopting the ports" [--as <name>] # I am working in this tree
14
+ // node qa/plan.mjs --beat ["what I am on now"] # still here (every few minutes)
15
+ // node qa/plan.mjs --release # done, or gone
16
+ //
17
+ // The hold is LIVENESS, not a lock: nothing waits on it and it gates nothing.
18
+ // It exists so "is the agent working or wedged?" is `tail` on a file instead of
19
+ // filesystem archaeology — the question that got a healthy agent killed
20
+ // mid-proof on 2026-09-04 — and so the Stop hook can say "wait for it" instead
21
+ // of "run the lane" at a tree that would not compile. A heartbeat older than
22
+ // five minutes is a crashed writer; a hold older than the ceiling is a wedge.
23
+ //
13
24
  // Unlike walk-status (a fail-open status surface), this CLI is a WRITER the
14
25
  // agent invokes deliberately: bad input gets a refusal and exit 1, because a
15
26
  // silently-dropped declaration would leave the surfaces lying about position.
@@ -18,6 +29,7 @@ import path from "node:path";
18
29
  import { fileURLToPath } from "node:url";
19
30
 
20
31
  import { deriveChain, markStep, readPlan, renderChain, setPlan, clearPlan } from "./lib/plan.mjs";
32
+ import { assessHold, beatHold, claimHold, describeHold, readHold, releaseHold } from "./lib/agent-hold.mjs";
21
33
 
22
34
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
23
35
  const args = process.argv.slice(2);
@@ -37,7 +49,24 @@ function out(result) {
37
49
  process.exit(0);
38
50
  }
39
51
 
40
- if (args.includes("--set")) {
52
+ function holdOut(result) {
53
+ if (!result.ok) {
54
+ process.stderr.write(`plan: ${result.error}\n`);
55
+ process.exit(1);
56
+ }
57
+ const line = describeHold(assessHold(readHold(ROOT)));
58
+ process.stdout.write(`${line ?? "No agent holds this tree."}\n`);
59
+ process.exit(0);
60
+ }
61
+
62
+ if (args.includes("--hold")) {
63
+ holdOut(claimHold(ROOT, { holder: valueOf("--as") ?? process.env.CMP_AGENT ?? "an agent", note: valueOf("--hold") ?? "" }));
64
+ } else if (args.includes("--beat")) {
65
+ const note = valueOf("--beat");
66
+ holdOut(beatHold(ROOT, note === null ? {} : { note }));
67
+ } else if (args.includes("--release")) {
68
+ holdOut(releaseHold(ROOT));
69
+ } else if (args.includes("--set")) {
41
70
  const spec = valueOf("--set");
42
71
  if (spec === null) {
43
72
  process.stderr.write('plan: --set needs a value: --set "step | step | …"\n');
@@ -21,6 +21,7 @@ import { fileURLToPath } from "node:url";
21
21
 
22
22
  import { computeInputsHash } from "./lib/inputs-hash.mjs";
23
23
  import { evaluateReceipt, readReceipt } from "./lib/receipt-validate.mjs";
24
+ import { readHold, assessHold, describeHold, holdExplains } from "./lib/agent-hold.mjs";
24
25
 
25
26
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
26
27
 
@@ -175,12 +176,24 @@ if (asHook) {
175
176
  // INSTRUCTION: "run the lane" is wrong advice when one is already running,
176
177
  // and a gate that tells you to do the thing you are doing trains you to
177
178
  // stop reading it.
179
+ //
180
+ // An agent HOLDING the tree is the same shape of correction one level out:
181
+ // a lane in flight explains a receipt that is about to arrive, a hold
182
+ // explains a tree that is mid-edit and would not compile if you ran one.
183
+ // Reported ~15 false alarms in one evening at payment-blueprint, every one
184
+ // of which should have said "wait". It only ever changes the instruction —
185
+ // and only for the two refusals a working agent legitimately causes
186
+ // (holdExplains), never for a FAIL, a forgery or a skipped device tier,
187
+ // where the hold is not the cause and offering it would misdirect.
178
188
  const flight = laneInFlight();
189
+ const hold = holdExplains(result, readReceipt(ROOT)) ? assessHold(readHold(ROOT)) : null;
179
190
  const act = flight
180
191
  ? `A full check is ALREADY RUNNING${flight.step ? ` (${flight.step}${flight.index && flight.total ? `, step ${flight.index} of ${flight.total}` : ""})` : ""} — ` +
181
192
  `wait for it to finish and commit its receipt. Do NOT start a second one; two lanes fight over the same build directory.`
182
- : "Run `node qa/verify.mjs` (it checks every promise and writes the receipt), " +
183
- "commit the receipt, or see README §Verification enforcement to bypass.";
193
+ : hold?.held
194
+ ? describeHold(hold)
195
+ : "Run `node qa/verify.mjs` (it checks every promise and writes the receipt), " +
196
+ "commit the receipt, or see README §Verification enforcement to bypass.";
184
197
  process.stderr.write(
185
198
  `■ Prove — not done: the promises are not yet checked against this tree. ${result.reason}. ${act}\n`,
186
199
  );
@@ -104,7 +104,7 @@ Profiles:
104
104
  smoke the smallest end-to-end lane: every pure-Node gate through the real
105
105
  runner, receipt and journal — no Gradle, no device. Seconds. Proves the
106
106
  FRAMEWORK returns, both ways; never the change (its receipt is refused
107
- as done-evidence). Driven by scripts/framework-check.mjs.
107
+ as done-evidence). Driven by qa/framework-check.mjs.
108
108
  scaffold spec coverage + build + unit tests (what \`create-cmp --verify\`
109
109
  proves at stamp time)
110
110
  local everything; device-dependent steps SKIP when no device is