@jaimevalasek/aioson 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +729 -232
  3. package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
  4. package/docs/integrations/sdlc-genius-boundary.md +76 -0
  5. package/docs/integrations/sdlc-genius-eval-matrix.md +75 -0
  6. package/docs/integrations/sdlc-genius-install-checklist.md +93 -0
  7. package/docs/integrations/sdlc-genius-review-samples.md +86 -0
  8. package/docs/pt/README.md +3 -0
  9. package/docs/pt/agentes.md +1 -0
  10. package/docs/pt/comandos-cli.md +888 -2
  11. package/docs/pt/design-hybrid-forge.md +255 -6
  12. package/docs/pt/devlog-pipeline.md +270 -0
  13. package/docs/pt/fluxo-artefatos.md +178 -0
  14. package/docs/pt/hooks-session-guard.md +454 -0
  15. package/docs/pt/monitor-de-contexto.md +59 -5
  16. package/docs/pt/sdd-automation-scripts.md +557 -0
  17. package/docs/pt/site-forge.md +309 -0
  18. package/docs/pt/spec-learnings-pipeline.md +265 -0
  19. package/package.json +1 -1
  20. package/src/a2a/client.js +165 -0
  21. package/src/a2a/server.js +223 -0
  22. package/src/cli.js +235 -1
  23. package/src/commands/agent-audit.js +397 -0
  24. package/src/commands/agent-export-skill.js +229 -0
  25. package/src/commands/artifact-validate.js +189 -0
  26. package/src/commands/brief-gen.js +405 -0
  27. package/src/commands/brief-validate.js +65 -0
  28. package/src/commands/classify.js +256 -0
  29. package/src/commands/context-compact.js +49 -0
  30. package/src/commands/context-health.js +175 -0
  31. package/src/commands/context-monitor.js +71 -0
  32. package/src/commands/context-trim.js +177 -0
  33. package/src/commands/detect-test-runner.js +55 -0
  34. package/src/commands/devlog-export-brains.js +27 -0
  35. package/src/commands/devlog-process.js +292 -0
  36. package/src/commands/devlog-watch.js +131 -0
  37. package/src/commands/feature-close.js +165 -0
  38. package/src/commands/gate-check.js +228 -0
  39. package/src/commands/hooks-emit.js +253 -0
  40. package/src/commands/hooks-install.js +347 -0
  41. package/src/commands/learning-auto-promote.js +195 -0
  42. package/src/commands/learning-evolve.js +18 -9
  43. package/src/commands/learning-export.js +103 -0
  44. package/src/commands/learning-rollback.js +164 -0
  45. package/src/commands/live.js +25 -1
  46. package/src/commands/pattern-detect.js +33 -0
  47. package/src/commands/preflight-context.js +30 -0
  48. package/src/commands/preflight.js +208 -0
  49. package/src/commands/pulse-update.js +130 -0
  50. package/src/commands/runner-daemon.js +274 -0
  51. package/src/commands/runner-plan.js +70 -0
  52. package/src/commands/runner-queue-from-plan.js +166 -0
  53. package/src/commands/runner-queue.js +189 -0
  54. package/src/commands/runner-run.js +129 -0
  55. package/src/commands/runtime.js +47 -1
  56. package/src/commands/self-implement-loop.js +256 -0
  57. package/src/commands/session-guard.js +218 -0
  58. package/src/commands/sizing.js +165 -0
  59. package/src/commands/skill.js +65 -0
  60. package/src/commands/spec-checkpoint.js +177 -0
  61. package/src/commands/spec-status.js +79 -0
  62. package/src/commands/spec-sync.js +190 -0
  63. package/src/commands/spec-tasks.js +288 -0
  64. package/src/commands/squad-autorun.js +1220 -0
  65. package/src/commands/squad-bus.js +217 -0
  66. package/src/commands/squad-card.js +149 -0
  67. package/src/commands/squad-daemon.js +134 -0
  68. package/src/commands/squad-dependency-graph.js +164 -0
  69. package/src/commands/squad-review.js +106 -0
  70. package/src/commands/squad-scaffold.js +55 -0
  71. package/src/commands/squad-tool-register.js +157 -0
  72. package/src/commands/state-save.js +122 -0
  73. package/src/commands/update.js +2 -0
  74. package/src/commands/verify-gate.js +572 -0
  75. package/src/commands/workflow-execute.js +241 -0
  76. package/src/constants.js +9 -0
  77. package/src/install-profile.js +2 -2
  78. package/src/install-wizard.js +3 -2
  79. package/src/installer.js +6 -0
  80. package/src/lib/health-check.js +158 -0
  81. package/src/lib/hook-protocol.js +76 -0
  82. package/src/mcp/apps/squad-dashboard/app.js +163 -0
  83. package/src/mcp/apps/squad-dashboard/index.html +261 -0
  84. package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -0
  85. package/src/mcp/resources/squad-state.js +130 -0
  86. package/src/preflight-engine.js +443 -0
  87. package/src/runner/cascade.js +97 -0
  88. package/src/runner/cli-launcher.js +109 -0
  89. package/src/runner/plan-importer.js +63 -0
  90. package/src/runner/queue-store.js +159 -0
  91. package/src/runtime-store.js +61 -3
  92. package/src/squad/agent-teams-adapter.js +264 -0
  93. package/src/squad/brief-validator.js +350 -0
  94. package/src/squad/bus-bridge.js +140 -0
  95. package/src/squad/context-compactor.js +265 -0
  96. package/src/squad/cross-ai-synthesizer.js +250 -0
  97. package/src/squad/hooks-generator.js +196 -0
  98. package/src/squad/inter-squad-events.js +175 -0
  99. package/src/squad/intra-bus.js +345 -0
  100. package/src/squad/learning-extractor.js +213 -0
  101. package/src/squad/pattern-detector.js +365 -0
  102. package/src/squad/preflight-context.js +296 -0
  103. package/src/squad/recovery-context.js +242 -71
  104. package/src/squad/reflection.js +365 -0
  105. package/src/squad/squad-scaffold.js +177 -0
  106. package/src/squad/state-manager.js +310 -0
  107. package/src/squad/task-decomposer.js +652 -0
  108. package/src/squad/verify-gate.js +303 -0
  109. package/src/updater.js +4 -5
  110. package/src/worker-runner.js +186 -1
  111. package/template/.aioson/agents/analyst.md +62 -1
  112. package/template/.aioson/agents/architect.md +61 -1
  113. package/template/.aioson/agents/design-hybrid-forge.md +14 -0
  114. package/template/.aioson/agents/dev.md +242 -24
  115. package/template/.aioson/agents/deyvin.md +66 -8
  116. package/template/.aioson/agents/discovery-design-doc.md +44 -0
  117. package/template/.aioson/agents/genome.md +14 -0
  118. package/template/.aioson/agents/neo.md +78 -1
  119. package/template/.aioson/agents/orache.md +50 -4
  120. package/template/.aioson/agents/orchestrator.md +197 -1
  121. package/template/.aioson/agents/pm.md +35 -0
  122. package/template/.aioson/agents/product.md +50 -5
  123. package/template/.aioson/agents/profiler-enricher.md +14 -0
  124. package/template/.aioson/agents/profiler-forge.md +14 -0
  125. package/template/.aioson/agents/profiler-researcher.md +14 -0
  126. package/template/.aioson/agents/qa.md +172 -21
  127. package/template/.aioson/agents/setup.md +79 -9
  128. package/template/.aioson/agents/sheldon.md +131 -6
  129. package/template/.aioson/agents/site-forge.md +1753 -0
  130. package/template/.aioson/agents/squad.md +162 -0
  131. package/template/.aioson/agents/tester.md +53 -0
  132. package/template/.aioson/agents/ux-ui.md +34 -1
  133. package/template/.aioson/brains/README.md +128 -0
  134. package/template/.aioson/brains/_index.json +16 -0
  135. package/template/.aioson/brains/scripts/query.js +103 -0
  136. package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
  137. package/template/.aioson/config.md +143 -13
  138. package/template/.aioson/constitution.md +33 -0
  139. package/template/.aioson/context/project-pulse.md +34 -0
  140. package/template/.aioson/docs/LAYERS.md +79 -0
  141. package/template/.aioson/docs/README.md +76 -0
  142. package/template/.aioson/docs/example-external-api-context.md +72 -0
  143. package/template/.aioson/locales/en/agents/architect.md +17 -0
  144. package/template/.aioson/locales/en/agents/dev.md +79 -13
  145. package/template/.aioson/locales/en/agents/orache.md +6 -0
  146. package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
  147. package/template/.aioson/locales/en/agents/product.md +50 -0
  148. package/template/.aioson/locales/en/agents/sheldon.md +115 -0
  149. package/template/.aioson/locales/en/agents/squad.md +14 -0
  150. package/template/.aioson/locales/en/agents/tester.md +6 -0
  151. package/template/.aioson/locales/es/agents/analyst.md +2 -0
  152. package/template/.aioson/locales/es/agents/architect.md +19 -0
  153. package/template/.aioson/locales/es/agents/dev.md +64 -4
  154. package/template/.aioson/locales/es/agents/deyvin.md +2 -0
  155. package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
  156. package/template/.aioson/locales/es/agents/genome.md +2 -0
  157. package/template/.aioson/locales/es/agents/neo.md +2 -0
  158. package/template/.aioson/locales/es/agents/orache.md +2 -0
  159. package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
  160. package/template/.aioson/locales/es/agents/pair.md +2 -0
  161. package/template/.aioson/locales/es/agents/pm.md +2 -0
  162. package/template/.aioson/locales/es/agents/product.md +52 -0
  163. package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
  164. package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
  165. package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
  166. package/template/.aioson/locales/es/agents/qa.md +2 -0
  167. package/template/.aioson/locales/es/agents/setup.md +2 -0
  168. package/template/.aioson/locales/es/agents/sheldon.md +117 -0
  169. package/template/.aioson/locales/es/agents/squad.md +16 -0
  170. package/template/.aioson/locales/es/agents/tester.md +9 -0
  171. package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
  172. package/template/.aioson/locales/fr/agents/analyst.md +2 -0
  173. package/template/.aioson/locales/fr/agents/architect.md +19 -0
  174. package/template/.aioson/locales/fr/agents/dev.md +64 -4
  175. package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
  176. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
  177. package/template/.aioson/locales/fr/agents/genome.md +2 -0
  178. package/template/.aioson/locales/fr/agents/neo.md +2 -0
  179. package/template/.aioson/locales/fr/agents/orache.md +2 -0
  180. package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
  181. package/template/.aioson/locales/fr/agents/pair.md +2 -0
  182. package/template/.aioson/locales/fr/agents/pm.md +2 -0
  183. package/template/.aioson/locales/fr/agents/product.md +52 -0
  184. package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
  185. package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
  186. package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
  187. package/template/.aioson/locales/fr/agents/qa.md +2 -0
  188. package/template/.aioson/locales/fr/agents/setup.md +2 -0
  189. package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
  190. package/template/.aioson/locales/fr/agents/squad.md +16 -0
  191. package/template/.aioson/locales/fr/agents/tester.md +9 -0
  192. package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
  193. package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
  194. package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
  195. package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
  196. package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
  197. package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
  198. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
  199. package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
  200. package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
  201. package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
  202. package/template/.aioson/locales/pt-BR/agents/setup.md +101 -18
  203. package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
  204. package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
  205. package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
  206. package/template/.aioson/rules/README.md +69 -0
  207. package/template/.aioson/rules/data-format-convention.md +136 -0
  208. package/template/.aioson/rules/example-monetary-values.md +30 -0
  209. package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
  210. package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
  211. package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
  212. package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
  213. package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
  214. package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
  215. package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
  216. package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
  217. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +1 -0
  218. package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
  219. package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
  220. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
  221. package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
  222. package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +35 -0
  223. package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
  224. package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
  225. package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
  226. package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +4 -1
  227. package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +15 -0
  228. package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +32 -0
  229. package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +20 -0
  230. package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
  231. package/template/.aioson/skills/static/context-budget-guide.md +46 -0
  232. package/template/.aioson/skills/static/harness-sensors.md +74 -0
  233. package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
  234. package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
  235. package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
  236. package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
  237. package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
  238. package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
  239. package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
  240. package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
  241. package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
  242. package/template/.aioson/skills/static/threejs-patterns.md +929 -0
  243. package/template/.aioson/skills/static/web-research-cache.md +112 -0
  244. package/template/.aioson/tasks/implementation-plan.md +21 -1
  245. package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
  246. package/template/.claude/commands/aioson/agent/orache.md +5 -0
  247. package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
  248. package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
  249. package/template/AGENTS.md +55 -3
  250. package/template/CLAUDE.md +30 -0
  251. package/template/OPENCODE.md +4 -0
  252. package/template/researchs/.gitkeep +0 -0
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { launchCLI, detectCLI } = require('../runner/cli-launcher');
5
+ const { runWithCascade, parseCascadeChain } = require('../runner/cascade');
6
+ const { openRuntimeDb, startRun, updateRun, createRunKey } = require('../runtime-store');
7
+
8
+ /**
9
+ * aioson runner:run — executa uma única task headless usando o CLI de AI ativo.
10
+ *
11
+ * Usage:
12
+ * aioson runner:run . --task="Fix the auth modal" --agent=dev
13
+ * aioson runner:run . --task="..." --agent=qa --timeout=300
14
+ * aioson runner:run . --task="..." --agent=dev --cascade=haiku,sonnet
15
+ * aioson runner:run . --task="..." --dry-run
16
+ */
17
+ async function runRunnerRun({ args, options = {}, logger }) {
18
+ const projectDir = path.resolve(process.cwd(), args[0] || '.');
19
+ const { task, agent = 'dev', dryRun, cascade: cascadeStr } = options;
20
+ const timeout = options.timeout ? Number(options.timeout) * 1000 : 120000;
21
+
22
+ if (!task) {
23
+ logger.error('--task is required. Example: aioson runner:run . --task="Fix the login modal"');
24
+ return { ok: false };
25
+ }
26
+
27
+ const agentFile = path.join(projectDir, '.aioson', 'agents', `${agent}.md`);
28
+ const prompt = buildRunnerPrompt(task, agentFile);
29
+
30
+ if (dryRun) {
31
+ let cli;
32
+ try { cli = await detectCLI(); } catch { cli = 'claude'; }
33
+ logger.log(`[dry-run] Would run: ${cli} -p "${prompt.slice(0, 120)}..."`);
34
+ logger.log(`[dry-run] Agent: @${agent} | Timeout: ${timeout / 1000}s`);
35
+ if (cascadeStr) logger.log(`[dry-run] Cascade: ${cascadeStr}`);
36
+ return { ok: true, dryRun: true };
37
+ }
38
+
39
+ logger.log(`[runner] Task: ${task}`);
40
+ logger.log(`[runner] Agent: @${agent} | Timeout: ${timeout / 1000}s`);
41
+ if (cascadeStr) logger.log(`[runner] Cascade: ${cascadeStr}`);
42
+
43
+ const start = Date.now();
44
+
45
+ // Abre DB para registrar evento (melhor esforço — não bloqueia se não existir)
46
+ let db = null;
47
+ let runKey = null;
48
+ try {
49
+ const handle = await openRuntimeDb(projectDir, {});
50
+ if (handle) {
51
+ db = handle.db;
52
+ runKey = createRunKey(agent);
53
+ startRun(db, {
54
+ runKey,
55
+ agentName: agent,
56
+ agentKind: 'runner',
57
+ source: 'runner',
58
+ title: task.slice(0, 80),
59
+ status: 'running',
60
+ message: `Runner task started: ${task.slice(0, 80)}`
61
+ });
62
+ }
63
+ } catch { /* dashboard logging is best-effort */ }
64
+
65
+ let result;
66
+ const cascadeChain = parseCascadeChain(cascadeStr);
67
+
68
+ if (cascadeChain.length > 0) {
69
+ const cascadeResult = await runWithCascade(projectDir, prompt, cascadeChain, {
70
+ timeout,
71
+ onProgress: ({ model, attempt, maxAttempts, status, reason }) => {
72
+ if (status === 'running') {
73
+ logger.log(`[cascade] ${model} attempt ${attempt}/${maxAttempts}...`);
74
+ } else if (status === 'gate_failed') {
75
+ logger.log(`[cascade] ${model} attempt ${attempt} — gate failed${reason ? ': ' + reason : ''}, escalating...`);
76
+ } else if (status === 'cli_failed') {
77
+ logger.log(`[cascade] ${model} attempt ${attempt} — CLI failed, retrying...`);
78
+ }
79
+ }
80
+ });
81
+ if (cascadeResult.ok) {
82
+ result = cascadeResult.result;
83
+ logger.log(`[runner] Completed via ${cascadeResult.modelUsed} (attempt ${cascadeResult.attempts})`);
84
+ } else {
85
+ result = { ok: false, output: '', completionMarker: false };
86
+ logger.error(`[runner] ${cascadeResult.error}`);
87
+ }
88
+ } else {
89
+ result = await launchCLI(projectDir, prompt, {
90
+ timeout,
91
+ onData: (chunk) => process.stdout.write(chunk)
92
+ });
93
+ }
94
+
95
+ const elapsed = Math.round((Date.now() - start) / 1000);
96
+ const status = result.ok ? 'completed' : 'failed';
97
+
98
+ logger.log(`\n[runner] Task ${status} in ${elapsed}s`);
99
+ if (result.completionMarker) logger.log('[runner] TASK_COMPLETE marker detected');
100
+
101
+ // Atualiza run no dashboard
102
+ if (db && runKey) {
103
+ try {
104
+ updateRun(db, {
105
+ runKey,
106
+ status,
107
+ message: `Runner task ${status}: ${task.slice(0, 80)}`,
108
+ payload: { task, agent, elapsed, ok: result.ok, completionMarker: result.completionMarker }
109
+ });
110
+ } catch { /* best-effort */ }
111
+ try { db.close(); } catch { /* noop */ }
112
+ }
113
+
114
+ return { ok: result.ok, output: result.output, elapsed, completionMarker: result.completionMarker };
115
+ }
116
+
117
+ function buildRunnerPrompt(task, agentFile) {
118
+ return [
119
+ 'You are operating in autonomous headless mode. Complete the following task independently.',
120
+ `Agent role: read ${agentFile} for your operating instructions.`,
121
+ '',
122
+ `Task: ${task}`,
123
+ '',
124
+ 'When the task is complete, write TASK_COMPLETE on a new line as the final output.',
125
+ 'If you cannot complete the task, write TASK_FAILED: [reason].'
126
+ ].join('\n');
127
+ }
128
+
129
+ module.exports = { runRunnerRun };
@@ -1179,6 +1179,11 @@ async function runAgentDone({ args, options = {}, logger, t }) {
1179
1179
  const summary = String(options.summary || options.message || `${normalizedAgent} session completed`).trim();
1180
1180
  const title = options.title ? String(options.title).trim() : null;
1181
1181
  const status = options.status || 'completed';
1182
+ const verdict = options.verdict ? String(options.verdict).trim().toUpperCase() : null;
1183
+ const planStepId = options['plan-step'] ? String(options['plan-step']).trim() : null;
1184
+ const artifactPaths = options.artifacts
1185
+ ? String(options.artifacts).split(',').map((p) => p.trim()).filter(Boolean)
1186
+ : [];
1182
1187
 
1183
1188
  const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
1184
1189
 
@@ -1194,9 +1199,24 @@ async function runAgentDone({ args, options = {}, logger, t }) {
1194
1199
  eventType: 'agent_done',
1195
1200
  phase: 'live',
1196
1201
  status: 'running',
1197
- message: summary
1202
+ message: summary,
1203
+ verdict,
1204
+ planStepId
1198
1205
  });
