@mmerterden/multi-agent-pipeline 16.17.1 → 16.19.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 (202) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +5 -5
  3. package/README.tr.md +2 -2
  4. package/docs/FIGMA_PIPELINE.md +1 -1
  5. package/docs/adr/0001-three-model-triage.md +4 -2
  6. package/docs/architecture.md +2 -2
  7. package/docs/ecosystem.md +13 -11
  8. package/docs/features.md +2 -2
  9. package/index.js +1 -1
  10. package/install/_common.mjs +25 -1
  11. package/install/_dev-only-files.mjs +2 -2
  12. package/install/_mcp-register.mjs +4 -3
  13. package/install/_plugin-skills.mjs +1 -3
  14. package/install/copilot.mjs +18 -9
  15. package/install/index.mjs +2 -4
  16. package/install/templates/copilot-instructions.md +7 -7
  17. package/package.json +1 -1
  18. package/pipeline/claude-md-template.md +2 -2
  19. package/pipeline/commands/multi-agent/SKILL.md +5 -5
  20. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +1 -1
  21. package/pipeline/commands/multi-agent/design-check/SKILL.md +1 -1
  22. package/pipeline/commands/multi-agent/help/SKILL.md +2 -2
  23. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
  24. package/pipeline/commands/multi-agent/review/SKILL.md +32 -18
  25. package/pipeline/commands/multi-agent/review-analysis/SKILL.md +1 -1
  26. package/pipeline/commands/multi-agent/store-ready/SKILL.md +2 -2
  27. package/pipeline/commands/multi-agent/sync/SKILL.md +2 -2
  28. package/pipeline/commands/sim-test.md +54 -15
  29. package/pipeline/lib/credential-inventory.sh +15 -2
  30. package/pipeline/lib/credential-store-resolver.sh +14 -4
  31. package/pipeline/lib/credential-store.sh +8 -2
  32. package/pipeline/lib/extract-conventions.sh +1 -14
  33. package/pipeline/lib/fetch-confluence.sh +12 -4
  34. package/pipeline/lib/fetch-crashlytics.sh +11 -8
  35. package/pipeline/lib/fetch-document.sh +1 -1
  36. package/pipeline/lib/fetch-figma-annotations.sh +7 -5
  37. package/pipeline/lib/fetch-fortify.sh +5 -3
  38. package/pipeline/lib/fetch-graylog.sh +5 -3
  39. package/pipeline/lib/figma-mcp-refresh.sh +1 -1
  40. package/pipeline/lib/figma-screenshot.sh +27 -24
  41. package/pipeline/lib/figma-token.sh +8 -4
  42. package/pipeline/lib/issue-fetcher.sh +0 -1
  43. package/pipeline/lib/jira-publish.sh +7 -5
  44. package/pipeline/lib/md2confluence-v3.py +13 -7
  45. package/pipeline/lib/multi-repo-pipeline.sh +18 -8
  46. package/pipeline/lib/plan-todos.sh +11 -0
  47. package/pipeline/lib/post-pr-review.sh +9 -2
  48. package/pipeline/lib/repo-cache.sh +18 -10
  49. package/pipeline/lib/review-watch.sh +60 -14
  50. package/pipeline/lib/shadow-git.sh +8 -4
  51. package/pipeline/lib/vercel-deploy.sh +2 -2
  52. package/pipeline/multi-agent-refs/_dev-context.md +5 -2
  53. package/pipeline/multi-agent-refs/analysis/locked.md +4 -4
  54. package/pipeline/multi-agent-refs/analysis/render.md +1 -1
  55. package/pipeline/multi-agent-refs/cross-cli-contract.md +1 -1
  56. package/pipeline/multi-agent-refs/outside-the-pipeline.md +6 -6
  57. package/pipeline/multi-agent-refs/phases/log-format.md +1 -1
  58. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  59. package/pipeline/multi-agent-refs/phases/phase-0-init.md +4 -2
  60. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +3 -3
  61. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
  62. package/pipeline/multi-agent-refs/phases/phase-4-review.md +26 -21
  63. package/pipeline/multi-agent-refs/platform-parity.md +40 -28
  64. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  65. package/pipeline/rules/figma-pipeline.md +18 -72
  66. package/pipeline/rules/outside-the-pipeline.md +4 -3
  67. package/pipeline/schemas/agent-state.schema.json +1 -1
  68. package/pipeline/schemas/prefs.schema.json +38 -22
  69. package/pipeline/schemas/token-budget.json +1 -1
  70. package/pipeline/scripts/_stack-routing.mjs +1 -1
  71. package/pipeline/scripts/agent-guard.py +102 -21
  72. package/pipeline/scripts/anonymize-findings.mjs +7 -6
  73. package/pipeline/scripts/build-skills-index.mjs +14 -3
  74. package/pipeline/scripts/cost-budget-check.mjs +5 -3
  75. package/pipeline/scripts/cost-lib.sh +0 -15
  76. package/pipeline/scripts/diff-explain.mjs +22 -12
  77. package/pipeline/scripts/gc-refs.sh +6 -2
  78. package/pipeline/scripts/gc-tmp.sh +1 -1
  79. package/pipeline/scripts/gc-worktrees.sh +1 -1
  80. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
  81. package/pipeline/scripts/github-ssh-setup.sh +7 -2
  82. package/pipeline/scripts/graph-build.mjs +2 -2
  83. package/pipeline/scripts/jira-wiki-escape.mjs +2 -1
  84. package/pipeline/scripts/keychain.py +12 -11
  85. package/pipeline/scripts/learning-curve.mjs +1 -1
  86. package/pipeline/scripts/migrate-prefs.mjs +1 -1
  87. package/pipeline/scripts/output-quality-check.sh +3 -1
  88. package/pipeline/scripts/phase-tracker.sh +1 -1
  89. package/pipeline/scripts/phase0-exit-gate.mjs +2 -1
  90. package/pipeline/scripts/plan-coverage-gate.mjs +2 -1
  91. package/pipeline/scripts/pre-commit-check.sh +23 -13
  92. package/pipeline/scripts/prune-logs.sh +1 -1
  93. package/pipeline/scripts/render-agent-log-cost.sh +3 -1
  94. package/pipeline/scripts/render-cost-summary.sh +4 -2
  95. package/pipeline/scripts/render-work-summary.sh +5 -3
  96. package/pipeline/scripts/repo-map.mjs +3 -2
  97. package/pipeline/scripts/scan-skills.sh +6 -2
  98. package/pipeline/scripts/search-logs.sh +8 -6
  99. package/pipeline/scripts/sign-skills.sh +3 -1
  100. package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
  101. package/pipeline/scripts/triage-memory.mjs +25 -4
  102. package/pipeline/scripts/uninstall.mjs +20 -12
  103. package/pipeline/scripts/update-check.sh +2 -2
  104. package/pipeline/scripts/update-issue-progress.sh +6 -5
  105. package/pipeline/scripts/validate-analysis-doc.mjs +6 -6
  106. package/pipeline/scripts/verify-skills.sh +3 -1
  107. package/pipeline/scripts/worktree-finalize.sh +22 -10
  108. package/pipeline/skills/.skill-manifest.json +81 -57
  109. package/pipeline/skills/.skills-index.json +19 -19
  110. package/pipeline/skills/shared/README.md +8 -8
  111. package/pipeline/skills/shared/core/multi-agent/SKILL.md +7 -7
  112. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +1 -1
  113. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
  114. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +1 -1
  115. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +2 -2
  116. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +23 -9
  117. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  118. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +56 -0
  119. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +0 -4
  120. package/pipeline/skills/shared/external/api-patterns/SKILL.md +12 -24
  121. package/pipeline/skills/shared/external/app-store-changelog/references/release-notes-guidelines.md +34 -0
  122. package/pipeline/skills/shared/external/app-store-changelog/scripts/collect_release_changes.sh +33 -0
  123. package/pipeline/skills/shared/external/architecture/SKILL.md +7 -9
  124. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +0 -4
  125. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +0 -1
  126. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +0 -14
  127. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +13 -13
  128. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +16 -16
  129. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +6 -6
  130. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +13 -13
  131. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -23
  132. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +18 -18
  133. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +30 -30
  134. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +11 -11
  135. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +33 -33
  136. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +52 -52
  137. package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +1 -1
  138. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +11 -11
  139. package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -1
  140. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +7 -3
  141. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -15
  142. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +0 -5
  143. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Package.swift +17 -0
  144. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/Resources/.keep +0 -0
  145. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/main.swift +11 -0
  146. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/version.env +2 -0
  147. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/build_icon.sh +49 -0
  148. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/compile_and_run.sh +63 -0
  149. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/launch.sh +28 -0
  150. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/make_appcast.sh +82 -0
  151. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +206 -0
  152. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +52 -0
  153. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +52 -0
  154. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/version.env +2 -0
  155. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/packaging.md +17 -0
  156. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/release.md +32 -0
  157. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/scaffold.md +79 -0
  158. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +0 -1
  159. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +0 -4
  160. package/pipeline/skills/shared/external/swift-concurrency-expert/references/approachable-concurrency.md +63 -0
  161. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +272 -0
  162. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md +33 -0
  163. package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +150 -0
  164. package/pipeline/skills/shared/external/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  165. package/pipeline/skills/shared/external/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  166. package/pipeline/skills/shared/external/swiftui-performance-audit/references/profiling-intake.md +44 -0
  167. package/pipeline/skills/shared/external/swiftui-performance-audit/references/report-template.md +47 -0
  168. package/pipeline/skills/shared/external/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  169. package/pipeline/skills/shared/external/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  170. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +201 -0
  171. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/async-state.md +96 -0
  172. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +46 -0
  173. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/controls.md +57 -0
  174. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/deeplinks.md +66 -0
  175. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/focus.md +90 -0
  176. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/form.md +97 -0
  177. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/grids.md +71 -0
  178. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +71 -0
  179. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  180. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  181. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/list.md +86 -0
  182. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  183. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/macos-settings.md +71 -0
  184. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  185. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/media.md +73 -0
  186. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +101 -0
  187. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +159 -0
  188. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/overlay.md +45 -0
  189. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/performance.md +62 -0
  190. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/previews.md +48 -0
  191. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  192. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scrollview.md +87 -0
  193. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/searchable.md +71 -0
  194. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +155 -0
  195. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/split-views.md +72 -0
  196. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +114 -0
  197. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/theming.md +71 -0
  198. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/title-menus.md +93 -0
  199. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/top-bar.md +49 -0
  200. package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +161 -0
  201. package/pipeline/skills/skills-index.md +8 -8
  202. package/pipeline/skills/shared/external/help-skills/SKILL.md +0 -166
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: "Run parallel review on a branch's 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 and sets approve/needs-work review state. Use when a branch diff or a pull request needs reviewing before it merges."
3
3
  description-tr: "Bir branch diff'i veya Pull Request üzerinde paralel review koşar: Claude Code'da 3 model (Fable + Opus + Sonnet), Copilot CLI'da 3 model (GPT + Opus + Sonnet). PR girdisinde bulgu başına inline yorum atar ve approve/needs-work durumunu işaretler."
