@jaimevalasek/aioson 1.7.0 → 1.8.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 (383) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +153 -10
  3. package/docs/en/cli-reference.md +56 -1
  4. package/docs/en/i18n.md +18 -18
  5. package/docs/en/schemas/index.json +10 -0
  6. package/docs/en/schemas/parallel-assign.schema.json +9 -0
  7. package/docs/en/schemas/parallel-doctor.schema.json +36 -0
  8. package/docs/en/schemas/parallel-guard.schema.json +63 -0
  9. package/docs/en/schemas/parallel-merge.schema.json +84 -0
  10. package/docs/en/schemas/parallel-status.schema.json +91 -1
  11. package/docs/integrations/apps-publish-marketplace.md +94 -0
  12. package/docs/pt/README.md +9 -0
  13. package/docs/pt/agentes.md +324 -3
  14. package/docs/pt/clientes-ai.md +7 -3
  15. package/docs/pt/comandos-cli.md +160 -13
  16. package/docs/pt/compress-agents.md +304 -0
  17. package/docs/pt/design-docs-governance.md +59 -0
  18. package/docs/pt/feature-archive.md +191 -0
  19. package/docs/pt/genome-3.0-spec.md +115 -4
  20. package/docs/pt/genome-distribution.md +232 -0
  21. package/docs/pt/inicio-rapido.md +1 -0
  22. package/docs/pt/motor-hardening.md +492 -0
  23. package/docs/pt/runner-system.md +113 -0
  24. package/package.json +2 -1
  25. package/src/agent-manifests.js +66 -0
  26. package/src/agents.js +27 -7
  27. package/src/autonomy-policy.js +139 -0
  28. package/src/brain-query.js +161 -0
  29. package/src/cli.js +1377 -1099
  30. package/src/commands/agents.js +102 -7
  31. package/src/commands/artifact-validate.js +33 -4
  32. package/src/commands/auth.js +272 -0
  33. package/src/commands/brain-query.js +44 -0
  34. package/src/commands/briefing.js +344 -0
  35. package/src/commands/commit-prepare.js +547 -0
  36. package/src/commands/compress-agents.js +416 -0
  37. package/src/commands/context-health.js +4 -2
  38. package/src/commands/context-trim.js +17 -11
  39. package/src/commands/design-hybrid-options.js +3 -3
  40. package/src/commands/devlog-process.js +6 -4
  41. package/src/commands/dossier.js +423 -0
  42. package/src/commands/feature-archive.js +513 -0
  43. package/src/commands/feature-close.js +123 -18
  44. package/src/commands/gate-approve.js +198 -0
  45. package/src/commands/gate-check.js +24 -5
  46. package/src/commands/genome-doctor.js +166 -9
  47. package/src/commands/git-guard.js +170 -0
  48. package/src/commands/harness.js +121 -0
  49. package/src/commands/implementation-plan.js +47 -20
  50. package/src/commands/init.js +6 -2
  51. package/src/commands/install.js +6 -2
  52. package/src/commands/live.js +497 -56
  53. package/src/commands/locale-apply.js +9 -6
  54. package/src/commands/locale-diff.js +11 -112
  55. package/src/commands/mcp-doctor.js +2 -1
  56. package/src/commands/mcp-init.js +4 -10
  57. package/src/commands/memory.js +234 -0
  58. package/src/commands/parallel-assign.js +107 -27
  59. package/src/commands/parallel-doctor.js +416 -3
  60. package/src/commands/parallel-guard.js +241 -0
  61. package/src/commands/parallel-init.js +66 -4
  62. package/src/commands/parallel-merge.js +299 -0
  63. package/src/commands/parallel-status.js +147 -3
  64. package/src/commands/preflight.js +63 -4
  65. package/src/commands/qa-init.js +10 -5
  66. package/src/commands/revision.js +235 -0
  67. package/src/commands/scaffold-complete.js +188 -0
  68. package/src/commands/security-audit.js +275 -0
  69. package/src/commands/security-scan.js +376 -0
  70. package/src/commands/self-implement-loop.js +46 -2
  71. package/src/commands/setup-context.js +11 -10
  72. package/src/commands/squad-agent-create.js +51 -9
  73. package/src/commands/squad-investigate.js +53 -0
  74. package/src/commands/squad-plan.js +33 -1
  75. package/src/commands/squad-scaffold.js +4 -3
  76. package/src/commands/squad-score.js +71 -14
  77. package/src/commands/squad-status.js +22 -1
  78. package/src/commands/squad-validate.js +93 -2
  79. package/src/commands/store-genome.js +304 -0
  80. package/src/commands/store-skill.js +247 -0
  81. package/src/commands/store-squad.js +431 -0
  82. package/src/commands/store-system.js +392 -0
  83. package/src/commands/tool-capabilities.js +63 -0
  84. package/src/commands/update.js +3 -3
  85. package/src/commands/verify-gate.js +40 -0
  86. package/src/commands/workflow-execute.js +644 -155
  87. package/src/commands/workflow-harden.js +231 -0
  88. package/src/commands/workflow-heal.js +136 -0
  89. package/src/commands/workflow-next.js +460 -22
  90. package/src/commands/workflow-status.js +328 -138
  91. package/src/commands/workspace.js +144 -0
  92. package/src/constants.js +55 -75
  93. package/src/context-memory.js +133 -4
  94. package/src/context-writer.js +2 -1
  95. package/src/context.js +32 -2
  96. package/src/doctor.js +46 -6
  97. package/src/dossier/codemap-store.js +267 -0
  98. package/src/dossier/dossier-bootstrap.js +222 -0
  99. package/src/dossier/dossier-compact.js +159 -0
  100. package/src/dossier/lock.js +128 -0
  101. package/src/dossier/revision-store.js +313 -0
  102. package/src/dossier/schema.js +155 -0
  103. package/src/dossier/store.js +400 -0
  104. package/src/execution-gateway.js +3 -0
  105. package/src/friction-scanner.js +202 -0
  106. package/src/genome-schema.js +24 -1
  107. package/src/genomes.js +33 -0
  108. package/src/handoff-contract.js +363 -0
  109. package/src/handoff-validator.js +45 -0
  110. package/src/harness/circuit-breaker.js +135 -0
  111. package/src/i18n/messages/en.js +317 -22
  112. package/src/i18n/messages/es.js +259 -18
  113. package/src/i18n/messages/fr.js +260 -18
  114. package/src/i18n/messages/pt-BR.js +313 -22
  115. package/src/install-profile.js +0 -16
  116. package/src/installer.js +70 -6
  117. package/src/lib/git-commit-guard.js +691 -0
  118. package/src/lib/security/artifact-reader.js +167 -0
  119. package/src/lib/security/exit-codes.js +51 -0
  120. package/src/lib/security/findings-writer.js +176 -0
  121. package/src/lib/security/runtime-events.js +77 -0
  122. package/src/lib/security/secrets-regex.js +115 -0
  123. package/src/lib/store/security-scan.js +173 -0
  124. package/src/lib/terminal-checkbox.js +130 -0
  125. package/src/lib/tmux-launcher.js +163 -0
  126. package/src/lib/tool-capabilities.js +102 -0
  127. package/src/locales.js +12 -8
  128. package/src/parallel-workspace.js +756 -0
  129. package/src/parser.js +8 -1
  130. package/src/path-guard.js +47 -0
  131. package/src/preflight-engine.js +237 -26
  132. package/src/self-healing.js +142 -0
  133. package/src/session-handoff.js +111 -1
  134. package/src/squad/squad-scaffold.js +183 -19
  135. package/src/test-briefing.js +226 -0
  136. package/src/updater.js +1 -1
  137. package/src/utils.js +3 -0
  138. package/src/workflow-gates.js +185 -0
  139. package/template/.aioson/agents/analyst.md +76 -130
  140. package/template/.aioson/agents/architect.md +53 -86
  141. package/template/.aioson/agents/committer.md +161 -0
  142. package/template/.aioson/agents/copywriter.md +463 -0
  143. package/template/.aioson/agents/cypher.md +252 -0
  144. package/template/.aioson/agents/dev.md +112 -600
  145. package/template/.aioson/agents/deyvin.md +33 -235
  146. package/template/.aioson/agents/discover.md +235 -0
  147. package/template/.aioson/agents/discovery-design-doc.md +17 -252
  148. package/template/.aioson/agents/genome.md +76 -26
  149. package/template/.aioson/agents/manifests/analyst.manifest.json +26 -0
  150. package/template/.aioson/agents/manifests/architect.manifest.json +23 -0
  151. package/template/.aioson/agents/manifests/committer.manifest.json +23 -0
  152. package/template/.aioson/agents/manifests/dev.manifest.json +37 -0
  153. package/template/.aioson/agents/manifests/orchestrator.manifest.json +30 -0
  154. package/template/.aioson/agents/manifests/pentester.manifest.json +39 -0
  155. package/template/.aioson/agents/manifests/pm.manifest.json +26 -0
  156. package/template/.aioson/agents/manifests/product.manifest.json +23 -0
  157. package/template/.aioson/agents/manifests/qa.manifest.json +25 -0
  158. package/template/.aioson/agents/manifests/setup.manifest.json +20 -0
  159. package/template/.aioson/agents/manifests/ux-ui.manifest.json +24 -0
  160. package/template/.aioson/agents/neo.md +10 -8
  161. package/template/.aioson/agents/orache.md +2 -6
  162. package/template/.aioson/agents/orchestrator.md +81 -182
  163. package/template/.aioson/agents/pentester.md +235 -0
  164. package/template/.aioson/agents/pm.md +40 -104
  165. package/template/.aioson/agents/product.md +99 -344
  166. package/template/.aioson/agents/profiler-enricher.md +57 -6
  167. package/template/.aioson/agents/profiler-forge.md +17 -7
  168. package/template/.aioson/agents/profiler-researcher.md +29 -6
  169. package/template/.aioson/agents/qa.md +165 -410
  170. package/template/.aioson/agents/setup.md +52 -262
  171. package/template/.aioson/agents/sheldon.md +122 -754
  172. package/template/.aioson/agents/site-forge.md +111 -1583
  173. package/template/.aioson/agents/squad.md +139 -1820
  174. package/template/.aioson/agents/tester.md +10 -0
  175. package/template/.aioson/agents/ux-ui.md +103 -645
  176. package/template/.aioson/agents/validator.md +69 -0
  177. package/template/.aioson/brains/scripts/query.js +5 -1
  178. package/template/.aioson/config/autonomy-protocol.json +43 -0
  179. package/template/.aioson/config.md +43 -15
  180. package/template/.aioson/constitution.md +36 -33
  181. package/template/.aioson/context/design-doc.md +136 -0
  182. package/template/.aioson/context/project-map.md +57 -0
  183. package/template/.aioson/design-docs/code-reuse.md +48 -0
  184. package/template/.aioson/design-docs/componentization.md +47 -0
  185. package/template/.aioson/design-docs/file-size.md +52 -0
  186. package/template/.aioson/design-docs/folder-structure.md +51 -0
  187. package/template/.aioson/design-docs/naming.md +54 -0
  188. package/template/.aioson/docs/LAYERS.md +12 -2
  189. package/template/.aioson/docs/dev/execution-discipline.md +106 -0
  190. package/template/.aioson/docs/dev/stack-conventions.md +83 -0
  191. package/template/.aioson/docs/deyvin/continuity-recovery.md +57 -0
  192. package/template/.aioson/docs/deyvin/debugging-escalation.md +30 -0
  193. package/template/.aioson/docs/deyvin/pair-execution.md +44 -0
  194. package/template/.aioson/docs/deyvin/runtime-handoffs.md +36 -0
  195. package/template/.aioson/docs/product/conversation-playbook.md +116 -0
  196. package/template/.aioson/docs/product/prd-contract.md +107 -0
  197. package/template/.aioson/docs/product/quality-lens.md +57 -0
  198. package/template/.aioson/docs/product/research-loop.md +65 -0
  199. package/template/.aioson/docs/sheldon/enrichment-paths.md +134 -0
  200. package/template/.aioson/docs/sheldon/quality-lens.md +57 -0
  201. package/template/.aioson/docs/sheldon/research-loop.md +56 -0
  202. package/template/.aioson/docs/sheldon/web-intelligence.md +75 -0
  203. package/template/.aioson/docs/site-forge-build.md +195 -0
  204. package/template/.aioson/docs/site-forge-extraction.md +135 -0
  205. package/template/.aioson/docs/site-forge-qa.md +155 -0
  206. package/template/.aioson/docs/site-forge-recon.md +434 -0
  207. package/template/.aioson/docs/site-forge-transform.md +249 -0
  208. package/template/.aioson/docs/squad/content-output.md +91 -0
  209. package/template/.aioson/docs/squad/creation-flow.md +135 -0
  210. package/template/.aioson/docs/squad/domain-classification.md +117 -0
  211. package/template/.aioson/docs/squad/genome-bindings.md +47 -0
  212. package/template/.aioson/docs/squad/package-contract.md +234 -0
  213. package/template/.aioson/docs/squad/quality-lens.md +56 -0
  214. package/template/.aioson/docs/squad/research-loop.md +59 -0
  215. package/template/.aioson/docs/squad/session-operations.md +117 -0
  216. package/template/.aioson/docs/squad/workflow-quality.md +165 -0
  217. package/template/.aioson/docs/ux-ui/accessibility-audit.md +55 -0
  218. package/template/.aioson/docs/ux-ui/audit-mode.md +86 -0
  219. package/template/.aioson/docs/ux-ui/component-map.md +35 -0
  220. package/template/.aioson/docs/ux-ui/design-execution.md +111 -0
  221. package/template/.aioson/docs/ux-ui/design-gate.md +27 -0
  222. package/template/.aioson/docs/ux-ui/research-mode.md +39 -0
  223. package/template/.aioson/docs/ux-ui/site-delivery.md +156 -0
  224. package/template/.aioson/docs/ux-ui/token-contract.md +57 -0
  225. package/template/.aioson/genomes/copywriting.md +204 -0
  226. package/template/.aioson/genomes/copywriting.meta.json +48 -0
  227. package/template/.aioson/git-guard.json +11 -0
  228. package/template/.aioson/mcp/servers.md +0 -1
  229. package/template/.aioson/rules/agent-language-policy.md +93 -0
  230. package/template/.aioson/rules/aioson-context-boundary.md +63 -0
  231. package/template/.aioson/rules/canonical-path-contract.md +47 -0
  232. package/template/.aioson/rules/data-format-convention.md +24 -86
  233. package/template/.aioson/rules/disk-first-artifacts.md +44 -0
  234. package/template/.aioson/rules/output-brevity.md +44 -0
  235. package/template/.aioson/rules/prd-section-ownership.md +49 -0
  236. package/template/.aioson/rules/security-baseline.md +139 -0
  237. package/template/.aioson/rules/spec-level-ownership.md +61 -0
  238. package/template/.aioson/rules/squad-driver-pattern.md +81 -0
  239. package/template/.aioson/schemas/squad-blueprint.schema.json +24 -0
  240. package/template/.aioson/schemas/squad-manifest.schema.json +44 -0
  241. package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +2 -0
  242. package/template/.aioson/skills/marketing/references/anti-patterns.md +254 -0
  243. package/template/.aioson/skills/marketing/references/fascinations.md +192 -0
  244. package/template/.aioson/skills/marketing/references/five-acts.md +248 -0
  245. package/template/.aioson/skills/marketing/references/market-intelligence.md +198 -0
  246. package/template/.aioson/skills/marketing/references/offer-structure.md +203 -0
  247. package/template/.aioson/skills/marketing/references/one-belief.md +149 -0
  248. package/template/.aioson/skills/marketing/references/patterns.md +218 -0
  249. package/template/.aioson/skills/marketing/references/pms-research.md +193 -0
  250. package/template/.aioson/skills/marketing/vsl-craft.md +385 -0
  251. package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +30 -0
  252. package/template/.aioson/skills/process/secure-tdd/SKILL.md +97 -0
  253. package/template/.aioson/skills/process/secure-tdd/references/nextjs.md +81 -0
  254. package/template/.aioson/skills/process/secure-tdd/references/node-express.md +91 -0
  255. package/template/.aioson/skills/process/secure-tdd/references/planned-stacks.md +33 -0
  256. package/template/.aioson/skills/static/harness-validate/SKILL.md +46 -0
  257. package/template/.aioson/skills/static/landing-page-deploy.md +192 -0
  258. package/template/.aioson/skills/static/landing-page-forge.md +730 -0
  259. package/template/.aioson/skills/static/ui-ux-modern.md +1 -0
  260. package/template/.aioson/skills/static/web-research-cache.md +3 -0
  261. package/template/.aioson/tasks/squad-create.md +56 -7
  262. package/template/.aioson/tasks/squad-design.md +80 -2
  263. package/template/.aioson/tasks/squad-investigate.md +14 -1
  264. package/template/.aioson/templates/squads/digital-marketing-agency/template.json +96 -0
  265. package/template/.claude/commands/aioson/agent/committer.md +5 -0
  266. package/template/.claude/commands/aioson/agent/copywriter.md +5 -0
  267. package/template/.claude/commands/aioson/agent/cypher.md +5 -0
  268. package/template/.claude/commands/aioson/agent/pair.md +5 -0
  269. package/template/.claude/commands/aioson/agent/validator.md +5 -0
  270. package/template/.gemini/commands/aios-analyst.toml +6 -3
  271. package/template/.gemini/commands/aios-architect.toml +7 -6
  272. package/template/.gemini/commands/aios-committer.toml +7 -0
  273. package/template/.gemini/commands/aios-copywriter.toml +7 -0
  274. package/template/.gemini/commands/aios-cypher.toml +7 -0
  275. package/template/.gemini/commands/aios-dev.toml +8 -7
  276. package/template/.gemini/commands/aios-deyvin.toml +6 -5
  277. package/template/.gemini/commands/aios-discovery-design-doc.toml +6 -3
  278. package/template/.gemini/commands/aios-genome.toml +7 -0
  279. package/template/.gemini/commands/aios-neo.toml +5 -3
  280. package/template/.gemini/commands/aios-orache.toml +7 -0
  281. package/template/.gemini/commands/aios-orchestrator.toml +8 -7
  282. package/template/.gemini/commands/aios-pair.toml +6 -5
  283. package/template/.gemini/commands/aios-pm.toml +8 -7
  284. package/template/.gemini/commands/aios-product.toml +5 -3
  285. package/template/.gemini/commands/aios-qa.toml +6 -5
  286. package/template/.gemini/commands/aios-setup.toml +5 -2
  287. package/template/.gemini/commands/aios-sheldon.toml +7 -0
  288. package/template/.gemini/commands/aios-site-forge.toml +7 -0
  289. package/template/.gemini/commands/aios-squad.toml +7 -0
  290. package/template/.gemini/commands/aios-tester.toml +6 -5
  291. package/template/.gemini/commands/aios-ux-ui.toml +8 -7
  292. package/template/.gemini/commands/aios-validator.toml +7 -0
  293. package/template/AGENTS.md +12 -1
  294. package/template/CLAUDE.md +6 -1
  295. package/template/.aioson/locales/en/agents/analyst.md +0 -244
  296. package/template/.aioson/locales/en/agents/architect.md +0 -245
  297. package/template/.aioson/locales/en/agents/dev.md +0 -397
  298. package/template/.aioson/locales/en/agents/deyvin.md +0 -137
  299. package/template/.aioson/locales/en/agents/discovery-design-doc.md +0 -27
  300. package/template/.aioson/locales/en/agents/genome.md +0 -212
  301. package/template/.aioson/locales/en/agents/neo.md +0 -8
  302. package/template/.aioson/locales/en/agents/orache.md +0 -6
  303. package/template/.aioson/locales/en/agents/orchestrator.md +0 -189
  304. package/template/.aioson/locales/en/agents/pair.md +0 -5
  305. package/template/.aioson/locales/en/agents/pm.md +0 -84
  306. package/template/.aioson/locales/en/agents/product.md +0 -378
  307. package/template/.aioson/locales/en/agents/profiler-enricher.md +0 -5
  308. package/template/.aioson/locales/en/agents/profiler-forge.md +0 -5
  309. package/template/.aioson/locales/en/agents/profiler-researcher.md +0 -5
  310. package/template/.aioson/locales/en/agents/qa.md +0 -270
  311. package/template/.aioson/locales/en/agents/setup.md +0 -421
  312. package/template/.aioson/locales/en/agents/sheldon.md +0 -455
  313. package/template/.aioson/locales/en/agents/squad.md +0 -449
  314. package/template/.aioson/locales/en/agents/tester.md +0 -6
  315. package/template/.aioson/locales/en/agents/ux-ui.md +0 -668
  316. package/template/.aioson/locales/es/agents/analyst.md +0 -225
  317. package/template/.aioson/locales/es/agents/architect.md +0 -245
  318. package/template/.aioson/locales/es/agents/dev.md +0 -370
  319. package/template/.aioson/locales/es/agents/deyvin.md +0 -99
  320. package/template/.aioson/locales/es/agents/discovery-design-doc.md +0 -21
  321. package/template/.aioson/locales/es/agents/genome.md +0 -104
  322. package/template/.aioson/locales/es/agents/neo.md +0 -50
  323. package/template/.aioson/locales/es/agents/orache.md +0 -105
  324. package/template/.aioson/locales/es/agents/orchestrator.md +0 -194
  325. package/template/.aioson/locales/es/agents/pair.md +0 -7
  326. package/template/.aioson/locales/es/agents/pm.md +0 -90
  327. package/template/.aioson/locales/es/agents/product.md +0 -372
  328. package/template/.aioson/locales/es/agents/profiler-enricher.md +0 -7
  329. package/template/.aioson/locales/es/agents/profiler-forge.md +0 -7
  330. package/template/.aioson/locales/es/agents/profiler-researcher.md +0 -7
  331. package/template/.aioson/locales/es/agents/qa.md +0 -198
  332. package/template/.aioson/locales/es/agents/setup.md +0 -405
  333. package/template/.aioson/locales/es/agents/sheldon.md +0 -309
  334. package/template/.aioson/locales/es/agents/squad.md +0 -532
  335. package/template/.aioson/locales/es/agents/tester.md +0 -9
  336. package/template/.aioson/locales/es/agents/ux-ui.md +0 -212
  337. package/template/.aioson/locales/fr/agents/analyst.md +0 -225
  338. package/template/.aioson/locales/fr/agents/architect.md +0 -245
  339. package/template/.aioson/locales/fr/agents/dev.md +0 -370
  340. package/template/.aioson/locales/fr/agents/deyvin.md +0 -99
  341. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +0 -21
  342. package/template/.aioson/locales/fr/agents/genome.md +0 -104
  343. package/template/.aioson/locales/fr/agents/neo.md +0 -50
  344. package/template/.aioson/locales/fr/agents/orache.md +0 -106
  345. package/template/.aioson/locales/fr/agents/orchestrator.md +0 -194
  346. package/template/.aioson/locales/fr/agents/pair.md +0 -7
  347. package/template/.aioson/locales/fr/agents/pm.md +0 -90
  348. package/template/.aioson/locales/fr/agents/product.md +0 -372
  349. package/template/.aioson/locales/fr/agents/profiler-enricher.md +0 -7
  350. package/template/.aioson/locales/fr/agents/profiler-forge.md +0 -7
  351. package/template/.aioson/locales/fr/agents/profiler-researcher.md +0 -7
  352. package/template/.aioson/locales/fr/agents/qa.md +0 -198
  353. package/template/.aioson/locales/fr/agents/setup.md +0 -405
  354. package/template/.aioson/locales/fr/agents/sheldon.md +0 -309
  355. package/template/.aioson/locales/fr/agents/squad.md +0 -532
  356. package/template/.aioson/locales/fr/agents/tester.md +0 -9
  357. package/template/.aioson/locales/fr/agents/ux-ui.md +0 -212
  358. package/template/.aioson/locales/pt-BR/agents/analyst.md +0 -319
  359. package/template/.aioson/locales/pt-BR/agents/architect.md +0 -284
  360. package/template/.aioson/locales/pt-BR/agents/dev.md +0 -483
  361. package/template/.aioson/locales/pt-BR/agents/deyvin.md +0 -184
  362. package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +0 -198
  363. package/template/.aioson/locales/pt-BR/agents/genome.md +0 -297
  364. package/template/.aioson/locales/pt-BR/agents/neo.md +0 -208
  365. package/template/.aioson/locales/pt-BR/agents/orache.md +0 -137
  366. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +0 -324
  367. package/template/.aioson/locales/pt-BR/agents/pair.md +0 -5
  368. package/template/.aioson/locales/pt-BR/agents/pm.md +0 -182
  369. package/template/.aioson/locales/pt-BR/agents/product.md +0 -466
  370. package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +0 -5
  371. package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +0 -5
  372. package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +0 -5
  373. package/template/.aioson/locales/pt-BR/agents/qa.md +0 -300
  374. package/template/.aioson/locales/pt-BR/agents/setup.md +0 -533
  375. package/template/.aioson/locales/pt-BR/agents/sheldon.md +0 -323
  376. package/template/.aioson/locales/pt-BR/agents/squad.md +0 -1330
  377. package/template/.aioson/locales/pt-BR/agents/tester.md +0 -449
  378. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +0 -669
  379. package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
  380. package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
  381. package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
  382. package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
  383. package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
