@mmerterden/multi-agent-pipeline 20.2.1 → 20.3.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 (621) hide show
  1. package/CHANGELOG.md +912 -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 +616 -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/user-facing.mjs +82 -0
  148. package/pipeline/lib/user-facing.sh +58 -0
  149. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  150. package/pipeline/multi-agent-refs/analysis/evidence.md +4 -0
  151. package/pipeline/multi-agent-refs/analysis/locked.md +2 -2
  152. package/pipeline/multi-agent-refs/analysis/render.md +4 -3
  153. package/pipeline/multi-agent-refs/analysis/resolve.md +3 -1
  154. package/pipeline/multi-agent-refs/analysis/synthesis.md +4 -5
  155. package/pipeline/multi-agent-refs/analysis-template.md +10 -17
  156. package/pipeline/multi-agent-refs/channels/jira.md +1 -1
  157. package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
  158. package/pipeline/multi-agent-refs/conventions-defaults.md +32 -32
  159. package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -17
  160. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +37 -9
  161. package/pipeline/multi-agent-refs/features/autopilot-operations.md +277 -0
  162. package/pipeline/multi-agent-refs/features/constitution.md +196 -0
  163. package/pipeline/multi-agent-refs/features/doctor.md +6 -3
  164. package/pipeline/multi-agent-refs/features/external-context-injection.md +6 -1
  165. package/pipeline/multi-agent-refs/features/jira-context.md +1 -1
  166. package/pipeline/multi-agent-refs/features/maturity-followup.md +43 -23
  167. package/pipeline/multi-agent-refs/features/model-fallback.md +9 -0
  168. package/pipeline/multi-agent-refs/features/phone-api.md +306 -0
  169. package/pipeline/multi-agent-refs/features/plan-critic.md +159 -0
  170. package/pipeline/multi-agent-refs/features/research.md +150 -0
  171. package/pipeline/multi-agent-refs/features/review-decision.md +185 -0
  172. package/pipeline/multi-agent-refs/features/scaffold.md +160 -0
  173. package/pipeline/multi-agent-refs/features/security-audit.md +6 -2
  174. package/pipeline/multi-agent-refs/features/skill-conformance.md +4 -1
  175. package/pipeline/multi-agent-refs/features/stack-adapters.md +116 -0
  176. package/pipeline/multi-agent-refs/features/unattended-gates.md +316 -0
  177. package/pipeline/multi-agent-refs/features/unattended-security.md +731 -0
  178. package/pipeline/multi-agent-refs/features/url-enrichment.md +2 -2
  179. package/pipeline/multi-agent-refs/features/usage-reporting.md +127 -35
  180. package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
  181. package/pipeline/multi-agent-refs/features/visual-evidence.md +7 -7
  182. package/pipeline/multi-agent-refs/keychain.md +6 -11
  183. package/pipeline/multi-agent-refs/payload-contracts.md +2 -2
  184. package/pipeline/multi-agent-refs/phases/log-format.md +2 -3
  185. package/pipeline/multi-agent-refs/phases/modes.md +10 -12
  186. package/pipeline/multi-agent-refs/phases/operations.md +11 -5
  187. package/pipeline/multi-agent-refs/phases/phase-0-init.md +73 -81
  188. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +22 -22
  189. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +42 -66
  190. package/pipeline/multi-agent-refs/phases/phase-3-review.md +45 -79
  191. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +28 -38
  192. package/pipeline/multi-agent-refs/phases/phase-5-report.md +27 -25
  193. package/pipeline/multi-agent-refs/phases.md +1 -1
  194. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  195. package/pipeline/multi-agent-refs/progress-contract.md +13 -16
  196. package/pipeline/multi-agent-refs/readiness-review.md +1 -1
  197. package/pipeline/multi-agent-refs/research/engine.md +91 -0
  198. package/pipeline/multi-agent-refs/rules.md +6 -4
  199. package/pipeline/multi-agent-refs/setup/repo-discovery.md +2 -2
  200. package/pipeline/multi-agent-refs/tracker-contract.md +2 -2
  201. package/pipeline/multi-agent-refs/unattended-contract.md +68 -16
  202. package/pipeline/rules/figma-pipeline.md +12 -12
  203. package/pipeline/schemas/agent-state.schema.json +480 -18
  204. package/pipeline/schemas/analysis-spec.schema.json +4 -4
  205. package/pipeline/schemas/answer-request.schema.json +24 -0
  206. package/pipeline/schemas/answer-result.schema.json +28 -0
  207. package/pipeline/schemas/autopilot-config.schema.json +111 -13
  208. package/pipeline/schemas/command-parameters.schema.json +99 -0
  209. package/pipeline/schemas/constitution.schema.json +56 -0
  210. package/pipeline/schemas/contract-error.schema.json +52 -0
  211. package/pipeline/schemas/design-check-config.schema.json +5 -1
  212. package/pipeline/schemas/issues.schema.json +61 -0
  213. package/pipeline/schemas/launch-plan.schema.json +46 -0
  214. package/pipeline/schemas/launch-request.schema.json +45 -0
  215. package/pipeline/schemas/launch.json +61 -0
  216. package/pipeline/schemas/launch.schema.json +84 -0
  217. package/pipeline/schemas/phases.json +2 -2
  218. package/pipeline/schemas/phases.schema.json +68 -0
  219. package/pipeline/schemas/phone-devices.schema.json +61 -0
  220. package/pipeline/schemas/phone-signed-request.schema.json +67 -0
  221. package/pipeline/schemas/plan-critique.schema.json +99 -0
  222. package/pipeline/schemas/plan-todos.schema.json +7 -7
  223. package/pipeline/schemas/planning-output.schema.json +5 -0
  224. package/pipeline/schemas/pr-request.schema.json +46 -0
  225. package/pipeline/schemas/prefs.schema.json +79 -8
  226. package/pipeline/schemas/research-output.schema.json +118 -0
  227. package/pipeline/schemas/review-file-exclusions.schema.json +25 -0
  228. package/pipeline/schemas/reviewer-output.schema.json +40 -4
  229. package/pipeline/schemas/run-questions.json +392 -0
  230. package/pipeline/schemas/run-questions.schema.json +118 -0
  231. package/pipeline/schemas/runs-index.schema.json +189 -0
  232. package/pipeline/schemas/scaffold-manifest.schema.json +43 -0
  233. package/pipeline/schemas/secret-patterns.schema.json +28 -0
  234. package/pipeline/schemas/stack-adapters.json +527 -0
  235. package/pipeline/schemas/stack-adapters.schema.json +184 -0
  236. package/pipeline/schemas/token-budget.json +1 -1
  237. package/pipeline/schemas/token-budget.schema.json +26 -0
  238. package/pipeline/schemas/triage-output.schema.json +64 -3
  239. package/pipeline/schemas/unattended-policy.json +139 -0
  240. package/pipeline/schemas/unattended-policy.schema.json +73 -0
  241. package/pipeline/schemas/unattended-profile.json +248 -0
  242. package/pipeline/schemas/unattended-profile.schema.json +198 -0
  243. package/pipeline/schemas/worktrees.schema.json +51 -0
  244. package/pipeline/scripts/README.md +1 -0
  245. package/pipeline/scripts/_autopilot-config.mjs +130 -0
  246. package/pipeline/scripts/_autopilot-ops.mjs +567 -0
  247. package/pipeline/scripts/_autopilot-outcomes.mjs +174 -0
  248. package/pipeline/scripts/_command-contract.mjs +384 -0
  249. package/pipeline/scripts/_cost.mjs +40 -0
  250. package/pipeline/scripts/_notices.mjs +160 -0
  251. package/pipeline/scripts/_phone-auth.mjs +485 -0
  252. package/pipeline/scripts/_pre-existing.mjs +294 -0
  253. package/pipeline/scripts/_redact.mjs +77 -0
  254. package/pipeline/scripts/_run-paths.mjs +4 -2
  255. package/pipeline/scripts/_stack-adapter.mjs +678 -0
  256. package/pipeline/scripts/_stack-routing.mjs +1 -1
  257. package/pipeline/scripts/agent-guard.py +348 -37
  258. package/pipeline/scripts/agent-guard.sh +41 -13
  259. package/pipeline/scripts/analysis-story-tree.mjs +79 -3
  260. package/pipeline/scripts/answer-question.mjs +181 -0
  261. package/pipeline/scripts/audit-log-rotate.sh +1 -4
  262. package/pipeline/scripts/audit-log.sh +4 -4
  263. package/pipeline/scripts/autopilot-arming.mjs +389 -21
  264. package/pipeline/scripts/autopilot-awake.mjs +255 -0
  265. package/pipeline/scripts/autopilot-intake.mjs +137 -36
  266. package/pipeline/scripts/autopilot-menubar.swift +156 -44
  267. package/pipeline/scripts/autopilot-publish.mjs +1625 -0
  268. package/pipeline/scripts/autopilot-runner.mjs +1678 -222
  269. package/pipeline/scripts/autopilot-status.sh +198 -33
  270. package/pipeline/scripts/build-lock.sh +120 -0
  271. package/pipeline/scripts/build-references.mjs +4 -1
  272. package/pipeline/scripts/build-stack-plugins.mjs +59 -22
  273. package/pipeline/scripts/capture-flush.sh +1 -1
  274. package/pipeline/scripts/capture-resume.sh +13 -9
  275. package/pipeline/scripts/check-derived-drift.mjs +52 -11
  276. package/pipeline/scripts/commands.mjs +88 -0
  277. package/pipeline/scripts/constitution.mjs +362 -0
  278. package/pipeline/scripts/contract-server.mjs +776 -0
  279. package/pipeline/scripts/cost-analyze.mjs +89 -39
  280. package/pipeline/scripts/diff-explain.mjs +12 -1
  281. package/pipeline/scripts/doctor.mjs +77 -28
  282. package/pipeline/scripts/evidence-gate.mjs +192 -12
  283. package/pipeline/scripts/feedback-send.mjs +4 -2
  284. package/pipeline/scripts/gate-ledger.mjs +449 -0
  285. package/pipeline/scripts/gc-abandoned.sh +132 -13
  286. package/pipeline/scripts/gen-facts.mjs +31 -15
  287. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
  288. package/pipeline/scripts/github-ssh-setup.sh +140 -29
  289. package/pipeline/scripts/graph-mermaid.mjs +4 -1
  290. package/pipeline/scripts/issues.mjs +236 -0
  291. package/pipeline/scripts/jira-attach.sh +6 -2
  292. package/pipeline/scripts/jira-search.sh +4 -3
  293. package/pipeline/scripts/keychain-save.sh +125 -24
  294. package/pipeline/scripts/keychain.py +63 -93
  295. package/pipeline/scripts/launch-request.mjs +747 -0
  296. package/pipeline/scripts/localize-commands.mjs +4 -10
  297. package/pipeline/scripts/log-metric.sh +6 -5
  298. package/pipeline/scripts/maturity-followup.mjs +13 -4
  299. package/pipeline/scripts/memory-save.sh +25 -0
  300. package/pipeline/scripts/migrate-prefs.mjs +4 -3
  301. package/pipeline/scripts/open-questions-gate.mjs +276 -0
  302. package/pipeline/scripts/phase-tracker.sh +41 -27
  303. package/pipeline/scripts/phase0-exit-gate.mjs +22 -4
  304. package/pipeline/scripts/phone-devices.mjs +224 -0
  305. package/pipeline/scripts/plan-coverage-gate.mjs +200 -66
  306. package/pipeline/scripts/plan-critique-gate.mjs +591 -0
  307. package/pipeline/scripts/pr-request.mjs +188 -0
  308. package/pipeline/scripts/pre-commit-check.sh +115 -4
  309. package/pipeline/scripts/probe-evidence-capability.sh +44 -5
  310. package/pipeline/scripts/record-phase.mjs +71 -0
  311. package/pipeline/scripts/render-agent-log-cost.sh +17 -2
  312. package/pipeline/scripts/render-cost-summary.sh +1 -1
  313. package/pipeline/scripts/render-work-summary.sh +1 -1
  314. package/pipeline/scripts/require-supported-version.sh +4 -1
  315. package/pipeline/scripts/research-gate.mjs +704 -0
  316. package/pipeline/scripts/review-decision-gate.mjs +403 -0
  317. package/pipeline/scripts/routine-registry.mjs +5 -2
  318. package/pipeline/scripts/runs-index.mjs +135 -27
  319. package/pipeline/scripts/scaffold-gate.mjs +393 -0
  320. package/pipeline/scripts/skill-conformance.mjs +25 -8
  321. package/pipeline/scripts/skill-siblings.mjs +2 -1
  322. package/pipeline/scripts/smoke-cross-cli-behavior.sh +32 -6
  323. package/pipeline/scripts/smoke-schema-validation.sh +6 -2
  324. package/pipeline/scripts/spec-consistency-gate.mjs +469 -0
  325. package/pipeline/scripts/symbol-existence-gate.mjs +450 -0
  326. package/pipeline/scripts/test-gap-scan.mjs +40 -2
  327. package/pipeline/scripts/test-integrity-gate.mjs +20 -4
  328. package/pipeline/scripts/test-strength.mjs +484 -0
  329. package/pipeline/scripts/test-summary.mjs +651 -0
  330. package/pipeline/scripts/triage-memory.mjs +49 -9
  331. package/pipeline/scripts/unattended_policy.py +2786 -0
  332. package/pipeline/scripts/uninstall.mjs +10 -10
  333. package/pipeline/scripts/update-issue-progress.sh +1 -1
  334. package/pipeline/scripts/usage-identity.mjs +288 -0
  335. package/pipeline/scripts/usage-register.mjs +185 -65
  336. package/pipeline/scripts/usage-report.mjs +230 -66
  337. package/pipeline/scripts/validate-complaint-doc.mjs +28 -10
  338. package/pipeline/scripts/validate-planning.mjs +6 -0
  339. package/pipeline/scripts/verify-citations.mjs +151 -38
  340. package/pipeline/scripts/verify.mjs +58 -18
  341. package/pipeline/scripts/worktree-prepare.sh +126 -0
  342. package/pipeline/scripts/worktrees.mjs +124 -0
  343. package/pipeline/scripts/write-state.mjs +48 -17
  344. package/pipeline/skills/.skill-manifest.json +222 -226
  345. package/pipeline/skills/.skills-index.json +77 -88
  346. package/pipeline/skills/shared/README.md +44 -45
  347. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +3 -2
  348. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +3 -2
  349. package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -9
  350. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +6 -5
  351. package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +11 -3
  352. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +2 -1
  353. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -3
  354. package/pipeline/skills/shared/core/multi-agent-autopilot-off/SKILL.md +32 -6
  355. package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +66 -8
  356. package/pipeline/skills/shared/core/multi-agent-autopilot-status/SKILL.md +27 -9
  357. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -1
  358. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +7 -4
  359. package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +3 -2
  360. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +2 -1
  361. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +7 -5
  362. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +2 -1
  363. package/pipeline/skills/shared/core/multi-agent-doctor/SKILL.md +3 -2
  364. package/pipeline/skills/shared/core/multi-agent-feedback/SKILL.md +2 -1
  365. package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +2 -1
  366. package/pipeline/skills/shared/core/multi-agent-garbage-collect/SKILL.md +2 -1
  367. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +2 -1
  368. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -1
  369. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -1
  370. package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +3 -2
  371. package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +2 -1
  372. package/pipeline/skills/shared/core/multi-agent-kill/SKILL.md +7 -4
  373. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -1
  374. package/pipeline/skills/shared/core/multi-agent-log/SKILL.md +2 -1
  375. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +2 -1
  376. package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -1
  377. package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +2 -1
  378. package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +2 -1
  379. package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +2 -1
  380. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +2 -1
  381. package/pipeline/skills/shared/core/multi-agent-research/SKILL.md +35 -0
  382. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +7 -3
  383. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +7 -5
  384. package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +2 -1
  385. package/pipeline/skills/shared/core/multi-agent-review-issue/SKILL.md +3 -2
  386. package/pipeline/skills/shared/core/multi-agent-review-jira/SKILL.md +2 -1
  387. package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +2 -1
  388. package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +2 -1
  389. package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +2 -1
  390. package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +2 -1
  391. package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +3 -2
  392. package/pipeline/skills/shared/core/multi-agent-scaffold/SKILL.md +30 -0
  393. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +2 -1
  394. package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +4 -3
  395. package/pipeline/skills/shared/core/multi-agent-security-review/SKILL.md +2 -1
  396. package/pipeline/skills/shared/core/multi-agent-serve/SKILL.md +60 -0
  397. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +8 -8
  398. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +2 -1
  399. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +7 -8
  400. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -1
  401. package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +2 -1
  402. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +10 -10
  403. package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +2 -1
  404. package/pipeline/skills/shared/core/multi-agent-test-accessibility/SKILL.md +2 -1
  405. package/pipeline/skills/shared/core/multi-agent-test-dark-mode/SKILL.md +2 -1
  406. package/pipeline/skills/shared/core/multi-agent-test-dynamic-type/SKILL.md +2 -1
  407. package/pipeline/skills/shared/core/multi-agent-test-screenshots/SKILL.md +2 -1
  408. package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -1
  409. package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +3 -2
  410. package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +2 -1
  411. package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +46 -0
  412. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +9 -3
  413. package/pipeline/skills/shared/external/NOTICE-paul-hudson-skills.md +54 -0
  414. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +9 -11
  415. package/pipeline/skills/shared/external/NOTICE-vibeship-spawner-skills.md +203 -0
  416. package/pipeline/skills/shared/external/NOTICE-xcode-build-skills.md +10 -3
  417. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +131 -25
  418. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +4 -3
  419. package/pipeline/skills/shared/external/alarmkit/SKILL.md +2 -1
  420. package/pipeline/skills/shared/external/android-architecture/SKILL.md +85 -76
  421. package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +51 -3
  422. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +9 -0
  423. package/pipeline/skills/shared/external/android-datastore/SKILL.md +4 -3
  424. package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +4 -3
  425. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +106 -181
  426. package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +147 -0
  427. package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +45 -4
  428. package/pipeline/skills/shared/external/android-performance/SKILL.md +27 -2
  429. package/pipeline/skills/shared/external/android-performance/references/patterns.md +37 -37
  430. package/pipeline/skills/shared/external/api-patterns/SKILL.md +111 -67
  431. package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +128 -0
  432. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +136 -186
  433. package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +140 -0
  434. package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +207 -0
  435. package/pipeline/skills/shared/external/api-security-best-practices/references/operations.md +66 -0
  436. package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +175 -0
  437. package/pipeline/skills/shared/external/app-clips/SKILL.md +2 -0
  438. package/pipeline/skills/shared/external/app-intents/SKILL.md +2 -0
  439. package/pipeline/skills/shared/external/app-store-changelog/SKILL.md +4 -3
  440. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +2 -0
  441. package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -0
  442. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +2 -0
  443. package/pipeline/skills/shared/external/architecture/SKILL.md +106 -39
  444. package/pipeline/skills/shared/external/architecture/references/adr-and-review.md +76 -0
  445. package/pipeline/skills/shared/external/authentication/SKILL.md +2 -0
  446. package/pipeline/skills/shared/external/avkit/SKILL.md +2 -0
  447. package/pipeline/skills/shared/external/background-processing/SKILL.md +2 -1
  448. package/pipeline/skills/shared/external/backlog/SKILL.md +3 -2
  449. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +2 -0
  450. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +1 -1
  451. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +3 -2
  452. package/pipeline/skills/shared/external/clean-code/SKILL.md +192 -90
  453. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +2 -1
  454. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +1 -1
  455. package/pipeline/skills/shared/external/compose-components/SKILL.md +4 -4
  456. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +22 -22
  457. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +10 -10
  458. package/pipeline/skills/shared/external/compose-testing/SKILL.md +13 -6
  459. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +59 -59
  460. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +2 -0
  461. package/pipeline/skills/shared/external/context-compression/SKILL.md +118 -250
  462. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +2 -0
  463. package/pipeline/skills/shared/external/core-data/SKILL.md +2 -0
  464. package/pipeline/skills/shared/external/core-motion/SKILL.md +2 -0
  465. package/pipeline/skills/shared/external/core-nfc/SKILL.md +2 -0
  466. package/pipeline/skills/shared/external/coreml/SKILL.md +2 -1
  467. package/pipeline/skills/shared/external/council/SKILL.md +2 -1
  468. package/pipeline/skills/shared/external/cryptokit/SKILL.md +2 -0
  469. package/pipeline/skills/shared/external/css-modern/SKILL.md +3 -2
  470. package/pipeline/skills/shared/external/database-patterns/SKILL.md +3 -2
  471. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +2 -0
  472. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +130 -20
  473. package/pipeline/skills/shared/external/debugging-strategies/references/hard-cases.md +54 -0
  474. package/pipeline/skills/shared/external/device-integrity/SKILL.md +2 -0
  475. package/pipeline/skills/shared/external/docker-expert/SKILL.md +137 -381
  476. package/pipeline/skills/shared/external/docker-expert/references/patterns.md +98 -0
  477. package/pipeline/skills/shared/external/energykit/SKILL.md +2 -1
  478. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +3 -1
  479. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +2 -2
  480. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +116 -185
  481. package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +235 -0
  482. package/pipeline/skills/shared/external/fastapi-pro/references/testing-and-deployment.md +69 -0
  483. package/pipeline/skills/shared/external/firebase/SKILL.md +4 -3
  484. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +151 -293
  485. package/pipeline/skills/shared/external/github-actions-templates/references/patterns.md +96 -0
  486. package/pipeline/skills/shared/external/healthkit/SKILL.md +2 -0
  487. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +69 -72
  488. package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +111 -0
  489. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +77 -86
  490. package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +98 -0
  491. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +150 -78
  492. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +76 -97
  493. package/pipeline/skills/shared/external/hig-components-system/references/surfaces.md +98 -0
  494. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +48 -76
  495. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +132 -0
  496. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +147 -106
  497. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +61 -77
  498. package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +92 -0
  499. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +147 -77
  500. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +61 -121
  501. package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +108 -0
  502. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +2 -0
  503. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +1 -1
  504. package/pipeline/skills/shared/external/html-semantic/SKILL.md +3 -2
  505. package/pipeline/skills/shared/external/humanizer/SKILL.md +2 -1
  506. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +2 -0
  507. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +2 -1
  508. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +14 -2
  509. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
  510. package/pipeline/skills/shared/external/ios-debugger-agent/SKILL.md +4 -3
  511. package/pipeline/skills/shared/external/ios-localization/SKILL.md +8 -6
  512. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +2 -2
  513. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +11 -11
  514. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +2 -1
  515. package/pipeline/skills/shared/external/ios-networking/SKILL.md +2 -0
  516. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +2 -0
  517. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +102 -193
  518. package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +103 -0
  519. package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -2
  520. package/pipeline/skills/shared/external/live-activities/evals/evals.json +2 -2
  521. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +7 -9
  522. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +3 -3
  523. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +2 -2
  524. package/pipeline/skills/shared/external/macos-menubar-tuist-app/SKILL.md +4 -3
  525. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +5 -3
  526. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +2 -0
  527. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +1 -1
  528. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +2 -0
  529. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +1 -1
  530. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +149 -46
  531. package/pipeline/skills/shared/external/monorepo-architect/references/patterns.md +70 -0
  532. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +2 -0
  533. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +1 -1
  534. package/pipeline/skills/shared/external/natural-language/SKILL.md +2 -0
  535. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +3 -2
  536. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +91 -21
  537. package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +247 -0
  538. package/pipeline/skills/shared/external/nodejs-backend-patterns/references/testing-and-frameworks.md +57 -0
  539. package/pipeline/skills/shared/external/observability-engineer/SKILL.md +155 -232
  540. package/pipeline/skills/shared/external/observability-engineer/references/patterns.md +75 -0
  541. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +2 -0
  542. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +2 -2
  543. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +18 -18
  544. package/pipeline/skills/shared/external/pdfkit/SKILL.md +2 -1
  545. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -0
  546. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
  547. package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -0
  548. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +2 -1
  549. package/pipeline/skills/shared/external/push-notifications/SKILL.md +2 -0
  550. package/pipeline/skills/shared/external/python-patterns/SKILL.md +3 -2
  551. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +3 -2
  552. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +2 -2
  553. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
  554. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +3 -2
  555. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +7 -7
  556. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +69 -69
  557. package/pipeline/skills/shared/external/room-database/references/patterns.md +126 -126
  558. package/pipeline/skills/shared/external/search-first/SKILL.md +2 -1
  559. package/pipeline/skills/shared/external/security-review/SKILL.md +4 -3
  560. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +2 -2
  561. package/pipeline/skills/shared/external/signal-community/SKILL.md +1 -1
  562. package/pipeline/skills/shared/external/skill-creator/SKILL.md +2 -1
  563. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +2 -1
  564. package/pipeline/skills/shared/external/spm-build-analysis/SKILL.md +2 -0
  565. package/pipeline/skills/shared/external/storekit/SKILL.md +2 -0
  566. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +2 -0
  567. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +2 -1
  568. package/pipeline/skills/shared/external/swift-charts/SKILL.md +2 -1
  569. package/pipeline/skills/shared/external/swift-codable/SKILL.md +2 -0
  570. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +3 -1
  571. package/pipeline/skills/shared/external/swift-concurrency-expert/SKILL.md +5 -4
  572. package/pipeline/skills/shared/external/swift-concurrency-pro/SKILL.md +2 -1
  573. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +2 -0
  574. package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -1
  575. package/pipeline/skills/shared/external/swift-security/SKILL.md +2 -1
  576. package/pipeline/skills/shared/external/swift-testing/SKILL.md +2 -0
  577. package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +2 -1
  578. package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +10 -0
  579. package/pipeline/skills/shared/external/swiftdata/SKILL.md +2 -0
  580. package/pipeline/skills/shared/external/swiftdata-pro/SKILL.md +2 -1
  581. package/pipeline/skills/shared/external/swiftlint/SKILL.md +2 -0
  582. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +2 -0
  583. package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +3 -1
  584. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +2 -0
  585. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +2 -0
  586. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +2 -0
  587. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +2 -0
  588. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +3 -1
  589. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +3 -1
  590. package/pipeline/skills/shared/external/swiftui-performance-audit/SKILL.md +5 -4
  591. package/pipeline/skills/shared/external/swiftui-pro/SKILL.md +2 -1
  592. package/pipeline/skills/shared/external/swiftui-ui-patterns/SKILL.md +5 -4
  593. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +2 -0
  594. package/pipeline/skills/shared/external/swiftui-view-refactor/SKILL.md +4 -3
  595. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +2 -0
  596. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +3 -2
  597. package/pipeline/skills/shared/external/testing-backend/SKILL.md +3 -2
  598. package/pipeline/skills/shared/external/tipkit/SKILL.md +2 -0
  599. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +3 -2
  600. package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -0
  601. package/pipeline/skills/shared/external/vue-composition/SKILL.md +3 -2
  602. package/pipeline/skills/shared/external/weatherkit/SKILL.md +2 -0
  603. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +3 -2
  604. package/pipeline/skills/shared/external/web-performance/SKILL.md +3 -2
  605. package/pipeline/skills/shared/external/web-testing/SKILL.md +3 -2
  606. package/pipeline/skills/shared/external/widgetkit/SKILL.md +2 -0
  607. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +1 -1
  608. package/pipeline/skills/shared/external/xcode-build-benchmark/SKILL.md +2 -0
  609. package/pipeline/skills/shared/external/xcode-build-fixer/SKILL.md +2 -0
  610. package/pipeline/skills/shared/external/xcode-build-orchestrator/SKILL.md +2 -0
  611. package/pipeline/skills/shared/external/xcode-compilation-analyzer/SKILL.md +2 -0
  612. package/pipeline/skills/shared/external/xcode-project-analyzer/SKILL.md +2 -0
  613. package/pipeline/skills/skills-index.md +40 -41
  614. package/docs/token-budget-history.md +0 -24
  615. package/pipeline/skills/shared/external/agentflow/SKILL.md +0 -199
  616. package/pipeline/skills/shared/external/android-ui-verification/SKILL.md +0 -66
  617. package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +0 -299
  618. package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +0 -255
  619. package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +0 -167
  620. package/pipeline/skills/shared/external/closed-loop-delivery/SKILL.md +0 -116
  621. package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -216