1199
1206
 
1207
+ if (artifactPaths.length > 0) {
1208
+ for (const filePath of artifactPaths) {
1209
+ try {
1210
+ attachArtifact(db, {
1211
+ runKey: session.runKey,
1212
+ agentName: normalizedAgent,
1213
+ kind: 'output',
1214
+ filePath
1215
+ });
1216
+ } catch { /* non-fatal */ }
1217
+ }
1218
+ }
1219
+
1200
1220
  if (!options.json) {
1201
1221
  logger.log(`agent:done — ${normalizedAgent} | live session active, event logged | run: ${session.runKey} (${dbPath})`);
1202
1222
  }
@@ -1219,6 +1239,32 @@ async function runAgentDone({ args, options = {}, logger, t }) {
1219
1239
  summary
1220
1240
  });
1221
1241
 
1242
+ if (verdict || planStepId) {
1243
+ appendRunEvent(db, {
1244
+ runKey,
1245
+ eventType: 'agent_done',
1246
+ phase: 'direct',
1247
+ status: 'completed',
1248
+ message: summary,
1249
+ verdict,
1250
+ planStepId
1251
+ });
1252
+ }
1253
+
1254
+ if (artifactPaths.length > 0) {
1255
+ for (const filePath of artifactPaths) {
1256
+ try {
1257
+ attachArtifact(db, {
1258
+ runKey,
1259
+ taskKey,
1260
+ agentName: normalizedAgent,
1261
+ kind: 'output',
1262
+ filePath
1263
+ });
1264
+ } catch { /* non-fatal */ }
1265
+ }
1266
+ }
1267
+
1222
1268
  if (!options.json) {
1223
1269
  logger.log(`agent:done — ${normalizedAgent} | task: ${taskKey} | run: ${runKey} (${dbPath})`);
1224
1270
  }