4
- argument-hint: "[#N | repo#N | PR-URL | branch] - optional: PR by number/URL, repo+number, or local branch. Supports GitHub and Bitbucket Server URLs. If omitted (interactive), open GitHub + Bitbucket PRs are listed for multi-select; autopilot falls back to the current branch."
4
+ argument-hint: "[#N | repo#N | PR-URL | branch] [--with <path|owner/repo>] - optional: PR by number/URL, repo+number, or local branch. Supports GitHub and Bitbucket Server URLs. If omitted (interactive), open GitHub + Bitbucket PRs are listed for multi-select; autopilot falls back to the current branch. --with names the counterpart app repo for the platform-parity cross-check."
5
5
  ---
6
6
 
7
7
  # multi-agent review - Review Only Mode
@@ -117,12 +117,12 @@ jq --arg sha "$HEAD_SHA" '.review.headCommitSha = $sha' "$AGENT_STATE" > "$tmp_s
117
117
  **pr mode - bitbucket-server:**
118
118
 
119
119
  ```bash
120
- # Resolve credentials via prefs.keychainMapping (never hardcode key names).
120
+ # Resolve credentials via prefs.global.keychainMapping (never hardcode key names).
121
121
  . "$HOME/.claude/lib/credential-store-resolver.sh" \
122
122
  || . "$HOME/.copilot/lib/credential-store-resolver.sh"
123
123
  resolve_credential_store
124
- USER_KEY=$(jq -r '.keychainMapping.bitbucket_user' "$HOME/.claude/multi-agent-preferences.json")
125
- TOKEN_KEY=$(jq -r '.keychainMapping.bitbucket_token' "$HOME/.claude/multi-agent-preferences.json")
124
+ USER_KEY=$(jq -r '.global.keychainMapping.bitbucket_user' "$HOME/.claude/multi-agent-preferences.json")
125
+ TOKEN_KEY=$(jq -r '.global.keychainMapping.bitbucket_token' "$HOME/.claude/multi-agent-preferences.json")
126
126
  BB_USER=$("$CRED_STORE" get "$USER_KEY")
127
127
  BB_TOKEN=$("$CRED_STORE" get "$TOKEN_KEY")
128
128
 
@@ -175,15 +175,25 @@ Scope note: a guide governs only files under its own directory - a guide found
175
175
 
176
176
  ### 3. Launch parallel reviewers - host-CLI dependent
177
177
 
178
+ Every host runs three reviewers; only the second slot differs, because GPT-5.4 exists on Copilot and Codex but not on Claude Code, where Opus fills it.
179
+
178
180
  **Claude Code (3 in parallel):**
179
181
  - Agent 1: `claude-fable-5` → security + architecture
180
- - Agent 2: `claude-sonnet-5` → general quality
182
+ - Agent 2: `claude-opus-5` → edge cases, alternate perspective
183
+ - Agent 3: `claude-sonnet-5` → general quality
181
184
 
182
185
  **Copilot CLI (3 in parallel):**
183
186
  - Agent 1: `claude-opus-5` → security + architecture (Fable 5 is not offered on Copilot CLI)
184
187
  - Agent 2: `gpt-5.4` → edge cases, alternate perspective
185
188
  - Agent 3: `claude-sonnet-5` → general quality
186
189
 
190
+ **Codex CLI (3 in parallel, single vendor):**
191
+ - Agent 1: `gpt-5.6` @ `xhigh` → security + architecture
192
+ - Agent 2: `gpt-5.4` @ `high` → edge cases, alternate perspective
193
+ - Agent 3: `gpt-5.6` @ `medium` → general quality
194
+
195
+ With the `fable` rung disabled by prefs the Claude Code panel is two reviewers (Opus + Sonnet); see `$HOME/.claude/multi-agent-refs/features/model-fallback.md`.
196
+
187
197
  Each reviewer receives the diff, the module review guides from Step 2b (when any were found), plus the standard reviewer system prompt (see `$HOME/.claude/multi-agent-refs/phases/phase-4-review.md` for the prompt contract). Output: structured `findings[]` per reviewer.
188
198
 
189
199
  ### 4. Store-compliance cross-reference
@@ -203,21 +213,24 @@ Catalog-only - does NOT invoke binaries. For a full scan, use `/multi-agent:te
203
213
 
204
214
  ### 4b. Platform parity cross-check (advisory, read-only)
205
215
 
206
- When `state.siblings[]` holds a checked-out repo whose `stack` is the other
207
- mobile platform and the diff touches a screen, a service, a request model or a
208
- localization file, compare the change against that repo on four axes: endpoints
209
- called, parameters sent, business rules around the call, localization keys used.
216
+ When a counterpart repo resolves whose `stack` is the other mobile platform and
217
+ the diff touches a screen, a service, a request model or a localization file,
218
+ compare the change against that repo on four axes: endpoints called, parameters
219
+ sent, business rules around the call, localization keys used.
210
220
 
211
- That array is written by the Phase 0 dev-context picker, so a standalone review
212
- of a PR URL - no pipeline run, no state file - has no siblings and skips this
213
- step. The counterpart repo is a human's choice in the picker; guessing one from
214
- a PR URL would be a cross-repo read nobody authorized.
221
+ The counterpart is resolved automatically, in both directions (ios ↔ android),
222
+ from four sources in this order, first hit wins: `--with <path|owner/repo>` (a
223
+ one-off override, remembered like any other resolution), then a remembered
224
+ `prefs.projects[<slug>].counterpartRoots[]`, then the primary checkout's sibling
225
+ directories, then `state.siblings[]` from the Phase 0 dev-context picker. None of
226
+ them is a precondition for the others. One match is used and remembered, so the
227
+ next review of the same project asks nothing.
215
228
 
216
- The counterpart is located through the code graph, capped at 8 files, and is
217
- **read only** - never edited, staged, committed, pushed or built. Output is its
218
- own `Platform parity` section; findings are advisory and never blocking.
229
+ Several candidates → interactive runs ask once; autopilot and non-interactive
230
+ runs skip silently, because an unattended run must not block on a picker. No
231
+ candidate → skip silently, run unaffected. Stack comes from marker files, and
232
+ you never guess a stack from the repo name.
219
233
 
220
- No counterpart repo, or nothing comparable in the diff → skipped silently.
221
234
  Contract: `$HOME/.claude/multi-agent-refs/platform-parity.md`.
222
235
 
223
236
  ### 5. Triage (Fable)
