@jaimevalasek/aioson 1.5.1 → 1.7.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 (341) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +729 -226
  3. package/docs/design-previews/aurora-command-ui-website.html +884 -0
  4. package/docs/design-previews/aurora-command-ui.html +682 -0
  5. package/docs/design-previews/bold-editorial-ui-website.html +658 -0
  6. package/docs/design-previews/bold-editorial-ui.html +717 -0
  7. package/docs/design-previews/clean-saas-ui-website.html +1202 -0
  8. package/docs/design-previews/clean-saas-ui.html +549 -0
  9. package/docs/design-previews/cognitive-core-ui-website.html +1009 -0
  10. package/docs/design-previews/cognitive-core-ui.html +463 -0
  11. package/docs/design-previews/glassmorphism-ui-website.html +572 -0
  12. package/docs/design-previews/glassmorphism-ui.html +886 -0
  13. package/docs/design-previews/index.html +699 -0
  14. package/docs/design-previews/interface-design-website.html +1187 -0
  15. package/docs/design-previews/interface-design.html +513 -0
  16. package/docs/design-previews/neo-brutalist-ui-website.html +621 -0
  17. package/docs/design-previews/neo-brutalist-ui.html +797 -0
  18. package/docs/design-previews/premium-command-center-ui-website.html +1217 -0
  19. package/docs/design-previews/premium-command-center-ui.html +552 -0
  20. package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
  21. package/docs/design-previews/warm-craft-ui-website.html +684 -0
  22. package/docs/design-previews/warm-craft-ui.html +739 -0
  23. package/docs/en/cli-reference.md +20 -9
  24. package/docs/integrations/sdlc-genius-boundary.md +76 -0
  25. package/docs/integrations/sdlc-genius-eval-matrix.md +75 -0
  26. package/docs/integrations/sdlc-genius-install-checklist.md +93 -0
  27. package/docs/integrations/sdlc-genius-review-samples.md +86 -0
  28. package/docs/pt/README.md +10 -0
  29. package/docs/pt/agent-sharding.md +132 -0
  30. package/docs/pt/agentes.md +9 -2
  31. package/docs/pt/busca-de-contexto.md +129 -0
  32. package/docs/pt/cache-de-contexto.md +156 -0
  33. package/docs/pt/comandos-cli.md +915 -1
  34. package/docs/pt/design-hybrid-forge.md +356 -0
  35. package/docs/pt/devlog-pipeline.md +270 -0
  36. package/docs/pt/fluxo-artefatos.md +178 -0
  37. package/docs/pt/hooks-session-guard.md +454 -0
  38. package/docs/pt/inicio-rapido.md +54 -3
  39. package/docs/pt/inteligencia-adaptativa.md +324 -0
  40. package/docs/pt/monitor-de-contexto.md +158 -0
  41. package/docs/pt/recuperacao-de-sessao.md +125 -0
  42. package/docs/pt/sandbox.md +125 -0
  43. package/docs/pt/sdd-automation-scripts.md +557 -0
  44. package/docs/pt/site-forge.md +309 -0
  45. package/docs/pt/skills.md +98 -6
  46. package/docs/pt/spec-learnings-pipeline.md +265 -0
  47. package/package.json +1 -1
  48. package/src/a2a/client.js +165 -0
  49. package/src/a2a/server.js +223 -0
  50. package/src/agent-loader.js +280 -0
  51. package/src/cli.js +329 -1
  52. package/src/commands/agent-audit.js +397 -0
  53. package/src/commands/agent-export-skill.js +229 -0
  54. package/src/commands/agent-loader.js +85 -0
  55. package/src/commands/artifact-validate.js +189 -0
  56. package/src/commands/brief-gen.js +405 -0
  57. package/src/commands/brief-validate.js +65 -0
  58. package/src/commands/classify.js +256 -0
  59. package/src/commands/context-cache.js +90 -0
  60. package/src/commands/context-compact.js +49 -0
  61. package/src/commands/context-health.js +175 -0
  62. package/src/commands/context-monitor.js +163 -0
  63. package/src/commands/context-search.js +66 -0
  64. package/src/commands/context-trim.js +177 -0
  65. package/src/commands/design-hybrid-options.js +385 -0
  66. package/src/commands/detect-test-runner.js +55 -0
  67. package/src/commands/devlog-export-brains.js +27 -0
  68. package/src/commands/devlog-process.js +292 -0
  69. package/src/commands/devlog-watch.js +131 -0
  70. package/src/commands/feature-close.js +165 -0
  71. package/src/commands/gate-check.js +228 -0
  72. package/src/commands/health.js +214 -0
  73. package/src/commands/hooks-emit.js +253 -0
  74. package/src/commands/hooks-install.js +347 -0
  75. package/src/commands/init.js +54 -13
  76. package/src/commands/install.js +52 -13
  77. package/src/commands/learning-auto-promote.js +195 -0
  78. package/src/commands/learning-evolve.js +364 -0
  79. package/src/commands/learning-export.js +103 -0
  80. package/src/commands/learning-rollback.js +164 -0
  81. package/src/commands/live.js +59 -1
  82. package/src/commands/pattern-detect.js +33 -0
  83. package/src/commands/preflight-context.js +30 -0
  84. package/src/commands/preflight.js +208 -0
  85. package/src/commands/pulse-update.js +130 -0
  86. package/src/commands/recovery.js +43 -0
  87. package/src/commands/runner-daemon.js +274 -0
  88. package/src/commands/runner-plan.js +70 -0
  89. package/src/commands/runner-queue-from-plan.js +166 -0
  90. package/src/commands/runner-queue.js +189 -0
  91. package/src/commands/runner-run.js +129 -0
  92. package/src/commands/runtime.js +47 -1
  93. package/src/commands/sandbox.js +37 -0
  94. package/src/commands/self-implement-loop.js +256 -0
  95. package/src/commands/session-guard.js +218 -0
  96. package/src/commands/setup-context.js +22 -2
  97. package/src/commands/setup.js +178 -0
  98. package/src/commands/sizing.js +165 -0
  99. package/src/commands/skill.js +144 -32
  100. package/src/commands/spec-checkpoint.js +177 -0
  101. package/src/commands/spec-status.js +79 -0
  102. package/src/commands/spec-sync.js +190 -0
  103. package/src/commands/spec-tasks.js +288 -0
  104. package/src/commands/squad-autorun.js +1220 -0
  105. package/src/commands/squad-bus.js +217 -0
  106. package/src/commands/squad-card.js +149 -0
  107. package/src/commands/squad-daemon.js +134 -0
  108. package/src/commands/squad-dependency-graph.js +164 -0
  109. package/src/commands/squad-review.js +106 -0
  110. package/src/commands/squad-scaffold.js +55 -0
  111. package/src/commands/squad-tool-register.js +157 -0
  112. package/src/commands/state-save.js +122 -0
  113. package/src/commands/tool-registry-cmd.js +232 -0
  114. package/src/commands/update.js +9 -0
  115. package/src/commands/verify-gate.js +572 -0
  116. package/src/commands/workflow-execute.js +241 -0
  117. package/src/constants.js +18 -0
  118. package/src/context-cache.js +159 -0
  119. package/src/context-search.js +326 -0
  120. package/src/design-variation-catalog.js +503 -0
  121. package/src/i18n/messages/en.js +32 -2
  122. package/src/i18n/messages/es.js +30 -2
  123. package/src/i18n/messages/fr.js +30 -2
  124. package/src/i18n/messages/pt-BR.js +32 -2
  125. package/src/install-animation.js +260 -0
  126. package/src/install-profile.js +143 -0
  127. package/src/install-wizard.js +475 -0
  128. package/src/installer.js +44 -10
  129. package/src/lib/health-check.js +158 -0
  130. package/src/lib/hook-protocol.js +76 -0
  131. package/src/mcp/apps/squad-dashboard/app.js +163 -0
  132. package/src/mcp/apps/squad-dashboard/index.html +261 -0
  133. package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -0
  134. package/src/mcp/resources/squad-state.js +130 -0
  135. package/src/parser.js +7 -1
  136. package/src/preflight-engine.js +443 -0
  137. package/src/recovery-context-session.js +154 -0
  138. package/src/runner/cascade.js +97 -0
  139. package/src/runner/cli-launcher.js +109 -0
  140. package/src/runner/plan-importer.js +63 -0
  141. package/src/runner/queue-store.js +159 -0
  142. package/src/runtime-store.js +158 -4
  143. package/src/sandbox.js +177 -0
  144. package/src/squad/agent-teams-adapter.js +264 -0
  145. package/src/squad/brief-validator.js +350 -0
  146. package/src/squad/bus-bridge.js +140 -0
  147. package/src/squad/context-compactor.js +265 -0
  148. package/src/squad/cross-ai-synthesizer.js +250 -0
  149. package/src/squad/hooks-generator.js +196 -0
  150. package/src/squad/inter-squad-events.js +175 -0
  151. package/src/squad/intra-bus.js +345 -0
  152. package/src/squad/learning-extractor.js +213 -0
  153. package/src/squad/pattern-detector.js +365 -0
  154. package/src/squad/preflight-context.js +296 -0
  155. package/src/squad/recovery-context.js +242 -71
  156. package/src/squad/reflection.js +365 -0
  157. package/src/squad/squad-scaffold.js +177 -0
  158. package/src/squad/state-manager.js +310 -0
  159. package/src/squad/task-decomposer.js +652 -0
  160. package/src/squad/verify-gate.js +303 -0
  161. package/src/tool-executor.js +94 -0
  162. package/src/updater.js +10 -3
  163. package/src/worker-runner.js +186 -1
  164. package/template/.aioson/agents/analyst.md +119 -3
  165. package/template/.aioson/agents/architect.md +98 -0
  166. package/template/.aioson/agents/design-hybrid-forge.md +141 -0
  167. package/template/.aioson/agents/dev.md +335 -14
  168. package/template/.aioson/agents/deyvin.md +117 -2
  169. package/template/.aioson/agents/discovery-design-doc.md +44 -0
  170. package/template/.aioson/agents/genome.md +14 -0
  171. package/template/.aioson/agents/neo.md +78 -1
  172. package/template/.aioson/agents/orache.md +50 -4
  173. package/template/.aioson/agents/orchestrator.md +197 -1
  174. package/template/.aioson/agents/pm.md +93 -0
  175. package/template/.aioson/agents/product.md +77 -4
  176. package/template/.aioson/agents/profiler-enricher.md +14 -0
  177. package/template/.aioson/agents/profiler-forge.md +14 -0
  178. package/template/.aioson/agents/profiler-researcher.md +14 -0
  179. package/template/.aioson/agents/qa.md +249 -19
  180. package/template/.aioson/agents/setup.md +144 -12
  181. package/template/.aioson/agents/sheldon.md +237 -11
  182. package/template/.aioson/agents/site-forge.md +1753 -0
  183. package/template/.aioson/agents/squad.md +162 -0
  184. package/template/.aioson/agents/tester.md +209 -0
  185. package/template/.aioson/agents/ux-ui.md +34 -1
  186. package/template/.aioson/brains/README.md +128 -0
  187. package/template/.aioson/brains/_index.json +16 -0
  188. package/template/.aioson/brains/scripts/query.js +103 -0
  189. package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
  190. package/template/.aioson/config.md +158 -13
  191. package/template/.aioson/constitution.md +33 -0
  192. package/template/.aioson/context/forensics/.gitkeep +0 -0
  193. package/template/.aioson/context/project-pulse.md +34 -0
  194. package/template/.aioson/context/seeds/seed-example.md +27 -0
  195. package/template/.aioson/context/user-profile.md +42 -0
  196. package/template/.aioson/docs/LAYERS.md +79 -0
  197. package/template/.aioson/docs/README.md +76 -0
  198. package/template/.aioson/docs/example-external-api-context.md +72 -0
  199. package/template/.aioson/locales/en/agents/architect.md +17 -0
  200. package/template/.aioson/locales/en/agents/dev.md +79 -13
  201. package/template/.aioson/locales/en/agents/orache.md +6 -0
  202. package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
  203. package/template/.aioson/locales/en/agents/product.md +50 -0
  204. package/template/.aioson/locales/en/agents/setup.md +33 -1
  205. package/template/.aioson/locales/en/agents/sheldon.md +115 -0
  206. package/template/.aioson/locales/en/agents/squad.md +14 -0
  207. package/template/.aioson/locales/en/agents/tester.md +6 -0
  208. package/template/.aioson/locales/es/agents/analyst.md +2 -0
  209. package/template/.aioson/locales/es/agents/architect.md +19 -0
  210. package/template/.aioson/locales/es/agents/dev.md +64 -4
  211. package/template/.aioson/locales/es/agents/deyvin.md +2 -0
  212. package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
  213. package/template/.aioson/locales/es/agents/genome.md +2 -0
  214. package/template/.aioson/locales/es/agents/neo.md +2 -0
  215. package/template/.aioson/locales/es/agents/orache.md +2 -0
  216. package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
  217. package/template/.aioson/locales/es/agents/pair.md +2 -0
  218. package/template/.aioson/locales/es/agents/pm.md +2 -0
  219. package/template/.aioson/locales/es/agents/product.md +52 -0
  220. package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
  221. package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
  222. package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
  223. package/template/.aioson/locales/es/agents/qa.md +2 -0
  224. package/template/.aioson/locales/es/agents/setup.md +35 -1
  225. package/template/.aioson/locales/es/agents/sheldon.md +117 -0
  226. package/template/.aioson/locales/es/agents/squad.md +16 -0
  227. package/template/.aioson/locales/es/agents/tester.md +9 -0
  228. package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
  229. package/template/.aioson/locales/fr/agents/analyst.md +2 -0
  230. package/template/.aioson/locales/fr/agents/architect.md +19 -0
  231. package/template/.aioson/locales/fr/agents/dev.md +64 -4
  232. package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
  233. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
  234. package/template/.aioson/locales/fr/agents/genome.md +2 -0
  235. package/template/.aioson/locales/fr/agents/neo.md +2 -0
  236. package/template/.aioson/locales/fr/agents/orache.md +2 -0
  237. package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
  238. package/template/.aioson/locales/fr/agents/pair.md +2 -0
  239. package/template/.aioson/locales/fr/agents/pm.md +2 -0
  240. package/template/.aioson/locales/fr/agents/product.md +52 -0
  241. package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
  242. package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
  243. package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
  244. package/template/.aioson/locales/fr/agents/qa.md +2 -0
  245. package/template/.aioson/locales/fr/agents/setup.md +35 -1
  246. package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
  247. package/template/.aioson/locales/fr/agents/squad.md +16 -0
  248. package/template/.aioson/locales/fr/agents/tester.md +9 -0
  249. package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
  250. package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
  251. package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
  252. package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
  253. package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
  254. package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
  255. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
  256. package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
  257. package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
  258. package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
  259. package/template/.aioson/locales/pt-BR/agents/setup.md +134 -19
  260. package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
  261. package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
  262. package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
  263. package/template/.aioson/rules/README.md +69 -0
  264. package/template/.aioson/rules/data-format-convention.md +136 -0
  265. package/template/.aioson/rules/example-monetary-values.md +30 -0
  266. package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
  267. package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -0
  268. package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -0
  269. package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -0
  270. package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -0
  271. package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -0
  272. package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -0
  273. package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -0
  274. package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -0
  275. package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -0
  276. package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -0
  277. package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -0
  278. package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -0
  279. package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -0
  280. package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -0
  281. package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -0
  282. package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -0
  283. package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -0
  284. package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -0
  285. package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -0
  286. package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -0
  287. package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -0
  288. package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -0
  289. package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -0
  290. package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -0
  291. package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
  292. package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
  293. package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
  294. package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
  295. package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
  296. package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
  297. package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
  298. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +46 -0
  299. package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
  300. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -0
  301. package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
  302. package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -0
  303. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -0
  304. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
  305. package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
  306. package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -0
  307. package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +101 -0
  308. package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
  309. package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
  310. package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
  311. package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -0
  312. package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +147 -0
  313. package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -0
  314. package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -0
  315. package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +306 -0
  316. package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +149 -0
  317. package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +208 -0
  318. package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -0
  319. package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
  320. package/template/.aioson/skills/static/context-budget-guide.md +46 -0
  321. package/template/.aioson/skills/static/harness-sensors.md +74 -0
  322. package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
  323. package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
  324. package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
  325. package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
  326. package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
  327. package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
  328. package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
  329. package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
  330. package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
  331. package/template/.aioson/skills/static/threejs-patterns.md +929 -0
  332. package/template/.aioson/skills/static/web-research-cache.md +112 -0
  333. package/template/.aioson/tasks/implementation-plan.md +21 -1
  334. package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
  335. package/template/.claude/commands/aioson/agent/orache.md +5 -0
  336. package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
  337. package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
  338. package/template/AGENTS.md +75 -1
  339. package/template/CLAUDE.md +31 -0
  340. package/template/OPENCODE.md +4 -0
  341. package/template/researchs/.gitkeep +0 -0
