buildanything 1.8.0 → 2.1.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 (494) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +17 -3
  3. package/CHANGELOG.md +57 -0
  4. package/README.md +57 -61
  5. package/agents/a11y-architect.md +168 -0
  6. package/agents/briefing-officer.md +172 -0
  7. package/agents/business-model.md +82 -29
  8. package/agents/code-architect.md +80 -0
  9. package/agents/code-reviewer.md +256 -0
  10. package/agents/code-simplifier.md +72 -0
  11. package/agents/design-brand-guardian.md +312 -53
  12. package/agents/design-critic.md +144 -0
  13. package/agents/design-inclusive-visuals-specialist.md +8 -19
  14. package/agents/design-ui-designer.md +352 -56
  15. package/agents/design-ux-architect.md +418 -55
  16. package/agents/design-ux-researcher.md +359 -49
  17. package/agents/engineering-ai-engineer.md +28 -36
  18. package/agents/engineering-backend-architect.md +187 -36
  19. package/agents/engineering-data-engineer.md +227 -43
  20. package/agents/engineering-devops-automator.md +229 -74
  21. package/agents/engineering-frontend-developer.md +223 -34
  22. package/agents/engineering-mobile-app-builder.md +8 -1
  23. package/agents/engineering-rapid-prototyper.md +45 -11
  24. package/agents/engineering-security-engineer.md +265 -61
  25. package/agents/engineering-senior-developer.md +141 -19
  26. package/agents/engineering-sre.md +86 -0
  27. package/agents/engineering-technical-writer.md +287 -41
  28. package/agents/feature-intel.md +111 -0
  29. package/agents/ios-app-review-guardian.md +21 -2
  30. package/agents/ios-foundation-models-specialist.md +22 -2
  31. package/agents/ios-product-reality-auditor.md +292 -0
  32. package/agents/ios-storekit-specialist.md +11 -2
  33. package/agents/ios-swift-architect.md +29 -1
  34. package/agents/ios-swift-search.md +9 -1
  35. package/agents/ios-swift-ui-design.md +40 -5
  36. package/agents/marketing-app-store-optimizer.md +248 -64
  37. package/agents/planner.md +221 -0
  38. package/agents/pr-test-analyzer.md +64 -0
  39. package/agents/product-feedback-synthesizer.md +70 -2
  40. package/agents/product-owner.md +163 -0
  41. package/agents/product-reality-auditor.md +216 -0
  42. package/agents/product-spec-writer.md +176 -0
  43. package/agents/refactor-cleaner.md +110 -0
  44. package/agents/security-reviewer.md +129 -0
  45. package/agents/silent-failure-hunter.md +55 -0
  46. package/agents/swift-build-resolver.md +121 -0
  47. package/agents/swift-reviewer.md +113 -0
  48. package/agents/tech-feasibility.md +26 -4
  49. package/agents/testing-api-tester.md +238 -59
  50. package/agents/testing-evidence-collector.md +50 -1
  51. package/agents/testing-performance-benchmarker.md +23 -1
  52. package/agents/testing-reality-checker.md +7 -1
  53. package/agents/visual-research.md +118 -0
  54. package/bin/adapters/cycle-counter-tool.ts +155 -0
  55. package/bin/adapters/scribe-tool.ts +73 -0
  56. package/bin/adapters/state-save-tool.ts +130 -0
  57. package/bin/adapters/write-lease-tool.ts +127 -0
  58. package/bin/buildanything-runtime.js +15 -0
  59. package/bin/buildanything-runtime.ts +241 -0
  60. package/bin/graph-index.js +24 -0
  61. package/bin/graph-index.ts +340 -0
  62. package/bin/mcp-servers/graph-mcp.js +26 -0
  63. package/bin/mcp-servers/graph-mcp.ts +481 -0
  64. package/bin/mcp-servers/orchestrator-mcp.js +26 -0
  65. package/bin/mcp-servers/orchestrator-mcp.ts +361 -0
  66. package/bin/setup.js +312 -76
  67. package/commands/add-feature.md +2 -0
  68. package/commands/build.md +994 -265
  69. package/commands/fix.md +1 -1
  70. package/commands/idea-sweep.md +2 -2
  71. package/commands/self-check.md +121 -0
  72. package/commands/setup.md +61 -9
  73. package/commands/ux-review.md +5 -5
  74. package/commands/verify.md +9 -9
  75. package/docs/migration/agents.yaml +729 -0
  76. package/docs/migration/phase-graph.yaml +1504 -0
  77. package/docs/migration/sdk-host-compat.md +18 -0
  78. package/hooks/compile-writer-owner-cache.ts +171 -0
  79. package/hooks/design-md-lint +4 -0
  80. package/hooks/design-md-lint.ts +295 -0
  81. package/hooks/hooks.json +36 -0
  82. package/hooks/pre-tool-use +19 -0
  83. package/hooks/pre-tool-use.ts +807 -0
  84. package/hooks/record-mode-transitions.ts +235 -0
  85. package/hooks/session-start +71 -1
  86. package/hooks/subagent-start +17 -0
  87. package/hooks/subagent-start.ts +472 -0
  88. package/hooks/subagent-stop +17 -0
  89. package/hooks/subagent-stop.ts +153 -0
  90. package/package.json +26 -4
  91. package/protocols/agent-prompt-authoring.md +165 -0
  92. package/protocols/architecture-schema.md +178 -0
  93. package/protocols/cleanup.md +4 -0
  94. package/protocols/decision-log.md +135 -0
  95. package/protocols/design-md-authoring.md +520 -0
  96. package/protocols/design-md-spec.md +362 -0
  97. package/protocols/fake-data-detector.md +1 -1
  98. package/protocols/ios-context.md +10 -11
  99. package/protocols/ios-fake-data-detector.md +65 -0
  100. package/protocols/ios-phase-branches.md +299 -39
  101. package/protocols/launch-readiness.md +262 -0
  102. package/protocols/metric-loop.md +62 -2
  103. package/protocols/page-spec-schema.md +234 -0
  104. package/protocols/product-spec-schema.md +354 -0
  105. package/protocols/smoke-test.md +9 -1
  106. package/protocols/sprint-tasks-schema.md +53 -0
  107. package/protocols/state-schema.json +423 -0
  108. package/protocols/state-schema.md +202 -0
  109. package/protocols/verify.md +91 -3
  110. package/protocols/web-phase-branches.md +395 -75
  111. package/skills/ios/_VENDORED.md +2 -0
  112. package/skills/ios/app-store-connect-metadata/SKILL.md +148 -0
  113. package/skills/ios/asc-privacy-manifest/SKILL.md +350 -0
  114. package/skills/ios/hig-components-content/SKILL.md +86 -0
  115. package/skills/ios/hig-components-content/references/activity-views.md +79 -0
  116. package/skills/ios/hig-components-content/references/charts.md +180 -0
  117. package/skills/ios/hig-components-content/references/collections.md +48 -0
  118. package/skills/ios/hig-components-content/references/color-wells.md +42 -0
  119. package/skills/ios/hig-components-content/references/image-views.md +82 -0
  120. package/skills/ios/hig-components-content/references/image-wells.md +34 -0
  121. package/skills/ios/hig-components-content/references/lockups.md +78 -0
  122. package/skills/ios/hig-components-content/references/web-views.md +36 -0
  123. package/skills/ios/hig-components-controls/SKILL.md +88 -0
  124. package/skills/ios/hig-components-controls/references/combo-boxes.md +40 -0
  125. package/skills/ios/hig-components-controls/references/controls.md +112 -0
  126. package/skills/ios/hig-components-controls/references/gauges.md +74 -0
  127. package/skills/ios/hig-components-controls/references/labels.md +92 -0
  128. package/skills/ios/hig-components-controls/references/pickers.md +128 -0
  129. package/skills/ios/hig-components-controls/references/rating-indicators.md +38 -0
  130. package/skills/ios/hig-components-controls/references/segmented-controls.md +94 -0
  131. package/skills/ios/hig-components-controls/references/sliders.md +92 -0
  132. package/skills/ios/hig-components-controls/references/steppers.md +40 -0
  133. package/skills/ios/hig-components-controls/references/text-fields.md +88 -0
  134. package/skills/ios/hig-components-controls/references/text-views.md +56 -0
  135. package/skills/ios/hig-components-controls/references/toggles.md +127 -0
  136. package/skills/ios/hig-components-controls/references/token-fields.md +48 -0
  137. package/skills/ios/hig-components-controls/references/virtual-keyboards.md +156 -0
  138. package/skills/ios/hig-components-dialogs/SKILL.md +76 -0
  139. package/skills/ios/hig-components-dialogs/references/action-sheets.md +74 -0
  140. package/skills/ios/hig-components-dialogs/references/alerts.md +158 -0
  141. package/skills/ios/hig-components-dialogs/references/digit-entry-views.md +32 -0
  142. package/skills/ios/hig-components-dialogs/references/popovers.md +81 -0
  143. package/skills/ios/hig-components-dialogs/references/sheets.md +157 -0
  144. package/skills/ios/hig-components-layout/SKILL.md +99 -0
  145. package/skills/ios/hig-components-layout/references/boxes.md +48 -0
  146. package/skills/ios/hig-components-layout/references/column-views.md +44 -0
  147. package/skills/ios/hig-components-layout/references/lists-and-tables.md +99 -0
  148. package/skills/ios/hig-components-layout/references/ornaments.md +56 -0
  149. package/skills/ios/hig-components-layout/references/outline-views.md +64 -0
  150. package/skills/ios/hig-components-layout/references/panels.md +75 -0
  151. package/skills/ios/hig-components-layout/references/scroll-views.md +123 -0
  152. package/skills/ios/hig-components-layout/references/sidebars.md +109 -0
  153. package/skills/ios/hig-components-layout/references/split-views.md +110 -0
  154. package/skills/ios/hig-components-layout/references/tab-bars.md +173 -0
  155. package/skills/ios/hig-components-layout/references/tab-views.md +68 -0
  156. package/skills/ios/hig-components-layout/references/windows.md +188 -0
  157. package/skills/ios/hig-components-menus/SKILL.md +81 -0
  158. package/skills/ios/hig-components-menus/references/action-button.md +61 -0
  159. package/skills/ios/hig-components-menus/references/buttons.md +261 -0
  160. package/skills/ios/hig-components-menus/references/context-menus.md +105 -0
  161. package/skills/ios/hig-components-menus/references/disclosure-controls.md +84 -0
  162. package/skills/ios/hig-components-menus/references/dock-menus.md +40 -0
  163. package/skills/ios/hig-components-menus/references/edit-menus.md +88 -0
  164. package/skills/ios/hig-components-menus/references/menus.md +171 -0
  165. package/skills/ios/hig-components-menus/references/pop-up-buttons.md +70 -0
  166. package/skills/ios/hig-components-menus/references/pull-down-buttons.md +77 -0
  167. package/skills/ios/hig-components-menus/references/the-menu-bar.md +303 -0
  168. package/skills/ios/hig-components-menus/references/toolbars.md +256 -0
  169. package/skills/ios/hig-components-search/SKILL.md +68 -0
  170. package/skills/ios/hig-components-search/references/page-controls.md +120 -0
  171. package/skills/ios/hig-components-search/references/path-controls.md +40 -0
  172. package/skills/ios/hig-components-search/references/search-fields.md +189 -0
  173. package/skills/ios/hig-components-status/SKILL.md +80 -0
  174. package/skills/ios/hig-components-status/references/activity-rings.md +105 -0
  175. package/skills/ios/hig-components-status/references/progress-indicators.md +116 -0
  176. package/skills/ios/hig-components-status/references/status-bars.md +38 -0
  177. package/skills/ios/hig-components-system/SKILL.md +88 -0
  178. package/skills/ios/hig-components-system/references/app-clips.md +387 -0
  179. package/skills/ios/hig-components-system/references/app-shortcuts.md +114 -0
  180. package/skills/ios/hig-components-system/references/complications.md +425 -0
  181. package/skills/ios/hig-components-system/references/home-screen-quick-actions.md +42 -0
  182. package/skills/ios/hig-components-system/references/live-activities.md +442 -0
  183. package/skills/ios/hig-components-system/references/notifications.md +153 -0
  184. package/skills/ios/hig-components-system/references/top-shelf.md +135 -0
  185. package/skills/ios/hig-components-system/references/watch-faces.md +40 -0
  186. package/skills/ios/hig-components-system/references/widgets.md +517 -0
  187. package/skills/ios/hig-foundations/SKILL.md +98 -0
  188. package/skills/ios/hig-foundations/references/accessibility.md +291 -0
  189. package/skills/ios/hig-foundations/references/app-icons.md +210 -0
  190. package/skills/ios/hig-foundations/references/branding.md +44 -0
  191. package/skills/ios/hig-foundations/references/color.md +274 -0
  192. package/skills/ios/hig-foundations/references/dark-mode.md +116 -0
  193. package/skills/ios/hig-foundations/references/icons.md +263 -0
  194. package/skills/ios/hig-foundations/references/images.md +176 -0
  195. package/skills/ios/hig-foundations/references/immersive-experiences.md +174 -0
  196. package/skills/ios/hig-foundations/references/inclusion.md +189 -0
  197. package/skills/ios/hig-foundations/references/layout.md +425 -0
  198. package/skills/ios/hig-foundations/references/materials.md +238 -0
  199. package/skills/ios/hig-foundations/references/motion.md +103 -0
  200. package/skills/ios/hig-foundations/references/privacy.md +231 -0
  201. package/skills/ios/hig-foundations/references/right-to-left.md +206 -0
  202. package/skills/ios/hig-foundations/references/sf-symbols.md +310 -0
  203. package/skills/ios/hig-foundations/references/spatial-layout.md +142 -0
  204. package/skills/ios/hig-foundations/references/typography.md +1146 -0
  205. package/skills/ios/hig-foundations/references/writing.md +91 -0
  206. package/skills/ios/hig-inputs/SKILL.md +94 -0
  207. package/skills/ios/hig-inputs/references/apple-pencil-and-scribble.md +148 -0
  208. package/skills/ios/hig-inputs/references/camera-control.md +107 -0
  209. package/skills/ios/hig-inputs/references/digital-crown.md +83 -0
  210. package/skills/ios/hig-inputs/references/eyes.md +120 -0
  211. package/skills/ios/hig-inputs/references/focus-and-selection.md +120 -0
  212. package/skills/ios/hig-inputs/references/game-controls.md +156 -0
  213. package/skills/ios/hig-inputs/references/gestures.md +208 -0
  214. package/skills/ios/hig-inputs/references/gyro-and-accelerometer.md +40 -0
  215. package/skills/ios/hig-inputs/references/keyboards.md +234 -0
  216. package/skills/ios/hig-inputs/references/nearby-interactions.md +70 -0
  217. package/skills/ios/hig-inputs/references/pointing-devices.md +237 -0
  218. package/skills/ios/hig-inputs/references/remotes.md +67 -0
  219. package/skills/ios/hig-inputs/references/spatial-interactions.md +70 -0
  220. package/skills/ios/hig-patterns/SKILL.md +104 -0
  221. package/skills/ios/hig-patterns/references/charting-data.md +81 -0
  222. package/skills/ios/hig-patterns/references/collaboration-and-sharing.md +86 -0
  223. package/skills/ios/hig-patterns/references/drag-and-drop.md +134 -0
  224. package/skills/ios/hig-patterns/references/entering-data.md +69 -0
  225. package/skills/ios/hig-patterns/references/feedback.md +67 -0
  226. package/skills/ios/hig-patterns/references/file-management.md +135 -0
  227. package/skills/ios/hig-patterns/references/going-full-screen.md +79 -0
  228. package/skills/ios/hig-patterns/references/launching.md +81 -0
  229. package/skills/ios/hig-patterns/references/live-viewing-apps.md +79 -0
  230. package/skills/ios/hig-patterns/references/loading.md +59 -0
  231. package/skills/ios/hig-patterns/references/managing-accounts.md +107 -0
  232. package/skills/ios/hig-patterns/references/managing-notifications.md +99 -0
  233. package/skills/ios/hig-patterns/references/modality.md +82 -0
  234. package/skills/ios/hig-patterns/references/multitasking.md +131 -0
  235. package/skills/ios/hig-patterns/references/offering-help.md +117 -0
  236. package/skills/ios/hig-patterns/references/onboarding.md +69 -0
  237. package/skills/ios/hig-patterns/references/playing-audio.md +124 -0
  238. package/skills/ios/hig-patterns/references/playing-haptics.md +280 -0
  239. package/skills/ios/hig-patterns/references/playing-video.md +180 -0
  240. package/skills/ios/hig-patterns/references/printing.md +50 -0
  241. package/skills/ios/hig-patterns/references/ratings-and-reviews.md +48 -0
  242. package/skills/ios/hig-patterns/references/searching.md +70 -0
  243. package/skills/ios/hig-patterns/references/settings.md +84 -0
  244. package/skills/ios/hig-patterns/references/undo-and-redo.md +58 -0
  245. package/skills/ios/hig-patterns/references/workouts.md +76 -0
  246. package/skills/ios/hig-platforms/SKILL.md +84 -0
  247. package/skills/ios/hig-platforms/references/designing-for-games.md +159 -0
  248. package/skills/ios/hig-platforms/references/designing-for-ios.md +66 -0
  249. package/skills/ios/hig-platforms/references/designing-for-ipados.md +64 -0
  250. package/skills/ios/hig-platforms/references/designing-for-macos.md +70 -0
  251. package/skills/ios/hig-platforms/references/designing-for-tvos.md +68 -0
  252. package/skills/ios/hig-platforms/references/designing-for-visionos.md +85 -0
  253. package/skills/ios/hig-platforms/references/designing-for-watchos.md +74 -0
  254. package/skills/ios/hig-project-context/SKILL.md +133 -0
  255. package/skills/ios/hig-technologies/SKILL.md +107 -0
  256. package/skills/ios/hig-technologies/references/airplay.md +125 -0
  257. package/skills/ios/hig-technologies/references/always-on.md +62 -0
  258. package/skills/ios/hig-technologies/references/apple-pay.md +441 -0
  259. package/skills/ios/hig-technologies/references/augmented-reality.md +247 -0
  260. package/skills/ios/hig-technologies/references/carekit.md +224 -0
  261. package/skills/ios/hig-technologies/references/carplay.md +119 -0
  262. package/skills/ios/hig-technologies/references/game-center.md +343 -0
  263. package/skills/ios/hig-technologies/references/generative-ai.md +110 -0
  264. package/skills/ios/hig-technologies/references/healthkit.md +120 -0
  265. package/skills/ios/hig-technologies/references/homekit.md +343 -0
  266. package/skills/ios/hig-technologies/references/icloud.md +52 -0
  267. package/skills/ios/hig-technologies/references/id-verifier.md +73 -0
  268. package/skills/ios/hig-technologies/references/imessage-apps-and-stickers.md +105 -0
  269. package/skills/ios/hig-technologies/references/in-app-purchase.md +263 -0
  270. package/skills/ios/hig-technologies/references/live-photos.md +54 -0
  271. package/skills/ios/hig-technologies/references/mac-catalyst.md +216 -0
  272. package/skills/ios/hig-technologies/references/machine-learning.md +394 -0
  273. package/skills/ios/hig-technologies/references/maps.md +221 -0
  274. package/skills/ios/hig-technologies/references/nfc.md +51 -0
  275. package/skills/ios/hig-technologies/references/photo-editing.md +40 -0
  276. package/skills/ios/hig-technologies/references/researchkit.md +134 -0
  277. package/skills/ios/hig-technologies/references/shareplay.md +142 -0
  278. package/skills/ios/hig-technologies/references/shazamkit.md +47 -0
  279. package/skills/ios/hig-technologies/references/sign-in-with-apple.md +288 -0
  280. package/skills/ios/hig-technologies/references/siri.md +523 -0
  281. package/skills/ios/hig-technologies/references/tap-to-pay-on-iphone.md +208 -0
  282. package/skills/ios/hig-technologies/references/voiceover.md +90 -0
  283. package/skills/ios/hig-technologies/references/wallet.md +420 -0
  284. package/skills/ios/ios-bootstrap/SKILL.md +17 -8
  285. package/skills/ios/swift-actor-persistence/SKILL.md +143 -0
  286. package/skills/ios/swift-concurrency-6-2/SKILL.md +216 -0
  287. package/skills/ios/swift-protocol-di-testing/SKILL.md +190 -0
  288. package/skills/ios/swiftui-design-tokens/SKILL.md +475 -0
  289. package/skills/ios/writing-for-interfaces/SKILL.md +75 -0
  290. package/skills/web/accessibility/SKILL.md +146 -0
  291. package/skills/web/aceternity-ui/SKILL.md +719 -0
  292. package/skills/web/aceternity-ui/metadata.json +10 -0
  293. package/skills/web/api-design/SKILL.md +523 -0
  294. package/skills/web/chart-accessibility/SKILL.md +332 -0
  295. package/skills/web/composition-patterns/AGENTS.md +946 -0
  296. package/skills/web/composition-patterns/README.md +60 -0
  297. package/skills/web/composition-patterns/SKILL.md +89 -0
  298. package/skills/web/composition-patterns/metadata.json +11 -0
  299. package/skills/web/composition-patterns/rules/_sections.md +29 -0
  300. package/skills/web/composition-patterns/rules/_template.md +24 -0
  301. package/skills/web/composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
  302. package/skills/web/composition-patterns/rules/architecture-compound-components.md +112 -0
  303. package/skills/web/composition-patterns/rules/patterns-children-over-render-props.md +87 -0
  304. package/skills/web/composition-patterns/rules/patterns-explicit-variants.md +100 -0
  305. package/skills/web/composition-patterns/rules/react19-no-forwardref.md +42 -0
  306. package/skills/web/composition-patterns/rules/state-context-interface.md +191 -0
  307. package/skills/web/composition-patterns/rules/state-decouple-implementation.md +113 -0
  308. package/skills/web/composition-patterns/rules/state-lift-state.md +125 -0
  309. package/skills/web/cost-aware-llm-pipeline/SKILL.md +183 -0
  310. package/skills/web/database-migrations/SKILL.md +429 -0
  311. package/skills/web/deployment-patterns/SKILL.md +427 -0
  312. package/skills/web/docker-patterns/SKILL.md +364 -0
  313. package/skills/web/e2e-testing/SKILL.md +326 -0
  314. package/skills/web/lighthouse-ci/SKILL.md +361 -0
  315. package/skills/web/mcp-server-patterns/SKILL.md +69 -0
  316. package/skills/web/next-best-practices/SKILL.md +153 -0
  317. package/skills/web/next-best-practices/async-patterns.md +87 -0
  318. package/skills/web/next-best-practices/bundling.md +180 -0
  319. package/skills/web/next-best-practices/data-patterns.md +297 -0
  320. package/skills/web/next-best-practices/debug-tricks.md +105 -0
  321. package/skills/web/next-best-practices/directives.md +73 -0
  322. package/skills/web/next-best-practices/error-handling.md +227 -0
  323. package/skills/web/next-best-practices/file-conventions.md +140 -0
  324. package/skills/web/next-best-practices/font.md +245 -0
  325. package/skills/web/next-best-practices/functions.md +108 -0
  326. package/skills/web/next-best-practices/hydration-error.md +91 -0
  327. package/skills/web/next-best-practices/image.md +173 -0
  328. package/skills/web/next-best-practices/metadata.md +301 -0
  329. package/skills/web/next-best-practices/parallel-routes.md +287 -0
  330. package/skills/web/next-best-practices/route-handlers.md +146 -0
  331. package/skills/web/next-best-practices/rsc-boundaries.md +159 -0
  332. package/skills/web/next-best-practices/runtime-selection.md +39 -0
  333. package/skills/web/next-best-practices/scripts.md +141 -0
  334. package/skills/web/next-best-practices/self-hosting.md +371 -0
  335. package/skills/web/next-best-practices/suspense-boundaries.md +67 -0
  336. package/skills/web/next-cache-components/SKILL.md +411 -0
  337. package/skills/web/postgres-best-practices/SKILL.md +14 -0
  338. package/skills/web/postgres-best-practices/references/schema-design.md +9 -0
  339. package/skills/web/react-best-practices/AGENTS.md +3810 -0
  340. package/skills/web/react-best-practices/README.md +123 -0
  341. package/skills/web/react-best-practices/SKILL.md +149 -0
  342. package/skills/web/react-best-practices/metadata.json +15 -0
  343. package/skills/web/react-best-practices/rules/_sections.md +46 -0
  344. package/skills/web/react-best-practices/rules/_template.md +28 -0
  345. package/skills/web/react-best-practices/rules/advanced-effect-event-deps.md +56 -0
  346. package/skills/web/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  347. package/skills/web/react-best-practices/rules/advanced-init-once.md +42 -0
  348. package/skills/web/react-best-practices/rules/advanced-use-latest.md +39 -0
  349. package/skills/web/react-best-practices/rules/async-api-routes.md +38 -0
  350. package/skills/web/react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
  351. package/skills/web/react-best-practices/rules/async-defer-await.md +82 -0
  352. package/skills/web/react-best-practices/rules/async-dependencies.md +51 -0
  353. package/skills/web/react-best-practices/rules/async-parallel.md +28 -0
  354. package/skills/web/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  355. package/skills/web/react-best-practices/rules/bundle-analyzable-paths.md +63 -0
  356. package/skills/web/react-best-practices/rules/bundle-barrel-imports.md +60 -0
  357. package/skills/web/react-best-practices/rules/bundle-conditional.md +31 -0
  358. package/skills/web/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  359. package/skills/web/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  360. package/skills/web/react-best-practices/rules/bundle-preload.md +50 -0
  361. package/skills/web/react-best-practices/rules/client-event-listeners.md +74 -0
  362. package/skills/web/react-best-practices/rules/client-localstorage-schema.md +71 -0
  363. package/skills/web/react-best-practices/rules/client-passive-event-listeners.md +48 -0
  364. package/skills/web/react-best-practices/rules/client-swr-dedup.md +56 -0
  365. package/skills/web/react-best-practices/rules/js-batch-dom-css.md +107 -0
  366. package/skills/web/react-best-practices/rules/js-cache-function-results.md +80 -0
  367. package/skills/web/react-best-practices/rules/js-cache-property-access.md +28 -0
  368. package/skills/web/react-best-practices/rules/js-cache-storage.md +70 -0
  369. package/skills/web/react-best-practices/rules/js-combine-iterations.md +32 -0
  370. package/skills/web/react-best-practices/rules/js-early-exit.md +50 -0
  371. package/skills/web/react-best-practices/rules/js-flatmap-filter.md +60 -0
  372. package/skills/web/react-best-practices/rules/js-hoist-regexp.md +45 -0
  373. package/skills/web/react-best-practices/rules/js-index-maps.md +37 -0
  374. package/skills/web/react-best-practices/rules/js-length-check-first.md +49 -0
  375. package/skills/web/react-best-practices/rules/js-min-max-loop.md +82 -0
  376. package/skills/web/react-best-practices/rules/js-request-idle-callback.md +105 -0
  377. package/skills/web/react-best-practices/rules/js-set-map-lookups.md +24 -0
  378. package/skills/web/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  379. package/skills/web/react-best-practices/rules/rendering-activity.md +26 -0
  380. package/skills/web/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  381. package/skills/web/react-best-practices/rules/rendering-conditional-render.md +40 -0
  382. package/skills/web/react-best-practices/rules/rendering-content-visibility.md +38 -0
  383. package/skills/web/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  384. package/skills/web/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  385. package/skills/web/react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  386. package/skills/web/react-best-practices/rules/rendering-resource-hints.md +85 -0
  387. package/skills/web/react-best-practices/rules/rendering-script-defer-async.md +68 -0
  388. package/skills/web/react-best-practices/rules/rendering-svg-precision.md +28 -0
  389. package/skills/web/react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  390. package/skills/web/react-best-practices/rules/rerender-defer-reads.md +39 -0
  391. package/skills/web/react-best-practices/rules/rerender-dependencies.md +45 -0
  392. package/skills/web/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  393. package/skills/web/react-best-practices/rules/rerender-derived-state.md +29 -0
  394. package/skills/web/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  395. package/skills/web/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  396. package/skills/web/react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  397. package/skills/web/react-best-practices/rules/rerender-memo.md +44 -0
  398. package/skills/web/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  399. package/skills/web/react-best-practices/rules/rerender-no-inline-components.md +82 -0
  400. package/skills/web/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  401. package/skills/web/react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
  402. package/skills/web/react-best-practices/rules/rerender-transitions.md +40 -0
  403. package/skills/web/react-best-practices/rules/rerender-use-deferred-value.md +59 -0
  404. package/skills/web/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  405. package/skills/web/react-best-practices/rules/server-after-nonblocking.md +73 -0
  406. package/skills/web/react-best-practices/rules/server-auth-actions.md +96 -0
  407. package/skills/web/react-best-practices/rules/server-cache-lru.md +41 -0
  408. package/skills/web/react-best-practices/rules/server-cache-react.md +76 -0
  409. package/skills/web/react-best-practices/rules/server-dedup-props.md +65 -0
  410. package/skills/web/react-best-practices/rules/server-hoist-static-io.md +149 -0
  411. package/skills/web/react-best-practices/rules/server-no-shared-module-state.md +50 -0
  412. package/skills/web/react-best-practices/rules/server-parallel-fetching.md +83 -0
  413. package/skills/web/react-best-practices/rules/server-parallel-nested-fetching.md +34 -0
  414. package/skills/web/react-best-practices/rules/server-serialization.md +38 -0
  415. package/skills/web/seo/SKILL.md +154 -0
  416. package/skills/web/web-design-guidelines/SKILL.md +39 -0
  417. package/skills/web/zap-scan-config/SKILL.md +444 -0
  418. package/skills/web/zap-scan-config/assets/.gitkeep +9 -0
  419. package/skills/web/zap-scan-config/assets/github_action.yml +207 -0
  420. package/skills/web/zap-scan-config/assets/gitlab_ci.yml +226 -0
  421. package/skills/web/zap-scan-config/assets/zap_automation.yaml +196 -0
  422. package/skills/web/zap-scan-config/assets/zap_context.xml +192 -0
  423. package/skills/web/zap-scan-config/references/EXAMPLE.md +40 -0
  424. package/skills/web/zap-scan-config/references/api_testing_guide.md +475 -0
  425. package/skills/web/zap-scan-config/references/authentication_guide.md +431 -0
  426. package/skills/web/zap-scan-config/references/false_positive_handling.md +427 -0
  427. package/skills/web/zap-scan-config/references/owasp_mapping.md +255 -0
  428. package/src/graph/ids.ts +86 -0
  429. package/src/graph/index.ts +32 -0
  430. package/src/graph/parser/architecture.ts +603 -0
  431. package/src/graph/parser/component-manifest.ts +268 -0
  432. package/src/graph/parser/decisions-jsonl.ts +407 -0
  433. package/src/graph/parser/design-md-pass2.ts +253 -0
  434. package/src/graph/parser/design-md.ts +477 -0
  435. package/src/graph/parser/page-spec.ts +496 -0
  436. package/src/graph/parser/product-spec.ts +930 -0
  437. package/src/graph/parser/screenshot.ts +342 -0
  438. package/src/graph/parser/sprint-tasks.ts +317 -0
  439. package/src/graph/storage/index.ts +1154 -0
  440. package/src/graph/types.ts +432 -0
  441. package/src/graph/util/dhash.ts +84 -0
  442. package/src/lrr/aggregator.ts +175 -0
  443. package/src/orchestrator/hooks/context-header.ts +119 -0
  444. package/src/orchestrator/hooks/token-accounting-emitter.ts +77 -0
  445. package/src/orchestrator/hooks/token-accounting.ts +112 -0
  446. package/src/orchestrator/mcp/cycle-counter.ts +130 -0
  447. package/src/orchestrator/mcp/scribe.ts +294 -0
  448. package/src/orchestrator/mcp/state-save.ts +149 -0
  449. package/src/orchestrator/mcp/write-lease.ts +184 -0
  450. package/src/orchestrator/phase4-shared-context.ts +57 -0
  451. package/src/orchestrator/schemas/backward-edge.ts +46 -0
  452. package/agents/agentic-identity-trust.md +0 -121
  453. package/agents/data-consolidation-agent.md +0 -39
  454. package/agents/design-image-prompt-engineer.md +0 -105
  455. package/agents/design-visual-storyteller.md +0 -147
  456. package/agents/design-whimsy-injector.md +0 -89
  457. package/agents/engineering-autonomous-optimization-architect.md +0 -105
  458. package/agents/market-intel.md +0 -35
  459. package/agents/marketing-instagram-curator.md +0 -111
  460. package/agents/marketing-reddit-community-builder.md +0 -121
  461. package/agents/marketing-social-media-strategist.md +0 -74
  462. package/agents/marketing-tiktok-strategist.md +0 -123
  463. package/agents/marketing-twitter-engager.md +0 -124
  464. package/agents/marketing-wechat-official-account.md +0 -143
  465. package/agents/marketing-xiaohongshu-specialist.md +0 -136
  466. package/agents/marketing-zhihu-strategist.md +0 -160
  467. package/agents/product-behavioral-nudge-engine.md +0 -78
  468. package/agents/project-management-experiment-tracker.md +0 -102
  469. package/agents/report-distribution-agent.md +0 -43
  470. package/agents/risk-analysis.md +0 -45
  471. package/agents/sales-data-extraction-agent.md +0 -46
  472. package/agents/specialized-cultural-intelligence-strategist.md +0 -65
  473. package/agents/specialized-developer-advocate.md +0 -146
  474. package/agents/support-analytics-reporter.md +0 -133
  475. package/agents/support-executive-summary-generator.md +0 -64
  476. package/agents/support-finance-tracker.md +0 -145
  477. package/agents/support-legal-compliance-checker.md +0 -129
  478. package/agents/support-support-responder.md +0 -91
  479. package/agents/testing-accessibility-auditor.md +0 -110
  480. package/agents/testing-test-results-analyzer.md +0 -97
  481. package/agents/testing-tool-evaluator.md +0 -76
  482. package/agents/testing-workflow-optimizer.md +0 -99
  483. package/agents/user-research.md +0 -40
  484. package/protocols/brainstorm.md +0 -99
  485. package/protocols/design.md +0 -269
  486. package/protocols/planning.md +0 -87
  487. package/skills/ios/ios-hig/SKILL.md +0 -41
  488. package/skills/ios/ios-hig/references/accessibility.md +0 -81
  489. package/skills/ios/ios-hig/references/content.md +0 -142
  490. package/skills/ios/ios-hig/references/feedback.md +0 -123
  491. package/skills/ios/ios-hig/references/interaction.md +0 -199
  492. package/skills/ios/ios-hig/references/performance-platform.md +0 -129
  493. package/skills/ios/ios-hig/references/privacy-permissions.md +0 -181
  494. package/skills/ios/ios-hig/references/visual-design.md +0 -84
