shapeup-sdlc 1.7.0 → 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 +17 -3
- package/README.md +124 -103
- package/SECURITY.md +37 -30
- package/bin/init.mjs +77 -47
- package/bin/lib/grant.mjs +145 -0
- package/commands/build.md +7 -6
- package/commands/ship.md +29 -16
- package/commands/wire.md +1 -1
- package/hooks/dispatch-receipt.mjs +195 -0
- package/hooks/gate-intake.mjs +3 -3
- package/hooks/gate-zerowork.mjs +25 -24
- package/hooks/hooks.json +9 -48
- package/hooks/lib/decision.mjs +19 -8
- package/hooks/safety-spine.mjs +3 -3
- package/hooks/sandbox-guard.mjs +84 -40
- package/{skills/tech-lead/scripts/compile-order.mjs → kernel/compile.mjs} +283 -41
- package/{skills/tech-lead/scripts/gate-answers.mjs → kernel/gate.mjs} +58 -14
- package/kernel/harness.mjs +134 -0
- package/{skills/tech-lead/scripts/fit-check.mjs → kernel/init/fit.mjs} +15 -12
- package/{skills/tech-lead/scripts/init-run.mjs → kernel/init/run.mjs} +152 -31
- package/{skills/tech-lead/scripts → kernel}/lib/argv.mjs +52 -31
- package/{skills/tech-lead/scripts/lib/contract-md.mjs → kernel/lib/contract.mjs} +251 -7
- 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} +9 -10
- package/kernel/probe/eval.mjs +77 -0
- package/kernel/probe/leg.mjs +125 -0
- package/{skills/tech-lead/scripts/resume-state.mjs → kernel/probe/resume.mjs} +204 -63
- package/{skills/tech-lead/scripts → kernel/probe}/stats.mjs +93 -11
- package/kernel/probe/t0.mjs +66 -0
- package/{skills/ba-pitch-analyzer/scripts/board-derive.mjs → kernel/reduce/board.mjs} +99 -23
- package/kernel/reduce/graph.mjs +437 -0
- package/{skills/tech-lead/scripts/hill-derive.mjs → kernel/reduce/hill.mjs} +28 -9
- 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} +61 -13
- package/{skills/tech-lead/scripts/run-snapshot.mjs → kernel/reduce/snapshot.mjs} +21 -15
- package/{skills/spec-evaluator/scripts/verdict-ledger.mjs → kernel/reduce/verdict.mjs} +13 -7
- 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} +15 -12
- 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} +134 -24
- package/{skills/tech-lead/scripts/trace-lint.mjs → kernel/verify/trace.mjs} +26 -11
- package/oracles/http-oracle.mjs +1 -1
- package/oracles/process-oracle.mjs +1 -1
- package/oracles/snapshot-oracle.mjs +1 -1
- package/oracles/test-oracle.mjs +1 -1
- package/package.json +11 -9
- package/skills/ba-pitch-analyzer/SKILL.md +5 -5
- 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 +2 -0
- package/skills/ba-pitch-analyzer/references/task-generation.md +2 -2
- package/skills/qa-edge-hunter/SKILL.md +7 -3
- package/skills/scope-architect/SKILL.md +40 -6
- package/skills/solution-architect/SKILL.md +19 -4
- package/skills/spec-evaluator/SKILL.md +12 -2
- package/skills/task-executor/SKILL.md +7 -6
- package/skills/tech-lead/SKILL.md +50 -38
- package/skills/tech-lead/references/gates.md +69 -20
- package/skills/tech-lead/references/protocol.md +832 -0
- package/skills/tech-lead/schemas/domain.schema.json +341 -66
- package/skills/tech-lead/schemas/work-order.schema.json +11 -2
- package/skills/tech-lead/schemas/work-result.schema.json +1 -1
- package/skills/tech-lead/workflows/shapeup-run.js +1313 -750
- package/hooks/anti-rationalization.mjs +0 -238
- package/hooks/compact-snapshot.mjs +0 -47
- package/hooks/gate-deadline.mjs +0 -147
- package/hooks/gate-l2.mjs +0 -161
- package/hooks/session-rehydrate.mjs +0 -108
- 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 -261
- package/skills/tech-lead/references/hard-rules.md +0 -34
- package/skills/tech-lead/references/invocation.md +0 -45
- package/skills/tech-lead/references/ledger-schema.md +0 -213
- package/skills/tech-lead/references/round-protocol.md +0 -234
- package/skills/tech-lead/references/state-model.md +0 -66
- package/skills/tech-lead/scripts/ingest-result.mjs +0 -258
- package/skills/tech-lead/scripts/lib/is-main.mjs +0 -81
- package/skills/tech-lead/scripts/lib/paths.mjs +0 -280
- package/skills/tech-lead/scripts/run-workflow.mjs +0 -381
- package/skills/translator/README.md +0 -66
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
// Slop cleaner — advisory Stop hook (v1.2).
|
|
1
|
+
// leftovers — what the run's own diff still carries that nobody meant to ship.
|
|
3
2
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// lines — and mentions them to the user on stop.
|
|
3
|
+
// TODO/FIXME markers, `console.log`/`debugger`, blocks of commented-out code, one file swallowing
|
|
4
|
+
// hundreds of added lines. Added lines only: a marker the feature did not introduce is somebody
|
|
5
|
+
// else's, and a report that lists those is a report people learn to skim.
|
|
8
6
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
7
|
+
// WHY IT IS PART OF THE SHIP REPORT AND NOT A STOP HOOK. It used to be an advisory Stop hook that
|
|
8
|
+
// printed once, into a transcript, at the moment a session ended — the channel least likely to be
|
|
9
|
+
// read and impossible to check later. The ship report is the artifact a human actually reads at
|
|
10
|
+
// GATE L4 and the one a teammate finds on `git pull`, so a leftover recorded there is a leftover
|
|
11
|
+
// somebody can act on. Nothing about the check changed; only where its answer lands.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
13
|
+
// Advisory by construction: it is a SECTION, never a verdict. QA is a level-up, not a gate.
|
|
14
14
|
|
|
15
15
|
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { spawnSync } from "node:child_process";
|
|
18
|
-
import {
|
|
19
|
-
import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
|
|
20
|
-
import { LOCAL, SHARED, resultsDir } from "../skills/tech-lead/scripts/lib/paths.mjs";
|
|
18
|
+
import { LOCAL, SHARED, resultsDir } from "../lib/paths.mjs";
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Harness bookkeeping is never "slop" — skip both storage roots, whichever names they carry.
|
|
20
|
+
// Harness bookkeeping is never a leftover — skip both storage roots, whichever names they carry.
|
|
25
21
|
const SKIP_PATH = new RegExp(`^(${[LOCAL, SHARED].map((r) => r.replace(/[.\\]/g, "\\$&")).join("|")})/`);
|
|
26
22
|
const MAX_FILES = 30;
|
|
27
23
|
const MAX_ADDED_LINES_PER_FILE = 400;
|
|
@@ -32,8 +28,16 @@ const MARKERS = [
|
|
|
32
28
|
{ name: "debugger", re: /^\s*debugger\b/ },
|
|
33
29
|
];
|
|
34
30
|
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
31
|
+
/**
|
|
32
|
+
* Scan a unified diff for leftovers in ADDED lines only.
|
|
33
|
+
*
|
|
34
|
+
* Added lines only, deliberately: a TODO the feature did not introduce is somebody else's, and a
|
|
35
|
+
* report that lists it is a report people learn to skim.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} diffText - A unified diff.
|
|
38
|
+
* @returns {Array<{file:string, markers:Object<string,number>, added:number, big:boolean, commented_code:boolean}>}
|
|
39
|
+
* One entry per dirty file, capped at MAX_FILES.
|
|
40
|
+
*/
|
|
37
41
|
export function scanDiff(diffText) {
|
|
38
42
|
const findings = [];
|
|
39
43
|
let current = null;
|
|
@@ -78,7 +82,11 @@ export function scanDiff(diffText) {
|
|
|
78
82
|
return findings;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* One human-readable fragment per finding.
|
|
87
|
+
* @param {Array} findings - The result of {@link scanDiff}.
|
|
88
|
+
* @returns {string[]} At most six lines, each naming a file and what it carries.
|
|
89
|
+
*/
|
|
82
90
|
export function summarize(findings) {
|
|
83
91
|
return findings.slice(0, 6).map((f) => {
|
|
84
92
|
const bits = Object.entries(f.markers).map(([name, n]) => `${name} ×${n}`);
|
|
@@ -88,12 +96,24 @@ export function summarize(findings) {
|
|
|
88
96
|
});
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Run git, returning stdout on success and null otherwise.
|
|
101
|
+
* @param {string} cwd - Working directory.
|
|
102
|
+
* @param {string[]} args - Arguments after `git`.
|
|
103
|
+
* @returns {(string|null)} stdout, or null when git failed or is absent.
|
|
104
|
+
*/
|
|
91
105
|
function git(cwd, args) {
|
|
92
106
|
const r = spawnSync("git", args, { cwd, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
|
|
93
107
|
return r.status === 0 ? r.stdout : null;
|
|
94
108
|
}
|
|
95
109
|
|
|
96
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* The run's change set as a unified diff — git first, WorkResult fallback.
|
|
112
|
+
* @param {string} cwd - Project root.
|
|
113
|
+
* @param {string} slug - Feature slug.
|
|
114
|
+
* @returns {(string|null)} A unified diff, or null when there is nothing to scan.
|
|
115
|
+
*/
|
|
116
|
+
export
|
|
97
117
|
function collectDiff(cwd, slug) {
|
|
98
118
|
const tracked = git(cwd, ["diff", "HEAD"]);
|
|
99
119
|
if (tracked !== null) {
|
|
@@ -137,40 +157,3 @@ function collectDiff(cwd, slug) {
|
|
|
137
157
|
} catch { return null; }
|
|
138
158
|
return diff || null;
|
|
139
159
|
}
|
|
140
|
-
|
|
141
|
-
async function main() {
|
|
142
|
-
await runHook("slop-cleaner", async () => {
|
|
143
|
-
const raw = await readStdin();
|
|
144
|
-
let p;
|
|
145
|
-
/** Stay silent — with the reason on the record (hooks/lib/decision.mjs). */
|
|
146
|
-
const defer = (reason, rule) => settle({ verdict: "allow", event: "Stop", cwd: p?.cwd, reason, rule });
|
|
147
|
-
try { p = JSON.parse(raw || "{}"); }
|
|
148
|
-
catch (e) { settle({ verdict: "error", event: "Stop", reason: `unparseable payload: ${e.message}` }); }
|
|
149
|
-
|
|
150
|
-
if (p.stop_hook_active) defer("stop_hook_active — never participate in a stop-hook loop", "loop-guard");
|
|
151
|
-
|
|
152
|
-
const cwd = p.cwd || process.cwd();
|
|
153
|
-
const slug = activeSlug(cwd);
|
|
154
|
-
if (!slug) defer("no active run — stay silent", "no-run");
|
|
155
|
-
|
|
156
|
-
const diff = collectDiff(cwd, slug);
|
|
157
|
-
if (!diff) defer("no diff to scan", "no-diff");
|
|
158
|
-
|
|
159
|
-
const findings = scanDiff(diff);
|
|
160
|
-
if (findings.length === 0) defer("diff scanned, no leftovers found — inspected and permitted", "diff-clean");
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
verdict: "allow", event: "Stop", cwd, subject: slug, rule: "slop-found", emit: true,
|
|
164
|
-
reason: `${findings.length} leftover(s) in the recent diff — advisory note emitted, not a block`,
|
|
165
|
-
payload: {
|
|
166
|
-
systemMessage:
|
|
167
|
-
`slop-cleaner (advisory): recent edits carry leftovers — ${summarize(findings).join("; ")}. ` +
|
|
168
|
-
`Not blocking — consider a cleanup pass.`,
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (isMain(import.meta.url)) {
|
|
175
|
-
main();
|
|
176
|
-
}
|
|
@@ -21,21 +21,21 @@
|
|
|
21
21
|
// Zero dependencies, zero network.
|
|
22
22
|
//
|
|
23
23
|
// Usage:
|
|
24
|
-
// node ship
|
|
24
|
+
// node `harness reduce ship` --slug <slug> [--cwd <dir>] [--verdict PASS|FAIL|not-evaluated]
|
|
25
25
|
// [--qa run|skipped] [--stdout]
|
|
26
26
|
//
|
|
27
27
|
// Exit: 0 written (path on stdout), 2 usage error.
|
|
28
28
|
|
|
29
29
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from "node:fs";
|
|
30
30
|
import { join, dirname } from "node:path";
|
|
31
|
-
import {
|
|
32
|
-
import { runArgs } from "./lib/argv.mjs";
|
|
31
|
+
import { runArgs } from "../lib/argv.mjs";
|
|
33
32
|
import {
|
|
34
33
|
report as reportPath, tasksDir, verdictsDir, trials, evaluationDir, qaDir,
|
|
35
|
-
roundLedger, discoveryLedger, receipt as receiptPath, harnessRun, relShared,
|
|
36
|
-
} from "
|
|
37
|
-
import { readTrials } from "
|
|
38
|
-
import { ratchetReport } from "
|
|
34
|
+
roundLedger, discoveryLedger, receipt as receiptPath, harnessRun, relShared, resultsDir,
|
|
35
|
+
} from "../lib/paths.mjs";
|
|
36
|
+
import { readTrials } from "../verify/t0.mjs";
|
|
37
|
+
import { ratchetReport } from "../probe/stats.mjs";
|
|
38
|
+
import { collectDiff, scanDiff, summarize } from "./leftovers.mjs";
|
|
39
39
|
|
|
40
40
|
/** @returns {string} Today as `YYYY-MM-DD` (UTC). */
|
|
41
41
|
const today = () => new Date().toISOString().slice(0, 10);
|
|
@@ -67,7 +67,7 @@ export function frontmatter(text) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
* Board census, from task frontmatter — the same two-source discipline
|
|
70
|
+
* Board census, from task frontmatter — the same two-source discipline the GATE L2 block uses, reduced to
|
|
71
71
|
* the authoritative one.
|
|
72
72
|
* @param {string} cwd - Project root.
|
|
73
73
|
* @param {string} slug - Feature slug.
|
|
@@ -166,7 +166,7 @@ export function section(md, heading) {
|
|
|
166
166
|
export function buildReport(facts) {
|
|
167
167
|
const {
|
|
168
168
|
slug, at, verdict, qa, rounds, board, t0, artifacts, ratchet,
|
|
169
|
-
evalCriteria, evalBugs, qaFindings, decisions, discovered, intakeSha,
|
|
169
|
+
evalCriteria, evalBugs, qaFindings, decisions, discovered, intakeSha, leftovers,
|
|
170
170
|
} = facts;
|
|
171
171
|
|
|
172
172
|
const L = [];
|
|
@@ -191,6 +191,16 @@ export function buildReport(facts) {
|
|
|
191
191
|
"> The verdict above grades what was built, not what was planned.", "");
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
// Leftovers — advisory, and a SECTION rather than a verdict. It was a Stop hook that printed once
|
|
195
|
+
// into a transcript; here it lands in the artifact a human reads at GATE L4 and a teammate finds
|
|
196
|
+
// on `git pull`, which is the difference between a note and a record.
|
|
197
|
+
if (leftovers?.length) {
|
|
198
|
+
L.push("## Leftovers (advisory)", "");
|
|
199
|
+
L.push("Markers in lines this run ADDED. Not a gate and not part of the verdict — a cleanup list.", "");
|
|
200
|
+
for (const line of leftovers) L.push(`- ${line}`);
|
|
201
|
+
L.push("");
|
|
202
|
+
}
|
|
203
|
+
|
|
194
204
|
if (t0.length) {
|
|
195
205
|
L.push("## Verification (T0)", "");
|
|
196
206
|
L.push("The surviving trial per scope — the one describing code that is actually on the branch.", "");
|
|
@@ -245,6 +255,32 @@ export function buildReport(facts) {
|
|
|
245
255
|
return L.join("\n");
|
|
246
256
|
}
|
|
247
257
|
|
|
258
|
+
/**
|
|
259
|
+
* How many BUILD/EVAL rounds this run actually completed.
|
|
260
|
+
*
|
|
261
|
+
* `harness-run.md`'s `rounds_used` frontmatter field is written ONCE, at GATE L0.1 (`init run`),
|
|
262
|
+
* as `0` — nothing in the round loop ever rewrites it as rounds complete. The orchestrator's own
|
|
263
|
+
* `RunReturn` carries the real count (`shapeup-run.js`'s `rounds_used: round`), but that value
|
|
264
|
+
* never reaches `reduce ship`, so every real run's report printed "Rounds used | 0" beside its own
|
|
265
|
+
* `results/evaluate-r1.json` — a claim the frontmatter makes about the run, contradicted by the
|
|
266
|
+
* artifact sitting next to it. Derived instead, the same way `probe resume`'s `eval_rounds_done`
|
|
267
|
+
* already does: the highest `evaluate-r<N>.json` result on disk. Falls back to the frontmatter
|
|
268
|
+
* value only when no EVAL round ever ran (the `--tiny` lane has no round concept at all), so a
|
|
269
|
+
* bare or tiny run's reporting is unchanged.
|
|
270
|
+
* @param {string} cwd - Project root.
|
|
271
|
+
* @param {string} slug - Feature slug.
|
|
272
|
+
* @param {(string|undefined)} fallback - `run.rounds_used` from the frontmatter.
|
|
273
|
+
* @returns {(number|string|undefined)} The derived round count, or the fallback.
|
|
274
|
+
*/
|
|
275
|
+
function roundsUsed(cwd, slug, fallback) {
|
|
276
|
+
const dir = resultsDir(cwd, slug);
|
|
277
|
+
const done = (existsSync(dir) ? readdirSync(dir) : [])
|
|
278
|
+
.map((f) => f.match(/^evaluate-r(\d+)\.json$/))
|
|
279
|
+
.filter(Boolean)
|
|
280
|
+
.map((m) => Number(m[1]));
|
|
281
|
+
return done.length ? Math.max(...done) : fallback;
|
|
282
|
+
}
|
|
283
|
+
|
|
248
284
|
/**
|
|
249
285
|
* Gather every fact from disk and render the report.
|
|
250
286
|
* @param {{cwd:string, slug:string, verdict?:string, qa?:string}} opts - Inputs.
|
|
@@ -264,7 +300,7 @@ export function generate({ cwd, slug, verdict, qa }) {
|
|
|
264
300
|
at: today(),
|
|
265
301
|
verdict: verdict || run.final_verdict || "not-evaluated",
|
|
266
302
|
qa: qa || (huntReport ? "run" : "skipped"),
|
|
267
|
-
rounds: run.rounds_used,
|
|
303
|
+
rounds: roundsUsed(cwd, slug, run.rounds_used),
|
|
268
304
|
intakeSha: receipt.intake_sha256,
|
|
269
305
|
board: boardCensus(cwd, slug),
|
|
270
306
|
t0: t0Summary(cwd, slug),
|
|
@@ -275,6 +311,11 @@ export function generate({ cwd, slug, verdict, qa }) {
|
|
|
275
311
|
qaFindings: section(huntReport, /^#+\s*Findings?\b/i),
|
|
276
312
|
decisions: section(ledger, /^#+\s*Decisions?\b/i),
|
|
277
313
|
discovered: section(discovery, /^#+\s*Discovered\b/i),
|
|
314
|
+
leftovers: (() => {
|
|
315
|
+
// Advisory, and derived like everything else here: the run's own diff, added lines only.
|
|
316
|
+
const diff = collectDiff(cwd, slug);
|
|
317
|
+
return diff ? summarize(scanDiff(diff)) : [];
|
|
318
|
+
})(),
|
|
278
319
|
};
|
|
279
320
|
return { markdown: buildReport(facts), path: reportPath(cwd, slug), facts };
|
|
280
321
|
}
|
|
@@ -282,7 +323,7 @@ export function generate({ cwd, slug, verdict, qa }) {
|
|
|
282
323
|
// ---------------------------------------------------------------------------
|
|
283
324
|
/** The typed argv contract (see `./lib/argv.mjs`). */
|
|
284
325
|
export const ARGV_SPEC = {
|
|
285
|
-
usage: "
|
|
326
|
+
usage: "harness.mjs reduce ship --slug <slug> [--cwd <dir>] [--verdict PASS|FAIL|not-evaluated] " +
|
|
286
327
|
"[--qa run|skipped] [--stdout]",
|
|
287
328
|
_: { arity: 0, max: 0, name: "(no positional operands)" },
|
|
288
329
|
slug: { type: "str", required: true },
|
|
@@ -292,8 +333,15 @@ export const ARGV_SPEC = {
|
|
|
292
333
|
stdout: { type: "flag" },
|
|
293
334
|
};
|
|
294
335
|
|
|
295
|
-
|
|
296
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Generate the ship report from the artifacts on disk.
|
|
338
|
+
*
|
|
339
|
+
* @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
|
|
340
|
+
* @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
|
|
341
|
+
* call `process.exit()` with the subcommand's documented code rather than returning.
|
|
342
|
+
*/
|
|
343
|
+
export async function cli(rawArgv) {
|
|
344
|
+
const args = runArgs(ARGV_SPEC, rawArgv);
|
|
297
345
|
const cwd = args.cwd || process.cwd();
|
|
298
346
|
const { markdown, path } = generate({ cwd, slug: args.slug, verdict: args.verdict, qa: args.qa });
|
|
299
347
|
if (args.stdout) {
|
|
@@ -9,22 +9,22 @@
|
|
|
9
9
|
// re-dispatching an already-ingested order, miscounting attempts (breaking the inner circuit
|
|
10
10
|
// breaker), or "remembering" a hill phase instead of re-deriving it.
|
|
11
11
|
//
|
|
12
|
-
// Consumers
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// Consumers: `--write` persists it as an audit anchor; `--format text` prints the rehydrate_hint
|
|
13
|
+
// for whoever asks. Nothing fires it automatically — the orchestrator re-derives after a context
|
|
14
|
+
// loss by running it, which answers whenever the question is asked rather than only at the two
|
|
15
|
+
// moments a lifecycle hook happened to see.
|
|
15
16
|
//
|
|
16
17
|
// Output is self-validated against the registry before it is emitted — the same
|
|
17
|
-
// refuse-to-emit-schema-drift discipline as compile
|
|
18
|
+
// refuse-to-emit-schema-drift discipline as `harness compile`.
|
|
18
19
|
//
|
|
19
|
-
// Usage: node
|
|
20
|
+
// Usage: node `harness reduce snapshot` [--cwd <dir>] [--format json|text] [--write]
|
|
20
21
|
// exit 0 with empty stdout when no run is active (fail-open), 1 on schema drift.
|
|
21
22
|
|
|
22
23
|
import { readFileSync, readdirSync, existsSync, writeFileSync } from "node:fs";
|
|
23
24
|
import { resolve, join } from "node:path";
|
|
24
|
-
import { validate } from "
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { localDir, localRoot, relLocal, globLocal, runSnapshot as runSnapshotPath } from "./lib/paths.mjs";
|
|
25
|
+
import { validate } from "../verify/envelope.mjs";
|
|
26
|
+
import { runArgs } from "../lib/argv.mjs";
|
|
27
|
+
import { localDir, localRoot, relLocal, globLocal, runSnapshot as runSnapshotPath } from "../lib/paths.mjs";
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Read a JSON file, tolerating absence/parse errors.
|
|
@@ -84,13 +84,13 @@ function findRun(cwd) {
|
|
|
84
84
|
// The pointer names the run the sandbox guard is scoping, NOT necessarily a run still open.
|
|
85
85
|
// `.shapeup/active-scope` has existed since v0.3 and nothing has ever cleared it, because
|
|
86
86
|
// its original reader (sandbox-guard) fails OPEN on a stale one — a pointer at a finished run
|
|
87
|
-
// simply stops matching any substrate.
|
|
87
|
+
// simply stops matching any substrate. `harness reduce graph --subgraph run` is the opposite kind of reader: it
|
|
88
88
|
// turns a pointer into "a run is ALREADY OPEN in this workspace … do NOT open a new run", and
|
|
89
89
|
// it now fires on `startup`/`clear`. Following the pointer without checking the run's status
|
|
90
90
|
// therefore made every cold session in every repo that had EVER run the harness open with a
|
|
91
91
|
// false claim about its own workspace — including sessions with nothing to do with the harness,
|
|
92
92
|
// and including the case where the user's next act is to open the run the injection forbids.
|
|
93
|
-
// This function's own contract (and
|
|
93
|
+
// This function's own contract (and `harness reduce graph --subgraph run`'s header) always said "a run only for an
|
|
94
94
|
// active-scope pointer or a mid-run harness-run.md"; the status check is what makes that true.
|
|
95
95
|
const pointer = readJSON(join(root, "active-scope"));
|
|
96
96
|
if (pointer?.slug && isMidRun(root, pointer.slug)) {
|
|
@@ -237,16 +237,22 @@ function assertValid(snapshot) {
|
|
|
237
237
|
|
|
238
238
|
/** The typed argv contract (see `./lib/argv.mjs`). */
|
|
239
239
|
export const ARGV_SPEC = {
|
|
240
|
-
usage: "
|
|
240
|
+
usage: "harness.mjs reduce snapshot [--cwd <dir>] [--format json|text] [--write]",
|
|
241
241
|
_: { arity: 0, max: 0, name: "(no positional operands)" },
|
|
242
242
|
cwd: { type: "path" },
|
|
243
243
|
format: { type: "enum", values: ["json", "text"], default: "json" },
|
|
244
244
|
write: { type: "flag" },
|
|
245
245
|
};
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Derive the file-based run snapshot, and optionally freeze it to disk.
|
|
249
|
+
*
|
|
250
|
+
* @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
|
|
251
|
+
* @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
|
|
252
|
+
* call `process.exit()` with the subcommand's documented code rather than returning.
|
|
253
|
+
*/
|
|
254
|
+
export async function cli(rawArgv) {
|
|
255
|
+
const args = runArgs(ARGV_SPEC, rawArgv);
|
|
250
256
|
const cwd = resolve(args.cwd || process.cwd());
|
|
251
257
|
const format = args.format;
|
|
252
258
|
|
|
@@ -12,12 +12,11 @@
|
|
|
12
12
|
// { run, task, dimension, criterion, verdict:"PASS"|"FAIL", confidence, reprobed, flip, evidence, at }
|
|
13
13
|
//
|
|
14
14
|
// Library use:
|
|
15
|
-
// import { reconcile, detectFlips, stability } from "
|
|
15
|
+
// import { reconcile, detectFlips, stability } from "kernel/reduce/verdict.mjs";
|
|
16
16
|
// const { records, summary } = reconcile(priorLines, currentRecords);
|
|
17
17
|
|
|
18
18
|
// Most recent prior line for a (dimension, criterion), by highest run number.
|
|
19
|
-
import {
|
|
20
|
-
import { runArgs } from "../../tech-lead/scripts/lib/argv.mjs";
|
|
19
|
+
import { runArgs } from "../lib/argv.mjs";
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* Find the most recent prior ledger line for a record's (dimension, criterion), by highest run.
|
|
@@ -139,15 +138,22 @@ export function parseLedger(text) {
|
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
// --- CLI entry: summarize a ledger file -------------------------------------
|
|
142
|
-
/** The typed argv contract (see `
|
|
141
|
+
/** The typed argv contract (see `kernel/lib/argv.mjs`). */
|
|
143
142
|
export const ARGV_SPEC = {
|
|
144
|
-
usage: "
|
|
143
|
+
usage: "harness.mjs reduce verdict <.verdicts-TASK.jsonl>",
|
|
145
144
|
_: { arity: 1, max: 1, name: ".verdicts-TASK.jsonl" },
|
|
146
145
|
};
|
|
147
146
|
|
|
148
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Reconcile a verdict ledger and report its flips and stability.
|
|
149
|
+
*
|
|
150
|
+
* @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
|
|
151
|
+
* @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
|
|
152
|
+
* call `process.exit()` with the subcommand's documented code rather than returning.
|
|
153
|
+
*/
|
|
154
|
+
export async function cli(rawArgv) {
|
|
149
155
|
const { readFileSync } = await import("node:fs");
|
|
150
|
-
const path = runArgs(ARGV_SPEC)._[0];
|
|
156
|
+
const path = runArgs(ARGV_SPEC, rawArgv)._[0];
|
|
151
157
|
let lines;
|
|
152
158
|
try { lines = parseLedger(readFileSync(path, "utf8")); }
|
|
153
159
|
catch (e) { console.error(`cannot read ledger ${path}: ${e.message}`); process.exit(2); }
|