@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,165 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson feature:close — close a feature after QA sign-off.
5
+ *
6
+ * Updates spec-{slug}.md (adds QA sign-off block), features.md (sets status to done),
7
+ * and project-pulse.md (removes from active work).
8
+ *
9
+ * Usage:
10
+ * aioson feature:close . --feature=checkout --verdict=PASS
11
+ * aioson feature:close . --feature=checkout --verdict=PASS --residual="Email delivery not tested E2E"
12
+ * aioson feature:close . --feature=checkout --verdict=FAIL --notes="Auth edge case missing"
13
+ */
14
+
15
+ const fs = require('node:fs/promises');
16
+ const path = require('node:path');
17
+ const { contextDir, readFileSafe, parseFrontmatter } = require('../preflight-engine');
18
+
19
+ function nowDate() {
20
+ return new Date().toISOString().slice(0, 10);
21
+ }
22
+
23
+ async function updateSpecFile(specPath, verdict, residual, date) {
24
+ const content = await readFileSafe(specPath);
25
+ if (!content) return false;
26
+
27
+ const signOff = [
28
+ '',
29
+ '## QA Sign-off',
30
+ '',
31
+ `- **Date:** ${date}`,
32
+ `- **Verdict:** ${verdict}`,
33
+ residual ? `- **Residual:** ${residual}` : null,
34
+ `- **Gate D (execution):** ${verdict === 'PASS' ? 'approved' : 'rejected'}`,
35
+ ''
36
+ ].filter((l) => l !== null).join('\n');
37
+
38
+ // Update gate_execution in frontmatter first (on original content)
39
+ const newStatus = verdict === 'PASS' ? 'approved' : 'rejected';
40
+ const fm = parseFrontmatter(content);
41
+ let baseContent = content;
42
+ if (Object.keys(fm).length > 0) {
43
+ baseContent = content.replace(
44
+ /^---\r?\n[\s\S]*?\r?\n---/,
45
+ (block) => {
46
+ if (block.includes('gate_execution')) {
47
+ return block.replace(/gate_execution:\s*.+/, `gate_execution: ${newStatus}`);
48
+ }
49
+ return block.replace(/^---\r?\n/, `---\ngate_execution: ${newStatus}\n`);
50
+ }
51
+ );
52
+ }
53
+
54
+ // Now apply QA sign-off on top of the frontmatter-updated content
55
+ if (baseContent.includes('## QA Sign-off')) {
56
+ const updated = baseContent.replace(
57
+ /## QA Sign-off[\s\S]*?(?=\n##|\s*$)/,
58
+ signOff.trimStart()
59
+ );
60
+ await fs.writeFile(specPath, updated, 'utf8');
61
+ } else {
62
+ await fs.writeFile(specPath, baseContent + signOff, 'utf8');
63
+ }
64
+
65
+ return true;
66
+ }
67
+
68
+ async function updateFeaturesFile(featuresPath, slug, verdict, date) {
69
+ const content = await readFileSafe(featuresPath);
70
+ if (!content) return false;
71
+
72
+ const status = verdict === 'PASS' ? 'done' : 'qa_failed';
73
+
74
+ // Try to find and update the feature row
75
+ const updated = content.replace(
76
+ new RegExp(`(\\|[^|]*${slug}[^|]*\\|[^|]*\\|)[^|]*(\\|)`, 'g'),
77
+ (match, before, after) => `${before} ${status} (${date}) ${after}`
78
+ );
79
+
80
+ if (updated !== content) {
81
+ await fs.writeFile(featuresPath, updated, 'utf8');
82
+ return true;
83
+ }
84
+
85
+ // Append if not found
86
+ const line = `| ${slug} | ${verdict === 'PASS' ? 'done' : 'qa_failed'} | ${date} | QA ${verdict} |`;
87
+ await fs.appendFile(featuresPath, `\n${line}\n`, 'utf8');
88
+ return true;
89
+ }
90
+
91
+ async function runFeatureClose({ args, options = {}, logger }) {
92
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
93
+ const slug = options.feature ? String(options.feature) : null;
94
+ const verdict = options.verdict ? String(options.verdict).toUpperCase() : null;
95
+ const residual = options.residual ? String(options.residual) : null;
96
+ const notes = options.notes ? String(options.notes) : null;
97
+
98
+ if (!slug) {
99
+ if (options.json) return { ok: false, reason: 'missing_feature' };
100
+ logger.log('--feature=<slug> is required.');
101
+ return { ok: false };
102
+ }
103
+
104
+ if (!verdict || !['PASS', 'FAIL'].includes(verdict)) {
105
+ if (options.json) return { ok: false, reason: 'invalid_verdict' };
106
+ logger.log('--verdict=PASS or --verdict=FAIL is required.');
107
+ return { ok: false };
108
+ }
109
+
110
+ const today = nowDate();
111
+ const dir = contextDir(targetDir);
112
+ const updates = [];
113
+
114
+ // 1. Update spec file
115
+ const specPath = path.join(dir, `spec-${slug}.md`);
116
+ const specUpdated = await updateSpecFile(specPath, verdict, residual || notes, today);
117
+ if (specUpdated) {
118
+ updates.push(`spec-${slug}.md: added QA sign-off (${today}, ${verdict})`);
119
+ } else {
120
+ updates.push(`spec-${slug}.md: not found (skipped)`);
121
+ }
122
+
123
+ // 2. Update features.md
124
+ const featuresPath = path.join(dir, 'features.md');
125
+ const featuresContent = await readFileSafe(featuresPath);
126
+ if (featuresContent) {
127
+ await updateFeaturesFile(featuresPath, slug, verdict, today);
128
+ updates.push(`features.md: ${slug} → ${verdict === 'PASS' ? 'done' : 'qa_failed'} (${today})`);
129
+ } else {
130
+ updates.push('features.md: not found (skipped)');
131
+ }
132
+
133
+ // 3. Update project-pulse.md
134
+ const pulsePath = path.join(dir, 'project-pulse.md');
135
+ const pulseContent = await readFileSafe(pulsePath);
136
+ if (pulseContent) {
137
+ const fm = parseFrontmatter(pulseContent);
138
+ const status = verdict === 'PASS' ? 'closed' : 'qa_failed';
139
+ const updatedPulse = pulseContent
140
+ .replace(/active_feature:\s*.+/, `active_feature: (none)`)
141
+ .replace(/active_work:\s*".+"/, `active_work: ""`)
142
+ .replace(/last_agent:\s*.+/, `last_agent: qa`)
143
+ .replace(/last_gate:\s*.+/, `last_gate: Gate D: ${verdict === 'PASS' ? 'approved' : 'rejected'}`);
144
+ await fs.writeFile(pulsePath, updatedPulse, 'utf8');
145
+ updates.push('project-pulse.md: updated active work');
146
+ }
147
+
148
+ const result = {
149
+ ok: true,
150
+ feature: slug,
151
+ verdict,
152
+ date: today,
153
+ residual: residual || notes || null,
154
+ updates
155
+ };
156
+
157
+ if (options.json) return result;
158
+
159
+ logger.log(`Feature closure — ${slug}:`);
160
+ for (const u of updates) logger.log(` ${u}`);
161
+
162
+ return result;
163
+ }
164
+
165
+ module.exports = { runFeatureClose };
@@ -0,0 +1,228 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson gate:check — check if a phase gate is approved for a feature.
5
+ *
6
+ * Reads spec-{slug}.md frontmatter and artifact chain to verify gate status.
7
+ * Returns PASS or BLOCKED with evidence. No LLM calls.
8
+ *
9
+ * Usage:
10
+ * aioson gate:check . --feature=checkout --gate=C
11
+ * aioson gate:check . --feature=checkout --gate=D
12
+ * aioson gate:check . --feature=checkout --gate=C --json
13
+ */
14
+
15
+ const path = require('node:path');
16
+ const {
17
+ contextDir,
18
+ readFileSafe,
19
+ fileExists,
20
+ parseGatesFromSpec,
21
+ parseFrontmatter,
22
+ GATE_NAMES,
23
+ GATE_ALIASES
24
+ } = require('../preflight-engine');
25
+
26
+ const BAR = '━'.repeat(35);
27
+
28
+ const GATE_PREREQUISITES = {
29
+ A: [],
30
+ B: ['A'],
31
+ C: ['A', 'B'],
32
+ D: ['A', 'B', 'C']
33
+ };
34
+
35
+ const GATE_REQUIRED_ARTIFACTS = {
36
+ A: (slug) => [`requirements-${slug}.md`],
37
+ B: (slug) => ['architecture.md'],
38
+ C: (slug) => [`implementation-plan-${slug}.md`],
39
+ D: (slug) => [] // Gate D validated by QA sign-off in spec
40
+ };
41
+
42
+ const GATE_DESCRIPTIONS = {
43
+ A: 'requirements',
44
+ B: 'design',
45
+ C: 'plan',
46
+ D: 'execution'
47
+ };
48
+
49
+ async function checkGate(targetDir, slug, gateLetter) {
50
+ const dir = contextDir(targetDir);
51
+ const specFile = path.join(dir, `spec-${slug}.md`);
52
+ const specContent = await readFileSafe(specFile);
53
+ const gates = specContent ? parseGatesFromSpec(specContent) : {};
54
+ const fm = specContent ? parseFrontmatter(specContent) : {};
55
+
56
+ const gateName = GATE_NAMES[gateLetter];
57
+ const gateStatus = gates[gateName] || 'pending';
58
+ const prerequisites = GATE_PREREQUISITES[gateLetter] || [];
59
+
60
+ const evidence = [];
61
+ const missing = [];
62
+
63
+ // Check prerequisites
64
+ for (const prereq of prerequisites) {
65
+ const prereqName = GATE_NAMES[prereq];
66
+ const prereqStatus = gates[prereqName] || 'pending';
67
+ if (prereqStatus === 'approved') {
68
+ evidence.push({ type: 'prereq', gate: prereq, name: prereqName, status: 'approved', ok: true });
69
+ } else {
70
+ evidence.push({ type: 'prereq', gate: prereq, name: prereqName, status: prereqStatus, ok: false });
71
+ missing.push(`Gate ${prereq} (${prereqName}) not approved: ${prereqStatus}`);
72
+ }
73
+ }
74
+
75
+ // Check required artifacts
76
+ const requiredFiles = GATE_REQUIRED_ARTIFACTS[gateLetter](slug);
77
+ for (const fileName of requiredFiles) {
78
+ const filePath = path.join(dir, fileName);
79
+ const exists = await fileExists(filePath);
80
+ if (exists) {
81
+ let detail = null;
82
+ const content = await readFileSafe(filePath);
83
+ if (content) {
84
+ const fileFm = parseFrontmatter(content);
85
+ if (fileFm.status) detail = `status: ${fileFm.status}`;
86
+ }
87
+ evidence.push({ type: 'artifact', file: fileName, exists: true, detail, ok: true });
88
+ } else {
89
+ evidence.push({ type: 'artifact', file: fileName, exists: false, ok: false });
90
+ missing.push(`${fileName} not found`);
91
+ }
92
+ }
93
+
94
+ // Gate D: check for QA sign-off in spec
95
+ if (gateLetter === 'D') {
96
+ if (specContent && specContent.includes('## QA Sign-off')) {
97
+ // Check verdict
98
+ const passMatch = specContent.match(/\*\*Verdict:\*\*\s*(PASS|FAIL)/i);
99
+ const passVerdict = passMatch ? passMatch[1].toUpperCase() : null;
100
+ if (passVerdict === 'PASS') {
101
+ evidence.push({ type: 'qa_signoff', verdict: 'PASS', ok: true });
102
+ } else if (passVerdict === 'FAIL') {
103
+ evidence.push({ type: 'qa_signoff', verdict: 'FAIL', ok: false });
104
+ missing.push('QA sign-off verdict: FAIL');
105
+ } else {
106
+ evidence.push({ type: 'qa_signoff', verdict: null, ok: false });
107
+ missing.push('QA sign-off found but verdict unclear');
108
+ }
109
+ } else {
110
+ // Check spec last_checkpoint for completion indicators
111
+ const checkpoint = fm.last_checkpoint || '';
112
+ if (checkpoint.toLowerCase().includes('complet') || checkpoint.toLowerCase().includes('done')) {
113
+ evidence.push({ type: 'checkpoint', value: checkpoint, ok: true });
114
+ } else {
115
+ evidence.push({ type: 'qa_signoff', exists: false, ok: false });
116
+ missing.push('No QA sign-off in spec file');
117
+ }
118
+ }
119
+
120
+ // Also check spec version for explicit gate_execution
121
+ if (gates.execution && gates.execution !== 'pending') {
122
+ const gateD = gates.execution;
123
+ evidence.push({ type: 'gate_field', field: 'gate_execution', value: gateD, ok: gateD === 'approved' });
124
+ if (gateD !== 'approved') missing.push(`gate_execution: ${gateD}`);
125
+ }
126
+ }
127
+
128
+ const allOk = missing.length === 0;
129
+ const result = allOk ? 'PASS' : 'BLOCKED';
130
+
131
+ let recommendation = '';
132
+ if (result === 'PASS') {
133
+ const nextAgents = { A: '@architect', B: '@dev or @pm', C: '@dev', D: 'feature complete' };
134
+ recommendation = `${nextAgents[gateLetter] || 'proceed'} can proceed`;
135
+ } else {
136
+ const fixAgents = { A: 'complete requirements (@analyst)', B: 'complete design (@architect)', C: 'approve implementation plan', D: 'complete implementation and run @qa' };
137
+ recommendation = `BLOCKED — ${fixAgents[gateLetter] || 'resolve missing items'} first`;
138
+ }
139
+
140
+ return {
141
+ gate: gateLetter,
142
+ gate_name: gateName,
143
+ feature: slug,
144
+ status: gateStatus,
145
+ result,
146
+ evidence,
147
+ missing,
148
+ recommendation
149
+ };
150
+ }
151
+
152
+ async function runGateCheck({ args, options = {}, logger }) {
153
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
154
+ const slug = options.feature ? String(options.feature) : null;
155
+ let gateLetter = options.gate ? String(options.gate).toUpperCase() : null;
156
+
157
+ if (!slug) {
158
+ if (options.json) return { ok: false, reason: 'missing_feature' };
159
+ logger.log('--feature=<slug> is required.');
160
+ return { ok: false };
161
+ }
162
+
163
+ if (!gateLetter) {
164
+ if (options.json) return { ok: false, reason: 'missing_gate' };
165
+ logger.log('--gate=<A|B|C|D> is required.');
166
+ return { ok: false };
167
+ }
168
+
169
+ // Allow gate name aliases (requirements → A, etc.)
170
+ if (GATE_ALIASES[gateLetter.toLowerCase()]) {
171
+ gateLetter = GATE_ALIASES[gateLetter.toLowerCase()];
172
+ }
173
+
174
+ if (!GATE_NAMES[gateLetter]) {
175
+ if (options.json) return { ok: false, reason: 'invalid_gate', gate: gateLetter };
176
+ logger.log(`Invalid gate: ${gateLetter}. Use A, B, C, or D.`);
177
+ return { ok: false };
178
+ }
179
+
180
+ const check = await checkGate(targetDir, slug, gateLetter);
181
+
182
+ const result = { ok: check.result === 'PASS', ...check };
183
+
184
+ if (options.json) return result;
185
+
186
+ logger.log('');
187
+ logger.log(`Gate ${gateLetter} (${check.gate_name}) — ${slug}`);
188
+ logger.log(BAR);
189
+ logger.log(`Status: ${check.status}`);
190
+
191
+ const prereqs = check.evidence.filter((e) => e.type === 'prereq');
192
+ if (prereqs.length > 0) {
193
+ logger.log('Prerequisites met:');
194
+ for (const p of prereqs) {
195
+ const icon = p.ok ? ' ✓' : ' ✗';
196
+ logger.log(`${icon} Gate ${p.gate} (${p.name}): ${p.status}`);
197
+ }
198
+ }
199
+
200
+ const artifacts = check.evidence.filter((e) => e.type === 'artifact');
201
+ if (artifacts.length > 0) {
202
+ logger.log('Artifacts:');
203
+ for (const a of artifacts) {
204
+ const icon = a.ok ? ' ✓' : ' ✗';
205
+ const detail = a.detail ? ` (${a.detail})` : '';
206
+ logger.log(`${icon} ${a.file}${a.ok ? ' exists' : ' missing'}${detail}`);
207
+ }
208
+ }
209
+
210
+ const qaEvidence = check.evidence.filter((e) => e.type === 'qa_signoff' || e.type === 'checkpoint' || e.type === 'gate_field');
211
+ if (qaEvidence.length > 0) {
212
+ for (const q of qaEvidence) {
213
+ const icon = q.ok ? ' ✓' : ' ✗';
214
+ if (q.type === 'qa_signoff') logger.log(`${icon} QA sign-off: ${q.exists === false ? 'missing' : `verdict ${q.verdict || 'unclear'}`}`);
215
+ if (q.type === 'checkpoint') logger.log(` ✓ last_checkpoint: "${q.value}"`);
216
+ if (q.type === 'gate_field') logger.log(`${icon} gate_execution: ${q.value}`);
217
+ }
218
+ }
219
+
220
+ logger.log('');
221
+ const resultIcon = check.result === 'PASS' ? '✓' : '✗';
222
+ logger.log(`Result: ${resultIcon} ${check.result} — ${check.recommendation}`);
223
+ logger.log('');
224
+
225
+ return result;
226
+ }
227
+
228
+ module.exports = { runGateCheck };
@@ -0,0 +1,253 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson hooks:emit [projectDir] --agent=<name> --source=<tool>
5
+ *
6
+ * Called by Claude Code / Antigravity / Codex hooks on every tool use.
7
+ * Reads the hook payload from stdin (JSON), maps it to an AIOSON runtime event,
8
+ * and writes it to the active live session in SQLite + events.ndjson.
9
+ *
10
+ * If no live session exists, auto-starts one (--no-launch mode) before emitting.
11
+ *
12
+ * Designed to be fast (< 50ms hot path) and never block the agent.
13
+ */
14
+
15
+ const path = require('node:path');
16
+ const fs = require('node:fs/promises');
17
+ const { execFileSync } = require('node:child_process');
18
+ const {
19
+ openRuntimeDb,
20
+ resolveRuntimePaths,
21
+ readAgentSession,
22
+ appendRunEvent,
23
+ startTask,
24
+ startRun,
25
+ writeAgentSession
26
+ } = require('../runtime-store');
27
+
28
+ const HOOKS_EMIT_VERSION = '1';
29
+
30
+ // Tool name → event_type mapping
31
+ const TOOL_EVENT_MAP = {
32
+ Write: 'artifact',
33
+ Edit: 'artifact',
34
+ MultiEdit: 'artifact',
35
+ Bash: 'step_done',
36
+ Task: 'note',
37
+ TodoWrite: 'note',
38
+ WebSearch: 'note',
39
+ WebFetch: 'note'
40
+ };
41
+
42
+ // Tools to skip — too noisy, no meaningful event
43
+ const SKIP_TOOLS = new Set(['Read', 'Glob', 'Grep', 'LS', 'NotebookRead', 'mcp__']);
44
+
45
+ function nowIso() {
46
+ return new Date().toISOString();
47
+ }
48
+
49
+ function readStdin() {
50
+ return new Promise((resolve) => {
51
+ let data = '';
52
+ if (process.stdin.isTTY) { resolve(null); return; }
53
+ process.stdin.setEncoding('utf8');
54
+ process.stdin.on('data', (chunk) => { data += chunk; });
55
+ process.stdin.on('end', () => {
56
+ try { resolve(JSON.parse(data)); }
57
+ catch { resolve(null); }
58
+ });
59
+ // Timeout: don't block if stdin is empty
60
+ setTimeout(() => resolve(null), 500);
61
+ });
62
+ }
63
+
64
+ function buildEventFromPayload(payload, source) {
65
+ if (!payload) return null;
66
+
67
+ const toolName = payload.tool_name || payload.toolName || null;
68
+ if (!toolName) return null;
69
+
70
+ // Skip noisy read-only tools
71
+ if (SKIP_TOOLS.has(toolName) || [...SKIP_TOOLS].some((p) => toolName.startsWith(p))) {
72
+ return null;
73
+ }
74
+
75
+ const eventType = TOOL_EVENT_MAP[toolName] || 'note';
76
+ const input = payload.tool_input || payload.toolInput || {};
77
+
78
+ let message = `[${source}] ${toolName}`;
79
+ let filePath = null;
80
+
81
+ if (toolName === 'Write' || toolName === 'Edit' || toolName === 'MultiEdit') {
82
+ filePath = input.file_path || input.path || null;
83
+ message = filePath ? `${toolName}: ${path.basename(filePath)}` : `${toolName}`;
84
+ } else if (toolName === 'Bash') {
85
+ const cmd = String(input.command || input.cmd || '').trim().slice(0, 80);
86
+ message = cmd ? `$ ${cmd}` : 'Bash';
87
+ } else if (toolName === 'Task') {
88
+ const desc = String(input.description || input.prompt || '').slice(0, 80);
89
+ message = desc || 'Task launched';
90
+ } else if (toolName === 'TodoWrite') {
91
+ message = 'Task list updated';
92
+ }
93
+
94
+ return {
95
+ eventType,
96
+ message,
97
+ filePath,
98
+ toolName,
99
+ sessionId: payload.session_id || payload.sessionId || null
100
+ };
101
+ }
102
+
103
+ async function ensureOrCreateLiveSession(targetDir, agentName, source, runtimeDir) {
104
+ // Fast path: check existing session file
105
+ const session = await readAgentSession(runtimeDir, agentName);
106
+ if (session && !session.finished && session.source === 'live' && session.runKey) {
107
+ return session.runKey;
108
+ }
109
+
110
+ // No session — auto-start one inline (no-launch mode)
111
+ const now = nowIso();
112
+ const sessionKey = `hooks-${agentName}-${Date.now()}`;
113
+ const title = `[hooks] ${agentName} via ${source}`;
114
+
115
+ const { db } = await openRuntimeDb(targetDir);
116
+ try {
117
+ const taskKey = startTask(db, {
118
+ sessionKey,
119
+ title,
120
+ status: 'running',
121
+ createdBy: agentName,
122
+ taskKind: 'live_session',
123
+ metaJson: { tool_session: source, path: targetDir, auto_started: true }
124
+ });
125
+
126
+ const runKey = startRun(db, {
127
+ taskKey,
128
+ agentName,
129
+ agentKind: 'official',
130
+ sessionKey,
131
+ source: 'live',
132
+ title,
133
+ eventType: 'session_started',
134
+ phase: 'live',
135
+ message: `Auto-started by hooks:emit (${source})`,
136
+ payload: { tool_session: source, path: targetDir, hooks_version: HOOKS_EMIT_VERSION }
137
+ });
138
+
139
+ await writeAgentSession(runtimeDir, agentName, {
140
+ runKey,
141
+ taskKey,
142
+ sessionKey,
143
+ startedAt: now,
144
+ finished: false,
145
+ source: 'live'
146
+ });
147
+
148
+ // Write state.json for dashboard live view
149
+ const stateDir = path.join(runtimeDir, 'live', sessionKey);
150
+ await fs.mkdir(stateDir, { recursive: true });
151
+ await fs.writeFile(path.join(stateDir, 'state.json'), JSON.stringify({
152
+ session_key: sessionKey,
153
+ run_key: runKey,
154
+ task_key: taskKey,
155
+ agent_name: agentName,
156
+ tool_session: source,
157
+ status: 'running',
158
+ started_at: now,
159
+ updated_at: now,
160
+ auto_started: true,
161
+ last_events: [{ ts: now, type: 'session_started', summary: `Auto-started by hooks:emit (${source})` }]
162
+ }, null, 2), 'utf8');
163
+
164
+ return runKey;
165
+ } finally {
166
+ db.close();
167
+ }
168
+ }
169
+
170
+ async function appendLiveEventFile(runtimeDir, runKey, event) {
171
+ // Find the session dir for this runKey
172
+ try {
173
+ const liveRoot = path.join(runtimeDir, 'live');
174
+ const entries = await fs.readdir(liveRoot).catch(() => []);
175
+ for (const entry of entries) {
176
+ const statePath = path.join(liveRoot, entry, 'state.json');
177
+ try {
178
+ const state = JSON.parse(await fs.readFile(statePath, 'utf8'));
179
+ if (state.run_key === runKey) {
180
+ const eventsPath = path.join(liveRoot, entry, 'events.ndjson');
181
+ await fs.appendFile(eventsPath, JSON.stringify(event) + '\n', 'utf8');
182
+
183
+ // Update state.json updated_at + last_events
184
+ state.updated_at = event.ts;
185
+ const lastEvents = state.last_events || [];
186
+ lastEvents.push({ ts: event.ts, type: event.type, summary: event.summary || event.message });
187
+ state.last_events = lastEvents.slice(-10); // keep last 10
188
+ await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf8');
189
+ break;
190
+ }
191
+ } catch { /* skip */ }
192
+ }
193
+ } catch { /* non-fatal */ }
194
+ }
195
+
196
+ async function runHooksEmit({ args, options = {} }) {
197
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
198
+ const agentName = options.agent ? String(options.agent).replace(/^@/, '') : 'dev';
199
+ const source = options.source ? String(options.source).trim() : 'claude';
200
+
201
+ // Silence all output — hooks must be silent
202
+ const logger = { log: () => {}, error: () => {} };
203
+
204
+ try {
205
+ const { runtimeDir } = resolveRuntimePaths(targetDir);
206
+
207
+ // Read hook payload from stdin
208
+ const payload = await readStdin();
209
+ const event = buildEventFromPayload(payload, source);
210
+
211
+ // Skip if no meaningful event (read-only tools, etc.)
212
+ if (!event) return { ok: true, skipped: true };
213
+
214
+ const now = nowIso();
215
+
216
+ // Ensure live session exists (fast path: session file read)
217
+ const runKey = await ensureOrCreateLiveSession(targetDir, agentName, source, runtimeDir);
218
+
219
+ // Write to SQLite
220
+ const { db } = await openRuntimeDb(targetDir, { mustExist: true });
221
+ try {
222
+ appendRunEvent(db, {
223
+ runKey,
224
+ eventType: event.eventType,
225
+ phase: 'live',
226
+ status: 'running',
227
+ message: event.message,
228
+ payload: event.filePath ? { file: event.filePath, tool: event.toolName } : { tool: event.toolName },
229
+ createdAt: now
230
+ });
231
+ } finally {
232
+ db.close();
233
+ }
234
+
235
+ // Append to events.ndjson for real-time dashboard view
236
+ await appendLiveEventFile(runtimeDir, runKey, {
237
+ ts: now,
238
+ type: event.eventType,
239
+ message: event.message,
240
+ tool: event.toolName,
241
+ file: event.filePath || undefined,
242
+ source,
243
+ agent: agentName
244
+ });
245
+
246
+ return { ok: true, runKey, event: event.eventType, message: event.message };
247
+ } catch {
248
+ // Never fail — hooks must not block the agent
249
+ return { ok: false };
250
+ }
251
+ }
252
+
253
+ module.exports = { runHooksEmit };