@massa-ai/codex-plugin 1.35.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/massa-ai/references/agent-orchestration.md +4 -0
- package/skills/massa-ai/references/artifact-persistence.md +46 -0
- package/skills/massa-ai/references/audit-report-io.md +50 -0
- package/skills/massa-ai/references/brownfield-mapping.md +22 -0
- package/skills/massa-ai/references/codebase-investigation.md +1 -1
- package/skills/massa-ai/references/discrimination-sensor.md +42 -0
- package/skills/massa-ai/references/implementation-delivery.md +8 -3
- package/skills/massa-ai/references/knowledge-verification-chain.md +25 -0
- package/skills/massa-ai/references/maestro/cloud.md +2 -0
- package/skills/massa-ai/references/maestro/fact-ledger.md +2 -0
- package/skills/massa-ai/references/maestro/patterns.md +7 -1
- package/skills/massa-ai/references/mobile-figma-matcher/core.md +13 -0
- package/skills/massa-ai/references/spec-driven/artifact-store.md +2 -32
- package/skills/massa-ai/references/spec-driven/design.md +1 -1
- package/skills/massa-ai/references/spec-driven/validate.md +9 -31
- package/skills/massa-ai/references/verification-ladder.md +16 -0
- package/skills/massa-ai/scripts/check_fix_closure.ts +335 -0
- package/skills/massa-ai/scripts/check_specs_delivered.ts +61 -15
- package/skills/massa-ai/scripts/validate_audit_report.ts +2 -1
- package/skills/massa-ai/workflows/architecture/architecture-fix.md +29 -19
- package/skills/massa-ai/workflows/bugs/bugs-fix.md +27 -16
- package/skills/massa-ai/workflows/code-quality/code-quality-fix.md +34 -21
- package/skills/massa-ai/workflows/debug.md +42 -15
- package/skills/massa-ai/workflows/exploration.md +1 -16
- package/skills/massa-ai/workflows/feature.md +24 -4
- package/skills/massa-ai/workflows/general.md +26 -4
- package/skills/massa-ai/workflows/implementation/implementation-fix.md +23 -16
- package/skills/massa-ai/workflows/maestro/maestro-fix.md +23 -4
- package/skills/massa-ai/workflows/mobile-figma/mobile-figma-fix.md +49 -16
- package/skills/massa-ai/workflows/refactor.md +32 -3
- package/skills/massa-ai/workflows/requirements/requirements-fix.md +29 -17
- package/skills/massa-ai/workflows/security/security-fix.md +28 -17
- package/skills/massa-ai/workflows/spec-driven.md +4 -21
- package/skills/massa-ai/workflows/tests/tests-fix.md +31 -17
- package/skills/massa-ai/references/spec-driven/brownfield-mapping.md +0 -16
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* check_fix_closure.ts - deterministic gate: a `*-fix` workflow's Fix Closure
|
|
4
|
+
* Report is committed, complete, and evidence-backed before Propose/Evidence
|
|
5
|
+
* Gate (the fix-family analogue of check_specs_delivered.ts).
|
|
6
|
+
*
|
|
7
|
+
* Three conjunctive checks against the closure contract in
|
|
8
|
+
* `references/audit-report-io.md` (Fix Closure Report Contract):
|
|
9
|
+
*
|
|
10
|
+
* 1. The closure file exists, is tracked on HEAD, and is porcelain-clean
|
|
11
|
+
* (not modified-but-uncommitted).
|
|
12
|
+
* 2. Every finding ID selected from the source audit report has a Closure
|
|
13
|
+
* Matrix row with a terminal status (`fixed|blocked|deferred|skipped`).
|
|
14
|
+
* The selected set is every finding in the source report, or the subset
|
|
15
|
+
* named via --findings. The source report path comes from the closure
|
|
16
|
+
* file's `Source Report:` metadata line, or --report.
|
|
17
|
+
* 3. No `fixed` row carries a placeholder Command/Artifact or Result cell
|
|
18
|
+
* (empty, `<template>`, `TBD`, `-`, or `n/a`).
|
|
19
|
+
*
|
|
20
|
+
* Reuses the `### <PREFIX>-N:` finding parser exported by
|
|
21
|
+
* validate_audit_report.ts, so the two scripts cannot drift on ID shape.
|
|
22
|
+
* Bun builtins only. Run from the project root, or pass --root.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* bun skills/massa-ai/scripts/check_fix_closure.ts <closure.md> --family <family>
|
|
26
|
+
* [--report <report.md>] [--findings ID,ID,...] [--root DIR]
|
|
27
|
+
*
|
|
28
|
+
* Exit codes: 0 pass, 1 closure incomplete/dirty/placeholder (reasons named),
|
|
29
|
+
* 2 usage/git error.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
33
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
34
|
+
|
|
35
|
+
import { FAMILIES, parseFindings, splitLines } from "./validate_audit_report.ts";
|
|
36
|
+
|
|
37
|
+
const TERMINAL_STATUSES = new Set(["fixed", "blocked", "deferred", "skipped"]);
|
|
38
|
+
const PLACEHOLDER_CELL_RE = /^(<.*>|TBD|-|n\/a)?$/i;
|
|
39
|
+
|
|
40
|
+
function runGit(args: string[], root: string): { exitCode: number; stdout: string; stderr: string } {
|
|
41
|
+
try {
|
|
42
|
+
const proc = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
|
|
43
|
+
return { exitCode: proc.exitCode ?? -1, stdout: proc.stdout.toString(), stderr: proc.stderr.toString() };
|
|
44
|
+
} catch {
|
|
45
|
+
console.error("check_fix_closure: git not found on PATH");
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ClosureRow {
|
|
51
|
+
findingId: string;
|
|
52
|
+
status: string;
|
|
53
|
+
commandArtifact: string;
|
|
54
|
+
result: string;
|
|
55
|
+
line: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Parse `Source Report:` and `Finding Selector:` metadata lines. */
|
|
59
|
+
function parseClosureMetadata(lines: string[]): Map<string, string> {
|
|
60
|
+
const fields = new Map<string, string>();
|
|
61
|
+
const lineRe = /^([A-Za-z][A-Za-z ./]*?):\s*(.*)$/;
|
|
62
|
+
for (const raw of lines) {
|
|
63
|
+
const line = raw.trim();
|
|
64
|
+
if (/^##\s+\S/.test(line)) break;
|
|
65
|
+
const m = lineRe.exec(line);
|
|
66
|
+
if (m) fields.set(m[1]!.trim(), m[2]!.trim());
|
|
67
|
+
}
|
|
68
|
+
return fields;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Split a markdown table row into trimmed cells (drops leading/trailing empties). */
|
|
72
|
+
function tableCells(line: string): string[] {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (!trimmed.startsWith("|")) return [];
|
|
75
|
+
const cells = trimmed.split("|").map((c) => c.trim());
|
|
76
|
+
// First and last entries are the empty strings outside the outer pipes.
|
|
77
|
+
return cells.slice(1, cells.length - 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Rows of the `## Closure Matrix` table, resolved by header-name lookup. */
|
|
81
|
+
function parseClosureMatrix(lines: string[], errors: string[]): ClosureRow[] {
|
|
82
|
+
let inMatrix = false;
|
|
83
|
+
let header: string[] | null = null;
|
|
84
|
+
const rows: ClosureRow[] = [];
|
|
85
|
+
for (let i = 0; i < lines.length; i++) {
|
|
86
|
+
const line = lines[i]!;
|
|
87
|
+
if (/^##\s+Closure Matrix\s*$/.test(line.trim())) {
|
|
88
|
+
inMatrix = true;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (inMatrix && /^##\s+\S/.test(line)) break;
|
|
92
|
+
if (!inMatrix) continue;
|
|
93
|
+
const cells = tableCells(line);
|
|
94
|
+
if (cells.length === 0) continue;
|
|
95
|
+
if (header === null) {
|
|
96
|
+
header = cells;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (cells.every((c) => /^[-: ]*$/.test(c))) continue; // separator row
|
|
100
|
+
const col = (name: string): string => {
|
|
101
|
+
const idx = header!.findIndex((h) => h.toLowerCase() === name.toLowerCase());
|
|
102
|
+
return idx >= 0 && idx < cells.length ? cells[idx]! : "";
|
|
103
|
+
};
|
|
104
|
+
rows.push({
|
|
105
|
+
findingId: col("Finding ID"),
|
|
106
|
+
status: col("Status").toLowerCase(),
|
|
107
|
+
commandArtifact: col("Command/Artifact"),
|
|
108
|
+
result: col("Result"),
|
|
109
|
+
line: i + 1,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (header !== null) {
|
|
113
|
+
for (const required of ["Finding ID", "Status", "Command/Artifact", "Result"]) {
|
|
114
|
+
if (!header.some((h) => h.toLowerCase() === required.toLowerCase())) {
|
|
115
|
+
errors.push(`Closure Matrix header is missing the '${required}' column`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
errors.push("no '## Closure Matrix' table found in the closure report");
|
|
120
|
+
}
|
|
121
|
+
return rows;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface CheckResult {
|
|
125
|
+
errors: string[];
|
|
126
|
+
selected: string[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function check(
|
|
130
|
+
root: string,
|
|
131
|
+
closurePath: string,
|
|
132
|
+
family: string,
|
|
133
|
+
reportArg: string | null,
|
|
134
|
+
findingsArg: string[] | null,
|
|
135
|
+
): CheckResult {
|
|
136
|
+
const errors: string[] = [];
|
|
137
|
+
|
|
138
|
+
// 1. Committed and clean.
|
|
139
|
+
const relClosure = relative(root, resolve(closurePath)).split(sep).join("/");
|
|
140
|
+
const tracked = runGit(["ls-tree", "-r", "--name-only", "HEAD"], root);
|
|
141
|
+
if (tracked.exitCode !== 0) {
|
|
142
|
+
console.error(`check_fix_closure: git ls-tree failed: ${tracked.stderr.trim()}`);
|
|
143
|
+
process.exit(2);
|
|
144
|
+
}
|
|
145
|
+
if (!splitLines(tracked.stdout).includes(relClosure)) {
|
|
146
|
+
errors.push(`closure report not tracked on HEAD: ${relClosure}`);
|
|
147
|
+
}
|
|
148
|
+
const porcelain = runGit(["status", "--porcelain", "--", relClosure], root);
|
|
149
|
+
for (const ln of splitLines(porcelain.stdout).filter((l) => l.trim() !== "")) {
|
|
150
|
+
errors.push(`closure report uncommitted/modified: ${ln.trim()}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const closureText = readFileSync(closurePath, "utf-8");
|
|
154
|
+
const closureLines = splitLines(closureText);
|
|
155
|
+
const meta = parseClosureMetadata(closureLines);
|
|
156
|
+
|
|
157
|
+
// 2. Every selected source-report finding has a terminal-status row.
|
|
158
|
+
const reportPath = reportArg ?? meta.get("Source Report") ?? null;
|
|
159
|
+
const composite = family === "implementation";
|
|
160
|
+
let selected: string[] = [];
|
|
161
|
+
if (findingsArg !== null) {
|
|
162
|
+
selected = findingsArg;
|
|
163
|
+
} else if (reportPath !== null && reportPath !== "") {
|
|
164
|
+
const absReport = isAbsolute(reportPath) ? reportPath : join(root, reportPath);
|
|
165
|
+
if (!existsSync(absReport)) {
|
|
166
|
+
errors.push(`source report not found: ${reportPath}`);
|
|
167
|
+
} else {
|
|
168
|
+
const reportLines = splitLines(readFileSync(absReport, "utf-8"));
|
|
169
|
+
selected = parseFindings(reportLines, composite)
|
|
170
|
+
.filter((f) => f.num !== null)
|
|
171
|
+
.map((f) => f.raw);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
errors.push("no source report: closure has no 'Source Report:' line and no --report/--findings was given");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const rows = parseClosureMatrix(closureLines, errors);
|
|
178
|
+
const rowById = new Map<string, ClosureRow>();
|
|
179
|
+
for (const row of rows) {
|
|
180
|
+
if (row.findingId !== "") rowById.set(row.findingId, row);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const id of selected) {
|
|
184
|
+
const row = rowById.get(id);
|
|
185
|
+
if (row === undefined) {
|
|
186
|
+
errors.push(`selected finding has no Closure Matrix row: ${id}`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (!TERMINAL_STATUSES.has(row.status)) {
|
|
190
|
+
errors.push(
|
|
191
|
+
`L${row.line}: finding ${id} has non-terminal status '${row.status}' (expected fixed|blocked|deferred|skipped)`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 3. No fixed row with placeholder evidence.
|
|
197
|
+
for (const row of rows) {
|
|
198
|
+
if (row.status !== "fixed") continue;
|
|
199
|
+
if (PLACEHOLDER_CELL_RE.test(row.commandArtifact)) {
|
|
200
|
+
errors.push(`L${row.line}: fixed finding ${row.findingId} has a placeholder Command/Artifact cell`);
|
|
201
|
+
}
|
|
202
|
+
if (PLACEHOLDER_CELL_RE.test(row.result)) {
|
|
203
|
+
errors.push(`L${row.line}: fixed finding ${row.findingId} has a placeholder Result cell`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return { errors, selected };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const USAGE =
|
|
211
|
+
"usage: check_fix_closure.ts [-h] --family NAME [--report PATH] [--findings IDS] [--root ROOT] closure.md";
|
|
212
|
+
const HELP = `${USAGE}
|
|
213
|
+
|
|
214
|
+
Gate: the Fix Closure Report is committed, complete, and evidence-backed
|
|
215
|
+
before Propose/Evidence Gate (references/audit-report-io.md, Fix Closure
|
|
216
|
+
Report Contract).
|
|
217
|
+
|
|
218
|
+
positional arguments:
|
|
219
|
+
closure.md Path to the Fix Closure Report markdown file
|
|
220
|
+
|
|
221
|
+
options:
|
|
222
|
+
-h, --help show this help message and exit
|
|
223
|
+
--family NAME One of: ${Object.keys(FAMILIES).join(", ")}
|
|
224
|
+
--report PATH Source audit report (default: the closure's 'Source Report:' line)
|
|
225
|
+
--findings IDS Comma-separated finding IDs (default: every finding in the source report)
|
|
226
|
+
--root ROOT Project root (default: current dir)`;
|
|
227
|
+
|
|
228
|
+
interface Args {
|
|
229
|
+
closure: string;
|
|
230
|
+
family: string;
|
|
231
|
+
report: string | null;
|
|
232
|
+
findings: string[] | null;
|
|
233
|
+
root: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function printUsageError(msg: string): void {
|
|
237
|
+
process.stderr.write(`${USAGE}\ncheck_fix_closure.ts: error: ${msg}\n`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function parseArgs(argv: string[]): Args | null {
|
|
241
|
+
let family: string | null = null;
|
|
242
|
+
let report: string | null = null;
|
|
243
|
+
let findings: string[] | null = null;
|
|
244
|
+
let root = ".";
|
|
245
|
+
const positionals: string[] = [];
|
|
246
|
+
const takeValue = (i: number, name: string): string | null => {
|
|
247
|
+
if (i + 1 >= argv.length) {
|
|
248
|
+
printUsageError(`argument ${name}: expected one argument`);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return argv[i + 1]!;
|
|
252
|
+
};
|
|
253
|
+
for (let i = 0; i < argv.length; i++) {
|
|
254
|
+
const a = argv[i]!;
|
|
255
|
+
if (a === "--family") {
|
|
256
|
+
const v = takeValue(i, "--family");
|
|
257
|
+
if (v === null) return null;
|
|
258
|
+
family = v;
|
|
259
|
+
i++;
|
|
260
|
+
} else if (a.startsWith("--family=")) {
|
|
261
|
+
family = a.slice("--family=".length);
|
|
262
|
+
} else if (a === "--report") {
|
|
263
|
+
const v = takeValue(i, "--report");
|
|
264
|
+
if (v === null) return null;
|
|
265
|
+
report = v;
|
|
266
|
+
i++;
|
|
267
|
+
} else if (a.startsWith("--report=")) {
|
|
268
|
+
report = a.slice("--report=".length);
|
|
269
|
+
} else if (a === "--findings") {
|
|
270
|
+
const v = takeValue(i, "--findings");
|
|
271
|
+
if (v === null) return null;
|
|
272
|
+
findings = v.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
273
|
+
i++;
|
|
274
|
+
} else if (a.startsWith("--findings=")) {
|
|
275
|
+
findings = a
|
|
276
|
+
.slice("--findings=".length)
|
|
277
|
+
.split(",")
|
|
278
|
+
.map((s) => s.trim())
|
|
279
|
+
.filter((s) => s !== "");
|
|
280
|
+
} else if (a === "--root") {
|
|
281
|
+
const v = takeValue(i, "--root");
|
|
282
|
+
if (v === null) return null;
|
|
283
|
+
root = v;
|
|
284
|
+
i++;
|
|
285
|
+
} else if (a.startsWith("--root=")) {
|
|
286
|
+
root = a.slice("--root=".length);
|
|
287
|
+
} else if (a === "-h" || a === "--help") {
|
|
288
|
+
console.log(HELP);
|
|
289
|
+
process.exit(0);
|
|
290
|
+
} else if (a.startsWith("-") && a !== "-") {
|
|
291
|
+
printUsageError(`unrecognized arguments: ${a}`);
|
|
292
|
+
return null;
|
|
293
|
+
} else {
|
|
294
|
+
positionals.push(a);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (positionals.length !== 1) {
|
|
298
|
+
printUsageError("exactly one closure.md path is required");
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
if (family === null) {
|
|
302
|
+
printUsageError("the following arguments are required: --family");
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
if (!(family in FAMILIES)) {
|
|
306
|
+
printUsageError(`unknown family '${family}' (known: ${Object.keys(FAMILIES).join(", ")})`);
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
return { closure: positionals[0]!, family, report, findings, root };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function main(argv: string[]): number {
|
|
313
|
+
const args = parseArgs(argv);
|
|
314
|
+
if (args === null) return 2;
|
|
315
|
+
const root = resolve(args.root);
|
|
316
|
+
const closurePath = isAbsolute(args.closure) ? args.closure : join(root, args.closure);
|
|
317
|
+
if (!existsSync(closurePath)) {
|
|
318
|
+
console.error(`check_fix_closure: closure report not found: ${args.closure}`);
|
|
319
|
+
return 2;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const { errors, selected } = check(root, closurePath, args.family, args.report, args.findings);
|
|
323
|
+
|
|
324
|
+
for (const e of errors) console.log(` ERROR ${e}`);
|
|
325
|
+
console.log(`\ncheck_fix_closure: checked ${selected.length} selected finding(s):`);
|
|
326
|
+
for (const id of selected) console.log(` - ${id}`);
|
|
327
|
+
console.log(`check_fix_closure: ${errors.length} error(s)`);
|
|
328
|
+
return errors.length ? 1 : 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (import.meta.main) {
|
|
332
|
+
process.exit(main(process.argv.slice(2)));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export { check, parseClosureMatrix, tableCells };
|
|
@@ -26,7 +26,13 @@
|
|
|
26
26
|
* --root.
|
|
27
27
|
*
|
|
28
28
|
* Usage:
|
|
29
|
-
* bun skills/massa-ai/scripts/check_specs_delivered.ts <feature> [--root DIR]
|
|
29
|
+
* bun skills/massa-ai/scripts/check_specs_delivered.ts <feature> [--root DIR] [--kind KIND]
|
|
30
|
+
*
|
|
31
|
+
* --kind selects the artifact shape gated (default `feature`, byte-identical
|
|
32
|
+
* to the historical behavior): `quick` gates .specs/quick/<slug>/
|
|
33
|
+
* TASK.md+SUMMARY.md, `debug` gates .specs/debug/<slug>/REPORT.md, `refactor`
|
|
34
|
+
* gates .specs/refactors/<slug>/CHARACTERIZATION.md (+ present PLAN/SENSOR).
|
|
35
|
+
* Non-feature kinds do not require the project STATE files.
|
|
30
36
|
*
|
|
31
37
|
* Exit codes: 0 all required paths clean + tracked, 1 a required path is dirty,
|
|
32
38
|
* untracked, or not tracked on HEAD (paths named), 2 usage/git error.
|
|
@@ -46,6 +52,25 @@ const STATE_FILES = [
|
|
|
46
52
|
join(".specs", "project", "FEATURES.json"),
|
|
47
53
|
];
|
|
48
54
|
|
|
55
|
+
interface KindConfig {
|
|
56
|
+
/** Slug parent dir under .specs/, e.g. ["features"]. */
|
|
57
|
+
dir: string[];
|
|
58
|
+
required: string[];
|
|
59
|
+
optional: string[];
|
|
60
|
+
/** Whether the project STATE files are part of the gate. */
|
|
61
|
+
stateFiles: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// `feature` is the default and must stay byte-identical to the historical
|
|
65
|
+
// behavior (frozen pyts-golden corpus). The non-feature kinds gate the light
|
|
66
|
+
// workflows' durable artifacts and do NOT require the project STATE files.
|
|
67
|
+
const KINDS: Record<string, KindConfig> = {
|
|
68
|
+
feature: { dir: ["features"], required: FEATURE_REQUIRED, optional: FEATURE_OPTIONAL, stateFiles: true },
|
|
69
|
+
quick: { dir: ["quick"], required: ["TASK.md", "SUMMARY.md"], optional: [], stateFiles: false },
|
|
70
|
+
debug: { dir: ["debug"], required: ["REPORT.md"], optional: [], stateFiles: false },
|
|
71
|
+
refactor: { dir: ["refactors"], required: ["CHARACTERIZATION.md"], optional: ["PLAN.md", "SENSOR.md"], stateFiles: false },
|
|
72
|
+
};
|
|
73
|
+
|
|
49
74
|
/** Mirrors Python's str.splitlines(): universal newline split, no trailing empty element. */
|
|
50
75
|
function splitLines(text: string): string[] {
|
|
51
76
|
if (text === "") return [];
|
|
@@ -92,39 +117,41 @@ function trackedOnHead(root: string): Set<string> {
|
|
|
92
117
|
return new Set(splitLines(out));
|
|
93
118
|
}
|
|
94
119
|
|
|
95
|
-
function resolveFeatureDir(root: string, feature: string): string {
|
|
120
|
+
function resolveFeatureDir(root: string, feature: string, kind: KindConfig): string {
|
|
96
121
|
if (isAbsolute(feature) || feature.includes(sep)) {
|
|
97
122
|
return resolve(feature);
|
|
98
123
|
}
|
|
99
|
-
return join(root, ".specs",
|
|
124
|
+
return join(root, ".specs", ...kind.dir, feature);
|
|
100
125
|
}
|
|
101
126
|
|
|
102
127
|
/** Repo-root-relative, '/'-separated paths this feature must have tracked. */
|
|
103
|
-
function requiredPaths(root: string, feature: string): string[] {
|
|
104
|
-
const fdir = resolveFeatureDir(root, feature);
|
|
128
|
+
function requiredPaths(root: string, feature: string, kind: KindConfig): string[] {
|
|
129
|
+
const fdir = resolveFeatureDir(root, feature, kind);
|
|
105
130
|
const fdirRel = relative(root, fdir);
|
|
106
|
-
const paths =
|
|
131
|
+
const paths = kind.required.map((name) => join(fdirRel, name));
|
|
107
132
|
if (existsSync(fdir) && statSync(fdir).isDirectory()) {
|
|
108
|
-
for (const name of
|
|
133
|
+
for (const name of kind.optional) {
|
|
109
134
|
const candidate = join(fdir, name);
|
|
110
135
|
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
|
111
136
|
paths.push(join(fdirRel, name));
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
139
|
}
|
|
115
|
-
|
|
140
|
+
if (kind.stateFiles) {
|
|
141
|
+
paths.push(...STATE_FILES);
|
|
142
|
+
}
|
|
116
143
|
return paths.map((p) => p.split(sep).join("/"));
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
/** Return { errors, checked }. errors empty = pass. */
|
|
120
|
-
function check(root: string, feature: string): { errors: string[]; checked: string[] } {
|
|
147
|
+
function check(root: string, feature: string, kind: KindConfig): { errors: string[]; checked: string[] } {
|
|
121
148
|
const errors: string[] = [];
|
|
122
149
|
|
|
123
150
|
for (const ln of porcelainDirtyPaths(root)) {
|
|
124
151
|
errors.push(`uncommitted/untracked under .specs/: ${ln.trim()}`);
|
|
125
152
|
}
|
|
126
153
|
|
|
127
|
-
const paths = requiredPaths(root, feature);
|
|
154
|
+
const paths = requiredPaths(root, feature, kind);
|
|
128
155
|
const tracked = trackedOnHead(root);
|
|
129
156
|
for (const p of paths) {
|
|
130
157
|
if (!tracked.has(p)) {
|
|
@@ -135,21 +162,27 @@ function check(root: string, feature: string): { errors: string[]; checked: stri
|
|
|
135
162
|
return { errors, checked: paths };
|
|
136
163
|
}
|
|
137
164
|
|
|
138
|
-
const USAGE = "usage: check_specs_delivered.ts [-h] [--root ROOT] feature";
|
|
165
|
+
const USAGE = "usage: check_specs_delivered.ts [-h] [--root ROOT] [--kind KIND] feature";
|
|
139
166
|
const HELP = `${USAGE}
|
|
140
167
|
|
|
141
168
|
Gate: .specs/ artifacts committed on the branch before PR (GATE-02).
|
|
142
169
|
|
|
143
170
|
positional arguments:
|
|
144
|
-
feature
|
|
171
|
+
feature Slug under the kind's .specs/ directory, or a direct path
|
|
145
172
|
|
|
146
173
|
options:
|
|
147
174
|
-h, --help show this help message and exit
|
|
148
|
-
--root ROOT Project root containing .specs/ (default: current dir)
|
|
175
|
+
--root ROOT Project root containing .specs/ (default: current dir)
|
|
176
|
+
--kind KIND One of: ${Object.keys(KINDS).join(", ")} (default: feature)
|
|
177
|
+
feature .specs/features/<slug>/ spec.md (+ present phase files) + project STATE files
|
|
178
|
+
quick .specs/quick/<slug>/ TASK.md + SUMMARY.md
|
|
179
|
+
debug .specs/debug/<slug>/ REPORT.md
|
|
180
|
+
refactor .specs/refactors/<slug>/ CHARACTERIZATION.md (+ present PLAN.md/SENSOR.md)`;
|
|
149
181
|
|
|
150
182
|
interface Args {
|
|
151
183
|
feature: string;
|
|
152
184
|
root: string;
|
|
185
|
+
kind: string;
|
|
153
186
|
}
|
|
154
187
|
|
|
155
188
|
function printUsageError(msg: string): void {
|
|
@@ -158,6 +191,7 @@ function printUsageError(msg: string): void {
|
|
|
158
191
|
|
|
159
192
|
function parseArgs(argv: string[]): Args | null {
|
|
160
193
|
let root = ".";
|
|
194
|
+
let kind = "feature";
|
|
161
195
|
const positionals: string[] = [];
|
|
162
196
|
for (let i = 0; i < argv.length; i++) {
|
|
163
197
|
const a = argv[i]!;
|
|
@@ -169,6 +203,14 @@ function parseArgs(argv: string[]): Args | null {
|
|
|
169
203
|
root = argv[++i]!;
|
|
170
204
|
} else if (a.startsWith("--root=")) {
|
|
171
205
|
root = a.slice("--root=".length);
|
|
206
|
+
} else if (a === "--kind") {
|
|
207
|
+
if (i + 1 >= argv.length) {
|
|
208
|
+
printUsageError("argument --kind: expected one argument");
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
kind = argv[++i]!;
|
|
212
|
+
} else if (a.startsWith("--kind=")) {
|
|
213
|
+
kind = a.slice("--kind=".length);
|
|
172
214
|
} else if (a === "-h" || a === "--help") {
|
|
173
215
|
console.log(HELP);
|
|
174
216
|
process.exit(0);
|
|
@@ -187,7 +229,11 @@ function parseArgs(argv: string[]): Args | null {
|
|
|
187
229
|
printUsageError(`unrecognized arguments: ${positionals.slice(1).join(" ")}`);
|
|
188
230
|
return null;
|
|
189
231
|
}
|
|
190
|
-
|
|
232
|
+
if (!(kind in KINDS)) {
|
|
233
|
+
printUsageError(`argument --kind: invalid choice: '${kind}' (choose from ${Object.keys(KINDS).join(", ")})`);
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
return { feature: positionals[0]!, root, kind };
|
|
191
237
|
}
|
|
192
238
|
|
|
193
239
|
function main(argv: string[]): number {
|
|
@@ -195,7 +241,7 @@ function main(argv: string[]): number {
|
|
|
195
241
|
if (args === null) return 2;
|
|
196
242
|
const root = resolve(args.root);
|
|
197
243
|
|
|
198
|
-
const { errors, checked } = check(root, args.feature);
|
|
244
|
+
const { errors, checked } = check(root, args.feature, KINDS[args.kind]!);
|
|
199
245
|
|
|
200
246
|
for (const e of errors) console.log(` ERROR ${e}`);
|
|
201
247
|
console.log(`\ncheck_specs_delivered: checked ${checked.length} path(s):`);
|
|
@@ -3,7 +3,7 @@ name: architecture-fix
|
|
|
3
3
|
description: "Executes fixes from a saved architecture audit report; not for findings-only review or broad new design work with missing requirements."
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.2.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
### Architecture Fix
|
|
@@ -12,7 +12,7 @@ Execute fixes from an architecture audit markdown report only.
|
|
|
12
12
|
|
|
13
13
|
Load `references/project-context.md` (intake sweep) before the first substantive read.
|
|
14
14
|
|
|
15
|
-
Before the first repository mutation, load `references/implementation-delivery.md` (delivery chain: worktree, atomic commits, PR, CI watch, merge gate) and `references/code-annotation.md` (doc blocks, rationale, test coverage). After two consecutive failed fixes on one symptom, stop editing and load `references/root-cause-scripts.md`.
|
|
15
|
+
Before the first repository mutation, load `references/implementation-delivery.md` (delivery chain: worktree, atomic commits, PR, CI watch, merge gate) — its Stage 3 delivery-authorization scope covers one go-ahead through PR creation; force-push/deploy/merge stay separately gated — and `references/code-annotation.md` (doc blocks, rationale, test coverage). After two consecutive failed fixes on one symptom, stop editing and load `references/root-cause-scripts.md`.
|
|
16
16
|
|
|
17
17
|
**Isolation Gate — before the first file edit:** execute `references/implementation-delivery.md` Stage 0–1 now (fetch base, create the worktree + branch, work inside it) and record the worktree path + branch — or one of Stage 1's two legal skip reasons, verbatim — before any repository mutation.
|
|
18
18
|
|
|
@@ -30,6 +30,9 @@ Not for findings-only architecture review — route to `workflows/architecture/a
|
|
|
30
30
|
- `references/verification-ladder.md` before non-trivial edits
|
|
31
31
|
- `references/context-firewall.md` before inspecting large diffs, dependency graphs, generated reports, or broad search output
|
|
32
32
|
- `references/agent-orchestration.md` only for large/high-risk findings, disjoint implementation slices, or independent verification
|
|
33
|
+
- `references/discrimination-sensor.md` before marking any Standard+/Spec-driven-sized or high/critical-severity finding `fixed`
|
|
34
|
+
- `references/knowledge-verification-chain.md` when a fix leans on an external library or API — the trigger is step 8's ports/adapters clause on "external dependency pressure"
|
|
35
|
+
- `references/brownfield-mapping.md` (Minimum Bar only, Standard+ findings) when step 3's recall returns no hit for the target and no gate command is derivable from the report's evidence; its CONCERNS.md is satisfied by citation when the report's lens evidence already maps the risk surface — cite that evidence rather than deriving CONCERNS.md fresh
|
|
33
36
|
3. `recall` -> load ADRs, known boundaries, coupling patterns, accepted exceptions, rejected refactors, verification recipes, and project constraints for the report target.
|
|
34
37
|
4. Select the architecture audit report with execution focus:
|
|
35
38
|
- Establish the report selector, target focus, and optional finding selector before selecting a report. Target focus can be a module, boundary, flow, files/globs, branch comparison, commit range, symbol/class/function, or explicit whole-repo target.
|
|
@@ -65,7 +68,7 @@ Not for findings-only architecture review — route to `workflows/architecture/a
|
|
|
65
68
|
- Characterize current behavior before moving code with tests, static checks, import graphs, source inspection, or artifact snapshots.
|
|
66
69
|
- Keep public contracts stable unless the audit finding explicitly requires contract change.
|
|
67
70
|
- Update tests, docs, and imports only where required by the architecture fix.
|
|
68
|
-
10. Use agent orchestration only when it improves signal. Dispatch per `references/agent-orchestration.md`:
|
|
71
|
+
10. Use agent orchestration only when it improves signal — except the verifier dispatch below, which is tier-gated mandatory rather than discretionary, carved out under `references/agent-orchestration.md`'s Independent Verification Exception. Dispatch per `references/agent-orchestration.md`:
|
|
69
72
|
|
|
70
73
|
> **Dispatch: `massa-ai-builder`** (role: `builder`) — charter `skills/agents/builder/SKILL.md`
|
|
71
74
|
> - trigger: large/high-risk finding, disjoint implementation slice, or explicit subagent request
|
|
@@ -78,33 +81,37 @@ Not for findings-only architecture review — route to `workflows/architecture/a
|
|
|
78
81
|
> - memory: suggest-only; main agent persists reusable architecture patterns
|
|
79
82
|
> - persona: optional — the active route's cataloged id only, never the persona prompt, passed as advisory framing only — it never overrides the agent's charter Restrictions, scope, or permissions; omit when no persona is routed
|
|
80
83
|
|
|
81
|
-
> **Dispatch: `massa-ai-verification-agent`** (role: `verification-agent`) — charter `skills/agents/verification-agent/SKILL.md`
|
|
82
|
-
> - trigger: independent verification of a high-risk or multi-file architecture fix
|
|
83
|
-
> - scope: the fixed finding's dependency direction, tests, imports, and report claim closure
|
|
84
|
-
> - permissions: read-only
|
|
85
|
-
> - inputs: the finding, the applied fix, the verification suggestion, and validation assets
|
|
86
|
-
> - sensors: deterministic command (targeted tests, import-cycle check, dependency-direction check) and report claim closure check
|
|
87
|
-
> - output: confirmed/disproven closure verdict with evidence
|
|
88
|
-
> - firewall: raw test output/logs summarized
|
|
89
|
-
> - memory: suggest-only; main agent persists reusable verification recipes
|
|
90
|
-
> - persona: optional — the active route's cataloged id only, never the persona prompt, passed as advisory framing only — it never overrides the agent's charter Restrictions, scope, or permissions; omit when no persona is routed
|
|
91
|
-
|
|
92
84
|
> **Dispatch: `massa-ai-reviewer`** (role: `reviewer`) — charter `skills/agents/reviewer/SKILL.md`
|
|
93
|
-
> - trigger: implementation complete, before the verification gate — never optional
|
|
85
|
+
> - trigger: implementation of the architecture finding complete, before the verification gate — never optional
|
|
94
86
|
> - scope: the fix's diff surface and its task/AC context
|
|
95
87
|
> - permissions: read-only
|
|
96
|
-
> - inputs: diff, acceptance context, recalled code-quality conventions
|
|
88
|
+
> - inputs: diff, ARCH acceptance context, recalled code-quality conventions
|
|
97
89
|
> - sensors: bugs, regressions, missing edge cases, smells introduced by the diff
|
|
98
|
-
> - output: ranked findings, blocking vs advisory; blocking findings become fix items before verification runs
|
|
90
|
+
> - output: ranked findings, blocking vs advisory; blocking findings become architecture fix items before verification runs
|
|
99
91
|
> - firewall: summarized findings only, never raw diff dumps
|
|
100
|
-
> - memory: suggest-only; main agent persists
|
|
92
|
+
> - memory: suggest-only; main agent persists review outcomes for the architecture fix
|
|
101
93
|
> - fallback: if the subagent is unavailable, run a standalone fresh-eyes review against this output contract and record the skipped-delegation reason
|
|
102
94
|
> - persona: optional — the active route's cataloged id only, never the persona prompt, passed as advisory framing only — it never overrides the agent's charter Restrictions, scope, or permissions; omit when no persona is routed
|
|
103
95
|
|
|
96
|
+
> **Dispatch: `massa-ai-verification-agent`** (role: `verification-agent`) — charter `skills/agents/verification-agent/SKILL.md`
|
|
97
|
+
> - trigger: mandatory at Standard+/Spec-driven-sized findings or high/critical severity, per the Independent Verification Mandate in `references/verification-ladder.md`'s Mandatory Verification Fix Gate; Quick-tier findings take the fallback below instead
|
|
98
|
+
> - scope: the fixed finding's dependency direction, seam/adapter shape, tests, imports, and report claim closure
|
|
99
|
+
> - permissions: read-only
|
|
100
|
+
> - inputs: the finding, the applied fix, the verification suggestion, dependency-direction/import-cycle evidence, and validation assets
|
|
101
|
+
> - sensors: deterministic command (targeted tests, import-cycle check, dependency-direction check), report claim closure check; discrimination sensor per `references/discrimination-sensor.md` (mutate the fixed dependency direction, seam, or boundary contract in a scratch worktree)
|
|
102
|
+
> - output: confirmed/disproven closure verdict with evidence, feeding the Fix Closure Report's Independent Verifier column
|
|
103
|
+
> - firewall: raw test output/logs summarized
|
|
104
|
+
> - memory: suggest-only; main agent persists architecture verification outcomes
|
|
105
|
+
> - fallback: if the subagent is unavailable, run a standalone fresh-eyes re-check of the architecture closure evidence against this output contract and record the skipped-delegation reason
|
|
106
|
+
> - persona: optional — the active route's cataloged id only, never the persona prompt, passed as advisory framing only — it never overrides the agent's charter Restrictions, scope, or permissions; omit when no persona is routed
|
|
107
|
+
|
|
104
108
|
11. Verify each completed finding:
|
|
105
109
|
- If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
|
|
106
110
|
`bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
|
|
107
111
|
- Apply the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check for each selected finding or coherent group.
|
|
112
|
+
- Run the sensors at the mandate's own tier gate: dispatch the verification-agent block above at Standard+/Spec-driven size or high/critical severity; a Quick-tier finding runs its fallback fresh-eyes self-check instead — the hop is skippable, the check never is.
|
|
113
|
+
- A surviving mutant on the discrimination sensor marks the finding's Closure Matrix row `blocked` and records a `surviving_mutant` signal via `references/lessons.md`.
|
|
114
|
+
- The fix→re-verify loop is capped per `references/verification-ladder.md`'s Bounded Fix→Re-verify Loop; exhausting it also marks the row `blocked`. That cap counts re-verify cycles across the whole finding and is a different counter from the two-consecutive-failed-fixes breaker in this file's preamble, which fires inside a single edit iteration.
|
|
108
115
|
- A finding cannot be marked `fixed` when a target-relevant command or artifact check exists but was not attempted; if verification cannot run, mark it `blocked`, `deferred`, or `skipped` with an allowed skipped-check reason.
|
|
109
116
|
- Run the report's verification suggestion when available.
|
|
110
117
|
- Add static checks for dependency direction/import cycles when feasible.
|
|
@@ -116,7 +123,8 @@ Not for findings-only architecture review — route to `workflows/architecture/a
|
|
|
116
123
|
12. At completion, persist only durable knowledge:
|
|
117
124
|
- Accepted architecture constraints, new seams, rejected broad refactors, reusable dependency checks, or recurring coupling patterns after scoring with the Importance Calibration System.
|
|
118
125
|
- Use required tags: `project:<projectId>`, `session:<workflowSessionId>`, `workflow:architecture-fix`, `entity:<entity>`, and one `memory:<tier>` tag.
|
|
119
|
-
13.
|
|
126
|
+
13. Write the Fix Closure Report per `references/audit-report-io.md`'s Fix Closure Report Contract, at `audits/architecture/<YYYY-MM-DD architecture-fix-closure>.md`, sibling of the consumed audit report. Then run `bun skills/massa-ai/scripts/check_fix_closure.ts <closure.md> --family architecture` before the Propose/Evidence Gate step below; a non-zero exit blocks completion. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
|
|
127
|
+
14. Complete the Evidence Gate from `references/evidence-gate.md`.
|
|
120
128
|
|
|
121
129
|
## Examples
|
|
122
130
|
|
|
@@ -133,3 +141,5 @@ User asks: "Fix finding ARCH-2 from audits/architecture/2026-06-06 architecture-
|
|
|
133
141
|
1. Read the specified report and only execute `ARCH-2`.
|
|
134
142
|
2. Preserve unaffected architecture findings for later.
|
|
135
143
|
3. Report evidence for `ARCH-2` closure and residual risks.
|
|
144
|
+
|
|
145
|
+
<!-- validator anchors: Stage 3 delivery-authorization scope | Independent Verification Exception | surviving_mutant | Bounded Fix→Re-verify Loop | Fix Closure Report Contract | CONCERNS.md is satisfied by citation | graceful degradation preserved -->
|