@jaimevalasek/aioson 1.3.0 → 1.4.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/README.md +19 -2
- 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 +111 -13
- package/docs/pt/automacao-squads.md +407 -0
- package/docs/pt/cenarios.md +3 -3
- package/docs/pt/clientes-ai.md +62 -0
- package/docs/pt/comandos-cli.md +167 -17
- package/docs/pt/deyvin.md +115 -0
- package/docs/pt/genome-3.0-spec.md +11 -11
- package/docs/pt/inicio-rapido.md +45 -0
- 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-genoma.md → squad-genome.md} +81 -75
- package/docs/testing/genome-2.0-rollout.md +1 -1
- package/package.json +3 -3
- package/src/agents.js +21 -5
- package/src/backup-provider.js +303 -0
- package/src/cli.js +178 -2
- package/src/commands/agents.js +22 -4
- 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 +833 -2
- package/src/commands/scan-project.js +288 -24
- package/src/commands/setup-context.js +23 -0
- package/src/commands/skill.js +558 -0
- package/src/commands/squad-agent-create.js +788 -0
- package/src/commands/squad-doctor.js +51 -1
- package/src/commands/squad-investigate.js +261 -0
- package/src/commands/squad-learning.js +209 -0
- package/src/commands/squad-pipeline.js +247 -1
- package/src/commands/squad-plan.js +329 -0
- package/src/commands/squad-status.js +1 -1
- package/src/commands/squad-validate.js +57 -1
- package/src/commands/test-agents.js +6 -1
- package/src/commands/workflow-next.js +8 -1
- package/src/commands/workflow-status.js +250 -0
- package/src/constants.js +80 -16
- package/src/context-memory.js +837 -0
- package/src/context-writer.js +2 -0
- 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 +206 -7
- package/src/i18n/messages/es.js +123 -6
- package/src/i18n/messages/fr.js +122 -5
- package/src/i18n/messages/pt-BR.js +205 -12
- package/src/installer.js +30 -2
- package/src/lib/genomes/compat.js +1 -1
- package/src/runtime-store.js +780 -42
- package/src/session-handoff.js +77 -0
- package/template/.aioson/agents/analyst.md +36 -9
- package/template/.aioson/agents/architect.md +20 -5
- package/template/.aioson/agents/dev.md +135 -15
- package/template/.aioson/agents/deyvin.md +166 -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/orache.md +371 -0
- package/template/.aioson/agents/orchestrator.md +37 -2
- package/template/.aioson/agents/pair.md +5 -0
- package/template/.aioson/agents/pm.md +17 -5
- package/template/.aioson/agents/product.md +58 -22
- 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 +17 -5
- package/template/.aioson/agents/setup.md +81 -5
- package/template/.aioson/agents/squad.md +675 -28
- package/template/.aioson/agents/ux-ui.md +277 -34
- package/template/.aioson/config.md +175 -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 +26 -4
- package/template/.aioson/locales/en/agents/architect.md +10 -0
- package/template/.aioson/locales/en/agents/dev.md +89 -4
- package/template/.aioson/locales/en/agents/deyvin.md +129 -0
- package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
- package/template/.aioson/locales/en/agents/orchestrator.md +36 -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 +7 -0
- package/template/.aioson/locales/en/agents/setup.md +51 -5
- package/template/.aioson/locales/en/agents/squad.md +203 -15
- package/template/.aioson/locales/en/agents/ux-ui.md +375 -35
- package/template/.aioson/locales/es/agents/analyst.md +16 -4
- package/template/.aioson/locales/es/agents/architect.md +10 -0
- package/template/.aioson/locales/es/agents/dev.md +70 -2
- package/template/.aioson/locales/es/agents/deyvin.md +89 -0
- package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
- package/template/.aioson/locales/es/agents/orache.md +103 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +36 -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 +7 -0
- package/template/.aioson/locales/es/agents/setup.md +28 -5
- package/template/.aioson/locales/es/agents/squad.md +221 -15
- package/template/.aioson/locales/es/agents/ux-ui.md +26 -25
- package/template/.aioson/locales/fr/agents/analyst.md +16 -4
- package/template/.aioson/locales/fr/agents/architect.md +10 -0
- package/template/.aioson/locales/fr/agents/dev.md +70 -2
- package/template/.aioson/locales/fr/agents/deyvin.md +89 -0
- package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
- package/template/.aioson/locales/fr/agents/orache.md +104 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +36 -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 +7 -0
- package/template/.aioson/locales/fr/agents/setup.md +28 -5
- package/template/.aioson/locales/fr/agents/squad.md +216 -10
- package/template/.aioson/locales/fr/agents/ux-ui.md +26 -25
- package/template/.aioson/locales/pt-BR/agents/analyst.md +26 -4
- package/template/.aioson/locales/pt-BR/agents/architect.md +10 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +93 -4
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +129 -0
- package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
- package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +36 -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 +35 -17
- package/template/.aioson/locales/pt-BR/agents/qa.md +7 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +51 -5
- package/template/.aioson/locales/pt-BR/agents/squad.md +486 -47
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +361 -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 +11 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +257 -1
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +157 -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 +172 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +490 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +237 -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 +350 -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-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/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/patterns/.gitkeep +0 -0
- package/template/.aioson/skills/squad/references/.gitkeep +0 -0
- package/template/.aioson/tasks/implementation-plan.md +288 -0
- package/template/.aioson/tasks/squad-create.md +1 -1
- 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-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/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/.gemini/GEMINI.md +2 -0
- package/template/.gemini/commands/aios-deyvin.toml +6 -0
- package/template/.gemini/commands/aios-pair.toml +6 -0
- package/template/AGENTS.md +34 -6
- package/template/CLAUDE.md +31 -4
- package/template/OPENCODE.md +6 -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 };
|
|
@@ -7,7 +7,7 @@ const { flattenGenomeBindings, mergeGenomeBindings } = require('../genomes/bindi
|
|
|
7
7
|
const SQUADS_DIR = '.aioson/squads';
|
|
8
8
|
const AGENTS_ROOT = 'agents';
|
|
9
9
|
const OUTPUT_ROOT = 'output';
|
|
10
|
-
const LOGS_ROOT = '
|
|
10
|
+
const LOGS_ROOT = 'aioson-logs';
|
|
11
11
|
const SKIP_FILES = new Set(['memory.md', '.gitkeep']);
|
|
12
12
|
const SESSION_HTML_RE = /\.html?$/i;
|
|
13
13
|
|
|
@@ -142,7 +142,62 @@ async function validateSemanticDeep(projectDir, slug, manifest) {
|
|
|
142
142
|
}
|
|
143
143
|
} catch { warnings.push('AGENTS.md not found'); }
|
|
144
144
|
|
|
145
|
-
// 5.
|
|
145
|
+
// 5. Output strategy validation
|
|
146
|
+
const outputStrategy = manifest.outputStrategy && typeof manifest.outputStrategy === 'object'
|
|
147
|
+
? manifest.outputStrategy
|
|
148
|
+
: null;
|
|
149
|
+
|
|
150
|
+
if (outputStrategy) {
|
|
151
|
+
const validModes = ['files', 'sqlite', 'hybrid'];
|
|
152
|
+
if (outputStrategy.mode && !validModes.includes(outputStrategy.mode)) {
|
|
153
|
+
errors.push(`Invalid outputStrategy.mode: "${outputStrategy.mode}" (expected: ${validModes.join(', ')})`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const delivery = outputStrategy.delivery && typeof outputStrategy.delivery === 'object'
|
|
157
|
+
? outputStrategy.delivery
|
|
158
|
+
: null;
|
|
159
|
+
|
|
160
|
+
if (delivery) {
|
|
161
|
+
const webhooks = Array.isArray(delivery.webhooks) ? delivery.webhooks : [];
|
|
162
|
+
for (const wh of webhooks) {
|
|
163
|
+
if (!wh.slug) {
|
|
164
|
+
errors.push('Webhook missing required "slug" field');
|
|
165
|
+
}
|
|
166
|
+
if (!wh.trigger) {
|
|
167
|
+
errors.push(`Webhook "${wh.slug || '?'}" missing required "trigger" field`);
|
|
168
|
+
}
|
|
169
|
+
const validTriggers = ['on-publish', 'on-create', 'manual'];
|
|
170
|
+
if (wh.trigger && !validTriggers.includes(wh.trigger)) {
|
|
171
|
+
warnings.push(`Webhook "${wh.slug}" has unknown trigger: "${wh.trigger}"`);
|
|
172
|
+
}
|
|
173
|
+
if (wh.url && wh.url.includes('{{ENV:')) {
|
|
174
|
+
const envMatch = wh.url.match(/\{\{ENV:(\w+)\}\}/);
|
|
175
|
+
if (envMatch && !process.env[envMatch[1]]) {
|
|
176
|
+
warnings.push(`Webhook "${wh.slug}" references unset env var: ${envMatch[1]}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (wh.worker) {
|
|
180
|
+
const workerPath = path.join(projectDir, wh.worker);
|
|
181
|
+
if (!(await pathExists(workerPath))) {
|
|
182
|
+
warnings.push(`Webhook "${wh.slug}" worker not found: ${wh.worker}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (delivery.autoPublish && webhooks.length === 0 && !delivery.cloudPublish) {
|
|
188
|
+
warnings.push('autoPublish is enabled but no webhooks or cloudPublish configured');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (outputStrategy.mode === 'files' && outputStrategy.dataOutput && outputStrategy.dataOutput.enabled) {
|
|
193
|
+
warnings.push('outputStrategy.mode is "files" but dataOutput.enabled is true — consider "hybrid"');
|
|
194
|
+
}
|
|
195
|
+
if (outputStrategy.mode === 'sqlite' && outputStrategy.fileOutput && outputStrategy.fileOutput.enabled) {
|
|
196
|
+
warnings.push('outputStrategy.mode is "sqlite" but fileOutput.enabled is true — consider "hybrid"');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 6. Readiness não contradiz blockers
|
|
146
201
|
if (manifest.readiness) {
|
|
147
202
|
for (const [dim, val] of Object.entries(manifest.readiness)) {
|
|
148
203
|
if (val && val.status === 'ready' && val.blocker) {
|
|
@@ -207,6 +262,7 @@ async function runSquadValidate({ args = [], options = {}, logger = console } =
|
|
|
207
262
|
logger.log(` Structure: ${structure.errors.length === 0 ? '\u2705 PASS' : '\u274c FAIL'}`);
|
|
208
263
|
logger.log(` Semantics: ${semantics.errors.length === 0 ? (semantics.warnings.length > 0 ? '\u26a0\ufe0f WARNINGS' : '\u2705 PASS') : '\u274c FAIL'}`);
|
|
209
264
|
logger.log(` Semantic deep: ${semanticDeep.errors.length === 0 ? (semanticDeep.warnings.length > 0 ? '\u26a0\ufe0f WARNINGS' : '\u2705 PASS') : '\u274c FAIL'}`);
|
|
265
|
+
logger.log(` Output strategy: ${manifest.outputStrategy ? `${manifest.outputStrategy.mode || 'unknown'} mode` : 'not configured'}`);
|
|
210
266
|
|
|
211
267
|
if (allErrors.length > 0) {
|
|
212
268
|
logger.log('');
|
|
@@ -14,7 +14,7 @@ const AGENTS = [
|
|
|
14
14
|
'qa',
|
|
15
15
|
'orchestrator',
|
|
16
16
|
'squad',
|
|
17
|
-
'
|
|
17
|
+
'genome',
|
|
18
18
|
'profiler-researcher',
|
|
19
19
|
'profiler-enricher',
|
|
20
20
|
'profiler-forge'
|
|
@@ -167,6 +167,11 @@ async function runTestAgents({ options, logger }) {
|
|
|
167
167
|
addCheck(`skill: ${skill}`, content !== null && content.length > 100);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
const packagedDesignSkill = await readFile(
|
|
171
|
+
path.join(TEMPLATE_DIR, '.aioson', 'skills', 'design', 'cognitive-ui', 'SKILL.md')
|
|
172
|
+
);
|
|
173
|
+
addCheck('skill package: cognitive-ui', packagedDesignSkill !== null && packagedDesignSkill.length > 100);
|
|
174
|
+
|
|
170
175
|
// ── Summary ──────────────────────────────────────────────────────────────
|
|
171
176
|
log('');
|
|
172
177
|
const total = passed + failed;
|