@@ -0,0 +1,160 @@
1
+ // Third-party attribution for pipeline/skills/shared/external.
2
+ //
3
+ // A vendored skill carries `metadata.source: "<upstream> (<license>)"` in its
4
+ // frontmatter, and its upstream's NOTICE-*.md lists it in a `## Skills (N)`
5
+ // section. The licenses involved (MIT, Apache 2.0, PolyForm Perimeter) require
6
+ // the notice to travel with every copy, and build-stack-plugins.mjs ships a
7
+ // NOTICE only beside the skills that section lists, so the two records are
8
+ // checked against each other here: lint-skills fails on a mismatch and the
9
+ // builder refuses to publish one.
10
+ //
11
+ // A skill whose header says only `metadata.source: community` came from a
12
+ // community collection whose repository and license are not known. Content of
13
+ // unknown license cannot ship verbatim, so such a skill is an attribution
14
+ // problem whether or not a NOTICE lists it.
15
+
16
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
17
+ import { join } from "node:path";
18
+
19
+ export const NOTICE_FILE = /^NOTICE-.*\.md$/;
20
+
21
+ // A URL in the parentheses names a location, not a license.
22
+ const LICENSED = /^"?(?<upstream>[^"(]+?)\s*\((?<license>(?!https?:)[^)]+)\)"?\s*$/;
23
+
24
+ // Written on a skill authored in this repository.
25
+ export const AUTHORED_SOURCE = "multi-agent-pipeline";
26
+
27
+ // Written on a skill taken from a community collection with no named upstream.
28
+ export const COMMUNITY_SOURCE = "community";
29
+
30
+ /** `{ declared, skills }` from a NOTICE's `## Skills (N)` section; `declared` is null without one. */
31
+ export function parseNoticeSkills(text) {
32
+ const src = String(text).replace(/\r\n?/g, "\n");
33
+ const head = src.match(/^##[ \t]+Skills[ \t]+\((\d+)\)[ \t]*$/m);
34
+ if (!head) return { declared: null, skills: [] };
35
+ const rest = src.slice(head.index + head[0].length);
36
+ const next = rest.search(/^##[ \t]/m);
37
+ const section = next === -1 ? rest : rest.slice(0, next);
38
+ return {
39
+ declared: Number(head[1]),
40
+ skills: [...section.matchAll(/`([a-z0-9][a-z0-9_-]*)`/g)].map((m) => m[1]),
41
+ };
42
+ }
43
+
44
+ /** The raw `metadata.source` value of a SKILL.md, unquoted, or "". */
45
+ export function metadataSource(skillMd) {
46
+ const src = String(skillMd)
47
+ .replace(/^\uFEFF/, "")
48
+ .replace(/\r\n?/g, "\n");
49
+ const fm = src.match(/^---\n([\s\S]*?)\n---/);
50
+ if (!fm) return "";
51
+ let inside = false;
52
+ for (const line of fm[1].split("\n")) {
53
+ if (/^\S/.test(line)) {
54
+ inside = /^metadata:\s*(#.*)?$/.test(line);
55
+ continue;
56
+ }
57
+ const m = inside && line.match(/^\s+source:\s*(.*?)\s*$/);
58
+ if (m) return m[1].replace(/^(["'])(.*)\1$/, "$2");
59
+ }
60
+ return "";
61
+ }
62
+
63
+ /** `{ upstream, license }` when the skill is vendored from a licensed upstream, else null. */
64
+ export function licensedSource(skillMd) {
65
+ const m = metadataSource(skillMd).match(LICENSED);
66
+ return m ? { upstream: m.groups.upstream.trim(), license: m.groups.license.trim() } : null;
67
+ }
68
+
69
+ const isDir = (p) => {
70
+ try {
71
+ return statSync(p).isDirectory();
72
+ } catch {
73
+ return false;
74
+ }
75
+ };
76
+
77
+ function skillDirs(externalDir) {
78
+ return readdirSync(externalDir)
79
+ .filter((n) => isDir(join(externalDir, n)) && existsSync(join(externalDir, n, "SKILL.md")))
80
+ .sort();
81
+ }
82
+
83
+ /** Every NOTICE-*.md in the directory with its bytes and parsed Skills section. */
84
+ export function readNotices(externalDir) {
85
+ if (!existsSync(externalDir)) return [];
86
+ return readdirSync(externalDir)
87
+ .filter((n) => NOTICE_FILE.test(n))
88
+ .sort()
89
+ .map((file) => {
90
+ const bytes = readFileSync(join(externalDir, file));
91
+ const { declared, skills } = parseNoticeSkills(bytes.toString("utf8"));
92
+ return {
93
+ file,
94
+ bytes,
95
+ text: bytes.toString("utf8"),
96
+ declared,
97
+ skills: new Set(skills),
98
+ listed: skills,
99
+ };
100
+ });
101
+ }
102
+
103
+ /** Why the NOTICE files and the skills' `metadata.source` lines disagree; empty when they agree. */
104
+ export function noticeProblems(externalDir) {
105
+ const problems = [];
106
+ const skills = new Set(skillDirs(externalDir));
107
+ const owner = new Map();
108
+ const notices = readNotices(externalDir);
109
+ for (const n of notices) {
110
+ if (n.declared === null) {
111
+ problems.push(`${n.file}: no '## Skills (N)' section listing the skills it covers`);
112
+ continue;
113
+ }
114
+ if (n.declared !== n.listed.length) {
115
+ problems.push(`${n.file}: declares ${n.declared} skills but lists ${n.listed.length}`);
116
+ }
117
+ for (const s of n.listed) {
118
+ if (!skills.has(s)) problems.push(`${n.file}: lists '${s}', which is not a skill directory`);
119
+ if (owner.has(s)) problems.push(`${s}: listed by both ${owner.get(s)} and ${n.file}`);
120
+ else owner.set(s, n.file);
121
+ }
122
+ }
123
+ const byFile = new Map(notices.map((n) => [n.file, n]));
124
+ for (const s of skills) {
125
+ const md = readFileSync(join(externalDir, s, "SKILL.md"), "utf8");
126
+ const src = licensedSource(md);
127
+ const file = owner.get(s);
128
+ if (metadataSource(md) === COMMUNITY_SOURCE) {
129
+ problems.push(
130
+ `${s}: metadata.source '${COMMUNITY_SOURCE}' names no upstream or license, and content of unknown license cannot ship`,
131
+ );
132
+ } else if (src && !file) {
133
+ problems.push(
134
+ `${s}: vendored from ${src.upstream} (${src.license}) but no NOTICE-*.md lists it`,
135
+ );
136
+ } else if (src && !byFile.get(file).text.includes(src.upstream)) {
137
+ problems.push(`${s}: names upstream '${src.upstream}', which ${file} does not mention`);
138
+ } else if (!src && file) {
139
+ problems.push(
140
+ `${s}: listed by ${file} but carries no 'metadata.source: "<upstream> (<license>)"'`,
141
+ );
142
+ }
143
+ }
144
+ return problems;
145
+ }
146
+
147
+ /**
148
+ * Skills whose `metadata.source` is set but names no licensed upstream and is not
149
+ * the authored marker: attribution that cannot be checked against a NOTICE.
150
+ */
151
+ export function unnamedUpstreams(externalDir) {
152
+ const out = [];
153
+ for (const s of skillDirs(externalDir)) {
154
+ const md = readFileSync(join(externalDir, s, "SKILL.md"), "utf8");
155
+ const source = metadataSource(md);
156
+ if (!source || source === AUTHORED_SOURCE || licensedSource(md)) continue;
157
+ out.push({ skill: s, source });
158
+ }
159
+ return out;
160
+ }
@@ -0,0 +1,485 @@
1
+ /**
2
+ * @file _phone-auth.mjs - device signatures for the phone routes of
3
+ * contract-server.mjs.
4
+ *
5
+ * A phone reaches the server through a forwarder (a private network or a
6
+ * relay; features/phone-api.md), so it cannot read the bearer token file and
7
+ * must not be handed a copy: a token is a bearer secret, valid from anywhere
8
+ * for as long as the server runs. A phone instead holds an Ed25519 private key
9
+ * that never leaves it, and signs every request. This machine keeps only the
10
+ * public halves, in a registry the enrolment CLI (phone-devices.mjs) writes.
11
+ *
12
+ * The signed text is canonical: seven fields joined by "\n", each one refused
13
+ * if it could carry a newline, so two different requests can never produce the
14
+ * same text.
15
+ *
16
+ * MA-PHONE-SIG-1
17
+ * <method> GET or POST, as sent
18
+ * <target> the request target exactly as sent: path plus query
19
+ * <body sha256 hex> of the raw body bytes; the empty body has a digest too
20
+ * <device id>
21
+ * <timestamp> Unix seconds
22
+ * <nonce> 16 to 64 base64url characters, fresh per request
23
+ *
24
+ * A request is refused, in this order, when it is unsigned, names a device the
25
+ * registry does not hold, carries a timestamp more than WINDOW_SEC behind this
26
+ * machine's clock, more than FUTURE_SKEW_SEC ahead of it, or earlier than the
27
+ * server's own start, fails the signature, comes from a revoked device, or
28
+ * repeats a nonce inside the window.
29
+ *
30
+ * Seen nonces are kept on disk (nonces.json, 0600, bounded, pruned as their
31
+ * window closes), not only in memory, so a request accepted by one server
32
+ * process is still a replay to the next one. The start-time floor alone cannot
33
+ * do that: a timestamp ahead of the clock stays later than a restart for as
34
+ * long as it is ahead. The small future skew bounds that lead, and the floor
35
+ * still refuses anything signed before the process started, which covers a
36
+ * nonce file that was lost or could not be read. Only a hash of each nonce is
37
+ * stored.
38
+ *
39
+ * The registry is read on every request, so a revocation or a launch toggle
40
+ * takes effect on the next request. A registry that other users can write, or
41
+ * that another user owns, is treated as empty.
42
+ *
43
+ * The audit log records one line per phone request whose signature verified,
44
+ * accepted or not: time, device id, method, verb, status and outcome. Never the
45
+ * signature, the nonce, the query or the body. It keeps AUDIT_GENERATIONS
46
+ * rotated files. A refusal decided before a signature verified (unsigned,
47
+ * unknown device, stale timestamp, bad signature, unusable registry) costs an
48
+ * anonymous sender nothing to produce, so it goes to a separate refusals log,
49
+ * rate-limited per process, where a flood cannot rotate away the rows that
50
+ * record what an enrolled device did. A device id alone proves nothing: it
51
+ * travels in clear on every request.
52
+ *
53
+ * Zero runtime dependencies (ADR-0004): node:crypto and node:fs.
54
+ *
55
+ * @module pipeline/scripts/_phone-auth
56
+ */
57
+
58
+ import { Buffer } from "node:buffer";
59
+ import { createHash, createPublicKey, randomBytes, sign, verify } from "node:crypto";
60
+ import {
61
+ appendFileSync,
62
+ chmodSync,
63
+ existsSync,
64
+ mkdirSync,
65
+ readFileSync,
66
+ renameSync,
67
+ rmSync,
68
+ statSync,
69
+ writeFileSync,
70
+ } from "node:fs";
71
+ import { dirname, join } from "node:path";
72
+ import { autopilotRoot } from "../lib/pr-request-location.mjs";
73
+
74
+ export const PHONE_PREFIX = "/v1/phone/";
75
+ export const SIGNATURE_VERSION = "MA-PHONE-SIG-1";
76
+ export const WINDOW_SEC = 60;
77
+ export const FUTURE_SKEW_SEC = 5;
78
+ export const SCOPES = ["read", "answer", "launch"];
79
+ export const REGISTRY_VERSION = "1.0.0";
80
+ export const NONCE_STORE_MAX = 4096;
81
+ export const HEADERS = {
82
+ device: "x-ma-device",
83
+ timestamp: "x-ma-timestamp",
84
+ nonce: "x-ma-nonce",
85
+ signature: "x-ma-signature",
86
+ };
87
+
88
+ export const DEVICE_ID = /^d-[0-9a-f]{16}$/;
89
+ const TIMESTAMP = /^[0-9]{1,12}$/;
90
+ const NONCE = /^[A-Za-z0-9_-]{16,64}$/;
91
+ const SIGNATURE = /^[A-Za-z0-9_-]{86}$/;
92
+ const METHOD = /^[A-Z]{1,10}$/;
93
+ const TARGET = /^\/[\x21-\x7e]{0,4095}$/;
94
+ const DIGEST = /^[0-9a-f]{64}$/;
95
+ const VERB = /^[a-z0-9-]{1,32}$/;
96
+ const SPKI_ED25519_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
97
+ const AUDIT_MAX_BYTES = 256 * 1024;
98
+ export const AUDIT_GENERATIONS = 5;
99
+ export const REFUSAL_ROWS_PER_MINUTE = 30;
100
+ /** Outcomes decided before any signature verified; they go to the refusals log. */
101
+ export const UNAUTHENTICATED_OUTCOMES = [
102
+ "unsigned",
103
+ "unknown-device",
104
+ "stale-request",
105
+ "bad-signature",
106
+ "registry-insecure",
107
+ "registry-unreadable",
108
+ ];
109
+
110
+ /**
111
+ * Where the phone files live: under the autopilot root, which an unattended
112
+ * run may not write (unattended-policy.json, protectedHostEntries).
113
+ */
114
+ export function phoneDir(env = process.env) {
115
+ return join(autopilotRoot(env), "phone");
116
+ }
117
+ export const registryPath = (env = process.env) => join(phoneDir(env), "devices.json");
118
+ export const auditPath = (env = process.env) => join(phoneDir(env), "audit.jsonl");
119
+ export const refusalsPath = (env = process.env) => join(phoneDir(env), "refusals.jsonl");
120
+ export const noncesPath = (env = process.env) => join(phoneDir(env), "nonces.json");
121
+
122
+ /** SHA-256 of the raw body bytes, lowercase hex. */
123
+ export const bodyDigest = (buf) =>
124
+ createHash("sha256")
125
+ .update(buf ?? Buffer.alloc(0))
126
+ .digest("hex");
127
+
128
+ /** The text a device signs. Throws on any field that could break the framing. */
129
+ export function canonicalPayload({
130
+ method,
131
+ target,
132
+ bodyDigest: digest,
133
+ deviceId,
134
+ timestamp,
135
+ nonce,
136
+ }) {
137
+ const checks = [
138
+ [METHOD, method, "method"],
139
+ [TARGET, target, "target"],
140
+ [DIGEST, digest, "body digest"],
141
+ [DEVICE_ID, deviceId, "device id"],
142
+ [TIMESTAMP, timestamp, "timestamp"],
143
+ [NONCE, nonce, "nonce"],
144
+ ];
145
+ for (const [re, v, name] of checks) {
146
+ if (typeof v !== "string" || !re.test(v)) throw new Error(`${name} is not canonical`);
147
+ }
148
+ return [SIGNATURE_VERSION, method, target, digest, deviceId, timestamp, nonce].join("\n");
149
+ }
150
+
151
+ /** A raw 32-byte Ed25519 public key (base64url) as a KeyObject. */
152
+ export function publicKeyObject(raw) {
153
+ const bytes = Buffer.from(String(raw), "base64url");
154
+ if (bytes.length !== 32) throw new Error("an Ed25519 public key is 32 bytes");
155
+ return createPublicKey({
156
+ key: Buffer.concat([SPKI_ED25519_PREFIX, bytes]),
157
+ format: "der",
158
+ type: "spki",
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Normalize what a person pastes at enrolment: a raw base64url key or an SPKI
164
+ * PEM block. Returns the raw key as base64url; throws on anything that is not
165
+ * an Ed25519 public key.
166
+ */
167
+ export function parsePublicKey(input) {
168
+ const text = String(input ?? "").trim();
169
+ if (/PRIVATE KEY/.test(text)) throw new Error("that is a private key; paste the public half");
170
+ let key;
171
+ if (text.startsWith("-----BEGIN")) {
172
+ key = createPublicKey({ key: text, format: "pem" });
173
+ } else {
174
+ if (!/^[A-Za-z0-9_-]{43}=?$/.test(text)) throw new Error("not a base64url Ed25519 key");
175
+ key = publicKeyObject(text.replace(/=$/, ""));
176
+ }
177
+ if (key.asymmetricKeyType !== "ed25519") throw new Error("the key is not Ed25519");
178
+ const der = key.export({ format: "der", type: "spki" });
179
+ return der.subarray(SPKI_ED25519_PREFIX.length).toString("base64url");
180
+ }
181
+
182
+ /** Short, stable identifier of a public key, for listing and the audit trail. */
183
+ export const fingerprint = (raw) =>
184
+ createHash("sha256").update(Buffer.from(raw, "base64url")).digest("hex").slice(0, 16);
185
+
186
+ export const newDeviceId = () => `d-${randomBytes(8).toString("hex")}`;
187
+
188
+ const emptyRegistry = () => ({
189
+ schemaVersion: REGISTRY_VERSION,
190
+ launchEnabled: false,
191
+ devices: [],
192
+ });
193
+
194
+ /**
195
+ * The registry, or an empty one when it is missing, unreadable, or writable by
196
+ * anyone but its owner. `insecure` says which of those it was.
197
+ */
198
+ export function readRegistry(env = process.env) {
199
+ const file = registryPath(env);
200
+ if (!existsSync(file)) return { ...emptyRegistry(), insecure: null };
201
+ try {
202
+ const st = statSync(file);
203
+ const uid = typeof process.getuid === "function" ? process.getuid() : st.uid;
204
+ if ((st.mode & 0o022) !== 0 || st.uid !== uid) {
205
+ return { ...emptyRegistry(), insecure: "registry-insecure" };
206
+ }
207
+ const doc = JSON.parse(readFileSync(file, "utf8"));
208
+ if (!doc || !Array.isArray(doc.devices))
209
+ return { ...emptyRegistry(), insecure: "registry-unreadable" };
210
+ return {
211
+ schemaVersion: doc.schemaVersion ?? REGISTRY_VERSION,
212
+ launchEnabled: doc.launchEnabled === true,
213
+ devices: doc.devices,
214
+ insecure: null,
215
+ };
216
+ } catch {
217
+ return { ...emptyRegistry(), insecure: "registry-unreadable" };
218
+ }
219
+ }
220
+
221
+ /** Replace the registry: a 0600 file written beside it and renamed over it. */
222
+ export function writeRegistry(reg, env = process.env) {
223
+ const dir = phoneDir(env);
224
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
225
+ chmodSync(dir, 0o700);
226
+ const file = registryPath(env);
227
+ const tmp = `${file}.${process.pid}.tmp`;
228
+ const doc = {
229
+ schemaVersion: REGISTRY_VERSION,
230
+ launchEnabled: reg.launchEnabled === true,
231
+ devices: reg.devices,
232
+ };
233
+ rmSync(tmp, { force: true });
234
+ writeFileSync(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: 0o600, flag: "wx" });
235
+ renameSync(tmp, file);
236
+ chmodSync(file, 0o600);
237
+ return file;
238
+ }
239
+
240
+ function readNonceFile(file) {
241
+ try {
242
+ const doc = JSON.parse(readFileSync(file, "utf8"));
243
+ return doc && typeof doc.seen === "object" && doc.seen !== null ? doc.seen : {};
244
+ } catch {
245
+ return {};
246
+ }
247
+ }
248
+
249
+ function writeNonceFile(file, seen) {
250
+ try {
251
+ const dir = dirname(file);
252
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
253
+ const tmp = `${file}.${process.pid}.tmp`;
254
+ rmSync(tmp, { force: true });
255
+ writeFileSync(tmp, JSON.stringify({ seen: Object.fromEntries(seen) }) + "\n", {
256
+ mode: 0o600,
257
+ flag: "wx",
258
+ });
259
+ renameSync(tmp, file);
260
+ chmodSync(file, 0o600);
261
+ } catch {
262
+ // The in-memory copy still refuses a replay to this process.
263
+ }
264
+ }
265
+
266
+ const nonceKey = (key) => createHash("sha256").update(String(key)).digest("hex").slice(0, 32);
267
+
268
+ /**
269
+ * Nonces seen inside their window. Bounded: when full after dropping expired
270
+ * entries it answers "full" instead of forgetting a live nonce, because
271
+ * eviction is exactly what a replay would need. With `file`, every check first
272
+ * merges what other processes recorded there and every acceptance is written
273
+ * back, so the store outlives a restart.
274
+ */
275
+ export function createNonceStore({ max = NONCE_STORE_MAX, file = null } = {}) {
276
+ const seen = new Map();
277
+ const prune = (now) => {
278
+ for (const [k, exp] of seen) if (exp < now) seen.delete(k);
279
+ };
280
+ const merge = () => {
281
+ if (!file) return;
282
+ for (const [k, exp] of Object.entries(readNonceFile(file))) {
283
+ if (typeof exp !== "number") continue;
284
+ if (!(seen.get(k) >= exp)) seen.set(k, exp);
285
+ }
286
+ };
287
+ return {
288
+ remember(key, expiresAt, now) {
289
+ const k = file ? nonceKey(key) : key;
290
+ merge();
291
+ const exp = seen.get(k);
292
+ if (exp !== undefined && exp >= now) return "replayed";
293
+ if (exp !== undefined) seen.delete(k);
294
+ if (seen.size >= max) prune(now);
295
+ if (seen.size >= max) return "full";
296
+ seen.set(k, expiresAt);
297
+ if (file) {
298
+ prune(now);
299
+ writeNonceFile(file, seen);
300
+ }
301
+ return "ok";
302
+ },
303
+ size: () => seen.size,
304
+ };
305
+ }
306
+
307
+ const header = (headers, name) => {
308
+ const v = headers[name];
309
+ return Array.isArray(v) ? null : (v ?? null);
310
+ };
311
+
312
+ const refuse = (status, code, deviceId = null) => ({ ok: false, status, code, deviceId });
313
+
314
+ /**
315
+ * Decide whether a phone request is authentic. Pure apart from the nonce store
316
+ * it is handed.
317
+ *
318
+ * @returns {{ok: true, device: object} | {ok: false, status: number, code: string, deviceId: string|null}}
319
+ */
320
+ export function verifyRequest({
321
+ method,
322
+ target,
323
+ headers,
324
+ body,
325
+ registry,
326
+ nonces,
327
+ nowSec,
328
+ notBeforeSec,
329
+ }) {
330
+ const deviceId = header(headers, HEADERS.device);
331
+ const ts = header(headers, HEADERS.timestamp);
332
+ const nonce = header(headers, HEADERS.nonce);
333
+ const sig = header(headers, HEADERS.signature);
334
+ if (
335
+ !DEVICE_ID.test(deviceId ?? "") ||
336
+ !TIMESTAMP.test(ts ?? "") ||
337
+ !NONCE.test(nonce ?? "") ||
338
+ !SIGNATURE.test(sig ?? "")
339
+ ) {
340
+ return refuse(401, "unsigned", DEVICE_ID.test(deviceId ?? "") ? deviceId : null);
341
+ }
342
+ const device = registry.devices.find((d) => d && d.id === deviceId);
343
+ if (!device) return refuse(401, "unknown-device", deviceId);
344
+ const t = Number(ts);
345
+ if (nowSec - t > WINDOW_SEC || t - nowSec > FUTURE_SKEW_SEC || t < notBeforeSec) {
346
+ return refuse(401, "stale-request", deviceId);
347
+ }
348
+ let text;
349
+ try {
350
+ text = canonicalPayload({
351
+ method,
352
+ target,
353
+ bodyDigest: bodyDigest(body),
354
+ deviceId,
355
+ timestamp: ts,
356
+ nonce,
357
+ });
358
+ } catch {
359
+ return refuse(401, "bad-signature", deviceId);
360
+ }
361
+ let good;
362
+ try {
363
+ good = verify(
364
+ null,
365
+ Buffer.from(text, "utf8"),
366
+ publicKeyObject(device.publicKey),
367
+ Buffer.from(sig, "base64url"),
368
+ );
369
+ } catch {
370
+ good = false;
371
+ }
372
+ if (!good) return refuse(401, "bad-signature", deviceId);
373
+ if (device.revokedAt) return refuse(401, "revoked-device", deviceId);
374
+ const seen = nonces.remember(`${deviceId}:${nonce}`, t + WINDOW_SEC, nowSec);
375
+ if (seen === "replayed") return refuse(401, "replayed", deviceId);
376
+ if (seen === "full") return refuse(503, "replay-store-full", deviceId);
377
+ return { ok: true, device };
378
+ }
379
+
380
+ /**
381
+ * The headers a device sends. The reference for a client implementation, and
382
+ * what the tests and smokes sign with.
383
+ */
384
+ export function signRequest({
385
+ method,
386
+ target,
387
+ body = Buffer.alloc(0),
388
+ deviceId,
389
+ privateKey,
390
+ timestamp,
391
+ nonce,
392
+ }) {
393
+ const ts = String(timestamp ?? Math.floor(Date.now() / 1000));
394
+ const n = nonce ?? randomBytes(18).toString("base64url");
395
+ const text = canonicalPayload({
396
+ method,
397
+ target,
398
+ bodyDigest: bodyDigest(Buffer.isBuffer(body) ? body : Buffer.from(String(body), "utf8")),
399
+ deviceId,
400
+ timestamp: ts,
401
+ nonce: n,
402
+ });
403
+ return {
404
+ "X-MA-Device": deviceId,
405
+ "X-MA-Timestamp": ts,
406
+ "X-MA-Nonce": n,
407
+ "X-MA-Signature": sign(null, Buffer.from(text, "utf8"), privateKey).toString("base64url"),
408
+ };
409
+ }
410
+
411
+ /** The verb a phone path names, or "unknown" when it is not a plain word. */
412
+ export function verbOf(pathname) {
413
+ const seg = String(pathname).slice(PHONE_PREFIX.length).split("/")[0];
414
+ return VERB.test(seg) ? seg : "unknown";
415
+ }
416
+
417
+ function rotate(file, generations) {
418
+ if (!existsSync(file) || statSync(file).size <= AUDIT_MAX_BYTES) return;
419
+ for (let i = generations - 1; i >= 1; i--) {
420
+ if (existsSync(`${file}.${i}`)) renameSync(`${file}.${i}`, `${file}.${i + 1}`);
421
+ }
422
+ renameSync(file, `${file}.1`);
423
+ }
424
+
425
+ /** Per-process budget for the refusals log: rows written and dropped this minute. */
426
+ const refusalBudget = { minute: -1, written: 0, dropped: 0 };
427
+
428
+ /** Reset the refusals budget; for tests that call appendAudit directly. */
429
+ export function resetRefusalBudget() {
430
+ refusalBudget.minute = -1;
431
+ refusalBudget.written = 0;
432
+ refusalBudget.dropped = 0;
433
+ }
434
+
435
+ /**
436
+ * One audit line. Only fixed-shape fields: nothing a caller controls reaches
437
+ * the file unless it matched a strict pattern first. A refusal decided before
438
+ * a signature verified goes to the refusals log instead, at most
439
+ * REFUSAL_ROWS_PER_MINUTE rows a minute; the first row written after rows were
440
+ * dropped carries `suppressed`, their count. Best effort: a log that cannot be
441
+ * written never turns a refusal into an acceptance or the reverse.
442
+ */
443
+ export function appendAudit(
444
+ { deviceId, method, verb, status, outcome },
445
+ env = process.env,
446
+ nowMs = Date.now(),
447
+ ) {
448
+ try {
449
+ const dir = phoneDir(env);
450
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
451
+ const row = {
452
+ at: new Date(nowMs).toISOString(),
453
+ deviceId: DEVICE_ID.test(deviceId ?? "") ? deviceId : null,
454
+ method: METHOD.test(method ?? "") ? method : "?",
455
+ verb: VERB.test(verb ?? "") ? verb : "unknown",
456
+ status,
457
+ outcome: VERB.test(outcome ?? "") ? outcome : "unknown",
458
+ };
459
+ let file = auditPath(env);
460
+ let generations = AUDIT_GENERATIONS;
461
+ if (UNAUTHENTICATED_OUTCOMES.includes(row.outcome)) {
462
+ const minute = Math.floor(nowMs / 60000);
463
+ if (minute !== refusalBudget.minute) {
464
+ refusalBudget.minute = minute;
465
+ refusalBudget.written = 0;
466
+ }
467
+ if (refusalBudget.written >= REFUSAL_ROWS_PER_MINUTE) {
468
+ refusalBudget.dropped++;
469
+ return;
470
+ }
471
+ refusalBudget.written++;
472
+ if (refusalBudget.dropped) {
473
+ row.suppressed = refusalBudget.dropped;
474
+ refusalBudget.dropped = 0;
475
+ }
476
+ file = refusalsPath(env);
477
+ generations = 1;
478
+ }
479
+ rotate(file, generations);
480
+ appendFileSync(file, JSON.stringify(row) + "\n", { mode: 0o600 });
481
+ chmodSync(file, 0o600);
482
+ } catch {
483
+ // The request's answer does not depend on the log.
484
+ }
485
+ }