@mmerterden/multi-agent-pipeline 20.2.1 → 20.3.1

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 (622) hide show
  1. package/CHANGELOG.md +919 -1
  2. package/README.md +104 -81
  3. package/README.tr.md +103 -62
  4. package/docs/FIGMA_PIPELINE.md +35 -35
  5. package/docs/adr/0006-skills-core-external-split.md +1 -1
  6. package/docs/adr/0007-multi-tool-adapter-framework.md +6 -0
  7. package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +7 -0
  8. package/docs/architecture.md +50 -14
  9. package/docs/best-practices.md +1 -1
  10. package/docs/ecosystem.md +56 -32
  11. package/docs/facts.json +10 -10
  12. package/docs/features.md +97 -5
  13. package/docs/recovery-guide.md +7 -14
  14. package/docs/server-readiness.md +31 -24
  15. package/index.js +1 -1
  16. package/install/_common.mjs +3 -5
  17. package/install/_platform-filter.mjs +23 -1
  18. package/install/_unattended-profile.mjs +321 -75
  19. package/install/claude.mjs +51 -10
  20. package/install/codex.mjs +2 -0
  21. package/install/copilot.mjs +2 -0
  22. package/install/index.mjs +30 -17
  23. package/install/templates/claude-hooks.json +16 -5
  24. package/install/templates/copilot-instructions.md +1 -1
  25. package/install/templates/multi-agent-autopilot-awake.plist.template +48 -0
  26. package/install/templates/multi-agent-autopilot.plist.template +12 -5
  27. package/install/unattended-profile-legacy.json +80 -0
  28. package/manifest.json +617 -483
  29. package/package.json +8 -3
  30. package/pipeline/agents/code-reviewer.md +10 -0
  31. package/pipeline/agents/plan-critic.md +98 -0
  32. package/pipeline/agents/security-auditor.md +10 -0
  33. package/pipeline/agents/task-clarifier.md +10 -0
  34. package/pipeline/commands/multi-agent/SKILL.md +2 -2
  35. package/pipeline/commands/multi-agent/analysis/SKILL.md +6 -2
  36. package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +12 -1
  37. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +4 -0
  38. package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
  39. package/pipeline/commands/multi-agent/autopilot-off/SKILL.md +36 -6
  40. package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +77 -12
  41. package/pipeline/commands/multi-agent/autopilot-status/SKILL.md +29 -8
  42. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +4 -0
  43. package/pipeline/commands/multi-agent/channels/SKILL.md +9 -9
  44. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +5 -1
  45. package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -0
  46. package/pipeline/commands/multi-agent/design-check/SKILL.md +16 -11
  47. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +4 -0
  48. package/pipeline/commands/multi-agent/doctor/SKILL.md +4 -0
  49. package/pipeline/commands/multi-agent/feedback/SKILL.md +5 -1
  50. package/pipeline/commands/multi-agent/forget/SKILL.md +4 -0
  51. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +4 -0
  52. package/pipeline/commands/multi-agent/graph/SKILL.md +4 -0
  53. package/pipeline/commands/multi-agent/help/SKILL.md +37 -39
  54. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +4 -0
  55. package/pipeline/commands/multi-agent/issue/SKILL.md +7 -1
  56. package/pipeline/commands/multi-agent/jira/SKILL.md +7 -1
  57. package/pipeline/commands/multi-agent/kill/SKILL.md +9 -3
  58. package/pipeline/commands/multi-agent/language/SKILL.md +4 -0
  59. package/pipeline/commands/multi-agent/log/SKILL.md +4 -0
  60. package/pipeline/commands/multi-agent/manual-test/SKILL.md +5 -0
  61. package/pipeline/commands/multi-agent/model/SKILL.md +4 -0
  62. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -0
  63. package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +4 -0
  64. package/pipeline/commands/multi-agent/purge/SKILL.md +4 -0
  65. package/pipeline/commands/multi-agent/refactor/SKILL.md +5 -0
  66. package/pipeline/commands/multi-agent/research/SKILL.md +49 -0
  67. package/pipeline/commands/multi-agent/resume/SKILL.md +29 -6
  68. package/pipeline/commands/multi-agent/review/SKILL.md +28 -9
  69. package/pipeline/commands/multi-agent/review-analysis/SKILL.md +5 -1
  70. package/pipeline/commands/multi-agent/review-issue/SKILL.md +4 -0
  71. package/pipeline/commands/multi-agent/review-jira/SKILL.md +4 -0
  72. package/pipeline/commands/multi-agent/route-off/SKILL.md +4 -0
  73. package/pipeline/commands/multi-agent/route-on/SKILL.md +4 -0
  74. package/pipeline/commands/multi-agent/route-status/SKILL.md +4 -0
  75. package/pipeline/commands/multi-agent/routines/SKILL.md +4 -0
  76. package/pipeline/commands/multi-agent/save/SKILL.md +6 -2
  77. package/pipeline/commands/multi-agent/scaffold/SKILL.md +47 -0
  78. package/pipeline/commands/multi-agent/scan/SKILL.md +4 -0
  79. package/pipeline/commands/multi-agent/search/SKILL.md +12 -8
  80. package/pipeline/commands/multi-agent/security-review/SKILL.md +4 -0
  81. package/pipeline/commands/multi-agent/serve/SKILL.md +62 -0
  82. package/pipeline/commands/multi-agent/setup/SKILL.md +27 -36
  83. package/pipeline/commands/multi-agent/stack/SKILL.md +4 -0
  84. package/pipeline/commands/multi-agent/status/SKILL.md +10 -9
  85. package/pipeline/commands/multi-agent/steer/SKILL.md +5 -1
  86. package/pipeline/commands/multi-agent/store-ready/SKILL.md +4 -0
  87. package/pipeline/commands/multi-agent/sync/SKILL.md +13 -13
  88. package/pipeline/commands/multi-agent/test/SKILL.md +4 -0
  89. package/pipeline/commands/multi-agent/test-accessibility/SKILL.md +4 -0
  90. package/pipeline/commands/multi-agent/test-dark-mode/SKILL.md +4 -0
  91. package/pipeline/commands/multi-agent/test-dynamic-type/SKILL.md +4 -0
  92. package/pipeline/commands/multi-agent/test-screenshots/SKILL.md +5 -1
  93. package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +4 -0
  94. package/pipeline/commands/multi-agent/uninstall/SKILL.md +5 -1
  95. package/pipeline/commands/multi-agent/update/SKILL.md +4 -0
  96. package/pipeline/contract/CHANGELOG.md +74 -0
  97. package/pipeline/contract/README.md +126 -0
  98. package/pipeline/contract/build.mjs +427 -0
  99. package/pipeline/contract/fixtures/answer-result.json +11 -0
  100. package/pipeline/contract/fixtures/error-invalid-request.json +8 -0
  101. package/pipeline/contract/fixtures/error-unauthorized.json +5 -0
  102. package/pipeline/contract/fixtures/error-unsigned.json +5 -0
  103. package/pipeline/contract/fixtures/issues-empty.json +18 -0
  104. package/pipeline/contract/fixtures/launch-plan.json +31 -0
  105. package/pipeline/contract/fixtures/runs-awaiting-question.json +70 -0
  106. package/pipeline/contract/fixtures/runs-empty.json +6 -0
  107. package/pipeline/contract/fixtures/runs-failed.json +84 -0
  108. package/pipeline/contract/fixtures/runs-old-schema.json +70 -0
  109. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +101 -0
  110. package/pipeline/contract/fixtures/runs-pr-opened.json +106 -0
  111. package/pipeline/contract/fixtures/runs-running.json +84 -0
  112. package/pipeline/contract/fixtures/worktrees-empty.json +4 -0
  113. package/pipeline/contract/frozen/toolbox.json +107 -0
  114. package/pipeline/contract/manifest.json +263 -0
  115. package/pipeline/contract/types/index.d.ts +343 -0
  116. package/pipeline/lib/_jira-auth.sh +6 -2
  117. package/pipeline/lib/account-resolver.sh +1 -1
  118. package/pipeline/lib/autopilot-state.sh +19 -0
  119. package/pipeline/lib/context-link-extractor.sh +12 -5
  120. package/pipeline/lib/credential-inventory.sh +12 -5
  121. package/pipeline/lib/credential-store.sh +116 -185
  122. package/pipeline/lib/fetch-confluence.sh +44 -3
  123. package/pipeline/lib/fetch-document.sh +3 -4
  124. package/pipeline/lib/fetch-fortify.sh +1 -1
  125. package/pipeline/lib/figma-mcp-refresh.sh +2 -2
  126. package/pipeline/lib/figma-token.sh +5 -1
  127. package/pipeline/lib/issue-fetcher.sh +233 -16
  128. package/pipeline/lib/json-file-lock.mjs +172 -0
  129. package/pipeline/lib/model-dispatch.sh +21 -12
  130. package/pipeline/lib/model-rung.sh +6 -1
  131. package/pipeline/lib/multi-repo-pipeline.sh +1 -1
  132. package/pipeline/lib/outbound-gate.mjs +46 -16
  133. package/pipeline/lib/parse-complaints.sh +14 -7
  134. package/pipeline/lib/plan-todos.sh +3 -3
  135. package/pipeline/lib/post-pr-review.sh +9 -9
  136. package/pipeline/lib/pr-request-location.mjs +85 -0
  137. package/pipeline/lib/regular-file.mjs +153 -0
  138. package/pipeline/lib/repo-hygiene.sh +17 -0
  139. package/pipeline/lib/route-state.sh +5 -1
  140. package/pipeline/lib/run-paths.sh +3 -2
  141. package/pipeline/lib/stack-detect.sh +19 -1
  142. package/pipeline/lib/unattended-profile-check.mjs +178 -0
  143. package/pipeline/lib/unattended-settings-location.mjs +28 -0
  144. package/pipeline/lib/unattended.mjs +76 -0
  145. package/pipeline/lib/unattended.sh +32 -0
  146. package/pipeline/lib/untrusted.mjs +76 -0
  147. package/pipeline/lib/usage-endpoint.mjs +33 -0
  148. package/pipeline/lib/user-facing.mjs +82 -0
  149. package/pipeline/lib/user-facing.sh +58 -0
  150. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  151. package/pipeline/multi-agent-refs/analysis/evidence.md +4 -0
  152. package/pipeline/multi-agent-refs/analysis/locked.md +2 -2
  153. package/pipeline/multi-agent-refs/analysis/render.md +4 -3
  154. package/pipeline/multi-agent-refs/analysis/resolve.md +3 -1
  155. package/pipeline/multi-agent-refs/analysis/synthesis.md +4 -5
  156. package/pipeline/multi-agent-refs/analysis-template.md +10 -17
  157. package/pipeline/multi-agent-refs/channels/jira.md +1 -1
  158. package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
  159. package/pipeline/multi-agent-refs/conventions-defaults.md +32 -32
  160. package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -17
  161. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +37 -9
  162. package/pipeline/multi-agent-refs/features/autopilot-operations.md +277 -0
  163. package/pipeline/multi-agent-refs/features/constitution.md +196 -0
  164. package/pipeline/multi-agent-refs/features/doctor.md +6 -3
  165. package/pipeline/multi-agent-refs/features/external-context-injection.md +6 -1
  166. package/pipeline/multi-agent-refs/features/jira-context.md +1 -1
  167. package/pipeline/multi-agent-refs/features/maturity-followup.md +43 -23
  168. package/pipeline/multi-agent-refs/features/model-fallback.md +9 -0
  169. package/pipeline/multi-agent-refs/features/phone-api.md +306 -0
  170. package/pipeline/multi-agent-refs/features/plan-critic.md +159 -0
  171. package/pipeline/multi-agent-refs/features/research.md +150 -0
  172. package/pipeline/multi-agent-refs/features/review-decision.md +185 -0
  173. package/pipeline/multi-agent-refs/features/scaffold.md +160 -0
  174. package/pipeline/multi-agent-refs/features/security-audit.md +6 -2
  175. package/pipeline/multi-agent-refs/features/skill-conformance.md +4 -1
  176. package/pipeline/multi-agent-refs/features/stack-adapters.md +116 -0
  177. package/pipeline/multi-agent-refs/features/unattended-gates.md +316 -0
  178. package/pipeline/multi-agent-refs/features/unattended-security.md +731 -0
  179. package/pipeline/multi-agent-refs/features/url-enrichment.md +2 -2
  180. package/pipeline/multi-agent-refs/features/usage-reporting.md +127 -35
  181. package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
  182. package/pipeline/multi-agent-refs/features/visual-evidence.md +7 -7
  183. package/pipeline/multi-agent-refs/keychain.md +6 -11
  184. package/pipeline/multi-agent-refs/payload-contracts.md +2 -2
  185. package/pipeline/multi-agent-refs/phases/log-format.md +2 -3
  186. package/pipeline/multi-agent-refs/phases/modes.md +10 -12
  187. package/pipeline/multi-agent-refs/phases/operations.md +11 -5
  188. package/pipeline/multi-agent-refs/phases/phase-0-init.md +73 -81
  189. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +22 -22
  190. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +42 -66
  191. package/pipeline/multi-agent-refs/phases/phase-3-review.md +45 -79
  192. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +28 -38
  193. package/pipeline/multi-agent-refs/phases/phase-5-report.md +27 -25
  194. package/pipeline/multi-agent-refs/phases.md +1 -1
  195. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  196. package/pipeline/multi-agent-refs/progress-contract.md +13 -16
  197. package/pipeline/multi-agent-refs/readiness-review.md +1 -1
  198. package/pipeline/multi-agent-refs/research/engine.md +91 -0
  199. package/pipeline/multi-agent-refs/rules.md +6 -4
  200. package/pipeline/multi-agent-refs/setup/repo-discovery.md +2 -2
  201. package/pipeline/multi-agent-refs/tracker-contract.md +2 -2
  202. package/pipeline/multi-agent-refs/unattended-contract.md +68 -16
  203. package/pipeline/rules/figma-pipeline.md +12 -12
  204. package/pipeline/schemas/agent-state.schema.json +480 -18
  205. package/pipeline/schemas/analysis-spec.schema.json +4 -4
  206. package/pipeline/schemas/answer-request.schema.json +24 -0
  207. package/pipeline/schemas/answer-result.schema.json +28 -0
  208. package/pipeline/schemas/autopilot-config.schema.json +111 -13
  209. package/pipeline/schemas/command-parameters.schema.json +99 -0
  210. package/pipeline/schemas/constitution.schema.json +56 -0
  211. package/pipeline/schemas/contract-error.schema.json +52 -0
  212. package/pipeline/schemas/design-check-config.schema.json +5 -1
  213. package/pipeline/schemas/issues.schema.json +61 -0
  214. package/pipeline/schemas/launch-plan.schema.json +46 -0
  215. package/pipeline/schemas/launch-request.schema.json +45 -0
  216. package/pipeline/schemas/launch.json +61 -0
  217. package/pipeline/schemas/launch.schema.json +84 -0
  218. package/pipeline/schemas/phases.json +2 -2
  219. package/pipeline/schemas/phases.schema.json +68 -0
  220. package/pipeline/schemas/phone-devices.schema.json +61 -0
  221. package/pipeline/schemas/phone-signed-request.schema.json +67 -0
  222. package/pipeline/schemas/plan-critique.schema.json +99 -0
  223. package/pipeline/schemas/plan-todos.schema.json +7 -7
  224. package/pipeline/schemas/planning-output.schema.json +5 -0
  225. package/pipeline/schemas/pr-request.schema.json +46 -0
  226. package/pipeline/schemas/prefs.schema.json +79 -8
  227. package/pipeline/schemas/research-output.schema.json +118 -0
  228. package/pipeline/schemas/review-file-exclusions.schema.json +25 -0
  229. package/pipeline/schemas/reviewer-output.schema.json +40 -4
  230. package/pipeline/schemas/run-questions.json +392 -0
  231. package/pipeline/schemas/run-questions.schema.json +118 -0
  232. package/pipeline/schemas/runs-index.schema.json +189 -0
  233. package/pipeline/schemas/scaffold-manifest.schema.json +43 -0
  234. package/pipeline/schemas/secret-patterns.schema.json +28 -0
  235. package/pipeline/schemas/stack-adapters.json +527 -0
  236. package/pipeline/schemas/stack-adapters.schema.json +184 -0
  237. package/pipeline/schemas/token-budget.json +1 -1
  238. package/pipeline/schemas/token-budget.schema.json +26 -0
  239. package/pipeline/schemas/triage-output.schema.json +64 -3
  240. package/pipeline/schemas/unattended-policy.json +139 -0
  241. package/pipeline/schemas/unattended-policy.schema.json +73 -0
  242. package/pipeline/schemas/unattended-profile.json +248 -0
  243. package/pipeline/schemas/unattended-profile.schema.json +198 -0
  244. package/pipeline/schemas/worktrees.schema.json +51 -0
  245. package/pipeline/scripts/README.md +1 -0
  246. package/pipeline/scripts/_autopilot-config.mjs +130 -0
  247. package/pipeline/scripts/_autopilot-ops.mjs +567 -0
  248. package/pipeline/scripts/_autopilot-outcomes.mjs +174 -0
  249. package/pipeline/scripts/_command-contract.mjs +384 -0
  250. package/pipeline/scripts/_cost.mjs +40 -0
  251. package/pipeline/scripts/_notices.mjs +160 -0
  252. package/pipeline/scripts/_phone-auth.mjs +485 -0
  253. package/pipeline/scripts/_pre-existing.mjs +294 -0
  254. package/pipeline/scripts/_redact.mjs +77 -0
  255. package/pipeline/scripts/_run-paths.mjs +4 -2
  256. package/pipeline/scripts/_stack-adapter.mjs +678 -0
  257. package/pipeline/scripts/_stack-routing.mjs +1 -1
  258. package/pipeline/scripts/agent-guard.py +348 -37
  259. package/pipeline/scripts/agent-guard.sh +41 -13
  260. package/pipeline/scripts/analysis-story-tree.mjs +79 -3
  261. package/pipeline/scripts/answer-question.mjs +181 -0
  262. package/pipeline/scripts/audit-log-rotate.sh +1 -4
  263. package/pipeline/scripts/audit-log.sh +4 -4
  264. package/pipeline/scripts/autopilot-arming.mjs +389 -21
  265. package/pipeline/scripts/autopilot-awake.mjs +255 -0
  266. package/pipeline/scripts/autopilot-intake.mjs +137 -36
  267. package/pipeline/scripts/autopilot-menubar.swift +156 -44
  268. package/pipeline/scripts/autopilot-publish.mjs +1625 -0
  269. package/pipeline/scripts/autopilot-runner.mjs +1678 -222
  270. package/pipeline/scripts/autopilot-status.sh +198 -33
  271. package/pipeline/scripts/build-lock.sh +120 -0
  272. package/pipeline/scripts/build-references.mjs +4 -1
  273. package/pipeline/scripts/build-stack-plugins.mjs +59 -22
  274. package/pipeline/scripts/capture-flush.sh +1 -1
  275. package/pipeline/scripts/capture-resume.sh +13 -9
  276. package/pipeline/scripts/check-derived-drift.mjs +52 -11
  277. package/pipeline/scripts/commands.mjs +88 -0
  278. package/pipeline/scripts/constitution.mjs +362 -0
  279. package/pipeline/scripts/contract-server.mjs +776 -0
  280. package/pipeline/scripts/cost-analyze.mjs +89 -39
  281. package/pipeline/scripts/diff-explain.mjs +12 -1
  282. package/pipeline/scripts/doctor.mjs +77 -28
  283. package/pipeline/scripts/evidence-gate.mjs +192 -12
  284. package/pipeline/scripts/feedback-send.mjs +4 -2
  285. package/pipeline/scripts/gate-ledger.mjs +449 -0
  286. package/pipeline/scripts/gc-abandoned.sh +132 -13
  287. package/pipeline/scripts/gen-facts.mjs +31 -15
  288. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
  289. package/pipeline/scripts/github-ssh-setup.sh +140 -29
  290. package/pipeline/scripts/graph-mermaid.mjs +4 -1
  291. package/pipeline/scripts/issues.mjs +236 -0
  292. package/pipeline/scripts/jira-attach.sh +6 -2
  293. package/pipeline/scripts/jira-search.sh +4 -3
  294. package/pipeline/scripts/keychain-save.sh +125 -24
  295. package/pipeline/scripts/keychain.py +63 -93
  296. package/pipeline/scripts/launch-request.mjs +747 -0
  297. package/pipeline/scripts/localize-commands.mjs +4 -10
  298. package/pipeline/scripts/log-metric.sh +6 -5
  299. package/pipeline/scripts/maturity-followup.mjs +13 -4
  300. package/pipeline/scripts/memory-save.sh +25 -0
  301. package/pipeline/scripts/migrate-prefs.mjs +4 -3
  302. package/pipeline/scripts/open-questions-gate.mjs +276 -0
  303. package/pipeline/scripts/phase-tracker.sh +41 -27
  304. package/pipeline/scripts/phase0-exit-gate.mjs +22 -4
  305. package/pipeline/scripts/phone-devices.mjs +224 -0
  306. package/pipeline/scripts/plan-coverage-gate.mjs +200 -66
  307. package/pipeline/scripts/plan-critique-gate.mjs +591 -0
  308. package/pipeline/scripts/pr-request.mjs +188 -0
  309. package/pipeline/scripts/pre-commit-check.sh +115 -4
  310. package/pipeline/scripts/probe-evidence-capability.sh +44 -5
  311. package/pipeline/scripts/record-phase.mjs +71 -0
  312. package/pipeline/scripts/render-agent-log-cost.sh +17 -2
  313. package/pipeline/scripts/render-cost-summary.sh +1 -1
  314. package/pipeline/scripts/render-work-summary.sh +1 -1
  315. package/pipeline/scripts/require-supported-version.sh +4 -1
  316. package/pipeline/scripts/research-gate.mjs +704 -0
  317. package/pipeline/scripts/review-decision-gate.mjs +403 -0
  318. package/pipeline/scripts/routine-registry.mjs +5 -2
  319. package/pipeline/scripts/runs-index.mjs +135 -27
  320. package/pipeline/scripts/scaffold-gate.mjs +393 -0
  321. package/pipeline/scripts/skill-conformance.mjs +25 -8
  322. package/pipeline/scripts/skill-siblings.mjs +2 -1
  323. package/pipeline/scripts/smoke-cross-cli-behavior.sh +32 -6
  324. package/pipeline/scripts/smoke-schema-validation.sh +6 -2
  325. package/pipeline/scripts/spec-consistency-gate.mjs +469 -0
  326. package/pipeline/scripts/symbol-existence-gate.mjs +450 -0
  327. package/pipeline/scripts/test-gap-scan.mjs +40 -2
  328. package/pipeline/scripts/test-integrity-gate.mjs +20 -4
  329. package/pipeline/scripts/test-strength.mjs +484 -0
  330. package/pipeline/scripts/test-summary.mjs +651 -0
  331. package/pipeline/scripts/triage-memory.mjs +49 -9
  332. package/pipeline/scripts/unattended_policy.py +2786 -0
  333. package/pipeline/scripts/uninstall.mjs +10 -10
  334. package/pipeline/scripts/update-issue-progress.sh +1 -1
  335. package/pipeline/scripts/usage-identity.mjs +288 -0
  336. package/pipeline/scripts/usage-register.mjs +187 -67
  337. package/pipeline/scripts/usage-report.mjs +232 -67
  338. package/pipeline/scripts/validate-complaint-doc.mjs +28 -10
  339. package/pipeline/scripts/validate-planning.mjs +6 -0
  340. package/pipeline/scripts/verify-citations.mjs +151 -38
  341. package/pipeline/scripts/verify.mjs +58 -18
  342. package/pipeline/scripts/worktree-prepare.sh +126 -0
  343. package/pipeline/scripts/worktrees.mjs +124 -0
  344. package/pipeline/scripts/write-state.mjs +48 -17
  345. package/pipeline/skills/.skill-manifest.json +222 -226
  346. package/pipeline/skills/.skills-index.json +77 -88
  347. package/pipeline/skills/shared/README.md +44 -45
  348. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +3 -2
  349. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +3 -2
  350. package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -9
  351. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +6 -5
  352. package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +11 -3
  353. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +2 -1
  354. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -3
  355. package/pipeline/skills/shared/core/multi-agent-autopilot-off/SKILL.md +32 -6
  356. package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +66 -8
  357. package/pipeline/skills/shared/core/multi-agent-autopilot-status/SKILL.md +27 -9
  358. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -1
  359. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +7 -4
  360. package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +3 -2
  361. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +2 -1
  362. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +7 -5
  363. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +2 -1
  364. package/pipeline/skills/shared/core/multi-agent-doctor/SKILL.md +3 -2
  365. package/pipeline/skills/shared/core/multi-agent-feedback/SKILL.md +2 -1
  366. package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +2 -1
  367. package/pipeline/skills/shared/core/multi-agent-garbage-collect/SKILL.md +2 -1
  368. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +2 -1
  369. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -1
  370. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -1
  371. package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +3 -2
  372. package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +2 -1
  373. package/pipeline/skills/shared/core/multi-agent-kill/SKILL.md +7 -4
  374. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -1
  375. package/pipeline/skills/shared/core/multi-agent-log/SKILL.md +2 -1
  376. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +2 -1
  377. package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -1
  378. package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +2 -1
  379. package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +2 -1
  380. package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +2 -1
  381. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +2 -1
  382. package/pipeline/skills/shared/core/multi-agent-research/SKILL.md +35 -0
  383. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +7 -3
  384. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +7 -5
  385. package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +2 -1
  386. package/pipeline/skills/shared/core/multi-agent-review-issue/SKILL.md +3 -2
  387. package/pipeline/skills/shared/core/multi-agent-review-jira/SKILL.md +2 -1
  388. package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +2 -1
  389. package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +2 -1
  390. package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +2 -1
  391. package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +2 -1
  392. package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +3 -2
  393. package/pipeline/skills/shared/core/multi-agent-scaffold/SKILL.md +30 -0
  394. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +2 -1
  395. package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +4 -3
  396. package/pipeline/skills/shared/core/multi-agent-security-review/SKILL.md +2 -1
  397. package/pipeline/skills/shared/core/multi-agent-serve/SKILL.md +60 -0
  398. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +8 -8
  399. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +2 -1
  400. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +7 -8
  401. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -1
  402. package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +2 -1
  403. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +10 -10
  404. package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +2 -1
  405. package/pipeline/skills/shared/core/multi-agent-test-accessibility/SKILL.md +2 -1
  406. package/pipeline/skills/shared/core/multi-agent-test-dark-mode/SKILL.md +2 -1
  407. package/pipeline/skills/shared/core/multi-agent-test-dynamic-type/SKILL.md +2 -1
  408. package/pipeline/skills/shared/core/multi-agent-test-screenshots/SKILL.md +2 -1
  409. package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -1
  410. package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +3 -2
  411. package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +2 -1
  412. package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +46 -0
  413. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +9 -3
  414. package/pipeline/skills/shared/external/NOTICE-paul-hudson-skills.md +54 -0
  415. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +9 -11
  416. package/pipeline/skills/shared/external/NOTICE-vibeship-spawner-skills.md +203 -0
  417. package/pipeline/skills/shared/external/NOTICE-xcode-build-skills.md +10 -3
  418. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +131 -25
  419. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +4 -3
  420. package/pipeline/skills/shared/external/alarmkit/SKILL.md +2 -1
  421. package/pipeline/skills/shared/external/android-architecture/SKILL.md +85 -76
  422. package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +51 -3
  423. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +9 -0
  424. package/pipeline/skills/shared/external/android-datastore/SKILL.md +4 -3
  425. package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +4 -3
  426. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +106 -181
  427. package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +147 -0
  428. package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +45 -4
  429. package/pipeline/skills/shared/external/android-performance/SKILL.md +27 -2
  430. package/pipeline/skills/shared/external/android-performance/references/patterns.md +37 -37
  431. package/pipeline/skills/shared/external/api-patterns/SKILL.md +111 -67
  432. package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +128 -0
  433. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +136 -186
  434. package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +140 -0
  435. package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +207 -0
  436. package/pipeline/skills/shared/external/api-security-best-practices/references/operations.md +66 -0
  437. package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +175 -0
  438. package/pipeline/skills/shared/external/app-clips/SKILL.md +2 -0
  439. package/pipeline/skills/shared/external/app-intents/SKILL.md +2 -0
  440. package/pipeline/skills/shared/external/app-store-changelog/SKILL.md +4 -3
  441. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +2 -0
  442. package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -0
  443. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +2 -0
  444. package/pipeline/skills/shared/external/architecture/SKILL.md +106 -39
  445. package/pipeline/skills/shared/external/architecture/references/adr-and-review.md +76 -0
  446. package/pipeline/skills/shared/external/authentication/SKILL.md +2 -0
  447. package/pipeline/skills/shared/external/avkit/SKILL.md +2 -0
  448. package/pipeline/skills/shared/external/background-processing/SKILL.md +2 -1
  449. package/pipeline/skills/shared/external/backlog/SKILL.md +3 -2
  450. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +2 -0
  451. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +1 -1
  452. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +3 -2
  453. package/pipeline/skills/shared/external/clean-code/SKILL.md +192 -90
  454. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +2 -1
  455. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +1 -1
  456. package/pipeline/skills/shared/external/compose-components/SKILL.md +4 -4
  457. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +22 -22
  458. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +10 -10
  459. package/pipeline/skills/shared/external/compose-testing/SKILL.md +13 -6
  460. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +59 -59
  461. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +2 -0
  462. package/pipeline/skills/shared/external/context-compression/SKILL.md +118 -250
  463. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +2 -0
  464. package/pipeline/skills/shared/external/core-data/SKILL.md +2 -0
  465. package/pipeline/skills/shared/external/core-motion/SKILL.md +2 -0
  466. package/pipeline/skills/shared/external/core-nfc/SKILL.md +2 -0
  467. package/pipeline/skills/shared/external/coreml/SKILL.md +2 -1
  468. package/pipeline/skills/shared/external/council/SKILL.md +2 -1
  469. package/pipeline/skills/shared/external/cryptokit/SKILL.md +2 -0
  470. package/pipeline/skills/shared/external/css-modern/SKILL.md +3 -2
  471. package/pipeline/skills/shared/external/database-patterns/SKILL.md +3 -2
  472. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +2 -0
  473. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +130 -20
  474. package/pipeline/skills/shared/external/debugging-strategies/references/hard-cases.md +54 -0
  475. package/pipeline/skills/shared/external/device-integrity/SKILL.md +2 -0
  476. package/pipeline/skills/shared/external/docker-expert/SKILL.md +137 -381
  477. package/pipeline/skills/shared/external/docker-expert/references/patterns.md +98 -0
  478. package/pipeline/skills/shared/external/energykit/SKILL.md +2 -1
  479. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +3 -1
  480. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +2 -2
  481. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +116 -185
  482. package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +235 -0
  483. package/pipeline/skills/shared/external/fastapi-pro/references/testing-and-deployment.md +69 -0
  484. package/pipeline/skills/shared/external/firebase/SKILL.md +4 -3
  485. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +151 -293
  486. package/pipeline/skills/shared/external/github-actions-templates/references/patterns.md +96 -0
  487. package/pipeline/skills/shared/external/healthkit/SKILL.md +2 -0
  488. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +69 -72
  489. package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +111 -0
  490. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +77 -86
  491. package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +98 -0
  492. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +150 -78
  493. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +76 -97
  494. package/pipeline/skills/shared/external/hig-components-system/references/surfaces.md +98 -0
  495. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +48 -76
  496. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +132 -0
  497. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +147 -106
  498. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +61 -77
  499. package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +92 -0
  500. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +147 -77
  501. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +61 -121
  502. package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +108 -0
  503. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +2 -0
  504. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +1 -1
  505. package/pipeline/skills/shared/external/html-semantic/SKILL.md +3 -2
  506. package/pipeline/skills/shared/external/humanizer/SKILL.md +2 -1
  507. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +2 -0
  508. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +2 -1
  509. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +14 -2
  510. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
  511. package/pipeline/skills/shared/external/ios-debugger-agent/SKILL.md +4 -3
  512. package/pipeline/skills/shared/external/ios-localization/SKILL.md +8 -6
  513. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +2 -2
  514. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +11 -11
  515. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +2 -1
  516. package/pipeline/skills/shared/external/ios-networking/SKILL.md +2 -0
  517. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +2 -0
  518. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +102 -193
  519. package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +103 -0
  520. package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -2
  521. package/pipeline/skills/shared/external/live-activities/evals/evals.json +2 -2
  522. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +7 -9
  523. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +3 -3
  524. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +2 -2
  525. package/pipeline/skills/shared/external/macos-menubar-tuist-app/SKILL.md +4 -3
  526. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +5 -3
  527. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +2 -0
  528. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +1 -1
  529. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +2 -0
  530. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +1 -1
  531. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +149 -46
  532. package/pipeline/skills/shared/external/monorepo-architect/references/patterns.md +70 -0
  533. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +2 -0
  534. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +1 -1
  535. package/pipeline/skills/shared/external/natural-language/SKILL.md +2 -0
  536. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +3 -2
  537. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +91 -21
  538. package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +247 -0
  539. package/pipeline/skills/shared/external/nodejs-backend-patterns/references/testing-and-frameworks.md +57 -0
  540. package/pipeline/skills/shared/external/observability-engineer/SKILL.md +155 -232
  541. package/pipeline/skills/shared/external/observability-engineer/references/patterns.md +75 -0
  542. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +2 -0
  543. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +2 -2
  544. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +18 -18
  545. package/pipeline/skills/shared/external/pdfkit/SKILL.md +2 -1
  546. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -0
  547. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
  548. package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -0
  549. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +2 -1
  550. package/pipeline/skills/shared/external/push-notifications/SKILL.md +2 -0
  551. package/pipeline/skills/shared/external/python-patterns/SKILL.md +3 -2
  552. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +3 -2
  553. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +2 -2
  554. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
  555. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +3 -2
  556. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +7 -7
  557. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +69 -69
  558. package/pipeline/skills/shared/external/room-database/references/patterns.md +126 -126
  559. package/pipeline/skills/shared/external/search-first/SKILL.md +2 -1
  560. package/pipeline/skills/shared/external/security-review/SKILL.md +4 -3
  561. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +2 -2
  562. package/pipeline/skills/shared/external/signal-community/SKILL.md +1 -1
  563. package/pipeline/skills/shared/external/skill-creator/SKILL.md +2 -1
  564. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +2 -1
  565. package/pipeline/skills/shared/external/spm-build-analysis/SKILL.md +2 -0
  566. package/pipeline/skills/shared/external/storekit/SKILL.md +2 -0
  567. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +2 -0
  568. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +2 -1
  569. package/pipeline/skills/shared/external/swift-charts/SKILL.md +2 -1
  570. package/pipeline/skills/shared/external/swift-codable/SKILL.md +2 -0
  571. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +3 -1
  572. package/pipeline/skills/shared/external/swift-concurrency-expert/SKILL.md +5 -4
  573. package/pipeline/skills/shared/external/swift-concurrency-pro/SKILL.md +2 -1
  574. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +2 -0
  575. package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -1
  576. package/pipeline/skills/shared/external/swift-security/SKILL.md +2 -1
  577. package/pipeline/skills/shared/external/swift-testing/SKILL.md +2 -0
  578. package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +2 -1
  579. package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +10 -0
  580. package/pipeline/skills/shared/external/swiftdata/SKILL.md +2 -0
  581. package/pipeline/skills/shared/external/swiftdata-pro/SKILL.md +2 -1
  582. package/pipeline/skills/shared/external/swiftlint/SKILL.md +2 -0
  583. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +2 -0
  584. package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +3 -1
  585. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +2 -0
  586. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +2 -0
  587. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +2 -0
  588. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +2 -0
  589. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +3 -1
  590. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +3 -1
  591. package/pipeline/skills/shared/external/swiftui-performance-audit/SKILL.md +5 -4
  592. package/pipeline/skills/shared/external/swiftui-pro/SKILL.md +2 -1
  593. package/pipeline/skills/shared/external/swiftui-ui-patterns/SKILL.md +5 -4
  594. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +2 -0
  595. package/pipeline/skills/shared/external/swiftui-view-refactor/SKILL.md +4 -3
  596. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +2 -0
  597. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +3 -2
  598. package/pipeline/skills/shared/external/testing-backend/SKILL.md +3 -2
  599. package/pipeline/skills/shared/external/tipkit/SKILL.md +2 -0
  600. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +3 -2
  601. package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -0
  602. package/pipeline/skills/shared/external/vue-composition/SKILL.md +3 -2
  603. package/pipeline/skills/shared/external/weatherkit/SKILL.md +2 -0
  604. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +3 -2
  605. package/pipeline/skills/shared/external/web-performance/SKILL.md +3 -2
  606. package/pipeline/skills/shared/external/web-testing/SKILL.md +3 -2
  607. package/pipeline/skills/shared/external/widgetkit/SKILL.md +2 -0
  608. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +1 -1
  609. package/pipeline/skills/shared/external/xcode-build-benchmark/SKILL.md +2 -0
  610. package/pipeline/skills/shared/external/xcode-build-fixer/SKILL.md +2 -0
  611. package/pipeline/skills/shared/external/xcode-build-orchestrator/SKILL.md +2 -0
  612. package/pipeline/skills/shared/external/xcode-compilation-analyzer/SKILL.md +2 -0
  613. package/pipeline/skills/shared/external/xcode-project-analyzer/SKILL.md +2 -0
  614. package/pipeline/skills/skills-index.md +40 -41
  615. package/docs/token-budget-history.md +0 -24
  616. package/pipeline/skills/shared/external/agentflow/SKILL.md +0 -199
  617. package/pipeline/skills/shared/external/android-ui-verification/SKILL.md +0 -66
  618. package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +0 -299
  619. package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +0 -255
  620. package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +0 -167
  621. package/pipeline/skills/shared/external/closed-loop-delivery/SKILL.md +0 -116
  622. package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -216
