agy-squad 0.0.0-stage → 0.1.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.
Files changed (65) hide show
  1. package/.agents/skills/full-review/SKILL.md +89 -0
  2. package/.agents/skills/full-review/references/evidence-intake.md +146 -0
  3. package/.agents/skills/full-review/references/report-template.md +216 -0
  4. package/.agents/skills/full-review/references/review-roles.md +91 -0
  5. package/.agents/skills/pr-review/SKILL.md +60 -0
  6. package/.agents/skills/pr-review/references/pr-review-standard.md +163 -0
  7. package/.agents/skills/self-diagnosis/SKILL.md +63 -0
  8. package/.agents/skills/self-diagnosis/references/execution-and-teardown.md +71 -0
  9. package/.agents/skills/self-diagnosis/references/optimization-and-review-rubric.md +61 -0
  10. package/.agents/skills/self-diagnosis/resources/comparative-research-manifest.json +42 -0
  11. package/.agents/skills/self-diagnosis/resources/self-diagnosis-manifest.json +144 -0
  12. package/.agents/skills/self-diagnosis/scripts/diagnose.mjs +1971 -0
  13. package/.agents/skills/swarm-governance/SKILL.md +59 -0
  14. package/.agents/skills/swarm-governance/references/bundling-and-wire-format.md +88 -0
  15. package/.agents/skills/swarm-governance/references/intake-and-planning.md +120 -0
  16. package/.agents/skills/swarm-governance/references/large-swarms.md +39 -0
  17. package/CHANGELOG.md +16 -0
  18. package/LICENSE +21 -0
  19. package/README.md +156 -2
  20. package/bin/agy-squad.mjs +293 -0
  21. package/docs/assets/architecture-overview.svg +39 -0
  22. package/docs/assets/manifest-and-modes.svg +39 -0
  23. package/docs/assets/role-and-verdict-gate.svg +39 -0
  24. package/docs/assets/skill-role-workflows.svg +39 -0
  25. package/docs/assets/swarm-lifecycle.svg +39 -0
  26. package/docs/benchmarks.md +102 -0
  27. package/docs/concepts.md +81 -0
  28. package/docs/core-workflows.md +128 -0
  29. package/docs/getting-started.md +116 -0
  30. package/docs/index.md +47 -0
  31. package/docs/plans/LEDGER.md +24 -0
  32. package/docs/plans/comparative-harness-architecture-retest.md +1174 -0
  33. package/docs/plans/comparative-harness-architecture-teamwork.md +1170 -0
  34. package/docs/plans/comparative-harness-architecture.md +256 -0
  35. package/docs/plans/native-enforcement-ledger.md +126 -0
  36. package/docs/plans/pr3-option-b-fixes.md +803 -0
  37. package/docs/plans/wsl-cli-verification.md +55 -0
  38. package/docs/reviews/post-pr5-governance-review.md +221 -0
  39. package/docs/reviews/pr1-code-review-ledger.md +581 -0
  40. package/docs/reviews/pr1-production-readiness-review.md +764 -0
  41. package/docs/reviews/pr2-documentation-review-ledger.md +380 -0
  42. package/docs/reviews/pr3-squad-alignment-full-review.md +1021 -0
  43. package/docs/reviews/pr4-swarm-governance-pivot-review.md +593 -0
  44. package/docs/reviews/pr5-full-review-direction.md +330 -0
  45. package/docs/reviews/pr5-independent-review.md +266 -0
  46. package/docs/reviews/pr5-layered-swarm-review.md +513 -0
  47. package/docs/reviews/pr5-phase2-swarm-planner-review.md +470 -0
  48. package/docs/reviews/pr5-reconciled-review.md +573 -0
  49. package/docs/reviews/rebuild-wsl-dispatch-review-r2.md +427 -0
  50. package/docs/reviews/rebuild-wsl-dispatch-review.md +552 -0
  51. package/docs/roles-and-workflows.md +115 -0
  52. package/docs/stopping-agents.md +39 -0
  53. package/docs/tools.md +122 -0
  54. package/install.ps1 +5 -0
  55. package/install.sh +4 -0
  56. package/mcp/briefs.mjs +202 -0
  57. package/mcp/dispatch.mjs +280 -0
  58. package/mcp/handoffs.mjs +814 -0
  59. package/mcp/index.js +322 -0
  60. package/mcp/planner.mjs +755 -0
  61. package/mcp/verdicts.mjs +224 -0
  62. package/package.json +57 -4
  63. package/plugin.json +20 -0
  64. package/scripts/install-register.mjs +510 -0
  65. package/scripts/release-preflight.mjs +142 -0
