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.
- package/.claude/settings.local.example.json +5 -5
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +54 -107
- package/README.md +165 -151
- package/SECURITY.md +49 -27
- package/bin/init.mjs +93 -108
- package/bin/lib/grant.mjs +145 -0
- package/commands/build.md +20 -0
- package/commands/eval.md +5 -4
- package/commands/scopes.md +5 -4
- package/commands/shape.md +1 -1
- package/commands/ship.md +53 -7
- package/commands/wire.md +1 -1
- package/hooks/dispatch-receipt.mjs +195 -0
- package/hooks/gate-intake.mjs +16 -17
- package/hooks/gate-zerowork.mjs +107 -25
- package/hooks/hooks.json +9 -48
- package/hooks/lib/decision.mjs +38 -19
- package/hooks/safety-spine.mjs +4 -4
- package/hooks/sandbox-guard.mjs +130 -50
- package/{skills/tech-lead/scripts/compile-order.mjs → kernel/compile.mjs} +344 -55
- package/{skills/tech-lead/scripts/gate-answers.mjs → kernel/gate.mjs} +69 -26
- package/kernel/harness.mjs +134 -0
- package/{skills/tech-lead/scripts/fit-check.mjs → kernel/init/fit.mjs} +25 -22
- package/kernel/init/run.mjs +489 -0
- package/{skills/tech-lead/scripts → kernel}/lib/argv.mjs +52 -31
- package/{skills/tech-lead/scripts/lib/contract-md.mjs → kernel/lib/contract.mjs} +261 -17
- package/kernel/lib/paths.mjs +491 -0
- package/kernel/probe/concurrency.mjs +510 -0
- package/{skills/tech-lead/scripts/aegis-digest.mjs → kernel/probe/digest.mjs} +10 -11
- package/kernel/probe/eval.mjs +77 -0
- package/kernel/probe/leg.mjs +125 -0
- package/kernel/probe/resume.mjs +528 -0
- package/{skills/tech-lead/scripts → kernel/probe}/stats.mjs +102 -21
- package/kernel/probe/t0.mjs +66 -0
- package/{skills/ba-pitch-analyzer/scripts/board-derive.mjs → kernel/reduce/board.mjs} +104 -28
- package/kernel/reduce/graph.mjs +437 -0
- package/kernel/reduce/hill.mjs +152 -0
- package/kernel/reduce/ingest.mjs +633 -0
- package/{hooks/slop-cleaner.mjs → kernel/reduce/leftovers.mjs} +40 -57
- package/{skills/tech-lead/scripts/ship-report.mjs → kernel/reduce/ship.mjs} +93 -14
- package/{skills/tech-lead/scripts/run-snapshot.mjs → kernel/reduce/snapshot.mjs} +22 -21
- package/{skills/spec-evaluator/scripts/verdict-ledger.mjs → kernel/reduce/verdict.mjs} +14 -8
- package/kernel/report/export.mjs +325 -0
- package/kernel/report/facts.mjs +347 -0
- package/{skills/tech-lead/scripts/budget-check.mjs → kernel/verify/budget.mjs} +22 -25
- package/kernel/verify/dispatch.mjs +114 -0
- package/{skills/tech-lead/scripts/validate-envelope.mjs → kernel/verify/envelope.mjs} +20 -15
- package/{skills/tech-lead/scripts/lib → kernel/verify}/ratchet-tree.mjs +36 -14
- package/kernel/verify/skills.mjs +125 -0
- package/kernel/verify/spec.mjs +559 -0
- package/{skills/tech-lead/scripts/t0-verify.mjs → kernel/verify/t0.mjs} +138 -28
- package/{skills/tech-lead/scripts/trace-lint.mjs → kernel/verify/trace.mjs} +28 -13
- package/oracles/_shared.mjs +1 -1
- package/oracles/http-oracle.mjs +2 -2
- package/oracles/index.mjs +1 -1
- package/oracles/process-oracle.mjs +2 -2
- package/oracles/snapshot-oracle.mjs +2 -2
- package/oracles/test-oracle.mjs +2 -2
- package/package.json +11 -13
- package/skills/ba-pitch-analyzer/SKILL.md +24 -19
- package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +1 -1
- package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +12 -7
- package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +6 -3
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +5 -4
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +23 -13
- package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +17 -12
- package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +17 -38
- package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +3 -0
- package/skills/ba-pitch-analyzer/references/contract-patterns.md +8 -4
- package/skills/ba-pitch-analyzer/references/doc-schemas.md +3 -1
- package/skills/ba-pitch-analyzer/references/task-generation.md +8 -8
- package/skills/ba-pitch-analyzer/references/test-surface.md +1 -1
- package/skills/coach/SKILL.md +3 -3
- package/skills/orient/SKILL.md +2 -1
- package/skills/qa-edge-hunter/SKILL.md +15 -41
- package/skills/scope-architect/SKILL.md +57 -18
- package/skills/scope-hammer/SKILL.md +5 -5
- package/skills/shapeup/SKILL.md +3 -3
- package/skills/shapeup/resources/context-compaction.md +4 -3
- package/skills/solution-architect/SKILL.md +37 -15
- package/skills/spec-evaluator/SKILL.md +24 -7
- package/skills/spec-evaluator/references/dimensions/_registry.md +2 -2
- package/skills/spec-evaluator/references/dimensions/completeness.md +1 -1
- package/skills/spec-evaluator/references/dimensions/integration.md +98 -67
- package/skills/spec-evaluator/references/dimensions/spec-conformance.md +4 -4
- package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +1 -1
- package/skills/spec-evaluator/references/probing.md +42 -23
- package/skills/spec-evaluator/references/report-schema.md +2 -2
- package/skills/task-executor/SKILL.md +32 -18
- package/skills/tech-lead/SKILL.md +127 -438
- package/skills/tech-lead/references/gates.md +140 -49
- package/skills/tech-lead/references/protocol.md +832 -0
- package/skills/tech-lead/schemas/domain.schema.json +645 -198
- package/skills/tech-lead/schemas/gate-answers.schema.json +2 -2
- package/skills/tech-lead/schemas/work-order.schema.json +11 -2
- package/skills/tech-lead/schemas/work-result.schema.json +56 -18
- package/skills/tech-lead/workflows/shapeup-run.js +1474 -0
- package/skills/translator/SKILL.md +2 -2
- package/hooks/anti-rationalization.mjs +0 -244
- package/hooks/compact-snapshot.mjs +0 -47
- package/hooks/gate-deadline.mjs +0 -151
- package/hooks/gate-l2.mjs +0 -161
- package/hooks/session-rehydrate.mjs +0 -109
- package/skills/advisor-protocol/SKILL.md +0 -171
- package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +0 -239
- package/skills/spec-evaluator/README.md +0 -93
- package/skills/tech-lead/README.md +0 -71
- package/skills/tech-lead/references/delegation.md +0 -254
- package/skills/tech-lead/references/invocation.md +0 -45
- package/skills/tech-lead/references/ledger-schema.md +0 -214
- package/skills/tech-lead/references/round-protocol.md +0 -184
- package/skills/tech-lead/references/state-model.md +0 -66
- package/skills/tech-lead/scripts/ingest-result.mjs +0 -270
- package/skills/tech-lead/scripts/init-run.mjs +0 -326
- package/skills/tech-lead/scripts/lib/is-main.mjs +0 -82
- package/skills/tech-lead/scripts/lib/paths.mjs +0 -278
- package/skills/translator/README.md +0 -66
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// T0 mechanical verification layer
|
|
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
|
|
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
|
|
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}/
|
|
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 "
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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: "
|
|
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
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* @
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
33
|
+
// Zero dependencies, zero network — same discipline as `harness verify t0` / `harness compile`.
|
|
34
34
|
//
|
|
35
|
-
// Usage: node
|
|
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 "
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
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
|
-
//
|
|
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: "
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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;
|
package/oracles/_shared.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Shared helpers for evaluation-contract oracle runners
|
|
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
|
package/oracles/http-oracle.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// `http` oracle for the evaluation contract
|
|
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 "../
|
|
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
|
|
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
|
|
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 "../
|
|
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
|
|
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 "../
|
|
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) {
|
package/oracles/test-oracle.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// `test` oracle for the evaluation contract
|
|
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 "../
|
|
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": "
|
|
4
|
-
"description": "Shape Up for coding agents
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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) · `
|
|
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
|
|
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[]
|
|
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
|
|
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}/
|
|
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}/
|
|
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
|
|
97
|
+
## The other three operations — same craft, different payload + whitelist
|
|
98
98
|
|
|
99
99
|
| Operation | Essence | Never |
|
|
100
100
|
|---|---|---|
|
|
101
|
-
| `
|
|
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 —
|
|
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
|
|
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}/
|
|
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
|
|
172
|
-
order, `--
|
|
173
|
-
|
|
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
|
|
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
|