@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,347 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson hooks:install [projectDir] --agent=<name> --tool=<claude|antigravity|codex|all>
|
|
5
|
+
*
|
|
6
|
+
* Installs AIOSON event hooks into the AI tool's settings file.
|
|
7
|
+
* After installation, every Write/Edit/Bash tool call and session stop
|
|
8
|
+
* automatically emits a runtime event to SQLite — no manual aioson calls needed.
|
|
9
|
+
*
|
|
10
|
+
* Supported tools:
|
|
11
|
+
* --tool=claude → ~/.claude/settings.json
|
|
12
|
+
* --tool=antigravity → ~/.gemini/antigravity/hooks.json (+ .agents/hooks.json in project)
|
|
13
|
+
* --tool=codex → ~/.codex/config.yaml (limited: no hook system, documents workaround)
|
|
14
|
+
* --tool=all → installs for all detected tools
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const fs = require('node:fs/promises');
|
|
19
|
+
const os = require('node:os');
|
|
20
|
+
|
|
21
|
+
const HOME = os.homedir();
|
|
22
|
+
|
|
23
|
+
// ─── Config file paths ────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const CONFIG_PATHS = {
|
|
26
|
+
claude: path.join(HOME, '.claude', 'settings.json'),
|
|
27
|
+
antigravity: path.join(HOME, '.gemini', 'antigravity', 'hooks.json'),
|
|
28
|
+
antigravity_workspace: '.agents/hooks.json' // relative to project
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ─── Hook command templates ───────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
function makeEmitCommand(agentName, source) {
|
|
34
|
+
// $PWD is the project directory at hook execution time
|
|
35
|
+
return `aioson hooks:emit "$PWD" --agent=${agentName} --source=${source} 2>/dev/null || true`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeDoneCommand(agentName) {
|
|
39
|
+
return `aioson agent:done "$PWD" --agent=${agentName} --summary="Session ended via ${agentName} hook" 2>/dev/null || true`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Claude Code ─────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
function buildClaudeHooks(agentName) {
|
|
45
|
+
const emitCmd = makeEmitCommand(agentName, 'claude');
|
|
46
|
+
const doneCmd = makeDoneCommand(agentName);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
PostToolUse: [
|
|
50
|
+
{
|
|
51
|
+
matcher: 'Write|Edit|MultiEdit',
|
|
52
|
+
hooks: [{ type: 'command', command: emitCmd }]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
matcher: 'Bash',
|
|
56
|
+
hooks: [{ type: 'command', command: emitCmd }]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
matcher: 'Task|TodoWrite',
|
|
60
|
+
hooks: [{ type: 'command', command: emitCmd }]
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
Stop: [
|
|
64
|
+
{
|
|
65
|
+
hooks: [{ type: 'command', command: doneCmd }]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function installClaudeHooks(agentName, dryRun, logger) {
|
|
72
|
+
const configPath = CONFIG_PATHS.claude;
|
|
73
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
74
|
+
|
|
75
|
+
let existing = {};
|
|
76
|
+
try {
|
|
77
|
+
existing = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
|
78
|
+
} catch { /* file doesn't exist yet */ }
|
|
79
|
+
|
|
80
|
+
const newHooks = buildClaudeHooks(agentName);
|
|
81
|
+
|
|
82
|
+
// Merge: add AIOSON hooks without removing existing ones
|
|
83
|
+
const merged = { ...existing };
|
|
84
|
+
if (!merged.hooks) merged.hooks = {};
|
|
85
|
+
|
|
86
|
+
for (const [event, hookList] of Object.entries(newHooks)) {
|
|
87
|
+
if (!merged.hooks[event]) {
|
|
88
|
+
merged.hooks[event] = hookList;
|
|
89
|
+
} else {
|
|
90
|
+
// Remove any existing AIOSON hooks (to avoid duplicates on reinstall)
|
|
91
|
+
const filtered = merged.hooks[event].filter((entry) => {
|
|
92
|
+
const cmd = entry.hooks?.[0]?.command || '';
|
|
93
|
+
return !cmd.includes('aioson hooks:emit') && !cmd.includes('aioson agent:done');
|
|
94
|
+
});
|
|
95
|
+
merged.hooks[event] = [...filtered, ...hookList];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!dryRun) {
|
|
100
|
+
await fs.writeFile(configPath, JSON.stringify(merged, null, 2), 'utf8');
|
|
101
|
+
logger.log(` ✓ Claude Code — ${configPath}`);
|
|
102
|
+
} else {
|
|
103
|
+
logger.log(` [dry-run] Would write: ${configPath}`);
|
|
104
|
+
logger.log(` Hooks to add:`);
|
|
105
|
+
logger.log(` PostToolUse (Write|Edit|MultiEdit|Bash|Task|TodoWrite) → hooks:emit`);
|
|
106
|
+
logger.log(` Stop → agent:done`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { tool: 'claude', configPath, hooks: newHooks };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── Antigravity ─────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
function buildAntigravityHooks(agentName) {
|
|
115
|
+
const emitCmd = makeEmitCommand(agentName, 'antigravity');
|
|
116
|
+
const doneCmd = makeDoneCommand(agentName);
|
|
117
|
+
const startCmd = `aioson live:start "$PWD" --agent=${agentName} --tool=antigravity --no-launch 2>/dev/null || true`;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
SessionStart: [{ type: 'command', command: startCmd }],
|
|
121
|
+
PostToolUse: [
|
|
122
|
+
{ matcher: 'Write|Edit|MultiEdit', hooks: [{ type: 'command', command: emitCmd }] },
|
|
123
|
+
{ matcher: 'Bash', hooks: [{ type: 'command', command: emitCmd }] },
|
|
124
|
+
{ matcher: 'Task|TodoWrite', hooks: [{ type: 'command', command: emitCmd }] }
|
|
125
|
+
],
|
|
126
|
+
SessionEnd: [{ type: 'command', command: doneCmd }],
|
|
127
|
+
Stop: [{ type: 'command', command: doneCmd }]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function installAntigravityHooks(agentName, projectDir, dryRun, logger) {
|
|
132
|
+
const globalPath = CONFIG_PATHS.antigravity;
|
|
133
|
+
const workspacePath = path.join(projectDir, CONFIG_PATHS.antigravity_workspace);
|
|
134
|
+
|
|
135
|
+
const hooks = buildAntigravityHooks(agentName);
|
|
136
|
+
|
|
137
|
+
if (!dryRun) {
|
|
138
|
+
await fs.mkdir(path.dirname(globalPath), { recursive: true });
|
|
139
|
+
await fs.mkdir(path.dirname(workspacePath), { recursive: true });
|
|
140
|
+
|
|
141
|
+
// Global hooks
|
|
142
|
+
let globalExisting = {};
|
|
143
|
+
try { globalExisting = JSON.parse(await fs.readFile(globalPath, 'utf8')); } catch { /* new file */ }
|
|
144
|
+
const mergedGlobal = mergeAntigravityHooks(globalExisting, hooks);
|
|
145
|
+
await fs.writeFile(globalPath, JSON.stringify(mergedGlobal, null, 2), 'utf8');
|
|
146
|
+
logger.log(` ✓ Antigravity global — ${globalPath}`);
|
|
147
|
+
|
|
148
|
+
// Workspace hooks (project-scoped, takes priority)
|
|
149
|
+
let wsExisting = {};
|
|
150
|
+
try { wsExisting = JSON.parse(await fs.readFile(workspacePath, 'utf8')); } catch { /* new file */ }
|
|
151
|
+
const mergedWs = mergeAntigravityHooks(wsExisting, hooks);
|
|
152
|
+
await fs.writeFile(workspacePath, JSON.stringify(mergedWs, null, 2), 'utf8');
|
|
153
|
+
logger.log(` ✓ Antigravity workspace — ${workspacePath}`);
|
|
154
|
+
} else {
|
|
155
|
+
logger.log(` [dry-run] Would write: ${globalPath}`);
|
|
156
|
+
logger.log(` [dry-run] Would write: ${workspacePath}`);
|
|
157
|
+
logger.log(` Hooks to add: SessionStart → live:start, PostToolUse → hooks:emit, SessionEnd/Stop → agent:done`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { tool: 'antigravity', globalPath, workspacePath, hooks };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function mergeAntigravityHooks(existing, newHooks) {
|
|
164
|
+
const merged = { ...existing };
|
|
165
|
+
if (!merged.hooks) merged.hooks = {};
|
|
166
|
+
|
|
167
|
+
for (const [event, entries] of Object.entries(newHooks)) {
|
|
168
|
+
const existingEntries = Array.isArray(merged.hooks[event]) ? merged.hooks[event] : [];
|
|
169
|
+
// Remove previous AIOSON entries
|
|
170
|
+
const filtered = existingEntries.filter((e) => {
|
|
171
|
+
const cmd = (e.command || e.hooks?.[0]?.command || '');
|
|
172
|
+
return !cmd.includes('aioson');
|
|
173
|
+
});
|
|
174
|
+
const newEntries = Array.isArray(entries) ? entries : [entries];
|
|
175
|
+
merged.hooks[event] = [...filtered, ...newEntries];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return merged;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ─── Codex (OpenAI) ───────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
async function installCodexHooks(agentName, dryRun, logger) {
|
|
184
|
+
// Codex CLI does not have a native hook system as of 2026.
|
|
185
|
+
// The workaround: add a shell alias that wraps `codex` and calls live:start before / agent:done after.
|
|
186
|
+
const configPath = path.join(HOME, '.codex', 'config.yaml');
|
|
187
|
+
const wrapperPath = path.join(HOME, '.codex', 'aioson-wrapper.sh');
|
|
188
|
+
|
|
189
|
+
const wrapperScript = `#!/bin/bash
|
|
190
|
+
# AIOSON session wrapper for Codex CLI
|
|
191
|
+
# Generated by: aioson hooks:install --tool=codex --agent=${agentName}
|
|
192
|
+
# Usage: replace \`codex\` calls with \`codex-aioson\` OR add to .bashrc:
|
|
193
|
+
# alias codex='${wrapperPath}'
|
|
194
|
+
|
|
195
|
+
PROJECT_DIR="\${1:-$PWD}"
|
|
196
|
+
AGENT="${agentName}"
|
|
197
|
+
|
|
198
|
+
# Start live session before Codex runs
|
|
199
|
+
aioson live:start "$PROJECT_DIR" --agent="$AGENT" --tool=codex --no-launch 2>/dev/null || true
|
|
200
|
+
|
|
201
|
+
# Run Codex with all original arguments
|
|
202
|
+
codex-bin "$@"
|
|
203
|
+
EXIT_CODE=$?
|
|
204
|
+
|
|
205
|
+
# Register session end
|
|
206
|
+
aioson agent:done "$PROJECT_DIR" --agent="$AGENT" --summary="Codex session ended" 2>/dev/null || true
|
|
207
|
+
|
|
208
|
+
exit $EXIT_CODE
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
if (!dryRun) {
|
|
212
|
+
await fs.mkdir(path.dirname(wrapperPath), { recursive: true });
|
|
213
|
+
await fs.writeFile(wrapperPath, wrapperScript, 'utf8');
|
|
214
|
+
await fs.chmod(wrapperPath, 0o755);
|
|
215
|
+
logger.log(` ✓ Codex wrapper — ${wrapperPath}`);
|
|
216
|
+
logger.log(` ⚠ Codex has no native hooks. Add this to ~/.bashrc:`);
|
|
217
|
+
logger.log(` alias codex='${wrapperPath}'`);
|
|
218
|
+
logger.log(` Or rename: mv $(which codex) $(which codex)-bin`);
|
|
219
|
+
} else {
|
|
220
|
+
logger.log(` [dry-run] Would write: ${wrapperPath}`);
|
|
221
|
+
logger.log(` ⚠ Codex has no native hook system. Wrapper script approach only.`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { tool: 'codex', wrapperPath, limited: true };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ─── Detection ───────────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
async function detectInstalledTools() {
|
|
230
|
+
const detected = [];
|
|
231
|
+
const checks = [
|
|
232
|
+
{ tool: 'claude', path: path.join(HOME, '.claude') },
|
|
233
|
+
{ tool: 'antigravity', path: path.join(HOME, '.gemini', 'antigravity') },
|
|
234
|
+
{ tool: 'codex', path: path.join(HOME, '.codex') }
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
for (const { tool, path: p } of checks) {
|
|
238
|
+
try {
|
|
239
|
+
await fs.access(p);
|
|
240
|
+
detected.push(tool);
|
|
241
|
+
} catch { /* not installed */ }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return detected;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
async function runHooksInstall({ args, options = {}, logger }) {
|
|
250
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
251
|
+
const agentName = options.agent ? String(options.agent).replace(/^@/, '') : 'dev';
|
|
252
|
+
const dryRun = options['dry-run'] || options.dryRun || false;
|
|
253
|
+
let tool = options.tool ? String(options.tool).trim().toLowerCase() : 'all';
|
|
254
|
+
|
|
255
|
+
if (tool === 'all') {
|
|
256
|
+
const detected = await detectInstalledTools();
|
|
257
|
+
if (detected.length === 0) {
|
|
258
|
+
logger.log('No supported AI tools detected. Install Claude Code, Antigravity, or Codex first.');
|
|
259
|
+
return { ok: false, reason: 'no_tools_detected' };
|
|
260
|
+
}
|
|
261
|
+
logger.log(`Detected tools: ${detected.join(', ')}`);
|
|
262
|
+
tool = detected.join(',');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const tools = tool.split(',').map((t) => t.trim()).filter(Boolean);
|
|
266
|
+
const results = [];
|
|
267
|
+
|
|
268
|
+
logger.log(`Hooks Install — agent: @${agentName}${dryRun ? ' [dry-run]' : ''}`);
|
|
269
|
+
logger.log('─'.repeat(50));
|
|
270
|
+
|
|
271
|
+
for (const t of tools) {
|
|
272
|
+
try {
|
|
273
|
+
if (t === 'claude') {
|
|
274
|
+
results.push(await installClaudeHooks(agentName, dryRun, logger));
|
|
275
|
+
} else if (t === 'antigravity') {
|
|
276
|
+
results.push(await installAntigravityHooks(agentName, projectDir, dryRun, logger));
|
|
277
|
+
} else if (t === 'codex') {
|
|
278
|
+
results.push(await installCodexHooks(agentName, dryRun, logger));
|
|
279
|
+
} else {
|
|
280
|
+
logger.log(` ⚠ Unknown tool: ${t} — supported: claude, antigravity, codex`);
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
logger.log(` ✗ ${t}: ${err.message}`);
|
|
284
|
+
results.push({ tool: t, error: err.message });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
logger.log('─'.repeat(50));
|
|
289
|
+
|
|
290
|
+
if (!dryRun) {
|
|
291
|
+
logger.log('');
|
|
292
|
+
logger.log('Hooks installed. From now on:');
|
|
293
|
+
logger.log(' • Every file write/edit → logged as artifact event');
|
|
294
|
+
logger.log(' • Every bash command → logged as step_done event');
|
|
295
|
+
logger.log(' • Session end → logged as agent:done');
|
|
296
|
+
logger.log('');
|
|
297
|
+
logger.log('To verify: aioson live:status . --agent=' + agentName);
|
|
298
|
+
logger.log('To uninstall: aioson hooks:uninstall --tool=' + tools.join(','));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (options.json) {
|
|
302
|
+
return { ok: true, results, agentName, dryRun };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return { ok: true, results, agentName, dryRun };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function runHooksUninstall({ args, options = {}, logger }) {
|
|
309
|
+
const agentName = options.agent ? String(options.agent).replace(/^@/, '') : 'dev';
|
|
310
|
+
const dryRun = options['dry-run'] || options.dryRun || false;
|
|
311
|
+
const tool = options.tool ? String(options.tool).trim().toLowerCase() : 'claude';
|
|
312
|
+
const tools = tool.split(',').map((t) => t.trim()).filter(Boolean);
|
|
313
|
+
|
|
314
|
+
logger.log(`Hooks Uninstall — agent: @${agentName}${dryRun ? ' [dry-run]' : ''}`);
|
|
315
|
+
logger.log('─'.repeat(50));
|
|
316
|
+
|
|
317
|
+
for (const t of tools) {
|
|
318
|
+
if (t === 'claude') {
|
|
319
|
+
try {
|
|
320
|
+
const configPath = CONFIG_PATHS.claude;
|
|
321
|
+
const existing = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
|
322
|
+
if (existing.hooks) {
|
|
323
|
+
for (const event of Object.keys(existing.hooks)) {
|
|
324
|
+
existing.hooks[event] = (existing.hooks[event] || []).filter((entry) => {
|
|
325
|
+
const cmd = entry.hooks?.[0]?.command || entry.command || '';
|
|
326
|
+
return !cmd.includes('aioson hooks:emit') && !cmd.includes('aioson agent:done') && !cmd.includes('aioson live:start');
|
|
327
|
+
});
|
|
328
|
+
if (existing.hooks[event].length === 0) delete existing.hooks[event];
|
|
329
|
+
}
|
|
330
|
+
if (Object.keys(existing.hooks).length === 0) delete existing.hooks;
|
|
331
|
+
}
|
|
332
|
+
if (!dryRun) {
|
|
333
|
+
await fs.writeFile(configPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
334
|
+
logger.log(` ✓ Claude Code hooks removed — ${configPath}`);
|
|
335
|
+
} else {
|
|
336
|
+
logger.log(` [dry-run] Would remove AIOSON hooks from: ${configPath}`);
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
logger.log(` Claude Code settings not found — nothing to remove`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return { ok: true };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
module.exports = { runHooksInstall, runHooksUninstall };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson learning:auto-promote — auto-promote frequent learnings to rules files.
|
|
5
|
+
*
|
|
6
|
+
* Scans project_learnings with frequency >= threshold and promotes eligible ones
|
|
7
|
+
* to .aioson/rules/. Domain learnings (not promotable to universal rules) are noted
|
|
8
|
+
* but not written. No LLM calls.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* aioson learning:auto-promote .
|
|
12
|
+
* aioson learning:auto-promote . --threshold=3
|
|
13
|
+
* aioson learning:auto-promote . --threshold=2 --dry-run
|
|
14
|
+
* aioson learning:auto-promote . --json
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('node:fs/promises');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { openRuntimeDb, listProjectLearnings } = require('../runtime-store');
|
|
20
|
+
|
|
21
|
+
const DEFAULT_THRESHOLD = 3;
|
|
22
|
+
const RULES_DIR = '.aioson/rules';
|
|
23
|
+
const BAR = '━'.repeat(30);
|
|
24
|
+
|
|
25
|
+
// Only 'process' and 'quality' type learnings become universal rules.
|
|
26
|
+
// 'domain' learnings are project-specific and should not become global rules.
|
|
27
|
+
// 'preference' learnings go to project.context.md (handled by learning:evolve).
|
|
28
|
+
const PROMOTABLE_TYPES = new Set(['process', 'quality']);
|
|
29
|
+
|
|
30
|
+
function slugify(title) {
|
|
31
|
+
return title
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
34
|
+
.replace(/^-+|-+$/g, '')
|
|
35
|
+
.slice(0, 50);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildRuleContent(learning) {
|
|
39
|
+
const confidence = learning.confidence === 'high' ? ' (high confidence)' : learning.confidence === 'low' ? ' (low confidence)' : '';
|
|
40
|
+
const lines = [
|
|
41
|
+
'---',
|
|
42
|
+
`title: ${learning.title}`,
|
|
43
|
+
`type: ${learning.type}`,
|
|
44
|
+
`frequency: ${learning.frequency}`,
|
|
45
|
+
`source: auto-promoted`,
|
|
46
|
+
'agents: all',
|
|
47
|
+
'---',
|
|
48
|
+
'',
|
|
49
|
+
`# ${learning.title}${confidence}`,
|
|
50
|
+
''
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
if (learning.evidence) {
|
|
54
|
+
lines.push(`> ${learning.evidence}`, '');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (learning.type === 'process') {
|
|
58
|
+
lines.push('**When:** During any implementation or workflow session.');
|
|
59
|
+
lines.push(`**Rule:** ${learning.title}`);
|
|
60
|
+
} else if (learning.type === 'quality') {
|
|
61
|
+
lines.push('**When:** Before committing or delivering a deliverable.');
|
|
62
|
+
lines.push(`**Rule:** ${learning.title}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
lines.push('');
|
|
66
|
+
return lines.join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function runLearningAutoPromote({ args, options = {}, logger }) {
|
|
70
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
71
|
+
const threshold = options.threshold ? parseInt(options.threshold) : DEFAULT_THRESHOLD;
|
|
72
|
+
const dryRun = Boolean(options['dry-run'] || options.dry);
|
|
73
|
+
|
|
74
|
+
if (isNaN(threshold) || threshold < 1) {
|
|
75
|
+
if (options.json) return { ok: false, reason: 'invalid_threshold' };
|
|
76
|
+
logger.log('--threshold must be a positive integer (default: 3)');
|
|
77
|
+
return { ok: false };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const handle = await openRuntimeDb(targetDir, { mustExist: true });
|
|
81
|
+
if (!handle) {
|
|
82
|
+
if (options.json) return { ok: false, reason: 'no_runtime', message: 'No runtime database found. Run aioson runtime:init first.' };
|
|
83
|
+
logger.log('No runtime database found. Run aioson runtime:init first.');
|
|
84
|
+
return { ok: false };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let learnings;
|
|
88
|
+
try {
|
|
89
|
+
learnings = listProjectLearnings(handle.db, 'active');
|
|
90
|
+
} finally {
|
|
91
|
+
handle.db.close();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Filter by threshold
|
|
95
|
+
const eligible = learnings.filter((l) => Number(l.frequency || 1) >= threshold);
|
|
96
|
+
|
|
97
|
+
if (!options.json) {
|
|
98
|
+
logger.log('');
|
|
99
|
+
logger.log('Learning Auto-Promotion');
|
|
100
|
+
logger.log(BAR);
|
|
101
|
+
logger.log(`Scanning project_learnings (frequency ≥ ${threshold}):`);
|
|
102
|
+
logger.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const promoted = [];
|
|
106
|
+
const noted = [];
|
|
107
|
+
const skipped = [];
|
|
108
|
+
|
|
109
|
+
for (const learning of eligible) {
|
|
110
|
+
if (!PROMOTABLE_TYPES.has(learning.type)) {
|
|
111
|
+
noted.push({
|
|
112
|
+
title: learning.title,
|
|
113
|
+
type: learning.type,
|
|
114
|
+
frequency: learning.frequency,
|
|
115
|
+
reason: `${learning.type} learning — not promotable to universal rule`
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const slug = slugify(learning.title);
|
|
121
|
+
const fileName = `process-${slug}.md`;
|
|
122
|
+
const filePath = path.join(targetDir, RULES_DIR, fileName);
|
|
123
|
+
|
|
124
|
+
// Check if already exists
|
|
125
|
+
let alreadyExists = false;
|
|
126
|
+
try {
|
|
127
|
+
await fs.access(filePath);
|
|
128
|
+
alreadyExists = true;
|
|
129
|
+
} catch { /* ok */ }
|
|
130
|
+
|
|
131
|
+
if (alreadyExists) {
|
|
132
|
+
skipped.push({ title: learning.title, file: fileName, reason: 'already exists' });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const content = buildRuleContent(learning);
|
|
137
|
+
|
|
138
|
+
if (!dryRun) {
|
|
139
|
+
await fs.mkdir(path.join(targetDir, RULES_DIR), { recursive: true });
|
|
140
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
promoted.push({
|
|
144
|
+
learning_id: learning.learning_id,
|
|
145
|
+
title: learning.title,
|
|
146
|
+
type: learning.type,
|
|
147
|
+
frequency: learning.frequency,
|
|
148
|
+
file: path.join(RULES_DIR, fileName)
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const result = {
|
|
153
|
+
ok: true,
|
|
154
|
+
threshold,
|
|
155
|
+
dry_run: dryRun,
|
|
156
|
+
eligible: eligible.length,
|
|
157
|
+
promoted: promoted.length,
|
|
158
|
+
noted: noted.length,
|
|
159
|
+
skipped: skipped.length,
|
|
160
|
+
promoted_items: promoted,
|
|
161
|
+
noted_items: noted,
|
|
162
|
+
skipped_items: skipped
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (options.json) return result;
|
|
166
|
+
|
|
167
|
+
// Human output
|
|
168
|
+
for (const p of promoted) {
|
|
169
|
+
const dryStr = dryRun ? ' (dry-run)' : '';
|
|
170
|
+
logger.log(` ✓ "${p.title}" (freq: ${p.frequency}) → promoted to ${p.file}${dryStr}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const n of noted) {
|
|
174
|
+
logger.log(` ○ "${n.title}" (freq: ${n.frequency}) → ${n.reason}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const s of skipped) {
|
|
178
|
+
logger.log(` — "${s.title}" → ${s.reason}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
logger.log('');
|
|
182
|
+
if (promoted.length > 0) {
|
|
183
|
+
logger.log(`${promoted.length} rule${promoted.length !== 1 ? 's' : ''} ${dryRun ? 'would be' : ''} created.`);
|
|
184
|
+
if (!dryRun) logger.log('Run: aioson learning:evolve to apply to agent genomes.');
|
|
185
|
+
} else if (eligible.length === 0) {
|
|
186
|
+
logger.log(`No learnings with frequency ≥ ${threshold} found.`);
|
|
187
|
+
} else {
|
|
188
|
+
logger.log('No new rules to create.');
|
|
189
|
+
}
|
|
190
|
+
logger.log('');
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = { runLearningAutoPromote };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs/promises');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const { randomUUID } = require('node:crypto');
|
|
5
6
|
const { openRuntimeDb, listSquadLearnings, listProjectLearnings, promoteSquadLearning, promoteProjectLearning } = require('../runtime-store');
|
|
6
7
|
|
|
7
8
|
const AGENTS_DIR = path.join('.aioson', 'agents');
|
|
@@ -265,17 +266,25 @@ async function applyProposed(proposed, projectDir, db, logger, quiet, squadSlug)
|
|
|
265
266
|
if (!quiet) logger.log(` ✓ Aplicado: ${delta.file} (+${delta.count} learnings)`);
|
|
266
267
|
}
|
|
267
268
|
|
|
268
|
-
// Registra no log
|
|
269
|
+
// Registra no evolution-log.jsonl (5.5: per-delta entries with UUIDs for rollback)
|
|
269
270
|
const evolutionDir = path.resolve(projectDir, EVOLUTION_DIR);
|
|
270
271
|
await fs.mkdir(evolutionDir, { recursive: true });
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
272
|
+
const perDeltaLogFile = path.join(evolutionDir, 'evolution-log.jsonl');
|
|
273
|
+
const ts = new Date().toISOString();
|
|
274
|
+
for (const delta of proposed.slice(0, evolved)) {
|
|
275
|
+
const logEntry = JSON.stringify({
|
|
276
|
+
id: randomUUID(),
|
|
277
|
+
ts,
|
|
278
|
+
type: 'append',
|
|
279
|
+
file: delta.file,
|
|
280
|
+
section: delta.section || null,
|
|
281
|
+
content: delta.content,
|
|
282
|
+
learning_ids: delta.sourceIds || [],
|
|
283
|
+
squad: squadSlug || null,
|
|
284
|
+
status: 'applied'
|
|
285
|
+
});
|
|
286
|
+
await fs.appendFile(perDeltaLogFile, `${logEntry}\n`, 'utf8');
|
|
287
|
+
}
|
|
279
288
|
|
|
280
289
|
if (!quiet) logger.log(`\n${evolved} delta(s) aplicado(s) com sucesso.`);
|
|
281
290
|
} finally {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb } = require('../runtime-store');
|
|
6
|
+
|
|
7
|
+
function slugify(text) {
|
|
8
|
+
return String(text || '')
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
11
|
+
.replace(/^-+|-+$/g, '')
|
|
12
|
+
.slice(0, 80) || 'learning';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildNodeContent(learning) {
|
|
16
|
+
const id = slugify(`${learning.type}-${learning.title}`);
|
|
17
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
content: `---
|
|
21
|
+
id: ${id}
|
|
22
|
+
type: ${learning.type}
|
|
23
|
+
title: ${learning.title}
|
|
24
|
+
frequency: ${learning.frequency || 1}
|
|
25
|
+
last_reinforced: ${learning.last_reinforced ? learning.last_reinforced.slice(0, 10) : now}
|
|
26
|
+
source_feature: ${learning.feature_slug || 'project'}
|
|
27
|
+
promoted_to: null
|
|
28
|
+
created_at: ${learning.created_at ? learning.created_at.slice(0, 10) : now}
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# ${learning.title}
|
|
32
|
+
|
|
33
|
+
**Evidence:** ${learning.evidence || `Detected in ${learning.frequency || 1} session(s).`}
|
|
34
|
+
|
|
35
|
+
## Applications
|
|
36
|
+
- Review and apply this learning in future sessions of type: ${learning.type}
|
|
37
|
+
|
|
38
|
+
## Links
|
|
39
|
+
<!-- Add cross-references here -->
|
|
40
|
+
`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function runLearningExport({ args, options = {}, logger }) {
|
|
45
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
46
|
+
const minFrequency = Number(options['min-frequency'] || options.minFrequency || 1);
|
|
47
|
+
const brainsDir = path.join(targetDir, '.aioson', 'brains');
|
|
48
|
+
|
|
49
|
+
const { db, dbPath } = await openRuntimeDb(targetDir, { mustExist: true });
|
|
50
|
+
|
|
51
|
+
if (!db) {
|
|
52
|
+
if (!options.json) logger.log('No runtime database found.');
|
|
53
|
+
return { ok: false, reason: 'no_db' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const learnings = db.prepare(`
|
|
58
|
+
SELECT learning_id, feature_slug, type, title, frequency, last_reinforced,
|
|
59
|
+
evidence, source_session, created_at
|
|
60
|
+
FROM project_learnings
|
|
61
|
+
WHERE status = 'active' AND frequency >= ?
|
|
62
|
+
ORDER BY frequency DESC, updated_at DESC
|
|
63
|
+
`).all(minFrequency);
|
|
64
|
+
|
|
65
|
+
if (learnings.length === 0) {
|
|
66
|
+
if (!options.json) logger.log(`No learnings with frequency >= ${minFrequency}.`);
|
|
67
|
+
return { ok: true, exported: 0, dbPath };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await fs.mkdir(brainsDir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
const exported = [];
|
|
73
|
+
for (const learning of learnings) {
|
|
74
|
+
const { id, content } = buildNodeContent(learning);
|
|
75
|
+
const filePath = path.join(brainsDir, `${id}.md`);
|
|
76
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
77
|
+
exported.push({ id, filePath, frequency: learning.frequency });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const promotable = learnings.filter((l) => l.frequency >= 5).length;
|
|
81
|
+
|
|
82
|
+
if (options.json) {
|
|
83
|
+
return { ok: true, exported: exported.length, nodes: exported, promotable, dbPath };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
logger.log(`Learning Export — min-frequency: ${minFrequency}`);
|
|
87
|
+
logger.log('─'.repeat(50));
|
|
88
|
+
for (const { id, frequency } of exported) {
|
|
89
|
+
logger.log(` ${id}.md ✓ (frequency: ${frequency})`);
|
|
90
|
+
}
|
|
91
|
+
logger.log('─'.repeat(50));
|
|
92
|
+
logger.log(`${exported.length} nodes written to .aioson/brains/`);
|
|
93
|
+
if (promotable > 0) {
|
|
94
|
+
logger.log(`${promotable} learning(s) with frequency ≥ 5 — run: aioson learning:evolve to promote to genome`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { ok: true, exported: exported.length, nodes: exported, promotable, dbPath };
|
|
98
|
+
} finally {
|
|
99
|
+
db.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { runLearningExport };
|