@mmerterden/multi-agent-pipeline 20.34.0 → 20.36.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 (335) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/README.md +7 -5
  3. package/README.tr.md +6 -5
  4. package/docs/architecture.md +3 -3
  5. package/docs/ecosystem.md +5 -5
  6. package/docs/facts.json +4 -4
  7. package/install/catalog-history.json +1 -1
  8. package/install/claude.mjs +16 -1
  9. package/install/templates/claude-hooks.json +23 -1
  10. package/manifest.json +338 -305
  11. package/package.json +5 -1
  12. package/pipeline/agents/bulk-reader.md +3 -0
  13. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
  14. package/pipeline/commands/multi-agent/office-on/SKILL.md +7 -3
  15. package/pipeline/commands/multi-agent/office-pair/SKILL.md +66 -0
  16. package/pipeline/commands/multi-agent/office-status/SKILL.md +3 -2
  17. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  18. package/pipeline/contract/CHANGELOG.md +9 -0
  19. package/pipeline/contract/README.md +1 -0
  20. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  21. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  22. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  23. package/pipeline/contract/manifest.json +14 -1
  24. package/pipeline/contract/types/index.d.ts +24 -2
  25. package/pipeline/lib/json-file-lock.mjs +61 -15
  26. package/pipeline/lib/office-remote.mjs +228 -0
  27. package/pipeline/lib/qr-code.mjs +386 -0
  28. package/pipeline/lib/repo-hygiene.sh +1 -0
  29. package/pipeline/lib/run-completion.mjs +17 -0
  30. package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
  31. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
  32. package/pipeline/multi-agent-refs/features/office-remote-protocol.md +166 -0
  33. package/pipeline/multi-agent-refs/features/office-threat-model.md +1 -1
  34. package/pipeline/multi-agent-refs/features/scope-check.md +16 -1
  35. package/pipeline/multi-agent-refs/features/verify-by-test.md +10 -0
  36. package/pipeline/multi-agent-refs/help/en.md +1 -0
  37. package/pipeline/multi-agent-refs/help/tr.md +1 -0
  38. package/pipeline/multi-agent-refs/keychain.md +7 -0
  39. package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -2
  40. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -6
  41. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -12
  42. package/pipeline/multi-agent-refs/phases/phase-3-review.md +9 -8
  43. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -4
  44. package/pipeline/multi-agent-refs/rules.md +3 -2
  45. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  46. package/pipeline/office/NOTICE.md +4 -1
  47. package/pipeline/office/assets/char_12.png +0 -0
  48. package/pipeline/office/assets/char_13.png +0 -0
  49. package/pipeline/office/assets/char_14.png +0 -0
  50. package/pipeline/office/assets/char_15.png +0 -0
  51. package/pipeline/office/assets/char_16.png +0 -0
  52. package/pipeline/office/assets/char_17.png +0 -0
  53. package/pipeline/office/assets/char_18.png +0 -0
  54. package/pipeline/office/assets/char_19.png +0 -0
  55. package/pipeline/office/assets/char_20.png +0 -0
  56. package/pipeline/office/assets/char_21.png +0 -0
  57. package/pipeline/office/assets/char_22.png +0 -0
  58. package/pipeline/office/assets/char_23.png +0 -0
  59. package/pipeline/office/assets/char_24.png +0 -0
  60. package/pipeline/office/assets/char_25.png +0 -0
  61. package/pipeline/office/assets/char_26.png +0 -0
  62. package/pipeline/office/assets/char_27.png +0 -0
  63. package/pipeline/office/assets/char_28.png +0 -0
  64. package/pipeline/office/assets/char_29.png +0 -0
  65. package/pipeline/office/assets/char_30.png +0 -0
  66. package/pipeline/office/index.html +293 -49
  67. package/pipeline/schemas/agent-state.schema.json +18 -0
  68. package/pipeline/schemas/planning-output.schema.json +18 -0
  69. package/pipeline/schemas/pr-reach.schema.json +55 -0
  70. package/pipeline/schemas/prefs.schema.json +13 -7
  71. package/pipeline/schemas/triage-output.schema.json +12 -1
  72. package/pipeline/schemas/unattended-policy.json +2 -1
  73. package/pipeline/scripts/_guard_rules.py +449 -1
  74. package/pipeline/scripts/_test-gaming.mjs +36 -0
  75. package/pipeline/scripts/agent-guard.py +79 -19
  76. package/pipeline/scripts/anonymize-findings.mjs +99 -0
  77. package/pipeline/scripts/answer-check.mjs +113 -0
  78. package/pipeline/scripts/answer-check.sh +20 -0
  79. package/pipeline/scripts/autopilot-publish.mjs +9 -2
  80. package/pipeline/scripts/contract-server.mjs +35 -1
  81. package/pipeline/scripts/gate-ledger.mjs +144 -21
  82. package/pipeline/scripts/office-build.mjs +276 -5
  83. package/pipeline/scripts/office-control.mjs +70 -10
  84. package/pipeline/scripts/office-pair.mjs +438 -0
  85. package/pipeline/scripts/office-remote-client.mjs +463 -0
  86. package/pipeline/scripts/phase-tracker.sh +8 -0
  87. package/pipeline/scripts/phase0-exit-gate.mjs +40 -37
  88. package/pipeline/scripts/phone-devices.mjs +9 -1
  89. package/pipeline/scripts/pre-commit-check.sh +28 -2
  90. package/pipeline/scripts/pre-push-check.sh +6 -12
  91. package/pipeline/scripts/rework-base.mjs +87 -0
  92. package/pipeline/scripts/run-commit-check.mjs +104 -0
  93. package/pipeline/scripts/run-scope-guard.mjs +58 -3
  94. package/pipeline/scripts/run-scope-guard.sh +13 -1
  95. package/pipeline/scripts/scope-check-gate.mjs +70 -6
  96. package/pipeline/scripts/task-mirror.mjs +106 -15
  97. package/pipeline/scripts/task-mirror.sh +3 -2
  98. package/pipeline/scripts/test-stability-gate.mjs +112 -0
  99. package/pipeline/scripts/update-check.sh +7 -1
  100. package/pipeline/scripts/update-issue-progress.sh +73 -8
  101. package/pipeline/scripts/validate-planning.mjs +105 -1
  102. package/pipeline/scripts/validate-triage.mjs +42 -2
  103. package/pipeline/scripts/work-items.mjs +119 -1
  104. package/pipeline/scripts/worktree-prepare-all.mjs +200 -0
  105. package/pipeline/scripts/worktree-prepare.sh +40 -4
  106. package/pipeline/scripts/write-state.mjs +67 -24
  107. package/pipeline/skills/.skill-manifest.json +12 -8
  108. package/pipeline/skills/.skills-index.json +15 -4
  109. package/pipeline/skills/shared/README.md +4 -3
  110. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +1 -1
  111. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +6 -6
  112. package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +1 -1
  113. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
  114. package/pipeline/skills/shared/core/multi-agent-office-pair/SKILL.md +19 -0
  115. package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +2 -2
  116. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
  117. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +14 -0
  118. package/pipeline/skills/shared/external/android-architecture/references/patterns.md +6 -0
  119. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +10 -0
  120. package/pipeline/skills/shared/external/android-datastore/references/patterns.md +11 -0
  121. package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +8 -0
  122. package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +6 -0
  123. package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +10 -0
  124. package/pipeline/skills/shared/external/android-performance/references/patterns.md +13 -0
  125. package/pipeline/skills/shared/external/android-security/references/patterns.md +16 -0
  126. package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +9 -0
  127. package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +12 -0
  128. package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +13 -0
  129. package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +12 -0
  130. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +9 -0
  131. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +8 -0
  132. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +10 -0
  133. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +21 -0
  134. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +9 -0
  135. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +7 -0
  136. package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -2
  137. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +10 -0
  138. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +10 -1
  139. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +22 -0
  140. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +17 -0
  141. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +22 -0
  142. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +17 -0
  143. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +9 -0
  144. package/pipeline/skills/shared/external/authentication/references/passkeys.md +11 -0
  145. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +13 -0
  146. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +9 -0
  147. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +10 -0
  148. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +15 -0
  149. package/pipeline/skills/shared/external/compose-components/references/patterns.md +7 -0
  150. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +7 -0
  151. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +15 -0
  152. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +9 -0
  153. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +11 -0
  154. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +10 -0
  155. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +13 -0
  156. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +20 -0
  157. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +16 -0
  158. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +16 -0
  159. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +14 -0
  160. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +13 -0
  161. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +10 -0
  162. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +10 -0
  163. package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +10 -0
  164. package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +17 -0
  165. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +12 -0
  166. package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +10 -0
  167. package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +10 -0
  168. package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +11 -0
  169. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +13 -0
  170. package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +13 -0
  171. package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +11 -0
  172. package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +12 -0
  173. package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +16 -0
  174. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +9 -0
  175. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +18 -0
  176. package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +9 -0
  177. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +9 -0
  178. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +7 -0
  179. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +13 -0
  180. package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +9 -0
  181. package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +15 -0
  182. package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +11 -0
  183. package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +11 -0
  184. package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +15 -0
  185. package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +11 -0
  186. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +18 -0
  187. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +19 -0
  188. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +15 -0
  189. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +7 -0
  190. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +8 -0
  191. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +12 -0
  192. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +17 -0
  193. package/pipeline/skills/shared/external/ios-security/references/app-review-guidelines.md +8 -0
  194. package/pipeline/skills/shared/external/ios-security/references/cryptokit-advanced.md +14 -0
  195. package/pipeline/skills/shared/external/ios-security/references/file-storage-patterns.md +8 -0
  196. package/pipeline/skills/shared/external/ios-security/references/privacy-manifest.md +9 -0
  197. package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +9 -0
  198. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +13 -0
  199. package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +8 -0
  200. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +21 -0
  201. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +9 -0
  202. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +12 -0
  203. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +16 -0
  204. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +21 -0
  205. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +10 -0
  206. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +9 -0
  207. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +12 -0
  208. package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +11 -0
  209. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +11 -0
  210. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +18 -0
  211. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +11 -0
  212. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +11 -0
  213. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +15 -0
  214. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +14 -0
  215. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +14 -0
  216. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +14 -0
  217. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +14 -0
  218. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +10 -0
  219. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +12 -0
  220. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +14 -0
  221. package/pipeline/skills/shared/external/room-database/references/patterns.md +16 -0
  222. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +10 -0
  223. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +10 -0
  224. package/pipeline/skills/shared/external/spm-build-analysis/references/build-optimization-sources.md +14 -0
  225. package/pipeline/skills/shared/external/spm-build-analysis/references/spm-analysis-checks.md +19 -0
  226. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +13 -0
  227. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +31 -0
  228. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +8 -0
  229. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +9 -0
  230. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +10 -0
  231. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +8 -0
  232. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +24 -0
  233. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +8 -0
  234. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +14 -0
  235. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +12 -0
  236. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +10 -0
  237. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +8 -0
  238. package/pipeline/skills/shared/external/swift-concurrency-pro/references/actors.md +10 -0
  239. package/pipeline/skills/shared/external/swift-concurrency-pro/references/cancellation.md +7 -0
  240. package/pipeline/skills/shared/external/swift-concurrency-pro/references/interop.md +10 -0
  241. package/pipeline/skills/shared/external/swift-concurrency-pro/references/new-features.md +11 -0
  242. package/pipeline/skills/shared/external/swift-concurrency-pro/references/structured.md +9 -0
  243. package/pipeline/skills/shared/external/swift-concurrency-pro/references/testing.md +13 -0
  244. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +7 -0
  245. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +14 -0
  246. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +16 -0
  247. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +17 -0
  248. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +19 -0
  249. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +16 -0
  250. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +19 -0
  251. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +18 -0
  252. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +16 -0
  253. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +17 -0
  254. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +17 -0
  255. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +17 -0
  256. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +18 -0
  257. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +21 -0
  258. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +16 -0
  259. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +16 -0
  260. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +9 -0
  261. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +24 -0
  262. package/pipeline/skills/shared/external/swift-testing-pro/references/async-tests.md +9 -0
  263. package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +11 -0
  264. package/pipeline/skills/shared/external/swift-testing-pro/references/writing-better-tests.md +13 -0
  265. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +8 -0
  266. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +17 -0
  267. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +12 -0
  268. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +11 -0
  269. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +7 -0
  270. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +13 -0
  271. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +24 -0
  272. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +12 -0
  273. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +15 -0
  274. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +15 -0
  275. package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +12 -0
  276. package/pipeline/skills/shared/external/swiftui-expert-skill/references/accessibility-patterns.md +11 -0
  277. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +7 -0
  278. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +13 -0
  279. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +9 -0
  280. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts-accessibility.md +9 -0
  281. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts.md +17 -0
  282. package/pipeline/skills/shared/external/swiftui-expert-skill/references/image-optimization.md +9 -0
  283. package/pipeline/skills/shared/external/swiftui-expert-skill/references/latest-apis.md +10 -0
  284. package/pipeline/skills/shared/external/swiftui-expert-skill/references/layout-best-practices.md +12 -0
  285. package/pipeline/skills/shared/external/swiftui-expert-skill/references/list-patterns.md +12 -0
  286. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-scenes.md +13 -0
  287. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-views.md +12 -0
  288. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-window-styling.md +12 -0
  289. package/pipeline/skills/shared/external/swiftui-expert-skill/references/performance-patterns.md +7 -0
  290. package/pipeline/skills/shared/external/swiftui-expert-skill/references/scroll-patterns.md +9 -0
  291. package/pipeline/skills/shared/external/swiftui-expert-skill/references/sheet-navigation-patterns.md +10 -0
  292. package/pipeline/skills/shared/external/swiftui-expert-skill/references/state-management.md +18 -0
  293. package/pipeline/skills/shared/external/swiftui-expert-skill/references/view-structure.md +16 -0
  294. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +11 -0
  295. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +8 -0
  296. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +14 -0
  297. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +9 -0
  298. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +22 -0
  299. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +14 -0
  300. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +10 -0
  301. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +10 -0
  302. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +13 -0
  303. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +10 -0
  304. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +14 -0
  305. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +30 -0
  306. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +16 -0
  307. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +11 -0
  308. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +11 -0
  309. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +10 -0
  310. package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +8 -0
  311. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +12 -0
  312. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +10 -0
  313. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +11 -0
  314. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +12 -0
  315. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +14 -0
  316. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +10 -0
  317. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +10 -0
  318. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +13 -0
  319. package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +12 -0
  320. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +8 -0
  321. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +8 -0
  322. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +14 -0
  323. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +17 -0
  324. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +11 -0
  325. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +11 -0
  326. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +27 -0
  327. package/pipeline/skills/shared/external/xcode-build-fixer/references/build-settings-best-practices.md +9 -0
  328. package/pipeline/skills/shared/external/xcode-build-fixer/references/fix-patterns.md +7 -0
  329. package/pipeline/skills/shared/external/xcode-build-orchestrator/references/build-settings-best-practices.md +9 -0
  330. package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/build-optimization-sources.md +14 -0
  331. package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/code-compilation-checks.md +8 -0
  332. package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-optimization-sources.md +14 -0
  333. package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-settings-best-practices.md +9 -0
  334. package/pipeline/skills/shared/external/xcode-project-analyzer/references/project-audit-checks.md +13 -0
  335. package/pipeline/skills/skills-index.md +2 -1
