@mmerterden/multi-agent-pipeline 20.33.0 → 20.35.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 (390) hide show
  1. package/CHANGELOG.md +98 -1
  2. package/README.md +3 -1
  3. package/README.tr.md +2 -1
  4. package/docs/adr/0018-cross-repo-links-not-a-global-graph.md +43 -0
  5. package/docs/adr/README.md +2 -1
  6. package/docs/facts.json +5 -4
  7. package/docs/features.md +1 -1
  8. package/install/_dev-only-files.mjs +1 -0
  9. package/install/catalog-history.json +1 -1
  10. package/install/claude.mjs +24 -2
  11. package/install/templates/claude-hooks.json +34 -1
  12. package/manifest.json +393 -339
  13. package/package.json +3 -1
  14. package/pipeline/agents/bulk-reader.md +3 -0
  15. package/pipeline/agents/code-reviewer.md +9 -0
  16. package/pipeline/agents/explorer.md +4 -0
  17. package/pipeline/agents/plan-critic.md +4 -2
  18. package/pipeline/commands/multi-agent/SKILL.md +17 -136
  19. package/pipeline/commands/multi-agent/channels/SKILL.md +16 -298
  20. package/pipeline/commands/multi-agent/graph/SKILL.md +5 -2
  21. package/pipeline/commands/multi-agent/language/SKILL.md +3 -3
  22. package/pipeline/commands/multi-agent/office-on/SKILL.md +7 -3
  23. package/pipeline/commands/multi-agent/office-status/SKILL.md +3 -2
  24. package/pipeline/commands/multi-agent/resume/SKILL.md +8 -22
  25. package/pipeline/commands/multi-agent/review/SKILL.md +7 -131
  26. package/pipeline/commands/multi-agent/setup/SKILL.md +19 -224
  27. package/pipeline/contract/CHANGELOG.md +9 -0
  28. package/pipeline/contract/README.md +6 -2
  29. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  30. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  31. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  32. package/pipeline/contract/manifest.json +14 -1
  33. package/pipeline/contract/types/index.d.ts +24 -2
  34. package/pipeline/lib/json-file-lock.mjs +61 -15
  35. package/pipeline/lib/office-remote.mjs +228 -0
  36. package/pipeline/lib/qr-code.mjs +360 -0
  37. package/pipeline/lib/repo-hygiene.sh +1 -0
  38. package/pipeline/lib/run-completion.mjs +17 -0
  39. package/pipeline/multi-agent-refs/channels/generator.md +146 -0
  40. package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
  41. package/pipeline/multi-agent-refs/channels/menu.md +55 -0
  42. package/pipeline/multi-agent-refs/channels/multi-repo.md +6 -0
  43. package/pipeline/multi-agent-refs/channels/preview.md +25 -0
  44. package/pipeline/multi-agent-refs/channels/reference.md +98 -0
  45. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +1 -1
  46. package/pipeline/multi-agent-refs/features/code-graph.md +27 -0
  47. package/pipeline/multi-agent-refs/features/office-remote-protocol.md +166 -0
  48. package/pipeline/multi-agent-refs/features/office-threat-model.md +2 -2
  49. package/pipeline/multi-agent-refs/features/plan-critic.md +5 -2
  50. package/pipeline/multi-agent-refs/features/review-delta.md +4 -1
  51. package/pipeline/multi-agent-refs/features/scope-check.md +25 -1
  52. package/pipeline/multi-agent-refs/features/verify-by-test.md +10 -0
  53. package/pipeline/multi-agent-refs/keychain.md +7 -0
  54. package/pipeline/multi-agent-refs/knowledge.md +9 -0
  55. package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +163 -0
  56. package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -2
  57. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +8 -10
  58. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +18 -20
  59. package/pipeline/multi-agent-refs/phases/phase-3-review.md +20 -14
  60. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +8 -7
  61. package/pipeline/multi-agent-refs/phases/phase-5-report.md +6 -3
  62. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  63. package/pipeline/multi-agent-refs/resume/details.md +35 -0
  64. package/pipeline/multi-agent-refs/review/inputs.md +87 -0
  65. package/pipeline/multi-agent-refs/review/module-guides.md +25 -0
  66. package/pipeline/multi-agent-refs/review/valven.md +42 -0
  67. package/pipeline/multi-agent-refs/rules.md +3 -2
  68. package/pipeline/multi-agent-refs/setup/accounts.md +27 -0
  69. package/pipeline/multi-agent-refs/setup/app-store-connect.md +5 -0
  70. package/pipeline/multi-agent-refs/setup/firebase.md +5 -0
  71. package/pipeline/multi-agent-refs/setup/google-play.md +4 -0
  72. package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +21 -0
  73. package/pipeline/multi-agent-refs/setup/language.md +46 -0
  74. package/pipeline/multi-agent-refs/setup/multi-repo-group.md +51 -0
  75. package/pipeline/multi-agent-refs/setup/summaries.md +63 -0
  76. package/pipeline/multi-agent-refs/setup/token-save-flow.md +51 -0
  77. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  78. package/pipeline/office/NOTICE.md +4 -1
  79. package/pipeline/office/assets/char_12.png +0 -0
  80. package/pipeline/office/assets/char_13.png +0 -0
  81. package/pipeline/office/assets/char_14.png +0 -0
  82. package/pipeline/office/assets/char_15.png +0 -0
  83. package/pipeline/office/assets/char_16.png +0 -0
  84. package/pipeline/office/assets/char_17.png +0 -0
  85. package/pipeline/office/assets/char_18.png +0 -0
  86. package/pipeline/office/assets/char_19.png +0 -0
  87. package/pipeline/office/assets/char_20.png +0 -0
  88. package/pipeline/office/assets/char_21.png +0 -0
  89. package/pipeline/office/assets/char_22.png +0 -0
  90. package/pipeline/office/assets/char_23.png +0 -0
  91. package/pipeline/office/assets/char_24.png +0 -0
  92. package/pipeline/office/assets/char_25.png +0 -0
  93. package/pipeline/office/assets/char_26.png +0 -0
  94. package/pipeline/office/assets/char_27.png +0 -0
  95. package/pipeline/office/assets/char_28.png +0 -0
  96. package/pipeline/office/assets/char_29.png +0 -0
  97. package/pipeline/office/assets/char_30.png +0 -0
  98. package/pipeline/office/index.html +304 -55
  99. package/pipeline/office/toolkit.json +1 -1
  100. package/pipeline/rules/response-shape.md +6 -7
  101. package/pipeline/schemas/agent-state.schema.json +36 -0
  102. package/pipeline/schemas/phases.json +4 -4
  103. package/pipeline/schemas/plan-critique.schema.json +9 -2
  104. package/pipeline/schemas/planning-output.schema.json +43 -2
  105. package/pipeline/schemas/pr-reach.schema.json +55 -0
  106. package/pipeline/schemas/prefs.schema.json +37 -8
  107. package/pipeline/schemas/reviewer-output.schema.json +20 -2
  108. package/pipeline/schemas/token-budget.json +4 -4
  109. package/pipeline/schemas/triage-output.schema.json +32 -3
  110. package/pipeline/schemas/unattended-policy.json +2 -1
  111. package/pipeline/scripts/_graph-analysis.mjs +270 -0
  112. package/pipeline/scripts/_guard_rules.py +449 -1
  113. package/pipeline/scripts/_memory-policy.mjs +165 -0
  114. package/pipeline/scripts/_secret-scan.mjs +105 -0
  115. package/pipeline/scripts/_test-gaming.mjs +36 -0
  116. package/pipeline/scripts/_unified-diff.mjs +2 -2
  117. package/pipeline/scripts/agent-guard.py +79 -19
  118. package/pipeline/scripts/anonymize-findings.mjs +99 -0
  119. package/pipeline/scripts/answer-check.mjs +113 -0
  120. package/pipeline/scripts/answer-check.sh +20 -0
  121. package/pipeline/scripts/autopilot-publish.mjs +9 -2
  122. package/pipeline/scripts/capture-flush.sh +12 -2
  123. package/pipeline/scripts/check-read-size.sh +9 -0
  124. package/pipeline/scripts/code-graph-rules/android.json +8 -3
  125. package/pipeline/scripts/contract-server.mjs +71 -16
  126. package/pipeline/scripts/debt-ledger.mjs +323 -0
  127. package/pipeline/scripts/file-context.mjs +261 -0
  128. package/pipeline/scripts/file-context.sh +20 -0
  129. package/pipeline/scripts/gate-ledger.mjs +144 -21
  130. package/pipeline/scripts/graph-links.mjs +230 -0
  131. package/pipeline/scripts/graph-query.mjs +133 -1
  132. package/pipeline/scripts/graph-report.mjs +65 -0
  133. package/pipeline/scripts/learn-from-transcripts.mjs +12 -48
  134. package/pipeline/scripts/learnings-ledger.mjs +143 -24
  135. package/pipeline/scripts/office-build.mjs +276 -5
  136. package/pipeline/scripts/office-control.mjs +70 -10
  137. package/pipeline/scripts/office-pair.mjs +407 -0
  138. package/pipeline/scripts/office-remote-client.mjs +463 -0
  139. package/pipeline/scripts/phase-tracker.sh +8 -0
  140. package/pipeline/scripts/phase0-exit-gate.mjs +40 -37
  141. package/pipeline/scripts/phone-devices.mjs +9 -1
  142. package/pipeline/scripts/plan-critique-gate.mjs +11 -2
  143. package/pipeline/scripts/pre-commit-check.sh +28 -2
  144. package/pipeline/scripts/pre-push-check.sh +6 -12
  145. package/pipeline/scripts/rework-base.mjs +87 -0
  146. package/pipeline/scripts/rework-rulings.mjs +106 -0
  147. package/pipeline/scripts/run-commit-check.mjs +104 -0
  148. package/pipeline/scripts/run-scope-guard.mjs +58 -3
  149. package/pipeline/scripts/run-scope-guard.sh +13 -1
  150. package/pipeline/scripts/scope-check-gate.mjs +213 -2
  151. package/pipeline/scripts/task-mirror.mjs +106 -15
  152. package/pipeline/scripts/task-mirror.sh +3 -2
  153. package/pipeline/scripts/test-stability-gate.mjs +112 -0
  154. package/pipeline/scripts/triage-memory.mjs +64 -37
  155. package/pipeline/scripts/update-check.sh +7 -1
  156. package/pipeline/scripts/update-issue-progress.sh +73 -8
  157. package/pipeline/scripts/validate-planning.mjs +172 -4
  158. package/pipeline/scripts/validate-reviewer.mjs +20 -0
  159. package/pipeline/scripts/validate-triage.mjs +149 -4
  160. package/pipeline/scripts/work-items.mjs +119 -1
  161. package/pipeline/scripts/worktree-prepare-all.mjs +200 -0
  162. package/pipeline/scripts/worktree-prepare.sh +40 -4
  163. package/pipeline/scripts/write-state.mjs +67 -24
  164. package/pipeline/skills/.skill-manifest.json +9 -9
  165. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +5 -5
  166. package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +1 -1
  167. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
  168. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +5 -2
  169. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +4 -4
  170. package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +2 -2
  171. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +2 -0
  172. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +4 -4
  173. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +14 -0
  174. package/pipeline/skills/shared/external/android-architecture/references/patterns.md +6 -0
  175. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +10 -0
  176. package/pipeline/skills/shared/external/android-datastore/references/patterns.md +11 -0
  177. package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +8 -0
  178. package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +6 -0
  179. package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +10 -0
  180. package/pipeline/skills/shared/external/android-performance/references/patterns.md +13 -0
  181. package/pipeline/skills/shared/external/android-security/references/patterns.md +16 -0
  182. package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +9 -0
  183. package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +12 -0
  184. package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +13 -0
  185. package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +12 -0
  186. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +9 -0
  187. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +8 -0
  188. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +10 -0
  189. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +21 -0
  190. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +9 -0
  191. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +7 -0
  192. package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -2
  193. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +10 -0
  194. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +10 -1
  195. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +22 -0
  196. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +17 -0
  197. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +22 -0
  198. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +17 -0
  199. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +9 -0
  200. package/pipeline/skills/shared/external/authentication/references/passkeys.md +11 -0
  201. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +13 -0
  202. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +9 -0
  203. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +10 -0
  204. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +15 -0
  205. package/pipeline/skills/shared/external/compose-components/references/patterns.md +7 -0
  206. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +7 -0
  207. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +15 -0
  208. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +9 -0
  209. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +11 -0
  210. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +10 -0
  211. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +13 -0
  212. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +20 -0
  213. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +16 -0
  214. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +16 -0
  215. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +14 -0
  216. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +13 -0
  217. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +10 -0
  218. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +10 -0
  219. package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +10 -0
  220. package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +17 -0
  221. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +12 -0
  222. package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +10 -0
  223. package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +10 -0
  224. package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +11 -0
  225. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +13 -0
  226. package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +13 -0
  227. package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +11 -0
  228. package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +12 -0
  229. package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +16 -0
  230. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +9 -0
  231. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +18 -0
  232. package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +9 -0
  233. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +9 -0
  234. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +7 -0
  235. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +13 -0
  236. package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +9 -0
  237. package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +15 -0
  238. package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +11 -0
  239. package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +11 -0
  240. package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +15 -0
  241. package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +11 -0
  242. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +18 -0
  243. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +19 -0
  244. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +15 -0
  245. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +7 -0
  246. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +8 -0
  247. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +12 -0
  248. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +17 -0
  249. package/pipeline/skills/shared/external/ios-security/references/app-review-guidelines.md +8 -0
  250. package/pipeline/skills/shared/external/ios-security/references/cryptokit-advanced.md +14 -0
  251. package/pipeline/skills/shared/external/ios-security/references/file-storage-patterns.md +8 -0
  252. package/pipeline/skills/shared/external/ios-security/references/privacy-manifest.md +9 -0
  253. package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +9 -0
  254. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +13 -0
  255. package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +8 -0
  256. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +21 -0
  257. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +9 -0
  258. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +12 -0
  259. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +16 -0
  260. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +21 -0
  261. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +10 -0
  262. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +9 -0
  263. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +12 -0
  264. package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +11 -0
  265. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +11 -0
  266. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +18 -0
  267. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +11 -0
  268. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +11 -0
  269. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +15 -0
  270. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +14 -0
  271. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +14 -0
  272. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +14 -0
  273. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +14 -0
  274. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +10 -0
  275. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +12 -0
  276. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +14 -0
  277. package/pipeline/skills/shared/external/room-database/references/patterns.md +16 -0
  278. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +10 -0
  279. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +10 -0
  280. package/pipeline/skills/shared/external/spm-build-analysis/references/build-optimization-sources.md +14 -0
  281. package/pipeline/skills/shared/external/spm-build-analysis/references/spm-analysis-checks.md +19 -0
  282. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +13 -0
  283. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +31 -0
  284. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +8 -0
  285. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +9 -0
  286. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +10 -0
  287. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +8 -0
  288. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +24 -0
  289. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +8 -0
  290. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +14 -0
  291. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +12 -0
  292. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +10 -0
  293. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +8 -0
  294. package/pipeline/skills/shared/external/swift-concurrency-pro/references/actors.md +10 -0
  295. package/pipeline/skills/shared/external/swift-concurrency-pro/references/cancellation.md +7 -0
  296. package/pipeline/skills/shared/external/swift-concurrency-pro/references/interop.md +10 -0
  297. package/pipeline/skills/shared/external/swift-concurrency-pro/references/new-features.md +11 -0
  298. package/pipeline/skills/shared/external/swift-concurrency-pro/references/structured.md +9 -0
  299. package/pipeline/skills/shared/external/swift-concurrency-pro/references/testing.md +13 -0
  300. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +7 -0
  301. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +14 -0
  302. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +16 -0
  303. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +17 -0
  304. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +19 -0
  305. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +16 -0
  306. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +19 -0
  307. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +18 -0
  308. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +16 -0
  309. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +17 -0
  310. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +17 -0
  311. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +17 -0
  312. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +18 -0
  313. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +21 -0
  314. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +16 -0
  315. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +16 -0
  316. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +9 -0
  317. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +24 -0
  318. package/pipeline/skills/shared/external/swift-testing-pro/references/async-tests.md +9 -0
  319. package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +11 -0
  320. package/pipeline/skills/shared/external/swift-testing-pro/references/writing-better-tests.md +13 -0
  321. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +8 -0
  322. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +17 -0
  323. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +12 -0
  324. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +11 -0
  325. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +7 -0
  326. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +13 -0
  327. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +24 -0
  328. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +12 -0
  329. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +15 -0
  330. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +15 -0
  331. package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +12 -0
  332. package/pipeline/skills/shared/external/swiftui-expert-skill/references/accessibility-patterns.md +11 -0
  333. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +7 -0
  334. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +13 -0
  335. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +9 -0
  336. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts-accessibility.md +9 -0
  337. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts.md +17 -0
  338. package/pipeline/skills/shared/external/swiftui-expert-skill/references/image-optimization.md +9 -0
  339. package/pipeline/skills/shared/external/swiftui-expert-skill/references/latest-apis.md +10 -0
  340. package/pipeline/skills/shared/external/swiftui-expert-skill/references/layout-best-practices.md +12 -0
  341. package/pipeline/skills/shared/external/swiftui-expert-skill/references/list-patterns.md +12 -0
  342. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-scenes.md +13 -0
  343. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-views.md +12 -0
  344. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-window-styling.md +12 -0
  345. package/pipeline/skills/shared/external/swiftui-expert-skill/references/performance-patterns.md +7 -0
  346. package/pipeline/skills/shared/external/swiftui-expert-skill/references/scroll-patterns.md +9 -0
  347. package/pipeline/skills/shared/external/swiftui-expert-skill/references/sheet-navigation-patterns.md +10 -0
  348. package/pipeline/skills/shared/external/swiftui-expert-skill/references/state-management.md +18 -0
  349. package/pipeline/skills/shared/external/swiftui-expert-skill/references/view-structure.md +16 -0
  350. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +11 -0
  351. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +8 -0
  352. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +14 -0
  353. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +9 -0
  354. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +22 -0
  355. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +14 -0
  356. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +10 -0
  357. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +10 -0
  358. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +13 -0
  359. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +10 -0
  360. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +14 -0
  361. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +30 -0
  362. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +16 -0
  363. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +11 -0
  364. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +11 -0
  365. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +10 -0
  366. package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +8 -0
  367. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +12 -0
  368. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +10 -0
  369. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +11 -0
  370. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +12 -0
  371. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +14 -0
  372. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +10 -0
  373. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +10 -0
  374. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +13 -0
  375. package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +12 -0
  376. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +8 -0
  377. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +8 -0
  378. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +14 -0
  379. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +17 -0
  380. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +11 -0
  381. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +11 -0
  382. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +27 -0
  383. package/pipeline/skills/shared/external/xcode-build-fixer/references/build-settings-best-practices.md +9 -0
  384. package/pipeline/skills/shared/external/xcode-build-fixer/references/fix-patterns.md +7 -0
  385. package/pipeline/skills/shared/external/xcode-build-orchestrator/references/build-settings-best-practices.md +9 -0
  386. package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/build-optimization-sources.md +14 -0
  387. package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/code-compilation-checks.md +8 -0
  388. package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-optimization-sources.md +14 -0
  389. package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-settings-best-practices.md +9 -0
  390. package/pipeline/skills/shared/external/xcode-project-analyzer/references/project-audit-checks.md +13 -0
