@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
package/src/genomes.js
CHANGED
|
@@ -22,6 +22,7 @@ const GENOME_V3_SECTION_KEYS = [
|
|
|
22
22
|
'cognitiveProfile',
|
|
23
23
|
'communicationStyle',
|
|
24
24
|
'biases',
|
|
25
|
+
'traitInteractions',
|
|
25
26
|
'conflictResolution'
|
|
26
27
|
];
|
|
27
28
|
const GENOME_SECTION_KEYS = [...GENOME_CANONICAL_SECTION_KEYS, ...GENOME_V3_SECTION_KEYS];
|
|
@@ -102,6 +103,21 @@ function normalizeStringArray(value) {
|
|
|
102
103
|
.filter(Boolean);
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
const GENOME_RELATION_TYPES = ['complementa', 'contradiz', 'depende-de', 'sobrepos'];
|
|
107
|
+
|
|
108
|
+
function normalizeRelations(value) {
|
|
109
|
+
if (!Array.isArray(value)) return [];
|
|
110
|
+
return value
|
|
111
|
+
.map((entry) => {
|
|
112
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
113
|
+
const genome = normalizeText(entry.genome || entry.slug);
|
|
114
|
+
const type = normalizeText(entry.type);
|
|
115
|
+
if (!genome) return null;
|
|
116
|
+
return { genome, type };
|
|
117
|
+
})
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
}
|
|
120
|
+
|
|
105
121
|
function normalizeGenomeVersion(value, fallback = 2) {
|
|
106
122
|
const parsed = Number.parseInt(String(value || ''), 10);
|
|
107
123
|
if (!Number.isFinite(parsed)) return fallback;
|
|
@@ -193,6 +209,9 @@ function createEmptyGenome() {
|
|
|
193
209
|
enneagram: '',
|
|
194
210
|
bigFive: '',
|
|
195
211
|
mbti: '',
|
|
212
|
+
hexacoH: '',
|
|
213
|
+
anchorPrompt: '',
|
|
214
|
+
relations: [],
|
|
196
215
|
confidence: '',
|
|
197
216
|
profilerReport: '',
|
|
198
217
|
hybridMode: '',
|
|
@@ -210,6 +229,7 @@ function createEmptyGenome() {
|
|
|
210
229
|
cognitiveProfile: [],
|
|
211
230
|
communicationStyle: [],
|
|
212
231
|
biases: [],
|
|
232
|
+
traitInteractions: [],
|
|
213
233
|
conflictResolution: [],
|
|
214
234
|
evidence: [],
|
|
215
235
|
applicationNotes: []
|
|
@@ -289,6 +309,9 @@ function normalizeGenome(input = {}) {
|
|
|
289
309
|
enneagram: normalizeText(merged.enneagram),
|
|
290
310
|
bigFive: normalizeText(merged.bigFive || merged.big_five),
|
|
291
311
|
mbti: normalizeText(merged.mbti),
|
|
312
|
+
hexacoH: normalizeOptionalEnum(merged.hexacoH || merged.hexaco_h, GENOME_CONFIDENCE_LEVELS),
|
|
313
|
+
anchorPrompt: normalizeText(merged.anchorPrompt || merged.anchor_prompt),
|
|
314
|
+
relations: normalizeRelations(merged.relations),
|
|
292
315
|
confidence: normalizeOptionalEnum(merged.confidence, GENOME_CONFIDENCE_LEVELS),
|
|
293
316
|
profilerReport: normalizeText(merged.profilerReport || merged.profiler_report),
|
|
294
317
|
hybridMode: normalizeOptionalEnum(
|
|
@@ -315,6 +338,7 @@ function countGenomeSections(input = {}) {
|
|
|
315
338
|
cognitiveProfile: genome.sections.cognitiveProfile.length,
|
|
316
339
|
communicationStyle: genome.sections.communicationStyle.length,
|
|
317
340
|
biases: genome.sections.biases.length,
|
|
341
|
+
traitInteractions: genome.sections.traitInteractions.length,
|
|
318
342
|
conflictResolution: genome.sections.conflictResolution.length,
|
|
319
343
|
evidence: genome.sections.evidence.length,
|
|
320
344
|
applicationNotes: genome.sections.applicationNotes.length
|
|
@@ -334,6 +358,7 @@ function normalizeCounts(value = {}) {
|
|
|
334
358
|
cognitiveProfile: normalizeInteger(value.cognitiveProfile, 0),
|
|
335
359
|
communicationStyle: normalizeInteger(value.communicationStyle, 0),
|
|
336
360
|
biases: normalizeInteger(value.biases, 0),
|
|
361
|
+
traitInteractions: normalizeInteger(value.traitInteractions, 0),
|
|
337
362
|
conflictResolution: normalizeInteger(value.conflictResolution, 0),
|
|
338
363
|
evidence: normalizeInteger(value.evidence, 0),
|
|
339
364
|
applicationNotes: normalizeInteger(value.applicationNotes, 0)
|
|
@@ -390,6 +415,9 @@ function normalizeGenomeMeta(input = {}) {
|
|
|
390
415
|
enneagram: normalizeText(input.enneagram || (genome && genome.enneagram)),
|
|
391
416
|
bigFive: normalizeText(input.bigFive || input.big_five || (genome && genome.bigFive)),
|
|
392
417
|
mbti: normalizeText(input.mbti || (genome && genome.mbti)),
|
|
418
|
+
hexacoH: normalizeOptionalEnum(input.hexacoH || input.hexaco_h || (genome && genome.hexacoH), GENOME_CONFIDENCE_LEVELS),
|
|
419
|
+
anchorPrompt: normalizeText(input.anchorPrompt || input.anchor_prompt || (genome && genome.anchorPrompt)),
|
|
420
|
+
relations: normalizeRelations(input.relations || (genome && genome.relations)),
|
|
393
421
|
confidence: normalizeOptionalEnum(input.confidence || (genome && genome.confidence), GENOME_CONFIDENCE_LEVELS),
|
|
394
422
|
profilerReport: normalizeText(input.profilerReport || input.profiler_report || (genome && genome.profilerReport)),
|
|
395
423
|
hybridMode: normalizeOptionalEnum(
|
|
@@ -416,6 +444,10 @@ function normalizeGenomeMeta(input = {}) {
|
|
|
416
444
|
squads: normalizeStringArray(input.bindings && input.bindings.squads),
|
|
417
445
|
agents: normalizeStringArray(input.bindings && input.bindings.agents)
|
|
418
446
|
},
|
|
447
|
+
dependencies: {
|
|
448
|
+
skills: normalizeStringArray(input.dependencies && input.dependencies.skills),
|
|
449
|
+
genomes: normalizeStringArray(input.dependencies && input.dependencies.genomes)
|
|
450
|
+
},
|
|
419
451
|
createdAt,
|
|
420
452
|
updatedAt
|
|
421
453
|
};
|
|
@@ -455,6 +487,7 @@ module.exports = {
|
|
|
455
487
|
GENOME_FORMATS,
|
|
456
488
|
GENOME_CONFIDENCE_LEVELS,
|
|
457
489
|
GENOME_HYBRID_MODES,
|
|
490
|
+
GENOME_RELATION_TYPES,
|
|
458
491
|
GENOME_SECTION_KEYS,
|
|
459
492
|
GENOME_CANONICAL_SECTION_KEYS,
|
|
460
493
|
GENOME_V3_SECTION_KEYS,
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* handoff-contract — machine-verified agent output contracts.
|
|
5
|
+
*
|
|
6
|
+
* Each agent role declares what it must produce before the workflow
|
|
7
|
+
* allows handoff to the next stage. This catches incomplete agent
|
|
8
|
+
* sessions early instead of discovering missing artifacts later.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const { readFileSafe, fileExists } = require('./preflight-engine');
|
|
13
|
+
|
|
14
|
+
// Contract definitions per agent stage
|
|
15
|
+
const CONTRACTS = {
|
|
16
|
+
setup: {
|
|
17
|
+
artifacts: ['.aioson/context/project.context.md'],
|
|
18
|
+
gates: [],
|
|
19
|
+
contextUpdates: []
|
|
20
|
+
},
|
|
21
|
+
product: {
|
|
22
|
+
artifacts: (targetDir, state) => {
|
|
23
|
+
if (state.mode === 'feature' && state.featureSlug) {
|
|
24
|
+
return [`.aioson/context/prd-${state.featureSlug}.md`];
|
|
25
|
+
}
|
|
26
|
+
return ['.aioson/context/prd.md'];
|
|
27
|
+
},
|
|
28
|
+
gates: [],
|
|
29
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
30
|
+
},
|
|
31
|
+
analyst: {
|
|
32
|
+
artifacts: (targetDir, state) => {
|
|
33
|
+
if (state.mode === 'feature' && state.featureSlug) {
|
|
34
|
+
return [
|
|
35
|
+
`.aioson/context/requirements-${state.featureSlug}.md`,
|
|
36
|
+
`.aioson/context/spec-${state.featureSlug}.md`
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
return ['.aioson/context/discovery.md'];
|
|
40
|
+
},
|
|
41
|
+
gates: ['A'], // Gate A must be approved
|
|
42
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
43
|
+
},
|
|
44
|
+
architect: {
|
|
45
|
+
artifacts: ['.aioson/context/architecture.md'],
|
|
46
|
+
gates: ['B'],
|
|
47
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
48
|
+
},
|
|
49
|
+
'ux-ui': {
|
|
50
|
+
artifacts: ['.aioson/context/ui-spec.md'],
|
|
51
|
+
gates: ['B'],
|
|
52
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
53
|
+
},
|
|
54
|
+
pm: {
|
|
55
|
+
artifacts: (targetDir, state) => {
|
|
56
|
+
// @pm owns implementation-plan only for MEDIUM features (AC-SDLC-16)
|
|
57
|
+
if (state.mode === 'feature' && state.featureSlug && state.classification === 'MEDIUM') {
|
|
58
|
+
return [`.aioson/context/implementation-plan-${state.featureSlug}.md`];
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
},
|
|
62
|
+
gates: [],
|
|
63
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
64
|
+
},
|
|
65
|
+
orchestrator: {
|
|
66
|
+
artifacts: ['.aioson/context/parallel'],
|
|
67
|
+
gates: [],
|
|
68
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
69
|
+
},
|
|
70
|
+
dev: {
|
|
71
|
+
artifacts: [],
|
|
72
|
+
gates: ['C'],
|
|
73
|
+
contextUpdates: ['.aioson/context/project-pulse.md', '.aioson/context/dev-state.md']
|
|
74
|
+
},
|
|
75
|
+
tester: {
|
|
76
|
+
artifacts: [],
|
|
77
|
+
gates: [],
|
|
78
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
79
|
+
},
|
|
80
|
+
pentester: {
|
|
81
|
+
artifacts: (targetDir, state) => {
|
|
82
|
+
if (state.mode === 'feature' && state.featureSlug) {
|
|
83
|
+
return [`.aioson/context/security-findings-${state.featureSlug}.json`];
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
86
|
+
},
|
|
87
|
+
gates: [],
|
|
88
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
89
|
+
},
|
|
90
|
+
qa: {
|
|
91
|
+
artifacts: [],
|
|
92
|
+
gates: ['D'],
|
|
93
|
+
contextUpdates: ['.aioson/context/project-pulse.md']
|
|
94
|
+
},
|
|
95
|
+
committer: {
|
|
96
|
+
artifacts: [],
|
|
97
|
+
gates: [],
|
|
98
|
+
contextUpdates: []
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
async function readSecurityFindings(findingsPath) {
|
|
103
|
+
try {
|
|
104
|
+
const content = await readFileSafe(findingsPath);
|
|
105
|
+
if (!content) return { ok: false, reason: 'empty_file' };
|
|
106
|
+
const data = JSON.parse(content);
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
reviewContract: data.review_contract && typeof data.review_contract === 'object'
|
|
110
|
+
? data.review_contract
|
|
111
|
+
: null,
|
|
112
|
+
findings: Array.isArray(data.findings) ? data.findings : []
|
|
113
|
+
};
|
|
114
|
+
} catch {
|
|
115
|
+
return { ok: false, reason: 'invalid_json' };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isNonEmptyString(value) {
|
|
120
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function validateReviewContract(reviewContract) {
|
|
124
|
+
const missing = [];
|
|
125
|
+
|
|
126
|
+
if (!reviewContract || typeof reviewContract !== 'object') {
|
|
127
|
+
return ['review_contract'];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const field of ['scope_mode', 'evidence_policy', 'findings_artifact_path']) {
|
|
131
|
+
if (!isNonEmptyString(reviewContract[field])) {
|
|
132
|
+
missing.push(field);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
reviewContract.target_mode === 'app_target' &&
|
|
138
|
+
!isNonEmptyString(reviewContract.target_scope)
|
|
139
|
+
) {
|
|
140
|
+
missing.push('target_scope');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return missing;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getFindingIdentifier(finding) {
|
|
147
|
+
if (isNonEmptyString(finding?.id)) return finding.id.trim();
|
|
148
|
+
if (isNonEmptyString(finding?.finding_id)) return finding.finding_id.trim();
|
|
149
|
+
return 'unknown-finding';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function resolveArtifacts(contract, targetDir, state) {
|
|
153
|
+
const raw = typeof contract.artifacts === 'function'
|
|
154
|
+
? contract.artifacts(targetDir, state)
|
|
155
|
+
: contract.artifacts;
|
|
156
|
+
return raw.map((p) => path.join(targetDir, p));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseFrontmatterValue(markdown, key) {
|
|
160
|
+
const fmMatch = String(markdown || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
161
|
+
if (!fmMatch) return null;
|
|
162
|
+
for (const line of fmMatch[1].split(/\r?\n/)) {
|
|
163
|
+
const idx = line.indexOf(':');
|
|
164
|
+
if (idx === -1) continue;
|
|
165
|
+
const currentKey = line.slice(0, idx).trim();
|
|
166
|
+
if (currentKey !== key) continue;
|
|
167
|
+
return line.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function resolveClassification(targetDir, state) {
|
|
173
|
+
const explicit = isNonEmptyString(state?.classification) ? state.classification.trim().toUpperCase() : null;
|
|
174
|
+
if (explicit) return explicit;
|
|
175
|
+
const contextPath = path.join(targetDir, '.aioson', 'context', 'project.context.md');
|
|
176
|
+
const content = await readFileSafe(contextPath);
|
|
177
|
+
const inferred = parseFrontmatterValue(content, 'classification');
|
|
178
|
+
return isNonEmptyString(inferred) ? inferred.trim().toUpperCase() : null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function checkGateApproval(targetDir, gateLetter, slug) {
|
|
182
|
+
const specPath = slug
|
|
183
|
+
? path.join(targetDir, '.aioson', 'context', `spec-${slug}.md`)
|
|
184
|
+
: path.join(targetDir, '.aioson', 'context', 'spec.md');
|
|
185
|
+
const content = await readFileSafe(specPath);
|
|
186
|
+
if (!content) {
|
|
187
|
+
if (!slug) return { ok: true, reason: 'project_mode_without_spec' };
|
|
188
|
+
return { ok: false, reason: 'spec_missing' };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const gateNames = { A: 'requirements', B: 'design', C: 'plan', D: 'execution' };
|
|
192
|
+
const gateName = gateNames[gateLetter];
|
|
193
|
+
|
|
194
|
+
// Parse frontmatter
|
|
195
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
196
|
+
if (!fmMatch) return { ok: false, reason: 'no_frontmatter' };
|
|
197
|
+
|
|
198
|
+
const fm = {};
|
|
199
|
+
for (const line of fmMatch[1].split(/\r?\n/)) {
|
|
200
|
+
const idx = line.indexOf(':');
|
|
201
|
+
if (idx === -1) continue;
|
|
202
|
+
const key = line.slice(0, idx).trim();
|
|
203
|
+
const val = line.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
|
|
204
|
+
if (key) fm[key] = val;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check explicit gate field
|
|
208
|
+
const gateVal = fm[`gate_${gateName}`] || fm[`gate${gateLetter}`] || fm[`gate_${gateLetter}`];
|
|
209
|
+
if (gateVal && gateVal.toLowerCase() === 'approved') {
|
|
210
|
+
return { ok: true };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check phase_gates JSON
|
|
214
|
+
if (fm.phase_gates) {
|
|
215
|
+
try {
|
|
216
|
+
const parsed = JSON.parse(fm.phase_gates.replace(/'/g, '"'));
|
|
217
|
+
if (parsed[gateName] === 'approved') return { ok: true };
|
|
218
|
+
} catch {
|
|
219
|
+
// ignore
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check content for gate approval lines
|
|
224
|
+
const gateLineRe = new RegExp(`gate\\s+${gateLetter}[^:]*:\\s*approved`, 'i');
|
|
225
|
+
if (gateLineRe.test(content)) {
|
|
226
|
+
return { ok: true };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Gate D: also accept QA sign-off section with PASS verdict
|
|
230
|
+
if (gateLetter === 'D') {
|
|
231
|
+
if (content.includes('## QA Sign-off')) {
|
|
232
|
+
const passMatch = content.match(/\*\*Verdict:\*\*\s*(PASS)/i);
|
|
233
|
+
if (passMatch) {
|
|
234
|
+
return { ok: true };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { ok: false, reason: `gate_${gateName}_not_approved` };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function validateHandoffContract(targetDir, state, stageName) {
|
|
243
|
+
const contract = CONTRACTS[stageName];
|
|
244
|
+
if (!contract) {
|
|
245
|
+
// Unknown stage — allow pass-through
|
|
246
|
+
return { ok: true, stage: stageName, missing: [] };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const missing = [];
|
|
250
|
+
const classification = await resolveClassification(targetDir, state);
|
|
251
|
+
|
|
252
|
+
// 1. Artifacts
|
|
253
|
+
const artifactPaths = await resolveArtifacts(contract, targetDir, state);
|
|
254
|
+
for (const p of artifactPaths) {
|
|
255
|
+
if (!(await fileExists(p))) {
|
|
256
|
+
missing.push(`missing artifact: ${path.relative(targetDir, p)}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 2. Gates
|
|
261
|
+
for (const gateLetter of contract.gates) {
|
|
262
|
+
const gateCheck = await checkGateApproval(targetDir, gateLetter, state.featureSlug);
|
|
263
|
+
if (!gateCheck.ok) {
|
|
264
|
+
missing.push(`gate ${gateLetter} not approved (${gateCheck.reason})`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 3. Context updates (soft check — just warn if completely missing)
|
|
269
|
+
for (const p of contract.contextUpdates) {
|
|
270
|
+
const abs = path.join(targetDir, p);
|
|
271
|
+
if (!(await fileExists(abs))) {
|
|
272
|
+
missing.push(`missing context file: ${p} (recommended)`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 4. Security findings check — qa stage only
|
|
277
|
+
// Blocks on open high/critical findings with recommended_gate_status=block.
|
|
278
|
+
if (stageName === 'qa' && state.featureSlug) {
|
|
279
|
+
const findingsPath = path.join(
|
|
280
|
+
targetDir,
|
|
281
|
+
`.aioson/context/security-findings-${state.featureSlug}.json`
|
|
282
|
+
);
|
|
283
|
+
const requiresFindingsArtifact = state.mode === 'feature' && classification === 'MEDIUM';
|
|
284
|
+
if (!(await fileExists(findingsPath))) {
|
|
285
|
+
if (requiresFindingsArtifact) {
|
|
286
|
+
missing.push(`missing artifact: ${path.relative(targetDir, findingsPath)} (required for MEDIUM Gate D security audit)`);
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
const envelope = await readSecurityFindings(findingsPath);
|
|
290
|
+
if (!envelope || envelope.ok === false) {
|
|
291
|
+
missing.push(
|
|
292
|
+
`security: invalid findings artifact in ${path.relative(targetDir, findingsPath)} (${envelope?.reason || 'invalid_json'})`
|
|
293
|
+
);
|
|
294
|
+
} else {
|
|
295
|
+
const reviewContractMissing = validateReviewContract(envelope.reviewContract);
|
|
296
|
+
if (reviewContractMissing.length > 0) {
|
|
297
|
+
missing.push(
|
|
298
|
+
`security: invalid review_contract in ${path.relative(targetDir, findingsPath)} (missing: ${reviewContractMissing.join(', ')})`
|
|
299
|
+
);
|
|
300
|
+
} else {
|
|
301
|
+
const blockers = envelope.findings.filter(
|
|
302
|
+
(f) =>
|
|
303
|
+
(f.status === 'open' || f.status === 'needs_validation') &&
|
|
304
|
+
f.recommended_gate_status === 'block' &&
|
|
305
|
+
(f.severity === 'high' || f.severity === 'critical')
|
|
306
|
+
);
|
|
307
|
+
if (blockers.length > 0) {
|
|
308
|
+
missing.push(
|
|
309
|
+
`security: ${blockers.length} unresolved high/critical finding(s) blocking gate: ${blockers.map((f) => getFindingIdentifier(f)).join(', ')}`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Only hard-block on artifacts and gates; context updates are warnings unless
|
|
318
|
+
// we are in strict mode. For now, treat everything as blocking to harden handoffs.
|
|
319
|
+
const hardBlockers = missing.filter((m) => !m.includes('(recommended)'));
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
ok: hardBlockers.length === 0,
|
|
323
|
+
stage: stageName,
|
|
324
|
+
missing: hardBlockers,
|
|
325
|
+
warnings: missing.filter((m) => m.includes('(recommended)'))
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function formatContractError(result) {
|
|
330
|
+
const lines = [
|
|
331
|
+
`[Handoff Contract BLOCKED]`,
|
|
332
|
+
`Stage: @${result.stage}`,
|
|
333
|
+
'',
|
|
334
|
+
'Missing deliverables:',
|
|
335
|
+
...result.missing.map((m) => ` - ${m}`)
|
|
336
|
+
];
|
|
337
|
+
if (result.warnings.length > 0) {
|
|
338
|
+
lines.push('', 'Warnings:');
|
|
339
|
+
lines.push(...result.warnings.map((w) => ` - ${w}`));
|
|
340
|
+
}
|
|
341
|
+
lines.push('', 'Complete these items before finishing the stage.');
|
|
342
|
+
return lines.join('\n');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Returns pending blocking revisions for the active feature (or [] for legacy features).
|
|
346
|
+
// Safe to call when dossier feature dir does not exist — returns [] silently.
|
|
347
|
+
async function getBlockingRevisions(targetDir, featureSlug) {
|
|
348
|
+
if (!featureSlug) return [];
|
|
349
|
+
try {
|
|
350
|
+
const { getBlockingRevisions: getBlockers } = require('./dossier/revision-store');
|
|
351
|
+
const ctxDir = path.join(targetDir, '.aioson', 'context');
|
|
352
|
+
return await getBlockers({ slug: featureSlug, contextDir: ctxDir });
|
|
353
|
+
} catch {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
module.exports = {
|
|
359
|
+
validateHandoffContract,
|
|
360
|
+
formatContractError,
|
|
361
|
+
getBlockingRevisions,
|
|
362
|
+
CONTRACTS
|
|
363
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { readAgentManifest, canAgentPerform } = require('./agent-manifests');
|
|
4
|
+
|
|
5
|
+
async function validateHandoffProtocol(targetDir, protocol) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
if (!protocol || typeof protocol !== 'object') {
|
|
8
|
+
return { ok: false, errors: ['Missing handoff protocol payload'] };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const toAgentId = protocol.to && protocol.to.agent_id ? String(protocol.to.agent_id).trim() : '';
|
|
12
|
+
const requiredCapability = protocol.to && protocol.to.capability_required
|
|
13
|
+
? String(protocol.to.capability_required).trim()
|
|
14
|
+
: '';
|
|
15
|
+
|
|
16
|
+
if (protocol.to && typeof protocol.to === 'object' && !toAgentId) {
|
|
17
|
+
errors.push('Missing to.agent_id in handoff protocol');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (toAgentId) {
|
|
21
|
+
const toManifest = await readAgentManifest(targetDir, toAgentId);
|
|
22
|
+
if (!toManifest) {
|
|
23
|
+
errors.push(`Agent ${toAgentId} has no capability manifest`);
|
|
24
|
+
} else if (requiredCapability && !canAgentPerform(toManifest, requiredCapability)) {
|
|
25
|
+
errors.push(`Agent ${toAgentId} does not declare capability ${requiredCapability}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const validation = protocol.validation && typeof protocol.validation === 'object'
|
|
30
|
+
? protocol.validation
|
|
31
|
+
: {};
|
|
32
|
+
|
|
33
|
+
if (validation.handoff_contract_ok === false) {
|
|
34
|
+
errors.push('Handoff contract validation failed');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
ok: errors.length === 0,
|
|
39
|
+
errors
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
validateHandoffProtocol
|
|
45
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Circuit Breaker para o AIOSON — Módulo Puro
|
|
5
|
+
* Estados: CLOSED | OPEN | HALF_OPEN
|
|
6
|
+
* Persistência: progress.json
|
|
7
|
+
*/
|
|
8
|
+
class CircuitBreaker {
|
|
9
|
+
constructor(contractPath, progressPath) {
|
|
10
|
+
this.contractPath = contractPath;
|
|
11
|
+
this.progressPath = progressPath;
|
|
12
|
+
this.contract = null;
|
|
13
|
+
this.progress = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Carrega os arquivos de contrato e progresso do disco.
|
|
18
|
+
* Se o progresso estiver corrompido, tenta recuperar ou cria novo.
|
|
19
|
+
*/
|
|
20
|
+
async load() {
|
|
21
|
+
try {
|
|
22
|
+
this.contract = JSON.parse(fs.readFileSync(this.contractPath, 'utf8'));
|
|
23
|
+
} catch (err) {
|
|
24
|
+
throw new Error(`[CircuitBreaker] Falha ao carregar contrato: ${err.message}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(this.progressPath)) {
|
|
29
|
+
this.progress = JSON.parse(fs.readFileSync(this.progressPath, 'utf8'));
|
|
30
|
+
} else {
|
|
31
|
+
this.progress = this._createInitialProgress();
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// Recuperação em caso de JSON corrompido
|
|
35
|
+
console.warn(`[CircuitBreaker] Progress corrompido, recriando...`);
|
|
36
|
+
this.progress = this._createInitialProgress();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Verifica se uma nova iteração é permitida.
|
|
42
|
+
* Retorna { allowed: boolean, reason: string|null }
|
|
43
|
+
*/
|
|
44
|
+
check() {
|
|
45
|
+
if (!this.progress || !this.contract) return { allowed: true, reason: null };
|
|
46
|
+
|
|
47
|
+
const { circuit_state, iterations, consecutive_errors } = this.progress;
|
|
48
|
+
const { max_steps, error_streak_limit } = this.contract.governor;
|
|
49
|
+
|
|
50
|
+
if (circuit_state === 'OPEN') {
|
|
51
|
+
return { allowed: false, reason: 'circuit_open' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (max_steps > 0 && iterations >= max_steps) {
|
|
55
|
+
return { allowed: false, reason: 'max_steps_reached' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (error_streak_limit > 0 && consecutive_errors >= error_streak_limit) {
|
|
59
|
+
return { allowed: false, reason: 'error_streak_limit_reached' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { allowed: true, reason: null };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Registra um sucesso no loop.
|
|
67
|
+
* Reseta erros consecutivos e pode fechar o circuit se estiver HALF_OPEN.
|
|
68
|
+
*/
|
|
69
|
+
async recordSuccess() {
|
|
70
|
+
this.progress.consecutive_errors = 0;
|
|
71
|
+
this.progress.iterations += 1;
|
|
72
|
+
this.progress.last_updated = new Date().toISOString();
|
|
73
|
+
this.progress.ready_for_done_gate = true;
|
|
74
|
+
|
|
75
|
+
if (this.progress.circuit_state === 'HALF_OPEN') {
|
|
76
|
+
this.progress.circuit_state = 'CLOSED';
|
|
77
|
+
this.progress.status = 'in_progress';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await this._save();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Registra um erro no loop.
|
|
85
|
+
* Incrementa erros consecutivos e abre o circuit se atingir limites.
|
|
86
|
+
*/
|
|
87
|
+
async recordError(reason) {
|
|
88
|
+
this.progress.consecutive_errors += 1;
|
|
89
|
+
this.progress.last_error = reason;
|
|
90
|
+
this.progress.last_updated = new Date().toISOString();
|
|
91
|
+
this.progress.ready_for_done_gate = false;
|
|
92
|
+
|
|
93
|
+
const { error_streak_limit, max_steps } = this.contract.governor;
|
|
94
|
+
|
|
95
|
+
if (error_streak_limit > 0 && this.progress.consecutive_errors >= error_streak_limit) {
|
|
96
|
+
this.progress.circuit_state = 'OPEN';
|
|
97
|
+
this.progress.status = 'circuit_open';
|
|
98
|
+
this.progress.last_error = `error_streak_limit_reached: ${reason}`;
|
|
99
|
+
} else if (max_steps > 0 && this.progress.iterations >= max_steps) {
|
|
100
|
+
this.progress.circuit_state = 'OPEN';
|
|
101
|
+
this.progress.status = 'circuit_open';
|
|
102
|
+
this.progress.last_error = `max_steps_reached: ${reason}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await this._save();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_createInitialProgress() {
|
|
109
|
+
return {
|
|
110
|
+
feature: this.contract ? this.contract.feature : 'unknown',
|
|
111
|
+
phase: 1,
|
|
112
|
+
status: 'in_progress',
|
|
113
|
+
completed_steps: [],
|
|
114
|
+
last_error: null,
|
|
115
|
+
session_count: 1,
|
|
116
|
+
last_updated: new Date().toISOString(),
|
|
117
|
+
circuit_state: 'CLOSED',
|
|
118
|
+
iterations: 0,
|
|
119
|
+
consecutive_errors: 0,
|
|
120
|
+
ready_for_done_gate: false
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async _save() {
|
|
125
|
+
fs.writeFileSync(this.progressPath, JSON.stringify(this.progress, null, 2), 'utf8');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getState() {
|
|
129
|
+
return this.progress ? this.progress.circuit_state : 'CLOSED';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
createCircuitBreaker: (contractPath, progressPath) => new CircuitBreaker(contractPath, progressPath)
|
|
135
|
+
};
|