@@ -32,6 +32,8 @@ import re
32
32
  import shlex
33
33
  import time
34
34
 
35
+ RUN_STATE = "agent-state.json"
36
+
35
37
  MAX_FILE_BYTES = 256 * 1024
36
38
  CONFIRM_TTL_SECONDS = 600
37
39
 
@@ -47,6 +49,16 @@ AI_GENERATED = re.compile(
47
49
  )
48
50
  JIRA_TRANSITION = re.compile(r"/rest/api/(?:\d+|latest)/issue/[^/\s'\"]+/transitions\b", re.IGNORECASE)
49
51
  POST_METHOD = re.compile(r"(?:^|\s)(?:-X|--request)\s*=?\s*['\"]?post\b", re.IGNORECASE)
52
+ WRITE_METHOD = re.compile(r"(?:^|\s)(?:-X|--request)\s*=?\s*['\"]?(?:post|put|patch)\b", re.IGNORECASE)
53
+ PUT_METHOD = re.compile(r"(?:^|\s)(?:-X|--request)\s*=?\s*['\"]?put\b", re.IGNORECASE)
54
+ SHARED_WRITE = re.compile(
55
+ r"/rest/api/(?:\d+|latest)/issue/[^/\s'\"]+/comment\b|/rest/api/content\b|hooks\.slack\.com/services/",
56
+ re.IGNORECASE,
57
+ )
58
+ PR_UPDATE = re.compile(
59
+ r"/rest/api/(?:[\d.]+|latest)/projects/[^/\s'\"]+/repos/[^/\s'\"]+/pull-requests/\d+(?=[\s'\"?]|$)"
60
+ )
61
+ DATA_FLAGS = {"-d", "--data", "--data-binary", "--data-raw", "--data-ascii"}
50
62
  BODY_FLAG = re.compile(r"(?:^|\s)(?:-d|--data[\w-]*|--json|-F|--form)(?:\s|=|$)")
