@mmerterden/multi-agent-pipeline 20.30.0 → 20.34.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 (195) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +47 -1
  3. package/README.tr.md +61 -1
  4. package/docs/adr/0018-cross-repo-links-not-a-global-graph.md +43 -0
  5. package/docs/adr/README.md +2 -1
  6. package/docs/facts.json +6 -5
  7. package/docs/features.md +1 -1
  8. package/install/_dev-only-files.mjs +1 -0
  9. package/install/_unattended-profile.mjs +61 -0
  10. package/install/catalog-history.json +1 -1
  11. package/install/claude.mjs +16 -2
  12. package/install/index.mjs +10 -1
  13. package/install/templates/claude-hooks.json +23 -1
  14. package/manifest.json +198 -134
  15. package/package.json +4 -1
  16. package/pipeline/agents/code-reviewer.md +9 -0
  17. package/pipeline/agents/explorer.md +4 -0
  18. package/pipeline/agents/plan-critic.md +4 -2
  19. package/pipeline/commands/multi-agent/SKILL.md +17 -136
  20. package/pipeline/commands/multi-agent/analysis/SKILL.md +4 -4
  21. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -297
  22. package/pipeline/commands/multi-agent/graph/SKILL.md +5 -2
  23. package/pipeline/commands/multi-agent/language/SKILL.md +3 -3
  24. package/pipeline/commands/multi-agent/office-on/SKILL.md +8 -7
  25. package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
  26. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -1
  27. package/pipeline/commands/multi-agent/resume/SKILL.md +8 -22
  28. package/pipeline/commands/multi-agent/review/SKILL.md +11 -134
  29. package/pipeline/commands/multi-agent/scenario-audit/SKILL.md +4 -2
  30. package/pipeline/commands/multi-agent/setup/SKILL.md +19 -224
  31. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -0
  32. package/pipeline/commands/multi-agent/test-gen/SKILL.md +29 -3
  33. package/pipeline/contract/CHANGELOG.md +46 -0
  34. package/pipeline/contract/README.md +27 -10
  35. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  36. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  37. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  38. package/pipeline/contract/fixtures/launch-plan.json +1 -1
  39. package/pipeline/contract/fixtures/runs-awaiting-question.json +6 -1
  40. package/pipeline/contract/fixtures/runs-empty.json +1 -1
  41. package/pipeline/contract/fixtures/runs-failed.json +6 -1
  42. package/pipeline/contract/fixtures/runs-old-schema.json +6 -1
  43. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +5 -1
  44. package/pipeline/contract/fixtures/runs-pr-opened.json +6 -1
  45. package/pipeline/contract/fixtures/runs-running.json +6 -1
  46. package/pipeline/contract/manifest.json +153 -19
  47. package/pipeline/contract/types/index.d.ts +304 -6
  48. package/pipeline/lib/bg-launch.mjs +106 -0
  49. package/pipeline/lib/code-digest.mjs +49 -0
  50. package/pipeline/lib/credential-resolve.mjs +42 -2
  51. package/pipeline/multi-agent-refs/channels/generator.md +146 -0
  52. package/pipeline/multi-agent-refs/channels/menu.md +55 -0
  53. package/pipeline/multi-agent-refs/channels/multi-repo.md +6 -0
  54. package/pipeline/multi-agent-refs/channels/preview.md +25 -0
  55. package/pipeline/multi-agent-refs/channels/reference.md +98 -0
  56. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +1 -1
  57. package/pipeline/multi-agent-refs/features/code-graph.md +27 -0
  58. package/pipeline/multi-agent-refs/features/office-threat-model.md +24 -6
  59. package/pipeline/multi-agent-refs/features/plan-critic.md +5 -2
  60. package/pipeline/multi-agent-refs/features/review-delta.md +4 -1
  61. package/pipeline/multi-agent-refs/features/scope-check.md +10 -1
  62. package/pipeline/multi-agent-refs/features/unattended-security.md +1 -1
  63. package/pipeline/multi-agent-refs/help/en.md +2 -2
  64. package/pipeline/multi-agent-refs/help/tr.md +2 -2
  65. package/pipeline/multi-agent-refs/knowledge.md +9 -0
  66. package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +163 -0
  67. package/pipeline/multi-agent-refs/phases/modes.md +7 -7
  68. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
  69. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -8
  70. package/pipeline/multi-agent-refs/phases/phase-3-review.md +12 -7
  71. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +7 -3
  72. package/pipeline/multi-agent-refs/phases/phase-5-report.md +19 -10
  73. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  74. package/pipeline/multi-agent-refs/resume/details.md +35 -0
  75. package/pipeline/multi-agent-refs/review/inputs.md +87 -0
  76. package/pipeline/multi-agent-refs/review/module-guides.md +25 -0
  77. package/pipeline/multi-agent-refs/review/valven.md +42 -0
  78. package/pipeline/multi-agent-refs/review-standalone-tracker.md +37 -0
  79. package/pipeline/multi-agent-refs/setup/accounts.md +27 -0
  80. package/pipeline/multi-agent-refs/setup/app-store-connect.md +5 -0
  81. package/pipeline/multi-agent-refs/setup/firebase.md +5 -0
  82. package/pipeline/multi-agent-refs/setup/google-play.md +4 -0
  83. package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +21 -0
  84. package/pipeline/multi-agent-refs/setup/language.md +46 -0
  85. package/pipeline/multi-agent-refs/setup/multi-repo-group.md +51 -0
  86. package/pipeline/multi-agent-refs/setup/summaries.md +63 -0
  87. package/pipeline/multi-agent-refs/setup/token-save-flow.md +51 -0
  88. package/pipeline/multi-agent-refs/tracker-contract.md +8 -3
  89. package/pipeline/office/NOTICE.md +4 -1
  90. package/pipeline/office/assets/ARCHIVE_BOXES.png +0 -0
  91. package/pipeline/office/assets/COFFEE_MACHINE.png +0 -0
  92. package/pipeline/office/assets/COUNTER_SINK.png +0 -0
  93. package/pipeline/office/assets/DEVICE_STAND.png +0 -0
  94. package/pipeline/office/assets/FILING_CABINET.png +0 -0
  95. package/pipeline/office/assets/FRIDGE.png +0 -0
  96. package/pipeline/office/assets/MAILBOX.png +0 -0
  97. package/pipeline/office/assets/MOP_BUCKET.png +0 -0
  98. package/pipeline/office/assets/PRINTER.png +0 -0
  99. package/pipeline/office/assets/RECEPTION_DESK.png +0 -0
  100. package/pipeline/office/assets/SERVER_RACK.png +0 -0
  101. package/pipeline/office/assets/SERVER_RACK_2.png +0 -0
  102. package/pipeline/office/assets/TOILET_STALL.png +0 -0
  103. package/pipeline/office/assets/VENDING.png +0 -0
  104. package/pipeline/office/assets/WASHBASIN.png +0 -0
  105. package/pipeline/office/assets/WATER_COOLER.png +0 -0
  106. package/pipeline/office/index.html +3936 -819
  107. package/pipeline/office/toolkit.json +3 -0
  108. package/pipeline/rules/response-shape.md +6 -7
  109. package/pipeline/schemas/activity.schema.json +188 -4
  110. package/pipeline/schemas/agent-state.schema.json +511 -115
  111. package/pipeline/schemas/contract-error.schema.json +12 -1
  112. package/pipeline/schemas/job-change.schema.json +72 -0
  113. package/pipeline/schemas/job-request.schema.json +38 -0
  114. package/pipeline/schemas/launch.json +4 -4
  115. package/pipeline/schemas/office-credentials.schema.json +71 -0
  116. package/pipeline/schemas/office-jobs.json +253 -0
  117. package/pipeline/schemas/office-jobs.schema.json +62 -0
  118. package/pipeline/schemas/office-memory.schema.json +105 -0
  119. package/pipeline/schemas/office-settings-request.schema.json +30 -0
  120. package/pipeline/schemas/office-settings.schema.json +67 -0
  121. package/pipeline/schemas/phases.json +14 -6
  122. package/pipeline/schemas/phases.schema.json +86 -19
  123. package/pipeline/schemas/plan-critique.schema.json +9 -2
  124. package/pipeline/schemas/planning-output.schema.json +25 -2
  125. package/pipeline/schemas/prefs.schema.json +24 -1
  126. package/pipeline/schemas/prs.schema.json +5 -0
  127. package/pipeline/schemas/prune-logs-request.schema.json +26 -0
  128. package/pipeline/schemas/prune-logs.schema.json +52 -0
  129. package/pipeline/schemas/repo-change.schema.json +54 -0
  130. package/pipeline/schemas/repo-request.schema.json +29 -0
  131. package/pipeline/schemas/repos.schema.json +49 -1
  132. package/pipeline/schemas/reviewer-output.schema.json +20 -2
  133. package/pipeline/schemas/runs-index.schema.json +257 -46
  134. package/pipeline/schemas/system-status.schema.json +111 -0
  135. package/pipeline/schemas/token-budget.json +4 -4
  136. package/pipeline/schemas/tools.schema.json +5 -0
  137. package/pipeline/schemas/triage-output.schema.json +20 -2
  138. package/pipeline/schemas/unattended-profile.json +7 -2
  139. package/pipeline/scripts/_active-run.mjs +2 -1
  140. package/pipeline/scripts/_graph-analysis.mjs +270 -0
  141. package/pipeline/scripts/_memory-policy.mjs +165 -0
  142. package/pipeline/scripts/_secret-scan.mjs +105 -0
  143. package/pipeline/scripts/_spawn-async.mjs +9 -5
  144. package/pipeline/scripts/_unified-diff.mjs +2 -2
  145. package/pipeline/scripts/activity-snapshot.mjs +219 -23
  146. package/pipeline/scripts/autopilot-runner.mjs +7 -5
  147. package/pipeline/scripts/capture-flush.sh +12 -2
  148. package/pipeline/scripts/check-read-size.sh +9 -0
  149. package/pipeline/scripts/code-graph-rules/android.json +8 -3
  150. package/pipeline/scripts/contract-server.mjs +575 -28
  151. package/pipeline/scripts/debt-ledger.mjs +323 -0
  152. package/pipeline/scripts/file-context.mjs +261 -0
  153. package/pipeline/scripts/file-context.sh +20 -0
  154. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -1
  155. package/pipeline/scripts/graph-links.mjs +230 -0
  156. package/pipeline/scripts/graph-query.mjs +133 -1
  157. package/pipeline/scripts/graph-report.mjs +65 -0
  158. package/pipeline/scripts/job-launch.mjs +477 -0
  159. package/pipeline/scripts/launch-request.mjs +94 -9
  160. package/pipeline/scripts/learn-from-transcripts.mjs +12 -48
  161. package/pipeline/scripts/learnings-ledger.mjs +143 -24
  162. package/pipeline/scripts/office-build.mjs +148 -11
  163. package/pipeline/scripts/office-control.mjs +34 -7
  164. package/pipeline/scripts/office-credentials.mjs +106 -0
  165. package/pipeline/scripts/office-memory.mjs +200 -0
  166. package/pipeline/scripts/office-prune.mjs +125 -0
  167. package/pipeline/scripts/office-settings.mjs +145 -0
  168. package/pipeline/scripts/phase-tracker.sh +12 -5
  169. package/pipeline/scripts/plan-critique-gate.mjs +11 -2
  170. package/pipeline/scripts/prune-logs.sh +53 -2
  171. package/pipeline/scripts/rework-rulings.mjs +106 -0
  172. package/pipeline/scripts/runs-index.mjs +62 -2
  173. package/pipeline/scripts/scope-check-gate.mjs +149 -2
  174. package/pipeline/scripts/system-status.mjs +196 -0
  175. package/pipeline/scripts/task-mirror.mjs +150 -0
  176. package/pipeline/scripts/task-mirror.sh +20 -0
  177. package/pipeline/scripts/toolkit-tools.mjs +33 -7
  178. package/pipeline/scripts/transcript-index.mjs +4 -1
  179. package/pipeline/scripts/triage-memory.mjs +64 -37
  180. package/pipeline/scripts/validate-planning.mjs +68 -4
  181. package/pipeline/scripts/validate-reviewer.mjs +20 -0
  182. package/pipeline/scripts/validate-triage.mjs +107 -2
  183. package/pipeline/scripts/work-items.mjs +191 -22
  184. package/pipeline/skills/.skill-manifest.json +9 -9
  185. package/pipeline/skills/.skills-index.json +1 -1
  186. package/pipeline/skills/shared/README.md +1 -1
  187. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +5 -2
  188. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +4 -4
  189. package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +3 -4
  190. package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +4 -1
  191. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +21 -0
  192. package/pipeline/skills/shared/core/multi-agent-scenario-audit/SKILL.md +4 -2
  193. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +4 -4
  194. package/pipeline/skills/shared/core/multi-agent-test-gen/SKILL.md +26 -3
  195. package/pipeline/skills/skills-index.md +1 -1
