shapeup-sdlc 1.6.3 → 3.0.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.
Files changed (118) hide show
  1. package/.claude/settings.local.example.json +5 -5
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +54 -107
  4. package/README.md +165 -151
  5. package/SECURITY.md +49 -27
  6. package/bin/init.mjs +93 -108
  7. package/bin/lib/grant.mjs +145 -0
  8. package/commands/build.md +20 -0
  9. package/commands/eval.md +5 -4
  10. package/commands/scopes.md +5 -4
  11. package/commands/shape.md +1 -1
  12. package/commands/ship.md +53 -7
  13. package/commands/wire.md +1 -1
  14. package/hooks/dispatch-receipt.mjs +195 -0
  15. package/hooks/gate-intake.mjs +16 -17
  16. package/hooks/gate-zerowork.mjs +107 -25
  17. package/hooks/hooks.json +9 -48
  18. package/hooks/lib/decision.mjs +38 -19
  19. package/hooks/safety-spine.mjs +4 -4
  20. package/hooks/sandbox-guard.mjs +130 -50
  21. package/{skills/tech-lead/scripts/compile-order.mjs → kernel/compile.mjs} +344 -55
  22. package/{skills/tech-lead/scripts/gate-answers.mjs → kernel/gate.mjs} +69 -26
  23. package/kernel/harness.mjs +134 -0
  24. package/{skills/tech-lead/scripts/fit-check.mjs → kernel/init/fit.mjs} +25 -22
  25. package/kernel/init/run.mjs +489 -0
  26. package/{skills/tech-lead/scripts → kernel}/lib/argv.mjs +52 -31
  27. package/{skills/tech-lead/scripts/lib/contract-md.mjs → kernel/lib/contract.mjs} +261 -17
  28. package/kernel/lib/paths.mjs +491 -0
  29. package/kernel/probe/concurrency.mjs +510 -0
  30. package/{skills/tech-lead/scripts/aegis-digest.mjs → kernel/probe/digest.mjs} +10 -11
  31. package/kernel/probe/eval.mjs +77 -0
  32. package/kernel/probe/leg.mjs +125 -0
  33. package/kernel/probe/resume.mjs +528 -0
  34. package/{skills/tech-lead/scripts → kernel/probe}/stats.mjs +102 -21
  35. package/kernel/probe/t0.mjs +66 -0
  36. package/{skills/ba-pitch-analyzer/scripts/board-derive.mjs → kernel/reduce/board.mjs} +104 -28
  37. package/kernel/reduce/graph.mjs +437 -0
  38. package/kernel/reduce/hill.mjs +152 -0
  39. package/kernel/reduce/ingest.mjs +633 -0
  40. package/{hooks/slop-cleaner.mjs → kernel/reduce/leftovers.mjs} +40 -57
  41. package/{skills/tech-lead/scripts/ship-report.mjs → kernel/reduce/ship.mjs} +93 -14
  42. package/{skills/tech-lead/scripts/run-snapshot.mjs → kernel/reduce/snapshot.mjs} +22 -21
  43. package/{skills/spec-evaluator/scripts/verdict-ledger.mjs → kernel/reduce/verdict.mjs} +14 -8
  44. package/kernel/report/export.mjs +325 -0
  45. package/kernel/report/facts.mjs +347 -0
  46. package/{skills/tech-lead/scripts/budget-check.mjs → kernel/verify/budget.mjs} +22 -25
  47. package/kernel/verify/dispatch.mjs +114 -0
  48. package/{skills/tech-lead/scripts/validate-envelope.mjs → kernel/verify/envelope.mjs} +20 -15
  49. package/{skills/tech-lead/scripts/lib → kernel/verify}/ratchet-tree.mjs +36 -14
  50. package/kernel/verify/skills.mjs +125 -0
  51. package/kernel/verify/spec.mjs +559 -0
  52. package/{skills/tech-lead/scripts/t0-verify.mjs → kernel/verify/t0.mjs} +138 -28
  53. package/{skills/tech-lead/scripts/trace-lint.mjs → kernel/verify/trace.mjs} +28 -13
  54. package/oracles/_shared.mjs +1 -1
  55. package/oracles/http-oracle.mjs +2 -2
  56. package/oracles/index.mjs +1 -1
  57. package/oracles/process-oracle.mjs +2 -2
  58. package/oracles/snapshot-oracle.mjs +2 -2
  59. package/oracles/test-oracle.mjs +2 -2
  60. package/package.json +11 -13
  61. package/skills/ba-pitch-analyzer/SKILL.md +24 -19
  62. package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +1 -1
  63. package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +12 -7
  64. package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +6 -3
  65. package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +5 -4
  66. package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +23 -13
  67. package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +17 -12
  68. package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +17 -38
  69. package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +3 -0
  70. package/skills/ba-pitch-analyzer/references/contract-patterns.md +8 -4
  71. package/skills/ba-pitch-analyzer/references/doc-schemas.md +3 -1
  72. package/skills/ba-pitch-analyzer/references/task-generation.md +8 -8
  73. package/skills/ba-pitch-analyzer/references/test-surface.md +1 -1
  74. package/skills/coach/SKILL.md +3 -3
  75. package/skills/orient/SKILL.md +2 -1
  76. package/skills/qa-edge-hunter/SKILL.md +15 -41
  77. package/skills/scope-architect/SKILL.md +57 -18
  78. package/skills/scope-hammer/SKILL.md +5 -5
  79. package/skills/shapeup/SKILL.md +3 -3
  80. package/skills/shapeup/resources/context-compaction.md +4 -3
  81. package/skills/solution-architect/SKILL.md +37 -15
  82. package/skills/spec-evaluator/SKILL.md +24 -7
  83. package/skills/spec-evaluator/references/dimensions/_registry.md +2 -2
  84. package/skills/spec-evaluator/references/dimensions/completeness.md +1 -1
  85. package/skills/spec-evaluator/references/dimensions/integration.md +98 -67
  86. package/skills/spec-evaluator/references/dimensions/spec-conformance.md +4 -4
  87. package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +1 -1
  88. package/skills/spec-evaluator/references/probing.md +42 -23
  89. package/skills/spec-evaluator/references/report-schema.md +2 -2
  90. package/skills/task-executor/SKILL.md +32 -18
  91. package/skills/tech-lead/SKILL.md +127 -438
  92. package/skills/tech-lead/references/gates.md +140 -49
  93. package/skills/tech-lead/references/protocol.md +832 -0
  94. package/skills/tech-lead/schemas/domain.schema.json +645 -198
  95. package/skills/tech-lead/schemas/gate-answers.schema.json +2 -2
  96. package/skills/tech-lead/schemas/work-order.schema.json +11 -2
  97. package/skills/tech-lead/schemas/work-result.schema.json +56 -18
  98. package/skills/tech-lead/workflows/shapeup-run.js +1474 -0
  99. package/skills/translator/SKILL.md +2 -2
  100. package/hooks/anti-rationalization.mjs +0 -244
  101. package/hooks/compact-snapshot.mjs +0 -47
  102. package/hooks/gate-deadline.mjs +0 -151
  103. package/hooks/gate-l2.mjs +0 -161
  104. package/hooks/session-rehydrate.mjs +0 -109
  105. package/skills/advisor-protocol/SKILL.md +0 -171
  106. package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +0 -239
  107. package/skills/spec-evaluator/README.md +0 -93
  108. package/skills/tech-lead/README.md +0 -71
  109. package/skills/tech-lead/references/delegation.md +0 -254
  110. package/skills/tech-lead/references/invocation.md +0 -45
  111. package/skills/tech-lead/references/ledger-schema.md +0 -214
  112. package/skills/tech-lead/references/round-protocol.md +0 -184
  113. package/skills/tech-lead/references/state-model.md +0 -66
  114. package/skills/tech-lead/scripts/ingest-result.mjs +0 -270
  115. package/skills/tech-lead/scripts/init-run.mjs +0 -326
  116. package/skills/tech-lead/scripts/lib/is-main.mjs +0 -82
  117. package/skills/tech-lead/scripts/lib/paths.mjs +0 -278
  118. package/skills/translator/README.md +0 -66
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- // T0 mechanical verification layer (design spec v1.1 §3.5, Blueprint A/E).
2
+ // T0 mechanical verification layer.
3
3
  //
