@mmerterden/multi-agent-pipeline 16.11.0 → 16.13.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 (67) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +9 -7
  3. package/README.tr.md +9 -7
  4. package/docs/adr/0001-three-model-triage.md +5 -0
  5. package/docs/adr/0010-own-code-graph.md +129 -0
  6. package/docs/adr/README.md +1 -0
  7. package/docs/architecture.md +2 -2
  8. package/docs/ecosystem.md +5 -5
  9. package/docs/features.md +26 -2
  10. package/package.json +1 -1
  11. package/pipeline/claude-md-template.md +1 -1
  12. package/pipeline/commands/multi-agent/SKILL.md +1 -1
  13. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
  14. package/pipeline/commands/multi-agent/graph/SKILL.md +105 -0
  15. package/pipeline/commands/multi-agent/help/SKILL.md +10 -10
  16. package/pipeline/commands/multi-agent/resume-local/SKILL.md +2 -2
  17. package/pipeline/commands/multi-agent/review/SKILL.md +3 -3
  18. package/pipeline/commands/multi-agent/review-analysis/SKILL.md +1 -1
  19. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -12
  20. package/pipeline/commands/multi-agent/uninstall/SKILL.md +9 -7
  21. package/pipeline/lib/figma-screenshot.sh +107 -7
  22. package/pipeline/lib/md2confluence-v3.py +133 -25
  23. package/pipeline/multi-agent-refs/analysis/locked.md +4 -3
  24. package/pipeline/multi-agent-refs/analysis/render.md +44 -9
  25. package/pipeline/multi-agent-refs/analysis/review.md +17 -1
  26. package/pipeline/multi-agent-refs/analysis-template-corporate.md +14 -3
  27. package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -10
  28. package/pipeline/multi-agent-refs/features/code-graph.md +62 -0
  29. package/pipeline/multi-agent-refs/features/model-fallback.md +44 -2
  30. package/pipeline/multi-agent-refs/knowledge.md +7 -1
  31. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
  32. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +5 -0
  33. package/pipeline/multi-agent-refs/phases/phase-4-review.md +9 -9
  34. package/pipeline/multi-agent-refs/phases/phase-7-report.md +2 -0
  35. package/pipeline/preferences-template.json +2 -0
  36. package/pipeline/schemas/analysis-spec.schema.json +2 -0
  37. package/pipeline/schemas/code-graph.schema.json +91 -0
  38. package/pipeline/schemas/prefs.schema.json +45 -0
  39. package/pipeline/schemas/reviewer-output.schema.json +1 -1
  40. package/pipeline/schemas/token-budget.json +2 -2
  41. package/pipeline/schemas/triage-output.schema.json +1 -1
  42. package/pipeline/scripts/_code-graph.mjs +518 -0
  43. package/pipeline/scripts/_path-match.mjs +87 -0
  44. package/pipeline/scripts/anonymize-findings.mjs +1 -1
  45. package/pipeline/scripts/code-graph-rules/android.json +130 -0
  46. package/pipeline/scripts/code-graph-rules/ios.json +95 -0
  47. package/pipeline/scripts/code-graph-rules/node.json +151 -0
  48. package/pipeline/scripts/code-graph-rules/python.json +91 -0
  49. package/pipeline/scripts/graph-affected.mjs +161 -0
  50. package/pipeline/scripts/graph-build.mjs +157 -0
  51. package/pipeline/scripts/graph-query.mjs +191 -0
  52. package/pipeline/scripts/graph-report.mjs +237 -0
  53. package/pipeline/scripts/smoke-cross-cli-behavior.sh +13 -10
  54. package/pipeline/scripts/test-gap-rules/ios.json +38 -10
  55. package/pipeline/scripts/test-gap-scan.mjs +2 -21
  56. package/pipeline/scripts/uninstall.mjs +11 -2
  57. package/pipeline/scripts/validate-analysis-doc.mjs +85 -23
  58. package/pipeline/scripts/validate-code-graph.mjs +174 -0
  59. package/pipeline/skills/.skills-index.json +14 -3
  60. package/pipeline/skills/shared/README.md +6 -5
  61. package/pipeline/skills/shared/core/multi-agent/SKILL.md +3 -3
  62. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +106 -0
  63. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
  64. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +2 -2
  65. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +11 -11
  66. package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +4 -4
  67. package/pipeline/skills/skills-index.md +4 -3
@@ -42,6 +42,7 @@ import { readFileSync, existsSync, readdirSync } from "node:fs";
42
42
  import { execFileSync } from "node:child_process";
43
43
  import { join, dirname, extname, basename } from "node:path";
44
44
  import { fileURLToPath } from "node:url";
45
+ import { isExcludedBy } from "./_path-match.mjs";
45
46
 
46
47
  const __dirname = dirname(fileURLToPath(import.meta.url));
47
48
 
@@ -118,27 +119,7 @@ function loadDiff() {
118
119
  }
119
120
 
120
121
  function isExcluded(path) {
121
- if (!Array.isArray(rules.excludePathGlobs)) return false;
122
- for (const pat of rules.excludePathGlobs) {
123
- if (pat.endsWith("/")) {
124
- if (path.includes(pat) || path.startsWith(pat)) return true;
125
- } else if (pat.includes("**")) {
126
- // Tokenize ** before the single-* pass so the * rewrite cannot mangle
127
- // the already-translated .* (which broke cross-directory matches).
128
- const DOUBLE_STAR = "\u0000";
129
- const re = new RegExp(
130
- "^" +
131
- pat
132
- .replace(/\./g, "\\.")
133
- .replace(/\*\*/g, DOUBLE_STAR)
134
- .replace(/\*/g, "[^/]*")
135
- .replace(new RegExp(DOUBLE_STAR, "g"), ".*") +
136
- "$",
137
- );
138
- if (re.test(path)) return true;
139
- } else if (path.endsWith(pat)) return true;
140
- }
141
- return false;
122
+ return isExcludedBy(path, rules.excludePathGlobs);
142
123
  }