@@ -8,8 +8,12 @@
8
8
  // Zero deps. Reads JSON from path arg or STDIN.
9
9
  //
10
10
  // Usage:
11
- // node validate-planning.mjs path/to/plan.json
12
- // cat plan.json | node validate-planning.mjs -
11
+ // node validate-planning.mjs path/to/plan.json [--spec <path>]
12
+ // cat plan.json | node validate-planning.mjs - [--spec <path>]
13
+ //
14
+ // --spec names the analysis document or task text the plan was written from.
15
+ // A plan more than SPEC_RATIO times its length gets a warning: a plan should
16
+ // record decisions, and one far longer than its spec is usually a transcript.
13
17
  //
14
18
  // Exit codes:
15
19
  // 0 - valid
@@ -27,11 +31,29 @@ import { planOverlaps } from "./_plan-overlap.mjs";
27
31
  const ALLOWED_TYPES = new Set(["test", "code", "refactor", "docs", "chore"]);
28
32
  const TASK_ID_RE = /^T\d+$/;
29
33
  const NAME_LISTS = ["creates", "removes", "produces", "consumes"];
34
+ const REVIEW_FOCUS_MAX = 5;
35
+ const SPEC_RATIO = 4;
36
+ const SPEC_MIN_CHARS = 400;
37
+
38
+ function specPath() {
39
+ const i = process.argv.indexOf("--spec");
40
+ return i > 0 ? process.argv[i + 1] : null;
41
+ }
42
+
43
+ /** The plan path: the first argument that is neither a flag nor a flag's value. */
44
+ function inputArg() {
45
+ const args = process.argv.slice(2);
46
+ for (let i = 0; i < args.length; i++) {
47
+ if (args[i] === "--spec") i++;
48
+ else if (!args[i].startsWith("--")) return args[i];
49
+ }
50
+ return undefined;
51
+ }
30
52
 