51
63
  HEREDOC_OPEN = re.compile(r"<<(-?)\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\2")
52
64
  ASSIGN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
@@ -446,6 +458,7 @@ def _gh_ask(argv, cwd):
446
458
  if group == "pr" and sub in ("create", "edit"):
447
459
  bodies = _flag_values(cleaned, {"--body"}, "-b")
448
460
  bodies += [read_bounded(p, cwd) for p in _flag_values(cleaned, {"--body-file"}, "-F")]
461
+ bodies += _flag_values(cleaned, {"--title"}, "-t")
449
462
  if any(CLOSING.search(b or "") for b in bodies):
450
463
  return (
451
464
  f"gh pr {sub} with a closing keyword (Closes/Fixes/Resolves #N) closes the issue on merge; "
@@ -468,10 +481,445 @@ def _jira_ask(argv, seg):
468
481
  return None
469
482
 
470
483
 
484
+ def _shared_ask(argv, seg):
485
+ """A comment, page or message posted to a system other people read."""
486
+ if not argv or os.path.basename(argv[0]) not in HTTP_CLIENTS:
487
+ return None
488
+ if not SHARED_WRITE.search(seg):
489
+ return None
490
+ if WRITE_METHOD.search(seg) or BODY_FLAG.search(seg):
491
+ return "this posts to a shared system (Jira comment, Confluence page or Slack); the user confirms each post"
492
+ return None
493
+
494
+
495
+ def _payload(argv, cwd):
496
+ """The request body a curl-like command sends, read from @file when given."""
497
+ for i, a in enumerate(argv):
498
+ val = None
499
+ if a in DATA_FLAGS and i + 1 < len(argv):
500
+ val = argv[i + 1]
501
+ else:
502
+ for f in DATA_FLAGS:
503
+ if a.startswith(f + "="):
504
+ val = a.split("=", 1)[1]
505
+ if val is not None:
506
+ return read_bounded(val[1:], cwd) if val.startswith("@") else val
507
+ return None
508
+
509
+
510
+ def pr_update_reason(toks, seg, cwd=None):
511
+ """A Bitbucket pull request PUT replaces the whole record: a body without
512
+ reviewers, fromRef or toRef wipes them."""
513
+ _, argv = command_word(toks)
514
+ if not argv or os.path.basename(argv[0]) not in HTTP_CLIENTS:
515
+ return None
516
+ if not PR_UPDATE.search(seg) or not PUT_METHOD.search(seg):
517
+ return None
518
+ body = _payload(argv, cwd)
519
+ try:
520
+ doc = json.loads(body) if body else None
521
+ except ValueError:
522
+ doc = None
523
+ if not isinstance(doc, dict):
524
+ return "this pull request update sends no readable JSON body; send the full record with reviewers, fromRef and toRef"
525
+ missing = [k for k in ("fromRef", "toRef") if not doc.get(k)]
526
+ if not doc.get("reviewers"):
527
+ missing.insert(0, "reviewers")
528
+ if missing:
529
+ return (
530
+ "a pull request PUT replaces the record, and this body has no "
531
+ + ", ".join(missing)
532
+ + "; read the current pull request and send its reviewers, fromRef, toRef and draft back"
533
+ )
534
+ return None
535
+
536
+
537
+ AGENT_NAME = re.compile(r"^[A-Za-z0-9._:-]{1,80}$")
538
+ GH_WRITE_SUBS = {
539
+ ("pr", "create"), ("pr", "edit"), ("pr", "comment"), ("pr", "review"), ("pr", "merge"),
540
+ ("pr", "close"), ("pr", "ready"), ("issue", "create"), ("issue", "edit"),
541
+ ("issue", "comment"), ("issue", "close"), ("issue", "reopen"), ("release", "create"),
542
+ }
543
+
544
+
545
+ def read_only_agent(agent_type, env=None):
546
+ """True when the subagent's definition denies it the Write tool: it reviews,
547
+ reads or reports, and anything it does outside its answer is a side effect."""
548
+ if not isinstance(agent_type, str) or not AGENT_NAME.match(agent_type):
549
+ return False
550
+ env = os.environ if env is None else env
551
+ home = env.get("HOME") or os.path.expanduser("~")
552
+ name = agent_type.split(":")[-1]
553
+ for root in (os.path.join(home, ".claude", "agents"), os.path.join(os.getcwd(), ".claude", "agents")):
554
+ text = read_bounded(os.path.join(root, name + ".md"), None)
555
+ if not text.startswith("---"):
556
+ continue
557
+ head = text.split("---", 2)[1] if text.count("---") >= 2 else ""
558
+ m = re.search(r"^disallowedTools:\s*(.+)$", head, re.MULTILINE)
559
+ return bool(m and re.search(r"\bWrite\b", m.group(1)))
560
+ return False
561
+
562
+
563
+ def subagent_side_effect(cmd):
564
+ """What in this command reaches past a read-only subagent's answer, or None."""
565
+ stripped, _ = strip_heredoc_bodies(cmd)
566
+ if re.search(r"write-state\.mjs", stripped):
567
+ return "it writes run state (write-state.mjs)"
568
+ for seg in split_simple(stripped):
569
+ toks = tokens(seg)
570
+ split = _git_split(toks)
571
+ if split and split[2] in WRITE_SUBS:
572
+ return f"it runs git {split[2]}"
573
+ _, argv = command_word(toks)
574
+ if not argv:
575
+ continue
576
+ base = os.path.basename(argv[0])
577
+ if base in HTTP_CLIENTS and (WRITE_METHOD.search(seg) or BODY_FLAG.search(seg)):
578
+ return f"it sends a write request with {base}"
579
+ if base == "gh":
580
+ plains = [a for a in argv[1:] if not a.startswith("-")]
581
+ if len(plains) >= 2 and (GH_ALIASES.get(plains[0], plains[0]), plains[1]) in GH_WRITE_SUBS:
582
+ return f"it runs gh {plains[0]} {plains[1]}"
583
+ if plains[:1] == ["api"] and (
584
+ re.search(r"(?:^|\s)(?:-X|--method)\s*=?\s*(?:post|put|patch|delete)\b", seg, re.I)
585
+ or re.search(r"(?:^|\s)(?:-f|-F|--field|--raw-field|--input)(?:\s|=)", seg)
586
+ ):
587
+ return "it writes through gh api"
588
+ return None
589
+
590
+
591
+ CLEANUP_SCRIPTS = {"purge.sh": None, "prune-logs.sh": "--yes", "run-kill.mjs": "apply"}
592
+
593
+
594
+ def _cleanup_ask(argv):
595
+ """purge, prune-logs --yes and run-kill apply delete run history for good."""
596
+ if not argv:
597
+ return None
598
+ rest = argv
599
+ if os.path.basename(argv[0]) in ("bash", "sh", "zsh", "node"):
600
+ rest = argv[1:]
601
+ if not rest:
602
+ return None
603
+ name = os.path.basename(rest[0])
604
+ if name not in CLEANUP_SCRIPTS:
605
+ return None
606
+ needs = CLEANUP_SCRIPTS[name]
607
+ if needs and needs not in rest[1:]:
608
+ return None
609
+ return f"{name} deletes run logs or worktrees for good; the user confirms each clean-up"
610
+
611
+
471
612
  def ask_reason(toks, seg, cwd=None):
472
613
  """Why a person should confirm this segment, or None."""
473
614
  _, argv = command_word(toks)
474
- return _gh_ask(argv, cwd) or _jira_ask(argv, seg)
615
+ return _gh_ask(argv, cwd) or _jira_ask(argv, seg) or _shared_ask(argv, seg) or _cleanup_ask(argv)
616
+
617
+
618
+ GH_TEXT_SUBS = {
619
+ ("pr", "create"), ("pr", "edit"), ("pr", "comment"), ("pr", "review"),
620
+ ("issue", "create"), ("issue", "edit"), ("issue", "comment"),
621
+ }
622
+
623
+
624
+ def gh_text(toks, cwd=None):
625
+ """The title and body text a gh pull request or issue write publishes."""
626
+ _, argv = command_word(toks)
627
+ if not argv or os.path.basename(argv[0]) != "gh":
628
+ return ""
629
+ args = [a for a in argv[1:]]
630
+ plains = [a for a in args if not a.startswith("-")]
631
+ if len(plains) < 2:
632
+ return ""
633
+ if (GH_ALIASES.get(plains[0], plains[0]), plains[1]) not in GH_TEXT_SUBS:
634
+ return ""
635
+ parts = _flag_values(args, {"--body", "--title"}, "-b") + _flag_values(args, {"--title"}, "-t")
636
+ parts += [read_bounded(p, cwd) for p in _flag_values(args, {"--body-file"}, "-F")]
637
+ return "\n".join(p for p in parts if p)
638
+
639
+
640
+ def vercel_token_reason(toks):
641
+ """A --token on the vercel command line lands in the transcript."""
642
+ _, argv = command_word(toks)
643
+ if not argv:
644
+ return None
645
+ words = [os.path.basename(argv[0])] + argv[1:3]
646
+ if "vercel" not in words:
647
+ return None
648
+ if any(a in ("--token", "-t") or a.startswith("--token=") for a in argv[1:]):
649
+ return (
650
+ "vercel with --token puts the token on the command line; run it through the "
651
+ "vercel wrapper, which reads the token from the credential store"
652
+ )
653
+ return None
654
+
655
+
656
+ SECRET_READ = re.compile(
657
+ r"credential-store\.sh['\"]?\s+get\b|\bsecurity\s+find-(?:generic|internet)-password\b[^;&|\n]*\s-w\b"
658
+ )
659
+
660
+
661
+ def secret_echo_reason(cmd):
662
+ """A stored secret read with nothing capturing or consuming its output prints
663
+ it into the transcript."""
664
+ stripped, _ = strip_heredoc_bodies(cmd)
665
+ for m in SECRET_READ.finditer(stripped):
666
+ before = stripped[: m.start()]
667
+ if before.count("$(") > before.count(")") or before.count("`") % 2:
668
+ continue
669
+ tail = re.split(r";|&&|\|\||\n", stripped[m.end():], maxsplit=1)[0]
670
+ if re.search(r"(?<!\|)\|(?!\|)|>", tail):
671
+ continue
672
+ return (
673
+ "this prints a stored secret into the transcript; capture it in a variable "
674
+ "(VAR=\"$(...)\"), pipe it into the consumer, or send it to /dev/null"
675
+ )
676
+ return None
677
+
678
+
679
+ # --- the run this session drives ----------------------------------------------
680
+
681
+
682
+ def _logs_root(env=None):
683
+ env = os.environ if env is None else env
684
+ return env.get("LOGS_ROOT") or os.path.join(os.path.expanduser("~"), ".claude", "logs", "multi-agent")
685
+
686
+
687
+ LIVE_STATUSES = ("in_progress", "awaiting_input")
688
+ _DRIVING = {}
689
+
690
+
691
+ def driving_run(session_id, env=None):
692
+ """The live run whose hostSessionId is this session, or None: status
693
+ in_progress or awaiting_input (the statuses _active-run.mjs treats as
694
+ live), the most recently written one when several match. Read once per
695
+ hook process.
696
+
697
+ Runs live at <root>/<project>/<task>/agent-state.json or <root>/<task>/; both
698
+ depths are read. A missing session id matches nothing."""
699
+ if not isinstance(session_id, str) or not session_id:
700
+ return None
701
+ root = _logs_root(env)
702
+ key = (root, session_id)
703
+ if key not in _DRIVING:
704
+ _DRIVING[key] = _scan_driving(root, session_id)
705
+ return _DRIVING[key]
706
+
707
+
708
+ def _scan_driving(root, session_id):
709
+ candidates = []
710
+ try:
711
+ for a in os.listdir(root):
712
+ pa = os.path.join(root, a)
713
+ if not os.path.isdir(pa):
714
+ continue
715
+ candidates.append(os.path.join(pa, RUN_STATE))
716
+ try:
717
+ for b in os.listdir(pa):
718
+ candidates.append(os.path.join(pa, b, RUN_STATE))
719
+ except OSError:
720
+ continue
721
+ except OSError:
722
+ return None
723
+ best, best_t = None, -1.0
724
+ for path in candidates:
725
+ try:
726
+ st = os.stat(path)
727
+ if st.st_size > 4 * 1024 * 1024:
728
+ continue
729
+ with open(path, encoding="utf-8") as fh:
730
+ doc = json.load(fh)
731
+ except (OSError, ValueError):
732
+ continue
733
+ if not isinstance(doc, dict) or doc.get("hostSessionId") != session_id:
734
+ continue
735
+ if doc.get("status") in LIVE_STATUSES and st.st_mtime > best_t:
736
+ best, best_t = doc, st.st_mtime
737
+ return best
738
+
739
+
740
+ def run_commit_reason(doc):
741
+ """Why a commit in the attended run this session drives needs the user's
742
+ yes (rules.md, "No auto-commit"), or None for no run or an unattended one."""
743
+ if not isinstance(doc, dict):
744
+ return None
745
+ launch = doc.get("launchRequest") if isinstance(doc.get("launchRequest"), dict) else {}
746
+ if doc.get("autopilot") is True or launch.get("mode") in ("autopilot", "background"):
747
+ return None
748
+ return (
749
+ "this commits inside the pipeline run this session is driving (" + str(doc.get("taskId") or "?")
750
+ + "); a commit waits for the user's explicit yes"
751
+ )
752
+
753
+
754
+ # --- output format inside a run --------------------------------------------------
755
+
756
+ # A repo profile may put the ticket first in any of its styles: [KEY-1], (KEY-1),
757
+ # KEY-1:, #1 (repo-profile-derive.mjs PREFIX_RE). The type and scope follow the
758
+ # same reader's CONV_RE: a lowercase word, any parenthesised scope.
759
+ TICKET_PREFIX = r"(\s*[\[(]?\s*(?:[A-Z][A-Z0-9]+-\d+|#\d+)\s*[\])]?:?\s+)?"
760
+ COMMIT_SUBJECT = re.compile(r"^" + TICKET_PREFIX + r"[a-z]+(\([^)]+\))?!?:\s+\S")
761
+ SUBJECT_MAX = 72
762
+ SKIPPED_SUBJECTS = re.compile(r"^(Merge |Revert \"|fixup! |squash! |amend! )|^\s*wip\b", re.I)
763
+ ENTITY = re.compile(r"&(amp|lt|gt|quot|#39|#x27);")
764
+ FENCE = re.compile(r"```.*?(```|$)", re.S)
765
+ INLINE = re.compile(r"`[^`\n]*`")
766
+ JIRA_CODE = re.compile(r"\{(code|noformat)[^}]*\}.*?(\{\1\}|$)", re.S)
767
+ # A literal backslash-n meant as a line break sits between two pieces of
768
+ # text: glued to the word before it and followed by more text
769
+ # ("Summary\nThe change", "Changes:\n- a"), or after a space before a
770
+ # capital ("Summary \nNext"). One ending a token (printf hello\n), quoted or
771
+ # bracketed is a mention of the escape; \r\n and drive paths (C:\Users\name)
772
+ # are removed before the search.
773
+ LOST_BREAK = re.compile(r"(?<=[^\s\\'\"`(\[])\\n(?=[^\s'\"`)\]])|(?<=\s)\\n(?=[A-Z])")
774
+ CRLF = re.compile(r"\\r\\n")
775
+ DRIVE_PATH = re.compile(r"\b[A-Za-z]:\\\S*")
776
+
777
+
778
+ def _subject(message):
779
+ """The first line of a commit message that carries text. A heredoc message
780
+ (`-m "$(cat <<EOF ...)"`) reaches the hook with the opening quote on its
781
+ own line, ahead of the body."""
782
+ for line in (message or "").split("\n"):
783
+ if re.search(r"[A-Za-z0-9]", line):
784
+ return line.strip()
785
+ return ""
786
+
787
+
788
+ def profile_conventional(cwd):
789
+ """False when the repo's stored profile says its subjects are not
790
+ conventional, else True (no profile, unreadable, or conventional)."""
791
+ script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "repo-profile.mjs")
792
+ if not os.path.isfile(script):
793
+ return True
794
+ try:
795
+ import subprocess
796
+
797
+ r = subprocess.run(
798
+ ["node", script, "resolve", "commit.format", "--repo", cwd or os.getcwd()],
799
+ capture_output=True, text=True, timeout=3,
800
+ )
801
+ doc = json.loads(r.stdout) if r.returncode == 0 and r.stdout.strip() else {}
802
+ except (OSError, ValueError, Exception):
803
+ return True
804
+ value = doc.get("value") if isinstance(doc, dict) and doc.get("use") else None
805
+ return not (isinstance(value, dict) and value.get("conventional") is False)
806
+
807
+
808
+ def commit_subject_reason(message, cwd=None):
809
+ """Why a commit subject breaks the commit convention (rules.md), or None.
810
+
811
+ Not judged: a subject the hook cannot read for certain (a shell expansion
812
+ in it), git's merge, revert, fixup and squash subjects, checkpoint commits
813
+ (wip ...), and the type check in a repo whose profile is not conventional."""
814
+ subject = _subject(message)
815
+ if not subject or "$" in subject or "`" in subject or SKIPPED_SUBJECTS.search(subject):
816
+ return None
817
+ if len(subject) > SUBJECT_MAX:
818
+ return "the commit subject is " + str(len(subject)) + " characters; the convention caps it at " + str(SUBJECT_MAX)
819
+ if not COMMIT_SUBJECT.match(subject) and profile_conventional(cwd):
820
+ return (
821
+ "the commit subject does not start with a conventional type, as in type(scope): description "
822
+ "(after a repo profile's ticket prefix if any)"
823
+ )
824
+ return None
825
+
826
+
827
+ def body_text_reason(text, entities=False):
828
+ """Why text published as a PR, issue or Jira body will render wrong, or
829
+ None: a literal backslash-n where a line break was meant, outside Markdown
830
+ and Jira code. With entities, an HTML entity too: a Jira payload renders
831
+ it verbatim, while Markdown on GitHub decodes it (and needs &lt; for a
832
+ literal angle bracket)."""
833
+ prose = DRIVE_PATH.sub(" ", CRLF.sub(" ", INLINE.sub("", FENCE.sub("", JIRA_CODE.sub("", text or "")))))
834
+ if LOST_BREAK.search(prose):
835
+ return "the body carries a literal \\n; write real line breaks (a heredoc or a body file)"
836
+ m = ENTITY.search(prose) if entities else None
837
+ if m:
838
+ return "the body carries the HTML entity " + m.group(0) + "; write the character itself"
839
+ return None
840
+
841
+
842
+ def _json_strings(value, out):
843
+ """The prose strings of a JSON body, leaving out ADF code blocks and text
844
+ marked as code."""
845
+ if isinstance(value, str):
846
+ out.append(value)
847
+ elif isinstance(value, dict):
848
+ if value.get("type") == "codeBlock":
849
+ return out
850
+ marks = value.get("marks")
851
+ if isinstance(marks, list) and any(isinstance(m, dict) and m.get("type") == "code" for m in marks):
852
+ return out
853
+ for v in value.values():
854
+ _json_strings(v, out)
855
+ elif isinstance(value, list):
856
+ for v in value:
857
+ _json_strings(v, out)
858
+ return out
859
+
860
+
861
+ def payload_files(argv):
862
+ """Files a request sends as its body: curl -d/--data/--data-binary/--json
863
+ @file and --data-urlencode [name]@file, and gh api --input file and
864
+ -F/--field name=@file. "@-" (stdin) is the caller's to resolve."""
865
+ if argv and os.path.basename(argv[0]) == "gh" and len(argv) > 1 and argv[1] == "api":
866
+ out, i = [], 2
867
+ while i < len(argv):
868
+ a = argv[i]
869
+ if a == "--input" and i + 1 < len(argv):
870
+ out.append(argv[i + 1])
871
+ i += 1
872
+ elif a in ("-F", "--field") and i + 1 < len(argv):
873
+ v = argv[i + 1]
874
+ if "=@" in v and not v.endswith("=@-"):
875
+ out.append(v.split("=@", 1)[1])
876
+ i += 1
877
+ i += 1
878
+ return out
879
+ if not argv or os.path.basename(argv[0]) not in HTTP_CLIENTS:
880
+ return []
881
+ out, i = [], 1
882
+ while i < len(argv):
883
+ a = argv[i]
884
+ val = None
885
+ if a in ("-d", "--data", "--data-binary", "--data-ascii", "--json") and i + 1 < len(argv):
886
+ val = argv[i + 1]
887
+ i += 1
888
+ elif a.startswith(("--data=", "--data-binary=", "--data-ascii=", "--json=")):
889
+ val = a.split("=", 1)[1]
890
+ elif a == "--data-urlencode" and i + 1 < len(argv):
891
+ m = re.match(r"^[A-Za-z0-9_.-]*@(.+)$", argv[i + 1])
892
+ i += 1
893
+ val = "@" + m.group(1) if m else None
894
+ if val and val.startswith("@") and len(val) > 1 and val != "@-":
895
+ out.append(val[1:])
896
+ i += 1
897
+ return out
898
+
899
+
900
+ def stdin_payload(argv):
901
+ """True when the request reads its body from stdin (-d @-, --data-binary @-, --input -)."""
902
+ return "@-" in argv or (len(argv) > 2 and argv[1] == "api" and "--input" in argv and "-" in argv)
903
+
904
+
905
+ # Jira issue and comment endpoints only: Bitbucket Server (/rest/api/1.0/)
906
+ # takes Markdown and Confluence storage format needs entities.
907
+ JIRA_API = re.compile(r"/rest/api/(?:2|3|latest)/(?:issue|comment)\b", re.I)
908
+
909
+
910
+ def payload_reason(path, cwd, entities=True, raw=None):
911
+ """body_text_reason over a request body file (or raw text). A JSON body is
912
+ judged on its decoded string values, where an escaped newline is a real
913
+ line break. Entities are judged only for a Jira target, which renders
914
+ them verbatim; GitHub and Bitbucket Markdown decode them."""
915
+ raw = read_bounded(path, cwd) if raw is None else raw
916
+ if not raw:
917
+ return None
918
+ try:
919
+ text = "\n".join(_json_strings(json.loads(raw), []))
920
+ except ValueError:
921
+ text = raw
922
+ return body_text_reason(text, entities=entities)
475
923
 
476
924
 
477
925
  # --- confirmations ---------------------------------------------------------------
@@ -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
  }
@@ -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: