@jaimevalasek/aioson 1.6.0 → 1.7.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 +49 -0
- package/README.md +729 -232
- package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
- package/docs/integrations/sdlc-genius-boundary.md +76 -0
- package/docs/integrations/sdlc-genius-eval-matrix.md +75 -0
- package/docs/integrations/sdlc-genius-install-checklist.md +93 -0
- package/docs/integrations/sdlc-genius-review-samples.md +86 -0
- package/docs/pt/README.md +3 -0
- package/docs/pt/agentes.md +1 -0
- package/docs/pt/comandos-cli.md +888 -2
- package/docs/pt/design-hybrid-forge.md +255 -6
- package/docs/pt/devlog-pipeline.md +270 -0
- package/docs/pt/fluxo-artefatos.md +178 -0
- package/docs/pt/hooks-session-guard.md +454 -0
- package/docs/pt/monitor-de-contexto.md +59 -5
- package/docs/pt/sdd-automation-scripts.md +557 -0
- package/docs/pt/site-forge.md +309 -0
- package/docs/pt/spec-learnings-pipeline.md +265 -0
- package/package.json +1 -1
- package/src/a2a/client.js +165 -0
- package/src/a2a/server.js +223 -0
- package/src/cli.js +235 -1
- package/src/commands/agent-audit.js +397 -0
- package/src/commands/agent-export-skill.js +229 -0
- package/src/commands/artifact-validate.js +189 -0
- package/src/commands/brief-gen.js +405 -0
- package/src/commands/brief-validate.js +65 -0
- package/src/commands/classify.js +256 -0
- package/src/commands/context-compact.js +49 -0
- package/src/commands/context-health.js +175 -0
- package/src/commands/context-monitor.js +71 -0
- package/src/commands/context-trim.js +177 -0
- package/src/commands/detect-test-runner.js +55 -0
- package/src/commands/devlog-export-brains.js +27 -0
- package/src/commands/devlog-process.js +292 -0
- package/src/commands/devlog-watch.js +131 -0
- package/src/commands/feature-close.js +165 -0
- package/src/commands/gate-check.js +228 -0
- package/src/commands/hooks-emit.js +253 -0
- package/src/commands/hooks-install.js +347 -0
- package/src/commands/learning-auto-promote.js +195 -0
- package/src/commands/learning-evolve.js +18 -9
- package/src/commands/learning-export.js +103 -0
- package/src/commands/learning-rollback.js +164 -0
- package/src/commands/live.js +25 -1
- package/src/commands/pattern-detect.js +33 -0
- package/src/commands/preflight-context.js +30 -0
- package/src/commands/preflight.js +208 -0
- package/src/commands/pulse-update.js +130 -0
- package/src/commands/runner-daemon.js +274 -0
- package/src/commands/runner-plan.js +70 -0
- package/src/commands/runner-queue-from-plan.js +166 -0
- package/src/commands/runner-queue.js +189 -0
- package/src/commands/runner-run.js +129 -0
- package/src/commands/runtime.js +47 -1
- package/src/commands/self-implement-loop.js +256 -0
- package/src/commands/session-guard.js +218 -0
- package/src/commands/sizing.js +165 -0
- package/src/commands/skill.js +65 -0
- package/src/commands/spec-checkpoint.js +177 -0
- package/src/commands/spec-status.js +79 -0
- package/src/commands/spec-sync.js +190 -0
- package/src/commands/spec-tasks.js +288 -0
- package/src/commands/squad-autorun.js +1220 -0
- package/src/commands/squad-bus.js +217 -0
- package/src/commands/squad-card.js +149 -0
- package/src/commands/squad-daemon.js +134 -0
- package/src/commands/squad-dependency-graph.js +164 -0
- package/src/commands/squad-review.js +106 -0
- package/src/commands/squad-scaffold.js +55 -0
- package/src/commands/squad-tool-register.js +157 -0
- package/src/commands/state-save.js +122 -0
- package/src/commands/update.js +2 -0
- package/src/commands/verify-gate.js +572 -0
- package/src/commands/workflow-execute.js +241 -0
- package/src/constants.js +9 -0
- package/src/install-profile.js +2 -2
- package/src/install-wizard.js +3 -2
- package/src/installer.js +6 -0
- package/src/lib/health-check.js +158 -0
- package/src/lib/hook-protocol.js +76 -0
- package/src/mcp/apps/squad-dashboard/app.js +163 -0
- package/src/mcp/apps/squad-dashboard/index.html +261 -0
- package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -0
- package/src/mcp/resources/squad-state.js +130 -0
- package/src/preflight-engine.js +443 -0
- package/src/runner/cascade.js +97 -0
- package/src/runner/cli-launcher.js +109 -0
- package/src/runner/plan-importer.js +63 -0
- package/src/runner/queue-store.js +159 -0
- package/src/runtime-store.js +61 -3
- package/src/squad/agent-teams-adapter.js +264 -0
- package/src/squad/brief-validator.js +350 -0
- package/src/squad/bus-bridge.js +140 -0
- package/src/squad/context-compactor.js +265 -0
- package/src/squad/cross-ai-synthesizer.js +250 -0
- package/src/squad/hooks-generator.js +196 -0
- package/src/squad/inter-squad-events.js +175 -0
- package/src/squad/intra-bus.js +345 -0
- package/src/squad/learning-extractor.js +213 -0
- package/src/squad/pattern-detector.js +365 -0
- package/src/squad/preflight-context.js +296 -0
- package/src/squad/recovery-context.js +242 -71
- package/src/squad/reflection.js +365 -0
- package/src/squad/squad-scaffold.js +177 -0
- package/src/squad/state-manager.js +310 -0
- package/src/squad/task-decomposer.js +652 -0
- package/src/squad/verify-gate.js +303 -0
- package/src/updater.js +4 -5
- package/src/worker-runner.js +186 -1
- package/template/.aioson/agents/analyst.md +62 -1
- package/template/.aioson/agents/architect.md +61 -1
- package/template/.aioson/agents/design-hybrid-forge.md +14 -0
- package/template/.aioson/agents/dev.md +242 -24
- package/template/.aioson/agents/deyvin.md +66 -8
- package/template/.aioson/agents/discovery-design-doc.md +44 -0
- package/template/.aioson/agents/genome.md +14 -0
- package/template/.aioson/agents/neo.md +78 -1
- package/template/.aioson/agents/orache.md +50 -4
- package/template/.aioson/agents/orchestrator.md +197 -1
- package/template/.aioson/agents/pm.md +35 -0
- package/template/.aioson/agents/product.md +50 -5
- package/template/.aioson/agents/profiler-enricher.md +14 -0
- package/template/.aioson/agents/profiler-forge.md +14 -0
- package/template/.aioson/agents/profiler-researcher.md +14 -0
- package/template/.aioson/agents/qa.md +172 -21
- package/template/.aioson/agents/setup.md +79 -9
- package/template/.aioson/agents/sheldon.md +131 -6
- package/template/.aioson/agents/site-forge.md +1753 -0
- package/template/.aioson/agents/squad.md +162 -0
- package/template/.aioson/agents/tester.md +53 -0
- package/template/.aioson/agents/ux-ui.md +34 -1
- package/template/.aioson/brains/README.md +128 -0
- package/template/.aioson/brains/_index.json +16 -0
- package/template/.aioson/brains/scripts/query.js +103 -0
- package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
- package/template/.aioson/config.md +143 -13
- package/template/.aioson/constitution.md +33 -0
- package/template/.aioson/context/project-pulse.md +34 -0
- package/template/.aioson/docs/LAYERS.md +79 -0
- package/template/.aioson/docs/README.md +76 -0
- package/template/.aioson/docs/example-external-api-context.md +72 -0
- package/template/.aioson/locales/en/agents/architect.md +17 -0
- package/template/.aioson/locales/en/agents/dev.md +79 -13
- package/template/.aioson/locales/en/agents/orache.md +6 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
- package/template/.aioson/locales/en/agents/product.md +50 -0
- package/template/.aioson/locales/en/agents/sheldon.md +115 -0
- package/template/.aioson/locales/en/agents/squad.md +14 -0
- package/template/.aioson/locales/en/agents/tester.md +6 -0
- package/template/.aioson/locales/es/agents/analyst.md +2 -0
- package/template/.aioson/locales/es/agents/architect.md +19 -0
- package/template/.aioson/locales/es/agents/dev.md +64 -4
- package/template/.aioson/locales/es/agents/deyvin.md +2 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/es/agents/genome.md +2 -0
- package/template/.aioson/locales/es/agents/neo.md +2 -0
- package/template/.aioson/locales/es/agents/orache.md +2 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/pair.md +2 -0
- package/template/.aioson/locales/es/agents/pm.md +2 -0
- package/template/.aioson/locales/es/agents/product.md +52 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/es/agents/qa.md +2 -0
- package/template/.aioson/locales/es/agents/setup.md +2 -0
- package/template/.aioson/locales/es/agents/sheldon.md +117 -0
- package/template/.aioson/locales/es/agents/squad.md +16 -0
- package/template/.aioson/locales/es/agents/tester.md +9 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/fr/agents/analyst.md +2 -0
- package/template/.aioson/locales/fr/agents/architect.md +19 -0
- package/template/.aioson/locales/fr/agents/dev.md +64 -4
- package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/fr/agents/genome.md +2 -0
- package/template/.aioson/locales/fr/agents/neo.md +2 -0
- package/template/.aioson/locales/fr/agents/orache.md +2 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/pair.md +2 -0
- package/template/.aioson/locales/fr/agents/pm.md +2 -0
- package/template/.aioson/locales/fr/agents/product.md +52 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/fr/agents/qa.md +2 -0
- package/template/.aioson/locales/fr/agents/setup.md +2 -0
- package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
- package/template/.aioson/locales/fr/agents/squad.md +16 -0
- package/template/.aioson/locales/fr/agents/tester.md +9 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
- package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
- package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
- package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
- package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +101 -18
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
- package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
- package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
- package/template/.aioson/rules/README.md +69 -0
- package/template/.aioson/rules/data-format-convention.md +136 -0
- package/template/.aioson/rules/example-monetary-values.md +30 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
- package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
- package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +1 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +35 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +4 -1
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +15 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +32 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +20 -0
- package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
- package/template/.aioson/skills/static/context-budget-guide.md +46 -0
- package/template/.aioson/skills/static/harness-sensors.md +74 -0
- package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
- package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
- package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
- package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
- package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
- package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
- package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
- package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
- package/template/.aioson/skills/static/threejs-patterns.md +929 -0
- package/template/.aioson/skills/static/web-research-cache.md +112 -0
- package/template/.aioson/tasks/implementation-plan.md +21 -1
- package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/orache.md +5 -0
- package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
- package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
- package/template/AGENTS.md +55 -3
- package/template/CLAUDE.md +30 -0
- package/template/OPENCODE.md +4 -0
- package/template/researchs/.gitkeep +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson squad:bus — Intra-squad message bus CLI
|
|
5
|
+
*
|
|
6
|
+
* Subcommands:
|
|
7
|
+
* post — Post a message to the bus
|
|
8
|
+
* read — Read messages from the bus
|
|
9
|
+
* watch — Watch for new messages in real time (polls)
|
|
10
|
+
* summary — Show bus activity summary for a session
|
|
11
|
+
* clear — Delete the bus file for a session
|
|
12
|
+
* list — List sessions with active bus files
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* aioson squad:bus . --squad=content-team post --from=researcher --type=finding --content="..."
|
|
16
|
+
* aioson squad:bus . --squad=content-team read --session=SESSION_ID [--to=writer] [--type=finding]
|
|
17
|
+
* aioson squad:bus . --squad=content-team watch --session=SESSION_ID [--to=writer]
|
|
18
|
+
* aioson squad:bus . --squad=content-team summary --session=SESSION_ID
|
|
19
|
+
* aioson squad:bus . --squad=content-team clear --session=SESSION_ID
|
|
20
|
+
* aioson squad:bus . --squad=content-team list
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const bus = require('../squad/intra-bus');
|
|
25
|
+
|
|
26
|
+
function formatMessage(msg, { compact = false } = {}) {
|
|
27
|
+
if (compact) {
|
|
28
|
+
return `[${msg.ts.slice(0, 19)}] ${msg.from}→${msg.to} (${msg.type}): ${String(msg.content).slice(0, 120)}`;
|
|
29
|
+
}
|
|
30
|
+
return [
|
|
31
|
+
`id: ${msg.id}`,
|
|
32
|
+
`from: ${msg.from}`,
|
|
33
|
+
`to: ${msg.to}`,
|
|
34
|
+
`type: ${msg.type}`,
|
|
35
|
+
`ts: ${msg.ts}`,
|
|
36
|
+
`content: ${msg.content}`,
|
|
37
|
+
msg.metadata && Object.keys(msg.metadata).length > 0
|
|
38
|
+
? `meta: ${JSON.stringify(msg.metadata)}`
|
|
39
|
+
: null
|
|
40
|
+
].filter(Boolean).join('\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function runSquadBus({ args, options = {}, logger }) {
|
|
44
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
45
|
+
const sub = String(options.sub || args[1] || 'read').trim();
|
|
46
|
+
const squadSlug = String(options.squad || options.s || '').trim();
|
|
47
|
+
const sessionId = String(options.session || '').trim();
|
|
48
|
+
|
|
49
|
+
if (!squadSlug) {
|
|
50
|
+
logger.error('Error: --squad is required');
|
|
51
|
+
return { ok: false, error: 'missing_squad' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── POST ──────────────────────────────────────────────────────────────────
|
|
55
|
+
if (sub === 'post') {
|
|
56
|
+
const from = String(options.from || '').trim();
|
|
57
|
+
const to = String(options.to || '*').trim();
|
|
58
|
+
const type = String(options.type || 'finding').trim();
|
|
59
|
+
const content = String(options.content || options.message || '').trim();
|
|
60
|
+
const sid = sessionId || String(options.s || 'default').trim();
|
|
61
|
+
|
|
62
|
+
if (!from) { logger.error('Error: --from is required for post'); return { ok: false }; }
|
|
63
|
+
if (!content) { logger.error('Error: --content is required for post'); return { ok: false }; }
|
|
64
|
+
|
|
65
|
+
let metadata = {};
|
|
66
|
+
if (options.meta) {
|
|
67
|
+
try { metadata = JSON.parse(options.meta); } catch { /* ignore */ }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const msg = await bus.post(targetDir, squadSlug, sid, { from, to, type, content, metadata });
|
|
71
|
+
|
|
72
|
+
if (options.json) return { ok: true, message: msg };
|
|
73
|
+
|
|
74
|
+
logger.log(`✓ Posted to bus [${sid}]`);
|
|
75
|
+
logger.log(formatMessage(msg));
|
|
76
|
+
return { ok: true, message: msg };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── READ ──────────────────────────────────────────────────────────────────
|
|
80
|
+
if (sub === 'read') {
|
|
81
|
+
if (!sessionId) { logger.error('Error: --session is required for read'); return { ok: false }; }
|
|
82
|
+
|
|
83
|
+
const filters = {
|
|
84
|
+
from: options.from || undefined,
|
|
85
|
+
to: options.to || undefined,
|
|
86
|
+
type: options.type || undefined,
|
|
87
|
+
since: options.since || undefined,
|
|
88
|
+
last: options.last ? Number(options.last) : undefined
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const messages = await bus.read(targetDir, squadSlug, sessionId, filters);
|
|
92
|
+
|
|
93
|
+
if (options.json) return { ok: true, messages };
|
|
94
|
+
|
|
95
|
+
if (messages.length === 0) {
|
|
96
|
+
logger.log('No messages found.');
|
|
97
|
+
return { ok: true, messages: [] };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
logger.log(`Bus [${sessionId}] — ${messages.length} message(s):`);
|
|
101
|
+
logger.log('─'.repeat(60));
|
|
102
|
+
for (const msg of messages) {
|
|
103
|
+
logger.log(formatMessage(msg, { compact: !!options.compact }));
|
|
104
|
+
logger.log('─'.repeat(60));
|
|
105
|
+
}
|
|
106
|
+
return { ok: true, messages };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── WATCH ─────────────────────────────────────────────────────────────────
|
|
110
|
+
if (sub === 'watch') {
|
|
111
|
+
if (!sessionId) { logger.error('Error: --session is required for watch'); return { ok: false }; }
|
|
112
|
+
|
|
113
|
+
const pollMs = options.poll ? Number(options.poll) : 1500;
|
|
114
|
+
const timeoutMs = options.timeout ? Number(options.timeout) * 1000 : 10 * 60 * 1000; // 10m default
|
|
115
|
+
|
|
116
|
+
logger.log(`Watching bus [${sessionId}] (Ctrl+C to stop, timeout ${Math.round(timeoutMs / 60000)}m)...`);
|
|
117
|
+
|
|
118
|
+
const filters = {
|
|
119
|
+
to: options.to || undefined,
|
|
120
|
+
type: options.type || undefined
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
let count = 0;
|
|
124
|
+
const stop = bus.watch(targetDir, squadSlug, sessionId, (msg) => {
|
|
125
|
+
count++;
|
|
126
|
+
logger.log(formatMessage(msg, { compact: false }));
|
|
127
|
+
logger.log('─'.repeat(60));
|
|
128
|
+
}, { pollMs, timeoutMs, ...filters });
|
|
129
|
+
|
|
130
|
+
await new Promise((resolve) => {
|
|
131
|
+
process.on('SIGINT', resolve);
|
|
132
|
+
process.on('SIGTERM', resolve);
|
|
133
|
+
setTimeout(resolve, timeoutMs);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
stop();
|
|
137
|
+
logger.log(`Watch ended. ${count} message(s) received.`);
|
|
138
|
+
return { ok: true, count };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── SUMMARY ───────────────────────────────────────────────────────────────
|
|
142
|
+
if (sub === 'summary') {
|
|
143
|
+
if (!sessionId) { logger.error('Error: --session is required for summary'); return { ok: false }; }
|
|
144
|
+
|
|
145
|
+
const s = await bus.summary(targetDir, squadSlug, sessionId);
|
|
146
|
+
|
|
147
|
+
if (options.json) return { ok: true, summary: s };
|
|
148
|
+
|
|
149
|
+
logger.log(`Bus summary [${sessionId}]`);
|
|
150
|
+
logger.log('─'.repeat(50));
|
|
151
|
+
logger.log(`Total messages : ${s.total}`);
|
|
152
|
+
if (s.total === 0) {
|
|
153
|
+
logger.log('No messages yet.');
|
|
154
|
+
return { ok: true, summary: s };
|
|
155
|
+
}
|
|
156
|
+
logger.log(`First message : ${s.first_ts}`);
|
|
157
|
+
logger.log(`Last message : ${s.last_ts}`);
|
|
158
|
+
logger.log('');
|
|
159
|
+
logger.log('By type:');
|
|
160
|
+
for (const [type, count] of Object.entries(s.by_type)) {
|
|
161
|
+
logger.log(` ${type.padEnd(12)} ${count}`);
|
|
162
|
+
}
|
|
163
|
+
logger.log('');
|
|
164
|
+
logger.log('By executor:');
|
|
165
|
+
for (const [exec, count] of Object.entries(s.by_executor)) {
|
|
166
|
+
logger.log(` ${exec.padEnd(20)} ${count}`);
|
|
167
|
+
}
|
|
168
|
+
if (s.blocks.length > 0) {
|
|
169
|
+
logger.log('');
|
|
170
|
+
logger.log(`⚠ Blocks (${s.blocks.length}):`);
|
|
171
|
+
for (const b of s.blocks) {
|
|
172
|
+
logger.log(` [${b.ts.slice(0, 19)}] ${b.from}: ${b.content}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { ok: true, summary: s };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── CLEAR ─────────────────────────────────────────────────────────────────
|
|
179
|
+
if (sub === 'clear') {
|
|
180
|
+
if (!sessionId) { logger.error('Error: --session is required for clear'); return { ok: false }; }
|
|
181
|
+
|
|
182
|
+
const result = await bus.clear(targetDir, squadSlug, sessionId);
|
|
183
|
+
|
|
184
|
+
if (options.json) return result;
|
|
185
|
+
|
|
186
|
+
if (result.ok) {
|
|
187
|
+
logger.log(`✓ Bus cleared for session [${sessionId}]`);
|
|
188
|
+
} else {
|
|
189
|
+
logger.log(`No bus file found for session [${sessionId}]`);
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── LIST ──────────────────────────────────────────────────────────────────
|
|
195
|
+
if (sub === 'list') {
|
|
196
|
+
const sessions = await bus.listSessions(targetDir, squadSlug);
|
|
197
|
+
|
|
198
|
+
if (options.json) return { ok: true, sessions };
|
|
199
|
+
|
|
200
|
+
if (sessions.length === 0) {
|
|
201
|
+
logger.log('No bus sessions found.');
|
|
202
|
+
return { ok: true, sessions: [] };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
logger.log(`Bus sessions for squad "${squadSlug}" (${sessions.length}):`);
|
|
206
|
+
logger.log('─'.repeat(60));
|
|
207
|
+
for (const s of sessions) {
|
|
208
|
+
logger.log(` ${s.session_id.padEnd(36)} ${(s.size_bytes / 1024).toFixed(1)}KB ${s.modified_at.slice(0, 19)}`);
|
|
209
|
+
}
|
|
210
|
+
return { ok: true, sessions };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
logger.error(`Unknown subcommand: ${sub}. Valid: post, read, watch, summary, clear, list`);
|
|
214
|
+
return { ok: false, error: 'unknown_subcommand' };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { runSquadBus };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson squad:card — Generate A2A Agent Card from squad manifest
|
|
5
|
+
*
|
|
6
|
+
* Creates a Google A2A v1.0 compatible Agent Card that describes a squad's
|
|
7
|
+
* capabilities, enabling discovery by external A2A-compatible agents.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* aioson squad:card . --squad=content-team
|
|
11
|
+
* aioson squad:card . --squad=content-team --output=.well-known/agent.json
|
|
12
|
+
* aioson squad:card . --squad=content-team --port=3847 --json
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs/promises');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
|
|
18
|
+
const SQUADS_DIR = path.join('.aioson', 'squads');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Read squad manifest.
|
|
22
|
+
*/
|
|
23
|
+
async function readManifest(projectDir, squadSlug) {
|
|
24
|
+
const manifestPath = path.join(projectDir, SQUADS_DIR, squadSlug, 'squad.manifest.json');
|
|
25
|
+
return JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generate an A2A Agent Card from a squad manifest.
|
|
30
|
+
*
|
|
31
|
+
* A2A Agent Card spec (v1.0):
|
|
32
|
+
* - name, description, url, version
|
|
33
|
+
* - capabilities: streaming, pushNotifications
|
|
34
|
+
* - skills[]: id, name, description
|
|
35
|
+
* - defaultInputModes, defaultOutputModes
|
|
36
|
+
*/
|
|
37
|
+
function generateAgentCard(manifest, options = {}) {
|
|
38
|
+
const { port = 3847, host = 'localhost' } = options;
|
|
39
|
+
const baseUrl = `http://${host}:${port}/a2a/${manifest.slug}`;
|
|
40
|
+
|
|
41
|
+
// Map executors to A2A skills
|
|
42
|
+
const skills = (manifest.executors || []).map((executor) => ({
|
|
43
|
+
id: executor.slug,
|
|
44
|
+
name: executor.title || executor.slug,
|
|
45
|
+
description: executor.role || `Executor: ${executor.slug}`
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// Add workflow-level skills
|
|
49
|
+
if (manifest.workflows) {
|
|
50
|
+
for (const wf of manifest.workflows) {
|
|
51
|
+
skills.push({
|
|
52
|
+
id: `workflow-${wf.slug}`,
|
|
53
|
+
name: wf.title || wf.slug,
|
|
54
|
+
description: `Workflow: ${wf.title || wf.slug}`
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const card = {
|
|
60
|
+
name: manifest.name || manifest.slug,
|
|
61
|
+
description: manifest.mission || manifest.goal || `AIOSON Squad: ${manifest.slug}`,
|
|
62
|
+
url: baseUrl,
|
|
63
|
+
version: manifest.schemaVersion || '1.0.0',
|
|
64
|
+
provider: {
|
|
65
|
+
organization: 'AIOSON',
|
|
66
|
+
url: 'https://aiosforge.dev'
|
|
67
|
+
},
|
|
68
|
+
capabilities: {
|
|
69
|
+
streaming: true,
|
|
70
|
+
pushNotifications: true,
|
|
71
|
+
stateTransitionHistory: true
|
|
72
|
+
},
|
|
73
|
+
authentication: {
|
|
74
|
+
schemes: ['none']
|
|
75
|
+
},
|
|
76
|
+
skills,
|
|
77
|
+
defaultInputModes: ['text/plain', 'application/json'],
|
|
78
|
+
defaultOutputModes: ['text/plain', 'application/json']
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Add port information
|
|
82
|
+
if (manifest.ports) {
|
|
83
|
+
if (manifest.ports.inputs) {
|
|
84
|
+
card.inputPorts = manifest.ports.inputs.map((p) => ({
|
|
85
|
+
key: p.key,
|
|
86
|
+
dataType: p.dataType || 'any',
|
|
87
|
+
description: p.description,
|
|
88
|
+
required: p.required || false
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
if (manifest.ports.outputs) {
|
|
92
|
+
card.outputPorts = manifest.ports.outputs.map((p) => ({
|
|
93
|
+
key: p.key,
|
|
94
|
+
dataType: p.dataType || 'any',
|
|
95
|
+
description: p.description
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return card;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* CLI handler.
|
|
105
|
+
*/
|
|
106
|
+
async function runSquadCard({ args, options = {}, logger }) {
|
|
107
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
108
|
+
const squadSlug = String(options.squad || options.s || '').trim();
|
|
109
|
+
|
|
110
|
+
if (!squadSlug) {
|
|
111
|
+
logger.error('Error: --squad is required');
|
|
112
|
+
return { ok: false, error: 'missing_squad' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let manifest;
|
|
116
|
+
try {
|
|
117
|
+
manifest = await readManifest(targetDir, squadSlug);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
logger.error(`Error reading manifest: ${err.message}`);
|
|
120
|
+
return { ok: false, error: 'manifest_not_found' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const port = Number(options.port || 3847);
|
|
124
|
+
const host = options.host || 'localhost';
|
|
125
|
+
const card = generateAgentCard(manifest, { port, host });
|
|
126
|
+
|
|
127
|
+
// Write to output path
|
|
128
|
+
const outputPath = options.output
|
|
129
|
+
? path.resolve(targetDir, options.output)
|
|
130
|
+
: path.join(targetDir, '.well-known', `agent-${squadSlug}.json`);
|
|
131
|
+
|
|
132
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
133
|
+
await fs.writeFile(outputPath, JSON.stringify(card, null, 2), 'utf8');
|
|
134
|
+
|
|
135
|
+
if (options.json) return card;
|
|
136
|
+
|
|
137
|
+
logger.log(`A2A Agent Card for "${manifest.name || squadSlug}":`);
|
|
138
|
+
logger.log(` URL: ${card.url}`);
|
|
139
|
+
logger.log(` Skills: ${card.skills.length}`);
|
|
140
|
+
for (const s of card.skills) {
|
|
141
|
+
logger.log(` - ${s.id}: ${s.description}`);
|
|
142
|
+
}
|
|
143
|
+
logger.log('');
|
|
144
|
+
logger.log(`Output: ${path.relative(targetDir, outputPath)}`);
|
|
145
|
+
|
|
146
|
+
return { ok: true, card, outputPath: path.relative(targetDir, outputPath) };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = { runSquadCard, generateAgentCard };
|
|
@@ -4,6 +4,7 @@ const fs = require('node:fs/promises');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { SquadDaemon } = require('../squad-daemon');
|
|
6
6
|
const { openRuntimeDb } = require('../runtime-store');
|
|
7
|
+
const { consume: consumeInterSquadEvents } = require('../squad/inter-squad-events');
|
|
7
8
|
|
|
8
9
|
async function handleStart(projectDir, squadSlug, options, { logger, t }) {
|
|
9
10
|
if (!squadSlug) {
|
|
@@ -186,11 +187,144 @@ async function handleLogs(projectDir, squadSlug, { logger, t }) {
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
// ─── Persistent Execution Loop (4.3) ─────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Parse a loop-delay string like '30s', '2m', '1h' into milliseconds.
|
|
194
|
+
*/
|
|
195
|
+
function parseLoopDelay(str, defaultMs = 30_000) {
|
|
196
|
+
const s = String(str || '').trim().toLowerCase();
|
|
197
|
+
const match = s.match(/^(\d+)(s|m|h)?$/);
|
|
198
|
+
if (!match) return defaultMs;
|
|
199
|
+
const val = parseInt(match[1], 10);
|
|
200
|
+
const unit = match[2] || 's';
|
|
201
|
+
if (unit === 'h') return val * 3_600_000;
|
|
202
|
+
if (unit === 'm') return val * 60_000;
|
|
203
|
+
return val * 1_000;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Persistent daemon loop for a squad.
|
|
208
|
+
*
|
|
209
|
+
* Each iteration:
|
|
210
|
+
* 1. Check inter_squad_events for this squad (via manifest subscriptions)
|
|
211
|
+
* 2. If pending events: trigger squad:autorun to process them
|
|
212
|
+
* 3. Write daemon-alive.json heartbeat
|
|
213
|
+
* 4. Sleep loop-delay
|
|
214
|
+
*
|
|
215
|
+
* Exits gracefully on SIGTERM (waits for current iteration to finish).
|
|
216
|
+
*/
|
|
217
|
+
async function handlePersistent(projectDir, squadSlug, options, { logger }) {
|
|
218
|
+
if (!squadSlug) {
|
|
219
|
+
logger.error('Error: --squad is required for --persistent');
|
|
220
|
+
return { ok: false, error: 'missing_squad' };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const loopDelayMs = parseLoopDelay(options['loop-delay'] || options.loopDelay, 30_000);
|
|
224
|
+
|
|
225
|
+
// Load manifest for subscriptions
|
|
226
|
+
let manifest = {};
|
|
227
|
+
try {
|
|
228
|
+
const manifestPath = path.join(projectDir, '.aioson', 'squads', squadSlug, 'squad.manifest.json');
|
|
229
|
+
manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
230
|
+
} catch { /* manifest optional */ }
|
|
231
|
+
|
|
232
|
+
const subscriptions = [
|
|
233
|
+
...(manifest.subscriptions || []),
|
|
234
|
+
...(manifest.depends_on || []).map((d) => d.event).filter(Boolean)
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
const aliveJsonPath = path.join(projectDir, '.aioson', 'squads', squadSlug, 'daemon-alive.json');
|
|
238
|
+
|
|
239
|
+
logger.log(`Persistent daemon — squad: ${squadSlug}`);
|
|
240
|
+
logger.log(`Loop delay: ${loopDelayMs / 1000}s`);
|
|
241
|
+
logger.log(`Subscriptions: ${subscriptions.length > 0 ? subscriptions.join(', ') : '(none)'}`);
|
|
242
|
+
logger.log('Press Ctrl+C to stop');
|
|
243
|
+
logger.log('');
|
|
244
|
+
|
|
245
|
+
let running = true;
|
|
246
|
+
process.on('SIGTERM', () => { running = false; });
|
|
247
|
+
process.on('SIGINT', () => { running = false; });
|
|
248
|
+
|
|
249
|
+
let iteration = 0;
|
|
250
|
+
|
|
251
|
+
while (running) {
|
|
252
|
+
iteration++;
|
|
253
|
+
const now = new Date().toISOString();
|
|
254
|
+
|
|
255
|
+
// Write heartbeat
|
|
256
|
+
await fs.mkdir(path.dirname(aliveJsonPath), { recursive: true });
|
|
257
|
+
await fs.writeFile(aliveJsonPath, JSON.stringify({
|
|
258
|
+
squad: squadSlug,
|
|
259
|
+
iteration,
|
|
260
|
+
last_check: now,
|
|
261
|
+
subscriptions,
|
|
262
|
+
pid: process.pid
|
|
263
|
+
}, null, 2), 'utf8').catch(() => {});
|
|
264
|
+
|
|
265
|
+
// Check inter-squad events
|
|
266
|
+
if (subscriptions.length > 0) {
|
|
267
|
+
const events = await consumeInterSquadEvents(projectDir, {
|
|
268
|
+
toSquad: squadSlug,
|
|
269
|
+
subscriptions
|
|
270
|
+
}).catch(() => []);
|
|
271
|
+
|
|
272
|
+
if (events.length > 0) {
|
|
273
|
+
logger.log(`[${now.slice(11, 19)}] ${events.length} inter-squad event(s) received:`);
|
|
274
|
+
for (const ev of events) {
|
|
275
|
+
logger.log(` ← [${ev.fromSquad}] ${ev.event}`);
|
|
276
|
+
}
|
|
277
|
+
logger.log(` → Triggering squad:autorun for "${squadSlug}"...`);
|
|
278
|
+
|
|
279
|
+
// Trigger autorun via CLI subprocess
|
|
280
|
+
const { spawnSync } = require('node:child_process');
|
|
281
|
+
const goal = events.map((e) => `Process event: ${e.event} from ${e.fromSquad}`).join('; ');
|
|
282
|
+
const autorunResult = spawnSync('aioson', [
|
|
283
|
+
'squad:autorun', projectDir,
|
|
284
|
+
`--squad=${squadSlug}`,
|
|
285
|
+
`--goal=${goal}`,
|
|
286
|
+
'--reflect'
|
|
287
|
+
], {
|
|
288
|
+
encoding: 'utf8',
|
|
289
|
+
timeout: 300_000, // 5 min per autorun
|
|
290
|
+
stdio: 'pipe'
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (autorunResult.status === 0) {
|
|
294
|
+
logger.log(' ✓ Autorun completed');
|
|
295
|
+
} else {
|
|
296
|
+
logger.log(` ✗ Autorun exited ${autorunResult.status}: ${(autorunResult.stderr || '').trim().slice(0, 100)}`);
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
logger.log(`[${now.slice(11, 19)}] No pending events for "${squadSlug}" — sleeping ${loopDelayMs / 1000}s`);
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
logger.log(`[${now.slice(11, 19)}] Iteration ${iteration} — no subscriptions configured, heartbeat only`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Sleep loop-delay (interruptible)
|
|
306
|
+
if (running) {
|
|
307
|
+
await new Promise((resolve) => setTimeout(resolve, loopDelayMs));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
logger.log('');
|
|
312
|
+
logger.log(`Persistent daemon stopped (${iteration} iteration(s) completed)`);
|
|
313
|
+
await fs.unlink(aliveJsonPath).catch(() => {});
|
|
314
|
+
|
|
315
|
+
return { ok: true, iterations: iteration };
|
|
316
|
+
}
|
|
317
|
+
|
|
189
318
|
async function runSquadDaemon({ args, options, logger, t }) {
|
|
190
319
|
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
191
320
|
const sub = options.sub || 'status';
|
|
192
321
|
const squadSlug = options.squad;
|
|
193
322
|
|
|
323
|
+
// --persistent flag triggers persistent loop mode regardless of sub
|
|
324
|
+
if (options.persistent) {
|
|
325
|
+
return handlePersistent(targetDir, squadSlug, options, { logger });
|
|
326
|
+
}
|
|
327
|
+
|
|
194
328
|
switch (sub) {
|
|
195
329
|
case 'start':
|
|
196
330
|
return handleStart(targetDir, squadSlug, options, { logger, t });
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson squad:dependency-graph [projectDir]
|
|
5
|
+
*
|
|
6
|
+
* Reads squad manifests and renders a dependency graph showing which squads
|
|
7
|
+
* depend on outputs from other squads via `depends_on[]` in their manifest.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* aioson squad:dependency-graph .
|
|
11
|
+
* aioson squad:dependency-graph . --format=mermaid
|
|
12
|
+
* aioson squad:dependency-graph . --json
|
|
13
|
+
*
|
|
14
|
+
* Flags:
|
|
15
|
+
* --format Output format: ascii (default) | mermaid
|
|
16
|
+
* --json Return JSON with squads and edges
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('node:fs/promises');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
|
|
22
|
+
async function loadSquadManifests(projectDir) {
|
|
23
|
+
const squadsDir = path.join(projectDir, '.aioson', 'squads');
|
|
24
|
+
let entries;
|
|
25
|
+
try {
|
|
26
|
+
entries = await fs.readdir(squadsDir, { withFileTypes: true });
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const manifests = [];
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (!entry.isDirectory()) continue;
|
|
34
|
+
const manifestPath = path.join(squadsDir, entry.name, 'squad.manifest.json');
|
|
35
|
+
try {
|
|
36
|
+
const raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
37
|
+
manifests.push(raw);
|
|
38
|
+
} catch { /* skip invalid or missing manifests */ }
|
|
39
|
+
}
|
|
40
|
+
return manifests;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function buildEdges(manifests) {
|
|
44
|
+
const edges = [];
|
|
45
|
+
for (const manifest of manifests) {
|
|
46
|
+
for (const dep of manifest.depends_on || []) {
|
|
47
|
+
edges.push({
|
|
48
|
+
from: dep.squad,
|
|
49
|
+
to: manifest.slug,
|
|
50
|
+
event: dep.event || '*',
|
|
51
|
+
inputMapping: dep.input_mapping || null
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return edges;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderAscii(manifests, edges) {
|
|
59
|
+
const lines = [];
|
|
60
|
+
lines.push('Inter-Squad Dependency Graph');
|
|
61
|
+
lines.push('─'.repeat(44));
|
|
62
|
+
|
|
63
|
+
if (edges.length === 0) {
|
|
64
|
+
lines.push('(No inter-squad dependencies declared)');
|
|
65
|
+
lines.push('');
|
|
66
|
+
lines.push('Declare dependencies in your squad manifest:');
|
|
67
|
+
lines.push(' "depends_on": [');
|
|
68
|
+
lines.push(' { "squad": "content-team", "event": "episode.created",');
|
|
69
|
+
lines.push(' "input_mapping": { "file": "tasks[0].context.episode_file" } }');
|
|
70
|
+
lines.push(' ]');
|
|
71
|
+
} else {
|
|
72
|
+
for (const edge of edges) {
|
|
73
|
+
const mapping = edge.inputMapping
|
|
74
|
+
? ` [maps: ${JSON.stringify(edge.inputMapping)}]`
|
|
75
|
+
: '';
|
|
76
|
+
lines.push(` [${edge.from}] ──(${edge.event})──▶ [${edge.to}]${mapping}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const involvedSlugs = new Set([
|
|
81
|
+
...edges.map((e) => e.from),
|
|
82
|
+
...edges.map((e) => e.to)
|
|
83
|
+
]);
|
|
84
|
+
const isolated = manifests.filter((m) => !involvedSlugs.has(m.slug));
|
|
85
|
+
if (isolated.length > 0) {
|
|
86
|
+
lines.push('');
|
|
87
|
+
lines.push('Isolated squads (no declared dependencies):');
|
|
88
|
+
for (const m of isolated) {
|
|
89
|
+
lines.push(` [${m.slug}] ${m.mission ? '— ' + m.mission : ''}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderMermaid(manifests, edges) {
|
|
97
|
+
const lines = ['graph LR'];
|
|
98
|
+
const slugs = new Set(manifests.map((m) => m.slug));
|
|
99
|
+
|
|
100
|
+
// Add squad nodes with labels
|
|
101
|
+
for (const m of manifests) {
|
|
102
|
+
lines.push(` ${m.slug}["${m.name || m.slug}"]`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add dependency nodes that might not have manifests in this project
|
|
106
|
+
for (const edge of edges) {
|
|
107
|
+
if (!slugs.has(edge.from)) {
|
|
108
|
+
lines.push(` ${edge.from}["${edge.from} (external)"]`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
lines.push('');
|
|
113
|
+
|
|
114
|
+
// Add edges
|
|
115
|
+
for (const edge of edges) {
|
|
116
|
+
lines.push(` ${edge.from} -->|"${edge.event}"| ${edge.to}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return lines.join('\n');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function runSquadDependencyGraph({ args, options = {}, logger }) {
|
|
123
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
124
|
+
const format = String(options.format || 'ascii').trim().toLowerCase();
|
|
125
|
+
|
|
126
|
+
const manifests = await loadSquadManifests(projectDir);
|
|
127
|
+
|
|
128
|
+
if (manifests.length === 0) {
|
|
129
|
+
logger.log('No squad manifests found in .aioson/squads/');
|
|
130
|
+
logger.log('Create a squad first: aioson squad:create .');
|
|
131
|
+
return { ok: true, squads: 0, edges: [] };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const edges = buildEdges(manifests);
|
|
135
|
+
|
|
136
|
+
if (options.json) {
|
|
137
|
+
return {
|
|
138
|
+
ok: true,
|
|
139
|
+
squads: manifests.map((m) => ({ slug: m.slug, name: m.name, dependsOn: m.depends_on || [] })),
|
|
140
|
+
edges
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
logger.log('');
|
|
145
|
+
|
|
146
|
+
if (format === 'mermaid') {
|
|
147
|
+
logger.log(renderMermaid(manifests, edges));
|
|
148
|
+
} else {
|
|
149
|
+
logger.log(renderAscii(manifests, edges));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
logger.log('');
|
|
153
|
+
logger.log(`${manifests.length} squad(s) · ${edges.length} dependency edge(s)`);
|
|
154
|
+
logger.log('');
|
|
155
|
+
if (edges.length > 0) {
|
|
156
|
+
logger.log('Tips:');
|
|
157
|
+
logger.log(' • Squads publish events via: aioson inter-squad:publish . --from=<squad> --event=<name> --payload=\'{"key":"val"}\'');
|
|
158
|
+
logger.log(' • Squads consume events automatically at the start of squad:autorun');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { ok: true, squads: manifests.length, edges };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { runSquadDependencyGraph };
|