@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,463 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @file office-remote-client.mjs - the machine end of the office remote
4
+ * layer (features/office-remote-protocol.md).
5
+ *
6
+ * It polls the relay for frames, opens each with the paired device's key,
7
+ * checks the passkey tier of the request inside, forwards an accepted request
8
+ * to the local server's phone routes and seals the response back. The local
9
+ * server still verifies the device signature, window, nonce and scopes; this
10
+ * file adds what the relay path needs on top:
11
+ *
12
+ * - a frame opens only with its device's key, in its direction, with a
13
+ * counter above the last one accepted from that device;
14
+ * - the target is a phone route (`/v1/phone/`), the method GET or POST, the
15
+ * device header names the device the frame came from;
16
+ * - the tier: `read` (GET) needs a passkey session that has not been idle for
17
+ * more than READ_IDLE_MS; `low` (an answer) needs a passkey within
18
+ * LOW_WINDOW_MS; `high` (launch and every other POST) needs a passkey
19
+ * assertion bound to this very request (its challenge is SHA-256 of the
20
+ * request's canonical signed text);
21
+ * - no `Authorization` header is ever added, and `Host` is the loopback.
22
+ *
23
+ * A refused frame still gets a sealed reply with the reason, so the phone can
24
+ * show it. One audit line per frame: time, device, counter, path without
25
+ * query, tier, verdict and reason; never a body, header value or signature.
26
+ *
27
+ * Config: <phone dir>/remote.json (0600), written by office-pair. Remote is off
28
+ * unless that file exists and the office server is running.
29
+ *
30
+ * Usage: office-remote-client.mjs once | loop
31
+ *
32
+ * @module pipeline/scripts/office-remote-client
33
+ */
34
+
35
+ import { createHash } from "node:crypto";
36
+ import {
37
+ appendFileSync,
38
+ chmodSync,
39
+ closeSync,
40
+ existsSync,
41
+ mkdirSync,
42
+ openSync,
43
+ readFileSync,
44
+ renameSync,
45
+ rmSync,
46
+ statSync,
47
+ writeFileSync,
48
+ } from "node:fs";
49
+ import { request as httpRequest } from "node:http";
50
+ import { dirname, join } from "node:path";
51
+
52
+ import { directionKeys, openFrame, sealFrame, verifyPasskey } from "../lib/office-remote.mjs";
53
+ import { invokedDirectly } from "../lib/invoked-directly.mjs";
54
+ import {
55
+ bodyDigest,
56
+ canonicalPayload,
57
+ DEVICE_ID,
58
+ HEADERS,
59
+ PHONE_PREFIX,
60
+ phoneDir,
61
+ } from "./_phone-auth.mjs";
62
+
63
+ export const READ_IDLE_MS = 5 * 60_000;
64
+ export const LOW_WINDOW_MS = 2 * 60_000;
65
+ const POLL_TIMEOUT_MS = 30_000;
66
+ const MAX_BODY = 256 * 1024;
67
+ const PASSKEY_HEADER = "x-ma-passkey";
68
+ const FORWARDED = new Set([...Object.values(HEADERS), "content-type", "accept"]);
69
+ const AUDIT_MAX_BYTES = 1024 * 1024;
70
+
71
+ export const configPath = (env = process.env) => join(phoneDir(env), "remote.json");
72
+ export const framesLogPath = (env = process.env) => join(phoneDir(env), "remote-frames.jsonl");
73
+ export const lockPath = (env = process.env) => join(phoneDir(env), "remote-client.lock");
74
+
75
+ /**
76
+ * The target's path when it is already in the canonical form the local
77
+ * server routes on, else null. The server parses the target as a URL, which
78
+ * decodes dot segments (`%2e%2e`), turns `\` into `/` and drops a fragment;
79
+ * a target those steps would change is refused here, so the tier and the
80
+ * prefix are judged on the same path the server executes.
81
+ */
82
+ export function canonicalPath(target) {
83
+ if (typeof target !== "string" || !target.startsWith("/") || /%2e|\\|#/i.test(target))
84
+ return null;
85
+ let u;
86
+ try {
87
+ u = new URL(target, "http://127.0.0.1");
88
+ } catch {
89
+ return null;
90
+ }
91
+ return u.pathname + u.search === target ? u.pathname : null;
92
+ }
93
+
94
+ /** The passkey tier of a phone route. Anything not known to be milder is high. */
95
+ export function tierOf(method, pathname) {
96
+ if (method === "GET" && /^\/v1\/phone\/runs(\/[^/]+)?$/.test(pathname)) return "read";
97
+ if (method === "POST" && /^\/v1\/phone\/runs\/[^/]+\/answer$/.test(pathname)) return "low";
98
+ return "high";
99
+ }
100
+
101
+ const lower = (headers) =>
102
+ Object.fromEntries(
103
+ Object.entries(headers && typeof headers === "object" ? headers : {}).map(([k, v]) => [
104
+ String(k).toLowerCase(),
105
+ String(v),
106
+ ]),
107
+ );
108
+
109
+ /** The challenge a passkey must sign for this request. */
110
+ export function requestChallenge(req, headers, body) {
111
+ const text = canonicalPayload({
112
+ method: req.method,
113
+ target: req.target,
114
+ bodyDigest: bodyDigest(body),
115
+ deviceId: headers[HEADERS.device],
116
+ timestamp: headers[HEADERS.timestamp],
117
+ nonce: headers[HEADERS.nonce],
118
+ });
119
+ return createHash("sha256").update(text, "utf8").digest();
120
+ }
121
+
122
+ /**
123
+ * Whether the request's tier is satisfied, updating the device's passkey
124
+ * record when an assertion verifies.
125
+ *
126
+ * @returns {{ok: true} | {ok: false, reason: string}}
127
+ */
128
+ export function passkeyDecision(device, tier, req, headers, body, now) {
129
+ const raw = headers[PASSKEY_HEADER];
130
+ if (raw) {
131
+ let assertion;
132
+ try {
133
+ assertion = JSON.parse(raw);
134
+ } catch {
135
+ return { ok: false, reason: "the passkey header is not JSON" };
136
+ }
137
+ let challenge;
138
+ try {
139
+ challenge = requestChallenge(req, headers, body);
140
+ } catch (err) {
141
+ return { ok: false, reason: err.message };
142
+ }
143
+ const pk = device.passkey || {};
144
+ const v = verifyPasskey({
145
+ assertion,
146
+ credential: pk.credential,
147
+ rpId: pk.rpId,
148
+ origin: pk.origin,
149
+ challenge,
150
+ prevSignCount: Number(pk.signCount) || 0,
151
+ });
152
+ if (!v.ok) return v;
153
+ device.passkey = { ...pk, signCount: v.signCount };
154
+ device.lastPasskeyAt = now;
155
+ device.lastActivityAt = now;
156
+ return { ok: true };
157
+ }
158
+ if (tier === "high") return { ok: false, reason: "this action needs a passkey for this request" };
159
+ const last = Number(device.lastPasskeyAt) || 0;
160
+ if (tier === "low" && now - last > LOW_WINDOW_MS) {
161
+ return { ok: false, reason: "confirm with the passkey again (the low-risk window has passed)" };
162
+ }
163
+ if (tier === "read" && (!last || now - (Number(device.lastActivityAt) || 0) > READ_IDLE_MS)) {
164
+ return { ok: false, reason: "the session is locked; open it with the passkey" };
165
+ }
166
+ device.lastActivityAt = now;
167
+ return { ok: true };
168
+ }
169
+
170
+ function keysFor(config, device) {
171
+ return directionKeys({
172
+ privatePem: config.machinePrivatePem,
173
+ peerPublic: device.sessionPublic,
174
+ pairingId: config.pairingId,
175
+ role: "machine",
176
+ });
177
+ }
178
+
179
+ function reply(config, device, inReplyTo, response) {
180
+ device.sendCounter = (Number(device.sendCounter) || 0) + 1;
181
+ const sealed = sealFrame(keysFor(config, device).send, device.sendCounter, "machine-to-phone", {
182
+ inReplyTo,
183
+ ...response,
184
+ });
185
+ return { deviceId: device.id, ...sealed };
186
+ }
187
+
188
+ const refuse = (status, reason) => ({
189
+ status,
190
+ headers: { "content-type": "application/json" },
191
+ body: Buffer.from(JSON.stringify({ error: "remote-refused", reason })).toString("base64url"),
192
+ });
193
+
194
+ /**
195
+ * Open, judge and forward one frame. Mutates the device's counters and passkey
196
+ * record in `config`; returns the sealed reply (or null for a frame that could
197
+ * not be opened at all) and the audit row.
198
+ */
199
+ export async function handleFrame(config, frame, { forward, now = Date.now() }) {
200
+ const id =
201
+ typeof frame?.deviceId === "string" && DEVICE_ID.test(frame.deviceId) ? frame.deviceId : null;
202
+ const device =
203
+ id && config.devices && Object.hasOwn(config.devices, id) ? config.devices[id] : null;
204
+ const row = {
205
+ at: new Date(now).toISOString(),
206
+ device: id,
207
+ counter: Number.isSafeInteger(frame?.counter) ? frame.counter : null,
208
+ };
209
+ if (!device || device.revoked)
210
+ return {
211
+ reply: null,
212
+ row: { ...row, verdict: "dropped", reason: "unknown or revoked device" },
213
+ };
214
+ const opened = openFrame(
215
+ keysFor(config, device).recv,
216
+ frame,
217
+ "phone-to-machine",
218
+ device.recvCounter,
219
+ );
220
+ if (!opened.ok)
221
+ return { reply: null, row: { ...row, verdict: "dropped", reason: opened.reason } };
222
+ device.recvCounter = opened.counter;
223
+ const req = opened.value || {};
224
+ const target = typeof req.target === "string" ? req.target : "";
225
+ const canonical = canonicalPath(target);
226
+ const path = canonical ?? "(not canonical)";
227
+ const out = (status, reason, extra = {}) => ({
228
+ reply: reply(config, device, opened.counter, refuse(status, reason)),
229
+ row: { ...row, path, verdict: "refused", reason, ...extra },
230
+ });
231
+ if (!["GET", "POST"].includes(req.method)) return out(405, "only GET and POST are carried");
232
+ if (canonical === null) return out(400, "the target is not in canonical form");
233
+ if (!canonical.startsWith(PHONE_PREFIX)) return out(403, "only phone routes are carried");
234
+ const headers = lower(req.headers);
235
+ if (headers[HEADERS.device] !== device.id)
236
+ return out(403, "the signed device is not the paired device");
237
+ const body = Buffer.from(typeof req.body === "string" ? req.body : "", "base64url");
238
+ if (body.length > MAX_BODY) return out(413, "the body is too large");
239
+ const tier = tierOf(req.method, path);
240
+ const pass = passkeyDecision(device, tier, req, headers, body, now);
241
+ if (!pass.ok) return out(401, pass.reason, { tier });
242
+ const forwarded = Object.fromEntries(Object.entries(headers).filter(([k]) => FORWARDED.has(k)));
243
+ const response = await forward({ method: req.method, target, headers: forwarded, body });
244
+ return {
245
+ reply: reply(config, device, opened.counter, response),
246
+ row: { ...row, path, tier, verdict: "forwarded", status: response.status },
247
+ };
248
+ }
249
+
250
+ /** Forward to the local server: loopback Host, no Authorization, body bytes untouched. */
251
+ export function localForwarder(baseUrl) {
252
+ const base = new URL(baseUrl);
253
+ return ({ method, target, headers, body }) =>
254
+ new Promise((resolve) => {
255
+ const req = httpRequest(
256
+ {
257
+ host: "127.0.0.1",
258
+ port: base.port,
259
+ method,
260
+ path: target,
261
+ headers: { ...headers, host: `127.0.0.1:${base.port}`, "content-length": body.length },
262
+ timeout: POLL_TIMEOUT_MS,
263
+ },
264
+ (res) => {
265
+ const chunks = [];
266
+ let size = 0;
267
+ res.on("data", (c) => {
268
+ size += c.length;
269
+ if (size <= MAX_BODY) chunks.push(c);
270
+ });
271
+ res.on("end", () =>
272
+ resolve({
273
+ status: res.statusCode || 502,
274
+ headers: { "content-type": res.headers["content-type"] || "application/json" },
275
+ body: Buffer.concat(chunks).toString("base64url"),
276
+ }),
277
+ );
278
+ },
279
+ );
280
+ req.on("error", () => resolve(refuse(502, "the local server did not answer")));
281
+ req.on("timeout", () => req.destroy());
282
+ req.end(body);
283
+ });
284
+ }
285
+
286
+ export function readConfig(env = process.env) {
287
+ try {
288
+ return JSON.parse(readFileSync(configPath(env), "utf8"));
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
293
+
294
+ export function writeConfig(config, env = process.env) {
295
+ const file = configPath(env);
296
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
297
+ const tmp = `${file}.${process.pid}.tmp`;
298
+ writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
299
+ chmodSync(tmp, 0o600);
300
+ renameSync(tmp, file);
301
+ }
302
+
303
+ function audit(row, env) {
304
+ try {
305
+ const file = framesLogPath(env);
306
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
307
+ if (existsSync(file) && statSync(file).size > AUDIT_MAX_BYTES) renameSync(file, `${file}.1`);
308
+ appendFileSync(file, JSON.stringify(row) + "\n", { mode: 0o600 });
309
+ } catch {
310
+ /* an audit line that cannot be written never changes a verdict */
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Write this poll's counters and passkey records onto the config as it is on
316
+ * disk now, so a revoke or a new pairing made while the poll ran is kept: a
317
+ * device that is gone from the file stays gone, and a different pairing id
318
+ * means the file was re-paired and this poll's state is dropped.
319
+ */
320
+ function persist(config, env) {
321
+ const fresh = readConfig(env);
322
+ if (!fresh || fresh.pairingId !== config.pairingId) return;
323
+ for (const [id, d] of Object.entries(config.devices ?? {})) {
324
+ if (!fresh.devices || !Object.hasOwn(fresh.devices, id)) continue;
325
+ const { recvCounter, sendCounter, passkey, lastPasskeyAt, lastActivityAt } = d;
326
+ fresh.devices[id] = {
327
+ ...fresh.devices[id],
328
+ recvCounter,
329
+ sendCounter,
330
+ passkey: { ...fresh.devices[id].passkey, signCount: passkey?.signCount ?? 0 },
331
+ lastPasskeyAt,
332
+ lastActivityAt,
333
+ };
334
+ }
335
+ writeConfig(fresh, env);
336
+ }
337
+
338
+ function holderAlive(pid) {
339
+ if (!Number.isInteger(pid) || pid <= 0) return false;
340
+ try {
341
+ process.kill(pid, 0);
342
+ return true;
343
+ } catch (e) {
344
+ return e.code === "EPERM";
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Hold the single-instance lock: two clients would seal replies from the same
350
+ * counter, reusing an AES-GCM nonce. A lock whose process is gone is taken over.
351
+ *
352
+ * @returns {(() => void) | null} the release function, or null when another client holds it
353
+ */
354
+ export function acquireLock(env = process.env) {
355
+ const file = lockPath(env);
356
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
357
+ for (let attempt = 0; attempt < 2; attempt++) {
358
+ try {
359
+ const fd = openSync(file, "wx", 0o600);
360
+ writeFileSync(fd, String(process.pid));
361
+ closeSync(fd);
362
+ return () => rmSync(file, { force: true });
363
+ } catch (err) {
364
+ if (err.code !== "EEXIST") throw err;
365
+ if (holderAlive(Number(readFileSync(file, "utf8")))) return null;
366
+ rmSync(file, { force: true });
367
+ }
368
+ }
369
+ return null;
370
+ }
371
+
372
+ /**
373
+ * One poll: fetch frames, handle each, post the replies, persist counters.
374
+ *
375
+ * @returns {Promise<{handled: number}>}
376
+ */
377
+ export async function runOnce({
378
+ config,
379
+ fetchImpl = fetch,
380
+ forward,
381
+ env = process.env,
382
+ now = Date.now,
383
+ }) {
384
+ const base = String(config.relayUrl).replace(/\/+$/, "");
385
+ const auth = { Authorization: `Bearer ${config.relayToken}` };
386
+ const res = await fetchImpl(
387
+ `${base}/api/office/relay/machine?pairing=${encodeURIComponent(config.pairingId)}`,
388
+ {
389
+ headers: auth,
390
+ signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
391
+ },
392
+ );
393
+ if (!res.ok) throw new Error(`the relay answered ${res.status}`);
394
+ const { frames = [] } = await res.json();
395
+ const replies = [];
396
+ for (const frame of Array.isArray(frames) ? frames : []) {
397
+ const { reply: r, row } = await handleFrame(config, frame, { forward, now: now() });
398
+ audit(row, env);
399
+ if (r) replies.push(r);
400
+ }
401
+ persist(config, env);
402
+ if (replies.length) {
403
+ const post = await fetchImpl(
404
+ `${base}/api/office/relay/machine?pairing=${encodeURIComponent(config.pairingId)}`,
405
+ {
406
+ method: "POST",
407
+ headers: { ...auth, "content-type": "application/json" },
408
+ body: JSON.stringify({ frames: replies }),
409
+ signal: AbortSignal.timeout(POLL_TIMEOUT_MS),
410
+ },
411
+ );
412
+ if (!post.ok) throw new Error(`the relay refused the replies (${post.status})`);
413
+ }
414
+ return { handled: Array.isArray(frames) ? frames.length : 0 };
415
+ }
416
+
417
+ async function main(argv) {
418
+ const mode = argv[0] || "once";
419
+ if (!readConfig() || !existsSync(configPath())) {
420
+ process.stderr.write("office-remote-client: not paired (run office-pair first)\n");
421
+ return 1;
422
+ }
423
+ const { current } = await import("./office-control.mjs");
424
+ const server = current();
425
+ if (!server) {
426
+ process.stderr.write("office-remote-client: the office is not running (office-on)\n");
427
+ return 1;
428
+ }
429
+ const release = acquireLock();
430
+ if (!release) {
431
+ process.stderr.write("office-remote-client: another client is already running\n");
432
+ return 1;
433
+ }
434
+ const forward = localForwarder(server.url);
435
+ try {
436
+ do {
437
+ const config = readConfig();
438
+ if (!config?.pairingId) return 1;
439
+ try {
440
+ await runOnce({ config, forward });
441
+ } catch (err) {
442
+ process.stderr.write(`office-remote-client: ${err.message}\n`);
443
+ if (mode !== "loop") return 1;
444
+ await new Promise((r) => setTimeout(r, 5000));
445
+ }
446
+ } while (mode === "loop" && current());
447
+ return 0;
448
+ } finally {
449
+ release();
450
+ }
451
+ }
452
+
453
+ if (invokedDirectly(import.meta.url)) {
454
+ main(process.argv.slice(2)).then(
455
+ (code) => {
456
+ process.exitCode = code;
457
+ },
458
+ (err) => {
459
+ process.stderr.write(`office-remote-client: ${err.message}\n`);
460
+ process.exitCode = 1;
461
+ },
462
+ );
463
+ }
@@ -1123,6 +1123,14 @@ case "$ACTION" in
1123
1123
  mkdir -p "$TRACKER_DIR" 2>/dev/null
1124
1124
  NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1125
1125
  acquire_state_lock
1126
+ # A tracker that already holds phases is a run's history: a resume
1127
+ # continues it, and only an explicit --force starts it over.
1128
+ if [ "${2:-}" != "--force" ] && [ -f "$TRACKER_FILE" ] &&
1129
+ [ "$(jq -r '(.phases // []) | length' "$TRACKER_FILE" 2>/dev/null || echo 0)" != "0" ]; then
1130
+ release_state_lock
1131
+ echo "init: $TRACKER_FILE already holds phases; resume the run, or pass --force to start the tracker over" >&2
1132
+ exit 3
1133
+ fi
1126
1134
  save_state "$(jq -nc --arg id "$TASK_ID" --arg ts "$NOW" \
1127
1135
  '{task_id:$id,started_at:$ts,phases:[]}')"
1128
1136
  release_state_lock
@@ -1,43 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * phase0-exit-gate.mjs - Phase 0 may not be marked completed until it has
4
- * actually produced its own output.
4
+ * produced its own output.
5
5
  *
6
- * Why this exists. Phase 0 Step 7 says to classify the task and "persist to
7
- * `agent-state.json.taskType`", and Phase 3 branches on that field to route a
8
- * Figma-driven task to the component skills (`create-screen` / `create-component`
9
- * in the stack plugin) instead of generic development. On a real run the tracker
10
- * showed Phase 0 `completed` while the task directory held only
11
- * `tracker-state.json` - no `agent-state.json` at all. With no `taskType`, the
12
- * dispatch could not fire, so a Figma-driven screen was built as generic work: no
13
- * token-compliance check, no Code Connect publish, no 14-item component review,
14
- * and spacing guessed at 16 where the frame said `Spacing/12`. Half the commits on
15
- * that branch were rework.
6
+ * Phase 0 owns decisions every later phase reads back out of agent-state.json:
7
+ * the task type (Phase 3 routes a Figma-driven task to the component skills on
8
+ * it), the repository and base branch with the evidence for how the branch was
9
+ * chosen (`baseBranchSource`, `baseBranchEvidence.refProvenance` agreeing with
10
+ * `baseFetchStatus`), the dev-context siblings, the Figma access tier when the
11
+ * input carries a Figma URL, and, through require-supported-version.sh, that
12
+ * the installed pipeline is not below the published floor.
16
13
  *
17
- * A second run then showed the other half of the same failure: a Jira task was
18
- * implemented without the repo or branch picker ever appearing, straight on whatever
19
- * the local checkout was pointing at. Step 3 and Step 8 own those decisions, and
20
- * `agent-state.json` is the only durable evidence they happened - so the gate asserts
21
- * their output too, not just `taskType`.
22
- *
23
- * A third run got past that, because `baseBranch` and `baseFetchStatus` can be filled
24
- * in by a branch that was never chosen. The remote had one PR-targetable branch, the
25
- * one-option `AskUserQuestion` was refused by the host (its schema needs two), and the
26
- * run announced "only candidate, continuing with it" and carried on. The branch was
27
- * right; nothing was asked. `baseBranchSource` is the field that separates those two,
28
- * and the dev-context picker never ran at all - no `siblings`, so Phase 4's parity
29
- * cross-check had nothing to read and could not tell an empty answer from no answer.
30
- *
31
- * A fourth showed the same shape one layer down: `git fetch origin` failed on a
32
- * restricted network, `git branch -r` printed the remote-tracking cache anyway, and a
33
- * weeks-old local list was presented as the remote's answer. Degrading to local refs is
34
- * correct; reporting them as remote is not, so `baseBranchEvidence.refProvenance` has to
35
- * agree with `baseFetchStatus`.
36
- *
37
- * A phase that reports success without its output is worse than one that fails:
38
- * every later phase then reasons from a field that is not there. So this is a
39
- * gate, not a lint - the spec already said what to write, and prose alone did
40
- * not make it happen.
14
+ * A phase that reports success without its output is worse than one that
15
+ * fails: later phases then reason from fields that are not there. So this is a
16
+ * gate on the output, not on the phase's status.
41
17
  *
42
18
  * Usage:
43
19
  * node phase0-exit-gate.mjs <task_id> [--input "<original user input>"] [--json]
@@ -48,9 +24,10 @@
48
24
  */
49
25
 
50
26
  import { existsSync, readFileSync } from "fs";
51
- import { join } from "path";
27
+ import { dirname, join } from "path";
52
28
  import { homedir } from "os";
53
- import { pathToFileURL } from "url";
29
+ import { fileURLToPath, pathToFileURL } from "url";
30
+ import { spawnSync } from "child_process";
54
31
 
55
32
  import { gatesActive } from "../lib/unattended.mjs";
56
33
 
@@ -337,6 +314,27 @@ export function evaluate(state, extraInput = "", { env = process.env } = {}) {
337
314
  return { ok: failures.length === 0, failures, taskType: taskType || undefined, figmaSeen };
338
315
  }
339
316
 
317
+ /**
318
+ * The failure line when the installed pipeline is below the published floor,
319
+ * or null. Asks require-supported-version.sh with --cache-only: the answer
320
+ * Step 0.6 cached, at any age, and never the network, so the gate costs no
321
+ * wait and passes when nothing is cached.
322
+ *
323
+ * @param {{env?: NodeJS.ProcessEnv}} [opts]
324
+ * @returns {string|null}
325
+ */
326
+ export function versionFloorFailure({ env = process.env } = {}) {
327
+ const script = join(dirname(fileURLToPath(import.meta.url)), "require-supported-version.sh");
328
+ if (!existsSync(script)) return null;
329
+ const r = spawnSync("bash", [script, "--cache-only"], { env, encoding: "utf8", timeout: 10_000 });
330
+ if (r.status !== 3) return null;
331
+ const [, local = "?", latest = "?", required = "?"] = String(r.stdout).trim().split("|");
332
+ return (
333
+ `the installed pipeline v${local} is below the required v${required} (latest v${latest}). ` +
334
+ "Run /multi-agent:update, then start the command again."
335
+ );
336
+ }
337
+
340
338
  /** @param {string} taskId */
341
339
  function statePathFor(taskId) {
342
340
  return join(homedir(), ".claude", "logs", "multi-agent", taskId, "agent-state.json");
@@ -365,6 +363,11 @@ function main(argv) {
365
363
  }
366
364
 
367
365
  const result = evaluate(state, extraInput);
366
+ const floor = versionFloorFailure();
367
+ if (floor) {
368
+ result.failures.push(floor);
369
+ result.ok = false;
370
+ }
368
371
 
369
372
  if (asJson) {
370
373
  console.log(JSON.stringify({ taskId, statePath: path, ...result }, null, 2));
@@ -14,7 +14,9 @@
14
14
  * launch POST /v1/phone/launch, and only while `launch on` is set
15
15
  *
16
16
  * The registry is <autopilot root>/phone/devices.json (0600, directory 0700;
17
- * phone-devices.schema.json). An unattended run may not change it: every
17
+ * phone-devices.schema.json). `revoke` also drops the device's office remote
18
+ * keys (office-remote-client.mjs), so neither path reaches the machine any
19
+ * more. An unattended run may not change it: every
18
20
  * subcommand but `list` refuses under MULTI_AGENT_UNATTENDED=1, and the guard
19
21
  * blocks the same subcommands by name (unattended-policy.json).
20
22
  *
@@ -37,6 +39,7 @@ import { readFileSync } from "node:fs";
37
39
  import { runMain } from "../lib/fatal.mjs";
38
40
  import { invokedDirectly } from "../lib/invoked-directly.mjs";
39
41
  import { isUnattended } from "../lib/unattended.mjs";
42
+ import { readConfig, writeConfig } from "./office-remote-client.mjs";
40
43
  import {
41
44
  DEVICE_ID,
42
45
  SCOPES,
@@ -197,6 +200,11 @@ function main(argv) {
197
200
  }
198
201
  d.revokedAt = new Date().toISOString();
199
202
  writeRegistry(reg);
203
+ const remote = readConfig();
204
+ if (remote?.devices?.[id]) {
205
+ delete remote.devices[id];
206
+ writeConfig(remote);
207
+ }
200
208
  process.stdout.write(JSON.stringify({ id, revokedAt: d.revokedAt }) + "\n");
201
209
  return 0;
202
210
  }
@@ -4,7 +4,9 @@
4
4
  * answer per objection, and a verdict decided by constitution rule id.
5
5
  *
6
6
  * At the end of Phase 1 the plan-critic persona writes objections to the plan
7
- * under four lenses (scope, feasibility, security, a cheaper alternative), and
7
+ * under six lenses (scope, feasibility, security, a cheaper alternative, an
8
+ * input the spec implies but no test exercises, a decision left to the
9
+ * implementer), and
8
10
  * the planner answers each one exactly once: accept and revise the plan, or
9
11
  * rebut with evidence. Both halves live in one file,
10
12
  * `<worktree>/.pipeline/plan-critique.json` (schemas/plan-critique.schema.json).
@@ -59,7 +61,14 @@ import { requirementIds } from "./spec-consistency-gate.mjs";
59
61
  import { resolveOne } from "./verify-citations.mjs";
60
62
 
61
63
  export const GATE = "plan-critique";
62
- export const LENSES = ["scope", "feasibility", "security", "alternative"];
64
+ export const LENSES = [
65
+ "scope",
66
+ "feasibility",
67
+ "security",
68
+ "alternative",
69
+ "unstated-input",
70
+ "underspecified",
71
+ ];
63
72
  export const ANCHOR_KINDS = ["file", "plan", "analysis", "inference"];
64
73
  export const ACTIONS = ["accept", "rebut"];
65
74
  const OBJ_ID = /^OBJ-\d{2,3}$/;