@jaimevalasek/aioson 1.3.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 +456 -0
- package/CODE_OF_CONDUCT.md +12 -0
- package/CONTRIBUTING.md +13 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/bin/aioson.js +4 -0
- package/docs/en/cli-reference.md +398 -0
- package/docs/en/i18n.md +52 -0
- package/docs/en/json-schemas.md +41 -0
- package/docs/en/mcp.md +56 -0
- package/docs/en/parallel.md +82 -0
- package/docs/en/qa-browser.md +339 -0
- package/docs/en/release-flow.md +22 -0
- package/docs/en/release-notes-template.md +41 -0
- package/docs/en/release.md +28 -0
- package/docs/en/schemas/agent-prompt.schema.json +17 -0
- package/docs/en/schemas/agents.schema.json +32 -0
- package/docs/en/schemas/context-validate.schema.json +36 -0
- package/docs/en/schemas/doctor.schema.json +89 -0
- package/docs/en/schemas/error.schema.json +24 -0
- package/docs/en/schemas/i18n-add.schema.json +15 -0
- package/docs/en/schemas/index.json +116 -0
- package/docs/en/schemas/info.schema.json +39 -0
- package/docs/en/schemas/init.schema.json +48 -0
- package/docs/en/schemas/install.schema.json +60 -0
- package/docs/en/schemas/locale-apply.schema.json +30 -0
- package/docs/en/schemas/mcp-doctor.schema.json +95 -0
- package/docs/en/schemas/mcp-init.schema.json +122 -0
- package/docs/en/schemas/package-test.schema.json +24 -0
- package/docs/en/schemas/parallel-assign.schema.json +57 -0
- package/docs/en/schemas/parallel-doctor.schema.json +86 -0
- package/docs/en/schemas/parallel-init.schema.json +53 -0
- package/docs/en/schemas/parallel-status.schema.json +94 -0
- package/docs/en/schemas/setup-context.schema.json +39 -0
- package/docs/en/schemas/smoke.schema.json +23 -0
- package/docs/en/schemas/update.schema.json +48 -0
- package/docs/en/schemas/workflow-plan.schema.json +30 -0
- package/docs/en/web3.md +54 -0
- package/docs/pt/README.md +46 -0
- package/docs/pt/advisor-spec.md +335 -0
- package/docs/pt/agentes.md +453 -0
- package/docs/pt/cenarios.md +1230 -0
- package/docs/pt/clientes-ai.md +224 -0
- package/docs/pt/comandos-cli.md +511 -0
- package/docs/pt/genome-3.0-spec.md +296 -0
- package/docs/pt/guia-engineer.md +226 -0
- package/docs/pt/inicio-rapido.md +138 -0
- package/docs/pt/profiler-system.md +214 -0
- package/docs/pt/runtime-observability.md +72 -0
- package/docs/pt/squad-genoma.md +777 -0
- package/docs/pt/web3.md +797 -0
- package/docs/testing/genome-2.0-manual-regression.md +23 -0
- package/docs/testing/genome-2.0-matrix.md +36 -0
- package/docs/testing/genome-2.0-rollout.md +184 -0
- package/package.json +50 -0
- package/src/agents.js +56 -0
- package/src/cli.js +497 -0
- package/src/commands/agents.js +142 -0
- package/src/commands/cloud.js +1767 -0
- package/src/commands/config.js +90 -0
- package/src/commands/context-validate.js +91 -0
- package/src/commands/doctor.js +123 -0
- package/src/commands/genome-doctor.js +41 -0
- package/src/commands/genome-migrate.js +49 -0
- package/src/commands/i18n-add.js +56 -0
- package/src/commands/info.js +41 -0
- package/src/commands/init.js +75 -0
- package/src/commands/install.js +68 -0
- package/src/commands/locale-apply.js +51 -0
- package/src/commands/locale-diff.js +126 -0
- package/src/commands/mcp-doctor.js +406 -0
- package/src/commands/mcp-init.js +379 -0
- package/src/commands/package-e2e.js +273 -0
- package/src/commands/parallel-assign.js +403 -0
- package/src/commands/parallel-doctor.js +437 -0
- package/src/commands/parallel-init.js +249 -0
- package/src/commands/parallel-status.js +290 -0
- package/src/commands/qa-doctor.js +185 -0
- package/src/commands/qa-init.js +161 -0
- package/src/commands/qa-report.js +58 -0
- package/src/commands/qa-run.js +873 -0
- package/src/commands/qa-scan.js +337 -0
- package/src/commands/runtime.js +948 -0
- package/src/commands/scan-project.js +1107 -0
- package/src/commands/setup-context.js +650 -0
- package/src/commands/smoke.js +426 -0
- package/src/commands/squad-doctor.js +358 -0
- package/src/commands/squad-export.js +46 -0
- package/src/commands/squad-pipeline.js +97 -0
- package/src/commands/squad-repair-genomes.js +39 -0
- package/src/commands/squad-status.js +424 -0
- package/src/commands/squad-validate.js +230 -0
- package/src/commands/test-agents.js +194 -0
- package/src/commands/update.js +55 -0
- package/src/commands/workflow-next.js +594 -0
- package/src/commands/workflow-plan.js +108 -0
- package/src/constants.js +314 -0
- package/src/context-parse-reason.js +22 -0
- package/src/context-writer.js +150 -0
- package/src/context.js +217 -0
- package/src/detector.js +261 -0
- package/src/doctor.js +289 -0
- package/src/execution-gateway.js +461 -0
- package/src/genome-files.js +198 -0
- package/src/genome-format.js +442 -0
- package/src/genome-schema.js +215 -0
- package/src/genomes/bindings.js +281 -0
- package/src/genomes.js +467 -0
- package/src/i18n/index.js +103 -0
- package/src/i18n/messages/en.js +784 -0
- package/src/i18n/messages/es.js +718 -0
- package/src/i18n/messages/fr.js +725 -0
- package/src/i18n/messages/pt-BR.js +818 -0
- package/src/i18n/scaffold.js +64 -0
- package/src/installer.js +232 -0
- package/src/lib/genomes/compat.js +206 -0
- package/src/lib/genomes/migrate.js +90 -0
- package/src/lib/squads/genome-repair.js +49 -0
- package/src/locales.js +84 -0
- package/src/onboarding.js +305 -0
- package/src/parser.js +53 -0
- package/src/prompt-tool.js +20 -0
- package/src/qa-html-report.js +472 -0
- package/src/runtime-store.js +1527 -0
- package/src/squads/apply-genome.js +21 -0
- package/src/squads/genome-binding-service.js +154 -0
- package/src/updater.js +32 -0
- package/src/utils.js +46 -0
- package/src/version.js +50 -0
- package/template/.aioson/advisors/.gitkeep +1 -0
- package/template/.aioson/agents/analyst.md +225 -0
- package/template/.aioson/agents/architect.md +221 -0
- package/template/.aioson/agents/dev.md +201 -0
- package/template/.aioson/agents/discovery-design-doc.md +196 -0
- package/template/.aioson/agents/genoma.md +300 -0
- package/template/.aioson/agents/orchestrator.md +107 -0
- package/template/.aioson/agents/pm.md +89 -0
- package/template/.aioson/agents/product.md +361 -0
- package/template/.aioson/agents/profiler-enricher.md +266 -0
- package/template/.aioson/agents/profiler-forge.md +188 -0
- package/template/.aioson/agents/profiler-researcher.md +245 -0
- package/template/.aioson/agents/qa.md +344 -0
- package/template/.aioson/agents/setup.md +381 -0
- package/template/.aioson/agents/squad.md +837 -0
- package/template/.aioson/agents/ux-ui.md +416 -0
- package/template/.aioson/config.md +56 -0
- package/template/.aioson/context/.gitkeep +0 -0
- package/template/.aioson/context/parallel/.gitkeep +0 -0
- package/template/.aioson/context/spec.md.template +37 -0
- package/template/.aioson/genomas/.gitkeep +0 -0
- package/template/.aioson/locales/en/agents/analyst.md +214 -0
- package/template/.aioson/locales/en/agents/architect.md +210 -0
- package/template/.aioson/locales/en/agents/dev.md +187 -0
- package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
- package/template/.aioson/locales/en/agents/genoma.md +212 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
- package/template/.aioson/locales/en/agents/pm.md +77 -0
- package/template/.aioson/locales/en/agents/product.md +310 -0
- package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/en/agents/qa.md +214 -0
- package/template/.aioson/locales/en/agents/setup.md +342 -0
- package/template/.aioson/locales/en/agents/squad.md +247 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
- package/template/.aioson/locales/es/agents/analyst.md +203 -0
- package/template/.aioson/locales/es/agents/architect.md +208 -0
- package/template/.aioson/locales/es/agents/dev.md +183 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/es/agents/genoma.md +102 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/es/agents/pm.md +81 -0
- package/template/.aioson/locales/es/agents/product.md +310 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/es/agents/qa.md +163 -0
- package/template/.aioson/locales/es/agents/setup.md +347 -0
- package/template/.aioson/locales/es/agents/squad.md +247 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/fr/agents/analyst.md +203 -0
- package/template/.aioson/locales/fr/agents/architect.md +208 -0
- package/template/.aioson/locales/fr/agents/dev.md +183 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/fr/agents/genoma.md +102 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/fr/agents/pm.md +81 -0
- package/template/.aioson/locales/fr/agents/product.md +310 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/fr/agents/qa.md +163 -0
- package/template/.aioson/locales/fr/agents/setup.md +347 -0
- package/template/.aioson/locales/fr/agents/squad.md +247 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
- package/template/.aioson/mcp/servers.md +24 -0
- package/template/.aioson/profiler-reports/.gitkeep +1 -0
- package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
- package/template/.aioson/schemas/genome-meta.schema.json +150 -0
- package/template/.aioson/schemas/genome.schema.json +115 -0
- package/template/.aioson/schemas/readiness.schema.json +27 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
- package/template/.aioson/skills/dynamic/README.md +30 -0
- package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
- package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
- package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
- package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
- package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
- package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
- package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
- package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
- package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
- package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
- package/template/.aioson/skills/static/django-patterns.md +342 -0
- package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
- package/template/.aioson/skills/static/filament-patterns.md +267 -0
- package/template/.aioson/skills/static/flux-ui-components.md +262 -0
- package/template/.aioson/skills/static/git-conventions.md +227 -0
- package/template/.aioson/skills/static/interface-design.md +372 -0
- package/template/.aioson/skills/static/jetstream-setup.md +200 -0
- package/template/.aioson/skills/static/laravel-conventions.md +491 -0
- package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
- package/template/.aioson/skills/static/node-express-patterns.md +317 -0
- package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
- package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
- package/template/.aioson/skills/static/rails-conventions.md +307 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
- package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
- package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
- package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
- package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
- package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
- package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
- package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
- package/template/.aioson/squads/.artisan/.gitkeep +0 -0
- package/template/.aioson/squads/.gitkeep +0 -0
- package/template/.aioson/squads/memory.md +5 -0
- package/template/.aioson/tasks/squad-analyze.md +83 -0
- package/template/.aioson/tasks/squad-create.md +99 -0
- package/template/.aioson/tasks/squad-design.md +100 -0
- package/template/.aioson/tasks/squad-export.md +20 -0
- package/template/.aioson/tasks/squad-extend.md +68 -0
- package/template/.aioson/tasks/squad-pipeline.md +122 -0
- package/template/.aioson/tasks/squad-repair.md +85 -0
- package/template/.aioson/tasks/squad-validate.md +58 -0
- package/template/.aioson/templates/squads/content-basic/template.json +21 -0
- package/template/.aioson/templates/squads/media-channel/template.json +24 -0
- package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
- package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
- package/template/.claude/commands/aioson/analyst.md +5 -0
- package/template/.claude/commands/aioson/architect.md +5 -0
- package/template/.claude/commands/aioson/dev.md +5 -0
- package/template/.claude/commands/aioson/orchestrator.md +5 -0
- package/template/.claude/commands/aioson/pm.md +5 -0
- package/template/.claude/commands/aioson/qa.md +5 -0
- package/template/.claude/commands/aioson/setup.md +5 -0
- package/template/.claude/commands/aioson/ux-ui.md +5 -0
- package/template/.gemini/GEMINI.md +10 -0
- package/template/.gemini/commands/aios-analyst.toml +4 -0
- package/template/.gemini/commands/aios-architect.toml +7 -0
- package/template/.gemini/commands/aios-dev.toml +8 -0
- package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
- package/template/.gemini/commands/aios-orchestrator.toml +8 -0
- package/template/.gemini/commands/aios-pm.toml +8 -0
- package/template/.gemini/commands/aios-product.toml +4 -0
- package/template/.gemini/commands/aios-qa.toml +6 -0
- package/template/.gemini/commands/aios-setup.toml +3 -0
- package/template/.gemini/commands/aios-ux-ui.toml +8 -0
- package/template/AGENTS.md +67 -0
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +24 -0
- package/template/aioson-models.json +40 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { flattenGenomeBindings, mergeGenomeBindings } = require('../genomes/bindings');
|
|
6
|
+
|
|
7
|
+
const SQUADS_DIR = '.aioson/squads';
|
|
8
|
+
const AGENTS_ROOT = 'agents';
|
|
9
|
+
const OUTPUT_ROOT = 'output';
|
|
10
|
+
const LOGS_ROOT = 'aios-logs';
|
|
11
|
+
const SKIP_FILES = new Set(['memory.md', '.gitkeep']);
|
|
12
|
+
const SESSION_HTML_RE = /\.html?$/i;
|
|
13
|
+
|
|
14
|
+
function extractField(content, ...labels) {
|
|
15
|
+
for (const label of labels) {
|
|
16
|
+
const regex = new RegExp(`^(?:${label}):\\s*(.+)$`, 'im');
|
|
17
|
+
const match = String(content || '').match(regex);
|
|
18
|
+
if (match) return String(match[1]).trim();
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseListSection(content, heading) {
|
|
24
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
25
|
+
const startIndex = lines.findIndex((line) => line.trim() === `${heading}:`);
|
|
26
|
+
if (startIndex === -1) return [];
|
|
27
|
+
|
|
28
|
+
const values = [];
|
|
29
|
+
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i];
|
|
31
|
+
if (/^\S.+:$/.test(line.trim())) break;
|
|
32
|
+
const match = line.match(/^\s*-\s+(.+?)\s*$/);
|
|
33
|
+
if (match) values.push(match[1].trim());
|
|
34
|
+
}
|
|
35
|
+
return values;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeRel(relPath) {
|
|
39
|
+
return String(relPath || '')
|
|
40
|
+
.replace(/\\/g, '/')
|
|
41
|
+
.replace(/^\.\//, '')
|
|
42
|
+
.replace(/\/+$/, '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function pathExists(targetPath) {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(targetPath);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function readDirNames(targetPath) {
|
|
55
|
+
try {
|
|
56
|
+
return await fs.readdir(targetPath);
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function getLatestHtml(outputDirAbs) {
|
|
63
|
+
const latestAlias = path.join(outputDirAbs, 'latest.html');
|
|
64
|
+
if (await pathExists(latestAlias)) {
|
|
65
|
+
const stat = await fs.stat(latestAlias).catch(() => null);
|
|
66
|
+
return { absPath: latestAlias, mtime: stat?.mtime || null };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const sessionEntries = await readDirNames(outputDirAbs);
|
|
70
|
+
const candidates = [];
|
|
71
|
+
|
|
72
|
+
for (const file of sessionEntries) {
|
|
73
|
+
if (file === 'latest.html') continue;
|
|
74
|
+
if (!SESSION_HTML_RE.test(file)) continue;
|
|
75
|
+
const absPath = path.join(outputDirAbs, file);
|
|
76
|
+
const stat = await fs.stat(absPath).catch(() => null);
|
|
77
|
+
if (!stat?.isFile()) continue;
|
|
78
|
+
candidates.push({ absPath, mtime: stat.mtime });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const legacySession = path.join(outputDirAbs, 'session.html');
|
|
82
|
+
if (await pathExists(legacySession)) {
|
|
83
|
+
const stat = await fs.stat(legacySession).catch(() => null);
|
|
84
|
+
candidates.push({ absPath: legacySession, mtime: stat?.mtime || null });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
candidates.sort((a, b) => {
|
|
88
|
+
if (!a.mtime) return 1;
|
|
89
|
+
if (!b.mtime) return -1;
|
|
90
|
+
return b.mtime - a.mtime;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return candidates[0] || null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function collectDirStats(targetDir, relDir, options = {}) {
|
|
97
|
+
const normalized = normalizeRel(relDir);
|
|
98
|
+
if (!normalized) {
|
|
99
|
+
return { relDir: normalized, absDir: null, exists: false, entries: [] };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const absDir = path.join(targetDir, normalized);
|
|
103
|
+
const entries = await readDirNames(absDir);
|
|
104
|
+
const files = [];
|
|
105
|
+
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (SKIP_FILES.has(entry)) continue;
|
|
108
|
+
const absPath = path.join(absDir, entry);
|
|
109
|
+
const stat = await fs.stat(absPath).catch(() => null);
|
|
110
|
+
if (!stat) continue;
|
|
111
|
+
const match = typeof options.filter === 'function' ? options.filter(entry, stat) : true;
|
|
112
|
+
if (!match) continue;
|
|
113
|
+
files.push({ name: entry, absPath, stat });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
relDir: normalized,
|
|
118
|
+
absDir,
|
|
119
|
+
exists: files.length > 0 || (await pathExists(absDir)),
|
|
120
|
+
entries: files
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function buildSquadRecordFromMetadata(targetDir, file) {
|
|
125
|
+
const squadsDir = path.join(targetDir, SQUADS_DIR);
|
|
126
|
+
const filePath = path.join(squadsDir, file);
|
|
127
|
+
const content = await fs.readFile(filePath, 'utf8').catch(() => null);
|
|
128
|
+
if (!content) return null;
|
|
129
|
+
|
|
130
|
+
const trimmed = content.trim();
|
|
131
|
+
if (!trimmed || trimmed.startsWith('<!--')) return null;
|
|
132
|
+
|
|
133
|
+
const slug = file.replace(/\.md$/, '');
|
|
134
|
+
const squadName =
|
|
135
|
+
extractField(content, 'Squad', 'Squad Ativo', 'Squad Activo', 'Squad Actif') || slug;
|
|
136
|
+
const mode = extractField(content, 'Mode', 'Modo') || '—';
|
|
137
|
+
const goal = extractField(content, 'Goal', 'Objetivo', 'Objectif') || '—';
|
|
138
|
+
const agentsDir = normalizeRel(extractField(content, 'Agents', 'AgentsDir') || `${AGENTS_ROOT}/${slug}`);
|
|
139
|
+
const outputDir = normalizeRel(extractField(content, 'Output', 'OutputDir') || `${OUTPUT_ROOT}/${slug}`);
|
|
140
|
+
const logsDir = normalizeRel(
|
|
141
|
+
extractField(content, 'Logs', 'LogsDir') || `${normalizeRel(LOGS_ROOT)}/${slug}`
|
|
142
|
+
);
|
|
143
|
+
const genomes = parseListSection(content, 'Genomes');
|
|
144
|
+
const agentGenomes = parseListSection(content, 'AgentGenomes');
|
|
145
|
+
const latestSession = normalizeRel(
|
|
146
|
+
extractField(content, 'LatestSession', 'Latest Session', 'UltimaSessao', 'DerniereSession')
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const agents = await collectDirStats(targetDir, agentsDir, {
|
|
150
|
+
filter: (entry, stat) => stat.isFile() && entry.endsWith('.md')
|
|
151
|
+
});
|
|
152
|
+
const specialists = agents.entries.filter((entry) => entry.name !== 'orquestrador.md');
|
|
153
|
+
|
|
154
|
+
const sessions = await collectDirStats(targetDir, outputDir, {
|
|
155
|
+
filter: (entry, stat) => stat.isFile() && SESSION_HTML_RE.test(entry) && entry !== 'latest.html'
|
|
156
|
+
});
|
|
157
|
+
const logs = await collectDirStats(targetDir, logsDir, {
|
|
158
|
+
filter: (entry, stat) => stat.isFile()
|
|
159
|
+
});
|
|
160
|
+
const outputExists = await pathExists(path.join(targetDir, outputDir));
|
|
161
|
+
const latestHtml = latestSession
|
|
162
|
+
? {
|
|
163
|
+
absPath: path.join(targetDir, latestSession),
|
|
164
|
+
mtime: (await fs.stat(path.join(targetDir, latestSession)).catch(() => null))?.mtime || null
|
|
165
|
+
}
|
|
166
|
+
: await getLatestHtml(path.join(targetDir, outputDir));
|
|
167
|
+
|
|
168
|
+
const metaStat = await fs.stat(filePath).catch(() => null);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
slug,
|
|
172
|
+
file,
|
|
173
|
+
metadataPath: filePath,
|
|
174
|
+
squadName,
|
|
175
|
+
mode,
|
|
176
|
+
goal,
|
|
177
|
+
agentsDir,
|
|
178
|
+
outputDir,
|
|
179
|
+
logsDir,
|
|
180
|
+
genomes,
|
|
181
|
+
agentGenomes,
|
|
182
|
+
latestSession,
|
|
183
|
+
agentCount: agents.entries.length,
|
|
184
|
+
specialistCount: specialists.length,
|
|
185
|
+
sessionCount: sessions.entries.length,
|
|
186
|
+
logCount: logs.entries.length,
|
|
187
|
+
latestHtml: latestHtml
|
|
188
|
+
? normalizeRel(path.relative(targetDir, latestHtml.absPath))
|
|
189
|
+
: outputExists && (await pathExists(path.join(targetDir, outputDir, 'session.html')))
|
|
190
|
+
? normalizeRel(path.join(outputDir, 'session.html'))
|
|
191
|
+
: '—',
|
|
192
|
+
mtime: latestHtml?.mtime || metaStat?.mtime || null
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function buildSquadRecordFromPackageDir(targetDir, slug) {
|
|
197
|
+
const packageDir = path.join(targetDir, SQUADS_DIR, slug);
|
|
198
|
+
const manifestPath = path.join(packageDir, 'squad.manifest.json');
|
|
199
|
+
const summaryPath = path.join(packageDir, 'squad.md');
|
|
200
|
+
const manifestRaw = await fs.readFile(manifestPath, 'utf8').catch(() => null);
|
|
201
|
+
if (!manifestRaw) return null;
|
|
202
|
+
|
|
203
|
+
let manifest = null;
|
|
204
|
+
try {
|
|
205
|
+
manifest = JSON.parse(manifestRaw);
|
|
206
|
+
} catch {
|
|
207
|
+
manifest = null;
|
|
208
|
+
}
|
|
209
|
+
if (!manifest || typeof manifest !== 'object') return null;
|
|
210
|
+
|
|
211
|
+
const summaryContent = await fs.readFile(summaryPath, 'utf8').catch(() => null);
|
|
212
|
+
const rules = manifest.rules && typeof manifest.rules === 'object' ? manifest.rules : {};
|
|
213
|
+
const packageInfo = manifest.package && typeof manifest.package === 'object' ? manifest.package : {};
|
|
214
|
+
const agentsDir = normalizeRel(packageInfo.agentsDir || path.join(SQUADS_DIR, slug, 'agents'));
|
|
215
|
+
const outputDir = normalizeRel(rules.outputsDir || `${OUTPUT_ROOT}/${slug}`);
|
|
216
|
+
const logsDir = normalizeRel(rules.logsDir || `${normalizeRel(LOGS_ROOT)}/${slug}`);
|
|
217
|
+
const latestSession = normalizeRel(
|
|
218
|
+
extractField(summaryContent || '', 'LatestSession', 'Latest Session', 'UltimaSessao', 'DerniereSession') ||
|
|
219
|
+
`${OUTPUT_ROOT}/${slug}/latest.html`
|
|
220
|
+
);
|
|
221
|
+
const genomeBindings = mergeGenomeBindings({
|
|
222
|
+
blueprintBindings: manifest.genomeBindings,
|
|
223
|
+
manifestBindings: manifest.genomeBindings || manifest.genomes,
|
|
224
|
+
legacyExecutors: manifest.executors
|
|
225
|
+
});
|
|
226
|
+
const flattenedBindings = flattenGenomeBindings(genomeBindings);
|
|
227
|
+
const genomes = flattenedBindings
|
|
228
|
+
.filter((item) => item.scope === 'squad')
|
|
229
|
+
.map((item) => item.slug);
|
|
230
|
+
const agentGenomes = flattenedBindings
|
|
231
|
+
.filter((item) => item.scope !== 'squad' && item.agentSlug)
|
|
232
|
+
.map((item) => `${item.agentSlug}: ${item.slug}`);
|
|
233
|
+
const fallbackGenomes = genomes.length > 0 ? genomes : parseListSection(summaryContent || '', 'Genomes');
|
|
234
|
+
const fallbackAgentGenomes =
|
|
235
|
+
agentGenomes.length > 0 ? agentGenomes : parseListSection(summaryContent || '', 'AgentGenomes');
|
|
236
|
+
|
|
237
|
+
const agents = await collectDirStats(targetDir, agentsDir, {
|
|
238
|
+
filter: (entry, stat) => stat.isFile() && entry.endsWith('.md')
|
|
239
|
+
});
|
|
240
|
+
const specialists = agents.entries.filter(
|
|
241
|
+
(entry) => entry.name !== 'orquestrador.md' && entry.name !== 'agents.md'
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const sessions = await collectDirStats(targetDir, outputDir, {
|
|
245
|
+
filter: (entry, stat) => stat.isFile() && SESSION_HTML_RE.test(entry) && entry !== 'latest.html'
|
|
246
|
+
});
|
|
247
|
+
const logs = await collectDirStats(targetDir, logsDir, {
|
|
248
|
+
filter: (entry, stat) => stat.isFile()
|
|
249
|
+
});
|
|
250
|
+
const outputExists = await pathExists(path.join(targetDir, outputDir));
|
|
251
|
+
const latestHtml = latestSession
|
|
252
|
+
? {
|
|
253
|
+
absPath: path.join(targetDir, latestSession),
|
|
254
|
+
mtime: (await fs.stat(path.join(targetDir, latestSession)).catch(() => null))?.mtime || null
|
|
255
|
+
}
|
|
256
|
+
: await getLatestHtml(path.join(targetDir, outputDir));
|
|
257
|
+
const manifestStat = await fs.stat(manifestPath).catch(() => null);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
slug,
|
|
261
|
+
file: `${slug}/squad.manifest.json`,
|
|
262
|
+
metadataPath: manifestPath,
|
|
263
|
+
squadName: String(manifest.name || slug),
|
|
264
|
+
mode: String(manifest.mode || 'content'),
|
|
265
|
+
goal: String(manifest.goal || '—'),
|
|
266
|
+
agentsDir,
|
|
267
|
+
outputDir,
|
|
268
|
+
logsDir,
|
|
269
|
+
genomes: fallbackGenomes,
|
|
270
|
+
agentGenomes: fallbackAgentGenomes,
|
|
271
|
+
latestSession,
|
|
272
|
+
agentCount: agents.entries.length,
|
|
273
|
+
specialistCount: specialists.length,
|
|
274
|
+
sessionCount: sessions.entries.length,
|
|
275
|
+
logCount: logs.entries.length,
|
|
276
|
+
latestHtml: latestHtml
|
|
277
|
+
? normalizeRel(path.relative(targetDir, latestHtml.absPath))
|
|
278
|
+
: outputExists && (await pathExists(path.join(targetDir, outputDir, 'session.html')))
|
|
279
|
+
? normalizeRel(path.join(outputDir, 'session.html'))
|
|
280
|
+
: '—',
|
|
281
|
+
mtime: latestHtml?.mtime || manifestStat?.mtime || null
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function buildFallbackSquadRecords(targetDir, metadataSlugs) {
|
|
286
|
+
const agentsRootAbs = path.join(targetDir, AGENTS_ROOT);
|
|
287
|
+
const entries = await readDirNames(agentsRootAbs);
|
|
288
|
+
const squads = [];
|
|
289
|
+
|
|
290
|
+
for (const entry of entries) {
|
|
291
|
+
if (metadataSlugs.has(entry)) continue;
|
|
292
|
+
const absDir = path.join(agentsRootAbs, entry);
|
|
293
|
+
const stat = await fs.stat(absDir).catch(() => null);
|
|
294
|
+
if (!stat?.isDirectory()) continue;
|
|
295
|
+
|
|
296
|
+
const agents = await collectDirStats(targetDir, path.join(AGENTS_ROOT, entry), {
|
|
297
|
+
filter: (name, itemStat) => itemStat.isFile() && name.endsWith('.md')
|
|
298
|
+
});
|
|
299
|
+
if (agents.entries.length === 0) continue;
|
|
300
|
+
|
|
301
|
+
const sessions = await collectDirStats(targetDir, path.join(OUTPUT_ROOT, entry), {
|
|
302
|
+
filter: (name, itemStat) => itemStat.isFile() && SESSION_HTML_RE.test(name) && name !== 'latest.html'
|
|
303
|
+
});
|
|
304
|
+
const logs = await collectDirStats(targetDir, path.join(LOGS_ROOT, entry), {
|
|
305
|
+
filter: (name, itemStat) => itemStat.isFile()
|
|
306
|
+
});
|
|
307
|
+
const latestHtml = await getLatestHtml(path.join(targetDir, OUTPUT_ROOT, entry));
|
|
308
|
+
|
|
309
|
+
squads.push({
|
|
310
|
+
slug: entry,
|
|
311
|
+
file: `${entry}.md`,
|
|
312
|
+
metadataPath: path.join(targetDir, SQUADS_DIR, `${entry}.md`),
|
|
313
|
+
squadName: entry,
|
|
314
|
+
mode: '—',
|
|
315
|
+
goal: '—',
|
|
316
|
+
agentsDir: `${AGENTS_ROOT}/${entry}`,
|
|
317
|
+
outputDir: `${OUTPUT_ROOT}/${entry}`,
|
|
318
|
+
logsDir: `${normalizeRel(LOGS_ROOT)}/${entry}`,
|
|
319
|
+
genomes: [],
|
|
320
|
+
agentGenomes: [],
|
|
321
|
+
latestSession: latestHtml ? normalizeRel(path.relative(targetDir, latestHtml.absPath)) : '—',
|
|
322
|
+
agentCount: agents.entries.length,
|
|
323
|
+
specialistCount: agents.entries.filter((item) => item.name !== 'orquestrador.md').length,
|
|
324
|
+
sessionCount: sessions.entries.length,
|
|
325
|
+
logCount: logs.entries.length,
|
|
326
|
+
latestHtml: latestHtml ? normalizeRel(path.relative(targetDir, latestHtml.absPath)) : '—',
|
|
327
|
+
mtime: latestHtml?.mtime || stat.mtime
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return squads;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function runSquadStatus({ args, logger, t }) {
|
|
335
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
336
|
+
const squadsDir = path.join(targetDir, SQUADS_DIR);
|
|
337
|
+
const metadataEntries = await fs.readdir(squadsDir, { withFileTypes: true }).catch(() => []);
|
|
338
|
+
const packageDirs = metadataEntries
|
|
339
|
+
.filter((entry) => entry.isDirectory())
|
|
340
|
+
.map((entry) => entry.name)
|
|
341
|
+
.filter(Boolean);
|
|
342
|
+
const mdFiles = metadataEntries
|
|
343
|
+
.filter((entry) => entry.isFile())
|
|
344
|
+
.map((entry) => entry.name)
|
|
345
|
+
.filter((file) => file.endsWith('.md') && !SKIP_FILES.has(file));
|
|
346
|
+
|
|
347
|
+
const squads = [];
|
|
348
|
+
for (const slug of packageDirs) {
|
|
349
|
+
const squad = await buildSquadRecordFromPackageDir(targetDir, slug);
|
|
350
|
+
if (squad) squads.push(squad);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
for (const file of mdFiles) {
|
|
354
|
+
if (packageDirs.includes(file.replace(/\.md$/, ''))) continue;
|
|
355
|
+
const squad = await buildSquadRecordFromMetadata(targetDir, file);
|
|
356
|
+
if (squad) squads.push(squad);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const fallbackSquads = await buildFallbackSquadRecords(
|
|
360
|
+
targetDir,
|
|
361
|
+
new Set(squads.map((item) => item.slug))
|
|
362
|
+
);
|
|
363
|
+
squads.push(...fallbackSquads);
|
|
364
|
+
|
|
365
|
+
if (squads.length === 0) {
|
|
366
|
+
logger.log(t('squad_status.no_squad'));
|
|
367
|
+
logger.log(t('squad_status.hint'));
|
|
368
|
+
return { ok: true, active: false, squads: [] };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
squads.sort((a, b) => {
|
|
372
|
+
if (!a.mtime) return 1;
|
|
373
|
+
if (!b.mtime) return -1;
|
|
374
|
+
return b.mtime - a.mtime;
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
logger.log(t('squad_status.squads_found', { count: squads.length }));
|
|
378
|
+
logger.log('');
|
|
379
|
+
|
|
380
|
+
for (let i = 0; i < squads.length; i++) {
|
|
381
|
+
const squad = squads[i];
|
|
382
|
+
const marker = i === 0 ? ` ${t('squad_status.most_recent')}` : '';
|
|
383
|
+
logger.log(t('squad_status.squad_item', { file: squad.file, marker }));
|
|
384
|
+
logger.log(t('squad_status.name', { value: squad.squadName }));
|
|
385
|
+
logger.log(t('squad_status.mode', { value: squad.mode }));
|
|
386
|
+
logger.log(t('squad_status.goal', { value: squad.goal }));
|
|
387
|
+
logger.log(
|
|
388
|
+
t('squad_status.agents', {
|
|
389
|
+
specialists: squad.specialistCount,
|
|
390
|
+
total: squad.agentCount,
|
|
391
|
+
path: squad.agentsDir
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
logger.log(
|
|
395
|
+
t('squad_status.sessions', {
|
|
396
|
+
count: squad.sessionCount,
|
|
397
|
+
path: squad.outputDir
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
logger.log(t('squad_status.latest_html', { value: squad.latestHtml }));
|
|
401
|
+
logger.log(
|
|
402
|
+
t('squad_status.logs', {
|
|
403
|
+
count: squad.logCount,
|
|
404
|
+
path: squad.logsDir
|
|
405
|
+
})
|
|
406
|
+
);
|
|
407
|
+
logger.log(
|
|
408
|
+
t('squad_status.genomes', {
|
|
409
|
+
count: squad.genomes.length,
|
|
410
|
+
agent_count: squad.agentGenomes.length
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
if (i < squads.length - 1) logger.log('');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
ok: true,
|
|
418
|
+
active: true,
|
|
419
|
+
squads,
|
|
420
|
+
count: squads.length
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = { runSquadStatus };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
async function pathExists(targetPath) {
|
|
7
|
+
try { await fs.access(targetPath); return true; } catch { return false; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function readJsonIfExists(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
} catch { return null; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function validateManifestFields(manifest) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
const warnings = [];
|
|
20
|
+
const required = ['schemaVersion', 'slug', 'name', 'mode', 'mission', 'goal'];
|
|
21
|
+
|
|
22
|
+
for (const field of required) {
|
|
23
|
+
if (!manifest[field]) {
|
|
24
|
+
errors.push(`Missing required field: ${field}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (manifest.slug && !/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(manifest.slug)) {
|
|
29
|
+
errors.push(`Invalid slug format: "${manifest.slug}" (must be kebab-case)`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (manifest.mode && !['content', 'software', 'research', 'mixed'].includes(manifest.mode)) {
|
|
33
|
+
warnings.push(`Unknown mode: "${manifest.mode}"`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { errors, warnings };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function validateStructure(projectDir, slug, manifest) {
|
|
40
|
+
const errors = [];
|
|
41
|
+
const warnings = [];
|
|
42
|
+
const squadDir = path.join(projectDir, '.aioson', 'squads', slug);
|
|
43
|
+
|
|
44
|
+
const requiredFiles = [
|
|
45
|
+
{ rel: 'squad.manifest.json', label: 'Manifest' },
|
|
46
|
+
{ rel: 'agents/agents.md', label: 'Agents manifesto' },
|
|
47
|
+
{ rel: 'agents/orquestrador.md', label: 'Orchestrator agent' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const { rel, label } of requiredFiles) {
|
|
51
|
+
if (!(await pathExists(path.join(squadDir, rel)))) {
|
|
52
|
+
errors.push(`Missing required file: ${rel} (${label})`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check executor files
|
|
57
|
+
const executors = Array.isArray(manifest.executors) ? manifest.executors : [];
|
|
58
|
+
for (const exec of executors) {
|
|
59
|
+
if (exec.file) {
|
|
60
|
+
const absPath = path.join(projectDir, exec.file);
|
|
61
|
+
if (!(await pathExists(absPath))) {
|
|
62
|
+
errors.push(`Executor "${exec.slug}" file not found: ${exec.file}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check output dir (warning only)
|
|
68
|
+
const outputDir = path.join(projectDir, 'output', slug);
|
|
69
|
+
if (!(await pathExists(outputDir))) {
|
|
70
|
+
warnings.push(`Output directory not found: output/${slug}/`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { errors, warnings };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function validateSemantics(manifest) {
|
|
77
|
+
const errors = [];
|
|
78
|
+
const warnings = [];
|
|
79
|
+
const executors = Array.isArray(manifest.executors) ? manifest.executors : [];
|
|
80
|
+
|
|
81
|
+
// Check for duplicate slugs
|
|
82
|
+
const slugs = executors.map(e => e.slug);
|
|
83
|
+
const dupes = slugs.filter((s, i) => slugs.indexOf(s) !== i);
|
|
84
|
+
if (dupes.length > 0) {
|
|
85
|
+
errors.push(`Duplicate executor slugs: ${[...new Set(dupes)].join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check executors without skills
|
|
89
|
+
for (const exec of executors) {
|
|
90
|
+
const skills = Array.isArray(exec.skills) ? exec.skills : [];
|
|
91
|
+
if (skills.length === 0) {
|
|
92
|
+
warnings.push(`Executor "${exec.slug}" has no skills declared`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { errors, warnings };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function validateSemanticDeep(projectDir, slug, manifest) {
|
|
100
|
+
const errors = [];
|
|
101
|
+
const warnings = [];
|
|
102
|
+
|
|
103
|
+
// 1. Slug do manifesto bate com diretório
|
|
104
|
+
if (manifest.slug && manifest.slug !== slug) {
|
|
105
|
+
errors.push(`Slug mismatch: manifest says "${manifest.slug}" but directory is "${slug}"`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 2. Skills referenciadas pelos executores estão declaradas no manifesto
|
|
109
|
+
const declaredSkills = Array.isArray(manifest.skills) ? manifest.skills.map(s => s.slug) : [];
|
|
110
|
+
const executors = Array.isArray(manifest.executors) ? manifest.executors : [];
|
|
111
|
+
for (const exec of executors) {
|
|
112
|
+
const execSkills = Array.isArray(exec.skills) ? exec.skills : [];
|
|
113
|
+
for (const skillSlug of execSkills) {
|
|
114
|
+
if (!declaredSkills.includes(skillSlug)) {
|
|
115
|
+
warnings.push(`Executor "${exec.slug}" references skill "${skillSlug}" not declared in manifest.skills`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 3. Content blueprints têm sections válidas
|
|
121
|
+
const blueprints = Array.isArray(manifest.contentBlueprints) ? manifest.contentBlueprints : [];
|
|
122
|
+
for (const bp of blueprints) {
|
|
123
|
+
if (!bp.sections || bp.sections.length === 0) {
|
|
124
|
+
warnings.push(`Content blueprint "${bp.slug}" has no sections defined`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 4. CLAUDE.md e AGENTS.md mencionam o squad
|
|
129
|
+
const claudeMd = path.join(projectDir, 'CLAUDE.md');
|
|
130
|
+
const agentsMd = path.join(projectDir, 'AGENTS.md');
|
|
131
|
+
try {
|
|
132
|
+
const claudeContent = await fs.readFile(claudeMd, 'utf8');
|
|
133
|
+
if (!claudeContent.includes(slug)) {
|
|
134
|
+
warnings.push(`CLAUDE.md does not reference squad "${slug}"`);
|
|
135
|
+
}
|
|
136
|
+
} catch { warnings.push('CLAUDE.md not found'); }
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const agentsContent = await fs.readFile(agentsMd, 'utf8');
|
|
140
|
+
if (!agentsContent.includes(slug)) {
|
|
141
|
+
warnings.push(`AGENTS.md does not reference squad "${slug}"`);
|
|
142
|
+
}
|
|
143
|
+
} catch { warnings.push('AGENTS.md not found'); }
|
|
144
|
+
|
|
145
|
+
// 5. Readiness não contradiz blockers
|
|
146
|
+
if (manifest.readiness) {
|
|
147
|
+
for (const [dim, val] of Object.entries(manifest.readiness)) {
|
|
148
|
+
if (val && val.status === 'ready' && val.blocker) {
|
|
149
|
+
warnings.push(`Readiness "${dim}" is "ready" but has blocker: "${val.blocker}"`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { errors, warnings };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function runSquadValidate({ args = [], options = {}, logger = console } = {}) {
|
|
158
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
159
|
+
const slug = options.squad || args[1];
|
|
160
|
+
|
|
161
|
+
if (!slug) {
|
|
162
|
+
logger.error('Usage: aioson squad:validate [path] --squad=<slug>');
|
|
163
|
+
return { valid: false, errors: ['No slug provided'], warnings: [] };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const manifestPath = path.join(projectDir, '.aioson', 'squads', slug, 'squad.manifest.json');
|
|
167
|
+
const manifest = await readJsonIfExists(manifestPath);
|
|
168
|
+
|
|
169
|
+
if (!manifest) {
|
|
170
|
+
logger.error(`Squad "${slug}" not found or invalid manifest at ${manifestPath}`);
|
|
171
|
+
return { valid: false, errors: ['Manifest not found or invalid JSON'], warnings: [] };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const allErrors = [];
|
|
175
|
+
const allWarnings = [];
|
|
176
|
+
|
|
177
|
+
// Layer 1: Schema
|
|
178
|
+
const schema = validateManifestFields(manifest);
|
|
179
|
+
allErrors.push(...schema.errors);
|
|
180
|
+
allWarnings.push(...schema.warnings);
|
|
181
|
+
|
|
182
|
+
// Layer 2: Structure
|
|
183
|
+
const structure = await validateStructure(projectDir, slug, manifest);
|
|
184
|
+
allErrors.push(...structure.errors);
|
|
185
|
+
allWarnings.push(...structure.warnings);
|
|
186
|
+
|
|
187
|
+
// Layer 3: Semantics (basic)
|
|
188
|
+
const semantics = await validateSemantics(manifest);
|
|
189
|
+
allErrors.push(...semantics.errors);
|
|
190
|
+
allWarnings.push(...semantics.warnings);
|
|
191
|
+
|
|
192
|
+
// Layer 4: Semantic deep
|
|
193
|
+
const semanticDeep = await validateSemanticDeep(projectDir, slug, manifest);
|
|
194
|
+
allErrors.push(...semanticDeep.errors);
|
|
195
|
+
allWarnings.push(...semanticDeep.warnings);
|
|
196
|
+
|
|
197
|
+
// Report
|
|
198
|
+
const valid = allErrors.length === 0;
|
|
199
|
+
const status = valid
|
|
200
|
+
? (allWarnings.length > 0 ? 'VALID (with warnings)' : 'VALID')
|
|
201
|
+
: 'INVALID';
|
|
202
|
+
|
|
203
|
+
logger.log('');
|
|
204
|
+
logger.log(`\u2550\u2550 Squad Validation: ${slug} \u2550\u2550`);
|
|
205
|
+
logger.log('');
|
|
206
|
+
logger.log(` Schema: ${schema.errors.length === 0 ? '\u2705 PASS' : '\u274c FAIL'}`);
|
|
207
|
+
logger.log(` Structure: ${structure.errors.length === 0 ? '\u2705 PASS' : '\u274c FAIL'}`);
|
|
208
|
+
logger.log(` Semantics: ${semantics.errors.length === 0 ? (semantics.warnings.length > 0 ? '\u26a0\ufe0f WARNINGS' : '\u2705 PASS') : '\u274c FAIL'}`);
|
|
209
|
+
logger.log(` Semantic deep: ${semanticDeep.errors.length === 0 ? (semanticDeep.warnings.length > 0 ? '\u26a0\ufe0f WARNINGS' : '\u2705 PASS') : '\u274c FAIL'}`);
|
|
210
|
+
|
|
211
|
+
if (allErrors.length > 0) {
|
|
212
|
+
logger.log('');
|
|
213
|
+
logger.log(' Errors:');
|
|
214
|
+
for (const err of allErrors) logger.log(` \u274c ${err}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (allWarnings.length > 0) {
|
|
218
|
+
logger.log('');
|
|
219
|
+
logger.log(' Warnings:');
|
|
220
|
+
for (const warn of allWarnings) logger.log(` \u26a0\ufe0f ${warn}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
logger.log('');
|
|
224
|
+
logger.log(` Result: ${status}`);
|
|
225
|
+
logger.log('');
|
|
226
|
+
|
|
227
|
+
return { valid, errors: allErrors, warnings: allWarnings, status };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = { runSquadValidate };
|