143
124
 
144
125
  // git quotes a `diff --git` path (core.quotePath, on by default) whenever it
@@ -32,6 +32,7 @@
32
32
  * - $HOME/.claude/multi-agent-preferences.json (pipeline settings)
33
33
  * - $HOME/.claude/logs/multi-agent/ (task logs, state, metrics, audit trail)
34
34
  * - $HOME/.claude/memory/multi-agent/ (per-repo learnings ledger + triage corpus)
35
+ * - $HOME/.claude/knowledge/ (per-repo knowledge base + code graph)
35
36
  *
36
37
  * @module pipeline/scripts/runtime/uninstall
37
38
  */
@@ -726,6 +727,7 @@ export async function main() {
726
727
  console.log(" - ~/.claude/multi-agent-preferences.json (pipeline settings)");
727
728
  console.log(" - ~/.claude/logs/multi-agent/ (task logs, state, metrics, audit trail)");
728
729
  console.log(" - ~/.claude/memory/multi-agent/ (per-repo learnings ledger + triage corpus)");
730
+ console.log(" - ~/.claude/knowledge/ (per-repo knowledge base + code graph)");
729
731
  console.log("");
730
732
  }
731
733
  console.log(" PRESERVED (never touched):");
@@ -834,12 +836,19 @@ export async function main() {
834
836
  // architectural facts and past review findings written in prose about the
835
837
  // repos they were collected from. On a corporate checkout that is concrete
836
838
  // information about the codebase, and it survived even --all-data.
839
+ //
840
+ // The knowledge root is the same class of data and was missing for the same
841
+ // reason: architecture.md, patterns.md, gotchas.md and decisions.md are prose
842
+ // about a private codebase, and code-graph.json is every symbol name in it.
843
+ // Leaving that behind after "remove everything" is the worst version of this
844
+ // bug, because the graph is the most complete of the three.
837
845
  if (allData && HOME) {
838
846
  console.log("");
839
- console.log(" [Full cleanup] Removing pipeline settings, logs + memory...");
847
+ console.log(" [Full cleanup] Removing pipeline settings, logs, memory + knowledge...");
840
848
  preservePrefsCredentialMapping(join(HOME, ".claude", "multi-agent-preferences.json"));
841
849
  rmIfExists(join(HOME, ".claude", "logs", "multi-agent"));
842
850
  rmIfExists(join(HOME, ".claude", "memory", "multi-agent"));
851
+ rmIfExists(join(HOME, ".claude", "knowledge"));
843
852
  }
844
853
 
845
854
  // Legacy adapter-file cleanup (adapters removed in v10.7.0). These blocks
@@ -917,7 +926,7 @@ export async function main() {
917
926
  if (!allData) {
918
927
  console.log("");
919
928
  console.log(
920
- " Kept your pipeline settings, logs + per-repo memory. To remove those too, re-run with --all-data.",
929
+ " Kept your pipeline settings, logs, per-repo memory + knowledge base. To remove those too, re-run with --all-data.",
921
930
  );
922
931
  } else {
923
932
  console.log("");
@@ -215,11 +215,15 @@ function main() {
215
215
  }
216
216
  }
217
217
 
218
- // 2a. Corporate profile: every EKLENECEK owes an open question.
218
+ // 2a. Corporate profile: every EKLENECEK owes an open question, by id.
219
219
  //
220
220
  // A placeholder with nobody assigned to resolve it is how "to be filled in"
221
- // reaches production. The pairing is what makes the backbone guarantee worth
222
- // having: the section renders, AND the gap it admits to is tracked.
221
+ // reaches production. This used to pair a gap with Section 20 by matching the
222
+ // section NUMBER inside the risks block, which is ambiguous in both directions:
223
+ // "9" is a substring of "19" and "5.9" of "5.9.5", so the regex grew guards for
224
+ // cases it still could not separate. Stable ids remove the ambiguity the same
225
+ // way IG / UC / FG already do: the body writes EKLENECEK (AS-07) and Section 20
226
+ // defines AS-07, and each side is checked against the other.
223
227
  if (profile === "corporate") {
224
228
  const lines = text.split("\n");
225
229
  const risksStart = lines.findIndex((l) =>
@@ -232,29 +236,61 @@ function main() {
232
236
  risksBlock += `${lines[i]}\n`;
233
237
  }
234
238
  }
235
- let currentSection = null;
236
- const unpaired = new Set();
239
+ const AS_ID = /\bAS-(\d{2,3})\b/g;
240
+ const definedIds = new Set(
241
+ [...risksBlock.matchAll(AS_ID)].map((m) => `AS-${m[1]}`),
242
+ );
243
+ const referencedIds = new Set();
237
244
  for (let i = 0; i < lines.length; i++) {
238
- const m = lines[i].match(/^#{2,4}\s+(\d+(?:\.\d+)*)\.?\s/);
239
- if (m) currentSection = m[1];
240
- if (!lines[i].includes("EKLENECEK")) continue;
241
245
  if (risksStart !== -1 && i > risksStart) continue;
242
- if (!currentSection) continue;
243
- // The number has to stand alone: substring matching would let section "2"
244
- // be satisfied by a "2.3" in some other row, and "9" by a "19". The
245
- // trailing guard rejects a following digit and a following ".<digit>",
246
- // but NOT a sentence-ending period - "etkilediği bölüm 9." is a real
247
- // pairing, and rejecting it would block a correct document.
248
- const token = new RegExp(
249
- `(^|[^\\d.])${currentSection.replace(/\./g, "\\.")}(?!\\d|\\.\\d)`,
250
- "m",
251
- );
252
- if (!token.test(risksBlock)) unpaired.add(currentSection);
246
+ for (const m of lines[i].matchAll(AS_ID)) referencedIds.add(`AS-${m[1]}`);
247
+ if (!lines[i].includes("EKLENECEK")) continue;
248
+ // A gap that names no id cannot be traced to an owner, which is the whole
249
+ // point of admitting it.
250
+ if (!/\bAS-\d{2,3}\b/.test(lines[i])) {
251
+ errors.push(
252
+ `line ${i + 1}: EKLENECEK carries no AS-NN reference (Locked 33)`,
253
+ );
254
+ }
253
255
  }
254
- for (const sec of unpaired) {
255
- errors.push(
256
- `section ${sec} carries EKLENECEK but no Risks and Open Questions row references it (Locked 33)`,
257
- );
256
+ for (const id of referencedIds) {
257
+ if (!definedIds.has(id)) {
258
+ errors.push(
259
+ `${id} is referenced in the body but not defined in Risks and Open Questions (Locked 33)`,
260
+ );
261
+ }
262
+ }
263
+ for (const id of definedIds) {
264
+ if (!referencedIds.has(id)) {
265
+ // An open question with no body reference is allowed only when the row
266
+ // says so; otherwise it is a question about nothing the document raises.
267
+ const row = risksBlock
268
+ .split("\n")
269
+ .find((l) => l.includes(id)) || "";
270
+ if (!/(no body reference|govde referansi yok)/i.test(row)) {
271
+ errors.push(
272
+ `${id} is defined in Risks and Open Questions but never referenced in the body (Locked 33)`,
273
+ );
274
+ }
275
+ }
276
+ }
277
+
278
+ // 2b. JSON does not fit in a table cell (Locked 37).
279
+ //
280
+ // One document rendered `Request` as inline JSON with a field note glued to
281
+ // the end, and `Response` as a proper fenced block below the table: two
282
+ // formats for the same thing, one table apart. The payload belongs under the
283
+ // table; the cell says which of the three states it is in.
284
+ for (let i = 0; i < lines.length; i++) {
285
+ const line = lines[i];
286
+ if (!/^\|\s*\*\*(Request|Response)\*\*\s*\|/.test(line)) continue;
287
+ const cell = line.split("|")[2] || "";
288
+ if (/[{}]/.test(cell)) {
289
+ errors.push(
290
+ `line ${i + 1}: ${/Request/.test(line) ? "Request" : "Response"} cell holds JSON; ` +
291
+ "put the payload in a fenced block under the table (Locked 37)",
292
+ );
293
+ }
258
294
  }
259
295
 
260
296
  // The traceability matrix is the most expensive thing in the document to get
@@ -295,6 +331,32 @@ function main() {
295
331
  }
296
332
  }
297
333
 
334
+ // 2c. Diagram labels keep their diacritics.
335
+ //
336
+ // The punctuation gate skips fenced blocks, so a mermaid diagram is the one
337
+ // place a Turkish document can quietly go ASCII: one run shipped `Hayir` and
338
+ // `Basarili` in its flow chart while every other line was correct. A warning,
339
+ // not an error - node ids are legitimately ASCII, only labels are checked.
340
+ if (parsed.fm.language === "tr") {
341
+ const mmLines = text.split("\n");
342
+ let inMermaid = false;
343
+ for (let i = 0; i < mmLines.length; i++) {
344
+ const t = mmLines[i].trim();
345
+ if (/^```mermaid\s*$/.test(t)) { inMermaid = true; continue; }
346
+ if (inMermaid && t === "```") { inMermaid = false; continue; }
347
+ if (!inMermaid) continue;
348
+ const labels = [...mmLines[i].matchAll(/[[({|]([^\]})|]{3,})[\]})|]/g)].map((m) => m[1]);
349
+ for (const lbl of labels) {
350
+ if (/^[\x20-\x7E]+$/.test(lbl) && /\b(Hayir|Basarili|Basarisiz|Odeme|Iptal|Gecerli|Gecersiz|Secim|Dogrulama|Uyari|Baslangic|Bitis|Onay|Aciklama)\b/.test(lbl)) {
351
+ errors.push(
352
+ `WARN line ${i + 1}: diagram label "${lbl}" looks like Turkish flattened to ASCII (Locked 7)`,
353
+ );
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+
298
360
  // 2b. Opt-in coverage sections must be present when the front-matter says so.
299
361
  const uiTests = String(parsed?.fm?.ui_tests || "false").toLowerCase() === "true";
300
362
  const a11yDepth = String(parsed?.fm?.a11y_depth || "basic").toLowerCase();
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ // validate-code-graph.mjs - schema validator for graph-build.mjs output.
3
+ //
4
+ // Zero deps (ADR-0004). Reads JSON from path or STDIN, validates against
5
+ // pipeline/schemas/code-graph.schema.json.
6
+ //
7
+ // Edge endpoints are checked against the node set, not just for type: a graph
8
+ // whose edges point at ids no node declares still parses as valid JSON, but
9
+ // every traversal built on it silently drops those hops.
10
+ //
11
+ // Exit codes:
12
+ // 0 - valid
13
+ // 1 - invalid
14
+ // 64 - usage error
15
+
16
+ import { readFileSync } from "node:fs";
17
+
18
+ const ALLOWED_STACK = new Set(["ios", "android", "node", "python"]);
19
+ const ALLOWED_NODE_KIND = new Set(["file", "symbol", "module"]);
20
+ const ALLOWED_EDGE_KIND = new Set(["defines", "imports", "references"]);
21
+ const MAX_REPORTED_DANGLING = 5;
22
+
23
+ function readInput() {
24
+ const arg = process.argv[2];
25
+ if (!arg) {
26
+ console.error("error: provide a path or '-' for STDIN");
27
+ process.exit(64);
28
+ }
29
+ if (arg === "-") {
30
+ return new Promise((resolve, reject) => {
31
+ const chunks = [];
32
+ process.stdin.setEncoding("utf-8");
33
+ process.stdin.on("data", (c) => chunks.push(c));
34
+ process.stdin.on("end", () => resolve(chunks.join("")));
35
+ process.stdin.on("error", reject);
36
+ });
37
+ }
38
+ return Promise.resolve(readFileSync(arg, "utf-8"));
39
+ }
40
+
41
+ function validateStats(obj, errors) {
42
+ if (typeof obj.stats !== "object" || obj.stats === null || Array.isArray(obj.stats)) {
43
+ errors.push("stats must be an object");
44
+ return;
45
+ }
46
+ for (const key of ["files", "nodes", "edges"]) {
47
+ if (!Number.isInteger(obj.stats[key]) || obj.stats[key] < 0) {
48
+ errors.push(`stats.${key} must be a non-negative integer`);
49
+ }
50
+ }
51
+ if (Array.isArray(obj.nodes) && obj.stats.nodes !== obj.nodes.length) {
52
+ errors.push(
53
+ `stats.nodes (${obj.stats.nodes}) does not match nodes.length (${obj.nodes.length})`,
54
+ );
55
+ }
56
+ if (Array.isArray(obj.edges) && obj.stats.edges !== obj.edges.length) {
57
+ errors.push(
58
+ `stats.edges (${obj.stats.edges}) does not match edges.length (${obj.edges.length})`,
59
+ );
60
+ }
61
+ }
62
+
63
+ function validateNodes(obj, errors) {
64
+ if (!Array.isArray(obj.nodes)) {
65
+ errors.push("nodes must be an array");
66
+ return new Set();
67
+ }
68
+ const ids = new Set();
69
+ obj.nodes.forEach((n, i) => {
70
+ const p = `nodes[${i}]`;
71
+ if (typeof n !== "object" || n === null) {
72
+ errors.push(`${p} must be an object`);
73
+ return;
74
+ }
75
+ if (typeof n.id !== "string" || n.id.length === 0) errors.push(`${p}.id must be a string`);
76
+ else if (ids.has(n.id)) errors.push(`${p}.id is duplicated: ${n.id}`);
77
+ else ids.add(n.id);
78
+ if (!ALLOWED_NODE_KIND.has(n.kind))
79
+ errors.push(`${p}.kind must be one of ${[...ALLOWED_NODE_KIND].join(",")}`);
80
+ if (typeof n.name !== "string") errors.push(`${p}.name must be a string`);
81
+ if (!Number.isInteger(n.degree) || n.degree < 0)
82
+ errors.push(`${p}.degree must be a non-negative integer`);
83
+ });
84
+ return ids;
85
+ }
86
+
87
+ function validateEdges(obj, ids, errors) {
88
+ if (!Array.isArray(obj.edges)) {
89
+ errors.push("edges must be an array");
90
+ return;
91
+ }
92
+ const dangling = [];
93
+ obj.edges.forEach((e, i) => {
94
+ const p = `edges[${i}]`;
95
+ if (typeof e !== "object" || e === null) {
96
+ errors.push(`${p} must be an object`);
97
+ return;
98
+ }
99
+ if (typeof e.from !== "string") errors.push(`${p}.from must be a string`);
100
+ if (typeof e.to !== "string") errors.push(`${p}.to must be a string`);
101
+ if (!ALLOWED_EDGE_KIND.has(e.kind))
102
+ errors.push(`${p}.kind must be one of ${[...ALLOWED_EDGE_KIND].join(",")}`);
103
+ if (ids.size) {
104
+ if (typeof e.from === "string" && !ids.has(e.from)) dangling.push(`${p}.from -> ${e.from}`);
105
+ if (typeof e.to === "string" && !ids.has(e.to)) dangling.push(`${p}.to -> ${e.to}`);
106
+ }
107
+ });
108
+ if (dangling.length) {
109
+ errors.push(
110
+ `${dangling.length} edge endpoint(s) reference no node: ` +
111
+ dangling.slice(0, MAX_REPORTED_DANGLING).join("; ") +
112
+ (dangling.length > MAX_REPORTED_DANGLING ? "; ..." : ""),
113
+ );
114
+ }
115
+ }
116
+
117
+ function validateManifest(obj, errors) {
118
+ if (obj.manifest === undefined) return;
119
+ if (typeof obj.manifest !== "object" || obj.manifest === null || Array.isArray(obj.manifest)) {
120
+ errors.push("manifest must be an object");
121
+ return;
122
+ }
123
+ for (const [path, entry] of Object.entries(obj.manifest)) {
124
+ if (typeof entry !== "object" || entry === null) {
125
+ errors.push(`manifest["${path}"] must be an object`);
126
+ continue;
127
+ }
128
+ if (!Number.isInteger(entry.size) || entry.size < 0)
129
+ errors.push(`manifest["${path}"].size must be a non-negative integer`);
130
+ if (!Number.isInteger(entry.mtimeMs) || entry.mtimeMs < 0)
131
+ errors.push(`manifest["${path}"].mtimeMs must be a non-negative integer`);
132
+ }
133
+ }
134
+
135
+ export function validate(obj) {
136
+ const errors = [];
137
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj))
138
+ return ["root must be an object"];
139
+
140
+ if (obj.schemaVersion !== "1.0.0") errors.push(`schemaVersion must be "1.0.0"`);
141
+ if (!ALLOWED_STACK.has(obj.stack))
142
+ errors.push(`stack must be one of ${[...ALLOWED_STACK].join(",")}`);
143
+ if (typeof obj.root !== "string" || obj.root.length === 0)
144
+ errors.push("root must be a non-empty string");
145
+ if (typeof obj.generatedAt !== "string" || Number.isNaN(Date.parse(obj.generatedAt)))
146
+ errors.push("generatedAt must be an ISO 8601 timestamp");
147
+ if (obj.baseCommit !== undefined && obj.baseCommit !== null && typeof obj.baseCommit !== "string")
148
+ errors.push("baseCommit must be a string or null");
149
+
150
+ validateStats(obj, errors);
151
+ const ids = validateNodes(obj, errors);
152
+ validateEdges(obj, ids, errors);
153
+ validateManifest(obj, errors);
154
+
155
+ return errors;
156
+ }
157
+
158
+ (async () => {
159
+ const raw = await readInput();
160
+ let obj;
161
+ try {
162
+ obj = JSON.parse(raw);
163
+ } catch (e) {
164
+ console.log(JSON.stringify({ ok: false, errors: [`JSON parse error: ${e.message}`] }));
165
+ process.exit(1);
166
+ }
167
+ const errors = validate(obj);
168
+ if (errors.length === 0) {
169
+ console.log(JSON.stringify({ ok: true, errors: [] }));
170
+ process.exit(0);
171
+ }
172
+ console.log(JSON.stringify({ ok: false, errors }));
173
+ process.exit(1);
174
+ })();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "skillCount": 210,
3
+ "skillCount": 211,
4
4
  "entries": [
5
5
  {
6
6
  "name": "accessibility-compliance-accessibility-audit",
@@ -950,7 +950,7 @@
950
950
  },
951
951
  {
952
952
  "name": "multi-agent",
953
- "description": "Task orchestrator: runs the full pipeline from a Jira ID or GitHub Issue URL - analysis → plan → TDD development → parallel review (Fable + Sonnet on Claude Code, GPT + Opus + Sonnet on Copilot CLI) → commit → log. Every step is written to agent-log.md. Use when given a Jira ID, a GitHub issue or ",
953
+ "description": "Task orchestrator: runs the full pipeline from a Jira ID or GitHub Issue URL - analysis → plan → TDD development → parallel review (Fable + Opus + Sonnet on Claude Code, GPT + Opus + Sonnet on Copilot CLI) → commit → log. Every step is written to agent-log.md. Use when given a Jira ID, a GitHub is",
954
954
  "platform": null,
955
955
  "group": "core",
956
956
  "plugin": null,
@@ -1135,6 +1135,17 @@
1135
1135
  "triggerPaths": [],
1136
1136
  "relativePath": "shared/core/multi-agent-garbage-collect/SKILL.md"
1137
1137
  },
1138
+ {
1139
+ "name": "multi-agent-graph",
1140
+ "description": "Build and query this repo's code graph: a deterministic, LLM-free map of symbols, imports and references used to narrow Phase 1's Explore scope and to keep the knowledge base current. Read-only on code, costs no API tokens. Use when a task needs to know where something lives or what depends on it be",
1141
+ "platform": null,
1142
+ "group": "core",
1143
+ "plugin": null,
1144
+ "invokeAs": "multi-agent-graph",
1145
+ "triggerKeywords": [],
1146
+ "triggerPaths": [],
1147
+ "relativePath": "shared/core/multi-agent-graph/SKILL.md"
1148
+ },
1138
1149
  {
1139
1150
  "name": "multi-agent-help",
1140
1151
  "description": "Multi-agent pipeline usage guide - renders in EN or TR per prefs.global.outputLanguage (falls back to promptLanguage for backward compatibility). Use when asked what the pipeline can do, which mode to pick, or what a command means.",
@@ -1313,7 +1324,7 @@
1313
1324
  },
1314
1325
  {
1315
1326
  "name": "multi-agent-review",
1316
- "description": "Run parallel review on a branch diff or a Pull Request: 2 models on Claude Code (Fable + Sonnet), 3 models on Copilot CLI (GPT + Opus + Sonnet). On PR input, posts per-finding inline comments + approve/needs-work. With no input (interactive), lists open GitHub + Bitbucket PRs to multi-select. Use wh",
1327
+ "description": "Run parallel review on a branch diff or a Pull Request: 3 models on Claude Code (Fable + Opus + Sonnet), 3 models on Copilot CLI (GPT + Opus + Sonnet). On PR input, posts per-finding inline comments + approve/needs-work. With no input (interactive), lists open GitHub + Bitbucket PRs to multi-select.",
1317
1328
  "platform": null,
1318
1329
  "group": "core",
1319
1330
  "plugin": null,
@@ -2,11 +2,11 @@
2
2
 
3
3
  Single source of truth for skills delivered to both Claude Code (`~/.claude/skills/`) and Copilot CLI (`~/.copilot/skills/`) by the installer.
4
4
 
5
- **Total:** 210 skills (56 core + 154 external). Auto-generated by `scripts/gen-skills-index.mjs` - do not edit by hand.
5
+ **Total:** 211 skills (57 core + 154 external). Auto-generated by `scripts/gen-skills-index.mjs` - do not edit by hand.
6
6
 
7
7
  ## Directory layout
8
8
 
9
- - **`core/`** - 56 `multi-agent*` orchestration skills that are pipeline-critical. Edits here are core-code changes.
9
+ - **`core/`** - 57 `multi-agent*` orchestration skills that are pipeline-critical. Edits here are core-code changes.
10
10
  - **`external/`** - 154 iOS / Android / generic skills imported from the upstream skill library. Mirrors of third-party guidance.
11
11
  - Install destinations (ADR-0009): Claude Code gets NO local copy of `external/` - it loads those skills from the `multi-agent-plugins` marketplace, namespaced (`ai-<stack>-toolkit:<name>`); only the two compliance catalogs from `core/` land in `~/.claude/skills/`. Copilot CLI and Codex CLI receive a flat copy filtered to the enabled stacks. `external/` remains the single authoring source that `build-stack-plugins.mjs` publishes from.
12
12
 
@@ -14,7 +14,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
14
14
 
15
15
  ## Categories
16
16
 
17
- - [Pipeline Orchestration](#pipeline-orchestration) - 56
17
+ - [Pipeline Orchestration](#pipeline-orchestration) - 57
18
18
  - [iOS / Apple Ecosystem](#ios-apple-ecosystem) - 90
19
19
  - [Android / Kotlin](#android-kotlin) - 13
20
20
  - [Web](#web) - 10
@@ -44,6 +44,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
44
44
  | [`multi-agent-feedback`](./core/multi-agent-feedback/) | `core` | Send one message to the maintainer: a bug, an idea or a question. Only the text you type is sent - no logs, no repo names, no paths. Shows t |
45
45
  | [`multi-agent-forget`](./core/multi-agent-forget/) | `core` | Remove a saved /multi-agent routine (created by /multi-agent:save): deletes its local-only command and its registry entry. Asks which one an |
46
46
  | [`multi-agent-garbage-collect`](./core/multi-agent-garbage-collect/) | `core` | Sweep leftover /tmp scratch (picker state, review diffs, channel payloads, analysis drafts) from past runs. Dry-run first; confirms before d |
47
+ | [`multi-agent-graph`](./core/multi-agent-graph/) | `core` | Build and query this repo's code graph: a deterministic, LLM-free map of symbols, imports and references used to narrow Phase 1's Explore sc |
47
48
  | [`multi-agent-help`](./core/multi-agent-help/) | `core` | Multi-agent pipeline usage guide - renders in EN or TR per prefs.global.outputLanguage (falls back to promptLanguage for backward compatib |
48
49
  | [`multi-agent-ios-coding-standard`](./core/multi-agent-ios-coding-standard/) | `core` | Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to dev/dev-l |
49
50
  | [`multi-agent-issue`](./core/multi-agent-issue/) | `core` | List unassigned GitHub issues, pick one, auto-assign, and launch the multi-agent pipeline. Use when a GitHub issue should be picked up and s |
@@ -60,7 +61,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
60
61
  | [`multi-agent-refactor`](./core/multi-agent-refactor/) | `core` | Analyse the project: extract adapted best-practices, hunt real bugs + improvement areas, check upstream drift of derived skills, research th |
61
62
  | [`multi-agent-resume`](./core/multi-agent-resume/) | `core` | Resume a stopped or failed task from the phase where it left off. Use when a task stopped or failed and should carry on from where it left o |
62
63
  | [`multi-agent-resume-local`](./core/multi-agent-resume-local/) | `core` | Continue already-done LOCAL work through the pipeline tail: Review → Build+Test → Commit/PR → Report (technical analysis + Jira test-scenari |
63
- | [`multi-agent-review`](./core/multi-agent-review/) | `core` | Run parallel review on a branch diff or a Pull Request: 2 models on Claude Code (Fable + Sonnet), 3 models on Copilot CLI (GPT + Opus + Sonn |
64
+ | [`multi-agent-review`](./core/multi-agent-review/) | `core` | Run parallel review on a branch diff or a Pull Request: 3 models on Claude Code (Fable + Opus + Sonnet), 3 models on Copilot CLI (GPT + Opus |
64
65
  | [`multi-agent-review-analysis`](./core/multi-agent-review-analysis/) | `core` | Review a written analysis document instead of a diff: resolve it from a path, a Confluence page or a Jira issue, run the deterministic gates |
65
66
  | [`multi-agent-review-issue`](./core/multi-agent-review-issue/) | `core` | Assess whether a GitHub issue is ready for multi-agent development: fetch it, grade scope / acceptance criteria / repro / design / API / sta |
66
67
  | [`multi-agent-review-jira`](./core/multi-agent-review-jira/) | `core` | Assess whether a Jira issue is ready for multi-agent development: fetch it, grade scope / acceptance criteria / repro / design / API / stack |
@@ -258,6 +259,6 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
258
259
  | [`xcode-build-benchmark`](./external/xcode-build-benchmark/) | `external` | Benchmark Xcode clean and incremental builds with repeatable inputs, timing summaries, and timestamped `.build-benchmark/` artifacts. Use wh |
259
260
  | [`xcode-build-fixer`](./external/xcode-build-fixer/) | `external` | Apply approved Xcode build optimization changes following best practices, then re-benchmark to verify improvement. Use when a developer has |
260
261
  | [`xcode-build-orchestrator`](./external/xcode-build-orchestrator/) | `external` | Orchestrate Xcode build optimization by benchmarking first, running the specialist analysis skills, prioritizing findings, requesting explic |
261
- | [`xcode-compilation-analyzer`](./external/xcode-compilation-analyzer/) | `external` | Analyze Swift and mixed-language compile hotspots using build timing summaries and Swift web diagnostics, then produce a recommend-firs |
262
+ | [`xcode-compilation-analyzer`](./external/xcode-compilation-analyzer/) | `external` | Analyze Swift and mixed-language compile hotspots using build timing summaries and Swift frontend diagnostics, then produce a recommend-firs |
262
263
  | [`xcode-project-analyzer`](./external/xcode-project-analyzer/) | `external` | Audit Xcode project configuration, build settings, scheme behavior, and script phases to find build-time improvements with explicit approval |
263
264
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: multi-agent
3
3
  language: en
4
- description: "Task orchestrator: runs the full pipeline from a Jira ID or GitHub Issue URL - analysis → plan → TDD development → parallel review (Fable + Sonnet on Claude Code, GPT + Opus + Sonnet on Copilot CLI) → commit → log. Every step is written to agent-log.md. Use when given a Jira ID, a GitHub issue or a free-text task and the whole pipeline should run."
4
+ description: "Task orchestrator: runs the full pipeline from a Jira ID or GitHub Issue URL - analysis → plan → TDD development → parallel review (Fable + Opus + Sonnet on Claude Code, GPT + Opus + Sonnet on Copilot CLI) → commit → log. Every step is written to agent-log.md. Use when given a Jira ID, a GitHub issue or a free-text task and the whole pipeline should run."
5
5
  user-invocable: true
6
6
  argument-hint: '"PROJ-12345" "feature/PROJ-12345-flight-filter" | "https://github.com/.../issues/316" | status | log #1 | resume #1 | kill #1 | clear-logs | purge | review'
7
7
  ---
@@ -455,7 +455,7 @@ For each todo (respecting dependency order):
455
455
  ### Phase 4: Review (parallel + triage)
456
456
  0. **Diff Risk Scoring (advisory, v8.3+)** - before reviewer dispatch run `node $HOME/.claude/scripts/diff-risk-score.mjs --base "$BASE_BRANCH" --top 5` and inject the top-N risk-ranked files as a `${PRIORITY_FILES}` block into each reviewer's prompt. Heuristic, deterministic, sub-second, never gates the pipeline. Disabled when `prefs.global.diffRiskAdvisory = false`. Signals: security paths (×3), schema migrations (×4), public API surfaces (×2), no-test-change (×2.5), complexity delta (×1.5), UI-critical paths (×1.5), loc changed (×1).
457
457
  1. Launch **code-reviewer** agents in parallel. Reviewer set depends on which CLI is hosting the pipeline:
458
- - **Claude Code** (2 reviewers): `claude-fable-5` (deep security + architecture) + `claude-sonnet-5` (quality + correctness)
458
+ - **Claude Code** (3 reviewers): `claude-fable-5` (deep security + architecture) + `claude-sonnet-5` (quality + correctness)
459
459
  - **Copilot CLI** (3 reviewers): `gpt-5.4` (edge cases, different perspective) + `claude-opus-5` + `claude-sonnet-5` (Fable 5 is not offered on Copilot CLI)
460
460
  - Triage: single top-tier pass over merged findings (`claude-fable-5` on Claude Code, `claude-opus-5` on Copilot CLI)
461
461
  2. Collect findings, classify:
@@ -656,7 +656,7 @@ Phase 3: Dev
656
656
  └─ TDD, token rules, code style - from global instructions
657
657
 
658
658
  Phase 4: Review (parallel + triage)
659
- Claude Code (2 parallel):
659
+ Claude Code (3 parallel):
660
660
  ├─ code-reviewer (claude-opus) → all 5 review skills
661
661
  └─ code-reviewer (claude-sonnet) → all 5 review skills
662
662
  Copilot CLI (3 parallel):
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: multi-agent-graph
3
+ language: en
4
+ description: "Build and query this repo's code graph: a deterministic, LLM-free map of symbols, imports and references used to narrow Phase 1's Explore scope and to keep the knowledge base current. Read-only on code, costs no API tokens. Use when a task needs to know where something lives or what depends on it before reading files."
5
+ user-invocable: true
6
+ argument-hint: "[build | refresh | ask \"<question>\" | affected \"<symbol>\" | report | status]"
7
+ ---
8
+ # multi-agent-graph - code graph build and query
9
+
10
+ **Input**: $ARGUMENTS
11
+
12
+ A code graph is a map of what this repo declares and what refers to what, built by
13
+ regex over comment-stripped source. It exists so Phase 1 can narrow its Explore
14
+ fan-out and Phase 7 can refresh `~/.claude/knowledge/<project>/` without an LLM
15
+ pass. It answers "where does this live" and "what depends on this". It does not
16
+ answer "what calls this at runtime": call graphs and type resolution need a real
17
+ parser, which would be an npm runtime dependency, and ADR-0004 forbids one.
18
+
19
+ No worktree, no branch, no commit, no pipeline chaining.
20
+
21
+ ## Sub-commands
22
+
23
+ | Input | Runs | Notes |
24
+ |---|---|---|
25
+ | `build` | `graph-build.mjs --root <repo> --stack <stack>` | Writes `~/.claude/knowledge/<project>/code-graph.json` |
26
+ | `refresh` | same as `build` | A full rebuild takes seconds, so there is no separate incremental path |
27
+ | `ask "<question>"` | `graph-query.mjs "<question>" --budget N` | Token-budgeted traversal; default budget 2000 |
28
+ | `affected "<symbol>"` | `graph-affected.mjs "<symbol>" --depth N` | Reverse traversal: the blast radius of a change |
29
+ | `report` | `graph-report.mjs` | Writes `GRAPH_REPORT.md` beside the graph |
30
+ | `status` | `graph-report.mjs --status` | One line: stack, scale, build time and whether `baseCommit` still matches HEAD. Never read the graph file yourself - it is 22MB on a large repo |
31
+
32
+ With no argument, run `status`, then offer `build` when no graph exists and
33
+ `refresh` when `baseCommit` differs from the current HEAD.
34
+
35
+ ## Steps
36
+
37
+ 1. **Resolve the repo.** `PROJECT_ROOT` is the current repo root unless the user
38
+ named another. Derive the graph path once and pass it to every call below:
39
+
40
+ ```bash
41
+ GRAPH_PATH="$HOME/.claude/knowledge/$(basename "$PROJECT_ROOT")/code-graph.json"
42
+ ```
43
+
44
+ The query, affected and report scripts default that path from the CWD's
45
+ basename, which is not the same thing when the shell sits in a worktree or a
46
+ sub-package, so a defaulted path can point at a graph that was never built.
47
+
48
+ 2. **Resolve the stack.** Read `state.detectedStack` when a run is in flight;
49
+ otherwise detect from project markers the way Phase 1 Step 2 does. Only stacks
50
+ with a file in `$HOME/.claude/scripts/code-graph-rules/` can be built. A stack with
51
+ no rule file is reported as unsupported, never guessed at.
52
+
53
+ 3. **Run the sub-command.** Every script is read-only on the repo and writes only
54
+ under `~/.claude/knowledge/`:
55
+
56
+ ```bash
57
+ node $HOME/.claude/scripts/graph-build.mjs --root "$PROJECT_ROOT" --stack "$STACK" --out "$GRAPH_PATH"
58
+ node $HOME/.claude/scripts/graph-query.mjs "<question>" --graph "$GRAPH_PATH" --budget 2000
59
+ node $HOME/.claude/scripts/graph-affected.mjs "<symbol>" --graph "$GRAPH_PATH" --depth 2
60
+ node $HOME/.claude/scripts/graph-report.mjs --graph "$GRAPH_PATH"
61
+ node $HOME/.claude/scripts/graph-report.mjs --graph "$GRAPH_PATH" --status
62
+ ```
63
+
64
+ 4. **Validate after a build.** A graph that parses but whose edges point at
65
+ missing nodes yields silently truncated traversals, so the build is not
66
+ reported as successful until the validator agrees:
67
+
68
+ ```bash
69
+ node $HOME/.claude/scripts/validate-code-graph.mjs "$GRAPH_PATH"
70
+ ```
71
+
72
+ A non-zero exit fails CLOSED: report the validator's `errors[]` verbatim and
73
+ do not record the graph as usable.
74
+
75
+ 5. **Report.** One line for a build (`files / nodes / edges / elapsed`), the
76
+ traversal output as-is for `ask` and `affected`, the report path for `report`.
77
+
78
+ ## What the output is for
79
+
80
+ `ask` returns ranked nodes plus their neighbourhood within a token budget. Feed
81
+ it to an Explore agent as the starting file set rather than pasting it into a
82
+ final answer: it is a search result, not an explanation.
83
+
84
+ `affected` returns dependents, which is what `analysis.touchedAreas[]` wants.
85
+
86
+ ## Limits worth stating when reporting
87
+
88
+ - References resolve only when a name maps to exactly one declaration. A type
89
+ declared in two files is ambiguous and is deliberately dropped, so `affected`
90
+ under-reports for duplicated names rather than fanning out to every candidate.
91
+ - Only type-like symbols are reference targets. Functions appear in the graph
92
+ through their declaring file, not as targets, because a bare lowercase name
93
+ matched across files is almost never a call to that exact declaration.
94
+ - Comments and string literals are stripped before extraction, so a name that
95
+ appears only in prose or in a string produces no edge.
96
+ - A nested declaration is a node but never a reference target. A Kotlin sealed
97
+ case or a Python inner class named `Icon` or `Color` is declared exactly once,
98
+ so the ambiguity rule above does not catch it, and every file that merely
99
+ mentions the framework type of that name would otherwise gain an edge to it.
100
+ - On stacks whose exported unit is a function (Node most of all) the symbol
101
+ layer is thin by design and the import graph between files carries the value.
102
+ Ask `affected "<file>.mjs"` there, not `affected "<functionName>"`.
103
+ - `affected` at `--depth 1` returns direct symbol references only. An import
104
+ whose module name matches a declaring file's basename reaches the symbol
105
+ through that file, so its importers appear at depth 2. Keep the default
106
+ depth of 2 unless direct references are what you actually want.
@@ -63,7 +63,7 @@ Pipeline (after Phase 0):
63
63
  Phase 2: Planning -> Task breakdown + architecture review + Plan Approval Gate
64
64
  Phase 3: Dev -> TDD: test -> code -> build (Sonnet) + build queue
65
65
  Phase 4: Review -> Deterministic gates + parallel AI review + Fable triage
66
- (Claude Code: Fable + Sonnet · Copilot CLI: GPT-5.4 + Opus + Sonnet)
66
+ (Claude Code: Fable + Opus + Sonnet · Copilot CLI: GPT-5.4 + Opus + Sonnet)
67
67
  Phase 5: Test -> Optional: switch to branch, test in Xcode
68
68
  Phase 6: Commit -> Commit -> push -> PR + issue body update (never auto-closes)
69
69
  Phase 7: Report -> Channels dispatcher (PR · Jira · Confluence · Wiki, multi-select)
@@ -240,7 +240,7 @@ Pipeline (Phase 0'dan sonra):
240
240
  Phase 2: Planning -> Task kırılımı + mimari inceleme + Plan Onay Kapısı
241
241
  Phase 3: Dev -> TDD: test -> kod -> build (Sonnet) + build queue
242
242
  Phase 4: Review -> Deterministik kapılar + paralel AI review + Fable triage
243
- (Claude Code: Fable + Sonnet · Copilot CLI: GPT-5.4 + Opus + Sonnet)
243
+ (Claude Code: Fable + Opus + Sonnet · Copilot CLI: GPT-5.4 + Opus + Sonnet)
244
244
  Phase 5: Test -> Opsiyonel: branch'e geç, Xcode'da test
245
245
  Phase 6: Commit -> Commit -> push -> PR + issue body güncelleme (hiç auto-close yok)
246
246
  Phase 7: Report -> Channels dispatcher (PR · Jira · Confluence · Wiki, multi-select)