@@ -0,0 +1,294 @@
1
+ /**
2
+ * scribe_decision MCP handler.
3
+ *
4
+ * The ONLY writer to docs/plans/decisions.jsonl.
5
+ * Subagents return deviation_row objects → orchestrator routes them here.
6
+ * Append-only — NEVER rewrites or truncates the file.
7
+ *
8
+ * Spec: protocols/decision-log.md
9
+ * Schema: docs/migration/decisions.schema.json
10
+ * Migration ref: MIGRATION-PLAN-FINAL.md §4 Stage 1 (tasks 1.2.1–1.2.3)
11
+ */
12
+
13
+ import { writeFileSync, readFileSync, existsSync, mkdirSync, renameSync } from 'node:fs';
14
+ import { dirname } from 'node:path';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export interface RejectedAlternative {
21
+ approach: string;
22
+ reason: string;
23
+ revisit_criterion: string;
24
+ }
25
+
26
+ export interface DecisionRow {
27
+ decision_id: string;
28
+ phase: string;
29
+ timestamp: string;
30
+ decision: string;
31
+ chosen_approach: string;
32
+ rejected_alternatives: RejectedAlternative[];
33
+ decided_by: string;
34
+ ref: string;
35
+ status: 'open' | 'triggered' | 'resolved';
36
+ }
37
+
38
+ export interface ScribeInput {
39
+ phase: string;
40
+ summary: string;
41
+ decided_by: string;
42
+ impact_level: 'low' | 'medium' | 'high' | 'critical';
43
+ chosen_approach: string;
44
+ rejected_alternatives?: RejectedAlternative[];
45
+ ref: string;
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Constants — sourced from protocols/decision-log.md §Hard Field Constraints
50
+ // ---------------------------------------------------------------------------
51
+
52
+ const VALID_IMPACTS = ['low', 'medium', 'high', 'critical'] as const;
53
+ const VALID_STATUSES = ['open', 'triggered', 'resolved'] as const;
54
+ const MAX_ALTERNATIVES = 3;
55
+ const MAX_ROWS_PER_PHASE = 5;
56
+ const MAX_DECISION_LEN = 240;
57
+ const MAX_CHOSEN_APPROACH_LEN = 480;
58
+ const MAX_APPROACH_LEN = 240;
59
+ const MAX_REASON_LEN = 400;
60
+ const MAX_REVISIT_LEN = 240;
61
+
62
+ /** Ref field pattern from decisions.schema.json */
63
+ const REF_PATTERN = /^[a-zA-Z0-9_\-./]+\.(md|json|jsonl|yaml|yml)(#[a-zA-Z0-9_\-/.]+)?$/;
64
+
65
+ /** Phase string pattern from decisions.schema.json. Leading minus allowed for Phase -1 (iOS bootstrap). */
66
+ const PHASE_PATTERN = /^-?[0-9]+(\.[0-9]+)?$/;
67
+
68
+ /** Encode a phase token for embedding in a decision_id. Negative phases get an "N" prefix
69
+ * instead of "-" to avoid colliding with the "-" delimiter in the ID format. So phase "-1"
70
+ * becomes "N1" in the ID slot, yielding "D-N1-01". Round-trippable via decodePhaseFromId. */
71
+ function encodePhaseForId(phase: string): string {
72
+ return phase.startsWith('-') ? `N${phase.slice(1)}` : phase;
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Per-phase sequence counters (task 1.2.3 — ID allocation)
77
+ // ---------------------------------------------------------------------------
78
+
79
+ const counters: Record<string, number> = {};
80
+
81
+ /**
82
+ * Load existing counters from a decisions.jsonl file.
83
+ * Scans all rows to find the max seq per phase so new IDs don't collide.
84
+ */
85
+ export function loadCounters(filePath: string): void {
86
+ if (!existsSync(filePath)) return;
87
+ const content = readFileSync(filePath, 'utf-8').trim();
88
+ if (!content) return;
89
+ const lines = content.split('\n').filter(Boolean);
90
+ for (const line of lines) {
91
+ try {
92
+ const row = JSON.parse(line) as DecisionRow;
93
+ const match = row.decision_id.match(/^D-(.+)-(\d{2,})$/);
94
+ if (match) {
95
+ const phase = match[1];
96
+ const seq = parseInt(match[2], 10);
97
+ counters[phase] = Math.max(counters[phase] ?? 0, seq);
98
+ }
99
+ } catch {
100
+ /* skip malformed lines */
101
+ }
102
+ }
103
+ }
104
+
105
+ /** Allocate the next decision ID for a phase. Format: D-{encodedPhase}-{seq} */
106
+ function allocateId(phase: string): string {
107
+ const encoded = encodePhaseForId(phase);
108
+ counters[encoded] = (counters[encoded] ?? 0) + 1;
109
+ return `D-${encoded}-${String(counters[encoded]).padStart(2, '0')}`;
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // Exclusive-write lock (task 1.2.3)
114
+ // ---------------------------------------------------------------------------
115
+
116
+ let writeLocked = false;
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Schema validation (task 1.2.2)
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * Count rows for a given phase in the target file.
124
+ * Used to enforce the max-5-rows-per-phase constraint.
125
+ */
126
+ function countPhaseRows(filePath: string, phase: string): number {
127
+ if (!existsSync(filePath)) return 0;
128
+ const content = readFileSync(filePath, 'utf-8').trim();
129
+ if (!content) return 0;
130
+ let count = 0;
131
+ for (const line of content.split('\n')) {
132
+ try {
133
+ const row = JSON.parse(line) as DecisionRow;
134
+ if (row.phase === phase) count++;
135
+ } catch {
136
+ /* skip */
137
+ }
138
+ }
139
+ return count;
140
+ }
141
+
142
+ /**
143
+ * Validate a single rejected alternative against schema constraints.
144
+ */
145
+ function validateAlternative(alt: RejectedAlternative, index: number): void {
146
+ if (!alt.approach || alt.approach.length === 0) {
147
+ throw new Error(`rejected_alternatives[${index}].approach is required`);
148
+ }
149
+ if (alt.approach.length > MAX_APPROACH_LEN) {
150
+ throw new Error(`rejected_alternatives[${index}].approach exceeds ${MAX_APPROACH_LEN} chars`);
151
+ }
152
+ if (!alt.reason || alt.reason.length === 0) {
153
+ throw new Error(`rejected_alternatives[${index}].reason is required`);
154
+ }
155
+ if (alt.reason.length > MAX_REASON_LEN) {
156
+ throw new Error(`rejected_alternatives[${index}].reason exceeds ${MAX_REASON_LEN} chars (max 2 sentences)`);
157
+ }
158
+ if (!alt.revisit_criterion || alt.revisit_criterion.length === 0) {
159
+ throw new Error(`rejected_alternatives[${index}].revisit_criterion is required`);
160
+ }
161
+ if (alt.revisit_criterion.length > MAX_REVISIT_LEN) {
162
+ throw new Error(`rejected_alternatives[${index}].revisit_criterion exceeds ${MAX_REVISIT_LEN} chars (max 1 sentence)`);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Validate scribe input against the decision log schema.
168
+ * Throws on any validation failure.
169
+ */
170
+ export function validate(input: ScribeInput): void {
171
+ if (!input.phase || !PHASE_PATTERN.test(input.phase)) {
172
+ throw new Error(`Invalid phase: "${input.phase ?? ''}". Must match pattern ${PHASE_PATTERN.source}`);
173
+ }
174
+ if (!input.summary || input.summary.length === 0) {
175
+ throw new Error('summary is required');
176
+ }
177
+ if (input.summary.length > MAX_DECISION_LEN) {
178
+ throw new Error(`summary exceeds ${MAX_DECISION_LEN} chars`);
179
+ }
180
+ if (!input.chosen_approach || input.chosen_approach.length === 0) {
181
+ throw new Error('chosen_approach is required');
182
+ }
183
+ if (input.chosen_approach.length > MAX_CHOSEN_APPROACH_LEN) {
184
+ throw new Error(`chosen_approach exceeds ${MAX_CHOSEN_APPROACH_LEN} chars`);
185
+ }
186
+ if (!input.decided_by || input.decided_by.length === 0) {
187
+ throw new Error('decided_by is required');
188
+ }
189
+ if (!VALID_IMPACTS.includes(input.impact_level as typeof VALID_IMPACTS[number])) {
190
+ throw new Error(`Invalid impact_level: ${input.impact_level}. Must be one of: ${VALID_IMPACTS.join(', ')}`);
191
+ }
192
+ if (!input.ref || input.ref.length === 0 || !REF_PATTERN.test(input.ref)) {
193
+ throw new Error(`Invalid ref: "${input.ref ?? ''}". Must match pattern ${REF_PATTERN.source}`);
194
+ }
195
+
196
+ const alts = input.rejected_alternatives ?? [];
197
+ if (alts.length > MAX_ALTERNATIVES) {
198
+ throw new Error(`Max ${MAX_ALTERNATIVES} rejected alternatives per decision row`);
199
+ }
200
+ for (let i = 0; i < alts.length; i++) {
201
+ validateAlternative(alts[i], i);
202
+ }
203
+ }
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // MCP handler (task 1.2.1)
207
+ // ---------------------------------------------------------------------------
208
+
209
+ /**
210
+ * scribe_decision — in-process MCP tool handler.
211
+ *
212
+ * Validates input, allocates a sequential ID, acquires the exclusive-write
213
+ * lock, and appends a single JSON line to the decisions file.
214
+ * Returns the written DecisionRow.
215
+ *
216
+ * @param input - ScribeInput from the calling subagent
217
+ * @param filePath - Absolute path to decisions.jsonl (default: docs/plans/decisions.jsonl)
218
+ */
219
+ export function scribeDecision(input: ScribeInput, filePath: string): DecisionRow {
220
+ if (writeLocked) {
221
+ throw new Error('Exclusive write lock held — concurrent scribe call rejected');
222
+ }
223
+
224
+ writeLocked = true;
225
+ try {
226
+ validate(input);
227
+
228
+ // Auto-rehydrate counters from disk (idempotent — takes max per phase)
229
+ loadCounters(filePath);
230
+
231
+ // Enforce max 5 rows per phase
232
+ const existing = countPhaseRows(filePath, input.phase);
233
+ if (existing >= MAX_ROWS_PER_PHASE) {
234
+ throw new Error(
235
+ `Phase "${input.phase}" already has ${existing} decision rows (max ${MAX_ROWS_PER_PHASE}). ` +
236
+ 'Split into sub-phases or consolidate before adding more.',
237
+ );
238
+ }
239
+
240
+ const row: DecisionRow = {
241
+ decision_id: allocateId(input.phase),
242
+ phase: input.phase,
243
+ timestamp: new Date().toISOString(),
244
+ decision: input.summary,
245
+ chosen_approach: input.chosen_approach,
246
+ rejected_alternatives: input.rejected_alternatives ?? [],
247
+ decided_by: input.decided_by,
248
+ ref: input.ref,
249
+ status: 'open',
250
+ };
251
+
252
+ // Ensure parent directory exists
253
+ const dir = dirname(filePath);
254
+ if (!existsSync(dir)) {
255
+ mkdirSync(dir, { recursive: true });
256
+ }
257
+
258
+ // ATOMIC: read existing + append new row + write-to-tmp + rename
259
+ const existingContent = existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';
260
+ const newContent = existingContent + JSON.stringify(row) + '\n';
261
+ const tmp = `${filePath}.tmp`;
262
+ writeFileSync(tmp, newContent, 'utf-8');
263
+ renameSync(tmp, filePath); // atomic on POSIX same-filesystem
264
+
265
+ return row;
266
+ } finally {
267
+ writeLocked = false;
268
+ }
269
+ }
270
+
271
+ // ---------------------------------------------------------------------------
272
+ // Test helpers
273
+ // ---------------------------------------------------------------------------
274
+
275
+ /**
276
+ * Reset internal state (counters + lock). For testing only.
277
+ */
278
+ export function reset(): void {
279
+ for (const key of Object.keys(counters)) delete counters[key];
280
+ writeLocked = false;
281
+ }
282
+
283
+ /** Expose constants for test assertions */
284
+ export const LIMITS = {
285
+ MAX_ALTERNATIVES,
286
+ MAX_ROWS_PER_PHASE,
287
+ MAX_DECISION_LEN,
288
+ MAX_CHOSEN_APPROACH_LEN,
289
+ MAX_APPROACH_LEN,
290
+ MAX_REASON_LEN,
291
+ MAX_REVISIT_LEN,
292
+ VALID_IMPACTS,
293
+ VALID_STATUSES,
294
+ } as const;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * state_save MCP handler.
3
+ *
4
+ * Atomic state persistence for .build-state.json (and any state file).
5
+ * All mutations to .build-state.json MUST route through this MCP —
6
+ * raw Write|Edit is denied by the writer-owner hook (Stage 3.3.1).
7
+ *
8
+ * Protocol: write-to-.tmp + fsync + rename (POSIX atomic on same filesystem).
9
+ * Every write produces a SHA-256 integrity checksum for verification.
10
+ *
11
+ * Spec: MIGRATION-PLAN-FINAL.md §4 Stage 3 (tasks 3.2.1–3.2.3)
12
+ */
13
+
14
+ import {
15
+ writeFileSync,
16
+ readFileSync,
17
+ renameSync,
18
+ unlinkSync,
19
+ existsSync,
20
+ mkdirSync,
21
+ openSync,
22
+ fsyncSync,
23
+ closeSync,
24
+ } from 'node:fs';
25
+ import { createHash } from 'node:crypto';
26
+ import { dirname } from 'node:path';
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Types
30
+ // ---------------------------------------------------------------------------
31
+
32
+ export interface StateSaveResult {
33
+ success: boolean;
34
+ path: string;
35
+ sha256: string;
36
+ bytesWritten: number;
37
+ }
38
+
39
+ export interface StateReadResult {
40
+ state: Record<string, unknown>;
41
+ sha256: string;
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Exclusive-write lock — prevents concurrent state_save calls
46
+ // ---------------------------------------------------------------------------
47
+
48
+ let writeLocked = false;
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Core: atomic state save (tasks 3.2.1 + 3.2.2 + 3.2.3)
52
+ // ---------------------------------------------------------------------------
53
+
54
+ /**
55
+ * Atomically save state to a JSON file.
56
+ *
57
+ * Protocol (write-to-.tmp + fsync + os.replace):
58
+ * 1. Serialize state to deterministic JSON
59
+ * 2. Compute SHA-256 checksum over serialized content
60
+ * 3. Write to {path}.tmp
61
+ * 4. fsync the .tmp fd (flush to disk — survives power loss)
62
+ * 5. rename .tmp → target (POSIX atomic on same filesystem)
63
+ * 6. On failure: delete .tmp, release lock, re-throw
64
+ *
65
+ * Callers: orchestrator phase transitions, cycle_counter_check MCP,
66
+ * SubagentStart hook, write-lease persistence, token accounting.
67
+ */
68
+ export function stateSave(path: string, state: Record<string, unknown>): StateSaveResult {
69
+ if (writeLocked) {
70
+ throw new Error('Exclusive write lock held — concurrent state_save call rejected');
71
+ }
72
+
73
+ writeLocked = true;
74
+ const tmp = `${path}.tmp`;
75
+
76
+ try {
77
+ // Ensure parent directory exists
78
+ const dir = dirname(path);
79
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
80
+
81
+ // 1. Serialize — deterministic pretty-print + trailing newline
82
+ const content = JSON.stringify(state, null, 2) + '\n';
83
+
84
+ // 2. SHA-256 integrity checksum (task 3.2.3)
85
+ const sha256 = createHash('sha256').update(content).digest('hex');
86
+
87
+ // 3. Write to .tmp
88
+ writeFileSync(tmp, content, 'utf-8');
89
+
90
+ // 4. fsync — flush kernel buffers to disk (task 3.2.2)
91
+ // Open with 'r+' (read-write) to guarantee data buffer flush on all platforms.
92
+ // Opening with 'r' (read-only) may only flush metadata on some systems.
93
+ const fd = openSync(tmp, 'r+');
94
+ try {
95
+ fsyncSync(fd);
96
+ } finally {
97
+ closeSync(fd);
98
+ }
99
+
100
+ // 5. Atomic rename — os.replace() equivalent (task 3.2.2)
101
+ renameSync(tmp, path);
102
+
103
+ return { success: true, path, sha256, bytesWritten: Buffer.byteLength(content) };
104
+ } catch (err) {
105
+ // 6. Clean up .tmp on failure — never leave partial state
106
+ try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }
107
+ throw err;
108
+ } finally {
109
+ writeLocked = false;
110
+ }
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // State read + integrity verification
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /**
118
+ * Read state from a JSON file and compute its SHA-256 checksum.
119
+ * Used by SubagentStart hook, cycle_counter_check, write-lease init.
120
+ */
121
+ export function stateRead(path: string): StateReadResult {
122
+ const content = readFileSync(path, 'utf-8');
123
+ const sha256 = createHash('sha256').update(content).digest('hex');
124
+ return { state: JSON.parse(content) as Record<string, unknown>, sha256 };
125
+ }
126
+
127
+ /**
128
+ * Verify integrity of a state file against an expected SHA-256 checksum.
129
+ * Returns true if the file content matches the expected hash.
130
+ *
131
+ * Use case: crash recovery — confirm .build-state.json wasn't corrupted
132
+ * by a partial write (the atomic protocol prevents this, but defense-in-depth).
133
+ */
134
+ export function verifyIntegrity(path: string, expectedSha256: string): boolean {
135
+ const content = readFileSync(path, 'utf-8');
136
+ const actual = createHash('sha256').update(content).digest('hex');
137
+ return actual === expectedSha256;
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Test helpers
142
+ // ---------------------------------------------------------------------------
143
+
144
+ /**
145
+ * Reset internal state (write lock). For testing only.
146
+ */
147
+ export function reset(): void {
148
+ writeLocked = false;
149
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Write-lease MCP handler (A5).
3
+ * Prevents intra-phase file collisions by requiring implementer dispatches
4
+ * to acquire exclusive leases on file paths before Write|Edit operations.
5
+ *
6
+ * Leases are persisted atomically to .build-state.json.active_write_leases[]
7
+ * so the PreToolUse hook (which re-reads from disk) sees them.
8
+ */
9
+
10
+ import { existsSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
11
+
12
+ export interface Lease {
13
+ holder: string; // task_id of the lease holder
14
+ paths: string[]; // file paths under lease
15
+ acquired_at: string; // ISO timestamp
16
+ }
17
+
18
+ export interface LeaseConflict {
19
+ holder: string; // task_id of the existing lease holder
20
+ paths: string[]; // overlapping file paths
21
+ }
22
+
23
+ export interface AcquireResult {
24
+ granted: boolean;
25
+ lease?: Lease;
26
+ conflict?: LeaseConflict;
27
+ }
28
+
29
+ /** In-memory lease store — kept in sync with disk via persistLeases(). */
30
+ const leases: Lease[] = [];
31
+
32
+ /** Path to .build-state.json — set via init() or defaults to docs/plans/.build-state.json */
33
+ let statePath = 'docs/plans/.build-state.json';
34
+
35
+ /**
36
+ * Initialize the lease manager with the state file path.
37
+ * Loads existing leases from disk into memory.
38
+ */
39
+ export function init(buildStatePath: string): void {
40
+ statePath = buildStatePath;
41
+ leases.length = 0;
42
+ if (!existsSync(statePath)) return;
43
+ try {
44
+ const state = JSON.parse(readFileSync(statePath, 'utf-8'));
45
+ const diskLeases = state?.active_write_leases;
46
+ if (Array.isArray(diskLeases)) {
47
+ for (const l of diskLeases) {
48
+ if (l?.holder && Array.isArray(l?.paths)) {
49
+ leases.push({ holder: l.holder, paths: l.paths, acquired_at: l.acquired_at ?? '' });
50
+ }
51
+ }
52
+ }
53
+ } catch { /* fresh state or parse error — start with empty leases */ }
54
+ }
55
+
56
+ /**
57
+ * Persist current leases to .build-state.json atomically.
58
+ * Uses write-to-.tmp + rename (same protocol as state_save MCP).
59
+ *
60
+ * CRITICAL: If persist fails, the in-memory lease exists but the PreToolUse
61
+ * hook (a separate process) reads from disk and won't see it — creating a
62
+ * silent fail-closed where legitimate writes are denied. On persist failure,
63
+ * we roll back the in-memory state to match disk and re-throw so the caller
64
+ * knows the acquire didn't stick.
65
+ */
66
+ function persistLeases(): void {
67
+ if (!existsSync(statePath)) return;
68
+ try {
69
+ const state = JSON.parse(readFileSync(statePath, 'utf-8'));
70
+ state.active_write_leases = leases.map(l => ({ ...l }));
71
+ const tmp = `${statePath}.tmp`;
72
+ writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf-8');
73
+ renameSync(tmp, statePath);
74
+ } catch (err) {
75
+ // Persist failed — roll back in-memory state to match what's on disk,
76
+ // then re-throw. This prevents the MCP thinking a lease exists while
77
+ // the hook (reading disk) sees nothing and denies writes.
78
+ try {
79
+ const diskState = JSON.parse(readFileSync(statePath, 'utf-8'));
80
+ const diskLeases = diskState?.active_write_leases;
81
+ leases.length = 0;
82
+ if (Array.isArray(diskLeases)) {
83
+ for (const l of diskLeases) {
84
+ if (l?.holder && Array.isArray(l?.paths)) {
85
+ leases.push({ holder: l.holder, paths: l.paths, acquired_at: l.acquired_at ?? '' });
86
+ }
87
+ }
88
+ }
89
+ } catch { /* disk unreadable — empty leases is the safest state */ leases.length = 0; }
90
+ throw new Error(`write-lease: persist failed, in-memory rolled back: ${err instanceof Error ? err.message : err}`);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Process-level async mutex for acquireWriteLease.
96
+ *
97
+ * Serializes all callers through a promise chain so that even if two async
98
+ * dispatch paths call acquireWriteLease concurrently, the overlap check and
99
+ * push are never interleaved. This prevents a TOCTOU race where both callers
100
+ * read the leases array before either persists, granting conflicting leases.
101
+ */
102
+ let acquireLock: Promise<void> = Promise.resolve();
103
+
104
+ /**
105
+ * Acquire a write lease for the given file paths.
106
+ * Persists to disk atomically so the PreToolUse hook sees the lease.
107
+ *
108
+ * Serialized via an async mutex (promise chain) to prevent TOCTOU races
109
+ * when called from concurrent async dispatch paths.
110
+ */
111
+ export async function acquireWriteLease(taskId: string, filePaths: string[]): Promise<AcquireResult> {
112
+ const result = acquireLock.then(() => {
113
+ if (!taskId) throw new Error('task_id is required');
114
+ if (!filePaths.length) throw new Error('file_paths must be non-empty');
115
+
116
+ for (const existing of leases) {
117
+ const overlap = filePaths.filter(p => existing.paths.includes(p));
118
+ if (overlap.length > 0) {
119
+ return { granted: false, conflict: { holder: existing.holder, paths: overlap } };
120
+ }
121
+ }
122
+
123
+ const lease: Lease = { holder: taskId, paths: [...filePaths], acquired_at: new Date().toISOString() };
124
+ leases.push(lease);
125
+ persistLeases();
126
+ return { granted: true, lease };
127
+ });
128
+ acquireLock = result.then(() => {}, () => {}); // advance chain regardless of success/failure
129
+ return result;
130
+ }
131
+
132
+ /**
133
+ * Release ALL leases held by a task (handles multiple leases per task).
134
+ * Persists to disk. Called by SubagentStop hook on dispatch return.
135
+ */
136
+ export function releaseLease(taskId: string): boolean {
137
+ let released = false;
138
+ for (let i = leases.length - 1; i >= 0; i--) {
139
+ if (leases[i].holder === taskId) {
140
+ leases.splice(i, 1);
141
+ released = true;
142
+ }
143
+ }
144
+ if (released) persistLeases();
145
+ return released;
146
+ }
147
+
148
+ /**
149
+ * Release specific paths for a task. Persists to disk.
150
+ */
151
+ export function releasePathsForTask(taskId: string, paths: string[]): void {
152
+ const lease = leases.find(l => l.holder === taskId);
153
+ if (!lease) return;
154
+ lease.paths = lease.paths.filter(p => !paths.includes(p));
155
+ if (lease.paths.length === 0) {
156
+ releaseLease(taskId);
157
+ } else {
158
+ persistLeases();
159
+ }
160
+ }
161
+
162
+ /** Get all active leases. */
163
+ export function getActiveLeases(): readonly Lease[] {
164
+ return leases;
165
+ }
166
+
167
+ /**
168
+ * Check if a specific file path is leased and by whom.
169
+ * Used by the writer-owner PreToolUse hook extension.
170
+ */
171
+ export function checkPathLease(filePath: string, callerTaskId: string): { allowed: boolean; conflict?: LeaseConflict } {
172
+ for (const lease of leases) {
173
+ if (lease.paths.includes(filePath)) {
174
+ if (lease.holder === callerTaskId) return { allowed: true };
175
+ return { allowed: false, conflict: { holder: lease.holder, paths: [filePath] } };
176
+ }
177
+ }
178
+ return { allowed: false, conflict: undefined };
179
+ }
180
+
181
+ /** Reset all leases (for testing). */
182
+ export function reset(): void {
183
+ leases.length = 0;
184
+ }
@@ -0,0 +1,57 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ export interface SprintContextInput {
4
+ buildState: Record<string, unknown>;
5
+ refs: Record<string, unknown>;
6
+ architecture: string;
7
+ qualityTargets: Record<string, unknown>;
8
+ iosFeatures?: string[];
9
+ }
10
+
11
+ export interface SprintContextBlock {
12
+ content: string;
13
+ hash: string;
14
+ }
15
+
16
+ /**
17
+ * Render the sprint-scoped shared-context block for Phase 4 dispatches.
18
+ * Injected once per sprint via SubagentStart hook; hash-cached for reuse.
19
+ */
20
+ export function renderSprintContext(input: SprintContextInput): SprintContextBlock {
21
+ const sections = [
22
+ `## Architecture Snapshot\n${input.architecture}`,
23
+ `## Quality Targets\n${JSON.stringify(input.qualityTargets, null, 2)}`,
24
+ `## Refs Index\n${JSON.stringify(input.refs, null, 2)}`,
25
+ ];
26
+ if (input.iosFeatures?.length) {
27
+ sections.push(`## iOS Features\n${input.iosFeatures.join(', ')}`);
28
+ }
29
+ const content = sections.join('\n\n');
30
+ const hash = createHash('sha256').update(content).digest('hex').slice(0, 16);
31
+ return { content, hash };
32
+ }
33
+
34
+ /**
35
+ * Hash only the inputs that affect rendered output (excludes buildState).
36
+ * Cheaper than a full renderSprintContext — no section joins or content hashing.
37
+ * Callers should store this hash and pass it to shouldInvalidate.
38
+ */
39
+ export function inputHash(input: SprintContextInput): string {
40
+ return createHash('sha256')
41
+ .update(JSON.stringify({
42
+ architecture: input.architecture,
43
+ qualityTargets: input.qualityTargets,
44
+ refs: input.refs,
45
+ iosFeatures: input.iosFeatures ?? null,
46
+ }))
47
+ .digest('hex')
48
+ .slice(0, 16);
49
+ }
50
+
51
+ /**
52
+ * Check if the sprint context needs re-rendering (hash invalidation).
53
+ * Compares input hashes directly — avoids a full render just to get a hash.
54
+ */
55
+ export function shouldInvalidate(currentInputHash: string, newInput: SprintContextInput): boolean {
56
+ return inputHash(newInput) !== currentInputHash;
57
+ }