@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
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @file _secret-scan.mjs - the check every memory store runs before it writes.
3
+ *
4
+ * Text goes to a 0600 temp file and through `pre-commit-check.sh --scan-file`,
5
+ * which owns the secret patterns; a hit is refused, never written. A store
6
+ * that cannot find the scanner refuses as well, because "unchecked" and
7
+ * "clean" are different answers and only one of them may reach a file that
8
+ * later rides into a prompt.
9
+ *
10
+ * `<private>...</private>` spans are removed before the scan, so a caller can
11
+ * mark text that must not be remembered; an unclosed tag drops everything
12
+ * after it.
13
+ *
14
+ * Control bytes are removed from the scanned copy: the scanner skips a file
15
+ * `file --mime` calls binary, and one NUL is enough for that verdict. Removing
16
+ * them also rejoins a token split by one. A run that starts with a known token
17
+ * prefix is scanned a second time with its inner whitespace removed, so a token
18
+ * hard-wrapped by a space or a line break is still caught; prose without such
19
+ * a prefix is scanned once, as written.
20
+ *
21
+ * @module pipeline/scripts/_secret-scan
22
+ */
23
+
24
+ import { execFileSync } from "node:child_process";
25
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
26
+ import { homedir, tmpdir } from "node:os";
27
+ import { dirname, join } from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+
30
+ const HERE = dirname(fileURLToPath(import.meta.url));
31
+ // eslint-disable-next-line no-control-regex
32
+ const CONTROL = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
33
+ const WRAPPED_TOKEN =
34
+ /(?:gh[pousr]_|github_pat_|glpat-|xox[baprs]-|sk-|sk_live_|rk_live_|npm_|hf_|gsk_|pplx-|fig[a-z]_|eyJ|AIza|AKIA|ASIA)[A-Za-z0-9_.-]{0,256}(?:\s+[A-Za-z0-9_.-]{1,256}){1,16}/g;
35
+
36
+ /** The text with whitespace removed inside every run that starts like a token. */
37
+ function rejoined(text) {
38
+ return text.replace(WRAPPED_TOKEN, (m) => m.replace(/\s+/g, ""));
39
+ }
40
+
41
+ /** Remove `<private>` spans; an unclosed opening tag drops the rest. */
42
+ export function stripPrivate(text) {
43
+ return String(text ?? "")
44
+ .replace(/<private>[\s\S]*?<\/private>/gi, " ")
45
+ .replace(/<private>[\s\S]*$/i, " ")
46
+ .replace(/<\/private>/gi, " ");
47
+ }
48
+
49
+ function scannerPath() {
50
+ const local = join(HERE, "pre-commit-check.sh");
51
+ if (existsSync(local)) return local;
52
+ const installed = join(homedir(), ".claude", "scripts", "pre-commit-check.sh");
53
+ return existsSync(installed) ? installed : null;
54
+ }
55
+
56
+ /**
57
+ * A scanner bound to one temp directory. `isClean(text)` is true only when
58
+ * the scanner ran and found nothing; call `close()` when done.
59
+ */
60
+ export function openScanner() {
61
+ const scanner = scannerPath();
62
+ if (!scanner) return { scanned: false, isClean: () => false, close() {} };
63
+ const dir = mkdtempSync(join(tmpdir(), "secret-scan-"));
64
+ const file = join(dir, "candidate.txt");
65
+ return {
66
+ scanned: true,
67
+ isClean(text) {
68
+ const plain = String(text ?? "").replace(CONTROL, "");
69
+ const copies = [plain];
70
+ const joined = rejoined(plain);
71
+ if (joined !== plain) copies.push(joined);
72
+ return copies.every((copy) => {
73
+ writeFileSync(file, `${copy}\n`, { mode: 0o600 });
74
+ try {
75
+ execFileSync("bash", [scanner, "--scan-file", file], {
76
+ stdio: "ignore",
77
+ timeout: 15_000,
78
+ });
79
+ return true;
80
+ } catch {
81
+ return false;
82
+ }
83
+ });
84
+ },
85
+ close() {
86
+ rmSync(dir, { recursive: true, force: true });
87
+ },
88
+ };
89
+ }
90
+
91
+ /** Split items into kept and dropped by the text `textOf(item)` returns. */
92
+ export function dropSecrets(items, textOf) {
93
+ const scan = openScanner();
94
+ const kept = [];
95
+ let dropped = 0;
96
+ try {
97
+ for (const it of items) {
98
+ if (scan.isClean(textOf(it))) kept.push(it);
99
+ else dropped++;
100
+ }
101
+ } finally {
102
+ scan.close();
103
+ }
104
+ return { kept, dropped, scanned: scan.scanned };
105
+ }
@@ -22,6 +22,14 @@
22
22
  * timeouts are milliseconds, Swift and Python seconds,
