@jaimevalasek/aioson 1.7.2 → 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 (362) hide show
  1. package/CHANGELOG.md +35 -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 +42 -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/cypher.md +252 -0
  143. package/template/.aioson/agents/dev.md +112 -628
  144. package/template/.aioson/agents/deyvin.md +33 -236
  145. package/template/.aioson/agents/discover.md +235 -0
  146. package/template/.aioson/agents/discovery-design-doc.md +17 -252
  147. package/template/.aioson/agents/genome.md +76 -26
  148. package/template/.aioson/agents/manifests/analyst.manifest.json +26 -0
  149. package/template/.aioson/agents/manifests/architect.manifest.json +23 -0
  150. package/template/.aioson/agents/manifests/committer.manifest.json +23 -0
  151. package/template/.aioson/agents/manifests/dev.manifest.json +37 -0
  152. package/template/.aioson/agents/manifests/orchestrator.manifest.json +30 -0
  153. package/template/.aioson/agents/manifests/pentester.manifest.json +39 -0
  154. package/template/.aioson/agents/manifests/pm.manifest.json +26 -0
  155. package/template/.aioson/agents/manifests/product.manifest.json +23 -0
  156. package/template/.aioson/agents/manifests/qa.manifest.json +25 -0
  157. package/template/.aioson/agents/manifests/setup.manifest.json +20 -0
  158. package/template/.aioson/agents/manifests/ux-ui.manifest.json +24 -0
  159. package/template/.aioson/agents/neo.md +5 -7
  160. package/template/.aioson/agents/orache.md +2 -6
  161. package/template/.aioson/agents/orchestrator.md +81 -182
  162. package/template/.aioson/agents/pentester.md +235 -0
  163. package/template/.aioson/agents/pm.md +40 -104
  164. package/template/.aioson/agents/product.md +99 -344
  165. package/template/.aioson/agents/profiler-enricher.md +57 -6
  166. package/template/.aioson/agents/profiler-forge.md +17 -7
  167. package/template/.aioson/agents/profiler-researcher.md +29 -6
  168. package/template/.aioson/agents/qa.md +168 -514
  169. package/template/.aioson/agents/setup.md +52 -278
  170. package/template/.aioson/agents/sheldon.md +122 -754
  171. package/template/.aioson/agents/site-forge.md +111 -1583
  172. package/template/.aioson/agents/squad.md +139 -2010
  173. package/template/.aioson/agents/tester.md +10 -0
  174. package/template/.aioson/agents/ux-ui.md +104 -812
  175. package/template/.aioson/agents/validator.md +69 -0
  176. package/template/.aioson/brains/scripts/query.js +5 -1
  177. package/template/.aioson/config/autonomy-protocol.json +43 -0
  178. package/template/.aioson/config.md +43 -15
  179. package/template/.aioson/constitution.md +36 -33
  180. package/template/.aioson/context/design-doc.md +136 -0
  181. package/template/.aioson/context/project-map.md +57 -0
  182. package/template/.aioson/design-docs/code-reuse.md +48 -0
  183. package/template/.aioson/design-docs/componentization.md +47 -0
  184. package/template/.aioson/design-docs/file-size.md +52 -0
  185. package/template/.aioson/design-docs/folder-structure.md +51 -0
  186. package/template/.aioson/design-docs/naming.md +54 -0
  187. package/template/.aioson/docs/LAYERS.md +12 -2
  188. package/template/.aioson/docs/dev/execution-discipline.md +106 -0
  189. package/template/.aioson/docs/dev/stack-conventions.md +83 -0
  190. package/template/.aioson/docs/deyvin/continuity-recovery.md +57 -0
  191. package/template/.aioson/docs/deyvin/debugging-escalation.md +30 -0
  192. package/template/.aioson/docs/deyvin/pair-execution.md +44 -0
  193. package/template/.aioson/docs/deyvin/runtime-handoffs.md +36 -0
  194. package/template/.aioson/docs/product/conversation-playbook.md +116 -0
  195. package/template/.aioson/docs/product/prd-contract.md +107 -0
  196. package/template/.aioson/docs/product/quality-lens.md +57 -0
  197. package/template/.aioson/docs/product/research-loop.md +65 -0
  198. package/template/.aioson/docs/sheldon/enrichment-paths.md +134 -0
  199. package/template/.aioson/docs/sheldon/quality-lens.md +57 -0
  200. package/template/.aioson/docs/sheldon/research-loop.md +56 -0
  201. package/template/.aioson/docs/sheldon/web-intelligence.md +75 -0
  202. package/template/.aioson/docs/site-forge-build.md +195 -0
  203. package/template/.aioson/docs/site-forge-extraction.md +135 -0
  204. package/template/.aioson/docs/site-forge-qa.md +155 -0
  205. package/template/.aioson/docs/site-forge-recon.md +434 -0
  206. package/template/.aioson/docs/site-forge-transform.md +249 -0
  207. package/template/.aioson/docs/squad/content-output.md +91 -0
  208. package/template/.aioson/docs/squad/creation-flow.md +135 -0
  209. package/template/.aioson/docs/squad/domain-classification.md +117 -0
  210. package/template/.aioson/docs/squad/genome-bindings.md +47 -0
  211. package/template/.aioson/docs/squad/package-contract.md +234 -0
  212. package/template/.aioson/docs/squad/quality-lens.md +56 -0
  213. package/template/.aioson/docs/squad/research-loop.md +59 -0
  214. package/template/.aioson/docs/squad/session-operations.md +117 -0
  215. package/template/.aioson/docs/squad/workflow-quality.md +165 -0
  216. package/template/.aioson/docs/ux-ui/accessibility-audit.md +55 -0
  217. package/template/.aioson/docs/ux-ui/audit-mode.md +86 -0
  218. package/template/.aioson/docs/ux-ui/component-map.md +35 -0
  219. package/template/.aioson/docs/ux-ui/design-execution.md +111 -0
  220. package/template/.aioson/docs/ux-ui/design-gate.md +27 -0
  221. package/template/.aioson/docs/ux-ui/research-mode.md +39 -0
  222. package/template/.aioson/docs/ux-ui/site-delivery.md +156 -0
  223. package/template/.aioson/docs/ux-ui/token-contract.md +57 -0
  224. package/template/.aioson/genomes/copywriting.meta.json +48 -0
  225. package/template/.aioson/git-guard.json +11 -0
  226. package/template/.aioson/mcp/servers.md +0 -1
  227. package/template/.aioson/rules/agent-language-policy.md +93 -0
  228. package/template/.aioson/rules/aioson-context-boundary.md +63 -0
  229. package/template/.aioson/rules/canonical-path-contract.md +47 -0
  230. package/template/.aioson/rules/data-format-convention.md +24 -86
  231. package/template/.aioson/rules/disk-first-artifacts.md +44 -0
  232. package/template/.aioson/rules/output-brevity.md +44 -0
  233. package/template/.aioson/rules/prd-section-ownership.md +49 -0
  234. package/template/.aioson/rules/security-baseline.md +139 -0
  235. package/template/.aioson/rules/spec-level-ownership.md +61 -0
  236. package/template/.aioson/rules/squad-driver-pattern.md +81 -0
  237. package/template/.aioson/schemas/squad-blueprint.schema.json +24 -0
  238. package/template/.aioson/schemas/squad-manifest.schema.json +44 -0
  239. package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +30 -0
  240. package/template/.aioson/skills/process/secure-tdd/SKILL.md +97 -0
  241. package/template/.aioson/skills/process/secure-tdd/references/nextjs.md +81 -0
  242. package/template/.aioson/skills/process/secure-tdd/references/node-express.md +91 -0
  243. package/template/.aioson/skills/process/secure-tdd/references/planned-stacks.md +33 -0
  244. package/template/.aioson/skills/static/harness-validate/SKILL.md +46 -0
  245. package/template/.aioson/skills/static/web-research-cache.md +3 -0
  246. package/template/.aioson/tasks/squad-create.md +35 -8
  247. package/template/.aioson/tasks/squad-design.md +50 -2
  248. package/template/.aioson/tasks/squad-investigate.md +14 -1
  249. package/template/.claude/commands/aioson/agent/committer.md +5 -0
  250. package/template/.claude/commands/aioson/agent/copywriter.md +5 -0
  251. package/template/.claude/commands/aioson/agent/cypher.md +5 -0
  252. package/template/.claude/commands/aioson/agent/pair.md +5 -0
  253. package/template/.claude/commands/aioson/agent/validator.md +5 -0
  254. package/template/.gemini/commands/aios-analyst.toml +6 -3
  255. package/template/.gemini/commands/aios-architect.toml +7 -6
  256. package/template/.gemini/commands/aios-committer.toml +7 -0
  257. package/template/.gemini/commands/aios-copywriter.toml +7 -0
  258. package/template/.gemini/commands/aios-cypher.toml +7 -0
  259. package/template/.gemini/commands/aios-dev.toml +8 -7
  260. package/template/.gemini/commands/aios-deyvin.toml +6 -5
  261. package/template/.gemini/commands/aios-discovery-design-doc.toml +6 -3
  262. package/template/.gemini/commands/aios-genome.toml +7 -0
  263. package/template/.gemini/commands/aios-neo.toml +5 -3
  264. package/template/.gemini/commands/aios-orache.toml +7 -0
  265. package/template/.gemini/commands/aios-orchestrator.toml +8 -7
  266. package/template/.gemini/commands/aios-pair.toml +6 -5
  267. package/template/.gemini/commands/aios-pm.toml +8 -7
  268. package/template/.gemini/commands/aios-product.toml +5 -3
  269. package/template/.gemini/commands/aios-qa.toml +6 -5
  270. package/template/.gemini/commands/aios-setup.toml +5 -2
  271. package/template/.gemini/commands/aios-sheldon.toml +7 -0
  272. package/template/.gemini/commands/aios-site-forge.toml +7 -0
  273. package/template/.gemini/commands/aios-squad.toml +7 -0
  274. package/template/.gemini/commands/aios-tester.toml +6 -5
  275. package/template/.gemini/commands/aios-ux-ui.toml +8 -7
  276. package/template/.gemini/commands/aios-validator.toml +7 -0
  277. package/template/AGENTS.md +12 -1
  278. package/template/CLAUDE.md +5 -1
  279. package/template/.aioson/locales/en/agents/analyst.md +0 -244
  280. package/template/.aioson/locales/en/agents/architect.md +0 -245
  281. package/template/.aioson/locales/en/agents/dev.md +0 -397
  282. package/template/.aioson/locales/en/agents/deyvin.md +0 -137
  283. package/template/.aioson/locales/en/agents/discovery-design-doc.md +0 -27
  284. package/template/.aioson/locales/en/agents/genome.md +0 -212
  285. package/template/.aioson/locales/en/agents/neo.md +0 -8
  286. package/template/.aioson/locales/en/agents/orache.md +0 -6
  287. package/template/.aioson/locales/en/agents/orchestrator.md +0 -189
  288. package/template/.aioson/locales/en/agents/pair.md +0 -5
  289. package/template/.aioson/locales/en/agents/pm.md +0 -84
  290. package/template/.aioson/locales/en/agents/product.md +0 -378
  291. package/template/.aioson/locales/en/agents/profiler-enricher.md +0 -5
  292. package/template/.aioson/locales/en/agents/profiler-forge.md +0 -5
  293. package/template/.aioson/locales/en/agents/profiler-researcher.md +0 -5
  294. package/template/.aioson/locales/en/agents/qa.md +0 -270
  295. package/template/.aioson/locales/en/agents/setup.md +0 -421
  296. package/template/.aioson/locales/en/agents/sheldon.md +0 -455
  297. package/template/.aioson/locales/en/agents/squad.md +0 -449
  298. package/template/.aioson/locales/en/agents/tester.md +0 -6
  299. package/template/.aioson/locales/en/agents/ux-ui.md +0 -668
  300. package/template/.aioson/locales/es/agents/analyst.md +0 -225
  301. package/template/.aioson/locales/es/agents/architect.md +0 -245
  302. package/template/.aioson/locales/es/agents/dev.md +0 -370
  303. package/template/.aioson/locales/es/agents/deyvin.md +0 -99
  304. package/template/.aioson/locales/es/agents/discovery-design-doc.md +0 -21
  305. package/template/.aioson/locales/es/agents/genome.md +0 -104
  306. package/template/.aioson/locales/es/agents/neo.md +0 -50
  307. package/template/.aioson/locales/es/agents/orache.md +0 -105
  308. package/template/.aioson/locales/es/agents/orchestrator.md +0 -194
  309. package/template/.aioson/locales/es/agents/pair.md +0 -7
  310. package/template/.aioson/locales/es/agents/pm.md +0 -90
  311. package/template/.aioson/locales/es/agents/product.md +0 -372
  312. package/template/.aioson/locales/es/agents/profiler-enricher.md +0 -7
  313. package/template/.aioson/locales/es/agents/profiler-forge.md +0 -7
  314. package/template/.aioson/locales/es/agents/profiler-researcher.md +0 -7
  315. package/template/.aioson/locales/es/agents/qa.md +0 -198
  316. package/template/.aioson/locales/es/agents/setup.md +0 -405
  317. package/template/.aioson/locales/es/agents/sheldon.md +0 -309
  318. package/template/.aioson/locales/es/agents/squad.md +0 -532
  319. package/template/.aioson/locales/es/agents/tester.md +0 -9
  320. package/template/.aioson/locales/es/agents/ux-ui.md +0 -212
  321. package/template/.aioson/locales/fr/agents/analyst.md +0 -225
  322. package/template/.aioson/locales/fr/agents/architect.md +0 -245
  323. package/template/.aioson/locales/fr/agents/dev.md +0 -370
  324. package/template/.aioson/locales/fr/agents/deyvin.md +0 -99
  325. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +0 -21
  326. package/template/.aioson/locales/fr/agents/genome.md +0 -104
  327. package/template/.aioson/locales/fr/agents/neo.md +0 -50
  328. package/template/.aioson/locales/fr/agents/orache.md +0 -106
  329. package/template/.aioson/locales/fr/agents/orchestrator.md +0 -194
  330. package/template/.aioson/locales/fr/agents/pair.md +0 -7
  331. package/template/.aioson/locales/fr/agents/pm.md +0 -90
  332. package/template/.aioson/locales/fr/agents/product.md +0 -372
  333. package/template/.aioson/locales/fr/agents/profiler-enricher.md +0 -7
  334. package/template/.aioson/locales/fr/agents/profiler-forge.md +0 -7
  335. package/template/.aioson/locales/fr/agents/profiler-researcher.md +0 -7
  336. package/template/.aioson/locales/fr/agents/qa.md +0 -198
  337. package/template/.aioson/locales/fr/agents/setup.md +0 -405
  338. package/template/.aioson/locales/fr/agents/sheldon.md +0 -309
  339. package/template/.aioson/locales/fr/agents/squad.md +0 -532
  340. package/template/.aioson/locales/fr/agents/tester.md +0 -9
  341. package/template/.aioson/locales/fr/agents/ux-ui.md +0 -212
  342. package/template/.aioson/locales/pt-BR/agents/analyst.md +0 -319
  343. package/template/.aioson/locales/pt-BR/agents/architect.md +0 -284
  344. package/template/.aioson/locales/pt-BR/agents/dev.md +0 -483
  345. package/template/.aioson/locales/pt-BR/agents/deyvin.md +0 -184
  346. package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +0 -198
  347. package/template/.aioson/locales/pt-BR/agents/genome.md +0 -297
  348. package/template/.aioson/locales/pt-BR/agents/neo.md +0 -208
  349. package/template/.aioson/locales/pt-BR/agents/orache.md +0 -137
  350. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +0 -324
  351. package/template/.aioson/locales/pt-BR/agents/pair.md +0 -5
  352. package/template/.aioson/locales/pt-BR/agents/pm.md +0 -182
  353. package/template/.aioson/locales/pt-BR/agents/product.md +0 -466
  354. package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +0 -5
  355. package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +0 -5
  356. package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +0 -5
  357. package/template/.aioson/locales/pt-BR/agents/qa.md +0 -300
  358. package/template/.aioson/locales/pt-BR/agents/setup.md +0 -533
  359. package/template/.aioson/locales/pt-BR/agents/sheldon.md +0 -323
  360. package/template/.aioson/locales/pt-BR/agents/squad.md +0 -1330
  361. package/template/.aioson/locales/pt-BR/agents/tester.md +0 -449
  362. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +0 -669
