@jaimevalasek/aioson 1.6.0 → 1.7.2

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 (275) hide show
  1. package/CHANGELOG.md +74 -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 +22 -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/copywriter.md +463 -0
  114. package/template/.aioson/agents/design-hybrid-forge.md +14 -0
  115. package/template/.aioson/agents/dev.md +271 -25
  116. package/template/.aioson/agents/deyvin.md +67 -8
  117. package/template/.aioson/agents/discovery-design-doc.md +44 -0
  118. package/template/.aioson/agents/genome.md +14 -0
  119. package/template/.aioson/agents/neo.md +83 -2
  120. package/template/.aioson/agents/orache.md +50 -4
  121. package/template/.aioson/agents/orchestrator.md +197 -1
  122. package/template/.aioson/agents/pm.md +35 -0
  123. package/template/.aioson/agents/product.md +50 -5
  124. package/template/.aioson/agents/profiler-enricher.md +14 -0
  125. package/template/.aioson/agents/profiler-forge.md +14 -0
  126. package/template/.aioson/agents/profiler-researcher.md +14 -0
  127. package/template/.aioson/agents/qa.md +273 -21
  128. package/template/.aioson/agents/setup.md +96 -10
  129. package/template/.aioson/agents/sheldon.md +131 -6
  130. package/template/.aioson/agents/site-forge.md +1753 -0
  131. package/template/.aioson/agents/squad.md +352 -0
  132. package/template/.aioson/agents/tester.md +53 -0
  133. package/template/.aioson/agents/ux-ui.md +203 -4
  134. package/template/.aioson/brains/README.md +128 -0
  135. package/template/.aioson/brains/_index.json +16 -0
  136. package/template/.aioson/brains/scripts/query.js +103 -0
  137. package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
  138. package/template/.aioson/config.md +143 -13
  139. package/template/.aioson/constitution.md +33 -0
  140. package/template/.aioson/context/project-pulse.md +34 -0
  141. package/template/.aioson/docs/LAYERS.md +79 -0
  142. package/template/.aioson/docs/README.md +76 -0
  143. package/template/.aioson/docs/example-external-api-context.md +72 -0
  144. package/template/.aioson/genomes/copywriting.md +204 -0
  145. package/template/.aioson/locales/en/agents/architect.md +17 -0
  146. package/template/.aioson/locales/en/agents/dev.md +79 -13
  147. package/template/.aioson/locales/en/agents/orache.md +6 -0
  148. package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
  149. package/template/.aioson/locales/en/agents/product.md +50 -0
  150. package/template/.aioson/locales/en/agents/sheldon.md +115 -0
  151. package/template/.aioson/locales/en/agents/squad.md +14 -0
  152. package/template/.aioson/locales/en/agents/tester.md +6 -0
  153. package/template/.aioson/locales/es/agents/analyst.md +2 -0
  154. package/template/.aioson/locales/es/agents/architect.md +19 -0
  155. package/template/.aioson/locales/es/agents/dev.md +64 -4
  156. package/template/.aioson/locales/es/agents/deyvin.md +2 -0
  157. package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
  158. package/template/.aioson/locales/es/agents/genome.md +2 -0
  159. package/template/.aioson/locales/es/agents/neo.md +2 -0
  160. package/template/.aioson/locales/es/agents/orache.md +2 -0
  161. package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
  162. package/template/.aioson/locales/es/agents/pair.md +2 -0
  163. package/template/.aioson/locales/es/agents/pm.md +2 -0
  164. package/template/.aioson/locales/es/agents/product.md +52 -0
  165. package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
  166. package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
  167. package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
  168. package/template/.aioson/locales/es/agents/qa.md +2 -0
  169. package/template/.aioson/locales/es/agents/setup.md +2 -0
  170. package/template/.aioson/locales/es/agents/sheldon.md +117 -0
  171. package/template/.aioson/locales/es/agents/squad.md +16 -0
  172. package/template/.aioson/locales/es/agents/tester.md +9 -0
  173. package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
  174. package/template/.aioson/locales/fr/agents/analyst.md +2 -0
  175. package/template/.aioson/locales/fr/agents/architect.md +19 -0
  176. package/template/.aioson/locales/fr/agents/dev.md +64 -4
  177. package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
  178. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
  179. package/template/.aioson/locales/fr/agents/genome.md +2 -0
  180. package/template/.aioson/locales/fr/agents/neo.md +2 -0
  181. package/template/.aioson/locales/fr/agents/orache.md +2 -0
  182. package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
  183. package/template/.aioson/locales/fr/agents/pair.md +2 -0
  184. package/template/.aioson/locales/fr/agents/pm.md +2 -0
  185. package/template/.aioson/locales/fr/agents/product.md +52 -0
  186. package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
  187. package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
  188. package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
  189. package/template/.aioson/locales/fr/agents/qa.md +2 -0
  190. package/template/.aioson/locales/fr/agents/setup.md +2 -0
  191. package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
  192. package/template/.aioson/locales/fr/agents/squad.md +16 -0
  193. package/template/.aioson/locales/fr/agents/tester.md +9 -0
  194. package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
  195. package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
  196. package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
  197. package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
  198. package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
  199. package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
  200. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
  201. package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
  202. package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
  203. package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
  204. package/template/.aioson/locales/pt-BR/agents/setup.md +101 -18
  205. package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
  206. package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
  207. package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
  208. package/template/.aioson/rules/README.md +69 -0
  209. package/template/.aioson/rules/data-format-convention.md +136 -0
  210. package/template/.aioson/rules/example-monetary-values.md +30 -0
  211. package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
  212. package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +2 -0
  213. package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
  214. package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
  215. package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
  216. package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
  217. package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
  218. package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
  219. package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
  220. package/template/.aioson/skills/marketing/references/anti-patterns.md +254 -0
  221. package/template/.aioson/skills/marketing/references/fascinations.md +192 -0
  222. package/template/.aioson/skills/marketing/references/five-acts.md +248 -0
  223. package/template/.aioson/skills/marketing/references/market-intelligence.md +198 -0
  224. package/template/.aioson/skills/marketing/references/offer-structure.md +203 -0
  225. package/template/.aioson/skills/marketing/references/one-belief.md +149 -0
  226. package/template/.aioson/skills/marketing/references/patterns.md +218 -0
  227. package/template/.aioson/skills/marketing/references/pms-research.md +193 -0
  228. package/template/.aioson/skills/marketing/vsl-craft.md +385 -0
  229. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +1 -0
  230. package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
  231. package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
  232. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
  233. package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
  234. package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +35 -0
  235. package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
  236. package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
  237. package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
  238. package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +4 -1
  239. package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +15 -0
  240. package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +32 -0
  241. package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +20 -0
  242. package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
  243. package/template/.aioson/skills/static/context-budget-guide.md +46 -0
  244. package/template/.aioson/skills/static/harness-sensors.md +74 -0
  245. package/template/.aioson/skills/static/landing-page-deploy.md +192 -0
  246. package/template/.aioson/skills/static/landing-page-forge.md +730 -0
  247. package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
  248. package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
  249. package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
  250. package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
  251. package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
  252. package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
  253. package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
  254. package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
  255. package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
  256. package/template/.aioson/skills/static/threejs-patterns.md +929 -0
  257. package/template/.aioson/skills/static/ui-ux-modern.md +1 -0
  258. package/template/.aioson/skills/static/web-research-cache.md +112 -0
  259. package/template/.aioson/tasks/implementation-plan.md +21 -1
  260. package/template/.aioson/tasks/squad-create.md +22 -0
  261. package/template/.aioson/tasks/squad-design.md +30 -0
  262. package/template/.aioson/templates/squads/digital-marketing-agency/template.json +96 -0
  263. package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
  264. package/template/.claude/commands/aioson/agent/orache.md +5 -0
  265. package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
  266. package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
  267. package/template/AGENTS.md +55 -3
  268. package/template/CLAUDE.md +31 -0
  269. package/template/OPENCODE.md +4 -0
  270. package/template/researchs/.gitkeep +0 -0
  271. package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
  272. package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
  273. package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
  274. package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
  275. package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
