@mmerterden/multi-agent-pipeline 16.18.0 → 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 (200) hide show
  1. package/CHANGELOG.md +45 -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 +27 -14
  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 +21 -16
  63. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  64. package/pipeline/rules/figma-pipeline.md +18 -72
  65. package/pipeline/rules/outside-the-pipeline.md +4 -3
  66. package/pipeline/schemas/agent-state.schema.json +1 -1
  67. package/pipeline/schemas/token-budget.json +1 -1
  68. package/pipeline/scripts/_stack-routing.mjs +1 -1
  69. package/pipeline/scripts/agent-guard.py +102 -21
  70. package/pipeline/scripts/anonymize-findings.mjs +7 -6
  71. package/pipeline/scripts/build-skills-index.mjs +14 -3
  72. package/pipeline/scripts/cost-budget-check.mjs +5 -3
  73. package/pipeline/scripts/cost-lib.sh +0 -15
  74. package/pipeline/scripts/diff-explain.mjs +22 -12
  75. package/pipeline/scripts/gc-refs.sh +6 -2
  76. package/pipeline/scripts/gc-tmp.sh +1 -1
  77. package/pipeline/scripts/gc-worktrees.sh +1 -1
  78. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
  79. package/pipeline/scripts/github-ssh-setup.sh +7 -2
  80. package/pipeline/scripts/graph-build.mjs +2 -2
  81. package/pipeline/scripts/jira-wiki-escape.mjs +2 -1
  82. package/pipeline/scripts/keychain.py +12 -11
  83. package/pipeline/scripts/learning-curve.mjs +1 -1
  84. package/pipeline/scripts/migrate-prefs.mjs +1 -1
  85. package/pipeline/scripts/output-quality-check.sh +3 -1
  86. package/pipeline/scripts/phase-tracker.sh +1 -1
  87. package/pipeline/scripts/phase0-exit-gate.mjs +2 -1
  88. package/pipeline/scripts/plan-coverage-gate.mjs +2 -1
  89. package/pipeline/scripts/pre-commit-check.sh +23 -13
  90. package/pipeline/scripts/prune-logs.sh +1 -1
  91. package/pipeline/scripts/render-agent-log-cost.sh +3 -1
  92. package/pipeline/scripts/render-cost-summary.sh +4 -2
  93. package/pipeline/scripts/render-work-summary.sh +5 -3
  94. package/pipeline/scripts/repo-map.mjs +3 -2
  95. package/pipeline/scripts/scan-skills.sh +6 -2
  96. package/pipeline/scripts/search-logs.sh +8 -6
  97. package/pipeline/scripts/sign-skills.sh +3 -1
  98. package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
  99. package/pipeline/scripts/triage-memory.mjs +25 -4
  100. package/pipeline/scripts/uninstall.mjs +20 -12
  101. package/pipeline/scripts/update-check.sh +2 -2
  102. package/pipeline/scripts/update-issue-progress.sh +6 -5
  103. package/pipeline/scripts/validate-analysis-doc.mjs +6 -6
  104. package/pipeline/scripts/verify-skills.sh +3 -1
  105. package/pipeline/scripts/worktree-finalize.sh +22 -10
  106. package/pipeline/skills/.skill-manifest.json +81 -57
  107. package/pipeline/skills/.skills-index.json +19 -19
  108. package/pipeline/skills/shared/README.md +8 -8
  109. package/pipeline/skills/shared/core/multi-agent/SKILL.md +7 -7
  110. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +1 -1
  111. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
  112. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +1 -1
  113. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +2 -2
  114. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +23 -14
  115. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  116. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +56 -0
  117. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +0 -4
  118. package/pipeline/skills/shared/external/api-patterns/SKILL.md +12 -24
  119. package/pipeline/skills/shared/external/app-store-changelog/references/release-notes-guidelines.md +34 -0
  120. package/pipeline/skills/shared/external/app-store-changelog/scripts/collect_release_changes.sh +33 -0
  121. package/pipeline/skills/shared/external/architecture/SKILL.md +7 -9
  122. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +0 -4
  123. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +0 -1
  124. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +0 -14
  125. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +13 -13
  126. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +16 -16
  127. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +6 -6
  128. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +13 -13
  129. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -23
  130. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +18 -18
  131. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +30 -30
  132. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +11 -11
  133. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +33 -33
  134. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +52 -52
  135. package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +1 -1
  136. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +11 -11
  137. package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -1
  138. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +7 -3
  139. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -15
  140. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +0 -5
  141. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Package.swift +17 -0
  142. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/Resources/.keep +0 -0
  143. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/main.swift +11 -0
  144. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/bootstrap/version.env +2 -0
  145. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/build_icon.sh +49 -0
  146. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/compile_and_run.sh +63 -0
  147. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/launch.sh +28 -0
  148. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/make_appcast.sh +82 -0
  149. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +206 -0
  150. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +52 -0
  151. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +52 -0
  152. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/version.env +2 -0
  153. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/packaging.md +17 -0
  154. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/release.md +32 -0
  155. package/pipeline/skills/shared/external/macos-spm-app-packaging/references/scaffold.md +79 -0
  156. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +0 -1
  157. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +0 -4
  158. package/pipeline/skills/shared/external/swift-concurrency-expert/references/approachable-concurrency.md +63 -0
  159. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +272 -0
  160. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md +33 -0
  161. package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +150 -0
  162. package/pipeline/skills/shared/external/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  163. package/pipeline/skills/shared/external/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  164. package/pipeline/skills/shared/external/swiftui-performance-audit/references/profiling-intake.md +44 -0
  165. package/pipeline/skills/shared/external/swiftui-performance-audit/references/report-template.md +47 -0
  166. package/pipeline/skills/shared/external/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  167. package/pipeline/skills/shared/external/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  168. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +201 -0
  169. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/async-state.md +96 -0
  170. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +46 -0
  171. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/controls.md +57 -0
  172. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/deeplinks.md +66 -0
  173. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/focus.md +90 -0
  174. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/form.md +97 -0
  175. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/grids.md +71 -0
  176. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +71 -0
  177. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  178. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  179. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/list.md +86 -0
  180. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  181. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/macos-settings.md +71 -0
  182. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  183. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/media.md +73 -0
  184. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +101 -0
  185. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +159 -0
  186. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/overlay.md +45 -0
  187. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/performance.md +62 -0
  188. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/previews.md +48 -0
  189. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  190. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scrollview.md +87 -0
  191. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/searchable.md +71 -0
  192. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +155 -0
  193. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/split-views.md +72 -0
  194. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +114 -0
  195. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/theming.md +71 -0
  196. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/title-menus.md +93 -0
  197. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/top-bar.md +49 -0
  198. package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +161 -0
  199. package/pipeline/skills/skills-index.md +8 -8
  200. package/pipeline/skills/shared/external/help-skills/SKILL.md +0 -166