4
4
  // Runs a scope's e2e fixtures + DB probe (zero LLM tokens), then — on green — the seesaw
5
5
  // regression check (re-runs every FINISHED scope's fixtures from the registry). Writes one
6
6
  // verdict artifact per attempt that spec-evaluator (T1) must cite; a verdict without it is
7
- // structurally invalid (PA4 countermeasure, DD-7). No agent can fabricate this file's contents
7
+ // structurally invalid. No agent can fabricate this file's contents
8
8
  // because it is produced by actually running the commands.
9
9
  //
10
- // Zero dependencies, zero network — same discipline as oracles/* and gate-l2.mjs.
10
+ // Zero dependencies, zero network — same discipline as oracles/* and the GATE L2 block.
11
11
  //
12
12
  // THE PAWL (v1.5). This script also owns the ratchet's comparison and its history. The attempt
13
13
  // loop used to be a BUDGETED RETRY LOOP wearing a ratchet's shape: `computeVerdict` returned four
@@ -29,7 +29,7 @@
29
29
  // N+1 build on attempt N instead of restarting from unexplained code.
30
30
  //
31
31
  // Usage:
32
- // node "${CLAUDE_PLUGIN_ROOT}/skills/tech-lead/scripts/t0-verify.mjs" <scope-contract.json> \
32
+ // node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" verify t0 <scope-contract.json> \
33
33
  // --round N --attempt M [--cwd <dir>] [--out <dir>] [--seesaw-registry <path>] [--no-seesaw]
34
34
  // [--no-ratchet]
35
35
  //
@@ -39,11 +39,28 @@ import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, rea
39
39
  import { join, dirname } from "node:path";
40
40
  import { spawnSync } from "node:child_process";
41
41
  import { createHash } from "node:crypto";
42
- import { digest } from "./aegis-digest.mjs";
43
- import { isMain } from "./lib/is-main.mjs";
44
- import { runArgs } from "./lib/argv.mjs";
45
- import { snapshot, restore, keptRef } from "./lib/ratchet-tree.mjs";
46
- import { readContract, SCOPE_CONTRACT } from "./lib/contract-md.mjs";
42
+ import { digest } from "../probe/digest.mjs";
43
+ import { runArgs } from "../lib/argv.mjs";
44
+ import { snapshot, restore, keptRef } from "./ratchet-tree.mjs";
45
+ import { readContract, SCOPE_CONTRACT } from "../lib/contract.mjs";
46
+ import { runIdFromRoot, localRoot, SHARED } from "../lib/paths.mjs";
47
+
48
+ /**
49
+ * The feature slug a scope contract belongs to, from its path.
50
+ *
51
+ * Scope contracts live at `<SHARED>/<slug>/scopes/<id>.md`, so the slug is the segment two levels
52
+ * above the file. Derived rather than flagged because every caller already names the contract, and
53
+ * a second way to say the same thing is a second thing to get wrong.
54
+ *
55
+ * @param {string} contractPath - Path to the scope contract, absolute or relative.
56
+ * @returns {string} The slug, or "" when the path does not have the expected shape — in which case
57
+ * the caller's `--out` is the only sensible answer and its absence is a usage error.
58
+ */
59
+ export function slugFromContractPath(contractPath) {
60
+ const parts = String(contractPath).split(/[/\\]/);
61
+ const i = parts.lastIndexOf("scopes");
62
+ return i > 0 ? parts[i - 1] : "";
63
+ }
47
64
 
48
65
  /**
49
66
  * Run one shell command and capture its outcome (10-minute timeout).
@@ -71,7 +88,24 @@ function runCommand(cmd, cwd) {
71
88
  */
72
89
  export function runFixtures(fixtures, cwd) {
73
90
  const results = (fixtures || []).map((cmd) => runCommand(cmd, cwd));
74
- return { pass: results.every((r) => r.pass), results };
91
+ // AN ABSENCE IS NOT A PASS, and this is the whole "measured, not claimed" invariant in one line.
92
+ //
93
+ // `[].every(...)` is `true`, so a scope whose contract declared no fixtures — or whose fixtures
94
+ // failed to PARSE, which is how it actually happened — scored `pass: true` and the verdict came
95
+ // back `overall: "green"`. Measured directly:
96
+ //
97
+ // runFixtures(undefined) -> pass=true, results=0
98
+ // computeVerdict(…) -> {"overall":"green"}
99
+ //
100
+ // Nothing ran, and the scope was certified. That is not a weaker version of the guarantee, it is
101
+ // the inverse of it: the one layer the evaluator is required to cite, green on zero evidence. The
102
+ // same reasoning the decision ledger states one directory over — "an absence proves nothing and
103
+ // must not sharpen the message".
104
+ //
105
+ // `ran` is reported rather than folded into `pass` so the two facts stay distinguishable: a scope
106
+ // with nothing to run is a contract that is not finished, which is different from a scope whose
107
+ // fixtures ran and failed, and the ratchet should be able to tell them apart.
108
+ return { pass: results.length > 0 && results.every((r) => r.pass), ran: results.length > 0, results };
75
109
  }