@@ -11,6 +11,17 @@
11
11
  // Usage:
12
12
  // node pipeline/scripts/validate-triage.mjs path/to/triage.json
13
13
  // cat triage.json | node pipeline/scripts/validate-triage.mjs -
14
+ // [--reviewers a.json,b.json] # every reviewer cannotVerify row needs a triage row
15
+ // [--reviewers-dir <dir>] # same, for every reviewer-*.json in <dir>
16
+ // [--anonymized] # the verdict was judged on anonymized input:
17
+ // # no finding may carry `reviewer`, labels only
18
+ //
19
+ // An accepted finding names its source as `reviewer` (a model family),
20
+ // `foundBy` (an anonymized "Source A" label, or "security-auditor") or `tag`
21
+ // (a deterministic gate finding). Triage judges anonymized findings and
22
+ // carries `foundBy` through; anonymize-findings.mjs --restore writes the
23
+ // family back afterwards, so a `reviewer` in the verdict itself means the
24
+ // judge was told whose findings it graded.
14
25
  //
15
26
  // Exit codes:
16
27
  // 0 - valid, safe to act on
@@ -21,9 +32,12 @@
21
32
  // Output: JSON result to stdout with shape:
22
33
  // {"ok": bool, "code": N, "errors": [...], "warnings": [...], "corrected": {...}|null}