@@ -81,6 +81,8 @@ if ! command -v resolve_figma_token >/dev/null 2>&1; then
81
81
  fi
82
82
 
83
83
  FIGMA_API="https://api.figma.com/v1"
84
+ WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/figma-screenshot.XXXXXX")
85
+ trap 'rm -rf "$WORK_DIR"' EXIT INT TERM
84
86
  PARALLEL_DL=4
85
87
  HTTP_TIMEOUT=60
86
88
  MAX_IDS_PER_CALL=50
@@ -126,7 +128,8 @@ Options:
126
128
  hold the other channel's design" before a run may write that a
127
129
  channel has none. Emits JSON, downloads nothing.
128
130
  --feature-slug Feature name to match against, for --discover-sections.
129
- --output-dir Output directory. Default: /tmp/figma-screenshots-<ts>.
131
+ --output-dir Output directory. Default: a fresh mktemp dir
132
+ ($TMPDIR/figma-screenshots.XXXXXX).
130
133
  --scale Render scale (1-4). Default: 2.
131
134
  --format Image format. Default: png.
132
135
  --help Show this help text and exit.
@@ -256,7 +259,7 @@ fetch_nodes_metadata() {
256
259
  fetch_image_urls() {
257
260
  local ids="$1"
258
261
  local tmp
259
- tmp=$(mktemp)
262
+ tmp=$(mktemp "$WORK_DIR/images.XXXXXX")
260
263
  local url="$FIGMA_API/images/$FILE_KEY?ids=$ids&format=$FORMAT&scale=$SCALE"
261
264
  local code
262
265
  code=$(curl_figma "$tmp" "$url")
@@ -281,7 +284,7 @@ render_and_download() {
281
284
 
282
285
  # Emit a job list (nodeId<TAB>url<TAB>outPath) for parallel download.
283
286
  local jobs
284
- jobs=$(mktemp)
287
+ jobs=$(mktemp "$WORK_DIR/jobs.XXXXXX")
285
288
  local key_prefix="${FILE_KEY:0:4}"
286
289
  printf '%s' "$urls_json" | jq -r 'to_entries[] | "\(.key)\t\(.value)"' \
287
290
  | while IFS=$'\t' read -r nid url; do
@@ -292,7 +295,7 @@ render_and_download() {
292
295
 
293
296
  # Parallel download. xargs -P keeps the channel count bounded.
294
297
  local results
295
- results=$(mktemp)
298
+ results=$(mktemp "$WORK_DIR/results.XXXXXX")
296
299
  # shellcheck disable=SC2016
297
300
  awk -F'\t' '{printf "%s\037%s\037%s\n", $1,$2,$3}' "$jobs" \
298
301
  | xargs -P "$PARALLEL_DL" -I{} bash -c '
@@ -389,17 +392,19 @@ if [ "$#" -eq 0 ]; then
389
392
  exit 1
390
393
  fi
391
394
 
395
+ need_value() { [ "$#" -ge 2 ] || die 1 "$1 requires a value"; }
396
+
392
397
  while [ "$#" -gt 0 ]; do
393
398
  case "$1" in
394
399
  --help|-h) print_help; exit 0 ;;
395
- --file-key) FILE_KEY="${2:-}"; shift 2 ;;
396
- --node-id) NODE_ID="${2:-}"; shift 2 ;;
397
- --section) SECTION_URL="${2:-}"; shift 2 ;;
400
+ --file-key) need_value "$@"; FILE_KEY="$2"; shift 2 ;;
401
+ --node-id) need_value "$@"; NODE_ID="$2"; shift 2 ;;
402
+ --section) need_value "$@"; SECTION_URL="$2"; shift 2 ;;
398
403
  --discover-sections) DISCOVER=1; shift ;;
399
- --feature-slug) FEATURE_SLUG="${2:-}"; shift 2 ;;
400
- --output-dir) OUTPUT_DIR="${2:-}"; shift 2 ;;
401
- --scale) SCALE="${2:-2}"; shift 2 ;;
402
- --format) FORMAT="${2:-png}"; shift 2 ;;
404
+ --feature-slug) need_value "$@"; FEATURE_SLUG="$2"; shift 2 ;;
405
+ --output-dir) need_value "$@"; OUTPUT_DIR="$2"; shift 2 ;;
406
+ --scale) need_value "$@"; SCALE="${2:-2}"; shift 2 ;;
407
+ --format) need_value "$@"; FORMAT="${2:-png}"; shift 2 ;;
403
408
  *) die 1 "unknown argument: $1" ;;
404
409
  esac
405
410
  done
@@ -431,7 +436,7 @@ if [ -z "${FIGMA_TOKEN:-}" ]; then
431
436
  fi
432
437
 
433
438
  if [ -z "$OUTPUT_DIR" ]; then
434
- OUTPUT_DIR="/tmp/figma-screenshots-$(date +%Y%m%d-%H%M%S)"
439
+ OUTPUT_DIR=$(mktemp -d "${TMPDIR:-/tmp}/figma-screenshots.XXXXXX")
435
440
  fi
436
441
  mkdir -p "$OUTPUT_DIR"
437
442
 