@@ -0,0 +1,267 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { isValidSlug } = require('./schema');
6
+
7
+ const FEATURES_SUBDIR = 'features';
8
+ const DOSSIER_FILENAME = 'dossier.md';
9
+
10
+ const VALID_ROLES = new Set([
11
+ 'command-entry', 'core-module', 'io-layer', 'store', 'schema',
12
+ 'test', 'util', 'config', 'integration', 'cli', 'other'
13
+ ]);
14
+ const VALID_COUPLING = new Set(['low', 'medium', 'high']);
15
+ const LINES_REGEX = /^\d+-\d+$/;
16
+
17
+ function dossierPath(contextDir, slug) {
18
+ return path.join(contextDir, FEATURES_SUBDIR, slug, DOSSIER_FILENAME);
19
+ }
20
+
21
+ function parseCodeMapBlock(raw) {
22
+ const marker = '```yaml\n';
23
+ const start = raw.indexOf('## Code Map\n');
24
+ if (start === -1) return null;
25
+ const blockStart = raw.indexOf(marker, start);
26
+ if (blockStart === -1) return null;
27
+ const codeStart = blockStart + marker.length;
28
+ const codeEnd = raw.indexOf('\n```', codeStart);
29
+ if (codeEnd === -1) return null;
30
+ return { blockStart, codeStart, codeEnd };
31
+ }
32
+
33
+ function parseYamlCodeMap(yamlText) {
34
+ const result = { files: [], modules: [], patterns: [] };
35
+ const lines = yamlText.split('\n');
36
+ let currentSection = null;
37
+ let currentItem = null;
38
+
39
+ const flushItem = () => {
40
+ if (currentItem !== null && currentSection !== null) {
41
+ result[currentSection].push(currentItem);
42
+ currentItem = null;
43
+ }
44
+ };
45
+
46
+ for (const line of lines) {
47
+ if (!line.trim()) continue;
48
+
49
+ // Section header
50
+ const sectionMatch = line.match(/^(files|modules|patterns):\s*(\[\])?$/);
51
+ if (sectionMatch) {
52
+ flushItem();
53
+ currentSection = sectionMatch[1];
54
+ continue;
55
+ }
56
+
57
+ // List item start: "- key: value"
58
+ const listItemMatch = line.match(/^-\s+(\w+):\s*(.*)$/);
59
+ if (listItemMatch && currentSection) {
60
+ flushItem();
61
+ currentItem = {};
62
+ currentItem[listItemMatch[1]] = listItemMatch[2].replace(/^["']|["']$/g, '');
63
+ continue;
64
+ }
65
+
66
+ // Continuation: " key: value"
67
+ const kvMatch = line.match(/^\s+(\w+):\s*(.*)$/);
68
+ if (kvMatch && currentItem !== null) {
69
+ currentItem[kvMatch[1]] = kvMatch[2].replace(/^["']|["']$/g, '');
70
+ }
71
+ }
72
+ flushItem();
73
+ return result;
74
+ }
75
+
76
+ function serializeCodeMap(map) {
77
+ const lines = [];
78
+ const serializeList = (key, items) => {
79
+ if (!items || items.length === 0) {
80
+ lines.push(`${key}: []`);
81
+ return;
82
+ }
83
+ lines.push(`${key}:`);
84
+ for (const item of items) {
85
+ const entries = Object.entries(item);
86
+ if (entries.length === 0) continue;
87
+ lines.push(`- ${entries[0][0]}: ${entries[0][1]}`);
88
+ for (const [k, v] of entries.slice(1)) {
89
+ lines.push(` ${k}: ${v}`);
90
+ }
91
+ }
92
+ };
93
+ serializeList('files', map.files);
94
+ serializeList('modules', map.modules);
95
+ serializeList('patterns', map.patterns);
96
+ return lines.join('\n');
97
+ }
98
+
99
+ function validateFileEntry(entry) {
100
+ const errors = [];
101
+ if (!entry.path || typeof entry.path !== 'string') errors.push('path is required');
102
+ if (entry.lines && !LINES_REGEX.test(entry.lines)) errors.push(`lines must be int-int (got: ${entry.lines})`);
103
+ if (entry.role && !VALID_ROLES.has(entry.role)) errors.push(`role must be one of [${[...VALID_ROLES].join(', ')}]`);
104
+ if (entry.coupling_risk && !VALID_COUPLING.has(entry.coupling_risk)) errors.push(`coupling_risk must be low|medium|high`);
105
+ return errors;
106
+ }
107
+
108
+ async function addCodemap({ slug, contextDir, filePath, lines, role, coupling, addedBy, now = () => new Date() }) {
109
+ if (!isValidSlug(slug)) {
110
+ const err = new Error(`invalid slug: ${JSON.stringify(slug)}`);
111
+ err.code = 'EDOSSIERSLUG';
112
+ throw err;
113
+ }
114
+ const errors = validateFileEntry({ path: filePath, lines, role, coupling_risk: coupling });
115
+ if (errors.length > 0) {
116
+ const err = new Error(`invalid codemap entry: ${errors.join('; ')}`);
117
+ err.code = 'ECODEMAPVALIDATION';
118
+ err.errors = errors;
119
+ throw err;
120
+ }
121
+
122
+ // Warn (not error) when the file path doesn't exist on disk — may be a planned file
123
+ let fileWarn = null;
124
+ try {
125
+ await fs.access(path.resolve(contextDir, '..', '..', filePath));
126
+ } catch {
127
+ fileWarn = 'file_not_found';
128
+ }
129
+
130
+ const p = dossierPath(contextDir, slug);
131
+ let raw;
132
+ try {
133
+ raw = await fs.readFile(p, 'utf8');
134
+ } catch (err) {
135
+ if (err && err.code === 'ENOENT') {
136
+ const e = new Error(`dossier not found for slug "${slug}"`);
137
+ e.code = 'EDOSSIERMISSING';
138
+ throw e;
139
+ }
140
+ throw err;
141
+ }
142
+
143
+ const parsed = parseCodeMapBlock(raw);
144
+ let map = { files: [], modules: [], patterns: [] };
145
+ if (parsed) {
146
+ const yamlText = raw.slice(parsed.codeStart, parsed.codeEnd);
147
+ map = parseYamlCodeMap(yamlText);
148
+ }
149
+
150
+ // Idempotency: dedupe by (path, lines)
151
+ const existing = map.files.find(f => f.path === filePath && f.lines === (lines || ''));
152
+ if (existing) {
153
+ return { added: false, path: filePath, warn: fileWarn };
154
+ }
155
+
156
+ const entry = { path: filePath };
157
+ if (lines) entry.lines = lines;
158
+ if (role) entry.role = role;
159
+ if (coupling) entry.coupling_risk = coupling;
160
+ if (addedBy) entry.added_by = addedBy;
161
+ entry.added_at = now().toISOString();
162
+
163
+ map.files.push(entry);
164
+ const newYaml = serializeCodeMap(map);
165
+
166
+ let newRaw;
167
+ if (parsed) {
168
+ newRaw = raw.slice(0, parsed.codeStart) + newYaml + raw.slice(parsed.codeEnd);
169
+ } else {
170
+ // Insert a Code Map section if absent (shouldn't happen in well-formed dossiers)
171
+ newRaw = raw + `\n## Code Map\n\n\`\`\`yaml\n${newYaml}\n\`\`\`\n`;
172
+ }
173
+
174
+ await fs.writeFile(p, newRaw, 'utf8');
175
+ return { added: true, path: filePath, warn: fileWarn };
176
+ }
177
+
178
+ async function linkRule({ slug, contextDir, rulePath, reason, targetDir }) {
179
+ if (!isValidSlug(slug)) {
180
+ const err = new Error(`invalid slug: ${JSON.stringify(slug)}`);
181
+ err.code = 'EDOSSIERSLUG';
182
+ throw err;
183
+ }
184
+
185
+ // Validate rule path exists in .aioson/rules/ or .aioson/design-docs/
186
+ const base = targetDir || process.cwd();
187
+ const absRule = path.resolve(base, rulePath);
188
+ const rulesDir = path.join(base, '.aioson', 'rules');
189
+ const designDocsDir = path.join(base, '.aioson', 'design-docs');
190
+
191
+ const inRules = absRule.startsWith(rulesDir + path.sep) || absRule.startsWith(rulesDir + '/');
192
+ const inDesignDocs = absRule.startsWith(designDocsDir + path.sep) || absRule.startsWith(designDocsDir + '/');
193
+
194
+ if (!inRules && !inDesignDocs) {
195
+ const err = new Error(`rule path must be under .aioson/rules/ or .aioson/design-docs/ (got: ${rulePath})`);
196
+ err.code = 'ELINKREULEPATH';
197
+ throw err;
198
+ }
199
+
200
+ try {
201
+ await fs.access(absRule);
202
+ } catch {
203
+ const err = new Error(`rule file not found: ${absRule}`);
204
+ err.code = 'ELINKREULENOTFOUND';
205
+ err.path = absRule;
206
+ throw err;
207
+ }
208
+
209
+ const p = dossierPath(contextDir, slug);
210
+ let raw;
211
+ try {
212
+ raw = await fs.readFile(p, 'utf8');
213
+ } catch (err) {
214
+ if (err && err.code === 'ENOENT') {
215
+ const e = new Error(`dossier not found for slug "${slug}"`);
216
+ e.code = 'EDOSSIERMISSING';
217
+ throw e;
218
+ }
219
+ throw err;
220
+ }
221
+
222
+ const rulesSection = '## Rules & Design-Docs aplicáveis';
223
+ const entry = `- [${rulePath}](${rulePath})${reason ? ` — ${reason}` : ''}`;
224
+
225
+ // Idempotency: don't duplicate same path
226
+ if (raw.includes(`[${rulePath}]`)) {
227
+ return { added: false, path: rulePath };
228
+ }
229
+
230
+ const lines = raw.split('\n');
231
+ let sectionEnd = lines.length;
232
+ let inSection = false;
233
+
234
+ for (let i = 0; i < lines.length; i++) {
235
+ if (lines[i].trimEnd() === rulesSection) {
236
+ inSection = true;
237
+ } else if (inSection && /^## /.test(lines[i])) {
238
+ sectionEnd = i;
239
+ break;
240
+ }
241
+ }
242
+
243
+ const before = lines.slice(0, sectionEnd);
244
+ const after = lines.slice(sectionEnd);
245
+
246
+ // Remove placeholder line if present
247
+ const placeholderIdx = before.findIndex(l => l.includes('_(vazio —') || l.includes('_(empty'));
248
+ if (placeholderIdx !== -1) before.splice(placeholderIdx, 1);
249
+
250
+ while (before.length > 0 && before[before.length - 1].trim() === '') before.pop();
251
+ before.push('', entry);
252
+
253
+ const newRaw = [...before, '', ...after].join('\n');
254
+ await fs.writeFile(p, newRaw, 'utf8');
255
+ return { added: true, path: rulePath };
256
+ }
257
+
258
+ module.exports = {
259
+ addCodemap,
260
+ linkRule,
261
+ parseCodeMapBlock,
262
+ parseYamlCodeMap,
263
+ serializeCodeMap,
264
+ validateFileEntry,
265
+ VALID_ROLES,
266
+ VALID_COUPLING
267
+ };
@@ -0,0 +1,222 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs/promises');
5
+ const path = require('node:path');
6
+
7
+ const { isValidSlug, SCHEMA_VERSION, ALLOWED_CLASSIFICATIONS, validateFrontmatter } = require('./schema');
8
+ const { featureDir, dossierPath, parseSections, parseFrontmatter } = require('./store');
9
+
10
+ // Maps canonical artifact filenames to the agent that typically produces them.
11
+ const ARTIFACT_AGENTS = {
12
+ [`prd`]: 'product',
13
+ [`spec`]: 'architect',
14
+ [`sheldon-enrichment`]: 'sheldon',
15
+ [`requirements`]: 'analyst',
16
+ [`architecture`]: 'architect'
17
+ };
18
+
19
+ async function fileExists(p) {
20
+ try { await fs.access(p); return true; } catch { return false; }
21
+ }
22
+
23
+ async function readText(p) {
24
+ try { return await fs.readFile(p, 'utf8'); } catch { return null; }
25
+ }
26
+
27
+ function extractSection(markdown, headingNames) {
28
+ if (!markdown) return null;
29
+ const sections = parseSections(markdown);
30
+ for (const name of headingNames) {
31
+ if (sections[name]) {
32
+ const t = sections[name].trim();
33
+ if (t) return t;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
39
+ function artifactHash(artifacts) {
40
+ const keys = Object.keys(artifacts).sort();
41
+ const payload = keys.map(k => `${k}=${artifacts[k] ? artifacts[k].length : 0}`).join(';');
42
+ return crypto.createHash('sha256').update(payload).digest('hex').slice(0, 12);
43
+ }
44
+
45
+ function buildBootstrapDossier({ slug, classification, createdAt, artifacts, why, what, agentTrail }) {
46
+ const fm = [
47
+ '---',
48
+ `feature_slug: ${slug}`,
49
+ `schema_version: "${SCHEMA_VERSION}"`,
50
+ `created_by: dossier-init`,
51
+ `created_at: ${createdAt}`,
52
+ `status: ${artifacts.done ? 'closed' : 'active'}`,
53
+ `classification: ${classification}`,
54
+ `last_updated_by: dossier-init`,
55
+ `last_updated_at: ${createdAt}`,
56
+ `bootstrap_hash: ${artifactHash(artifacts)}`,
57
+ '---',
58
+ ''
59
+ ].join('\n');
60
+
61
+ const whyText = why || '_(não encontrado — preencher manualmente)_';
62
+ const whatText = what || '_(não encontrado — preencher manualmente)_';
63
+
64
+ const trailLines = agentTrail.length > 0
65
+ ? agentTrail.map(e => `- **${e.timestamp}** | @${e.agent} | _${e.artifact}_`).join('\n')
66
+ : '_(sintetizado a partir de artefatos existentes)_';
67
+
68
+ const body = [
69
+ '## Why',
70
+ '',
71
+ whyText,
72
+ '',
73
+ '## What',
74
+ '',
75
+ whatText,
76
+ '',
77
+ '## Code Map',
78
+ '',
79
+ '```yaml',
80
+ 'files: []',
81
+ 'modules: []',
82
+ 'patterns: []',
83
+ '```',
84
+ '',
85
+ '## Rules & Design-Docs aplicáveis',
86
+ '',
87
+ '_(populado via dossier:link-rule)_',
88
+ '',
89
+ '## Agent Trail',
90
+ '',
91
+ trailLines,
92
+ '',
93
+ '## Revision Requests',
94
+ '',
95
+ '_(vazio)_',
96
+ ''
97
+ ].join('\n');
98
+
99
+ return fm + body;
100
+ }
101
+
102
+ async function initFromExisting({ slug, contextDir, classification, targetDir, now = () => new Date() } = {}) {
103
+ if (!isValidSlug(slug)) {
104
+ const err = new Error(`invalid slug (must be kebab-case): ${JSON.stringify(slug)}`);
105
+ err.code = 'EDOSSIERSLUG';
106
+ throw err;
107
+ }
108
+
109
+ const dir = featureDir(contextDir, slug);
110
+ const p = dossierPath(contextDir, slug);
111
+
112
+ // Check if dossier already exists (idempotency guard)
113
+ if (await fileExists(p)) {
114
+ // Check if it has the same bootstrap_hash — if artifacts unchanged, it's a no-op
115
+ const raw = await readText(p);
116
+ const fmParse = raw ? parseFrontmatter(raw) : { ok: false };
117
+ if (fmParse.ok && fmParse.data.bootstrap_hash) {
118
+ // Re-compute hash to detect changes
119
+ const artifacts = await gatherArtifacts(slug, contextDir, targetDir);
120
+ const newHash = artifactHash(artifacts);
121
+ if (newHash === fmParse.data.bootstrap_hash) {
122
+ return { created: false, reason: 'unchanged', path: p };
123
+ }
124
+ }
125
+ const err = new Error(`dossier already exists at ${p} — use dossier:show to inspect`);
126
+ err.code = 'EDOSSIEREXISTS';
127
+ err.path = p;
128
+ throw err;
129
+ }
130
+
131
+ const artifacts = await gatherArtifacts(slug, contextDir, targetDir);
132
+
133
+ // Must have at least one artifact to synthesize from
134
+ const hasAny = Object.values(artifacts).some(Boolean);
135
+ if (!hasAny) {
136
+ const err = new Error(`no artifacts found for slug "${slug}" — use dossier:init without --from-existing`);
137
+ err.code = 'EBOOTSTRAPEMPTY';
138
+ throw err;
139
+ }
140
+
141
+ // Resolve classification
142
+ let cls = classification;
143
+ if (!cls) {
144
+ const ctxPath = path.join(contextDir, 'project.context.md');
145
+ const ctxRaw = await readText(ctxPath);
146
+ if (ctxRaw) {
147
+ const m = ctxRaw.match(/^classification:\s*"?([A-Z]+)"?\s*$/m);
148
+ if (m && ALLOWED_CLASSIFICATIONS.has(m[1])) cls = m[1];
149
+ }
150
+ }
151
+ cls = cls || 'MEDIUM';
152
+
153
+ // Extract Why/What
154
+ const prdContent = artifacts.prd || artifacts.prdGlobal;
155
+ const why = extractSection(prdContent, ['Problem', 'Why', 'Vision', 'Problema']);
156
+ const what = extractSection(prdContent, ['Escopo do MVP', 'Scope', 'What', 'Escopo']);
157
+
158
+ // Build agent trail from artifact metadata
159
+ const createdAt = now().toISOString();
160
+ const agentTrail = buildAgentTrail(artifacts, createdAt);
161
+
162
+ // Validate frontmatter before writing
163
+ const fmCheck = validateFrontmatter({
164
+ feature_slug: slug,
165
+ schema_version: SCHEMA_VERSION,
166
+ created_by: 'dossier-init',
167
+ created_at: createdAt,
168
+ status: artifacts.done ? 'closed' : 'active',
169
+ classification: cls,
170
+ last_updated_by: 'dossier-init',
171
+ last_updated_at: createdAt
172
+ });
173
+ if (!fmCheck.valid) {
174
+ const err = new Error(`schema error: ${fmCheck.errors.join('; ')}`);
175
+ err.code = 'EDOSSIERSCHEMA';
176
+ throw err;
177
+ }
178
+
179
+ const markdown = buildBootstrapDossier({ slug, classification: cls, createdAt, artifacts, why, what, agentTrail });
180
+
181
+ await fs.mkdir(dir, { recursive: true });
182
+ await fs.writeFile(p, markdown, 'utf8');
183
+
184
+ return { created: true, path: p, classification: cls, artifactsFound: Object.keys(artifacts).filter(k => artifacts[k]) };
185
+ }
186
+
187
+ async function gatherArtifacts(slug, contextDir, targetDir) {
188
+ const base = targetDir || path.join(contextDir, '..', '..');
189
+ const artifacts = {};
190
+
191
+ // Per-slug artifacts
192
+ artifacts.prd = await readText(path.join(contextDir, `prd-${slug}.md`));
193
+ artifacts.spec = await readText(path.join(contextDir, `spec-${slug}.md`));
194
+ artifacts.sheldonEnrichment = await readText(path.join(contextDir, `sheldon-enrichment-${slug}.md`));
195
+ artifacts.requirements = await readText(path.join(contextDir, `requirements-${slug}.md`));
196
+ artifacts.architecture = await readText(path.join(contextDir, `architecture-${slug}.md`));
197
+
198
+ // Global PRD fallback
199
+ artifacts.prdGlobal = !artifacts.prd ? await readText(path.join(contextDir, 'prd.md')) : null;
200
+
201
+ // done/ directory (feature already closed)
202
+ const doneDir = path.join(contextDir, 'done', slug);
203
+ artifacts.done = await fileExists(doneDir) ? doneDir : null;
204
+
205
+ return artifacts;
206
+ }
207
+
208
+ function buildAgentTrail(artifacts, fallbackTimestamp) {
209
+ const trail = [];
210
+ const add = (artifact, agent) => {
211
+ if (artifacts[artifact]) trail.push({ artifact, agent, timestamp: fallbackTimestamp });
212
+ };
213
+ add('prd', 'product');
214
+ add('prdGlobal', 'product');
215
+ add('requirements', 'analyst');
216
+ add('sheldonEnrichment', 'sheldon');
217
+ add('architecture', 'architect');
218
+ add('spec', 'architect');
219
+ return trail;
220
+ }
221
+
222
+ module.exports = { initFromExisting };
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { isValidSlug } = require('./schema');
6
+
7
+ const FEATURES_SUBDIR = 'features';
8
+ const DOSSIER_FILENAME = 'dossier.md';
9
+ const HISTORY_FILENAME = 'dossier-history.md';
10
+ const MAX_ACTIVE_SIZE = 15000;
11
+ const TARGET_ACTIVE_SIZE = 10000;
12
+
13
+ // Sections whose content can be migrated once the gate they belong to is closed.
14
+ // The ordering reflects which gates close first in MEDIUM flow.
15
+ const MIGRATABLE_SECTIONS = [
16
+ 'Why',
17
+ 'What',
18
+ 'Rules & Design-Docs aplicáveis'
19
+ ];
20
+
21
+ function dossierPath(contextDir, slug) {
22
+ return path.join(contextDir, FEATURES_SUBDIR, slug, DOSSIER_FILENAME);
23
+ }
24
+
25
+ function historyPath(contextDir, slug) {
26
+ return path.join(contextDir, FEATURES_SUBDIR, slug, HISTORY_FILENAME);
27
+ }
28
+
29
+ function splitSections(raw) {
30
+ const result = [];
31
+ const lines = raw.split('\n');
32
+ let current = null;
33
+ let buf = [];
34
+ let frontmatter = null;
35
+ let inFm = false;
36
+ let fmClosed = false;
37
+ let fmBuf = [];
38
+
39
+ for (let i = 0; i < lines.length; i++) {
40
+ const line = lines[i];
41
+ if (!fmClosed) {
42
+ if (i === 0 && line === '---') { inFm = true; fmBuf.push(line); continue; }
43
+ if (inFm) {
44
+ fmBuf.push(line);
45
+ if (line === '---' && i > 0) { fmClosed = true; frontmatter = fmBuf.join('\n'); continue; }
46
+ continue;
47
+ }
48
+ fmClosed = true;
49
+ }
50
+ const m = line.match(/^##\s+(.+?)\s*$/);
51
+ if (m) {
52
+ if (current !== null) result.push({ heading: current, lines: buf });
53
+ current = m[1].trim();
54
+ buf = [];
55
+ } else {
56
+ if (current !== null) buf.push(line);
57
+ }
58
+ }
59
+ if (current !== null) result.push({ heading: current, lines: buf });
60
+ return { frontmatter, sections: result };
61
+ }
62
+
63
+ function joinSections(frontmatter, sections) {
64
+ const parts = [];
65
+ if (frontmatter) parts.push(frontmatter, '');
66
+ for (const sec of sections) {
67
+ parts.push(`## ${sec.heading}`);
68
+ parts.push(...sec.lines);
69
+ }
70
+ return parts.join('\n');
71
+ }
72
+
73
+ async function compact({ slug, contextDir, now = () => new Date(), force = false } = {}) {
74
+ if (!isValidSlug(slug)) {
75
+ const err = new Error(`invalid slug: ${JSON.stringify(slug)}`);
76
+ err.code = 'EDOSSIERSLUG';
77
+ throw err;
78
+ }
79
+
80
+ const p = dossierPath(contextDir, slug);
81
+ let raw;
82
+ try {
83
+ raw = await fs.readFile(p, 'utf8');
84
+ } catch (err) {
85
+ if (err && err.code === 'ENOENT') {
86
+ const e = new Error(`dossier not found for slug "${slug}"`);
87
+ e.code = 'EDOSSIERMISSING';
88
+ throw e;
89
+ }
90
+ throw err;
91
+ }
92
+
93
+ if (!force && Buffer.byteLength(raw, 'utf8') <= MAX_ACTIVE_SIZE) {
94
+ return { compacted: false, reason: 'size_ok', sizeBytes: Buffer.byteLength(raw, 'utf8') };
95
+ }
96
+
97
+ const { frontmatter, sections } = splitSections(raw);
98
+ const date = now().toISOString().slice(0, 10);
99
+ const hp = historyPath(contextDir, slug);
100
+
101
+ // Load or initialize history
102
+ let historyContent = '';
103
+ try {
104
+ historyContent = await fs.readFile(hp, 'utf8');
105
+ } catch {
106
+ historyContent = `# Dossier History — ${slug}\n\n`;
107
+ }
108
+
109
+ const migratedHeadings = [];
110
+ const activeSections = [];
111
+
112
+ for (const sec of sections) {
113
+ const body = sec.lines.join('\n').trim();
114
+ const isEmpty = !body || body.startsWith('_(vazio') || body.startsWith('_(empty') || body.startsWith('_(preencher');
115
+
116
+ if (MIGRATABLE_SECTIONS.includes(sec.heading) && !isEmpty) {
117
+ // Append to history (append-only, never compact again)
118
+ const anchor = sec.heading.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+/g, '-');
119
+ historyContent += `\n## [Migrated from active dossier on ${date}] ${sec.heading} {#${anchor}-${date}}\n\n${body}\n`;
120
+ migratedHeadings.push(sec.heading);
121
+
122
+ // Replace in active with summary + link
123
+ activeSections.push({
124
+ heading: sec.heading,
125
+ lines: [``, `_(migrated to [dossier-history.md](dossier-history.md#${anchor}-${date}))_`, ``]
126
+ });
127
+ } else {
128
+ activeSections.push(sec);
129
+ }
130
+ }
131
+
132
+ if (migratedHeadings.length === 0) {
133
+ return { compacted: false, reason: 'nothing_migratable', sizeBytes: Buffer.byteLength(raw, 'utf8') };
134
+ }
135
+
136
+ const newRaw = joinSections(frontmatter, activeSections);
137
+ await fs.writeFile(p, newRaw, 'utf8');
138
+ // History is append-only
139
+ await fs.writeFile(hp, historyContent, 'utf8');
140
+
141
+ return {
142
+ compacted: true,
143
+ migratedSections: migratedHeadings,
144
+ activeSizeBytes: Buffer.byteLength(newRaw, 'utf8'),
145
+ historyPath: hp
146
+ };
147
+ }
148
+
149
+ async function shouldCompact({ slug, contextDir } = {}) {
150
+ const p = dossierPath(contextDir, slug);
151
+ try {
152
+ const stat = await fs.stat(p);
153
+ return stat.size > MAX_ACTIVE_SIZE;
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ module.exports = { compact, shouldCompact, MAX_ACTIVE_SIZE, TARGET_ACTIVE_SIZE, dossierPath, historyPath };