76
110
 
77
111
  /**
@@ -167,7 +201,7 @@ export function score({ fixtures, dbProbe, seesaw }) {
167
201
  * • REGRESSIONS DOMINATE. Breaking a previously-finished scope is never an improvement, whatever
168
202
  * the new scope's fixtures did. This is what lets the old seesaw branch collapse into the
169
203
  * general rule rather than needing a special case.
170
- * • DIFFERENT `fixtures_total` IS INCOMPARABLE, not worse. A scope split or remap changes the
204
+ * • DIFFERENT `fixtures_total` IS INCOMPARABLE, not worse. A re-slice changes the
171
205
  * denominator; comparing across it is a category error, so the ratchet treats it as a baseline
172
206
  * reset (`rebased`) rather than issuing a false verdict.
173
207
  *
@@ -183,9 +217,43 @@ export function better(next, current) {
183
217
  if (next.regressions !== current.regressions) return next.regressions < current.regressions;
184
218
  if (next.fixtures_passed !== current.fixtures_passed) return next.fixtures_passed > current.fixtures_passed;
185
219
  if (next.db_probe !== current.db_probe) return (next.db_probe ?? 0) > (current.db_probe ?? 0);
220
+ // EVERY COMPONENT TIES. What that means depends entirely on whether the incumbent was green.
221
+ //
222
+ // On a RED scope a tie is the sawtooth the pawl exists to stop, and it stays `false` — that is the
223
+ // case the rule above was written for and the case the guard fixtures assert (2/5 vs 2/5).
224
+ //
225
+ // On a GREEN scope there is no score left to win. Every further attempt ties BY CONSTRUCTION, so
226
+ // `false` restores the tree every time and the scope can never change again: the pawl stops being
227
+ // a pawl and becomes a weld. That is exactly the state a spec-conformance round is in. EVAL
228
+ // returns FAIL citing bugs T0's fixtures do not test — a bare stack trace, an error message that
229
+ // does not match the catalogue, validation ordered after a load — the next round's workers fix
230
+ // them, T0 scores 1/1 again because its fixtures never tested the prose, and the ratchet throws
231
+ // the fix away.
232
+ //
233
+ // Measured on a live run: round 2 produced six trials, 0 kept, 6 reverted, `bin/todo.js` byte-for-
234
+ // byte unchanged, and all three cited bugs reproducing. The FAIL → fix → re-evaluate loop — the
235
+ // harness's central promise — is inert for precisely the defects EVAL is best at finding.
236
+ //
237
+ // So: a green tie KEEPS. The tree may move when the score cannot, because by then the work being
238
+ // done is work T0 does not measure, and refusing it does not protect the scope — it freezes it.
239
+ if (isGreenScore(next) && isGreenScore(current)) return true;
186
240
  return false;
187
241
  }
188
242
 
243
+ /**
244
+ * Is this score a clean pass — every fixture passing, none of them absent, no outstanding regression?
245
+ *
246
+ * `fixtures_total > 0` is load-bearing: a scope with no fixtures has nothing to be green ABOUT, and
247
+ * treating its empty score as a pass is the same absence-reads-as-success mistake `runFixtures`
248
+ * made one function above.
249
+ *
250
+ * @param {{regressions:number, fixtures_passed:number, fixtures_total:number}} s - A trial score.
251
+ * @returns {boolean} True when the score represents a real, complete pass.
252
+ */
253
+ function isGreenScore(s) {
254
+ return s.regressions === 0 && s.fixtures_total > 0 && s.fixtures_passed === s.fixtures_total;
255
+ }
256
+
189
257
  /**
190
258
  * The one rule that replaces the protocol's two red branches.
191
259
  *
@@ -322,12 +390,12 @@ function sha256(text) {
322
390
  * the retry, so the artifact recording the regression was silently replaced by the one recording
323
391
  * the recovery, at the same path. Reproduced against the shipped script: two runs at
324
392
  * `--round 1 --attempt 2`, one red and one green, left ONE file — the red verdict was gone. This
325
- * is the same missing-identity-key defect the benchmark logged as its own #9, recurring on the one
393
+ * is the same missing-identity-key defect logged elsewhere in this codebase, recurring on the one
326
394
  * artifact the evaluator is structurally required to cite.
327
395
  *
328
396
  * An `existsSync` guard would be check-then-write: still racy, and still a policy expressed in code
329
397
  * rather than a property of the store. `flag: "wx"` makes overwriting IMPOSSIBLE — the filesystem
330
- * refuses with EEXIST and the loop moves to the next ordinal. Same class of move as `lib/is-main.mjs`:
398
+ * refuses with EEXIST and the loop moves to the next ordinal. Same class of move as the entry-point guard:
331
399
  * replace a fragile comparison with one that cannot silently be wrong.
332
400
  *
333
401
  * The evaluator's citation contract is unaffected: it re-hashes whatever path it is handed, and
@@ -364,7 +432,7 @@ export function writeArtifact(outDir, round, attempt, verdictBody) {
364
432
 
365
433
  /** The typed argv contract (see `./lib/argv.mjs`). */
