@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.
- package/CHANGELOG.md +35 -0
- package/README.md +153 -10
- package/docs/en/cli-reference.md +56 -1
- package/docs/en/i18n.md +18 -18
- package/docs/en/schemas/index.json +10 -0
- package/docs/en/schemas/parallel-assign.schema.json +9 -0
- package/docs/en/schemas/parallel-doctor.schema.json +36 -0
- package/docs/en/schemas/parallel-guard.schema.json +63 -0
- package/docs/en/schemas/parallel-merge.schema.json +84 -0
- package/docs/en/schemas/parallel-status.schema.json +91 -1
- package/docs/integrations/apps-publish-marketplace.md +94 -0
- package/docs/pt/README.md +9 -0
- package/docs/pt/agentes.md +324 -3
- package/docs/pt/clientes-ai.md +7 -3
- package/docs/pt/comandos-cli.md +160 -13
- package/docs/pt/compress-agents.md +304 -0
- package/docs/pt/design-docs-governance.md +59 -0
- package/docs/pt/feature-archive.md +191 -0
- package/docs/pt/genome-3.0-spec.md +115 -4
- package/docs/pt/genome-distribution.md +232 -0
- package/docs/pt/inicio-rapido.md +1 -0
- package/docs/pt/motor-hardening.md +492 -0
- package/docs/pt/runner-system.md +113 -0
- package/package.json +2 -1
- package/src/agent-manifests.js +66 -0
- package/src/agents.js +27 -7
- package/src/autonomy-policy.js +139 -0
- package/src/brain-query.js +161 -0
- package/src/cli.js +1377 -1099
- package/src/commands/agents.js +102 -7
- package/src/commands/artifact-validate.js +33 -4
- package/src/commands/auth.js +272 -0
- package/src/commands/brain-query.js +44 -0
- package/src/commands/briefing.js +344 -0
- package/src/commands/commit-prepare.js +547 -0
- package/src/commands/compress-agents.js +416 -0
- package/src/commands/context-health.js +4 -2
- package/src/commands/context-trim.js +17 -11
- package/src/commands/design-hybrid-options.js +3 -3
- package/src/commands/devlog-process.js +6 -4
- package/src/commands/dossier.js +423 -0
- package/src/commands/feature-archive.js +513 -0
- package/src/commands/feature-close.js +123 -18
- package/src/commands/gate-approve.js +198 -0
- package/src/commands/gate-check.js +24 -5
- package/src/commands/genome-doctor.js +166 -9
- package/src/commands/git-guard.js +170 -0
- package/src/commands/harness.js +121 -0
- package/src/commands/implementation-plan.js +47 -20
- package/src/commands/init.js +6 -2
- package/src/commands/install.js +6 -2
- package/src/commands/live.js +497 -56
- package/src/commands/locale-apply.js +9 -6
- package/src/commands/locale-diff.js +11 -112
- package/src/commands/mcp-doctor.js +2 -1
- package/src/commands/mcp-init.js +4 -10
- package/src/commands/memory.js +234 -0
- package/src/commands/parallel-assign.js +107 -27
- package/src/commands/parallel-doctor.js +416 -3
- package/src/commands/parallel-guard.js +241 -0
- package/src/commands/parallel-init.js +66 -4
- package/src/commands/parallel-merge.js +299 -0
- package/src/commands/parallel-status.js +147 -3
- package/src/commands/preflight.js +63 -4
- package/src/commands/qa-init.js +10 -5
- package/src/commands/revision.js +235 -0
- package/src/commands/scaffold-complete.js +188 -0
- package/src/commands/security-audit.js +275 -0
- package/src/commands/security-scan.js +376 -0
- package/src/commands/self-implement-loop.js +46 -2
- package/src/commands/setup-context.js +11 -10
- package/src/commands/squad-agent-create.js +51 -9
- package/src/commands/squad-investigate.js +53 -0
- package/src/commands/squad-plan.js +33 -1
- package/src/commands/squad-scaffold.js +4 -3
- package/src/commands/squad-score.js +71 -14
- package/src/commands/squad-status.js +22 -1
- package/src/commands/squad-validate.js +93 -2
- package/src/commands/store-genome.js +304 -0
- package/src/commands/store-skill.js +247 -0
- package/src/commands/store-squad.js +431 -0
- package/src/commands/store-system.js +392 -0
- package/src/commands/tool-capabilities.js +63 -0
- package/src/commands/update.js +3 -3
- package/src/commands/verify-gate.js +40 -0
- package/src/commands/workflow-execute.js +644 -155
- package/src/commands/workflow-harden.js +231 -0
- package/src/commands/workflow-heal.js +136 -0
- package/src/commands/workflow-next.js +460 -22
- package/src/commands/workflow-status.js +328 -138
- package/src/commands/workspace.js +144 -0
- package/src/constants.js +42 -75
- package/src/context-memory.js +133 -4
- package/src/context-writer.js +2 -1
- package/src/context.js +32 -2
- package/src/doctor.js +46 -6
- package/src/dossier/codemap-store.js +267 -0
- package/src/dossier/dossier-bootstrap.js +222 -0
- package/src/dossier/dossier-compact.js +159 -0
- package/src/dossier/lock.js +128 -0
- package/src/dossier/revision-store.js +313 -0
- package/src/dossier/schema.js +155 -0
- package/src/dossier/store.js +400 -0
- package/src/execution-gateway.js +3 -0
- package/src/friction-scanner.js +202 -0
- package/src/genome-schema.js +24 -1
- package/src/genomes.js +33 -0
- package/src/handoff-contract.js +363 -0
- package/src/handoff-validator.js +45 -0
- package/src/harness/circuit-breaker.js +135 -0
- package/src/i18n/messages/en.js +317 -22
- package/src/i18n/messages/es.js +259 -18
- package/src/i18n/messages/fr.js +260 -18
- package/src/i18n/messages/pt-BR.js +313 -22
- package/src/install-profile.js +0 -16
- package/src/installer.js +70 -6
- package/src/lib/git-commit-guard.js +691 -0
- package/src/lib/security/artifact-reader.js +167 -0
- package/src/lib/security/exit-codes.js +51 -0
- package/src/lib/security/findings-writer.js +176 -0
- package/src/lib/security/runtime-events.js +77 -0
- package/src/lib/security/secrets-regex.js +115 -0
- package/src/lib/store/security-scan.js +173 -0
- package/src/lib/terminal-checkbox.js +130 -0
- package/src/lib/tmux-launcher.js +163 -0
- package/src/lib/tool-capabilities.js +102 -0
- package/src/locales.js +12 -8
- package/src/parallel-workspace.js +756 -0
- package/src/parser.js +8 -1
- package/src/path-guard.js +47 -0
- package/src/preflight-engine.js +237 -26
- package/src/self-healing.js +142 -0
- package/src/session-handoff.js +111 -1
- package/src/squad/squad-scaffold.js +183 -19
- package/src/test-briefing.js +226 -0
- package/src/updater.js +1 -1
- package/src/utils.js +3 -0
- package/src/workflow-gates.js +185 -0
- package/template/.aioson/agents/analyst.md +76 -130
- package/template/.aioson/agents/architect.md +53 -86
- package/template/.aioson/agents/committer.md +161 -0
- package/template/.aioson/agents/cypher.md +252 -0
- package/template/.aioson/agents/dev.md +112 -628
- package/template/.aioson/agents/deyvin.md +33 -236
- package/template/.aioson/agents/discover.md +235 -0
- package/template/.aioson/agents/discovery-design-doc.md +17 -252
- package/template/.aioson/agents/genome.md +76 -26
- package/template/.aioson/agents/manifests/analyst.manifest.json +26 -0
- package/template/.aioson/agents/manifests/architect.manifest.json +23 -0
- package/template/.aioson/agents/manifests/committer.manifest.json +23 -0
- package/template/.aioson/agents/manifests/dev.manifest.json +37 -0
- package/template/.aioson/agents/manifests/orchestrator.manifest.json +30 -0
- package/template/.aioson/agents/manifests/pentester.manifest.json +39 -0
- package/template/.aioson/agents/manifests/pm.manifest.json +26 -0
- package/template/.aioson/agents/manifests/product.manifest.json +23 -0
- package/template/.aioson/agents/manifests/qa.manifest.json +25 -0
- package/template/.aioson/agents/manifests/setup.manifest.json +20 -0
- package/template/.aioson/agents/manifests/ux-ui.manifest.json +24 -0
- package/template/.aioson/agents/neo.md +5 -7
- package/template/.aioson/agents/orache.md +2 -6
- package/template/.aioson/agents/orchestrator.md +81 -182
- package/template/.aioson/agents/pentester.md +235 -0
- package/template/.aioson/agents/pm.md +40 -104
- package/template/.aioson/agents/product.md +99 -344
- package/template/.aioson/agents/profiler-enricher.md +57 -6
- package/template/.aioson/agents/profiler-forge.md +17 -7
- package/template/.aioson/agents/profiler-researcher.md +29 -6
- package/template/.aioson/agents/qa.md +168 -514
- package/template/.aioson/agents/setup.md +52 -278
- package/template/.aioson/agents/sheldon.md +122 -754
- package/template/.aioson/agents/site-forge.md +111 -1583
- package/template/.aioson/agents/squad.md +139 -2010
- package/template/.aioson/agents/tester.md +10 -0
- package/template/.aioson/agents/ux-ui.md +104 -812
- package/template/.aioson/agents/validator.md +69 -0
- package/template/.aioson/brains/scripts/query.js +5 -1
- package/template/.aioson/config/autonomy-protocol.json +43 -0
- package/template/.aioson/config.md +43 -15
- package/template/.aioson/constitution.md +36 -33
- package/template/.aioson/context/design-doc.md +136 -0
- package/template/.aioson/context/project-map.md +57 -0
- package/template/.aioson/design-docs/code-reuse.md +48 -0
- package/template/.aioson/design-docs/componentization.md +47 -0
- package/template/.aioson/design-docs/file-size.md +52 -0
- package/template/.aioson/design-docs/folder-structure.md +51 -0
- package/template/.aioson/design-docs/naming.md +54 -0
- package/template/.aioson/docs/LAYERS.md +12 -2
- package/template/.aioson/docs/dev/execution-discipline.md +106 -0
- package/template/.aioson/docs/dev/stack-conventions.md +83 -0
- package/template/.aioson/docs/deyvin/continuity-recovery.md +57 -0
- package/template/.aioson/docs/deyvin/debugging-escalation.md +30 -0
- package/template/.aioson/docs/deyvin/pair-execution.md +44 -0
- package/template/.aioson/docs/deyvin/runtime-handoffs.md +36 -0
- package/template/.aioson/docs/product/conversation-playbook.md +116 -0
- package/template/.aioson/docs/product/prd-contract.md +107 -0
- package/template/.aioson/docs/product/quality-lens.md +57 -0
- package/template/.aioson/docs/product/research-loop.md +65 -0
- package/template/.aioson/docs/sheldon/enrichment-paths.md +134 -0
- package/template/.aioson/docs/sheldon/quality-lens.md +57 -0
- package/template/.aioson/docs/sheldon/research-loop.md +56 -0
- package/template/.aioson/docs/sheldon/web-intelligence.md +75 -0
- package/template/.aioson/docs/site-forge-build.md +195 -0
- package/template/.aioson/docs/site-forge-extraction.md +135 -0
- package/template/.aioson/docs/site-forge-qa.md +155 -0
- package/template/.aioson/docs/site-forge-recon.md +434 -0
- package/template/.aioson/docs/site-forge-transform.md +249 -0
- package/template/.aioson/docs/squad/content-output.md +91 -0
- package/template/.aioson/docs/squad/creation-flow.md +135 -0
- package/template/.aioson/docs/squad/domain-classification.md +117 -0
- package/template/.aioson/docs/squad/genome-bindings.md +47 -0
- package/template/.aioson/docs/squad/package-contract.md +234 -0
- package/template/.aioson/docs/squad/quality-lens.md +56 -0
- package/template/.aioson/docs/squad/research-loop.md +59 -0
- package/template/.aioson/docs/squad/session-operations.md +117 -0
- package/template/.aioson/docs/squad/workflow-quality.md +165 -0
- package/template/.aioson/docs/ux-ui/accessibility-audit.md +55 -0
- package/template/.aioson/docs/ux-ui/audit-mode.md +86 -0
- package/template/.aioson/docs/ux-ui/component-map.md +35 -0
- package/template/.aioson/docs/ux-ui/design-execution.md +111 -0
- package/template/.aioson/docs/ux-ui/design-gate.md +27 -0
- package/template/.aioson/docs/ux-ui/research-mode.md +39 -0
- package/template/.aioson/docs/ux-ui/site-delivery.md +156 -0
- package/template/.aioson/docs/ux-ui/token-contract.md +57 -0
- package/template/.aioson/genomes/copywriting.meta.json +48 -0
- package/template/.aioson/git-guard.json +11 -0
- package/template/.aioson/mcp/servers.md +0 -1
- package/template/.aioson/rules/agent-language-policy.md +93 -0
- package/template/.aioson/rules/aioson-context-boundary.md +63 -0
- package/template/.aioson/rules/canonical-path-contract.md +47 -0
- package/template/.aioson/rules/data-format-convention.md +24 -86
- package/template/.aioson/rules/disk-first-artifacts.md +44 -0
- package/template/.aioson/rules/output-brevity.md +44 -0
- package/template/.aioson/rules/prd-section-ownership.md +49 -0
- package/template/.aioson/rules/security-baseline.md +139 -0
- package/template/.aioson/rules/spec-level-ownership.md +61 -0
- package/template/.aioson/rules/squad-driver-pattern.md +81 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +24 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +44 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +30 -0
- package/template/.aioson/skills/process/secure-tdd/SKILL.md +97 -0
- package/template/.aioson/skills/process/secure-tdd/references/nextjs.md +81 -0
- package/template/.aioson/skills/process/secure-tdd/references/node-express.md +91 -0
- package/template/.aioson/skills/process/secure-tdd/references/planned-stacks.md +33 -0
- package/template/.aioson/skills/static/harness-validate/SKILL.md +46 -0
- package/template/.aioson/skills/static/web-research-cache.md +3 -0
- package/template/.aioson/tasks/squad-create.md +35 -8
- package/template/.aioson/tasks/squad-design.md +50 -2
- package/template/.aioson/tasks/squad-investigate.md +14 -1
- package/template/.claude/commands/aioson/agent/committer.md +5 -0
- package/template/.claude/commands/aioson/agent/copywriter.md +5 -0
- package/template/.claude/commands/aioson/agent/cypher.md +5 -0
- package/template/.claude/commands/aioson/agent/pair.md +5 -0
- package/template/.claude/commands/aioson/agent/validator.md +5 -0
- package/template/.gemini/commands/aios-analyst.toml +6 -3
- package/template/.gemini/commands/aios-architect.toml +7 -6
- package/template/.gemini/commands/aios-committer.toml +7 -0
- package/template/.gemini/commands/aios-copywriter.toml +7 -0
- package/template/.gemini/commands/aios-cypher.toml +7 -0
- package/template/.gemini/commands/aios-dev.toml +8 -7
- package/template/.gemini/commands/aios-deyvin.toml +6 -5
- package/template/.gemini/commands/aios-discovery-design-doc.toml +6 -3
- package/template/.gemini/commands/aios-genome.toml +7 -0
- package/template/.gemini/commands/aios-neo.toml +5 -3
- package/template/.gemini/commands/aios-orache.toml +7 -0
- package/template/.gemini/commands/aios-orchestrator.toml +8 -7
- package/template/.gemini/commands/aios-pair.toml +6 -5
- package/template/.gemini/commands/aios-pm.toml +8 -7
- package/template/.gemini/commands/aios-product.toml +5 -3
- package/template/.gemini/commands/aios-qa.toml +6 -5
- package/template/.gemini/commands/aios-setup.toml +5 -2
- package/template/.gemini/commands/aios-sheldon.toml +7 -0
- package/template/.gemini/commands/aios-site-forge.toml +7 -0
- package/template/.gemini/commands/aios-squad.toml +7 -0
- package/template/.gemini/commands/aios-tester.toml +6 -5
- package/template/.gemini/commands/aios-ux-ui.toml +8 -7
- package/template/.gemini/commands/aios-validator.toml +7 -0
- package/template/AGENTS.md +12 -1
- package/template/CLAUDE.md +5 -1
- package/template/.aioson/locales/en/agents/analyst.md +0 -244
- package/template/.aioson/locales/en/agents/architect.md +0 -245
- package/template/.aioson/locales/en/agents/dev.md +0 -397
- package/template/.aioson/locales/en/agents/deyvin.md +0 -137
- package/template/.aioson/locales/en/agents/discovery-design-doc.md +0 -27
- package/template/.aioson/locales/en/agents/genome.md +0 -212
- package/template/.aioson/locales/en/agents/neo.md +0 -8
- package/template/.aioson/locales/en/agents/orache.md +0 -6
- package/template/.aioson/locales/en/agents/orchestrator.md +0 -189
- package/template/.aioson/locales/en/agents/pair.md +0 -5
- package/template/.aioson/locales/en/agents/pm.md +0 -84
- package/template/.aioson/locales/en/agents/product.md +0 -378
- package/template/.aioson/locales/en/agents/profiler-enricher.md +0 -5
- package/template/.aioson/locales/en/agents/profiler-forge.md +0 -5
- package/template/.aioson/locales/en/agents/profiler-researcher.md +0 -5
- package/template/.aioson/locales/en/agents/qa.md +0 -270
- package/template/.aioson/locales/en/agents/setup.md +0 -421
- package/template/.aioson/locales/en/agents/sheldon.md +0 -455
- package/template/.aioson/locales/en/agents/squad.md +0 -449
- package/template/.aioson/locales/en/agents/tester.md +0 -6
- package/template/.aioson/locales/en/agents/ux-ui.md +0 -668
- package/template/.aioson/locales/es/agents/analyst.md +0 -225
- package/template/.aioson/locales/es/agents/architect.md +0 -245
- package/template/.aioson/locales/es/agents/dev.md +0 -370
- package/template/.aioson/locales/es/agents/deyvin.md +0 -99
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +0 -21
- package/template/.aioson/locales/es/agents/genome.md +0 -104
- package/template/.aioson/locales/es/agents/neo.md +0 -50
- package/template/.aioson/locales/es/agents/orache.md +0 -105
- package/template/.aioson/locales/es/agents/orchestrator.md +0 -194
- package/template/.aioson/locales/es/agents/pair.md +0 -7
- package/template/.aioson/locales/es/agents/pm.md +0 -90
- package/template/.aioson/locales/es/agents/product.md +0 -372
- package/template/.aioson/locales/es/agents/profiler-enricher.md +0 -7
- package/template/.aioson/locales/es/agents/profiler-forge.md +0 -7
- package/template/.aioson/locales/es/agents/profiler-researcher.md +0 -7
- package/template/.aioson/locales/es/agents/qa.md +0 -198
- package/template/.aioson/locales/es/agents/setup.md +0 -405
- package/template/.aioson/locales/es/agents/sheldon.md +0 -309
- package/template/.aioson/locales/es/agents/squad.md +0 -532
- package/template/.aioson/locales/es/agents/tester.md +0 -9
- package/template/.aioson/locales/es/agents/ux-ui.md +0 -212
- package/template/.aioson/locales/fr/agents/analyst.md +0 -225
- package/template/.aioson/locales/fr/agents/architect.md +0 -245
- package/template/.aioson/locales/fr/agents/dev.md +0 -370
- package/template/.aioson/locales/fr/agents/deyvin.md +0 -99
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +0 -21
- package/template/.aioson/locales/fr/agents/genome.md +0 -104
- package/template/.aioson/locales/fr/agents/neo.md +0 -50
- package/template/.aioson/locales/fr/agents/orache.md +0 -106
- package/template/.aioson/locales/fr/agents/orchestrator.md +0 -194
- package/template/.aioson/locales/fr/agents/pair.md +0 -7
- package/template/.aioson/locales/fr/agents/pm.md +0 -90
- package/template/.aioson/locales/fr/agents/product.md +0 -372
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +0 -7
- package/template/.aioson/locales/fr/agents/profiler-forge.md +0 -7
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +0 -7
- package/template/.aioson/locales/fr/agents/qa.md +0 -198
- package/template/.aioson/locales/fr/agents/setup.md +0 -405
- package/template/.aioson/locales/fr/agents/sheldon.md +0 -309
- package/template/.aioson/locales/fr/agents/squad.md +0 -532
- package/template/.aioson/locales/fr/agents/tester.md +0 -9
- package/template/.aioson/locales/fr/agents/ux-ui.md +0 -212
- package/template/.aioson/locales/pt-BR/agents/analyst.md +0 -319
- package/template/.aioson/locales/pt-BR/agents/architect.md +0 -284
- package/template/.aioson/locales/pt-BR/agents/dev.md +0 -483
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +0 -184
- package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +0 -198
- package/template/.aioson/locales/pt-BR/agents/genome.md +0 -297
- package/template/.aioson/locales/pt-BR/agents/neo.md +0 -208
- package/template/.aioson/locales/pt-BR/agents/orache.md +0 -137
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +0 -324
- package/template/.aioson/locales/pt-BR/agents/pair.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/pm.md +0 -182
- package/template/.aioson/locales/pt-BR/agents/product.md +0 -466
- package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/qa.md +0 -300
- package/template/.aioson/locales/pt-BR/agents/setup.md +0 -533
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +0 -323
- package/template/.aioson/locales/pt-BR/agents/squad.md +0 -1330
- package/template/.aioson/locales/pt-BR/agents/tester.md +0 -449
- 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 };
|