@@ -0,0 +1,513 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson feature:archive — move artefatos de uma feature done para .aioson/context/done/{slug}/
5
+ *
6
+ * Designed to be called by agents automatically (e.g. from feature:close --verdict=PASS)
7
+ * so the end user never needs to type archive commands manually.
8
+ *
9
+ * Usage:
10
+ * aioson feature:archive . --feature=checkout
11
+ * aioson feature:archive . --feature=checkout --dry-run
12
+ * aioson feature:archive . --feature=checkout --restore
13
+ * aioson feature:archive . --feature=checkout --json
14
+ * aioson feature:archive . --feature=checkout --force (skip features.md status guard)
15
+ */
16
+
17
+ const fs = require('node:fs/promises');
18
+ const path = require('node:path');
19
+ const { contextDir, readFileSafe } = require('../preflight-engine');
20
+
21
+ const ARCHIVED_EXTENSIONS = ['md', 'yaml', 'yml', 'json'];
22
+
23
+ const GLOBAL_FILES = new Set([
24
+ 'project.context.md',
25
+ 'project-pulse.md',
26
+ 'project-map.md',
27
+ 'context-pack.md',
28
+ 'memory-index.md',
29
+ 'module-src.md',
30
+ 'features.md',
31
+ 'dev-state.md',
32
+ 'tasks.md',
33
+ 'discovery.md',
34
+ 'design-doc.md',
35
+ 'prd.md',
36
+ 'architecture.md',
37
+ 'spec.md',
38
+ 'spec.md.template',
39
+ 'test-plan.md',
40
+ 'test-inventory.md',
41
+ 'handoff-protocol.json',
42
+ 'last-handoff.json',
43
+ 'hardening-report.md',
44
+ 'qa-report-test-coverage.md',
45
+ 'sheldon-enrichment.md',
46
+ 'sheldon-validation.md'
47
+ ]);
48
+
49
+ function escapeRegExp(str) {
50
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
51
+ }
52
+
53
+ function buildSlugMatcher(slug) {
54
+ const extsGroup = ARCHIVED_EXTENSIONS.join('|');
55
+ // Accepts `<prefix>-<slug>.<ext>` and also `<prefix>-<slug>-<tail>.<ext>`
56
+ // (e.g. qa-report-pentester-agent-hardening.md). Prefix collisions with other
57
+ // slugs are filtered out via readOtherSlugs() before the matcher is applied.
58
+ return new RegExp(`^[a-z][a-z0-9-]*-${escapeRegExp(slug)}(?:-[a-z0-9][a-z0-9-]*)?\\.(${extsGroup})$`, 'i');
59
+ }
60
+
61
+ async function readOtherSlugs(featuresPath, currentSlug) {
62
+ const content = await readFileSafe(featuresPath);
63
+ if (!content) return [];
64
+ const slugs = new Set();
65
+ const lines = content.split(/\r?\n/);
66
+ for (const line of lines) {
67
+ const m = line.match(/^\|\s*([a-z][a-z0-9-]*)\s*\|/i);
68
+ if (!m) continue;
69
+ const s = m[1].toLowerCase();
70
+ if (s === 'slug' || s === currentSlug.toLowerCase()) continue;
71
+ slugs.add(s);
72
+ }
73
+ return Array.from(slugs);
74
+ }
75
+
76
+ function belongsToOtherSlug(fileName, slug, otherSlugs) {
77
+ // If another registered slug starts with `${slug}-` and the file suffix
78
+ // matches that longer slug (possibly with an extra tail), the file belongs
79
+ // to the longer-named feature, not to `slug`.
80
+ const base = fileName.replace(/\.(md|yaml|yml|json)$/i, '');
81
+ const slugLower = slug.toLowerCase();
82
+ for (const other of otherSlugs) {
83
+ if (!other.startsWith(`${slugLower}-`)) continue;
84
+ const idx = base.toLowerCase().lastIndexOf(`-${other}`);
85
+ if (idx === -1) continue;
86
+ const afterMatch = base.slice(idx + 1 + other.length);
87
+ if (afterMatch === '' || afterMatch.startsWith('-')) return true;
88
+ }
89
+ return false;
90
+ }
91
+
92
+ async function dirExists(dirPath) {
93
+ try {
94
+ const stat = await fs.stat(dirPath);
95
+ return stat.isDirectory();
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+
101
+ async function readDirSafe(dirPath) {
102
+ try {
103
+ return await fs.readdir(dirPath, { withFileTypes: true });
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ async function featureStatus(featuresPath, slug) {
110
+ const content = await readFileSafe(featuresPath);
111
+ if (!content) return { exists: false, status: null };
112
+ const row = new RegExp(`\\|\\s*${escapeRegExp(slug)}\\s*\\|\\s*([a-z_]+)\\s*\\|`, 'i');
113
+ const match = content.match(row);
114
+ if (!match) return { exists: false, status: null };
115
+ return { exists: true, status: match[1].toLowerCase() };
116
+ }
117
+
118
+ async function findSlugFiles(ctxDir, slug, otherSlugs = []) {
119
+ const matcher = buildSlugMatcher(slug);
120
+ const entries = await readDirSafe(ctxDir);
121
+ return entries
122
+ .filter((e) => e.isFile())
123
+ .map((e) => e.name)
124
+ .filter((name) => !GLOBAL_FILES.has(name))
125
+ .filter((name) => matcher.test(name))
126
+ .filter((name) => !belongsToOtherSlug(name, slug, otherSlugs));
127
+ }
128
+
129
+ async function findArchivedFiles(archiveDir) {
130
+ const entries = await readDirSafe(archiveDir);
131
+ return entries.filter((e) => e.isFile()).map((e) => e.name);
132
+ }
133
+
134
+ async function extractSummary(prdPath) {
135
+ const content = await readFileSafe(prdPath);
136
+ if (!content) return null;
137
+ const visionIdx = content.indexOf('## Vision');
138
+ if (visionIdx === -1) return null;
139
+ const after = content.slice(visionIdx + '## Vision'.length);
140
+ const lines = after.split(/\r?\n/);
141
+ for (const line of lines) {
142
+ const trimmed = line.trim();
143
+ if (!trimmed) continue;
144
+ if (trimmed.startsWith('#')) break;
145
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) continue;
146
+ return trimmed.replace(/\s+/g, ' ').slice(0, 160);
147
+ }
148
+ return null;
149
+ }
150
+
151
+ async function readCompletedDate(featuresPath, slug) {
152
+ const content = await readFileSafe(featuresPath);
153
+ if (!content) return null;
154
+ const re = new RegExp(`\\|\\s*${escapeRegExp(slug)}\\s*\\|[^|]*\\|[^|]*\\|\\s*([^|]+?)\\s*\\|`, 'i');
155
+ const match = content.match(re);
156
+ if (!match) return null;
157
+ const raw = match[1].trim();
158
+ if (!raw || raw === '—' || raw === '-' || raw.toLowerCase() === 'tbd') return null;
159
+ const isoMatch = raw.match(/\d{4}-\d{2}-\d{2}/);
160
+ return isoMatch ? isoMatch[0] : raw;
161
+ }
162
+
163
+ function manifestHeader() {
164
+ return [
165
+ '# Archived Features Manifest',
166
+ '',
167
+ '> Features whose artefacts were moved into `.aioson/context/done/{slug}/` after QA sign-off.',
168
+ '> Agents that need historical awareness (@cypher, @neo, @discover, @sheldon) read this file instead of globbing archived PRDs.',
169
+ '',
170
+ '| slug | completed | files | summary |',
171
+ '|------|-----------|-------|---------|',
172
+ ''
173
+ ].join('\n');
174
+ }
175
+
176
+ function parseManifest(content) {
177
+ if (!content) return { header: manifestHeader(), rows: new Map() };
178
+ const rows = new Map();
179
+ const lines = content.split(/\r?\n/);
180
+ for (const line of lines) {
181
+ const trimmed = line.trim();
182
+ if (!trimmed.startsWith('|')) continue;
183
+ if (/^\|\s*-+\s*\|/.test(trimmed)) continue;
184
+ if (/^\|\s*slug\s*\|/i.test(trimmed)) continue;
185
+ const cols = trimmed.split('|').slice(1, -1).map((c) => c.trim());
186
+ if (cols.length < 4) continue;
187
+ const [slug, completed, files, summary] = cols;
188
+ if (!slug) continue;
189
+ rows.set(slug, { slug, completed, files, summary });
190
+ }
191
+ return { header: manifestHeader(), rows };
192
+ }
193
+
194
+ function renderManifest(rows) {
195
+ const sorted = Array.from(rows.values()).sort((a, b) => {
196
+ if (a.completed && b.completed) return b.completed.localeCompare(a.completed);
197
+ if (a.completed) return -1;
198
+ if (b.completed) return 1;
199
+ return a.slug.localeCompare(b.slug);
200
+ });
201
+ const body = sorted
202
+ .map((r) => `| ${r.slug} | ${r.completed || '—'} | ${r.files} | ${r.summary || '—'} |`)
203
+ .join('\n');
204
+ return manifestHeader() + body + (body ? '\n' : '');
205
+ }
206
+
207
+ async function updateManifest(manifestPath, entry, mode) {
208
+ const existing = await readFileSafe(manifestPath);
209
+ const { rows } = parseManifest(existing);
210
+ if (mode === 'remove') {
211
+ rows.delete(entry.slug);
212
+ } else {
213
+ rows.set(entry.slug, entry);
214
+ }
215
+ await fs.writeFile(manifestPath, renderManifest(rows), 'utf8');
216
+ }
217
+
218
+ async function runFeatureArchive({ args = [], options = {}, logger }) {
219
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
220
+ const slug = options.feature ? String(options.feature) : null;
221
+ const dryRun = Boolean(options['dry-run'] || options.dryRun);
222
+ const restore = Boolean(options.restore);
223
+ const force = Boolean(options.force);
224
+ const jsonOut = Boolean(options.json);
225
+
226
+ const log = (msg) => { if (logger && !jsonOut) logger.log(msg); };
227
+
228
+ if (!slug) {
229
+ if (jsonOut) return { ok: false, reason: 'missing_feature' };
230
+ log('--feature=<slug> is required.');
231
+ return { ok: false };
232
+ }
233
+
234
+ if (!/^[a-z][a-z0-9-]*$/i.test(slug)) {
235
+ if (jsonOut) return { ok: false, reason: 'invalid_slug' };
236
+ log(`Invalid slug "${slug}" — use lowercase letters, digits and hyphens only.`);
237
+ return { ok: false };
238
+ }
239
+
240
+ const ctxDir = contextDir(targetDir);
241
+ const doneDir = path.join(ctxDir, 'done');
242
+ const archiveDir = path.join(doneDir, slug);
243
+ const manifestPath = path.join(doneDir, 'MANIFEST.md');
244
+ const featuresPath = path.join(ctxDir, 'features.md');
245
+
246
+ if (!(await dirExists(ctxDir))) {
247
+ if (jsonOut) return { ok: false, reason: 'no_context_dir' };
248
+ log(`.aioson/context/ not found at ${targetDir}. Run aioson setup first.`);
249
+ return { ok: false };
250
+ }
251
+
252
+ if (restore) {
253
+ return await runRestore({
254
+ slug, ctxDir, archiveDir, manifestPath, dryRun, jsonOut, log
255
+ });
256
+ }
257
+
258
+ const status = await featureStatus(featuresPath, slug);
259
+ if (!status.exists && !force) {
260
+ if (jsonOut) return { ok: false, reason: 'not_in_features', slug };
261
+ log(`Feature "${slug}" is not registered in features.md. Use --force to archive anyway.`);
262
+ return { ok: false };
263
+ }
264
+ if (status.exists && status.status !== 'done' && !force) {
265
+ if (jsonOut) return { ok: false, reason: 'not_done', slug, status: status.status };
266
+ log(`Feature "${slug}" has status "${status.status}" in features.md — only "done" features can be archived. Use --force to override.`);
267
+ return { ok: false };
268
+ }
269
+
270
+ const otherSlugs = await readOtherSlugs(featuresPath, slug);
271
+ const rootFiles = await findSlugFiles(ctxDir, slug, otherSlugs);
272
+ const alreadyArchived = (await dirExists(archiveDir)) ? await findArchivedFiles(archiveDir) : [];
273
+ const dossierSourceDir = path.join(ctxDir, 'features', slug);
274
+ const dossierTargetDir = path.join(archiveDir, 'dossier');
275
+ const hasDossierToMove = await dirExists(dossierSourceDir);
276
+ const dossierAlreadyArchived = await dirExists(dossierTargetDir);
277
+
278
+ if (
279
+ rootFiles.length === 0 &&
280
+ alreadyArchived.length === 0 &&
281
+ !hasDossierToMove &&
282
+ !dossierAlreadyArchived
283
+ ) {
284
+ if (jsonOut) return { ok: true, slug, moved: [], skipped: [], alreadyArchived: [], noop: true };
285
+ log(`No files matched "*-${slug}.{${ARCHIVED_EXTENSIONS.join(',')}}" in .aioson/context/ root and no features/${slug}/ dossier dir — nothing to archive.`);
286
+ return { ok: true, noop: true };
287
+ }
288
+
289
+ const toMove = [];
290
+ const toSkip = [];
291
+ for (const name of rootFiles) {
292
+ if (alreadyArchived.includes(name)) {
293
+ toSkip.push({ name, reason: 'already_archived' });
294
+ } else {
295
+ toMove.push(name);
296
+ }
297
+ }
298
+
299
+ const completed = await readCompletedDate(featuresPath, slug) || new Date().toISOString().slice(0, 10);
300
+ const prdName = `prd-${slug}.md`;
301
+ const prdPathInRoot = path.join(ctxDir, prdName);
302
+ const prdPathInArchive = path.join(archiveDir, prdName);
303
+ const summarySource = rootFiles.includes(prdName) ? prdPathInRoot
304
+ : alreadyArchived.includes(prdName) ? prdPathInArchive
305
+ : null;
306
+ const summary = summarySource ? await extractSummary(summarySource) : null;
307
+
308
+ const dossierPlan = hasDossierToMove
309
+ ? (dossierAlreadyArchived ? { action: 'skip', reason: 'already_archived' } : { action: 'move' })
310
+ : (dossierAlreadyArchived ? { action: 'noop', reason: 'already_archived' } : null);
311
+
312
+ if (dryRun) {
313
+ const result = {
314
+ ok: true,
315
+ dryRun: true,
316
+ slug,
317
+ targetDir: path.relative(targetDir, archiveDir),
318
+ move: toMove,
319
+ skip: toSkip,
320
+ dossier: dossierPlan
321
+ ? {
322
+ source: path.relative(targetDir, dossierSourceDir),
323
+ target: path.relative(targetDir, dossierTargetDir),
324
+ ...dossierPlan
325
+ }
326
+ : null,
327
+ manifestEntry: {
328
+ slug,
329
+ completed,
330
+ files: String(toMove.length + alreadyArchived.length),
331
+ summary: summary || '—'
332
+ }
333
+ };
334
+ if (jsonOut) return result;
335
+ log(`[dry-run] feature:archive — ${slug}:`);
336
+ log(` target: ${path.relative(targetDir, archiveDir)}/`);
337
+ log(` would move: ${toMove.length} file(s)`);
338
+ for (const f of toMove) log(` • ${f}`);
339
+ if (toSkip.length) {
340
+ log(` would skip: ${toSkip.length} file(s)`);
341
+ for (const s of toSkip) log(` • ${s.name} (${s.reason})`);
342
+ }
343
+ if (dossierPlan && dossierPlan.action === 'move') {
344
+ log(` would move dossier dir: features/${slug}/ → ${path.relative(targetDir, dossierTargetDir)}/`);
345
+ } else if (dossierPlan && dossierPlan.action === 'skip') {
346
+ log(` would skip dossier dir: already archived at ${path.relative(targetDir, dossierTargetDir)}/`);
347
+ }
348
+ log(` manifest entry: | ${slug} | ${completed} | ${toMove.length + alreadyArchived.length} | ${summary || '—'} |`);
349
+ return result;
350
+ }
351
+
352
+ await fs.mkdir(archiveDir, { recursive: true });
353
+
354
+ const moved = [];
355
+ for (const name of toMove) {
356
+ const from = path.join(ctxDir, name);
357
+ const to = path.join(archiveDir, name);
358
+ await fs.rename(from, to);
359
+ moved.push(name);
360
+ }
361
+
362
+ let dossierResult = null;
363
+ if (dossierPlan && dossierPlan.action === 'move') {
364
+ await fs.rename(dossierSourceDir, dossierTargetDir);
365
+ dossierResult = {
366
+ action: 'moved',
367
+ source: path.relative(targetDir, dossierSourceDir),
368
+ target: path.relative(targetDir, dossierTargetDir)
369
+ };
370
+ try {
371
+ const parent = path.join(ctxDir, 'features');
372
+ const remaining = await fs.readdir(parent);
373
+ if (remaining.length === 0) await fs.rmdir(parent);
374
+ } catch {
375
+ // parent missing or non-empty — leave it
376
+ }
377
+ } else if (dossierPlan && dossierPlan.action === 'skip') {
378
+ dossierResult = {
379
+ action: 'skipped',
380
+ reason: dossierPlan.reason,
381
+ target: path.relative(targetDir, dossierTargetDir)
382
+ };
383
+ }
384
+
385
+ const totalArchived = (await findArchivedFiles(archiveDir)).length;
386
+ const entry = {
387
+ slug,
388
+ completed,
389
+ files: String(totalArchived),
390
+ summary: summary || '—'
391
+ };
392
+ await updateManifest(manifestPath, entry, 'upsert');
393
+
394
+ const result = {
395
+ ok: true,
396
+ slug,
397
+ completed,
398
+ archiveDir: path.relative(targetDir, archiveDir),
399
+ moved,
400
+ skipped: toSkip,
401
+ totalArchived,
402
+ dossier: dossierResult,
403
+ manifestEntry: entry
404
+ };
405
+
406
+ if (jsonOut) return result;
407
+ log(`feature:archive — ${slug}:`);
408
+ log(` archive dir: ${path.relative(targetDir, archiveDir)}/`);
409
+ log(` moved: ${moved.length} file(s)`);
410
+ for (const f of moved) log(` • ${f}`);
411
+ if (toSkip.length) {
412
+ log(` skipped: ${toSkip.length} file(s) already in archive`);
413
+ for (const s of toSkip) log(` • ${s.name}`);
414
+ }
415
+ if (dossierResult && dossierResult.action === 'moved') {
416
+ log(` moved dossier dir: ${dossierResult.source}/ → ${dossierResult.target}/`);
417
+ } else if (dossierResult && dossierResult.action === 'skipped') {
418
+ log(` skipped dossier dir: already archived at ${dossierResult.target}/`);
419
+ }
420
+ log(` manifest updated: .aioson/context/done/MANIFEST.md`);
421
+ return result;
422
+ }
423
+
424
+ async function runRestore({ slug, ctxDir, archiveDir, manifestPath, dryRun, jsonOut, log }) {
425
+ if (!(await dirExists(archiveDir))) {
426
+ if (jsonOut) return { ok: false, reason: 'nothing_to_restore', slug };
427
+ log(`No archive found at .aioson/context/done/${slug}/ — nothing to restore.`);
428
+ return { ok: false };
429
+ }
430
+
431
+ const dossierTargetDir = path.join(archiveDir, 'dossier');
432
+ const dossierSourceDir = path.join(ctxDir, 'features', slug);
433
+ const hasDossierToRestore = await dirExists(dossierTargetDir);
434
+ const dossierConflict = hasDossierToRestore && (await dirExists(dossierSourceDir));
435
+
436
+ const archived = await findArchivedFiles(archiveDir);
437
+ const conflicts = [];
438
+ const toRestore = [];
439
+ for (const name of archived) {
440
+ const rootPath = path.join(ctxDir, name);
441
+ try {
442
+ await fs.access(rootPath);
443
+ conflicts.push(name);
444
+ } catch {
445
+ toRestore.push(name);
446
+ }
447
+ }
448
+ if (dossierConflict) conflicts.push(`features/${slug}/`);
449
+
450
+ if (conflicts.length > 0) {
451
+ if (jsonOut) return { ok: false, reason: 'restore_conflict', slug, conflicts };
452
+ log(`Cannot restore "${slug}" — files already exist in .aioson/context/ root:`);
453
+ for (const c of conflicts) log(` • ${c}`);
454
+ log(`Resolve manually before retrying --restore.`);
455
+ return { ok: false };
456
+ }
457
+
458
+ if (dryRun) {
459
+ const result = {
460
+ ok: true,
461
+ dryRun: true,
462
+ slug,
463
+ restore: toRestore,
464
+ dossier: hasDossierToRestore ? { action: 'restore', target: path.relative(ctxDir, dossierSourceDir) } : null
465
+ };
466
+ if (jsonOut) return result;
467
+ log(`[dry-run] feature:archive --restore — ${slug}:`);
468
+ log(` would restore: ${toRestore.length} file(s)`);
469
+ for (const f of toRestore) log(` • ${f}`);
470
+ if (hasDossierToRestore) log(` would restore dossier dir: ${path.relative(ctxDir, dossierTargetDir)}/ → features/${slug}/`);
471
+ return result;
472
+ }
473
+
474
+ const restored = [];
475
+ for (const name of toRestore) {
476
+ const from = path.join(archiveDir, name);
477
+ const to = path.join(ctxDir, name);
478
+ await fs.rename(from, to);
479
+ restored.push(name);
480
+ }
481
+
482
+ let dossierRestored = null;
483
+ if (hasDossierToRestore) {
484
+ await fs.mkdir(path.dirname(dossierSourceDir), { recursive: true });
485
+ await fs.rename(dossierTargetDir, dossierSourceDir);
486
+ dossierRestored = path.relative(ctxDir, dossierSourceDir);
487
+ }
488
+
489
+ try {
490
+ await fs.rmdir(archiveDir);
491
+ } catch {
492
+ // Directory not empty (manual files) — leave it alone.
493
+ }
494
+
495
+ await updateManifest(manifestPath, { slug }, 'remove');
496
+
497
+ const result = {
498
+ ok: true,
499
+ slug,
500
+ restored,
501
+ dossierRestored,
502
+ archiveDir: path.relative(ctxDir, archiveDir)
503
+ };
504
+ if (jsonOut) return result;
505
+ log(`feature:archive --restore — ${slug}:`);
506
+ log(` restored: ${restored.length} file(s)`);
507
+ for (const f of restored) log(` • ${f}`);
508
+ if (dossierRestored) log(` restored dossier dir: ${dossierRestored}/`);
509
+ log(` manifest updated: .aioson/context/done/MANIFEST.md`);
510
+ return result;
511
+ }
512
+
513
+ module.exports = { runFeatureArchive };
@@ -15,11 +15,83 @@
15
15
  const fs = require('node:fs/promises');
16
16
  const path = require('node:path');
17
17
  const { contextDir, readFileSafe, parseFrontmatter } = require('../preflight-engine');
18
+ const { runFeatureArchive } = require('./feature-archive');
18
19
 
19
20
  function nowDate() {
20
21
  return new Date().toISOString().slice(0, 10);
21
22
  }
22
23
 
24
+ function nowTimestamp() {
25
+ return new Date().toISOString();
26
+ }
27
+
28
+ function quoteYaml(value) {
29
+ return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
30
+ }
31
+
32
+ function extractRecentActivities(content) {
33
+ if (!content) return [];
34
+ const activityMatch = content.match(/## Recent Activity\n([\s\S]*?)(?=\n##|\s*$)/);
35
+ if (!activityMatch) return [];
36
+ return activityMatch[1]
37
+ .split('\n')
38
+ .filter((line) => line.trim().startsWith('-'))
39
+ .slice(-2);
40
+ }
41
+
42
+ async function updateProjectPulseFile(pulsePath, slug, verdict, summary, date) {
43
+ const existing = await readFileSafe(pulsePath);
44
+ if (!existing) return false;
45
+
46
+ const fm = parseFrontmatter(existing);
47
+ const gate = `Gate D: ${verdict === 'PASS' ? 'approved' : 'rejected'}`;
48
+ const recentActivities = extractRecentActivities(existing);
49
+ let activityLine = `- ${date} @qa → ${slug} (${gate}) VERDICT: ${verdict}`;
50
+ if (summary) activityLine += `: ${summary}`;
51
+ const dedupedActivities = recentActivities.filter((line) => line !== activityLine);
52
+
53
+ const activeFeature = verdict === 'PASS' ? '(none)' : slug;
54
+ const activeWork = verdict === 'PASS' ? '' : `${slug} → @qa → qa_failed`;
55
+ const blockers = verdict === 'PASS'
56
+ ? 'none'
57
+ : (summary || fm.blockers || 'QA blockers pending');
58
+ const nextRecommendation = verdict === 'PASS'
59
+ ? '@product start the next feature'
60
+ : '@dev fix QA blockers and return to @qa';
61
+
62
+ const lines = [
63
+ '---',
64
+ `last_updated: ${nowTimestamp()}`,
65
+ 'last_agent: qa',
66
+ `last_gate: ${gate}`,
67
+ `active_feature: ${activeFeature}`,
68
+ `active_work: ${quoteYaml(activeWork)}`,
69
+ `blockers: ${quoteYaml(blockers)}`,
70
+ `next_recommendation: ${quoteYaml(nextRecommendation)}`,
71
+ '---',
72
+ '',
73
+ '# Project Pulse',
74
+ '',
75
+ '## Status',
76
+ '',
77
+ '- **Last agent:** @qa',
78
+ `- **Last gate:** ${gate}`,
79
+ `- **Active feature:** ${activeFeature}`,
80
+ `- **Active work:** ${activeWork || 'none'}`,
81
+ `- **Blockers:** ${blockers}`,
82
+ `- **Next:** ${nextRecommendation}`,
83
+ '',
84
+ '## Recent Activity',
85
+ '',
86
+ ...dedupedActivities,
87
+ activityLine,
88
+ ''
89
+ ];
90
+
91
+ await fs.writeFile(pulsePath, lines.join('\n'), 'utf8');
92
+ return true;
93
+ }
94
+
23
95
  async function updateSpecFile(specPath, verdict, residual, date) {
24
96
  const content = await readFileSafe(specPath);
25
97
  if (!content) return false;
@@ -65,26 +137,34 @@ async function updateSpecFile(specPath, verdict, residual, date) {
65
137
  return true;
66
138
  }
67
139
 
140
+ function escapeSlugForRegex(slug) {
141
+ return slug.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
142
+ }
143
+
68
144
  async function updateFeaturesFile(featuresPath, slug, verdict, date) {
69
145
  const content = await readFileSafe(featuresPath);
70
146
  if (!content) return false;
71
147
 
72
148
  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}`
149
+ const rowRe = new RegExp(
150
+ `^(\\|\\s*${escapeSlugForRegex(slug)}\\s*\\|)\\s*[^|]*\\s*\\|\\s*([^|]*)\\s*\\|\\s*([^|]*)\\s*\\|(.*)$`,
151
+ 'm'
78
152
  );
79
153
 
154
+ const updated = content.replace(rowRe, (match, slugCol, startedCol, _completedCol, rest) => {
155
+ const started = startedCol.trim() || date;
156
+ return `${slugCol} ${status} | ${started} | ${date} |${rest}`;
157
+ });
158
+
80
159
  if (updated !== content) {
81
160
  await fs.writeFile(featuresPath, updated, 'utf8');
82
161
  return true;
83
162
  }
84
163
 
85
164
  // 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');
165
+ const line = `| ${slug} | ${status} | ${date} | ${date} |`;
166
+ const needsNewline = !content.endsWith('\n');
167
+ await fs.appendFile(featuresPath, `${needsNewline ? '\n' : ''}${line}\n`, 'utf8');
88
168
  return true;
89
169
  }
90
170
 
@@ -132,17 +212,41 @@ async function runFeatureClose({ args, options = {}, logger }) {
132
212
 
133
213
  // 3. Update project-pulse.md
134
214
  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');
215
+ const pulseUpdated = await updateProjectPulseFile(
216
+ pulsePath,
217
+ slug,
218
+ verdict,
219
+ residual || notes || null,
220
+ today
221
+ );
222
+ if (pulseUpdated) {
145
223
  updates.push('project-pulse.md: updated active work');
224
+ } else {
225
+ updates.push('project-pulse.md: not found (skipped)');
226
+ }
227
+
228
+ // 4. Auto-archive on PASS (default-on — user never has to remember).
229
+ // Disable explicitly with --no-archive when needed (e.g. re-running feature:close idempotently).
230
+ let archive = null;
231
+ const skipArchive = options['no-archive'] === true || options.archive === false;
232
+ if (verdict === 'PASS' && !skipArchive) {
233
+ try {
234
+ archive = await runFeatureArchive({
235
+ args: [targetDir],
236
+ options: { feature: slug, json: true },
237
+ logger: null
238
+ });
239
+ if (archive && archive.ok && archive.moved && archive.moved.length > 0) {
240
+ updates.push(`archive: moved ${archive.moved.length} file(s) to ${archive.archiveDir}/`);
241
+ updates.push(`archive: manifest updated at .aioson/context/done/MANIFEST.md`);
242
+ } else if (archive && archive.ok && archive.noop) {
243
+ updates.push('archive: nothing to move (already clean)');
244
+ } else if (archive && !archive.ok) {
245
+ updates.push(`archive: skipped (${archive.reason || 'unknown'})`);
246
+ }
247
+ } catch (err) {
248
+ updates.push(`archive: failed (${err.message || err})`);
249
+ }
146
250
  }
147
251
 
148
252
  const result = {
@@ -151,7 +255,8 @@ async function runFeatureClose({ args, options = {}, logger }) {
151
255
  verdict,
152
256
  date: today,
153
257
  residual: residual || notes || null,
154
- updates
258
+ updates,
259
+ archive
155
260
  };
156
261
 
157
262
  if (options.json) return result;