@mmerterden/multi-agent-pipeline 20.34.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 (321) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README.md +1 -0
  3. package/docs/facts.json +2 -2
  4. package/install/catalog-history.json +1 -1
  5. package/install/claude.mjs +16 -1
  6. package/install/templates/claude-hooks.json +23 -1
  7. package/manifest.json +324 -293
  8. package/package.json +1 -1
  9. package/pipeline/agents/bulk-reader.md +3 -0
  10. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
  11. package/pipeline/commands/multi-agent/office-on/SKILL.md +7 -3
  12. package/pipeline/commands/multi-agent/office-status/SKILL.md +3 -2
  13. package/pipeline/contract/CHANGELOG.md +9 -0
  14. package/pipeline/contract/README.md +1 -0
  15. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  16. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  17. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  18. package/pipeline/contract/manifest.json +14 -1
  19. package/pipeline/contract/types/index.d.ts +24 -2
  20. package/pipeline/lib/json-file-lock.mjs +61 -15
  21. package/pipeline/lib/office-remote.mjs +228 -0
  22. package/pipeline/lib/qr-code.mjs +360 -0
  23. package/pipeline/lib/repo-hygiene.sh +1 -0
  24. package/pipeline/lib/run-completion.mjs +17 -0
  25. package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
  26. package/pipeline/multi-agent-refs/features/office-remote-protocol.md +166 -0
  27. package/pipeline/multi-agent-refs/features/office-threat-model.md +1 -1
  28. package/pipeline/multi-agent-refs/features/scope-check.md +16 -1
  29. package/pipeline/multi-agent-refs/features/verify-by-test.md +10 -0
  30. package/pipeline/multi-agent-refs/keychain.md +7 -0
  31. package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -2
  32. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -6
  33. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -12
  34. package/pipeline/multi-agent-refs/phases/phase-3-review.md +9 -8
  35. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -4
  36. package/pipeline/multi-agent-refs/rules.md +3 -2
  37. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  38. package/pipeline/office/NOTICE.md +4 -1
  39. package/pipeline/office/assets/char_12.png +0 -0
  40. package/pipeline/office/assets/char_13.png +0 -0
  41. package/pipeline/office/assets/char_14.png +0 -0
  42. package/pipeline/office/assets/char_15.png +0 -0
  43. package/pipeline/office/assets/char_16.png +0 -0
  44. package/pipeline/office/assets/char_17.png +0 -0
  45. package/pipeline/office/assets/char_18.png +0 -0
  46. package/pipeline/office/assets/char_19.png +0 -0
  47. package/pipeline/office/assets/char_20.png +0 -0
  48. package/pipeline/office/assets/char_21.png +0 -0
  49. package/pipeline/office/assets/char_22.png +0 -0
  50. package/pipeline/office/assets/char_23.png +0 -0
  51. package/pipeline/office/assets/char_24.png +0 -0
  52. package/pipeline/office/assets/char_25.png +0 -0
  53. package/pipeline/office/assets/char_26.png +0 -0
  54. package/pipeline/office/assets/char_27.png +0 -0
  55. package/pipeline/office/assets/char_28.png +0 -0
  56. package/pipeline/office/assets/char_29.png +0 -0
  57. package/pipeline/office/assets/char_30.png +0 -0
  58. package/pipeline/office/index.html +293 -49
  59. package/pipeline/schemas/agent-state.schema.json +18 -0
  60. package/pipeline/schemas/planning-output.schema.json +18 -0
  61. package/pipeline/schemas/pr-reach.schema.json +55 -0
  62. package/pipeline/schemas/prefs.schema.json +13 -7
  63. package/pipeline/schemas/triage-output.schema.json +12 -1
  64. package/pipeline/schemas/unattended-policy.json +2 -1
  65. package/pipeline/scripts/_guard_rules.py +449 -1
  66. package/pipeline/scripts/_test-gaming.mjs +36 -0
  67. package/pipeline/scripts/agent-guard.py +79 -19
  68. package/pipeline/scripts/anonymize-findings.mjs +99 -0
  69. package/pipeline/scripts/answer-check.mjs +113 -0
  70. package/pipeline/scripts/answer-check.sh +20 -0
  71. package/pipeline/scripts/autopilot-publish.mjs +9 -2
  72. package/pipeline/scripts/contract-server.mjs +35 -1
  73. package/pipeline/scripts/gate-ledger.mjs +144 -21
  74. package/pipeline/scripts/office-build.mjs +276 -5
  75. package/pipeline/scripts/office-control.mjs +70 -10
  76. package/pipeline/scripts/office-pair.mjs +407 -0
  77. package/pipeline/scripts/office-remote-client.mjs +463 -0
  78. package/pipeline/scripts/phase-tracker.sh +8 -0
  79. package/pipeline/scripts/phase0-exit-gate.mjs +40 -37
  80. package/pipeline/scripts/phone-devices.mjs +9 -1
  81. package/pipeline/scripts/pre-commit-check.sh +28 -2
  82. package/pipeline/scripts/pre-push-check.sh +6 -12
  83. package/pipeline/scripts/rework-base.mjs +87 -0
  84. package/pipeline/scripts/run-commit-check.mjs +104 -0
  85. package/pipeline/scripts/run-scope-guard.mjs +58 -3
  86. package/pipeline/scripts/run-scope-guard.sh +13 -1
  87. package/pipeline/scripts/scope-check-gate.mjs +70 -6
  88. package/pipeline/scripts/task-mirror.mjs +106 -15
  89. package/pipeline/scripts/task-mirror.sh +3 -2
  90. package/pipeline/scripts/test-stability-gate.mjs +112 -0
  91. package/pipeline/scripts/update-check.sh +7 -1
  92. package/pipeline/scripts/update-issue-progress.sh +73 -8
  93. package/pipeline/scripts/validate-planning.mjs +105 -1
  94. package/pipeline/scripts/validate-triage.mjs +42 -2
  95. package/pipeline/scripts/work-items.mjs +119 -1
  96. package/pipeline/scripts/worktree-prepare-all.mjs +200 -0
  97. package/pipeline/scripts/worktree-prepare.sh +40 -4
  98. package/pipeline/scripts/write-state.mjs +67 -24
  99. package/pipeline/skills/.skill-manifest.json +5 -5
  100. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +5 -5
  101. package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +1 -1
  102. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
  103. package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +2 -2
  104. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +14 -0
  105. package/pipeline/skills/shared/external/android-architecture/references/patterns.md +6 -0
  106. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +10 -0
  107. package/pipeline/skills/shared/external/android-datastore/references/patterns.md +11 -0
  108. package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +8 -0
  109. package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +6 -0
  110. package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +10 -0
  111. package/pipeline/skills/shared/external/android-performance/references/patterns.md +13 -0
  112. package/pipeline/skills/shared/external/android-security/references/patterns.md +16 -0
  113. package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +9 -0
  114. package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +12 -0
  115. package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +13 -0
  116. package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +12 -0
  117. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +9 -0
  118. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +8 -0
  119. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +10 -0
  120. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +21 -0
  121. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +9 -0
  122. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +7 -0
  123. package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -2
  124. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +10 -0
  125. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +10 -1
  126. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +22 -0
  127. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +17 -0
  128. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +22 -0
  129. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +17 -0
  130. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +9 -0
  131. package/pipeline/skills/shared/external/authentication/references/passkeys.md +11 -0
  132. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +13 -0
  133. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +9 -0
  134. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +10 -0
  135. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +15 -0
  136. package/pipeline/skills/shared/external/compose-components/references/patterns.md +7 -0
  137. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +7 -0
  138. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +15 -0
  139. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +9 -0
  140. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +11 -0
  141. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +10 -0
  142. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +13 -0
  143. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +20 -0
  144. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +16 -0
  145. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +16 -0
  146. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +14 -0
  147. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +13 -0
  148. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +10 -0
  149. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +10 -0
  150. package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +10 -0
  151. package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +17 -0
  152. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +12 -0
  153. package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +10 -0
  154. package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +10 -0
  155. package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +11 -0
  156. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +13 -0
  157. package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +13 -0
  158. package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +11 -0
  159. package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +12 -0
  160. package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +16 -0
  161. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +9 -0
  162. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +18 -0
  163. package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +9 -0
  164. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +9 -0
  165. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +7 -0
  166. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +13 -0
  167. package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +9 -0
  168. package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +15 -0
  169. package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +11 -0
  170. package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +11 -0
  171. package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +15 -0
  172. package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +11 -0
  173. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +18 -0
  174. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +19 -0
  175. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +15 -0
  176. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +7 -0
  177. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +8 -0
  178. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +12 -0
  179. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +17 -0
  180. package/pipeline/skills/shared/external/ios-security/references/app-review-guidelines.md +8 -0
  181. package/pipeline/skills/shared/external/ios-security/references/cryptokit-advanced.md +14 -0
  182. package/pipeline/skills/shared/external/ios-security/references/file-storage-patterns.md +8 -0
  183. package/pipeline/skills/shared/external/ios-security/references/privacy-manifest.md +9 -0
  184. package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +9 -0
  185. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +13 -0
  186. package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +8 -0
  187. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +21 -0
  188. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +9 -0
  189. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +12 -0
  190. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +16 -0
  191. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +21 -0
  192. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +10 -0
  193. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +9 -0
  194. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +12 -0
  195. package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +11 -0
  196. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +11 -0
  197. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +18 -0
  198. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +11 -0
  199. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +11 -0
  200. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +15 -0
  201. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +14 -0
  202. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +14 -0
  203. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +14 -0
  204. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +14 -0
  205. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +10 -0
  206. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +12 -0
  207. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +14 -0
  208. package/pipeline/skills/shared/external/room-database/references/patterns.md +16 -0
  209. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +10 -0
  210. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +10 -0
  211. package/pipeline/skills/shared/external/spm-build-analysis/references/build-optimization-sources.md +14 -0
  212. package/pipeline/skills/shared/external/spm-build-analysis/references/spm-analysis-checks.md +19 -0
  213. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +13 -0
  214. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +31 -0
  215. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +8 -0
  216. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +9 -0
  217. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +10 -0
  218. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +8 -0
  219. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +24 -0
  220. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +8 -0
  221. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +14 -0
  222. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +12 -0
  223. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +10 -0
  224. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +8 -0
  225. package/pipeline/skills/shared/external/swift-concurrency-pro/references/actors.md +10 -0
  226. package/pipeline/skills/shared/external/swift-concurrency-pro/references/cancellation.md +7 -0
  227. package/pipeline/skills/shared/external/swift-concurrency-pro/references/interop.md +10 -0
  228. package/pipeline/skills/shared/external/swift-concurrency-pro/references/new-features.md +11 -0
  229. package/pipeline/skills/shared/external/swift-concurrency-pro/references/structured.md +9 -0
  230. package/pipeline/skills/shared/external/swift-concurrency-pro/references/testing.md +13 -0
  231. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +7 -0
  232. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +14 -0
  233. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +16 -0
  234. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +17 -0
  235. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +19 -0
  236. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +16 -0
  237. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +19 -0
  238. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +18 -0
  239. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +16 -0
  240. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +17 -0
  241. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +17 -0
  242. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +17 -0
  243. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +18 -0
  244. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +21 -0
  245. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +16 -0
  246. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +16 -0
  247. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +9 -0
  248. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +24 -0
  249. package/pipeline/skills/shared/external/swift-testing-pro/references/async-tests.md +9 -0
  250. package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +11 -0
  251. package/pipeline/skills/shared/external/swift-testing-pro/references/writing-better-tests.md +13 -0
  252. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +8 -0
  253. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +17 -0
  254. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +12 -0
  255. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +11 -0
  256. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +7 -0
  257. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +13 -0
  258. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +24 -0
  259. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +12 -0
  260. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +15 -0
  261. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +15 -0
  262. package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +12 -0
  263. package/pipeline/skills/shared/external/swiftui-expert-skill/references/accessibility-patterns.md +11 -0
  264. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +7 -0
  265. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +13 -0
  266. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +9 -0
  267. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts-accessibility.md +9 -0
  268. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts.md +17 -0
  269. package/pipeline/skills/shared/external/swiftui-expert-skill/references/image-optimization.md +9 -0
  270. package/pipeline/skills/shared/external/swiftui-expert-skill/references/latest-apis.md +10 -0
  271. package/pipeline/skills/shared/external/swiftui-expert-skill/references/layout-best-practices.md +12 -0
  272. package/pipeline/skills/shared/external/swiftui-expert-skill/references/list-patterns.md +12 -0
  273. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-scenes.md +13 -0
  274. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-views.md +12 -0
  275. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-window-styling.md +12 -0
  276. package/pipeline/skills/shared/external/swiftui-expert-skill/references/performance-patterns.md +7 -0
  277. package/pipeline/skills/shared/external/swiftui-expert-skill/references/scroll-patterns.md +9 -0
  278. package/pipeline/skills/shared/external/swiftui-expert-skill/references/sheet-navigation-patterns.md +10 -0
  279. package/pipeline/skills/shared/external/swiftui-expert-skill/references/state-management.md +18 -0
  280. package/pipeline/skills/shared/external/swiftui-expert-skill/references/view-structure.md +16 -0
  281. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +11 -0
  282. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +8 -0
  283. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +14 -0
  284. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +9 -0
  285. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +22 -0
  286. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +14 -0
  287. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +10 -0
  288. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +10 -0
  289. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +13 -0
  290. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +10 -0
  291. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +14 -0
  292. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +30 -0
  293. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +16 -0
  294. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +11 -0
  295. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +11 -0
  296. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +10 -0
  297. package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +8 -0
  298. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +12 -0
  299. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +10 -0
  300. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +11 -0
  301. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +12 -0
  302. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +14 -0
  303. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +10 -0
  304. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +10 -0
  305. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +13 -0
  306. package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +12 -0
  307. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +8 -0
  308. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +8 -0
  309. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +14 -0
  310. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +17 -0
  311. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +11 -0
  312. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +11 -0
  313. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +27 -0
  314. package/pipeline/skills/shared/external/xcode-build-fixer/references/build-settings-best-practices.md +9 -0
  315. package/pipeline/skills/shared/external/xcode-build-fixer/references/fix-patterns.md +7 -0
  316. package/pipeline/skills/shared/external/xcode-build-orchestrator/references/build-settings-best-practices.md +9 -0
  317. package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/build-optimization-sources.md +14 -0
  318. package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/code-compilation-checks.md +8 -0
  319. package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-optimization-sources.md +14 -0
  320. package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-settings-best-practices.md +9 -0
  321. package/pipeline/skills/shared/external/xcode-project-analyzer/references/project-audit-checks.md +13 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "20.34.0",