@@ -0,0 +1,776 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @file contract-server.mjs - the pipeline's client contract over HTTP, on
4
+ * this machine only, for as long as someone is using it.
5
+ *
6
+ * The contract has two carriers. The first is the CLI: runs-index.mjs,
7
+ * commands.mjs, issues.mjs, worktrees.mjs, launch-request.mjs and
8
+ * answer-question.mjs print JSON a client decodes. This is the second, for a
9
+ * client that would rather hold a socket than spawn a process per read. It
10
+ * adds no behaviour: every route calls the function the matching CLI calls
11
+ * and serializes it the same way, so for the same input the body is the same
12
+ * bytes the CLI prints (test/contract-parity.test.mjs holds that).
13
+ *
14
+ * Routes (pipeline/contract/manifest.json lists them with their schemas):
15
+ *
16
+ * GET /v1/runs runs-index.mjs --json [--group G] [--redact]
17
+ * GET /v1/runs/{id} runs-index.mjs --json --task-id {id} [--redact]
18
+ * GET /v1/commands commands.mjs --json
19
+ * GET /v1/questions launch-request.mjs questions
20
+ * GET /v1/issues issues.mjs --json [--source S] [--project K]... [--max N]
21
+ * GET /v1/worktrees worktrees.mjs --json [--measure] [--redact]
22
+ * GET /v1/launch-spec launch-request.mjs spec
23
+ * POST /v1/launch?repo=P write a launch request, return launch-request.mjs plan
24
+ * POST /v1/runs/{id}/answer answer-question.mjs --question Q --answer IDS
25
+ *
26
+ * Phone routes, the same handlers behind a device signature instead of the
27
+ * bearer token, always redacted (features/phone-api.md):
28
+ *
29
+ * GET /v1/phone/runs scope read
30
+ * GET /v1/phone/runs/{id} scope read
31
+ * POST /v1/phone/runs/{id}/answer scope answer
32
+ * POST /v1/phone/launch?repo=P scope launch, and only after `phone-devices.mjs launch on`
33
+ *
34
+ * There is deliberately no route that merges, marks ready, runs a command or
35
+ * starts a host. POST /v1/launch writes the request file and returns the argv
36
+ * and environment launch.json declares; the client starts the process, and
37
+ * nothing here ever spawns `claude`.
38
+ *
39
+ * Security model. The listener binds 127.0.0.1 and nothing else: any other
40
+ * --host, 0.0.0.0 and :: included, is refused at startup. Each start mints a
41
+ * random token (32 bytes from crypto.randomBytes) and writes it to a 0600 file
42
+ * under ~/.claude/logs/multi-agent/contract-server/ (0700); --print-token also
43
+ * puts it on the ready line for a parent that spawned the server itself. Every
44
+ * request is checked in this order:
45
+ *
46
+ * 1. Host must be 127.0.0.1:<port> or localhost:<port>, which is what stops
47
+ * a DNS-rebound page from reaching the socket under another name (403);
48
+ * 2. an Origin header must be one of --allow-origin; without the flag no
49
+ * Origin is accepted and no CORS header is ever sent (403);
50
+ * 3. an allowed preflight is answered, before authentication, since a
51
+ * browser sends no credentials on one (204);
52
+ * 4. a path under /v1/phone/ leaves here for the device-signature checks in
53
+ * _phone-auth.mjs (401 per refusal, then 404 unknown-verb, 403
54
+ * forbidden-scope); the bearer token is not consulted there, and a
55
+ * signature is not consulted anywhere else;
56
+ * 5. `Authorization: Bearer <token>`, compared in constant time over SHA-256
57
+ * digests so neither content nor length leaks through timing (401);
58
+ * 6. the route, its method and its query parameters, each validated (404,
59
+ * 405, 400); a POST body must be application/json (415), at most 64 KiB,
60
+ * and valid against the route's schema (400).
61
+ *
62
+ * `?redact=1` is `--redact`. A request that carries an Origin is a browser
63
+ * page, which can relay what it reads anywhere, so it is always served
64
+ * redacted whatever the query says.
65
+ *
66
+ * Lifetime. The server exits after --idle-minutes without a request (15 by
67
+ * default) and on SIGTERM, SIGINT or SIGHUP, removing its token file either
68
+ * way. It is started on demand by `/multi-agent:serve` or by a client, and is
69
+ * never installed as a service.
70
+ *
71
+ * Zero runtime dependencies (ADR-0004): node:http, node:crypto and the
72
+ * producers' own modules.
73
+ *
74
+ * Usage:
75
+ * node contract-server.mjs [--port N] [--idle-minutes N] [--allow-origin <origin>]... [--print-token]
76
+ *
77
+ * Stdout: one JSON line once listening - {contractVersion, url, port, pid,
78
+ * tokenFile, idleMinutes[, token]}.
79
+ * Exit: 0 stopped (idle or signal), 1 could not listen, 2 usage error.
80
+ *
81
+ * @module pipeline/scripts/contract-server
82
+ */
83
+
84
+ import { Buffer } from "node:buffer";
85
+ import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
86
+ import { chmodSync, existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
87
+ import { createServer } from "node:http";
88
+ import { homedir } from "node:os";
89
+ import { isAbsolute, join } from "node:path";
90
+ import { clearTimeout, setTimeout } from "node:timers";
91
+ import { answerStateFile } from "./answer-question.mjs";
92
+ import { catalog } from "./commands.mjs";
93
+ import { collect } from "./issues.mjs";
94
+ import {
95
+ launchRepoAllowed,
96
+ launchSpec,
97
+ planLaunch,
98
+ questionsDocument,
99
+ requestsDir,
100
+ workingTreeState,
101
+ writeRequestFile,
102
+ } from "./launch-request.mjs";
103
+ import { GROUP_NAMES, envelope, selectRuns } from "./runs-index.mjs";
104
+ import {
105
+ PHONE_PREFIX,
106
+ appendAudit,
107
+ createNonceStore,
108
+ noncesPath,
109
+ readRegistry,
110
+ verbOf,
111
+ verifyRequest,
112
+ } from "./_phone-auth.mjs";
113
+ import { buildList } from "./worktrees.mjs";
114
+ import { runMain } from "../lib/fatal.mjs";
115
+ import { invokedDirectly } from "../lib/invoked-directly.mjs";
116
+
117
+ /** The contract as a whole; pipeline/contract/manifest.json carries the same. */
118
+ export const CONTRACT_VERSION = "1.2.0";
119
+ /** The majors `?v=` accepts. A new major keeps the previous one here for one minor. */
120
+ export const SUPPORTED_MAJORS = [1];
121
+ export const BIND_HOST = "127.0.0.1";
122
+ export const MAX_BODY_BYTES = 64 * 1024;
123
+ /** How much of an oversized body is drained before the socket is dropped. */
124
+ const MAX_DRAIN_BYTES = 1024 * 1024;
125
+ const DEFAULT_IDLE_MINUTES = 15;
126
+ const MAX_IDLE_MINUTES = 24 * 60;
127
+ const RUN_ID = /^[A-Za-z0-9#][A-Za-z0-9#._-]{0,127}$/;
128
+ const PROJECT_KEY = /^[A-Z][A-Z0-9_]*$/;
129
+ const QUESTION_ID = /^[a-z][a-z0-9-]*$/;
130
+ const OPTION_ID = /^[a-z0-9][a-z0-9+_-]*$/;
131
+
132
+ /** Where token files live. Fixed under HOME, never under LOGS_ROOT. */
133
+ export function tokenDir() {
134
+ return join(homedir(), ".claude", "logs", "multi-agent", "contract-server");
135
+ }
136
+
137
+ /** The exact bytes a CLI prints for the same object. */
138
+ export const serialize = (obj) => JSON.stringify(obj, null, 2) + "\n";
139
+
140
+ class HttpError extends Error {
141
+ constructor(status, code, message, extra = {}) {
142
+ super(message);
143
+ this.status = status;
144
+ this.code = code;
145
+ this.extra = extra;
146
+ }
147
+ }
148
+
149
+ const flag = (v) => v === "1";
150
+ const one = (q, name) => (q[name] === undefined ? null : q[name][0]);
151
+
152
+ /** Query-parameter rules: name -> {repeat?, check(value) -> ok}. */
153
+ const Q = {
154
+ redact: { check: (v) => v === "0" || v === "1" },
155
+ group: { check: (v) => GROUP_NAMES.includes(v) },
156
+ measure: { check: (v) => v === "0" || v === "1" },
157
+ source: { check: (v) => ["jira", "github", "all"].includes(v) },
158
+ project: { repeat: true, check: (v) => PROJECT_KEY.test(v) },
159
+ max: { check: (v) => /^[0-9]{1,3}$/.test(v) && Number(v) >= 1 && Number(v) <= 200 },
160
+ repo: { check: (v) => v.length <= 4096 && isAbsolute(v) },
161
+ };
162
+
163
+ function runRecordFor(id) {
164
+ if (!RUN_ID.test(id)) throw new HttpError(404, "not-found", "no such run");
165
+ const { found, records } = selectRuns({ taskId: id });
166
+ if (!found) throw new HttpError(404, "not-found", "no such run");
167
+ return records;
168
+ }
169
+
170
+ function stateFileOf(record) {
171
+ for (const p of [
172
+ join(record.dir, "agent-state.json"),
173
+ join(record.dir, "artifacts", "agent-state.json"),
174
+ ]) {
175
+ if (existsSync(p)) return p;
176
+ }
177
+ return null;
178
+ }
179
+
180
+ /** answer-request.schema.json, checked by hand (ADR-0004). */
181
+ export function answerBodyErrors(body) {
182
+ if (!body || typeof body !== "object" || Array.isArray(body))
183
+ return ["body is not a JSON object"];
184
+ const errors = [];
185
+ for (const k of Object.keys(body)) {
186
+ if (k !== "questionId" && k !== "optionIds") errors.push(`unknown field ${k}`);
187
+ }
188
+ if (
189
+ typeof body.questionId !== "string" ||
190
+ body.questionId.length > 100 ||
191
+ !QUESTION_ID.test(body.questionId)
192
+ ) {
193
+ errors.push("questionId must be a question id");
194
+ }
195
+ const ids = body.optionIds;
196
+ if (!Array.isArray(ids) || ids.length < 1 || ids.length > 50) {
197
+ errors.push("optionIds must be an array of 1 to 50 option ids");
198
+ } else if (!ids.every((id) => typeof id === "string" && id.length <= 100 && OPTION_ID.test(id))) {
199
+ errors.push("every optionIds entry must be an option id");
200
+ }
201
+ return errors;
202
+ }
203
+
204
+ const PHONE_REFUSAL = {
205
+ unsigned: "a device signature is required",
206
+ "unknown-device": "this device is not enrolled here",
207
+ "stale-request": "the timestamp is outside the accepted window",
208
+ "bad-signature": "the signature does not match the request",
209
+ "revoked-device": "this device was revoked",
210
+ replayed: "this request was already received",
211
+ "replay-store-full": "too many requests in the window; retry shortly",
212
+ };
213
+
214
+ const ANSWER_REFUSAL = {
215
+ "no-state": [409, "not-waiting"],
216
+ "not-waiting": [409, "not-waiting"],
217
+ "wrong-question": [409, "wrong-question"],
218
+ "state-moved": [409, "state-moved"],
219
+ "no-answer": [422, "wrong-arity"],
220
+ arity: [422, "wrong-arity"],
221
+ "not-an-option": [422, "not-an-option"],
222
+ };
223
+
224
+ /**
225
+ * POST /v1/launch and /v1/phone/launch. `channel` decides what the input may
226
+ * be (launch-request.mjs inputErrors): the phone takes structured references
227
+ * only. Either way the repo must be a configured or registered one.
228
+ */
229
+ function launchRun({ q, body, redact, channel = "desktop" }) {
230
+ const repoPath = one(q, "repo");
231
+ if (!repoPath) throw new HttpError(400, "invalid-query", "repo is required");
232
+ if (!existsSync(repoPath) || !statSync(repoPath).isDirectory()) {
233
+ throw new HttpError(400, "invalid-query", "repo is not a directory");
234
+ }
235
+ if (!launchRepoAllowed(repoPath)) {
236
+ throw new HttpError(
237
+ 403,
238
+ "repo-not-allowed",
239
+ "repo is neither a configured autopilot repo nor one registered with launch-request.mjs register-repo",
240
+ );
241
+ }
242
+ const sessionId = randomUUID();
243
+ const planned = join(requestsDir(), `${sessionId}.json`);
244
+ const args = {
245
+ request: body,
246
+ requestFile: planned,
247
+ repoPath,
248
+ sessionId,
249
+ workingTree: workingTreeState(repoPath),
250
+ channel,
251
+ };
252
+ const dry = planLaunch(args);
253
+ if (dry.code !== 0) {
254
+ const status = dry.reason === "no-launch-shape" ? 422 : 400;
255
+ throw new HttpError(status, dry.reason, "the launch request was refused", {
256
+ errors: dry.out.errors,
257
+ });
258
+ }
259
+ const written = writeRequestFile(body, sessionId);
260
+ return planLaunch({ ...args, requestFile: written, redact }).out;
261
+ }
262
+
263
+ function answerRun({ params, body }) {
264
+ const errors = answerBodyErrors(body);
265
+ if (errors.length) {
266
+ throw new HttpError(
267
+ 400,
268
+ "invalid-body",
269
+ "the answer does not match answer-request.schema.json",
270
+ {
271
+ errors,
272
+ },
273
+ );
274
+ }
275
+ const [record] = runRecordFor(params.id);
276
+ const file = stateFileOf(record);
277
+ if (!file) throw new HttpError(409, "not-waiting", "this run is not waiting on a question");
278
+ const r = answerStateFile(file, body.optionIds, { questionId: body.questionId });
279
+ if (r.code === 0) return r.out;
280
+ const [status, code] = ANSWER_REFUSAL[r.reason] ?? [500, "internal"];
281
+ throw new HttpError(status, code, status === 500 ? "the answer could not be written" : r.error);
282
+ }
283
+
284
+ const listRuns = ({ q, redact }) =>
285
+ envelope(selectRuns({ group: one(q, "group") }).records, { redact });
286
+ const oneRun = ({ params, redact }) => envelope(runRecordFor(params.id), { redact });
287
+
288
+ /**
289
+ * Every route. `query` names the parameters beyond `v` and `redact` that a
290
+ * route accepts; `body` marks a route that reads a JSON body. A route with
291
+ * `scope` is a phone route: it is reached only with a device signature, never
292
+ * with the bearer token, and is always served redacted.
293
+ */
294
+ export const ROUTES = [
295
+ { method: "GET", path: "/v1/runs", query: ["group"], handle: listRuns },
296
+ { method: "GET", path: "/v1/runs/{id}", query: [], handle: oneRun },
297
+ {
298
+ method: "GET",
299
+ path: "/v1/commands",
300
+ query: [],
301
+ handle: () => {
302
+ const out = catalog();
303
+ if (!out) throw new HttpError(500, "internal", "no command tree found");
304
+ return out;
305
+ },
306
+ },
307
+ { method: "GET", path: "/v1/questions", query: [], handle: () => questionsDocument() },
308
+ {
309
+ method: "GET",
310
+ path: "/v1/issues",
311
+ query: ["source", "project", "max"],
312
+ handle: ({ q }) =>
313
+ collect({
314
+ source: one(q, "source") ?? "all",
315
+ projects: q.project ?? [],
316
+ max: Number(one(q, "max") ?? 50),
317
+ }),
318
+ },
319
+ {
320
+ method: "GET",
321
+ path: "/v1/worktrees",
322
+ query: ["measure"],
323
+ handle: ({ q, redact }) => buildList({ doMeasure: flag(one(q, "measure")), redact }),
324
+ },
325
+ { method: "GET", path: "/v1/launch-spec", query: [], handle: () => launchSpec() },
326
+ { method: "POST", path: "/v1/launch", query: ["repo"], body: true, handle: launchRun },
327
+ { method: "POST", path: "/v1/runs/{id}/answer", query: [], body: true, handle: answerRun },
328
+ { method: "GET", path: "/v1/phone/runs", query: ["group"], scope: "read", handle: listRuns },
329
+ { method: "GET", path: "/v1/phone/runs/{id}", query: [], scope: "read", handle: oneRun },
330
+ {
331
+ method: "POST",
332
+ path: "/v1/phone/runs/{id}/answer",
333
+ query: [],
334
+ body: true,
335
+ scope: "answer",
336
+ handle: answerRun,
337
+ },
338
+ {
339
+ method: "POST",
340
+ path: "/v1/phone/launch",
341
+ query: ["repo"],
342
+ body: true,
343
+ scope: "launch",
344
+ handle: (ctx) => {
345
+ if (!ctx.registry.launchEnabled) {
346
+ throw new HttpError(403, "launch-disabled", "launch from a phone is not enabled here");
347
+ }
348
+ return launchRun({ ...ctx, channel: "phone" });
349
+ },
350
+ },
351
+ ].map((r) => ({
352
+ ...r,
353
+ phone: r.path.startsWith(PHONE_PREFIX),
354
+ pattern: new RegExp(`^${r.path.replace("{id}", "(?<id>[^/]+)")}$`),
355
+ }));
356
+
357
+ /**
358
+ * The route for a path and method, or the HttpError that says why not. A phone
359
+ * path is looked up among phone routes only and a desktop path among desktop
360
+ * routes only, so neither credential can reach the other family.
361
+ */
362
+ export function match(method, pathname) {
363
+ const phone = isPhonePath(pathname);
364
+ const hits = [];
365
+ for (const r of ROUTES) {
366
+ if (r.phone !== phone) continue;
367
+ const m = r.pattern.exec(pathname);
368
+ if (m) hits.push({ route: r, params: m.groups ?? {} });
369
+ }
370
+ if (!hits.length) {
371
+ if (phone) throw new HttpError(404, "unknown-verb", "the phone API has no such verb");
372
+ throw new HttpError(404, "not-found", "no such route");
373
+ }
374
+ const hit = hits.find((h) => h.route.method === method);
375
+ if (!hit) {
376
+ throw new HttpError(
377
+ 405,
378
+ "method-not-allowed",
379
+ `use ${hits.map((h) => h.route.method).join(", ")}`,
380
+ {
381
+ allow: hits.map((h) => h.route.method).join(", "),
382
+ },
383
+ );
384
+ }
385
+ if (hit.params.id !== undefined) {
386
+ try {
387
+ hit.params.id = decodeURIComponent(hit.params.id);
388
+ } catch {
389
+ throw new HttpError(404, "not-found", "no such run");
390
+ }
391
+ }
392
+ return hit;
393
+ }
394
+
395
+ /** Query parameters as {name: [values]}, after every rule a route declares. */
396
+ export function parseQuery(route, searchParams) {
397
+ const q = {};
398
+ for (const [k, v] of searchParams) {
399
+ if (k !== "v" && k !== "redact" && !route.query.includes(k)) {
400
+ throw new HttpError(400, "invalid-query", `unknown query parameter ${k}`);
401
+ }
402
+ (q[k] ??= []).push(v);
403
+ }
404
+ for (const [k, values] of Object.entries(q)) {
405
+ if (k === "v") {
406
+ if (
407
+ values.length !== 1 ||
408
+ !SUPPORTED_MAJORS.includes(Number(values[0])) ||
409
+ !/^[0-9]+$/.test(values[0])
410
+ ) {
411
+ throw new HttpError(400, "unsupported-version", "this server does not serve that major", {
412
+ supported: SUPPORTED_MAJORS,
413
+ });
414
+ }
415
+ continue;
416
+ }
417
+ const rule = Q[k];
418
+ if (values.length > 1 && !rule.repeat)
419
+ throw new HttpError(400, "invalid-query", `${k} is given twice`);
420
+ for (const v of values) {
421
+ if (!rule.check(v)) throw new HttpError(400, "invalid-query", `${k} has an invalid value`);
422
+ }
423
+ }
424
+ return q;
425
+ }
426
+
427
+ const sha = (s) => createHash("sha256").update(s).digest();
428
+
429
+ /** Does this path belong to the phone family? */
430
+ export const isPhonePath = (pathname) =>
431
+ pathname === PHONE_PREFIX.slice(0, -1) || pathname.startsWith(PHONE_PREFIX);
432
+
433
+ /** Constant-time bearer check: equal-length digests, whatever the input. */
434
+ export function authorized(header, tokenDigest) {
435
+ const m = /^Bearer ([A-Za-z0-9_-]{1,512})$/.exec(String(header ?? ""));
436
+ return timingSafeEqual(sha(m ? m[1] : ""), tokenDigest) && Boolean(m);
437
+ }
438
+
439
+ function readRawBody(req) {
440
+ return new Promise((resolve, reject) => {
441
+ const chunks = [];
442
+ let size = 0;
443
+ let over = false;
444
+ req.on("data", (chunk) => {
445
+ size += chunk.length;
446
+ if (size > MAX_BODY_BYTES) over = true;
447
+ if (size > MAX_DRAIN_BYTES) req.destroy();
448
+ if (!over) chunks.push(chunk);
449
+ });
450
+ req.on("end", () => {
451
+ if (over) {
452
+ reject(new HttpError(400, "invalid-body", `the body is over ${MAX_BODY_BYTES} bytes`));
453
+ return;
454
+ }
455
+ resolve(Buffer.concat(chunks));
456
+ });
457
+ req.on("close", () => {
458
+ if (!req.complete) reject(new HttpError(400, "invalid-body", "the body was cut off"));
459
+ });
460
+ req.on("error", reject);
461
+ });
462
+ }
463
+
464
+ function parseJson(raw) {
465
+ try {
466
+ return JSON.parse(raw.toString("utf8"));
467
+ } catch {
468
+ throw new HttpError(400, "invalid-body", "the body is not JSON");
469
+ }
470
+ }
471
+
472
+ const readBody = async (req) => parseJson(await readRawBody(req));
473
+
474
+ function drain(req) {
475
+ let size = 0;
476
+ req.on("data", (chunk) => {
477
+ size += chunk.length;
478
+ if (size > MAX_DRAIN_BYTES) req.destroy();
479
+ });
480
+ req.resume();
481
+ }
482
+
483
+ function parseArgs(argv) {
484
+ const opts = { port: 0, idleMinutes: DEFAULT_IDLE_MINUTES, origins: [], printToken: false };
485
+ for (let i = 0; i < argv.length; i++) {
486
+ const a = argv[i];
487
+ const value = () => {
488
+ const v = argv[++i];
489
+ if (v === undefined) throw new Error(`${a} needs a value`);
490
+ return v;
491
+ };
492
+ if (a === "--host") {
493
+ const h = value();
494
+ if (h !== BIND_HOST) throw new Error(`binds only ${BIND_HOST}; --host ${h} is refused`);
495
+ } else if (a === "--port") {
496
+ const p = value();
497
+ if (!/^[0-9]{1,5}$/.test(p) || Number(p) > 65535) throw new Error("--port is 0 to 65535");
498
+ opts.port = Number(p);
499
+ } else if (a === "--idle-minutes") {
500
+ const n = Number(value());
501
+ if (!Number.isFinite(n) || n <= 0 || n > MAX_IDLE_MINUTES) {
502
+ throw new Error(`--idle-minutes is more than 0 and at most ${MAX_IDLE_MINUTES}`);
503
+ }
504
+ opts.idleMinutes = n;
505
+ } else if (a === "--allow-origin") {
506
+ const o = value();
507
+ let parsed;
508
+ try {
509
+ parsed = new URL(o);
510
+ } catch {
511
+ parsed = null;
512
+ }
513
+ if (!parsed || !/^https?:$/.test(parsed.protocol) || parsed.origin !== o) {
514
+ throw new Error(`--allow-origin takes an origin like http://127.0.0.1:5173, not ${o}`);
515
+ }
516
+ opts.origins.push(o);
517
+ } else if (a === "--print-token") opts.printToken = true;
518
+ else if (a === "-h" || a === "--help") opts.help = true;
519
+ else throw new Error(`unknown argument ${a}`);
520
+ }
521
+ return opts;
522
+ }
523
+
524
+ /** The token file this process wrote, removed on every way out. */
525
+ let liveTokenFile = null;
526
+ const removeTokenFile = () => {
527
+ if (liveTokenFile) rmSync(liveTokenFile, { force: true });
528
+ liveTokenFile = null;
529
+ };
530
+
531
+ function writeTokenFile(record) {
532
+ const dir = tokenDir();
533
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
534
+ chmodSync(dir, 0o700);
535
+ const file = join(dir, `${process.pid}.json`);
536
+ rmSync(file, { force: true });
537
+ writeFileSync(file, JSON.stringify(record, null, 2) + "\n", { mode: 0o600, flag: "wx" });
538
+ liveTokenFile = file;
539
+ return file;
540
+ }
541
+
542
+ /**
543
+ * Start listening. Resolves with the ready record once the port is known.
544
+ *
545
+ * @param {{port?: number, idleMinutes?: number, origins?: string[], printToken?: boolean}} opts
546
+ */
547
+ export function serve(opts) {
548
+ const token = randomBytes(32).toString("base64url");
549
+ const tokenDigest = sha(token);
550
+ const origins = new Set(opts.origins ?? []);
551
+ const idleMs = Math.round((opts.idleMinutes ?? DEFAULT_IDLE_MINUTES) * 60000);
552
+ let hosts = new Set();
553
+ let startSec = Infinity;
554
+ const nonces = createNonceStore({ file: noncesPath() });
555
+ let tokenFile = null;
556
+ let idleTimer = null;
557
+ let stopping = false;
558
+
559
+ // requireHostHeader off: a request without Host reaches the Host check below
560
+ // and is refused there with a contract error body, like any other wrong Host.
561
+ const server = createServer({
562
+ requestTimeout: 120000,
563
+ headersTimeout: 10000,
564
+ requireHostHeader: false,
565
+ });
566
+ server.maxHeadersCount = 50;
567
+
568
+ const send = (res, status, obj, extra = {}) => {
569
+ const body = serialize(obj);
570
+ res.writeHead(status, {
571
+ "Content-Type": "application/json; charset=utf-8",
572
+ "Content-Length": Buffer.byteLength(body),
573
+ "Cache-Control": "no-store",
574
+ "X-Content-Type-Options": "nosniff",
575
+ "X-Contract-Version": CONTRACT_VERSION,
576
+ ...extra,
577
+ });
578
+ res.end(body);
579
+ };
580
+ const fail = (res, err, extra) => {
581
+ const body = { contractVersion: CONTRACT_VERSION, error: err.code, message: err.message };
582
+ if (err.extra?.errors) body.errors = err.extra.errors;
583
+ if (err.extra?.supported) body.supported = err.extra.supported;
584
+ const headers = { ...extra };
585
+ if (err.extra?.allow) headers.Allow = err.extra.allow;
586
+ if (err.status === 401) headers["WWW-Authenticate"] = err.extra?.scheme ?? "Bearer";
587
+ send(res, err.status, body, headers);
588
+ };
589
+
590
+ const stop = (why) => {
591
+ if (stopping) return;
592
+ stopping = true;
593
+ clearTimeout(idleTimer);
594
+ removeTokenFile();
595
+ process.stderr.write(`contract-server: stopping (${why})\n`);
596
+ server.close(() => process.exit(0));
597
+ server.closeAllConnections();
598
+ setTimeout(() => process.exit(0), 2000).unref();
599
+ };
600
+ const arm = () => {
601
+ clearTimeout(idleTimer);
602
+ idleTimer = setTimeout(() => stop("idle"), idleMs);
603
+ };
604
+
605
+ /**
606
+ * A phone request: the signature decides who is asking, before the route is
607
+ * even looked up, and the device's scopes decide what it may ask for. The
608
+ * bearer token plays no part in either direction. Every outcome is audited.
609
+ */
610
+ const servePhone = async (req, res, url, cors) => {
611
+ const audit = { deviceId: null, method: req.method, verb: verbOf(url.pathname) };
612
+ let insecure = null;
613
+ try {
614
+ const raw = await readRawBody(req);
615
+ const registry = readRegistry();
616
+ insecure = registry.insecure;
617
+ const v = verifyRequest({
618
+ method: req.method,
619
+ target: req.url ?? "",
620
+ headers: req.headers,
621
+ body: raw,
622
+ registry,
623
+ nonces,
624
+ nowSec: Math.floor(Date.now() / 1000),
625
+ notBeforeSec: startSec,
626
+ });
627
+ audit.deviceId = v.ok ? v.device.id : v.deviceId;
628
+ if (!v.ok) {
629
+ throw new HttpError(v.status, v.code, PHONE_REFUSAL[v.code], {
630
+ scheme: "MA-Phone-Signature",
631
+ });
632
+ }
633
+ const { route, params } = match(req.method, url.pathname);
634
+ if (!Array.isArray(v.device.scopes) || !v.device.scopes.includes(route.scope)) {
635
+ throw new HttpError(403, "forbidden-scope", `this device has no ${route.scope} scope`);
636
+ }
637
+ const q = parseQuery(route, url.searchParams);
638
+ if (q.redact && one(q, "redact") !== "1") {
639
+ throw new HttpError(400, "invalid-query", "phone responses are always redacted");
640
+ }
641
+ let body = null;
642
+ if (route.body) {
643
+ const type = String(req.headers["content-type"] ?? "");
644
+ if (!/^application\/json(\s*;.*)?$/i.test(type)) {
645
+ throw new HttpError(415, "unsupported-media-type", "the body must be application/json");
646
+ }
647
+ body = parseJson(raw);
648
+ }
649
+ send(res, 200, route.handle({ q, params, body, redact: true, registry }), cors);
650
+ appendAudit({ ...audit, status: 200, outcome: "accepted" });
651
+ } catch (err) {
652
+ const e = err instanceof HttpError ? err : new HttpError(500, "internal", "internal error");
653
+ if (!(err instanceof HttpError))
654
+ process.stderr.write(`contract-server: ${err?.message ?? err}\n`);
655
+ fail(res, e, cors);
656
+ const outcome = e.code === "unknown-device" && insecure ? insecure : e.code;
657
+ appendAudit({ ...audit, status: e.status, outcome });
658
+ }
659
+ };
660
+
661
+ server.on("request", async (req, res) => {
662
+ arm();
663
+ res.on("finish", arm);
664
+ const origin = req.headers.origin;
665
+ const cors =
666
+ origin && origins.has(origin)
667
+ ? { "Access-Control-Allow-Origin": origin, Vary: "Origin" }
668
+ : {};
669
+ try {
670
+ if (!hosts.has(String(req.headers.host ?? "").toLowerCase())) {
671
+ throw new HttpError(403, "forbidden-host", "the Host header does not name this server");
672
+ }
673
+ if (origin !== undefined && !origins.has(origin)) {
674
+ throw new HttpError(403, "forbidden-origin", "this origin is not allowed");
675
+ }
676
+ if (req.method === "OPTIONS" && origin !== undefined) {
677
+ drain(req);
678
+ res.writeHead(204, {
679
+ ...cors,
680
+ "Access-Control-Allow-Methods": "GET, POST",
681
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
682
+ "Access-Control-Max-Age": "600",
683
+ "Cache-Control": "no-store",
684
+ });
685
+ res.end();
686
+ return;
687
+ }
688
+ const url = new URL(req.url ?? "/", `http://${BIND_HOST}`);
689
+ if (isPhonePath(url.pathname)) {
690
+ await servePhone(req, res, url, cors);
691
+ return;
692
+ }
693
+ if (!authorized(req.headers.authorization, tokenDigest)) {
694
+ throw new HttpError(401, "unauthorized", "a bearer token is required");
695
+ }
696
+ const { route, params } = match(req.method, url.pathname);
697
+ const q = parseQuery(route, url.searchParams);
698
+ const redact = flag(one(q, "redact")) || origin !== undefined;
699
+ let body = null;
700
+ if (route.body) {
701
+ const type = String(req.headers["content-type"] ?? "");
702
+ if (!/^application\/json(\s*;.*)?$/i.test(type)) {
703
+ drain(req);
704
+ throw new HttpError(415, "unsupported-media-type", "the body must be application/json");
705
+ }
706
+ body = await readBody(req);
707
+ } else {
708
+ drain(req);
709
+ }
710
+ send(res, 200, route.handle({ q, params, body, redact }), cors);
711
+ } catch (err) {
712
+ if (err instanceof HttpError) {
713
+ fail(res, err, cors);
714
+ return;
715
+ }
716
+ process.stderr.write(`contract-server: ${err?.message ?? err}\n`);
717
+ fail(res, new HttpError(500, "internal", "internal error"), cors);
718
+ }
719
+ });
720
+
721
+ return new Promise((resolve, reject) => {
722
+ server.once("error", reject);
723
+ server.listen(opts.port ?? 0, BIND_HOST, () => {
724
+ const { port } = server.address();
725
+ hosts = new Set([`${BIND_HOST}:${port}`, `localhost:${port}`]);
726
+ const record = {
727
+ contractVersion: CONTRACT_VERSION,
728
+ url: `http://${BIND_HOST}:${port}`,
729
+ port,
730
+ pid: process.pid,
731
+ idleMinutes: opts.idleMinutes ?? DEFAULT_IDLE_MINUTES,
732
+ };
733
+ const started = new Date();
734
+ startSec = Math.floor(started.getTime() / 1000);
735
+ tokenFile = writeTokenFile({ ...record, token, startedAt: started.toISOString() });
736
+ for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) process.on(sig, () => stop(sig));
737
+ arm();
738
+ resolve({ ...record, tokenFile, ...(opts.printToken ? { token } : {}) });
739
+ });
740
+ });
741
+ }
742
+
743
+ async function main(argv) {
744
+ let opts;
745
+ try {
746
+ opts = parseArgs(argv);
747
+ } catch (err) {
748
+ process.stderr.write(`contract-server: ${err.message}\n`);
749
+ return 2;
750
+ }
751
+ if (opts.help) {
752
+ process.stdout.write(
753
+ "Usage: contract-server.mjs [--port N] [--idle-minutes N] [--allow-origin <origin>]... [--print-token]\n",
754
+ );
755
+ return 0;
756
+ }
757
+ try {
758
+ const ready = await serve(opts);
759
+ process.stdout.write(JSON.stringify(ready) + "\n");
760
+ } catch (err) {
761
+ process.stderr.write(`contract-server: could not listen (${err.code ?? err.message})\n`);
762
+ return 1;
763
+ }
764
+ return undefined;
765
+ }
766
+
767
+ if (invokedDirectly(import.meta.url)) {
768
+ runMain(
769
+ "contract-server",
770
+ async () => {
771
+ const code = await main(process.argv.slice(2));
772
+ if (code !== undefined) process.exitCode = code;
773
+ },
774
+ { cleanup: removeTokenFile },
775
+ );
776
+ }