@@ -0,0 +1,292 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { openRuntimeDb, startTask, startRun, updateRun, updateTask, appendRunEvent, attachArtifact } = require('../runtime-store');
6
+
7
+ function nowIso() {
8
+ return new Date().toISOString();
9
+ }
10
+
11
+ function createLearningId() {
12
+ return `learning-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
13
+ }
14
+
15
+ function parseFrontmatter(content) {
16
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
17
+ if (!match) return null;
18
+ const result = {};
19
+ for (const line of match[1].split(/\r?\n/)) {
20
+ const colonIdx = line.indexOf(':');
21
+ if (colonIdx === -1) continue;
22
+ const key = line.slice(0, colonIdx).trim();
23
+ const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
24
+ if (key) result[key] = value === 'null' ? null : value;
25
+ }
26
+ return result;
27
+ }
28
+
29
+ function extractSection(content, sectionName) {
30
+ const re = new RegExp(`^#{1,4}\\s+${sectionName}[\\s\\S]*?(?=^#{1,4}\\s|\\Z)`, 'im');
31
+ const match = content.match(re);
32
+ if (!match) return '';
33
+ return match[0].replace(/^#{1,4}\s+\S[^\n]*\n/, '').trim();
34
+ }
35
+
36
+ function extractListItems(content, sectionName) {
37
+ const section = extractSection(content, sectionName);
38
+ const items = [];
39
+ for (const line of section.split(/\r?\n/)) {
40
+ const trimmed = line.replace(/^[-*]\s*/, '').trim();
41
+ if (trimmed && trimmed.length > 2) items.push(trimmed);
42
+ }
43
+ return items;
44
+ }
45
+
46
+ function extractTaggedLearnings(content) {
47
+ const section = extractSection(content, 'Learnings');
48
+ const learnings = [];
49
+ for (const line of section.split(/\r?\n/)) {
50
+ const trimmed = line.replace(/^[-*]\s*/, '').trim();
51
+ if (!trimmed) continue;
52
+ const typeMatch = trimmed.match(/^\[(process|domain|quality|preference)\]\s+(.+)/i);
53
+ if (typeMatch) {
54
+ learnings.push({ type: typeMatch[1].toLowerCase(), title: typeMatch[2].trim() });
55
+ } else if (trimmed.length > 5) {
56
+ learnings.push({ type: 'process', title: trimmed });
57
+ }
58
+ }
59
+ return learnings;
60
+ }
61
+
62
+ function extractSummary(content) {
63
+ const section = extractSection(content, 'Summary');
64
+ if (section) return section.split(/\r?\n/)[0].trim();
65
+ // Fallback: first non-empty line of body after frontmatter
66
+ const body = content.replace(/^---[\s\S]*?---\r?\n/, '');
67
+ const firstHeading = body.match(/^#\s+(.+)/m);
68
+ return firstHeading ? firstHeading[1].trim() : null;
69
+ }
70
+
71
+ function upsertProjectLearning(db, { title, type, featureSlug, evidence, sourceSession }) {
72
+ const existing = db.prepare(
73
+ 'SELECT learning_id, frequency FROM project_learnings WHERE title = ? AND (feature_slug = ? OR (feature_slug IS NULL AND ? IS NULL))'
74
+ ).get(title, featureSlug || null, featureSlug || null);
75
+
76
+ if (existing) {
77
+ db.prepare(
78
+ 'UPDATE project_learnings SET frequency = ?, last_reinforced = ?, updated_at = ? WHERE learning_id = ?'
79
+ ).run(existing.frequency + 1, nowIso(), nowIso(), existing.learning_id);
80
+ return { action: 'updated', learningId: existing.learning_id };
81
+ }
82
+
83
+ const learningId = createLearningId();
84
+ db.prepare(`
85
+ INSERT INTO project_learnings
86
+ (learning_id, feature_slug, type, title, confidence, frequency, last_reinforced,
87
+ applies_to, source_session, evidence, status, created_at, updated_at)
88
+ VALUES (?, ?, ?, ?, 'medium', 1, ?, 'project', ?, ?, 'active', ?, ?)
89
+ `).run(learningId, featureSlug || null, type, title, nowIso(), sourceSession || null, evidence || null, nowIso(), nowIso());
90
+ return { action: 'inserted', learningId };
91
+ }
92
+
93
+ async function markAsProcessed(filePath, processedAt) {
94
+ const content = await fs.readFile(filePath, 'utf8');
95
+ const ts = processedAt || nowIso();
96
+
97
+ // If file already has processed_at, skip
98
+ if (/^processed_at:/m.test(content)) return;
99
+
100
+ // Inject processed_at into frontmatter
101
+ const updated = content.replace(/^(---\r?\n[\s\S]*?)(---)/m, `$1processed_at: ${ts}\n$2`);
102
+ await fs.writeFile(filePath, updated, 'utf8');
103
+ }
104
+
105
+ async function processDevlogFile(db, filePath) {
106
+ const content = await fs.readFile(filePath, 'utf8');
107
+ const fm = parseFrontmatter(content);
108
+
109
+ if (!fm || !fm.agent) {
110
+ return { status: 'malformed', file: path.basename(filePath), reason: 'missing frontmatter or agent field' };
111
+ }
112
+
113
+ // Skip already-processed devlogs
114
+ if (fm.processed_at) {
115
+ return { status: 'skipped', file: path.basename(filePath), reason: 'already processed' };
116
+ }
117
+
118
+ const body = content.replace(/^---[\s\S]*?---\r?\n?/, '');
119
+ const summary = extractSummary(body) || `@${fm.agent} devlog`;
120
+ const featureSlug = fm.feature && fm.feature !== 'project' ? fm.feature : null;
121
+ const sessionKey = fm.session_key || null;
122
+ const startedAt = fm.started_at || fm.session_start || nowIso();
123
+ const finishedAt = fm.finished_at || fm.session_end || nowIso();
124
+ const status = fm.status === 'partial' ? 'running' : 'completed';
125
+ const verdict = fm.verdict ? String(fm.verdict).trim().toUpperCase() : null;
126
+ const planStepId = fm.plan_step || null;
127
+
128
+ // Create task + run
129
+ const taskKey = startTask(db, {
130
+ title: `devlog: ${summary}`,
131
+ squadSlug: null,
132
+ sessionKey: sessionKey || undefined,
133
+ status,
134
+ createdBy: fm.agent
135
+ });
136
+
137
+ const runKey = startRun(db, {
138
+ taskKey,
139
+ agentName: fm.agent,
140
+ agentKind: 'devlog',
141
+ squadSlug: null,
142
+ title: `@${fm.agent} devlog`,
143
+ message: summary
144
+ });
145
+
146
+ // Register artifacts
147
+ const artifactPaths = extractListItems(body, 'Artifacts');
148
+ for (const filePath_ of artifactPaths) {
149
+ // Only register file-like entries (containing a slash or dot)
150
+ if (/[/.]/.test(filePath_)) {
151
+ attachArtifact(db, { runKey, agentName: fm.agent, kind: 'output', filePath: filePath_ });
152
+ }
153
+ }
154
+
155
+ // Register decisions as execution events
156
+ const decisions = extractListItems(body, 'Decisions');
157
+ for (const decision of decisions) {
158
+ appendRunEvent(db, {
159
+ runKey,
160
+ eventType: 'decision',
161
+ phase: 'devlog',
162
+ status: 'completed',
163
+ message: decision,
164
+ createdAt: finishedAt
165
+ });
166
+ }
167
+
168
+ // Upsert learnings
169
+ const learnings = extractTaggedLearnings(body);
170
+ for (const { type, title } of learnings) {
171
+ upsertProjectLearning(db, { title, type, featureSlug, sourceSession: sessionKey || path.basename(filePath) });
172
+ }
173
+
174
+ // Log verdict if present
175
+ if (verdict && verdict !== 'NULL') {
176
+ appendRunEvent(db, {
177
+ runKey,
178
+ eventType: 'qa_verdict',
179
+ phase: 'devlog',
180
+ status: 'completed',
181
+ message: `QA VERDICT: ${verdict}`,
182
+ verdict,
183
+ planStepId,
184
+ createdAt: finishedAt
185
+ });
186
+ }
187
+
188
+ // Close run
189
+ updateRun(db, runKey, {
190
+ status,
191
+ summary,
192
+ finishedAt
193
+ });
194
+
195
+ if (status === 'completed') {
196
+ updateTask(db, taskKey, { status: 'completed', finishedAt });
197
+ }
198
+
199
+ // Mark devlog as processed
200
+ await markAsProcessed(filePath, nowIso());
201
+
202
+ return {
203
+ status: 'ok',
204
+ file: path.basename(filePath),
205
+ runKey,
206
+ taskKey,
207
+ featureSlug,
208
+ artifactsCount: artifactPaths.filter((p) => /[/.]/.test(p)).length,
209
+ decisionsCount: decisions.length,
210
+ learningsCount: learnings.length,
211
+ verdict: verdict && verdict !== 'NULL' ? verdict : null
212
+ };
213
+ }
214
+
215
+ async function runDevlogProcess({ args, options = {}, logger }) {
216
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
217
+ const logsDir = path.join(targetDir, 'aioson-logs');
218
+
219
+ let entries;
220
+ try {
221
+ entries = await fs.readdir(logsDir);
222
+ } catch {
223
+ if (!options.json) logger.log('No aioson-logs/ directory found — nothing to process.');
224
+ return { ok: true, processed: 0, skipped: 0, malformed: 0 };
225
+ }
226
+
227
+ const devlogFiles = entries
228
+ .filter((f) => f.startsWith('devlog-') && f.endsWith('.md'))
229
+ .sort();
230
+
231
+ if (devlogFiles.length === 0) {
232
+ if (!options.json) logger.log('No devlog files found.');
233
+ return { ok: true, processed: 0, skipped: 0, malformed: 0 };
234
+ }
235
+
236
+ const { db, dbPath } = await openRuntimeDb(targetDir);
237
+ const results = [];
238
+
239
+ try {
240
+ for (const file of devlogFiles) {
241
+ const result = await processDevlogFile(db, path.join(logsDir, file));
242
+ results.push(result);
243
+ }
244
+ } finally {
245
+ db.close();
246
+ }
247
+
248
+ const processed = results.filter((r) => r.status === 'ok');
249
+ const skipped = results.filter((r) => r.status === 'skipped');
250
+ const malformed = results.filter((r) => r.status === 'malformed');
251
+
252
+ const totalArtifacts = processed.reduce((s, r) => s + (r.artifactsCount || 0), 0);
253
+ const totalLearnings = processed.reduce((s, r) => s + (r.learningsCount || 0), 0);
254
+
255
+ if (options.json) {
256
+ return { ok: true, results, processed: processed.length, skipped: skipped.length, malformed: malformed.length, totalArtifacts, totalLearnings, dbPath };
257
+ }
258
+
259
+ logger.log(`Devlog Processing — ${path.basename(targetDir)}`);
260
+ logger.log('─'.repeat(50));
261
+
262
+ if (results.length === 0) {
263
+ logger.log('No devlogs to process.');
264
+ } else {
265
+ logger.log(`Found ${devlogFiles.length} devlog(s):`);
266
+ logger.log('');
267
+ for (const r of results) {
268
+ if (r.status === 'ok') {
269
+ logger.log(`${r.file}`);
270
+ logger.log(` Agent: @${r.featureSlug ? `${r.featureSlug}` : 'project'} | run: ${r.runKey}`);
271
+ if (r.artifactsCount > 0) logger.log(` Artifacts: ${r.artifactsCount} registered ✓`);
272
+ if (r.decisionsCount > 0) logger.log(` Decisions: ${r.decisionsCount} logged ✓`);
273
+ if (r.learningsCount > 0) logger.log(` Learnings: ${r.learningsCount} upserted ✓`);
274
+ if (r.verdict) logger.log(` Verdict: ${r.verdict} ✓`);
275
+ } else if (r.status === 'skipped') {
276
+ logger.log(`${r.file} — skipped (${r.reason})`);
277
+ } else {
278
+ logger.log(`${r.file} — ⚠ ${r.reason}. Fix frontmatter and re-run.`);
279
+ }
280
+ }
281
+ }
282
+
283
+ logger.log('─'.repeat(50));
284
+ logger.log(`Processed: ${processed.length}/${devlogFiles.length} devlogs`);
285
+ if (totalLearnings > 0) logger.log(`New learnings: ${totalLearnings} (queued for brains export)`);
286
+ if (totalArtifacts > 0) logger.log(`Artifacts registered: ${totalArtifacts}`);
287
+ if (malformed.length > 0) logger.log(`Malformed (skipped): ${malformed.length}`);
288
+
289
+ return { ok: true, results, processed: processed.length, skipped: skipped.length, malformed: malformed.length, totalArtifacts, totalLearnings, dbPath };
290
+ }
291
+
292
+ module.exports = { runDevlogProcess, processDevlogFile };
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const fsSync = require('node:fs');
5
+ const path = require('node:path');
6
+ const { openRuntimeDb } = require('../runtime-store');
7
+ const { processDevlogFile } = require('./devlog-process');
8
+
9
+ const POLL_INTERVAL_MS = 5000;
10
+ const WSL_VERSION_PATH = '/proc/version';
11
+
12
+ async function isWsl2() {
13
+ try {
14
+ const version = await fs.readFile(WSL_VERSION_PATH, 'utf8');
15
+ return version.toLowerCase().includes('microsoft');
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ async function processNewDevlog(targetDir, filePath, logger) {
22
+ const { db, dbPath } = await openRuntimeDb(targetDir).catch(() => ({ db: null }));
23
+ if (!db) {
24
+ logger.log(`[DEVLOG WATCHER] No database available — skipping ${path.basename(filePath)}`);
25
+ return;
26
+ }
27
+ try {
28
+ const result = await processDevlogFile(db, filePath);
29
+ if (result.status === 'ok') {
30
+ const parts = [];
31
+ if (result.learningsCount > 0) parts.push(`${result.learningsCount} learnings`);
32
+ if (result.artifactsCount > 0) parts.push(`${result.artifactsCount} artifacts`);
33
+ if (result.verdict) parts.push(`VERDICT: ${result.verdict}`);
34
+ const detail = parts.length > 0 ? ` → ${parts.join(', ')} → SQLite ✓` : ' → SQLite ✓';
35
+ logger.log(`[${new Date().toISOString().slice(11, 19)}] Processed: ${result.file}${detail}`);
36
+ } else if (result.status === 'skipped') {
37
+ // silently skip already-processed files
38
+ } else {
39
+ logger.log(`[${new Date().toISOString().slice(11, 19)}] ⚠ ${result.file}: ${result.reason}`);
40
+ }
41
+ } finally {
42
+ db.close();
43
+ }
44
+ }
45
+
46
+ async function watchWithFsWatch(logsDir, targetDir, logger) {
47
+ logger.log(`[DEVLOG WATCHER] Using fs.watch on ${logsDir}`);
48
+
49
+ return new Promise((resolve) => {
50
+ const watcher = fsSync.watch(logsDir, { persistent: true }, async (eventType, filename) => {
51
+ if (!filename || !filename.startsWith('devlog-') || !filename.endsWith('.md')) return;
52
+ if (eventType !== 'rename' && eventType !== 'change') return;
53
+
54
+ const filePath = path.join(logsDir, filename);
55
+ // Small delay to ensure file is fully written
56
+ setTimeout(async () => {
57
+ try {
58
+ await fs.access(filePath);
59
+ logger.log(`[${new Date().toISOString().slice(11, 19)}] New: ${filename} → processing...`);
60
+ await processNewDevlog(targetDir, filePath, logger);
61
+ } catch { /* file may have been removed */ }
62
+ }, 200);
63
+ });
64
+
65
+ process.on('SIGINT', () => { watcher.close(); resolve(); });
66
+ process.on('SIGTERM', () => { watcher.close(); resolve(); });
67
+ });
68
+ }
69
+
70
+ async function watchWithPolling(logsDir, targetDir, logger) {
71
+ logger.log(`[DEVLOG WATCHER] WSL2 detected — using polling (${POLL_INTERVAL_MS / 1000}s interval)`);
72
+
73
+ const seen = new Set();
74
+
75
+ // Seed with already-existing files so we don't reprocess them
76
+ try {
77
+ const entries = await fs.readdir(logsDir);
78
+ for (const f of entries) {
79
+ if (f.startsWith('devlog-') && f.endsWith('.md')) seen.add(f);
80
+ }
81
+ } catch { /* logsDir not yet created */ }
82
+
83
+ return new Promise((resolve) => {
84
+ const timer = setInterval(async () => {
85
+ try {
86
+ const entries = await fs.readdir(logsDir);
87
+ const devlogs = entries.filter((f) => f.startsWith('devlog-') && f.endsWith('.md'));
88
+ for (const filename of devlogs) {
89
+ if (!seen.has(filename)) {
90
+ seen.add(filename);
91
+ const filePath = path.join(logsDir, filename);
92
+ logger.log(`[${new Date().toISOString().slice(11, 19)}] New: ${filename} → processing...`);
93
+ await processNewDevlog(targetDir, filePath, logger);
94
+ }
95
+ }
96
+ } catch { /* directory may not exist yet */ }
97
+ }, POLL_INTERVAL_MS);
98
+
99
+ process.on('SIGINT', () => { clearInterval(timer); resolve(); });
100
+ process.on('SIGTERM', () => { clearInterval(timer); resolve(); });
101
+ });
102
+ }
103
+
104
+ async function runDevlogWatch({ args, options = {}, logger }) {
105
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
106
+ const logsDir = path.join(targetDir, 'aioson-logs');
107
+ const usePolling = options.poll || await isWsl2();
108
+
109
+ // Ensure the directory exists
110
+ await fs.mkdir(logsDir, { recursive: true });
111
+
112
+ logger.log(`[DEVLOG WATCHER] Watching ${logsDir} for new devlogs...`);
113
+ logger.log('[DEVLOG WATCHER] Press Ctrl+C to stop.');
114
+
115
+ if (usePolling) {
116
+ await watchWithPolling(logsDir, targetDir, logger);
117
+ } else {
118
+ try {
119
+ await watchWithFsWatch(logsDir, targetDir, logger);
120
+ } catch (err) {
121
+ // Fall back to polling if fs.watch fails (can happen in some environments)
122
+ logger.log(`[DEVLOG WATCHER] fs.watch failed (${err.message}) — falling back to polling`);
123
+ await watchWithPolling(logsDir, targetDir, logger);
124
+ }
125
+ }
126
+
127
+ logger.log('[DEVLOG WATCHER] Stopped.');
128
+ return { ok: true };
129
+ }
130
+
131
+ module.exports = { runDevlogWatch };
@@ -0,0 +1,165 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson feature:close — close a feature after QA sign-off.
5
+ *
6
+ * Updates spec-{slug}.md (adds QA sign-off block), features.md (sets status to done),
7
+ * and project-pulse.md (removes from active work).
8
+ *
9
+ * Usage:
10
+ * aioson feature:close . --feature=checkout --verdict=PASS
11
+ * aioson feature:close . --feature=checkout --verdict=PASS --residual="Email delivery not tested E2E"
12
+ * aioson feature:close . --feature=checkout --verdict=FAIL --notes="Auth edge case missing"
13
+ */
14
+
15
+ const fs = require('node:fs/promises');
16
+ const path = require('node:path');
17
+ const { contextDir, readFileSafe, parseFrontmatter } = require('../preflight-engine');
18
+
19
+ function nowDate() {
20
+ return new Date().toISOString().slice(0, 10);
21
+ }
22
+
23
+ async function updateSpecFile(specPath, verdict, residual, date) {
24
+ const content = await readFileSafe(specPath);
25
+ if (!content) return false;
26
+
27
+ const signOff = [
28
+ '',
29
+ '## QA Sign-off',
30
+ '',
31
+ `- **Date:** ${date}`,
32
+ `- **Verdict:** ${verdict}`,
33
+ residual ? `- **Residual:** ${residual}` : null,
34
+ `- **Gate D (execution):** ${verdict === 'PASS' ? 'approved' : 'rejected'}`,
35
+ ''
36
+ ].filter((l) => l !== null).join('\n');
37
+
38
+ // Update gate_execution in frontmatter first (on original content)
39
+ const newStatus = verdict === 'PASS' ? 'approved' : 'rejected';
40
+ const fm = parseFrontmatter(content);
41
+ let baseContent = content;
42
+ if (Object.keys(fm).length > 0) {
43
+ baseContent = content.replace(
44
+ /^---\r?\n[\s\S]*?\r?\n---/,
45
+ (block) => {
46
+ if (block.includes('gate_execution')) {
47
+ return block.replace(/gate_execution:\s*.+/, `gate_execution: ${newStatus}`);
48
+ }
49
+ return block.replace(/^---\r?\n/, `---\ngate_execution: ${newStatus}\n`);
50
+ }
51
+ );
52
+ }
53
+
54
+ // Now apply QA sign-off on top of the frontmatter-updated content
55
+ if (baseContent.includes('## QA Sign-off')) {
56
+ const updated = baseContent.replace(
57
+ /## QA Sign-off[\s\S]*?(?=\n##|\s*$)/,
58
+ signOff.trimStart()
59
+ );
60
+ await fs.writeFile(specPath, updated, 'utf8');
61
+ } else {
62
+ await fs.writeFile(specPath, baseContent + signOff, 'utf8');
63
+ }
64
+
65
+ return true;
66
+ }
67
+
68
+ async function updateFeaturesFile(featuresPath, slug, verdict, date) {
69
+ const content = await readFileSafe(featuresPath);
70
+ if (!content) return false;
71
+
72
+ const status = verdict === 'PASS' ? 'done' : 'qa_failed';
73
+
74
+ // Try to find and update the feature row
75
+ const updated = content.replace(
76
+ new RegExp(`(\\|[^|]*${slug}[^|]*\\|[^|]*\\|)[^|]*(\\|)`, 'g'),
77
+ (match, before, after) => `${before} ${status} (${date}) ${after}`
78
+ );
79
+
80
+ if (updated !== content) {
81
+ await fs.writeFile(featuresPath, updated, 'utf8');
82
+ return true;
83
+ }
84
+
85
+ // Append if not found
86
+ const line = `| ${slug} | ${verdict === 'PASS' ? 'done' : 'qa_failed'} | ${date} | QA ${verdict} |`;
87
+ await fs.appendFile(featuresPath, `\n${line}\n`, 'utf8');
88
+ return true;
89
+ }
90
+
91
+ async function runFeatureClose({ args, options = {}, logger }) {
92
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
93
+ const slug = options.feature ? String(options.feature) : null;
94
+ const verdict = options.verdict ? String(options.verdict).toUpperCase() : null;
95
+ const residual = options.residual ? String(options.residual) : null;
96
+ const notes = options.notes ? String(options.notes) : null;
97
+
98
+ if (!slug) {
99
+ if (options.json) return { ok: false, reason: 'missing_feature' };
100
+ logger.log('--feature=<slug> is required.');
101
+ return { ok: false };
102
+ }
103
+
104
+ if (!verdict || !['PASS', 'FAIL'].includes(verdict)) {
105
+ if (options.json) return { ok: false, reason: 'invalid_verdict' };
106
+ logger.log('--verdict=PASS or --verdict=FAIL is required.');
107
+ return { ok: false };
108
+ }
109
+
110
+ const today = nowDate();
111
+ const dir = contextDir(targetDir);
112
+ const updates = [];
113
+
114
+ // 1. Update spec file
115
+ const specPath = path.join(dir, `spec-${slug}.md`);
116
+ const specUpdated = await updateSpecFile(specPath, verdict, residual || notes, today);
117
+ if (specUpdated) {
118
+ updates.push(`spec-${slug}.md: added QA sign-off (${today}, ${verdict})`);
119
+ } else {
120
+ updates.push(`spec-${slug}.md: not found (skipped)`);
121
+ }
122
+
123
+ // 2. Update features.md
124
+ const featuresPath = path.join(dir, 'features.md');
125
+ const featuresContent = await readFileSafe(featuresPath);
126
+ if (featuresContent) {
127
+ await updateFeaturesFile(featuresPath, slug, verdict, today);
128
+ updates.push(`features.md: ${slug} → ${verdict === 'PASS' ? 'done' : 'qa_failed'} (${today})`);
129
+ } else {
130
+ updates.push('features.md: not found (skipped)');
131
+ }
132
+
133
+ // 3. Update project-pulse.md
134
+ const pulsePath = path.join(dir, 'project-pulse.md');
135
+ const pulseContent = await readFileSafe(pulsePath);
136
+ if (pulseContent) {
137
+ const fm = parseFrontmatter(pulseContent);
138
+ const status = verdict === 'PASS' ? 'closed' : 'qa_failed';
139
+ const updatedPulse = pulseContent
140
+ .replace(/active_feature:\s*.+/, `active_feature: (none)`)
141
+ .replace(/active_work:\s*".+"/, `active_work: ""`)
142
+ .replace(/last_agent:\s*.+/, `last_agent: qa`)
143
+ .replace(/last_gate:\s*.+/, `last_gate: Gate D: ${verdict === 'PASS' ? 'approved' : 'rejected'}`);
144
+ await fs.writeFile(pulsePath, updatedPulse, 'utf8');
145
+ updates.push('project-pulse.md: updated active work');
146
+ }
147
+
148
+ const result = {
149
+ ok: true,
150
+ feature: slug,
151
+ verdict,
152
+ date: today,
153
+ residual: residual || notes || null,
154
+ updates
155
+ };
156
+
157
+ if (options.json) return result;
158
+
159
+ logger.log(`Feature closure — ${slug}:`);
160
+ for (const u of updates) logger.log(` ${u}`);
161
+
162
+ return result;
163
+ }
164
+
165
+ module.exports = { runFeatureClose };