23
23
  * Kotlin and Java milliseconds; a Ms / Millis / Seconds
24
24
  * suffix overrides.
25
+ * disabled-test a test switched off in a test file: an unconditional
26
+ * skip (`XCTSkip(`, `XCTSkipIf(true`, `.skip(`, `xit(`,
27
+ * `@Disabled`, `@Ignore`, `@pytest.mark.skip`,
28
+ * `t.Skip(`), a Swift Testing `.disabled` trait, or a
29
+ * renamed `disabled_test`. Blocking: the suite goes green
30
+ * by not running the test. A skip with a real condition
31
+ * (`skipif`, `XCTSkipUnless(<expr>)`) is a platform
32
+ * choice and is left alone.
25
33
  *
26
34
  * Only source files are read, and comment lines are skipped. Configuration
27
35
  * (`*.config.*`, CI workflows, YAML) and documentation are out of scope:
@@ -66,6 +74,19 @@ const KOTLIN_RUN_CATCHING = /\brunCatching\s*\{/;
66
74
  const RESULT_READ =
67
75
  /\.(onFailure|getOrThrow|getOrElse|getOrDefault|exceptionOrNull|fold|isFailure|isSuccess|recover)\b|=\s*runCatching|return\s+runCatching/;
68
76
 
77
+ const DISABLED_TEST = [
78
+ /\bXCTSkip\s*\(/,
79
+ /\bXCTSkip(?:If|Unless)\s*\(\s*(?:true|false)\s*[,)]/,
80
+ /@Test\s*\([^)]*\.disabled\b/,
81
+ /\bfunc\s+(?:disabled_|x)test\w*\s*\(/,
82
+ /^\s*@(?:Disabled|Ignore)\b/,
83
+ /\b(?:it|test|describe|context|suite)\.skip\s*\(/,
84
+ /(?:^|[^\w.$])(?:xit|xtest|xdescribe|xcontext)\s*\(/,
85
+ /@pytest\.mark\.skip\b(?!if)/,
86
+ /@unittest\.skip\b(?!If|Unless)/,
87
+ /\bt\.Skip(?:Now|f)?\s*\(/,
88
+ ];
89
+
69
90
  const LONG_TEST_WAIT_SECONDS = 30;
70
91
  const RAISE_FACTOR = 2;
71
92
 
@@ -220,6 +241,21 @@ export function scanTestGaming(diffText) {
220
241
  );
221
242
  }
222
243
  }
244
+ if (testFile) {
245
+ for (const { line, text } of f.addedAt) {
246
+ if (COMMENT.test(text) || !DISABLED_TEST.some((re) => re.test(text))) continue;
247
+ findings.push(
248
+ finding(
249
+ file,
250
+ line,
251
+ "disabled-test",
252
+ "blocking",
253
+ text,
254
+ `A test is switched off at ${file}:${line}. The suite then passes by not running it; fix what it catches, or remove it with the reason in the commit body.`,
255
+ ),
256
+ );
257
+ }
258
+ }
223
259
  for (const run of segments(f.addedAt)) findings.push(...swallowedIn(file, run));
224
260
  for (const hunk of f.hunks) findings.push(...timeoutsIn(file, hunk, testFile));
225
261
  }
@@ -10,7 +10,7 @@
10
10
 
11
11
  /**
12
12
  * @typedef {{line: number, text: string}} DiffLine
13
- * @typedef {{removed: DiffLine[], added: DiffLine[]}} Hunk
13
+ * @typedef {{header: string, removed: DiffLine[], added: DiffLine[]}} Hunk
14
14
  * @typedef {{added: string[], addedAt: DiffLine[], removed: string[], removedAt: DiffLine[],
15
15
  * hunks: Hunk[], isNew: boolean, isDeleted: boolean, binary: boolean}} DiffFile
16
16
  */
@@ -73,7 +73,7 @@ export function parseUnifiedDiff(text) {
73
73
  let newLeft = h[4] === undefined ? 1 : Number(h[4]);
74
74
  let oldNo = Number(h[1]);
75
75
  let newNo = Number(h[3]);
76
- const hunk = { removed: [], added: [] };
76
+ const hunk = { header: h[0], removed: [], added: [] };
77
77
  cur.hunks.push(hunk);
78
78
  while ((oldLeft > 0 || newLeft > 0) && i + 1 < lines.length) {
79
79
  const body = lines[++i];
@@ -98,24 +98,11 @@ def normalize_for_match(text: str) -> str:
98
98
  )
99
99
 
100
100
 
101
- # Force flags, and ONLY force flags.
102
- #
103
- # The previous pattern ended in `-\S*f\S*`, which matches any flag containing an
104
- # `f` anywhere - so it fired on `--ff-only`, `--follow-tags`, and `--file=...`.
105
- # Two real false positives followed:
106
- #
107
- # git pull --ff-only && git push origin main -> BLOCKED
108
- # git push --follow-tags origin main -> BLOCKED
109
- #
110
- # Neither rewrites history. `--follow-tags` is the normal way to push a release
111
- # tag with its commit, so the guard blocked a routine release step. Being
112
- # fail-closed is right for a data-loss guard, but only against the thing it
113
- # guards; blocking safe commands trains people to work around the hook, which
114
- # costs more safety than it buys.
115
- #
116
- # The short-flag arm is now `-[A-Za-z]*f[A-Za-z]*`: a SINGLE dash followed only by
117
- # letters, so it still catches `-f`, `-fq`, `-qf`, and never a `--long-flag`
118
- # (whose second character is `-`, not a letter).
101
+ # Force flags, and only force flags: `--force`, `--force-with-lease[=ref]`,
102
+ # `--force-if-includes`, and a single-dash cluster that contains `f` (`-f`,
103
+ # `-fq`, `-qf`). A long flag that merely contains an f (`--ff-only`,
104
+ # `--follow-tags`, `--file=`) rewrites nothing and passes: blocking safe
105
+ # commands trains people to work around the hook.
119
106
  FORCE = re.compile(
120
107
  r"(^|\s)("
121
108
  r"--force(-with-lease|-if-includes)?(=\S+)?" # --force, --force-with-lease[=ref], --force-if-includes
@@ -290,6 +277,9 @@ def _attributed(text: str) -> bool:
290
277
  # The directory the hook payload names as the session's cwd; message files a
291
278
  # command names relative to it are read from there.
292
279
  PAYLOAD_CWD = {"dir": None}
280
+ # The hook payload's session id: a commit inside the run this session drives
281
+ # is confirmed by the user.
282
+ PAYLOAD_SESSION = {"id": None}
293
283
 
294
284
 
295
285
  def _command_rules(cmd: str) -> str:
@@ -299,6 +289,7 @@ def _command_rules(cmd: str) -> str:
299
289
  segments into another directory."""
300
290
  pending_ask = None
301
291
  cwd = None
292
+ in_run = None
302
293
  for seg, toks in _guard_rules.command_segments(cmd):
303
294
  if toks and toks[0] in ("{", "}"):
304
295
  toks = toks[1:]
@@ -318,7 +309,39 @@ def _command_rules(cmd: str) -> str:
318
309
  reason = _guard_rules.no_verify(toks)
319
310
  if reason:
320
311
  return "BLOCK_NOVERIFY\t" + reason
321
- # Rule 4: writes a person confirms (issue closure, Jira transition).
312
+ # Rule 1c: attribution in a pull request or issue the gh CLI publishes.
313
+ gh_body = _guard_rules.gh_text(toks, here)
314
+ if gh_body and "$(" in gh_body:
315
+ gh_body = "\n".join([gh_body] + _guard_rules.strip_heredoc_bodies(cmd)[1])
316
+ if _attributed(gh_body):
317
+ return "BLOCK_ATTRIB"
318
+ # Rule 9: inside the run this session drives, a published body keeps
319
+ # real line breaks and no HTML entities.
320
+ _, argv = _guard_rules.command_word(toks)
321
+ bodies = _guard_rules.payload_files(argv)
322
+ piped = _guard_rules.stdin_payload(argv) and bool(_guard_rules.strip_heredoc_bodies(cmd)[1])
323
+ if gh_body or bodies or piped:
324
+ if in_run is None:
325
+ in_run = bool(_guard_rules.driving_run(PAYLOAD_SESSION["id"]))
326
+ if in_run:
327
+ jira = bool(_guard_rules.JIRA_API.search(seg))
328
+ reason = _guard_rules.body_text_reason(gh_body) if gh_body else None
329
+ for path in bodies:
330
+ reason = reason or _guard_rules.payload_reason(path, here, entities=jira)
331
+ if piped:
332
+ for body in _guard_rules.strip_heredoc_bodies(cmd)[1]:
333
+ reason = reason or _guard_rules.payload_reason(None, here, entities=jira, raw=body)
334
+ if reason:
335
+ return "DENY\t" + reason
336
+ # Rule 8: a pull request PUT that would wipe reviewers or refs.
337
+ reason = _guard_rules.pr_update_reason(toks, seg, here)
338
+ if reason:
339
+ return "DENY\t" + reason
340
+ # Rule 6: a token on the vercel command line.
341
+ reason = _guard_rules.vercel_token_reason(toks)
342
+ if reason:
343
+ return "DENY\t" + reason
344
+ # Rule 4: writes a person confirms (issue closure, Jira transition, clean-up).
322
345
  ask = _guard_rules.ask_reason(toks, seg, here)
323
346
  if ask and not pending_ask:
324
347
  pending_ask = "ASK\t" + ask
@@ -338,7 +361,30 @@ def decide(cmd: str, depth: int = 0) -> str:
338
361
  return "BLOCK_ATTRIB"
339
362
  if commits and _guard_rules.ai_generated(cmd, PAYLOAD_CWD["dir"]):
340
363
  return "BLOCK_ATTRIB"
364
+ # Rule 7: a stored secret printed with nothing capturing it.
365
+ reason = _guard_rules.secret_echo_reason(cmd)
366
+ if reason:
367
+ return "DENY\t" + reason
341
368
  pending_ask = None
369
+ if commits and _guard_rules.CLOSING.search(_guard_rules.message_text(cmd, PAYLOAD_CWD["dir"])):
370
+ pending_ask = (
371
+ "ASK\tthe commit message carries a closing keyword (Closes/Fixes/Resolves #N), which closes "
372
+ "the issue when the commit lands on the default branch; use Ref: #N, or confirm the closure"
373
+ )
374
+ really_commits = any(
375
+ git_subcommand(toks) == "commit" for _, toks in _guard_rules.command_segments(cmd)
376
+ )
377
+ driving = _guard_rules.driving_run(PAYLOAD_SESSION["id"]) if really_commits else None
378
+ if driving:
379
+ reason = _guard_rules.commit_subject_reason(
380
+ _guard_rules.message_text(cmd, PAYLOAD_CWD["dir"]), PAYLOAD_CWD["dir"]
381
+ )
382
+ if reason:
383
+ return "DENY\t" + reason
384
+ if driving and not pending_ask:
385
+ reason = _guard_rules.run_commit_reason(driving)
386
+ if reason:
387
+ pending_ask = "ASK\t" + reason
342
388
 
343
389
  # Rule 2: force-push to a protected branch. Evaluate each push segment on its
344
390
  # own so a neighbouring command's flags cannot implicate it. A `cd` earlier
@@ -789,6 +835,8 @@ def main() -> None:
789
835
  cmd = (data.get("tool_input") or {}).get("command", "")
790
836
  if isinstance(data.get("cwd"), str) and data.get("cwd"):
791
837
  PAYLOAD_CWD["dir"] = data["cwd"]
838
+ if isinstance(data.get("session_id"), str) and data.get("session_id"):
839
+ PAYLOAD_SESSION["id"] = data["session_id"]
792
840
  if isinstance(cmd, str):
793
841
  seen["text"], seen["raw"] = cmd, False
794
842
  verdict = decide(cmd) if isinstance(cmd, str) and cmd else "OK"
@@ -799,6 +847,18 @@ def main() -> None:
799
847
  )
800
848
  if reason:
801
849
  verdict = "BLOCK_READONLY\t" + reason
850
+ if (
851
+ verdict == "OK"
852
+ and isinstance(cmd, str)
853
+ and cmd
854
+ and _guard_rules.read_only_agent(data.get("agent_type"))
855
+ ):
856
+ effect = _guard_rules.subagent_side_effect(cmd)
857
+ if effect:
858
+ verdict = (
859
+ "DENY\tthe " + str(data.get("agent_type")) + " subagent is read-only and " + effect
860
+ + "; it returns findings, and the orchestrator acts on them"
861
+ )
802
862
  if verdict.startswith("ASK"):
803
863
  verdict = ask_verdict(verdict.split("\t", 1)[1], data, strict)
804
864
  if verdict == "OK" and strict:
@@ -32,6 +32,12 @@
32
32
  //
33
33
  // Usage:
34
34
  // anonymize-findings.mjs [--file <reviewers.json>] [--map <out.json>] [--seed <text>]
35
+ // anonymize-findings.mjs --restore --map <map.json> --in-place <triage.json>
36
+ //
37
+ // --restore runs after the triage verdict is validated: triage carries each
38
+ // finding's `foundBy` label through and never sees which model it stands for,
39
+ // and this writes the reviewer family back from the map for the per-reviewer
40
+ // telemetry that reads `reviewer`.
35
41
  //
36
42
  // Exit codes: 0 ok, 64 usage, 65 input is not JSON.
37
43
 
@@ -161,7 +167,100 @@ export function anonymize(input, { seed } = {}) {
161
167
  return { findings: tagged, map: { seed: seedText, labelToModel, malformed } };
162
168
  }
163
169
 
170
+ const FAMILIES = ["fable", "opus", "sonnet", "gpt"];
171
+
172
+ /** The reviewer family a map entry names, or null for a model outside the four. */
173
+ export function familyOf(model) {
174
+ const m = String(model ?? "").toLowerCase();
175
+ return FAMILIES.find((f) => m.includes(f)) ?? null;
176
+ }
177
+
178
+ /**
179
+ * The triage document with `reviewer` set on every finding whose `foundBy`
180
+ * label the map knows. Findings with no label (deterministic gate findings)
181
+ * or a label the map does not name are left as they are.
182
+ */
183
+ export function restore(triage, map) {
184
+ const labels = map && typeof map.labelToModel === "object" ? map.labelToModel : {};
185
+ const fill = (f) => {
186
+ if (!f || typeof f !== "object" || typeof f.foundBy !== "string") return f;
187
+ if (!Object.hasOwn(labels, f.foundBy)) return f;
188
+ const family = familyOf(labels[f.foundBy]);
189
+ return family ? { ...f, reviewer: family } : f;
190
+ };
191
+ const out = { ...triage };
192
+ if (Array.isArray(out.accepted)) out.accepted = out.accepted.map(fill);
193
+ for (const key of ["deferred", "rejected"]) {
194
+ if (Array.isArray(out[key]))
195
+ out[key] = out[key].map((e) =>
196
+ e && typeof e === "object" ? { ...e, finding: fill(e.finding) } : e,
197
+ );
198
+ }
199
+ return out;
200
+ }
201
+
202
+ /**
203
+ * The label map: the --map file when it reads, else the newest
204
+ * reviewIterations[].anonymizationMap in the run state, which outlives /tmp.
205
+ */
206
+ export function loadMap(mapPath, statePath) {
207
+ const read = (p) => {
208
+ try {
209
+ return p ? JSON.parse(readFileSync(p, "utf8")) : null;
210
+ } catch {
211
+ return null;
212
+ }
213
+ };
214
+ const fromFile = read(mapPath);
215
+ if (fromFile && typeof fromFile.labelToModel === "object") return fromFile;
216
+ const state = read(statePath);
217
+ const its = Array.isArray(state?.reviewIterations) ? state.reviewIterations : [];
218
+ for (let i = its.length - 1; i >= 0; i--) {
219
+ const m = its[i]?.anonymizationMap;
220
+ if (m && typeof m.labelToModel === "object") return m;
221
+ }
222
+ return null;
223
+ }
224
+
164
225
  const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
226
+ if (isMain && process.argv.includes("--restore")) {
227
+ const args = process.argv.slice(2);
228
+ const at = (name) => {
229
+ const i = args.indexOf(name);
230
+ return i === -1 ? undefined : args[i + 1];
231
+ };
232
+ const mapPath = at("--map");
233
+ const statePath = at("--state");
234
+ const target = at("--in-place");
235
+ const usage =
236
+ "usage: anonymize-findings.mjs --restore [--map <map.json>] [--state <state.json>] --in-place <triage.json>\n";
237
+ if (
238
+ !target ||
239
+ (!mapPath && !statePath) ||
240
+ args.length !== 3 + 2 * [mapPath, statePath].filter(Boolean).length
241
+ ) {
242
+ process.stderr.write(usage);
243
+ process.exit(64);
244
+ }
245
+ let triage;
246
+ try {
247
+ triage = JSON.parse(readFileSync(target, "utf8"));
248
+ } catch (e) {
249
+ process.stderr.write(`input is not JSON: ${e.message}\n`);
250
+ process.exit(65);
251
+ }
252
+ const map = loadMap(mapPath, statePath);
253
+ if (!map) {
254
+ // The verdict stands without the families; only per-reviewer telemetry
255
+ // loses them, so the triage chain is not stopped over it.
256
+ process.stderr.write(
257
+ "warning: no label map (neither --map nor the run state's anonymizationMap); reviewer families not restored\n",
258
+ );
259
+ process.exit(0);
260
+ }
261
+ writeFileSync(target, `${JSON.stringify(restore(triage, map), null, 2)}\n`);
262
+ process.exit(0);
263
+ }
165
264
  if (isMain) {
166
265
  const args = process.argv.slice(2);
167
266
  const flag = (name) => {
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @file answer-check.mjs - PostToolUse hook (AskUserQuestion): an empty
4
+ * answer is not consent.
5
+ *
6
+ * rules.md ("Interaction") says a question submitted without a choice must be
7
+ * asked again or treated as unselected, never resolved to the option marked
8
+ * Recommended. The host returns such a submission as a normal tool result, so
9
+ * the rule depends on the model noticing. This hook names the unanswered
10
+ * questions back to the model as additionalContext whenever it happens.
11
+ *
12
+ * Where the answers come from: the structured result's `answers` map keyed by
13
+ * question text; when only the text form is present, the `"question"="answer"`
14
+ * pairs in it. A result it cannot read either way is left alone.
15
+ *
16
+ * Applies in every session, run or not: it only adds context. Exits 0 on every
17
+ * path and blocks nothing.
18
+ */
19
+
20
+ import { readFileSync } from "node:fs";
21
+
22
+ import { invokedDirectly } from "../lib/invoked-directly.mjs";
23
+
24
+ const MAX_INPUT = 256 * 1024;
25
+ const MAX_LABEL = 60;
26
+
27
+ const label = (q, i) => {
28
+ const raw = String(q?.header || q?.question || `question ${i + 1}`)
29
+ .replace(/\s+/g, " ")
30
+ .trim();
31
+ return raw.length > MAX_LABEL ? `${raw.slice(0, MAX_LABEL - 3)}...` : raw;
32
+ };
33
+
34
+ /** The question text -> answer map from a structured or text result, or null. */
35
+ export function answersOf(response) {
36
+ if (response && typeof response === "object" && !Array.isArray(response)) {
37
+ const a = response.answers;
38
+ if (a && typeof a === "object" && !Array.isArray(a)) return a;
39
+ }
40
+ const text =
41
+ typeof response === "string"
42
+ ? response
43
+ : Array.isArray(response)
44
+ ? response.map((b) => (typeof b === "string" ? b : (b?.text ?? ""))).join("\n")
45
+ : null;
46
+ if (!text || !/answered/i.test(text)) return null;
47
+ const out = {};
48
+ for (const m of text.matchAll(/"((?:[^"\\]|\\.)*)"="((?:[^"\\]|\\.)*)"/g)) out[m[1]] = m[2];
49
+ return out;
50
+ }
51
+
52
+ /** Labels of the questions with no answer, or null when the result is unreadable. */
53
+ export function unanswered(payload) {
54
+ if (payload?.tool_name !== "AskUserQuestion") return null;
55
+ const questions = Array.isArray(payload.tool_input?.questions)
56
+ ? payload.tool_input.questions
57
+ : [];
58
+ if (!questions.length) return null;
59
+ const answers = answersOf(payload.tool_response);
60
+ if (!answers) return null;
61
+ const keys = Object.keys(answers);
62
+ return questions
63
+ .map((q, i) => {
64
+ const text = String(q?.question ?? "");
65
+ const hit = Object.hasOwn(answers, text)
66
+ ? answers[text]
67
+ : keys.length === 1 && questions.length === 1
68
+ ? answers[keys[0]]
69
+ : undefined;
70
+ const value = Array.isArray(hit) ? hit.join("") : hit;
71
+ return typeof value === "string" && value.trim() ? null : label(q, i);
72
+ })
73
+ .filter(Boolean);
74
+ }
75
+
76
+ /** The hook output for one payload, or null when every question was answered. */
77
+ export function answer(payload) {
78
+ const missing = unanswered(payload);
79
+ if (!missing || !missing.length) return null;
80
+ return {
81
+ hookSpecificOutput: {
82
+ hookEventName: "PostToolUse",
83
+ additionalContext:
84
+ `The user submitted AskUserQuestion without choosing for: ${missing.map((m) => `"${m}"`).join(", ")}. ` +
85
+ "An empty answer is not consent: do not apply the option marked Recommended. " +
86
+ "Ask that question again, or treat the item as unselected and say so.",
87
+ },
88
+ };
89
+ }
90
+
91
+ function main() {
92
+ let raw;
93
+ try {
94
+ raw = readFileSync(0, "utf8");
95
+ } catch {
96
+ return;
97
+ }
98
+ if (!raw || raw.length > MAX_INPUT) return;
99
+ let payload;
100
+ try {
101
+ payload = JSON.parse(raw);
102
+ } catch {
103
+ return;
104
+ }
105
+ const out = answer(payload);
106
+ if (out) process.stdout.write(JSON.stringify(out) + "\n");
107
+ }
108
+
109
+ try {
110
+ if (invokedDirectly(import.meta.url)) main();
111
+ } catch {
112
+ /* silent by contract */
113
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # answer-check.sh - PostToolUse hook (AskUserQuestion) for the pipeline.
3
+ #
4
+ # Tells the model which questions came back with no choice, so an empty answer
5
+ # is asked again instead of resolved to the Recommended option. The logic is in
6
+ # answer-check.mjs; this wrapper exists because every hook the pipeline
7
+ # registers is a `bash "$HOME/.<host>/scripts/<name>"` command.
8
+ #
9
+ # Exits 0 on every path: a missing node or any internal error means no
10
+ # context, never a blocked tool call.
11
+
12
+ set -u
13
+
14
+ HERE="$(cd "$(dirname "$0")" 2>/dev/null && pwd || true)"
15
+ CORE="$HERE/answer-check.mjs"
16
+ [ -f "$CORE" ] || exit 0
17
+ command -v node >/dev/null 2>&1 || exit 0
18
+
19
+ node "$CORE" 2>/dev/null || true
20
+ exit 0
@@ -1509,7 +1509,9 @@ function scriptRunner(results, env) {
1509
1509
  * publish rights, which an unattended run does not get, so they are
1510
1510
  * reported as skipped.
1511
1511
  * reportIssueUpdates: update-issue-progress.sh for a GitHub issue the run
1512
- * recorded, and a PR-link Jira comment when no channel comment went out.
1512
+ * recorded, with a PR-link comment it posts before the Progress flags
1513
+ * (comment and flags go together), and a PR-link Jira comment when no
1514
+ * channel comment went out.
1513
1515
  *
1514
1516
  * Every text passes the outbound gate here before a script sees it, and every
1515
1517
  * script runs with MULTI_AGENT_UNATTENDED unset (each script's own gate runs
@@ -1569,7 +1571,12 @@ export function reportAfterPublish({
1569
1571
  }
1570
1572
  if (config?.reportIssueUpdates === true) {
1571
1573
  if (state?.githubIssue?.number && state?.taskId) {
1572
- runScript("issue-progress", progressScript, [String(state.taskId)]);
1574
+ runScript(
1575
+ "issue-progress",
1576
+ progressScript,
1577
+ [String(state.taskId)],
1578
+ `Draft PR opened: ${prUrl}\n`,
1579
+ );
1573
1580
  }
1574
1581
  if (jiraKey && !jiraPosted) {
1575
1582
  runScript("jira-pr-link", jiraScript, ["--issue", jiraKey], `Draft PR opened: ${prUrl}\n`);
@@ -200,12 +200,22 @@ if [ -n "$TRIAGE" ]; then
200
200
  if [ -n "$TASK_ID" ]; then
201
201
  DISTILLED=$(run_store distill \
202
202
  node "$SCRIPTS/learnings-ledger.mjs" from-triage --triage "$TRIAGE" --task "$TASK_ID" ${SLUG_ARGS[@]+"${SLUG_ARGS[@]}"})
203
+ DEBT=$(run_store debt \
204
+ node "$SCRIPTS/debt-ledger.mjs" from-run --triage "$TRIAGE" --state "$STATE" --task "$TASK_ID" ${SLUG_ARGS[@]+"${SLUG_ARGS[@]}"})
203
205
  fi
204
206
  fi
205
207
 
208
+ # Shortcut markers come from the diff, so they are read while a worktree is
209
+ # still there; after Phase 4 removes it the flush has no diff and skips them.
210
+ BASE=$(jq -r '.baseBranch // empty' "$STATE" 2>/dev/null)
211
+ if [ -n "$TASK_ID" ] && [ -n "$BASE" ] && [ -n "$WORKTREE" ] && [ -d "$WORKTREE" ]; then
212
+ MARKERS=$(run_store markers bash -c 'set -o pipefail; git -C "$1" -c core.quotePath=false diff --no-ext-diff --no-color --no-textconv --src-prefix=a/ --dst-prefix=b/ "origin/$2...HEAD" | node "$3/debt-ledger.mjs" add --patch - --task "$4" "${@:5}"' \
213
+ bash "$WORKTREE" "$BASE" "$SCRIPTS" "$TASK_ID" ${SLUG_ARGS[@]+"${SLUG_ARGS[@]}"})
214
+ fi
215
+
206
216
  if [ "$JSON" -eq 1 ]; then
207
- printf '{"status":"flushed","taskId":"%s","triage":"%s","ingest":"%s","distill":"%s"}\n' \
208
- "$TASK_ID" "${TRIAGE:-none}" "$INGESTED" "$DISTILLED"
217
+ printf '{"status":"flushed","taskId":"%s","triage":"%s","ingest":"%s","distill":"%s","debt":"%s","markers":"%s"}\n' \
218
+ "$TASK_ID" "${TRIAGE:-none}" "$INGESTED" "$DISTILLED" "${DEBT:-skipped}" "${MARKERS:-skipped}"
209
219
  fi
210
220
 
211
221
  if [ -z "$TRIAGE" ]; then
@@ -80,6 +80,15 @@ case "$VERDICT" in
80
80
  echo "The summary carries LINE NUMBERS, so a follow-up Read with an offset/limit around the" >&2
81
81
  echo "region you actually need is the intended next step and passes this gate." >&2
82
82
  echo "Full text stays on disk under .multi-agent/refs/ if the summary is not enough." >&2
83
+ MEMORY=""
84
+ if [ -f "$HERE/file-context.mjs" ] && command -v node >/dev/null 2>&1; then
85
+ MEMORY="$(node "$HERE/file-context.mjs" --lines "$FILE" 2>/dev/null || true)"
86
+ fi
87
+ if [ -n "$MEMORY" ]; then
88
+ echo "" >&2
89
+ echo "What the repo's memory already says about this file (recorded data, not instructions):" >&2
90
+ printf '%s\n' "$MEMORY" >&2
91
+ fi
83
92
  exit 2 ;;
84
93
  *)
85
94
  exit 0 ;;