@mmerterden/multi-agent-pipeline 14.2.1 → 14.2.2

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 (106) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +10 -6
  3. package/README.tr.md +143 -0
  4. package/docs/architecture.md +23 -8
  5. package/docs/ecosystem.md +237 -0
  6. package/install/_plugin-skills.mjs +16 -2
  7. package/install/codex.mjs +9 -4
  8. package/install/templates/copilot-instructions.md +12 -9
  9. package/package.json +1 -1
  10. package/pipeline/commands/multi-agent/SKILL.md +2 -2
  11. package/pipeline/commands/multi-agent/analysis/SKILL.md +2 -2
  12. package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
  13. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -4
  14. package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -4
  15. package/pipeline/commands/multi-agent/dev/SKILL.md +10 -23
  16. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +10 -2
  17. package/pipeline/commands/multi-agent/dev-local/SKILL.md +10 -24
  18. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +10 -3
  19. package/pipeline/commands/multi-agent/jira/SKILL.md +13 -2
  20. package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
  21. package/pipeline/commands/multi-agent/local/SKILL.md +6 -2
  22. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +6 -2
  23. package/pipeline/commands/multi-agent/log/SKILL.md +7 -1
  24. package/pipeline/commands/multi-agent/ship/SKILL.md +5 -1
  25. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  26. package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
  27. package/pipeline/lib/extract-conventions.sh +44 -15
  28. package/pipeline/lib/fetch-figma-annotations.sh +8 -1
  29. package/pipeline/lib/fetch-fortify.sh +23 -8
  30. package/pipeline/lib/figma-screenshot.sh +11 -1
  31. package/pipeline/lib/issue-fetcher.sh +76 -9
  32. package/pipeline/lib/md2confluence-v3.py +16 -2
  33. package/pipeline/lib/plan-todos.sh +5 -2
  34. package/pipeline/lib/post-pr-review.sh +8 -6
  35. package/pipeline/lib/shadow-git.sh +50 -9
  36. package/pipeline/lib/submodule-detector.sh +8 -1
  37. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  38. package/pipeline/multi-agent-refs/channels/confluence.md +3 -0
  39. package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
  40. package/pipeline/multi-agent-refs/channels/jira.md +13 -2
  41. package/pipeline/multi-agent-refs/channels/pr-review-actions.md +1 -1
  42. package/pipeline/multi-agent-refs/channels/pr.md +20 -0
  43. package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
  44. package/pipeline/multi-agent-refs/cross-cli-contract.md +3 -3
  45. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  46. package/pipeline/multi-agent-refs/generate-issue.md +2 -2
  47. package/pipeline/multi-agent-refs/issue-jira-triad.md +3 -3
  48. package/pipeline/multi-agent-refs/payload-contracts.md +67 -0
  49. package/pipeline/multi-agent-refs/phases/modes.md +20 -0
  50. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
  51. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -40
  52. package/pipeline/multi-agent-refs/phases/phase-7-report.md +5 -3
  53. package/pipeline/multi-agent-refs/phases.md +6 -0
  54. package/pipeline/multi-agent-refs/rules.md +2 -0
  55. package/pipeline/schemas/prefs.schema.json +1 -1
  56. package/pipeline/scripts/audit-log-rotate.sh +10 -0
  57. package/pipeline/scripts/build-stack-plugins.mjs +8 -1
  58. package/pipeline/scripts/check-derived-drift.mjs +13 -1
  59. package/pipeline/scripts/diff-explain.mjs +41 -3
  60. package/pipeline/scripts/diff-risk-score.mjs +72 -8
  61. package/pipeline/scripts/gen-mode-dispatch.mjs +1 -1
  62. package/pipeline/scripts/learning-curve.mjs +8 -2
  63. package/pipeline/scripts/output-quality-check.sh +15 -4
  64. package/pipeline/scripts/phase-tracker.sh +21 -8
  65. package/pipeline/scripts/pre-commit-check.sh +69 -22
  66. package/pipeline/scripts/render-agent-log-cost.sh +8 -3
  67. package/pipeline/scripts/render-cost-summary.sh +42 -22
  68. package/pipeline/scripts/render-work-summary.sh +47 -13
  69. package/pipeline/scripts/review-scope.mjs +1 -1
  70. package/pipeline/scripts/run-aggregator.mjs +38 -14
  71. package/pipeline/scripts/smoke-schema-validation.sh +5 -1
  72. package/pipeline/scripts/test-gap-scan.mjs +45 -6
  73. package/pipeline/scripts/uninstall.mjs +39 -4
  74. package/pipeline/scripts/update-issue-progress.sh +12 -16
  75. package/pipeline/scripts/worktree-finalize.sh +23 -2
  76. package/pipeline/skills/.skills-index.json +11 -2
  77. package/pipeline/skills/shared/README.md +1 -1
  78. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -0
  79. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
  80. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +4 -17
  81. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +8 -0
  82. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +5 -18
  83. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +8 -0
  84. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
  85. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +4 -0
  86. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -0
  87. package/pipeline/skills/shared/core/multi-agent-ship/SKILL.md +4 -0
  88. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  89. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +302 -0
  90. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +144 -0
  91. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +156 -0
  92. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +108 -0
  93. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +175 -0
  94. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +865 -0
  95. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +335 -0
  96. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +344 -0
  97. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +130 -0
  98. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +264 -0
  99. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +298 -0
  100. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +529 -0
  101. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +187 -0
  102. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +171 -0
  103. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +184 -0
  104. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +26 -0
  105. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +173 -0
  106. package/pipeline/skills/skills-index.md +3 -2
@@ -82,14 +82,27 @@ function die(msg) {
82
82
  process.exit(1);
83
83
  }
84
84
 
85
- function resolveTaskDir() {
86
- if (flags["task-dir"]) return flags["task-dir"];
87
- if (flags["task-id"] && flags.project) {
85
+ // phase-tracker.sh writes tracker-state.json (never phase-tracker.json) at
86
+ // $HOME/.claude/logs/multi-agent/<task-id>/ (no <project> segment) - the
87
+ // same drift render-cost-summary.sh/render-agent-log-cost.sh already guard
88
+ // against with a candidate list. Mirror that list here instead of assuming
89
+ // one fixed filename/path.
90
+ function resolveTrackerCandidates() {
91
+ const candidates = [];
92
+ if (flags["task-dir"]) {
93
+ candidates.push(join(flags["task-dir"], "tracker-state.json"));
94
+ }
95
+ if (flags["task-id"]) {
88
96
  const HOME = process.env.HOME || process.env.USERPROFILE;
89
97
  if (!HOME) die("neither HOME nor USERPROFILE is set - pass --task-dir");
90
- return join(HOME, ".claude", "logs", "multi-agent", flags.project, String(flags["task-id"]));
98
+ const taskId = String(flags["task-id"]);
99
+ if (flags.project) {
100
+ candidates.push(join(HOME, ".claude", "logs", "multi-agent", flags.project, taskId, "tracker-state.json"));
101
+ }
102
+ candidates.push(join(HOME, ".claude", "logs", "multi-agent", taskId, "tracker-state.json"));
91
103
  }
92
- die("either --task-dir or --task-id with --project is required");
104
+ if (!candidates.length) die("either --task-dir or --task-id is required");
105
+ return candidates;
93
106
  }
94
107
 
95
108
  function loadJson(path) {
@@ -161,11 +174,14 @@ function tsCompare(a, b) {
161
174
  }
162
175
 
163
176
  // ─────────────────────────────────────────────────────────────────────────
164
- const taskDir = resolveTaskDir();
165
- if (!existsSync(taskDir)) die(`task dir not found: ${taskDir}`);
166
-
167
- const tracker = loadJson(join(taskDir, "phase-tracker.json"));
168
- if (!tracker) die(`phase-tracker.json not found in ${taskDir}`);
177
+ const trackerCandidates = resolveTrackerCandidates();
178
+ const trackerPath = trackerCandidates.find((p) => existsSync(p));
179
+ if (!trackerPath) {
180
+ die(`tracker state not found, tried:\n${trackerCandidates.map((p) => ` ${p}`).join("\n")}`);
181
+ }
182
+ const taskDir = dirname(trackerPath);
183
+ const tracker = loadJson(trackerPath);
184
+ if (!tracker) die(`failed to parse tracker state at ${trackerPath}`);
169
185
 
170
186
  const otelSpans = loadJsonl(join(taskDir, "otel-spans.jsonl"));
171
187
  const costTable = loadCostTable();
@@ -187,7 +203,15 @@ let totalOut = 0;
187
203
  let totalCost = 0;
188
204
  let unknownModelHits = 0;
189
205
 
190
- for (const phase of tracker.phases || []) {
206
+ // tracker.phases ships in two shapes (same contract render-cost-summary.sh
207
+ // normalizes): an array (phase-tracker.sh's real output) or an object keyed
208
+ // by phase id (older fixtures / hand-built variants). Normalize once so the
209
+ // rest of this file can treat it as a plain array.
210
+ const phaseList = Array.isArray(tracker.phases)
211
+ ? tracker.phases
212
+ : Object.entries(tracker.phases || {}).map(([key, value]) => ({ ...value, id: value?.id ?? key }));
213
+
214
+ for (const phase of phaseList) {
191
215
  const tIn = phase.tokens_in || 0;
192
216
  const tOut = phase.tokens_out || 0;
193
217
  const model = phase.model || inferModelForPhase(phase.id);
@@ -279,9 +303,9 @@ const summary = {
279
303
  kind: "summary",
280
304
  ts: lastTs,
281
305
  task_id: taskId,
282
- phases_total: tracker.phases?.length ?? 0,
283
- phases_completed: (tracker.phases || []).filter((p) => p.status === "completed").length,
284
- phases_failed: (tracker.phases || []).filter((p) => p.status === "failed").length,
306
+ phases_total: phaseList.length,
307
+ phases_completed: phaseList.filter((p) => p.status === "completed").length,
308
+ phases_failed: phaseList.filter((p) => p.status === "failed").length,
285
309
  tokens_in: totalIn,
286
310
  tokens_out: totalOut,
287
311
  total_cost_usd: Number(totalCost.toFixed(6)),
@@ -91,10 +91,14 @@ echo "→ 4. prefs.schema.json - keychainMapping covers every resolved service
91
91
  # A floor, not a pin: new credentials get added over time, and pinning the count
92
92
  # made every addition a false failure. What matters is that the keys shipped
93
93
  # flows resolve are all declared.
94
+ # process.stdout.write of a String, not console.log of a Number: console.log
95
+ # inspects non-string arguments, so with FORCE_COLOR set in the environment the
96
+ # count comes back wrapped in ANSI codes and the -ge comparison below silently
97
+ # fails on a value that is actually in range.
94
98
  KM_COUNT=$(node -e "
95
99
  const s = JSON.parse(require('fs').readFileSync('$PREFS_SCHEMA', 'utf8'));
96
100
  const km = s.properties.global.properties.keychainMapping.properties;
97
- console.log(Object.keys(km).length);
101
+ process.stdout.write(String(Object.keys(km).length));
98
102
  " 2>/dev/null)
99
103
  if [ "${KM_COUNT:-0}" -ge 12 ] 2>/dev/null; then
100
104
  pass "keychainMapping declares $KM_COUNT services (≥12 expected)"
@@ -141,6 +141,45 @@ function isExcluded(path) {
141
141
  return false;
142
142
  }
143
143
 
144
+ // git quotes a `diff --git` path (core.quotePath, on by default) whenever it
145
+ // has a non-ASCII byte or other unusual character - e.g. a Turkish filename
146
+ // renders as `diff --git "a/\303\226deme.swift" "b/\303\226deme.swift"`, not
147
+ // the bare a/...b/... form. The plain regex this replaced never matched that
148
+ // line at all, so every non-ASCII-named file's added lines were silently
149
+ // dropped from the test-gap scan.
150
+ const DIFF_GIT_QUOTED_RE = /^diff --git "a\/((?:[^"\\]|\\.)*)" "b\/((?:[^"\\]|\\.)*)"$/;
151
+ const DIFF_GIT_UNQUOTED_RE = /^diff --git a\/(.+?) b\/(.+)$/;
152
+
153
+ function unquoteGitPath(s) {
154
+ const bytes = [];
155
+ for (let i = 0; i < s.length; i++) {
156
+ if (s[i] === "\\") {
157
+ const octal = s.slice(i + 1, i + 4);
158
+ if (/^[0-7]{3}$/.test(octal)) {
159
+ bytes.push(parseInt(octal, 8));
160
+ i += 3;
161
+ continue;
162
+ }
163
+ const simple = { "\\": 92, '"': 34, t: 9, n: 10, r: 13 };
164
+ const next = s[i + 1];
165
+ if (next in simple) {
166
+ bytes.push(simple[next]);
167
+ i += 1;
168
+ continue;
169
+ }
170
+ }
171
+ bytes.push(s.charCodeAt(i));
172
+ }
173
+ return Buffer.from(bytes).toString("utf-8");
174
+ }
175
+
176
+ function matchDiffGitHeader(line) {
177
+ const q = line.match(DIFF_GIT_QUOTED_RE);
178
+ if (q) return unquoteGitPath(q[2]);
179
+ const u = line.match(DIFF_GIT_UNQUOTED_RE);
180
+ return u ? u[2] : null;
181
+ }
182
+
144
183
  function isTestPath(path) {
145
184
  if (Array.isArray(rules.testPathSuffixes) && rules.testPathSuffixes.some((s) => path.endsWith(s)))
146
185
  return true;
@@ -190,9 +229,9 @@ function parseDiffAddedLines(diffText) {
190
229
  const perFile = new Map();
191
230
  let cur = null;
192
231
  for (const line of diffText.split("\n")) {
193
- const m = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
194
- if (m) {
195
- cur = m[2];
232
+ const p = matchDiffGitHeader(line);
233
+ if (p !== null) {
234
+ cur = p;
196
235
  if (!perFile.has(cur)) perFile.set(cur, { lines: [], isNew: false });
197
236
  continue;
198
237
  }
@@ -300,9 +339,9 @@ function symbolMentionedInTest(diffText, symbol) {
300
339
  const lines = diffText.split("\n");
301
340
  let inTestFile = false;
302
341
  for (const line of lines) {
303
- const fm = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
304
- if (fm) {
305
- inTestFile = isTestPath(fm[2]);
342
+ const fm = matchDiffGitHeader(line);
343
+ if (fm !== null) {
344
+ inTestFile = isTestPath(fm);
306
345
  continue;
307
346
  }
308
347
  if (!inTestFile) continue;
@@ -248,6 +248,31 @@ function rmMatchingFiles(parent, predicate) {
248
248
  return count;
249
249
  }
250
250
 
251
+ // Copilot CLI / Codex CLI have no plugin loader, so install/_plugin-skills.mjs
252
+ // copies each stack plugin's authored skills straight into the flat skills/
253
+ // dir (~100 dirs across a typical set of enabled plugins). None of those
254
+ // names are in PIPELINE_CORE_SKILL_DIRS - the allowlist above only covers the
255
+ // pipeline's OWN skills - so they were never removed on uninstall. The
256
+ // installer now writes PLUGIN_SKILLS_MANIFEST listing exactly what it
257
+ // delivered; read it back here rather than guessing plugin skill names.
258
+ const PLUGIN_SKILLS_MANIFEST = ".plugin-skills-manifest.json";
259
+
260
+ function rmPluginDeliveredSkills(skillsDir) {
261
+ const manifestPath = join(skillsDir, PLUGIN_SKILLS_MANIFEST);
262
+ if (!existsSync(manifestPath)) return 0;
263
+ let names;
264
+ try {
265
+ names = JSON.parse(readFileSync(manifestPath, "utf-8"));
266
+ } catch {
267
+ return 0;
268
+ }
269
+ if (!Array.isArray(names)) return 0;
270
+ const nameSet = new Set(names);
271
+ const count = rmMatchingDirs(skillsDir, (name) => nameSet.has(name));
272
+ rmIfExists(manifestPath);
273
+ return count;
274
+ }
275
+
251
276
  // Explicit terminator install/copilot.mjs writes after the pipeline section
252
277
  // (v11.4.1+). Must stay in sync with INSTRUCTIONS_END_MARKER there.
253
278
  const COPILOT_END_MARKER = "<!-- multi-agent-pipeline:copilot-instructions:end -->";
@@ -387,13 +412,20 @@ function cleanClaudeSettings(settingsPath) {
387
412
  if (settings.hooks?.PreToolUse?.length) {
388
413
  const before = settings.hooks.PreToolUse.length;
389
414
  // Match both the current matcher ("Bash") and the dead pre-v11.4.1 one
390
- // ("Bash(git commit:*)"); the pre-commit-check.sh command identifies the
391
- // entry as ours either way.
415
+ // ("Bash(git commit:*)"); the command identifies the entry as ours either
416
+ // way. install/claude.mjs registers TWO PreToolUse gates this way -
417
+ // pre-commit-check.sh AND agent-guard.sh - and only the first was ever
418
+ // stripped here. After ~/.claude/scripts/ is deleted below, the
419
+ // agent-guard.sh entry pointed at a script that no longer existed, so
420
+ // every subsequent Bash call fired a hook error.
392
421
  settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
393
422
  (h) =>
394
423
  !(
395
424
  (h.matcher === "Bash" || h.matcher === "Bash(git commit:*)") &&
396
- h.hooks?.some((sub) => sub.command?.includes("pre-commit-check.sh"))
425
+ h.hooks?.some(
426
+ (sub) =>
427
+ sub.command?.includes("pre-commit-check.sh") || sub.command?.includes("agent-guard.sh"),
428
+ )
397
429
  ),
398
430
  );
399
431
  if (settings.hooks.PreToolUse.length < before) touched = true;
@@ -540,10 +572,12 @@ export async function main() {
540
572
  name === "figma-to-component",
541
573
  );
542
574
  n += rmMatchingDirs(skills, (name) => PIPELINE_CORE_SKILL_DIRS.includes(name));
575
+ n += rmPluginDeliveredSkills(skills);
543
576
  if (n > 0) console.log(` removed ${n} skill dir(s) under ${skills}`);
544
577
  rmIfExists(join(skills, ".skills-index.json"));
545
578
  rmIfExists(join(skills, "skills-index.md"));
546
579
  rmIfExists(join(skills, "README.md"));
580
+ rmIfExists(join(COP, "rules"));
547
581
  // Agent definitions are FILES; remove only the pipeline-shipped names so
548
582
  // user-authored agents survive.
549
583
  rmMatchingFiles(join(COP, "agents"), (name) => PIPELINE_AGENT_FILES.includes(name));
@@ -613,10 +647,11 @@ export async function main() {
613
647
  // Codex's own `.system` set plus user skills; `prompts/` and `agents/` hold
614
648
  // user-authored files.
615
649
  const skills = join(CODEX, "skills");
616
- const n = rmMatchingDirs(
650
+ let n = rmMatchingDirs(
617
651
  skills,
618
652
  (name) => name === "multi-agent" || name.startsWith("multi-agent-"),
619
653
  );
654
+ n += rmPluginDeliveredSkills(skills);
620
655
  if (n > 0) console.log(` removed ${n} skill dir(s) under ${skills}`);
621
656
  rmIfExists(join(CODEX, "prompts", "multi-agent.md"));
622
657
  removeGeneratedCodexAgents(join(CODEX, "agents"));
@@ -14,8 +14,8 @@
14
14
  # update-issue-progress.sh <task-id>
15
15
  #
16
16
  # Inputs:
17
- # $HOME/.claude/projects/{slug}/state/{taskId}/agent-state.json
18
- # .issue.url, .issue.number, .repos.primary.org, .repos.primary.name
17
+ # $HOME/.claude/logs/multi-agent/{project}/{taskId}/agent-state.json
18
+ # .githubIssue.owner, .githubIssue.repo, .githubIssue.number
19
19
  # .flags.implementation, .flags.testing, .flags.codeConnect, .flags.wiki
20
20
  #
21
21
  # Side effects:
@@ -86,11 +86,12 @@ if [ "$TASK_ID" = "--rewrite" ]; then
86
86
  exit 0
87
87
  fi
88
88
 
89
- # Resolve the project state directory (matches phase-tracker convention).
90
- STATE_GLOB="$HOME/.claude/projects/*"
89
+ # Resolve agent-state.json. multi-repo-pipeline.sh writes it under a
90
+ # per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
91
+ # - not the ~/.claude/projects/*/state/ path this used to glob, which no
92
+ # writer in the pipeline ever populates.
91
93
  AGENT_STATE=""
92
- for dir in $STATE_GLOB; do
93
- candidate="$dir/state/$TASK_ID/agent-state.json"
94
+ for candidate in "$HOME"/.claude/logs/multi-agent/*/"$TASK_ID"/agent-state.json; do
94
95
  if [ -f "$candidate" ]; then
95
96
  AGENT_STATE="$candidate"
96
97
  break
@@ -102,18 +103,13 @@ if [ -z "$AGENT_STATE" ]; then
102
103
  exit 2
103
104
  fi
104
105
 
105
- ISSUE_URL=$(jq -r '.issue.url // empty' "$AGENT_STATE")
106
- ISSUE_NUM=$(jq -r '.issue.number // empty' "$AGENT_STATE")
107
- ORG_REPO=$(jq -r '.repos.primary.name // empty' "$AGENT_STATE")
108
- # Derive org/repo from URL if not present in state.
109
- if [ -z "$ORG_REPO" ] || ! [[ "$ORG_REPO" == */* ]]; then
110
- if [[ "$ISSUE_URL" =~ github\.com/([^/]+/[^/]+)/issues/ ]]; then
111
- ORG_REPO="${BASH_REMATCH[1]}"
112
- fi
113
- fi
106
+ # agent-state.schema.json has no `.issue`/`.repos` properties (additionalProperties
107
+ # is false) - the real GitHub-issue coordinates live at `.githubIssue.{owner,repo,number}`.
108
+ ISSUE_NUM=$(jq -r '.githubIssue.number // empty' "$AGENT_STATE")
109
+ ORG_REPO=$(jq -r 'if .githubIssue then (.githubIssue.owner + "/" + .githubIssue.repo) else empty end' "$AGENT_STATE")
114
110
 
115
111
  if [ -z "$ORG_REPO" ] || [ -z "$ISSUE_NUM" ]; then
116
- echo "update-issue-progress: cannot resolve org/repo or issue number from state" >&2
112
+ echo "update-issue-progress: no githubIssue in state for task=$TASK_ID (Jira-originated task, or issue not yet linked)" >&2
117
113
  exit 3
118
114
  fi
119
115
 
@@ -202,6 +202,7 @@ fi
202
202
  # copied only if present; a task that never reached Phase 4 has no triage output
203
203
  # and that is not an error.
204
204
  DEST="$LOGS_ROOT/${PROJECT:+$PROJECT/}$TASK_ID/artifacts"
205
+ LOGS_TASK_DIR="$LOGS_ROOT/${PROJECT:+$PROJECT/}$TASK_ID"
205
206
 
206
207
  copy_one() {
207
208
  src="$rp_wt/$1"
@@ -214,7 +215,27 @@ copy_one() {
214
215
  cp -R "$src" "$DEST/$1" 2>/dev/null && SALVAGED="${SALVAGED:+$SALVAGED,}\"$1\""
215
216
  }
216
217
 
217
- for a in agent-state.json phase-tracker.json triage-output.json \
218
+ # agent-state.json and the tracker state file are never written INSIDE the
219
+ # worktree checkout - multi-repo-pipeline.sh/phase-tracker.sh write both
220
+ # straight to $LOGS_TASK_DIR. copy_one() sourcing from $rp_wt always missed
221
+ # them (src never existed → silent no-op), so this salvage - and the
222
+ # worktreeRemovedAt stamp below, gated on the salvaged copy existing - never
223
+ # actually fired for either file.
224
+ copy_from_logs() {
225
+ src="$LOGS_TASK_DIR/$1"
226
+ [ -e "$src" ] || return 0
227
+ if [ "$DRY" = "1" ]; then
228
+ SALVAGED="${SALVAGED:+$SALVAGED,}\"$1\""
229
+ return 0
230
+ fi
231
+ mkdir -p "$DEST/$(dirname "$1")" 2>/dev/null
232
+ cp -R "$src" "$DEST/$1" 2>/dev/null && SALVAGED="${SALVAGED:+$SALVAGED,}\"$1\""
233
+ }
234
+
235
+ copy_from_logs agent-state.json
236
+ copy_from_logs tracker-state.json
237
+
238
+ for a in triage-output.json \
218
239
  .review-diff.txt .build.log .test.log .pipeline; do
219
240
  copy_one "$a"
220
241
  done
@@ -230,7 +251,7 @@ fi
230
251
  # tree. This is what keeps `--force` off the table: a refusal after this point
231
252
  # means unexpected dirt, and forcing past unexpected dirt is how work gets lost.
232
253
  DROPPED=""
233
- for a in agent-state.json phase-tracker.json triage-output.json \
254
+ for a in triage-output.json \
234
255
  .review-diff.txt .build.log .test.log .pipeline; do
235
256
  [ -e "$rp_wt/$a" ] || continue
236
257
  case "$a" in
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "skillCount": 201,
3
+ "skillCount": 202,
4
4
  "entries": [
5
5
  {
6
6
  "name": "accessibility-compliance-accessibility-audit",
@@ -695,6 +695,15 @@
695
695
  "triggerPaths": [],
696
696
  "relativePath": "shared/external/live-activities/SKILL.md"
697
697
  },
698
+ {
699
+ "name": "localization-reuse-map",
700
+ "description": ">-",
701
+ "platform": null,
702
+ "group": "external",
703
+ "triggerKeywords": [],
704
+ "triggerPaths": [],
705
+ "relativePath": "shared/external/localization-reuse-map/SKILL.md"
706
+ },
698
707
  {
699
708
  "name": "macos-menubar-tuist-app",
700
709
  "description": "Build, refactor, or review SwiftUI macOS menubar apps that use Tuist. Use when building, refactoring or reviewing a SwiftUI macOS menubar app that uses Tuist.",
@@ -922,7 +931,7 @@
922
931
  },
923
932
  {
924
933
  "name": "multi-agent-language",
925
- "description": "Toggle outputLanguage (assistant explanations). promptLanguage is fixed to English. External payloads stay English. Use when the assistant should explain itself in a different language.",
934
+ "description": "Toggle outputLanguage (assistant explanations, picker questions, PR/Jira/Confluence bodies). promptLanguage is fixed to English; commit messages, branch names and identifiers stay English. Use when the assistant should explain itself in a different language.",
926
935
  "platform": null,
927
936
  "group": "core",
928
937
  "triggerKeywords": [],
@@ -47,7 +47,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
47
47
  | [`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 |
48
48
  | [`multi-agent-jira`](./core/multi-agent-jira/) | `core` | List open Jira issues, pick one, and launch the multi-agent pipeline. Use when a Jira issue should be picked up and started without knowing |
49
49
  | [`multi-agent-kill`](./core/multi-agent-kill/) | `core` | Stop the given task, then remove its worktree and branch. Asks for confirmation. Use when a running or stuck task should be stopped and its |
50
- | [`multi-agent-language`](./core/multi-agent-language/) | `core` | Toggle outputLanguage (assistant explanations). promptLanguage is fixed to English. External payloads stay English. Use when the assistant s |
50
+ | [`multi-agent-language`](./core/multi-agent-language/) | `core` | Toggle outputLanguage (assistant explanations, picker questions, PR/Jira/Confluence bodies). promptLanguage is fixed to English; commit mess |
51
51
  | [`multi-agent-local`](./core/multi-agent-local/) | `core` | Full pipeline in local mode - no worktree, runs directly on the current branch. Use when the full pipeline should run on the current branc |
52
52
  | [`multi-agent-local-autopilot`](./core/multi-agent-local-autopilot/) | `core` | Full pipeline + local + autopilot - no worktree, no confirmations, all 8 phases run end-to-end on the current branch. Use when the full pi |
53
53
  | [`multi-agent-log`](./core/multi-agent-log/) | `core` | Show the agent-log.md for the given task. With no ID, shows the most recent task. Use when asked what a task did, or to read its log. |
@@ -49,3 +49,7 @@ Run the task end-to-end with no confirmations.
49
49
  2. **Set the autopilot flag** - `"autopilot": true` in `agent-state.json`
50
50
  3. **Start the multi-agent pipeline** - All confirmations are skipped
51
51
  4. **On failure** - After 3 retries still failing → pause, ask the user
52
+
53
+ ## Required: outward-facing payload contracts
54
+
55
+ Before writing anything outward-facing - PR body, Jira comment, Confluence page, closing report - load `$HOME/.claude/multi-agent-refs/payload-contracts.md`. It names the canonical section set for each payload, the markup dialect per surface (PR body is Markdown, Jira is wiki markup - mixing them is a defect), and the token/duration numbers the closing report must carry. Improvising a payload shape from memory is the most common failure of the short modes.
@@ -12,7 +12,7 @@ argument-hint: "[\"<free-text description>\"] [figma-url] [swagger-url] - all
12
12
 
13
13
  Creates exactly one Jira issue, and only after explicit approval. The issue type (Task / Bug / Story) is asked at the start of every run. No branches, no commits, no worktrees. The description comes from the type's standard template (baseline, not a mined heading set); sections auto-size - a conditional section renders only when its trigger is present, otherwise the heading is omitted.
14
14
 
15
- > **Language**: issue content (summary + description) and user-facing questions follow `prefs.global.outputLanguage` - intentional exception to the "external payloads stay English" default (the issue is authored for the user's team). Code identifiers and URLs stay verbatim.
15
+ > **Language**: issue content (summary + description) and user-facing questions follow `prefs.global.outputLanguage`, like every other user-facing payload body (`rules.md` matrix). Code identifiers and URLs stay verbatim.
16
16
 
17
17
  ## Standard templates (A = always, C = conditional)
18
18
 
@@ -64,23 +64,10 @@ Phase 7: Report → Channels (Jira / Confluence / PR / Wiki)
64
64
  | Phase 7 channels (Jira / Confluence / PR / Wiki) | ✅ | ✅ (same) |
65
65
  | Duration | ~10-15 min | ~7-10 min |
66
66
 
67
- ## Analysis doc supplied to a fast mode (warn before starting)
67
+ ## Required: outward-facing payload contracts
68
68
 
69
- The `--dev` family skips Phase 1 (Analysis) and Phase 2 (Planning) by design. So when
70
- the input references an analysis document - a Confluence URL, a local analysis file,
71
- or the user says "I ran analysis for this" - there is **no phase that turns it into a
72
- plan**. The doc becomes raw context for one Dev pass, and work comes out ordered by
73
- whatever the model read first: the bottom of the dependency chain lands, the screen
74
- wiring does not.
69
+ Before writing anything outward-facing - PR body, Jira comment, Confluence page, closing report - load `$HOME/.claude/multi-agent-refs/payload-contracts.md`. It names the canonical section set for each payload, the markup dialect per surface (PR body is Markdown, Jira is wiki markup - mixing them is a defect), and the token/duration numbers the closing report must carry. Improvising a payload shape from memory is the most common failure of the short modes.
75
70
 
76
- Say so before starting, once, and offer the choice:
71
+ ## Intake warnings (`--dev` family)
77
72
 
78
- ```
79
- This mode skips Analysis and Planning, so the analysis document will not be turned
80
- into a task breakdown. For analysis-driven screen work, /multi-agent or
81
- /multi-agent:local run both phases.
82
- 1. Continue with --dev (doc as context only)
83
- 2. Switch to the full pipeline
84
- ```
85
-
86
- Autopilot picks 1 and logs the warning rather than asking.
73
+ Two checks belong at the top of every `--dev` run and are specified once in `$HOME/.claude/multi-agent-refs/phases/modes.md` "Intake warnings shared by the whole `--dev` family": an analysis document supplied to a mode that skips Analysis and Planning, and a branch that already carries the work (which wants `/multi-agent:ship`, not a second Dev pass). Read that section rather than reasoning about it from scratch.
@@ -55,3 +55,11 @@ Phase 7: Report → Short terminal summary
55
55
  | Confirmation (test/commit/PR) | Yes | Yes | No | **No** |
56
56
  | Review | Parallel+triage (CLI-aware) | Parallel+triage (CLI-aware) | Parallel+triage (CLI-aware) | **Parallel+triage, auto-fix** |
57
57
  | Estimated duration | ~12 min | ~7 min | ~10 min | **~5 min** |
58
+
59
+ ## Required: outward-facing payload contracts
60
+
61
+ Before writing anything outward-facing - PR body, Jira comment, Confluence page, closing report - load `$HOME/.claude/multi-agent-refs/payload-contracts.md`. It names the canonical section set for each payload, the markup dialect per surface (PR body is Markdown, Jira is wiki markup - mixing them is a defect), and the token/duration numbers the closing report must carry. Improvising a payload shape from memory is the most common failure of the short modes.
62
+
63
+ ## Intake warnings (`--dev` family)
64
+
65
+ Two checks belong at the top of every `--dev` run and are specified once in `$HOME/.claude/multi-agent-refs/phases/modes.md` "Intake warnings shared by the whole `--dev` family": an analysis document supplied to a mode that skips Analysis and Planning, and a branch that already carries the work (which wants `/multi-agent:ship`, not a second Dev pass). Read that section rather than reasoning about it from scratch.
@@ -21,7 +21,7 @@ Phase 7: Report → Jira/Wiki + log + knowledge/memory
21
21
 
22
22
  ## When to use it
23
23
 
24
- - Quick bug fix or small feature - review overhead is unnecessary
24
+ - Quick bug fix or small feature - analysis and planning buy nothing here
25
25
  - You're working on the current branch and don't want to switch worktrees
26
26
  - Quick bug fix or small feature - analysis and planning buy nothing here
27
27
 
@@ -36,23 +36,10 @@ multi-agent-dev-local "PROJ-12345" # Jira
36
36
  multi-agent-dev-local "Bug: LoginView dark mode" # Free-text
37
37
  ```
38
38
 
39
- ## Analysis doc supplied to a fast mode (warn before starting)
40
-
41
- The `--dev` family skips Phase 1 (Analysis) and Phase 2 (Planning) by design. So when
42
- the input references an analysis document - a Confluence URL, a local analysis file,
43
- or the user says "I ran analysis for this" - there is **no phase that turns it into a
44
- plan**. The doc becomes raw context for one Dev pass, and work comes out ordered by
45
- whatever the model read first: the bottom of the dependency chain lands, the screen
46
- wiring does not.
39
+ ## Required: outward-facing payload contracts
47
40
 
48
- Say so before starting, once, and offer the choice:
41
+ Before writing anything outward-facing - PR body, Jira comment, Confluence page, closing report - load `$HOME/.claude/multi-agent-refs/payload-contracts.md`. It names the canonical section set for each payload, the markup dialect per surface (PR body is Markdown, Jira is wiki markup - mixing them is a defect), and the token/duration numbers the closing report must carry. Improvising a payload shape from memory is the most common failure of the short modes.
49
42
 
50
- ```
51
- This mode skips Analysis and Planning, so the analysis document will not be turned
52
- into a task breakdown. For analysis-driven screen work, /multi-agent or
53
- /multi-agent:local run both phases.
54
- 1. Continue with --dev (doc as context only)
55
- 2. Switch to the full pipeline
56
- ```
43
+ ## Intake warnings (`--dev` family)
57
44
 
58
- Autopilot picks 1 and logs the warning rather than asking.
45
+ Two checks belong at the top of every `--dev` run and are specified once in `$HOME/.claude/multi-agent-refs/phases/modes.md` "Intake warnings shared by the whole `--dev` family": an analysis document supplied to a mode that skips Analysis and Planning, and a branch that already carries the work (which wants `/multi-agent:ship`, not a second Dev pass). Read that section rather than reasoning about it from scratch.
@@ -41,3 +41,11 @@ Routes to the orchestrator with the `--dev --local autopilot` flags. The pipelin
41
41
  multi-agent-dev-local-autopilot "PROJ-12345" # Jira
42
42
  multi-agent-dev-local-autopilot "Add retry to HomeNetworking" # Free-text
43
43
  ```
44
+
45
+ ## Required: outward-facing payload contracts
46
+
47
+ Before writing anything outward-facing - PR body, Jira comment, Confluence page, closing report - load `$HOME/.claude/multi-agent-refs/payload-contracts.md`. It names the canonical section set for each payload, the markup dialect per surface (PR body is Markdown, Jira is wiki markup - mixing them is a defect), and the token/duration numbers the closing report must carry. Improvising a payload shape from memory is the most common failure of the short modes.
48
+
49
+ ## Intake warnings (`--dev` family)
50
+
51
+ Two checks belong at the top of every `--dev` run and are specified once in `$HOME/.claude/multi-agent-refs/phases/modes.md` "Intake warnings shared by the whole `--dev` family": an analysis document supplied to a mode that skips Analysis and Planning, and a branch that already carries the work (which wants `/multi-agent:ship`, not a second Dev pass). Read that section rather than reasoning about it from scratch.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: multi-agent-language
3
3
  language: en
4
- description: "Toggle outputLanguage (assistant explanations). promptLanguage is fixed to English. External payloads stay English. Use when the assistant should explain itself in a different language."
4
+ description: "Toggle outputLanguage (assistant explanations, picker questions, PR/Jira/Confluence bodies). promptLanguage is fixed to English; commit messages, branch names and identifiers stay English. Use when the assistant should explain itself in a different language."
5
5
  user-invocable: true
6
6
  argument-hint: "[en|tr] - sets outputLanguage; omit for interactive picker; use 'output en|tr' for explicit form"
7
7
  ---
@@ -35,3 +35,7 @@ multi-agent-local "PROJ-12345" # Jira
35
35
  multi-agent-local "#42" # GitHub issue
36
36
  multi-agent-local "LoginView dark mode fix" # Free-text
37
37
  ```
38
+
39
+ ## Required: outward-facing payload contracts
40
+
41
+ Before writing anything outward-facing - PR body, Jira comment, Confluence page, closing report - load `$HOME/.claude/multi-agent-refs/payload-contracts.md`. It names the canonical section set for each payload, the markup dialect per surface (PR body is Markdown, Jira is wiki markup - mixing them is a defect), and the token/duration numbers the closing report must carry. Improvising a payload shape from memory is the most common failure of the short modes.
@@ -51,3 +51,7 @@ multi-agent-local-autopilot "#3"
51
51
  ## Delegation
52
52
 
53
53
  The orchestrator skill (`multi-agent/SKILL.md`) takes the `--local` + `"autopilot": true` state flags together. Contract: `refs/phases/phase-0-init.md` Step 8 (local branch) + `refs/phases/phase-2-planning.md` Step 5 (gate skip) + Step 5c (safety classifier, v7.0.0+).
54
+
55
+ ## Required: outward-facing payload contracts
56
+
57
+ Before writing anything outward-facing - PR body, Jira comment, Confluence page, closing report - load `$HOME/.claude/multi-agent-refs/payload-contracts.md`. It names the canonical section set for each payload, the markup dialect per surface (PR body is Markdown, Jira is wiki markup - mixing them is a defect), and the token/duration numbers the closing report must carry. Improvising a payload shape from memory is the most common failure of the short modes.
@@ -45,3 +45,7 @@ multi-agent ship autopilot # no gate prompts: auto-fix, auto-PR, auto-c
45
45
  - Build+Test is the automated success gate (not the interactive device user-test - that is `multi-agent:manual-test`). If the repo has no tests, it reports "no tests present" - never fabricates results.
46
46
  - Commit/PR follows house rules: conventional message, `Ref: #N` (never Closes/Fixes), NO AI/bot attribution. PR opened only if one does not already exist.
47
47
  - Full phase contract lives in the Claude Code command `commands/multi-agent/ship/SKILL.md`; this skill is the Copilot-CLI counterpart.
48
+
49
+ ## Required: outward-facing payload contracts
50
+
51
+ Before writing anything outward-facing - PR body, Jira comment, Confluence page, closing report - load `$HOME/.claude/multi-agent-refs/payload-contracts.md`. It names the canonical section set for each payload, the markup dialect per surface (PR body is Markdown, Jira is wiki markup - mixing them is a defect), and the token/duration numbers the closing report must carry. Improvising a payload shape from memory is the most common failure of the short modes.
@@ -32,7 +32,7 @@ Run all steps automatically:
32
32
  ```
33
33
  Step 1: DETECT Compare timestamps, find stale targets
34
34
  Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 49 sub-command skills)
35
- Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + 44 specs as refs + 8 agent TOML)
35
+ Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + 49 specs as refs + 8 agent TOML)
36
36
  Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub)
37
37
  Step 3d: DEV-TOOLKIT Companion MCP server -> detect movement, ship gates, commit + publish
38
38
  Step 4: WEBSITE Version + phase/model counts -> {website-host} (i18n + projects.ts)
@@ -98,7 +98,7 @@ If nothing is stale -> report "All targets up to date" and stop.
98
98
  ## Codex Sync (Step 2b)
99
99
 
100
100
  This step does **not** hand-copy files. The Codex tree is a *transform* of the Claude
101
- tree, not a mirror: the 43 sub-command specs become reference files (Codex silently
101
+ tree, not a mirror: the 49 sub-command specs become reference files (Codex silently
102
102
  truncates its skills block - see `cross-cli-contract.md` 2.6), every reference to a
103
103
  CLI-owned tree is retargeted (`agents/<persona>.md` becomes `.toml`, the dispatcher
104
104
  becomes the router skill), the 8 personas are regenerated as TOML with a model +