23
34
 
24
- import { readFileSync } from "node:fs";
35
+ import { readdirSync, readFileSync } from "node:fs";
36
+ import { join } from "node:path";
25
37
 
26
38
  const ALLOWED_REVIEWERS = new Set(["fable", "opus", "sonnet", "gpt"]);
39
+ const FOUND_BY_RE = /^(Source [A-Z]|security-auditor)$/;
40
+ const ANONYMIZED = process.argv.includes("--anonymized");
27
41
  const ALLOWED_SEVERITIES = new Set(["blocking", "important", "suggestion"]);
28
42
  const ALLOWED_CONSENSUS_VERDICTS = new Set([
29
43
  "unanimous-pass",
@@ -35,9 +49,72 @@ const ALLOWED_VERIFICATION_RESULTS = new Set(["confirmed", "not-reproduced", "in
35
49
  const FINGERPRINT_RE = /^F:[0-9a-f]{8}$/;
36
50
  const OVER_REJECT_THRESHOLD = 0.8;
37
51
  const OVER_REJECT_MIN_FINDINGS = 5;
52
+ const CANNOT_VERIFY_RESOLUTIONS = new Set(["verified", "finding", "out-of-scope"]);
53
+ // A path (any run without spaces, quotes or parentheses that holds a letter),
54
+ // then :line. A time of day has no letter, so it is not one.
55
+ const FILE_LINE = /(?:^|[\s(`'"[])([^\s:`'"()]*\p{L}[^\s:`'"()]*):(\d+)\b/gu;
56
+ const fileLines = (text) => [...String(text).matchAll(FILE_LINE)].map((m) => `${m[1]}:${m[2]}`);
57
+ const PATH_EDGE = /[^\s(`'",;]/u;
58
+ /** True when `text` names `at` (file:line) as a whole token, not inside a longer path. */
59
+ function names(text, at) {
60
+ const t = String(text);
61
+ for (let i = t.indexOf(at); i !== -1; i = t.indexOf(at, i + 1)) {
62
+ const before = i === 0 ? "" : t[i - 1];
63
+ const after = t[i + at.length] ?? "";
64
+ if ((!before || !PATH_EDGE.test(before)) && !/\d/.test(after)) return true;
65
+ }
66
+ return false;
67
+ }
68
+ const normRequirement = (s) => String(s).toLowerCase().replace(/\s+/g, " ").trim();
69
+
70
+ /** Reviewer files named by --reviewers, or found by --reviewers-dir; null when neither is given. */
71
+ function reviewerFiles() {
72
+ const listAt = process.argv.indexOf("--reviewers");
73
+ const dirAt = process.argv.indexOf("--reviewers-dir");
74
+ if (listAt === -1 && dirAt === -1) return null;
75
+ const files = [];
76
+ if (listAt !== -1) {
77
+ const value = process.argv[listAt + 1];
78
+ if (!value || value.startsWith("--"))
79
+ throw new Error("--reviewers needs a comma-separated list");
80
+ files.push(...value.split(",").filter(Boolean));
81
+ }
82
+ if (dirAt !== -1) {
83
+ const dir = process.argv[dirAt + 1];
84
+ if (!dir || dir.startsWith("--")) throw new Error("--reviewers-dir needs a folder");
85
+ for (const name of readdirSync(dir).sort()) {
86
+ if (/^reviewer-.*\.json$/.test(name)) files.push(join(dir, name));
87
+ }
88
+ }
89
+ return files;
90
+ }
91
+
92
+ /** Requirements the reviewers could not verify from the diff, from their files. */
93
+ function reviewerRequirements() {
94
+ const files = reviewerFiles();
95
+ if (files === null) return null;
96
+ const out = new Set();
97
+ for (const p of files) {
98
+ const doc = JSON.parse(readFileSync(p, "utf-8"));
99
+ for (const row of doc?.cannotVerify ?? []) {
100
+ if (typeof row?.requirement === "string") out.add(normRequirement(row.requirement));
101
+ }
102
+ }
103
+ return out;
104
+ }
105
+
106
+ /** The input path: the first argument that is neither a flag nor a flag's value. */
107
+ function inputArg() {
108
+ const args = process.argv.slice(2);
109
+ for (let i = 0; i < args.length; i++) {
110
+ if (args[i] === "--reviewers" || args[i] === "--reviewers-dir") i++;
111
+ else if (!args[i].startsWith("--")) return args[i];
112
+ }
113
+ return undefined;
114
+ }
38
115
 
39
116
  function readInput() {
40
- const arg = process.argv[2];
117
+ const arg = inputArg();
41
118
  if (!arg) {
42
119
  console.error("error: provide a path or '-' for STDIN");
43
120
  process.exit(64); // EX_USAGE
@@ -108,11 +185,33 @@ function validateVerification(v, label, errors) {
108
185
  }
109
186
  }
110
187
 
188
+ /** The finding's source kind, or null when none is valid. */
189
+ function sourceOf(f) {
190
+ if (f?.foundBy !== undefined) return FOUND_BY_RE.test(String(f.foundBy)) ? "label" : null;
191
+ if (typeof f?.tag === "string" && f.tag) return "tag";
192
+ return ALLOWED_REVIEWERS.has(f?.reviewer) ? "reviewer" : null;
193
+ }
194
+
195
+ /** With --anonymized: every finding that names a reviewer, by list and index. */
196
+ function identityLeaks(parsed) {
197
+ const out = [];
198
+ (parsed.accepted || []).forEach((f, i) => {
199
+ if (f && Object.hasOwn(f, "reviewer")) out.push(`accepted[${i}]`);
200
+ });
201
+ for (const key of ["deferred", "rejected"]) {
202
+ (parsed[key] || []).forEach((e, i) => {
203
+ if (e?.finding && Object.hasOwn(e.finding, "reviewer")) out.push(`${key}[${i}].finding`);
204
+ });
205
+ }
206
+ return out;
207
+ }
208
+
111
209
  function validateAccepted(f, i, errors) {
112
210
  validateRawFinding(f, `accepted[${i}]`, errors);
113
- if (!ALLOWED_REVIEWERS.has(f.reviewer)) {
211
+ const source = sourceOf(f);
212
+ if (source === null) {
114
213
  errors.push(
115
- `accepted[${i}]: reviewer must be one of fable|opus|sonnet|gpt (got ${JSON.stringify(f.reviewer)}; haiku was removed in v2.1.0)`,
214
+ `accepted[${i}]: name the source as foundBy ("Source A".."Source Z" or "security-auditor"), tag, or reviewer fable|opus|sonnet|gpt (got reviewer ${JSON.stringify(f?.reviewer)}, foundBy ${JSON.stringify(f?.foundBy)})`,
116
215
  );
117
216
  }
118
217
  if (typeof f.fix !== "string" || f.fix.length < 4) {
@@ -213,6 +312,52 @@ function validate(raw) {
213
312
  parsed.accepted.forEach((f, i) => validateAccepted(f, i, errors));
214
313
  parsed.deferred.forEach((f, i) => validateClassified(f, "deferred", i, errors));
215
314
  parsed.rejected.forEach((f, i) => validateClassified(f, "rejected", i, errors));
315
+ if (ANONYMIZED) {
316
+ for (const at of identityLeaks(parsed)) {
317
+ errors.push(
318
+ `${at}: carries reviewer in an anonymized verdict; carry foundBy through, the family is restored after validation`,
319
+ );
320
+ }
321
+ }
322
+
323
+ // Every requirement a reviewer could not verify from the diff is settled here:
324
+ // checked in the unchanged code (verified), turned into a finding, or ruled
325
+ // out of scope, each with its evidence.
326
+ if (parsed.cannotVerify !== undefined && !Array.isArray(parsed.cannotVerify)) {
327
+ errors.push("cannotVerify must be an array when present");
328
+ }
329
+ const settled = new Set();
330
+ const list = (x) => (Array.isArray(x) ? x : []);
331
+ const kept = [...list(parsed.accepted), ...list(parsed.deferred).map((d) => d?.finding)]
332
+ .filter((f) => typeof f?.file === "string" && Number.isInteger(f?.line))
333
+ .map((f) => `${f.file}:${f.line}`);
334
+ (Array.isArray(parsed.cannotVerify) ? parsed.cannotVerify : []).forEach((row, i) => {
335
+ const label = `cannotVerify[${i}]`;
336
+ if (typeof row?.requirement !== "string" || row.requirement.trim().length < 4) {
337
+ errors.push(`${label}: requirement must be a string >= 4 chars`);
338
+ return;
339
+ }
340
+ if (!CANNOT_VERIFY_RESOLUTIONS.has(row.resolution)) {
341
+ errors.push(`${label}: resolution must be verified|finding|out-of-scope`);
342
+ }
343
+ if (typeof row.evidence !== "string" || row.evidence.trim().length < 4) {
344
+ errors.push(`${label}: evidence must say how it was settled`);
345
+ } else if (row.resolution === "verified" && !fileLines(row.evidence).length) {
346
+ errors.push(`${label}: a verified requirement cites the file:line it read`);
347
+ } else if (row.resolution === "finding" && !kept.some((at) => names(row.evidence, at))) {
348
+ errors.push(`${label}: evidence names no accepted or deferred finding (cite its file:line)`);
349
+ }
350
+ settled.add(normRequirement(row.requirement));
351
+ });
352
+ let required;
353
+ try {
354
+ required = reviewerRequirements();
355
+ } catch (err) {
356
+ errors.push(`--reviewers unreadable: ${err.message}`);
357
+ }
358
+ for (const req of required ?? []) {
359
+ if (!settled.has(req)) errors.push(`cannotVerify: reviewer requirement not settled: "${req}"`);
360
+ }
216
361
 
217
362
  // Optional consensus block (v3.1.0) + cross-check against the actual verdict
218
363
  // so the block is not merely decorative.
@@ -7,6 +7,13 @@
7
7
  * /multi-agent:jira, /multi-agent:issue and Phase 0 run;
8
8
  * title, type, status, score, blockers, warnings, summary
9
9
  * and a description excerpt, scrubbed
10
+ * reach how far each open GitHub pull request reaches: its
11
+ * changed files (`gh pr view --json files`) looked up in
12
+ * the repository's own code graph, and the files that
13
+ * depend on them counted (graph-affected, depth 2). A
14
+ * repository without a graph, a Bitbucket pull request or
15
+ * a name that is not owner/repo is listed with its reason
16
+ * and costs no gh call
10
17
  * prs open pull requests on GitHub where you are asked to
11
18
  * review or are the author (`gh search prs`, the queries
12
19
  * /multi-agent:review lists). Bitbucket is reported as a
@@ -34,6 +41,7 @@
34
41
  * Usage:
35
42
  * work-items.mjs maturity <key>
36
43
  * work-items.mjs prs
44
+ * work-items.mjs reach
37
45
  * work-items.mjs verdict <task-id> [--post]
38
46
  *
39
47
  * MA_GH_BIN stands in for gh, MA_ISSUE_FETCHER and MA_POST_PR_REVIEW for the
@@ -57,6 +65,8 @@ import { runMain } from "../lib/fatal.mjs";
57
65
  import { invokedDirectly } from "../lib/invoked-directly.mjs";
58
66
  import { resolveTokenAsync } from "../lib/credential-resolve.mjs";
59
67
  import { homedir } from "node:os";
68
+ import { affected } from "./graph-affected.mjs";
69
+ import { loadGraph } from "./graph-query.mjs";
60
70
 
61
71
  export const CONTRACT_VERSION = "1.1.0";
62
72
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -349,6 +359,113 @@ export async function pullRequests({ env = process.env, redact = false } = {}) {
349
359
  return { contractVersion: CONTRACT_VERSION, items, sources };
350
360
  }
351
361
 
362
+ const REACH_MAX = 20;
363
+ const REACH_PARALLEL = 3;
364
+ const REACH_DEPTH = 2;
365
+ const FILES_MAX = 300;
366
+ const NAME_PART = /^(?!\.{1,2}$)[A-Za-z0-9._-]{1,100}$/;
367
+
368
+ /** owner/repo with two plain parts; anything else never reaches gh. */
369
+ function plainRepo(repo) {
370
+ const parts = String(repo).split("/");
371
+ return parts.length === 2 && parts.every((x) => NAME_PART.test(x));
372
+ }
373
+
374
+ async function inBatches(items, size, fn) {
375
+ const out = [];
376
+ for (let i = 0; i < items.length; i += size) {
377
+ out.push(...(await Promise.all(items.slice(i, i + size).map(fn))));
378
+ }
379
+ return out;
380
+ }
381
+
382
+ /** The changed file paths of one GitHub pull request, or null when gh could not say. */
383
+ async function changedFiles(env, repo, number) {
384
+ const gh = env.MA_GH_BIN || "gh";
385
+ const r = await spawnAsync(
386
+ gh,
387
+ ["pr", "view", String(number), "--repo", repo, "--json", "files"],
388
+ {
389
+ env,
390
+ timeoutMs: TIMEOUT_MS,
391
+ },
392
+ );
393
+ if (r.error || r.status !== 0) return null;
394
+ try {
395
+ const files = JSON.parse(r.stdout)?.files;
396
+ return (Array.isArray(files) ? files : [])
397
+ .map((f) => f?.path)
398
+ .filter((path) => typeof path === "string" && path.length <= 1024)
399
+ .slice(0, FILES_MAX);
400
+ } catch {
401
+ return null;
402
+ }
403
+ }
404
+
405
+ /**
406
+ * How far each open pull request reaches, widest first. Changed paths are
407
+ * used only as keys into the local graph: nothing is read from disk by them
408
+ * and none is echoed back, only counts.
409
+ */
410
+ export async function prReach({ env = process.env, list = null } = {}) {
411
+ const prs = list ?? (await pullRequests({ env }));
412
+ const envByLogin = new Map();
413
+ for (const a of await ghAccounts(env)) {
414
+ const aEnv = await accountEnv(env, a);
415
+ if (aEnv) envByLogin.set(a.login, aEnv);
416
+ }
417
+ const knowledge = join(env.HOME || homedir(), ".claude", "knowledge");
418
+ const graphs = new Map();
419
+ const graphFor = (project) => {
420
+ if (!graphs.has(project)) {
421
+ const path = join(knowledge, project, "code-graph.json");
422
+ let graph;
423
+ try {
424
+ graph = existsSync(path) ? loadGraph(path) : null;
425
+ } catch {
426
+ graph = null;
427
+ }
428
+ graphs.set(project, graph);
429
+ }
430
+ return graphs.get(project);
431
+ };
432
+ const candidates = prs.items
433
+ .filter((p) => p.source !== "github" || plainRepo(p.repo))
434
+ .slice(0, REACH_MAX);
435
+ const rows = await inBatches(candidates, REACH_PARALLEL, async (p) => {
436
+ const row = { source: p.source, repo: p.repo, number: p.number, project: null };
437
+ if (p.source !== "github") return { ...row, reach: null, reason: "not measured off GitHub" };
438
+ const project = p.repo.split("/")[1];
439
+ const graph = graphFor(project);
440
+ if (!graph) return { ...row, reach: null, reason: `no code graph for ${project}` };
441
+ const files = await changedFiles(envByLogin.get(p.account) ?? env, p.repo, p.number);
442
+ if (!files) return { ...row, project, reach: null, reason: "gh could not list its files" };
443
+ const ids = new Set(graph.nodes.filter((n) => n.kind === "file").map((n) => n.id));
444
+ const seeds = files.map((f) => `file:${f}`).filter((id) => ids.has(id));
445
+ const byId = new Map(graph.nodes.map((n) => [n.id, n]));
446
+ const dependents = seeds.length
447
+ ? affected({ graph, seeds: seeds.map((id) => byId.get(id)), depth: REACH_DEPTH })
448
+ : [];
449
+ const reachedFiles = new Set(
450
+ dependents.filter((d) => d.node?.kind === "file").map((d) => d.id),
451
+ );
452
+ return {
453
+ ...row,
454
+ project,
455
+ changed: files.length,
456
+ inGraph: seeds.length,
457
+ reach: reachedFiles.size,
458
+ };
459
+ });
460
+ const updated = new Map(prs.items.map((p) => [`${p.repo}#${p.number}`, p.updatedAt ?? ""]));
461
+ rows.sort(
462
+ (a, b) =>
463
+ (b.reach ?? -1) - (a.reach ?? -1) ||
464
+ (updated.get(`${a.repo}#${a.number}`) < updated.get(`${b.repo}#${b.number}`) ? 1 : -1),
465
+ );
466
+ return { contractVersion: CONTRACT_VERSION, depth: REACH_DEPTH, items: rows };
467
+ }
468
+
352
469
  function stateOf(taskId) {
353
470
  const { found, records } = selectRuns({ taskId });
354
471
  if (!found || !records.length) throw new WorkError("no-run", "no such run");
@@ -449,11 +566,12 @@ async function main(argv) {
449
566
  let out;
450
567
  if (cmd === "maturity" && arg) out = await issueMaturity(arg);
451
568
  else if (cmd === "prs" && !arg) out = await pullRequests();
569
+ else if (cmd === "reach" && !arg) out = await prReach();
452
570
  else if (cmd === "verdict" && arg && (flag === undefined || flag === "--post")) {
453
571
  out = flag ? await postReviewVerdict(arg) : reviewVerdictPreview(arg);
454
572
  } else {
455
573
  process.stderr.write(
456
- "usage: work-items.mjs maturity <key> | prs | verdict <task-id> [--post]\n",
574
+ "usage: work-items.mjs maturity <key> | prs | reach | verdict <task-id> [--post]\n",
457
575
  );
458
576
  return 2;
459
577
  }
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @file worktree-prepare-all.mjs - the multi-repo worktree setup as one
4
+ * step: every repo gets its worktree, or none keeps one this step created.
5
+ *
6
+ * Phase 0 creates one worktree per repo of a multi-repo task. When the third
7
+ * repo's add fails, the first two must not stay behind on a half-made task.
8
+ * This runs worktree-prepare.sh serially in the given order and, on the first
9
+ * failure, rolls back what this call created, newest first:
10
+ *
11
+ * - the worktree is removed without --force, so a worktree holding changes
12
+ * is kept and reported instead of discarded;
13
+ * - the branch is deleted only when worktree-prepare created it here
14
+ * (newBranch) and it still points at origin/<base>, so a branch that
15
+ * existed before, or gained a commit, is never deleted.
16
+ *
17
+ * A worktree that was reused (it existed before the call) is never touched.
18
+ *
19
+ * Usage:
20
+ * worktree-prepare-all.mjs --repos <repos.json>
21
+ * repos.json: [{project, path, branch, base, name?, email?}, ...]
22
+ *
23
+ * Output (stdout, JSON): {ok, results[], failed, rolledBack[], kept[]}
24
+ * results each repo's worktree-prepare.sh output, in order, up to the failure
25
+ * failed the failing repo's output, or null
26
+ * rolledBack [{project, worktreePath, branchDeleted}]
27
+ * kept [{project, worktreePath, reason}] what the rollback left in place
28
+ *
29
+ * Exit: 0 every repo ready, 1 a repo failed (rollback done; see kept[]),
30
+ * 2 usage or a repos file that does not parse.
31
+ */
32
+
33
+ import { execFileSync, spawnSync } from "node:child_process";
34
+ import { readFileSync } from "node:fs";
35
+ import { dirname, join } from "node:path";
36
+ import { fileURLToPath } from "node:url";
37
+
38
+ import { invokedDirectly } from "../lib/invoked-directly.mjs";
39
+
40
+ const HERE = dirname(fileURLToPath(import.meta.url));
41
+ const PREPARE = join(HERE, "worktree-prepare.sh");
42
+
43
+ const git = (cwd, ...args) =>
44
+ execFileSync("git", ["-C", cwd, ...args], {
45
+ encoding: "utf8",
46
+ stdio: ["ignore", "pipe", "pipe"],
47
+ timeout: 60_000,
48
+ }).trim();
49
+
50
+ function prepareOne(repo, prepare) {
51
+ try {
52
+ return prepareOnce(repo, prepare);
53
+ } catch (err) {
54
+ return {
55
+ ok: false,
56
+ out: {
57
+ project: repo.project,
58
+ worktreePath: repo.path,
59
+ branch: repo.branch,
60
+ baseBranch: repo.base,
61
+ action: "failed",
62
+ error: String(err?.message || err).slice(0, 500),
63
+ },
64
+ };
65
+ }
66
+ }
67
+
68
+ function prepareOnce(repo, prepare) {
69
+ const args = [
70
+ prepare,
71
+ "--project",
72
+ repo.project,
73
+ "--path",
74
+ repo.path,
75
+ "--branch",
76
+ repo.branch,
77
+ "--base",
78
+ repo.base,
79
+ ];
80
+ if (repo.name) args.push("--name", repo.name);
81
+ if (repo.email) args.push("--email", repo.email);
82
+ const r = spawnSync("bash", args, { encoding: "utf8", timeout: 300_000 });
83
+ let out;
84
+ try {
85
+ out = JSON.parse(r.stdout);
86
+ } catch {
87
+ out = null;
88
+ }
89
+ if (r.status === 0 && out && (out.action === "created" || out.action === "reused"))
90
+ return { ok: true, out };
91
+ return {
92
+ ok: false,
93
+ out: out ?? {
94
+ project: repo.project,
95
+ worktreePath: repo.path,
96
+ branch: repo.branch,
97
+ baseBranch: repo.base,
98
+ action: "failed",
99
+ error: (r.stderr || r.error?.message || `exit ${r.status}`).trim().slice(0, 500),
100
+ },
101
+ };
102
+ }
103
+
104
+ /** Undo one worktree this call created. Returns a rolledBack or kept record. */
105
+ export function rollbackOne(res) {
106
+ const where = { project: res.project, worktreePath: res.worktreePath };
107
+ try {
108
+ git(res.project, "worktree", "remove", res.worktreePath);
109
+ } catch (err) {
110
+ return {
111
+ kept: {
112
+ ...where,
113
+ reason: `worktree not removed: ${String(err.stderr || err.message)
114
+ .trim()
115
+ .slice(0, 200)}`,
116
+ },
117
+ };
118
+ }
119
+ let branchDeleted = false;
120
+ if (res.newBranch === true) {
121
+ try {
122
+ const tip = git(res.project, "rev-parse", "--verify", `refs/heads/${res.branch}`);
123
+ const base = git(
124
+ res.project,
125
+ "rev-parse",
126
+ "--verify",
127
+ `refs/remotes/origin/${res.baseBranch}`,
128
+ );
129
+ if (tip === base) {
130
+ git(res.project, "branch", "-D", "--", res.branch);
131
+ branchDeleted = true;
132
+ }
133
+ } catch {
134
+ branchDeleted = false;
135
+ }
136
+ }
137
+ return { rolledBack: { ...where, branchDeleted } };
138
+ }
139
+
140
+ export function prepareAll(repos, { prepare = PREPARE } = {}) {
141
+ const results = [];
142
+ for (const repo of repos) {
143
+ const r = prepareOne(repo, prepare);
144
+ if (r.ok) {
145
+ results.push(r.out);
146
+ continue;
147
+ }
148
+ const rolledBack = [];
149
+ const kept = [];
150
+ for (const done of [...results].reverse()) {
151
+ if (done.action !== "created") continue;
152
+ const u = rollbackOne(done);
153
+ if (u.rolledBack) rolledBack.push(u.rolledBack);
154
+ else kept.push(u.kept);
155
+ }
156
+ return { ok: false, results, failed: r.out, rolledBack, kept };
157
+ }
158
+ return { ok: true, results, failed: null, rolledBack: [], kept: [] };
159
+ }
160
+
161
+ const FIELD = /^[^\0\n]+$/;
162
+
163
+ export function readRepos(text) {
164
+ const list = JSON.parse(text);
165
+ if (!Array.isArray(list) || !list.length) throw new Error("repos must be a non-empty array");
166
+ for (const r of list) {
167
+ for (const k of ["project", "path", "branch", "base"]) {
168
+ if (typeof r?.[k] !== "string" || !FIELD.test(r[k]))
169
+ throw new Error(`each repo needs a string ${k}`);
170
+ }
171
+ for (const k of ["branch", "base"])
172
+ if (r[k].startsWith("-")) throw new Error(`${k} must not start with -`);
173
+ for (const k of ["name", "email"]) {
174
+ if (r[k] !== undefined && (typeof r[k] !== "string" || !FIELD.test(r[k]))) {
175
+ throw new Error(`${k} must be a string when given`);
176
+ }
177
+ }
178
+ }
179
+ return list;
180
+ }
181
+
182
+ function main() {
183
+ const i = process.argv.indexOf("--repos");
184
+ if (i < 0 || !process.argv[i + 1] || process.argv.length !== 4) {
185
+ console.error("usage: worktree-prepare-all.mjs --repos <repos.json>");
186
+ return 2;
187
+ }
188
+ let repos;
189
+ try {
190
+ repos = readRepos(readFileSync(process.argv[i + 1], "utf8"));
191
+ } catch (err) {
192
+ console.error(`worktree-prepare-all: ${err.message}`);
193
+ return 2;
194
+ }
195
+ const out = prepareAll(repos);
196
+ process.stdout.write(JSON.stringify(out, null, 2) + "\n");
197
+ return out.ok ? 0 : 1;
198
+ }
199
+
200
+ if (invokedDirectly(import.meta.url)) process.exit(main());
@@ -27,10 +27,13 @@
27
27
  # [--email <git user.email>]
28
28
  #
29
29
  # Output (stdout, JSON): {project, worktreePath, branch, baseBranch,
30
- # action: created|reused|failed, pulled: true|false|null, error}
30
+ # action: created|reused|failed, pulled: true|false|null, newBranch, error}
31
+ # newBranch is true only when this call created the branch from origin/<base>,
32
+ # which is what tells worktree-prepare-all.mjs a rollback may delete it.
31
33
  #
32
34
  # Exit: 0 ready, 1 the add failed (nothing was created; the caller runs its
33
- # rollback / collision flow), 2 usage.
35
+ # rollback / collision flow), 2 usage, a path outside <project>/.worktrees/,
36
+ # or a new branch outside the naming convention.
34
37
  set -uo pipefail
35
38
 
36
39
  HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -59,11 +62,43 @@ git -C "$PROJECT" rev-parse --git-dir >/dev/null 2>&1 || {
59
62
  exit 2
60
63
  }
61
64
 
65
+ # The worktree lives in the project's own .worktrees/ directory, compared on
66
+ # physical paths so a symlink cannot place it elsewhere.
67
+ physical() { # nearest existing ancestor resolved, the rest appended
68
+ local p="$1" tail=""
69
+ while [ ! -e "$p" ] && [ "$p" != "/" ] && [ "$p" != "." ]; do
70
+ tail="/$(basename "$p")$tail"
71
+ p="$(dirname "$p")"
72
+ done
73
+ printf '%s%s' "$(cd "$p" 2>/dev/null && pwd -P)" "$tail"
74
+ }
75
+ WT_ROOT="$(physical "$PROJECT")/.worktrees/"
76
+ case "$(physical "$WT")/" in
77
+ "$WT_ROOT"?*/) ;;
78
+ *)
79
+ echo "worktree-prepare: --path must be inside $PROJECT/.worktrees/ (got $WT)" >&2
80
+ exit 2
81
+ ;;
82
+ esac
83
+ # A branch this call creates follows the naming convention; an existing one
84
+ # is re-entered as it is.
85
+ if ! git -C "$PROJECT" show-ref --verify --quiet "refs/heads/$BRANCH" &&
86
+ ! git -C "$PROJECT" show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
87
+ case "$BRANCH" in
88
+ feature/?* | bugfix/?* | hotfix/?* | release/?*) ;;
89
+ *)
90
+ echo "worktree-prepare: a new branch is named feature/<key>-<desc>, bugfix/<key>-<desc>, hotfix/<desc> or release/<version> (got $BRANCH)" >&2
91
+ exit 2
92
+ ;;
93
+ esac
94
+ fi
95
+ NEW_BRANCH=false
62
96
  report() { # $1 action, $2 pulled (true|false|null), $3 error
63
97
  jq -n --arg project "$PROJECT" --arg wt "$WT" --arg branch "$BRANCH" --arg base "$BASE" \
64
- --arg action "$1" --argjson pulled "$2" --arg error "$3" \
98
+ --arg action "$1" --argjson pulled "$2" --argjson newBranch "$NEW_BRANCH" --arg error "$3" \
65
99
  '{project: $project, worktreePath: $wt, branch: $branch, baseBranch: $base,
66
- action: $action, pulled: $pulled, error: (if $error == "" then null else $error end)}'
100
+ action: $action, pulled: $pulled, newBranch: $newBranch,
101
+ error: (if $error == "" then null else $error end)}'
67
102
  }
68
103
 
69
104
  # Physical path of $1, for comparing paths that may go through a symlink.
@@ -122,5 +157,6 @@ if ! err=$(git -C "$PROJECT" worktree add -q "${add_args[@]}" 2>&1); then
122
157
  report failed null "$err"
123
158
  exit 1
124
159
  fi
160
+ [ "${add_args[1]}" = "-b" ] && NEW_BRANCH=true
125
161
  set_identity
126
162
  report created null ""