31
53
  function readInput() {
32
- const arg = process.argv[2];
54
+ const arg = inputArg();
33
55
  if (!arg) {
34
- console.error("usage: validate-planning.mjs <path|->");
56
+ console.error("usage: validate-planning.mjs <path|-> [--spec <path>]");
35
57
  process.exit(64);
36
58
  }
37
59
  if (arg === "-") {
@@ -173,6 +195,33 @@ async function main() {
173
195
  }
174
196
  }
175
197
 
198
+ if (parsed.reviewFocus !== undefined) {
199
+ if (!Array.isArray(parsed.reviewFocus)) {
200
+ errors.push("reviewFocus: must be array if present");
201
+ } else {
202
+ if (parsed.reviewFocus.length > REVIEW_FOCUS_MAX) {
203
+ errors.push(`reviewFocus: at most ${REVIEW_FOCUS_MAX} entries`);
204
+ }
205
+ parsed.reviewFocus.forEach((r, i) => {
206
+ if (typeof r?.input !== "string" || r.input.trim().length < 4) {
207
+ errors.push(`reviewFocus[${i}].input: must name the input`);
208
+ }
209
+ if (!ids.has(r?.ownerTaskId)) {
210
+ errors.push(`reviewFocus[${i}].ownerTaskId: unknown task "${r?.ownerTaskId}"`);
211
+ }
212
+ });
213
+ }
214
+ }
215
+ if (
216
+ parsed.globalConstraints !== undefined &&
217
+ !(
218
+ Array.isArray(parsed.globalConstraints) &&
219
+ parsed.globalConstraints.every((c) => typeof c === "string" && c.length > 0)
220
+ )
221
+ ) {
222
+ errors.push("globalConstraints: must be an array of non-empty strings if present");
223
+ }
224
+
176
225
  // approved=false requires userFeedback
177
226
  if (errors.length === 0 && parsed.approved === false) {
178
227
  if (typeof parsed.userFeedback !== "string" || parsed.userFeedback.length === 0) {
@@ -189,6 +238,21 @@ async function main() {
189
238
  errors.push(...found.contradictions);
190
239
  }
191
240
 
241
+ const spec = specPath();
242
+ if (spec) {
243
+ let specChars = 0;
244
+ try {
245
+ specChars = readFileSync(spec, "utf-8").length;
246
+ } catch {
247
+ warnings.push(`spec not readable: ${spec}`);
248
+ }
249
+ if (specChars >= SPEC_MIN_CHARS && raw.length > specChars * SPEC_RATIO) {
250
+ warnings.push(
251
+ `plan is ${Math.round(raw.length / specChars)}x the length of its spec; record decisions, not a transcript`,
252
+ );
253
+ }
254
+ }
255
+
192
256
  if (errors.length > 0) {
193
257
  emit({ ok: false, code: 1, errors, warnings, overlaps });
194
258
  process.exitCode = 1;
@@ -264,6 +264,26 @@ async function main() {
264
264
  errors.push("approved must be boolean");
265
265
  }
266
266
 
267
+ // Requirements the diff cannot show: they live in code the change did not touch.
268
+ if (parsed.cannotVerify !== undefined) {
269
+ if (!Array.isArray(parsed.cannotVerify)) {
270
+ errors.push("cannotVerify must be an array when present");
271
+ } else {
272
+ parsed.cannotVerify.forEach((row, i) => {
273
+ const label = `cannotVerify[${i}]`;
274
+ if (typeof row?.requirement !== "string" || row.requirement.trim().length < 4) {
275
+ errors.push(`${label}: requirement must name what could not be verified`);
276
+ }
277
+ if (typeof row?.where !== "string" || !row.where.trim()) {
278
+ errors.push(`${label}: where must name the unchanged file or symbol`);
279
+ }
280
+ if (typeof row?.why !== "string" || row.why.trim().length < 4) {
281
+ errors.push(`${label}: why must say what the diff does not show`);
282
+ }
283
+ });
284
+ }
285
+ }
286
+
267
287
  // Conformance checklist, only when the orchestrator supplied criteria.
268
288
  const critIdx = process.argv.indexOf("--criteria");
269
289
  if (critIdx !== -1) {
@@ -11,6 +11,8 @@
11
11
  // Usage:
12
12
  // node pipeline/scripts/validate-triage.mjs path/to/triage.json
13
13
  // cat triage.json | node pipeline/scripts/validate-triage.mjs -
14
+ // [--reviewers a.json,b.json] # every reviewer cannotVerify row needs a triage row
15
+ // [--reviewers-dir <dir>] # same, for every reviewer-*.json in <dir>
14
16
  //
15
17
  // Exit codes:
16
18
  // 0 - valid, safe to act on
@@ -21,7 +23,8 @@
21
23
  // Output: JSON result to stdout with shape:
22
24
  // {"ok": bool, "code": N, "errors": [...], "warnings": [...], "corrected": {...}|null}
23
25
 
24
- import { readFileSync } from "node:fs";
26
+ import { readdirSync, readFileSync } from "node:fs";
27
+ import { join } from "node:path";
25
28
 
26
29
  const ALLOWED_REVIEWERS = new Set(["fable", "opus", "sonnet", "gpt"]);
27
30
  const ALLOWED_SEVERITIES = new Set(["blocking", "important", "suggestion"]);
@@ -35,9 +38,72 @@ const ALLOWED_VERIFICATION_RESULTS = new Set(["confirmed", "not-reproduced", "in
35
38
  const FINGERPRINT_RE = /^F:[0-9a-f]{8}$/;
36
39
  const OVER_REJECT_THRESHOLD = 0.8;
37
40
  const OVER_REJECT_MIN_FINDINGS = 5;
41
+ const CANNOT_VERIFY_RESOLUTIONS = new Set(["verified", "finding", "out-of-scope"]);
42
+ // A path (any run without spaces, quotes or parentheses that holds a letter),
43
+ // then :line. A time of day has no letter, so it is not one.
44
+ const FILE_LINE = /(?:^|[\s(`'"[])([^\s:`'"()]*\p{L}[^\s:`'"()]*):(\d+)\b/gu;
45
+ const fileLines = (text) => [...String(text).matchAll(FILE_LINE)].map((m) => `${m[1]}:${m[2]}`);
46
+ const PATH_EDGE = /[^\s(`'",;]/u;
47
+ /** True when `text` names `at` (file:line) as a whole token, not inside a longer path. */
48
+ function names(text, at) {
49
+ const t = String(text);
50
+ for (let i = t.indexOf(at); i !== -1; i = t.indexOf(at, i + 1)) {
51
+ const before = i === 0 ? "" : t[i - 1];
52
+ const after = t[i + at.length] ?? "";
53
+ if ((!before || !PATH_EDGE.test(before)) && !/\d/.test(after)) return true;
54
+ }
55
+ return false;
56
+ }
57
+ const normRequirement = (s) => String(s).toLowerCase().replace(/\s+/g, " ").trim();
58
+
59
+ /** Reviewer files named by --reviewers, or found by --reviewers-dir; null when neither is given. */
60
+ function reviewerFiles() {
61
+ const listAt = process.argv.indexOf("--reviewers");
62
+ const dirAt = process.argv.indexOf("--reviewers-dir");
63
+ if (listAt === -1 && dirAt === -1) return null;
64
+ const files = [];
65
+ if (listAt !== -1) {
66
+ const value = process.argv[listAt + 1];
67
+ if (!value || value.startsWith("--"))
68
+ throw new Error("--reviewers needs a comma-separated list");
69
+ files.push(...value.split(",").filter(Boolean));
70
+ }
71
+ if (dirAt !== -1) {
72
+ const dir = process.argv[dirAt + 1];
73
+ if (!dir || dir.startsWith("--")) throw new Error("--reviewers-dir needs a folder");
74
+ for (const name of readdirSync(dir).sort()) {
75
+ if (/^reviewer-.*\.json$/.test(name)) files.push(join(dir, name));
76
+ }
77
+ }
78
+ return files;
79
+ }
80
+
81
+ /** Requirements the reviewers could not verify from the diff, from their files. */
82
+ function reviewerRequirements() {
83
+ const files = reviewerFiles();
84
+ if (files === null) return null;
85
+ const out = new Set();
86
+ for (const p of files) {
87
+ const doc = JSON.parse(readFileSync(p, "utf-8"));
88
+ for (const row of doc?.cannotVerify ?? []) {
89
+ if (typeof row?.requirement === "string") out.add(normRequirement(row.requirement));
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /** The input path: the first argument that is neither a flag nor a flag's value. */
96
+ function inputArg() {
97
+ const args = process.argv.slice(2);
98
+ for (let i = 0; i < args.length; i++) {
99
+ if (args[i] === "--reviewers" || args[i] === "--reviewers-dir") i++;
100
+ else if (!args[i].startsWith("--")) return args[i];
101
+ }
102
+ return undefined;
103
+ }
38
104
 
39
105
  function readInput() {
40
- const arg = process.argv[2];
106
+ const arg = inputArg();
41
107
  if (!arg) {
42
108
  console.error("error: provide a path or '-' for STDIN");
43
109
  process.exit(64); // EX_USAGE
@@ -214,6 +280,45 @@ function validate(raw) {
214
280
  parsed.deferred.forEach((f, i) => validateClassified(f, "deferred", i, errors));
215
281
  parsed.rejected.forEach((f, i) => validateClassified(f, "rejected", i, errors));
216
282
 
283
+ // Every requirement a reviewer could not verify from the diff is settled here:
284
+ // checked in the unchanged code (verified), turned into a finding, or ruled
285
+ // out of scope, each with its evidence.
286
+ if (parsed.cannotVerify !== undefined && !Array.isArray(parsed.cannotVerify)) {
287
+ errors.push("cannotVerify must be an array when present");
288
+ }
289
+ const settled = new Set();
290
+ const list = (x) => (Array.isArray(x) ? x : []);
291
+ const kept = [...list(parsed.accepted), ...list(parsed.deferred).map((d) => d?.finding)]
292
+ .filter((f) => typeof f?.file === "string" && Number.isInteger(f?.line))
293
+ .map((f) => `${f.file}:${f.line}`);
294
+ (Array.isArray(parsed.cannotVerify) ? parsed.cannotVerify : []).forEach((row, i) => {
295
+ const label = `cannotVerify[${i}]`;
296
+ if (typeof row?.requirement !== "string" || row.requirement.trim().length < 4) {
297
+ errors.push(`${label}: requirement must be a string >= 4 chars`);
298
+ return;
299
+ }
300
+ if (!CANNOT_VERIFY_RESOLUTIONS.has(row.resolution)) {
301
+ errors.push(`${label}: resolution must be verified|finding|out-of-scope`);
302
+ }
303
+ if (typeof row.evidence !== "string" || row.evidence.trim().length < 4) {
304
+ errors.push(`${label}: evidence must say how it was settled`);
305
+ } else if (row.resolution === "verified" && !fileLines(row.evidence).length) {
306
+ errors.push(`${label}: a verified requirement cites the file:line it read`);
307
+ } else if (row.resolution === "finding" && !kept.some((at) => names(row.evidence, at))) {
308
+ errors.push(`${label}: evidence names no accepted or deferred finding (cite its file:line)`);
309
+ }
310
+ settled.add(normRequirement(row.requirement));
311
+ });
312
+ let required;
313
+ try {
314
+ required = reviewerRequirements();
315
+ } catch (err) {
316
+ errors.push(`--reviewers unreadable: ${err.message}`);
317
+ }
318
+ for (const req of required ?? []) {
319
+ if (!settled.has(req)) errors.push(`cannotVerify: reviewer requirement not settled: "${req}"`);
320
+ }
321
+
217
322
  // Optional consensus block (v3.1.0) + cross-check against the actual verdict
218
323
  // so the block is not merely decorative.
219
324
  if (parsed.consensus !== undefined) {
@@ -55,8 +55,10 @@ import { spawnAsync } from "./_spawn-async.mjs";
55
55
  import { readRegularJson } from "../lib/regular-file.mjs";
56
56
  import { runMain } from "../lib/fatal.mjs";
57
57
  import { invokedDirectly } from "../lib/invoked-directly.mjs";
58
+ import { resolveTokenAsync } from "../lib/credential-resolve.mjs";
59
+ import { homedir } from "node:os";
58
60
 
59
- export const CONTRACT_VERSION = "1.0.0";
61
+ export const CONTRACT_VERSION = "1.1.0";
60
62
  const HERE = dirname(fileURLToPath(import.meta.url));
61
63
  /** A Jira key, `owner/repo#N` or `#N`: the structured inputs launch accepts too. */
62
64
  export const ISSUE_KEY =
@@ -114,7 +116,130 @@ export async function issueMaturity(key, env = process.env) {
114
116
  };
115
117
  }
116
118
 
117
- async function ghSearch(env, role) {
119
+ /**
120
+ * The GitHub accounts `gh` is logged in to, as host and login: a personal and
121
+ * a corporate account are asked one by one, each with its own token, so a
122
+ * review request on either shows. Empty when `gh auth status` says nothing
123
+ * readable; the caller then asks with gh's active account alone.
124
+ */
125
+ async function ghAccounts(env) {
126
+ const gh = env.MA_GH_BIN || "gh";
127
+ const r = await spawnAsync(gh, ["auth", "status"], {
128
+ env,
129
+ timeoutMs: TIMEOUT_MS,
130
+ withStderr: true,
131
+ });
132
+ const text = r.stdout || "";
133
+ const out = [];
134
+ for (const m of text.matchAll(/Logged in to (\S+) account ([A-Za-z0-9_.-]+)/g))
135
+ if (!out.some((a) => a.host === m[1] && a.login === m[2]))
136
+ out.push({ host: m[1], login: m[2] });
137
+ return out;
138
+ }
139
+
140
+ /**
141
+ * gh's token for one account, carried in the environment only (GH_TOKEN, and
142
+ * GH_ENTERPRISE_TOKEN for an Enterprise Server host), never on a command line.
143
+ */
144
+ async function accountEnv(env, account) {
145
+ const gh = env.MA_GH_BIN || "gh";
146
+ const r = await spawnAsync(
147
+ gh,
148
+ ["auth", "token", "--hostname", account.host, "--user", account.login],
149
+ { env, timeoutMs: TIMEOUT_MS },
150
+ );
151
+ const token = (r.stdout || "").trim();
152
+ if (r.status !== 0 || !token) return null;
153
+ // gh reads GH_TOKEN for github.com and GH_ENTERPRISE_TOKEN for a GitHub
154
+ // Enterprise Server host, so an enterprise account gets both.
155
+ const enterprise = !/^(github\.com|[a-z0-9-]+\.ghe\.com)$/i.test(account.host);
156
+ return {
157
+ ...env,
158
+ GH_TOKEN: token,
159
+ GH_HOST: account.host,
160
+ ...(enterprise ? { GH_ENTERPRISE_TOKEN: token } : {}),
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Open pull requests on the configured Bitbucket Server where this user is a
166
+ * reviewer or the author, from its dashboard API. The token comes from the
167
+ * credential store and travels in the request header only.
168
+ */
169
+ async function bitbucketPrs(env) {
170
+ let host = env.MA_BITBUCKET_HOST || null;
171
+ if (!host) {
172
+ try {
173
+ const prefs = readRegularJson(
174
+ env.MULTI_AGENT_PREFS ||
175
+ join(env.HOME || homedir(), ".claude", "multi-agent-preferences.json"),
176
+ null,
177
+ );
178
+ host = prefs?.global?.hosts?.bitbucket ?? null;
179
+ } catch {
180
+ host = null;
181
+ }
182
+ }
183
+ if (typeof host !== "string" || !host) return { ok: false, items: [], reason: "not configured" };
184
+ // The token travels in a header, so only over TLS: a host written with
185
+ // http:// is refused unless it is this machine's own loopback.
186
+ const loopback = /^http:\/\/(127\.0\.0\.1|localhost)(:\d+)?(\/|$)/i.test(host);
187
+ if (/^http:\/\//i.test(host) && !loopback)
188
+ return { ok: false, items: [], reason: "https required" };
189
+ const base = /^https?:\/\//i.test(host) ? host.replace(/\/+$/, "") : `https://${host}`;
190
+ const token =
191
+ env.MA_BITBUCKET_TOKEN ||
192
+ (await resolveTokenAsync("bitbucket_token", "BITBUCKET_TOKEN", { env }));
193
+ if (!token) return { ok: false, items: [], reason: "no token in the credential store" };
194
+ // The same rule as credential-inventory.sh: Basic with the stored user (an
195
+ // app password), else the token as a bearer.
196
+ const user =
197
+ env.MA_BITBUCKET_USER || (await resolveTokenAsync("bitbucket_user", "BITBUCKET_USER", { env }));
198
+ const authorization = user
199
+ ? "Basic " + Buffer.from(`${user}:${token}`).toString("base64")
200
+ : `Bearer ${token}`;
201
+ const items = [];
202
+ try {
203
+ for (const role of ["REVIEWER", "AUTHOR"]) {
204
+ const res = await fetch(
205
+ `${base}/rest/api/1.0/dashboard/pull-requests?state=OPEN&role=${role}&limit=50`,
206
+ {
207
+ headers: { Authorization: authorization, Accept: "application/json" },
208
+ redirect: "error",
209
+ signal: AbortSignal.timeout(TIMEOUT_MS),
210
+ },
211
+ );
212
+ if (res.status === 401 || res.status === 403)
213
+ return { ok: false, items: [], reason: "the token was refused" };
214
+ if (!res.ok) return { ok: false, items: [], reason: `Bitbucket answered ${res.status}` };
215
+ const doc = await res.json();
216
+ for (const p of Array.isArray(doc?.values) ? doc.values : []) {
217
+ const repo = p?.toRef?.repository;
218
+ items.push({
219
+ source: "bitbucket",
220
+ account: null,
221
+ repo: String(repo ? `${repo.project?.key ?? ""}/${repo.slug ?? ""}` : ""),
222
+ number: Number(p?.id) || 0,
223
+ title: scrub(String(p?.title ?? "")),
224
+ url: typeof p?.links?.self?.[0]?.href === "string" ? p.links.self[0].href : null,
225
+ author: p?.author?.user?.name ? String(p.author.user.name) : null,
226
+ updatedAt: Number.isFinite(p?.updatedDate) ? new Date(p.updatedDate).toISOString() : null,
227
+ role: role === "REVIEWER" ? "reviewer" : "author",
228
+ });
229
+ }
230
+ }
231
+ } catch (e) {
232
+ const why = String(e?.cause?.message ?? e?.message ?? "");
233
+ return {
234
+ ok: false,
235
+ items: [],
236
+ reason: /redirect/i.test(why) ? "redirect refused" : "unreachable (VPN?)",
237
+ };
238
+ }
239
+ return { ok: true, items };
240
+ }
241
+
242
+ async function ghSearch(env, role, account = null) {
118
243
  const gh = env.MA_GH_BIN || "gh";
119
244
  const flag = role === "reviewer" ? "--review-requested=@me" : "--author=@me";
120
245
  const r = await spawnAsync(
@@ -139,6 +264,7 @@ async function ghSearch(env, role) {
139
264
  ok: true,
140
265
  items: (Array.isArray(rows) ? rows : []).map((p) => ({
141
266
  source: "github",
267
+ account,
142
268
  repo: String(p?.repository?.nameWithOwner ?? p?.repository?.name ?? ""),
143
269
  number: Number(p?.number) || 0,
144
270
  title: scrub(String(p?.title ?? "")),
@@ -153,31 +279,74 @@ async function ghSearch(env, role) {
153
279
  }
154
280
  }
155
281
 
156
- /** Open pull requests on every host the pipeline can list, one list. */
282
+ /**
283
+ * Open pull requests on every host the pipeline can list, one list: every
284
+ * GitHub account gh is logged in to (personal and corporate alike) and the
285
+ * configured Bitbucket Server.
286
+ */
157
287
  export async function pullRequests({ env = process.env, redact = false } = {}) {
158
- const [reviewer, author] = await Promise.all([
159
- ghSearch(env, "reviewer"),
160
- ghSearch(env, "author"),
161
- ]);
288
+ const accounts = await ghAccounts(env);
289
+ // gh before `auth token --user` hands out no token per account; then the
290
+ // active account answers alone, as a plain `gh search` does.
291
+ const envs = await Promise.all(accounts.map((a) => accountEnv(env, a)));
292
+ const perAccount = envs.some(Boolean);
293
+ const github = [];
294
+ if (accounts.length && perAccount) {
295
+ for (const [i, a] of accounts.entries()) {
296
+ const aEnv = envs[i];
297
+ if (!aEnv) {
298
+ github.push({
299
+ source: `github:${a.login}`,
300
+ ok: false,
301
+ items: [],
302
+ reason: "gh has no token for it",
303
+ });
304
+ continue;
305
+ }
306
+ const [rv, au] = await Promise.all([
307
+ ghSearch(aEnv, "reviewer", a.login),
308
+ ghSearch(aEnv, "author", a.login),
309
+ ]);
310
+ github.push({
311
+ source: `github:${a.login}`,
312
+ ok: rv.ok || au.ok,
313
+ items: [...rv.items, ...au.items],
314
+ reason: rv.ok || au.ok ? undefined : "gh could not list pull requests",
315
+ });
316
+ }
317
+ } else {
318
+ const [rv, au] = await Promise.all([ghSearch(env, "reviewer"), ghSearch(env, "author")]);
319
+ github.push({
320
+ source: "github",
321
+ ok: rv.ok || au.ok,
322
+ items: [...rv.items, ...au.items],
323
+ reason: rv.ok || au.ok ? undefined : "gh could not list pull requests",
324
+ });
325
+ }
326
+ const bitbucket = { source: "bitbucket", ...(await bitbucketPrs(env)) };
162
327
  const seen = new Set();
163
328
  const items = [];
164
- for (const p of [...reviewer.items, ...author.items]) {
165
- const k = `${p.repo}#${p.number}`;
166
- if (seen.has(k)) continue;
167
- seen.add(k);
168
- items.push(redact ? { ...p, url: null } : p);
329
+ const sources = [];
330
+ // A redacted answer (the phone's) names no login: accounts become numbers.
331
+ let accountNo = 0;
332
+ for (const src of [...github, bitbucket]) {
333
+ const label = redact && src.source.startsWith("github:") ? `github:${++accountNo}` : src.source;
334
+ let count = 0;
335
+ for (const p of src.items) {
336
+ const k = `${p.source}:${p.repo}#${p.number}`;
337
+ if (seen.has(k)) continue;
338
+ seen.add(k);
339
+ count++;
340
+ items.push(redact ? { ...p, url: null, account: null, author: null } : p);
341
+ }
342
+ sources.push(
343
+ src.ok
344
+ ? { source: label, ok: true, count }
345
+ : { source: label, ok: false, count: 0, reason: src.reason || "unreadable" },
346
+ );
169
347
  }
170
348
  items.sort((a, b) => ((a.updatedAt ?? "") < (b.updatedAt ?? "") ? 1 : -1));
171
- return {
172
- contractVersion: CONTRACT_VERSION,
173
- items,
174
- sources: [
175
- reviewer.ok || author.ok
176
- ? { source: "github", ok: true, count: items.length }
177
- : { source: "github", ok: false, count: 0, reason: "gh could not list pull requests" },
178
- { source: "bitbucket", ok: false, count: 0, reason: "not-implemented" },
179
- ],
180
- };
349
+ return { contractVersion: CONTRACT_VERSION, items, sources };
181
350
  }
182
351
 
183
352
  function stateOf(taskId) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-24T21:22:00Z",
3
+ "generatedAt": "2026-10-26T11:41:43Z",
4
4
  "skillCount": 237,
5
5
  "entries": [
6
6
  {
@@ -89,7 +89,7 @@
89
89
  },
90
90
  {
91
91
  "path": "shared/core/multi-agent-graph/SKILL.md",
92
- "sha256": "14204e089cbe76702a6d3492859a3a63d99f5bfa4cb69873f96a3b1873e04ed0"
92
+ "sha256": "d81b8470c9d571f3c1b70529bf9f010ab830b71086663e1075573c229345f235"
93
93
  },
94
94
  {
95
95
  "path": "shared/core/multi-agent-help/SKILL.md",
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "path": "shared/core/multi-agent-language/SKILL.md",
116
- "sha256": "ca4932ca4491546309666b67c8a9a676700ecc09c6ddab8696788464991b435f"
116
+ "sha256": "0d3b7df6702356073c11c9ec76e69b39a0d9ecc45b1cf3e3270a639324b3633f"
117
117
  },
118
118
  {
119
119
  "path": "shared/core/multi-agent-log/SKILL.md",
@@ -133,7 +133,7 @@
133
133
  },
134
134
  {
135
135
  "path": "shared/core/multi-agent-office-on/SKILL.md",
136
- "sha256": "f1841f3092275e54b669a2701796c187ecaa16d71b0cd4311915218319d6950c"
136
+ "sha256": "b46a2f79beeee65ae7ad6cbfc58b4631c5146a7761e91a99b8a056d6f49c690d"
137
137
  },
138
138
  {
139
139
  "path": "shared/core/multi-agent-office-status/SKILL.md",
@@ -145,7 +145,7 @@
145
145
  },
146
146
  {
147
147
  "path": "shared/core/multi-agent-prune-logs/SKILL.md",
148
- "sha256": "9e0745c2aac474b401126218a7e0d81a72942cd331ac0e591da72407d523fda9"
148
+ "sha256": "649622e6f18887f986e0acd8b743df0a345a3adf89ccab58d6c4b191b770c1ac"
149
149
  },
150
150
  {
151
151
  "path": "shared/core/multi-agent-prune-prompts/SKILL.md",
@@ -181,7 +181,7 @@
181
181
  },
182
182
  {
183
183
  "path": "shared/core/multi-agent-review/SKILL.md",
184
- "sha256": "e3a673e13b9b7a227334afe79e20e2be837c0900e16e88003b4692a1d74dea41"
184
+ "sha256": "91e238e9a6d1144582a0a73c580630fd1356214672db3cb399acad77ee8b19e5"
185
185
  },
186
186
  {
187
187
  "path": "shared/core/multi-agent-route-off/SKILL.md",
@@ -213,7 +213,7 @@
213
213
  },
214
214
  {
215
215
  "path": "shared/core/multi-agent-scenario-audit/SKILL.md",
216
- "sha256": "b31921106fa02f3ac48d3409b866a0d58a84dd98ec8888a4da37964ebbce48ef"
216
+ "sha256": "b8bc2de0e889007ae64d615e291ea6d369c96dbed0b62fed47cf19c372af4f1b"
217
217
  },
218
218
  {
219
219
  "path": "shared/core/multi-agent-search/SKILL.md",
@@ -229,7 +229,7 @@
229
229
  },
230
230
  {
231
231
  "path": "shared/core/multi-agent-setup/SKILL.md",
232
- "sha256": "28e80bacb521b3c41aac18859ca3921735970b0bf27f064bfc2b496db9f85016"
232
+ "sha256": "eaf7d4db93ddd392ba96da48d6dc2859b645b8c630ed9a73423498dd3e1d0b4a"
233
233
  },
234
234
  {
235
235
  "path": "shared/core/multi-agent-stack/SKILL.md",
@@ -265,7 +265,7 @@
265
265
  },
266
266
  {
267
267
  "path": "shared/core/multi-agent-test-gen/SKILL.md",
268
- "sha256": "0da450ed0c74ab610d3c8f0cf1b0facd2a4ae179afc832440939c3367de7e762"
268
+ "sha256": "a66d1171455d95df92a367095eeec11f50033d86c53a7d0032597cebd40d6f21"
269
269
  },
270
270
  {
271
271
  "path": "shared/core/multi-agent-test-screenshots/SKILL.md",
@@ -1368,7 +1368,7 @@
1368
1368
  },
1369
1369
  {
1370
1370
  "name": "multi-agent-office-on",
1371
- "description": "Open the office panel: a local browser page with agents at work, their chats, cost ledger and new runs. Use when you want to watch or drive runs from a browser.",
1371
+ "description": "Open the office: a local browser page that shows runs and agents live and drives the ecosystem (new runs, jobs, PRs, keys, memory, system, cleanup). Use when you want to watch or drive runs from a browser.",
1372
1372
  "platform": null,
1373
1373
  "group": "core",
1374
1374
  "plugin": null,
@@ -58,7 +58,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
58
58
  | [`multi-agent-manual-test`](./core/multi-agent-manual-test/) | `core` | Switch to the active task's branch and prepare it for manual testing in Xcode. Phase 3 user-test step, standalone (the UI Bug Hunter lives a |
59
59
  | [`multi-agent-model`](./core/multi-agent-model/) | `core` | Turn the top model rung on or off and keep the cost ledger's pricing in step with it. Use when asked to enable or disable Fable, or which mo |
60
60
  | [`multi-agent-office-off`](./core/multi-agent-office-off/) | `core` | Close the office panel and stop its local server. Use when done with the panel. |
61
- | [`multi-agent-office-on`](./core/multi-agent-office-on/) | `core` | Open the office panel: a local browser page with agents at work, their chats, cost ledger and new runs. Use when you want to watch or drive |
61
+ | [`multi-agent-office-on`](./core/multi-agent-office-on/) | `core` | Open the office: a local browser page that shows runs and agents live and drives the ecosystem (new runs, jobs, PRs, keys, memory, system, c |
62
62
  | [`multi-agent-office-status`](./core/multi-agent-office-status/) | `core` | Whether the office panel is running, its URL (never the token) and its last actions. Use when unsure if the panel is open. |
63
63
  | [`multi-agent-postmortem`](./core/multi-agent-postmortem/) | `core` | Post-mortem of a run from its session transcripts: loops, stalls, root-cause hypotheses on file:line, fixes. Read-only, redacted. Use when a |
64
64
  | [`multi-agent-prune-logs`](./core/multi-agent-prune-logs/) | `core` | Delete per-task project logs under ~/.claude/logs/multi-agent (filter by age/project/task). Audit trail + metrics are preserved. Dry-run fir |
@@ -2,7 +2,7 @@
2
2
  name: multi-agent-graph
3
3
  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."
4
4
  user-invocable: true
5
- argument-hint: "[build | refresh | ask \"<question>\" | affected \"<symbol>\" | report | status]"
5
+ argument-hint: "[build | refresh | ask \"<question>\" | affected \"<symbol>\" | path <from> <to> | links <a,b> | report | diff <older> | status]"
6
6
  metadata:
7
7
  language: en
8
8
  ---
@@ -27,7 +27,10 @@ No worktree, no branch, no commit, no pipeline chaining.
27
27
  | `refresh` | same as `build` | A full rebuild takes seconds, so there is no separate incremental path |
28
28
  | `ask "<question>"` | `graph-query.mjs "<question>" --budget N` | Token-budgeted traversal; default budget 2000 |
29
29
  | `affected "<symbol>"` | `graph-affected.mjs "<symbol>" --depth N` | Reverse traversal: the blast radius of a change |
30
- | `report` | `graph-report.mjs` | Writes `GRAPH_REPORT.md` beside the graph |
30
+ | `path <from> <to>` | `graph-query.mjs --path <from> <to>` | Shortest path between two symbols or files (id or name). An ambiguous name is refused with candidate ids, an unknown one gets its nearest names (exit 3); external modules are never walked through |
31
+ | `links <a,b,...>` | `graph-links.mjs build --projects <a,b,...>`, then `graph-links.mjs affected --project <b>` | Which files of one repository import another (package, module or Kotlin type names), for a run across a main repo and its libraries. Graphs stay separate (ADR-0018) |
32
+ | `report` | `graph-report.mjs` | Writes `GRAPH_REPORT.md` beside the graph: hubs, import cycles, clusters with cohesion, questions worth asking, modules, external dependencies, unconnected files, and symbols no other file references (candidates only - the extractor is regex, not a parser, so nothing gates on that list) |
33
+ | `diff <older-graph>` | `graph-report.mjs --diff <older-graph>` | Files, symbols and edges added or removed since an older build, and the import-cycle count before and after |
31
34
  | `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 |
32
35
 
33
36
  With no argument, run `status`, then offer `build` when no graph exists and
@@ -16,11 +16,11 @@ Two-axis language preference for the pipeline:
16
16
  | Field | Controls | Stored at | Mutability |
17
17
  |---|---|---|---|
18
18
  | `promptLanguage` | Interactive prompts during a pipeline run (account picker, project picker, dev-context picker, base-branch picker, branch-name picker, maturity ack, channels picker, Phase 3 test prompt, Phase 4 local-checkout prompt) | `prefs.global.promptLanguage` | **Fixed to `en`** - never toggled by this skill |
19
- | `outputLanguage` | Assistant's explanations, status updates, error messages, and pipeline-generated reports rendered to the user (NOT external payloads) | `prefs.global.outputLanguage` | Toggled by this skill |
19
+ | `outputLanguage` | Assistant's explanations, status updates, error messages, and pipeline-generated reports rendered to the user, and PR, Jira, issue and wiki bodies (commits, branch names and agent prompts stay English) | `prefs.global.outputLanguage` | Toggled by this skill |
20
20
 
21
21
  **Why promptLanguage is fixed:** it governs the picker's structural chrome only - the `AskUserQuestion` `header` chip, host error UI, and internal contract identifiers. The chip stays English because it is capped at 12 characters and most Turkish equivalents overflow it. Everything a user actually reads follows `outputLanguage`: the picker `question`, each option's `label`, and each option's `description`, per the canonical per-field matrix in `multi-agent-refs/rules.md`. A picker whose question or buttons are English on a Turkish run is a bug, not the contract. The host's own **Other** row is injected by the CLI and stays English on every run; nothing in the pipeline can localize it.
22
22
 
23
- **Always English regardless of either field**: commit messages, PR titles/bodies, Jira comments, wiki pages, reviewer/triage system prompts, agent-log.md payloads, skill-picker / confirmation / error UI exposed by the CLI host.
23
+ **Always English regardless of either field**: commit messages, PR title prefixes (`feat:`, `fix:`), branch names, code identifiers, file paths, `agent-log.md` payloads, reviewer/triage system prompts, and confirmation / error UI exposed by the CLI host. PR descriptions, Jira comments, GitHub issue comments and wiki/Confluence page **bodies** follow `outputLanguage`, per the `rules.md` matrix.
24
24
 
25
25
  ## Invocations
26
26
 
@@ -72,8 +72,8 @@ jq --arg ol "$NEW_OUTPUT" '
72
72
 
73
73
  Render in the new outputLanguage:
74
74
 
75
- - `en`: `outputLanguage=<Y>. promptLanguage stays "en". External payloads remain English.`
76
- - `tr`: `outputLanguage=<Y>. promptLanguage "en" sabit. Dış çıktılar İngilizce kalır.`
75
+ - `en`: `outputLanguage=<Y>. promptLanguage stays "en". Commits, branch names and agent prompts stay English; PR, Jira and wiki bodies follow outputLanguage.`
76
+ - `tr`: `outputLanguage=<Y>. promptLanguage "en" sabit. Commit'ler, branch adları ve ajan istemleri İngilizce kalır; PR, Jira ve wiki metinleri outputLanguage'i izler.`
77
77
 
78
78
  ## Validation
79
79