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,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `snapshot` oracle for the evaluation contract (Stage G, step 4b of the audit).
|
|
3
|
+
//
|
|
4
|
+
// Deliverable: a generator or a pure refactor — something whose acceptance is "produces exactly
|
|
5
|
+
// this output". The oracle runs the deliverable, captures its stdout, and diffs it against a
|
|
6
|
+
// committed *golden* file. An empty diff is PASS; any difference is FAIL with the unified diff as
|
|
7
|
+
// evidence. This is the natural oracle for "the refactor changed nothing observable" and for
|
|
8
|
+
// code/text generators.
|
|
9
|
+
//
|
|
10
|
+
// Invariants (carried from the evaluator's design):
|
|
11
|
+
// • Probe behavior, not code presence — the verdict cites the diff of observed output.
|
|
12
|
+
// • Absence of evidence = FAIL — a probe that cannot run, or a missing golden, FAILs.
|
|
13
|
+
// • One verdict per criterion.
|
|
14
|
+
//
|
|
15
|
+
// Contract shape:
|
|
16
|
+
// { "oracle": "snapshot", "criteria": [
|
|
17
|
+
// { "id": "S1", "desc": "greets by name",
|
|
18
|
+
// "probe": { "argv": ["Ada"], "stdin": "" },
|
|
19
|
+
// "golden": "greet.Ada.golden.txt" } ] }
|
|
20
|
+
// `golden` is resolved relative to the contract file (baseDir). Trailing whitespace on each
|
|
21
|
+
// line and a single trailing newline are normalized so a benign EOL diff is not a false FAIL.
|
|
22
|
+
//
|
|
23
|
+
// Library use: import { runContract } from ".../snapshot-oracle.mjs"
|
|
24
|
+
// CLI use: node snapshot-oracle.mjs <contract.json> "<command to run the deliverable>"
|
|
25
|
+
// exit 0 = all PASS, 1 = ≥1 FAIL, 2 = usage/contract error.
|
|
26
|
+
|
|
27
|
+
import { spawnSync } from "node:child_process";
|
|
28
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
29
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
30
|
+
import { CRASH_RE, formatReport } from "./_shared.mjs";
|
|
31
|
+
import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
|
|
32
|
+
|
|
33
|
+
// Normalize so trailing spaces and final-newline differences never cause a spurious FAIL.
|
|
34
|
+
function normalize(s) {
|
|
35
|
+
return s.replace(/\r\n/g, "\n").split("\n").map((l) => l.replace(/[ \t]+$/, "")).join("\n").replace(/\n+$/, "\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Minimal line-level unified diff (no deps). Returns "" when identical.
|
|
39
|
+
function unifiedDiff(expected, actual) {
|
|
40
|
+
const a = normalize(expected).split("\n");
|
|
41
|
+
const b = normalize(actual).split("\n");
|
|
42
|
+
if (a.join("\n") === b.join("\n")) return "";
|
|
43
|
+
const lines = ["--- golden", "+++ actual"];
|
|
44
|
+
const max = Math.max(a.length, b.length);
|
|
45
|
+
for (let i = 0; i < max; i++) {
|
|
46
|
+
if (a[i] === b[i]) { if (a[i] !== undefined) lines.push(` ${a[i]}`); }
|
|
47
|
+
else {
|
|
48
|
+
if (a[i] !== undefined) lines.push(`- ${a[i]}`);
|
|
49
|
+
if (b[i] !== undefined) lines.push(`+ ${b[i]}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return lines.join("\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function spawnProbe(cmd, probe) {
|
|
56
|
+
const [bin, ...baseArgs] = String(cmd).split(/\s+/).filter(Boolean);
|
|
57
|
+
const res = spawnSync(bin, [...baseArgs, ...(probe.argv || [])], {
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
cwd: probe.cwd,
|
|
60
|
+
timeout: probe.timeout_ms || 10_000,
|
|
61
|
+
input: probe.stdin,
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
code: res.status,
|
|
65
|
+
stdout: res.stdout || "",
|
|
66
|
+
combined: (res.stdout || "") + (res.stderr || ""),
|
|
67
|
+
spawnError: res.error?.message,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function runContract({ cmd, criteria, baseDir = "." }) {
|
|
72
|
+
const results = [];
|
|
73
|
+
let fails = 0;
|
|
74
|
+
for (const c of criteria) {
|
|
75
|
+
let g;
|
|
76
|
+
try {
|
|
77
|
+
if (!c.golden) throw new Error("criterion has no `golden` file");
|
|
78
|
+
const goldenPath = isAbsolute(c.golden) ? c.golden : join(baseDir, c.golden);
|
|
79
|
+
if (!existsSync(goldenPath)) throw new Error(`golden file not found: ${c.golden}`);
|
|
80
|
+
const golden = readFileSync(goldenPath, "utf8");
|
|
81
|
+
const r = spawnProbe(cmd, c.probe || {});
|
|
82
|
+
if (r.spawnError) throw new Error(`could not run deliverable: ${r.spawnError}`);
|
|
83
|
+
if (CRASH_RE.test(r.combined)) {
|
|
84
|
+
g = { pass: false, evidence: `deliverable crashed (stack trace / panic in output): ${JSON.stringify(r.combined.trim().slice(0, 120))}` };
|
|
85
|
+
} else {
|
|
86
|
+
const diff = unifiedDiff(golden, r.stdout);
|
|
87
|
+
g = diff
|
|
88
|
+
? { pass: false, evidence: `output differs from golden (exit ${r.code}):\n${diff}` }
|
|
89
|
+
: { pass: true, evidence: `identical to golden (${normalize(golden).split("\n").filter(Boolean).length} lines, exit ${r.code})` };
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
g = { pass: false, evidence: `probe threw: ${e.message}` }; // absence of evidence = FAIL
|
|
93
|
+
}
|
|
94
|
+
if (!g.pass) fails++;
|
|
95
|
+
results.push({ id: c.id, desc: c.desc, ...g });
|
|
96
|
+
}
|
|
97
|
+
return { fails, results };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { formatReport };
|
|
101
|
+
|
|
102
|
+
// --- CLI entry ---------------------------------------------------------------
|
|
103
|
+
if (isMain(import.meta.url)) {
|
|
104
|
+
const contractPath = process.argv[2];
|
|
105
|
+
const cmd = process.argv[3];
|
|
106
|
+
if (!contractPath || !cmd) {
|
|
107
|
+
console.error('usage: node snapshot-oracle.mjs <contract.json> "<command to run the deliverable>"');
|
|
108
|
+
process.exit(2);
|
|
109
|
+
}
|
|
110
|
+
let contract;
|
|
111
|
+
try { contract = JSON.parse(readFileSync(contractPath, "utf8")); }
|
|
112
|
+
catch (e) { console.error(`cannot read contract ${contractPath}: ${e.message}`); process.exit(2); }
|
|
113
|
+
if (!Array.isArray(contract.criteria) || contract.criteria.length === 0) {
|
|
114
|
+
console.error(`contract ${contractPath} has no criteria[]`); process.exit(2);
|
|
115
|
+
}
|
|
116
|
+
const summary = runContract({ cmd, criteria: contract.criteria, baseDir: dirname(contractPath) });
|
|
117
|
+
console.log(formatReport(`snapshot oracle: ${contractPath}`, summary));
|
|
118
|
+
process.exit(summary.fails === 0 ? 0 : 1);
|
|
119
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `test` oracle for the evaluation contract (Stage G, step 4a of the audit).
|
|
3
|
+
//
|
|
4
|
+
// Deliverable: a library / module whose acceptance is "its own test suite is green".
|
|
5
|
+
// The oracle runs the project's test command and grades the OBSERVED suite result —
|
|
6
|
+
// exit code, executed-test count, and failing-test names — never the source.
|
|
7
|
+
//
|
|
8
|
+
// Why this oracle exists: a CLI is judged by `process`, a web app by `ui`, but a *library*
|
|
9
|
+
// has no runtime surface to drive — its contract is its tests. (probing.md "TDD-1": a suite
|
|
10
|
+
// that runs zero tests is NOT green, so `min_tests` defaults to 1.)
|
|
11
|
+
//
|
|
12
|
+
// Invariants (carried from the evaluator's design):
|
|
13
|
+
// • Probe behavior, not code presence — the verdict cites the suite's own output.
|
|
14
|
+
// • Absence of evidence = FAIL — a suite that cannot run, or runs nothing, FAILs.
|
|
15
|
+
// • One verdict per criterion.
|
|
16
|
+
//
|
|
17
|
+
// Contract shape:
|
|
18
|
+
// { "oracle": "test", "criteria": [
|
|
19
|
+
// { "id": "T1", "desc": "suite green",
|
|
20
|
+
// "probe": { "cmd": "node --test ./mathx.test.mjs", "cwd": "." },
|
|
21
|
+
// "expect": { "exit": "==0", "min_tests": 1, "no_failures": true } } ] }
|
|
22
|
+
//
|
|
23
|
+
// `cmd` resolution: criterion.probe.cmd overrides the contract/CLI default. The parser
|
|
24
|
+
// understands node:test TAP (`# pass/# fail/# tests`, `ok`/`not ok`) and mocha-style
|
|
25
|
+
// "N passing / M failing"; falls back to counting TAP `ok`/`not ok` lines.
|
|
26
|
+
//
|
|
27
|
+
// Library use: import { runContract } from ".../test-oracle.mjs"
|
|
28
|
+
// CLI use: node test-oracle.mjs <contract.json> ["default test command"]
|
|
29
|
+
// exit 0 = all PASS, 1 = ≥1 FAIL, 2 = usage/contract error.
|
|
30
|
+
|
|
31
|
+
import { spawnSync } from "node:child_process";
|
|
32
|
+
import { readFileSync } from "node:fs";
|
|
33
|
+
import { matchNum, toRegExp, formatReport } from "./_shared.mjs";
|
|
34
|
+
import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
|
|
35
|
+
|
|
36
|
+
// Parse common test-runner output into { total, pass, fail, failing[] }.
|
|
37
|
+
function parseSuite(out) {
|
|
38
|
+
const r = { total: null, pass: null, fail: null, failing: [] };
|
|
39
|
+
|
|
40
|
+
// node:test summary lines — TAP reporter prefixes with `#`, the default spec reporter with `ℹ`.
|
|
41
|
+
const mt = out.match(/^[#ℹ]\s*tests\s+(\d+)/m);
|
|
42
|
+
const mp = out.match(/^[#ℹ]\s*pass\s+(\d+)/m);
|
|
43
|
+
const mf = out.match(/^[#ℹ]\s*fail\s+(\d+)/m);
|
|
44
|
+
if (mt) r.total = Number(mt[1]);
|
|
45
|
+
if (mp) r.pass = Number(mp[1]);
|
|
46
|
+
if (mf) r.fail = Number(mf[1]);
|
|
47
|
+
|
|
48
|
+
// mocha / jest-ish summary
|
|
49
|
+
if (r.pass === null) { const m = out.match(/(\d+)\s+passing/); if (m) r.pass = Number(m[1]); }
|
|
50
|
+
if (r.fail === null) { const m = out.match(/(\d+)\s+failing/); if (m) r.fail = Number(m[1]); }
|
|
51
|
+
|
|
52
|
+
// failing test names from TAP `not ok N - name`
|
|
53
|
+
for (const m of out.matchAll(/^not ok\s+\d+\s+-\s+(.+)$/gm)) r.failing.push(m[1].trim());
|
|
54
|
+
|
|
55
|
+
// Fallback: count raw TAP result lines if no summary was emitted.
|
|
56
|
+
if (r.total === null) {
|
|
57
|
+
const oks = (out.match(/^ok\s+\d+/gm) || []).length;
|
|
58
|
+
const noks = (out.match(/^not ok\s+\d+/gm) || []).length;
|
|
59
|
+
if (oks + noks > 0) { r.total = oks + noks; r.pass = oks; r.fail = noks; }
|
|
60
|
+
}
|
|
61
|
+
return r;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function runSuite(cmd, probe) {
|
|
65
|
+
const [bin, ...baseArgs] = String(cmd).split(/\s+/).filter(Boolean);
|
|
66
|
+
const res = spawnSync(bin, [...baseArgs, ...(probe.argv || [])], {
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
cwd: probe.cwd,
|
|
69
|
+
timeout: probe.timeout_ms || 60_000,
|
|
70
|
+
});
|
|
71
|
+
const out = (res.stdout || "") + (res.stderr || "");
|
|
72
|
+
return { code: res.status, out, spawnError: res.error?.message, parsed: parseSuite(out) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function grade(expect, r) {
|
|
76
|
+
const reasons = [];
|
|
77
|
+
let pass = true;
|
|
78
|
+
const p = r.parsed;
|
|
79
|
+
|
|
80
|
+
if (r.spawnError) { pass = false; reasons.push(`suite did not run: ${r.spawnError}`); }
|
|
81
|
+
if (!matchNum(expect.exit ?? "==0", r.code)) { pass = false; reasons.push(`suite exit ${r.code} ≠ expected ${expect.exit ?? "==0"}`); }
|
|
82
|
+
|
|
83
|
+
const minTests = expect.min_tests ?? 1;
|
|
84
|
+
if (p.total === null) { pass = false; reasons.push("no test results parsed (suite produced no recognizable output)"); }
|
|
85
|
+
else if (p.total < minTests) { pass = false; reasons.push(`only ${p.total} test(s) ran, need ≥${minTests}`); }
|
|
86
|
+
|
|
87
|
+
if ((expect.no_failures ?? true) && (p.fail ?? 0) > 0) {
|
|
88
|
+
pass = false; reasons.push(`${p.fail} failing: ${p.failing.join(", ") || "(names unparsed)"}`);
|
|
89
|
+
}
|
|
90
|
+
if (expect.stdout !== undefined && !toRegExp(expect.stdout).test(r.out)) {
|
|
91
|
+
pass = false; reasons.push(`output does not match ${expect.stdout}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const evidence =
|
|
95
|
+
`suite exit ${r.code}; tests=${p.total ?? "?"} pass=${p.pass ?? "?"} fail=${p.fail ?? "?"}` +
|
|
96
|
+
(p.failing.length ? `; failing=[${p.failing.join("; ")}]` : "") +
|
|
97
|
+
(reasons.length ? ` [${reasons.join("; ")}]` : "");
|
|
98
|
+
return { pass, evidence };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function runContract({ cmd, criteria }) {
|
|
102
|
+
const results = [];
|
|
103
|
+
let fails = 0;
|
|
104
|
+
for (const c of criteria) {
|
|
105
|
+
let g;
|
|
106
|
+
try {
|
|
107
|
+
const useCmd = (c.probe && c.probe.cmd) || cmd;
|
|
108
|
+
if (!useCmd) throw new Error("no test command (criterion.probe.cmd or contract default)");
|
|
109
|
+
g = grade(c.expect || {}, runSuite(useCmd, c.probe || {}));
|
|
110
|
+
} catch (e) {
|
|
111
|
+
g = { pass: false, evidence: `probe threw: ${e.message}` }; // absence of evidence = FAIL
|
|
112
|
+
}
|
|
113
|
+
if (!g.pass) fails++;
|
|
114
|
+
results.push({ id: c.id, desc: c.desc, ...g });
|
|
115
|
+
}
|
|
116
|
+
return { fails, results };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { formatReport };
|
|
120
|
+
|
|
121
|
+
// --- CLI entry ---------------------------------------------------------------
|
|
122
|
+
if (isMain(import.meta.url)) {
|
|
123
|
+
const contractPath = process.argv[2];
|
|
124
|
+
const defaultCmd = process.argv[3];
|
|
125
|
+
if (!contractPath) {
|
|
126
|
+
console.error('usage: node test-oracle.mjs <contract.json> ["default test command"]');
|
|
127
|
+
process.exit(2);
|
|
128
|
+
}
|
|
129
|
+
let contract;
|
|
130
|
+
try { contract = JSON.parse(readFileSync(contractPath, "utf8")); }
|
|
131
|
+
catch (e) { console.error(`cannot read contract ${contractPath}: ${e.message}`); process.exit(2); }
|
|
132
|
+
if (!Array.isArray(contract.criteria) || contract.criteria.length === 0) {
|
|
133
|
+
console.error(`contract ${contractPath} has no criteria[]`); process.exit(2);
|
|
134
|
+
}
|
|
135
|
+
const summary = runContract({ cmd: defaultCmd || contract.cmd, criteria: contract.criteria });
|
|
136
|
+
console.log(formatReport(`test oracle: ${contractPath}`, summary));
|
|
137
|
+
process.exit(summary.fails === 0 ? 0 : 1);
|
|
138
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shapeup-sdlc",
|
|
3
|
+
"version": "1.6.2",
|
|
4
|
+
"description": "Shape Up for coding agents — with gates the agent can't talk its way past. Harness for Claude Code, compiled to Cursor, Antigravity and Codex.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"shapeup-sdlc": "bin/init.mjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"skills/",
|
|
11
|
+
"!skills/**/evals/**",
|
|
12
|
+
"hooks/",
|
|
13
|
+
"commands/",
|
|
14
|
+
"oracles/",
|
|
15
|
+
".claude-plugin/",
|
|
16
|
+
"AGENTS.md",
|
|
17
|
+
".env.shapeup.example",
|
|
18
|
+
".claude/settings.local.example.json",
|
|
19
|
+
"SECURITY.md"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/nguyenvanphituoc/shapeup-sdlc-plugin.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/nguyenvanphituoc/shapeup-sdlc-plugin#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/nguyenvanphituoc/shapeup-sdlc-plugin/issues"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "node tests/structural.mjs",
|
|
37
|
+
"demo": "node tools/demo/record-demo.mjs",
|
|
38
|
+
"distribute": "node tools/distribute.js"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"shapeup",
|
|
42
|
+
"sdlc",
|
|
43
|
+
"cursor",
|
|
44
|
+
"antigravity",
|
|
45
|
+
"claude-code"
|
|
46
|
+
],
|
|
47
|
+
"author": "Liberty Nguyen",
|
|
48
|
+
"license": "MIT"
|
|
49
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: advisor-protocol
|
|
3
|
+
description: "Use this skill whenever a harness worker (task-executor, scope-architect) hits a decision it cannot make alone during Build Vertically — a design decision, a spec ambiguity, or a request to write outside its scope's substrate — and needs it adjudicated under a budget instead of guessed or asked ad hoc. Trigger on: \"escalate this decision\", \"ESCALATE\", \"adjudicate this ambiguity\", \"ask the advisor\", \"substrate expansion request\", \"resolve this design decision within budget\", \"how many escalations does this scope have left\". Also triggers when tech-lead needs to answer a worker's structured ESCALATE return during a build round."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Advisor Protocol (ESCALATE grammar + budgets)
|
|
7
|
+
|
|
8
|
+
The mechanism behind Shape Up's "advisor, not micromanager" posture (design spec v1.1 DD-1,
|
|
9
|
+
DD-8). A worker mid-attempt hits something it genuinely cannot decide alone — not "what should
|
|
10
|
+
I name this variable" (that's Principle A, resolved silently or at the worker's own gate), but
|
|
11
|
+
a decision whose wrong guess is expensive: a design trade-off the spec left open, a spec
|
|
12
|
+
ambiguity that changes behavior, or a file the worker needs to touch outside its scope's
|
|
13
|
+
`allowed_file_substrate`. Instead of guessing or opening an unbounded chat with the PO, the
|
|
14
|
+
worker emits one structured `ESCALATE` return and this skill adjudicates it.
|
|
15
|
+
|
|
16
|
+
**Why this exists as its own skill, not inline PO chat.** Zero-memory handoff (design spec
|
|
17
|
+
§3.6) means every attempt gets a *fresh* context — no chat history. An answer given once must
|
|
18
|
+
survive every future reset for that scope. This skill is what turns a transient Q&A into a
|
|
19
|
+
persisted decision: it writes the answer to the round-ledger (committed, Tier A) the moment
|
|
20
|
+
it's made, and every future brief for that scope reads it back. Escalation memory lives in
|
|
21
|
+
files, not in chat (DD-8).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## The ESCALATE grammar
|
|
26
|
+
|
|
27
|
+
A worker never asks a free-form question mid-attempt. It returns a structured block and stops
|
|
28
|
+
work on the blocked AC/file until an answer comes back:
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
ESCALATE:
|
|
32
|
+
scope_id: cart-creation
|
|
33
|
+
round: 2
|
|
34
|
+
attempt: 3
|
|
35
|
+
kind: design-decision | spec-ambiguity | substrate-expansion
|
|
36
|
+
question: "Contract leaves cart-total rounding unspecified — round half-up or banker's rounding?"
|
|
37
|
+
options: ["round half-up (matches existing order-total code)", "banker's rounding (matches contract's other money fields)"]
|
|
38
|
+
default_if_silent: "round half-up" # optional — see --unattended below
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Three kinds, three different resolution paths (below). `default_if_silent` is optional — a
|
|
42
|
+
worker proposes it when it has a reasonable, low-risk fallback; the advisor is never forced to
|
|
43
|
+
accept it.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Budget
|
|
48
|
+
|
|
49
|
+
**≤3 ESCALATEs per scope per round** (design spec §3.3). Counted per `scope_id` + `round`, reset
|
|
50
|
+
each new round. The budget exists so a struggling scope surfaces as *stuck* (routed to the
|
|
51
|
+
hill's stuck-split rule, ≥3 rounds at the same position → forced scope-architect split order) rather than
|
|
52
|
+
draining PO attention one question at a time.
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
1st–3rd ESCALATE this scope/round → adjudicate normally (below).
|
|
56
|
+
4th+ → do NOT ask again. Apply the most conservative resolution
|
|
57
|
+
(narrowest scope, no substrate expansion, closest precedent)
|
|
58
|
+
and flag it as a GATE-H hammer proposal: "scope exceeded its
|
|
59
|
+
escalation budget — question N unresolved, applied [X]".
|
|
60
|
+
The worker's attempt continues with that resolution.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Exceeding budget is never a hard stop — Build Vertically must keep moving; it's a signal that
|
|
64
|
+
this scope needs GATE H's attention, not a excuse to freeze the round.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Adjudication (in order — cheapest resolution wins)
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
1. Precedent check (free, no PO time):
|
|
72
|
+
Search this run's round-ledger.md "Decisions" section (this scope AND sibling scopes) and
|
|
73
|
+
shapeup/knowledge-base/*.md for an already-answered question that is the same or a
|
|
74
|
+
clear superset of this one. Found → reuse verbatim, log "resolved by precedent: [ref]",
|
|
75
|
+
do NOT count against budget (a repeat question is a zero-memory artifact, not a new decision).
|
|
76
|
+
|
|
77
|
+
2. kind: substrate-expansion:
|
|
78
|
+
Never silently approved. Present the requested path(s) + why the worker says it needs them.
|
|
79
|
+
PO/TL approves → dispatch a scope-architect remap order to add the path to the scope contract's
|
|
80
|
+
`shared_substrate` (never hand-edit the contract — `ba` is its sole writer, F.5). PO/TL
|
|
81
|
+
declines → worker re-plans within its existing substrate; log the decline as a decision too
|
|
82
|
+
(prevents re-asking).
|
|
83
|
+
|
|
84
|
+
3. kind: design-decision | spec-ambiguity, interactive/--auto:
|
|
85
|
+
Print the ESCALATE block verbatim + budget remaining for this scope/round. Ask (max 1):
|
|
86
|
+
pick an option / provide a different answer / defer to default_if_silent if offered.
|
|
87
|
+
Never answer on the PO's behalf — a wrong silent guess here is exactly what zero-memory
|
|
88
|
+
handoff makes expensive to unwind later.
|
|
89
|
+
|
|
90
|
+
4. --unattended (no PO available):
|
|
91
|
+
default_if_silent present → apply it, log "auto-resolved (default, unattended)".
|
|
92
|
+
No default offered → apply the most conservative option (narrowest scope / least
|
|
93
|
+
irreversible) and flag it as a GATE-H hammer proposal for PO review before ship — the
|
|
94
|
+
run must not block, but an unattended guess is never allowed to hide from the PO forever.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Persistence — the decision must survive a reset
|
|
100
|
+
|
|
101
|
+
The instant an ESCALATE is answered (any path above), append one row to the run's committed
|
|
102
|
+
`round-ledger.md` "Decisions" section — never batch it for later, never keep it only in the
|
|
103
|
+
current session (a crash before the next promotion point would lose it):
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
| Round | Scope | Kind | Question | Answer | Resolved by |
|
|
107
|
+
|-------|-------|------|----------|--------|-------------|
|
|
108
|
+
| 2 | cart-creation | design-decision | cart-total rounding | round half-up | PO (interactive) |
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Every subsequent isolated brief for that scope (task-executor's zero-memory handoff, design
|
|
112
|
+
spec §3.6) includes the full decisions table for its `scope_id` — this is how an answer given
|
|
113
|
+
once in round 2 is still known in round 5's fresh-context attempt without replaying any chat
|
|
114
|
+
history.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Envelope contract — the domain layer
|
|
119
|
+
|
|
120
|
+
Orchestrated, this skill is dispatched like every worker: a **WorkOrder** in (`--order <path>`,
|
|
121
|
+
operation `adjudicate`), a **WorkResult** out. The standalone flags below map 1:1 onto the
|
|
122
|
+
payload fields registered for this worker in the central domain registry
|
|
123
|
+
(`skills/tech-lead/schemas/domain.schema.json`, `x-payload-by-worker`):
|
|
124
|
+
|
|
125
|
+
| Payload field | Standalone flag | Meaning |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `payload.ledger` | `--ledger` | The run's committed `round-ledger.md` — every answer persists here |
|
|
128
|
+
| `payload.escalate` | `--escalate` | The worker's ESCALATE block to adjudicate (the `EscalateBlock` grammar above) |
|
|
129
|
+
| `payload.scope_id` | `--scope` | The scope the budget is counted against |
|
|
130
|
+
| `payload.round` | `--round` | The round the budget/decision is counted against |
|
|
131
|
+
|
|
132
|
+
The WorkResult may carry only `files_touched`, `artifacts`, `assumptions`, `deviations`
|
|
133
|
+
(`x-result-by-worker`): the adjudicated Decision itself persists to the committed round-ledger,
|
|
134
|
+
never to the envelope, so it survives every zero-memory reset.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Invocation
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Adjudicate one ESCALATE return from a worker (typical: tech-lead calls this mid-round)
|
|
142
|
+
/advisor-protocol --ledger shapeup/checkout-vnpay/round-ledger.md --escalate '<ESCALATE yaml>'
|
|
143
|
+
|
|
144
|
+
# Check remaining budget for a scope this round (worker or tech-lead, before emitting ESCALATE)
|
|
145
|
+
/advisor-protocol --budget --scope cart-creation --round 2 --ledger shapeup/checkout-vnpay/round-ledger.md
|
|
146
|
+
|
|
147
|
+
# Headless run — no PO available; apply defaults / conservative fallback, flag the rest for GATE H
|
|
148
|
+
/advisor-protocol --unattended --ledger ... --escalate '<ESCALATE yaml>'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Flags
|
|
152
|
+
| Flag | Effect |
|
|
153
|
+
|------|--------|
|
|
154
|
+
| `--ledger <path>` | The run's committed `round-ledger.md` (decisions persist here) |
|
|
155
|
+
| `--escalate <yaml\|path>` | The worker's ESCALATE block to adjudicate |
|
|
156
|
+
| `--budget --scope <id> --round <N>` | Report remaining ESCALATE budget for a scope this round, no adjudication |
|
|
157
|
+
| `--unattended` | No PO available — apply defaults/conservative fallback per rule 4 above |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Hard Rules (never override without explicit user instruction)
|
|
162
|
+
|
|
163
|
+
| Rule | Rationale |
|
|
164
|
+
|------|-----------|
|
|
165
|
+
| A worker never guesses a design decision or spec ambiguity silently | The whole point of ESCALATE — wrong silent guesses are the expensive failure mode zero-memory handoff amplifies |
|
|
166
|
+
| Substrate-expansion is never auto-approved | PA3 (cross-scope contamination) exists precisely because agents self-justify "just this once" writes |
|
|
167
|
+
| Budget is ≤3/scope/round; exceeding it never blocks the round | Signals a stuck scope to GATE L2's stuck-split rule instead of freezing Build Vertically |
|
|
168
|
+
| Every answer is persisted to the committed round-ledger the instant it's given | Zero-memory handoff means the *only* place a decision survives is a file, not a session |
|
|
169
|
+
| Precedent reuse never counts against budget | A repeat question is a zero-memory artifact, not a new decision — don't penalize the worker for context loss |
|
|
170
|
+
| An unattended conservative resolution is always flagged for GATE H | An automated guess must stay visible to the PO before ship, never silently absorbed |
|
|
171
|
+
| This skill never designs, builds, or judges | Advisor, not a fifth worker — it adjudicates, it does not decide scope or verify code |
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ba-pitch-analyzer
|
|
3
|
+
description: "Use this skill whenever a user provides a product requirement, pitch, or feature description and wants it broken down into structured, executable development tasks. Triggers on: \"analyze this pitch\", \"break this into tasks\", \"generate tasks from requirement\", \"act as BA\", \"create spec from PRD\", \"turn this into dev tasks\", or any request to decompose a feature into DDD-structured documents and tasks; also on Shape Up, bounded context, domain model, or use cases, and on a tech-lead --order dispatch. Produces a linked pitch → domain model → use cases → tasks document tree with BDD scenarios and a derived Test Surface."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BA Pitch Analyzer (spec-analyzer, pure worker v4.0)
|
|
7
|
+
|
|
8
|
+
**Decompose the pitch into a spec a machine can build and a judge can grade — invent nothing.**
|
|
9
|
+
|
|
10
|
+
Converts a Shape Up pitch (or any product requirement) into a fully linked document tree:
|
|
11
|
+
`_index` → `domain-model` → `ux-behavior` → `usecases/` → `integration` → `tasks/` — Obsidian
|
|
12
|
+
wikilinks + shared frontmatter taxonomy, navigable from pitch to atomic task.
|
|
13
|
+
|
|
14
|
+
You are the *planner* in a planner → doer → judge harness, and a pure worker: the order tells
|
|
15
|
+
you which operation to run and which files you may write; you return everything else as data.
|
|
16
|
+
You keep **no state** — no run-state.md, no pitch-hash cache, no counters. Same order in →
|
|
17
|
+
same artifacts out.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Input contract — the WorkOrder
|
|
22
|
+
|
|
23
|
+
Invoked as `--order <path>`. Fields you may rely on (absent = unknown; surface it, never guess):
|
|
24
|
+
|
|
25
|
+
| Field | What it is |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `operation` | `analyze` (pitch → full spec tree + board) · `generate-board` (regenerate the LOCAL board from the committed spec) · `reconcile` (fold discovered-ledger items into the board + UC invariants) · `retrofit-surface` (append `## Test Surface` to a pre-surface spec) · `coverage` (extract atomic requirement clauses → the SHARED `requirements.md` registry) |
|
|
28
|
+
| `payload.pitch` | The pitch/PRD path (analyze) |
|
|
29
|
+
| `payload.requirements` | (coverage) the REQ source to extract atomic clauses from — pitch / a customer-requirements doc / the use-case bodies. Absent → default to the pitch and record the choice in `assumptions[]` |
|
|
30
|
+
| `payload.lens` | `lite` \| `standard` \| `cross-context`. Absent → judge it: LITE for ≤2-week appetite, no third-party, ≤3 user-facing actions; STANDARD for multi-team, third-party, or bigger appetite; genuinely unclear → one binary question / one `escalates[]` entry |
|
|
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
|
+
| `payload.spec_folder` / `payload.feature` | Where the committed tree lives / the slug |
|
|
33
|
+
| `payload.discovered_ledger` | (reconcile) the ledger whose raw `[+]`/`~` lines you fold in |
|
|
34
|
+
| `payload.kb_rules_path` | Team guidelines (read if present) — steering, never spec |
|
|
35
|
+
| `substrate.allowed / append_only / frozen` | Your write contract for THIS operation. The old frozen-zone prose is now data the sandbox hook enforces: respect it, and when an operation genuinely needs a file outside it, ESCALATE — never widen |
|
|
36
|
+
| `interaction.pause_gates` | Caller policy. `true` (standalone default): pause at the phase checkpoints below, max 2 questions each. `false`: run straight through, surfacing questions as `assumptions[]`/`escalates[]` instead |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Core craft — the analysis pipeline (operation: analyze)
|
|
41
|
+
|
|
42
|
+
Phases, each with a checkpoint (pause only per `interaction`). Read the reference file before
|
|
43
|
+
its phase; templates live in `assets/templates/`.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
1 INGEST pitch + orient artifacts + KB. Extract slug, appetite, in/out boundaries,
|
|
47
|
+
rabbit holes, third-party mentions. No files written yet.
|
|
48
|
+
1b FEASIBILITY (third-party/API/SDK/webhook mentioned) verification questions + fallback
|
|
49
|
+
scope per API-NN → api-feasibility.md
|
|
50
|
+
2 DDD bounded contexts, aggregates (new vs extended), value objects, domain events,
|
|
51
|
+
repository interfaces → domain-model.md [references/ddd-patterns.md]
|
|
52
|
+
2b CONTRACTS (standard lens) typed Request/Response/Error per repository; two-pass rule:
|
|
53
|
+
unresolvable at spec time → `⏳ TBD — verify in TASK-NNN-spike-…`, resolved
|
|
54
|
+
post-SPIKE with citation → contracts/ [references/contract-patterns.md]
|
|
55
|
+
3 UX per screen: state table (idle→loading→error→success), error cases with
|
|
56
|
+
message+action, ASCII flows → ux-behavior.md [references/ux-behavior-patterns.md]
|
|
57
|
+
4 USE CASES one file per actor+action: typed Input/Output, numbered Steps, all error
|
|
58
|
+
cases with codes, ## System Flow (UI→API→UC→Repo→DB), ## Test Surface
|
|
59
|
+
(DERIVED ONLY from D1 Invariants · D2 Error Cases · D3 Contract shape ·
|
|
60
|
+
D4 No-gos — a sourceless test idea is a spec gap to raise, never a row to
|
|
61
|
+
invent) → usecases/ [references/test-surface.md]
|
|
62
|
+
5 INTEGRATION (standard lens) cross-system data flows, events, silent-failure risks
|
|
63
|
+
→ integration.md [references/integration-analysis.md]
|
|
64
|
+
6 TASKS atomic, ordered, executable → tasks/ (LOCAL root; the one uncommitted branch
|
|
65
|
+
of the tree — regenerable, machine-local) [references/task-generation.md]
|
|
66
|
+
7 DERIVE+LINT mechanical, not yours to grade:
|
|
67
|
+
node "${CLAUDE_PLUGIN_ROOT}/skills/ba-pitch-analyzer/scripts/board-derive.mjs" --slug <slug> --write
|
|
68
|
+
(unlocks = depends_on inverse; Σ hours; critical path; appetite arithmetic —
|
|
69
|
+
overflow is a fact you REPORT for the caller's HAMMER gate, never resolve)
|
|
70
|
+
node "${CLAUDE_PLUGIN_ROOT}/skills/ba-pitch-analyzer/scripts/spec-lint.mjs" --slug <slug>
|
|
71
|
+
(structure, wikilinks, edge symmetry — fix reds, then re-run; you never
|
|
72
|
+
self-grade with a hand-walked checklist)
|
|
73
|
+
→ scope-summary.md + synthesis.md (traceability matrix, risk register,
|
|
74
|
+
dependency graph — the JUDGMENT layers over board-derive's numbers)
|
|
75
|
+
8 INDEX _index.md (pitch digest + document map) + feedback.md template
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Task generation rules (the craft that makes tasks executable):**
|
|
79
|
+
- One task = one verifiable change (one package, one concern); AC checkable by running commands.
|
|
80
|
+
- `depends_on` explicit; `unlocks` NEVER hand-authored — board-derive recomputes it.
|
|
81
|
+
- Contract-first: an implementation task touching a repository requires its contract file
|
|
82
|
+
(else generate the contract-stub task first); third-party + `⏳ TBD` → the SPIKE task
|
|
83
|
+
precedes and blocks it (`time_box_hours` hard cap, `api_ref`, `blocks[]`).
|
|
84
|
+
- AC Trigger Matrix (full rules in references/task-generation.md): conditional rendering →
|
|
85
|
+
🔁 Inverse Conditions; data fetching → 📭 Empty & Null States; numeric limits → 🔢 Boundary
|
|
86
|
+
Values; FEAT + user actor or cross-layer → 🧪 BDD Scenarios; ≥1 service boundary →
|
|
87
|
+
🔗 Integration Flow; not triggered → remove the section entirely.
|
|
88
|
+
- After all implementation tasks: one integration-test task (DB round-trip, auth rejection,
|
|
89
|
+
cross-service BDD).
|
|
90
|
+
|
|
91
|
+
**Coverage trust = UC.** Every task carries `use_case_refs`; a task with none is an orphan
|
|
92
|
+
(red). An invariant-backed regression task still anchors to its owning UC — there is no
|
|
93
|
+
second path to green.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## The other four operations — same craft, different payload + whitelist
|
|
98
|
+
|
|
99
|
+
| Operation | Essence | Never |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `generate-board` | Re-derive the full task set fresh from the committed `usecases/` + `domain-model.md` (+ scope contracts if present — tasks respect their substrates). Numbering restarts at TASK-001. Initialize `status` from committed mechanical truth at SCOPE granularity (a scope with hill shard FINISHED → its tasks start `done`) — never join on task id; ids renumber per machine, the scope is the stable key. Then board-derive `--write` + regenerate scope-summary.md | touch the committed spec docs (frozen in your substrate) |
|
|
102
|
+
| `reconcile` | Verify `ledger.feature == payload.feature` (mismatch → STOP). Map each `[+]` Keep item → its owning UC; new task continues numbering (never renumber); `~`/Cut → synthesis "Hammered Out" row, no file. A Keep item asserting a new invariant → APPEND `[INV-NN]` + TS-INV row to that UC (append-only sections in your substrate). A new actor/action with no UC → `escalates[]` (spec-ambiguity): spawning a UC mid-cycle is silent re-shaping, the PO decides. Finish with board-derive (appetite overflow → report) + spec-lint | re-run phases 1–5; edit UC Steps; resolve the appetite HAMMER yourself |
|
|
103
|
+
| `retrofit-surface` | Append `## Test Surface` (derived rows only, after Error Cases) to each UC of a pre-surface spec; an all-sources-empty UC gets the explicit empty-sources line | touch anything else — append-only substrate |
|
|
104
|
+
| `coverage` | Extract **atomic** customer requirement clauses from `payload.requirements` (default: the pitch) and write the SHARED `shapeup/<slug>/requirements.md` registry: one `\| REQ-id \| clause (verbatim) \| source \| status \| note \|` row per clause. Split compound sentences into one testable clause each — the audit's dropped clauses ("side-step OR lure enemies into traps", "low-res world textures") were *lost inside* a bigger sentence. **Assign REQ-ids ONCE and freeze them** (they behave like scope_id, never TASK-NNN — every `covers:` link rots otherwise): re-running, append new clauses with fresh ids, mark a removed clause `CUT (PO-approved)`, never renumber or delete. Status starts `covered` (a live requirement); only the PO sets `CUT`. The REQ source itself is frozen — the registry is a separate derived file | edit the REQ source; renumber existing REQ-ids; delete a dropped clause instead of marking it CUT; invent a requirement not in the source |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Anti-rationalization table
|
|
109
|
+
|
|
110
|
+
| Excuse | Reality |
|
|
111
|
+
|---|---|
|
|
112
|
+
| "This test idea is obviously worth a row" | No D1–D4 source = no row. Raise it as a spec gap; inventing rows is how the judge ends up grading fiction. |
|
|
113
|
+
| "The discovered item obviously fits UC-03" | Run the actor/action match. 'Obviously' is how UCs silently widen — no match → ESCALATE. |
|
|
114
|
+
| "I'll fix the UC steps while reconciling" | Steps are frozen in your substrate. A step change is re-shaping — the PO's call, not yours. |
|
|
115
|
+
| "My output looks complete, score it 92" | You don't grade yourself. spec-lint reports facts; the judge judges. |
|
|
116
|
+
| "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
|
+
| "Re-scanning the codebase is safer than trusting orient" | code-surface.md IS the map. Re-scanning burns tokens and forks the truth. |
|
|
118
|
+
| "unlocks is quick to fill in by hand" | Hand-authored unlocks produced 10 asymmetric edges (KB-BA-001). board-derive computes it. |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Output contract — the WorkResult
|
|
123
|
+
|
|
124
|
+
Domain artifacts land inside your substrate (the committed spec tree + the LOCAL board). Then
|
|
125
|
+
write `.shapeup/<slug>/results/<order-suffix>.json`:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"schema_version": 1, "order_id": "<copied>", "worker": "ba-pitch-analyzer",
|
|
130
|
+
"status": "done | partial | escalated",
|
|
131
|
+
"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
|
+
"assumptions": ["lens=standard — third-party PSP present"],
|
|
134
|
+
"deviations": [],
|
|
135
|
+
"discoveries": [ { "marker": "+", "line": "appetite overflow 12h — candidate cuts: TASK-014, TASK-017" } ]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
You do NOT write: `run-state.md` (dead — the orchestrator owns run truth), `tasks/_index.md`
|
|
140
|
+
status flips for built work (ingest's job), scope contracts (scope-architect's), or any
|
|
141
|
+
`discovered_rounds` counter (the orchestrator counts rounds).
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Verification checklist
|
|
146
|
+
|
|
147
|
+
- [ ] Every file written matches the operation's substrate (allowed/append_only respected)
|
|
148
|
+
- [ ] Every task has `use_case_refs`, explicit `depends_on`, command-verifiable AC
|
|
149
|
+
- [ ] No hand-authored `unlocks`; board-derive ran `--write` after the last board change
|
|
150
|
+
- [ ] spec-lint reports 0 red (or each remaining red is explained in `deviations[]`)
|
|
151
|
+
- [ ] Test Surface rows all cite a D1–D4 source; gaps raised, not filled
|
|
152
|
+
- [ ] Appetite overflow (if any) reported as a discovery, not self-resolved
|
|
153
|
+
- [ ] The WorkResult validates against `work-result.schema.json`
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Invocation
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Orchestrated (tech-lead MAP SCOPES / round boundaries) — the canonical form
|
|
161
|
+
/ba-pitch-analyzer --order .shapeup/checkout-vnpay/orders/analyze.json
|
|
162
|
+
|
|
163
|
+
# Standalone — the preamble shim compiles the order (mode: standalone, pause_gates: true):
|
|
164
|
+
# node "${CLAUDE_PLUGIN_ROOT}/skills/tech-lead/scripts/compile-order.mjs" --operation analyze --slug <slug> \
|
|
165
|
+
# --worker ba-pitch-analyzer --payload '{"pitch": "docs/pitch.md", "lens": "standard"}'
|
|
166
|
+
/ba-pitch-analyzer docs/pitch.md # operation: analyze, lens judged
|
|
167
|
+
/ba-pitch-analyzer --lens standard docs/pitch.md # lens pinned
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Standalone keeps exactly two flags: the pitch input and `--lens`. Every retired flag is now
|
|
171
|
+
caller context: `--tasks-only` → a generate-board order, `--from-discovered` → a reconcile
|
|
172
|
+
order, `--surface-only` → a retrofit-surface order, `--remap`/`--split` → scope-architect
|
|
173
|
+
orders, `--status` → read `spec-lint.mjs`/`board-derive.mjs` output (zero LLM tokens),
|
|
174
|
+
`--auto`/`--skip-gate*` → `interaction.pause_gates`, `--upgrade` → an analyze order with the
|
|
175
|
+
standard lens over an existing lite tree (reconciliation pass: extend, never overwrite Steps).
|