@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,177 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ const CHARS_PER_TOKEN = 4;
7
+ const LARGE_SECTION_BULLETS = 20; // warn if a section has more than this many bullet lines
8
+
9
+ function estimateTokens(content) {
10
+ return Math.ceil(content.length / CHARS_PER_TOKEN);
11
+ }
12
+
13
+ function formatBytes(bytes) {
14
+ return `${(bytes / 1024).toFixed(1)}KB`;
15
+ }
16
+
17
+ async function loadFeatureStatuses(contextDir) {
18
+ const featuresPath = path.join(contextDir, 'features.md');
19
+ try {
20
+ const content = await fs.readFile(featuresPath, 'utf8');
21
+ const done = new Set();
22
+ for (const line of content.split(/\r?\n/)) {
23
+ const m = line.match(/[-|]\s*([a-z0-9_-]+)\s*[:|]\s*done/i);
24
+ if (m) done.add(m[1].toLowerCase());
25
+ }
26
+ return done;
27
+ } catch {
28
+ return new Set();
29
+ }
30
+ }
31
+
32
+ function findLargeSections(content, fileName) {
33
+ const sections = [];
34
+ const sectionRe = /^#{2,4}\s+(.+)/gm;
35
+ let match;
36
+ const sectionStarts = [];
37
+
38
+ while ((match = sectionRe.exec(content)) !== null) {
39
+ sectionStarts.push({ title: match[1].trim(), start: match.index });
40
+ }
41
+
42
+ for (let i = 0; i < sectionStarts.length; i++) {
43
+ const { title, start } = sectionStarts[i];
44
+ const end = i + 1 < sectionStarts.length ? sectionStarts[i + 1].start : content.length;
45
+ const body = content.slice(start, end);
46
+ const bulletCount = (body.match(/^[-*]\s/gm) || []).length;
47
+ if (bulletCount > LARGE_SECTION_BULLETS) {
48
+ sections.push({
49
+ file: fileName,
50
+ section: title,
51
+ bulletCount,
52
+ sizeBytes: body.length,
53
+ tokens: estimateTokens(body)
54
+ });
55
+ }
56
+ }
57
+ return sections;
58
+ }
59
+
60
+ async function runContextTrim({ args, options = {}, logger }) {
61
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
62
+ const contextDir = path.join(targetDir, '.aioson', 'context');
63
+ const archiveDir = path.join(contextDir, 'archive');
64
+ const dryRun = options['dry-run'] || options.dryRun || false;
65
+ const force = options.force || false;
66
+
67
+ let entries;
68
+ try {
69
+ entries = await fs.readdir(contextDir);
70
+ } catch {
71
+ if (!options.json) logger.log('No .aioson/context/ directory found.');
72
+ return { ok: false, reason: 'no_context_dir' };
73
+ }
74
+
75
+ const mdFiles = entries.filter((f) => f.endsWith('.md') && f !== 'archive');
76
+ const doneFeatures = await loadFeatureStatuses(contextDir);
77
+
78
+ const staleSpecs = [];
79
+ const largeSections = [];
80
+
81
+ for (const file of mdFiles) {
82
+ // Identify stale specs
83
+ if (file.startsWith('spec-')) {
84
+ const slug = file.replace(/^spec-/, '').replace(/\.md$/, '');
85
+ if (doneFeatures.has(slug)) {
86
+ const stat = await fs.stat(path.join(contextDir, file)).catch(() => null);
87
+ staleSpecs.push({
88
+ file,
89
+ slug,
90
+ sizeBytes: stat ? stat.size : 0
91
+ });
92
+ }
93
+ }
94
+
95
+ // Find large sections in active specs
96
+ if (file.startsWith('spec-') || file === 'spec.md') {
97
+ const content = await fs.readFile(path.join(contextDir, file), 'utf8').catch(() => null);
98
+ if (content) {
99
+ const found = findLargeSections(content, file);
100
+ largeSections.push(...found);
101
+ }
102
+ }
103
+ }
104
+
105
+ const totalStaleSaved = staleSpecs.reduce((s, r) => s + r.sizeBytes, 0);
106
+
107
+ if (options.json) {
108
+ return {
109
+ ok: true,
110
+ staleSpecs,
111
+ largeSections,
112
+ totalStaleSavedBytes: totalStaleSaved,
113
+ dryRun
114
+ };
115
+ }
116
+
117
+ logger.log('Context Trim Analysis');
118
+ logger.log('─'.repeat(50));
119
+
120
+ if (staleSpecs.length === 0 && largeSections.length === 0) {
121
+ logger.log('✓ No stale specs or oversized sections found.');
122
+ logger.log('');
123
+ return { ok: true, staleSpecs: [], largeSections: [], totalStaleSavedBytes: 0, dryRun };
124
+ }
125
+
126
+ if (staleSpecs.length > 0) {
127
+ logger.log(`Stale specs (feature: done):`);
128
+ for (const s of staleSpecs) {
129
+ logger.log(` ${s.file.padEnd(30)} — ${s.slug} is done (${formatBytes(s.sizeBytes)})`);
130
+ }
131
+ if (totalStaleSaved > 0) {
132
+ logger.log(` → Archiving saves ~${formatBytes(totalStaleSaved)} (~${estimateTokens(totalStaleSaved * CHARS_PER_TOKEN)} tokens) per session`);
133
+ }
134
+ logger.log('');
135
+ }
136
+
137
+ if (largeSections.length > 0) {
138
+ logger.log('Large sections in active specs:');
139
+ for (const s of largeSections) {
140
+ logger.log(` ${s.file} § ${s.section} (${s.bulletCount} entries, ${formatBytes(s.sizeBytes)})`);
141
+ logger.log(` → Consider trimming to last ${LARGE_SECTION_BULLETS} entries to save ~${formatBytes(s.sizeBytes - Math.floor(s.sizeBytes * LARGE_SECTION_BULLETS / s.bulletCount))}`);
142
+ }
143
+ logger.log('');
144
+ }
145
+
146
+ // Archive stale specs
147
+ if (staleSpecs.length > 0) {
148
+ if (force || dryRun) {
149
+ if (!dryRun) {
150
+ await fs.mkdir(archiveDir, { recursive: true });
151
+ for (const s of staleSpecs) {
152
+ const src = path.join(contextDir, s.file);
153
+ const dest = path.join(archiveDir, s.file);
154
+ await fs.rename(src, dest);
155
+ logger.log(` Archived: ${s.file} → context/archive/${s.file}`);
156
+ }
157
+ logger.log('');
158
+ logger.log(`${staleSpecs.length} spec(s) archived to .aioson/context/archive/`);
159
+ } else {
160
+ logger.log(`[dry-run] Would archive ${staleSpecs.length} stale spec(s) to .aioson/context/archive/`);
161
+ }
162
+ } else {
163
+ logger.log(`Run with --force to archive stale specs, or --dry-run to preview.`);
164
+ }
165
+ }
166
+
167
+ return {
168
+ ok: true,
169
+ staleSpecs,
170
+ largeSections,
171
+ totalStaleSavedBytes: totalStaleSaved,
172
+ archived: force && !dryRun ? staleSpecs.length : 0,
173
+ dryRun
174
+ };
175
+ }
176
+
177
+ module.exports = { runContextTrim };
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * aioson detect:test-runner — detect test runner from project files.
5
+ *
6
+ * Checks for phpunit.xml, jest.config.*, vitest.config.*, pytest.ini, .rspec,
7
+ * foundry.toml and package.json scripts. Returns runner name and run command.
8
+ *
9
+ * Usage:
10
+ * aioson detect:test-runner .
11
+ * aioson detect:test-runner . --json
12
+ */
13
+
14
+ const path = require('node:path');
15
+ const { detectTestRunner } = require('../preflight-engine');
16
+
17
+ const BAR = '━'.repeat(25);
18
+
19
+ async function runDetectTestRunner({ args, options = {}, logger }) {
20
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
21
+
22
+ const runner = await detectTestRunner(targetDir);
23
+
24
+ if (!runner) {
25
+ const result = { ok: true, detected: false, runner: null, command: null };
26
+ if (options.json) return result;
27
+ logger.log('');
28
+ logger.log('Test Runner Detection');
29
+ logger.log(BAR);
30
+ logger.log('No test runner detected.');
31
+ logger.log('');
32
+ return result;
33
+ }
34
+
35
+ const result = {
36
+ ok: true,
37
+ detected: true,
38
+ runner: runner.name,
39
+ command: runner.command,
40
+ config_file: runner.configFile
41
+ };
42
+
43
+ if (options.json) return result;
44
+
45
+ logger.log('');
46
+ logger.log('Test Runner Detection');
47
+ logger.log(BAR);
48
+ logger.log(`Found: ${runner.configFile} → ${runner.name}`);
49
+ logger.log(`Command: ${runner.command}`);
50
+ logger.log('');
51
+
52
+ return result;
53
+ }
54
+
55
+ module.exports = { runDetectTestRunner };
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const { runLearningExport } = require('./learning-export');
4
+
5
+ /**
6
+ * aioson devlog:export-brains [targetDir] [--min-frequency=N] [--json]
7
+ *
8
+ * Exports high-frequency project_learnings to .aioson/brains/ as Zettelkasten nodes.
9
+ * This is a focused wrapper over learning:export, intended to be run after devlog:process.
10
+ */
11
+ async function runDevlogExportBrains({ args, options = {}, logger }) {
12
+ // Default min-frequency to 2 for devlog pipeline (lower than the general export default of 1)
13
+ const minFrequency = options['min-frequency'] != null ? options['min-frequency'] : options.minFrequency ?? 2;
14
+ const result = await runLearningExport({
15
+ args,
16
+ options: { ...options, 'min-frequency': minFrequency },
17
+ logger
18
+ });
19
+
20
+ if (result.ok && !options.json && result.exported > 0) {
21
+ logger.log('Run: aioson learning:evolve to promote high-frequency nodes to genome.');
22
+ }
23
+
24
+ return result;
25
+ }
26
+
27
+ module.exports = { runDevlogExportBrains };
@@ -0,0 +1,292 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { openRuntimeDb, startTask, startRun, updateRun, updateTask, appendRunEvent, attachArtifact } = require('../runtime-store');
6
+
7
+ function nowIso() {
8
+ return new Date().toISOString();
9
+ }
10
+
11
+ function createLearningId() {
12
+ return `learning-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
13
+ }
14
+
15
+ function parseFrontmatter(content) {
16
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
17
+ if (!match) return null;
18
+ const result = {};
19
+ for (const line of match[1].split(/\r?\n/)) {
20
+ const colonIdx = line.indexOf(':');
21
+ if (colonIdx === -1) continue;
22
+ const key = line.slice(0, colonIdx).trim();
23
+ const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
24
+ if (key) result[key] = value === 'null' ? null : value;
25
+ }
26
+ return result;
27
+ }
28
+
29
+ function extractSection(content, sectionName) {
30
+ const re = new RegExp(`^#{1,4}\\s+${sectionName}[\\s\\S]*?(?=^#{1,4}\\s|\\Z)`, 'im');
31
+ const match = content.match(re);
32
+ if (!match) return '';
33
+ return match[0].replace(/^#{1,4}\s+\S[^\n]*\n/, '').trim();
34
+ }
35
+
36
+ function extractListItems(content, sectionName) {
37
+ const section = extractSection(content, sectionName);
38
+ const items = [];
39
+ for (const line of section.split(/\r?\n/)) {
40
+ const trimmed = line.replace(/^[-*]\s*/, '').trim();
41
+ if (trimmed && trimmed.length > 2) items.push(trimmed);
42
+ }
43
+ return items;
44
+ }
45
+
46
+ function extractTaggedLearnings(content) {
47
+ const section = extractSection(content, 'Learnings');
48
+ const learnings = [];
49
+ for (const line of section.split(/\r?\n/)) {
50
+ const trimmed = line.replace(/^[-*]\s*/, '').trim();
51
+ if (!trimmed) continue;
52
+ const typeMatch = trimmed.match(/^\[(process|domain|quality|preference)\]\s+(.+)/i);
53
+ if (typeMatch) {
54
+ learnings.push({ type: typeMatch[1].toLowerCase(), title: typeMatch[2].trim() });
55
+ } else if (trimmed.length > 5) {
56
+ learnings.push({ type: 'process', title: trimmed });
57
+ }
58
+ }
59
+ return learnings;
60
+ }
61
+
62
+ function extractSummary(content) {
63
+ const section = extractSection(content, 'Summary');
64
+ if (section) return section.split(/\r?\n/)[0].trim();
65
+ // Fallback: first non-empty line of body after frontmatter
66
+ const body = content.replace(/^---[\s\S]*?---\r?\n/, '');
67
+ const firstHeading = body.match(/^#\s+(.+)/m);
68
+ return firstHeading ? firstHeading[1].trim() : null;
69
+ }
70
+
71
+ function upsertProjectLearning(db, { title, type, featureSlug, evidence, sourceSession }) {
72
+ const existing = db.prepare(
73
+ 'SELECT learning_id, frequency FROM project_learnings WHERE title = ? AND (feature_slug = ? OR (feature_slug IS NULL AND ? IS NULL))'
74
+ ).get(title, featureSlug || null, featureSlug || null);
75
+
76
+ if (existing) {
77
+ db.prepare(
78
+ 'UPDATE project_learnings SET frequency = ?, last_reinforced = ?, updated_at = ? WHERE learning_id = ?'
79
+ ).run(existing.frequency + 1, nowIso(), nowIso(), existing.learning_id);
80
+ return { action: 'updated', learningId: existing.learning_id };
81
+ }
82
+
83
+ const learningId = createLearningId();
84
+ db.prepare(`
85
+ INSERT INTO project_learnings
86
+ (learning_id, feature_slug, type, title, confidence, frequency, last_reinforced,
87
+ applies_to, source_session, evidence, status, created_at, updated_at)
88
+ VALUES (?, ?, ?, ?, 'medium', 1, ?, 'project', ?, ?, 'active', ?, ?)
89
+ `).run(learningId, featureSlug || null, type, title, nowIso(), sourceSession || null, evidence || null, nowIso(), nowIso());
90
+ return { action: 'inserted', learningId };
91
+ }
92
+
93
+ async function markAsProcessed(filePath, processedAt) {
94
+ const content = await fs.readFile(filePath, 'utf8');
95
+ const ts = processedAt || nowIso();
96
+
97
+ // If file already has processed_at, skip
98
+ if (/^processed_at:/m.test(content)) return;
99
+
100
+ // Inject processed_at into frontmatter
101
+ const updated = content.replace(/^(---\r?\n[\s\S]*?)(---)/m, `$1processed_at: ${ts}\n$2`);
102
+ await fs.writeFile(filePath, updated, 'utf8');
103
+ }
104
+
105
+ async function processDevlogFile(db, filePath) {
106
+ const content = await fs.readFile(filePath, 'utf8');
107
+ const fm = parseFrontmatter(content);
108
+
109
+ if (!fm || !fm.agent) {
110
+ return { status: 'malformed', file: path.basename(filePath), reason: 'missing frontmatter or agent field' };
111
+ }
112
+
113
+ // Skip already-processed devlogs
114
+ if (fm.processed_at) {
115
+ return { status: 'skipped', file: path.basename(filePath), reason: 'already processed' };
116
+ }
117
+
118
+ const body = content.replace(/^---[\s\S]*?---\r?\n?/, '');
119
+ const summary = extractSummary(body) || `@${fm.agent} devlog`;
120
+ const featureSlug = fm.feature && fm.feature !== 'project' ? fm.feature : null;
121
+ const sessionKey = fm.session_key || null;
122
+ const startedAt = fm.started_at || fm.session_start || nowIso();
123
+ const finishedAt = fm.finished_at || fm.session_end || nowIso();
124
+ const status = fm.status === 'partial' ? 'running' : 'completed';
125
+ const verdict = fm.verdict ? String(fm.verdict).trim().toUpperCase() : null;
126
+ const planStepId = fm.plan_step || null;
127
+
128
+ // Create task + run
129
+ const taskKey = startTask(db, {
130
+ title: `devlog: ${summary}`,
131
+ squadSlug: null,
132
+ sessionKey: sessionKey || undefined,
133
+ status,
134
+ createdBy: fm.agent
135
+ });
136
+
137
+ const runKey = startRun(db, {
138
+ taskKey,
139
+ agentName: fm.agent,
140
+ agentKind: 'devlog',
141
+ squadSlug: null,
142
+ title: `@${fm.agent} devlog`,
143
+ message: summary
144
+ });
145
+
146
+ // Register artifacts
147
+ const artifactPaths = extractListItems(body, 'Artifacts');
148
+ for (const filePath_ of artifactPaths) {
149
+ // Only register file-like entries (containing a slash or dot)
150
+ if (/[/.]/.test(filePath_)) {
151
+ attachArtifact(db, { runKey, agentName: fm.agent, kind: 'output', filePath: filePath_ });
152
+ }
153
+ }
154
+
155
+ // Register decisions as execution events
156
+ const decisions = extractListItems(body, 'Decisions');
157
+ for (const decision of decisions) {
158
+ appendRunEvent(db, {
159
+ runKey,
160
+ eventType: 'decision',
161
+ phase: 'devlog',
162
+ status: 'completed',
163
+ message: decision,
164
+ createdAt: finishedAt
165
+ });
166
+ }
167
+
168
+ // Upsert learnings
169
+ const learnings = extractTaggedLearnings(body);
170
+ for (const { type, title } of learnings) {
171
+ upsertProjectLearning(db, { title, type, featureSlug, sourceSession: sessionKey || path.basename(filePath) });
172
+ }
173
+
174
+ // Log verdict if present
175
+ if (verdict && verdict !== 'NULL') {
176
+ appendRunEvent(db, {
177
+ runKey,
178
+ eventType: 'qa_verdict',
179
+ phase: 'devlog',
180
+ status: 'completed',
181
+ message: `QA VERDICT: ${verdict}`,
182
+ verdict,
183
+ planStepId,
184
+ createdAt: finishedAt
185
+ });
186
+ }
187
+
188
+ // Close run
189
+ updateRun(db, runKey, {
190
+ status,
191
+ summary,
192
+ finishedAt
193
+ });
194
+
195
+ if (status === 'completed') {
196
+ updateTask(db, taskKey, { status: 'completed', finishedAt });
197
+ }
198
+
199
+ // Mark devlog as processed
200
+ await markAsProcessed(filePath, nowIso());
201
+
202
+ return {
203
+ status: 'ok',
204
+ file: path.basename(filePath),
205
+ runKey,
206
+ taskKey,
207
+ featureSlug,
208
+ artifactsCount: artifactPaths.filter((p) => /[/.]/.test(p)).length,
209
+ decisionsCount: decisions.length,
210
+ learningsCount: learnings.length,
211
+ verdict: verdict && verdict !== 'NULL' ? verdict : null
212
+ };
213
+ }
214
+
215
+ async function runDevlogProcess({ args, options = {}, logger }) {
216
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
217
+ const logsDir = path.join(targetDir, 'aioson-logs');
218
+
219
+ let entries;
220
+ try {
221
+ entries = await fs.readdir(logsDir);
222
+ } catch {
223
+ if (!options.json) logger.log('No aioson-logs/ directory found — nothing to process.');
224
+ return { ok: true, processed: 0, skipped: 0, malformed: 0 };
225
+ }
226
+
227
+ const devlogFiles = entries
228
+ .filter((f) => f.startsWith('devlog-') && f.endsWith('.md'))
229
+ .sort();
230
+
231
+ if (devlogFiles.length === 0) {
232
+ if (!options.json) logger.log('No devlog files found.');
233
+ return { ok: true, processed: 0, skipped: 0, malformed: 0 };
234
+ }
235
+
236
+ const { db, dbPath } = await openRuntimeDb(targetDir);
237
+ const results = [];
238
+
239
+ try {
240
+ for (const file of devlogFiles) {
241
+ const result = await processDevlogFile(db, path.join(logsDir, file));
242
+ results.push(result);
243
+ }
244
+ } finally {
245
+ db.close();
246
+ }
247
+
248
+ const processed = results.filter((r) => r.status === 'ok');
249
+ const skipped = results.filter((r) => r.status === 'skipped');
250
+ const malformed = results.filter((r) => r.status === 'malformed');
251
+
252
+ const totalArtifacts = processed.reduce((s, r) => s + (r.artifactsCount || 0), 0);
253
+ const totalLearnings = processed.reduce((s, r) => s + (r.learningsCount || 0), 0);
254
+
255
+ if (options.json) {
256
+ return { ok: true, results, processed: processed.length, skipped: skipped.length, malformed: malformed.length, totalArtifacts, totalLearnings, dbPath };
257
+ }
258
+
259
+ logger.log(`Devlog Processing — ${path.basename(targetDir)}`);
260
+ logger.log('─'.repeat(50));
261
+
262
+ if (results.length === 0) {
263
+ logger.log('No devlogs to process.');
264
+ } else {
265
+ logger.log(`Found ${devlogFiles.length} devlog(s):`);
266
+ logger.log('');
267
+ for (const r of results) {
268
+ if (r.status === 'ok') {
269
+ logger.log(`${r.file}`);
270
+ logger.log(` Agent: @${r.featureSlug ? `${r.featureSlug}` : 'project'} | run: ${r.runKey}`);
271
+ if (r.artifactsCount > 0) logger.log(` Artifacts: ${r.artifactsCount} registered ✓`);
272
+ if (r.decisionsCount > 0) logger.log(` Decisions: ${r.decisionsCount} logged ✓`);
273
+ if (r.learningsCount > 0) logger.log(` Learnings: ${r.learningsCount} upserted ✓`);
274
+ if (r.verdict) logger.log(` Verdict: ${r.verdict} ✓`);
275
+ } else if (r.status === 'skipped') {
276
+ logger.log(`${r.file} — skipped (${r.reason})`);
277
+ } else {
278
+ logger.log(`${r.file} — ⚠ ${r.reason}. Fix frontmatter and re-run.`);
279
+ }
280
+ }
281
+ }
282
+
283
+ logger.log('─'.repeat(50));
284
+ logger.log(`Processed: ${processed.length}/${devlogFiles.length} devlogs`);
285
+ if (totalLearnings > 0) logger.log(`New learnings: ${totalLearnings} (queued for brains export)`);
286
+ if (totalArtifacts > 0) logger.log(`Artifacts registered: ${totalArtifacts}`);
287
+ if (malformed.length > 0) logger.log(`Malformed (skipped): ${malformed.length}`);
288
+
289
+ return { ok: true, results, processed: processed.length, skipped: skipped.length, malformed: malformed.length, totalArtifacts, totalLearnings, dbPath };
290
+ }
291
+
292
+ module.exports = { runDevlogProcess, processDevlogFile };
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const fsSync = require('node:fs');
5
+ const path = require('node:path');
6
+ const { openRuntimeDb } = require('../runtime-store');
7
+ const { processDevlogFile } = require('./devlog-process');
8
+
9
+ const POLL_INTERVAL_MS = 5000;
10
+ const WSL_VERSION_PATH = '/proc/version';
11
+
12
+ async function isWsl2() {
13
+ try {
14
+ const version = await fs.readFile(WSL_VERSION_PATH, 'utf8');
15
+ return version.toLowerCase().includes('microsoft');
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ async function processNewDevlog(targetDir, filePath, logger) {
22
+ const { db, dbPath } = await openRuntimeDb(targetDir).catch(() => ({ db: null }));
23
+ if (!db) {
24
+ logger.log(`[DEVLOG WATCHER] No database available — skipping ${path.basename(filePath)}`);
25
+ return;
26
+ }
27
+ try {
28
+ const result = await processDevlogFile(db, filePath);
29
+ if (result.status === 'ok') {
30
+ const parts = [];
31
+ if (result.learningsCount > 0) parts.push(`${result.learningsCount} learnings`);
32
+ if (result.artifactsCount > 0) parts.push(`${result.artifactsCount} artifacts`);
33
+ if (result.verdict) parts.push(`VERDICT: ${result.verdict}`);
34
+ const detail = parts.length > 0 ? ` → ${parts.join(', ')} → SQLite ✓` : ' → SQLite ✓';
35
+ logger.log(`[${new Date().toISOString().slice(11, 19)}] Processed: ${result.file}${detail}`);
36
+ } else if (result.status === 'skipped') {
37
+ // silently skip already-processed files
38
+ } else {
39
+ logger.log(`[${new Date().toISOString().slice(11, 19)}] ⚠ ${result.file}: ${result.reason}`);
40
+ }
41
+ } finally {
42
+ db.close();
43
+ }
44
+ }
45
+
46
+ async function watchWithFsWatch(logsDir, targetDir, logger) {
47
+ logger.log(`[DEVLOG WATCHER] Using fs.watch on ${logsDir}`);
48
+
49
+ return new Promise((resolve) => {
50
+ const watcher = fsSync.watch(logsDir, { persistent: true }, async (eventType, filename) => {
51
+ if (!filename || !filename.startsWith('devlog-') || !filename.endsWith('.md')) return;
52
+ if (eventType !== 'rename' && eventType !== 'change') return;
53
+
54
+ const filePath = path.join(logsDir, filename);
55
+ // Small delay to ensure file is fully written
56
+ setTimeout(async () => {
57
+ try {
58
+ await fs.access(filePath);
59
+ logger.log(`[${new Date().toISOString().slice(11, 19)}] New: ${filename} → processing...`);
60
+ await processNewDevlog(targetDir, filePath, logger);
61
+ } catch { /* file may have been removed */ }
62
+ }, 200);
63
+ });
64
+
65
+ process.on('SIGINT', () => { watcher.close(); resolve(); });
66
+ process.on('SIGTERM', () => { watcher.close(); resolve(); });
67
+ });
68
+ }
69
+
70
+ async function watchWithPolling(logsDir, targetDir, logger) {
71
+ logger.log(`[DEVLOG WATCHER] WSL2 detected — using polling (${POLL_INTERVAL_MS / 1000}s interval)`);
72
+
73
+ const seen = new Set();
74
+
75
+ // Seed with already-existing files so we don't reprocess them
76
+ try {
77
+ const entries = await fs.readdir(logsDir);
78
+ for (const f of entries) {
79
+ if (f.startsWith('devlog-') && f.endsWith('.md')) seen.add(f);
80
+ }
81
+ } catch { /* logsDir not yet created */ }
82
+
83
+ return new Promise((resolve) => {
84
+ const timer = setInterval(async () => {
85
+ try {
86
+ const entries = await fs.readdir(logsDir);
87
+ const devlogs = entries.filter((f) => f.startsWith('devlog-') && f.endsWith('.md'));
88
+ for (const filename of devlogs) {
89
+ if (!seen.has(filename)) {
90
+ seen.add(filename);
91
+ const filePath = path.join(logsDir, filename);
92
+ logger.log(`[${new Date().toISOString().slice(11, 19)}] New: ${filename} → processing...`);
93
+ await processNewDevlog(targetDir, filePath, logger);
94
+ }
95
+ }
96
+ } catch { /* directory may not exist yet */ }
97
+ }, POLL_INTERVAL_MS);
98
+
99
+ process.on('SIGINT', () => { clearInterval(timer); resolve(); });
100
+ process.on('SIGTERM', () => { clearInterval(timer); resolve(); });
101
+ });
102
+ }
103
+
104
+ async function runDevlogWatch({ args, options = {}, logger }) {
105
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
106
+ const logsDir = path.join(targetDir, 'aioson-logs');
107
+ const usePolling = options.poll || await isWsl2();
108
+
109
+ // Ensure the directory exists
110
+ await fs.mkdir(logsDir, { recursive: true });
111
+
112
+ logger.log(`[DEVLOG WATCHER] Watching ${logsDir} for new devlogs...`);
113
+ logger.log('[DEVLOG WATCHER] Press Ctrl+C to stop.');
114
+
115
+ if (usePolling) {
116
+ await watchWithPolling(logsDir, targetDir, logger);
117
+ } else {
118
+ try {
119
+ await watchWithFsWatch(logsDir, targetDir, logger);
120
+ } catch (err) {
121
+ // Fall back to polling if fs.watch fails (can happen in some environments)
122
+ logger.log(`[DEVLOG WATCHER] fs.watch failed (${err.message}) — falling back to polling`);
123
+ await watchWithPolling(logsDir, targetDir, logger);
124
+ }
125
+ }
126
+
127
+ logger.log('[DEVLOG WATCHER] Stopped.');
128
+ return { ok: true };
129
+ }
130
+
131
+ module.exports = { runDevlogWatch };