@@ -0,0 +1,1971 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { randomUUID } from "node:crypto";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { performance } from "node:perf_hooks";
7
+ import { fileURLToPath } from "node:url";
8
+ import { buildSubagentSpec } from "../../../../mcp/briefs.mjs";
9
+ import { handleDispatchSquad } from "../../../../mcp/dispatch.mjs";
10
+ import {
11
+ bundleReports,
12
+ computeEvidenceDigest,
13
+ ensureDirectory,
14
+ fingerprint,
15
+ inspectHandoffs,
16
+ messageLimit,
17
+ pruneReports,
18
+ reportPath,
19
+ verifyBundle
20
+ } from "../../../../mcp/handoffs.mjs";
21
+ import { planSquadCohort, scopesOverlap } from "../../../../mcp/planner.mjs";
22
+
23
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
24
+ const repoRoot = fs.realpathSync(path.resolve(scriptDir, "../../../.."));
25
+ const manifestRelative = ".agents/skills/self-diagnosis/resources/self-diagnosis-manifest.json";
26
+ const manifestAbsolute = path.join(repoRoot, manifestRelative);
27
+ const scratchBase = (process.env.AGY_SQUAD_SCRATCH_ROOT || "scratch").replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
28
+ if (!scratchBase || scratchBase.includes("..")) {
29
+ throw new Error("AGY_SQUAD_SCRATCH_ROOT must be a workspace-relative path without '..'.");
30
+ }
31
+ const scratchSquadRel = `${scratchBase}/agy-squad`;
32
+
33
+ const estimateTokens = bytes => Math.ceil(bytes / 4);
34
+
35
+ function removeEmpty(directory) {
36
+ try {
37
+ fs.rmdirSync(directory);
38
+ return true;
39
+ } catch (error) {
40
+ if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error.code)) throw error;
41
+ return false;
42
+ }
43
+ }
44
+
45
+ function listArtifacts(directory) {
46
+ const found = [];
47
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
48
+ const absolute = path.join(directory, entry.name);
49
+ if (entry.isDirectory()) {
50
+ found.push(...listArtifacts(absolute));
51
+ } else {
52
+ // REVIEW: JSON, logs, zero-byte files, and symlinks are all artifacts.
53
+ // Do not silently treat a non-report file as disposable.
54
+ found.push(path.relative(repoRoot, absolute).split(path.sep).join("/"));
55
+ }
56
+ }
57
+ return found;
58
+ }
59
+
60
+ function assertSafeDeleteTarget(relative) {
61
+ if (
62
+ typeof relative !== "string" ||
63
+ !relative ||
64
+ path.posix.isAbsolute(relative) ||
65
+ relative.includes("\\") ||
66
+ relative.split("/").some(part => !part || part === "." || part === "..")
67
+ ) {
68
+ throw new Error(`Invalid cleanup path: ${relative}`);
69
+ }
70
+ let absolute = repoRoot;
71
+ for (const part of relative.split("/")) {
72
+ absolute = path.join(absolute, part);
73
+ const info = fs.lstatSync(absolute); // REVIEW: lstat does NOT follow a symlink.
74
+ if (!info.isDirectory() || info.isSymbolicLink() || fs.realpathSync(absolute) !== absolute) {
75
+ throw new Error(`Refusing cleanup through a symlink/non-directory: ${absolute}`);
76
+ }
77
+ }
78
+ return absolute;
79
+ }
80
+
81
+ function resolveTargetReviewLedger(ledgerArg) {
82
+ const reviewsDir = path.join(repoRoot, "docs/reviews");
83
+ const reviewsReal = fs.existsSync(reviewsDir) ? fs.realpathSync(reviewsDir) : null;
84
+ if (ledgerArg) {
85
+ const clean = ledgerArg.replace(/\\/g, "/").replace(/^\.\/+/, "");
86
+ if (!clean.startsWith("docs/reviews/") || !clean.endsWith(".md") || clean.includes("..")) {
87
+ throw new Error("Flag '--ledger' must name a Markdown file under docs/reviews/ (e.g., docs/reviews/rebuild-wsl-dispatch-review-r2.md).");
88
+ }
89
+ const abs = path.resolve(repoRoot, clean);
90
+ if (!reviewsReal || !fs.existsSync(abs)) {
91
+ throw new Error(`Target PR review ledger '${clean}' does not exist.`);
92
+ }
93
+ const real = fs.realpathSync(abs);
94
+ const stat = fs.statSync(real);
95
+ if (path.dirname(real) !== reviewsReal || !stat.isFile()) {
96
+ throw new Error(`Target PR review ledger '${clean}' must be a regular file inside docs/reviews/.`);
97
+ }
98
+ return { relative: path.relative(repoRoot, real).replace(/\\/g, "/"), absolute: real, stat };
99
+ }
100
+ if (!reviewsReal) return null;
101
+ const candidates = [];
102
+ for (const entry of fs.readdirSync(reviewsReal, { withFileTypes: true })) {
103
+ if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) continue;
104
+ const absolute = path.join(reviewsReal, entry.name);
105
+ const real = fs.realpathSync(absolute);
106
+ if (path.dirname(real) !== reviewsReal) continue;
107
+ const stat = fs.statSync(real);
108
+ if (stat.isFile()) {
109
+ const revMatch = /-r(\d+)\.md$/i.exec(entry.name);
110
+ const revision = revMatch ? Number.parseInt(revMatch[1], 10) : 1;
111
+ const prMatch = /^pr(\d+)-/i.exec(entry.name);
112
+ const prNumber = prMatch ? Number.parseInt(prMatch[1], 10) : 0;
113
+ const isReconciled = /-reconciled(?:-review)?(?:-r\d+)?\.md$/i.test(entry.name);
114
+ const family = entry.name.replace(/-r\d+(?=\.md$)/i, "").toLowerCase();
115
+ candidates.push({
116
+ relative: `docs/reviews/${entry.name}`,
117
+ absolute: real,
118
+ stat,
119
+ family,
120
+ revision,
121
+ prNumber,
122
+ isReconciled
123
+ });
124
+ }
125
+ }
126
+ const latestByFamily = new Map();
127
+ for (const item of candidates) {
128
+ const prev = latestByFamily.get(item.family);
129
+ if (!prev || item.revision > prev.revision || (item.revision === prev.revision && item.stat.mtimeMs > prev.stat.mtimeMs)) {
130
+ latestByFamily.set(item.family, item);
131
+ }
132
+ }
133
+ const activeCandidates = [...latestByFamily.values()];
134
+ activeCandidates.sort((a, b) => {
135
+ if (Math.abs(b.stat.mtimeMs - a.stat.mtimeMs) > 2000) {
136
+ return b.stat.mtimeMs - a.stat.mtimeMs;
137
+ }
138
+ if (b.prNumber !== a.prNumber) {
139
+ return b.prNumber - a.prNumber;
140
+ }
141
+ if (b.isReconciled !== a.isReconciled) {
142
+ return b.isReconciled ? 1 : -1;
143
+ }
144
+ if (b.revision !== a.revision) {
145
+ return b.revision - a.revision;
146
+ }
147
+ return b.relative.localeCompare(a.relative);
148
+ });
149
+ return activeCandidates[0] ?? null;
150
+ }
151
+
152
+ const ALLOWED_LEDGER_STATUSES = new Set([
153
+ "Open (Pre-Remediation)",
154
+ "Closed",
155
+ "Residual (Open)",
156
+ "Closed (Post-Remediation)",
157
+ "Deferred / Won't Fix",
158
+ "Closed (By Design)",
159
+ "Informational"
160
+ ]);
161
+
162
+ const SECTION8_STATUSES = new Set([
163
+ "Residual (Open)",
164
+ "Closed (Post-Remediation)",
165
+ "Deferred / Won't Fix",
166
+ "Closed (By Design)",
167
+ "Informational"
168
+ ]);
169
+
170
+ function splitMarkdownRow(line) {
171
+ return line
172
+ .trim()
173
+ .replace(/^\|/, "")
174
+ .replace(/\|$/, "")
175
+ .split(/(?<!\\)\|/)
176
+ .map(cell => cell.trim());
177
+ }
178
+
179
+ function parseMarkdownTables(sectionText) {
180
+ const lines = sectionText.split(/\r?\n/);
181
+ const tables = [];
182
+ for (let i = 0; i + 1 < lines.length; i += 1) {
183
+ const hdrLine = lines[i].trim();
184
+ const sepLine = lines[i + 1].trim();
185
+ if (!hdrLine.startsWith("|") || !hdrLine.endsWith("|")) continue;
186
+ if (!/^\|\s*:?-{3,}:?(?:\s*\|\s*:?-{3,}:?)+\s*\|$/.test(sepLine)) continue;
187
+ const headers = splitMarkdownRow(hdrLine);
188
+ const rows = [];
189
+ let j = i + 2;
190
+ while (j < lines.length && lines[j].trim().startsWith("|") && lines[j].trim().endsWith("|")) {
191
+ rows.push(splitMarkdownRow(lines[j]));
192
+ j += 1;
193
+ }
194
+ tables.push({ headers, rows });
195
+ i = j - 1;
196
+ }
197
+ return tables;
198
+ }
199
+
200
+ function extractTopLevelSection(markdown, sectionNum) {
201
+ const headingRe = new RegExp(`^##\\s+${sectionNum}\\.\\s+.*$`, "m");
202
+ const match = headingRe.exec(markdown);
203
+ if (!match) return null;
204
+ const start = match.index;
205
+ const afterHeading = start + match[0].length;
206
+ const rest = markdown.slice(afterHeading);
207
+ const nextMatch = /^##\s+\d+\.\s+/m.exec(rest);
208
+ const body = nextMatch ? rest.slice(0, nextMatch.index) : rest;
209
+ return { heading: match[0].trim(), body };
210
+ }
211
+
212
+ const NON_FINDING_HYPHEN_TOKENS = new Set([
213
+ "UTF-8",
214
+ "UTF-16",
215
+ "SHA-1",
216
+ "SHA-256",
217
+ "SHA-384",
218
+ "SHA-512",
219
+ "JSON-RPC",
220
+ "CTRL-C",
221
+ "CTRL-D",
222
+ "PRE-FIX",
223
+ "RE-VERIFY",
224
+ "NON-ZERO",
225
+ "NON-EMPTY",
226
+ "READ-ONLY",
227
+ "FAIL-FAST",
228
+ "FAIL-SAFE",
229
+ "OPT-IN",
230
+ "END-TO-END",
231
+ "ONE-TO-ONE",
232
+ "STEP-BY-STEP",
233
+ "LINE-BY-LINE",
234
+ "MULTI-BYTE",
235
+ "SUB-AGENT"
236
+ ]);
237
+
238
+ function extractCohortFindingIds(markdown, { includeProseHeadingsAndBullets = false } = {}) {
239
+ const ids = new Set();
240
+ if (typeof markdown !== "string" || !markdown.trim()) return ids;
241
+ const idPattern = /^(?:[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+|F\d{2,})$/;
242
+ const addCandidate = raw => {
243
+ const clean = raw.replace(/\([^)]*\)/g, "").replace(/`/g, "").replace(/\*/g, "").trim();
244
+ if (idPattern.test(clean) && !NON_FINDING_HYPHEN_TOKENS.has(clean.toUpperCase())) {
245
+ ids.add(clean);
246
+ }
247
+ };
248
+ const stripped = markdown.replace(/(?:```[\s\S]*?```|~~~[\s\S]*?~~~)/g, "");
249
+ for (const table of parseMarkdownTables(stripped)) {
250
+ if (table.headers[0] !== "ID" && table.headers[0] !== "Finding ID") continue;
251
+ for (const row of table.rows) {
252
+ if (!row.length) continue;
253
+ addCandidate(row[0]);
254
+ }
255
+ }
256
+ for (const match of stripped.matchAll(/^#{3,4}\s+`([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+|F\d{2,})`/gm)) {
257
+ addCandidate(match[1]);
258
+ }
259
+ if (includeProseHeadingsAndBullets) {
260
+ // Plain, numbered, or styled headings (e.g., "### SD-TEST-01 — ...", "#### **SD-TEST-01:** ...", "### 2.1 SD-HEAD-02: ...")
261
+ const headingRe =
262
+ /^#{2,6}\s+(?:\d+(?:\.\d+)*[.)]?\s+)?(?:[*`\s]*(?:\[P[0-3]\]|\(P[0-3]\)|Finding)[*`\s]*[:—–-]?\s*)?[*`]*([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+|F\d{2,})[*`]*(?=$|\s*(?:[:—–(-]|[*`]))/gm;
263
+ for (const match of stripped.matchAll(headingRe)) {
264
+ addCandidate(match[1]);
265
+ }
266
+ // Plain or styled bullets / numbered items (e.g., "- SD-TEST-01 — ...", "- **SD-TEST-01:** ...", "- **[P2] `SD-TEST-01` (`SVC`):** ...")
267
+ const bulletRe =
268
+ /^\s*(?:[*+-]|\d+[.)])\s+(?:[*`\s]*(?:\[P[0-3]\]|\(P[0-3]\)|Finding)[*`\s]*[:—–-]?\s*)?[*`]*([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+|F\d{2,})[*`]*\s*(?:\([^)]*\)\s*)?[*`]*\s*(?:[:—–-]|[*`]+\s*[:—–-]|$)/gm;
269
+ for (const match of stripped.matchAll(bulletRe)) {
270
+ addCandidate(match[1]);
271
+ }
272
+ }
273
+ return ids;
274
+ }
275
+
276
+ function validateTargetReviewLedger(targetLedger, { requireCohortSection = false } = {}) {
277
+ const markdown = fs.readFileSync(targetLedger.absolute, "utf8");
278
+ const errors = [];
279
+ const openResiduals = [];
280
+ const openBlockingFindings = [];
281
+ const recordedFindingIds = extractCohortFindingIds(markdown);
282
+ const recordedBundleShas = [];
283
+ const statusById = new Map();
284
+
285
+ const recordIdStatus = (primaryId, cleanStatus, label) => {
286
+ if (!primaryId) return;
287
+ const prev = statusById.get(primaryId);
288
+ if (prev && prev.status !== cleanStatus) {
289
+ errors.push(
290
+ `${label} assigns conflicting status '${cleanStatus}' to '${primaryId}' (already recorded as '${prev.status}' in ${prev.label}).`
291
+ );
292
+ } else if (!prev) {
293
+ statusById.set(primaryId, { status: cleanStatus, label });
294
+ }
295
+ };
296
+
297
+ for (const reqSec of [1, 2, 3, 4, 5, 6, 7]) {
298
+ const sec = extractTopLevelSection(markdown, reqSec);
299
+ if (!sec) {
300
+ errors.push(`Missing mandatory '## ${reqSec}.' ledger section.`);
301
+ }
302
+ }
303
+
304
+ const sec1 = extractTopLevelSection(markdown, 1);
305
+ const expectedSessionHeaders = [
306
+ "Session #",
307
+ "Operator Conversation ID",
308
+ "Parent Session",
309
+ "Date",
310
+ "Base..Head + Working-Tree State",
311
+ "Skill / Topology Invoked"
312
+ ];
313
+ if (sec1) {
314
+ const sessionTable = parseMarkdownTables(sec1.body).find(
315
+ t => t.headers.length === expectedSessionHeaders.length && t.headers.every((h, idx) => h === expectedSessionHeaders[idx])
316
+ );
317
+ if (!sessionTable || sessionTable.rows.length === 0) {
318
+ errors.push(
319
+ "Section 1 must contain a populated Session Provenance table ('Session # | Operator Conversation ID | Parent Session | Date | Base..Head + Working-Tree State | Skill / Topology Invoked')."
320
+ );
321
+ }
322
+ }
323
+
324
+ const inferPriorityFromHeading = (text, fallback = "P2") => {
325
+ const m = /\b(P[0-3])\b/.exec(text);
326
+ return m ? m[1] : fallback;
327
+ };
328
+
329
+ const validateFindingTable = (table, label, { section8Only = false, defaultPriority = "P2" } = {}) => {
330
+ const idIdx = 0;
331
+ const statusIdx = table.headers.indexOf("Status");
332
+ const prioIdx = table.headers.findIndex(h => /^(?:Priority|Severity|Sev|Prio)$/i.test(h));
333
+ if (statusIdx === -1) {
334
+ errors.push(`${label} finding table is missing a 'Status' column.`);
335
+ return;
336
+ }
337
+ const seenIds = new Set();
338
+ for (const row of table.rows) {
339
+ if (row.length !== table.headers.length) {
340
+ errors.push(`${label} row '${row[0] ?? ""}' has ${row.length} columns (expected ${table.headers.length}).`);
341
+ continue;
342
+ }
343
+ const rawId = row[idIdx];
344
+ const primaryId = rawId.replace(/\([^)]*\)/g, "").replace(/`/g, "").trim();
345
+ if (
346
+ !primaryId ||
347
+ /\.\.|[&+,/]/.test(primaryId) ||
348
+ /(?:^|-)(?:HARDEN|UMBRELLA|BATCH|MISC)(?:-|$)/i.test(primaryId) ||
349
+ /^R\d+-OPT-P3-\d+$/i.test(primaryId)
350
+ ) {
351
+ errors.push(`${label} violates '1 ID = 1 Row' with compressed/umbrella ID '${rawId}'.`);
352
+ } else if (seenIds.has(primaryId)) {
353
+ errors.push(`${label} contains duplicate finding ID '${primaryId}'.`);
354
+ } else {
355
+ seenIds.add(primaryId);
356
+ }
357
+
358
+ const cleanStatus = row[statusIdx].replace(/\*\*|`/g, "").trim();
359
+ if (!ALLOWED_LEDGER_STATUSES.has(cleanStatus)) {
360
+ errors.push(`${label} row '${primaryId || rawId}' uses non-canonical status '${cleanStatus}'.`);
361
+ } else if (section8Only && !SECTION8_STATUSES.has(cleanStatus)) {
362
+ errors.push(`Section 8 row '${primaryId || rawId}' must use a residual/deferred/by-design status (found '${cleanStatus}').`);
363
+ } else if (!section8Only && SECTION8_STATUSES.has(cleanStatus)) {
364
+ errors.push(`${label} row '${primaryId || rawId}' has status '${cleanStatus}', which belongs in Section 8.`);
365
+ } else if (primaryId) {
366
+ recordIdStatus(primaryId, cleanStatus, label);
367
+ }
368
+
369
+ if (section8Only && cleanStatus === "Residual (Open)" && primaryId) {
370
+ openResiduals.push(primaryId);
371
+ } else if (!section8Only && cleanStatus === "Open (Pre-Remediation)" && primaryId) {
372
+ const prioText = prioIdx !== -1 ? row[prioIdx] : row.find(c => /\bP[0-3]\b/.test(c)) || defaultPriority;
373
+ const prio = /\b(P[0-3])\b/.exec(prioText)?.[1] ?? defaultPriority;
374
+ if (prio !== "P3") {
375
+ openBlockingFindings.push({ id: primaryId, priority: prio, label });
376
+ }
377
+ }
378
+ }
379
+ };
380
+
381
+ for (const secNum of [5, 6, 7]) {
382
+ const sec = extractTopLevelSection(markdown, secNum);
383
+ if (!sec) continue;
384
+ // Track subsections so tables under "### 5.4 P3" inherit P3 rather than P2
385
+ const chunks = sec.body.split(/^(###\s+[^\r\n]+)$/m);
386
+ let currentSubPrio = "P2";
387
+ for (const chunk of chunks) {
388
+ if (/^###\s+/.test(chunk)) {
389
+ currentSubPrio = inferPriorityFromHeading(chunk, "P2");
390
+ continue;
391
+ }
392
+ for (const table of parseMarkdownTables(chunk)) {
393
+ if ((table.headers[0] === "ID" || table.headers[0] === "Finding ID") && table.headers.includes("Status")) {
394
+ validateFindingTable(table, `Section ${secNum}`, { section8Only: false, defaultPriority: currentSubPrio });
395
+ }
396
+ }
397
+ // Also inspect atomic "#### `ID` — Title | P1 | Open (Pre-Remediation)" heading records
398
+ const strippedChunk = chunk.replace(/(?:```[\s\S]*?```|~~~[\s\S]*?~~~)/g, "");
399
+ const recRe = /^#{3,4}\s+`?([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+|F\d{2,})`?[^|\r\n]*\|\s*(P[0-3])\s*\|\s*([^\r\n|]+)$/gm;
400
+ for (const match of strippedChunk.matchAll(recRe)) {
401
+ const recId = match[1].trim();
402
+ const recPrio = match[2].trim();
403
+ const recStatus = match[3].replace(/\*\*|`/g, "").trim();
404
+ recordedFindingIds.add(recId);
405
+ if (!ALLOWED_LEDGER_STATUSES.has(recStatus)) {
406
+ errors.push(`Section ${secNum} record '${recId}' uses non-canonical status '${recStatus}'.`);
407
+ } else if (SECTION8_STATUSES.has(recStatus)) {
408
+ errors.push(`Section ${secNum} record '${recId}' has status '${recStatus}', which belongs in Section 8.`);
409
+ } else {
410
+ recordIdStatus(recId, recStatus, `Section ${secNum}`);
411
+ if (recStatus === "Open (Pre-Remediation)" && recPrio !== "P3") {
412
+ openBlockingFindings.push({ id: recId, priority: recPrio, label: `Section ${secNum}` });
413
+ }
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ const sec8 = extractTopLevelSection(markdown, 8);
420
+ const expectedSec8Headers = [
421
+ "ID",
422
+ "Wave/Session",
423
+ "Cat",
424
+ "Status",
425
+ "Source",
426
+ "Defect / Proposal",
427
+ "Disposition / Fix",
428
+ "Verification & Bundle SHA"
429
+ ];
430
+ if (!sec8 || !/^##\s+8\.\s+Post-Remediation Residuals & Deferred Dispositions\b/.test(sec8.heading)) {
431
+ errors.push("Missing mandatory '## 8. Post-Remediation Residuals & Deferred Dispositions' section.");
432
+ } else {
433
+ const sec8Tables = parseMarkdownTables(sec8.body);
434
+ const hasEmptyMarker = /None \(0 open residuals, 0 deferred items\)/.test(sec8.body);
435
+ const sec8Table = sec8Tables.find(
436
+ t => t.headers.length === expectedSec8Headers.length && t.headers.every((h, idx) => h === expectedSec8Headers[idx])
437
+ );
438
+ if (!sec8Table && !hasEmptyMarker) {
439
+ errors.push(
440
+ "Section 8 must contain either 'None (0 open residuals, 0 deferred items)' or the 8-column table ('ID | Wave/Session | Cat | Status | Source | Defect / Proposal | Disposition / Fix | Verification & Bundle SHA')."
441
+ );
442
+ } else if (sec8Table) {
443
+ if (sec8Table.rows.length === 0) {
444
+ errors.push("Section 8 table has 0 rows; use 'None (0 open residuals, 0 deferred items)' when empty.");
445
+ }
446
+ validateFindingTable(sec8Table, "Section 8", { section8Only: true });
447
+ }
448
+ }
449
+
450
+ if (/^##\s+(?:1[0-9]|[2-9]\d)\.\s+/m.test(markdown)) {
451
+ errors.push("Ad-hoc top-level sections beyond Section 9 are prohibited; consolidate all /self-diagnosis waves into Section 9.");
452
+ }
453
+
454
+ const sec9 = extractTopLevelSection(markdown, 9);
455
+ const expectedCohortHeaders = [
456
+ "Wave",
457
+ "Session ID",
458
+ "Stage",
459
+ "Subagent IDs",
460
+ "Pre-Fix Bundle SHA",
461
+ "Re-Verify Bundle SHA",
462
+ "Teardown"
463
+ ];
464
+ if (!sec9 && requireCohortSection) {
465
+ errors.push("Missing mandatory '## 9. `/self-diagnosis` Cohort & Bundle Ledger' section required before --teardown.");
466
+ } else if (sec9) {
467
+ if (!/^##\s+9\.\s+`?\/self-diagnosis`?\s+Cohort & Bundle Ledger\b/.test(sec9.heading)) {
468
+ errors.push("Section 9 must be titled '## 9. `/self-diagnosis` Cohort & Bundle Ledger'.");
469
+ }
470
+ const sec9Tables = parseMarkdownTables(sec9.body);
471
+ const cohortTable = sec9Tables.find(
472
+ t => t.headers.length === expectedCohortHeaders.length && t.headers.every((h, idx) => h === expectedCohortHeaders[idx])
473
+ );
474
+ if (!cohortTable || cohortTable.rows.length === 0) {
475
+ errors.push(
476
+ "Section 9 must contain a populated Cohort Provenance table ('Wave | Session ID | Stage | Subagent IDs | Pre-Fix Bundle SHA | Re-Verify Bundle SHA | Teardown')."
477
+ );
478
+ } else {
479
+ for (const row of cohortTable.rows) {
480
+ for (const colIdx of [4, 5]) {
481
+ const cell = row[colIdx] ?? "";
482
+ for (const match of cell.matchAll(/(?<![0-9a-f-])[0-9a-f]{12,64}(?![0-9a-f-])/gi)) {
483
+ recordedBundleShas.push(match[0].toLowerCase());
484
+ }
485
+ }
486
+ }
487
+ }
488
+ const findingTables = sec9Tables.filter(t => t !== cohortTable && (t.headers[0] === "ID" || t.headers[0] === "Finding ID"));
489
+ if (findingTables.length === 0 && !/None \(0 wave findings\)/.test(sec9.body)) {
490
+ errors.push("Section 9 must contain an atomic Wave Finding Ledger table (or 'None (0 wave findings)').");
491
+ }
492
+ for (const ft of findingTables) {
493
+ validateFindingTable(ft, "Section 9");
494
+ }
495
+ }
496
+
497
+ return { errors, openResiduals, openBlockingFindings, recordedFindingIds, recordedBundleShas };
498
+ }
499
+
500
+ function isCanaryPidAlive(canaryAbsDir) {
501
+ const ownerFile = path.join(canaryAbsDir, "canary-owner.json");
502
+ if (!fs.existsSync(ownerFile)) {
503
+ return false;
504
+ }
505
+ try {
506
+ const owner = JSON.parse(fs.readFileSync(ownerFile, "utf8"));
507
+ if (!Number.isInteger(owner?.pid) || owner.pid <= 0) {
508
+ return false;
509
+ }
510
+ process.kill(owner.pid, 0);
511
+ return true;
512
+ } catch (error) {
513
+ return error?.code === "EPERM";
514
+ }
515
+ }
516
+
517
+ function teardownSelfDiagnosisDirs({ squadId = "all", acceptedHash, force = false, ledgerPath } = {}) {
518
+ const manifest = JSON.parse(fs.readFileSync(manifestAbsolute, "utf8"));
519
+ const knownSquads = [...Object.keys(manifest.squads), "self_diagnosis"];
520
+ if (squadId !== "all" && !knownSquads.includes(squadId)) {
521
+ throw new Error(`Unknown self-diagnosis squadId '${squadId}'. Expected 'all' or one of: ${knownSquads.join(", ")}`);
522
+ }
523
+ if (acceptedHash && squadId === "all") {
524
+ throw new Error("Flag '--accept' requires a single squadId (not 'all').");
525
+ }
526
+ const targetLedger = resolveTargetReviewLedger(ledgerPath);
527
+ const targetSquads = squadId === "all" ? knownSquads : [squadId];
528
+ const scratchSquadRoot = path.join(repoRoot, scratchSquadRel);
529
+ const validatedDirs = [];
530
+ const tornDown = [];
531
+ const removedParents = [];
532
+ const accountedAuditFiles = new Set();
533
+ let ledgerCheck = null;
534
+
535
+ const ensureLedgerValid = failingRelDir => {
536
+ if (!targetLedger) {
537
+ throw new Error(
538
+ `Refusing to tear down ${failingRelDir}: no durable target PR review ledger found under docs/reviews/. Export or update docs/reviews/<slug>.md before --teardown.`
539
+ );
540
+ }
541
+ if (!ledgerCheck) {
542
+ ledgerCheck = validateTargetReviewLedger(targetLedger, { requireCohortSection: true });
543
+ if (ledgerCheck.errors.length > 0) {
544
+ throw new Error(
545
+ `Refusing to tear down ${failingRelDir}: ${targetLedger.relative} violates PR Review Standard v1.4.0 schema: ${ledgerCheck.errors.join(" ")}`
546
+ );
547
+ }
548
+ }
549
+ return ledgerCheck;
550
+ };
551
+
552
+ for (const id of targetSquads) {
553
+ const relDir = `${scratchSquadRel}/squad-${encodeURIComponent(id)}`;
554
+ const absDir = path.join(repoRoot, relDir);
555
+ const auditId = `integrity_auditor-${id}`;
556
+ const auditRelDir = `${scratchSquadRel}/squad-${encodeURIComponent(auditId)}`;
557
+ const auditAbsDir = path.join(repoRoot, auditRelDir);
558
+ const hasWorking = fs.existsSync(absDir);
559
+ const hasAudit = fs.existsSync(auditAbsDir);
560
+ if (!hasWorking && !hasAudit) {
561
+ if (acceptedHash && !force) {
562
+ throw new Error(`Cannot verify accepted hash for '${id}': directory ${relDir} does not exist.`);
563
+ }
564
+ continue;
565
+ }
566
+ if (hasWorking) assertSafeDeleteTarget(relDir); // REVIEW: --force skips evidence gates, never path safety.
567
+ if (hasAudit) assertSafeDeleteTarget(auditRelDir);
568
+ const bundleRel = reportPath(id, undefined, scratchSquadRel).replace(/\.md$/, ".sources.json");
569
+ const bundleAbs = path.join(repoRoot, bundleRel);
570
+
571
+ if (!force) {
572
+ const activeLedger = ensureLedgerValid(hasWorking ? relDir : auditRelDir);
573
+ if (hasWorking && fs.existsSync(bundleAbs)) {
574
+ const verified = verifyBundle(repoRoot, bundleRel, acceptedHash);
575
+ const bundleStat = fs.statSync(bundleAbs);
576
+ if (targetLedger.stat.mtimeMs + 1000 < bundleStat.mtimeMs) {
577
+ throw new Error(
578
+ `Refusing to tear down ${relDir} before updating the durable target PR review ledger (${targetLedger.relative}). Update ${targetLedger.relative} with this cohort's findings and bundle digest before running --teardown.`
579
+ );
580
+ }
581
+ const digestRecorded = activeLedger.recordedBundleShas.some(
582
+ token => verified.sha256 === token || verified.sha256.startsWith(token)
583
+ );
584
+ if (!digestRecorded) {
585
+ throw new Error(
586
+ `Refusing to tear down ${relDir}: bundle digest ${verified.sha256} is not recorded in Section 9 of ${targetLedger.relative}.`
587
+ );
588
+ }
589
+ const bundleJson = JSON.parse(fs.readFileSync(bundleAbs, "utf8"));
590
+ const leadReportAbs = path.join(repoRoot, reportPath(id, undefined, scratchSquadRel));
591
+ const looseReports = [];
592
+ for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) {
593
+ if (entry.isDirectory() && /^specialist-[1-9]\d*$/.test(entry.name)) {
594
+ const repPath = path.join(absDir, entry.name, "report.md");
595
+ if (fs.existsSync(repPath)) {
596
+ looseReports.push(fs.readFileSync(repPath, "utf8"));
597
+ }
598
+ }
599
+ }
600
+ const bundledAudits = [
601
+ ...(bundleJson.integrityAudit ? [bundleJson.integrityAudit] : []),
602
+ ...(Array.isArray(bundleJson.integrityAudits) ? bundleJson.integrityAudits : [])
603
+ ];
604
+ const auditMarkdowns = [];
605
+ const bundledAuditPaths = new Set();
606
+ for (const auditEntry of bundledAudits) {
607
+ if (auditEntry?.path) {
608
+ bundledAuditPaths.add(auditEntry.path);
609
+ accountedAuditFiles.add(auditEntry.path);
610
+ }
611
+ if (typeof auditEntry?.markdown === "string" && auditEntry.markdown.trim()) {
612
+ auditMarkdowns.push(auditEntry.markdown);
613
+ } else if (auditEntry?.path) {
614
+ const absAuditReport = path.join(repoRoot, auditEntry.path);
615
+ if (fs.existsSync(absAuditReport)) {
616
+ auditMarkdowns.push(fs.readFileSync(absAuditReport, "utf8"));
617
+ }
618
+ }
619
+ }
620
+ if (hasAudit) {
621
+ for (const entry of fs.readdirSync(auditAbsDir, { withFileTypes: true })) {
622
+ if (entry.isDirectory() && /^specialist-[1-9]\d*$/.test(entry.name)) {
623
+ const repPath = path.join(auditAbsDir, entry.name, "report.md");
624
+ if (fs.existsSync(repPath)) {
625
+ auditMarkdowns.push(fs.readFileSync(repPath, "utf8"));
626
+ }
627
+ }
628
+ }
629
+ }
630
+ const cohortMarkdowns = [
631
+ ...(fs.existsSync(leadReportAbs) ? [fs.readFileSync(leadReportAbs, "utf8")] : []),
632
+ ...(Array.isArray(bundleJson.reports) ? bundleJson.reports.map(r => r.markdown || "") : []),
633
+ ...looseReports,
634
+ ...auditMarkdowns
635
+ ];
636
+ const cohortFindingIds = new Set();
637
+ for (const md of cohortMarkdowns) {
638
+ for (const fid of extractCohortFindingIds(md, { includeProseHeadingsAndBullets: true })) {
639
+ cohortFindingIds.add(fid);
640
+ }
641
+ }
642
+ const missingIds = [...cohortFindingIds].filter(fid => !activeLedger.recordedFindingIds.has(fid));
643
+ if (missingIds.length > 0) {
644
+ throw new Error(
645
+ `Refusing to tear down ${relDir}: cohort finding ID(s) [${missingIds.join(", ")}] are missing from ${targetLedger.relative}.`
646
+ );
647
+ }
648
+ // REVIEW: Bundle verification does not attest arbitrary new files in the squad folder.
649
+ // Never delete diagnostics.json, trace.log, or an unlisted specialist report.
650
+ const expectedArtifacts = new Set([
651
+ bundleRel,
652
+ reportPath(id, undefined, scratchSquadRel),
653
+ ...bundleJson.reports.map(({ path: report }) => report)
654
+ ]);
655
+ const unexpectedArtifacts = listArtifacts(absDir).filter(file => !expectedArtifacts.has(file));
656
+ if (unexpectedArtifacts.length) {
657
+ throw new Error(
658
+ `Refusing to tear down ${relDir}: unbundled artifacts remain: ${unexpectedArtifacts.join(", ")}`
659
+ );
660
+ }
661
+ if (hasAudit) {
662
+ const unexpectedAuditArtifacts = listArtifacts(auditAbsDir).filter(file => !bundledAuditPaths.has(file));
663
+ if (unexpectedAuditArtifacts.length) {
664
+ throw new Error(
665
+ `Refusing to tear down ${auditRelDir}: unbundled artifacts remain: ${unexpectedAuditArtifacts.join(", ")}`
666
+ );
667
+ }
668
+ }
669
+ } else if (acceptedHash) {
670
+ throw new Error(`Cannot verify accepted hash for '${id}': missing bundle at ${bundleRel}`);
671
+ } else {
672
+ if (hasWorking && listArtifacts(absDir).length > 0) {
673
+ throw new Error(
674
+ `Refusing to tear down unbundled evidence in ${relDir} without bundle verification or --force.`
675
+ );
676
+ }
677
+ if (hasAudit && listArtifacts(auditAbsDir).length > 0) {
678
+ throw new Error(
679
+ `Refusing to tear down unbundled evidence in ${auditRelDir} without bundle verification or --force.`
680
+ );
681
+ }
682
+ }
683
+ }
684
+ if (hasWorking) validatedDirs.push({ absDir, relDir });
685
+ if (hasAudit) validatedDirs.push({ absDir: auditAbsDir, relDir: auditRelDir });
686
+ }
687
+
688
+ // Preflight flat unscoped squad-integrity_auditor and canary directories BEFORE deleting anything (SVC-002, SVC-006)
689
+ if ((squadId === "all" || squadId === "self_diagnosis") && fs.existsSync(scratchSquadRoot)) {
690
+ assertSafeDeleteTarget(scratchSquadRel); // REVIEW: don't enumerate external symlink trees.
691
+ const flatAuditRel = `${scratchSquadRel}/squad-integrity_auditor`;
692
+ const flatAuditAbs = path.join(repoRoot, flatAuditRel);
693
+ const hasSelfDiagBundleMatch = [...accountedAuditFiles].some(f => f.startsWith(`${flatAuditRel}/`));
694
+ if (fs.existsSync(flatAuditAbs) && (squadId === "all" || hasSelfDiagBundleMatch)) {
695
+ assertSafeDeleteTarget(flatAuditRel);
696
+ const flatAuditArtifacts = listArtifacts(flatAuditAbs);
697
+ const unbundledFlatAudit = flatAuditArtifacts.filter(file => !accountedAuditFiles.has(file));
698
+ if (!force && unbundledFlatAudit.length > 0) {
699
+ throw new Error(
700
+ `Refusing to tear down unbundled evidence in ${flatAuditRel} without bundle verification or --force.`
701
+ );
702
+ }
703
+ if (!force && flatAuditArtifacts.length > 0) {
704
+ const activeLedger = ensureLedgerValid(flatAuditRel);
705
+ const flatAuditFindingIds = new Set();
706
+ for (const relFile of flatAuditArtifacts) {
707
+ const absFile = path.join(repoRoot, relFile);
708
+ if (relFile.endsWith(".md") && fs.existsSync(absFile)) {
709
+ const md = fs.readFileSync(absFile, "utf8");
710
+ for (const fid of extractCohortFindingIds(md, { includeProseHeadingsAndBullets: true })) {
711
+ flatAuditFindingIds.add(fid);
712
+ }
713
+ }
714
+ }
715
+ const missingFlatIds = [...flatAuditFindingIds].filter(fid => !activeLedger.recordedFindingIds.has(fid));
716
+ if (missingFlatIds.length > 0) {
717
+ throw new Error(
718
+ `Refusing to tear down ${flatAuditRel}: cohort finding ID(s) [${missingFlatIds.join(", ")}] are missing from ${targetLedger.relative}.`
719
+ );
720
+ }
721
+ }
722
+ validatedDirs.push({ absDir: flatAuditAbs, relDir: flatAuditRel });
723
+ }
724
+ }
725
+
726
+ if (squadId === "all" && fs.existsSync(scratchSquadRoot)) {
727
+ for (const entry of fs.readdirSync(scratchSquadRoot, { withFileTypes: true })) {
728
+ if (entry.isDirectory() && entry.name.startsWith("self-diag-canary-")) {
729
+ const relCanary = `${scratchSquadRel}/${entry.name}`;
730
+ const absCanary = path.join(repoRoot, relCanary);
731
+ assertSafeDeleteTarget(relCanary);
732
+ if (!force && isCanaryPidAlive(absCanary)) {
733
+ continue;
734
+ }
735
+ validatedDirs.push({ absDir: absCanary, relDir: relCanary });
736
+ }
737
+ }
738
+ }
739
+
740
+ for (const { relDir } of validatedDirs) {
741
+ // REVIEW: Recheck the full parent chain immediately before destructive cleanup.
742
+ fs.rmSync(assertSafeDeleteTarget(relDir), { recursive: true, force: true });
743
+ tornDown.push(relDir);
744
+ }
745
+
746
+ for (const parentRel of [scratchSquadRel, scratchBase]) {
747
+ if (removeEmpty(path.join(repoRoot, parentRel))) {
748
+ removedParents.push(parentRel);
749
+ }
750
+ }
751
+
752
+ return { status: "TORN_DOWN", targetReviewLedger: targetLedger?.relative ?? null, tornDown, removedParents };
753
+ }
754
+
755
+ function parseFrontmatter(markdown) {
756
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/.exec(markdown);
757
+ if (!match) return null;
758
+ const raw = match[1];
759
+ let name = /^name:\s*(.+)$/m.exec(raw)?.[1]?.trim();
760
+ if (name && ((name.startsWith('"') && name.endsWith('"')) || (name.startsWith("'") && name.endsWith("'")))) {
761
+ name = name.slice(1, -1).trim();
762
+ }
763
+ const descMatch = /^description:\s*(?:[>|][-+]?\s*\r?\n)?([\s\S]*)$/m.exec(raw);
764
+ const descBody = descMatch ? descMatch[1].replace(/\r?\n[a-zA-Z0-9_-]+:[\s\S]*$/, "").trim() : "";
765
+ const hasDescription = descBody.length > 0;
766
+ return { name, hasDescription };
767
+ }
768
+
769
+ function extractMarkdownLinks(sourceFile, markdown) {
770
+ const dir = path.dirname(sourceFile);
771
+ const stripped = markdown
772
+ .replace(/<!--[\s\S]*?-->/g, "")
773
+ .replace(/(?:```[\s\S]*?```|~~~[\s\S]*?~~~)/g, "")
774
+ .replace(/(`+)([^\r\n]*?)\1/g, "CODE");
775
+ const links = [];
776
+ const linkRegex = /\[[^\]]+\]\(([^)#\s]*)(?:#([^)\s]*))?\)/g;
777
+ for (const match of stripped.matchAll(linkRegex)) {
778
+ const rawTarget = match[1];
779
+ const fragment = match[2] ?? "";
780
+ if (!rawTarget && !fragment) continue;
781
+ if (rawTarget && /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:|\/)/.test(rawTarget)) continue;
782
+ links.push({
783
+ link: rawTarget || `#${fragment}`,
784
+ fragment,
785
+ resolved: rawTarget ? path.resolve(dir, rawTarget) : sourceFile
786
+ });
787
+ }
788
+ return links;
789
+ }
790
+
791
+ function slugifyHeading(heading) {
792
+ return heading
793
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
794
+ .toLowerCase()
795
+ .replace(/`/g, "")
796
+ .replace(/[^\w\s-]/g, "")
797
+ .trim()
798
+ .replace(/\s/g, "-");
799
+ }
800
+
801
+ function extractHeadingSlugs(markdown) {
802
+ const slugs = new Set();
803
+ const counts = new Map();
804
+ const body = markdown.replace(/(?:```[\s\S]*?```|~~~[\s\S]*?~~~)/g, "");
805
+ for (const line of body.split(/\r?\n/)) {
806
+ const match = /^#{1,6}\s+(.+)$/.exec(line);
807
+ if (!match) continue;
808
+ const base = slugifyHeading(match[1]);
809
+ if (!base) continue;
810
+ const seen = counts.get(base) ?? 0;
811
+ counts.set(base, seen + 1);
812
+ slugs.add(seen === 0 ? base : `${base}-${seen}`);
813
+ }
814
+ return slugs;
815
+ }
816
+
817
+ const headingSlugCache = new Map();
818
+
819
+ function getCachedHeadingSlugs(filePath) {
820
+ if (!headingSlugCache.has(filePath)) {
821
+ headingSlugCache.set(filePath, extractHeadingSlugs(fs.readFileSync(filePath, "utf8")));
822
+ }
823
+ return headingSlugCache.get(filePath);
824
+ }
825
+
826
+ function extractHeadingSection(markdown, headingRegex) {
827
+ const lines = markdown.split(/\r?\n/);
828
+ let startIndex = -1;
829
+ let startLevel = 0;
830
+ for (let i = 0; i < lines.length; i++) {
831
+ const headingMatch = /^(#{1,6})\s+(.+)$/.exec(lines[i]);
832
+ if (!headingMatch) continue;
833
+ if (startIndex === -1) {
834
+ if (headingRegex.test(lines[i])) {
835
+ startIndex = i;
836
+ startLevel = headingMatch[1].length;
837
+ }
838
+ } else if (headingMatch[1].length <= startLevel) {
839
+ return lines.slice(startIndex, i).join("\n");
840
+ }
841
+ }
842
+ return startIndex === -1 ? "" : lines.slice(startIndex).join("\n");
843
+ }
844
+
845
+ function checkRelativeLinks(skillFile, markdown) {
846
+ const broken = [];
847
+ for (const item of extractMarkdownLinks(skillFile, markdown)) {
848
+ if (!fs.existsSync(item.resolved)) {
849
+ broken.push({ link: item.link, resolved: path.relative(repoRoot, item.resolved) });
850
+ } else if (item.fragment && item.resolved.endsWith(".md")) {
851
+ const slugs = getCachedHeadingSlugs(item.resolved);
852
+ if (!slugs.has(item.fragment.toLowerCase())) {
853
+ broken.push({
854
+ link: `${item.link}#${item.fragment}`,
855
+ resolved: `${path.relative(repoRoot, item.resolved)}#${item.fragment}`
856
+ });
857
+ }
858
+ }
859
+ }
860
+ return broken;
861
+ }
862
+
863
+ function walkFiles(dir) {
864
+ const results = [];
865
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
866
+ const full = path.join(dir, entry.name);
867
+ if (entry.isDirectory()) results.push(...walkFiles(full));
868
+ else if (entry.isFile()) results.push(full);
869
+ }
870
+ return results;
871
+ }
872
+
873
+ const RETIRED_SKILL_SYMBOL_RE =
874
+ /\b(?:subagent_brief|spawnPacingMs|agy-squad(?:\.mjs)?\s+run|terminal\.py|run-state\.mjs|AGY_SQUAD_RUN_DIR|AGY_SQUAD_SESSION|managed\s+`?manifest`?|adversarial_l1|adversarial_flat|adversarial_review|integrity-auditor)\b|\bnode\s+mcp\/handoffs\.mjs\b|(?<![a-zA-Z0-9_-])\.mcp-ready(?![a-zA-Z0-9_-])/gi;
875
+ const HISTORICAL_NOTICE_RE =
876
+ /\b(?:retired|historical|deprecated|removed|superseded|obsolete|replaced\s+by|do\s+not\s+use|never\s+use)\b/i;
877
+
878
+ function findSkillDriftMatches(markdown) {
879
+ const matched = new Set();
880
+ let activeHistoricalBlock = false;
881
+ for (const line of markdown.split(/\r?\n/)) {
882
+ if (/^\s*#{1,6}\s+/.test(line)) {
883
+ activeHistoricalBlock = HISTORICAL_NOTICE_RE.test(line);
884
+ }
885
+ if (activeHistoricalBlock) continue;
886
+ const segments = line.trim().startsWith("|")
887
+ ? line.split(/(?<!\\)\|/)
888
+ : line.split(/(?<=[.!?;])\s+/);
889
+ for (const segment of segments) {
890
+ RETIRED_SKILL_SYMBOL_RE.lastIndex = 0;
891
+ if (!RETIRED_SKILL_SYMBOL_RE.test(segment)) continue;
892
+ if (HISTORICAL_NOTICE_RE.test(segment)) continue;
893
+ RETIRED_SKILL_SYMBOL_RE.lastIndex = 0;
894
+ for (const match of segment.matchAll(RETIRED_SKILL_SYMBOL_RE)) {
895
+ matched.add(match[0].replace(/`/g, "").replace(/\s+/g, " ").trim());
896
+ }
897
+ }
898
+ }
899
+ return [...matched];
900
+ }
901
+
902
+ function auditSkills(ledgerArg) {
903
+ const skillsRoot = path.join(repoRoot, ".agents/skills");
904
+ const skillDirs = fs.readdirSync(skillsRoot, { withFileTypes: true }).filter(d => d.isDirectory());
905
+ const skills = [];
906
+ const findings = [];
907
+ const targetLedger = resolveTargetReviewLedger(ledgerArg);
908
+
909
+ let ledgerDisposition = "CLEAN";
910
+ let openLedgerBlockers = [];
911
+ if (!targetLedger) {
912
+ ledgerDisposition = "MISSING";
913
+ findings.push({
914
+ id: "DOC-LEDGER-MISSING",
915
+ category: "DOC",
916
+ priority: "P1",
917
+ summary: "No durable target PR review ledger found under docs/reviews/ (required pre-remediation and pre-teardown target)."
918
+ });
919
+ } else {
920
+ const ledgerCheck = validateTargetReviewLedger(targetLedger);
921
+ if (ledgerCheck.errors.length > 0) {
922
+ ledgerDisposition = "INVALID";
923
+ findings.push({
924
+ id: "DOC-LEDGER-SCHEMA",
925
+ category: "DOC",
926
+ priority: "P2",
927
+ summary: `${targetLedger.relative} violates PR Review Standard v1.4.0 ledger structure: ${ledgerCheck.errors.join(" ")}`
928
+ });
929
+ }
930
+ if (ledgerCheck.openBlockingFindings.length > 0) {
931
+ if (ledgerDisposition === "CLEAN") ledgerDisposition = "OPEN_BLOCKERS";
932
+ openLedgerBlockers = ledgerCheck.openBlockingFindings.map(item => `${item.id} (${item.priority})`);
933
+ const preview = openLedgerBlockers.slice(0, 10).join(", ");
934
+ findings.push({
935
+ id: "DOC-LEDGER-OPEN-BLOCKERS",
936
+ category: "DOC",
937
+ priority: "P2",
938
+ summary: `${targetLedger.relative} declares ${ledgerCheck.openBlockingFindings.length} Open (Pre-Remediation) blocking finding(s): ${preview}${openLedgerBlockers.length > 10 ? ", ..." : ""}.`
939
+ });
940
+ } else if (ledgerCheck.openResiduals.length > 0) {
941
+ if (ledgerDisposition === "CLEAN") ledgerDisposition = "RESIDUALS_ONLY";
942
+ findings.push({
943
+ id: "DOC-LEDGER-RESIDUALS-OPEN",
944
+ category: "DOC",
945
+ priority: "P3",
946
+ summary: `${targetLedger.relative} Section 8 tracks ${ledgerCheck.openResiduals.length} Residual (Open) item(s) awaiting post-remediation closure: ${ledgerCheck.openResiduals.join(", ")}.`
947
+ });
948
+ }
949
+ }
950
+
951
+ const rolesDocPath = path.join(repoRoot, "docs/roles-and-workflows.md");
952
+ const rolesDocExists = fs.existsSync(rolesDocPath);
953
+ const rolesSlugs = rolesDocExists
954
+ ? extractHeadingSlugs(fs.readFileSync(rolesDocPath, "utf8"))
955
+ : new Set();
956
+
957
+ if (!rolesDocExists) {
958
+ findings.push({
959
+ id: "DOC-ROLES-MISSING",
960
+ category: "DOC",
961
+ priority: "P2",
962
+ summary: "Canonical role and skill workflow reference docs/roles-and-workflows.md is missing."
963
+ });
964
+ }
965
+
966
+ for (const entry of skillDirs) {
967
+ const dir = path.join(skillsRoot, entry.name);
968
+ const skillMd = path.join(dir, "SKILL.md");
969
+ if (!fs.existsSync(skillMd)) {
970
+ findings.push({
971
+ id: `SKILL-DIAG-${entry.name}`,
972
+ category: "SKILL",
973
+ priority: "P1",
974
+ summary: `Missing SKILL.md in .agents/skills/${entry.name}`
975
+ });
976
+ continue;
977
+ }
978
+ const content = fs.readFileSync(skillMd, "utf8");
979
+ const bytes = Buffer.byteLength(content, "utf8");
980
+ const lines = content.replace(/\r?\n$/, "").split(/\r?\n/).length;
981
+ const fm = parseFrontmatter(content);
982
+ const brokenLinks = checkRelativeLinks(skillMd, content);
983
+ const allFiles = walkFiles(dir);
984
+ let totalBytes = 0;
985
+ let markdownBytes = 0;
986
+ for (const f of allFiles) {
987
+ const sz = fs.statSync(f).size;
988
+ totalBytes += sz;
989
+ if (f.endsWith(".md")) markdownBytes += sz;
990
+ }
991
+ const entryShare = totalBytes > 0 ? Number((bytes / totalBytes).toFixed(2)) : 1;
992
+ const proseShare = markdownBytes > 0 ? Number((bytes / markdownBytes).toFixed(2)) : 1;
993
+
994
+ if (!fm?.name || fm.name !== entry.name || !fm.hasDescription) {
995
+ findings.push({
996
+ id: `SKILL-FM-${entry.name}`,
997
+ category: "SKILL",
998
+ priority: "P1",
999
+ summary: `Invalid YAML frontmatter in .agents/skills/${entry.name}/SKILL.md (expected name '${entry.name}' and non-empty description).`
1000
+ });
1001
+ }
1002
+ const refsDir = path.join(dir, "references");
1003
+ const driftTargets = [
1004
+ skillMd,
1005
+ ...allFiles.filter(f => f.endsWith(".md") && f.startsWith(refsDir + path.sep))
1006
+ ];
1007
+ const driftEntries = [];
1008
+ for (const targetFile of driftTargets) {
1009
+ const fileText = targetFile === skillMd ? content : fs.readFileSync(targetFile, "utf8");
1010
+ const symbols = findSkillDriftMatches(fileText);
1011
+ if (symbols.length > 0) {
1012
+ driftEntries.push(`${path.relative(repoRoot, targetFile)} (${symbols.join(", ")})`);
1013
+ }
1014
+ }
1015
+ if (driftEntries.length > 0) {
1016
+ findings.push({
1017
+ id: `SKILL-DRIFT-${entry.name}`,
1018
+ category: "SKILL",
1019
+ priority: "P2",
1020
+ summary: `Skill instructions in ${driftEntries.join("; ")} reference retired Phase 1/2 symbol(s) without an explicit retired/historical notice.`
1021
+ });
1022
+ }
1023
+ if (brokenLinks.length) {
1024
+ findings.push({
1025
+ id: `SKILL-LINKS-${entry.name}`,
1026
+ category: "SKILL",
1027
+ priority: "P1",
1028
+ summary: `Broken relative links in .agents/skills/${entry.name}/SKILL.md: ${brokenLinks.map(b => b.link).join(", ")}`
1029
+ });
1030
+ }
1031
+ if (lines > 300 || bytes > 12288) {
1032
+ findings.push({
1033
+ id: `SKILL-BUDGET-${entry.name}`,
1034
+ category: "SKILL",
1035
+ priority: "P3",
1036
+ summary: `.agents/skills/${entry.name}/SKILL.md exceeds progressive-disclosure target (${lines} lines, ${bytes} bytes; target <= 250 lines / <= 10,240 bytes).`
1037
+ });
1038
+ }
1039
+ if (entryShare > 0.7 && totalBytes > 8192) {
1040
+ findings.push({
1041
+ id: `SKILL-SHARE-${entry.name}`,
1042
+ category: "SKILL",
1043
+ priority: "P3",
1044
+ summary: `.agents/skills/${entry.name}/SKILL.md entry share is ${Math.round(entryShare * 100)}% of ${totalBytes} B total skill bytes (target <= 70% when skill > 8 KB).`
1045
+ });
1046
+ }
1047
+
1048
+ for (const f of allFiles.filter(file => file.endsWith(".md"))) {
1049
+ if (f === skillMd) continue;
1050
+ const refBroken = checkRelativeLinks(f, fs.readFileSync(f, "utf8"));
1051
+ if (refBroken.length) {
1052
+ findings.push({
1053
+ id: `SKILL-REFLINKS-${entry.name}`,
1054
+ category: "SKILL",
1055
+ priority: "P2",
1056
+ summary: `Broken relative links in ${path.relative(repoRoot, f)}: ${refBroken.map(b => b.link).join(", ")}`
1057
+ });
1058
+ }
1059
+ }
1060
+
1061
+ const rolesLinks = extractMarkdownLinks(skillMd, content).filter(
1062
+ item => item.resolved === rolesDocPath && fs.existsSync(item.resolved)
1063
+ );
1064
+ const hasValidRolesLink = rolesLinks.some(
1065
+ item => !item.fragment || rolesSlugs.has(item.fragment.toLowerCase())
1066
+ );
1067
+ if (!hasValidRolesLink) {
1068
+ findings.push({
1069
+ id: `SKILL-ROLES-LINK-${entry.name}`,
1070
+ category: "SKILL",
1071
+ priority: "P2",
1072
+ summary: `.agents/skills/${entry.name}/SKILL.md must contain a valid relative Markdown link to docs/roles-and-workflows.md (with valid #anchor if specified).`
1073
+ });
1074
+ }
1075
+
1076
+ skills.push({
1077
+ name: entry.name,
1078
+ path: path.relative(repoRoot, skillMd),
1079
+ lines,
1080
+ bytes,
1081
+ estimatedTokens: estimateTokens(bytes),
1082
+ totalSkillBytes: totalBytes,
1083
+ markdownSkillBytes: markdownBytes,
1084
+ entryShareOfTotal: entryShare,
1085
+ proseShareOfMarkdown: proseShare,
1086
+ brokenLinksCount: brokenLinks.length
1087
+ });
1088
+ }
1089
+
1090
+ const closureChecks = [
1091
+ { label: "swarm status & monitor kill", test: s => /swarm\{action:\s*["']status["']\}|`?swarm\s+status`?/i.test(s) && /monitor(?:-\*)?/i.test(s) && /kill/i.test(s) },
1092
+ { label: "manage_subagents", test: s => /manage_subagents/i.test(s) },
1093
+ { label: "manage_task", test: s => /manage_task/i.test(s) },
1094
+ { label: "child command cleanup", test: s => /child\s+commands?|builds,\s*servers,\s*or\s*watchers/i.test(s) }
1095
+ ];
1096
+ for (const [checkId, relPath, category, sectionRe] of [
1097
+ ["SKILL-CLOSURE-CHECKLIST", ".agents/skills/swarm-governance/SKILL.md", "SKILL", /^##\s+5\.\s+Runtime Closure Checklist\b/i],
1098
+ ["DOC-CLOSURE-CHECKLIST-stopping-agents", "docs/stopping-agents.md", "DOC", /^##\s+Four-check runtime closure checklist\b/i],
1099
+ ["DOC-CLOSURE-CHECKLIST-roles-and-workflows", "docs/roles-and-workflows.md", "DOC", /^###\s+Four-check runtime closure gate\b/i]
1100
+ ]) {
1101
+ const absCheck = path.join(repoRoot, relPath);
1102
+ const checkRaw = fs.existsSync(absCheck) ? fs.readFileSync(absCheck, "utf8") : "";
1103
+ const section = extractHeadingSection(checkRaw, sectionRe);
1104
+ if (!section) {
1105
+ findings.push({
1106
+ id: checkId,
1107
+ category,
1108
+ priority: "P2",
1109
+ summary: `${relPath} is missing its dedicated runtime closure checklist section.`
1110
+ });
1111
+ continue;
1112
+ }
1113
+ const missingControls = closureChecks.filter(c => !c.test(section)).map(c => c.label);
1114
+ if (missingControls.length) {
1115
+ findings.push({
1116
+ id: checkId,
1117
+ category,
1118
+ priority: "P2",
1119
+ summary: `${relPath} runtime closure checklist is missing: ${missingControls.join(", ")}.`
1120
+ });
1121
+ }
1122
+ }
1123
+
1124
+ // Zensical nav & docs/assets/*.svg structural checks (SCRIPT-PR5-04)
1125
+ const zensicalPath = path.join(repoRoot, "zensical.toml");
1126
+ const docsRoot = path.join(repoRoot, "docs");
1127
+ if (fs.existsSync(zensicalPath) && fs.existsSync(docsRoot)) {
1128
+ const zensicalRaw = fs.readFileSync(zensicalPath, "utf8");
1129
+ const navMatch = zensicalRaw.match(/^nav\s*=\s*\[([\s\S]*?)^\]/m);
1130
+ const navFiles = new Set();
1131
+ if (navMatch) {
1132
+ for (const m of navMatch[1].matchAll(/=\s*"([^"]+\.md)"/g)) {
1133
+ navFiles.add(m[1]);
1134
+ if (!fs.existsSync(path.join(docsRoot, m[1]))) {
1135
+ findings.push({
1136
+ id: `DOC-ZENSICAL-MISSING-${m[1].replace(/[^a-zA-Z0-9]+/g, "-")}`,
1137
+ category: "DOC",
1138
+ priority: "P2",
1139
+ summary: `zensical.toml nav references missing file docs/${m[1]}.`
1140
+ });
1141
+ }
1142
+ }
1143
+ for (const entry of fs.readdirSync(docsRoot, { withFileTypes: true })) {
1144
+ if (entry.isFile() && entry.name.endsWith(".md") && !navFiles.has(entry.name)) {
1145
+ findings.push({
1146
+ id: `DOC-ZENSICAL-UNLISTED-${entry.name.replace(/\.md$/, "")}`,
1147
+ category: "DOC",
1148
+ priority: "P3",
1149
+ summary: `Top-level doc docs/${entry.name} is omitted from zensical.toml nav.`
1150
+ });
1151
+ }
1152
+ }
1153
+ }
1154
+ const assetsDir = path.join(docsRoot, "assets");
1155
+ if (fs.existsSync(assetsDir)) {
1156
+ const reviewsPrefix = path.join(docsRoot, "reviews") + path.sep;
1157
+ const allDocsMarkdown = walkFiles(docsRoot)
1158
+ .filter(f => f.endsWith(".md") && !f.startsWith(reviewsPrefix))
1159
+ .map(f => fs.readFileSync(f, "utf8"))
1160
+ .join("\n");
1161
+ for (const entry of fs.readdirSync(assetsDir, { withFileTypes: true })) {
1162
+ if (!entry.isFile() || !entry.name.endsWith(".svg")) continue;
1163
+ const svgPath = path.join(assetsDir, entry.name);
1164
+ const svgRaw = fs.readFileSync(svgPath, "utf8");
1165
+ const hasRootSvg = /<svg\b[^>]*>[\s\S]*<\/svg>\s*$/i.test(svgRaw);
1166
+ const hasBareAmp = /&(?!(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);)/g.test(svgRaw);
1167
+ if (!hasRootSvg || hasBareAmp) {
1168
+ findings.push({
1169
+ id: `DOC-SVG-XML-${entry.name.replace(/\.svg$/, "")}`,
1170
+ category: "DOC",
1171
+ priority: "P2",
1172
+ summary: `docs/assets/${entry.name} failed SVG XML sanity check (root <svg> envelope or unescaped '&').`
1173
+ });
1174
+ }
1175
+ if (!allDocsMarkdown.includes(entry.name)) {
1176
+ findings.push({
1177
+ id: `DOC-SVG-ORPHAN-${entry.name.replace(/\.svg$/, "")}`,
1178
+ category: "DOC",
1179
+ priority: "P3",
1180
+ summary: `docs/assets/${entry.name} is not referenced by any file under docs/.`
1181
+ });
1182
+ }
1183
+ }
1184
+ }
1185
+ }
1186
+
1187
+ return {
1188
+ skills,
1189
+ findings,
1190
+ targetReviewLedger: targetLedger?.relative ?? null,
1191
+ ledgerDisposition,
1192
+ openLedgerBlockers
1193
+ };
1194
+ }
1195
+
1196
+ function runSquadCanaryAndOptimization({ allowActiveCohort = false } = {}) {
1197
+ const manifest = JSON.parse(fs.readFileSync(manifestAbsolute, "utf8"));
1198
+ const squadIds = Object.keys(manifest.squads);
1199
+ const findings = [];
1200
+ const timings = {};
1201
+
1202
+ // 0. Check for lingering untorn-down self-diagnosis directories (L41)
1203
+ const scratchSquadRoot = path.join(repoRoot, scratchSquadRel);
1204
+ if (fs.existsSync(scratchSquadRoot)) {
1205
+ const checkDirs = [
1206
+ ...squadIds.map(id => ({ id, relDir: `${scratchSquadRel}/squad-${encodeURIComponent(id)}` })),
1207
+ ...squadIds.map(id => ({
1208
+ id: `integrity_auditor-${id}`,
1209
+ relDir: `${scratchSquadRel}/squad-${encodeURIComponent(`integrity_auditor-${id}`)}`
1210
+ })),
1211
+ { id: "integrity_auditor", relDir: `${scratchSquadRel}/squad-integrity_auditor` },
1212
+ { id: "self_diagnosis", relDir: `${scratchSquadRel}/squad-self_diagnosis` },
1213
+ { id: "integrity_auditor-self_diagnosis", relDir: `${scratchSquadRel}/squad-integrity_auditor-self_diagnosis` }
1214
+ ];
1215
+ for (const { id, relDir } of checkDirs) {
1216
+ if (fs.existsSync(path.join(repoRoot, relDir))) {
1217
+ findings.push({
1218
+ id: allowActiveCohort ? `SVC-ACTIVE-COHORT-${id}` : `SVC-TEARDOWN-${id}`,
1219
+ category: "SVC",
1220
+ priority: allowActiveCohort ? "P3" : "P2",
1221
+ summary: allowActiveCohort
1222
+ ? `Active self-diagnosis cohort directory '${relDir}' present (--allow-active-cohort set; tear down via '--teardown' after final verification).`
1223
+ : `Lingering self-diagnosis directory '${relDir}' was not torn down (L41). Run 'node .agents/skills/self-diagnosis/scripts/diagnose.mjs --teardown' after bundle verification (or pass '--allow-active-cohort' during an in-flight cohort).`
1224
+ });
1225
+ }
1226
+ }
1227
+ for (const entry of fs.readdirSync(scratchSquadRoot, { withFileTypes: true })) {
1228
+ if (entry.isDirectory() && entry.name.startsWith("self-diag-canary-")) {
1229
+ findings.push({
1230
+ id: allowActiveCohort ? `SVC-ACTIVE-CANARY-${entry.name}` : `SVC-TEARDOWN-${entry.name}`,
1231
+ category: "SVC",
1232
+ priority: allowActiveCohort ? "P3" : "P2",
1233
+ summary: allowActiveCohort
1234
+ ? `Concurrent self-diagnosis canary directory '${scratchSquadRel}/${entry.name}' present (--allow-active-cohort set).`
1235
+ : `Lingering self-diagnosis canary directory '${scratchSquadRel}/${entry.name}' was not torn down (L41).`
1236
+ });
1237
+ }
1238
+ }
1239
+ }
1240
+
1241
+ // 1. Measure dispatch_squad & squad_planner latency + validate payloads
1242
+ const t0 = performance.now();
1243
+ const leadRes = handleDispatchSquad({ squadId: "all_l1", manifestPath: manifestRelative }, repoRoot);
1244
+ if (leadRes.isError) {
1245
+ throw new Error(`Self-diagnosis all_l1 dispatch failed: ${leadRes.content[0].text}`);
1246
+ }
1247
+ const leads = JSON.parse(leadRes.content[0].text).Subagents;
1248
+ const stagePayloads = {};
1249
+ for (const id of squadIds) {
1250
+ const res = handleDispatchSquad({ squadId: id, manifestPath: manifestRelative }, repoRoot);
1251
+ if (res.isError) {
1252
+ throw new Error(`Self-diagnosis specialist dispatch failed for '${id}': ${res.content[0].text}`);
1253
+ }
1254
+ stagePayloads[id] = JSON.parse(res.content[0].text).Subagents;
1255
+ }
1256
+ timings.dispatchCompileMs = Number((performance.now() - t0).toFixed(2));
1257
+ const dispatchCallCount = 1 + squadIds.length;
1258
+ timings.dispatchCompilePerCallMs = Number((timings.dispatchCompileMs / dispatchCallCount).toFixed(2));
1259
+ if (timings.dispatchCompilePerCallMs > 25) {
1260
+ findings.push({
1261
+ id: "RUN-LATENCY-COMPILE",
1262
+ category: "RUN",
1263
+ priority: "P3",
1264
+ summary: `Stage compilation averaged ${timings.dispatchCompilePerCallMs} ms/call (${timings.dispatchCompileMs} ms total across ${dispatchCallCount} calls; target < 10 ms/call, flag > 25 ms/call).`
1265
+ });
1266
+ }
1267
+ const tPlan = performance.now();
1268
+ const planned = planSquadCohort({ manifestPath: manifestRelative, executionMode: "swarm" }, repoRoot);
1269
+ timings.squadPlannerMs = Number((performance.now() - tPlan).toFixed(2));
1270
+ if (timings.squadPlannerMs > 25) {
1271
+ findings.push({
1272
+ id: "RUN-LATENCY-PLANNER",
1273
+ category: "RUN",
1274
+ priority: "P3",
1275
+ summary: `squad_planner compilation took ${timings.squadPlannerMs} ms (target < 10 ms, flag > 25 ms).`
1276
+ });
1277
+ }
1278
+ if (!planned?.provenanceScaffold?.entries?.length) {
1279
+ findings.push({
1280
+ id: "RUN-PLANNER-SCAFFOLD",
1281
+ category: "RUN",
1282
+ priority: "P2",
1283
+ summary: "squad_planner returned an empty provenanceScaffold."
1284
+ });
1285
+ }
1286
+
1287
+ // 2. Check scopeFiles existence (lead + specialists) and overlap within each squad
1288
+ const isPackagedDistribution =
1289
+ !fs.existsSync(path.join(repoRoot, ".git")) &&
1290
+ !fs.existsSync(path.join(repoRoot, "tests")) &&
1291
+ !fs.existsSync(path.join(repoRoot, ".github"));
1292
+ const isCheckoutOnlyPath = relPath => /^(?:tests|\.github|benchmarks)\//.test(relPath);
1293
+ const packagedCheckoutOmissions = new Set();
1294
+ const topology = [];
1295
+ for (const [id, squad] of Object.entries(manifest.squads)) {
1296
+ const leadSpec = squad.lead ?? squad;
1297
+ for (const file of leadSpec.scopeFiles ?? []) {
1298
+ if (!fs.existsSync(path.join(repoRoot, file))) {
1299
+ if (isPackagedDistribution && isCheckoutOnlyPath(file)) {
1300
+ packagedCheckoutOmissions.add(file);
1301
+ } else {
1302
+ findings.push({
1303
+ id: `RUN-LEADSCOPE-${id}`,
1304
+ category: "RUN",
1305
+ priority: "P2",
1306
+ summary: `Missing lead scopeFile '${file}' in self-diagnosis squad '${id}'.`
1307
+ });
1308
+ }
1309
+ }
1310
+ }
1311
+ const specialists = squad.specialists ?? squad.children ?? [];
1312
+ const seen = new Set();
1313
+ const overlaps = new Set();
1314
+ let totalFiles = 0;
1315
+ const writableScopes = [];
1316
+ for (const sp of specialists) {
1317
+ for (const file of sp.scopeFiles) {
1318
+ totalFiles += 1;
1319
+ seen.add(file);
1320
+ if (!fs.existsSync(path.join(repoRoot, file))) {
1321
+ if (isPackagedDistribution && isCheckoutOnlyPath(file)) {
1322
+ packagedCheckoutOmissions.add(file);
1323
+ } else {
1324
+ findings.push({
1325
+ id: `RUN-SCOPE-${id}`,
1326
+ category: "RUN",
1327
+ priority: "P2",
1328
+ summary: `Missing specialist scopeFile '${file}' in self-diagnosis squad '${id}'.`
1329
+ });
1330
+ }
1331
+ }
1332
+ }
1333
+ if (sp.readOnly !== true) {
1334
+ writableScopes.push(sp.scopeFiles);
1335
+ }
1336
+ }
1337
+ for (let i = 0; i < writableScopes.length; i += 1) {
1338
+ for (let j = i + 1; j < writableScopes.length; j += 1) {
1339
+ for (const a of writableScopes[i]) {
1340
+ for (const b of writableScopes[j]) {
1341
+ if (scopesOverlap(a, b)) overlaps.add(`${a} <-> ${b}`);
1342
+ }
1343
+ }
1344
+ }
1345
+ }
1346
+ const overlapRatio = totalFiles > 0 ? Number((overlaps.size / totalFiles).toFixed(2)) : 0;
1347
+ if (overlapRatio > 0.25) {
1348
+ findings.push({
1349
+ id: `ARCH-OVERLAP-${id}`,
1350
+ category: "ARCH",
1351
+ priority: "P3",
1352
+ summary: `Squad '${id}' has ${overlapRatio * 100}% writable scopeFiles overlap across specialists (${[...overlaps].join(", ")}).`
1353
+ });
1354
+ }
1355
+ topology.push({
1356
+ squadId: id,
1357
+ specialistsCount: specialists.length,
1358
+ uniqueScopeFiles: seen.size,
1359
+ overlappingScopeFiles: [...overlaps],
1360
+ overlapRatio
1361
+ });
1362
+ }
1363
+ if (packagedCheckoutOmissions.size > 0) {
1364
+ findings.push({
1365
+ id: "DEP-PACKAGED-CHECKOUT-SCOPE",
1366
+ category: "DEP",
1367
+ priority: "P3",
1368
+ summary: `Running inside packaged distribution without git checkout; ${packagedCheckoutOmissions.size} checkout-only scopeFile(s) (${[...packagedCheckoutOmissions].join(", ")}) are omitted by package.json files allowlist.`
1369
+ });
1370
+ }
1371
+
1372
+ // 3. Measure prompt overhead budgets (mcp/briefs.mjs)
1373
+ const minimalAgent = { role: "R", objective: "O", scopeFiles: ["a"] };
1374
+ const specialistTemplateBytes = Buffer.byteLength(
1375
+ buildSubagentSpec(minimalAgent, "s", "m.json", 1).Prompt,
1376
+ "utf8"
1377
+ );
1378
+ const leadTemplateBytes = Buffer.byteLength(
1379
+ buildSubagentSpec(minimalAgent, "s", "m.json", undefined).Prompt,
1380
+ "utf8"
1381
+ );
1382
+
1383
+ for (const [budgetId, actual, limit, label] of [
1384
+ ["RUN-BUDGET-SPEC", specialistTemplateBytes, 1800, "Specialist brief template"],
1385
+ ["RUN-BUDGET-LEAD", leadTemplateBytes, 3300, "Lead brief template"]
1386
+ ]) {
1387
+ if (actual > limit) {
1388
+ findings.push({
1389
+ id: budgetId,
1390
+ category: "RUN",
1391
+ priority: "P3",
1392
+ summary: `${label} is ${actual} UTF-8 bytes (exceeds ${limit} B rubric budget).`
1393
+ });
1394
+ }
1395
+ }
1396
+
1397
+ // 4. Execute isolated workspace-local Pointer-Over-Wire & squad_planner canary with deterministic teardown (L4, L41)
1398
+ const created = [];
1399
+ const canaryRel = `${scratchSquadRel}/self-diag-canary-${randomUUID()}`;
1400
+ const canaryDir = ensureDirectory(repoRoot, canaryRel, created);
1401
+ fs.writeFileSync(path.join(canaryDir, "canary-owner.json"), JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }));
1402
+ const cleanupCanary = () => {
1403
+ fs.rmSync(canaryDir, { recursive: true, force: true });
1404
+ for (const dir of [...created].reverse()) {
1405
+ removeEmpty(dir);
1406
+ }
1407
+ };
1408
+ process.once("exit", cleanupCanary);
1409
+ try {
1410
+ fs.writeFileSync(path.join(canaryDir, "target.txt"), "self-diagnosis-canary\n");
1411
+ const canaryPlan = planSquadCohort({
1412
+ manifest,
1413
+ executionMode: "swarm",
1414
+ model: "gemini-4-hydrogen-high",
1415
+ includeIntegrityAuditor: true
1416
+ }, canaryDir);
1417
+ if (!canaryPlan.modelProvenance?.pinningSupported || !canaryPlan.integrityAuditPlan?.length) {
1418
+ throw new Error("Self-diagnosis squad_planner swarm canary validation failed.");
1419
+ }
1420
+
1421
+ for (const id of squadIds) {
1422
+ const s1 = reportPath(id, 1);
1423
+ const s2 = reportPath(id, 2);
1424
+ const leadSlot = reportPath(id);
1425
+ for (const [slot, text] of [
1426
+ [s1, `# ${id} Specialist 1\nVerified target.txt.\n`],
1427
+ [s2, `# ${id} Specialist 2\nVerified target.txt.\n`],
1428
+ [leadSlot, `# ${id} Lead Synthesis\nContent Verdict: PASS\nVerified both specialist reports against target.txt.\n`]
1429
+ ]) {
1430
+ const full = path.join(canaryDir, slot);
1431
+ fs.mkdirSync(path.dirname(full), { recursive: true });
1432
+ fs.writeFileSync(full, text);
1433
+ }
1434
+ }
1435
+
1436
+ const tHandoff = performance.now();
1437
+ const verifiedFiles = fingerprint(canaryDir, ["target.txt"]);
1438
+ const leadAssignments = [];
1439
+ const leadHandoffs = [];
1440
+
1441
+ for (const id of squadIds) {
1442
+ const s1 = reportPath(id, 1);
1443
+ const s2 = reportPath(id, 2);
1444
+ const leadSlot = reportPath(id);
1445
+ const auditSlot = reportPath(`integrity_auditor-${id}`, 1);
1446
+ const record = {
1447
+ workspace: canaryDir,
1448
+ squadId: id,
1449
+ verdict: "PASS",
1450
+ verifiedFiles,
1451
+ assignments: [
1452
+ { conversationId: `${id}-child-1`, report: s1 },
1453
+ { conversationId: `${id}-child-2`, report: s2 }
1454
+ ],
1455
+ handoffs: [
1456
+ { sender: `${id}-child-1`, message: `READY ${s1} — Specialist 1 verified ${id}.` },
1457
+ { sender: `${id}-child-2`, message: `READY ${s2} — Specialist 2 verified ${id}.` }
1458
+ ]
1459
+ };
1460
+ const { evidenceDigest } = computeEvidenceDigest(canaryDir, record);
1461
+ const auditFull = path.join(canaryDir, auditSlot);
1462
+ fs.mkdirSync(path.dirname(auditFull), { recursive: true });
1463
+ fs.writeFileSync(
1464
+ auditFull,
1465
+ `# ${id} Integrity Audit\nTarget Squad: ${id}\nIntegrity Verdict: CLEAN\nEvidence Digest: ${evidenceDigest}\n`
1466
+ );
1467
+ inspectHandoffs(canaryDir, record);
1468
+ const bundled = bundleReports(canaryDir, record);
1469
+ const verified = verifyBundle(canaryDir, bundled.path, bundled.sha256);
1470
+ const pruned = pruneReports(canaryDir, bundled.path, verified.sha256);
1471
+ if (pruned.removed.length !== 2) {
1472
+ throw new Error(`Expected 2 pruned reports in self-diagnosis canary for '${id}', got ${pruned.removed.length}`);
1473
+ }
1474
+ leadAssignments.push({ conversationId: `${id}-lead`, report: leadSlot });
1475
+ leadHandoffs.push({
1476
+ sender: `${id}-lead`,
1477
+ message: `READY ${leadSlot} — Lead synthesized and bundled ${id} (${verified.sha256.slice(0, 12)}).`
1478
+ });
1479
+ }
1480
+
1481
+ // Verify custom reportRoot handoff/bundle/verify/prune pipeline
1482
+ const customId = squadIds[0];
1483
+ const customRoot = "scratch/agy-squad/custom-cohort";
1484
+ const cs1 = reportPath(customId, 1, customRoot);
1485
+ const cs2 = reportPath(customId, 2, customRoot);
1486
+ const cLead = reportPath(customId, undefined, customRoot);
1487
+ const cAudit = reportPath(`integrity_auditor-${customId}`, 1, customRoot);
1488
+ for (const [slot, text] of [
1489
+ [cs1, `# ${customId} Custom 1\nVerified target.txt.\n`],
1490
+ [cs2, `# ${customId} Custom 2\nVerified target.txt.\n`],
1491
+ [cLead, `# ${customId} Custom Lead\nContent Verdict: PASS\nVerified custom reportRoot.\n`]
1492
+ ]) {
1493
+ const full = path.join(canaryDir, slot);
1494
+ fs.mkdirSync(path.dirname(full), { recursive: true });
1495
+ fs.writeFileSync(full, text);
1496
+ }
1497
+ const customRecord = {
1498
+ workspace: canaryDir,
1499
+ squadId: customId,
1500
+ reportRoot: customRoot,
1501
+ verdict: "PASS",
1502
+ verifiedFiles,
1503
+ assignments: [
1504
+ { conversationId: `${customId}-c1`, report: cs1 },
1505
+ { conversationId: `${customId}-c2`, report: cs2 }
1506
+ ],
1507
+ handoffs: [
1508
+ { sender: `${customId}-c1`, message: `READY ${cs1} — Custom 1 verified.` },
1509
+ { sender: `${customId}-c2`, message: `READY ${cs2} — Custom 2 verified.` }
1510
+ ]
1511
+ };
1512
+ const { evidenceDigest: customDigest } = computeEvidenceDigest(canaryDir, customRecord);
1513
+ const cAuditFull = path.join(canaryDir, cAudit);
1514
+ fs.mkdirSync(path.dirname(cAuditFull), { recursive: true });
1515
+ fs.writeFileSync(
1516
+ cAuditFull,
1517
+ `# ${customId} Custom Integrity Audit\nTarget Squad: ${customId}\nIntegrity Verdict: CLEAN\nEvidence Digest: ${customDigest}\n`
1518
+ );
1519
+ inspectHandoffs(canaryDir, customRecord);
1520
+ const customBundled = bundleReports(canaryDir, customRecord);
1521
+ const customVerified = verifyBundle(canaryDir, customBundled.path, customBundled.sha256);
1522
+ const customPruned = pruneReports(canaryDir, customBundled.path, customVerified.sha256);
1523
+ if (customPruned.removed.length !== 2) {
1524
+ throw new Error(`Expected 2 pruned custom-root reports for '${customId}', got ${customPruned.removed.length}`);
1525
+ }
1526
+
1527
+ // Verify Layered Full-Squad (all_l1) 3-lead receipt reconciliation
1528
+ const layeredInspected = inspectHandoffs(canaryDir, {
1529
+ workspace: canaryDir,
1530
+ assignments: leadAssignments,
1531
+ handoffs: leadHandoffs
1532
+ });
1533
+ if (layeredInspected.some(item => item.status !== "READY") || layeredInspected.length !== 3) {
1534
+ throw new Error("Layered 3-lead canary reconciliation failed.");
1535
+ }
1536
+ const totalCanarySquads = squadIds.length + 1;
1537
+ timings.handoffPipelineMs = Number((performance.now() - tHandoff).toFixed(2));
1538
+ timings.perSquadHandoffMs = Number((timings.handoffPipelineMs / totalCanarySquads).toFixed(2));
1539
+ if (timings.perSquadHandoffMs > 50) {
1540
+ findings.push({
1541
+ id: "SVC-LATENCY-HANDOFF",
1542
+ category: "SVC",
1543
+ priority: "P3",
1544
+ summary: `Handoff/bundle/verify/prune canary took ${timings.perSquadHandoffMs} ms per 2-specialist squad (${timings.handoffPipelineMs} ms total across ${totalCanarySquads} squads; target < 20 ms, flag > 50 ms per squad).`
1545
+ });
1546
+ }
1547
+ } finally {
1548
+ process.removeListener("exit", cleanupCanary);
1549
+ cleanupCanary();
1550
+ }
1551
+
1552
+ // 5. Measure MCP tools/list schema payload size via stdio
1553
+ const tMcp = performance.now();
1554
+ const serverRes = spawnSync(process.execPath, [path.join(repoRoot, "mcp/index.js")], {
1555
+ cwd: repoRoot,
1556
+ input: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }) + "\n",
1557
+ encoding: "utf8"
1558
+ });
1559
+ timings.mcpSpawnMs = Number((performance.now() - tMcp).toFixed(2));
1560
+ if (serverRes.status !== 0) {
1561
+ throw new Error(`MCP server tools/list check failed: ${serverRes.stderr}`);
1562
+ }
1563
+ let toolsPayload;
1564
+ try {
1565
+ toolsPayload = JSON.parse(serverRes.stdout.trim());
1566
+ } catch (error) {
1567
+ throw new Error(`MCP server tools/list returned invalid JSON: ${error.message}`);
1568
+ }
1569
+ const advertisedTools = Array.isArray(toolsPayload?.result?.tools)
1570
+ ? toolsPayload.result.tools.map(tool => tool?.name)
1571
+ : [];
1572
+ for (const requiredTool of ["dispatch_squad", "squad_planner"]) {
1573
+ if (!advertisedTools.includes(requiredTool)) {
1574
+ throw new Error(`MCP server tools/list is missing required tool '${requiredTool}'.`);
1575
+ }
1576
+ }
1577
+ const schemaBytes = Buffer.byteLength(serverRes.stdout.trim(), "utf8");
1578
+ if (schemaBytes > 4096) {
1579
+ findings.push({
1580
+ id: "MCP-BUDGET-SCHEMA",
1581
+ category: "MCP",
1582
+ priority: "P3",
1583
+ summary: `MCP tools/list response is ${schemaBytes} UTF-8 bytes (exceeds 4,096 B rubric budget).`
1584
+ });
1585
+ }
1586
+
1587
+ const compiledSquads = Object.fromEntries(
1588
+ Object.entries(stagePayloads).map(([id, agents]) => [
1589
+ id,
1590
+ agents.map((a, idx) => {
1591
+ const slot = a.Prompt.match(/^Report slot: (.+)$/m)?.[1]?.trim();
1592
+ const prefixBytes = Buffer.byteLength(`READY ${slot} — `, "utf8");
1593
+ const receiptHeadroomBytes = messageLimit - prefixBytes;
1594
+ if (receiptHeadroomBytes < 340) {
1595
+ findings.push({
1596
+ id: `SVC-HEADROOM-${id}-${idx + 1}`,
1597
+ category: "SVC",
1598
+ priority: "P2",
1599
+ summary: `Specialist ${idx + 1} in '${id}' has only ${receiptHeadroomBytes} B receipt headroom (prefix ${prefixBytes} B > 160 B limit).`
1600
+ });
1601
+ }
1602
+ return {
1603
+ Role: a.Role,
1604
+ TypeName: a.TypeName,
1605
+ Workspace: a.Workspace,
1606
+ reportSlot: slot,
1607
+ promptBytes: Buffer.byteLength(a.Prompt, "utf8"),
1608
+ receiptHeadroomBytes
1609
+ };
1610
+ })
1611
+ ])
1612
+ );
1613
+
1614
+ return {
1615
+ compiledStages: {
1616
+ leadsCount: leads.length,
1617
+ squads: compiledSquads
1618
+ },
1619
+ promptBudgets: {
1620
+ specialistTemplateBytes,
1621
+ specialistEstimatedTokens: estimateTokens(specialistTemplateBytes),
1622
+ leadTemplateBytes,
1623
+ leadEstimatedTokens: estimateTokens(leadTemplateBytes),
1624
+ mcpToolsListResponseBytes: schemaBytes,
1625
+ mcpToolsListEstimatedTokens: estimateTokens(schemaBytes)
1626
+ },
1627
+ topology,
1628
+ timings,
1629
+ findings
1630
+ };
1631
+ }
1632
+
1633
+ function buildPrReviewCategoryMatrix(allFindings) {
1634
+ const categories = ["ARCH", "DOC", "SKILL", "MCP", "RUN", "SVC", "DB", "SCRIPT", "TEST", "DEP"];
1635
+ return categories.map(cat => {
1636
+ const items = allFindings.filter(f => f.category === cat);
1637
+ const p1 = items.filter(f => f.priority === "P1").length;
1638
+ const p2 = items.filter(f => f.priority === "P2").length;
1639
+ const p3 = items.filter(f => f.priority === "P3").length;
1640
+ return {
1641
+ category: cat,
1642
+ p1,
1643
+ p2,
1644
+ p3,
1645
+ verdict:
1646
+ cat === "DB"
1647
+ ? "Not applicable"
1648
+ : p1 > 0 || p2 > 0
1649
+ ? "Changes required"
1650
+ : "No findings within reviewed scope"
1651
+ };
1652
+ });
1653
+ }
1654
+
1655
+ function parseCliArgs(argv) {
1656
+ let markdown = false;
1657
+ let printDispatch = false;
1658
+ let printPlan = false;
1659
+ let teardown = false;
1660
+ let squadId = "all_l1";
1661
+ let planSquadId;
1662
+ let topology;
1663
+ let executionMode = "swarm";
1664
+ let workspaceIsolation = "inherit";
1665
+ let teardownTarget = "all";
1666
+ let parentId;
1667
+ let acceptedHash;
1668
+ let ledgerPath;
1669
+ let force = false;
1670
+ let allowActiveCohort = false;
1671
+ let help = false;
1672
+
1673
+ for (let i = 0; i < argv.length; i += 1) {
1674
+ const arg = argv[i];
1675
+ if (arg === "--help" || arg === "-h") {
1676
+ help = true;
1677
+ } else if (arg === "--markdown") {
1678
+ markdown = true;
1679
+ } else if (arg === "--allow-active-cohort") {
1680
+ allowActiveCohort = true;
1681
+ } else if (arg === "--print-plan") {
1682
+ printPlan = true;
1683
+ const next = argv[i + 1];
1684
+ if (next && !next.startsWith("-")) {
1685
+ planSquadId = next;
1686
+ i += 1;
1687
+ }
1688
+ } else if (arg === "--topology") {
1689
+ const next = argv[i + 1];
1690
+ if (next !== "flat" && next !== "layered") {
1691
+ throw new Error("Flag '--topology' requires 'flat' or 'layered'.");
1692
+ }
1693
+ topology = next;
1694
+ i += 1;
1695
+ } else if (arg === "--flat") {
1696
+ topology = "flat";
1697
+ } else if (arg === "--mode") {
1698
+ const next = argv[i + 1];
1699
+ if (next !== "swarm" && next !== "subagent") {
1700
+ throw new Error("Flag '--mode' requires 'swarm' or 'subagent'.");
1701
+ }
1702
+ executionMode = next;
1703
+ i += 1;
1704
+ } else if (arg === "--workspace-isolation") {
1705
+ const next = argv[i + 1];
1706
+ if (next !== "inherit" && next !== "share" && next !== "branch") {
1707
+ throw new Error("Flag '--workspace-isolation' requires 'inherit', 'share', or 'branch'.");
1708
+ }
1709
+ workspaceIsolation = next;
1710
+ i += 1;
1711
+ } else if (arg === "--print-dispatch") {
1712
+ printDispatch = true;
1713
+ const next = argv[i + 1];
1714
+ if (next && !next.startsWith("-")) {
1715
+ squadId = next;
1716
+ i += 1;
1717
+ }
1718
+ } else if (arg === "--parent-id") {
1719
+ const next = argv[i + 1];
1720
+ if (!next || next.startsWith("-") || !/^[A-Za-z0-9._:-]+$/.test(next)) {
1721
+ throw new Error("Flag '--parent-id' requires a valid conversationId value (alphanumeric, '.', '_', ':', '-').");
1722
+ }
1723
+ parentId = next;
1724
+ i += 1;
1725
+ } else if (arg === "--teardown") {
1726
+ teardown = true;
1727
+ const next = argv[i + 1];
1728
+ if (next && !next.startsWith("-")) {
1729
+ teardownTarget = next;
1730
+ i += 1;
1731
+ }
1732
+ } else if (arg === "--accept") {
1733
+ const next = argv[i + 1];
1734
+ if (!next || !/^[0-9a-f]{64}$/.test(next)) {
1735
+ throw new Error("Flag '--accept' requires a 64-character lowercase hex SHA-256 bundle digest.");
1736
+ }
1737
+ acceptedHash = next;
1738
+ i += 1;
1739
+ } else if (arg === "--ledger") {
1740
+ const next = argv[i + 1];
1741
+ if (!next || next.startsWith("-")) {
1742
+ throw new Error("Flag '--ledger' requires a path under docs/reviews/.");
1743
+ }
1744
+ ledgerPath = next;
1745
+ i += 1;
1746
+ } else if (arg === "--force") {
1747
+ force = true;
1748
+ } else {
1749
+ throw new Error(
1750
+ `Unknown argument '${arg}'. Usage: diagnose.mjs [--markdown] [--allow-active-cohort] [--ledger <docs/reviews/...>] | [--print-plan [squadId] [--topology flat|layered | --flat] [--mode swarm|subagent] [--workspace-isolation inherit|share|branch]] | [--print-dispatch [squadId] [--parent-id <id>]] | [--teardown [all | <squadId> [--accept <sha256>]] [--ledger <docs/reviews/...>] [--force]]`
1751
+ );
1752
+ }
1753
+ }
1754
+ if ([markdown, printPlan, printDispatch, teardown].filter(Boolean).length > 1) {
1755
+ throw new Error("Flags '--markdown', '--print-plan', '--print-dispatch', and '--teardown' are mutually exclusive.");
1756
+ }
1757
+ if (parentId && !printDispatch) {
1758
+ throw new Error("Flag '--parent-id' is only valid with '--print-dispatch'.");
1759
+ }
1760
+ if (topology && !printPlan) {
1761
+ throw new Error("Flags '--topology' and '--flat' are only valid with '--print-plan'.");
1762
+ }
1763
+ if ((executionMode !== "swarm" || workspaceIsolation !== "inherit") && !printPlan) {
1764
+ throw new Error("Flags '--mode' and '--workspace-isolation' are only valid with '--print-plan'.");
1765
+ }
1766
+ if (ledgerPath && (printPlan || printDispatch)) {
1767
+ throw new Error("Flag '--ledger' is not valid with '--print-plan' or '--print-dispatch'.");
1768
+ }
1769
+ if (allowActiveCohort && (printPlan || printDispatch || teardown)) {
1770
+ throw new Error("Flag '--allow-active-cohort' is only valid in audit mode.");
1771
+ }
1772
+ if ((acceptedHash || force) && !teardown) {
1773
+ throw new Error("Flags '--accept' and '--force' are only valid with '--teardown'.");
1774
+ }
1775
+ if (acceptedHash && teardownTarget === "all") {
1776
+ throw new Error("Flag '--accept' requires a single squadId (not 'all').");
1777
+ }
1778
+ return {
1779
+ markdown,
1780
+ printPlan,
1781
+ printDispatch,
1782
+ teardown,
1783
+ squadId,
1784
+ planSquadId,
1785
+ topology,
1786
+ executionMode,
1787
+ workspaceIsolation,
1788
+ teardownTarget,
1789
+ parentId,
1790
+ acceptedHash,
1791
+ ledgerPath,
1792
+ force,
1793
+ allowActiveCohort,
1794
+ help
1795
+ };
1796
+ }
1797
+
1798
+ const USAGE =
1799
+ "Usage: node .agents/skills/self-diagnosis/scripts/diagnose.mjs [--markdown] [--allow-active-cohort] [--ledger <docs/reviews/...>] | [--print-plan [squadId] [--topology flat|layered | --flat] [--mode swarm|subagent] [--workspace-isolation inherit|share|branch]] | [--print-dispatch [squadId] [--parent-id <id>]] | [--teardown [all | <squadId> [--accept <sha256>]] [--ledger <docs/reviews/...>] [--force]] | [--help]";
1800
+
1801
+ let cli;
1802
+ try {
1803
+ cli = parseCliArgs(process.argv.slice(2));
1804
+ } catch (error) {
1805
+ console.error(error.message);
1806
+ process.exit(1);
1807
+ }
1808
+
1809
+ if (cli.help) {
1810
+ console.log(USAGE);
1811
+ process.exit(0);
1812
+ }
1813
+
1814
+ if (cli.teardown) {
1815
+ try {
1816
+ const res = teardownSelfDiagnosisDirs({
1817
+ squadId: cli.teardownTarget,
1818
+ acceptedHash: cli.acceptedHash,
1819
+ force: cli.force,
1820
+ ledgerPath: cli.ledgerPath
1821
+ });
1822
+ console.log(JSON.stringify(res, null, 2));
1823
+ process.exit(0);
1824
+ } catch (error) {
1825
+ console.error(error.message);
1826
+ process.exit(1);
1827
+ }
1828
+ }
1829
+
1830
+ if (cli.printPlan) {
1831
+ try {
1832
+ let planInput;
1833
+ if (cli.topology === "flat" && !cli.planSquadId) {
1834
+ const manifest = JSON.parse(fs.readFileSync(manifestAbsolute, "utf8"));
1835
+ const flatSpecialists = Object.values(manifest.squads).flatMap(
1836
+ squad => squad.specialists ?? squad.children ?? []
1837
+ );
1838
+ planInput = {
1839
+ manifest: { squads: { self_diagnosis: { specialists: flatSpecialists } } },
1840
+ topology: "flat",
1841
+ executionMode: cli.executionMode,
1842
+ workspaceIsolation: cli.workspaceIsolation,
1843
+ includeIntegrityAuditor: true
1844
+ };
1845
+ } else {
1846
+ planInput = {
1847
+ manifestPath: manifestRelative,
1848
+ ...(cli.planSquadId ? { squadId: cli.planSquadId } : {}),
1849
+ ...(cli.topology ? { topology: cli.topology } : {}),
1850
+ executionMode: cli.executionMode,
1851
+ workspaceIsolation: cli.workspaceIsolation,
1852
+ includeIntegrityAuditor: true
1853
+ };
1854
+ }
1855
+ const plan = planSquadCohort(planInput, repoRoot);
1856
+ console.log(JSON.stringify(plan, null, 2));
1857
+ process.exit(0);
1858
+ } catch (error) {
1859
+ console.error(error.message);
1860
+ process.exit(1);
1861
+ }
1862
+ }
1863
+
1864
+ if (cli.printDispatch) {
1865
+ let parsed;
1866
+ if (cli.squadId === "all_specialists") {
1867
+ const manifest = JSON.parse(fs.readFileSync(manifestAbsolute, "utf8"));
1868
+ const Subagents = [];
1869
+ for (const id of Object.keys(manifest.squads)) {
1870
+ const res = handleDispatchSquad({ squadId: id, manifestPath: manifestRelative }, repoRoot);
1871
+ if (res.isError) {
1872
+ console.error(res.content[0].text);
1873
+ process.exit(1);
1874
+ }
1875
+ Subagents.push(...JSON.parse(res.content[0].text).Subagents);
1876
+ }
1877
+ parsed = { Subagents };
1878
+ } else {
1879
+ const result = handleDispatchSquad({ squadId: cli.squadId, manifestPath: manifestRelative }, repoRoot);
1880
+ if (result.isError) {
1881
+ console.error(result.content[0].text);
1882
+ process.exit(1);
1883
+ }
1884
+ parsed = JSON.parse(result.content[0].text);
1885
+ }
1886
+ if (cli.parentId) {
1887
+ for (const agent of parsed.Subagents) {
1888
+ agent.Prompt += `\nParent conversation ID (send_message Recipient): ${cli.parentId}`;
1889
+ }
1890
+ }
1891
+ console.log(JSON.stringify(parsed, null, 2));
1892
+ process.exit(0);
1893
+ }
1894
+
1895
+ const skillAudit = auditSkills(cli.ledgerPath);
1896
+ const squadAudit = runSquadCanaryAndOptimization({ allowActiveCohort: cli.allowActiveCohort });
1897
+ const findings = [...skillAudit.findings, ...squadAudit.findings];
1898
+ const prReviewMatrix = buildPrReviewCategoryMatrix(findings);
1899
+ const blockingCount = findings.filter(f => f.priority === "P1" || f.priority === "P2").length;
1900
+ const LEDGER_DIAGNOSTIC_IDS = new Set(["DOC-LEDGER-OPEN-BLOCKERS", "DOC-LEDGER-SCHEMA", "DOC-LEDGER-MISSING"]);
1901
+ const profilerBlockingCount = findings.filter(
1902
+ f => !LEDGER_DIAGNOSTIC_IDS.has(f.id) && (f.priority === "P1" || f.priority === "P2")
1903
+ ).length;
1904
+
1905
+ const report = {
1906
+ status: blockingCount === 0 ? "HEALTHY" : "CHANGES_REQUIRED",
1907
+ profilerHealth: profilerBlockingCount === 0 ? "HEALTHY" : "CHANGES_REQUIRED",
1908
+ ledgerDisposition: skillAudit.ledgerDisposition,
1909
+ openLedgerBlockers: skillAudit.openLedgerBlockers,
1910
+ standardVersion: "1.4.0",
1911
+ manifestPath: manifestRelative,
1912
+ targetReviewLedger: skillAudit.targetReviewLedger,
1913
+ skills: skillAudit.skills,
1914
+ promptBudgets: squadAudit.promptBudgets,
1915
+ topology: squadAudit.topology,
1916
+ timings: squadAudit.timings,
1917
+ compiledStages: squadAudit.compiledStages,
1918
+ prReviewCategoryMatrix: prReviewMatrix,
1919
+ findings
1920
+ };
1921
+
1922
+ if (cli.markdown) {
1923
+ const findingLines =
1924
+ report.findings.length === 0
1925
+ ? ["- None (all rubric thresholds and contract checks passed)."]
1926
+ : report.findings.map(f => `- **[${f.priority}] \`${f.id}\` (\`${f.category}\`):** ${f.summary}`);
1927
+ const lines = [
1928
+ "# Agy-Squad Self-Diagnosis & Optimization Report",
1929
+ "",
1930
+ `- **Status:** \`${report.status}\` (Profiler: \`${report.profilerHealth}\`, Ledger: \`${report.ledgerDisposition}\`, PR Review Standard \`v${report.standardVersion}\`)`,
1931
+ `- **Self-Diagnosis Manifest:** \`${report.manifestPath}\``,
1932
+ `- **Target PR Review Ledger:** \`${report.targetReviewLedger ?? "MISSING"}\``,
1933
+ `- **Timings:** dispatch \`${report.timings.dispatchCompilePerCallMs} ms/call\` (\`${report.timings.dispatchCompileMs} ms\` total), squad_planner \`${report.timings.squadPlannerMs} ms\`, handoff+bundle+verify+prune \`${report.timings.handoffPipelineMs} ms\` (\`${report.timings.perSquadHandoffMs} ms/squad\`), mcp-stdio \`${report.timings.mcpSpawnMs} ms\``,
1934
+ "",
1935
+ "## 1. Workspace Skills Progressive Disclosure & Token Audit",
1936
+ "",
1937
+ "| Skill | Path | Lines | UTF-8 Bytes | Est. Tokens | Entry Share (All / Prose) | Broken Links |",
1938
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: |",
1939
+ ...report.skills.map(
1940
+ s =>
1941
+ `| \`${s.name}\` | \`${s.path}\` | ${s.lines} | ${s.bytes} | ~${s.estimatedTokens} | ${Math.round(
1942
+ s.entryShareOfTotal * 100
1943
+ )}% / ${Math.round(s.proseShareOfMarkdown * 100)}% | ${s.brokenLinksCount} |`
1944
+ ),
1945
+ "",
1946
+ "## 2. Compiled Brief & MCP Schema Budgets",
1947
+ "",
1948
+ `- **Specialist Template:** \`${report.promptBudgets.specialistTemplateBytes} B\` (~${report.promptBudgets.specialistEstimatedTokens} tokens)`,
1949
+ `- **Lead Template:** \`${report.promptBudgets.leadTemplateBytes} B\` (~${report.promptBudgets.leadEstimatedTokens} tokens)`,
1950
+ `- **MCP \`tools/list\` Response:** \`${report.promptBudgets.mcpToolsListResponseBytes} B\` (~${report.promptBudgets.mcpToolsListEstimatedTokens} tokens)`,
1951
+ "",
1952
+ "## 3. PR-Review Standard v1.4.0 Category Matrix",
1953
+ "",
1954
+ "| Category | P1 | P2 | P3 (Advisory) | Verdict |",
1955
+ "| --- | ---: | ---: | ---: | --- |",
1956
+ ...report.prReviewCategoryMatrix.map(
1957
+ c => `| \`${c.category}\` | ${c.p1} | ${c.p2} | ${c.p3} | **${c.verdict}** |`
1958
+ ),
1959
+ "",
1960
+ "## 4. Findings",
1961
+ "",
1962
+ ...findingLines
1963
+ ];
1964
+ console.log(lines.join("\n"));
1965
+ } else {
1966
+ console.log(JSON.stringify(report, null, 2));
1967
+ }
1968
+
1969
+ if (blockingCount > 0) {
1970
+ process.exitCode = 1;
1971
+ }