@@ -0,0 +1,443 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * preflight-engine — shared deterministic utilities for preflight, gate:check, artifact:validate.
5
+ * No LLM calls. Pure file parsing + logic.
6
+ */
7
+
8
+ const fs = require('node:fs/promises');
9
+ const path = require('node:path');
10
+
11
+ // ─── Frontmatter parser ───────────────────────────────────────────────────────
12
+
13
+ function parseFrontmatter(content) {
14
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
15
+ if (!match) return {};
16
+ const result = {};
17
+ for (const line of match[1].split(/\r?\n/)) {
18
+ const colonIdx = line.indexOf(':');
19
+ if (colonIdx === -1) continue;
20
+ const key = line.slice(0, colonIdx).trim();
21
+ const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
22
+ if (key) result[key] = value;
23
+ }
24
+ return result;
25
+ }
26
+
27
+ async function readFileSafe(filePath) {
28
+ try {
29
+ return await fs.readFile(filePath, 'utf8');
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ async function fileExists(filePath) {
36
+ try {
37
+ await fs.access(filePath);
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ async function fileStat(filePath) {
45
+ try {
46
+ return await fs.stat(filePath);
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ // ─── Framework detection ──────────────────────────────────────────────────────
53
+
54
+ const FRAMEWORK_INDICATORS = [
55
+ { file: 'composer.json', key: 'laravel/framework', name: 'Laravel' },
56
+ { file: 'composer.json', key: 'symfony/framework-bundle', name: 'Symfony' },
57
+ { file: 'package.json', key: '"next"', name: 'Next.js' },
58
+ { file: 'package.json', key: '"nuxt"', name: 'Nuxt.js' },
59
+ { file: 'package.json', key: '"react"', name: 'React' },
60
+ { file: 'package.json', key: '"vue"', name: 'Vue' },
61
+ { file: 'package.json', key: '"svelte"', name: 'Svelte' },
62
+ { file: 'package.json', key: '"express"', name: 'Express' },
63
+ { file: 'Gemfile', key: 'rails', name: 'Rails' },
64
+ { file: 'requirements.txt', key: 'django', name: 'Django' },
65
+ { file: 'requirements.txt', key: 'fastapi', name: 'FastAPI' },
66
+ { file: 'requirements.txt', key: 'flask', name: 'Flask' },
67
+ { file: 'go.mod', key: 'gin-gonic', name: 'Gin' },
68
+ { file: 'go.mod', key: 'echo', name: 'Echo' },
69
+ { file: 'Cargo.toml', key: 'actix-web', name: 'Actix' },
70
+ { file: 'foundry.toml', key: null, name: 'Foundry (Solidity)' }
71
+ ];
72
+
73
+ async function detectFramework(targetDir) {
74
+ for (const { file, key, name } of FRAMEWORK_INDICATORS) {
75
+ const filePath = path.join(targetDir, file);
76
+ const content = await readFileSafe(filePath);
77
+ if (!content) continue;
78
+ if (!key || content.toLowerCase().includes(key.toLowerCase())) {
79
+ return name;
80
+ }
81
+ }
82
+ return null;
83
+ }
84
+
85
+ // ─── Test runner detection ────────────────────────────────────────────────────
86
+
87
+ const TEST_RUNNER_INDICATORS = [
88
+ { file: 'phpunit.xml', name: 'Pest/PHPUnit', command: 'php artisan test' },
89
+ { file: 'phpunit.xml.dist', name: 'PHPUnit', command: './vendor/bin/phpunit' },
90
+ { file: 'jest.config.js', name: 'Jest', command: 'npx jest' },
91
+ { file: 'jest.config.ts', name: 'Jest', command: 'npx jest' },
92
+ { file: 'jest.config.mjs', name: 'Jest', command: 'npx jest' },
93
+ { file: 'vitest.config.js', name: 'Vitest', command: 'npx vitest' },
94
+ { file: 'vitest.config.ts', name: 'Vitest', command: 'npx vitest' },
95
+ { file: 'vitest.config.mjs', name: 'Vitest', command: 'npx vitest' },
96
+ { file: 'pytest.ini', name: 'Pytest', command: 'pytest' },
97
+ { file: 'setup.cfg', name: 'Pytest', command: 'pytest', key: '[tool:pytest]' },
98
+ { file: 'pyproject.toml', name: 'Pytest', command: 'pytest', key: '[tool.pytest' },
99
+ { file: '.rspec', name: 'RSpec', command: 'bundle exec rspec' },
100
+ { file: 'foundry.toml', name: 'Forge', command: 'forge test' },
101
+ { file: 'Makefile', name: 'Make', command: 'make test', key: 'test:' }
102
+ ];
103
+
104
+ async function detectTestRunner(targetDir) {
105
+ for (const { file, name, command, key } of TEST_RUNNER_INDICATORS) {
106
+ const filePath = path.join(targetDir, file);
107
+ const content = await readFileSafe(filePath);
108
+ if (!content) continue;
109
+ if (key && !content.includes(key)) continue;
110
+ return { name, command, configFile: file };
111
+ }
112
+
113
+ // Check package.json scripts (test, test:unit, test:e2e, etc.)
114
+ const pkgContent = await readFileSafe(path.join(targetDir, 'package.json'));
115
+ if (pkgContent) {
116
+ try {
117
+ const pkg = JSON.parse(pkgContent);
118
+ if (pkg.scripts) {
119
+ // Check all test-related script keys, prioritize "test" then "test:*"
120
+ const testKeys = Object.keys(pkg.scripts).filter((k) => k === 'test' || k.startsWith('test:'));
121
+ for (const key of testKeys) {
122
+ const script = pkg.scripts[key];
123
+ if (script.includes('jest')) return { name: 'Jest', command: `npm run ${key}`, configFile: 'package.json' };
124
+ if (script.includes('vitest')) return { name: 'Vitest', command: `npm run ${key}`, configFile: 'package.json' };
125
+ if (script.includes('mocha')) return { name: 'Mocha', command: `npm run ${key}`, configFile: 'package.json' };
126
+ if (script.includes('node --test') || script.includes('node:test')) return { name: 'node:test', command: `npm run ${key}`, configFile: 'package.json' };
127
+ }
128
+ if (pkg.scripts.test) {
129
+ return { name: 'npm test', command: 'npm test', configFile: 'package.json' };
130
+ }
131
+ }
132
+ } catch { /* ignore malformed package.json */ }
133
+ }
134
+
135
+ return null;
136
+ }
137
+
138
+ // ─── Context file paths ───────────────────────────────────────────────────────
139
+
140
+ function contextDir(targetDir) {
141
+ return path.join(targetDir, '.aioson', 'context');
142
+ }
143
+
144
+ function rulesDir(targetDir) {
145
+ return path.join(targetDir, '.aioson', 'rules');
146
+ }
147
+
148
+ function artifactPath(targetDir, name, slug) {
149
+ const dir = contextDir(targetDir);
150
+ if (slug) return path.join(dir, `${name}-${slug}.md`);
151
+ return path.join(dir, `${name}.md`);
152
+ }
153
+
154
+ // ─── Project context reader ───────────────────────────────────────────────────
155
+
156
+ async function loadProjectContext(targetDir) {
157
+ const filePath = path.join(contextDir(targetDir), 'project.context.md');
158
+ const content = await readFileSafe(filePath);
159
+ if (!content) return { exists: false, data: {} };
160
+ const data = parseFrontmatter(content);
161
+ return { exists: true, data, content };
162
+ }
163
+
164
+ // ─── Artifact scanner ─────────────────────────────────────────────────────────
165
+
166
+ async function scanArtifacts(targetDir, slug) {
167
+ const dir = contextDir(targetDir);
168
+
169
+ async function check(name, filePath) {
170
+ const stat = await fileStat(filePath);
171
+ if (!stat) return { exists: false };
172
+
173
+ const content = await readFileSafe(filePath);
174
+ const fm = content ? parseFrontmatter(content) : {};
175
+
176
+ return {
177
+ exists: true,
178
+ path: path.relative(targetDir, filePath),
179
+ size: stat.size,
180
+ frontmatter: fm,
181
+ content
182
+ };
183
+ }
184
+
185
+ const results = {
186
+ project_context: await check('project.context', path.join(dir, 'project.context.md')),
187
+ prd: slug ? await check('prd', path.join(dir, `prd-${slug}.md`)) : { exists: false },
188
+ sheldon_enrichment: slug ? await check('sheldon', path.join(dir, `sheldon-enrichment-${slug}.md`)) : { exists: false },
189
+ requirements: slug ? await check('requirements', path.join(dir, `requirements-${slug}.md`)) : { exists: false },
190
+ spec: slug ? await check('spec', path.join(dir, `spec-${slug}.md`)) : await check('spec', path.join(dir, 'spec.md')),
191
+ architecture: await check('architecture', path.join(dir, 'architecture.md')),
192
+ implementation_plan: slug ? await check('impl-plan', path.join(dir, `implementation-plan-${slug}.md`)) : { exists: false },
193
+ conformance: slug ? await check('conformance', path.join(dir, `conformance-${slug}.yaml`)) : { exists: false },
194
+ dev_state: await check('dev-state', path.join(dir, 'dev-state.md')),
195
+ features: await check('features', path.join(dir, 'features.md'))
196
+ };
197
+
198
+ return results;
199
+ }
200
+
201
+ // ─── Gate reader ─────────────────────────────────────────────────────────────
202
+
203
+ const GATE_NAMES = {
204
+ A: 'requirements',
205
+ B: 'design',
206
+ C: 'plan',
207
+ D: 'execution'
208
+ };
209
+
210
+ const GATE_ALIASES = {
211
+ requirements: 'A',
212
+ design: 'B',
213
+ plan: 'C',
214
+ execution: 'D'
215
+ };
216
+
217
+ function parseGatesFromSpec(content) {
218
+ if (!content) return {};
219
+ const fm = parseFrontmatter(content);
220
+ const gates = {};
221
+
222
+ // Try explicit gate fields: gate_requirements, gate_design, gate_plan, gate_execution
223
+ for (const [letter, name] of Object.entries(GATE_NAMES)) {
224
+ const val = fm[`gate_${name}`] || fm[`gate${letter}`] || fm[`gate_${letter}`];
225
+ if (val) gates[name] = val.toLowerCase();
226
+ }
227
+
228
+ // Try phase_gates JSON field
229
+ if (fm.phase_gates) {
230
+ try {
231
+ const parsed = JSON.parse(fm.phase_gates.replace(/'/g, '"'));
232
+ Object.assign(gates, parsed);
233
+ } catch {
234
+ // phase_gates field exists but is not valid JSON — gate data from this field is lost
235
+ }
236
+ }
237
+
238
+ // Try scanning content for gate approval lines
239
+ const gateLineRe = /gate\s+([A-D])[^:]*:\s*(approved|pending|rejected)/gi;
240
+ let m;
241
+ while ((m = gateLineRe.exec(content)) !== null) {
242
+ const letter = m[1].toUpperCase();
243
+ const name = GATE_NAMES[letter];
244
+ if (name && !gates[name]) gates[name] = m[2].toLowerCase();
245
+ }
246
+
247
+ return gates;
248
+ }
249
+
250
+ async function readPhaseGates(targetDir, slug) {
251
+ const specFile = slug
252
+ ? path.join(contextDir(targetDir), `spec-${slug}.md`)
253
+ : path.join(contextDir(targetDir), 'spec.md');
254
+
255
+ const content = await readFileSafe(specFile);
256
+ if (!content) return {};
257
+ return parseGatesFromSpec(content);
258
+ }
259
+
260
+ // ─── Dev state reader ─────────────────────────────────────────────────────────
261
+
262
+ async function readDevState(targetDir) {
263
+ const filePath = path.join(contextDir(targetDir), 'dev-state.md');
264
+ const content = await readFileSafe(filePath);
265
+ if (!content) return { exists: false };
266
+ const fm = parseFrontmatter(content);
267
+ return { exists: true, ...fm, content };
268
+ }
269
+
270
+ // ─── Project pulse reader ────────────────────────────────────────────────────
271
+
272
+ async function readProjectPulse(targetDir) {
273
+ const filePath = path.join(contextDir(targetDir), 'project-pulse.md');
274
+ const content = await readFileSafe(filePath);
275
+ if (!content) return { exists: false };
276
+ const fm = parseFrontmatter(content);
277
+ return { exists: true, ...fm, content };
278
+ }
279
+
280
+ // ─── Classification reader ────────────────────────────────────────────────────
281
+
282
+ async function detectClassification(targetDir, slug) {
283
+ // 1. Try project context
284
+ const ctx = await loadProjectContext(targetDir);
285
+ if (ctx.data.classification) return ctx.data.classification.toUpperCase();
286
+
287
+ // 2. Try spec frontmatter
288
+ if (slug) {
289
+ const specContent = await readFileSafe(path.join(contextDir(targetDir), `spec-${slug}.md`));
290
+ if (specContent) {
291
+ const fm = parseFrontmatter(specContent);
292
+ if (fm.classification) return fm.classification.toUpperCase();
293
+ }
294
+
295
+ // 3. Try PRD frontmatter
296
+ const prdContent = await readFileSafe(path.join(contextDir(targetDir), `prd-${slug}.md`));
297
+ if (prdContent) {
298
+ const fm = parseFrontmatter(prdContent);
299
+ if (fm.classification) return fm.classification.toUpperCase();
300
+ }
301
+ }
302
+
303
+ return null;
304
+ }
305
+
306
+ // ─── Rules discovery ──────────────────────────────────────────────────────────
307
+
308
+ async function discoverRules(targetDir, agent) {
309
+ const dir = rulesDir(targetDir);
310
+ const rules = [];
311
+
312
+ let entries;
313
+ try {
314
+ entries = await fs.readdir(dir);
315
+ } catch {
316
+ return rules;
317
+ }
318
+
319
+ for (const entry of entries) {
320
+ if (!entry.endsWith('.md')) continue;
321
+ const content = await readFileSafe(path.join(dir, entry));
322
+ if (!content) continue;
323
+
324
+ // Check applicability: universal rules or agent-specific
325
+ const fm = parseFrontmatter(content);
326
+ const applies = !fm.agents || fm.agents.includes('all') || fm.agents.includes(agent);
327
+ if (applies) rules.push(entry);
328
+ }
329
+
330
+ return rules;
331
+ }
332
+
333
+ // ─── Context package builder ──────────────────────────────────────────────────
334
+
335
+ function buildContextPackage(agent, slug, classification, artifacts, devState) {
336
+ const pkg = [];
337
+
338
+ if (artifacts.project_context.exists) pkg.push(artifacts.project_context.path);
339
+
340
+ if (slug) {
341
+ // Feature-specific context
342
+ if (artifacts.spec.exists) pkg.push(artifacts.spec.path);
343
+ if (artifacts.implementation_plan.exists) pkg.push(artifacts.implementation_plan.path);
344
+ if (artifacts.requirements.exists && ['analyst', 'architect', 'dev'].includes(agent)) {
345
+ pkg.push(artifacts.requirements.path);
346
+ }
347
+ }
348
+
349
+ // Agent-specific additions
350
+ if (agent === 'dev' && artifacts.dev_state.exists) pkg.push('dev-state.md (check for active state)');
351
+ if (agent === 'qa' && artifacts.spec.exists) pkg.push(artifacts.spec.path);
352
+ if (agent === 'architect' && artifacts.architecture.exists) pkg.push(artifacts.architecture.path);
353
+
354
+ return [...new Set(pkg)];
355
+ }
356
+
357
+ // ─── Readiness evaluator ─────────────────────────────────────────────────────
358
+
359
+ function evaluateReadiness(artifacts, phaseGates, classification, agent) {
360
+ const blockers = [];
361
+
362
+ if (!artifacts.project_context.exists) blockers.push('project.context.md missing');
363
+
364
+ if (agent === 'dev') {
365
+ if (!artifacts.spec.exists) blockers.push('spec file missing');
366
+ if (phaseGates.plan && phaseGates.plan !== 'approved') {
367
+ blockers.push(`Gate C (plan) not approved: ${phaseGates.plan || 'pending'}`);
368
+ }
369
+ }
370
+
371
+ if (agent === 'qa') {
372
+ if (!artifacts.spec.exists) blockers.push('spec file missing');
373
+ if (classification && classification !== 'MICRO') {
374
+ if (phaseGates.plan && phaseGates.plan !== 'approved') {
375
+ blockers.push(`Gate C (plan) not approved: ${phaseGates.plan || 'pending'}`);
376
+ }
377
+ }
378
+ }
379
+
380
+ if (agent === 'analyst') {
381
+ if (!artifacts.prd.exists) blockers.push('prd file missing');
382
+ }
383
+
384
+ if (agent === 'architect') {
385
+ if (!artifacts.requirements.exists) blockers.push('requirements file missing');
386
+ }
387
+
388
+ return blockers.length === 0
389
+ ? { status: 'READY', blockers: [] }
390
+ : { status: 'BLOCKED', blockers };
391
+ }
392
+
393
+ // ─── Spec version extractor ───────────────────────────────────────────────────
394
+
395
+ function extractSpecVersion(artifact) {
396
+ if (!artifact.exists) return null;
397
+ return artifact.frontmatter.version || null;
398
+ }
399
+
400
+ function extractLastCheckpoint(artifact) {
401
+ if (!artifact.exists) return null;
402
+ const fm = artifact.frontmatter;
403
+ if (fm.last_checkpoint) return fm.last_checkpoint;
404
+
405
+ // Scan content for checkpoint patterns — use last occurrence (most recent)
406
+ if (artifact.content) {
407
+ const matches = artifact.content.match(/last_checkpoint:\s*(.+)/g);
408
+ if (matches && matches.length > 0) {
409
+ const last = matches[matches.length - 1];
410
+ const val = last.replace(/^last_checkpoint:\s*/, '').trim().replace(/^["']|["']$/g, '');
411
+ return val;
412
+ }
413
+ }
414
+ return null;
415
+ }
416
+
417
+ // ─── Exports ──────────────────────────────────────────────────────────────────
418
+
419
+ module.exports = {
420
+ parseFrontmatter,
421
+ readFileSafe,
422
+ fileExists,
423
+ fileStat,
424
+ detectFramework,
425
+ detectTestRunner,
426
+ contextDir,
427
+ rulesDir,
428
+ artifactPath,
429
+ loadProjectContext,
430
+ scanArtifacts,
431
+ parseGatesFromSpec,
432
+ readPhaseGates,
433
+ readDevState,
434
+ readProjectPulse,
435
+ detectClassification,
436
+ discoverRules,
437
+ buildContextPackage,
438
+ evaluateReadiness,
439
+ extractSpecVersion,
440
+ extractLastCheckpoint,
441
+ GATE_NAMES,
442
+ GATE_ALIASES
443
+ };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const { launchCLI } = require('./cli-launcher');
4
+
5
+ const DEFAULT_ATTEMPTS = { haiku: 3, sonnet: 2, opus: 1 };
6
+
7
+ // Model IDs por alias. Injetados via ANTHROPIC_MODEL env var para Claude Code.
8
+ const MODEL_MAP = {
9
+ haiku: 'claude-haiku-4-5-20251001',
10
+ sonnet: 'claude-sonnet-4-6',
11
+ opus: 'claude-opus-4-6'
12
+ };
13
+
14
+ /**
15
+ * Executa uma task com cascade de modelos.
16
+ *
17
+ * Para cada modelo na cadeia:
18
+ * 1. Tenta até N vezes (DEFAULT_ATTEMPTS ou customizado)
19
+ * 2. Se result.ok: verifica via gateConfig (opcional)
20
+ * 3. Se aprovado: retorna resultado imediatamente
21
+ * 4. Se reprovado ou falha: tenta próximo modelo
22
+ *
23
+ * @param {string} projectDir
24
+ * @param {string} prompt
25
+ * @param {string[]} modelChain ex: ['haiku', 'sonnet', 'opus']
26
+ * @param {object} options
27
+ * @param {string} [options.tool] CLI a usar (padrão: auto-detectado)
28
+ * @param {Function} [options.gateCheck] fn(output: string) → {passed: boolean, reason?: string}
29
+ * @param {Function} [options.onProgress] fn({model, attempt, maxAttempts, status, reason?})
30
+ * @param {number} [options.timeout] Timeout em ms por tentativa
31
+ * @returns {Promise<{ok: boolean, result?, modelUsed?: string, attempts?: number, error?: string}>}
32
+ */
33
+ async function runWithCascade(projectDir, prompt, modelChain, options = {}) {
34
+ const { tool, gateCheck, onProgress, timeout } = options;
35
+
36
+ for (const modelAlias of modelChain) {
37
+ const maxAttempts = DEFAULT_ATTEMPTS[modelAlias] ?? 1;
38
+ const modelId = MODEL_MAP[modelAlias];
39
+
40
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
41
+ if (onProgress) {
42
+ onProgress({ model: modelAlias, attempt, maxAttempts, status: 'running' });
43
+ }
44
+
45
+ // Injeta o modelo via ANTHROPIC_MODEL (compatível com Claude Code)
46
+ const extraEnv = modelId ? { ANTHROPIC_MODEL: modelId } : {};
47
+
48
+ const result = await launchCLI(projectDir, prompt, {
49
+ tool,
50
+ timeout,
51
+ env: extraEnv
52
+ });
53
+
54
+ if (!result.ok) {
55
+ if (onProgress) {
56
+ onProgress({ model: modelAlias, attempt, maxAttempts, status: 'cli_failed' });
57
+ }
58
+ continue;
59
+ }
60
+
61
+ // Verifica qualidade se gate configurado
62
+ if (gateCheck) {
63
+ const gateResult = gateCheck(result.output);
64
+ if (gateResult.passed) {
65
+ return { ok: true, result, modelUsed: modelAlias, attempts: attempt };
66
+ }
67
+ if (onProgress) {
68
+ onProgress({
69
+ model: modelAlias,
70
+ attempt,
71
+ maxAttempts,
72
+ status: 'gate_failed',
73
+ reason: gateResult.reason || 'quality gate failed'
74
+ });
75
+ }
76
+ // Não retorna — tenta próxima tentativa ou próximo modelo
77
+ } else {
78
+ // Sem gate: aceita o resultado
79
+ return { ok: true, result, modelUsed: modelAlias, attempts: attempt };
80
+ }
81
+ }
82
+ }
83
+
84
+ return { ok: false, error: 'All cascade models exhausted without passing quality gate' };
85
+ }
86
+
87
+ /**
88
+ * Parseia uma string de cascade como "haiku,sonnet,opus" para um array.
89
+ * @param {string} cascadeStr
90
+ * @returns {string[]}
91
+ */
92
+ function parseCascadeChain(cascadeStr) {
93
+ if (!cascadeStr) return [];
94
+ return cascadeStr.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
95
+ }
96
+
97
+ module.exports = { runWithCascade, parseCascadeChain, MODEL_MAP, DEFAULT_ATTEMPTS };
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ const { spawn } = require('node:child_process');
4
+
5
+ /**
6
+ * Detecta qual CLI de AI está disponível no sistema.
7
+ * Usa AIOSON_RUNNER_TOOL env var se definida, depois tenta
8
+ * claude, codex, gemini, opencode em sequência.
9
+ */
10
+ async function detectCLI() {
11
+ const envTool = process.env.AIOSON_RUNNER_TOOL;
12
+ if (envTool) return envTool;
13
+
14
+ for (const cli of ['claude', 'codex', 'gemini', 'opencode']) {
15
+ const found = await new Promise((resolve) => {
16
+ const child = spawn('which', [cli], { stdio: 'pipe' });
17
+ child.on('close', (code) => resolve(code === 0));
18
+ child.on('error', () => resolve(false));
19
+ });
20
+ if (found) return cli;
21
+ }
22
+ throw new Error('No AI CLI found. Install claude, codex, gemini, or opencode.');
23
+ }
24
+
25
+ /**
26
+ * Monta os argumentos de headless para cada CLI.
27
+ */
28
+ function buildArgs(cli, prompt, options = {}) {
29
+ const { allowedTools, outputFormat } = options;
30
+
31
+ switch (cli) {
32
+ case 'claude':
33
+ return [
34
+ '-p', prompt,
35
+ '--output-format', outputFormat || 'stream-json',
36
+ '--dangerously-skip-permissions',
37
+ ...(allowedTools ? ['--allowedTools', allowedTools] : [])
38
+ ];
39
+ case 'codex':
40
+ return ['-p', prompt, '--quiet', '--no-interactive'];
41
+ case 'gemini':
42
+ return ['-p', prompt, '--quiet'];
43
+ default:
44
+ return ['-p', prompt];
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Spawna o CLI de AI com o prompt headless e retorna o output.
50
+ * Detecta TASK_COMPLETE no stream para sinalizar conclusão.
51
+ *
52
+ * @param {string} projectDir
53
+ * @param {string} prompt
54
+ * @param {object} options
55
+ * @returns {Promise<{ok: boolean, output: string, stderr: string, completionMarker: boolean, exitCode: number}>}
56
+ */
57
+ async function launchCLI(projectDir, prompt, options = {}) {
58
+ const { tool, timeout = 120000, onData, env: extraEnv } = options;
59
+ const cli = tool || await detectCLI();
60
+ const args = buildArgs(cli, prompt, options);
61
+
62
+ return new Promise((resolve) => {
63
+ const child = spawn(cli, args, {
64
+ cwd: projectDir,
65
+ env: { ...process.env, ...(extraEnv || {}) },
66
+ stdio: ['ignore', 'pipe', 'pipe']
67
+ });
68
+
69
+ let stdout = '';
70
+ let stderr = '';
71
+ let completionMarker = false;
72
+ let settled = false;
73
+
74
+ const timer = timeout > 0
75
+ ? setTimeout(() => {
76
+ if (!settled) child.kill('SIGTERM');
77
+ }, timeout)
78
+ : null;
79
+
80
+ child.stdout.on('data', (chunk) => {
81
+ const text = chunk.toString();
82
+ stdout += text;
83
+ if (onData) onData(text);
84
+ if (text.includes('TASK_COMPLETE')) completionMarker = true;
85
+ });
86
+
87
+ child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
88
+
89
+ child.on('close', (code) => {
90
+ settled = true;
91
+ if (timer) clearTimeout(timer);
92
+ resolve({
93
+ ok: code === 0,
94
+ output: stdout.trim(),
95
+ stderr: stderr.trim(),
96
+ completionMarker,
97
+ exitCode: code ?? -1
98
+ });
99
+ });
100
+
101
+ child.on('error', (err) => {
102
+ settled = true;
103
+ if (timer) clearTimeout(timer);
104
+ resolve({ ok: false, output: '', stderr: err.message, completionMarker: false, exitCode: -1 });
105
+ });
106
+ });
107
+ }
108
+
109
+ module.exports = { launchCLI, detectCLI, buildArgs };