@jaimevalasek/aioson 1.3.0 → 1.5.1
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 +31 -1
- package/LICENSE +661 -21
- package/README.md +22 -3
- package/docs/en/squad-dashboard.md +372 -0
- package/docs/openclaw-bridge.md +308 -0
- package/docs/pt/README.md +62 -2
- package/docs/pt/advisor-spec.md +5 -5
- package/docs/pt/agentes-customizados.md +670 -0
- package/docs/pt/agentes.md +235 -23
- package/docs/pt/automacao-squads.md +407 -0
- package/docs/pt/cenarios.md +49 -5
- package/docs/pt/clientes-ai.md +62 -0
- package/docs/pt/comandos-cli.md +226 -17
- package/docs/pt/deyvin.md +115 -0
- package/docs/pt/genome-3.0-spec.md +11 -11
- package/docs/pt/inicio-rapido.md +63 -2
- package/docs/pt/memoria-contexto.md +255 -0
- package/docs/pt/output-strategy-delivery.md +655 -0
- package/docs/pt/profiler-system.md +17 -17
- package/docs/pt/runtime-observability.md +5 -1
- package/docs/pt/skills.md +175 -0
- package/docs/pt/squad-dashboard.md +373 -0
- package/docs/pt/{squad-genoma.md → squad-genome.md} +81 -75
- package/docs/testing/genome-2.0-matrix.md +5 -5
- package/docs/testing/genome-2.0-rollout.md +10 -10
- package/package.json +4 -4
- package/src/agents.js +21 -5
- package/src/backup-local.js +74 -0
- package/src/backup-provider.js +303 -0
- package/src/cli.js +276 -2
- package/src/commands/agents.js +22 -4
- package/src/commands/backup-local-cmd.js +25 -0
- package/src/commands/backup.js +533 -0
- package/src/commands/cloud.js +17 -17
- package/src/commands/context-pack.js +45 -0
- package/src/commands/implementation-plan.js +340 -0
- package/src/commands/learning.js +134 -0
- package/src/commands/live.js +1583 -0
- package/src/commands/runtime.js +1075 -2
- package/src/commands/scan-project.js +288 -24
- package/src/commands/setup-context.js +30 -2
- package/src/commands/skill.js +558 -0
- package/src/commands/squad-agent-create.js +788 -0
- package/src/commands/squad-daemon.js +209 -0
- package/src/commands/squad-dashboard.js +39 -0
- package/src/commands/squad-deploy.js +64 -0
- package/src/commands/squad-doctor.js +103 -1
- package/src/commands/squad-investigate.js +261 -0
- package/src/commands/squad-learning.js +209 -0
- package/src/commands/squad-mcp.js +270 -0
- package/src/commands/squad-pipeline.js +247 -1
- package/src/commands/squad-plan.js +329 -0
- package/src/commands/squad-processes.js +56 -0
- package/src/commands/squad-recovery.js +42 -0
- package/src/commands/squad-roi.js +291 -0
- package/src/commands/squad-score.js +250 -0
- package/src/commands/squad-status.js +38 -2
- package/src/commands/squad-validate.js +118 -1
- package/src/commands/squad-webhook.js +160 -0
- package/src/commands/squad-worker.js +191 -0
- package/src/commands/squad-worktrees.js +75 -0
- package/src/commands/test-agents.js +6 -1
- package/src/commands/web-map.js +70 -0
- package/src/commands/web-scrape.js +71 -0
- package/src/commands/workflow-next.js +8 -1
- package/src/commands/workflow-status.js +250 -0
- package/src/constants.js +88 -16
- package/src/context-memory.js +837 -0
- package/src/context-writer.js +47 -1
- package/src/delivery-runner.js +319 -0
- package/src/genome-files.js +1 -1
- package/src/genome-format.js +1 -1
- package/src/i18n/messages/en.js +333 -8
- package/src/i18n/messages/es.js +240 -6
- package/src/i18n/messages/fr.js +239 -5
- package/src/i18n/messages/pt-BR.js +330 -12
- package/src/installer.js +30 -2
- package/src/lib/genomes/compat.js +1 -1
- package/src/lib/webhook-server.js +328 -0
- package/src/mcp-connectors/registry.js +602 -0
- package/src/runtime-store.js +1037 -42
- package/src/session-handoff.js +77 -0
- package/src/squad/external-session.js +180 -0
- package/src/squad/inter-squad.js +74 -0
- package/src/squad/recovery-context.js +201 -0
- package/src/squad/worktree-manager.js +114 -0
- package/src/squad-daemon.js +490 -0
- package/src/squad-dashboard/api.js +223 -0
- package/src/squad-dashboard/attachment-handler.js +93 -0
- package/src/squad-dashboard/context-monitor.js +157 -0
- package/src/squad-dashboard/execution-logs.js +115 -0
- package/src/squad-dashboard/hunk-review.js +209 -0
- package/src/squad-dashboard/metrics.js +133 -0
- package/src/squad-dashboard/process-monitor.js +125 -0
- package/src/squad-dashboard/renderer.js +858 -0
- package/src/squad-dashboard/server.js +232 -0
- package/src/squad-dashboard/styles.js +525 -0
- package/src/squad-dashboard/token-tracker.js +99 -0
- package/src/web.js +284 -0
- package/src/worker-runner.js +339 -0
- package/template/.aioson/agents/analyst.md +40 -9
- package/template/.aioson/agents/architect.md +24 -5
- package/template/.aioson/agents/dev.md +254 -25
- package/template/.aioson/agents/deyvin.md +174 -0
- package/template/.aioson/agents/discovery-design-doc.md +25 -1
- package/template/.aioson/agents/{genoma.md → genome.md} +20 -20
- package/template/.aioson/agents/neo.md +152 -0
- package/template/.aioson/agents/orache.md +388 -0
- package/template/.aioson/agents/orchestrator.md +63 -2
- package/template/.aioson/agents/pair.md +5 -0
- package/template/.aioson/agents/pm.md +17 -5
- package/template/.aioson/agents/product.md +113 -29
- package/template/.aioson/agents/profiler-enricher.md +1 -1
- package/template/.aioson/agents/profiler-forge.md +9 -9
- package/template/.aioson/agents/profiler-researcher.md +1 -1
- package/template/.aioson/agents/qa.md +18 -5
- package/template/.aioson/agents/setup.md +138 -18
- package/template/.aioson/agents/sheldon.md +603 -0
- package/template/.aioson/agents/squad.md +866 -28
- package/template/.aioson/agents/tester.md +254 -0
- package/template/.aioson/agents/ux-ui.md +289 -34
- package/template/.aioson/config.md +181 -0
- package/template/.aioson/context/spec.md.template +17 -0
- package/template/.aioson/genomes/.gitkeep +0 -0
- package/template/.aioson/installed-skills/.gitkeep +0 -0
- package/template/.aioson/locales/en/agents/analyst.md +34 -4
- package/template/.aioson/locales/en/agents/architect.md +18 -0
- package/template/.aioson/locales/en/agents/dev.md +155 -11
- package/template/.aioson/locales/en/agents/deyvin.md +137 -0
- package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
- package/template/.aioson/locales/en/agents/neo.md +8 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +62 -2
- package/template/.aioson/locales/en/agents/pair.md +5 -0
- package/template/.aioson/locales/en/agents/pm.md +7 -0
- package/template/.aioson/locales/en/agents/product.md +35 -17
- package/template/.aioson/locales/en/agents/qa.md +56 -0
- package/template/.aioson/locales/en/agents/setup.md +53 -6
- package/template/.aioson/locales/en/agents/sheldon.md +340 -0
- package/template/.aioson/locales/en/agents/squad.md +203 -15
- package/template/.aioson/locales/en/agents/ux-ui.md +383 -35
- package/template/.aioson/locales/es/agents/analyst.md +24 -4
- package/template/.aioson/locales/es/agents/architect.md +18 -0
- package/template/.aioson/locales/es/agents/dev.md +136 -9
- package/template/.aioson/locales/es/agents/deyvin.md +97 -0
- package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
- package/template/.aioson/locales/es/agents/neo.md +48 -0
- package/template/.aioson/locales/es/agents/orache.md +103 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +62 -2
- package/template/.aioson/locales/es/agents/pair.md +5 -0
- package/template/.aioson/locales/es/agents/pm.md +7 -0
- package/template/.aioson/locales/es/agents/product.md +13 -3
- package/template/.aioson/locales/es/agents/qa.md +33 -0
- package/template/.aioson/locales/es/agents/setup.md +30 -6
- package/template/.aioson/locales/es/agents/sheldon.md +192 -0
- package/template/.aioson/locales/es/agents/squad.md +284 -15
- package/template/.aioson/locales/es/agents/ux-ui.md +34 -25
- package/template/.aioson/locales/fr/agents/analyst.md +24 -4
- package/template/.aioson/locales/fr/agents/architect.md +18 -0
- package/template/.aioson/locales/fr/agents/dev.md +136 -9
- package/template/.aioson/locales/fr/agents/deyvin.md +97 -0
- package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
- package/template/.aioson/locales/fr/agents/neo.md +48 -0
- package/template/.aioson/locales/fr/agents/orache.md +104 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +62 -2
- package/template/.aioson/locales/fr/agents/pair.md +5 -0
- package/template/.aioson/locales/fr/agents/pm.md +7 -0
- package/template/.aioson/locales/fr/agents/product.md +13 -3
- package/template/.aioson/locales/fr/agents/qa.md +33 -0
- package/template/.aioson/locales/fr/agents/setup.md +30 -6
- package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
- package/template/.aioson/locales/fr/agents/squad.md +279 -10
- package/template/.aioson/locales/fr/agents/ux-ui.md +34 -25
- package/template/.aioson/locales/pt-BR/agents/analyst.md +45 -4
- package/template/.aioson/locales/pt-BR/agents/architect.md +29 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +167 -15
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +137 -0
- package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
- package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
- package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +62 -2
- package/template/.aioson/locales/pt-BR/agents/pair.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/pm.md +7 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +43 -20
- package/template/.aioson/locales/pt-BR/agents/qa.md +67 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +53 -6
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +591 -47
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +369 -22
- package/template/.aioson/my-agents/.gitkeep +0 -0
- package/template/.aioson/rules/.gitkeep +0 -0
- package/template/.aioson/rules/squad/.gitkeep +0 -0
- package/template/.aioson/rules/squad/README.md +50 -0
- package/template/.aioson/schemas/genome-meta.schema.json +1 -1
- package/template/.aioson/schemas/genome.schema.json +1 -1
- package/template/.aioson/schemas/squad-blueprint.schema.json +32 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +434 -1
- package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
- package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +203 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +272 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +524 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +277 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +289 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +437 -0
- package/template/.aioson/skills/design/interface-design/SKILL.md +47 -0
- package/template/.aioson/skills/design/interface-design/references/components-and-states.md +105 -0
- package/template/.aioson/skills/design/interface-design/references/design-directions.md +101 -0
- package/template/.aioson/skills/design/interface-design/references/handoff-and-quality.md +71 -0
- package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +74 -0
- package/template/.aioson/skills/design/interface-design/references/tokens-and-depth.md +173 -0
- package/template/.aioson/skills/design/premium-command-center-ui/SKILL.md +62 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/operations.md +74 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/patterns.md +116 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/validation.md +47 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/visual-system.md +215 -0
- package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
- package/template/.aioson/skills/design-system/SKILL.md +92 -0
- package/template/.aioson/skills/design-system/cognitive-core-ui.skill +0 -0
- package/template/.aioson/skills/design-system/components/SKILL.md +274 -0
- package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md +184 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md +250 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md +197 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md +231 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
- package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
- package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
- package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
- package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
- package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
- package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
- package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
- package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
- package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
- package/template/.aioson/skills/squad/SKILL.md +58 -0
- package/template/.aioson/skills/squad/domains/.gitkeep +0 -0
- package/template/.aioson/skills/squad/formats/.gitkeep +0 -0
- package/template/.aioson/skills/squad/formats/catalog.json +15 -0
- package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
- package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
- package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
- package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
- package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
- package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
- package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
- package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
- package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
- package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
- package/template/.aioson/skills/squad/patterns/.gitkeep +0 -0
- package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
- package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
- package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
- package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
- package/template/.aioson/skills/squad/references/.gitkeep +0 -0
- package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
- package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
- package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
- package/template/.aioson/skills/static/debugging-protocol.md +42 -0
- package/template/.aioson/skills/static/git-worktrees.md +36 -0
- package/template/.aioson/tasks/implementation-plan.md +307 -0
- package/template/.aioson/tasks/squad-create.md +1 -1
- package/template/.aioson/tasks/squad-design.md +28 -0
- package/template/.aioson/tasks/squad-execution-plan.md +279 -0
- package/template/.aioson/tasks/squad-export.md +1 -1
- package/template/.aioson/tasks/squad-investigate.md +44 -0
- package/template/.aioson/tasks/squad-learning-review.md +44 -0
- package/template/.aioson/tasks/squad-output-config.md +177 -0
- package/template/.aioson/tasks/squad-profile.md +48 -0
- package/template/.aioson/tasks/squad-review.md +61 -0
- package/template/.aioson/tasks/squad-task-decompose.md +66 -0
- package/template/.aioson/tasks/squad-validate.md +1 -1
- package/template/.claude/commands/aioson/agent/deyvin.md +5 -0
- package/template/.claude/commands/aioson/agent/discovery-design-doc.md +5 -0
- package/template/.claude/commands/aioson/agent/genome.md +5 -0
- package/template/.claude/commands/aioson/agent/neo.md +5 -0
- package/template/.claude/commands/aioson/agent/product.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-enricher.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-researcher.md +5 -0
- package/template/.claude/commands/aioson/agent/squad.md +5 -0
- package/template/.claude/commands/aioson/agent/tester.md +5 -0
- package/template/.gemini/GEMINI.md +3 -0
- package/template/.gemini/commands/aios-deyvin.toml +6 -0
- package/template/.gemini/commands/aios-neo.toml +4 -0
- package/template/.gemini/commands/aios-pair.toml +6 -0
- package/template/.gemini/commands/aios-tester.toml +6 -0
- package/template/AGENTS.md +37 -6
- package/template/CLAUDE.md +34 -4
- package/template/OPENCODE.md +8 -2
- package/template/squad-searches/.gitkeep +0 -0
- package/template/.aioson/skills/static/interface-design.md +0 -372
- package/template/.aioson/skills/static/premium-command-center-ui.md +0 -190
- /package/template/.aioson/{genomas → docs}/.gitkeep +0 -0
- /package/template/.claude/commands/aioson/{analyst.md → agent/analyst.md} +0 -0
- /package/template/.claude/commands/aioson/{architect.md → agent/architect.md} +0 -0
- /package/template/.claude/commands/aioson/{dev.md → agent/dev.md} +0 -0
- /package/template/.claude/commands/aioson/{orchestrator.md → agent/orchestrator.md} +0 -0
- /package/template/.claude/commands/aioson/{pm.md → agent/pm.md} +0 -0
- /package/template/.claude/commands/aioson/{qa.md → agent/qa.md} +0 -0
- /package/template/.claude/commands/aioson/{setup.md → agent/setup.md} +0 -0
- /package/template/.claude/commands/aioson/{ux-ui.md → agent/ux-ui.md} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
3
4
|
const path = require('node:path');
|
|
4
5
|
const {
|
|
5
6
|
openRuntimeDb,
|
|
@@ -9,6 +10,107 @@ const {
|
|
|
9
10
|
getTopologicalOrder
|
|
10
11
|
} = require('../runtime-store');
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Determine node completion status by checking handoffs.
|
|
15
|
+
* A node is "complete" if all its outgoing edges have consumed handoffs,
|
|
16
|
+
* or if it has no outgoing edges and has at least one incoming consumed handoff
|
|
17
|
+
* (or is the first node with no incoming edges and has produced output handoffs).
|
|
18
|
+
*/
|
|
19
|
+
function classifyNodes(db, pipelineSlug, order, edges) {
|
|
20
|
+
const handoffs = db
|
|
21
|
+
.prepare('SELECT * FROM squad_handoffs WHERE pipeline_slug = ? ORDER BY created_at DESC')
|
|
22
|
+
.all(pipelineSlug);
|
|
23
|
+
|
|
24
|
+
const outgoing = {};
|
|
25
|
+
const incoming = {};
|
|
26
|
+
for (const slug of order) {
|
|
27
|
+
outgoing[slug] = [];
|
|
28
|
+
incoming[slug] = [];
|
|
29
|
+
}
|
|
30
|
+
for (const edge of edges) {
|
|
31
|
+
if (outgoing[edge.source_squad]) outgoing[edge.source_squad].push(edge);
|
|
32
|
+
if (incoming[edge.target_squad]) incoming[edge.target_squad].push(edge);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const nodeStatus = {};
|
|
36
|
+
|
|
37
|
+
for (const slug of order) {
|
|
38
|
+
const outEdges = outgoing[slug];
|
|
39
|
+
const inEdges = incoming[slug];
|
|
40
|
+
|
|
41
|
+
// Check if all outgoing handoffs are consumed
|
|
42
|
+
if (outEdges.length > 0) {
|
|
43
|
+
const allProduced = outEdges.every(edge =>
|
|
44
|
+
handoffs.some(h =>
|
|
45
|
+
h.from_squad === edge.source_squad &&
|
|
46
|
+
h.from_port === edge.source_port &&
|
|
47
|
+
(h.status === 'consumed' || h.status === 'pending')
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
const allConsumed = outEdges.every(edge =>
|
|
51
|
+
handoffs.some(h =>
|
|
52
|
+
h.from_squad === edge.source_squad &&
|
|
53
|
+
h.from_port === edge.source_port &&
|
|
54
|
+
h.status === 'consumed'
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (allConsumed) {
|
|
59
|
+
nodeStatus[slug] = 'completed';
|
|
60
|
+
} else if (allProduced) {
|
|
61
|
+
nodeStatus[slug] = 'produced';
|
|
62
|
+
} else {
|
|
63
|
+
nodeStatus[slug] = 'pending';
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// Terminal node — check if all incoming handoffs are consumed
|
|
67
|
+
if (inEdges.length === 0) {
|
|
68
|
+
// Root node with no edges — mark pending
|
|
69
|
+
nodeStatus[slug] = 'pending';
|
|
70
|
+
} else {
|
|
71
|
+
const allInConsumed = inEdges.every(edge =>
|
|
72
|
+
handoffs.some(h =>
|
|
73
|
+
h.to_squad === edge.target_squad &&
|
|
74
|
+
h.to_port === edge.target_port &&
|
|
75
|
+
h.status === 'consumed'
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
nodeStatus[slug] = allInConsumed ? 'completed' : 'pending';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// First node with no incoming edges: if it has produced outputs, mark completed
|
|
84
|
+
for (const slug of order) {
|
|
85
|
+
if (incoming[slug].length === 0 && outgoing[slug].length > 0) {
|
|
86
|
+
if (nodeStatus[slug] === 'produced' || nodeStatus[slug] === 'completed') {
|
|
87
|
+
nodeStatus[slug] = 'completed';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { nodeStatus, handoffs };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function findNextPendingNode(order, nodeStatus) {
|
|
96
|
+
// Find first node that is pending and whose dependencies are all completed
|
|
97
|
+
return order.find(slug => nodeStatus[slug] === 'pending') || null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function findSquadOrchestratorAgent(projectDir, squadSlug) {
|
|
101
|
+
// Convention: squad orchestrator is at .aioson/squads/{slug}/agents/{slug}-orchestrator.md
|
|
102
|
+
// or the first agent in the squad
|
|
103
|
+
return `@${squadSlug}-orchestrator`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function requirePipelineSlug(slugArg, logger) {
|
|
107
|
+
if (!slugArg) {
|
|
108
|
+
logger.error('Usage: aioson squad:pipeline [path] --sub=run --pipeline=<slug>');
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return slugArg;
|
|
112
|
+
}
|
|
113
|
+
|
|
12
114
|
async function runSquadPipeline({ args = [], options = {}, logger = console } = {}) {
|
|
13
115
|
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
14
116
|
const subcommand = options.sub || args[1] || 'list';
|
|
@@ -87,7 +189,151 @@ async function runSquadPipeline({ args = [], options = {}, logger = console } =
|
|
|
87
189
|
return { ok: true, pipeline: dag.pipeline, handoffs: { pending, consumed, failed } };
|
|
88
190
|
}
|
|
89
191
|
|
|
90
|
-
|
|
192
|
+
// --- NEW: run (guided mode) ---
|
|
193
|
+
if (subcommand === 'run' || subcommand === 'continue') {
|
|
194
|
+
const slug = await requirePipelineSlug(slugArg, logger);
|
|
195
|
+
if (!slug) return { ok: false, error: 'missing_slug' };
|
|
196
|
+
|
|
197
|
+
const dag = getPipelineDAG(db, slug);
|
|
198
|
+
if (!dag) {
|
|
199
|
+
logger.error(`Pipeline not found: ${slug}`);
|
|
200
|
+
return { ok: false, error: 'not_found' };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const order = getTopologicalOrder(db, slug);
|
|
204
|
+
if (!order) {
|
|
205
|
+
logger.error('Cycle detected in pipeline — cannot run.');
|
|
206
|
+
return { ok: false, error: 'cycle_detected' };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const { nodeStatus, handoffs } = classifyNodes(db, slug, order, dag.edges);
|
|
210
|
+
|
|
211
|
+
// Show pipeline progress
|
|
212
|
+
logger.log('');
|
|
213
|
+
logger.log(`Pipeline: ${dag.pipeline.name || dag.pipeline.slug}`);
|
|
214
|
+
logger.log(`Mode: guided (the system suggests, you execute)`);
|
|
215
|
+
logger.log('');
|
|
216
|
+
|
|
217
|
+
logger.log('Progress:');
|
|
218
|
+
for (const nodeSlugg of order) {
|
|
219
|
+
const status = nodeStatus[nodeSlugg];
|
|
220
|
+
const icon = status === 'completed' ? '[v]'
|
|
221
|
+
: status === 'produced' ? '[~]'
|
|
222
|
+
: '[>]';
|
|
223
|
+
// Only mark the first pending as [>], rest as [ ]
|
|
224
|
+
const displayIcon = status === 'pending'
|
|
225
|
+
? (nodeSlugg === findNextPendingNode(order, nodeStatus) ? '[>]' : '[ ]')
|
|
226
|
+
: icon;
|
|
227
|
+
logger.log(` ${displayIcon} ${nodeSlugg} (${status})`);
|
|
228
|
+
}
|
|
229
|
+
logger.log('');
|
|
230
|
+
|
|
231
|
+
// Find next node to activate
|
|
232
|
+
const nextNode = findNextPendingNode(order, nodeStatus);
|
|
233
|
+
|
|
234
|
+
if (!nextNode) {
|
|
235
|
+
// All nodes completed
|
|
236
|
+
const allCompleted = order.every(s => nodeStatus[s] === 'completed');
|
|
237
|
+
if (allCompleted) {
|
|
238
|
+
logger.log('Pipeline completed! All nodes have been executed.');
|
|
239
|
+
return { ok: true, pipeline: slug, status: 'completed', nextNode: null };
|
|
240
|
+
}
|
|
241
|
+
logger.log('No actionable node found. Check pending handoffs.');
|
|
242
|
+
return { ok: true, pipeline: slug, status: 'blocked', nextNode: null };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check if next node has pending incoming handoffs to consume
|
|
246
|
+
const pendingIncoming = handoffs.filter(h =>
|
|
247
|
+
h.to_squad === nextNode && h.status === 'pending'
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Consume pending incoming handoffs for this node
|
|
251
|
+
if (pendingIncoming.length > 0) {
|
|
252
|
+
const updateStmt = db.prepare(
|
|
253
|
+
'UPDATE squad_handoffs SET status = ?, consumed_at = ? WHERE id = ?'
|
|
254
|
+
);
|
|
255
|
+
for (const h of pendingIncoming) {
|
|
256
|
+
updateStmt.run('consumed', new Date().toISOString(), h.id);
|
|
257
|
+
}
|
|
258
|
+
logger.log(`Consumed ${pendingIncoming.length} incoming handoff(s) for ${nextNode}.`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const orchestratorAgent = findSquadOrchestratorAgent(projectDir, nextNode);
|
|
262
|
+
logger.log(`Next: activate squad "${nextNode}"`);
|
|
263
|
+
logger.log(` Agent: ${orchestratorAgent}`);
|
|
264
|
+
logger.log(` Command: aioson agent ${orchestratorAgent} --tool=claude`);
|
|
265
|
+
logger.log('');
|
|
266
|
+
logger.log('After the squad completes its work, run:');
|
|
267
|
+
logger.log(` aioson squad:pipeline . --sub=run --pipeline=${slug}`);
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
ok: true,
|
|
271
|
+
pipeline: slug,
|
|
272
|
+
status: 'running',
|
|
273
|
+
nextNode,
|
|
274
|
+
orchestratorAgent,
|
|
275
|
+
nodeStatus
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- NEW: skip ---
|
|
280
|
+
if (subcommand === 'skip') {
|
|
281
|
+
const slug = await requirePipelineSlug(slugArg, logger);
|
|
282
|
+
if (!slug) return { ok: false, error: 'missing_slug' };
|
|
283
|
+
|
|
284
|
+
const dag = getPipelineDAG(db, slug);
|
|
285
|
+
if (!dag) {
|
|
286
|
+
logger.error(`Pipeline not found: ${slug}`);
|
|
287
|
+
return { ok: false, error: 'not_found' };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const order = getTopologicalOrder(db, slug);
|
|
291
|
+
if (!order) {
|
|
292
|
+
logger.error('Cycle detected — cannot skip.');
|
|
293
|
+
return { ok: false, error: 'cycle_detected' };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const { nodeStatus } = classifyNodes(db, slug, order, dag.edges);
|
|
297
|
+
const nextNode = findNextPendingNode(order, nodeStatus);
|
|
298
|
+
|
|
299
|
+
if (!nextNode) {
|
|
300
|
+
logger.log('No pending node to skip.');
|
|
301
|
+
return { ok: true, pipeline: slug, skipped: null };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Create synthetic handoffs for all outgoing edges of the skipped node
|
|
305
|
+
const outEdges = dag.edges.filter(e => e.source_squad === nextNode);
|
|
306
|
+
const insertHandoff = db.prepare(`
|
|
307
|
+
INSERT INTO squad_handoffs (id, pipeline_slug, from_squad, from_port, to_squad, to_port, payload_json, status, created_at)
|
|
308
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?)
|
|
309
|
+
`);
|
|
310
|
+
|
|
311
|
+
for (const edge of outEdges) {
|
|
312
|
+
const id = `skip-${nextNode}-${edge.target_squad}-${Date.now()}`;
|
|
313
|
+
insertHandoff.run(
|
|
314
|
+
id,
|
|
315
|
+
slug,
|
|
316
|
+
edge.source_squad,
|
|
317
|
+
edge.source_port,
|
|
318
|
+
edge.target_squad,
|
|
319
|
+
edge.target_port,
|
|
320
|
+
JSON.stringify({ skipped: true, reason: 'Node skipped by user' }),
|
|
321
|
+
new Date().toISOString()
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
logger.log(`Skipped node: ${nextNode}`);
|
|
326
|
+
if (outEdges.length > 0) {
|
|
327
|
+
logger.log(`Created ${outEdges.length} synthetic handoff(s) for downstream nodes.`);
|
|
328
|
+
}
|
|
329
|
+
logger.log('');
|
|
330
|
+
logger.log('Run again to see the next node:');
|
|
331
|
+
logger.log(` aioson squad:pipeline . --sub=run --pipeline=${slug}`);
|
|
332
|
+
|
|
333
|
+
return { ok: true, pipeline: slug, skipped: nextNode };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
logger.error(`Unknown subcommand: ${subcommand}. Available: list, show, status, run, continue, skip`);
|
|
91
337
|
return { ok: false, error: 'unknown_subcommand' };
|
|
92
338
|
} finally {
|
|
93
339
|
db.close();
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
const {
|
|
7
|
+
openRuntimeDb,
|
|
8
|
+
upsertSquadExecutionPlan,
|
|
9
|
+
getSquadExecutionPlan,
|
|
10
|
+
getSquadExecutionPlanBySquad,
|
|
11
|
+
listSquadExecutionPlans,
|
|
12
|
+
updateSquadExecutionPlanStatus,
|
|
13
|
+
upsertSquadPlanRound,
|
|
14
|
+
updateSquadPlanRoundStatus,
|
|
15
|
+
getSquadPlanRounds
|
|
16
|
+
} = require('../runtime-store');
|
|
17
|
+
|
|
18
|
+
const SQUADS_DIR = path.join('.aioson', 'squads');
|
|
19
|
+
|
|
20
|
+
async function pathExists(targetPath) {
|
|
21
|
+
try {
|
|
22
|
+
await fs.access(targetPath);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compute hash of squad manifest for staleness detection.
|
|
31
|
+
*/
|
|
32
|
+
async function computeManifestHash(projectDir, squadSlug) {
|
|
33
|
+
const manifestPath = path.resolve(projectDir, SQUADS_DIR, squadSlug, 'squad.manifest.json');
|
|
34
|
+
const hash = crypto.createHash('sha256');
|
|
35
|
+
try {
|
|
36
|
+
const stat = await fs.stat(manifestPath);
|
|
37
|
+
hash.update(`manifest:${stat.mtimeMs}`);
|
|
38
|
+
} catch {
|
|
39
|
+
hash.update('manifest:missing');
|
|
40
|
+
}
|
|
41
|
+
return hash.digest('hex').slice(0, 16);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse execution plan frontmatter.
|
|
46
|
+
*/
|
|
47
|
+
function parsePlanFrontmatter(content) {
|
|
48
|
+
const text = String(content || '');
|
|
49
|
+
const match = text.match(/^---\n([\s\S]*?)\n---/);
|
|
50
|
+
if (!match) return {};
|
|
51
|
+
const meta = {};
|
|
52
|
+
for (const line of match[1].split('\n')) {
|
|
53
|
+
const [key, ...rest] = line.split(':');
|
|
54
|
+
if (key && rest.length) {
|
|
55
|
+
meta[key.trim()] = rest.join(':').trim().replace(/^"(.*)"$/, '$1');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return meta;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Count rounds in an execution plan markdown.
|
|
63
|
+
*/
|
|
64
|
+
function countRounds(content) {
|
|
65
|
+
const text = String(content || '');
|
|
66
|
+
const matches = text.match(/^### Round \d+/gm);
|
|
67
|
+
return matches ? matches.length : 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the execution plan path for a squad.
|
|
72
|
+
*/
|
|
73
|
+
function planPath(projectDir, squadSlug) {
|
|
74
|
+
return path.resolve(projectDir, SQUADS_DIR, squadSlug, 'docs', 'execution-plan.md');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Subcommand: show <slug>
|
|
79
|
+
* Shows the execution plan for a squad.
|
|
80
|
+
*/
|
|
81
|
+
async function handleShow(projectDir, squadSlug, { logger, t }) {
|
|
82
|
+
if (!squadSlug) {
|
|
83
|
+
logger.error(t('squad_plan.slug_required'));
|
|
84
|
+
return { found: false };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const pp = planPath(projectDir, squadSlug);
|
|
88
|
+
if (!(await pathExists(pp))) {
|
|
89
|
+
logger.error(t('squad_plan.not_found', { slug: squadSlug }));
|
|
90
|
+
return { found: false };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const content = await fs.readFile(pp, 'utf8');
|
|
94
|
+
const meta = parsePlanFrontmatter(content);
|
|
95
|
+
const rounds = countRounds(content);
|
|
96
|
+
|
|
97
|
+
logger.log(`Execution Plan: ${squadSlug}`);
|
|
98
|
+
logger.log(`Status: ${meta.status || 'unknown'}`);
|
|
99
|
+
logger.log(`Rounds: ${rounds}`);
|
|
100
|
+
logger.log('');
|
|
101
|
+
logger.log(content);
|
|
102
|
+
|
|
103
|
+
return { found: true, meta, rounds };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Subcommand: status <slug>
|
|
108
|
+
* Shows progress of the execution plan from SQLite.
|
|
109
|
+
*/
|
|
110
|
+
async function handleStatus(projectDir, squadSlug, { logger, t }) {
|
|
111
|
+
if (!squadSlug) {
|
|
112
|
+
logger.error(t('squad_plan.slug_required'));
|
|
113
|
+
return { found: false };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
117
|
+
if (!handle) {
|
|
118
|
+
logger.error(t('squad_plan.no_runtime'));
|
|
119
|
+
return { found: false };
|
|
120
|
+
}
|
|
121
|
+
const { db } = handle;
|
|
122
|
+
try {
|
|
123
|
+
const plan = getSquadExecutionPlanBySquad(db, squadSlug);
|
|
124
|
+
if (!plan) {
|
|
125
|
+
logger.error(t('squad_plan.no_plan', { slug: squadSlug }));
|
|
126
|
+
return { found: false };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const rounds = getSquadPlanRounds(db, plan.plan_slug);
|
|
130
|
+
logger.log(`Plan: ${plan.plan_slug}`);
|
|
131
|
+
logger.log(`Squad: ${plan.squad_slug}`);
|
|
132
|
+
logger.log(`Status: ${plan.status}`);
|
|
133
|
+
logger.log(`Progress: ${plan.rounds_completed}/${plan.rounds_total}`);
|
|
134
|
+
logger.log('');
|
|
135
|
+
for (const rd of rounds) {
|
|
136
|
+
const icon = rd.status === 'completed' ? '✓' : rd.status === 'in_progress' ? '▸' : '○';
|
|
137
|
+
logger.log(` ${icon} Round ${rd.round_number}: ${rd.title} (@${rd.executor_slug}) [${rd.status}]`);
|
|
138
|
+
}
|
|
139
|
+
return { found: true, plan, rounds };
|
|
140
|
+
} finally {
|
|
141
|
+
db.close();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Subcommand: checkpoint <slug> <round-number>
|
|
147
|
+
* Marks a round as completed.
|
|
148
|
+
*/
|
|
149
|
+
async function handleCheckpoint(projectDir, squadSlug, roundNumber, { logger, t }) {
|
|
150
|
+
if (!squadSlug || !roundNumber || isNaN(Number(roundNumber))) {
|
|
151
|
+
logger.error(t('squad_plan.checkpoint_usage'));
|
|
152
|
+
return { updated: false };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
156
|
+
if (!handle) {
|
|
157
|
+
logger.error(t('squad_plan.no_runtime'));
|
|
158
|
+
return { updated: false };
|
|
159
|
+
}
|
|
160
|
+
const { db } = handle;
|
|
161
|
+
try {
|
|
162
|
+
const plan = getSquadExecutionPlanBySquad(db, squadSlug);
|
|
163
|
+
if (!plan) {
|
|
164
|
+
logger.error(t('squad_plan.no_plan', { slug: squadSlug }));
|
|
165
|
+
return { updated: false };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const updated = updateSquadPlanRoundStatus(db, plan.plan_slug, Number(roundNumber), 'completed');
|
|
169
|
+
if (updated) {
|
|
170
|
+
logger.log(t('squad_plan.round_completed', { round: roundNumber }));
|
|
171
|
+
} else {
|
|
172
|
+
logger.error(t('squad_plan.round_not_found', { round: roundNumber }));
|
|
173
|
+
}
|
|
174
|
+
return { updated };
|
|
175
|
+
} finally {
|
|
176
|
+
db.close();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Subcommand: stale <slug>
|
|
182
|
+
* Checks if the squad manifest changed after the plan was created.
|
|
183
|
+
*/
|
|
184
|
+
async function handleStale(projectDir, squadSlug, { logger, t }) {
|
|
185
|
+
if (!squadSlug) {
|
|
186
|
+
logger.error(t('squad_plan.slug_required'));
|
|
187
|
+
return { found: false, stale: false };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const pp = planPath(projectDir, squadSlug);
|
|
191
|
+
if (!(await pathExists(pp))) {
|
|
192
|
+
logger.error(t('squad_plan.not_found', { slug: squadSlug }));
|
|
193
|
+
return { found: false, stale: false };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const content = await fs.readFile(pp, 'utf8');
|
|
197
|
+
const meta = parsePlanFrontmatter(content);
|
|
198
|
+
if (!meta.created) {
|
|
199
|
+
logger.log(t('squad_plan.no_created_date'));
|
|
200
|
+
return { found: true, stale: false };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const planDate = new Date(meta.created);
|
|
204
|
+
const manifestFile = path.resolve(projectDir, SQUADS_DIR, squadSlug, 'squad.manifest.json');
|
|
205
|
+
let stale = false;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const stat = await fs.stat(manifestFile);
|
|
209
|
+
if (stat.mtime > planDate) {
|
|
210
|
+
logger.log(' ⚠ squad.manifest.json modified after plan was created');
|
|
211
|
+
stale = true;
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// manifest missing — not stale, just broken
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Also check blueprint
|
|
218
|
+
const designsDir = path.resolve(projectDir, SQUADS_DIR, '.designs');
|
|
219
|
+
try {
|
|
220
|
+
const files = await fs.readdir(designsDir);
|
|
221
|
+
const bpFile = files.find(f => f.startsWith(squadSlug) && f.endsWith('.blueprint.json'));
|
|
222
|
+
if (bpFile) {
|
|
223
|
+
const bpPath = path.join(designsDir, bpFile);
|
|
224
|
+
const stat = await fs.stat(bpPath);
|
|
225
|
+
if (stat.mtime > planDate) {
|
|
226
|
+
logger.log(' ⚠ blueprint modified after plan was created');
|
|
227
|
+
stale = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// designs dir may not exist
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (stale) {
|
|
235
|
+
logger.log(t('squad_plan.is_stale'));
|
|
236
|
+
} else {
|
|
237
|
+
logger.log(t('squad_plan.is_fresh'));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { found: true, stale };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Subcommand: register <slug>
|
|
245
|
+
* Registers an existing execution plan file into the runtime SQLite.
|
|
246
|
+
*/
|
|
247
|
+
async function handleRegister(projectDir, squadSlug, { logger, t }) {
|
|
248
|
+
if (!squadSlug) {
|
|
249
|
+
logger.error(t('squad_plan.slug_required'));
|
|
250
|
+
return { registered: false };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const pp = planPath(projectDir, squadSlug);
|
|
254
|
+
if (!(await pathExists(pp))) {
|
|
255
|
+
logger.error(t('squad_plan.not_found', { slug: squadSlug }));
|
|
256
|
+
return { registered: false };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const content = await fs.readFile(pp, 'utf8');
|
|
260
|
+
const meta = parsePlanFrontmatter(content);
|
|
261
|
+
const rounds = countRounds(content);
|
|
262
|
+
const hash = await computeManifestHash(projectDir, squadSlug);
|
|
263
|
+
|
|
264
|
+
const handle = await openRuntimeDb(projectDir);
|
|
265
|
+
const { db } = handle;
|
|
266
|
+
try {
|
|
267
|
+
const planSlug = upsertSquadExecutionPlan(db, {
|
|
268
|
+
squadSlug,
|
|
269
|
+
status: meta.status || 'draft',
|
|
270
|
+
roundsTotal: rounds,
|
|
271
|
+
roundsCompleted: 0,
|
|
272
|
+
basedOnBlueprint: meta.based_on_blueprint || null,
|
|
273
|
+
basedOnInvestigation: meta.based_on_investigation || null,
|
|
274
|
+
sourceHash: hash
|
|
275
|
+
});
|
|
276
|
+
logger.log(t('squad_plan.registered', { planSlug, rounds }));
|
|
277
|
+
return { registered: true, planSlug };
|
|
278
|
+
} finally {
|
|
279
|
+
db.close();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Main router for squad-plan subcommands.
|
|
285
|
+
*/
|
|
286
|
+
async function run(projectDir, args, context) {
|
|
287
|
+
const sub = args[0] || 'show';
|
|
288
|
+
const rest = args.slice(1);
|
|
289
|
+
|
|
290
|
+
switch (sub) {
|
|
291
|
+
case 'show':
|
|
292
|
+
return handleShow(projectDir, rest[0], context);
|
|
293
|
+
case 'status':
|
|
294
|
+
return handleStatus(projectDir, rest[0], context);
|
|
295
|
+
case 'checkpoint':
|
|
296
|
+
return handleCheckpoint(projectDir, rest[0], rest[1], context);
|
|
297
|
+
case 'stale':
|
|
298
|
+
return handleStale(projectDir, rest[0], context);
|
|
299
|
+
case 'register':
|
|
300
|
+
return handleRegister(projectDir, rest[0], context);
|
|
301
|
+
default:
|
|
302
|
+
context.logger.error(`Unknown subcommand: ${sub}. Available: show, status, checkpoint, stale, register`);
|
|
303
|
+
return { error: true };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Entry point for CLI integration (same signature as other commands).
|
|
309
|
+
*/
|
|
310
|
+
async function runSquadPlan({ args = [], options = {}, logger = console, t = (k) => k } = {}) {
|
|
311
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
312
|
+
const sub = options.sub || args[1] || 'show';
|
|
313
|
+
const slug = options.squad || args[2] || null;
|
|
314
|
+
const context = { logger, t };
|
|
315
|
+
|
|
316
|
+
if (sub === 'show') return handleShow(projectDir, slug, context);
|
|
317
|
+
if (sub === 'status') return handleStatus(projectDir, slug, context);
|
|
318
|
+
if (sub === 'checkpoint') {
|
|
319
|
+
const round = args[3] || options.round;
|
|
320
|
+
return handleCheckpoint(projectDir, slug, round, context);
|
|
321
|
+
}
|
|
322
|
+
if (sub === 'stale') return handleStale(projectDir, slug, context);
|
|
323
|
+
if (sub === 'register') return handleRegister(projectDir, slug, context);
|
|
324
|
+
|
|
325
|
+
logger.error(`Unknown subcommand: ${sub}. Available: show, status, checkpoint, stale, register`);
|
|
326
|
+
return { error: true };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = { run, runSquadPlan, handleShow, handleStatus, handleCheckpoint, handleStale, handleRegister };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { getActiveProcesses, stopProcess, stopSquadProcesses } = require('../squad-dashboard/process-monitor');
|
|
5
|
+
|
|
6
|
+
function formatElapsed(seconds) {
|
|
7
|
+
if (seconds == null) return '-';
|
|
8
|
+
if (seconds < 60) return `${seconds}s`;
|
|
9
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m${seconds % 60}s`;
|
|
10
|
+
return `${Math.floor(seconds / 3600)}h${Math.floor((seconds % 3600) / 60)}m`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function runSquadProcesses({ args, options, logger }) {
|
|
14
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
15
|
+
const squadFilter = options.squad || null;
|
|
16
|
+
|
|
17
|
+
// --stop <pid>
|
|
18
|
+
if (options.stop && options.stop !== true) {
|
|
19
|
+
const result = await stopProcess(projectDir, options.stop);
|
|
20
|
+
if (result.ok) {
|
|
21
|
+
logger.log(`Process ${options.stop} stopped.`);
|
|
22
|
+
} else {
|
|
23
|
+
logger.error(`Failed to stop ${options.stop}: ${result.error}`);
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// --stop-squad <squad>
|
|
29
|
+
if (options['stop-squad']) {
|
|
30
|
+
const slug = options['stop-squad'];
|
|
31
|
+
const results = await stopSquadProcesses(projectDir, slug);
|
|
32
|
+
const stopped = results.filter(r => r.ok).length;
|
|
33
|
+
logger.log(`Stopped ${stopped}/${results.length} processes for squad "${slug}".`);
|
|
34
|
+
return { ok: true, results };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// List
|
|
38
|
+
const processes = await getActiveProcesses(projectDir, squadFilter);
|
|
39
|
+
if (processes.length === 0) {
|
|
40
|
+
logger.log(squadFilter ? `No active processes for squad "${squadFilter}".` : 'No active processes.');
|
|
41
|
+
return { ok: true, processes: [] };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
logger.log(`Active processes (${processes.length}):`);
|
|
45
|
+
for (const proc of processes) {
|
|
46
|
+
const status = proc.alive ? '[*]' : '[ ]';
|
|
47
|
+
const elapsed = formatElapsed(proc.elapsedSeconds);
|
|
48
|
+
const ctx = proc.contextPct != null ? ` ctx:${proc.contextPct}%` : '';
|
|
49
|
+
const url = proc.url ? ` ${proc.url}` : '';
|
|
50
|
+
logger.log(` ${status} [${proc.squadSlug}] ${proc.agentSlug} pid:${proc.pid} elapsed:${elapsed}${ctx}${url}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { ok: true, processes };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { runSquadProcesses };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { generateRecovery, readRecovery } = require('../squad/recovery-context');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* aioson squad:recovery <project> --squad <squad> --agent <agent> [--show]
|
|
8
|
+
*
|
|
9
|
+
* Generates (or re-generates) the recovery-context.md for an agent.
|
|
10
|
+
* --show: print the generated content to stdout instead of just confirming.
|
|
11
|
+
*/
|
|
12
|
+
async function runSquadRecovery({ args, options, logger }) {
|
|
13
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
14
|
+
const squadSlug = options.squad || args[1];
|
|
15
|
+
const agentSlug = options.agent || args[2];
|
|
16
|
+
|
|
17
|
+
if (!squadSlug || !agentSlug) {
|
|
18
|
+
logger.error('Usage: aioson squad:recovery <project> --squad <squad> --agent <agent> [--show]');
|
|
19
|
+
return { ok: false };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await generateRecovery(projectDir, squadSlug, agentSlug);
|
|
23
|
+
|
|
24
|
+
if (!result.ok) {
|
|
25
|
+
logger.error(`Failed to generate recovery context: ${result.error}`);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
logger.log(`Recovery context generated: ${result.path} (~${result.tokens} tokens)`);
|
|
30
|
+
|
|
31
|
+
if (options.show) {
|
|
32
|
+
const content = await readRecovery(projectDir, squadSlug);
|
|
33
|
+
if (content) {
|
|
34
|
+
logger.log('');
|
|
35
|
+
logger.log(content);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { runSquadRecovery };
|