@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.
- package/CHANGELOG.md +49 -0
- package/README.md +729 -232
- package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
- package/docs/integrations/sdlc-genius-boundary.md +76 -0
- package/docs/integrations/sdlc-genius-eval-matrix.md +75 -0
- package/docs/integrations/sdlc-genius-install-checklist.md +93 -0
- package/docs/integrations/sdlc-genius-review-samples.md +86 -0
- package/docs/pt/README.md +3 -0
- package/docs/pt/agentes.md +1 -0
- package/docs/pt/comandos-cli.md +888 -2
- package/docs/pt/design-hybrid-forge.md +255 -6
- package/docs/pt/devlog-pipeline.md +270 -0
- package/docs/pt/fluxo-artefatos.md +178 -0
- package/docs/pt/hooks-session-guard.md +454 -0
- package/docs/pt/monitor-de-contexto.md +59 -5
- package/docs/pt/sdd-automation-scripts.md +557 -0
- package/docs/pt/site-forge.md +309 -0
- package/docs/pt/spec-learnings-pipeline.md +265 -0
- package/package.json +1 -1
- package/src/a2a/client.js +165 -0
- package/src/a2a/server.js +223 -0
- package/src/cli.js +235 -1
- package/src/commands/agent-audit.js +397 -0
- package/src/commands/agent-export-skill.js +229 -0
- package/src/commands/artifact-validate.js +189 -0
- package/src/commands/brief-gen.js +405 -0
- package/src/commands/brief-validate.js +65 -0
- package/src/commands/classify.js +256 -0
- package/src/commands/context-compact.js +49 -0
- package/src/commands/context-health.js +175 -0
- package/src/commands/context-monitor.js +71 -0
- package/src/commands/context-trim.js +177 -0
- package/src/commands/detect-test-runner.js +55 -0
- package/src/commands/devlog-export-brains.js +27 -0
- package/src/commands/devlog-process.js +292 -0
- package/src/commands/devlog-watch.js +131 -0
- package/src/commands/feature-close.js +165 -0
- package/src/commands/gate-check.js +228 -0
- package/src/commands/hooks-emit.js +253 -0
- package/src/commands/hooks-install.js +347 -0
- package/src/commands/learning-auto-promote.js +195 -0
- package/src/commands/learning-evolve.js +18 -9
- package/src/commands/learning-export.js +103 -0
- package/src/commands/learning-rollback.js +164 -0
- package/src/commands/live.js +25 -1
- package/src/commands/pattern-detect.js +33 -0
- package/src/commands/preflight-context.js +30 -0
- package/src/commands/preflight.js +208 -0
- package/src/commands/pulse-update.js +130 -0
- package/src/commands/runner-daemon.js +274 -0
- package/src/commands/runner-plan.js +70 -0
- package/src/commands/runner-queue-from-plan.js +166 -0
- package/src/commands/runner-queue.js +189 -0
- package/src/commands/runner-run.js +129 -0
- package/src/commands/runtime.js +47 -1
- package/src/commands/self-implement-loop.js +256 -0
- package/src/commands/session-guard.js +218 -0
- package/src/commands/sizing.js +165 -0
- package/src/commands/skill.js +65 -0
- package/src/commands/spec-checkpoint.js +177 -0
- package/src/commands/spec-status.js +79 -0
- package/src/commands/spec-sync.js +190 -0
- package/src/commands/spec-tasks.js +288 -0
- package/src/commands/squad-autorun.js +1220 -0
- package/src/commands/squad-bus.js +217 -0
- package/src/commands/squad-card.js +149 -0
- package/src/commands/squad-daemon.js +134 -0
- package/src/commands/squad-dependency-graph.js +164 -0
- package/src/commands/squad-review.js +106 -0
- package/src/commands/squad-scaffold.js +55 -0
- package/src/commands/squad-tool-register.js +157 -0
- package/src/commands/state-save.js +122 -0
- package/src/commands/update.js +2 -0
- package/src/commands/verify-gate.js +572 -0
- package/src/commands/workflow-execute.js +241 -0
- package/src/constants.js +9 -0
- package/src/install-profile.js +2 -2
- package/src/install-wizard.js +3 -2
- package/src/installer.js +6 -0
- package/src/lib/health-check.js +158 -0
- package/src/lib/hook-protocol.js +76 -0
- package/src/mcp/apps/squad-dashboard/app.js +163 -0
- package/src/mcp/apps/squad-dashboard/index.html +261 -0
- package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -0
- package/src/mcp/resources/squad-state.js +130 -0
- package/src/preflight-engine.js +443 -0
- package/src/runner/cascade.js +97 -0
- package/src/runner/cli-launcher.js +109 -0
- package/src/runner/plan-importer.js +63 -0
- package/src/runner/queue-store.js +159 -0
- package/src/runtime-store.js +61 -3
- package/src/squad/agent-teams-adapter.js +264 -0
- package/src/squad/brief-validator.js +350 -0
- package/src/squad/bus-bridge.js +140 -0
- package/src/squad/context-compactor.js +265 -0
- package/src/squad/cross-ai-synthesizer.js +250 -0
- package/src/squad/hooks-generator.js +196 -0
- package/src/squad/inter-squad-events.js +175 -0
- package/src/squad/intra-bus.js +345 -0
- package/src/squad/learning-extractor.js +213 -0
- package/src/squad/pattern-detector.js +365 -0
- package/src/squad/preflight-context.js +296 -0
- package/src/squad/recovery-context.js +242 -71
- package/src/squad/reflection.js +365 -0
- package/src/squad/squad-scaffold.js +177 -0
- package/src/squad/state-manager.js +310 -0
- package/src/squad/task-decomposer.js +652 -0
- package/src/squad/verify-gate.js +303 -0
- package/src/updater.js +4 -5
- package/src/worker-runner.js +186 -1
- package/template/.aioson/agents/analyst.md +62 -1
- package/template/.aioson/agents/architect.md +61 -1
- package/template/.aioson/agents/design-hybrid-forge.md +14 -0
- package/template/.aioson/agents/dev.md +242 -24
- package/template/.aioson/agents/deyvin.md +66 -8
- package/template/.aioson/agents/discovery-design-doc.md +44 -0
- package/template/.aioson/agents/genome.md +14 -0
- package/template/.aioson/agents/neo.md +78 -1
- package/template/.aioson/agents/orache.md +50 -4
- package/template/.aioson/agents/orchestrator.md +197 -1
- package/template/.aioson/agents/pm.md +35 -0
- package/template/.aioson/agents/product.md +50 -5
- package/template/.aioson/agents/profiler-enricher.md +14 -0
- package/template/.aioson/agents/profiler-forge.md +14 -0
- package/template/.aioson/agents/profiler-researcher.md +14 -0
- package/template/.aioson/agents/qa.md +172 -21
- package/template/.aioson/agents/setup.md +79 -9
- package/template/.aioson/agents/sheldon.md +131 -6
- package/template/.aioson/agents/site-forge.md +1753 -0
- package/template/.aioson/agents/squad.md +162 -0
- package/template/.aioson/agents/tester.md +53 -0
- package/template/.aioson/agents/ux-ui.md +34 -1
- package/template/.aioson/brains/README.md +128 -0
- package/template/.aioson/brains/_index.json +16 -0
- package/template/.aioson/brains/scripts/query.js +103 -0
- package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
- package/template/.aioson/config.md +143 -13
- package/template/.aioson/constitution.md +33 -0
- package/template/.aioson/context/project-pulse.md +34 -0
- package/template/.aioson/docs/LAYERS.md +79 -0
- package/template/.aioson/docs/README.md +76 -0
- package/template/.aioson/docs/example-external-api-context.md +72 -0
- package/template/.aioson/locales/en/agents/architect.md +17 -0
- package/template/.aioson/locales/en/agents/dev.md +79 -13
- package/template/.aioson/locales/en/agents/orache.md +6 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
- package/template/.aioson/locales/en/agents/product.md +50 -0
- package/template/.aioson/locales/en/agents/sheldon.md +115 -0
- package/template/.aioson/locales/en/agents/squad.md +14 -0
- package/template/.aioson/locales/en/agents/tester.md +6 -0
- package/template/.aioson/locales/es/agents/analyst.md +2 -0
- package/template/.aioson/locales/es/agents/architect.md +19 -0
- package/template/.aioson/locales/es/agents/dev.md +64 -4
- package/template/.aioson/locales/es/agents/deyvin.md +2 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/es/agents/genome.md +2 -0
- package/template/.aioson/locales/es/agents/neo.md +2 -0
- package/template/.aioson/locales/es/agents/orache.md +2 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/pair.md +2 -0
- package/template/.aioson/locales/es/agents/pm.md +2 -0
- package/template/.aioson/locales/es/agents/product.md +52 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/es/agents/qa.md +2 -0
- package/template/.aioson/locales/es/agents/setup.md +2 -0
- package/template/.aioson/locales/es/agents/sheldon.md +117 -0
- package/template/.aioson/locales/es/agents/squad.md +16 -0
- package/template/.aioson/locales/es/agents/tester.md +9 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/fr/agents/analyst.md +2 -0
- package/template/.aioson/locales/fr/agents/architect.md +19 -0
- package/template/.aioson/locales/fr/agents/dev.md +64 -4
- package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/fr/agents/genome.md +2 -0
- package/template/.aioson/locales/fr/agents/neo.md +2 -0
- package/template/.aioson/locales/fr/agents/orache.md +2 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/pair.md +2 -0
- package/template/.aioson/locales/fr/agents/pm.md +2 -0
- package/template/.aioson/locales/fr/agents/product.md +52 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/fr/agents/qa.md +2 -0
- package/template/.aioson/locales/fr/agents/setup.md +2 -0
- package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
- package/template/.aioson/locales/fr/agents/squad.md +16 -0
- package/template/.aioson/locales/fr/agents/tester.md +9 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
- package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
- package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
- package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
- package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +101 -18
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
- package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
- package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
- package/template/.aioson/rules/README.md +69 -0
- package/template/.aioson/rules/data-format-convention.md +136 -0
- package/template/.aioson/rules/example-monetary-values.md +30 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
- package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
- package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +1 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +35 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +4 -1
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +15 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +32 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +20 -0
- package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
- package/template/.aioson/skills/static/context-budget-guide.md +46 -0
- package/template/.aioson/skills/static/harness-sensors.md +74 -0
- package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
- package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
- package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
- package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
- package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
- package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
- package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
- package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
- package/template/.aioson/skills/static/threejs-patterns.md +929 -0
- package/template/.aioson/skills/static/web-research-cache.md +112 -0
- package/template/.aioson/tasks/implementation-plan.md +21 -1
- package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/orache.md +5 -0
- package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
- package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
- package/template/AGENTS.md +55 -3
- package/template/CLAUDE.md +30 -0
- package/template/OPENCODE.md +4 -0
- 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
|
+
};
|