@@ -0,0 +1,256 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson self:loop — Autonomous implement + verify loop
5
+ *
6
+ * Runs an agent task, verifies with verify-gate in a fresh context,
7
+ * and retries with feedback injection on failure.
8
+ *
9
+ * Usage:
10
+ * aioson self:loop . --agent=dev --task="implement stripe webhook handler" --max-iterations=3
11
+ * aioson self:loop . --agent=dev --task="..." --verification-criteria="all tests pass"
12
+ * aioson self:loop . --agent=dev --task="..." --spec=briefs/phase-1.md --artifact=src/
13
+ *
14
+ * Integrates with:
15
+ * - verify-gate.js for tiered verification (tiers 1–4)
16
+ * - intra-bus.js for recording attempts
17
+ * - state-manager.js for recording final result
18
+ */
19
+
20
+ const path = require('node:path');
21
+ const { execSync, execFileSync } = require('node:child_process');
22
+ const { randomUUID } = require('node:crypto');
23
+ const fs = require('node:fs/promises');
24
+
25
+ const bus = require('../squad/intra-bus');
26
+ const stateManager = require('../squad/state-manager');
27
+
28
+ // ─── Agent execution ─────────────────────────────────────────────────────────
29
+
30
+ /**
31
+ * Execute an agent task via the aioson CLI.
32
+ * Returns { ok, output, error }.
33
+ */
34
+ function executeAgent(projectDir, agent, task, feedbackContext, timeoutMs) {
35
+ const prompt = feedbackContext
36
+ ? `${task}\n\n---\nPrevious attempt feedback:\n${feedbackContext}`
37
+ : task;
38
+
39
+ try {
40
+ const output = execSync(
41
+ `aioson agent:prompt ${agent} . --tool=claude`,
42
+ {
43
+ cwd: projectDir,
44
+ input: prompt,
45
+ timeout: timeoutMs,
46
+ encoding: 'utf8',
47
+ maxBuffer: 1024 * 1024 * 5,
48
+ stdio: ['pipe', 'pipe', 'pipe']
49
+ }
50
+ );
51
+ return { ok: true, output: output.trim() };
52
+ } catch (err) {
53
+ return { ok: false, output: '', error: err.message.slice(0, 500) };
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Run verify-gate on the result.
59
+ * Returns { ok, verdict, issues }.
60
+ */
61
+ async function runVerification(projectDir, spec, artifact, criteria) {
62
+ if (!spec || !artifact) {
63
+ // Fallback: criteria-only verification
64
+ return criteriaOnlyVerify(projectDir, artifact, criteria);
65
+ }
66
+
67
+ try {
68
+ const output = execSync(
69
+ `aioson verify:gate . --spec="${spec}" --artifact="${artifact}" --json`,
70
+ {
71
+ cwd: projectDir,
72
+ timeout: 30_000,
73
+ encoding: 'utf8',
74
+ maxBuffer: 1024 * 1024
75
+ }
76
+ );
77
+ const result = JSON.parse(output);
78
+ return {
79
+ ok: result.verdict === 'PASS' || result.verdict === 'PASS_WITH_NOTES',
80
+ verdict: result.verdict,
81
+ issues: result.issues || []
82
+ };
83
+ } catch (err) {
84
+ return { ok: false, verdict: 'BLOCKED', issues: [{ message: err.message.slice(0, 200) }] };
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Simple criteria-based verification when no spec/artifact is given.
90
+ * Checks if verification criteria strings are present in the output directory.
91
+ */
92
+ async function criteriaOnlyVerify(projectDir, artifactPath, criteria) {
93
+ if (!criteria) {
94
+ return { ok: true, verdict: 'PASS', issues: [] };
95
+ }
96
+
97
+ const issues = [];
98
+ const criteriaList = criteria.split(',').map((c) => c.trim());
99
+
100
+ for (const criterion of criteriaList) {
101
+ if (/test/i.test(criterion)) {
102
+ // Check if tests pass
103
+ try {
104
+ execSync('npm test --if-present 2>&1', {
105
+ cwd: projectDir,
106
+ timeout: 60_000,
107
+ encoding: 'utf8'
108
+ });
109
+ } catch {
110
+ issues.push({ message: `Test criterion failed: "${criterion}"` });
111
+ }
112
+ }
113
+ }
114
+
115
+ return {
116
+ ok: issues.length === 0,
117
+ verdict: issues.length === 0 ? 'PASS' : 'FAIL_WITH_ISSUES',
118
+ issues
119
+ };
120
+ }
121
+
122
+ // ─── Public API ──────────────────────────────────────────────────────────────
123
+
124
+ /**
125
+ * Run the self-implement loop.
126
+ *
127
+ * @param {object} params
128
+ * @returns {Promise<object>} — { ok, iterations, verdict, feedback[] }
129
+ */
130
+ async function runSelfLoop({ args, options = {}, logger }) {
131
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
132
+ const agent = String(options.agent || options.a || 'dev').trim();
133
+ const task = String(options.task || options.t || '').trim();
134
+ const maxIterations = Math.min(Math.max(Number(options['max-iterations'] || 3), 1), 5);
135
+ const spec = options.spec ? String(options.spec).trim() : null;
136
+ const artifact = options.artifact ? String(options.artifact).trim() : null;
137
+ const criteria = options['verification-criteria'] || options.criteria || '';
138
+ const squad = options.squad ? String(options.squad).trim() : null;
139
+ const timeoutMs = (Number(options.timeout) || 300) * 1000;
140
+
141
+ if (!task) {
142
+ logger.error('Error: --task is required');
143
+ return { ok: false, error: 'missing_task' };
144
+ }
145
+
146
+ const sessionId = randomUUID();
147
+ const feedbackHistory = [];
148
+
149
+ logger.log(`Self-implement loop: @${agent} — "${task.slice(0, 60)}${task.length > 60 ? '...' : ''}"`);
150
+ logger.log(`Max iterations: ${maxIterations}`);
151
+ if (spec) logger.log(`Spec: ${spec}`);
152
+ if (artifact) logger.log(`Artifact: ${artifact}`);
153
+ logger.log('');
154
+
155
+ for (let iteration = 1; iteration <= maxIterations; iteration++) {
156
+ logger.log(`── Iteration ${iteration}/${maxIterations} ──────────────────────────`);
157
+
158
+ // Step 1: Execute agent
159
+ const feedbackContext = feedbackHistory.length > 0
160
+ ? feedbackHistory.map((f) => `[Iteration ${f.iteration}] ${f.verdict}: ${f.issues.map((i) => i.message).join('; ')}`).join('\n')
161
+ : null;
162
+
163
+ logger.log(` Running @${agent}...`);
164
+ const agentResult = executeAgent(targetDir, agent, task, feedbackContext, timeoutMs);
165
+
166
+ if (!agentResult.ok) {
167
+ logger.log(` ✗ Agent execution failed: ${agentResult.error?.slice(0, 100)}`);
168
+ // Record on bus if squad context
169
+ if (squad) {
170
+ await bus.post(targetDir, squad, sessionId, {
171
+ from: 'self-loop',
172
+ type: 'status',
173
+ content: `Iteration ${iteration} — agent failed: ${agentResult.error?.slice(0, 200)}`
174
+ }).catch(() => {});
175
+ }
176
+ continue;
177
+ }
178
+
179
+ // Step 2: Verify (fresh context)
180
+ logger.log(' Verifying...');
181
+ const verifyResult = await runVerification(targetDir, spec, artifact, criteria);
182
+
183
+ // Record on bus
184
+ if (squad) {
185
+ await bus.post(targetDir, squad, sessionId, {
186
+ from: 'self-loop',
187
+ type: verifyResult.ok ? 'result' : 'gap_closure_attempt',
188
+ content: `Iteration ${iteration}: ${verifyResult.verdict}`,
189
+ metadata: {
190
+ iteration,
191
+ verdict: verifyResult.verdict,
192
+ issues_count: verifyResult.issues.length
193
+ }
194
+ }).catch(() => {});
195
+ }
196
+
197
+ // Step 3: Check result
198
+ if (verifyResult.ok) {
199
+ logger.log(` ✓ PASS${iteration > 1 ? ` (after ${iteration} iteration${iteration > 1 ? 's' : ''})` : ''}`);
200
+
201
+ // Record success in state
202
+ if (squad) {
203
+ await stateManager.updateState(targetDir, squad, {
204
+ addDecision: [`self-loop: "${task.slice(0, 50)}" completed in ${iteration} iteration(s)`]
205
+ }).catch(() => {});
206
+ }
207
+
208
+ if (options.json) {
209
+ return { ok: true, iterations: iteration, verdict: verifyResult.verdict, feedback: feedbackHistory };
210
+ }
211
+
212
+ return { ok: true, iterations: iteration, verdict: verifyResult.verdict };
213
+ }
214
+
215
+ // Step 4: Collect feedback for next iteration
216
+ logger.log(` ✗ ${verifyResult.verdict} — ${verifyResult.issues.length} issue(s)`);
217
+ for (const issue of verifyResult.issues.slice(0, 5)) {
218
+ logger.log(` - ${issue.message}`);
219
+ }
220
+
221
+ feedbackHistory.push({
222
+ iteration,
223
+ verdict: verifyResult.verdict,
224
+ issues: verifyResult.issues
225
+ });
226
+ }
227
+
228
+ // Step 5: Exhausted — escalate
229
+ logger.log('');
230
+ logger.log(`✗ Max iterations (${maxIterations}) exhausted — escalating to user`);
231
+
232
+ if (squad) {
233
+ await bus.post(targetDir, squad, sessionId, {
234
+ from: 'self-loop',
235
+ type: 'block',
236
+ content: `Self-implement loop exhausted ${maxIterations} iterations for: "${task.slice(0, 100)}"`,
237
+ metadata: { exhausted: true, iterations: maxIterations }
238
+ }).catch(() => {});
239
+
240
+ await stateManager.updateState(targetDir, squad, {
241
+ addBlocker: [`self-loop exhausted: "${task.slice(0, 50)}" (${maxIterations} iterations)`]
242
+ }).catch(() => {});
243
+ }
244
+
245
+ const result = {
246
+ ok: false,
247
+ iterations: maxIterations,
248
+ verdict: 'EXHAUSTED',
249
+ feedback: feedbackHistory
250
+ };
251
+
252
+ if (options.json) return result;
253
+ return result;
254
+ }
255
+
256
+ module.exports = { runSelfLoop };
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson session:guard [projectDir] --agent=<name> --tool=<tool>
5
+ *
6
+ * Background supervisor that keeps a live session alive.
7
+ * - If no live session exists: auto-starts one (no-launch mode)
8
+ * - Polls every 30s to verify the session is still open
9
+ * - Detects inactivity (no events for --idle-minutes, default: 60) and closes gracefully
10
+ * - Works alongside hooks:emit — guard handles session lifecycle, hooks handle events
11
+ *
12
+ * Run in background:
13
+ * aioson session:guard . --agent=dev --tool=claude &
14
+ *
15
+ * Or as a foreground check (--once):
16
+ * aioson session:guard . --agent=dev --tool=claude --once
17
+ */
18
+
19
+ const path = require('node:path');
20
+ const fs = require('node:fs/promises');
21
+ const {
22
+ openRuntimeDb,
23
+ resolveRuntimePaths,
24
+ readAgentSession,
25
+ writeAgentSession,
26
+ startTask,
27
+ startRun,
28
+ updateRun,
29
+ updateTask,
30
+ appendRunEvent
31
+ } = require('../runtime-store');
32
+
33
+ const POLL_INTERVAL_MS = 30_000;
34
+ const DEFAULT_IDLE_MINUTES = 60;
35
+
36
+ function nowIso() { return new Date().toISOString(); }
37
+ function log(msg) { process.stderr.write(`[session:guard] ${msg}\n`); }
38
+
39
+ async function getLastEventTime(runtimeDir, sessionKey) {
40
+ const eventsPath = path.join(runtimeDir, 'live', sessionKey, 'events.ndjson');
41
+ try {
42
+ const content = await fs.readFile(eventsPath, 'utf8');
43
+ const lines = content.trim().split('\n').filter(Boolean);
44
+ if (lines.length === 0) return null;
45
+ const last = JSON.parse(lines[lines.length - 1]);
46
+ return last.ts ? new Date(last.ts) : null;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ async function startLiveSession(targetDir, runtimeDir, agentName, tool) {
53
+ const now = nowIso();
54
+ const sessionKey = `guard-${agentName}-${Date.now()}`;
55
+ const title = `[guard] ${agentName} via ${tool}`;
56
+
57
+ const { db } = await openRuntimeDb(targetDir);
58
+ try {
59
+ const taskKey = startTask(db, {
60
+ sessionKey,
61
+ title,
62
+ status: 'running',
63
+ createdBy: agentName,
64
+ taskKind: 'live_session',
65
+ metaJson: { tool_session: tool, path: targetDir, guarded: true }
66
+ });
67
+
68
+ const runKey = startRun(db, {
69
+ taskKey,
70
+ agentName,
71
+ agentKind: 'official',
72
+ sessionKey,
73
+ source: 'live',
74
+ title,
75
+ eventType: 'session_started',
76
+ phase: 'live',
77
+ message: `Session auto-started by session:guard (${tool})`,
78
+ payload: { tool_session: tool, path: targetDir, guarded: true }
79
+ });
80
+
81
+ await writeAgentSession(runtimeDir, agentName, {
82
+ runKey, taskKey, sessionKey,
83
+ startedAt: now, finished: false, source: 'live'
84
+ });
85
+
86
+ // Create state.json for dashboard
87
+ const stateDir = path.join(runtimeDir, 'live', sessionKey);
88
+ await fs.mkdir(stateDir, { recursive: true });
89
+ await fs.writeFile(path.join(stateDir, 'state.json'), JSON.stringify({
90
+ session_key: sessionKey, run_key: runKey, task_key: taskKey,
91
+ agent_name: agentName, tool_session: tool,
92
+ status: 'running', started_at: now, updated_at: now, guarded: true,
93
+ last_events: [{ ts: now, type: 'session_started', summary: `Auto-started by session:guard (${tool})` }]
94
+ }, null, 2), 'utf8');
95
+
96
+ log(`Session started: ${sessionKey} (run: ${runKey})`);
97
+ return { runKey, taskKey, sessionKey };
98
+ } finally {
99
+ db.close();
100
+ }
101
+ }
102
+
103
+ async function closeSession(targetDir, runtimeDir, agentName, runKey, taskKey, reason) {
104
+ const now = nowIso();
105
+ const { db } = await openRuntimeDb(targetDir, { mustExist: true });
106
+ try {
107
+ appendRunEvent(db, {
108
+ runKey, eventType: 'session_ended', phase: 'live',
109
+ status: 'completed', message: `Session closed by session:guard: ${reason}`,
110
+ createdAt: now
111
+ });
112
+ updateRun(db, runKey, { status: 'completed', summary: reason, finishedAt: now });
113
+ if (taskKey) updateTask(db, taskKey, { status: 'completed', finishedAt: now });
114
+
115
+ // Update state.json
116
+ const { db: _, ...rest } = await readAgentSession(runtimeDir, agentName).catch(() => ({}));
117
+ const sessionKey = rest?.sessionKey;
118
+ if (sessionKey) {
119
+ const statePath = path.join(runtimeDir, 'live', sessionKey, 'state.json');
120
+ try {
121
+ const state = JSON.parse(await fs.readFile(statePath, 'utf8'));
122
+ state.status = 'closed';
123
+ state.updated_at = now;
124
+ await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf8');
125
+ } catch { /* non-fatal */ }
126
+ }
127
+
128
+ // Clear session file
129
+ const sessionFile = path.join(runtimeDir, '.sessions', `${agentName}.json`);
130
+ try { await fs.unlink(sessionFile); } catch { /* already gone */ }
131
+
132
+ log(`Session closed: ${runKey} (${reason})`);
133
+ } finally {
134
+ db.close();
135
+ }
136
+ }
137
+
138
+ async function tick(targetDir, runtimeDir, agentName, tool, idleMs, state) {
139
+ const session = await readAgentSession(runtimeDir, agentName);
140
+
141
+ if (!session || session.finished) {
142
+ // No session — start one
143
+ const created = await startLiveSession(targetDir, runtimeDir, agentName, tool);
144
+ state.runKey = created.runKey;
145
+ state.taskKey = created.taskKey;
146
+ state.sessionKey = created.sessionKey;
147
+ state.startedAt = Date.now();
148
+ return;
149
+ }
150
+
151
+ // Session exists — check for idle timeout
152
+ const sessionKey = session.sessionKey;
153
+ const lastEvent = await getLastEventTime(runtimeDir, sessionKey);
154
+ const now = Date.now();
155
+ const lastActivity = lastEvent ? lastEvent.getTime() : state.startedAt;
156
+ const idleFor = now - lastActivity;
157
+
158
+ if (idleFor > idleMs) {
159
+ const idleMin = Math.round(idleFor / 60000);
160
+ log(`Idle for ${idleMin}m — closing session`);
161
+ await closeSession(targetDir, runtimeDir, agentName, session.runKey, session.taskKey,
162
+ `Idle for ${idleMin} minutes`);
163
+ state.runKey = null;
164
+ state.taskKey = null;
165
+ state.sessionKey = null;
166
+ }
167
+ }
168
+
169
+ async function runSessionGuard({ args, options = {}, logger }) {
170
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
171
+ const agentName = options.agent ? String(options.agent).replace(/^@/, '') : 'dev';
172
+ const tool = options.tool ? String(options.tool).trim() : 'claude';
173
+ const once = options.once || options['once'] || false;
174
+ const idleMinutes = Number(options['idle-minutes'] || options.idleMinutes || DEFAULT_IDLE_MINUTES);
175
+ const idleMs = idleMinutes * 60 * 1000;
176
+ const intervalMs = Number(options.interval || POLL_INTERVAL_MS);
177
+
178
+ const { runtimeDir } = resolveRuntimePaths(targetDir);
179
+ const state = { runKey: null, taskKey: null, sessionKey: null, startedAt: Date.now() };
180
+
181
+ if (!options.json) {
182
+ logger.log(`[session:guard] Watching: ${targetDir}`);
183
+ logger.log(`[session:guard] Agent: @${agentName} | Tool: ${tool} | Idle timeout: ${idleMinutes}m`);
184
+ logger.log(`[session:guard] Press Ctrl+C to stop.`);
185
+ }
186
+
187
+ await tick(targetDir, runtimeDir, agentName, tool, idleMs, state);
188
+
189
+ if (once) {
190
+ return { ok: true, runKey: state.runKey, sessionKey: state.sessionKey };
191
+ }
192
+
193
+ return new Promise((resolve) => {
194
+ const timer = setInterval(async () => {
195
+ try {
196
+ await tick(targetDir, runtimeDir, agentName, tool, idleMs, state);
197
+ } catch (err) {
198
+ log(`Error: ${err.message}`);
199
+ }
200
+ }, intervalMs);
201
+
202
+ const shutdown = async () => {
203
+ clearInterval(timer);
204
+ if (state.runKey) {
205
+ try {
206
+ await closeSession(targetDir, runtimeDir, agentName, state.runKey, state.taskKey, 'session:guard stopped');
207
+ } catch { /* best-effort */ }
208
+ }
209
+ if (!options.json) logger.log('[session:guard] Stopped.');
210
+ resolve({ ok: true });
211
+ };
212
+
213
+ process.on('SIGINT', shutdown);
214
+ process.on('SIGTERM', shutdown);
215
+ });
216
+ }
217
+
218
+ module.exports = { runSessionGuard };