3
+ "version": "20.35.0",
4
4
  "description": "6-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -33,6 +33,9 @@ number you report is a number the caller can open directly.
33
33
  restated as one region.
34
34
  - If you did not see the whole file, set `truncated: true`. Do not summarize a
35
35
  part as though it were the whole.
36
+ - **A script reads the answer, not a person.** Return the JSON object and nothing
37
+ around it, and write `why` as a short phrase. Shorten the wording, never the
38
+ content: a negation, a number or a path stays exactly as found.
36
39
 
37
40
  ## Output Format
38
41
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Multi-channel reporter - Jira/Confluence/Wiki/PR description/Board status. Multi-select channels + content, humanizer pass, reviewer-preserving Bitbucket PR PUT, GitHub Projects v2 board status move. Phase 5 delegates to this skill; users can invoke post-hoc by Jira ID / PR URL / branch."
2
+ description: "Multi-channel reporter - Jira/Confluence/Wiki/PR description/Board status. Multi-select channels + content, humanizer pass, reviewer-preserving Bitbucket PR PUT, GitHub Projects v2 board status move. Use to post or redo a run's report; Phase 5 delegates here, or invoke by Jira ID / PR URL / branch."
3
3
  description-tr: "Çok kanallı raporlayıcı - Jira/Confluence/Wiki/PR açıklaması/Board durumu. Çoklu kanal + içerik seçimi, humanizer geçişi, reviewer'ı koruyan Bitbucket PR PUT, GitHub Projects v2 board durum taşıma. Faz 5 bu skill'e delege eder; Jira ID / PR URL / branch ile sonradan da çağrılabilir."