366
434
  export const ARGV_SPEC = {
367
- usage: "t0-verify.mjs <scope-contract.json> --round N --attempt M [--cwd <dir>] [--out <dir>] " +
435
+ usage: "harness.mjs verify t0 <scope-contract.json> --round N --attempt M [--cwd <dir>] [--out <dir>] " +
368
436
  "[--seesaw-registry <path>] [--no-seesaw] [--no-ratchet]",
369
437
  _: { arity: 1, max: 1, name: "scope-contract.json" },
370
438
  round: { type: "int", min: 1, required: true },
@@ -377,25 +445,45 @@ export const ARGV_SPEC = {
377
445
  };
378
446
 
379
447
  /**
380
- * CLI entry: run a scope's fixtures + probe + seesaw, write the verdict artifact, print it, and
381
- * exit 0 (green) / 1 (red) / 2 (usage).
382
- * @returns {Promise<void>} Resolves after writing the artifact; the process exit code carries the verdict.
448
+ * Run the T0 evidence layer for one attempt and write the verdict artifact the judge cites.
449
+ *
450
+ * @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
451
+ * @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
452
+ * call `process.exit()` with the subcommand's documented code rather than returning.
383
453
  */
384
- async function main() {
385
- const args = runArgs(ARGV_SPEC);
454
+ export async function cli(rawArgv) {
455
+ const args = runArgs(ARGV_SPEC, rawArgv);
386
456
  const contractPath = args._[0];
387
- // Markdown first, legacy JSON second (ADR-0001, lib/contract-md.mjs).
457
+ // Markdown first, legacy JSON second (ADR-0001, lib/contract.mjs).
388
458
  const found = readContract(contractPath, SCOPE_CONTRACT);
389
459
  if (!found) { console.error(`t0-verify: no scope contract at ${contractPath} (.md or .json)`); process.exit(2); }
390
460
  const contract = found.contract;
391
461
  const cwd = args.cwd || process.cwd();
392
- const outDir = args.out || dirname(dirname(contractPath)); // default: <slug>/ (parent of scopes/)
462
+ // WHERE THE VERDICT LANDS, and the one thing about this file that must not be derived from the
463
+ // contract's own location.
464
+ //
465
+ // This used to be `dirname(dirname(contractPath))` — "the parent of scopes/" — which was correct
466
+ // while scope contracts and the run trace shared a root. ADR-0001 split the tiers and moved scope
467
+ // contracts to COMMITTED `shapeup/<slug>/scopes/`, so the default silently followed them: every
468
+ // T0 verdict and trial row was written to `shapeup/<slug>/t0/` while `probe t0` — the ONLY reader,
469
+ // and the one the build round's confirm stage asks — resolves `verdictsDir()` to
470
+ // `.shapeup/<slug>/t0/`. AGENTS.md is unambiguous that T0 artifacts are LOCAL tier.
471
+ //
472
+ // Measured on a live run: six verdicts and six trial rows were written, `foundation` among them
473
+ // with `fixtures_passed: 2/2, status: kept` — a real green — and the round still reported ZERO
474
+ // green scopes, tripped the inner breaker and returned `gate_h`. The evidence existed, was
475
+ // correct, and was invisible to the only thing that reads it. That is the same silent disconnect
476
+ // `hooks/lib/decision.mjs` records in its own header, one directory over.
477
+ //
478
+ // `--out` still wins: `verify t0` is also called with an explicit run root, and that caller knows
479
+ // better than any derivation.
480
+ const outDir = args.out || localRoot(cwd, slugFromContractPath(contractPath));
393
481
  const round = args.round;
394
482
  const attempt = args.attempt;
395
483
 
396
484
  const fixtures = runFixtures(contract.e2e_verification_fixtures, cwd);
397
485
  const dbProbe = runDbProbe(contract.db_probe, cwd);
398
- // --seesaw-registry is expected explicitly (tech-lead always passes it, delegation.md 3c);
486
+ // --seesaw-registry is expected explicitly (tech-lead always passes it, protocol.md 3c);
399
487
  // standalone CLI use without it simply skips the seesaw check rather than guessing a path.
400
488
  const seesawRegistry = args.noSeesaw ? null : args.seesawRegistry || null;
401
489
  const seesaw = args.noSeesaw || fixtures.pass === false
@@ -417,7 +505,13 @@ async function main() {
417
505
  const crashed = fixtures.results.some((r) => r.error) || !!dbProbe?.error;
418
506
  const { status, action } = decideStatus(verdictBetter, crashed);
419
507
 
508
+ // The run key, read from the receipt that lives in the run root this script was pointed at.
509
+ // `--out` IS that root, so identity comes from the receipt rather than from parsing a slug back
510
+ // out of a directory name; a caller that points `--out` somewhere else simply gets no key.
511
+ const runId = runIdFromRoot(outDir);
512
+
420
513
  const { path, sha256: hash, trial } = writeArtifact(outDir, round, attempt, {
514
+ ...(runId ? { run_id: runId } : {}),
421
515
  scope_id: contract.scope_id,
422
516
  fixtures: fixtures.results.map(({ cmd, exit, pass }) => ({ cmd, exit, pass })),
423
517
  db_probe: dbProbe && { cmd: dbProbe.cmd, exit: dbProbe.exit, pass: dbProbe.pass },
@@ -431,18 +525,37 @@ async function main() {
431
525
  // the pre-v1.5 behaviour, kept for standalone CLI use and for any caller managing its own tree.
432
526
  let tree = { ok: false, reason: "--no-ratchet" };
433
527
  if (!args.noRatchet) {
434
- tree = action === "keep" ? snapshot(contract.scope_id, cwd) : restore(contract.scope_id, cwd);
528
+ // The revert is bounded by what this scope was allowed to write. Unbounded, it rolls back every
529
+ // other scope building alongside it — see `restore`.
530
+ const own = contract.allowed_file_substrate || [];
531
+ tree = action === "keep" ? snapshot(contract.scope_id, cwd) : restore(contract.scope_id, cwd, own);
435
532
  // First trial with nothing to restore to: there is no kept tree yet BY DEFINITION. Take one,
436
533
  // so trial 2 has a floor to fall back to instead of inheriting the "no revert at all" defect.
437
- if (action === "restore" && !tree.ok) tree = { ...snapshot(contract.scope_id, cwd), fell_back_to: "snapshot" };
534
+ // Gated on `!baseline` (no prior kept/rebased trial exists at all), NOT on `!tree.ok` alone —
535
+ // a genuine restore failure with a real baseline to have restored to must NOT be silently
536
+ // promoted as the new kept tree; it stays `tree.ok === false` so the trial row records
537
+ // `tree_ref: null` and the failure stays visible instead of quietly becoming the baseline.
538
+ if (action === "restore" && !tree.ok && !baseline) tree = { ...snapshot(contract.scope_id, cwd), fell_back_to: "snapshot" };
438
539
  }
439
540
 
440
541
  // `baseline_trial` is the parent link — the experiment DAG (lineage, PARENT_OF and a genuine
441
542
  // SUPERSEDES edge) delivered as one field, with no graph store behind it.
442
- const trialNo = readTrials(trialsPath).length + 1;
543
+ //
544
+ // COUNTED WITHIN THE SCOPE, NEVER ACROSS THE FILE. This used to be
545
+ // `readTrials(trialsPath).length + 1` — every row in the run, whoever wrote it — which is a
546
+ // read-modify-write counter over a file that concurrent scopes append to. Scopes build in
547
+ // parallel, so several `verify t0` processes reach this line at once, each reads the same length
548
+ // and each writes the same ordinal: four concurrent scopes produced ordinals [1,1,1,4] and
549
+ // [1,1,3,3]. The ordinal is only ever consumed WITHIN a scope — `baseline` is picked from
550
+ // `priorTrials`, which is already filtered by `scope_id`, and the ratchet report groups by scope
551
+ // before it sorts — so counting the scope's own prior trials is both the race-free answer and the
552
+ // one the readers actually want. Two attempts of the SAME scope cannot race: one worker leg owns
553
+ // a scope and runs its attempts in sequence.
554
+ const trialNo = priorTrials.length + 1;
443
555
  const row = {
444
556
  schema_version: 1,
445
557
  trial: trialNo,
558
+ ...(runId ? { run_id: runId } : {}),
446
559
  round, attempt,
447
560
  scope_id: contract.scope_id,
448
561
  at: new Date().toISOString(),
@@ -465,6 +578,3 @@ async function main() {
465
578
  process.exit(verdict.overall === "green" ? 0 : 1);
466
579
  }
467
580
 
468
- if (isMain(import.meta.url)) {
469
- main();
470
- }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Traceability oracle (spine v1.3, plan docs/internal/plan/ratchet-and-receipt-plan.md §1.4 + §2).
2
+ // Traceability oracle (spine v1.3).
3
3
  //
4
4
  // ONE oracle, TWO mechanically-checkable assertions — nothing that merely *asserts* quality:
5
5
  //
@@ -30,18 +30,17 @@
30
30
  // Writes LOCAL .shapeup/<slug>/trace/report.json (regenerated each run)
31
31
  // LOCAL .shapeup/<slug>/trace/wiring.mmd (Mermaid view of the checked graph)
32
32
  //
33
- // Zero dependencies, zero network — same discipline as t0-verify.mjs / compile-order.mjs.
33
+ // Zero dependencies, zero network — same discipline as `harness verify t0` / `harness compile`.
34
34
  //
35
- // Usage: node skills/tech-lead/scripts/trace-lint.mjs --slug <slug> [--cwd <dir>] [--gate] [--quiet]
35
+ // Usage: node kernel/harness.mjs verify trace --slug <slug> [--cwd <dir>] [--gate] [--quiet]
36
36
  // Exit: advisory (default) → always 0. --gate → 1 when overall is red.
37
37
 
38
38
  import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "node:fs";
39
39
  import { resolve, join, dirname, relative, isAbsolute } from "node:path";
40
- import { readBoard } from "./compile-order.mjs";
41
- import { isMain } from "./lib/is-main.mjs";
42
- import { runArgs } from "./lib/argv.mjs";
43
- import { sharedRoot, traceDir, relLocal } from "./lib/paths.mjs";
44
- import { readContract, unreadableReason, WIRING_MAP, PROJECT_PROFILE } from "./lib/contract-md.mjs";
40
+ import { readBoard } from "../compile.mjs";
41
+ import { runArgs } from "../lib/argv.mjs";
42
+ import { sharedRoot, traceDir, relLocal } from "../lib/paths.mjs";
43
+ import { readContract, unreadableReason, LEGACY_LAYOUT, WIRING_MAP, PROJECT_PROFILE } from "../lib/contract.mjs";
45
44
 
46
45
  // --- requirements.md registry parser -----------------------------------------
47
46
  // A committed markdown table: | REQ-id | clause (verbatim) | source | status | note |
@@ -260,7 +259,7 @@ export function traceLint(slug, { cwd, gate = false }) {
260
259
  }
261
260
  if (wiringFound) wiringMap = wiringFound.contract;
262
261
 
263
- // HD-001. A map whose `## Wiring` table is under a heading the parser does not claim reads as
262
+ // A map whose `## Wiring` table is under a heading the parser does not claim reads as
264
263
  // zero entries, and the loop below then walks nothing and reports `0/0 engines reach <entry>` —
265
264
  // GREEN, for a committed file holding six correct rows. The gate whose entire purpose is that no
266
265
  // engine ships orphaned failing open, on a file that looks right to every human who reviews it.
@@ -273,6 +272,16 @@ export function traceLint(slug, { cwd, gate = false }) {
273
272
  skipped_reason: `wiring-map.md is present but unreadable (${wiringUnreadable}) — reachability cannot be claimed.` };
274
273
  }
275
274
 
275
+ // A map that parsed only through the migration reader still WORKS — but silence about that is
276
+ // how a temporary fallback becomes a permanent second format. Warn, so the next `wire` converges
277
+ // the file on the canonical table rather than the reader carrying the old shape indefinitely.
278
+ if (wiringMap && wiringMap[LEGACY_LAYOUT]) {
279
+ findings.push({ severity: "warn", code: "WIRING-LEGACY-LAYOUT", message:
280
+ "wiring-map.md is in the pre-canonical per-use-case layout (`### UC-xx` + a `| Field | Value |` table). " +
281
+ "It reads correctly through the migration reader, but the canonical form is one `## Wiring` table — " +
282
+ "re-run the wire operation to converge it, so this artifact stops having two shapes." });
283
+ }
284
+
276
285
  if (wiringMap) {
277
286
  let profileFound = null;
278
287
  try { profileFound = readContract(profilePath, PROJECT_PROFILE); }
@@ -329,7 +338,7 @@ export function traceLint(slug, { cwd, gate = false }) {
329
338
  // ---------------------------------------------------------------------------
330
339
  /** The typed argv contract (see `./lib/argv.mjs`). */
331
340
  export const ARGV_SPEC = {
332
- usage: "trace-lint.mjs --slug <slug> [--cwd <dir>] [--gate] [--quiet]",
341
+ usage: "harness.mjs verify trace --slug <slug> [--cwd <dir>] [--gate] [--quiet]",
333
342
  _: { arity: 0, max: 0, name: "(no positional operands)" },
334
343
  slug: { type: "str", required: true },
335
344
  cwd: { type: "path" },
@@ -337,9 +346,15 @@ export const ARGV_SPEC = {
337
346
  quiet: { type: "flag" },
338
347
  };
339
348
 
340
- const isMainModule = isMain(import.meta.url);
341
- if (isMainModule) {
342
- const args = runArgs(ARGV_SPEC);
349
+ /**
350
+ * Run the traceability oracle over the spine artifacts and write its report.
351
+ *
352
+ * @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
353
+ * @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
354
+ * call `process.exit()` with the subcommand's documented code rather than returning.
355
+ */
356
+ export async function cli(rawArgv) {
357
+ const args = runArgs(ARGV_SPEC, rawArgv);
343
358
  const cwd = resolve(args.cwd || process.cwd());
344
359
  const slug = args.slug;
345
360
  const gate = !!args.gate;
@@ -1,4 +1,4 @@
1
- // Shared helpers for evaluation-contract oracle runners (Stage G of the audit).
1
+ // Shared helpers for evaluation-contract oracle runners.
2
2
  //
3
3
  // The `process` oracle (step 1–3) inlined these; the test/snapshot/http oracles (steps 4–5)
4
4
  // import them so the matching grammar is identical across the registry. Keeping one definition
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // `http` oracle for the evaluation contract (Stage G, step 5 of the audit).
2
+ // `http` oracle for the evaluation contract.
3
3
  //
4
4
  // Deliverable: a service / API. The oracle starts the server, waits until it is reachable,
5
5
  // sends each criterion's request, and grades the OBSERVED response (status + body) — never the
@@ -34,7 +34,7 @@ import { spawn } from "node:child_process";
34
34
  import { createServer } from "node:net";
35
35
  import { readFileSync } from "node:fs";
36
36
  import { matchNum, toRegExp, formatReport } from "./_shared.mjs";
37
- import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
37
+ import { isMain } from "../kernel/lib/argv.mjs";
38
38
 
39
39
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
40
40
 
package/oracles/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // Oracle registry for the evaluation contract (Stage G of the audit).
1
+ // Oracle registry for the evaluation contract.
2
2
  //
3
3
  // The single entry point `spec-evaluator` dispatches through when a criterion / Test-Surface row
4
4
  // carries an `oracle:` tag. Adding a new oracle type = registering one runner here (the spec's
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Shared `process` oracle for the evaluation contract (Stage G of the audit).
2
+ // Shared `process` oracle for the evaluation contract.
3
3
  //
4
4
  // This is the promoted, parameterized form of the todo-cli prototype: instead of hard-coding
5
5
  // criteria, it reads a *declarative contract* (a list of criteria, each with a `probe` and an
@@ -25,7 +25,7 @@ import { spawnSync } from "node:child_process";
25
25
  import { mkdtempSync, writeFileSync, rmSync, readFileSync } from "node:fs";
26
26
  import { tmpdir } from "node:os";
27
27
  import { join } from "node:path";
28
- import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
28
+ import { isMain } from "../kernel/lib/argv.mjs";
29
29
 
30
30
  const CRASH_RE = /at\s+.*:\d+:\d+|Traceback|panic:|unhandled|Segmentation fault/i;
31
31
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // `snapshot` oracle for the evaluation contract (Stage G, step 4b of the audit).
2
+ // `snapshot` oracle for the evaluation contract.
3
3
  //
4
4
  // Deliverable: a generator or a pure refactor — something whose acceptance is "produces exactly
5
5
  // this output". The oracle runs the deliverable, captures its stdout, and diffs it against a
@@ -28,7 +28,7 @@ import { spawnSync } from "node:child_process";
28
28
  import { readFileSync, existsSync } from "node:fs";
29
29
  import { dirname, isAbsolute, join } from "node:path";
30
30
  import { CRASH_RE, formatReport } from "./_shared.mjs";
31
- import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
31
+ import { isMain } from "../kernel/lib/argv.mjs";
32
32
 
33
33
  // Normalize so trailing spaces and final-newline differences never cause a spurious FAIL.
34
34
  function normalize(s) {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // `test` oracle for the evaluation contract (Stage G, step 4a of the audit).
2
+ // `test` oracle for the evaluation contract.
3
3
  //
4
4
  // Deliverable: a library / module whose acceptance is "its own test suite is green".
5
5
  // The oracle runs the project's test command and grades the OBSERVED suite result —
@@ -31,7 +31,7 @@
31
31
  import { spawnSync } from "node:child_process";
32
32
  import { readFileSync } from "node:fs";
33
33
  import { matchNum, toRegExp, formatReport } from "./_shared.mjs";
34
- import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
34
+ import { isMain } from "../kernel/lib/argv.mjs";
35
35
 
36
36
  // Parse common test-runner output into { total, pass, fail, failing[] }.
37
37
  function parseSuite(out) {
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "shapeup-sdlc",
3
- "version": "1.6.3",
4
- "description": "Shape Up for coding agents with gates the agent can't talk its way past. Harness for Claude Code, compiled to Cursor, Antigravity and Codex.",
3
+ "version": "3.0.0",
4
+ "description": "Shape Up for coding agents \u2014 with gates the agent can't talk its way past. Harness for Claude Code.",
5
5
  "bin": {
6
6
  "shapeup-sdlc": "bin/init.mjs"
7
7
  },
8
8
  "files": [
9
+ ".claude-plugin/",
10
+ ".claude/settings.local.example.json",
11
+ ".env.shapeup.example",
12
+ "AGENTS.md",
13
+ "SECURITY.md",
9
14
  "bin/",
10
- "skills/",
11
- "!skills/**/evals/**",
12
- "hooks/",
13
15
  "commands/",
16
+ "hooks/",
17
+ "kernel/",
14
18
  "oracles/",
15
- ".claude-plugin/",
16
- "AGENTS.md",
17
- ".env.shapeup.example",
18
- ".claude/settings.local.example.json",
19
- "SECURITY.md"
19
+ "skills/"
20
20
  ],
21
21
  "repository": {
22
22
  "type": "git",
@@ -35,13 +35,11 @@
35
35
  "scripts": {
36
36
  "test": "node tests/structural.mjs",
37
37
  "demo": "node tools/demo/record-demo.mjs",
38
- "distribute": "node tools/distribute.js"
38
+ "test:grant": "node tests/grant/executing-grant.mjs"
39
39
  },
40
40
  "keywords": [
41
41
  "shapeup",
42
42
  "sdlc",
43
- "cursor",
44
- "antigravity",
45
43
  "claude-code"
46
44
  ],
47
45
  "author": "Liberty Nguyen",
@@ -24,16 +24,16 @@ Invoked as `--order <path>`. Fields you may rely on (absent = unknown; surface i
24
24
 
25
25
  | Field | What it is |
26
26
  |---|---|
27
- | `operation` | `analyze` (pitch → full spec tree + board) · `generate-board` (regenerate the LOCAL board from the committed spec) · `reconcile` (fold discovered-ledger items into the board + UC invariants) · `retrofit-surface` (append `## Test Surface` to a pre-surface spec) · `coverage` (extract atomic requirement clauses → the SHARED `requirements.md` registry) |
27
+ | `operation` | `analyze` (pitch → full spec tree + board) · `reconcile` (fold discovered-ledger items into the board + UC invariants) · `retrofit-surface` (append `## Test Surface` to a pre-surface spec) · `coverage` (extract atomic requirement clauses → the SHARED `requirements.md` registry) |
28
28
  | `payload.pitch` | The pitch/PRD path (analyze) |
29
29
  | `payload.requirements` | (coverage) the REQ source to extract atomic clauses from — pitch / a customer-requirements doc / the use-case bodies. Absent → default to the pitch and record the choice in `assumptions[]` |
30
- | `payload.lens` | `lite` \| `standard` \| `cross-context`. Absent → judge it: LITE for ≤2-week appetite, no third-party, ≤3 user-facing actions; STANDARD for multi-team, third-party, or bigger appetite; genuinely unclear → one binary question / one `escalates[]` entry |
30
+ | `payload.lens` | `lite` \| `standard` \| `cross-context`. Absent → judge it: LITE for ≤2-week appetite, no third-party, ≤3 user-facing actions; STANDARD for multi-team, third-party, or bigger appetite; genuinely unclear → one binary question, or `status: "escalated"` with the question in `deviations[]` |
31
31
  | `payload.orient_dir` | The Scout's artifacts — `code-surface.md` IS your codebase map (do not re-scan), `discovered-seed.md` seeds task gen, `spike-*.md` feeds feasibility |
32
32
  | `payload.spec_folder` / `payload.feature` | Where the committed tree lives / the slug |
33
33
  | `payload.discovered_ledger` | (reconcile) the ledger whose raw `[+]`/`~` lines you fold in |
34
34
  | `payload.kb_rules_path` | Team guidelines (read if present) — steering, never spec |
35
- | `substrate.allowed / append_only / frozen` | Your write contract for THIS operation. The old frozen-zone prose is now data the sandbox hook enforces: respect it, and when an operation genuinely needs a file outside it, ESCALATE — never widen |
36
- | `interaction.pause_gates` | Caller policy. `true` (standalone default): pause at the phase checkpoints below, max 2 questions each. `false`: run straight through, surfacing questions as `assumptions[]`/`escalates[]` instead |
35
+ | `substrate.allowed / append_only / frozen` | Your write contract for THIS operation. The old frozen-zone prose is now data the sandbox hook enforces (reading the order's envelope): respect it, and when an operation genuinely needs a file outside it, ESCALATE — never widen |
36
+ | `interaction.pause_gates` | Caller policy. `true` (standalone default): pause at the phase checkpoints below, max 2 questions each. `false`: run straight through, surfacing questions as `assumptions[]` (or `deviations[]` when they block) instead |
37
37
 
38
38
  ---
39
39
 
@@ -50,7 +50,7 @@ its phase; templates live in `assets/templates/`.
50
50
  2 DDD bounded contexts, aggregates (new vs extended), value objects, domain events,
51
51
  repository interfaces → domain-model.md [references/ddd-patterns.md]
52
52
  2b CONTRACTS (standard lens) typed Request/Response/Error per repository; two-pass rule:
53
- unresolvable at spec time → `⏳ TBD — verify in TASK-NNN-spike-…`, resolved
53
+ unresolvable at spec time → `⏳ TBD — verify in the [UC-x] spike`, resolved
54
54
  post-SPIKE with citation → contracts/ [references/contract-patterns.md]
55
55
  3 UX per screen: state table (idle→loading→error→success), error cases with
56
56
  message+action, ASCII flows → ux-behavior.md [references/ux-behavior-patterns.md]
@@ -64,10 +64,10 @@ its phase; templates live in `assets/templates/`.
64
64
  6 TASKS atomic, ordered, executable → tasks/ (LOCAL root; the one uncommitted branch
65
65
  of the tree — regenerable, machine-local) [references/task-generation.md]
66
66
  7 DERIVE+LINT mechanical, not yours to grade:
67
- node "${CLAUDE_PLUGIN_ROOT}/skills/ba-pitch-analyzer/scripts/board-derive.mjs" --slug <slug> --write
67
+ node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" reduce board --slug <slug> --write
68
68
  (unlocks = depends_on inverse; Σ hours; critical path; appetite arithmetic —
69
69
  overflow is a fact you REPORT for the caller's HAMMER gate, never resolve)
70
- node "${CLAUDE_PLUGIN_ROOT}/skills/ba-pitch-analyzer/scripts/spec-lint.mjs" --slug <slug>
70
+ node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" verify spec --slug <slug>
71
71
  (structure, wikilinks, edge symmetry — fix reds, then re-run; you never
72
72
  self-grade with a hand-walked checklist)
73
73
  → scope-summary.md + synthesis.md (traceability matrix, risk register,
@@ -94,15 +94,13 @@ second path to green.
94
94
 
95
95
  ---
96
96
 
97
- ## The other four operations — same craft, different payload + whitelist
97
+ ## The other three operations — same craft, different payload + whitelist
98
98
 
99
99
  | Operation | Essence | Never |
100
100
  |---|---|---|
101
- | `generate-board` | Re-derive the full task set fresh from the committed `usecases/` + `domain-model.md` (+ scope contracts if present tasks respect their substrates). Numbering restarts at TASK-001. Initialize `status` from committed mechanical truth at SCOPE granularity (a scope with hill shard FINISHED its tasks start `done`) never join on task id; ids renumber per machine, the scope is the stable key. Then board-derive `--write` + regenerate scope-summary.md | touch the committed spec docs (frozen in your substrate) |
102
- | `reconcile` | Verify `ledger.feature == payload.feature` (mismatch → STOP). Map each `[+]` Keep item → its owning UC; new task continues numbering (never renumber); `~`/Cut → synthesis "Hammered Out" row, no file. A Keep item asserting a new invariant → APPEND `[INV-NN]` + TS-INV row to that UC (append-only sections in your substrate). A new actor/action with no UC → `escalates[]` (spec-ambiguity): spawning a UC mid-cycle is silent re-shaping, the PO decides. Finish with board-derive (appetite overflow → report) + spec-lint | re-run phases 1–5; edit UC Steps; resolve the appetite HAMMER yourself |
101
+ | `reconcile` | Verify `ledger.feature == payload.feature` (mismatch STOP). Map each `[+]` Keep item its owning UC; new task continues numbering (never renumber); `~`/Cut synthesis "Hammered Out" row, no file. A Keep item asserting a new invariant → APPEND `[INV-NN]` + TS-INV row to that UC (append-only sections in your substrate). A new actor/action with no UC → `status: "escalated"` + a `deviations[]` spec-ambiguity entry: spawning a UC mid-cycle is silent re-shaping, the PO decides. Finish with board-derive (appetite overflow → report) + spec-lint | re-run phases 1–5; edit UC Steps; resolve the appetite HAMMER yourself |
103
102
  | `retrofit-surface` | Append `## Test Surface` (derived rows only, after Error Cases) to each UC of a pre-surface spec; an all-sources-empty UC gets the explicit empty-sources line | touch anything else — append-only substrate |
104
- | `coverage` | Extract **atomic** customer requirement clauses from `payload.requirements` (default: the pitch) and write the SHARED `shapeup/<slug>/requirements.md` registry: one `\| REQ-id \| clause (verbatim) \| source \| status \| note \|` row per clause. Split compound sentences into one testable clause each — the audit's dropped clauses ("side-step OR lure enemies into traps", "low-res world textures") were *lost inside* a bigger sentence. **Assign REQ-ids ONCE and freeze them** (they behave like scope_id, never TASK-NNN — every `covers:` link rots otherwise): re-running, append new clauses with fresh ids, mark a removed clause `CUT (PO-approved)`, never renumber or delete. Status starts `covered` (a live requirement); only the PO sets `CUT`. The REQ source itself is frozen — the registry is a separate derived file | edit the REQ source; renumber existing REQ-ids; delete a dropped clause instead of marking it CUT; invent a requirement not in the source |
105
-
103
+ | `coverage` | Extract **atomic** customer requirement clauses from `payload.requirements` (default: the pitch) and write the SHARED `shapeup/<slug>/requirements.md` registry: one `\| REQ-id \| clause (verbatim) \| source \| status \| note \|` row per clause. Split compound sentences into one testable clause each — a clause lost *inside* a bigger sentence is a requirement nothing can be traced to. **Assign REQ-ids ONCE and freeze them** (they behave like scope_id, never TASK-NNN — every `covers:` link rots otherwise): re-running, append new clauses with fresh ids, mark a removed clause `CUT (PO-approved)`, never renumber or delete. Status starts `covered` (a live requirement); only the PO sets `CUT`. The REQ source itself is frozen — the registry is a separate derived file | edit the REQ source; renumber existing REQ-ids; delete a dropped clause instead of marking it CUT; invent a requirement not in the source |
106
104
  ---
107
105
 
108
106
  ## Anti-rationalization table
@@ -115,12 +113,19 @@ second path to green.
115
113
  | "My output looks complete, score it 92" | You don't grade yourself. spec-lint reports facts; the judge judges. |
116
114
  | "The appetite overflow is small, drop a nice-to-have myself" | Overflow is a HAMMER gate for the caller. You report the fact and the candidate cuts. |
117
115
  | "Re-scanning the codebase is safer than trusting orient" | code-surface.md IS the map. Re-scanning burns tokens and forks the truth. |
118
- | "unlocks is quick to fill in by hand" | Hand-authored unlocks produced 10 asymmetric edges (KB-BA-001). board-derive computes it. |
116
+ | "unlocks is quick to fill in by hand" | Hand-authored unlocks drift asymmetric observed, repeatedly. board-derive computes it. |
119
117
 
120
118
  ---
121
119
 
122
120
  ## Output contract — the WorkResult
123
121
 
122
+ **Escalation rule.** If you return `status: "escalated"`, the **first** entry in `deviations[]`
123
+ must be the blocker: one specific, answerable question plus the context needed to answer it.
124
+ Nothing else in the envelope carries it — there is no `escalates[]` field — so a vague entry, or
125
+ the question buried under other notes, reaches the human as "something went wrong" and costs a
126
+ round. Write it so someone without your context can answer it in one reply.
127
+
128
+
124
129
  Domain artifacts land inside your substrate (the committed spec tree + the LOCAL board). Then
125
130
  write `.shapeup/<slug>/results/<order-suffix>.json`:
126
131
 
@@ -129,9 +134,8 @@ write `.shapeup/<slug>/results/<order-suffix>.json`:
129
134
  "schema_version": 1, "order_id": "<copied>", "worker": "ba-pitch-analyzer",
130
135
  "status": "done | partial | escalated",
131
136
  "artifacts": ["shapeup/<slug>/spec/domain-model.md", "…"],
132
- "escalates": [ { "kind": "spec-ambiguity", "question": "New actor 'auditor' has no UC — add UC-07 or cut?" } ],
133
137
  "assumptions": ["lens=standard — third-party PSP present"],
134
- "deviations": [],
138
+ "deviations": [ "ESCALATE spec-ambiguity — New actor 'auditor' has no UC — add UC-07 or cut?" ],
135
139
  "discoveries": [ { "marker": "+", "line": "appetite overflow 12h — candidate cuts: TASK-014, TASK-017" } ]
136
140
  }
137
141
  ```
@@ -161,15 +165,16 @@ status flips for built work (ingest's job), scope contracts (scope-architect's),
161
165
  /ba-pitch-analyzer --order .shapeup/checkout-vnpay/orders/analyze.json
162
166
 
163
167
  # Standalone — the preamble shim compiles the order (mode: standalone, pause_gates: true):
164
- # node "${CLAUDE_PLUGIN_ROOT}/skills/tech-lead/scripts/compile-order.mjs" --operation analyze --slug <slug> \
168
+ # node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" compile --operation analyze --slug <slug> \
165
169
  # --worker ba-pitch-analyzer --payload '{"pitch": "docs/pitch.md", "lens": "standard"}'
166
170
  /ba-pitch-analyzer docs/pitch.md # operation: analyze, lens judged
167
171
  /ba-pitch-analyzer --lens standard docs/pitch.md # lens pinned
168
172
  ```
169
173
 
170
174
  Standalone keeps exactly two flags: the pitch input and `--lens`. Every retired flag is now
171
- caller context: `--tasks-only` → a generate-board order, `--from-discovered` → a reconcile
172
- order, `--surface-only` → a retrofit-surface order, `--remap`/`--split` → scope-architect
173
- orders, `--status` → read `spec-lint.mjs`/`board-derive.mjs` output (zero LLM tokens),
175
+ caller context: `--tasks-only`/`--from-discovered` → a reconcile order, `--surface-only` → a
176
+ retrofit-surface order, `--coverage` → a coverage order, `--remap`/`--split` → a
177
+ scope-architect `map-scopes` order,
178
+ `--status` → read `harness verify spec`/`harness reduce board` output (zero LLM tokens),
174
179
  `--auto`/`--skip-gate*` → `interaction.pause_gates`, `--upgrade` → an analyze order with the
175
180
  standard lens over an existing lite tree (reconciliation pass: extend, never overwrite Steps).
@@ -64,7 +64,7 @@ audit_rules_version: "2.5"
64
64
 
65
65
  ## Audit Report
66
66
 
67
- *Generated from spec-lint.mjs output — do not edit manually.*
67
+ *Generated from harness verify spec output — do not edit manually.*
68
68
  *skill_version: 2.1 | audit_rules_version: 2.1*
69
69
 
70
70
  ### Score Summary