@@ -236,12 +249,13 @@ Triage also marks each finding as `accepted` (real issue), `deferred` (real but
236
249
  | Model | Verdict | Blocking | Important | Suggestion |
237
250
  |----------|-----------|----------|-----------|------------|
238
251
  | Fable | approved | 0 | 1 | 3 |
252
+ | Opus | approved | 0 | 2 | 2 |
239
253
  | Sonnet | rejected | 1 | 2 | 5 |
240
254
 
241
255
  Consensus: ⚠ DISAGREEMENT - see Fable triage
242
256
  ```
243
257
 
244
- This summary ALWAYS prints, regardless of input mode. The chat is the live conversation; on the PR side, the durable artifacts are inline comments + the review state (Step 7).
258
+ One row per reviewer that ran: Fable + Opus + Sonnet on Claude Code, Opus + GPT-5.4 + Sonnet on Copilot CLI, the three GPT rows on Codex CLI. This summary ALWAYS prints, regardless of input mode. The chat is the live conversation; on the PR side, the durable artifacts are inline comments + the review state (Step 7).
245
259
 
246
260
  ### 7. Post to PR - only when input.kind === "pr"
247
261
 
@@ -23,7 +23,7 @@ Read `$HOME/.claude/multi-agent-refs/analysis/review.md` and execute it:
23
23
 
24
24
  ## Why it cites Locked decisions
25
25
 
26
- The analysis flow already declares 35 Locked decisions and two deterministic validators. A reviewer that says "I would have written this differently" gives the author nothing to act on; one that says "Locked 34: the Confluence page is in the evidence record but not in Section 21" gives them a fix and a reason. Findings that map to no rule are still allowed, but they are marked as judgement, not dressed up as a violation.
26
+ The analysis flow already declares 36 Locked decisions and two deterministic validators. A reviewer that says "I would have written this differently" gives the author nothing to act on; one that says "Locked 34: the Confluence page is in the evidence record but not in Section 21" gives them a fix and a reason. Findings that map to no rule are still allowed, but they are marked as judgement, not dressed up as a violation.
27
27
 
28
28
  ## What it never does
29
29
 
@@ -183,7 +183,7 @@ is a guess the user must be able to correct.
183
183
 
184
184
  ### Mode A - build from the branch
185
185
 
186
- 1. Worktree at `{projectRoot}/{worktreeBasePath}/{taskId}` on the chosen branch.
186
+ 1. Worktree at `{projectRoot}/.worktrees/{taskId}` on the chosen branch (phase-0-init.md Step 6 convention; the `.worktrees` segment is fixed, not a preference).
187
187
  **Never under `$HOME`**, never a direct checkout of the main working tree.
188
188
  2. Resolve the scheme / module and workspace / project from prefs; ask if ambiguous.
189
189
  3. Build:
@@ -329,7 +329,7 @@ Print, and stop:
329
329
  declares - never run it
330
330
  - `/multi-agent:store-ready --resume` to re-run after fixes
331
331
  - `/multi-agent:channels` to land the findings in Jira / Confluence / Wiki / PR
332
- - `/multi-agent:fix-bug` when Gate 3 produced code-level findings
332
+ - the enabled stack plugin's `fix-bug` skill (`ai-ios-toolkit:fix-bug` or the stack equivalent) when Gate 3 produced code-level findings
333
333
 
334
334
  Never upload, never commit a Play edit, never bump the build number, never commit.
335
335
 
@@ -60,7 +60,7 @@ Run every step automatically:
60
60
  Step 1: PLATFORM Detect macOS / Linux / Windows (Git Bash / WSL); export PLATFORM env
61
61
  Step 1.5: DETECT Compare timestamps, find stale targets
62
62
  Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 55 sub-command skills)
63
- Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + 51 specs as refs + 8 agent TOML)
63
+ Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + 55 specs as refs + 8 agent TOML)
64
64
  Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub, bash -n on all sh)
65
65
  Step 3c: PLUGINS pipeline shared/external -> multi-agent-plugins marketplace (rebuild knowledge/,
66
66
  bump changed plugins' patch version, commit + push the plugins repo)
@@ -117,7 +117,7 @@ If nothing is stale → report "All targets up to date" and stop.
117
117
  - `~/.claude/CLAUDE.md`, `~/.claude/rules/`, `~/.claude/knowledge/`
118
118
  - `~/.claude/scripts/` - EXCEPT `pre-commit-check.sh`, `agent-guard.sh`, `agent-guard.py`, and `build-stack-plugins.mjs` (generic, synced)
119
119
  - `~/.claude/settings.json`
120
- - **Any `~/.claude/commands/multi-agent/*.md` whose frontmatter has `local-only: true`** - these are user/repo-specific alias wrappers that delegate to a private marketplace plugin (e.g. corporate `ai-ios-toolkit` skills exposed as `multi-agent:<name>`). Syncing them would leak the private plugin/skill names into the public pipeline. Filter before copy: skip every source file containing `local-only: true`, and after copy assert none reached `pipeline/commands/`.
120
+ - **Any `~/.claude/commands/multi-agent/*/SKILL.md` whose frontmatter has `local-only: true`** - these are user/repo-specific alias wrappers that delegate to a private marketplace plugin's skills exposed as `multi-agent:<name>`. Syncing them would leak the private plugin/skill names into the public pipeline. Filter before copy: skip every source file containing `local-only: true`, and after copy assert none reached `pipeline/commands/`.
121
121
  ```bash
122
122
  # backstop: no local-only wrapper may exist in the synced target
123
123
  grep -rl "^local-only: true" ~/multi-agent-pipeline/pipeline/commands/ 2>/dev/null \
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: "Mobile UI Bug Hunter - iOS Simulator (simctl) + Android Emulator (adb) - auto-detects platform"
3
- allowed-tools: Agent, Bash, Read, Write, Edit, TaskCreate, TaskUpdate, TaskList, TaskGet, mcp__multi-agent-toolkit__ios_list_devices, mcp__multi-agent-toolkit__ios_boot_device, mcp__multi-agent-toolkit__ios_screenshot, mcp__multi-agent-toolkit__ios_tap, mcp__multi-agent-toolkit__ios_swipe, mcp__multi-agent-toolkit__ios_type_text, mcp__multi-agent-toolkit__ios_launch_app, mcp__multi-agent-toolkit__ios_terminate_app, mcp__multi-agent-toolkit__ios_list_apps, mcp__multi-agent-toolkit__ios_go_home, mcp__multi-agent-toolkit__ios_set_appearance, mcp__multi-agent-toolkit__ios_set_content_size, mcp__multi-agent-toolkit__ios_set_locale, mcp__multi-agent-toolkit__ios_open_url, mcp__multi-agent-toolkit__ios_status_bar, mcp__multi-agent-toolkit__ios_push_notification, mcp__multi-agent-toolkit__ios_grant_permission, mcp__multi-agent-toolkit__ios_revoke_permission, mcp__multi-agent-toolkit__ios_reset_permissions, mcp__multi-agent-toolkit__ios_set_location, mcp__multi-agent-toolkit__ios_clear_location, mcp__multi-agent-toolkit__ios_set_increase_contrast, mcp__multi-agent-toolkit__ios_record_video, mcp__multi-agent-toolkit__ios_add_media, mcp__multi-agent-toolkit__ios_keychain_reset, mcp__multi-agent-toolkit__ios_get_app_container, mcp__multi-agent-toolkit__ios_erase_device, mcp__multi-agent-toolkit__ios_get_ui_tree, mcp__multi-agent-toolkit__android_list_devices, mcp__multi-agent-toolkit__android_screenshot, mcp__multi-agent-toolkit__android_tap, mcp__multi-agent-toolkit__android_swipe, mcp__multi-agent-toolkit__android_type_text, mcp__multi-agent-toolkit__android_key_event, mcp__multi-agent-toolkit__android_launch_app, mcp__multi-agent-toolkit__android_stop_app, mcp__multi-agent-toolkit__android_list_packages, mcp__multi-agent-toolkit__android_go_home, mcp__multi-agent-toolkit__android_go_back, mcp__multi-agent-toolkit__android_get_ui_tree, mcp__multi-agent-toolkit__android_set_dark_mode, mcp__multi-agent-toolkit__android_set_font_scale, mcp__multi-agent-toolkit__android_set_locale, mcp__multi-agent-toolkit__android_set_location, mcp__multi-agent-toolkit__android_grant_permission, mcp__multi-agent-toolkit__android_revoke_permission, mcp__multi-agent-toolkit__android_record_screen, mcp__multi-agent-toolkit__android_install_apk, mcp__multi-agent-toolkit__android_uninstall_app, mcp__multi-agent-toolkit__android_logcat, mcp__multi-agent-toolkit__android_get_screen_size, mcp__multi-agent-toolkit__android_open_url, mcp__multi-agent-toolkit__android_clear_app_data
3
+ allowed-tools: Agent, Bash, Read, Write, Edit, TaskCreate, TaskUpdate, TaskList, TaskGet, mcp__multi-agent-toolkit__ios_list_devices, mcp__multi-agent-toolkit__ios_boot_device, mcp__multi-agent-toolkit__ios_screenshot, mcp__multi-agent-toolkit__ios_tap, mcp__multi-agent-toolkit__ios_swipe, mcp__multi-agent-toolkit__ios_type_text, mcp__multi-agent-toolkit__ios_launch_app, mcp__multi-agent-toolkit__ios_terminate_app, mcp__multi-agent-toolkit__ios_list_apps, mcp__multi-agent-toolkit__ios_go_home, mcp__multi-agent-toolkit__ios_set_appearance, mcp__multi-agent-toolkit__ios_set_content_size, mcp__multi-agent-toolkit__ios_set_locale, mcp__multi-agent-toolkit__ios_open_url, mcp__multi-agent-toolkit__ios_status_bar, mcp__multi-agent-toolkit__ios_push_notification, mcp__multi-agent-toolkit__ios_grant_permission, mcp__multi-agent-toolkit__ios_revoke_permission, mcp__multi-agent-toolkit__ios_reset_permissions, mcp__multi-agent-toolkit__ios_set_location, mcp__multi-agent-toolkit__ios_clear_location, mcp__multi-agent-toolkit__ios_set_increase_contrast, mcp__multi-agent-toolkit__ios_record_video, mcp__multi-agent-toolkit__ios_add_media, mcp__multi-agent-toolkit__ios_keychain_reset, mcp__multi-agent-toolkit__ios_get_app_container, mcp__multi-agent-toolkit__ios_erase_device, mcp__multi-agent-toolkit__ios_get_ui_tree, mcp__multi-agent-toolkit__ios_accessibility_audit, mcp__multi-agent-toolkit__ios_accessibility_audit_deep, mcp__multi-agent-toolkit__ios_list_crashes, mcp__multi-agent-toolkit__agent_run_steps, mcp__multi-agent-toolkit__android_list_devices, mcp__multi-agent-toolkit__android_screenshot, mcp__multi-agent-toolkit__android_tap, mcp__multi-agent-toolkit__android_swipe, mcp__multi-agent-toolkit__android_type_text, mcp__multi-agent-toolkit__android_key_event, mcp__multi-agent-toolkit__android_launch_app, mcp__multi-agent-toolkit__android_stop_app, mcp__multi-agent-toolkit__android_list_packages, mcp__multi-agent-toolkit__android_go_home, mcp__multi-agent-toolkit__android_go_back, mcp__multi-agent-toolkit__android_get_ui_tree, mcp__multi-agent-toolkit__android_set_dark_mode, mcp__multi-agent-toolkit__android_set_font_scale, mcp__multi-agent-toolkit__android_set_locale, mcp__multi-agent-toolkit__android_set_location, mcp__multi-agent-toolkit__android_grant_permission, mcp__multi-agent-toolkit__android_revoke_permission, mcp__multi-agent-toolkit__android_record_screen, mcp__multi-agent-toolkit__android_install_apk, mcp__multi-agent-toolkit__android_uninstall_app, mcp__multi-agent-toolkit__android_logcat, mcp__multi-agent-toolkit__android_get_screen_size, mcp__multi-agent-toolkit__android_open_url, mcp__multi-agent-toolkit__android_clear_app_data, mcp__multi-agent-toolkit__android_accessibility_audit, mcp__multi-agent-toolkit__android_list_crashes
4
4
  ---
5
5
 
6
6
  # Mobile UI Bug Hunter
@@ -25,7 +25,7 @@ Auto-detects platform: iOS (simctl) or Android (adb). No external apps needed.
25
25
  For iOS: use `mcp__multi-agent-toolkit__ios_*` tools
26
26
  For Android: use `mcp__multi-agent-toolkit__android_*` tools
27
27
 
28
- **Android extras**: `get_ui_tree` returns XML with bounds/text/resource-id (uiautomator dump), `logcat` for crash detection, `go_back` button.
28
+ **Android extras**: `get_ui_tree` returns XML with bounds/text/resource-id (uiautomator dump), `logcat` for live output, `list_crashes` for the crash buffer, `go_back` button.
29
29
 
30
30
  ## Activation
31
31
 
@@ -90,23 +90,34 @@ Call: ios_status_bar (time: "09:41", battery_level: 100)
90
90
 
91
91
  ### Step 3 - Systematic Screen Exploration
92
92
 
93
- For each screen in the app:
93
+ For each screen in the app, read before you look, and batch what you can:
94
94
 
95
95
  ```
96
- Call: ios_screenshot
97
- -> Claude analyzes the image for bugs (see Bug Detection below)
96
+ Call: ios_get_ui_tree (text: labels, frames, traits; ~0.1 s, no image)
97
+ -> pick tap targets from element frames, never by guessing pixels from a picture
98
+
99
+ Call: agent_run_steps (one round trip for a scripted sequence)
100
+ steps: [ {tool: "ios_tap", args: {x, y}}, {wait_ms: 400},
101
+ {tool: "ios_screenshot", args: {path: "<dir>/<screen>.png"}},
102
+ {tool: "ios_get_ui_tree", args: {}} ]
98
103
 
99
- Call: ios_tap (on each tab bar item, button, navigation link)
100
- -> After each tap: ios_screenshot -> analyze
101
- -> Navigate back: ios_swipe (left edge swipe) or tap back button coordinates
104
+ Call: ios_screenshot (inline, only when a visual judgment is needed)
105
+ -> Claude analyzes the image for bugs (see Bug Detection below)
102
106
  ```
103
107
 
108
+ Every inline `ios_screenshot` / `android_screenshot` is an 800 px JPEG (about 60 KB) by
109
+ default; pass `format: "png"` or a larger `max_width` only when a pixel-level look is the
110
+ point. Bulk captures (dark mode, dynamic type, locale sweeps) always go through `path`,
111
+ so a run of 100 screens does not push 100 images through the model: look at the ones
112
+ whose tree or diff changed. A screen whose tree is unchanged after a tap is the
113
+ "button did not respond" finding without a second image.
114
+
104
115
  **Navigation strategy:**
105
116
 
106
- 1. Screenshot initial screen -> identify tab bar (usually bottom ~680-700y area)
107
- 2. Tap each tab position -> screenshot each
108
- 3. On each screen: tap interactive elements -> screenshot results
109
- 4. Scroll: `ios_swipe(200, 600, 200, 200)` to scroll down -> screenshot
117
+ 1. Tree of the initial screen -> tab bar items are the elements with a tab-bar trait or the bottom row of buttons; use their frames
118
+ 2. Tap each tab (batched via `agent_run_steps`) -> tree + file capture each
119
+ 3. On each screen: tap interactive elements from the tree -> tree after; inline screenshot only where the tree cannot tell (layout, contrast, clipping)
120
+ 4. Scroll: `ios_swipe(200, 600, 200, 200)` to scroll down -> tree, capture when new content appeared
110
121
  5. Type in text fields: `ios_type_text("test input")`
111
122
 
112
123
  ### Step 4 - Variant Testing (based on input argument)
@@ -131,9 +142,24 @@ Call: ios_set_content_size("medium") // reset
131
142
 
132
143
  **"accessibility":**
133
144
 
134
- - Check each screenshot for: small tap targets, missing labels, poor contrast
135
- - Verify minimum 44x44pt touch areas
136
- - Check text readability at default + large sizes
145
+ Two halves per screen, and the report keeps them apart: what the tool measured and what the screenshot suggests.
146
+
147
+ ```
148
+ On each screen, after ios_screenshot:
149
+ Call: ios_accessibility_audit (Android: android_accessibility_audit)
150
+ -> findings[]: missing labels / contentDescription, controls a screen reader cannot
151
+ name, tap targets under 44pt (iOS) / 48dp (Android), missing identifiers,
152
+ reading order that does not follow the layout. Each finding becomes a BUG task
153
+ with the element identifier from the tree, not a guess from the pixels.
154
+ -> measurable:false (with a reason) means the tree could not be read (on iOS,
155
+ Simulator.app itself must be running, a booted device is not enough). Record
156
+ the screen as "not audited: <reason>" in the report. Never read it as clean.
157
+ -> Pass `scope: "<prefix>"` to narrow to one screen's identifiers when the tree is large.
158
+ ```
159
+
160
+ The screenshot pass still runs on every screen for what the tree cannot see: poor contrast, text readability at default + large sizes, and visual crowding. Report those as screenshot findings, separately from the audit findings.
161
+
162
+ Optional deep pass (iOS only, minutes rather than seconds): when the project has an XCUITest target whose test calls `performAccessibilityAudit()`, run `ios_accessibility_audit_deep` (`scheme`, `project` or `workspace`, optional `test_identifier`) once at the end. It reaches contrast, Dynamic Type, clipped text, traits and hit regions that a tree dump cannot. The result says whether the named test ran; a test that never called the audit reports as "did not run", never as clean. Skip it, and say so in the report, when no such test exists.
137
163
 
138
164
  **"screenshot <lang>":**
139
165
 
@@ -172,6 +198,17 @@ no Gate 3 and no Android parity, so the copy is gone rather than kept in sync.
172
198
  - Run light mode -> all screens
173
199
  - Run dark mode -> all screens
174
200
  - Run large text -> all screens
201
+ - Crash sweep, after the last screen and before the report:
202
+
203
+ ```
204
+ Call: ios_list_crashes (app: <process name>, since_min: <minutes since Step 1 launch>, limit: 20)
205
+ Android: android_list_crashes (lines: 200)
206
+ -> Every report / stack newer than the launch becomes a BUG task with severity Critical,
207
+ the crashed process and the top frame in the description.
208
+ -> Empty result -> "Crashes: none during this run" in the report. Only the bounded window
209
+ counts; older reports on the host are not this run's.
210
+ ```
211
+
175
212
  - Compile complete report
176
213
 
177
214
  ### Step 5 - Bug Detection (on EVERY screenshot)
@@ -233,6 +270,8 @@ Then output full report:
233
270
  - Clean: {N}
234
271
  - With bugs: {N}
235
272
  - Critical: {N} | Major: {N} | Minor: {N}
273
+ - Accessibility audit (accessibility scenario): {N} audited, {N} not audited (reason per screen), deep pass: {ran / skipped: reason}
274
+ - Crashes (full scenario): {N} during this run
236
275
  ```
237
276
 
238
277
  Save to: `$HOME/.claude/logs/sim-test/{bundle_id}/{timestamp}.md`
@@ -46,7 +46,8 @@
46
46
  # not to enable, not a problem to report.
47
47
  #
48
48
  # Exit codes: 0 = inventory produced (or the queried key is present), 1 = queried key
49
- # absent, 3 = usage error.
49
+ # absent, 2 = no credential helper on this host (nothing could be probed), 3 = usage
50
+ # error.
50
51
 
51
52
  set -uo pipefail
52
53
 
@@ -290,11 +291,23 @@ if [ -n "$QUERY" ]; then
290
291
  echo "$QUERY: present - can $(capability_of "$QUERY")"
291
292
  fi
292
293
  exit 0 ;;
293
- 2) echo "$QUERY: no credential helper on this host" >&2; exit 1 ;;
294
+ 2) echo "$QUERY: no credential helper on this host - run the pipeline installer (credential-store.sh missing)" >&2; exit 2 ;;
294
295
  *) echo "$QUERY: NOT AVAILABLE - onboard it via /multi-agent:setup before relying on it" >&2; exit 1 ;;
295
296
  esac
296
297
  fi
297
298
 
299
+ # Without a helper nothing can be probed: every mapped key would read as
300
+ # "mapped-but-missing" and the inventory would send the user to re-onboard
301
+ # tokens that are sitting in the store.
302
+ if [ -z "$STORE" ]; then
303
+ if [ "$MODE" = "json" ]; then
304
+ echo '{"status":"no-backend","reason":"credential-store.sh not found on this host","credentials":[]}'
305
+ else
306
+ echo "no credential helper on this host - run the pipeline installer (credential-store.sh missing)" >&2
307
+ fi
308
+ exit 2
309
+ fi
310
+
298
311
  ROWS=""
299
312
  while IFS=$'\t' read -r key mapped; do
300
313
  [ -z "$key" ] && continue
@@ -81,12 +81,22 @@ MSG
81
81
  return 1
82
82
  }
83
83
 
84
- # When sourced (BASH_SOURCE != $0), auto-resolve into the caller's environment.
85
- # When run directly (./credential-store-resolver.sh), print the resolved path
86
- # or a non-zero exit code with the message above.
87
- if [ "${BASH_SOURCE[0]:-$0}" = "${0}" ]; then
84
+ # When sourced, auto-resolve into the caller's environment. When run directly
85
+ # (./credential-store-resolver.sh), print the resolved path or a non-zero exit
86
+ # code with the message above. zsh sources this too (the pipeline's Bash tool
87
+ # is zsh), where BASH_SOURCE is unset and $0 is the sourced file's own name, so
88
+ # the bash test alone always looked like a direct run there.
89
+ _csr_sourced=0
90
+ if [ -n "${ZSH_VERSION:-}" ]; then
91
+ case "${ZSH_EVAL_CONTEXT:-}" in *:file|*:file:*) _csr_sourced=1 ;; esac
92
+ elif [ -n "${BASH_SOURCE[0]:-}" ] && [ "${BASH_SOURCE[0]}" != "$0" ]; then
93
+ _csr_sourced=1
94
+ fi
95
+ if [ "$_csr_sourced" -eq 0 ]; then
96
+ unset _csr_sourced
88
97
  resolve_credential_store && echo "$CRED_STORE"
89
98
  else
99
+ unset _csr_sourced
90
100
  # `|| :` matters, and it is not cosmetic.
91
101
  #
92
102
  # A sourced file runs in the caller's shell, so a bare failing command at this top
@@ -164,6 +164,10 @@ do_get() {
164
164
  return 0
165
165
  fi
166
166
  audit_lookup "$logical" false
167
+ case "$rc" in
168
+ 2) echo "ERR: credential backend unavailable on $PLATFORM" >&2; return 2 ;;
169
+ 4) echo "ERR: credential backend error while reading '$logical'" >&2; return 4 ;;
170
+ esac
167
171
  return 1
168
172
  fi
169
173
  local val=""
@@ -204,8 +208,10 @@ do_set() {
204
208
  if [ "$val" = "-" ]; then
205
209
  val=$(cat)
206
210
  fi
207
- if delegate_to_python; then
208
- # Pass the value via stdin to keep it out of process listings / shell history.
211
+ # macOS writes go through `security -i` below: the secret travels on stdin
212
+ # and never lands on any argv, which is the property the delegate cannot
213
+ # promise from here.
214
+ if [ "$PLATFORM" != "macos" ] && delegate_to_python; then
209
215
  printf '%s' "$val" | python3 "$KEYCHAIN_PY" set "$key" -
210
216
  return $?
211
217
  fi
@@ -2,7 +2,7 @@
2
2
  # extract-conventions.sh
3
3
  #
4
4
  # Phase 1c helper for /multi-agent:analysis.
5
- # Scans a repo and emits 12 convention buckets as a single JSON object on stdout.
5
+ # Scans a repo and emits 13 convention buckets as a single JSON object on stdout.
6
6
  #
7
7
  # Usage:
8
8
  # extract-conventions.sh <repo-path> <platform>
@@ -202,19 +202,6 @@ files_to_json() {
202
202
  printf '%s\n' "$input" | head -5 | jq -R . | jq -s .
203
203
  }
204
204
 
205
- # Top-N suffix frequency from a stream of file basenames.
206
- # stdin: basenames (one per line)
207
- # arg1: regex (POSIX ERE) capturing the suffix in group 1
208
- # stdout: lines "count<TAB>suffix" sorted desc
209
- suffix_freq() {
210
- local re="$1"
211
- grep -Eo "$re" 2>/dev/null \
212
- | sort \
213
- | uniq -c \
214
- | sort -rn \
215
- | sed -E 's/^ *([0-9]+) +/\1\t/'
216
- }
217
-
218
205
  # Run a bucket function with a soft timeout. We wrap the function in a subshell
219
206
  # and use a watchdog so we stay compatible with macOS bash 3.2.
220
207
  run_bucket_with_timeout() {
@@ -48,10 +48,12 @@ HOST_OVERRIDE="${CONFLUENCE_HOST_OVERRIDE:-}"
48
48
  PAGE_ID=""
49
49
  TIMEOUT="${CONFLUENCE_TIMEOUT_SECONDS:-20}"
50
50
 
51
+ need_value() { [ $# -ge 2 ] || { echo "ERR: $1 needs a value" >&2; exit 4; }; }
52
+
51
53
  while [ $# -gt 0 ]; do
52
54
  case "$1" in
53
- --host) HOST_OVERRIDE="$2"; shift 2 ;;
54
- --page-id) PAGE_ID="$2"; shift 2 ;;
55
+ --host) need_value "$@"; HOST_OVERRIDE="$2"; shift 2 ;;
56
+ --page-id) need_value "$@"; PAGE_ID="$2"; shift 2 ;;
55
57
  -h|--help)
56
58
  echo "usage: $0 <page-url> | $0 --host <host> --page-id <id>" >&2
57
59
  exit 4 ;;
@@ -220,11 +222,17 @@ PAGE_ID_FINAL="$PAGE_ID_RESOLVED" \
220
222
  SPACE_KEY_FINAL="$SPACE_KEY" \
221
223
  PAGE_JSON_RAW="$PAGE_JSON" \
222
224
  python3 - <<'PY'
223
- import json, os, re, datetime, html
225
+ import json, os, re, sys, datetime, html
224
226
  from html.parser import HTMLParser
225
227
 
226
228
  raw = os.environ["PAGE_JSON_RAW"]
227
- data = json.loads(raw)
229
+ try:
230
+ data = json.loads(raw)
231
+ except ValueError:
232
+ # A 200 with an HTML body is an SSO login page or a proxy, not the page.
233
+ sys.stderr.write("ERR: Confluence GET for pageId=%s returned a non-JSON body (login page or proxy?)\n"
234
+ % os.environ.get("PAGE_ID_FINAL", ""))
235
+ sys.exit(3)
228
236
 
229
237
  storage = ((data.get("body") or {}).get("storage") or {}).get("value") or ""
230
238
  title = data.get("title") or "<untitled>"
@@ -67,13 +67,15 @@ ISSUE_ID=""
67
67
  SESSION_ID=""
68
68
  TIMEOUT="${FIREBASE_TIMEOUT_SECONDS:-20}"
69
69
 
70
+ need_value() { [ $# -ge 2 ] || { echo "ERR: $1 needs a value" >&2; exit 4; }; }
71
+
70
72
  while [ $# -gt 0 ]; do
71
73
  case "$1" in
72
- --project) PROJECT_ID="$2"; shift 2 ;;
73
- --platform) PLATFORM="$2"; shift 2 ;;
74
- --bundle) BUNDLE="$2"; shift 2 ;;
75
- --issue-id) ISSUE_ID="$2"; shift 2 ;;
76
- --session-id) SESSION_ID="$2"; shift 2 ;;
74
+ --project) need_value "$@"; PROJECT_ID="$2"; shift 2 ;;
75
+ --platform) need_value "$@"; PLATFORM="$2"; shift 2 ;;
76
+ --bundle) need_value "$@"; BUNDLE="$2"; shift 2 ;;
77
+ --issue-id) need_value "$@"; ISSUE_ID="$2"; shift 2 ;;
78
+ --session-id) need_value "$@"; SESSION_ID="$2"; shift 2 ;;
77
79
  -h|--help)
78
80
  echo "usage: $0 <issue-url> | $0 --project ... --platform ... --bundle ... --issue-id ... [--session-id ...]" >&2
79
81
  exit 4 ;;
@@ -234,6 +236,7 @@ fi
234
236
  # 1. Build header + claim, base64url
235
237
  # 2. Sign with the SA private key
236
238
  # 3. POST to oauth2.googleapis.com/token
239
+ TOKEN_RC=0
237
240
  ACCESS_TOKEN=$(SA_JSON_IN="$SA_JSON" python3 - <<'PY'
238
241
  import base64, json, os, sys, time, urllib.parse, urllib.request
239
242
 
@@ -302,10 +305,10 @@ except Exception as e:
302
305
  print("", file=sys.stderr)
303
306
  sys.exit(2)
304
307
  PY
305
- )
308
+ ) || TOKEN_RC=$?
306
309
 
307
- if [ -z "$ACCESS_TOKEN" ]; then
308
- echo "ERR: Firebase token exchange failed" >&2
310
+ if [ "$TOKEN_RC" -ne 0 ] || [ -z "$ACCESS_TOKEN" ]; then
311
+ echo "ERR: Firebase token exchange failed (exchange rc=$TOKEN_RC)" >&2
309
312
  exit 3
310
313
  fi
311
314
 
@@ -31,7 +31,7 @@
31
31
  # }
32
32
  #
33
33
  # Exit codes:
34
- # 0 success (may be degraded-empty for a pdf with no converter)
34
+ # 0 success
35
35
  # 3 download or read failure
36
36
  # 4 bad usage / unsupported extension
37
37
  # 6 converter not available (pdf only; the orchestrator treats this as
@@ -89,13 +89,15 @@ BATCH=4
89
89
  log() { printf '%s\n' "$*" >&2; }
90
90
  die() { local code="$1"; shift; log "fetch-figma-annotations: $*"; exit "$code"; }
91
91
 
92
+ need_value() { [ $# -ge 2 ] || die 1 "$1 requires a value"; }
93
+
92
94
  while [ $# -gt 0 ]; do
93
95
  case "$1" in
94
- --file-key) FILE_KEY="${2:-}"; shift 2 ;;
95
- --node-id) NODE_IDS="${2:-}"; shift 2 ;;
96
- --url) URL="${2:-}"; shift 2 ;;
97
- --lang-prefixes) LANG_PREFIXES="${2:-TR,EN}"; shift 2 ;;
98
- --batch) BATCH="${2:-4}"; shift 2 ;;
96
+ --file-key) need_value "$@"; FILE_KEY="$2"; shift 2 ;;
97
+ --node-id) need_value "$@"; NODE_IDS="$2"; shift 2 ;;
98
+ --url) need_value "$@"; URL="$2"; shift 2 ;;
99
+ --lang-prefixes) need_value "$@"; LANG_PREFIXES="${2:-TR,EN}"; shift 2 ;;
100
+ --batch) need_value "$@"; BATCH="${2:-4}"; shift 2 ;;
99
101
  -h|--help) grep -E '^#( |$)' "$0" | sed -E 's/^# ?//'; exit 0 ;;
100
102
  *) die 1 "unknown argument: $1" ;;
101
103
  esac
@@ -62,11 +62,13 @@ ISSUE_ID=""
62
62
  TIMEOUT="${FORTIFY_TIMEOUT_SECONDS:-25}"
63
63
  AUTH_SCHEME="${FORTIFY_AUTH_SCHEME:-auto}"
64
64
 
65
+ need_value() { [ $# -ge 2 ] || { echo "ERR: $1 needs a value" >&2; exit 4; }; }
66
+
65
67
  while [ $# -gt 0 ]; do
66
68
  case "$1" in
67
- --version-id) VERSION_ID="$2"; shift 2 ;;
68
- --issue) ISSUE_ID="$2"; shift 2 ;;
69
- --instance-id) ISSUE_ID="$2"; shift 2 ;;
69
+ --version-id) need_value "$@"; VERSION_ID="$2"; shift 2 ;;
70
+ --issue) need_value "$@"; ISSUE_ID="$2"; shift 2 ;;
71
+ --instance-id) need_value "$@"; ISSUE_ID="$2"; shift 2 ;;
70
72
  -h|--help)
71
73
  echo "usage: $0 <ssc-url> | $0 --version-id <id> [--issue <id>] | $0 --instance-id <id>" >&2
72
74
  exit 4 ;;
@@ -73,11 +73,13 @@ TIMEOUT="${GRAYLOG_TIMEOUT_SECONDS:-25}"
73
73
  RANGE="${GRAYLOG_RANGE_SECONDS:-86400}"
74
74
  LIMIT="${GRAYLOG_LIMIT:-200}"
75
75
 
76
+ need_value() { [ $# -ge 2 ] || { echo "ERR: $1 needs a value" >&2; exit 4; }; }
77
+
76
78
  while [ $# -gt 0 ]; do
77
79
  case "$1" in
78
- --trx) TRX_ID="$2"; shift 2 ;;
79
- --conv) CONV_ID="$2"; shift 2 ;;
80
- --env) ENVIRONMENT="$2"; shift 2 ;;
80
+ --trx) need_value "$@"; TRX_ID="$2"; shift 2 ;;
81
+ --conv) need_value "$@"; CONV_ID="$2"; shift 2 ;;
82
+ --env) need_value "$@"; ENVIRONMENT="$2"; shift 2 ;;
81
83
  -h|--help)
82
84
  echo "usage: $0 --trx <transaction-id> | --conv <conversation-id> [--env auto|prod|test]" >&2
83
85
  exit 4 ;;
@@ -85,7 +85,7 @@ refresh_grant_cfg() {
85
85
 
86
86
  RESPONSE=$(curl -s --max-time 20 -X POST "$TOKEN_ENDPOINT" \
87
87
  -H "Content-Type: application/x-www-form-urlencoded" \
88
- -K <(refresh_grant_cfg))
88
+ -K <(refresh_grant_cfg)) || RESPONSE=""
89
89
 
90
90
  NEW_ACCESS=$(printf '%s' "$RESPONSE" | jq -r '.access_token // empty' 2>/dev/null || true)
91
91
  NEW_REFRESH=$(printf '%s' "$RESPONSE" | jq -r '.refresh_token // empty' 2>/dev/null || true)