4
4
  argument-hint: '[Jira-id | PR-url | #N | Jira-url] [--channels pr,jira,confluence,wiki,board] [--content normal,technical,test,auto-diff,note,test-run] [--flow-run <runId>] [--message "..."] [--message-file path] [--board-status <key>] [--dry-run] [--append] [--ready]'
5
5
  parameters: [{"name":"target","kind":"text"},{"name":"--channels","kind":"enum","repeat":true,"enum":["pr","jira","confluence","wiki","board"]},{"name":"--content","kind":"enum","repeat":true,"enum":["normal","technical","test","auto-diff","note","test-run"]},{"name":"--flow-run","kind":"text"},{"name":"--message","kind":"text"},{"name":"--message-file","kind":"path"},{"name":"--board-status","kind":"text"},{"name":"--dry-run","kind":"flag"},{"name":"--append","kind":"flag"},{"name":"--ready","kind":"flag"}]
@@ -46,6 +46,10 @@ fresh bearer token per start). This command and `office-off` /
46
46
  `task-mirror` hook that install registers for every run.
47
47
  - The panel's writes call the same scripts the terminal commands call; it adds
48
48
  no behaviour of its own.
49
- - The server listens on 127.0.0.1 only. Remote access is not part of this
50
- installation; `office-status` says whether a separate remote layer is
51
- present.
49
+ - The server listens on 127.0.0.1 only. Remote access is off unless a phone
50
+ was paired: then `office-on` also starts `office-remote-client.mjs`, which
51
+ polls the relay over HTTPS (no inbound port) and stops with the server
52
+ (`features/office-remote-protocol.md`). `office-status` says whether a
53
+ device is paired. Pairing is the user's own step in their terminal, never
54
+ run from here: `! node ~/.claude/scripts/office-pair.mjs start`, then
55
+ `wait`, then `confirm <code the phone shows>`.
@@ -18,8 +18,9 @@ node "$HOME/.claude/scripts/office-control.mjs" status --json
18
18
  Report in `outputLanguage`:
19
19
 
20
20
  - `running`, and when it is, `url` and `idleMinutes` (0 means it runs until `office-off`);
21
- - `remote`: "not available in this installation" unless a separate remote
22
- layer is installed; report the line as given;
21
+ - `remote`: "off" until a phone is paired with `office-pair.mjs`, "configured"
22
+ when only the prefs block is set, "paired" once a device is; report the line
23
+ as given;
23
24
  - `pairedDevices`: the devices the phone registry lists; revoking one is
24
25
  `phone-devices.mjs revoke`;
25
26
  - `recentActions`: the last writes taken through the server (route, step,
@@ -3,6 +3,15 @@
3
3
  Versions of the client contract (`manifest.json` `contractVersion`), separate
4
4
  from the package version. See `README.md` for what counts as minor and major.
5
5
 
6
+ ## 1.7.0
7
+
8
+ Added. Nothing existing changes shape.
9
+
10
+ - `GET /v1/prs/reach` (`work-items.mjs reach`, local-only): for each open GitHub
11
+ pull request (at most 20), how many files in the repository's own code graph
12
+ depend on what it changes, widest first, with a reason when it cannot be
13
+ measured. Changed paths are never returned, only counts.
14
+
6
15
  ## 1.6.0
7
16
 
8
17
  Added. Nothing existing changes shape.
@@ -71,6 +71,7 @@ same bytes (`test/contract-parity.test.mjs`):
71
71
  | Post a review verdict (two steps) | `work-items.mjs verdict ID [--post]` | `POST /v1/runs/{id}/review-verdict` |
72
72
  | An issue's maturity | `work-items.mjs maturity KEY` | `GET /v1/issues/{key}/maturity` |
73
73
  | Open pull requests | `work-items.mjs prs` | `GET /v1/prs` |
74
+ | How far each open pull request reaches in its repository's graph | `work-items.mjs reach` | `GET /v1/prs/reach` |
74
75
  | Toolkit MCP tools | `toolkit-tools.mjs list` | `GET /v1/tools` |
75
76
  | Call an allowed toolkit tool (read-only, no location argument) | `toolkit-tools.mjs call NAME JSON` | `POST /v1/tools/{name}/call` |
76
77
  | A Confluence page, a Crashlytics issue, a Figma frame | `service-read.mjs confluence\|crashlytics\|figma ...` | `GET /v1/services/{confluence,crashlytics,figma}` |
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "1.6.0",
2
+ "contractVersion": "1.7.0",
3
3
  "error": "invalid-request",
4
4
  "message": "the launch request was refused",
5
5
  "errors": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "1.6.0",
2
+ "contractVersion": "1.7.0",
3
3
  "error": "unauthorized",
4
4
  "message": "a bearer token is required"
5
5
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "1.6.0",
2
+ "contractVersion": "1.7.0",
3
3
  "error": "unsigned",
4
4
  "message": "a device signature is required"
5
5
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "1.6.0",
2
+ "contractVersion": "1.7.0",
3
3
  "supportedMajors": [1],
4
4
  "description": "The pipeline's client contract: every surface a client reads or writes, the CLI invocation and the HTTP route that carry it, and the schema its JSON follows. Schemas are not copied here; schemasDir points at the one tree the producers validate against. Paths in cli are relative to the pipeline directory (installed: ~/.claude).",
5
5
  "schemasDir": "../schemas",
@@ -100,6 +100,7 @@
100
100
  "run-evidence.schema.json",
101
101
  "issue-maturity.schema.json",
102
102
  "prs.schema.json",
103
+ "pr-reach.schema.json",
103
104
  "review-verdict-request.schema.json",
104
105
  "review-verdict.schema.json",
105
106
  "tools.schema.json",
@@ -745,6 +746,18 @@
745
746
  "remote": "read",
746
747
  "risk": "read"
747
748
  },
749
+ {
750
+ "id": "prs-reach",
751
+ "method": "GET",
752
+ "path": "/v1/prs/reach",
753
+ "query": [],
754
+ "cli": ["node", "scripts/work-items.mjs", "reach"],
755
+ "schema": "pr-reach.schema.json",
756
+ "version": "1.1.0",
757
+ "note": "Run as a child process so parsing a large graph never holds up other routes. At most 20 GitHub pull requests, three gh calls at a time, each with its account's token in GH_TOKEN. A repository with no graph under ~/.claude/knowledge/<repo name>/ is listed with its reason and costs no gh call.",
758
+ "remote": "local-only",
759
+ "risk": "read"
760
+ },
748
761
  {
749
762
  "id": "tools",
750
763
  "method": "GET",
@@ -1,8 +1,8 @@
1
1
  // Generated by pipeline/contract/build.mjs from pipeline/schemas/*.schema.json.
2
2
  // Do not edit: change the schema and run `node pipeline/contract/build.mjs --write`.
3
3
 
4
- export type ContractVersion = "1.6.0";
5
- export type SurfaceId = "runs" | "run" | "commands" | "questions" | "issues" | "worktrees" | "launch-spec" | "repos" | "repo-add" | "repo-remove" | "launch" | "answer" | "run-log" | "resume" | "kill" | "gc" | "autopilot" | "autopilot-off" | "phone-runs" | "phone-run" | "phone-answer" | "phone-launch" | "autopilot-on" | "steer" | "activity" | "activity-stream" | "agent-chat" | "routines" | "routine-delete" | "jobs" | "job-start" | "stack" | "stack-set" | "ledger" | "system" | "prune-logs" | "settings" | "settings-set" | "credentials" | "memory" | "run-results" | "run-evidence" | "review-verdict" | "issue-maturity" | "prs" | "tools" | "tool-call" | "service-confluence" | "service-crashlytics" | "service-figma" | "permissions";
4
+ export type ContractVersion = "1.7.0";
5
+ export type SurfaceId = "runs" | "run" | "commands" | "questions" | "issues" | "worktrees" | "launch-spec" | "repos" | "repo-add" | "repo-remove" | "launch" | "answer" | "run-log" | "resume" | "kill" | "gc" | "autopilot" | "autopilot-off" | "phone-runs" | "phone-run" | "phone-answer" | "phone-launch" | "autopilot-on" | "steer" | "activity" | "activity-stream" | "agent-chat" | "routines" | "routine-delete" | "jobs" | "job-start" | "stack" | "stack-set" | "ledger" | "system" | "prune-logs" | "settings" | "settings-set" | "credentials" | "memory" | "run-results" | "run-evidence" | "review-verdict" | "issue-maturity" | "prs" | "prs-reach" | "tools" | "tool-call" | "service-confluence" | "service-crashlytics" | "service-figma" | "permissions";
6
6
 
7
7
  /** What `runs-index.mjs --json` prints: every run under the log root and the unattended log root (one record per task id), grouped per status/SKILL.md 3b. Fields are only ever added - a decoder ignores keys it does not know. `--redact` drops every x-sensitivity local field (and `logsRoot`) and scrubs the free text in the title, `now`, sub-step names, task subjects and the pending question. */
8
8
  export interface RunsIndex {
@@ -1202,6 +1202,28 @@ export interface Prs {
1202
1202
  }>;
1203
1203
  }
1204
1204
 
1205
+ /** What GET /v1/prs/reach returns (`work-items.mjs reach`): for each open GitHub pull request (at most 20), how many files in the repository's own code graph depend on what it changes, widest first. Changed paths serve only as keys into the local graph and are never returned. A pull request that cannot be measured carries reach null and its reason. */
1206
+ export interface PrReach {
1207
+ contractVersion: string;
1208
+ /** Reverse-dependency depth the count was taken at. */
1209
+ depth: number;
1210
+ items: Array<{
1211
+ source: "github" | "bitbucket";
1212
+ repo: string;
1213
+ number: number;
1214
+ /** The knowledge-folder project whose graph answered, or null. */
1215
+ project: string | null;
1216
+ /** Files the pull request changes. */
1217
+ changed?: number;
1218
+ /** Of those, files the graph knows. */
1219
+ inGraph?: number;
1220
+ /** Files that depend on the changed ones, within depth. Null when not measured. */
1221
+ reach: number | null;
1222
+ /** Why reach is null. */
1223
+ reason?: string;
1224
+ }>;
1225
+ }
1226
+
1205
1227
  /** The body of POST /v1/runs/{id}/review-verdict: empty for the preview, confirmToken to post. The verdict is never chosen by the request; it follows from the review's recorded findings. */
1206
1228
  export interface ReviewVerdictRequest {
1207
1229
  contractVersion: string;
@@ -18,9 +18,12 @@
18
18
 
19
19
  import {
20
20
  chmodSync,
21
+ closeSync,
22
+ fstatSync,
21
23
  linkSync,
22
24
  lstatSync,
23
25
  mkdirSync,
26
+ openSync,
24
27
  readFileSync,
25
28
  readlinkSync,
26
29
  realpathSync,
@@ -47,22 +50,70 @@ export class FileLockTimeout extends Error {
47
50
 
48
51
  const pause = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
49
52
 
50
- function staleIdentity(lockPath, staleMs) {
51
- let pid;
52
- let st;
53
+ /**
54
+ * The PID inside a lock file and that file's identity, read through one
55
+ * descriptor. Read by path twice, the PID can come from a lock that was
56
+ * released in between and the identity from the one acquired after it, which
57
+ * makes a live lock look like a dead holder's.
58
+ *
59
+ * @param {string} lockPath
60
+ * @returns {{pid: number, ino: number, mtimeMs: number} | null}
61
+ */
62
+ export function readLockOwner(lockPath) {
63
+ let fd;
53
64
  try {
54
- pid = parseInt(readFileSync(lockPath, "utf8").trim(), 10);
55
- st = statSync(lockPath);
65
+ fd = openSync(lockPath, "r");
66
+ const st = fstatSync(fd);
67
+ const pid = parseInt(readFileSync(fd, "utf8").trim(), 10);
68
+ return { pid, ino: st.ino, mtimeMs: st.mtimeMs };
56
69
  } catch {
57
70
  return null;
71
+ } finally {
72
+ if (fd !== undefined) closeSync(fd);
58
73
  }
59
- const age = Date.now() - st.mtimeMs;
60
- if (!Number.isInteger(pid) || pid <= 0) return age > staleMs ? st : null;
74
+ }
75
+
76
+ /**
77
+ * Remove the lock judged stale, and only that one. The lock is first moved to
78
+ * a name private to this process, so the file examined is the file removed; a
79
+ * live lock that took its place in the meantime is linked back as the same
80
+ * file, which its holder's release recognises by inode.
81
+ *
82
+ * @param {string} lockPath
83
+ * @param {{ino: number, mtimeMs: number}} judged
84
+ * @returns {boolean} true when the stale lock was removed
85
+ */
86
+ export function reapLock(lockPath, judged) {
87
+ const reap = `${lockPath}.${process.pid}.reap`;
61
88
  try {
62
- process.kill(pid, 0);
89
+ renameSync(lockPath, reap);
90
+ } catch {
91
+ return false;
92
+ }
93
+ const st = statSync(reap);
94
+ if (st.ino === judged.ino && st.mtimeMs === judged.mtimeMs) {
95
+ unlinkSync(reap);
96
+ return true;
97
+ }
98
+ try {
99
+ linkSync(reap, lockPath);
100
+ } catch {
101
+ /* a new holder already has the path; the displaced one keeps its inode */
102
+ }
103
+ unlinkSync(reap);
104
+ return false;
105
+ }
106
+
107
+ function staleIdentity(lockPath, staleMs) {
108
+ const owner = readLockOwner(lockPath);
109
+ if (!owner) return null;
110
+ const age = Date.now() - owner.mtimeMs;
111
+ if (!Number.isInteger(owner.pid) || owner.pid <= 0) return age > staleMs ? owner : null;
112
+ try {
113
+ process.kill(owner.pid, 0);
63
114
  return null;
64
115
  } catch (err) {
65
- return err.code === "ESRCH" ? st : null;
116
+ return err.code === "ESRCH" ? owner : null;
66
117
  }
67
118
  }
68
119
 
@@ -82,12 +133,7 @@ function acquire(lockPath, timeoutMs, staleMs) {
82
133
  }
83
134
  const stale = staleIdentity(lockPath, staleMs);
84
135
  if (stale) {
85
- try {
86
- const now = statSync(lockPath);
87
- if (now.ino === stale.ino && now.mtimeMs === stale.mtimeMs) unlinkSync(lockPath);
88
- } catch {
89
- /* gone already */
90
- }
136
+ reapLock(lockPath, stale);
91
137
  continue;
92
138
  }
93
139
  if (Date.now() >= deadline) throw new FileLockTimeout(lockPath, timeoutMs);
@@ -0,0 +1,228 @@
1
+ /**
2
+ * office-remote.mjs - the machine side of the office remote layer
3
+ * (features/office-remote-protocol.md).
4
+ *
5
+ * - Keys: P-256 ECDH between the machine key and the phone's session key,
6
+ * through HKDF-SHA-256 with the pairing id as salt, into one AES-256 key per
7
+ * direction.
8
+ * - Frames: AES-256-GCM; the 12-byte nonce is a 4-byte direction tag and an
9
+ * 8-byte counter, so a frame opens only in its own direction and a counter
10
+ * that does not exceed the last accepted one is refused as a replay.
11
+ * - Pairing: the phone's first message is sealed with a key that also mixes in
12
+ * the one-time pairing secret from the QR code, so the relay cannot open it
13
+ * even holding both public keys.
14
+ * - Comparison code: six digits from both public keys, the same on either end.
15
+ * - Passkey: a WebAuthn assertion checked against the paired credential key,
16
+ * rp id, origin, the request-bound challenge, user verification and the sign
17
+ * count.
18
+ *
19
+ * Zero runtime dependencies (ADR-0004): node:crypto only.
20
+ *
21
+ * @module pipeline/lib/office-remote
22
+ */
23
+
24
+ import {
25
+ createCipheriv,
26
+ createDecipheriv,
27
+ createHash,
28
+ createPrivateKey,
29
+ createPublicKey,
30
+ diffieHellman,
31
+ generateKeyPairSync,
32
+ hkdfSync,
33
+ timingSafeEqual,
34
+ verify,
35
+ } from "node:crypto";
36
+
37
+ const DIRECTIONS = { "phone-to-machine": 1, "machine-to-phone": 2 };
38
+ const INFO = "ma-office v1 ";
39
+ const MAX_COUNTER = 2 ** 48;
40
+
41
+ const b64u = (b) => Buffer.from(b).toString("base64url");
42
+ const fromB64u = (s) => Buffer.from(String(s || ""), "base64url");
43
+ const sha256 = (b) => createHash("sha256").update(b).digest();
44
+
45
+ /** A new machine key pair: PKCS#8 PEM private key, base64url SPKI public key. */
46
+ export function createMachineKeys() {
47
+ const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
48
+ return {
49
+ privatePem: privateKey.export({ type: "pkcs8", format: "pem" }),
50
+ publicKey: b64u(publicKey.export({ type: "spki", format: "der" })),
51
+ };
52
+ }
53
+
54
+ function shared(privatePem, peerPublic) {
55
+ return diffieHellman({
56
+ privateKey: createPrivateKey(privatePem),
57
+ publicKey: createPublicKey({ key: fromB64u(peerPublic), format: "der", type: "spki" }),
58
+ });
59
+ }
60
+
61
+ const derive = (ikm, pairingId, label) =>
62
+ Buffer.from(
63
+ hkdfSync("sha256", ikm, Buffer.from(String(pairingId)), Buffer.from(INFO + label), 32),
64
+ );
65
+
66
+ /**
67
+ * The two AES keys of a pairing, from this end's point of view.
68
+ *
69
+ * @param {{privatePem: string, peerPublic: string, pairingId: string, role: "machine"|"phone"}} p
70
+ * @returns {{send: Buffer, recv: Buffer}}
71
+ */
72
+ export function directionKeys({ privatePem, peerPublic, pairingId, role }) {
73
+ const ikm = shared(privatePem, peerPublic);
74
+ const p2m = derive(ikm, pairingId, "phone-to-machine");
75
+ const m2p = derive(ikm, pairingId, "machine-to-phone");
76
+ return role === "machine" ? { send: m2p, recv: p2m } : { send: p2m, recv: m2p };
77
+ }
78
+
79
+ function nonceFor(direction, counter) {
80
+ const tag = DIRECTIONS[direction];
81
+ if (!tag) throw new Error(`unknown direction ${direction}`);
82
+ if (!Number.isSafeInteger(counter) || counter < 1 || counter >= MAX_COUNTER) {
83
+ throw new Error("counter out of range");
84
+ }
85
+ const iv = Buffer.alloc(12);
86
+ iv.writeUInt32BE(tag, 0);
87
+ iv.writeUIntBE(counter, 6, 6);
88
+ return iv;
89
+ }
90
+
91
+ /** Seal a JSON value as one frame. */
92
+ export function sealFrame(key, counter, direction, value) {
93
+ const iv = nonceFor(direction, counter);
94
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
95
+ cipher.setAAD(Buffer.from(direction));
96
+ const body = Buffer.concat([cipher.update(JSON.stringify(value), "utf8"), cipher.final()]);
97
+ return { counter, ct: b64u(Buffer.concat([body, cipher.getAuthTag()])) };
98
+ }
99
+
100
+ /**
101
+ * Open one frame. Refuses a counter that does not exceed `lastCounter`, a
102
+ * frame for the other direction and any altered byte.
103
+ *
104
+ * @returns {{ok: true, value: unknown, counter: number} | {ok: false, reason: string}}
105
+ */
106
+ export function openFrame(key, frame, direction, lastCounter) {
107
+ const counter = Number(frame?.counter);
108
+ if (!Number.isSafeInteger(counter) || counter <= Number(lastCounter || 0)) {
109
+ return { ok: false, reason: "counter did not grow (replay or reorder)" };
110
+ }
111
+ let iv;
112
+ try {
113
+ iv = nonceFor(direction, counter);
114
+ } catch (err) {
115
+ return { ok: false, reason: err.message };
116
+ }
117
+ const raw = fromB64u(frame?.ct);
118
+ if (raw.length < 17) return { ok: false, reason: "frame too short" };
119
+ try {
120
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
121
+ decipher.setAAD(Buffer.from(direction));
122
+ decipher.setAuthTag(raw.subarray(raw.length - 16));
123
+ const text = Buffer.concat([
124
+ decipher.update(raw.subarray(0, raw.length - 16)),
125
+ decipher.final(),
126
+ ]);
127
+ return { ok: true, value: JSON.parse(text.toString("utf8")), counter };
128
+ } catch {
129
+ return { ok: false, reason: "frame did not authenticate" };
130
+ }
131
+ }
132
+
133
+ function pairingKey({ privatePem, peerPublic, secret, pairingId }) {
134
+ const ikm = Buffer.concat([shared(privatePem, peerPublic), fromB64u(secret)]);
135
+ return derive(ikm, pairingId, "pairing");
136
+ }
137
+
138
+ /** Seal the phone's pairing message (counter 1, phone to machine). */
139
+ export function sealPairing({ privatePem, peerPublic, secret, pairingId, value }) {
140
+ return sealFrame(
141
+ pairingKey({ privatePem, peerPublic, secret, pairingId }),
142
+ 1,
143
+ "phone-to-machine",
144
+ value,
145
+ );
146
+ }
147
+
148
+ /** Open the phone's pairing message with the machine key and the pairing secret. */
149
+ export function openPairing({ privatePem, peerPublic, secret, pairingId, sealed }) {
150
+ try {
151
+ const key = pairingKey({ privatePem, peerPublic, secret, pairingId });
152
+ const r = openFrame(key, sealed, "phone-to-machine", 0);
153
+ return r.ok ? { ok: true, value: r.value } : r;
154
+ } catch {
155
+ return { ok: false, reason: "pairing message could not be opened" };
156
+ }
157
+ }
158
+
159
+ /** Six digits from two public keys, independent of their order. */
160
+ export function comparisonCode(a, b) {
161
+ const [x, y] = [String(a), String(b)].sort();
162
+ const n = sha256(Buffer.from(`${INFO}compare\n${x}\n${y}`)).readUInt32BE(0) % 1_000_000;
163
+ return String(n).padStart(6, "0");
164
+ }
165
+
166
+ const sameBytes = (a, b) => a.length === b.length && timingSafeEqual(a, b);
167
+
168
+ /**
169
+ * Check a WebAuthn assertion against the paired passkey.
170
+ *
171
+ * @param {object} p
172
+ * @param {{authenticatorData: string, clientDataJSON: string, signature: string}} p.assertion base64url fields
173
+ * @param {string} p.credential base64url SPKI of the passkey public key
174
+ * @param {string} p.rpId
175
+ * @param {string} p.origin
176
+ * @param {Buffer} p.challenge the bytes this request binds (SHA-256 of its canonical signed text)
177
+ * @param {number} [p.prevSignCount]
178
+ * @returns {{ok: true, signCount: number} | {ok: false, reason: string}}
179
+ */
180
+ export function verifyPasskey({
181
+ assertion,
182
+ credential,
183
+ rpId,
184
+ origin,
185
+ challenge,
186
+ prevSignCount = 0,
187
+ }) {
188
+ const authData = fromB64u(assertion?.authenticatorData);
189
+ const clientRaw = fromB64u(assertion?.clientDataJSON);
190
+ if (authData.length < 37) return { ok: false, reason: "authenticator data too short" };
191
+ let client;
192
+ try {
193
+ client = JSON.parse(clientRaw.toString("utf8"));
194
+ } catch {
195
+ return { ok: false, reason: "client data is not JSON" };
196
+ }
197
+ if (client?.type !== "webauthn.get")
198
+ return { ok: false, reason: "client data type is not webauthn.get" };
199
+ if (!sameBytes(fromB64u(client.challenge), Buffer.from(challenge))) {
200
+ return { ok: false, reason: "challenge is not this request" };
201
+ }
202
+ if (client.origin !== origin) return { ok: false, reason: "origin is not the paired one" };
203
+ if (!sameBytes(authData.subarray(0, 32), sha256(Buffer.from(rpId)))) {
204
+ return { ok: false, reason: "rp id is not the paired one" };
205
+ }
206
+ const flags = authData[32];
207
+ if (!(flags & 0x01) || !(flags & 0x04))
208
+ return { ok: false, reason: "the user was not present and verified" };
209
+ const signCount = authData.readUInt32BE(33);
210
+ if ((signCount !== 0 || prevSignCount !== 0) && signCount <= prevSignCount) {
211
+ return { ok: false, reason: "sign count did not grow (a cloned authenticator or a replay)" };
212
+ }
213
+ let good;
214
+ try {
215
+ const key = createPublicKey({ key: fromB64u(credential), format: "der", type: "spki" });
216
+ good = verify(
217
+ "sha256",
218
+ Buffer.concat([authData, sha256(clientRaw)]),
219
+ key,
220
+ fromB64u(assertion.signature),
221
+ );
222
+ } catch {
223
+ good = false;
224
+ }
225
+ return good
226
+ ? { ok: true, signCount }
227
+ : { ok: false, reason: "signature does not verify with the paired passkey" };
228
+ }