@massa-ai/codex-plugin 1.34.0 → 1.35.1

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.
Files changed (39) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/skills/massa-ai/references/agent-orchestration.md +4 -0
  4. package/skills/massa-ai/references/artifact-persistence.md +46 -0
  5. package/skills/massa-ai/references/audit-report-io.md +50 -0
  6. package/skills/massa-ai/references/brownfield-mapping.md +22 -0
  7. package/skills/massa-ai/references/codebase-investigation.md +1 -1
  8. package/skills/massa-ai/references/discrimination-sensor.md +42 -0
  9. package/skills/massa-ai/references/implementation-delivery.md +17 -3
  10. package/skills/massa-ai/references/knowledge-verification-chain.md +25 -0
  11. package/skills/massa-ai/references/maestro/cloud.md +2 -0
  12. package/skills/massa-ai/references/maestro/fact-ledger.md +2 -0
  13. package/skills/massa-ai/references/maestro/patterns.md +7 -1
  14. package/skills/massa-ai/references/mobile-figma-matcher/core.md +13 -0
  15. package/skills/massa-ai/references/spec-driven/artifact-store.md +2 -32
  16. package/skills/massa-ai/references/spec-driven/design.md +1 -1
  17. package/skills/massa-ai/references/spec-driven/validate.md +9 -31
  18. package/skills/massa-ai/references/verification-ladder.md +16 -0
  19. package/skills/massa-ai/scripts/check_fix_closure.ts +335 -0
  20. package/skills/massa-ai/scripts/check_specs_delivered.ts +61 -15
  21. package/skills/massa-ai/scripts/validate_audit_report.ts +2 -1
  22. package/skills/massa-ai/workflows/architecture/architecture-fix.md +31 -19
  23. package/skills/massa-ai/workflows/bugs/bugs-fix.md +29 -16
  24. package/skills/massa-ai/workflows/code-quality/code-quality-fix.md +36 -21
  25. package/skills/massa-ai/workflows/debug.md +44 -15
  26. package/skills/massa-ai/workflows/design.md +2 -0
  27. package/skills/massa-ai/workflows/exploration.md +1 -16
  28. package/skills/massa-ai/workflows/feature.md +26 -4
  29. package/skills/massa-ai/workflows/general.md +28 -4
  30. package/skills/massa-ai/workflows/implementation/implementation-fix.md +25 -16
  31. package/skills/massa-ai/workflows/maestro/maestro-fix.md +25 -4
  32. package/skills/massa-ai/workflows/maestro/maestro.md +2 -0
  33. package/skills/massa-ai/workflows/mobile-figma/mobile-figma-fix.md +51 -16
  34. package/skills/massa-ai/workflows/refactor.md +34 -3
  35. package/skills/massa-ai/workflows/requirements/requirements-fix.md +31 -17
  36. package/skills/massa-ai/workflows/security/security-fix.md +30 -17
  37. package/skills/massa-ai/workflows/spec-driven.md +6 -21
  38. package/skills/massa-ai/workflows/tests/tests-fix.md +33 -17
  39. 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", "features", feature);
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 = FEATURE_REQUIRED.map((name) => join(fdirRel, name));
131
+ const paths = kind.required.map((name) => join(fdirRel, name));
107
132
  if (existsSync(fdir) && statSync(fdir).isDirectory()) {
108
- for (const name of FEATURE_OPTIONAL) {
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
- paths.push(...STATE_FILES);
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 Feature slug under <root>/.specs/features/, or a direct path
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
- return { feature: positionals[0]!, root };
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):`);
@@ -379,4 +379,5 @@ if (import.meta.main) {
379
379
  process.exit(main(process.argv.slice(2)));
380
380
  }
381
381
 
382
- export { check, FAMILIES, AREA_PREFIX };
382
+ export { check, FAMILIES, AREA_PREFIX, parseFindings, splitLines };
383
+ export type { RawFinding };
@@ -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.1.0"
6
+ version: "1.2.0"
7
7
  ---
8
8
 
9
9
  ### Architecture Fix
@@ -12,7 +12,9 @@ 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
+
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.
16
18
 
17
19
  Not for findings-only architecture review — route to `workflows/architecture/architecture-audit.md`. Not for broad new design work with missing requirements — route to `workflows/spec-driven.md`.
18
20
 
@@ -28,6 +30,9 @@ Not for findings-only architecture review — route to `workflows/architecture/a
28
30
  - `references/verification-ladder.md` before non-trivial edits
29
31
  - `references/context-firewall.md` before inspecting large diffs, dependency graphs, generated reports, or broad search output
30
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
31
36
  3. `recall` -> load ADRs, known boundaries, coupling patterns, accepted exceptions, rejected refactors, verification recipes, and project constraints for the report target.
32
37
  4. Select the architecture audit report with execution focus:
33
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.
@@ -63,7 +68,7 @@ Not for findings-only architecture review — route to `workflows/architecture/a
63
68
  - Characterize current behavior before moving code with tests, static checks, import graphs, source inspection, or artifact snapshots.
64
69
  - Keep public contracts stable unless the audit finding explicitly requires contract change.
65
70
  - Update tests, docs, and imports only where required by the architecture fix.
66
- 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`:
67
72
 
68
73
  > **Dispatch: `massa-ai-builder`** (role: `builder`) — charter `skills/agents/builder/SKILL.md`
69
74
  > - trigger: large/high-risk finding, disjoint implementation slice, or explicit subagent request
@@ -76,33 +81,37 @@ Not for findings-only architecture review — route to `workflows/architecture/a
76
81
  > - memory: suggest-only; main agent persists reusable architecture patterns
77
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
78
83
 
79
- > **Dispatch: `massa-ai-verification-agent`** (role: `verification-agent`) — charter `skills/agents/verification-agent/SKILL.md`
80
- > - trigger: independent verification of a high-risk or multi-file architecture fix
81
- > - scope: the fixed finding's dependency direction, tests, imports, and report claim closure
82
- > - permissions: read-only
83
- > - inputs: the finding, the applied fix, the verification suggestion, and validation assets
84
- > - sensors: deterministic command (targeted tests, import-cycle check, dependency-direction check) and report claim closure check
85
- > - output: confirmed/disproven closure verdict with evidence
86
- > - firewall: raw test output/logs summarized
87
- > - memory: suggest-only; main agent persists reusable verification recipes
88
- > - 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
89
-
90
84
  > **Dispatch: `massa-ai-reviewer`** (role: `reviewer`) — charter `skills/agents/reviewer/SKILL.md`
91
- > - trigger: implementation complete, before the verification gate — never optional
85
+ > - trigger: implementation of the architecture finding complete, before the verification gate — never optional
92
86
  > - scope: the fix's diff surface and its task/AC context
93
87
  > - permissions: read-only
94
- > - inputs: diff, acceptance context, recalled code-quality conventions
88
+ > - inputs: diff, ARCH acceptance context, recalled code-quality conventions
95
89
  > - sensors: bugs, regressions, missing edge cases, smells introduced by the diff
96
- > - 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
97
91
  > - firewall: summarized findings only, never raw diff dumps
98
- > - memory: suggest-only; main agent persists
92
+ > - memory: suggest-only; main agent persists review outcomes for the architecture fix
99
93
  > - fallback: if the subagent is unavailable, run a standalone fresh-eyes review against this output contract and record the skipped-delegation reason
100
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
101
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
+
102
108
  11. Verify each completed finding:
103
109
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
104
110
  `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
105
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.
106
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.
107
116
  - Run the report's verification suggestion when available.
108
117
  - Add static checks for dependency direction/import cycles when feasible.
@@ -114,7 +123,8 @@ Not for findings-only architecture review — route to `workflows/architecture/a
114
123
  12. At completion, persist only durable knowledge:
115
124
  - Accepted architecture constraints, new seams, rejected broad refactors, reusable dependency checks, or recurring coupling patterns after scoring with the Importance Calibration System.
116
125
  - Use required tags: `project:<projectId>`, `session:<workflowSessionId>`, `workflow:architecture-fix`, `entity:<entity>`, and one `memory:<tier>` tag.
117
- 13. Complete the Evidence Gate from `references/evidence-gate.md`.
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`.
118
128
 
119
129
  ## Examples
120
130
 
@@ -131,3 +141,5 @@ User asks: "Fix finding ARCH-2 from audits/architecture/2026-06-06 architecture-
131
141
  1. Read the specified report and only execute `ARCH-2`.
132
142
  2. Preserve unaffected architecture findings for later.
133
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 -->