shapeup-sdlc 1.6.2
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 +20 -0
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.env.shapeup.example +14 -0
- package/AGENTS.md +133 -0
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/SECURITY.md +72 -0
- package/bin/init.mjs +329 -0
- package/commands/build.md +14 -0
- package/commands/eval.md +15 -0
- package/commands/hammer.md +12 -0
- package/commands/orient.md +11 -0
- package/commands/qa.md +12 -0
- package/commands/retro.md +13 -0
- package/commands/scopes.md +14 -0
- package/commands/shape.md +12 -0
- package/commands/ship.md +53 -0
- package/commands/wire.md +11 -0
- package/hooks/anti-rationalization.mjs +244 -0
- package/hooks/compact-snapshot.mjs +47 -0
- package/hooks/gate-deadline.mjs +151 -0
- package/hooks/gate-intake.mjs +110 -0
- package/hooks/gate-l2.mjs +161 -0
- package/hooks/gate-zerowork.mjs +264 -0
- package/hooks/hooks.json +118 -0
- package/hooks/lib/decision.mjs +183 -0
- package/hooks/safety-spine.mjs +296 -0
- package/hooks/sandbox-guard.mjs +172 -0
- package/hooks/session-rehydrate.mjs +109 -0
- package/hooks/slop-cleaner.mjs +176 -0
- package/oracles/_shared.mjs +46 -0
- package/oracles/http-oracle.mjs +155 -0
- package/oracles/index.mjs +36 -0
- package/oracles/process-oracle.mjs +146 -0
- package/oracles/snapshot-oracle.mjs +119 -0
- package/oracles/test-oracle.mjs +138 -0
- package/package.json +49 -0
- package/skills/advisor-protocol/SKILL.md +171 -0
- package/skills/ba-pitch-analyzer/SKILL.md +175 -0
- package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +84 -0
- package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +65 -0
- package/skills/ba-pitch-analyzer/assets/templates/assess-report.tmpl.md +127 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/be-service.contract.tmpl.md +62 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/offline-storage.contract.tmpl.md +92 -0
- package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +66 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/context-map.tmpl.md +64 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/event-choreography.tmpl.md +77 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +104 -0
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +79 -0
- package/skills/ba-pitch-analyzer/assets/templates/domain-model.tmpl.md +79 -0
- package/skills/ba-pitch-analyzer/assets/templates/feedback.tmpl.md +87 -0
- package/skills/ba-pitch-analyzer/assets/templates/integration.tmpl.md +64 -0
- package/skills/ba-pitch-analyzer/assets/templates/run-state.tmpl.md +77 -0
- package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +105 -0
- package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +215 -0
- package/skills/ba-pitch-analyzer/assets/templates/task-board.tmpl.md +35 -0
- package/skills/ba-pitch-analyzer/assets/templates/task-spike.tmpl.md +86 -0
- package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +84 -0
- package/skills/ba-pitch-analyzer/assets/templates/usecase.tmpl.md +105 -0
- package/skills/ba-pitch-analyzer/assets/templates/ux-behavior.tmpl.md +65 -0
- package/skills/ba-pitch-analyzer/references/contract-patterns.md +152 -0
- package/skills/ba-pitch-analyzer/references/ddd-patterns.md +173 -0
- package/skills/ba-pitch-analyzer/references/doc-schemas.md +275 -0
- package/skills/ba-pitch-analyzer/references/integration-analysis.md +152 -0
- package/skills/ba-pitch-analyzer/references/task-generation.md +623 -0
- package/skills/ba-pitch-analyzer/references/test-surface.md +102 -0
- package/skills/ba-pitch-analyzer/references/ux-behavior-patterns.md +269 -0
- package/skills/ba-pitch-analyzer/scripts/board-derive.mjs +241 -0
- package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +239 -0
- package/skills/coach/SKILL.md +202 -0
- package/skills/orient/SKILL.md +262 -0
- package/skills/qa-edge-hunter/SKILL.md +369 -0
- package/skills/scope-architect/SKILL.md +111 -0
- package/skills/scope-hammer/SKILL.md +186 -0
- package/skills/shapeup/SKILL.md +403 -0
- package/skills/shapeup/resources/breadboard-reflection.md +88 -0
- package/skills/shapeup/resources/breadboarding.md +334 -0
- package/skills/shapeup/resources/context-compaction.md +240 -0
- package/skills/shapeup/resources/framing-doc.md +92 -0
- package/skills/shapeup/resources/kickoff-doc.md +95 -0
- package/skills/shapeup/resources/shaping.md +194 -0
- package/skills/shapeup/resources/spike.md +86 -0
- package/skills/solution-architect/SKILL.md +129 -0
- package/skills/spec-evaluator/README.md +93 -0
- package/skills/spec-evaluator/SKILL.md +213 -0
- package/skills/spec-evaluator/references/anti-leniency.md +50 -0
- package/skills/spec-evaluator/references/dimension-contract.md +126 -0
- package/skills/spec-evaluator/references/dimensions/_registry.md +40 -0
- package/skills/spec-evaluator/references/dimensions/completeness.md +102 -0
- package/skills/spec-evaluator/references/dimensions/integration.md +129 -0
- package/skills/spec-evaluator/references/dimensions/performance.md +48 -0
- package/skills/spec-evaluator/references/dimensions/security.md +60 -0
- package/skills/spec-evaluator/references/dimensions/spec-conformance.md +134 -0
- package/skills/spec-evaluator/references/dimensions/tdd-surface.md +110 -0
- package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +100 -0
- package/skills/spec-evaluator/references/probing.md +177 -0
- package/skills/spec-evaluator/references/report-schema.md +101 -0
- package/skills/spec-evaluator/references/verdict-ledger.md +92 -0
- package/skills/spec-evaluator/scripts/verdict-ledger.mjs +166 -0
- package/skills/task-executor/SKILL.md +194 -0
- package/skills/tech-lead/README.md +71 -0
- package/skills/tech-lead/SKILL.md +459 -0
- package/skills/tech-lead/references/delegation.md +254 -0
- package/skills/tech-lead/references/gates.md +379 -0
- package/skills/tech-lead/references/invocation.md +45 -0
- package/skills/tech-lead/references/ledger-schema.md +214 -0
- package/skills/tech-lead/references/round-protocol.md +184 -0
- package/skills/tech-lead/references/state-model.md +66 -0
- package/skills/tech-lead/references/tiny-lane.md +52 -0
- package/skills/tech-lead/schemas/domain.schema.json +2294 -0
- package/skills/tech-lead/schemas/gate-answers.schema.json +92 -0
- package/skills/tech-lead/schemas/work-order.schema.json +21 -0
- package/skills/tech-lead/schemas/work-result.schema.json +40 -0
- package/skills/tech-lead/scripts/aegis-digest.mjs +124 -0
- package/skills/tech-lead/scripts/budget-check.mjs +156 -0
- package/skills/tech-lead/scripts/compile-order.mjs +518 -0
- package/skills/tech-lead/scripts/fit-check.mjs +196 -0
- package/skills/tech-lead/scripts/gate-answers.mjs +338 -0
- package/skills/tech-lead/scripts/ingest-result.mjs +270 -0
- package/skills/tech-lead/scripts/init-run.mjs +326 -0
- package/skills/tech-lead/scripts/lib/argv.mjs +224 -0
- package/skills/tech-lead/scripts/lib/contract-md.mjs +481 -0
- package/skills/tech-lead/scripts/lib/is-main.mjs +82 -0
- package/skills/tech-lead/scripts/lib/paths.mjs +278 -0
- package/skills/tech-lead/scripts/lib/ratchet-tree.mjs +112 -0
- package/skills/tech-lead/scripts/run-snapshot.mjs +269 -0
- package/skills/tech-lead/scripts/ship-report.mjs +275 -0
- package/skills/tech-lead/scripts/stats.mjs +423 -0
- package/skills/tech-lead/scripts/t0-verify.mjs +470 -0
- package/skills/tech-lead/scripts/trace-lint.mjs +367 -0
- package/skills/tech-lead/scripts/validate-envelope.mjs +283 -0
- package/skills/translator/README.md +66 -0
- package/skills/translator/SKILL.md +258 -0
- package/skills/translator/references/preservation-rules.md +102 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SHIP REPORT — freeze one reviewable markdown report at GATE L4 (ADR-0001).
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS.
|
|
5
|
+
//
|
|
6
|
+
// Everything a reviewer wants from a run is in the LOCAL tier and therefore gitignored: the
|
|
7
|
+
// verdict and its refuted criteria, the QA findings, the T0 artifacts, the adjudicated decisions,
|
|
8
|
+
// the cut list. A pull request showed the spec and the code and NONE of the evidence that the one
|
|
9
|
+
// matched the other. The obvious fix — commit those artifacts — is the wrong one: they mutate
|
|
10
|
+
// throughout a run, so the deliverable tier would churn on every attempt and every round.
|
|
11
|
+
//
|
|
12
|
+
// So the split is by TIME rather than by artifact. They stay local while they change; their
|
|
13
|
+
// conclusions are frozen ONCE, when the run ends, into `shapeup/<slug>/REPORT.md`.
|
|
14
|
+
//
|
|
15
|
+
// EVERY NUMBER HERE IS DERIVED, NEVER PASSED IN. The report reduces over files the harness already
|
|
16
|
+
// wrote — the trial ledger, the verdict artifacts, the board, the discovery ledger, the round
|
|
17
|
+
// ledger. Nothing in it is a claim the orchestrator makes about its own run, which is the whole
|
|
18
|
+
// discipline this harness exists to enforce; a ship report that accepted a summary as input would
|
|
19
|
+
// be the "agent reports done" pathology wearing a deliverable's clothes.
|
|
20
|
+
//
|
|
21
|
+
// Zero dependencies, zero network.
|
|
22
|
+
//
|
|
23
|
+
// Usage:
|
|
24
|
+
// node ship-report.mjs --slug <slug> [--cwd <dir>] [--verdict PASS|FAIL|not-evaluated]
|
|
25
|
+
// [--qa run|skipped] [--stdout]
|
|
26
|
+
//
|
|
27
|
+
// Exit: 0 written (path on stdout), 2 usage error.
|
|
28
|
+
|
|
29
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from "node:fs";
|
|
30
|
+
import { join, dirname } from "node:path";
|
|
31
|
+
import { isMain } from "./lib/is-main.mjs";
|
|
32
|
+
import { runArgs } from "./lib/argv.mjs";
|
|
33
|
+
import {
|
|
34
|
+
report as reportPath, tasksDir, verdictsDir, trials, evaluationDir, qaDir,
|
|
35
|
+
roundLedger, discoveryLedger, receipt as receiptPath, harnessRun, relShared,
|
|
36
|
+
} from "./lib/paths.mjs";
|
|
37
|
+
import { readTrials } from "./t0-verify.mjs";
|
|
38
|
+
|
|
39
|
+
/** @returns {string} Today as `YYYY-MM-DD` (UTC). */
|
|
40
|
+
const today = () => new Date().toISOString().slice(0, 10);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read a file, tolerating absence — every section of this report is optional, because a run can
|
|
44
|
+
* legitimately end at GATE H with no QA pass or no evaluation.
|
|
45
|
+
* @param {string} p - Path.
|
|
46
|
+
* @returns {(string|null)} Contents, or null.
|
|
47
|
+
*/
|
|
48
|
+
function readIf(p) {
|
|
49
|
+
try { return existsSync(p) ? readFileSync(p, "utf8") : null; } catch { return null; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parse a Markdown frontmatter block into a flat scalar map.
|
|
54
|
+
* @param {string} text - Document text.
|
|
55
|
+
* @returns {Object<string,string>} Top-level `key: value` pairs.
|
|
56
|
+
*/
|
|
57
|
+
export function frontmatter(text) {
|
|
58
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text || "");
|
|
59
|
+
if (!m) return {};
|
|
60
|
+
const fm = {};
|
|
61
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
62
|
+
const kv = /^([A-Za-z_][\w-]*):\s*(.*)$/.exec(line.trim());
|
|
63
|
+
if (kv) fm[kv[1]] = kv[2].replace(/^["']|["']$/g, "");
|
|
64
|
+
}
|
|
65
|
+
return fm;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Board census, from task frontmatter — the same two-source discipline `gate-l2` uses, reduced to
|
|
70
|
+
* the authoritative one.
|
|
71
|
+
* @param {string} cwd - Project root.
|
|
72
|
+
* @param {string} slug - Feature slug.
|
|
73
|
+
* @returns {{total:number, done:number, unfinished:string[]}} Counts and the ids still open.
|
|
74
|
+
*/
|
|
75
|
+
export function boardCensus(cwd, slug) {
|
|
76
|
+
const dir = tasksDir(cwd, slug);
|
|
77
|
+
const out = { total: 0, done: 0, unfinished: [] };
|
|
78
|
+
if (!existsSync(dir)) return out;
|
|
79
|
+
for (const f of readdirSync(dir)) {
|
|
80
|
+
if (!/^TASK-[\w.-]+\.md$/i.test(f)) continue;
|
|
81
|
+
const body = readIf(join(dir, f)) || "";
|
|
82
|
+
const fm = frontmatter(body);
|
|
83
|
+
const id = fm.id || f.replace(/\.md$/, "");
|
|
84
|
+
out.total++;
|
|
85
|
+
if (fm.status === "done") out.done++;
|
|
86
|
+
else out.unfinished.push(id);
|
|
87
|
+
}
|
|
88
|
+
out.unfinished.sort();
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Per-scope T0 outcome, reduced from the trial ledger.
|
|
94
|
+
*
|
|
95
|
+
* The LAST trial per scope whose tree survived (`kept`/`rebased`) is the one describing the code
|
|
96
|
+
* that actually shipped — a `reverted` trial's tree was thrown away, so reporting its score would
|
|
97
|
+
* describe work nobody can see.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} cwd - Project root.
|
|
100
|
+
* @param {string} slug - Feature slug.
|
|
101
|
+
* @returns {Array<{scope_id:string, score:object, status:string, delta:string, trials:number}>}
|
|
102
|
+
* One row per scope, ordered by scope id.
|
|
103
|
+
*/
|
|
104
|
+
export function t0Summary(cwd, slug) {
|
|
105
|
+
const rows = readTrials(trials(cwd, slug));
|
|
106
|
+
const byScope = new Map();
|
|
107
|
+
for (const t of rows) {
|
|
108
|
+
if (!t.scope_id) continue;
|
|
109
|
+
const e = byScope.get(t.scope_id) || { scope_id: t.scope_id, trials: 0, last: null };
|
|
110
|
+
e.trials++;
|
|
111
|
+
if (t.status === "kept" || t.status === "rebased") e.last = t;
|
|
112
|
+
byScope.set(t.scope_id, e);
|
|
113
|
+
}
|
|
114
|
+
return [...byScope.values()]
|
|
115
|
+
.map((e) => ({
|
|
116
|
+
scope_id: e.scope_id,
|
|
117
|
+
trials: e.trials,
|
|
118
|
+
score: e.last?.score ?? null,
|
|
119
|
+
status: e.last?.status ?? "no surviving trial",
|
|
120
|
+
delta: e.last?.delta ?? "",
|
|
121
|
+
}))
|
|
122
|
+
.sort((a, b) => a.scope_id.localeCompare(b.scope_id));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Count the immutable verdict artifacts on disk — the evidence the evaluator had to cite.
|
|
127
|
+
* @param {string} cwd - Project root.
|
|
128
|
+
* @param {string} slug - Feature slug.
|
|
129
|
+
* @returns {number} How many artifacts exist.
|
|
130
|
+
*/
|
|
131
|
+
export function verdictArtifactCount(cwd, slug) {
|
|
132
|
+
const dir = verdictsDir(cwd, slug);
|
|
133
|
+
if (!existsSync(dir)) return 0;
|
|
134
|
+
try { return readdirSync(dir).filter((f) => f.endsWith(".json")).length; } catch { return 0; }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Lift a named section out of a markdown document.
|
|
139
|
+
* @param {(string|null)} md - Document text.
|
|
140
|
+
* @param {RegExp} heading - Matches the heading line that opens the section.
|
|
141
|
+
* @returns {(string|null)} The section body, trimmed, or null when absent/empty.
|
|
142
|
+
*/
|
|
143
|
+
export function section(md, heading) {
|
|
144
|
+
if (!md) return null;
|
|
145
|
+
const lines = md.split(/\r?\n/);
|
|
146
|
+
const start = lines.findIndex((l) => heading.test(l));
|
|
147
|
+
if (start === -1) return null;
|
|
148
|
+
const level = (lines[start].match(/^#+/) || ["#"])[0].length;
|
|
149
|
+
const out = [];
|
|
150
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
151
|
+
const h = lines[i].match(/^(#+)\s/);
|
|
152
|
+
if (h && h[1].length <= level) break;
|
|
153
|
+
out.push(lines[i]);
|
|
154
|
+
}
|
|
155
|
+
const body = out.join("\n").trim();
|
|
156
|
+
return body || null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Assemble the report. Pure given its inputs, so the structural tests can assert its shape
|
|
161
|
+
* without a filesystem.
|
|
162
|
+
* @param {object} facts - Derived facts (slug, verdict, board, t0, decisions, findings, …).
|
|
163
|
+
* @returns {string} The markdown document.
|
|
164
|
+
*/
|
|
165
|
+
export function buildReport(facts) {
|
|
166
|
+
const {
|
|
167
|
+
slug, at, verdict, qa, rounds, board, t0, artifacts,
|
|
168
|
+
evalCriteria, evalBugs, qaFindings, decisions, discovered, intakeSha,
|
|
169
|
+
} = facts;
|
|
170
|
+
|
|
171
|
+
const L = [];
|
|
172
|
+
L.push("---", "type: ship-report", `feature: ${slug}`, `date: ${at}`,
|
|
173
|
+
`verdict: ${verdict}`, `rounds_used: ${rounds ?? "~"}`, `qa: ${qa}`,
|
|
174
|
+
`intake_sha256: ${intakeSha ?? "~"}`, "---", "");
|
|
175
|
+
L.push(`# ${slug} — ship report`, "");
|
|
176
|
+
L.push("Frozen at GATE L4. Every figure below is derived from run artifacts on disk — the trial",
|
|
177
|
+
"ledger, the verdict artifacts, the board — never from a summary of the run.", "");
|
|
178
|
+
|
|
179
|
+
L.push("## Outcome", "");
|
|
180
|
+
L.push("| | |", "|---|---|");
|
|
181
|
+
L.push(`| Verdict | **${verdict}** |`);
|
|
182
|
+
L.push(`| Rounds used | ${rounds ?? "—"} |`);
|
|
183
|
+
L.push(`| Board | ${board.done}/${board.total} tasks done |`);
|
|
184
|
+
L.push(`| T0 artifacts | ${artifacts} |`);
|
|
185
|
+
L.push(`| QA | ${qa} |`);
|
|
186
|
+
L.push("");
|
|
187
|
+
|
|
188
|
+
if (board.unfinished.length) {
|
|
189
|
+
L.push(`> **${board.unfinished.length} task(s) did not finish:** ${board.unfinished.join(", ")}.`,
|
|
190
|
+
"> The verdict above grades what was built, not what was planned.", "");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (t0.length) {
|
|
194
|
+
L.push("## Verification (T0)", "");
|
|
195
|
+
L.push("The surviving trial per scope — the one describing code that is actually on the branch.", "");
|
|
196
|
+
L.push("| scope | fixtures | regressions | trials | last status | delta |", "|---|---|---|---|---|---|");
|
|
197
|
+
for (const s of t0) {
|
|
198
|
+
const f = s.score ? `${s.score.fixtures_passed}/${s.score.fixtures_total}` : "—";
|
|
199
|
+
const r = s.score ? String(s.score.regressions) : "—";
|
|
200
|
+
L.push(`| ${s.scope_id} | ${f} | ${r} | ${s.trials} | ${s.status} | ${s.delta || "—"} |`);
|
|
201
|
+
}
|
|
202
|
+
L.push("");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (evalCriteria) L.push("## Evaluation", "", evalCriteria, "");
|
|
206
|
+
if (evalBugs) L.push("### Refuted criteria and bugs", "", evalBugs, "");
|
|
207
|
+
if (qaFindings) L.push("## QA findings", "", qaFindings, "");
|
|
208
|
+
if (decisions) L.push("## Decisions adjudicated during the run", "", decisions, "");
|
|
209
|
+
if (discovered) L.push("## Discovered, not built", "", discovered, "");
|
|
210
|
+
|
|
211
|
+
L.push("---", "",
|
|
212
|
+
"*Run state (board, orders, results, T0 artifacts, evaluation and QA reports) stays in the",
|
|
213
|
+
"gitignored local tier — see `docs/design/adr/0001-consumer-file-organization.md`. This report",
|
|
214
|
+
"is the frozen conclusion of it.*", "");
|
|
215
|
+
return L.join("\n");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Gather every fact from disk and render the report.
|
|
220
|
+
* @param {{cwd:string, slug:string, verdict?:string, qa?:string}} opts - Inputs.
|
|
221
|
+
* @returns {{markdown:string, path:string, facts:object}} The document, its destination, the facts.
|
|
222
|
+
*/
|
|
223
|
+
export function generate({ cwd, slug, verdict, qa }) {
|
|
224
|
+
const run = frontmatter(readIf(harnessRun(cwd, slug)) || "");
|
|
225
|
+
const receipt = (() => { try { return JSON.parse(readIf(receiptPath(cwd, slug)) || "{}"); } catch { return {}; } })();
|
|
226
|
+
|
|
227
|
+
const evalReport = readIf(join(evaluationDir(cwd, slug), `EVAL-FEATURE-${slug}.md`));
|
|
228
|
+
const huntReport = readIf(join(qaDir(cwd, slug), "hunt-report.md"));
|
|
229
|
+
const ledger = readIf(roundLedger(cwd, slug));
|
|
230
|
+
const discovery = readIf(discoveryLedger(cwd, slug));
|
|
231
|
+
|
|
232
|
+
const facts = {
|
|
233
|
+
slug,
|
|
234
|
+
at: today(),
|
|
235
|
+
verdict: verdict || run.final_verdict || "not-evaluated",
|
|
236
|
+
qa: qa || (huntReport ? "run" : "skipped"),
|
|
237
|
+
rounds: run.rounds_used,
|
|
238
|
+
intakeSha: receipt.intake_sha256,
|
|
239
|
+
board: boardCensus(cwd, slug),
|
|
240
|
+
t0: t0Summary(cwd, slug),
|
|
241
|
+
artifacts: verdictArtifactCount(cwd, slug),
|
|
242
|
+
evalCriteria: section(evalReport, /^#+\s.*criteria/i) || section(evalReport, /^#+\s*spec-conformance/i),
|
|
243
|
+
evalBugs: section(evalReport, /^#+\s*Bugs?\b/i),
|
|
244
|
+
qaFindings: section(huntReport, /^#+\s*Findings?\b/i),
|
|
245
|
+
decisions: section(ledger, /^#+\s*Decisions?\b/i),
|
|
246
|
+
discovered: section(discovery, /^#+\s*Discovered\b/i),
|
|
247
|
+
};
|
|
248
|
+
return { markdown: buildReport(facts), path: reportPath(cwd, slug), facts };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
/** The typed argv contract (see `./lib/argv.mjs`). */
|
|
253
|
+
export const ARGV_SPEC = {
|
|
254
|
+
usage: "ship-report.mjs --slug <slug> [--cwd <dir>] [--verdict PASS|FAIL|not-evaluated] " +
|
|
255
|
+
"[--qa run|skipped] [--stdout]",
|
|
256
|
+
_: { arity: 0, max: 0, name: "(no positional operands)" },
|
|
257
|
+
slug: { type: "str", required: true },
|
|
258
|
+
cwd: { type: "path" },
|
|
259
|
+
verdict: { type: "str" },
|
|
260
|
+
qa: { type: "str" },
|
|
261
|
+
stdout: { type: "flag" },
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
if (isMain(import.meta.url)) {
|
|
265
|
+
const args = runArgs(ARGV_SPEC);
|
|
266
|
+
const cwd = args.cwd || process.cwd();
|
|
267
|
+
const { markdown, path } = generate({ cwd, slug: args.slug, verdict: args.verdict, qa: args.qa });
|
|
268
|
+
if (args.stdout) {
|
|
269
|
+
process.stdout.write(markdown);
|
|
270
|
+
} else {
|
|
271
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
272
|
+
writeFileSync(path, markdown, "utf8");
|
|
273
|
+
console.log(relShared(args.slug, "REPORT.md"));
|
|
274
|
+
}
|
|
275
|
+
}
|