@@ -444,14 +449,12 @@ mkdir -p "$OUTPUT_DIR"
444
449
  # "Elite Kart" on one section and "Elit Kart" on its sibling.
445
450
 
446
451
  run_discover_mode() {
447
- local body
448
- # Same as fetch_with_retry: the token goes through a curl config on a process
449
- # substitution, never argv, because argv is readable from ps.
450
- body=$(curl -sS --max-time "$HTTP_TIMEOUT" --connect-timeout 5 \
451
- -K <(printf 'header = "X-Figma-Token: %s"\n' "$FIGMA_TOKEN") \
452
- "https://api.figma.com/v1/files/${FILE_KEY}?depth=3") || die 3 "file tree fetch failed"
453
-
454
- printf '%s' "$body" | FEATURE_SLUG="$FEATURE_SLUG" FILE_KEY="$FILE_KEY" python3 -c '
452
+ local tree code
453
+ tree=$(mktemp "$WORK_DIR/tree.XXXXXX")
454
+ code=$(curl_figma "$tree" "$FIGMA_API/files/${FILE_KEY}?depth=3")
455
+ handle_http_code "$code" "file tree for $FILE_KEY"
456
+
457
+ FEATURE_SLUG="$FEATURE_SLUG" FILE_KEY="$FILE_KEY" python3 -c '
455
458
  import json, os, re, sys, unicodedata
456
459
 
457
460
  def fold(t):
@@ -475,11 +478,11 @@ def channel_of(name):
475
478
  return label
476
479
  return None
477
480
 
478
- raw = sys.stdin.read()
481
+ raw = open(sys.argv[1]).read()
479
482
  try:
480
483
  doc = json.loads(raw).get("document", {})
481
484
  except Exception:
482
- print(json.dumps({"error": "unparseable file response"})); sys.exit(3)
485
+ print(json.dumps({"error": "unparseable file response"})); sys.exit(6)
483
486
 
484
487
  slug_words = [w for w in fold(os.environ["FEATURE_SLUG"]).split() if len(w) > 2]
485
488
  out, weak, pages = [], [], 0
@@ -516,7 +519,7 @@ print(json.dumps({
516
519
  "candidates": sorted(out, key=lambda c: (c["channelGuess"] or "zz", c["id"])),
517
520
  "weakMatches": sorted(weak, key=lambda c: c["id"]),
518
521
  }, ensure_ascii=False, indent=2))
519
- '
522
+ ' "$tree"
520
523
  }
521
524
 
522
525
  # --- Section mode -----------------------------------------------------------
@@ -570,7 +573,7 @@ PY
570
573
 
571
574
  # Collect ids and chunk to MAX_IDS_PER_CALL.
572
575
  local ids_file
573
- ids_file=$(mktemp)
576
+ ids_file=$(mktemp "$WORK_DIR/ids.XXXXXX")
574
577
  awk -F'\t' '{print $1}' "$lookup" > "$ids_file"
575
578
 
576
579
  local rows
@@ -31,8 +31,9 @@
31
31
  # An unset or non-executable CRED_STORE is tolerated: resolution falls through to
32
32
  # the env fallback so a keychain-less CI box still works.
33
33
 
34
- # The canonical key first, the legacy key second. Space-separated so the loop
35
- # below stays POSIX-ish and works under bash 3.2 (stock macOS).
34
+ # The canonical key first, the legacy key second. Space-separated; the loop
35
+ # below splits explicitly because zsh (the pipeline's Bash tool) does not
36
+ # word-split an unquoted parameter, so `for key in $KEYS` saw one key there.
36
37
  FIGMA_TOKEN_LOGICAL_KEYS="${FIGMA_TOKEN_LOGICAL_KEYS:-figma figma_pat}"
37
38
 
38
39
  # resolve_figma_token
@@ -40,13 +41,16 @@ FIGMA_TOKEN_LOGICAL_KEYS="${FIGMA_TOKEN_LOGICAL_KEYS:-figma figma_pat}"
40
41
  resolve_figma_token() {
41
42
  local key tok
42
43
  if [ -n "${CRED_STORE:-}" ] && [ -x "${CRED_STORE:-}" ]; then
43
- for key in $FIGMA_TOKEN_LOGICAL_KEYS; do
44
+ while IFS= read -r key; do
45
+ [ -n "$key" ] || continue
44
46
  tok=$("$CRED_STORE" get "$key" 2>/dev/null || true)
45
47
  if [ -n "$tok" ]; then
46
48
  printf '%s' "$tok"
47
49
  return 0
48
50
  fi
49
- done
51
+ done <<EOF_KEYS
52
+ $(printf '%s\n' "$FIGMA_TOKEN_LOGICAL_KEYS" | tr ' ' '\n')
53
+ EOF_KEYS
50
54
  fi
51
55
  if [ -n "${FIGMA_PAT:-}" ]; then
52
56
  printf '%s' "$FIGMA_PAT"
@@ -43,7 +43,6 @@
43
43
  # Required env:
44
44
  # ACCOUNT_JIRA_TOKEN_KEY keychain service name (optional)
45
45
  # ACCOUNT_JIRA_HOST e.g. jira.example.com (optional)
46
- # ACCOUNT_GH_TOKEN_KEY keychain service name (optional)
47
46
  # ACCOUNT_DEFAULT_OWNER fallback GitHub owner (optional)
48
47
  # ACCOUNT_DEFAULT_REPO fallback GitHub repo (optional)
49
48
  # PROMPT_LANG tr|en - controls maturity.summary language (default: tr)
@@ -54,13 +54,15 @@ BACKUP_DIR="${JIRA_BACKUP_DIR:-$HOME/.claude/logs/multi-agent/jira-backups}"
54
54
 
55
55
  die() { echo "$1" >&2; exit "${2:-64}"; }
56
56
 
57
+ need_value() { [ "$#" -ge 2 ] || die "$1 requires a value"; }
58
+
57
59
  while [ "$#" -gt 0 ]; do
58
60
  case "$1" in
59
- --issue) ISSUE="${2:-}"; shift 2 ;;
60
- --body-file) BODY_FILE="${2:-}"; shift 2 ;;
61
- --target) TARGET="${2:-}"; shift 2 ;;
62
- --mode) MODE="${2:-}"; shift 2 ;;
63
- --backup-dir) BACKUP_DIR="${2:-}"; shift 2 ;;
61
+ --issue) need_value "$@"; ISSUE="$2"; shift 2 ;;
62
+ --body-file) need_value "$@"; BODY_FILE="$2"; shift 2 ;;
63
+ --target) need_value "$@"; TARGET="$2"; shift 2 ;;
64
+ --mode) need_value "$@"; MODE="$2"; shift 2 ;;
65
+ --backup-dir) need_value "$@"; BACKUP_DIR="$2"; shift 2 ;;
64
66
  --confirm-overwrite) CONFIRM=1; shift ;;
