@tekyzinc/gsd-t 4.0.29 → 4.2.10
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/CHANGELOG.md +37 -0
- package/README.md +6 -0
- package/bin/gsd-t-competition-judge.cjs +344 -0
- package/bin/gsd-t-traceability-gate.cjs +338 -0
- package/bin/gsd-t.js +29 -0
- package/commands/gsd-t-design-decompose.md +9 -2
- package/commands/gsd-t-help.md +16 -0
- package/commands/gsd-t-milestone.md +9 -2
- package/commands/gsd-t-partition.md +9 -2
- package/commands/gsd-t-plan.md +5 -3
- package/package.json +1 -1
- package/templates/CLAUDE-global.md +1 -1
- package/templates/prompts/pre-mortem-subagent.md +46 -0
- package/templates/workflows/gsd-t-phase.workflow.js +414 -19
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* gsd-t-traceability-gate — M83 D1
|
|
5
|
+
*
|
|
6
|
+
* The plan-phase acceptance-traceability gate. The deterministic half of
|
|
7
|
+
* Left-Shifted Plan Hardening (the adversarial pre-mortem agent is the
|
|
8
|
+
* generative half). Contract: .gsd-t/contracts/plan-hardening-contract.md.
|
|
9
|
+
*
|
|
10
|
+
* ORIGIN (NiceNote M5 incident, 2026-06-05): M5's headline capability (AC-6,
|
|
11
|
+
* 100MB+ chunked read) shipped as DEAD CODE — the chunk reader was built but
|
|
12
|
+
* openPath still materialized whole files, and NO test asserted the headline
|
|
13
|
+
* capability, so the suite stayed green. The triad burned 4 verify cycles
|
|
14
|
+
* re-litigating the milestone's reason to exist. Root cause: the plan never
|
|
15
|
+
* bound each acceptance criterion to (a) a real code path and (b) a test that
|
|
16
|
+
* FAILS if that path is absent. This gate enforces that binding BEFORE execute.
|
|
17
|
+
*
|
|
18
|
+
* What it checks, per `.gsd-t/domains/* /tasks.md` task block:
|
|
19
|
+
* - Every task that declares **Acceptance criteria** MUST declare **Files**
|
|
20
|
+
* (an implementing code path) — an AC with no path is an unbacked promise.
|
|
21
|
+
* - Every such task MUST declare a TEST reference (a Test/Tests field, a
|
|
22
|
+
* test-runner mention, or a Files entry matching a test path pattern) — an
|
|
23
|
+
* AC with no killing test is the dead-code class (passes vacuously / never
|
|
24
|
+
* exercised). The milestone's HEADLINE capability without a test is exactly
|
|
25
|
+
* the M5 failure.
|
|
26
|
+
* - A task tagged as the milestone HEADLINE (**Headline:** true, or an AC
|
|
27
|
+
* referencing the milestone's named capability) gets a STRICTER check: it
|
|
28
|
+
* MUST have a non-test Files entry (real implementation, not just a test)
|
|
29
|
+
* AND a test entry. A headline with only a test, or only an impl, fails.
|
|
30
|
+
*
|
|
31
|
+
* It does NOT judge whether the code is correct (that's verify) — only whether
|
|
32
|
+
* the PLAN is complete enough that execute can't produce a dead deliverable.
|
|
33
|
+
*
|
|
34
|
+
* Input: --milestone Mxx --project-dir PATH (reads .gsd-t/domains/* /tasks.md).
|
|
35
|
+
* OR --tasks <file> to check a single tasks.md (used by tests).
|
|
36
|
+
* Output: JSON envelope { ok, exitCode, milestone, tasks:[...], violations:[...] }.
|
|
37
|
+
* Exit: 0 all tasks traceable · 4 ≥1 violation (blocks execute) · 64 bad input.
|
|
38
|
+
*
|
|
39
|
+
* Hard rules: zero deps, never throws, pure/read-only.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const fs = require("node:fs");
|
|
43
|
+
const path = require("node:path");
|
|
44
|
+
|
|
45
|
+
// ─── tasks.md parsing ────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
// Red Team CRITICAL/HIGH-3/MEDIUM-1 (M83 verify): markdown field labels appear in
|
|
48
|
+
// BOTH `**Label**: v` (colon outside bold) and `**Label:** v` (colon inside) forms.
|
|
49
|
+
// Matching against the raw line missed the colon-inside form — defeating the entire
|
|
50
|
+
// gate on the canonical M5 dead-code plan. Fix: STRIP emphasis markers first, then
|
|
51
|
+
// match the colon-agnostic bare text. All field detection runs on the bared line.
|
|
52
|
+
function _bare(line) {
|
|
53
|
+
return String(line == null ? "" : line).replace(/[*_`]/g, "");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Path-safe bare: strips only emphasis that wraps labels (* and backtick), but
|
|
57
|
+
// PRESERVES underscores — pytest's test_*.py / *_test.py conventions depend on
|
|
58
|
+
// them, and TEST_PATH_RE has `_test\.` / `test_` alternatives (Red Team M83
|
|
59
|
+
// recheck HIGH: stripping `_` before the test-path scan false-failed Python plans).
|
|
60
|
+
function _barePath(s) {
|
|
61
|
+
return String(s == null ? "" : s).replace(/[*`]/g, "");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// A test reference is: an explicit Test/Tests field, a known runner mention, or a
|
|
65
|
+
// Files path that looks like a test file. Kept broad on purpose — the gate asserts
|
|
66
|
+
// a test is NAMED, not that it exists yet (plan precedes execute).
|
|
67
|
+
const TEST_PATH_RE = /(\.test\.|\.spec\.|(^|\/)tests?\/|(^|\/)e2e\/|_test\.|test_|cargo test|vitest|playwright|pytest|jest)/i;
|
|
68
|
+
// Field regexes run on the BARED line, so the colon can be anywhere the label ends.
|
|
69
|
+
const TEST_FIELD_RE = /^\s*[-*]?\s*(tests?|test\s*ref|test\s*coverage|verified\s*by)\s*:/i;
|
|
70
|
+
const FILES_FIELD_RE = /^\s*[-*]?\s*files?\s*:/i;
|
|
71
|
+
const AC_FIELD_RE = /^\s*[-*]?\s*(acceptance(\s*criteria)?|accept|ac)\s*:/i;
|
|
72
|
+
const HEADLINE_FIELD_RE = /^\s*[-*]?\s*headline\s*:\s*(true|yes)/i;
|
|
73
|
+
const HEADING_RE = /^(#{2,4})\s+(.*\S.*)$/;
|
|
74
|
+
|
|
75
|
+
// Headings that are structural, never tasks — so we don't mis-parse a Summary/
|
|
76
|
+
// Overview block as a behavioral task. Everything else that bears an AC field IS
|
|
77
|
+
// assessed (Red Team HIGH-2: do NOT gate task detection on heading wording —
|
|
78
|
+
// anchor on the AC, so a descriptive heading like "Implement the reader" is caught).
|
|
79
|
+
const NON_TASK_HEADING_RE = /^(summary|overview|notes?|context|goal|background|wave\s*history|index|integration\s*points?|dependencies|references?|appendix|tasks)\s*$/i;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse a tasks.md into candidate blocks: every `##`–`####` heading starts a
|
|
83
|
+
* block (except the structural-heading skip list). A block becomes a TASK for
|
|
84
|
+
* assessment iff it contains an acceptance-criteria field (decided later in
|
|
85
|
+
* assessTask) — but we keep ALL non-structural blocks so no AC-bearing block is
|
|
86
|
+
* ever dropped on heading wording.
|
|
87
|
+
* @returns {Array<{title, raw, lines}>}
|
|
88
|
+
*/
|
|
89
|
+
function parseTasks(md) {
|
|
90
|
+
const lines = (md || "").split(/\r?\n/);
|
|
91
|
+
const blocks = [];
|
|
92
|
+
let cur = null;
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const m = line.match(HEADING_RE);
|
|
95
|
+
if (m) {
|
|
96
|
+
const title = m[2].trim();
|
|
97
|
+
// Close any open block at every heading.
|
|
98
|
+
if (cur) { blocks.push(cur); cur = null; }
|
|
99
|
+
// Structural headings start no block; everything else does.
|
|
100
|
+
if (!NON_TASK_HEADING_RE.test(_bare(title).trim())) {
|
|
101
|
+
cur = { title, lines: [] };
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (cur) cur.lines.push(line);
|
|
106
|
+
}
|
|
107
|
+
if (cur) blocks.push(cur);
|
|
108
|
+
return blocks.map((t) => ({ title: t.title, raw: t.lines.join("\n"), lines: t.lines }));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── per-task traceability assessment ────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
// All field matching runs on the BARED line (emphasis stripped) so colon
|
|
114
|
+
// position inside/outside bold is irrelevant (Red Team CRITICAL fix).
|
|
115
|
+
function fieldValue(lines, re) {
|
|
116
|
+
for (const ln of lines) {
|
|
117
|
+
const bare = _bare(ln);
|
|
118
|
+
if (re.test(bare)) {
|
|
119
|
+
const idx = bare.indexOf(":");
|
|
120
|
+
return idx >= 0 ? bare.slice(idx + 1).trim() : "";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Like fieldValue but PRESERVES underscores in the returned value (label is still
|
|
127
|
+
// matched emphasis-agnostically) — used for value-level test-path scans so
|
|
128
|
+
// test_*.py / *_test.py survive (Red Team recheck HIGH).
|
|
129
|
+
function fieldValueRaw(lines, re) {
|
|
130
|
+
for (const ln of lines) {
|
|
131
|
+
if (re.test(_bare(ln))) {
|
|
132
|
+
const raw = _barePath(ln);
|
|
133
|
+
const idx = raw.indexOf(":");
|
|
134
|
+
return idx >= 0 ? raw.slice(idx + 1).trim() : "";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function hasMultiField(lines, re) {
|
|
141
|
+
return lines.some((ln) => re.test(_bare(ln)));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Collect the indented/bulleted sub-lines that follow an Acceptance-criteria
|
|
145
|
+
// label up to the next top-level field — these ARE the acceptance criteria, and
|
|
146
|
+
// an AC may name its own verifying test there ("…; verified by cargo test").
|
|
147
|
+
function _acBulletText(lines) {
|
|
148
|
+
const out = [];
|
|
149
|
+
let inAc = false;
|
|
150
|
+
for (const ln of lines) {
|
|
151
|
+
const bare = _bare(ln);
|
|
152
|
+
if (AC_FIELD_RE.test(bare)) { inAc = true; continue; }
|
|
153
|
+
if (!inAc) continue;
|
|
154
|
+
// A new NON-INDENTED "Label:" line closes the AC block.
|
|
155
|
+
if (/^\s*[-*]?\s*[a-z][a-z\s]{1,24}:/i.test(bare) && !/^\s{2,}/.test(ln)) {
|
|
156
|
+
inAc = false; continue;
|
|
157
|
+
}
|
|
158
|
+
out.push(_barePath(ln)); // preserve underscores for test-path detection
|
|
159
|
+
}
|
|
160
|
+
return out.join("\n");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A task is "behavioral" (subject to the gate) if it declares acceptance
|
|
165
|
+
* criteria — i.e. it promises an observable behavior. Pure-scaffolding tasks
|
|
166
|
+
* with no ACs are out of scope (nothing to trace).
|
|
167
|
+
*/
|
|
168
|
+
function assessTask(task) {
|
|
169
|
+
const lines = task.lines;
|
|
170
|
+
const hasAc = hasMultiField(lines, AC_FIELD_RE);
|
|
171
|
+
if (!hasAc) {
|
|
172
|
+
return { title: task.title, behavioral: false, violations: [] };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Underscore-preserving values for path/runner scans (Red Team recheck HIGH).
|
|
176
|
+
const filesVal = fieldValueRaw(lines, FILES_FIELD_RE) || "";
|
|
177
|
+
const hasFiles = hasMultiField(lines, FILES_FIELD_RE) && filesVal.replace(/[—–-]/g, "").trim().length > 0;
|
|
178
|
+
|
|
179
|
+
// Test reference (MEDIUM-1 fix): satisfied ONLY by a runner/test-path tied to a
|
|
180
|
+
// RELEVANT field — the Test field, the Files field, or the Acceptance-criteria
|
|
181
|
+
// value (where an AC may name its own verifying test, e.g. "…; verified by cargo
|
|
182
|
+
// test"). An incidental runner mention in an UNRELATED field (Dependencies,
|
|
183
|
+
// Notes, Scope) must NOT vacuously clear the killing-test requirement.
|
|
184
|
+
const hasTestField = hasMultiField(lines, TEST_FIELD_RE);
|
|
185
|
+
const testFieldVal = fieldValueRaw(lines, TEST_FIELD_RE) || "";
|
|
186
|
+
const acVal = fieldValueRaw(lines, AC_FIELD_RE) || "";
|
|
187
|
+
// AC criteria often span bullet sub-lines after the label; gather those too
|
|
188
|
+
// (underscore-preserving, so a test_*.py named in a bullet still matches).
|
|
189
|
+
const acBullets = _acBulletText(lines);
|
|
190
|
+
const filesHasTestPath = TEST_PATH_RE.test(filesVal);
|
|
191
|
+
const testFieldHasRunner = TEST_PATH_RE.test(testFieldVal);
|
|
192
|
+
const acHasRunner = TEST_PATH_RE.test(acVal) || TEST_PATH_RE.test(acBullets);
|
|
193
|
+
const hasTest = hasTestField || filesHasTestPath || testFieldHasRunner || acHasRunner;
|
|
194
|
+
|
|
195
|
+
// A non-test implementing path: a Files entry that is NOT only test files.
|
|
196
|
+
const fileTokens = filesVal.split(/[,\s]+/).map((s) => s.replace(/[`*()]/g, "").trim()).filter(Boolean);
|
|
197
|
+
const implTokens = fileTokens.filter((f) => /[./]/.test(f) && !TEST_PATH_RE.test(f));
|
|
198
|
+
const hasImplPath = implTokens.length > 0;
|
|
199
|
+
|
|
200
|
+
const isHeadline = lines.some((ln) => HEADLINE_FIELD_RE.test(_bare(ln)));
|
|
201
|
+
|
|
202
|
+
const violations = [];
|
|
203
|
+
if (!hasFiles) {
|
|
204
|
+
violations.push({ kind: "ac-without-path", detail: "task declares acceptance criteria but no **Files** implementing path — an unbacked promise." });
|
|
205
|
+
}
|
|
206
|
+
if (!hasTest) {
|
|
207
|
+
violations.push({ kind: "ac-without-test", detail: "task declares acceptance criteria but names no test (Test field, test path, or runner) — the dead-code class: it can pass vacuously / never be exercised." });
|
|
208
|
+
}
|
|
209
|
+
if (isHeadline && !hasImplPath) {
|
|
210
|
+
violations.push({ kind: "headline-without-impl", detail: "HEADLINE task has no non-test implementing path — the milestone's reason to exist is not bound to real code (the M5 AC-6 dead-code failure)." });
|
|
211
|
+
}
|
|
212
|
+
if (isHeadline && !hasTest) {
|
|
213
|
+
violations.push({ kind: "headline-without-test", detail: "HEADLINE task has no test proving the milestone's core capability is delivered (the missing >100MB-fixture failure)." });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
title: task.title,
|
|
218
|
+
behavioral: true,
|
|
219
|
+
isHeadline,
|
|
220
|
+
hasFiles, hasTest, hasImplPath,
|
|
221
|
+
violations,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── driver ──────────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
function listTasksFiles(projectDir, milestone) {
|
|
228
|
+
const domainsDir = path.join(projectDir, ".gsd-t", "domains");
|
|
229
|
+
let entries = [];
|
|
230
|
+
try {
|
|
231
|
+
entries = fs.readdirSync(domainsDir, { withFileTypes: true });
|
|
232
|
+
} catch {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
const out = [];
|
|
236
|
+
const mPrefix = milestone ? milestone.toLowerCase() : null;
|
|
237
|
+
for (const e of entries) {
|
|
238
|
+
if (!e.isDirectory()) continue;
|
|
239
|
+
// When a milestone is given, prefer domains whose name carries that mNN
|
|
240
|
+
// prefix; if none match, fall back to all domains (single-milestone repos).
|
|
241
|
+
const tasksPath = path.join(domainsDir, e.name, "tasks.md");
|
|
242
|
+
if (fs.existsSync(tasksPath)) out.push({ domain: e.name, tasksPath });
|
|
243
|
+
}
|
|
244
|
+
if (mPrefix) {
|
|
245
|
+
const matched = out.filter((d) => d.domain.toLowerCase().startsWith(mPrefix));
|
|
246
|
+
if (matched.length) return matched;
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function runGate({ projectDir = process.cwd(), milestone = null, tasksFile = null } = {}) {
|
|
252
|
+
let files;
|
|
253
|
+
if (tasksFile) {
|
|
254
|
+
files = [{ domain: path.basename(path.dirname(tasksFile)), tasksPath: tasksFile }];
|
|
255
|
+
} else {
|
|
256
|
+
files = listTasksFiles(projectDir, milestone);
|
|
257
|
+
}
|
|
258
|
+
if (!files.length) {
|
|
259
|
+
return { ok: false, exitCode: 64, milestone, reason: "no-tasks-files", tasks: [], violations: [] };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const taskResults = [];
|
|
263
|
+
const violations = [];
|
|
264
|
+
let behavioralCount = 0;
|
|
265
|
+
for (const f of files) {
|
|
266
|
+
let md;
|
|
267
|
+
try { md = fs.readFileSync(f.tasksPath, "utf8"); } catch { continue; }
|
|
268
|
+
for (const t of parseTasks(md)) {
|
|
269
|
+
const r = assessTask(t);
|
|
270
|
+
r.domain = f.domain;
|
|
271
|
+
taskResults.push(r);
|
|
272
|
+
if (r.behavioral) behavioralCount++;
|
|
273
|
+
for (const v of r.violations) {
|
|
274
|
+
violations.push({ domain: f.domain, task: r.title, ...v });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const ok = violations.length === 0;
|
|
280
|
+
return {
|
|
281
|
+
ok,
|
|
282
|
+
exitCode: ok ? 0 : 4,
|
|
283
|
+
milestone,
|
|
284
|
+
summary: {
|
|
285
|
+
tasksTotal: taskResults.length,
|
|
286
|
+
behavioral: behavioralCount,
|
|
287
|
+
violations: violations.length,
|
|
288
|
+
},
|
|
289
|
+
tasks: taskResults,
|
|
290
|
+
violations,
|
|
291
|
+
...(ok ? {} : { reason: "untraceable-acceptance-criteria" }),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ─── CLI ─────────────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
function parseArgs(argv) {
|
|
298
|
+
const o = { projectDir: process.cwd(), milestone: null, tasksFile: null, help: false };
|
|
299
|
+
for (let i = 0; i < argv.length; i++) {
|
|
300
|
+
const a = argv[i];
|
|
301
|
+
if (a === "--help" || a === "-h") o.help = true;
|
|
302
|
+
else if (a === "--project-dir") o.projectDir = argv[++i];
|
|
303
|
+
else if (a === "--milestone") o.milestone = argv[++i];
|
|
304
|
+
else if (a === "--tasks") o.tasksFile = argv[++i];
|
|
305
|
+
else if (a === "--json") {/* default */}
|
|
306
|
+
}
|
|
307
|
+
return o;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const HELP = `Usage: gsd-t traceability-gate [--milestone Mxx] [--project-dir PATH] [--tasks FILE]
|
|
311
|
+
|
|
312
|
+
Plan-phase acceptance-traceability gate (M83). Asserts every behavioral task in
|
|
313
|
+
the milestone's .gsd-t/domains/* /tasks.md binds its acceptance criteria to an
|
|
314
|
+
implementing **Files** path AND a named test. Headline tasks must have BOTH a
|
|
315
|
+
real implementation path and a test. Blocks execute on any violation.
|
|
316
|
+
|
|
317
|
+
--milestone Mxx Limit to domains whose name carries the mNN prefix.
|
|
318
|
+
--project-dir P Project root (default: cwd).
|
|
319
|
+
--tasks FILE Check a single tasks.md (overrides domain discovery).
|
|
320
|
+
|
|
321
|
+
Exit: 0 all traceable · 4 ≥1 violation · 64 no tasks files / bad input.`;
|
|
322
|
+
|
|
323
|
+
function main() {
|
|
324
|
+
const o = parseArgs(process.argv.slice(2));
|
|
325
|
+
if (o.help) { process.stdout.write(HELP + "\n"); process.exit(0); }
|
|
326
|
+
let res;
|
|
327
|
+
try {
|
|
328
|
+
res = runGate(o);
|
|
329
|
+
} catch (e) {
|
|
330
|
+
res = { ok: false, exitCode: 64, milestone: o.milestone, reason: `gate-error: ${e && e.message}`, tasks: [], violations: [] };
|
|
331
|
+
}
|
|
332
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
333
|
+
process.exit(res.exitCode);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (require.main === module) main();
|
|
337
|
+
|
|
338
|
+
module.exports = { runGate, parseTasks, assessTask, _internal: { fieldValue, TEST_PATH_RE } };
|
package/bin/gsd-t.js
CHANGED
|
@@ -1182,6 +1182,10 @@ const GLOBAL_BIN_TOOLS = [
|
|
|
1182
1182
|
// M57 — CI-parity verify-gate checks (structural build-coverage + containment-safe ci-parity).
|
|
1183
1183
|
"gsd-t-build-coverage.cjs",
|
|
1184
1184
|
"gsd-t-ci-parity.cjs",
|
|
1185
|
+
// M82 — Competition Mode generate-and-judge selection oracle.
|
|
1186
|
+
"gsd-t-competition-judge.cjs",
|
|
1187
|
+
// M83 — Plan-phase acceptance-traceability gate.
|
|
1188
|
+
"gsd-t-traceability-gate.cjs",
|
|
1185
1189
|
];
|
|
1186
1190
|
|
|
1187
1191
|
function installGlobalBinTools() {
|
|
@@ -2469,6 +2473,12 @@ const PROJECT_BIN_TOOLS = [
|
|
|
2469
2473
|
"cli-preflight.cjs", "parallel-cli.cjs", "parallel-cli-tee.cjs",
|
|
2470
2474
|
"gsd-t-context-brief.cjs",
|
|
2471
2475
|
"gsd-t-verify-gate.cjs", "gsd-t-verify-gate-judge.cjs",
|
|
2476
|
+
// M82 — Competition Mode judge + its disjointness oracle dependency, so a
|
|
2477
|
+
// project's gsd-t-phase workflow can score candidate partitions via the
|
|
2478
|
+
// project-local bin (runCli prefers bin/<tool>.cjs over the global binary).
|
|
2479
|
+
"gsd-t-competition-judge.cjs", "gsd-t-file-disjointness.cjs",
|
|
2480
|
+
// M83 — Plan-phase acceptance-traceability gate (runs in the plan workflow).
|
|
2481
|
+
"gsd-t-traceability-gate.cjs",
|
|
2472
2482
|
];
|
|
2473
2483
|
|
|
2474
2484
|
// Files that older versions of this installer copied into project bin/ but
|
|
@@ -4546,6 +4556,25 @@ if (require.main === module) {
|
|
|
4546
4556
|
});
|
|
4547
4557
|
process.exit(res.status == null ? 1 : res.status);
|
|
4548
4558
|
}
|
|
4559
|
+
case "competition-judge": {
|
|
4560
|
+
// M82 D1 — `gsd-t competition-judge` thin dispatcher to the generate-and-judge
|
|
4561
|
+
// selection oracle (objective partition judge + deterministic rubric selector).
|
|
4562
|
+
const { spawnSync } = require("child_process");
|
|
4563
|
+
const js = path.join(__dirname, "gsd-t-competition-judge.cjs");
|
|
4564
|
+
const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
|
|
4565
|
+
stdio: "inherit",
|
|
4566
|
+
});
|
|
4567
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
4568
|
+
}
|
|
4569
|
+
case "traceability-gate": {
|
|
4570
|
+
// M83 D1 — `gsd-t traceability-gate` plan-phase acceptance-traceability gate.
|
|
4571
|
+
const { spawnSync } = require("child_process");
|
|
4572
|
+
const js = path.join(__dirname, "gsd-t-traceability-gate.cjs");
|
|
4573
|
+
const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
|
|
4574
|
+
stdio: "inherit",
|
|
4575
|
+
});
|
|
4576
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
4577
|
+
}
|
|
4549
4578
|
case "metrics":
|
|
4550
4579
|
doMetrics(args.slice(1));
|
|
4551
4580
|
break;
|
|
@@ -25,14 +25,21 @@ Capture the design reference from `$ARGUMENTS` (Figma URL / image path). If Figm
|
|
|
25
25
|
args: {
|
|
26
26
|
phase: "design-decompose",
|
|
27
27
|
projectDir: ".",
|
|
28
|
-
userInput: "$ARGUMENTS"
|
|
28
|
+
userInput: "$ARGUMENTS",
|
|
29
|
+
// M82 Competition Mode (opt-in): `--competition N` (N 2..5) fans out N
|
|
30
|
+
// parallel decompositions; a blind, different-model, rubric judge (fidelity /
|
|
31
|
+
// completeness / reuse / simplicity) selects the winner. Useful when a design
|
|
32
|
+
// is ambiguous or the component boundaries aren't obvious.
|
|
33
|
+
competition: 1
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
```
|
|
32
37
|
|
|
38
|
+
**Competition Mode (`--competition N`).** When a design is ambiguous or the element/widget/page boundaries aren't obvious, `/gsd-t-design-decompose --competition 3` fans out N candidate decompositions and a blind, different-model rubric judge picks the best. Parse N (clamped 2..5). See `.gsd-t/contracts/competition-mode-contract.md`. Default off.
|
|
39
|
+
|
|
33
40
|
## Step 3: Interpret the result
|
|
34
41
|
|
|
35
|
-
The Workflow returns `{ status, artifacts, summary, decisions }
|
|
42
|
+
The Workflow returns `{ status, artifacts, summary, decisions }` (plus `competition: { n, winner, ranked }` when Competition Mode ran).
|
|
36
43
|
|
|
37
44
|
- `status === "complete"`: the element → widget → page contract tree is written under `.gsd-t/contracts/design/`.
|
|
38
45
|
- `status === "partial" | "blocked"`: the agent needs the design source (e.g. Figma auth) or a stack-capability decision. Surface it.
|
package/commands/gsd-t-help.md
CHANGED
|
@@ -479,6 +479,22 @@ Use these when user asks for help on a specific command:
|
|
|
479
479
|
- **Use when**: Test data hygiene. Catches the GSD-T-Board class (2442 orphaned `E2E_TEST_*` / `E2E_DRAG_*` ideas left in the production data store after a passing Verify run).
|
|
480
480
|
- **CLI**: `gsd-t test-data --list [--run <id>] [--json]` / `gsd-t test-data --purge --run <id> [--dry-run] [--json] [--project <dir>]`. Exit 0 on success, 4 on adapter errors, 64 on usage error.
|
|
481
481
|
|
|
482
|
+
### competition-judge (M82)
|
|
483
|
+
- **Summary**: The selection oracle for Competition Mode (generate-and-judge — the *generative* dual of the orthogonal validation triad). Two modes: `--kind partition` scores candidate domain decompositions via the file-disjointness oracle (parallelGroups / waveDepth / validity — a calculator, not an LLM critic, so it's immune to judge bias); `--kind generic` is a deterministic rubric selector that finalizes a winner from rubric scores an upstream blind/different-model judge supplied.
|
|
484
|
+
- **Auto-invoked**: Yes — by `gsd-t-phase.workflow.js` when an eligible phase (partition / milestone / design-decompose) is run with `competition: N` (N 2–5). Opt-in per phase via `/gsd-t-partition --competition N` etc. Default off.
|
|
485
|
+
- **Files**: `bin/gsd-t-competition-judge.cjs` (reuses `bin/gsd-t-file-disjointness.cjs`).
|
|
486
|
+
- **Use when**: Upstream, pre-contract, wide-solution-space decisions where the cost of a single draft is high (partition, milestone decomposition, ambiguous design decomposition). Never on post-contract phases (execute/verify/etc.) — those are owned by the adversarial triad.
|
|
487
|
+
- **CLI**: `gsd-t competition-judge [--in <spec.json>] [--project-dir <dir>]` (spec via stdin or `--in`). Exit 0 winner · 4 no valid candidate · 64 bad input.
|
|
488
|
+
- **Contract**: `.gsd-t/contracts/competition-mode-contract.md` v1.0.0 STABLE.
|
|
489
|
+
|
|
490
|
+
### traceability-gate (M83)
|
|
491
|
+
- **Summary**: Plan-phase acceptance-traceability gate — the deterministic half of Left-Shifted Plan Hardening. Parses `.gsd-t/domains/*/tasks.md` and asserts every behavioral task binds its acceptance criteria to a `**Files**` code path AND a named killing test; a `**Headline:** true` task must have both a real implementation path and a test. Catches the dead-deliverable class (a capability built but never tested/wired) at PLAN time instead of at verify.
|
|
492
|
+
- **Auto-invoked**: Yes — by `gsd-t-phase.workflow.js` at the end of the `plan` phase, blocking before execute (alongside the adversarial pre-mortem agent, protocol `templates/prompts/pre-mortem-subagent.md`).
|
|
493
|
+
- **Files**: `bin/gsd-t-traceability-gate.cjs`.
|
|
494
|
+
- **Use when**: Every plan phase (automatic). Origin: NiceNote M5 shipped its headline 100MB+ chunked-read as dead code with no test → 4 verify cycles.
|
|
495
|
+
- **CLI**: `gsd-t traceability-gate [--milestone <Mxx>] [--project-dir <dir>] [--tasks <file>]`. Exit 0 all traceable · 4 ≥1 untraceable AC (blocks execute) · 64 no tasks files.
|
|
496
|
+
- **Contract**: `.gsd-t/contracts/plan-hardening-contract.md` v1.0.0 STABLE.
|
|
497
|
+
|
|
482
498
|
## Unknown Command
|
|
483
499
|
|
|
484
500
|
If user asks for help on unrecognized command:
|
|
@@ -25,14 +25,21 @@ Read `.gsd-t/progress.md` (current version + completed milestones), `docs/requir
|
|
|
25
25
|
args: {
|
|
26
26
|
phase: "milestone",
|
|
27
27
|
projectDir: ".",
|
|
28
|
-
userInput: "$ARGUMENTS"
|
|
28
|
+
userInput: "$ARGUMENTS",
|
|
29
|
+
// M82 Competition Mode (opt-in): `--competition N` (N 2..5) fans out N
|
|
30
|
+
// parallel Self-MoA producers proposing different decomposition strategies
|
|
31
|
+
// (risk-first / value-first / dependency-first); a blind, different-model,
|
|
32
|
+
// rubric judge selects the winner. Coupled-thesis → pick-one (no Frankenstein).
|
|
33
|
+
competition: 1
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
```
|
|
32
37
|
|
|
38
|
+
**Competition Mode (`--competition N`).** Milestone decomposition is the highest-altitude decision in the system — different strategies are genuinely different. If the user invokes `/gsd-t-milestone --competition 3`, parse N (clamped 2..5) and pass `competition: N`. Because a milestone decomposition is a *coupled thesis*, the judge selects one winner whole (pick-one) and only salvages non-overlapping good line-items from the losers — it never Frankensteins. See `.gsd-t/contracts/competition-mode-contract.md`. Default off.
|
|
39
|
+
|
|
33
40
|
## Step 3: Interpret the result
|
|
34
41
|
|
|
35
|
-
The Workflow returns `{ status, artifacts, summary, decisions }
|
|
42
|
+
The Workflow returns `{ status, artifacts, summary, decisions }` (plus `competition: { n, winner, ranked }` when Competition Mode ran).
|
|
36
43
|
|
|
37
44
|
- `status === "complete"`: milestone defined and appended to progress.md with falsifiable SCs. Do NOT auto-partition for large/risky milestones — show the Next Up hint.
|
|
38
45
|
- `status === "blocked"`: the agent needs a scoping decision from the user.
|
|
@@ -30,14 +30,21 @@ Call the `Workflow` tool with:
|
|
|
30
30
|
phase: "partition",
|
|
31
31
|
milestone: "M{NN}",
|
|
32
32
|
projectDir: ".",
|
|
33
|
-
userInput: "$ARGUMENTS"
|
|
33
|
+
userInput: "$ARGUMENTS",
|
|
34
|
+
// M82 Competition Mode (opt-in): if the user passed `--competition N` in
|
|
35
|
+
// $ARGUMENTS (N in 2..5), set competition: N. N parallel Self-MoA producers
|
|
36
|
+
// propose partitions; the OBJECTIVE oracle judge (file-disjointness scoring)
|
|
37
|
+
// picks the most-parallelizable valid decomposition. Omit / set 1 = off.
|
|
38
|
+
competition: 1
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
```
|
|
37
42
|
|
|
43
|
+
**Competition Mode (`--competition N`).** Partition is the v1 beachhead for generate-and-judge: its judge is the file-disjointness oracle, so it is a calculator, not a biased critic. If the user invokes `/gsd-t-partition --competition 3`, parse N (clamped 2..5) and pass `competition: N`. The workflow fans out N candidate partitions, scores each on measured parallelism / wave-depth / boundary-cleanliness, and finalizes the winner. See `.gsd-t/contracts/competition-mode-contract.md`. Default off (single producer).
|
|
44
|
+
|
|
38
45
|
## Step 3: Interpret the result
|
|
39
46
|
|
|
40
|
-
The Workflow returns `{ status, artifacts, summary, decisions }
|
|
47
|
+
The Workflow returns `{ status, artifacts, summary, decisions }` (plus `competition: { n, winner, ranked }` when Competition Mode ran).
|
|
41
48
|
|
|
42
49
|
- `status === "complete"`: domains scoped, contracts drafted. Auto-advance to `/gsd-t-plan`.
|
|
43
50
|
- `status === "partial" | "blocked"`: read `summary` for what's missing (e.g. ambiguous scope needing discussion).
|
package/commands/gsd-t-plan.md
CHANGED
|
@@ -33,12 +33,14 @@ Read `.gsd-t/progress.md` and each domain's `scope.md`/`constraints.md`. The par
|
|
|
33
33
|
|
|
34
34
|
## Step 3: Interpret the result
|
|
35
35
|
|
|
36
|
-
The Workflow returns `{ status, artifacts, summary, decisions }`.
|
|
36
|
+
The Workflow returns `{ status, artifacts, summary, decisions, traceability?, preMortem? }`.
|
|
37
37
|
|
|
38
|
-
- `status === "complete"`: every domain has atomic tasks; `gsd-t parallel --dry-run` validates disjointness. Auto-advance to `/gsd-t-execute`.
|
|
39
|
-
- `status === "partial" | "blocked"`: read `summary` (e.g. file-overlap between domains
|
|
38
|
+
- `status === "complete"`: every domain has atomic tasks; `gsd-t parallel --dry-run` validates disjointness; **M83 plan hardening passed** (acceptance-traceability gate + adversarial pre-mortem). Auto-advance to `/gsd-t-execute`.
|
|
39
|
+
- `status === "partial" | "blocked"`: read `summary` (e.g. file-overlap between domains; or **M83 plan hardening blocked** — see `traceability.violations` / `preMortem.findings`: an AC not bound to a code path + killing test, or a predicted failure condition with no planned test. Fix `tasks.md` and re-run plan).
|
|
40
40
|
- `status === "failed"`: read `summary`.
|
|
41
41
|
|
|
42
|
+
**M83 Plan Hardening (runs automatically at the end of plan, blocking before execute).** Two gates ensure the plan can't produce a dead deliverable: (1) the deterministic **acceptance-traceability gate** (`gsd-t traceability-gate`) — every behavioral task's ACs must bind to a `**Files**` code path + a named test; the **Headline:** task needs both a real impl path and a test. (2) the adversarial **pre-mortem** agent (opus, fresh-context) — predicts edge-case/dead-deliverable/NFR failures and requires a test for each. Origin: NiceNote M5 shipped its headline (100MB+ chunked read) as dead code with no test, burning 4 verify cycles. Contract: `.gsd-t/contracts/plan-hardening-contract.md`.
|
|
43
|
+
|
|
42
44
|
## Document Ripple
|
|
43
45
|
|
|
44
46
|
The plan agent writes per-domain `tasks.md`, updates `integration-points.md`, and adds a Decision Log entry.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.10",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -328,7 +328,7 @@ Canonical scripts:
|
|
|
328
328
|
- `gsd-t-integrate.workflow.js` — cross-domain wire-up + light verify-gate
|
|
329
329
|
- `gsd-t-debug.workflow.js` — 2-cycle diagnose/fix/verify (CLAUDE.md Prime Rule)
|
|
330
330
|
- `gsd-t-quick.workflow.js` — preflight + brief + single-task + verify-gate (M56-D4)
|
|
331
|
-
- `gsd-t-phase.workflow.js` — generic upper-stage runner (partition / plan / discuss / impact / milestone / prd / design-decompose / doc-ripple)
|
|
331
|
+
- `gsd-t-phase.workflow.js` — generic upper-stage runner (partition / plan / discuss / impact / milestone / prd / design-decompose / doc-ripple). **M82 Competition Mode:** an opt-in `competition: N` arg (N 2–5) on eligible upstream phases (partition / milestone / discuss / design-decompose) fans out N parallel Self-MoA producers → a judge stage → a finalizer. Partition's judge is the OBJECTIVE file-disjointness oracle (`gsd-t competition-judge --kind partition` — a calculator, not an LLM critic, immune to judge bias, the v1 beachhead); subjective phases use a blind + shuffled + different-model + rubric judge whose pick is finalized deterministically by `--kind generic`. The generative dual of the orthogonal validation triad; watershed rule = generate-and-judge ABOVE the contract, attack-and-filter BELOW. Default off. Contract: `competition-mode-contract.md` v1.0.0. **M83 Plan Hardening:** the `plan` phase runs two blocking gates before execute — a deterministic acceptance-traceability gate (`gsd-t traceability-gate`: every AC binds to a code path + a killing test; the `Headline:` task needs both impl and test) and an adversarial pre-mortem agent (opus, fresh-context, protocol `pre-mortem-subagent.md`: predicts edge-case/dead-deliverable/NFR failures, each → a required test). The temporal dual of the Red Team (attack the design at plan, not just code at verify). Contract: `plan-hardening-contract.md` v1.0.0.
|
|
332
332
|
- `gsd-t-scan.workflow.js` — preflight → volume-probe → pipeline(per-slice deep finder → single verify) → synthesis → document → render (M66: fans out by codebase VOLUME, not a fixed 5-teammate dimension count; M67: deep document phase deterministically produces the full living-doc set + dimension files, per-doc fan-out)
|
|
333
333
|
|
|
334
334
|
**Runtime-native invariant (M81 — v4.0.29+):** the Workflow sandbox provides ONLY `agent/parallel/pipeline/log/phase/budget/args` — NO `require`/`fs`/`path`/`child_process`/`process`, and `args` arrives as a JSON STRING. Each workflow is self-contained: it `JSON.parse`s `args` and delegates every CLI call (preflight, verify-gate, brief, build-coverage, ci-parity, test-data, disjointness) to inline `async` helpers that run the command via an `agent()`'s Bash (preferring project-local `bin/<tool>.cjs`, else the global `gsd-t` PATH binary) and parse the JSON envelope — preserving the M55-D5 project-local-bin invariant. The old `require("./_lib.js")` pattern threw `ReferenceError` on first eval and silently broke every workflow except scan (TD-113, fixed M81); `_lib.js` is retired as a workflow dependency.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Pre-Mortem Subagent Prompt — Adversarial Plan Review (pre-execute)
|
|
2
|
+
|
|
3
|
+
You are an adversarial Pre-Mortem reviewer. You attack the PLAN, not the code — because the code does not exist yet. Your job is to predict, BEFORE a single line is executed, how this milestone will fail: the edge cases it will hit, the deliverables it will leave hollow, and the assumptions it is quietly making. You are the generative-adversarial dual of the Red Team: the Red Team attacks finished code at verify; you attack the design at plan, so the milestone is built right the FIRST time instead of being re-litigated across verify cycles.
|
|
4
|
+
|
|
5
|
+
**Inverted incentives.** Your value is measured by REAL failure conditions surfaced now, not by approving the plan. A plan you bless that later burns verify cycles is YOUR failure. Assume the plan is flawed and find where.
|
|
6
|
+
|
|
7
|
+
<!-- Workflow-stage invocation -->
|
|
8
|
+
**Invocation context.** When this protocol runs as a native Workflow `agent()` stage (via `templates/workflows/gsd-t-phase.workflow.js` plan phase), your **final emission MUST be a single StructuredOutput object** matching the PRE_MORTEM schema declared by the Workflow. Bash/git/Read tool use is permitted DURING analysis; the final emission is the JSON verdict.
|
|
9
|
+
|
|
10
|
+
<!-- brief-first rule -->
|
|
11
|
+
**Brief first.** If you're about to grep, read, or run something, check the brief at `$BRIEF_PATH` first (a ≤2,500-token snapshot of CLAUDE.md + contracts + scope + requirements). It identifies the milestone's acceptance criteria and high-risk surfaces — your starting attack surface. If unset/missing, fall back to reading the plan artifacts directly, but log the gap.
|
|
12
|
+
|
|
13
|
+
## What you are given
|
|
14
|
+
|
|
15
|
+
The milestone's PLAN: `.gsd-t/domains/*/{scope,constraints,tasks}.md`, the relevant `.gsd-t/contracts/`, and the acceptance criteria / FRs / NFRs in `docs/requirements.md`. Read the milestone's stated GOAL and its HEADLINE capability (the one thing the milestone exists to deliver).
|
|
16
|
+
|
|
17
|
+
## Hard Rules
|
|
18
|
+
|
|
19
|
+
- **Failure conditions = value.** A short list is failure. Exhaust every category below.
|
|
20
|
+
- **A finding must be CONCRETE and FALSIFIABLE.** "Could have edge cases" is not a finding. "A multi-byte UTF-8 codepoint split across a chunk boundary in `read_file_chunk` will corrupt or stall — there is no test for it" IS a finding.
|
|
21
|
+
- **Every blocking finding must become a REQUIRED TEST.** This is the core rule. Do not emit advisory notes — advisory notes get deferred, and a deferred edge case is exactly how the NiceNote M5 chunk reader shipped three distinct data-loss bugs across three verify cycles. For each finding, state the test that must exist in the plan before execute may start. If the plan already names that test, it is not a finding.
|
|
22
|
+
- **The headline capability gets the hardest scrutiny.** Ask explicitly: is the milestone's reason-to-exist (a) bound to a real code path in the plan, (b) reachable from a user action / entry point, and (c) covered by a test that FAILS if that path is dead? The NiceNote M5 milestone shipped its headline (100MB+ chunked read) as DEAD CODE because the plan never required a test that exercised it. Catch that here.
|
|
23
|
+
- **Deferral is illegitimate for a milestone's own headline.** If the plan defers the milestone's defining capability (or a core AC) to a later milestone, that is a blocking finding — an incomplete milestone, not a warning.
|
|
24
|
+
- Style/taste is NOT a finding. Theoretical purity is NOT a finding. Only predicted, concrete, testable failure.
|
|
25
|
+
|
|
26
|
+
## Attack Categories (exhaust ALL)
|
|
27
|
+
|
|
28
|
+
1. **Dead-deliverable / wiring gaps** — Is every acceptance criterion bound to a code path that is actually CALLED from an entry point? Could a capability be built but never invoked (the M5 dead-code class)? Is the headline reachable from a real user action?
|
|
29
|
+
2. **Boundary & edge inputs** — empty / null / huge / zero-length / off-by-one / max-size. For each data path the plan introduces: what is the worst input, and is there a test for it? (split codepoints, chunk boundaries, 0-byte files, files at exactly the threshold, unicode, path traversal.)
|
|
30
|
+
3. **Resource / NFR conditions** — memory, time, file-handle, DOM-node, payload-size ceilings. Does any NFR (performance, bounded memory, scale) have a FALSIFIABLE measured acceptance check in the plan? An NFR with no measured test is a blocking finding (the NiceNote NFR-1 160k-DOM-node class).
|
|
31
|
+
4. **Error & failure paths** — what happens when the new code's dependency fails, the input is malformed, the operation is interrupted mid-flight? Does the plan specify graceful degradation, and is there a test for the failure path (not just the happy path)?
|
|
32
|
+
5. **State / ordering / concurrency** — actions out of order, partial completion, re-entry, two things racing over a shared resource (the verify-gate port-race class). Does the plan account for it?
|
|
33
|
+
6. **Contract & integration seams** — at every cross-domain boundary the plan defines, do both sides agree on shape, error behavior, and who owns the shared file? Is there an integration test for the seam, not just unit tests on each side?
|
|
34
|
+
7. **Shallow-test traps** — does the plan's testing approach risk vacuous passes? (assertions gated behind `if (count > 0)`, `toBeVisible()` standing in for a functional check, `toHaveCount` with no state assertion.) Flag any planned test that would pass on a broken implementation.
|
|
35
|
+
8. **Missing acceptance coverage** — read requirements. Is there an AC / FR / NFR with no task that delivers it, or no test that proves it?
|
|
36
|
+
|
|
37
|
+
## Verdict
|
|
38
|
+
|
|
39
|
+
- **BLOCK** — one or more concrete, falsifiable failure conditions that the plan does not yet cover with a required test. The plan may NOT proceed to execute until each blocking finding is answered by a named required test (or the design is changed to make the condition impossible). This is the FAIL-equivalent.
|
|
40
|
+
- **CLEARED** — exhaustive search; every predicted failure condition is already covered by a named test in the plan, the headline is bound+reachable+tested, and every NFR has a measured acceptance check. (The plan-quality equivalent of GRUDGING-PASS — earned by exhaustion, not by haste.)
|
|
41
|
+
|
|
42
|
+
## Output (StructuredOutput)
|
|
43
|
+
|
|
44
|
+
Emit a single object: `{ verdict: "BLOCK" | "CLEARED", findings: [ { severity: "CRITICAL"|"HIGH"|"MEDIUM"|"LOW", category, condition, whyItFails, requiredTest, affectedAC? } ], headlineAssessment: { capability, boundToPath, reachable, hasKillingTest }, notes }`.
|
|
45
|
+
|
|
46
|
+
`requiredTest` is the load-bearing field: the specific test that must be added to the plan to close the finding. A finding without a `requiredTest` is incomplete — every blocking finding converts to a test the plan must adopt before execute.
|