@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,213 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Automatic Learning Extraction — Phase 5.1
5
+ *
6
+ * Reads bus messages and task results after a squad:autorun session and
7
+ * extracts structured learnings using heuristics (no LLM by default).
8
+ *
9
+ * Detected patterns:
10
+ * block + resolution → type: 'process' (error-pattern)
11
+ * verdict NEEDS_ITERATION + retry success → type: 'process' (correction)
12
+ * must_haves artifact failures → type: 'quality' (missing-dependency)
13
+ * tasks in wave > 1 blocking others → type: 'process' (dependency-pattern)
14
+ *
15
+ * With --llm-extract: uses model 'fast' for richer extraction (opt-in).
16
+ */
17
+
18
+ const { randomUUID } = require('node:crypto');
19
+ const fs = require('node:fs/promises');
20
+ const path = require('node:path');
21
+ const { openRuntimeDb, insertSquadLearning } = require('../runtime-store');
22
+
23
+ function nowIso() { return new Date().toISOString(); }
24
+
25
+ /**
26
+ * Extract learnings from a completed squad:autorun session.
27
+ *
28
+ * @param {string} projectDir
29
+ * @param {string} squadSlug
30
+ * @param {string} sessionId
31
+ * @param {{ busMessages: object[], taskResults: object[], reflectionReports: object[] }} data
32
+ * @returns {Promise<object[]>} Array of extracted learning objects
33
+ */
34
+ async function extractLearnings(projectDir, squadSlug, sessionId, data) {
35
+ const { busMessages = [], taskResults = [], reflectionReports = [] } = data;
36
+ const extracted = [];
37
+
38
+ // ── Pattern 1: Block + Resolution pairs → error-pattern ──────────────────
39
+ const blocks = busMessages.filter((m) => m.type === 'block');
40
+ const resolutions = busMessages.filter((m) => m.type === 'resolution');
41
+
42
+ for (const block of blocks) {
43
+ const hasResolution = resolutions.some(
44
+ (r) => r.metadata?.block_id === block.id || r.to === block.from
45
+ );
46
+ if (hasResolution) {
47
+ const title = `Executor "${block.from}" blocked on: ${String(block.content || '').slice(0, 80)}`;
48
+ extracted.push({
49
+ type: 'process',
50
+ title,
51
+ signal: 'implicit',
52
+ confidence: 'medium',
53
+ evidence: `Session ${sessionId} — block resolved via coordinator`,
54
+ source_session: sessionId
55
+ });
56
+ }
57
+ }
58
+
59
+ // ── Pattern 2: NEEDS_ITERATION + successful gap_closure → correction ──────
60
+ const gapAttempts = busMessages.filter((m) => m.type === 'gap_closure_attempt');
61
+ const resultMessages = busMessages.filter((m) => m.type === 'result');
62
+
63
+ for (const attempt of gapAttempts) {
64
+ const taskId = attempt.metadata?.task_id;
65
+ if (!taskId) continue;
66
+
67
+ const laterResult = resultMessages.find(
68
+ (r) => r.metadata?.task_id === taskId && r.ts > attempt.ts && r.metadata?.status === 'completed'
69
+ );
70
+ if (laterResult) {
71
+ const prevError = String(attempt.metadata?.prev_error || '').slice(0, 100);
72
+ extracted.push({
73
+ type: 'process',
74
+ title: `Task "${taskId}" succeeded after correction — initial failure: ${prevError}`,
75
+ signal: 'implicit',
76
+ confidence: 'high',
77
+ evidence: `Gap closure retry succeeded in session ${sessionId}`,
78
+ source_session: sessionId
79
+ });
80
+ }
81
+ }
82
+
83
+ // ── Pattern 3: must_haves artifact failures → quality ────────────────────
84
+ for (const result of taskResults) {
85
+ const mustHaves = result.task?.must_haves;
86
+ if (!mustHaves || result.finalStatus === 'completed') continue;
87
+
88
+ const artifacts = mustHaves.artifacts || [];
89
+ if (artifacts.length > 0 && result.finalStatus !== 'completed') {
90
+ extracted.push({
91
+ type: 'quality',
92
+ title: `Task "${result.task.id}" failed must_haves artifacts: ${artifacts.slice(0, 2).join(', ')}`,
93
+ signal: 'implicit',
94
+ confidence: 'medium',
95
+ evidence: `must_haves check failed in session ${sessionId}`,
96
+ source_session: sessionId
97
+ });
98
+ }
99
+ }
100
+
101
+ // ── Pattern 4: Escalated tasks after gap closure exhaustion → process ─────
102
+ const escalated = taskResults.filter(
103
+ (r) => r.finalStatus === 'escalated' && r.workerResult?.gap_closure_exhausted
104
+ );
105
+ for (const r of escalated) {
106
+ const lastErr = String(r.workerResult?.error || '').slice(0, 100);
107
+ extracted.push({
108
+ type: 'process',
109
+ title: `Task "${r.task.id}" cannot complete autonomously — requires human: ${lastErr}`,
110
+ signal: 'implicit',
111
+ confidence: 'high',
112
+ evidence: `Escalated after max gap closure retries in session ${sessionId}`,
113
+ source_session: sessionId
114
+ });
115
+ }
116
+
117
+ if (extracted.length === 0) return [];
118
+
119
+ // ── Persist to SQLite ──────────────────────────────────────────────────────
120
+ const handle = await openRuntimeDb(projectDir);
121
+ if (!handle) return extracted;
122
+ const { db } = handle;
123
+
124
+ try {
125
+ for (const learning of extracted) {
126
+ insertSquadLearning(db, {
127
+ squadSlug,
128
+ type: learning.type,
129
+ title: learning.title,
130
+ signal: learning.signal,
131
+ confidence: learning.confidence,
132
+ evidence: learning.evidence,
133
+ sourceSession: learning.source_session,
134
+ appliesTo: 'squad'
135
+ });
136
+ }
137
+ } finally {
138
+ db.close();
139
+ }
140
+
141
+ return extracted;
142
+ }
143
+
144
+ // ─── Per-Agent Persistent Memory (Plan 81 §Sprint 4) ────────────────────────
145
+
146
+ const AGENT_MEMORY_DIR = 'agent-memory';
147
+
148
+ /**
149
+ * Persist learnings to per-agent memory files.
150
+ *
151
+ * After extracting learnings from a session, writes relevant learnings
152
+ * to `.aioson/squads/{slug}/agent-memory/{executor}.md` so they can be
153
+ * loaded by worker-runner at spawn time.
154
+ *
155
+ * @param {string} projectDir
156
+ * @param {string} squadSlug
157
+ * @param {object[]} learnings — extracted learning objects
158
+ * @param {object[]} taskResults — task results with executor info
159
+ */
160
+ async function persistAgentMemory(projectDir, squadSlug, learnings, taskResults = []) {
161
+ if (learnings.length === 0) return;
162
+
163
+ const memoryDir = path.join(
164
+ projectDir, '.aioson', 'squads', squadSlug, AGENT_MEMORY_DIR
165
+ );
166
+ await fs.mkdir(memoryDir, { recursive: true });
167
+
168
+ // Group learnings by executor
169
+ const byExecutor = {};
170
+ for (const learning of learnings) {
171
+ // Match learning to executor via task results evidence
172
+ const executors = new Set();
173
+ for (const r of taskResults) {
174
+ if (!r.task?.executor) continue;
175
+ // Match by task id in the learning title or evidence
176
+ const taskId = r.task.id || '';
177
+ if (learning.title.includes(taskId) || learning.title.includes(r.task.executor)) {
178
+ executors.add(r.task.executor);
179
+ }
180
+ }
181
+ // If no specific executor matched, attribute to all executors in this session
182
+ if (executors.size === 0) {
183
+ for (const r of taskResults) {
184
+ if (r.task?.executor) executors.add(r.task.executor);
185
+ }
186
+ }
187
+ for (const exec of executors) {
188
+ if (!byExecutor[exec]) byExecutor[exec] = [];
189
+ byExecutor[exec].push(learning);
190
+ }
191
+ }
192
+
193
+ // Append to each executor's memory file
194
+ for (const [executor, execLearnings] of Object.entries(byExecutor)) {
195
+ const memPath = path.join(memoryDir, `${executor}.md`);
196
+ let existing = '';
197
+ try { existing = await fs.readFile(memPath, 'utf8'); } catch { /* new file */ }
198
+
199
+ const newEntries = execLearnings.map((l) =>
200
+ `- [${l.type}] ${l.title} (confidence: ${l.confidence})`
201
+ ).join('\n');
202
+
203
+ const header = existing ? '' : `# Agent Memory: ${executor}\n\n`;
204
+ const separator = existing ? `\n\n## Session ${nowIso().slice(0, 10)}\n\n` : '## Learnings\n\n';
205
+ const content = existing
206
+ ? existing.trimEnd() + separator + newEntries + '\n'
207
+ : header + separator + newEntries + '\n';
208
+
209
+ await fs.writeFile(memPath, content, 'utf8');
210
+ }
211
+ }
212
+
213
+ module.exports = { extractLearnings, persistAgentMemory };
@@ -0,0 +1,365 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Pattern Detector — Plan 80, Script 4
5
+ *
6
+ * Detects automation candidates from squad learnings, bus messages,
7
+ * STATE.md decisions, and devlogs. Uses heuristics only (no LLM).
8
+ *
9
+ * Heuristics:
10
+ * - Same learning type repeated ≥ N times → candidate for script
11
+ * - Block→resolution sequence identical ≥ 2x → candidate for automation
12
+ * - must_haves failing on same artifact ≥ 2x → candidate for pre-check
13
+ * - Wave dependency systematic between same executors → candidate for merge
14
+ *
15
+ * Can be used as stage 0 of learning:evolve pipeline.
16
+ */
17
+
18
+ const fs = require('node:fs/promises');
19
+ const path = require('node:path');
20
+
21
+ const SQUADS_DIR = path.join('.aioson', 'squads');
22
+
23
+ // ─── Data loaders ────────────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * Load learnings from SQLite for a given squad.
27
+ */
28
+ async function loadLearningsFromDb(projectDir, squadSlug, minOccurrences) {
29
+ let openRuntimeDb;
30
+ try {
31
+ ({ openRuntimeDb } = require('../runtime-store'));
32
+ } catch {
33
+ return [];
34
+ }
35
+
36
+ const handle = await openRuntimeDb(projectDir, { mustExist: true });
37
+ if (!handle) return [];
38
+ const { db } = handle;
39
+
40
+ try {
41
+ const rows = db.prepare(
42
+ 'SELECT * FROM squad_learnings WHERE squad_slug = ? AND frequency >= ? ORDER BY frequency DESC, created_at DESC'
43
+ ).all(squadSlug, minOccurrences);
44
+ return rows;
45
+ } catch {
46
+ return [];
47
+ } finally {
48
+ db.close();
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Load evolution log from SQLite.
54
+ */
55
+ async function loadEvolutionLog(projectDir, squadSlug) {
56
+ let openRuntimeDb;
57
+ try {
58
+ ({ openRuntimeDb } = require('../runtime-store'));
59
+ } catch {
60
+ return [];
61
+ }
62
+
63
+ const handle = await openRuntimeDb(projectDir, { mustExist: true });
64
+ if (!handle) return [];
65
+ const { db } = handle;
66
+
67
+ try {
68
+ const rows = db.prepare(
69
+ 'SELECT * FROM evolution_log WHERE squad_slug = ? ORDER BY applied_at DESC LIMIT 50'
70
+ ).all(squadSlug);
71
+ return rows;
72
+ } catch {
73
+ return [];
74
+ } finally {
75
+ db.close();
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Load STATE.md decisions.
81
+ */
82
+ async function loadStateDecisions(projectDir, squadSlug) {
83
+ const statePath = path.join(projectDir, SQUADS_DIR, squadSlug, 'STATE.md');
84
+ try {
85
+ const content = await fs.readFile(statePath, 'utf8');
86
+ const match = content.match(/## Decisions Made\n([\s\S]*?)(?=\n## |$)/);
87
+ if (!match) return [];
88
+ return match[1]
89
+ .split('\n')
90
+ .filter((l) => l.startsWith('- '))
91
+ .map((l) => l.slice(2).trim());
92
+ } catch {
93
+ return [];
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Load learnings index (manual).
99
+ */
100
+ async function loadManualLearnings(projectDir, squadSlug) {
101
+ const indexPath = path.join(projectDir, SQUADS_DIR, squadSlug, 'learnings', 'index.md');
102
+ try {
103
+ const content = await fs.readFile(indexPath, 'utf8');
104
+ return content
105
+ .split('\n')
106
+ .filter((l) => l.startsWith('- '))
107
+ .map((l) => l.slice(2).trim());
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Load devlog entries.
115
+ */
116
+ async function loadDevlogs(projectDir, squadSlug) {
117
+ const logsDir = path.join(projectDir, 'aioson-logs', squadSlug);
118
+ const entries = [];
119
+
120
+ try {
121
+ const files = await fs.readdir(logsDir);
122
+ const devlogs = files.filter((f) => f.startsWith('devlog-') && f.endsWith('.md')).sort().slice(-10);
123
+
124
+ for (const f of devlogs) {
125
+ const content = await fs.readFile(path.join(logsDir, f), 'utf8');
126
+ entries.push({ file: f, content });
127
+ }
128
+ } catch { /* no logs dir */ }
129
+
130
+ return entries;
131
+ }
132
+
133
+ // ─── Pattern heuristics ──────────────────────────────────────────────────────
134
+
135
+ /**
136
+ * Detect repeated learnings of the same type → candidate for script.
137
+ */
138
+ function detectRepeatedLearnings(learnings, minOccurrences) {
139
+ const candidates = [];
140
+
141
+ // Group by normalized title similarity
142
+ const groups = {};
143
+ for (const l of learnings) {
144
+ // Normalize title to group similar learnings
145
+ const key = l.type + ':' + normalizeTitle(l.title);
146
+ if (!groups[key]) groups[key] = [];
147
+ groups[key].push(l);
148
+ }
149
+
150
+ for (const [key, group] of Object.entries(groups)) {
151
+ if (group.length >= minOccurrences || group.some((l) => l.frequency >= minOccurrences)) {
152
+ const maxFreq = Math.max(...group.map((l) => l.frequency || 1));
153
+ const latest = group.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''))[0];
154
+ candidates.push({
155
+ priority: maxFreq >= 5 ? 'HIGH' : 'MEDIUM',
156
+ name: `repeated-${latest.type}-learning`,
157
+ pattern: `Same ${latest.type} learning repeated ${maxFreq}x: "${latest.title.slice(0, 80)}"`,
158
+ seen: group.length,
159
+ lastDate: latest.updated_at || latest.created_at,
160
+ automatable: 'yes',
161
+ proposed: `Create a pre-check script or rule that prevents this ${latest.type} issue`
162
+ });
163
+ }
164
+ }
165
+
166
+ return candidates;
167
+ }
168
+
169
+ /**
170
+ * Detect block→resolution sequences that repeat → candidate for automation.
171
+ */
172
+ function detectBlockResolutionPatterns(learnings) {
173
+ const candidates = [];
174
+
175
+ // Look for process learnings that mention "blocked" or "resolved"
176
+ const blockLearnings = learnings.filter(
177
+ (l) => l.type === 'process' && /block|resolv|retry|closure/i.test(l.title)
178
+ );
179
+
180
+ // Group by similarity
181
+ const groups = {};
182
+ for (const l of blockLearnings) {
183
+ const key = normalizeTitle(l.title);
184
+ if (!groups[key]) groups[key] = [];
185
+ groups[key].push(l);
186
+ }
187
+
188
+ for (const [, group] of Object.entries(groups)) {
189
+ if (group.length >= 2 || group.some((l) => l.frequency >= 2)) {
190
+ const latest = group[0];
191
+ candidates.push({
192
+ priority: 'HIGH',
193
+ name: 'block-resolution-automation',
194
+ pattern: `Block→resolution sequence repeated: "${latest.title.slice(0, 80)}"`,
195
+ seen: group.length,
196
+ lastDate: latest.updated_at || latest.created_at,
197
+ automatable: 'partial',
198
+ proposed: 'Create an automatic resolver or pre-validation to prevent this block'
199
+ });
200
+ }
201
+ }
202
+
203
+ return candidates;
204
+ }
205
+
206
+ /**
207
+ * Detect must_haves failures on same artifact → candidate for pre-check.
208
+ */
209
+ function detectMustHavesPatterns(learnings) {
210
+ const candidates = [];
211
+
212
+ const qualityLearnings = learnings.filter(
213
+ (l) => l.type === 'quality' && /must_haves|artifact/i.test(l.title)
214
+ );
215
+
216
+ const groups = {};
217
+ for (const l of qualityLearnings) {
218
+ const key = normalizeTitle(l.title);
219
+ if (!groups[key]) groups[key] = [];
220
+ groups[key].push(l);
221
+ }
222
+
223
+ for (const [, group] of Object.entries(groups)) {
224
+ if (group.length >= 2 || group.some((l) => l.frequency >= 2)) {
225
+ const latest = group[0];
226
+ candidates.push({
227
+ priority: 'MEDIUM',
228
+ name: 'must-haves-precheck',
229
+ pattern: `must_haves failing on same artifact: "${latest.title.slice(0, 80)}"`,
230
+ seen: group.length,
231
+ lastDate: latest.updated_at || latest.created_at,
232
+ automatable: 'yes',
233
+ proposed: 'Add artifact existence check to brief-validator or pre_run hook'
234
+ });
235
+ }
236
+ }
237
+
238
+ return candidates;
239
+ }
240
+
241
+ /**
242
+ * Detect patterns in STATE.md decisions that suggest systematic behavior.
243
+ */
244
+ function detectDecisionPatterns(decisions) {
245
+ const candidates = [];
246
+
247
+ // Look for repeated decision themes
248
+ const themes = {};
249
+ for (const d of decisions) {
250
+ // Extract theme keywords
251
+ const words = d.toLowerCase().split(/\s+/).filter((w) => w.length > 4);
252
+ for (const w of words) {
253
+ if (!themes[w]) themes[w] = 0;
254
+ themes[w]++;
255
+ }
256
+ }
257
+
258
+ // Find themes that appear in 3+ decisions
259
+ for (const [theme, count] of Object.entries(themes)) {
260
+ if (count >= 3 && !['completed', 'session', 'tasks'].includes(theme)) {
261
+ candidates.push({
262
+ priority: 'LOW',
263
+ name: `decision-theme-${theme}`,
264
+ pattern: `Theme "${theme}" appears in ${count} decisions — may indicate systematic pattern`,
265
+ seen: count,
266
+ lastDate: null,
267
+ automatable: 'partial',
268
+ proposed: 'Review decisions for extractable rule or automation'
269
+ });
270
+ }
271
+ }
272
+
273
+ return candidates;
274
+ }
275
+
276
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
277
+
278
+ function normalizeTitle(title) {
279
+ return (title || '')
280
+ .toLowerCase()
281
+ .replace(/[^a-z0-9\s]/g, '')
282
+ .replace(/\s+/g, ' ')
283
+ .trim()
284
+ .slice(0, 60);
285
+ }
286
+
287
+ // ─── Public API ──────────────────────────────────────────────────────────────
288
+
289
+ /**
290
+ * Detect automation candidates for a squad.
291
+ *
292
+ * @param {string} projectDir — Project root
293
+ * @param {string} squadSlug — Squad identifier
294
+ * @param {object} [options] — { minOccurrences }
295
+ * @returns {Promise<object>} — { candidates[], sources }
296
+ */
297
+ async function detectPatterns(projectDir, squadSlug, options = {}) {
298
+ const minOccurrences = options.minOccurrences || 3;
299
+
300
+ // Load all data sources in parallel
301
+ const [learnings, evolutionLog, decisions, manualLearnings, devlogs] = await Promise.all([
302
+ loadLearningsFromDb(projectDir, squadSlug, 1), // load all, filter later
303
+ loadEvolutionLog(projectDir, squadSlug),
304
+ loadStateDecisions(projectDir, squadSlug),
305
+ loadManualLearnings(projectDir, squadSlug),
306
+ loadDevlogs(projectDir, squadSlug)
307
+ ]);
308
+
309
+ const sources = {
310
+ db_learnings: learnings.length,
311
+ evolution_entries: evolutionLog.length,
312
+ state_decisions: decisions.length,
313
+ manual_learnings: manualLearnings.length,
314
+ devlog_files: devlogs.length
315
+ };
316
+
317
+ // Run all heuristics
318
+ const candidates = [
319
+ ...detectRepeatedLearnings(learnings, minOccurrences),
320
+ ...detectBlockResolutionPatterns(learnings),
321
+ ...detectMustHavesPatterns(learnings),
322
+ ...detectDecisionPatterns(decisions)
323
+ ];
324
+
325
+ // Sort by priority (HIGH > MEDIUM > LOW)
326
+ const priorityOrder = { HIGH: 0, MEDIUM: 1, LOW: 2 };
327
+ candidates.sort((a, b) => (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2));
328
+
329
+ return {
330
+ squad: squadSlug,
331
+ candidates,
332
+ total: candidates.length,
333
+ sources
334
+ };
335
+ }
336
+
337
+ /**
338
+ * Format detection results as a human-readable report.
339
+ */
340
+ function formatPatternReport(result) {
341
+ if (result.total === 0) {
342
+ return `No automation candidates detected for squad "${result.squad}".\nSources analyzed: ${JSON.stringify(result.sources)}`;
343
+ }
344
+
345
+ const lines = [];
346
+ lines.push(`Detected ${result.total} automation candidate${result.total > 1 ? 's' : ''} for squad "${result.squad}":`);
347
+ lines.push('');
348
+
349
+ for (let i = 0; i < result.candidates.length; i++) {
350
+ const c = result.candidates[i];
351
+ lines.push(`${i + 1}. [${c.priority}] ${c.name}`);
352
+ lines.push(` Pattern: ${c.pattern}`);
353
+ lines.push(` Seen: ${c.seen} sessions${c.lastDate ? ` — Last: ${c.lastDate}` : ''}`);
354
+ lines.push(` Automatable: ${c.automatable}`);
355
+ lines.push(` → Proposed: ${c.proposed}`);
356
+ lines.push('');
357
+ }
358
+
359
+ return lines.join('\n');
360
+ }
361
+
362
+ module.exports = {
363
+ detectPatterns,
364
+ formatPatternReport
365
+ };