65
67
  --dry-run) DRY_RUN=1; shift ;;
66
68
  -h|--help) sed -n '2,40p' "$0"; exit 0 ;;
@@ -69,6 +69,11 @@ PUNCT_MAP_DEFAULT_TR = {
69
69
  PUNCT_MAP_EN = dict(PUNCT_MAP_DEFAULT_TR)
70
70
  PUNCT_MAP_EN["\u00A7"] = "section"
71
71
 
72
+ # Typographic dashes (U+2010 to U+2015), quotes (U+2018 to U+201F), the
73
+ # ellipsis and the section sign, including the variants the maps above do
74
+ # not rewrite, so a residual can actually be reported.
75
+ FORBIDDEN_PUNCT_RE = re.compile("[\u2010-\u2015\u2018-\u201F\u2026\u00A7]")
76
+
72
77
 
73
78
  def detect_language(markdown: str, front_matter_lang: str | None) -> str:
74
79
  if front_matter_lang:
@@ -76,12 +81,11 @@ def detect_language(markdown: str, front_matter_lang: str | None) -> str:
76
81
  if lang.startswith("en"):
77
82
  return "en"
78
83
  return "tr"
79
- # Heuristic: presence of TR-only characters in headings
80
- for line in markdown.splitlines():
81
- if line.startswith("#"):
82
- if re.search(r"[ğışçöü]", line):
83
- return "tr"
84
- return "tr"
84
+ # Heuristic: a Turkish document carries Turkish-only letters somewhere in
85
+ # its body; a text without a single one is English.
86
+ if re.search(r"[ğışçöüĞİŞÇÖÜ]", markdown):
87
+ return "tr"
88
+ return "en"
85
89
 
86
90
 
87
91
  def apply_punctuation_gate(text: str, language: str) -> tuple[str, list[str]]:
@@ -91,7 +95,9 @@ def apply_punctuation_gate(text: str, language: str) -> tuple[str, list[str]]:
91
95
  out = out.replace(src, dst)
92
96
 
93
97
  warnings: list[str] = []
94
- leftover = [ch for ch in mapping.keys() if ch in out]
98
+ # The residual check must look wider than the mapping: every mapped
99
+ # character was just replaced, so testing those again can never fire.
100
+ leftover = sorted(set(FORBIDDEN_PUNCT_RE.findall(out)))
95
101
  if leftover:
96
102
  warnings.append(
97
103
  "punctuation gate: residual forbidden characters: "
@@ -33,7 +33,7 @@ set -euo pipefail
33
33
  CMD="${1:-}"
34
34
  STATE="${2:-}"
35
35
  [ -z "$CMD" ] || [ -z "$STATE" ] && {
36
- echo "usage: $0 <prepare|commit|push|pr|teardown> <state.json>" >&2
36
+ echo "usage: $0 <prepare|bridge|commit|push|pr|teardown> <state.json>" >&2
37
37
  exit 1
38
38
  }
39
39
  [ ! -f "$STATE" ] && { echo "ERR: state file not found: $STATE" >&2; exit 2; }
@@ -54,6 +54,8 @@ TASK_ID=$(python3 -c "import json; d=json.load(open('$STATE')); print(d['taskId'
54
54
  COMMIT_TYPE=$(python3 -c "import json; d=json.load(open('$STATE')); print(d.get('commitType','feat'))")
55
55
  ISSUE_KEY=$(python3 -c "import json; d=json.load(open('$STATE')); print(d['issueRef'].get('key') or '')")
56
56
  ISSUE_TITLE=$(python3 -c "import json; d=json.load(open('$STATE')); print(d['issueRef'].get('title') or '')")
57
+ IDENTITY_NAME=$(python3 -c "import json; d=json.load(open('$STATE')); print((d.get('identity') or {}).get('name') or '')")
58
+ IDENTITY_EMAIL=$(python3 -c "import json; d=json.load(open('$STATE')); print((d.get('identity') or {}).get('email') or '')")
57
59
 
58
60
  cmd_prepare() {
59
61
  mkdir -p "$WORKTREE_ROOT"
@@ -90,24 +92,32 @@ cmd_commit() {
90
92
  # loop - a piped `while` runs in a subshell and the summary exit would
91
93
  # always be 0 regardless of per-repo failures.
92
94
  local failed=0
95
+ # state.identity is the commit author/committer, as documented in the schema
96
+ # above. Git's own config is the fallback when the picker recorded none.
97
+ local identity_args=()
98
+ if [ -n "$IDENTITY_NAME" ] && [ -n "$IDENTITY_EMAIL" ]; then
99
+ identity_args=(-c "user.name=$IDENTITY_NAME" -c "user.email=$IDENTITY_EMAIL")
100
+ fi
93
101
  while IFS=$'\t' read -r name url provider; do
94
102
  local target="$WORKTREE_ROOT/$name"
95
103
  [ ! -d "$target" ] && continue
96
104
  cd "$target"
97
- # Use status --porcelain so untracked files count as "changes" too -
98
- # dev sessions usually add brand-new files alongside edits, and `git diff`
99
- # alone misses them.
100
- if [ -z "$(git status --porcelain 2>/dev/null)" ]; then
101
- echo "[$name] no changes, skipping commit"
105
+ # Only what the dev session staged is committed. A blanket `git add -A`
106
+ # here swept build output and nested-worktree gitlinks into the commit.
107
+ if git diff --cached --quiet 2>/dev/null; then
108
+ if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
109
+ echo "[$name] nothing staged (unstaged or untracked changes present) - stage what belongs in the commit, skipping"
110
+ else
111
+ echo "[$name] no changes, skipping commit"
112
+ fi
102
113
  cd - >/dev/null
103
114
  continue
104
115
  fi
105
- git add -A
106
116
  local subject="$COMMIT_TYPE($name): $ISSUE_TITLE"
107
117
  [ -n "$ISSUE_KEY" ] && subject="$subject [$ISSUE_KEY]"
108
118
  # Trim subject to 72 chars
109
119
  subject=$(printf '%s' "$subject" | cut -c1-72)
110
- if git commit --quiet -m "$subject"; then
120
+ if git ${identity_args[@]+"${identity_args[@]}"} commit --quiet -m "$subject"; then
111
121
  echo "[$name] committed: $subject"
112
122
  else
113
123
  echo "ERR: [$name] commit failed" >&2
@@ -155,10 +155,19 @@ do_start() {
155
155
  write_state "$state_path" "$new"
156
156
  }
157
157
 
158
+ require_todo() {
159
+ # $1 state path, $2 todo_id - exits 1 when the id is not in the plan.
160
+ if ! jq -e --arg id "$2" '(.plan.todos // []) | map(select(.id == $id)) | length > 0' "$1" >/dev/null 2>&1; then
161
+ echo "plan-todos: todo '$2' not found" >&2
162
+ exit 1
163
+ fi
164
+ }
165
+
158
166
  do_complete() {
159
167
  local task_id="${1:-}"; local todo_id="${2:-}"; local notes="${3:-}"
160
168
  [ -z "$task_id" ] || [ -z "$todo_id" ] && { echo "usage: complete <task-id> <todo-id> [notes]" >&2; exit 64; }
161
169
  local state_path; state_path=$(resolve_state "$task_id")
170
+ require_todo "$state_path" "$todo_id"
162
171
  local now; now=$(iso_now)
163
172
  local new
164
173
  new=$(jq --arg id "$todo_id" --arg t "$now" --arg n "$notes" '
@@ -176,6 +185,7 @@ do_fail() {
176
185
  local task_id="${1:-}"; local todo_id="${2:-}"; local reason="${3:-}"
177
186
  [ -z "$task_id" ] || [ -z "$todo_id" ] || [ -z "$reason" ] && { echo "usage: fail <task-id> <todo-id> <reason>" >&2; exit 64; }
178
187
  local state_path; state_path=$(resolve_state "$task_id")
188
+ require_todo "$state_path" "$todo_id"
179
189
  local now; now=$(iso_now)
180
190
  local new
181
191
  new=$(jq --arg id "$todo_id" --arg t "$now" --arg r "$reason" '
@@ -190,6 +200,7 @@ do_skip() {
190
200
  local task_id="${1:-}"; local todo_id="${2:-}"; local reason="${3:-}"
191
201
  [ -z "$task_id" ] || [ -z "$todo_id" ] || [ -z "$reason" ] && { echo "usage: skip <task-id> <todo-id> <reason>" >&2; exit 64; }
192
202
  local state_path; state_path=$(resolve_state "$task_id")
203
+ require_todo "$state_path" "$todo_id"
193
204
  local now; now=$(iso_now)
194
205
  local new
195
206
  new=$(jq --arg id "$todo_id" --arg t "$now" --arg r "$reason" '
@@ -408,9 +408,16 @@ post_bitbucket_server() {
408
408
  needs_work) status_payload='{"status":"NEEDS_WORK"}' ;;
409
409
  esac
410
410
 
411
- curl -sS -m 30 -K <(bb_auth_cfg "$auth_user" "$auth_token") -X PUT \
411
+ local http
412
+ http=$(curl -sS -m 30 -K <(bb_auth_cfg "$auth_user" "$auth_token") -X PUT \
412
413
  -H "Content-Type: application/json" "$base/participants/$user_slug" \
413
- -d "$status_payload" >/dev/null
414
+ -d "$status_payload" -o /dev/null -w '%{http_code}' || echo 000)
415
+ case "$http" in
416
+ 200|201) ;;
417
+ *)
418
+ echo "post-pr-review: bitbucket verdict PUT failed (HTTP $http) for PR #$PR_NUM - $DECISION was NOT recorded" >&2
419
+ return 4 ;;
420
+ esac
414
421
  }
415
422
 
416
423
  # --- Main switch ------------------------------------------------------------
@@ -18,12 +18,13 @@
18
18
  # ./repo-cache.sh bitbucket <workspace> [--refresh]
19
19
  #
20
20
  # Required env per-provider:
21
- # github -> GH_TOKEN_KEY (keychain service)
21
+ # github -> (none) - uses the gh CLI's own auth; GH_USER selects the account
22
22
  # bitbucket -> BB_TOKEN_KEY, BB_USER_KEY, BB_HOST (e.g. bitbucket.example.com)
23
23
  # local -> (none) - scopes are treated as filesystem roots; "scope=$HOME"
24
24
  # walks $HOME with maxdepth, scope=<path> walks just that path.
25
- # LOCAL_SCAN_ROOTS env can override the default root list.
26
- # LOCAL_SCAN_MAXDEPTH env (default 4) bounds traversal cost.
25
+ # LOCAL_SCAN_ROOTS env (colon-separated, like PATH) overrides the
26
+ # default root list. LOCAL_SCAN_MAXDEPTH env (default 4) bounds
27
+ # traversal cost.
27
28
 
28
29
  set -euo pipefail
29
30
 
@@ -211,16 +212,23 @@ print(json.dumps(out))
211
212
  local)
212
213
  # Scan filesystem roots for git repos. Default roots cover the common
213
214
  # places personal/work checkouts live; override via LOCAL_SCAN_ROOTS.
214
- DEFAULT_ROOTS="$HOME $HOME/Documents $HOME/Developer $HOME/projects $HOME/code $HOME/work"
215
- ROOTS="${LOCAL_SCAN_ROOTS:-$DEFAULT_ROOTS}"
216
- MAXDEPTH="${LOCAL_SCAN_MAXDEPTH:-4}"
217
- # When SCOPE is an absolute path use it directly; otherwise treat SCOPE
218
- # as a label and walk the configured roots.
215
+ # Roots are held in an array, never a whitespace-split string: a HOME with
216
+ # a space in it split into two nonexistent roots and the scan found nothing.
217
+ ROOTS=()
219
218
  if [ -d "$SCOPE" ]; then
220
- ROOTS="$SCOPE"
219
+ ROOTS=("$SCOPE")
220
+ elif [ -n "${LOCAL_SCAN_ROOTS:-}" ]; then
221
+ while IFS= read -r root; do
222
+ [ -n "$root" ] && ROOTS+=("$root")
223
+ done <<EOF_ROOTS
224
+ $(printf '%s\n' "$LOCAL_SCAN_ROOTS" | tr ':' '\n')
225
+ EOF_ROOTS
226
+ else
227
+ ROOTS=("$HOME" "$HOME/Documents" "$HOME/Developer" "$HOME/projects" "$HOME/code" "$HOME/work")
221
228
  fi
229
+ MAXDEPTH="${LOCAL_SCAN_MAXDEPTH:-4}"
222
230
  : > "$CACHE_TMP.list"
223
- for root in $ROOTS; do
231
+ for root in ${ROOTS[@]+"${ROOTS[@]}"}; do
224
232
  [ -d "$root" ] || continue
225
233
  # -prune avoids descending into already-found .git trees (nested submodules
226
234
  # show up via submodule-detector, not the top-level picker).
@@ -10,11 +10,16 @@
10
10
  # - Reads watched repos from prefs.global.reviewWatch.repos[] (or --repos).
11
11
  # - For each repo, lists open PRs since the last seen cursor (state in
12
12
  # ~/.claude/state/review-watch/<owner-repo>.json).
13
- # - For each PR not yet reviewed (or updated since last review), invokes
14
- # `bash $HOME/.claude/lib/post-pr-review.sh <task-id>` after writing the
15
- # PR input to agent-state.json. The reviewer dispatch itself is the
16
- # responsibility of /multi-agent:review's LLM loop; review-watch is just
17
- # the trigger.
13
+ # - For each PR not yet reviewed (or updated since last review), writes the
14
+ # PR input to a per-PR agent-state.json (see Storage) and prints the
15
+ # dispatch line. The review itself is /multi-agent:review's LLM loop,
16
+ # which fills `.review.findings` for that task id; review-watch never
17
+ # spends tokens.
18
+ # - On a later scan, a PR whose agent-state carries findings for the
19
+ # current head SHA is posted through post-pr-review.sh, and only a
20
+ # successful post stamps reviewed_prs[<n>] = <sha>. A PR is never marked
21
+ # reviewed without a review having been posted.
22
+ # REVIEW_WATCH_DRY_RUN=1 reports what would be posted and stamps nothing.
18
23
  # - Honors labelFilter (only review PRs carrying the label, default: all).
19
24
  # - Skips PRs the user authored (we don't review our own PRs through this
20
25
  # surface - that's what the in-pipeline Phase 4 / standalone /review is
@@ -36,13 +41,26 @@
36
41
  # ~/.claude/state/review-watch/<owner__repo>.json
37
42
  # { last_seen_iso: "2026-05-11T12:34:56Z",
38
43
  # reviewed_prs: { "1234": "head-sha-abc" } }
44
+ # ~/.claude/logs/multi-agent/review-watch/<owner__repo>-pr<n>/agent-state.json
45
+ # { taskId, review: { input: {...}, headCommitSha, findings? } }
46
+ # (task id <owner__repo>-pr<n>; post-pr-review.sh reads it by that id)
39
47
 
40
48
  set -euo pipefail
41
49
 
42
50
  PREFS="$HOME/.claude/multi-agent-preferences.json"
43
51
  STATE_DIR="$HOME/.claude/state/review-watch"
52
+ REVIEW_LOGS="$HOME/.claude/logs/multi-agent/review-watch"
44
53
  mkdir -p "$STATE_DIR"
45
54
 
55
+ POSTER=""
56
+ for _cand in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/post-pr-review.sh" \
57
+ "$HOME/.claude/lib/post-pr-review.sh" \
58
+ "$HOME/.copilot/lib/post-pr-review.sh" \
59
+ "$HOME/.codex/lib/post-pr-review.sh"; do
60
+ [ -f "$_cand" ] && { POSTER="$_cand"; break; }
61
+ done
62
+ unset _cand
63
+
46
64
  CMD="${1:-}"
47
65
  shift || true
48
66
 
@@ -192,16 +210,44 @@ review_one_repo() {
192
210
  continue
193
211
  fi
194
212
 
213
+ local task_id state_file
214
+ task_id="${repo//\//__}-pr${pr_num}"
215
+ state_file="$REVIEW_LOGS/$task_id/agent-state.json"
216
+
217
+ # Findings already written for THIS head by /multi-agent:review: post them.
218
+ # The stamp below is the only place reviewed_prs is written, and it is
219
+ # written only after post-pr-review.sh returned 0.
220
+ if [ -f "$state_file" ] \
221
+ && [ "$(jq -r '.review.headCommitSha // ""' "$state_file" 2>/dev/null)" = "$head_sha" ] \
222
+ && jq -e '.review.findings | type == "array"' "$state_file" >/dev/null 2>&1; then
223
+ if [ "${REVIEW_WATCH_DRY_RUN:-0}" = "1" ]; then
224
+ printf ' dry-run: would post the review for PR #%s (task %s) via post-pr-review.sh\n' "$pr_num" "$task_id"
225
+ elif [ -z "$POSTER" ]; then
226
+ err "post-pr-review.sh not found - PR #$pr_num stays pending"
227
+ elif bash "$POSTER" "$task_id"; then
228
+ printf ' posted review for PR #%s at %s\n' "$pr_num" "$head_sha"
229
+ state=$(jq --arg n "$pr_num" --arg s "$head_sha" \
230
+ '.reviewed_prs[$n] = $s' <<<"$state")
231
+ else
232
+ err "post-pr-review.sh failed for $repo PR #$pr_num - stays pending, not stamped"
233
+ fi
234
+ continue
235
+ fi
236
+
237
+ # No review for this head yet: write the input /multi-agent:review reads
238
+ # and hand off. review-watch does not run the LLM itself, because token
239
+ # cost decisions stay with the user; the PR stays un-stamped until a
240
+ # review is actually posted.
195
241
  printf ' dispatch review: %s PR #%s - %s\n' "$repo" "$pr_num" "$title"
196
- printf ' url=%s head=%s\n' "$url" "$head_sha"
197
-
198
- # Record that we reviewed this head SHA. The actual dispatch happens
199
- # via /multi-agent:review on the surface that invoked review-watch
200
- # (Claude Code session or future webhook handler). review-watch only
201
- # produces the dispatch list - it does not run the LLM itself, because
202
- # token cost decisions stay with the user.
203
- state=$(jq --arg n "$pr_num" --arg s "$head_sha" \
204
- '.reviewed_prs[$n] = $s' <<<"$state")
242
+ printf ' url=%s head=%s task=%s\n' "$url" "$head_sha" "$task_id"
243
+ mkdir -p "$(dirname "$state_file")"
244
+ jq -n --arg repo "$repo" --arg n "$pr_num" --arg sha "$head_sha" \
245
+ --arg url "$url" --arg title "$title" --arg t "$task_id" '
246
+ { schemaVersion: "2.1.0", taskId: $t,
247
+ review: { input: { kind: "pr", provider: "github", host: "github.com",
248
+ orgRepo: $repo, prNumber: ($n | tonumber),
249
+ url: $url, title: $title },
250
+ headCommitSha: $sha, iterationNumber: 1 } }' > "$state_file"
205
251
  done < <(jq -c '.[]' <<<"$prs_json")
206
252
 
207
253
  # Persist the NEWEST updatedAt seen this scan - the previous per-PR
@@ -163,10 +163,12 @@ do_list() {
163
163
  require_task_id "$task_id"
164
164
  local sd; sd=$(shadow_dir "$task_id")
165
165
  [ ! -d "$sd/.git" ] && { err "shadow not initialized for $task_id"; exit 1; }
166
- # Use the embedded `iso:` line from the commit message body so the listing
166
+ # Use the embedded `iso:` trailer from the commit message body so the listing
167
167
  # carries the snapshot timestamp without relying on committer date (which is
168
- # influenced by git env vars).
169
- sg "$sd" "$(pwd)" log --pretty=format:'%h %s' --no-decorate
168
+ # influenced by git env vars); committer date is the fallback for a snapshot
169
+ # written without one.
170
+ sg "$sd" "$(pwd)" log --pretty=format:'%h%x1f%(trailers:key=iso,valueonly,separator=%x20)%x1f%cI%x1f%s' --no-decorate \
171
+ | awk -F'\037' '{ d = $2; sub(/ .*/, "", d); if (d == "") d = $3; printf "%s %s %s\n", $1, d, $4 }'
170
172
  }
171
173
 
172
174
  do_restore() {
@@ -213,7 +215,9 @@ do_prune() {
213
215
  local older=""
214
216
  while [ "$#" -gt 0 ]; do
215
217
  case "$1" in
216
- --older-than-days) older="$2"; shift 2 ;;
218
+ --older-than-days)
219
+ [ "$#" -ge 2 ] || { err "--older-than-days needs a value"; exit 2; }
220
+ older="$2"; shift 2 ;;
217
221
  *) err "unknown flag: $1"; exit 2 ;;
218
222
  esac
219
223
  done
@@ -153,10 +153,10 @@ cmd_deploy() {
153
153
  if [ "$prod" = true ]; then
154
154
  cmd_args+=(--prod)
155
155
  fi
156
- cmd_args+=("${extra_args[@]}")
156
+ cmd_args+=(${extra_args[@]+"${extra_args[@]}"})
157
157
 
158
158
  # Refuse to run if the user is trying to pass --token=... via extra args.
159
- for a in "${extra_args[@]}"; do
159
+ for a in ${extra_args[@]+"${extra_args[@]}"}; do
160
160
  case "$a" in
161
161
  --token=*|--token)
162
162
  echo "ERROR: refused to deploy with --token argv. Pass the token via VERCEL_TOKEN env var instead." >&2
@@ -53,8 +53,11 @@ Selects extra repos the pipeline may touch beyond the primary repo(s) - typica
53
53
  entry, plus any selected `extras[]` that is not being given a worktree. The
54
54
  phases that consume them run hours later and have no access to the picker's
55
55
  return value, so a result that is not written here is a result nothing can
56
- read - Phase 4's platform-parity cross-check reads `state.siblings[]` and
57
- nothing else.
56
+ read. Phase 4's platform-parity cross-check reads `state.siblings[]` as the
57
+ fourth of its four counterpart sources, after `--with`,
58
+ `prefs.projects[<slug>].counterpartRoots[]` and the primary checkout's sibling
59
+ directories (`platform-parity.md`), so a submodule or a hand-picked repo that
60
+ is not written here is a candidate the check can never see.
58
61
 
59
62
  Resolve each entry's `stack` from its local checkout, with the marker table
60
63
  in `phases/phase-1-analysis.md` Step 2 (`.xcodeproj` / `Package.swift` →
@@ -1,14 +1,14 @@
1
- # Locked decisions (35)
1
+ # Locked decisions (36)
2
2
 
3
- > The 35 Locked decisions of the analysis flow. Loaded by `/multi-agent:analysis`, by `/multi-agent:analysis-resolve` (which inherits them) and by pipeline Phase 1 when it runs the analysis engine. Numbering is canonical: cite as `Locked <n> (<short label>)`.
3
+ > The 36 Locked decisions of the analysis flow. Loaded by `/multi-agent:analysis`, by `/multi-agent:analysis-resolve` (which inherits them) and by pipeline Phase 1 when it runs the analysis engine. Numbering is canonical: cite as `Locked <n> (<short label>)`.
4
4
 
5
5
  ### Index by category (v9.1.0+)
6
6
 
7
- Browse-friendly grouping of the 35 Locked decisions. Numbering stays canonical (matches the list below); the index is read-only navigation.
7
+ Browse-friendly grouping of the 36 Locked decisions. Numbering stays canonical (matches the list below); the index is read-only navigation.
8
8
 
9
9
  | Category | Decisions | Concern |
10
10
  |---|---|---|
11
- | **A. Governance** | 1, 5, 6, 7, 10, 26, 27, 32 | Run-level process rules: one feature per run, default output, auto-commit ban, punctuation policy, output picker timing, Pass B preview, evidence digest cache, analysis profile |
11
+ | **A. Governance** | 1, 5, 6, 7, 10, 26, 27, 32, 36 | Run-level process rules: one feature per run, default output, auto-commit ban, punctuation policy, output picker timing, Pass B preview, evidence digest cache, analysis profile, document reviewed before publish |
12
12
  | **B. Citation and Evidence** | 3, 4, 8, 11, 24, 30, 34 | Every fact in the doc traces back to a source: citation discipline, forward-looking spec, standards binding, repo-evidence reuse-first, Pass B footnote mandatory, analysis self-contained (pipeline-wide), references built from the evidence record |
13
13
  | **C. Output Format and Structure** | 2, 9, 13, 14, 16, 17, 20, 21, 25, 33, 35 | How the document is laid out: section omission rule, per-platform output split, Gherkin user stories, Goals + Non-Goals paired, Files-to-Add tag, API response variants exhaustive, localization mode (ownership-aware), References at the bottom, Lite mode, corporate backbone always renders, stack-optional render |
14
14
  | **D. Design Source and Pipeline Architecture** | 12, 22, 23 | Where design comes from and how the pipeline renders: Figma 3-tier access (BLOCKING), platform-agnostic template + Pass B render, convention extraction (Phase 1c) |
@@ -12,7 +12,7 @@
12
12
 
13
13
  **When `platforms[]` is empty** (Locked 35), the channels come from the evidence instead of from repo stack tags, so this loop runs once per derived channel (`mobile`, `web`, or one channel-agnostic pass). What drops is the development layer (corporate Part C, global Sections 13, 14, 15) and the Pass B projection are skipped, Section 20 carries a row recording that they await a repo selection, and the front-matter `platform` key reads `none`. Everything that does not need a target repository still renders in full.
14
14
 
15
- 3. **Humanizer pass (MANDATORY: actually invoke the `ai-common-toolkit:humanizer` skill on the rendered markdown - the punctuation grep alone does NOT satisfy this step)** (`technical-explanatory` tone for the scratch buffer; per-channel re-humanize happens in Phase 4 when actually emitting):
15
+ 3. **Humanizer pass (required: actually invoke the `ai-common-toolkit:humanizer` skill on the rendered markdown - the punctuation grep alone does NOT satisfy this step)** (`technical-explanatory` tone for the scratch buffer; per-channel re-humanize happens in Phase 4 when actually emitting):
16
16
  ```
17
17
  ai-common-toolkit:humanizer skill input:
18
18
  language: <tr|en>
@@ -207,7 +207,7 @@ Future changes that break an item in the "stay identical" list must update **bot
207
207
 
208
208
  Each file has a different frontmatter schema. The sync flow transforms between them:
209
209
 
210
- ### 3.1 Claude Code - `commands/multi-agent/{cmd}.md`
210
+ ### 3.1 Claude Code - `commands/multi-agent/{cmd}/SKILL.md`
211
211
 
212
212
  ```yaml
213
213
  ---
@@ -75,13 +75,13 @@ toolkit. Continue without one rather than guessing which might fit.
75
75
 
76
76
  ## multi-agent-toolkit MCP
77
77
 
78
- | Family | Reach for it when |
78
+ | Tool | Reach for it when |
79
79
  |---|---|
80
- | `ui-inspect` | you need to know what is actually on screen, not what the code implies |
81
- | `crash-logs` | a crash happened on a device or simulator |
82
- | `design-check` | a built screen has to be compared against its design |
83
- | `ios-app-store-audit` | a package is heading for review |
84
- | `ios-testflight` | validating a build before upload |
80
+ | `ios_get_ui_tree` / `android_get_ui_tree` | you need to know what is actually on screen, not what the code implies |
81
+ | `ios_list_crashes` / `android_list_crashes` | a crash happened on a device or simulator |
82
+ | `design_*` (`design_visual_compare`, `design_ui_geometry`, `design_report`, ...) | a built screen has to be compared against its design |
83
+ | `ios_app_store_audit` | a package is heading for review |
84
+ | `ios_testflight_validate` | validating a build before upload |
85
85
 
86
86
  Registered at user scope by the installer, and preserved by uninstall - the tools are
87
87
  useful with no pipeline at all. If it is not registered the tools simply are not
@@ -55,7 +55,7 @@ Phase 7: Report █░░░░░░░░░░░░░░░ 2s
55
55
  (emit from the last iteration's `triage.consensus` block, schema v3.1.0. One line stating `verdict` + `reviewerCount`; when `verdict` is `split` or `unverified`, list each `disagreements[]` entry so a human can confirm what the reviewers did not independently agree on. Omit the section when triage produced no consensus block.)
56
56
 
57
57
  ```
58
- Verdict: unverified (2 reviewers)
58
+ Verdict: unverified (3 reviewers)
59
59
  - Auth/KeychainStore.swift:40 - token persisted without access-control flag
60
60
  (both approved a keychain change - agreement unverified, confirm manually)
61
61
  ```