@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.
Files changed (288) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/CODE_OF_CONDUCT.md +12 -0
  3. package/CONTRIBUTING.md +13 -0
  4. package/LICENSE +21 -0
  5. package/README.md +254 -0
  6. package/bin/aioson.js +4 -0
  7. package/docs/en/cli-reference.md +398 -0
  8. package/docs/en/i18n.md +52 -0
  9. package/docs/en/json-schemas.md +41 -0
  10. package/docs/en/mcp.md +56 -0
  11. package/docs/en/parallel.md +82 -0
  12. package/docs/en/qa-browser.md +339 -0
  13. package/docs/en/release-flow.md +22 -0
  14. package/docs/en/release-notes-template.md +41 -0
  15. package/docs/en/release.md +28 -0
  16. package/docs/en/schemas/agent-prompt.schema.json +17 -0
  17. package/docs/en/schemas/agents.schema.json +32 -0
  18. package/docs/en/schemas/context-validate.schema.json +36 -0
  19. package/docs/en/schemas/doctor.schema.json +89 -0
  20. package/docs/en/schemas/error.schema.json +24 -0
  21. package/docs/en/schemas/i18n-add.schema.json +15 -0
  22. package/docs/en/schemas/index.json +116 -0
  23. package/docs/en/schemas/info.schema.json +39 -0
  24. package/docs/en/schemas/init.schema.json +48 -0
  25. package/docs/en/schemas/install.schema.json +60 -0
  26. package/docs/en/schemas/locale-apply.schema.json +30 -0
  27. package/docs/en/schemas/mcp-doctor.schema.json +95 -0
  28. package/docs/en/schemas/mcp-init.schema.json +122 -0
  29. package/docs/en/schemas/package-test.schema.json +24 -0
  30. package/docs/en/schemas/parallel-assign.schema.json +57 -0
  31. package/docs/en/schemas/parallel-doctor.schema.json +86 -0
  32. package/docs/en/schemas/parallel-init.schema.json +53 -0
  33. package/docs/en/schemas/parallel-status.schema.json +94 -0
  34. package/docs/en/schemas/setup-context.schema.json +39 -0
  35. package/docs/en/schemas/smoke.schema.json +23 -0
  36. package/docs/en/schemas/update.schema.json +48 -0
  37. package/docs/en/schemas/workflow-plan.schema.json +30 -0
  38. package/docs/en/web3.md +54 -0
  39. package/docs/pt/README.md +46 -0
  40. package/docs/pt/advisor-spec.md +335 -0
  41. package/docs/pt/agentes.md +453 -0
  42. package/docs/pt/cenarios.md +1230 -0
  43. package/docs/pt/clientes-ai.md +224 -0
  44. package/docs/pt/comandos-cli.md +511 -0
  45. package/docs/pt/genome-3.0-spec.md +296 -0
  46. package/docs/pt/guia-engineer.md +226 -0
  47. package/docs/pt/inicio-rapido.md +138 -0
  48. package/docs/pt/profiler-system.md +214 -0
  49. package/docs/pt/runtime-observability.md +72 -0
  50. package/docs/pt/squad-genoma.md +777 -0
  51. package/docs/pt/web3.md +797 -0
  52. package/docs/testing/genome-2.0-manual-regression.md +23 -0
  53. package/docs/testing/genome-2.0-matrix.md +36 -0
  54. package/docs/testing/genome-2.0-rollout.md +184 -0
  55. package/package.json +50 -0
  56. package/src/agents.js +56 -0
  57. package/src/cli.js +497 -0
  58. package/src/commands/agents.js +142 -0
  59. package/src/commands/cloud.js +1767 -0
  60. package/src/commands/config.js +90 -0
  61. package/src/commands/context-validate.js +91 -0
  62. package/src/commands/doctor.js +123 -0
  63. package/src/commands/genome-doctor.js +41 -0
  64. package/src/commands/genome-migrate.js +49 -0
  65. package/src/commands/i18n-add.js +56 -0
  66. package/src/commands/info.js +41 -0
  67. package/src/commands/init.js +75 -0
  68. package/src/commands/install.js +68 -0
  69. package/src/commands/locale-apply.js +51 -0
  70. package/src/commands/locale-diff.js +126 -0
  71. package/src/commands/mcp-doctor.js +406 -0
  72. package/src/commands/mcp-init.js +379 -0
  73. package/src/commands/package-e2e.js +273 -0
  74. package/src/commands/parallel-assign.js +403 -0
  75. package/src/commands/parallel-doctor.js +437 -0
  76. package/src/commands/parallel-init.js +249 -0
  77. package/src/commands/parallel-status.js +290 -0
  78. package/src/commands/qa-doctor.js +185 -0
  79. package/src/commands/qa-init.js +161 -0
  80. package/src/commands/qa-report.js +58 -0
  81. package/src/commands/qa-run.js +873 -0
  82. package/src/commands/qa-scan.js +337 -0
  83. package/src/commands/runtime.js +948 -0
  84. package/src/commands/scan-project.js +1107 -0
  85. package/src/commands/setup-context.js +650 -0
  86. package/src/commands/smoke.js +426 -0
  87. package/src/commands/squad-doctor.js +358 -0
  88. package/src/commands/squad-export.js +46 -0
  89. package/src/commands/squad-pipeline.js +97 -0
  90. package/src/commands/squad-repair-genomes.js +39 -0
  91. package/src/commands/squad-status.js +424 -0
  92. package/src/commands/squad-validate.js +230 -0
  93. package/src/commands/test-agents.js +194 -0
  94. package/src/commands/update.js +55 -0
  95. package/src/commands/workflow-next.js +594 -0
  96. package/src/commands/workflow-plan.js +108 -0
  97. package/src/constants.js +314 -0
  98. package/src/context-parse-reason.js +22 -0
  99. package/src/context-writer.js +150 -0
  100. package/src/context.js +217 -0
  101. package/src/detector.js +261 -0
  102. package/src/doctor.js +289 -0
  103. package/src/execution-gateway.js +461 -0
  104. package/src/genome-files.js +198 -0
  105. package/src/genome-format.js +442 -0
  106. package/src/genome-schema.js +215 -0
  107. package/src/genomes/bindings.js +281 -0
  108. package/src/genomes.js +467 -0
  109. package/src/i18n/index.js +103 -0
  110. package/src/i18n/messages/en.js +784 -0
  111. package/src/i18n/messages/es.js +718 -0
  112. package/src/i18n/messages/fr.js +725 -0
  113. package/src/i18n/messages/pt-BR.js +818 -0
  114. package/src/i18n/scaffold.js +64 -0
  115. package/src/installer.js +232 -0
  116. package/src/lib/genomes/compat.js +206 -0
  117. package/src/lib/genomes/migrate.js +90 -0
  118. package/src/lib/squads/genome-repair.js +49 -0
  119. package/src/locales.js +84 -0
  120. package/src/onboarding.js +305 -0
  121. package/src/parser.js +53 -0
  122. package/src/prompt-tool.js +20 -0
  123. package/src/qa-html-report.js +472 -0
  124. package/src/runtime-store.js +1527 -0
  125. package/src/squads/apply-genome.js +21 -0
  126. package/src/squads/genome-binding-service.js +154 -0
  127. package/src/updater.js +32 -0
  128. package/src/utils.js +46 -0
  129. package/src/version.js +50 -0
  130. package/template/.aioson/advisors/.gitkeep +1 -0
  131. package/template/.aioson/agents/analyst.md +225 -0
  132. package/template/.aioson/agents/architect.md +221 -0
  133. package/template/.aioson/agents/dev.md +201 -0
  134. package/template/.aioson/agents/discovery-design-doc.md +196 -0
  135. package/template/.aioson/agents/genoma.md +300 -0
  136. package/template/.aioson/agents/orchestrator.md +107 -0
  137. package/template/.aioson/agents/pm.md +89 -0
  138. package/template/.aioson/agents/product.md +361 -0
  139. package/template/.aioson/agents/profiler-enricher.md +266 -0
  140. package/template/.aioson/agents/profiler-forge.md +188 -0
  141. package/template/.aioson/agents/profiler-researcher.md +245 -0
  142. package/template/.aioson/agents/qa.md +344 -0
  143. package/template/.aioson/agents/setup.md +381 -0
  144. package/template/.aioson/agents/squad.md +837 -0
  145. package/template/.aioson/agents/ux-ui.md +416 -0
  146. package/template/.aioson/config.md +56 -0
  147. package/template/.aioson/context/.gitkeep +0 -0
  148. package/template/.aioson/context/parallel/.gitkeep +0 -0
  149. package/template/.aioson/context/spec.md.template +37 -0
  150. package/template/.aioson/genomas/.gitkeep +0 -0
  151. package/template/.aioson/locales/en/agents/analyst.md +214 -0
  152. package/template/.aioson/locales/en/agents/architect.md +210 -0
  153. package/template/.aioson/locales/en/agents/dev.md +187 -0
  154. package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
  155. package/template/.aioson/locales/en/agents/genoma.md +212 -0
  156. package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
  157. package/template/.aioson/locales/en/agents/pm.md +77 -0
  158. package/template/.aioson/locales/en/agents/product.md +310 -0
  159. package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
  160. package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
  161. package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
  162. package/template/.aioson/locales/en/agents/qa.md +214 -0
  163. package/template/.aioson/locales/en/agents/setup.md +342 -0
  164. package/template/.aioson/locales/en/agents/squad.md +247 -0
  165. package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
  166. package/template/.aioson/locales/es/agents/analyst.md +203 -0
  167. package/template/.aioson/locales/es/agents/architect.md +208 -0
  168. package/template/.aioson/locales/es/agents/dev.md +183 -0
  169. package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
  170. package/template/.aioson/locales/es/agents/genoma.md +102 -0
  171. package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
  172. package/template/.aioson/locales/es/agents/pm.md +81 -0
  173. package/template/.aioson/locales/es/agents/product.md +310 -0
  174. package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
  175. package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
  176. package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
  177. package/template/.aioson/locales/es/agents/qa.md +163 -0
  178. package/template/.aioson/locales/es/agents/setup.md +347 -0
  179. package/template/.aioson/locales/es/agents/squad.md +247 -0
  180. package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
  181. package/template/.aioson/locales/fr/agents/analyst.md +203 -0
  182. package/template/.aioson/locales/fr/agents/architect.md +208 -0
  183. package/template/.aioson/locales/fr/agents/dev.md +183 -0
  184. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
  185. package/template/.aioson/locales/fr/agents/genoma.md +102 -0
  186. package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
  187. package/template/.aioson/locales/fr/agents/pm.md +81 -0
  188. package/template/.aioson/locales/fr/agents/product.md +310 -0
  189. package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
  190. package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
  191. package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
  192. package/template/.aioson/locales/fr/agents/qa.md +163 -0
  193. package/template/.aioson/locales/fr/agents/setup.md +347 -0
  194. package/template/.aioson/locales/fr/agents/squad.md +247 -0
  195. package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
  196. package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
  197. package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
  198. package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
  199. package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
  200. package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
  201. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
  202. package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
  203. package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
  204. package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
  205. package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
  206. package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
  207. package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
  208. package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
  209. package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
  210. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
  211. package/template/.aioson/mcp/servers.md +24 -0
  212. package/template/.aioson/profiler-reports/.gitkeep +1 -0
  213. package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
  214. package/template/.aioson/schemas/genome-meta.schema.json +150 -0
  215. package/template/.aioson/schemas/genome.schema.json +115 -0
  216. package/template/.aioson/schemas/readiness.schema.json +27 -0
  217. package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
  218. package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
  219. package/template/.aioson/skills/dynamic/README.md +30 -0
  220. package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
  221. package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
  222. package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
  223. package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
  224. package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
  225. package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
  226. package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
  227. package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
  228. package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
  229. package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
  230. package/template/.aioson/skills/static/django-patterns.md +342 -0
  231. package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
  232. package/template/.aioson/skills/static/filament-patterns.md +267 -0
  233. package/template/.aioson/skills/static/flux-ui-components.md +262 -0
  234. package/template/.aioson/skills/static/git-conventions.md +227 -0
  235. package/template/.aioson/skills/static/interface-design.md +372 -0
  236. package/template/.aioson/skills/static/jetstream-setup.md +200 -0
  237. package/template/.aioson/skills/static/laravel-conventions.md +491 -0
  238. package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
  239. package/template/.aioson/skills/static/node-express-patterns.md +317 -0
  240. package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
  241. package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
  242. package/template/.aioson/skills/static/rails-conventions.md +307 -0
  243. package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
  244. package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
  245. package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
  246. package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
  247. package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
  248. package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
  249. package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
  250. package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
  251. package/template/.aioson/squads/.artisan/.gitkeep +0 -0
  252. package/template/.aioson/squads/.gitkeep +0 -0
  253. package/template/.aioson/squads/memory.md +5 -0
  254. package/template/.aioson/tasks/squad-analyze.md +83 -0
  255. package/template/.aioson/tasks/squad-create.md +99 -0
  256. package/template/.aioson/tasks/squad-design.md +100 -0
  257. package/template/.aioson/tasks/squad-export.md +20 -0
  258. package/template/.aioson/tasks/squad-extend.md +68 -0
  259. package/template/.aioson/tasks/squad-pipeline.md +122 -0
  260. package/template/.aioson/tasks/squad-repair.md +85 -0
  261. package/template/.aioson/tasks/squad-validate.md +58 -0
  262. package/template/.aioson/templates/squads/content-basic/template.json +21 -0
  263. package/template/.aioson/templates/squads/media-channel/template.json +24 -0
  264. package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
  265. package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
  266. package/template/.claude/commands/aioson/analyst.md +5 -0
  267. package/template/.claude/commands/aioson/architect.md +5 -0
  268. package/template/.claude/commands/aioson/dev.md +5 -0
  269. package/template/.claude/commands/aioson/orchestrator.md +5 -0
  270. package/template/.claude/commands/aioson/pm.md +5 -0
  271. package/template/.claude/commands/aioson/qa.md +5 -0
  272. package/template/.claude/commands/aioson/setup.md +5 -0
  273. package/template/.claude/commands/aioson/ux-ui.md +5 -0
  274. package/template/.gemini/GEMINI.md +10 -0
  275. package/template/.gemini/commands/aios-analyst.toml +4 -0
  276. package/template/.gemini/commands/aios-architect.toml +7 -0
  277. package/template/.gemini/commands/aios-dev.toml +8 -0
  278. package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
  279. package/template/.gemini/commands/aios-orchestrator.toml +8 -0
  280. package/template/.gemini/commands/aios-pm.toml +8 -0
  281. package/template/.gemini/commands/aios-product.toml +4 -0
  282. package/template/.gemini/commands/aios-qa.toml +6 -0
  283. package/template/.gemini/commands/aios-setup.toml +3 -0
  284. package/template/.gemini/commands/aios-ux-ui.toml +8 -0
  285. package/template/AGENTS.md +67 -0
  286. package/template/CLAUDE.md +31 -0
  287. package/template/OPENCODE.md +24 -0
  288. 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 };