@jaimevalasek/aioson 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -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 +55 -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/copywriter.md +463 -0
- package/template/.aioson/agents/cypher.md +252 -0
- package/template/.aioson/agents/dev.md +112 -600
- package/template/.aioson/agents/deyvin.md +33 -235
- 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 +10 -8
- 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 +165 -410
- package/template/.aioson/agents/setup.md +52 -262
- package/template/.aioson/agents/sheldon.md +122 -754
- package/template/.aioson/agents/site-forge.md +111 -1583
- package/template/.aioson/agents/squad.md +139 -1820
- package/template/.aioson/agents/tester.md +10 -0
- package/template/.aioson/agents/ux-ui.md +103 -645
- 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.md +204 -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/design/cognitive-core-ui/references/motion.md +2 -0
- package/template/.aioson/skills/marketing/references/anti-patterns.md +254 -0
- package/template/.aioson/skills/marketing/references/fascinations.md +192 -0
- package/template/.aioson/skills/marketing/references/five-acts.md +248 -0
- package/template/.aioson/skills/marketing/references/market-intelligence.md +198 -0
- package/template/.aioson/skills/marketing/references/offer-structure.md +203 -0
- package/template/.aioson/skills/marketing/references/one-belief.md +149 -0
- package/template/.aioson/skills/marketing/references/patterns.md +218 -0
- package/template/.aioson/skills/marketing/references/pms-research.md +193 -0
- package/template/.aioson/skills/marketing/vsl-craft.md +385 -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/landing-page-deploy.md +192 -0
- package/template/.aioson/skills/static/landing-page-forge.md +730 -0
- package/template/.aioson/skills/static/ui-ux-modern.md +1 -0
- package/template/.aioson/skills/static/web-research-cache.md +3 -0
- package/template/.aioson/tasks/squad-create.md +56 -7
- package/template/.aioson/tasks/squad-design.md +80 -2
- package/template/.aioson/tasks/squad-investigate.md +14 -1
- package/template/.aioson/templates/squads/digital-marketing-agency/template.json +96 -0
- 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 +6 -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/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
|
@@ -1,113 +1,495 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* aioson workflow:execute —
|
|
4
|
+
* aioson workflow:execute — unified runner shell on top of workflow:next.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* aioson workflow:execute . --feature=checkout --tool=claude --dry-run
|
|
11
|
-
* aioson workflow:execute . --feature=checkout --tool=claude
|
|
12
|
-
* aioson workflow:execute . --feature=checkout --tool=claude --start-from=dev
|
|
13
|
-
* aioson workflow:execute . --feature=checkout --json --dry-run
|
|
6
|
+
* Dry-run previews the next actionable steps using the real workflow state.
|
|
7
|
+
* Execution advances one or more valid checkpoints and writes a resumable snapshot.
|
|
14
8
|
*/
|
|
15
9
|
|
|
10
|
+
const fs = require('node:fs/promises');
|
|
16
11
|
const path = require('node:path');
|
|
17
|
-
const { execSync } = require('node:child_process');
|
|
18
12
|
const {
|
|
19
13
|
detectClassification,
|
|
20
14
|
scanArtifacts,
|
|
21
15
|
readPhaseGates
|
|
22
16
|
} = require('../preflight-engine');
|
|
17
|
+
const {
|
|
18
|
+
readAutonomyProtocol,
|
|
19
|
+
getToolPolicy,
|
|
20
|
+
canRunHeadless,
|
|
21
|
+
resolveEffectiveMode
|
|
22
|
+
} = require('../autonomy-policy');
|
|
23
|
+
const { readAgentManifest } = require('../agent-manifests');
|
|
24
|
+
const { exists, ensureDir } = require('../utils');
|
|
25
|
+
const { validateHandoffContract } = require('../handoff-contract');
|
|
26
|
+
const { readHandoff, readHandoffProtocol } = require('../session-handoff');
|
|
27
|
+
const {
|
|
28
|
+
STATE_RELATIVE_PATH,
|
|
29
|
+
buildDefaultWorkflowConfig,
|
|
30
|
+
readWorkflowConfig,
|
|
31
|
+
runWorkflowNext
|
|
32
|
+
} = require('./workflow-next');
|
|
33
|
+
const { runWorkflowStatus } = require('./workflow-status');
|
|
34
|
+
const {
|
|
35
|
+
PARALLEL_RELATIVE_DIR,
|
|
36
|
+
collectWritePathConflicts,
|
|
37
|
+
extractStatusWritePathItems
|
|
38
|
+
} = require('../parallel-workspace');
|
|
23
39
|
|
|
24
40
|
const BAR = '━'.repeat(45);
|
|
41
|
+
const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
|
|
42
|
+
|
|
43
|
+
const STEP_META = {
|
|
44
|
+
setup: { description: 'Initialize project context', gate_before: null, gate_after: null },
|
|
45
|
+
product: { description: 'Generate PRD', gate_before: null, gate_after: null },
|
|
46
|
+
analyst: { description: 'Map requirements + spec', gate_before: null, gate_after: 'A' },
|
|
47
|
+
architect: { description: 'Architecture design', gate_before: 'A', gate_after: 'B' },
|
|
48
|
+
'ux-ui': { description: 'UI/UX design', gate_before: 'A', gate_after: 'B', optional: true },
|
|
49
|
+
pm: { description: 'Backlog + PM plan', gate_before: 'B', gate_after: 'C' },
|
|
50
|
+
orchestrator: { description: 'Coordinate execution lanes', gate_before: 'C', gate_after: null },
|
|
51
|
+
dev: { description: 'Implementation', gate_before: null, gate_after: 'C' },
|
|
52
|
+
qa: { description: 'QA + feature closure', gate_before: 'C', gate_after: 'D' },
|
|
53
|
+
committer: { description: 'Prepare commit', gate_before: 'D', gate_after: null }
|
|
54
|
+
};
|
|
25
55
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{ agent: 'product', gate_before: null, gate_after: null, description: 'Generate PRD' },
|
|
32
|
-
{ agent: 'sheldon', gate_before: null, gate_after: null, description: 'Enrich PRD (recommended)', optional: true },
|
|
33
|
-
{ agent: 'analyst', gate_before: null, gate_after: 'A', description: 'Map requirements + spec' },
|
|
34
|
-
{ agent: 'dev', gate_before: 'A', gate_after: 'C', description: 'Implementation' },
|
|
35
|
-
{ agent: 'qa', gate_before: 'C', gate_after: 'D', description: 'QA + feature closure' }
|
|
36
|
-
],
|
|
37
|
-
MEDIUM: [
|
|
38
|
-
{ agent: 'product', gate_before: null, gate_after: null, description: 'Generate PRD' },
|
|
39
|
-
{ agent: 'sheldon', gate_before: null, gate_after: null, description: 'Enrich PRD (required)', optional: false },
|
|
40
|
-
{ agent: 'analyst', gate_before: null, gate_after: 'A', description: 'Map requirements + spec' },
|
|
41
|
-
{ agent: 'architect', gate_before: 'A', gate_after: 'B', description: 'Architecture design' },
|
|
42
|
-
{ agent: 'ux-ui', gate_before: 'A', gate_after: 'B', description: 'UI/UX design', optional: true },
|
|
43
|
-
{ agent: 'pm', gate_before: 'B', gate_after: null, description: 'Backlog + PM plan' },
|
|
44
|
-
{ agent: 'dev', gate_before: 'C', gate_after: 'C', description: 'Implementation' },
|
|
45
|
-
{ agent: 'qa', gate_before: 'C', gate_after: 'D', description: 'QA + feature closure' }
|
|
46
|
-
]
|
|
56
|
+
const GATE_NAMES = {
|
|
57
|
+
A: 'requirements',
|
|
58
|
+
B: 'design',
|
|
59
|
+
C: 'plan',
|
|
60
|
+
D: 'execution'
|
|
47
61
|
};
|
|
48
62
|
|
|
49
|
-
const
|
|
63
|
+
const GATE_RESPONSIBLE_AGENT = {
|
|
64
|
+
A: '@analyst',
|
|
65
|
+
B: '@architect',
|
|
66
|
+
C: '@pm (for MEDIUM) or @dev (for SMALL/MICRO)',
|
|
67
|
+
D: '@qa'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
async function readJsonIfExists(filePath) {
|
|
71
|
+
if (!(await exists(filePath))) return null;
|
|
72
|
+
try {
|
|
73
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
74
|
+
return JSON.parse(raw);
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function writeJson(filePath, payload) {
|
|
81
|
+
await ensureDir(path.dirname(filePath));
|
|
82
|
+
await fs.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeClassification(value, fallback = 'SMALL') {
|
|
86
|
+
const safe = String(value || '').trim().toUpperCase();
|
|
87
|
+
return ['MICRO', 'SMALL', 'MEDIUM'].includes(safe) ? safe : fallback;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeAgentName(value) {
|
|
91
|
+
return String(value || '').trim().toLowerCase().replace(/^@/, '');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findNextFromSequence(sequence, completed, skipped = []) {
|
|
95
|
+
const done = new Set([...(completed || []), ...(skipped || [])].map(normalizeAgentName));
|
|
96
|
+
return sequence.find((stage) => !done.has(normalizeAgentName(stage))) || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getFocusStage(state) {
|
|
100
|
+
if (!state || typeof state !== 'object') return null;
|
|
101
|
+
return state.current || state.next || null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isGateApproved(gates, gateLetter) {
|
|
105
|
+
if (!gateLetter) return true;
|
|
106
|
+
const gateName = GATE_NAMES[gateLetter];
|
|
107
|
+
return gateName ? gates[gateName] === 'approved' : true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function resolveFeatureSequence(targetDir, classification) {
|
|
111
|
+
const fallback = buildDefaultWorkflowConfig();
|
|
112
|
+
try {
|
|
113
|
+
const { config } = await readWorkflowConfig(targetDir);
|
|
114
|
+
const sequence = config.feature && config.feature[classification];
|
|
115
|
+
return Array.isArray(sequence) && sequence.length > 0
|
|
116
|
+
? [...sequence]
|
|
117
|
+
: [...(fallback.feature[classification] || fallback.feature.SMALL)];
|
|
118
|
+
} catch {
|
|
119
|
+
return [...(fallback.feature[classification] || fallback.feature.SMALL)];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function inferCompletedStagesFromArtifacts(sequence, artifacts, gates) {
|
|
124
|
+
const completed = [];
|
|
125
|
+
|
|
126
|
+
for (const stage of sequence) {
|
|
127
|
+
const normalized = normalizeAgentName(stage);
|
|
128
|
+
let inferred = false;
|
|
129
|
+
|
|
130
|
+
if (normalized === 'product') {
|
|
131
|
+
inferred = Boolean(artifacts.prd && artifacts.prd.exists);
|
|
132
|
+
} else if (normalized === 'analyst') {
|
|
133
|
+
inferred = Boolean(artifacts.requirements && artifacts.requirements.exists && gates.requirements === 'approved');
|
|
134
|
+
} else if (normalized === 'architect') {
|
|
135
|
+
inferred = Boolean(artifacts.architecture && artifacts.architecture.exists && gates.design === 'approved');
|
|
136
|
+
} else if (normalized === 'pm') {
|
|
137
|
+
inferred = Boolean(artifacts.implementation_plan && artifacts.implementation_plan.exists && gates.plan === 'approved');
|
|
138
|
+
} else if (normalized === 'qa') {
|
|
139
|
+
inferred = gates.execution === 'approved';
|
|
140
|
+
} else {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!inferred) break;
|
|
145
|
+
completed.push(normalized);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return completed;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function seedFeatureWorkflowState(targetDir, slug, classification, startFrom) {
|
|
152
|
+
const statePath = path.join(targetDir, STATE_RELATIVE_PATH);
|
|
153
|
+
const existing = await readJsonIfExists(statePath);
|
|
154
|
+
const focusStage = getFocusStage(existing);
|
|
155
|
+
|
|
156
|
+
if (
|
|
157
|
+
existing &&
|
|
158
|
+
existing.mode === 'feature' &&
|
|
159
|
+
existing.featureSlug &&
|
|
160
|
+
existing.featureSlug !== slug &&
|
|
161
|
+
focusStage
|
|
162
|
+
) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
reason: 'different_active_feature',
|
|
166
|
+
active_feature: existing.featureSlug,
|
|
167
|
+
active_stage: focusStage
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (existing && existing.mode === 'feature' && existing.featureSlug === slug) {
|
|
172
|
+
return {
|
|
173
|
+
ok: true,
|
|
174
|
+
resumed: true,
|
|
175
|
+
state: existing,
|
|
176
|
+
statePath: STATE_RELATIVE_PATH
|
|
177
|
+
};
|
|
178
|
+
}
|
|
50
179
|
|
|
51
|
-
async function buildExecutionPlan(targetDir, slug, classification, startFrom) {
|
|
52
|
-
const steps = WORKFLOW_BY_CLASSIFICATION[classification] || WORKFLOW_BY_CLASSIFICATION.SMALL;
|
|
53
180
|
const artifacts = await scanArtifacts(targetDir, slug);
|
|
54
181
|
const gates = await readPhaseGates(targetDir, slug);
|
|
182
|
+
const sequence = await resolveFeatureSequence(targetDir, classification);
|
|
183
|
+
const completed = inferCompletedStagesFromArtifacts(sequence, artifacts, gates);
|
|
184
|
+
const normalizedStartFrom = startFrom ? normalizeAgentName(startFrom) : null;
|
|
185
|
+
|
|
186
|
+
let skipped = [];
|
|
187
|
+
let next = findNextFromSequence(sequence, completed, skipped);
|
|
188
|
+
|
|
189
|
+
if (normalizedStartFrom && sequence.includes(normalizedStartFrom)) {
|
|
190
|
+
const targetIndex = sequence.indexOf(normalizedStartFrom);
|
|
191
|
+
skipped = sequence.slice(0, targetIndex).filter((stage) => !completed.includes(stage));
|
|
192
|
+
next = normalizedStartFrom;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const state = {
|
|
196
|
+
version: 1,
|
|
197
|
+
mode: 'feature',
|
|
198
|
+
classification,
|
|
199
|
+
sequence,
|
|
200
|
+
current: null,
|
|
201
|
+
next,
|
|
202
|
+
completed,
|
|
203
|
+
skipped,
|
|
204
|
+
featureSlug: slug,
|
|
205
|
+
detour: null,
|
|
206
|
+
updatedAt: new Date().toISOString()
|
|
207
|
+
};
|
|
55
208
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
209
|
+
await writeJson(statePath, state);
|
|
210
|
+
return {
|
|
211
|
+
ok: true,
|
|
212
|
+
resumed: false,
|
|
213
|
+
state,
|
|
214
|
+
statePath: STATE_RELATIVE_PATH
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function buildExecutionPlan(targetDir, slug, classification, workflowState = null, startFrom = null) {
|
|
219
|
+
const sequence = workflowState && Array.isArray(workflowState.sequence) && workflowState.sequence.length > 0
|
|
220
|
+
? [...workflowState.sequence]
|
|
221
|
+
: await resolveFeatureSequence(targetDir, classification);
|
|
222
|
+
const artifacts = await scanArtifacts(targetDir, slug);
|
|
223
|
+
const gates = await readPhaseGates(targetDir, slug);
|
|
224
|
+
const normalizedStartFrom = startFrom ? normalizeAgentName(startFrom) : null;
|
|
225
|
+
const focusStage = getFocusStage(workflowState);
|
|
226
|
+
const inferredCompleted = inferCompletedStagesFromArtifacts(sequence, artifacts, gates);
|
|
227
|
+
const completedSet = new Set((workflowState && workflowState.completed) || inferredCompleted);
|
|
228
|
+
const skippedSet = new Set((workflowState && workflowState.skipped) || []);
|
|
229
|
+
|
|
230
|
+
const applyStartFrom = Boolean(
|
|
231
|
+
normalizedStartFrom &&
|
|
232
|
+
sequence.includes(normalizedStartFrom) &&
|
|
233
|
+
(!workflowState || (!workflowState.current && workflowState.next === normalizedStartFrom))
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const scopedSequence = applyStartFrom
|
|
237
|
+
? sequence.slice(sequence.indexOf(normalizedStartFrom)).filter(Boolean)
|
|
238
|
+
: sequence;
|
|
239
|
+
|
|
240
|
+
const steps = [];
|
|
241
|
+
for (const [index, agent] of scopedSequence.entries()) {
|
|
242
|
+
const meta = STEP_META[agent] || {
|
|
243
|
+
description: `Execute @${agent}`,
|
|
244
|
+
gate_before: null,
|
|
245
|
+
gate_after: null,
|
|
246
|
+
optional: false
|
|
247
|
+
};
|
|
59
248
|
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
249
|
+
let status = 'pending';
|
|
250
|
+
if (completedSet.has(agent)) status = 'completed';
|
|
251
|
+
else if (skippedSet.has(agent)) status = 'skipped';
|
|
252
|
+
else if (focusStage === agent) status = 'active';
|
|
253
|
+
|
|
254
|
+
const predictedBlockers = [];
|
|
255
|
+
if (status !== 'completed' && status !== 'skipped' && meta.gate_before && !isGateApproved(gates, meta.gate_before)) {
|
|
256
|
+
const responsible = GATE_RESPONSIBLE_AGENT[meta.gate_before] || 'previous agent';
|
|
257
|
+
const featureArg = slug ? ` --feature=${slug}` : '';
|
|
258
|
+
predictedBlockers.push(
|
|
259
|
+
`Gate ${meta.gate_before} (${GATE_NAMES[meta.gate_before] || meta.gate_before}) not approved — ` +
|
|
260
|
+
`responsible: ${responsible} — ` +
|
|
261
|
+
`approve with: aioson gate:approve .${featureArg} --gate=${meta.gate_before}`
|
|
262
|
+
);
|
|
263
|
+
if (status === 'pending') status = 'blocked';
|
|
64
264
|
}
|
|
65
265
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
skip = true; skipReason = 'prd already exists';
|
|
72
|
-
} else if (step.agent === 'sheldon' && artifacts.sheldon_enrichment.exists) {
|
|
73
|
-
skip = true; skipReason = 'sheldon enrichment already exists';
|
|
74
|
-
} else if (step.agent === 'analyst' && artifacts.requirements.exists && gates.requirements === 'approved') {
|
|
75
|
-
skip = true; skipReason = 'requirements + Gate A already approved';
|
|
76
|
-
} else if (step.agent === 'architect' && artifacts.architecture.exists && gates.design === 'approved') {
|
|
77
|
-
skip = true; skipReason = 'architecture + Gate B already approved';
|
|
266
|
+
if (status === 'active' && workflowState) {
|
|
267
|
+
const contractCheck = await validateHandoffContract(targetDir, workflowState, agent);
|
|
268
|
+
if (!contractCheck.ok) {
|
|
269
|
+
predictedBlockers.push(...contractCheck.missing);
|
|
270
|
+
}
|
|
78
271
|
}
|
|
79
272
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
273
|
+
const skip = status === 'skipped' || (
|
|
274
|
+
status === 'completed' &&
|
|
275
|
+
inferredCompleted.includes(agent)
|
|
276
|
+
);
|
|
277
|
+
const skipReason = status === 'skipped'
|
|
278
|
+
? 'workflow state marks this stage as skipped'
|
|
279
|
+
: skip
|
|
280
|
+
? 'artifacts already satisfy this stage'
|
|
281
|
+
: null;
|
|
282
|
+
|
|
283
|
+
steps.push({
|
|
284
|
+
step: index + 1,
|
|
285
|
+
agent,
|
|
286
|
+
description: meta.description,
|
|
287
|
+
gate_before: meta.gate_before,
|
|
288
|
+
gate_after: meta.gate_after,
|
|
289
|
+
optional: Boolean(meta.optional),
|
|
290
|
+
status,
|
|
87
291
|
skip,
|
|
88
|
-
skip_reason: skipReason
|
|
292
|
+
skip_reason: skipReason,
|
|
293
|
+
predicted_blockers: predictedBlockers
|
|
89
294
|
});
|
|
90
295
|
}
|
|
91
296
|
|
|
92
|
-
return
|
|
297
|
+
return {
|
|
298
|
+
steps,
|
|
299
|
+
artifacts,
|
|
300
|
+
gates,
|
|
301
|
+
sequence
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function buildCheckpointPayload(activation, handoff, handoffProtocol) {
|
|
306
|
+
const safeActivation = activation && typeof activation === 'object' ? activation : {};
|
|
307
|
+
return {
|
|
308
|
+
active_stage: safeActivation.agent || null,
|
|
309
|
+
current: safeActivation.current || null,
|
|
310
|
+
next: safeActivation.next || null,
|
|
311
|
+
effective_mode: safeActivation.effectiveMode || null,
|
|
312
|
+
instruction_path: safeActivation.instructionPath || null,
|
|
313
|
+
prompt: safeActivation.prompt || null,
|
|
314
|
+
handoff,
|
|
315
|
+
handoff_protocol: handoffProtocol
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function writeExecutionCheckpoint(targetDir, payload) {
|
|
320
|
+
const execPath = path.join(targetDir, EXECUTION_STATE_RELATIVE_PATH);
|
|
321
|
+
const existing = await readJsonIfExists(execPath);
|
|
322
|
+
const history = Array.isArray(existing && existing.history) ? [...existing.history] : [];
|
|
323
|
+
if (payload.checkpoint) {
|
|
324
|
+
history.push({
|
|
325
|
+
at: new Date().toISOString(),
|
|
326
|
+
active_stage: payload.checkpoint.active_stage || null,
|
|
327
|
+
next: payload.checkpoint.next || null,
|
|
328
|
+
effective_mode: payload.checkpoint.effective_mode || null,
|
|
329
|
+
handoff_to: payload.checkpoint.handoff && payload.checkpoint.handoff.next_agent
|
|
330
|
+
? String(payload.checkpoint.handoff.next_agent).replace(/^@/, '')
|
|
331
|
+
: null
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const nextPayload = {
|
|
335
|
+
version: 1,
|
|
336
|
+
feature: payload.feature,
|
|
337
|
+
classification: payload.classification,
|
|
338
|
+
tool: payload.tool,
|
|
339
|
+
requested_mode: payload.requestedMode || null,
|
|
340
|
+
status: payload.status,
|
|
341
|
+
started_at: existing && existing.feature === payload.feature ? existing.started_at : new Date().toISOString(),
|
|
342
|
+
updated_at: new Date().toISOString(),
|
|
343
|
+
resumed_count: existing && existing.feature === payload.feature ? Number(existing.resumed_count || 0) + (payload.resumed ? 1 : 0) : (payload.resumed ? 1 : 0),
|
|
344
|
+
workflow_state_path: STATE_RELATIVE_PATH,
|
|
345
|
+
checkpoint: payload.checkpoint || null,
|
|
346
|
+
status_snapshot: payload.statusSnapshot || null,
|
|
347
|
+
suggestion: payload.suggestion || null,
|
|
348
|
+
resume_command: payload.resumeCommand || null,
|
|
349
|
+
history
|
|
350
|
+
};
|
|
351
|
+
await writeJson(execPath, nextPayload);
|
|
352
|
+
return nextPayload;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function readStatusSnapshot(targetDir, tool, t) {
|
|
356
|
+
return runWorkflowStatus({
|
|
357
|
+
args: [targetDir],
|
|
358
|
+
options: { json: true, tool },
|
|
359
|
+
logger: { log() {}, error() {} },
|
|
360
|
+
t
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function parseLaneStatusIndex(fileName) {
|
|
365
|
+
const match = String(fileName || '').match(/^agent-(\d+)\.status\.md$/);
|
|
366
|
+
if (!match) return null;
|
|
367
|
+
const value = Number(match[1]);
|
|
368
|
+
if (!Number.isFinite(value) || value <= 0) return null;
|
|
369
|
+
return Math.floor(value);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function readLaneWritePaths(parallelDir, index) {
|
|
373
|
+
const absPath = path.join(parallelDir, `agent-${index}.status.md`);
|
|
374
|
+
try {
|
|
375
|
+
const content = await fs.readFile(absPath, 'utf8');
|
|
376
|
+
return { lane: index, writePathItems: extractStatusWritePathItems(content) };
|
|
377
|
+
} catch {
|
|
378
|
+
return { lane: index, writePathItems: [] };
|
|
379
|
+
}
|
|
93
380
|
}
|
|
94
381
|
|
|
95
|
-
function
|
|
382
|
+
async function runLaneGuardPreflight(targetDir, laneIndex) {
|
|
383
|
+
const parallelDir = path.join(targetDir, PARALLEL_RELATIVE_DIR);
|
|
384
|
+
if (!(await exists(parallelDir))) {
|
|
385
|
+
return { ok: true, skipped: true, reason: 'no_parallel_workspace' };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let entries;
|
|
96
389
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
390
|
+
entries = await fs.readdir(parallelDir);
|
|
391
|
+
} catch {
|
|
392
|
+
return { ok: true, skipped: true, reason: 'parallel_dir_unreadable' };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const laneIndices = entries
|
|
396
|
+
.map(parseLaneStatusIndex)
|
|
397
|
+
.filter((v) => v !== null)
|
|
398
|
+
.sort((a, b) => a - b);
|
|
399
|
+
|
|
400
|
+
if (!laneIndices.includes(laneIndex)) {
|
|
401
|
+
return { ok: false, skipped: false, reason: 'lane_not_found', lane: laneIndex };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const lanes = await Promise.all(laneIndices.map((i) => readLaneWritePaths(parallelDir, i)));
|
|
405
|
+
const conflictReport = collectWritePathConflicts(lanes);
|
|
406
|
+
const laneConflicts = conflictReport.conflicts.filter((c) =>
|
|
407
|
+
Array.isArray(c.lanes) && c.lanes.includes(laneIndex)
|
|
408
|
+
);
|
|
409
|
+
const targetLane = lanes.find((l) => l.lane === laneIndex);
|
|
410
|
+
const writePathCount = targetLane ? targetLane.writePathItems.length : 0;
|
|
411
|
+
const unassigned = writePathCount === 0;
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
ok: laneConflicts.length === 0 && !unassigned,
|
|
415
|
+
skipped: false,
|
|
416
|
+
lane: laneIndex,
|
|
417
|
+
writePathCount,
|
|
418
|
+
unassigned,
|
|
419
|
+
conflictCount: laneConflicts.length,
|
|
420
|
+
conflicts: laneConflicts,
|
|
421
|
+
invalidCount: conflictReport.invalidCount,
|
|
422
|
+
invalidPatterns: conflictReport.invalidPatterns
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function performRunnerTransition(targetDir, suggestion, tool, requestedMode, logger, t) {
|
|
427
|
+
if (!suggestion || !suggestion.action) {
|
|
428
|
+
return { ok: false, reason: 'missing_suggestion' };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (suggestion.action === 'workflow_complete') {
|
|
432
|
+
return { ok: true, transition: 'complete', result: null };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (suggestion.action === 'activate_stage' || suggestion.action === 'continue_stage') {
|
|
436
|
+
const result = await runWorkflowNext({
|
|
437
|
+
args: [targetDir],
|
|
438
|
+
options: {
|
|
439
|
+
tool,
|
|
440
|
+
...(requestedMode ? { mode: requestedMode } : {}),
|
|
441
|
+
...(suggestion.agent ? { agent: suggestion.agent } : {})
|
|
442
|
+
},
|
|
443
|
+
logger,
|
|
444
|
+
t
|
|
445
|
+
});
|
|
446
|
+
return {
|
|
447
|
+
ok: true,
|
|
448
|
+
transition: 'activate',
|
|
449
|
+
agent: suggestion.agent || null,
|
|
450
|
+
result
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (suggestion.action === 'complete_stage' && suggestion.agent) {
|
|
455
|
+
const safeAgent = normalizeAgentName(suggestion.agent);
|
|
456
|
+
const result = await runWorkflowNext({
|
|
457
|
+
args: [targetDir],
|
|
458
|
+
options: {
|
|
459
|
+
tool,
|
|
460
|
+
complete: safeAgent,
|
|
461
|
+
...(requestedMode ? { mode: requestedMode } : {}),
|
|
462
|
+
...((safeAgent === 'dev' || safeAgent === 'qa') ? { 'auto-heal': true } : {})
|
|
463
|
+
},
|
|
464
|
+
logger,
|
|
465
|
+
t
|
|
466
|
+
});
|
|
467
|
+
return {
|
|
468
|
+
ok: true,
|
|
469
|
+
transition: 'complete',
|
|
470
|
+
agent: safeAgent,
|
|
471
|
+
result
|
|
472
|
+
};
|
|
101
473
|
}
|
|
474
|
+
|
|
475
|
+
return { ok: false, reason: 'unsupported_suggestion_action', action: suggestion.action };
|
|
102
476
|
}
|
|
103
477
|
|
|
104
478
|
async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
105
479
|
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
106
|
-
const slug = options.feature ? String(options.feature) : null;
|
|
107
|
-
const tool = options.tool ? String(options.tool) : 'claude';
|
|
480
|
+
const slug = options.feature ? String(options.feature).trim() : null;
|
|
481
|
+
const tool = options.tool ? String(options.tool).trim() : 'claude';
|
|
482
|
+
const requestedMode = options.mode ? String(options.mode).trim() : null;
|
|
108
483
|
const dryRun = Boolean(options['dry-run'] || options.dry);
|
|
109
|
-
const startFrom = options['start-from'] ? String(options['start-from']) : null;
|
|
484
|
+
const startFrom = options['start-from'] ? String(options['start-from']).trim() : null;
|
|
110
485
|
const skipOptional = Boolean(options['skip-optional']);
|
|
486
|
+
const parsedMaxCheckpoints = Number.parseInt(String(options['max-checkpoints'] || '1'), 10);
|
|
487
|
+
const maxCheckpoints = Number.isInteger(parsedMaxCheckpoints) && parsedMaxCheckpoints > 0
|
|
488
|
+
? parsedMaxCheckpoints
|
|
489
|
+
: 1;
|
|
490
|
+
const parsedLane = options.lane !== undefined ? Number(options.lane) : null;
|
|
491
|
+
const laneIndex = Number.isInteger(parsedLane) && parsedLane > 0 ? parsedLane : null;
|
|
492
|
+
const t = (key, payload) => payload?.agent || payload?.stage || key;
|
|
111
493
|
|
|
112
494
|
if (!slug) {
|
|
113
495
|
if (options.json) return { ok: false, reason: 'missing_feature' };
|
|
@@ -116,22 +498,89 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
116
498
|
}
|
|
117
499
|
|
|
118
500
|
let classification = await detectClassification(targetDir, slug);
|
|
119
|
-
if (!classification)
|
|
501
|
+
if (!classification) {
|
|
502
|
+
classification = options.classification ? String(options.classification).toUpperCase() : 'SMALL';
|
|
503
|
+
}
|
|
504
|
+
classification = normalizeClassification(classification, 'SMALL');
|
|
505
|
+
|
|
506
|
+
const autonomyProtocol = await readAutonomyProtocol(targetDir);
|
|
507
|
+
const toolPolicy = getToolPolicy(autonomyProtocol, tool);
|
|
508
|
+
if (requestedMode === 'headless' && !canRunHeadless(toolPolicy)) {
|
|
509
|
+
const failure = { ok: false, reason: 'headless_not_supported', tool };
|
|
510
|
+
if (options.json) return failure;
|
|
511
|
+
logger.error(`Tool ${tool} does not support headless mode. Fallback required.`);
|
|
512
|
+
return failure;
|
|
513
|
+
}
|
|
120
514
|
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
515
|
+
const parallelGuard = laneIndex !== null
|
|
516
|
+
? await runLaneGuardPreflight(targetDir, laneIndex)
|
|
517
|
+
: null;
|
|
124
518
|
|
|
125
|
-
if (
|
|
519
|
+
if (parallelGuard && !parallelGuard.ok && !parallelGuard.skipped) {
|
|
520
|
+
if (parallelGuard.reason === 'lane_not_found') {
|
|
521
|
+
const failure = { ok: false, reason: 'parallel_lane_not_found', lane: laneIndex };
|
|
522
|
+
if (options.json) return failure;
|
|
523
|
+
logger.error(`Lane ${laneIndex} not found in parallel workspace.`);
|
|
524
|
+
return failure;
|
|
525
|
+
}
|
|
526
|
+
if (parallelGuard.conflictCount > 0) {
|
|
527
|
+
logger.error(
|
|
528
|
+
`[parallel:guard] Lane ${laneIndex} has ${parallelGuard.conflictCount} write-scope conflict(s) with other lanes. Proceeding with warning.`
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
if (parallelGuard.unassigned) {
|
|
532
|
+
logger.error(
|
|
533
|
+
`[parallel:guard] Lane ${laneIndex} has no write paths declared. Run parallel:assign before executing.`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const seeded = await seedFeatureWorkflowState(targetDir, slug, classification, startFrom);
|
|
539
|
+
if (!seeded.ok) {
|
|
540
|
+
if (options.json) return seeded;
|
|
541
|
+
logger.error(
|
|
542
|
+
`Another active feature workflow is in progress: ${seeded.active_feature} (@${seeded.active_stage}).`
|
|
543
|
+
);
|
|
544
|
+
return seeded;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const planData = await buildExecutionPlan(targetDir, slug, classification, seeded.state, startFrom);
|
|
548
|
+
for (const step of planData.steps) {
|
|
549
|
+
const manifest = await readAgentManifest(targetDir, step.agent);
|
|
550
|
+
step.effective_mode = resolveEffectiveMode({
|
|
551
|
+
protocol: autonomyProtocol,
|
|
552
|
+
tool,
|
|
553
|
+
agentId: step.agent,
|
|
554
|
+
manifest,
|
|
555
|
+
requestedMode
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const activePlan = planData.steps.filter((step) => step.status !== 'completed' && !(skipOptional && step.optional));
|
|
560
|
+
const blockedSteps = planData.steps.filter((step) => step.predicted_blockers.length > 0);
|
|
561
|
+
const statusSnapshot = await readStatusSnapshot(targetDir, tool, t);
|
|
562
|
+
const resumeCommand = `aioson workflow:execute ${targetDir} --feature=${slug} --tool=${tool}${requestedMode ? ` --mode=${requestedMode}` : ''}${maxCheckpoints !== 1 ? ` --max-checkpoints=${maxCheckpoints}` : ''}`;
|
|
563
|
+
|
|
564
|
+
if (dryRun) {
|
|
126
565
|
const result = {
|
|
127
566
|
ok: true,
|
|
128
567
|
feature: slug,
|
|
129
568
|
classification,
|
|
130
569
|
tool,
|
|
570
|
+
requested_mode: requestedMode,
|
|
131
571
|
dry_run: true,
|
|
132
|
-
|
|
572
|
+
resumed: seeded.resumed,
|
|
573
|
+
state_path: seeded.statePath,
|
|
574
|
+
execution_state_path: EXECUTION_STATE_RELATIVE_PATH,
|
|
575
|
+
max_checkpoints: maxCheckpoints,
|
|
576
|
+
steps: planData.steps,
|
|
133
577
|
active_steps: activePlan.length,
|
|
134
|
-
|
|
578
|
+
blocked_steps: blockedSteps.length,
|
|
579
|
+
gates: planData.gates,
|
|
580
|
+
status_snapshot: statusSnapshot,
|
|
581
|
+
suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
|
|
582
|
+
resume_command: resumeCommand,
|
|
583
|
+
parallel_guard: parallelGuard
|
|
135
584
|
};
|
|
136
585
|
|
|
137
586
|
if (options.json) return result;
|
|
@@ -139,103 +588,143 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
139
588
|
logger.log('');
|
|
140
589
|
logger.log(`Workflow Execution Plan — ${slug} (${classification})`);
|
|
141
590
|
logger.log(BAR);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
591
|
+
for (const step of planData.steps) {
|
|
592
|
+
const gateInfo = step.gate_after ? ` → Gate ${step.gate_after}` : '';
|
|
593
|
+
const statusInfo = `[${step.status}]`;
|
|
594
|
+
const modeInfo = step.effective_mode ? ` [mode=${step.effective_mode}]` : '';
|
|
595
|
+
logger.log(`Step ${step.step}: @${step.agent} ${statusInfo} — ${step.description}${gateInfo}${modeInfo}`);
|
|
596
|
+
for (const blocker of step.predicted_blockers) {
|
|
597
|
+
logger.log(` - blocker: ${blocker}`);
|
|
598
|
+
}
|
|
149
599
|
}
|
|
150
|
-
|
|
151
600
|
logger.log('');
|
|
152
|
-
logger.log(`
|
|
153
|
-
logger.log(`
|
|
154
|
-
|
|
155
|
-
|
|
601
|
+
logger.log(`Blocked steps: ${blockedSteps.length} | Remaining: ${activePlan.length}`);
|
|
602
|
+
logger.log(`Resume state: ${seeded.resumed ? 'existing workflow state reused' : 'new workflow state seeded'}`);
|
|
603
|
+
if (statusSnapshot && statusSnapshot.suggestion && statusSnapshot.suggestion.command) {
|
|
604
|
+
logger.log(`Suggested command: ${statusSnapshot.suggestion.command}`);
|
|
605
|
+
}
|
|
156
606
|
logger.log('');
|
|
157
|
-
|
|
158
607
|
return result;
|
|
159
608
|
}
|
|
160
609
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
610
|
+
const executionTransitions = [];
|
|
611
|
+
let activation = null;
|
|
612
|
+
let currentStatus = statusSnapshot;
|
|
613
|
+
|
|
614
|
+
for (let index = 0; index < maxCheckpoints; index += 1) {
|
|
615
|
+
const suggestion = currentStatus && currentStatus.suggestion ? currentStatus.suggestion : null;
|
|
616
|
+
let transitionResult;
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
transitionResult = await performRunnerTransition(
|
|
620
|
+
targetDir,
|
|
621
|
+
suggestion,
|
|
622
|
+
tool,
|
|
623
|
+
requestedMode,
|
|
624
|
+
logger,
|
|
625
|
+
t
|
|
626
|
+
);
|
|
627
|
+
} catch (error) {
|
|
628
|
+
const failure = {
|
|
629
|
+
ok: false,
|
|
630
|
+
reason: 'workflow_next_failed',
|
|
631
|
+
feature: slug,
|
|
632
|
+
classification,
|
|
633
|
+
message: error.message,
|
|
634
|
+
transitions: executionTransitions
|
|
635
|
+
};
|
|
636
|
+
if (options.json) return failure;
|
|
637
|
+
logger.error(error.message);
|
|
638
|
+
return failure;
|
|
175
639
|
}
|
|
176
640
|
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
641
|
+
if (!transitionResult.ok) {
|
|
642
|
+
const failure = {
|
|
643
|
+
ok: false,
|
|
644
|
+
reason: transitionResult.reason || 'runner_transition_failed',
|
|
645
|
+
feature: slug,
|
|
646
|
+
classification,
|
|
647
|
+
transitions: executionTransitions
|
|
648
|
+
};
|
|
649
|
+
if (options.json) return failure;
|
|
650
|
+
logger.error(`Runner transition failed: ${failure.reason}`);
|
|
651
|
+
return failure;
|
|
181
652
|
}
|
|
182
653
|
|
|
183
|
-
|
|
184
|
-
if (step.gate_before) {
|
|
185
|
-
logger.log(`[Gate ${step.gate_before} check] pre-step ${step.step}...`);
|
|
186
|
-
const gateCmd = `aioson gate:check ${targetDir} --feature=${slug} --gate=${step.gate_before}`;
|
|
187
|
-
const gateResult = runCommand(gateCmd);
|
|
188
|
-
if (!gateResult.ok) {
|
|
189
|
-
logger.log(`[Gate ${step.gate_before}] BLOCKED — cannot proceed with @${step.agent}`);
|
|
190
|
-
executionLog.push({ step: step.step, agent: step.agent, status: 'blocked', gate: step.gate_before });
|
|
191
|
-
failed++;
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
logger.log(`[Step ${step.step}/${activePlan.length}] @${step.agent} — ${step.description}...`);
|
|
197
|
-
const agentCmd = `aioson agent:prompt ${targetDir} --agent=${step.agent} --feature=${slug} --tool=${tool}`;
|
|
198
|
-
const agentResult = runCommand(agentCmd);
|
|
199
|
-
|
|
200
|
-
if (!agentResult.ok) {
|
|
201
|
-
logger.log(`[Step ${step.step}] @${step.agent} FAILED`);
|
|
202
|
-
executionLog.push({ step: step.step, agent: step.agent, status: 'failed' });
|
|
203
|
-
failed++;
|
|
654
|
+
if (transitionResult.transition === 'complete' && !transitionResult.result) {
|
|
204
655
|
break;
|
|
205
656
|
}
|
|
206
657
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
658
|
+
activation = transitionResult.result || activation;
|
|
659
|
+
executionTransitions.push({
|
|
660
|
+
index: index + 1,
|
|
661
|
+
transition: transitionResult.transition,
|
|
662
|
+
agent: transitionResult.agent || null,
|
|
663
|
+
active_stage: activation && activation.agent ? activation.agent : null,
|
|
664
|
+
next_stage: activation && activation.next ? activation.next : null,
|
|
665
|
+
completed_stage: activation && activation.completedStage ? activation.completedStage : null
|
|
666
|
+
});
|
|
211
667
|
|
|
212
|
-
|
|
213
|
-
const pulseCmd = `aioson pulse:update ${targetDir} --agent=${step.agent} --feature=${slug} --action="${step.description}" 2>/dev/null || true`;
|
|
214
|
-
runCommand(pulseCmd);
|
|
668
|
+
currentStatus = await readStatusSnapshot(targetDir, tool, t);
|
|
215
669
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const allDone = completed === activePlan.length;
|
|
222
|
-
if (allDone) {
|
|
223
|
-
logger.log('');
|
|
224
|
-
logger.log(`Workflow complete: ${slug} → done`);
|
|
225
|
-
logger.log(`Total sessions: ${completed}`);
|
|
226
|
-
} else {
|
|
227
|
-
logger.log('');
|
|
228
|
-
logger.log(`Workflow stopped: ${completed} completed, ${failed} failed.`);
|
|
670
|
+
const nextSuggestion = currentStatus && currentStatus.suggestion ? currentStatus.suggestion : null;
|
|
671
|
+
if (!nextSuggestion || nextSuggestion.action === 'continue_stage' || nextSuggestion.action === 'workflow_complete') {
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
229
674
|
}
|
|
230
675
|
|
|
231
|
-
|
|
232
|
-
|
|
676
|
+
const handoff = await readHandoff(targetDir);
|
|
677
|
+
const handoffProtocol = await readHandoffProtocol(targetDir);
|
|
678
|
+
const refreshedStatus = currentStatus || await readStatusSnapshot(targetDir, tool, t);
|
|
679
|
+
const executionState = await writeExecutionCheckpoint(targetDir, {
|
|
233
680
|
feature: slug,
|
|
234
681
|
classification,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
682
|
+
tool,
|
|
683
|
+
requestedMode,
|
|
684
|
+
resumed: seeded.resumed,
|
|
685
|
+
status: activation && activation.agent ? 'active' : 'completed',
|
|
686
|
+
checkpoint: buildCheckpointPayload(activation, handoff, handoffProtocol),
|
|
687
|
+
statusSnapshot: refreshedStatus,
|
|
688
|
+
suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
|
|
689
|
+
resumeCommand
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const result = {
|
|
693
|
+
ok: true,
|
|
694
|
+
feature: slug,
|
|
695
|
+
classification,
|
|
696
|
+
tool,
|
|
697
|
+
requested_mode: requestedMode,
|
|
698
|
+
resumed: seeded.resumed,
|
|
699
|
+
state_path: seeded.statePath,
|
|
700
|
+
execution_state_path: EXECUTION_STATE_RELATIVE_PATH,
|
|
701
|
+
max_checkpoints: maxCheckpoints,
|
|
702
|
+
checkpoint: executionState.checkpoint,
|
|
703
|
+
execution_state: executionState,
|
|
704
|
+
status_snapshot: refreshedStatus,
|
|
705
|
+
suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
|
|
706
|
+
resume_command: resumeCommand,
|
|
707
|
+
transitions: executionTransitions,
|
|
708
|
+
active_stage: activation && activation.agent ? activation.agent : null,
|
|
709
|
+
next_stage: activation && activation.next ? activation.next : null,
|
|
710
|
+
handoff,
|
|
711
|
+
handoff_protocol: handoffProtocol,
|
|
712
|
+
parallel_guard: parallelGuard
|
|
238
713
|
};
|
|
714
|
+
|
|
715
|
+
if (!options.json) {
|
|
716
|
+
logger.log('');
|
|
717
|
+
logger.log(`Workflow checkpoint stored at ${EXECUTION_STATE_RELATIVE_PATH}`);
|
|
718
|
+
logger.log(`Feature: ${slug}`);
|
|
719
|
+
logger.log(`Resumed: ${seeded.resumed ? 'yes' : 'no'}`);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return result;
|
|
239
723
|
}
|
|
240
724
|
|
|
241
|
-
module.exports = {
|
|
725
|
+
module.exports = {
|
|
726
|
+
EXECUTION_STATE_RELATIVE_PATH,
|
|
727
|
+
buildExecutionPlan,
|
|
728
|
+
seedFeatureWorkflowState,
|
|
729
|
+
runWorkflowExecute
|
|
730
|
+
};
|