@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,948 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const {
6
+ resolveRuntimePaths,
7
+ openRuntimeDb,
8
+ runtimeStoreExists,
9
+ startTask,
10
+ updateTask,
11
+ startRun,
12
+ updateRun,
13
+ attachArtifact,
14
+ upsertContentItem,
15
+ getStatusSnapshot,
16
+ logAgentEvent
17
+ } = require('../runtime-store');
18
+
19
+ const ALLOWED_LAYOUTS = new Set(['document', 'tabs', 'accordion', 'stack', 'mixed']);
20
+ const DEFAULT_TEXT_FIELDS = ['content', 'text', 'body', 'lyrics', 'markdown'];
21
+
22
+ function resolveTargetDir(args) {
23
+ return path.resolve(process.cwd(), args[0] || '.');
24
+ }
25
+
26
+ function requireOption(options, key, t) {
27
+ const value = options[key];
28
+ if (value === undefined || value === null || String(value).trim() === '') {
29
+ throw new Error(t('runtime.option_required', { option: `--${key}` }));
30
+ }
31
+ return String(value).trim();
32
+ }
33
+
34
+ async function readJsonIfExists(filePath) {
35
+ try {
36
+ const raw = await fs.readFile(filePath, 'utf8');
37
+ return JSON.parse(raw);
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function maybeResolveContentPaths(targetDir, outputPath) {
44
+ if (!outputPath) return null;
45
+
46
+ const relative = String(outputPath).replace(/\\/g, '/').trim();
47
+ if (!/\/index\.html?$/i.test(relative)) return null;
48
+
49
+ const absoluteHtmlPath = path.isAbsolute(relative) ? relative : path.join(targetDir, relative);
50
+ const absoluteJsonPath = path.join(path.dirname(absoluteHtmlPath), 'content.json');
51
+
52
+ return {
53
+ relativeHtmlPath: path.isAbsolute(relative) ? path.relative(targetDir, absoluteHtmlPath).replace(/\\/g, '/') : relative,
54
+ relativeJsonPath: path.relative(targetDir, absoluteJsonPath).replace(/\\/g, '/'),
55
+ absoluteJsonPath
56
+ };
57
+ }
58
+
59
+ function asObject(value) {
60
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
61
+ }
62
+
63
+ function asString(value) {
64
+ return typeof value === 'string' ? value.trim() : '';
65
+ }
66
+
67
+ function asArray(value) {
68
+ return Array.isArray(value) ? value : [];
69
+ }
70
+
71
+ function normalizeStringArray(value) {
72
+ const values = Array.isArray(value)
73
+ ? value
74
+ : typeof value === 'string'
75
+ ? value.split(',')
76
+ : [];
77
+
78
+ return Array.from(
79
+ new Set(
80
+ values
81
+ .map((entry) => String(entry || '').trim())
82
+ .filter(Boolean)
83
+ )
84
+ );
85
+ }
86
+
87
+ function parseJsonArray(value) {
88
+ if (!value) return [];
89
+ try {
90
+ return normalizeStringArray(JSON.parse(value));
91
+ } catch {
92
+ return [];
93
+ }
94
+ }
95
+
96
+ function titleize(value) {
97
+ return String(value || '')
98
+ .replace(/\.[^.]+$/, '')
99
+ .replace(/[-_]+/g, ' ')
100
+ .replace(/\s+/g, ' ')
101
+ .trim()
102
+ .replace(/\b\w/g, (char) => char.toUpperCase());
103
+ }
104
+
105
+ function makeContentKey(value) {
106
+ return String(value || 'content')
107
+ .toLowerCase()
108
+ .replace(/[^a-z0-9]+/g, '-')
109
+ .replace(/^-+|-+$/g, '')
110
+ .slice(0, 80) || `content-${Date.now()}`;
111
+ }
112
+
113
+ function truncateText(value, max = 12000) {
114
+ const text = String(value || '').trim();
115
+ if (text.length <= max) return text;
116
+ return `${text.slice(0, max).trim()}\n\n[...]`;
117
+ }
118
+
119
+ function stripHtml(html) {
120
+ return String(html || '')
121
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
122
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
123
+ .replace(/<[^>]+>/g, ' ')
124
+ .replace(/\s+/g, ' ')
125
+ .trim();
126
+ }
127
+
128
+ function firstNonEmptyText(record, keys) {
129
+ for (const key of keys) {
130
+ const value = asString(record[key]);
131
+ if (value) return value;
132
+ }
133
+ return '';
134
+ }
135
+
136
+ function normalizeSimpleContentBlocks(content) {
137
+ const text = firstNonEmptyText(content, DEFAULT_TEXT_FIELDS);
138
+ if (text) {
139
+ return [
140
+ {
141
+ type: 'rich-text',
142
+ content: truncateText(text)
143
+ }
144
+ ];
145
+ }
146
+
147
+ const html = asString(content.html);
148
+ if (html) {
149
+ const preview = stripHtml(html);
150
+ return [
151
+ {
152
+ type: 'callout',
153
+ title: 'Conteudo HTML indexado automaticamente',
154
+ content:
155
+ 'Este item foi convertido para o indice de conteudos a partir de um arquivo HTML gerado pelo squad.'
156
+ },
157
+ {
158
+ type: 'rich-text',
159
+ content: truncateText(preview || 'Nao foi possivel extrair preview textual do HTML.')
160
+ }
161
+ ];
162
+ }
163
+
164
+ return [];
165
+ }
166
+
167
+ function isValidBlock(value) {
168
+ const block = asObject(value);
169
+ if (!block) return false;
170
+
171
+ const type = asString(block.type);
172
+ if (!type) return false;
173
+
174
+ if (type === 'tabs') {
175
+ const items = asArray(block.items);
176
+ return items.every((item) => {
177
+ const tab = asObject(item);
178
+ if (!tab || !asString(tab.label)) return false;
179
+ return asArray(tab.blocks).every(isValidBlock);
180
+ });
181
+ }
182
+
183
+ if (type === 'accordion') {
184
+ const items = asArray(block.items);
185
+ return items.every((item) => {
186
+ const entry = asObject(item);
187
+ if (!entry || !asString(entry.title)) return false;
188
+ const content = asString(entry.content);
189
+ const nestedBlocks = asArray(entry.blocks);
190
+ if (!content && nestedBlocks.length === 0) return false;
191
+ return nestedBlocks.every(isValidBlock);
192
+ });
193
+ }
194
+
195
+ if (type === 'section') {
196
+ return asArray(block.blocks).every(isValidBlock);
197
+ }
198
+
199
+ return true;
200
+ }
201
+
202
+ function validateContentPayload(payload) {
203
+ const content = asObject(payload);
204
+ if (!content) {
205
+ return { ok: false, reason: 'content.json must be an object' };
206
+ }
207
+
208
+ const contentKey = asString(content.contentKey || content.content_key);
209
+ if (!contentKey) {
210
+ return { ok: false, reason: 'content.json is missing contentKey' };
211
+ }
212
+
213
+ const title = asString(content.title);
214
+ if (!title) {
215
+ return { ok: false, reason: 'content.json is missing title' };
216
+ }
217
+
218
+ const contentType = asString(content.contentType || content.content_type);
219
+ if (!contentType) {
220
+ return { ok: false, reason: 'content.json is missing contentType' };
221
+ }
222
+
223
+ const layoutType = asString(content.layoutType || content.layout_type || 'document');
224
+ if (!ALLOWED_LAYOUTS.has(layoutType)) {
225
+ return { ok: false, reason: `content.json has unsupported layoutType: ${layoutType}` };
226
+ }
227
+
228
+ const blocks = asArray(content.blocks);
229
+ const normalizedBlocks = blocks.length > 0 ? blocks : normalizeSimpleContentBlocks(content);
230
+
231
+ if (normalizedBlocks.length === 0) {
232
+ return { ok: false, reason: 'content.json must include blocks or a simple text field' };
233
+ }
234
+
235
+ if (!normalizedBlocks.every(isValidBlock)) {
236
+ return { ok: false, reason: 'content.json contains invalid blocks' };
237
+ }
238
+
239
+ return {
240
+ ok: true,
241
+ normalized: {
242
+ ...content,
243
+ contentKey,
244
+ title,
245
+ contentType,
246
+ layoutType,
247
+ blocks: normalizedBlocks,
248
+ blueprint: asString(content.blueprint || content.blueprintSlug || content.blueprint_slug),
249
+ usedSkills: normalizeStringArray(
250
+ content.usedSkills ||
251
+ content.used_skills ||
252
+ content.meta?.usedSkills ||
253
+ content.meta?.used_skills ||
254
+ []
255
+ )
256
+ }
257
+ };
258
+ }
259
+
260
+ async function listFilesRecursive(rootDir) {
261
+ const result = [];
262
+ const queue = [rootDir];
263
+
264
+ while (queue.length > 0) {
265
+ const currentDir = queue.shift();
266
+ const entries = await fs.readdir(currentDir, { withFileTypes: true }).catch(() => []);
267
+ for (const entry of entries) {
268
+ const fullPath = path.join(currentDir, entry.name);
269
+ if (entry.isDirectory()) {
270
+ queue.push(fullPath);
271
+ continue;
272
+ }
273
+ if (entry.isFile()) {
274
+ result.push(fullPath);
275
+ }
276
+ }
277
+ }
278
+
279
+ return result;
280
+ }
281
+
282
+ function inferSquadSlugFromOutputPath(targetDir, absolutePath) {
283
+ const outputRoot = path.join(targetDir, 'output');
284
+ const relativePath = path.relative(outputRoot, absolutePath).replace(/\\/g, '/');
285
+ const [slug] = relativePath.split('/');
286
+ return slug || '';
287
+ }
288
+
289
+ function relativeContentKeyFromOutput(targetDir, absolutePath, squadSlug) {
290
+ const squadRoot = path.join(targetDir, 'output', squadSlug);
291
+ const relativeToSquad = path.relative(squadRoot, absolutePath).replace(/\\/g, '/');
292
+ return makeContentKey(relativeToSquad.replace(/\.[^.]+$/, ''));
293
+ }
294
+
295
+ function synthesizeContentPayload({ targetDir, absolutePath, squadSlug, rawContent }) {
296
+ const ext = path.extname(absolutePath).toLowerCase();
297
+ const relativePath = path.relative(targetDir, absolutePath).replace(/\\/g, '/');
298
+ const title = titleize(path.basename(absolutePath));
299
+ const contentKey = relativeContentKeyFromOutput(targetDir, absolutePath, squadSlug);
300
+
301
+ if (ext === '.md') {
302
+ return {
303
+ contentKey,
304
+ title,
305
+ contentType: 'text-content',
306
+ layoutType: 'document',
307
+ summary: `Conteudo indexado automaticamente de ${relativePath}.`,
308
+ blocks: [
309
+ {
310
+ type: 'rich-text',
311
+ content: truncateText(rawContent)
312
+ }
313
+ ],
314
+ meta: {
315
+ autoIndexed: true,
316
+ sourceFormat: 'markdown',
317
+ sourcePath: relativePath
318
+ }
319
+ };
320
+ }
321
+
322
+ if (ext === '.html' || ext === '.htm') {
323
+ const preview = stripHtml(rawContent);
324
+ return {
325
+ contentKey,
326
+ title,
327
+ contentType: 'html-content',
328
+ layoutType: 'document',
329
+ summary: `Conteudo HTML indexado automaticamente de ${relativePath}.`,
330
+ blocks: [
331
+ {
332
+ type: 'callout',
333
+ title: 'Preview indexado automaticamente',
334
+ content: 'O arquivo HTML original continua no output do squad. Este viewer mostra uma versao textual para indexacao e sync.'
335
+ },
336
+ {
337
+ type: 'rich-text',
338
+ content: truncateText(preview || 'Nao foi possivel gerar preview textual deste HTML.')
339
+ }
340
+ ],
341
+ meta: {
342
+ autoIndexed: true,
343
+ sourceFormat: 'html',
344
+ sourcePath: relativePath
345
+ }
346
+ };
347
+ }
348
+
349
+ return null;
350
+ }
351
+
352
+ async function resolveIngestCandidates(targetDir, options = {}) {
353
+ const outputRoot = path.join(targetDir, 'output');
354
+ const scopedRoot = options.squad ? path.join(outputRoot, String(options.squad).trim()) : outputRoot;
355
+ const rootExists = await fs.stat(scopedRoot).then((stat) => stat.isDirectory()).catch(() => false);
356
+
357
+ if (!rootExists) {
358
+ return [];
359
+ }
360
+
361
+ const allFiles = await listFilesRecursive(scopedRoot);
362
+ const contentJsonDirs = new Set(
363
+ allFiles
364
+ .filter((filePath) => path.basename(filePath).toLowerCase() === 'content.json')
365
+ .map((filePath) => path.dirname(filePath))
366
+ );
367
+
368
+ return allFiles.filter((filePath) => {
369
+ const ext = path.extname(filePath).toLowerCase();
370
+ const base = path.basename(filePath).toLowerCase();
371
+
372
+ if (base === 'content.json') return true;
373
+ if (ext !== '.md' && ext !== '.html' && ext !== '.htm') return false;
374
+ if (contentJsonDirs.has(path.dirname(filePath))) return false;
375
+
376
+ return true;
377
+ });
378
+ }
379
+
380
+ async function ingestContentCandidate(db, targetDir, absolutePath, options = {}) {
381
+ const baseName = path.basename(absolutePath).toLowerCase();
382
+ const relativePath = path.relative(targetDir, absolutePath).replace(/\\/g, '/');
383
+ const squadSlug = options.squad ? String(options.squad).trim() : inferSquadSlugFromOutputPath(targetDir, absolutePath);
384
+
385
+ if (!squadSlug) {
386
+ return { indexed: false, reason: 'missing_squad' };
387
+ }
388
+
389
+ if (baseName === 'content.json') {
390
+ const payload = await readJsonIfExists(absolutePath);
391
+ const validation = validateContentPayload(payload);
392
+ if (!validation.ok) {
393
+ return { indexed: false, reason: validation.reason };
394
+ }
395
+
396
+ const content = validation.normalized;
397
+ const siblingIndex = path.join(path.dirname(absolutePath), 'index.html');
398
+ const siblingHtmlExists = await fs.stat(siblingIndex).then((stat) => stat.isFile()).catch(() => false);
399
+
400
+ upsertContentItem(db, {
401
+ contentKey: content.contentKey,
402
+ taskKey: options.task || content.taskKey || content.task_key || null,
403
+ runKey: options.run || content.runKey || content.run_key || null,
404
+ squadSlug,
405
+ sessionKey: options.session || content.sessionKey || content.session_key || null,
406
+ title: content.title,
407
+ contentType: content.contentType,
408
+ layoutType: content.layoutType,
409
+ status: content.status || 'completed',
410
+ summary: content.summary || `Conteudo indexado automaticamente de ${relativePath}.`,
411
+ blueprintSlug: content.blueprint || null,
412
+ usedSkills: normalizeStringArray(options.usedSkills || content.usedSkills),
413
+ payload: content,
414
+ jsonPath: relativePath,
415
+ htmlPath: siblingHtmlExists
416
+ ? path.relative(targetDir, siblingIndex).replace(/\\/g, '/')
417
+ : null,
418
+ createdByAgent: options.agent || content.createdByAgent || content.created_by_agent || null
419
+ });
420
+
421
+ return { indexed: true, kind: 'content-json', contentKey: content.contentKey };
422
+ }
423
+
424
+ const rawContent = await fs.readFile(absolutePath, 'utf8').catch(() => '');
425
+ if (!rawContent.trim()) {
426
+ return { indexed: false, reason: 'empty_file' };
427
+ }
428
+
429
+ const payload = synthesizeContentPayload({
430
+ targetDir,
431
+ absolutePath,
432
+ squadSlug,
433
+ rawContent
434
+ });
435
+
436
+ if (!payload) {
437
+ return { indexed: false, reason: 'unsupported_file' };
438
+ }
439
+
440
+ upsertContentItem(db, {
441
+ contentKey: payload.contentKey,
442
+ taskKey: options.task || null,
443
+ runKey: options.run || null,
444
+ squadSlug,
445
+ sessionKey: options.session || null,
446
+ title: payload.title,
447
+ contentType: payload.contentType,
448
+ layoutType: payload.layoutType,
449
+ status: 'completed',
450
+ summary: payload.summary,
451
+ blueprintSlug: payload.blueprint || null,
452
+ usedSkills: normalizeStringArray(options.usedSkills),
453
+ payload,
454
+ jsonPath: null,
455
+ htmlPath: path.extname(absolutePath).toLowerCase().startsWith('.ht')
456
+ ? relativePath
457
+ : null,
458
+ createdByAgent: options.agent || null
459
+ });
460
+
461
+ return { indexed: true, kind: path.extname(absolutePath).toLowerCase(), contentKey: payload.contentKey };
462
+ }
463
+
464
+ async function withRuntimeDb(targetDir, t) {
465
+ const handle = await openRuntimeDb(targetDir, { mustExist: true });
466
+ if (!handle) {
467
+ throw new Error(t('runtime.store_missing', { path: resolveRuntimePaths(targetDir).dbPath }));
468
+ }
469
+ return handle;
470
+ }
471
+
472
+ async function runRuntimeInit({ args, options = {}, logger, t }) {
473
+ const targetDir = resolveTargetDir(args);
474
+ const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
475
+ db.close();
476
+
477
+ if (!options.json) {
478
+ logger.log(t('runtime.init_ok', { path: dbPath }));
479
+ }
480
+
481
+ return { ok: true, targetDir, runtimeDir, dbPath };
482
+ }
483
+
484
+ async function runRuntimeIngest({ args, options = {}, logger, t }) {
485
+ const targetDir = resolveTargetDir(args);
486
+ const { db, dbPath } = await withRuntimeDb(targetDir, t);
487
+ const ingestOptions = {
488
+ ...options,
489
+ usedSkills: normalizeStringArray(options['used-skills'] || options.usedSkills)
490
+ };
491
+
492
+ try {
493
+ const candidates = await resolveIngestCandidates(targetDir, ingestOptions);
494
+ let indexed = 0;
495
+ let skipped = 0;
496
+ const reasons = [];
497
+
498
+ for (const candidate of candidates) {
499
+ const result = await ingestContentCandidate(db, targetDir, candidate, ingestOptions);
500
+ if (result.indexed) {
501
+ indexed += 1;
502
+ continue;
503
+ }
504
+ skipped += 1;
505
+ if (result.reason) {
506
+ reasons.push(`${path.relative(targetDir, candidate).replace(/\\/g, '/')}: ${result.reason}`);
507
+ }
508
+ }
509
+
510
+ if (!options.json) {
511
+ logger.log(
512
+ t('runtime.ingest_ok', {
513
+ indexed,
514
+ skipped,
515
+ path: dbPath
516
+ })
517
+ );
518
+ if (reasons.length > 0) {
519
+ for (const reason of reasons.slice(0, 10)) {
520
+ logger.log(`- ${reason}`);
521
+ }
522
+ }
523
+ }
524
+
525
+ return {
526
+ ok: true,
527
+ targetDir,
528
+ dbPath,
529
+ indexed,
530
+ skipped,
531
+ reasons
532
+ };
533
+ } finally {
534
+ db.close();
535
+ }
536
+ }
537
+
538
+ async function runRuntimeTaskStart({ args, options = {}, logger, t }) {
539
+ const targetDir = resolveTargetDir(args);
540
+ const { db, dbPath } = await withRuntimeDb(targetDir, t);
541
+
542
+ try {
543
+ const taskKey = startTask(db, {
544
+ taskKey: options.task,
545
+ squadSlug: options.squad,
546
+ sessionKey: options.session,
547
+ title: requireOption(options, 'title', t),
548
+ goal: options.goal,
549
+ createdBy: options.by
550
+ });
551
+
552
+ if (!options.json) {
553
+ logger.log(t('runtime.task_start_ok', { task: taskKey, path: dbPath }));
554
+ }
555
+
556
+ return {
557
+ ok: true,
558
+ targetDir,
559
+ dbPath,
560
+ taskKey,
561
+ status: 'running'
562
+ };
563
+ } finally {
564
+ db.close();
565
+ }
566
+ }
567
+
568
+ async function runRuntimeStart({ args, options = {}, logger, t }) {
569
+ const targetDir = resolveTargetDir(args);
570
+ const { db, dbPath } = await withRuntimeDb(targetDir, t);
571
+
572
+ try {
573
+ const runKey = startRun(db, {
574
+ runKey: options.run,
575
+ taskKey: options.task,
576
+ agentName: requireOption(options, 'agent', t),
577
+ agentKind: options.kind,
578
+ squadSlug: options.squad,
579
+ sessionKey: options.session,
580
+ title: options.title,
581
+ message: options.message,
582
+ summary: options.summary,
583
+ usedSkills: normalizeStringArray(options['used-skills'] || options.usedSkills),
584
+ outputPath: options.output
585
+ });
586
+
587
+ const snapshot = getStatusSnapshot(db);
588
+ if (!options.json) {
589
+ logger.log(t('runtime.start_ok', { run: runKey, path: dbPath }));
590
+ }
591
+
592
+ return {
593
+ ok: true,
594
+ targetDir,
595
+ dbPath,
596
+ runKey,
597
+ status: 'running',
598
+ activeCount: snapshot.activeRuns.length
599
+ };
600
+ } finally {
601
+ db.close();
602
+ }
603
+ }
604
+
605
+ async function runRuntimeUpdate({ args, options = {}, logger, t }) {
606
+ const targetDir = resolveTargetDir(args);
607
+ const { db, dbPath } = await withRuntimeDb(targetDir, t);
608
+
609
+ try {
610
+ const runKey = requireOption(options, 'run', t);
611
+ const status = updateRun(db, {
612
+ runKey,
613
+ status: 'running',
614
+ taskKey: options.task,
615
+ eventType: 'progress',
616
+ message: options.message,
617
+ summary: options.summary,
618
+ usedSkills: normalizeStringArray(options['used-skills'] || options.usedSkills),
619
+ outputPath: options.output
620
+ });
621
+
622
+ if (!options.json) {
623
+ logger.log(t('runtime.update_ok', { run: runKey, path: dbPath }));
624
+ }
625
+
626
+ return {
627
+ ok: true,
628
+ targetDir,
629
+ dbPath,
630
+ runKey,
631
+ status
632
+ };
633
+ } finally {
634
+ db.close();
635
+ }
636
+ }
637
+
638
+ async function runRuntimeFinish({ args, options = {}, logger, t }) {
639
+ const targetDir = resolveTargetDir(args);
640
+ const { db, dbPath } = await withRuntimeDb(targetDir, t);
641
+
642
+ try {
643
+ const runKey = requireOption(options, 'run', t);
644
+ const status = updateRun(db, {
645
+ runKey,
646
+ status: 'completed',
647
+ taskKey: options.task,
648
+ eventType: 'finish',
649
+ message: options.message || options.summary || 'Run completed',
650
+ summary: options.summary,
651
+ usedSkills: normalizeStringArray(options['used-skills'] || options.usedSkills),
652
+ outputPath: options.output
653
+ });
654
+
655
+ const finishedRun = db
656
+ .prepare('SELECT run_key, task_key, squad_slug, session_key, agent_name, output_path, used_skills_json FROM agent_runs WHERE run_key = ?')
657
+ .get(runKey);
658
+ if (finishedRun && finishedRun.output_path) {
659
+ attachArtifact(db, {
660
+ taskKey: finishedRun.task_key,
661
+ runKey: finishedRun.run_key,
662
+ squadSlug: finishedRun.squad_slug,
663
+ agentName: finishedRun.agent_name,
664
+ filePath: finishedRun.output_path,
665
+ title: options.title || options.summary || 'Artifact generated'
666
+ });
667
+
668
+ const absoluteOutputPath = path.isAbsolute(finishedRun.output_path)
669
+ ? finishedRun.output_path
670
+ : path.join(targetDir, finishedRun.output_path);
671
+ const contentPaths = maybeResolveContentPaths(targetDir, finishedRun.output_path);
672
+ const preferredCandidate = contentPaths
673
+ ? (await fs
674
+ .stat(contentPaths.absoluteJsonPath)
675
+ .then((stat) => (stat.isFile() ? contentPaths.absoluteJsonPath : null))
676
+ .catch(() => null))
677
+ : absoluteOutputPath;
678
+
679
+ if (preferredCandidate) {
680
+ const ingestion = await ingestContentCandidate(db, targetDir, preferredCandidate, {
681
+ task: finishedRun.task_key,
682
+ run: finishedRun.run_key,
683
+ squad: finishedRun.squad_slug,
684
+ session: options.session || finishedRun.session_key,
685
+ agent: finishedRun.agent_name,
686
+ usedSkills: parseJsonArray(finishedRun.used_skills_json)
687
+ });
688
+ if (!ingestion.indexed && !options.json && logger?.log) {
689
+ logger.log(`[runtime] skipped content indexing for ${finishedRun.run_key}: ${ingestion.reason}`);
690
+ }
691
+ }
692
+ }
693
+
694
+ if (!options.json) {
695
+ logger.log(t('runtime.finish_ok', { run: runKey, path: dbPath }));
696
+ }
697
+
698
+ return {
699
+ ok: true,
700
+ targetDir,
701
+ dbPath,
702
+ runKey,
703
+ status
704
+ };
705
+ } finally {
706
+ db.close();
707
+ }
708
+ }
709
+
710
+ async function runRuntimeTaskFinish({ args, options = {}, logger, t }) {
711
+ const targetDir = resolveTargetDir(args);
712
+ const { db, dbPath } = await withRuntimeDb(targetDir, t);
713
+
714
+ try {
715
+ const taskKey = requireOption(options, 'task', t);
716
+ const taskStatus = updateTask(db, {
717
+ taskKey,
718
+ status: 'completed',
719
+ goal: options.goal
720
+ });
721
+
722
+ if (!options.json) {
723
+ logger.log(t('runtime.task_finish_ok', { task: taskKey, path: dbPath }));
724
+ }
725
+
726
+ return {
727
+ ok: true,
728
+ targetDir,
729
+ dbPath,
730
+ taskKey,
731
+ status: taskStatus
732
+ };
733
+ } finally {
734
+ db.close();
735
+ }
736
+ }
737
+
738
+ async function runRuntimeFail({ args, options = {}, logger, t }) {
739
+ const targetDir = resolveTargetDir(args);
740
+ const { db, dbPath } = await withRuntimeDb(targetDir, t);
741
+
742
+ try {
743
+ const runKey = requireOption(options, 'run', t);
744
+ const status = updateRun(db, {
745
+ runKey,
746
+ taskKey: options.task,
747
+ status: 'failed',
748
+ eventType: 'fail',
749
+ message: options.message || options.summary || 'Run failed',
750
+ summary: options.summary,
751
+ outputPath: options.output
752
+ });
753
+
754
+ if (!options.json) {
755
+ logger.log(t('runtime.fail_ok', { run: runKey, path: dbPath }));
756
+ }
757
+
758
+ return {
759
+ ok: true,
760
+ targetDir,
761
+ dbPath,
762
+ runKey,
763
+ status
764
+ };
765
+ } finally {
766
+ db.close();
767
+ }
768
+ }
769
+
770
+ async function runRuntimeTaskFail({ args, options = {}, logger, t }) {
771
+ const targetDir = resolveTargetDir(args);
772
+ const { db, dbPath } = await withRuntimeDb(targetDir, t);
773
+
774
+ try {
775
+ const taskKey = requireOption(options, 'task', t);
776
+ const status = updateTask(db, {
777
+ taskKey,
778
+ status: 'failed',
779
+ goal: options.goal
780
+ });
781
+
782
+ if (!options.json) {
783
+ logger.log(t('runtime.task_fail_ok', { task: taskKey, path: dbPath }));
784
+ }
785
+
786
+ return {
787
+ ok: true,
788
+ targetDir,
789
+ dbPath,
790
+ taskKey,
791
+ status
792
+ };
793
+ } finally {
794
+ db.close();
795
+ }
796
+ }
797
+
798
+ async function runRuntimeStatus({ args, options = {}, logger, t }) {
799
+ const targetDir = resolveTargetDir(args);
800
+ const { dbPath } = resolveRuntimePaths(targetDir);
801
+
802
+ if (!(await runtimeStoreExists(targetDir))) {
803
+ if (options.json) {
804
+ return { ok: false, error: 'store_missing', dbPath };
805
+ }
806
+ throw new Error(t('runtime.store_missing', { path: dbPath }));
807
+ }
808
+
809
+ const { db } = await openRuntimeDb(targetDir, { mustExist: true });
810
+
811
+ try {
812
+ const snapshot = getStatusSnapshot(db);
813
+ const payload = {
814
+ ok: true,
815
+ targetDir,
816
+ dbPath,
817
+ taskCounts: snapshot.taskCounts,
818
+ counts: snapshot.counts,
819
+ activeTasks: snapshot.activeTasks,
820
+ recentTasks: snapshot.recentTasks,
821
+ activeRuns: snapshot.activeRuns,
822
+ recentRuns: snapshot.recentRuns,
823
+ recentArtifacts: snapshot.recentArtifacts,
824
+ recentContentItems: snapshot.recentContentItems,
825
+ recentExecutionEvents: snapshot.recentExecutionEvents
826
+ };
827
+
828
+ if (!options.json) {
829
+ logger.log(t('runtime.status_title', { path: targetDir }));
830
+ logger.log(t('runtime.status_db', { path: dbPath }));
831
+ logger.log(
832
+ t('runtime.status_task_counts', {
833
+ queued: payload.taskCounts.queued,
834
+ running: payload.taskCounts.running,
835
+ completed: payload.taskCounts.completed,
836
+ failed: payload.taskCounts.failed
837
+ })
838
+ );
839
+ logger.log(
840
+ t('runtime.status_counts', {
841
+ queued: payload.counts.queued,
842
+ running: payload.counts.running,
843
+ completed: payload.counts.completed,
844
+ failed: payload.counts.failed
845
+ })
846
+ );
847
+ if (snapshot.activeTasks.length === 0) {
848
+ logger.log(t('runtime.status_no_active_tasks'));
849
+ } else {
850
+ logger.log(t('runtime.status_active_tasks_title'));
851
+ for (const task of snapshot.activeTasks) {
852
+ logger.log(
853
+ t('runtime.status_active_task_line', {
854
+ task: task.task_key,
855
+ squad: task.squad_slug || '—',
856
+ status: task.status,
857
+ title: task.title
858
+ })
859
+ );
860
+ }
861
+ }
862
+ if (snapshot.activeRuns.length === 0) {
863
+ logger.log(t('runtime.status_no_active'));
864
+ } else {
865
+ logger.log(t('runtime.status_active_title'));
866
+ for (const run of snapshot.activeRuns) {
867
+ logger.log(
868
+ t('runtime.status_active_line', {
869
+ agent: run.agent_name,
870
+ squad: run.squad_slug || '—',
871
+ status: run.status,
872
+ title: run.title || run.summary || '—'
873
+ })
874
+ );
875
+ }
876
+ }
877
+ }
878
+
879
+ return payload;
880
+ } finally {
881
+ db.close();
882
+ }
883
+ }
884
+
885
+ /**
886
+ * aioson runtime-log --agent=<name> --message=<text> [--type=<event>] [--finish] [--status=completed|failed] [--summary=<text>] [--title=<task-title>]
887
+ *
888
+ * Stateful single-command logger for official AIOSON agents.
889
+ * First call creates task + run in SQLite; subsequent calls add events.
890
+ * --finish closes the run and clears the session.
891
+ */
892
+ async function runRuntimeLog({ args, options = {}, logger, t }) {
893
+ const targetDir = resolveTargetDir(args);
894
+ const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
895
+
896
+ try {
897
+ const agentName = options.agent;
898
+ if (!agentName) {
899
+ throw new Error(t('runtime.log_agent_required'));
900
+ }
901
+
902
+ const { runKey, taskKey } = await logAgentEvent(db, runtimeDir, {
903
+ agentName,
904
+ squadSlug: options.squad || null,
905
+ message: options.message || '',
906
+ type: options.type || 'status',
907
+ taskTitle: options.title,
908
+ finish: Boolean(options.finish),
909
+ status: options.status,
910
+ summary: options.summary,
911
+ meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
912
+ });
913
+
914
+ if (!options.json) {
915
+ const isFinish = Boolean(options.finish);
916
+ logger.log(isFinish
917
+ ? t('runtime.log_finish_ok', { agent: agentName, run: runKey, path: dbPath })
918
+ : t('runtime.log_ok', { agent: agentName, run: runKey, path: dbPath })
919
+ );
920
+ }
921
+
922
+ return {
923
+ ok: true,
924
+ targetDir,
925
+ dbPath,
926
+ runKey,
927
+ taskKey,
928
+ agent: agentName,
929
+ finished: Boolean(options.finish)
930
+ };
931
+ } finally {
932
+ db.close();
933
+ }
934
+ }
935
+
936
+ module.exports = {
937
+ runRuntimeInit,
938
+ runRuntimeIngest,
939
+ runRuntimeTaskStart,
940
+ runRuntimeStart,
941
+ runRuntimeUpdate,
942
+ runRuntimeTaskFinish,
943
+ runRuntimeFinish,
944
+ runRuntimeTaskFail,
945
+ runRuntimeFail,
946
+ runRuntimeStatus,
947
+ runRuntimeLog
948
+ };