@jaimevalasek/aioson 1.6.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -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 +22 -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/copywriter.md +463 -0
- package/template/.aioson/agents/design-hybrid-forge.md +14 -0
- package/template/.aioson/agents/dev.md +271 -25
- package/template/.aioson/agents/deyvin.md +67 -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 +83 -2
- 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 +273 -21
- package/template/.aioson/agents/setup.md +96 -10
- package/template/.aioson/agents/sheldon.md +131 -6
- package/template/.aioson/agents/site-forge.md +1753 -0
- package/template/.aioson/agents/squad.md +352 -0
- package/template/.aioson/agents/tester.md +53 -0
- package/template/.aioson/agents/ux-ui.md +203 -4
- 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/genomes/copywriting.md +204 -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/cognitive-core-ui/references/motion.md +2 -0
- 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/marketing/references/anti-patterns.md +254 -0
- package/template/.aioson/skills/marketing/references/fascinations.md +192 -0
- package/template/.aioson/skills/marketing/references/five-acts.md +248 -0
- package/template/.aioson/skills/marketing/references/market-intelligence.md +198 -0
- package/template/.aioson/skills/marketing/references/offer-structure.md +203 -0
- package/template/.aioson/skills/marketing/references/one-belief.md +149 -0
- package/template/.aioson/skills/marketing/references/patterns.md +218 -0
- package/template/.aioson/skills/marketing/references/pms-research.md +193 -0
- package/template/.aioson/skills/marketing/vsl-craft.md +385 -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/landing-page-deploy.md +192 -0
- package/template/.aioson/skills/static/landing-page-forge.md +730 -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/ui-ux-modern.md +1 -0
- package/template/.aioson/skills/static/web-research-cache.md +112 -0
- package/template/.aioson/tasks/implementation-plan.md +21 -1
- package/template/.aioson/tasks/squad-create.md +22 -0
- package/template/.aioson/tasks/squad-design.md +30 -0
- package/template/.aioson/templates/squads/digital-marketing-agency/template.json +96 -0
- 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 +31 -0
- package/template/OPENCODE.md +4 -0
- package/template/researchs/.gitkeep +0 -0
- package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Context Compactor — Plan 80, Script 5
|
|
5
|
+
*
|
|
6
|
+
* Standalone context compaction for any agent session (not just squads).
|
|
7
|
+
* Produces a last-handoff.json compatible with recovery-context.js.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node context-compactor.js --agent=<agent> [--input=devlog.md] [--session=<id>]
|
|
11
|
+
*
|
|
12
|
+
* Reads: stdin or --input (devlog, notes, partial output)
|
|
13
|
+
* Produces: .aioson/context/last-handoff.json
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs/promises');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { randomUUID } = require('node:crypto');
|
|
19
|
+
|
|
20
|
+
const CONTEXT_DIR = path.join('.aioson', 'context');
|
|
21
|
+
|
|
22
|
+
// ─── Content extractors ──────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extract tool usage from session content.
|
|
26
|
+
* Looks for patterns like "Used X tool", "Called X", tool names in backticks.
|
|
27
|
+
*/
|
|
28
|
+
function extractToolsUsed(content) {
|
|
29
|
+
const tools = new Set();
|
|
30
|
+
const patterns = [
|
|
31
|
+
/\b(Read|Write|Edit|Glob|Grep|Bash|Agent)\b/g,
|
|
32
|
+
/tool[:\s]+["`](\w+)["`]/gi,
|
|
33
|
+
/using\s+(\w+)\s+tool/gi
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
for (const pattern of patterns) {
|
|
37
|
+
let match;
|
|
38
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
39
|
+
tools.add(match[1]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [...tools];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract recent requests/instructions from content.
|
|
48
|
+
* Looks for imperative sentences, commands, task descriptions.
|
|
49
|
+
*/
|
|
50
|
+
function extractRecentRequests(content) {
|
|
51
|
+
const requests = [];
|
|
52
|
+
const lines = content.split(/\r?\n/);
|
|
53
|
+
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
const trimmed = line.trim();
|
|
56
|
+
// Match task-like lines
|
|
57
|
+
if (/^[-*]\s*\[.\]/.test(trimmed)) {
|
|
58
|
+
requests.push(trimmed.replace(/^[-*]\s*\[.\]\s*/, ''));
|
|
59
|
+
}
|
|
60
|
+
// Match "Task:" or "Goal:" prefixed lines
|
|
61
|
+
if (/^(task|goal|objective|instruction):/i.test(trimmed)) {
|
|
62
|
+
requests.push(trimmed);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return requests.slice(-10);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Extract pending/incomplete work from content.
|
|
71
|
+
*/
|
|
72
|
+
function extractPendingWork(content) {
|
|
73
|
+
const pending = [];
|
|
74
|
+
const lines = content.split(/\r?\n/);
|
|
75
|
+
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
const trimmed = line.trim();
|
|
78
|
+
// Unchecked checkboxes
|
|
79
|
+
if (/^[-*]\s*\[\s\]/.test(trimmed)) {
|
|
80
|
+
pending.push(trimmed.replace(/^[-*]\s*\[\s\]\s*/, ''));
|
|
81
|
+
}
|
|
82
|
+
// Lines with TODO/FIXME/HACK
|
|
83
|
+
if (/\b(TODO|FIXME|HACK|PENDING)\b/i.test(trimmed)) {
|
|
84
|
+
pending.push(trimmed);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return pending.slice(-10);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extract key file references from content.
|
|
93
|
+
*/
|
|
94
|
+
function extractKeyFiles(content) {
|
|
95
|
+
const files = new Set();
|
|
96
|
+
// Match file paths with extensions
|
|
97
|
+
const pattern = /(?:^|\s)([\w./\\-]+\.\w{1,8})\b/g;
|
|
98
|
+
let match;
|
|
99
|
+
|
|
100
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
101
|
+
const candidate = match[1];
|
|
102
|
+
// Filter out obvious non-paths
|
|
103
|
+
if (candidate.includes('/') || candidate.includes('\\')) {
|
|
104
|
+
files.add(candidate);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return [...files].slice(0, 20);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Extract timeline events from content.
|
|
113
|
+
* Looks for timestamps, "Step N:", numbered lists with actions.
|
|
114
|
+
*/
|
|
115
|
+
function extractTimeline(content) {
|
|
116
|
+
const events = [];
|
|
117
|
+
const lines = content.split(/\r?\n/);
|
|
118
|
+
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
const trimmed = line.trim();
|
|
121
|
+
// ISO timestamps
|
|
122
|
+
if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(trimmed)) {
|
|
123
|
+
events.push(trimmed.slice(0, 120));
|
|
124
|
+
}
|
|
125
|
+
// "Step N:" pattern
|
|
126
|
+
if (/^step\s+\d+/i.test(trimmed)) {
|
|
127
|
+
events.push(trimmed.slice(0, 120));
|
|
128
|
+
}
|
|
129
|
+
// Section headers that indicate progress
|
|
130
|
+
if (/^#+\s*(phase|step|stage|sprint|iteration)\b/i.test(trimmed)) {
|
|
131
|
+
events.push(trimmed.replace(/^#+\s*/, ''));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return events.slice(-15);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── XML format ──────────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate <summary> XML compatible with Claude Code internal format.
|
|
142
|
+
*/
|
|
143
|
+
function toSummaryXml(summary, agent, sessionId) {
|
|
144
|
+
const lines = [
|
|
145
|
+
'<summary>',
|
|
146
|
+
`<agent>${agent}</agent>`,
|
|
147
|
+
`<session_id>${sessionId}</session_id>`,
|
|
148
|
+
'<tools_used>'
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
for (const tool of summary.tools_used) {
|
|
152
|
+
lines.push(` <tool>${tool}</tool>`);
|
|
153
|
+
}
|
|
154
|
+
lines.push('</tools_used>');
|
|
155
|
+
|
|
156
|
+
if (summary.recent_requests.length > 0) {
|
|
157
|
+
lines.push('<recent_requests>');
|
|
158
|
+
for (const req of summary.recent_requests) {
|
|
159
|
+
lines.push(` <request>${escapeXml(req)}</request>`);
|
|
160
|
+
}
|
|
161
|
+
lines.push('</recent_requests>');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (summary.pending_work.length > 0) {
|
|
165
|
+
lines.push('<pending_work>');
|
|
166
|
+
for (const item of summary.pending_work) {
|
|
167
|
+
lines.push(` <item>${escapeXml(item)}</item>`);
|
|
168
|
+
}
|
|
169
|
+
lines.push('</pending_work>');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (summary.key_files.length > 0) {
|
|
173
|
+
lines.push('<key_files>');
|
|
174
|
+
for (const f of summary.key_files) {
|
|
175
|
+
lines.push(` <file>${escapeXml(f)}</file>`);
|
|
176
|
+
}
|
|
177
|
+
lines.push('</key_files>');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
lines.push('</summary>');
|
|
181
|
+
return lines.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function escapeXml(str) {
|
|
185
|
+
return str
|
|
186
|
+
.replace(/&/g, '&')
|
|
187
|
+
.replace(/</g, '<')
|
|
188
|
+
.replace(/>/g, '>')
|
|
189
|
+
.replace(/"/g, '"');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Compact session context into a handoff JSON.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} projectDir — Project root
|
|
198
|
+
* @param {object} options — { agent, input, session, content }
|
|
199
|
+
* @returns {Promise<object>} — { ok, path, summary, xmlSummary }
|
|
200
|
+
*/
|
|
201
|
+
async function compactContext(projectDir, options = {}) {
|
|
202
|
+
const { agent = 'dev', input, session, content: rawContent } = options;
|
|
203
|
+
const sessionId = session || randomUUID();
|
|
204
|
+
|
|
205
|
+
// Read input content
|
|
206
|
+
let content = rawContent || '';
|
|
207
|
+
if (!content && input) {
|
|
208
|
+
const inputPath = path.resolve(projectDir, input);
|
|
209
|
+
try {
|
|
210
|
+
content = await fs.readFile(inputPath, 'utf8');
|
|
211
|
+
} catch (err) {
|
|
212
|
+
return { ok: false, error: `Cannot read input: ${err.message}` };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!content) {
|
|
217
|
+
return { ok: false, error: 'No content to compact (provide --input or pipe via stdin)' };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Extract structured data
|
|
221
|
+
const summary = {
|
|
222
|
+
tools_used: extractToolsUsed(content),
|
|
223
|
+
recent_requests: extractRecentRequests(content),
|
|
224
|
+
pending_work: extractPendingWork(content),
|
|
225
|
+
key_files: extractKeyFiles(content),
|
|
226
|
+
timeline: extractTimeline(content)
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const handoff = {
|
|
230
|
+
agent,
|
|
231
|
+
session_id: sessionId,
|
|
232
|
+
compacted_at: new Date().toISOString(),
|
|
233
|
+
summary,
|
|
234
|
+
resume_instruction: 'Continue from this checkpoint. Do not acknowledge this summary.'
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Write last-handoff.json
|
|
238
|
+
const outDir = path.join(projectDir, CONTEXT_DIR);
|
|
239
|
+
const outPath = path.join(outDir, 'last-handoff.json');
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
243
|
+
await fs.writeFile(outPath, JSON.stringify(handoff, null, 2), 'utf8');
|
|
244
|
+
} catch (err) {
|
|
245
|
+
return { ok: false, error: `Cannot write handoff: ${err.message}` };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Also generate XML format
|
|
249
|
+
const xmlSummary = toSummaryXml(summary, agent, sessionId);
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
ok: true,
|
|
253
|
+
path: outPath,
|
|
254
|
+
summary: handoff,
|
|
255
|
+
xmlSummary
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = {
|
|
260
|
+
compactContext,
|
|
261
|
+
toSummaryXml,
|
|
262
|
+
extractToolsUsed,
|
|
263
|
+
extractPendingWork,
|
|
264
|
+
extractKeyFiles
|
|
265
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cross-AI Review Synthesizer — Phase 4.4
|
|
5
|
+
*
|
|
6
|
+
* Detects available AI CLIs (claude, gemini, codex), sends identical review
|
|
7
|
+
* prompts to each (excluding the current runtime), and synthesizes the
|
|
8
|
+
* responses into a REVIEWS.md file.
|
|
9
|
+
*
|
|
10
|
+
* Detection: checks PATH for each CLI binary.
|
|
11
|
+
* Current runtime: detected via AIOSON_TOOL env var (set by claude/gemini/codex hooks).
|
|
12
|
+
*
|
|
13
|
+
* Output: outputs/REVIEWS.md with consensus, divergences, and per-reviewer sections.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { spawnSync } = require('node:child_process');
|
|
17
|
+
const fs = require('node:fs/promises');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
|
|
20
|
+
// CLI invocation patterns for each supported tool
|
|
21
|
+
const CLI_RUNNERS = {
|
|
22
|
+
claude: {
|
|
23
|
+
bin: 'claude',
|
|
24
|
+
buildArgs: (prompt) => ['--print', '--model', 'claude-haiku-4-5-20251001', prompt],
|
|
25
|
+
parseOutput: (stdout) => stdout.trim()
|
|
26
|
+
},
|
|
27
|
+
gemini: {
|
|
28
|
+
bin: 'gemini',
|
|
29
|
+
buildArgs: (prompt) => ['-p', prompt],
|
|
30
|
+
parseOutput: (stdout) => stdout.trim()
|
|
31
|
+
},
|
|
32
|
+
codex: {
|
|
33
|
+
bin: 'codex',
|
|
34
|
+
buildArgs: (prompt) => ['-q', prompt],
|
|
35
|
+
parseOutput: (stdout) => stdout.trim()
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if a binary is available in PATH.
|
|
41
|
+
*/
|
|
42
|
+
function binaryExists(bin) {
|
|
43
|
+
const result = spawnSync('which', [bin], { encoding: 'utf8', timeout: 3000 });
|
|
44
|
+
return result.status === 0 && Boolean(result.stdout.trim());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Detect which AI CLIs are available in the current environment.
|
|
49
|
+
* Excludes the current runtime (detected from AIOSON_TOOL or process title).
|
|
50
|
+
*/
|
|
51
|
+
function detectAvailableCLIs({ excludeCurrent = true } = {}) {
|
|
52
|
+
const currentTool = process.env.AIOSON_TOOL || '';
|
|
53
|
+
const available = [];
|
|
54
|
+
|
|
55
|
+
for (const [name, runner] of Object.entries(CLI_RUNNERS)) {
|
|
56
|
+
if (excludeCurrent && currentTool && currentTool.toLowerCase().includes(name)) continue;
|
|
57
|
+
if (binaryExists(runner.bin)) {
|
|
58
|
+
available.push(name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return available;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Send a prompt to a specific CLI and return its response.
|
|
67
|
+
*/
|
|
68
|
+
function queryCliReviewer(cliName, prompt, timeoutMs = 60_000) {
|
|
69
|
+
const runner = CLI_RUNNERS[cliName];
|
|
70
|
+
if (!runner) return { ok: false, error: `Unknown CLI: ${cliName}` };
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const result = spawnSync(runner.bin, runner.buildArgs(prompt), {
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
timeout: timeoutMs,
|
|
76
|
+
stdio: 'pipe'
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (result.status !== 0) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
cli: cliName,
|
|
83
|
+
error: (result.stderr || '').trim() || `exited with code ${result.status}`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const response = runner.parseOutput(result.stdout || '');
|
|
88
|
+
return { ok: true, cli: cliName, response };
|
|
89
|
+
} catch (err) {
|
|
90
|
+
return { ok: false, cli: cliName, error: err.message };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Build a review prompt for a given output file.
|
|
96
|
+
*/
|
|
97
|
+
function buildReviewPrompt(outputContent, reviewCriteria = []) {
|
|
98
|
+
const criteria = reviewCriteria.length > 0
|
|
99
|
+
? reviewCriteria.map((c, i) => `${i + 1}. ${c}`).join('\n')
|
|
100
|
+
: '1. Is the content complete and accurate?\n2. Are there any logical errors or gaps?\n3. What could be improved?';
|
|
101
|
+
|
|
102
|
+
return `You are reviewing the following output. Be concise and direct.
|
|
103
|
+
|
|
104
|
+
## Content to Review
|
|
105
|
+
|
|
106
|
+
\`\`\`
|
|
107
|
+
${outputContent.slice(0, 4000)}${outputContent.length > 4000 ? '\n... (truncated)' : ''}
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
## Review Criteria
|
|
111
|
+
|
|
112
|
+
${criteria}
|
|
113
|
+
|
|
114
|
+
## Your Review
|
|
115
|
+
|
|
116
|
+
Provide your review in 3 sections:
|
|
117
|
+
- **Strengths**: what works well
|
|
118
|
+
- **Issues**: specific problems found (if any)
|
|
119
|
+
- **Recommendations**: concrete improvements (max 3)
|
|
120
|
+
|
|
121
|
+
Keep total response under 400 words.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Synthesize multiple reviews into a REVIEWS.md document.
|
|
126
|
+
*/
|
|
127
|
+
function synthesizeReviews(reviews, outputPath, squadSlug) {
|
|
128
|
+
const ts = new Date().toISOString();
|
|
129
|
+
const successful = reviews.filter((r) => r.ok);
|
|
130
|
+
const failed = reviews.filter((r) => !r.ok);
|
|
131
|
+
|
|
132
|
+
const lines = [
|
|
133
|
+
`# Cross-AI Review`,
|
|
134
|
+
``,
|
|
135
|
+
`**Squad:** ${squadSlug || 'unknown'} `,
|
|
136
|
+
`**Output:** ${outputPath || 'unknown'} `,
|
|
137
|
+
`**Generated:** ${ts} `,
|
|
138
|
+
`**Reviewers:** ${successful.map((r) => r.cli).join(', ') || 'none'}`,
|
|
139
|
+
``
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
if (successful.length === 0) {
|
|
143
|
+
lines.push('> No AI reviewers were available or all failed.');
|
|
144
|
+
if (failed.length > 0) {
|
|
145
|
+
lines.push('');
|
|
146
|
+
lines.push('**Failed reviewers:**');
|
|
147
|
+
for (const f of failed) {
|
|
148
|
+
lines.push(`- ${f.cli}: ${f.error}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return lines.join('\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Per-reviewer sections
|
|
155
|
+
for (const review of successful) {
|
|
156
|
+
lines.push(`## ${review.cli.charAt(0).toUpperCase() + review.cli.slice(1)}'s Review`);
|
|
157
|
+
lines.push('');
|
|
158
|
+
lines.push(review.response);
|
|
159
|
+
lines.push('');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Divergence note (heuristic: reviewers that mention different keywords)
|
|
163
|
+
if (successful.length > 1) {
|
|
164
|
+
const issueKeywords = ['problem', 'error', 'issue', 'missing', 'incorrect', 'wrong', 'fail'];
|
|
165
|
+
const reviewersWithIssues = successful.filter((r) =>
|
|
166
|
+
issueKeywords.some((kw) => r.response.toLowerCase().includes(kw))
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
lines.push('---');
|
|
170
|
+
lines.push('');
|
|
171
|
+
lines.push('## Synthesis');
|
|
172
|
+
lines.push('');
|
|
173
|
+
|
|
174
|
+
if (reviewersWithIssues.length === 0) {
|
|
175
|
+
lines.push('**Consensus:** All reviewers found no significant issues.');
|
|
176
|
+
} else if (reviewersWithIssues.length === successful.length) {
|
|
177
|
+
lines.push(`**Consensus:** All reviewers identified issues — address before shipping.`);
|
|
178
|
+
} else {
|
|
179
|
+
lines.push(`**Divergence:** ${reviewersWithIssues.map((r) => r.cli).join(', ')} found issues; others approved.`);
|
|
180
|
+
}
|
|
181
|
+
lines.push('');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (failed.length > 0) {
|
|
185
|
+
lines.push('> **Note:** The following reviewers failed: ' + failed.map((f) => `${f.cli} (${f.error})`).join(', '));
|
|
186
|
+
lines.push('');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return lines.join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Run the full cross-AI review pipeline.
|
|
194
|
+
*
|
|
195
|
+
* @param {{ projectDir, outputFile, reviewCriteria?, squadSlug?, excludeCurrent?, synthesizeTo? }} opts
|
|
196
|
+
*/
|
|
197
|
+
async function runCrossAIReview({
|
|
198
|
+
projectDir,
|
|
199
|
+
outputFile,
|
|
200
|
+
reviewCriteria = [],
|
|
201
|
+
squadSlug = '',
|
|
202
|
+
excludeCurrent = true,
|
|
203
|
+
synthesizeTo = null,
|
|
204
|
+
timeoutMs = 60_000
|
|
205
|
+
}) {
|
|
206
|
+
// Read output content
|
|
207
|
+
let outputContent;
|
|
208
|
+
try {
|
|
209
|
+
outputContent = await fs.readFile(outputFile, 'utf8');
|
|
210
|
+
} catch {
|
|
211
|
+
return { ok: false, error: `Output file not found: ${outputFile}` };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Detect available CLIs
|
|
215
|
+
const clis = detectAvailableCLIs({ excludeCurrent });
|
|
216
|
+
if (clis.length === 0) {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
error: 'No AI CLIs detected. Install claude, gemini, or codex in PATH.',
|
|
220
|
+
detectedCLIs: []
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Build review prompt
|
|
225
|
+
const prompt = buildReviewPrompt(outputContent, reviewCriteria);
|
|
226
|
+
|
|
227
|
+
// Query each reviewer
|
|
228
|
+
const reviews = clis.map((cli) => queryCliReviewer(cli, prompt, timeoutMs));
|
|
229
|
+
|
|
230
|
+
// Synthesize
|
|
231
|
+
const markdown = synthesizeReviews(reviews, outputFile, squadSlug);
|
|
232
|
+
|
|
233
|
+
// Write REVIEWS.md
|
|
234
|
+
const reviewsPath = synthesizeTo ||
|
|
235
|
+
path.join(path.dirname(outputFile), 'REVIEWS.md');
|
|
236
|
+
|
|
237
|
+
await fs.mkdir(path.dirname(reviewsPath), { recursive: true });
|
|
238
|
+
await fs.writeFile(reviewsPath, markdown, 'utf8');
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
ok: true,
|
|
242
|
+
reviewers: clis,
|
|
243
|
+
successCount: reviews.filter((r) => r.ok).length,
|
|
244
|
+
failCount: reviews.filter((r) => !r.ok).length,
|
|
245
|
+
reviewsPath,
|
|
246
|
+
reviews
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = { runCrossAIReview, detectAvailableCLIs, synthesizeReviews, buildReviewPrompt };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hooks Generator — Plan 81, Phase 1.2
|
|
5
|
+
*
|
|
6
|
+
* Generates Claude Code .claude/settings.json hooks for squad execution.
|
|
7
|
+
* Uses hook types: command, agent.
|
|
8
|
+
*
|
|
9
|
+
* Generated hooks:
|
|
10
|
+
* - Stop hook: quality gate agent that blocks incomplete work
|
|
11
|
+
* - TaskCompleted hook: learning extraction after each task
|
|
12
|
+
* - PostToolUse hook: mailbox-to-bus bridge (SendMessage interception)
|
|
13
|
+
*
|
|
14
|
+
* The hooks are session-scoped: written before squad:autorun starts,
|
|
15
|
+
* cleaned up after the session ends (or left for the next session).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs/promises');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
|
|
21
|
+
const CLAUDE_DIR = '.claude';
|
|
22
|
+
const SETTINGS_FILE = 'settings.json';
|
|
23
|
+
const AGENTS_DIR = path.join(CLAUDE_DIR, 'agents');
|
|
24
|
+
|
|
25
|
+
// ─── Quality Gate Agent Template ─────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
function qualityGateAgentTemplate(squadSlug) {
|
|
28
|
+
return `# Squad Quality Gate — ${squadSlug}
|
|
29
|
+
|
|
30
|
+
You are a verification agent for the "${squadSlug}" squad. Before allowing a worker to finish:
|
|
31
|
+
|
|
32
|
+
1. Read the worker's output files listed in the task brief
|
|
33
|
+
2. Check must_haves from the task specification:
|
|
34
|
+
- **Tier 1 (Exists):** All required files exist on disk
|
|
35
|
+
- **Tier 2 (Substantive):** Files have meaningful content (no stubs, TODOs, or placeholders)
|
|
36
|
+
- **Tier 3 (Wired):** Files are referenced/imported where they should be
|
|
37
|
+
3. If any tier fails: respond with BLOCK and specific feedback on what is missing
|
|
38
|
+
4. If all pass: respond with ALLOW
|
|
39
|
+
|
|
40
|
+
## Output Format
|
|
41
|
+
|
|
42
|
+
\`\`\`json
|
|
43
|
+
{
|
|
44
|
+
"decision": "allow|block",
|
|
45
|
+
"tier_results": {
|
|
46
|
+
"exists": true,
|
|
47
|
+
"substantive": true,
|
|
48
|
+
"wired": true
|
|
49
|
+
},
|
|
50
|
+
"reason": "All tiers passed — output meets acceptance criteria",
|
|
51
|
+
"issues": []
|
|
52
|
+
}
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
## Important
|
|
56
|
+
|
|
57
|
+
- You have NO context from the generating agent's conversation
|
|
58
|
+
- Judge ONLY the artifacts on disk vs the spec requirements
|
|
59
|
+
- Be strict on Tier 1 (exists) and Tier 2 (substantive)
|
|
60
|
+
- Be lenient on Tier 3 (wired) — warn but don't block for minor wiring issues
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Settings Generator ──────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate Claude Code hooks configuration for a squad session.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} projectDir
|
|
70
|
+
* @param {string} squadSlug
|
|
71
|
+
* @param {object} [options] — { enableQualityGate, enableLearning, enableBusBridge }
|
|
72
|
+
* @returns {object} — hooks configuration object
|
|
73
|
+
*/
|
|
74
|
+
function generateHooksConfig(squadSlug, options = {}) {
|
|
75
|
+
const {
|
|
76
|
+
enableQualityGate = true,
|
|
77
|
+
enableLearning = true,
|
|
78
|
+
enableBusBridge = false
|
|
79
|
+
} = options;
|
|
80
|
+
|
|
81
|
+
const hooks = {};
|
|
82
|
+
|
|
83
|
+
// Stop hook: quality gate (blocks workers from finishing with incomplete work)
|
|
84
|
+
if (enableQualityGate) {
|
|
85
|
+
hooks.Stop = [{
|
|
86
|
+
type: 'agent',
|
|
87
|
+
agent: `.claude/agents/squad-quality-gate-${squadSlug}.md`,
|
|
88
|
+
matcher: `agent:squad-worker-*`
|
|
89
|
+
}];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// TaskCompleted hook: automatic learning extraction
|
|
93
|
+
if (enableLearning) {
|
|
94
|
+
if (!hooks.TaskCompleted) hooks.TaskCompleted = [];
|
|
95
|
+
hooks.TaskCompleted.push({
|
|
96
|
+
type: 'command',
|
|
97
|
+
command: `node -e "require('./src/squad/learning-extractor').extractLearnings(process.cwd(), '${squadSlug}', process.env.SESSION_ID || 'unknown', { busMessages: [], taskResults: [], reflectionReports: [] }).catch(() => {})"`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// PostToolUse hook: mailbox-to-bus bridge (intercepts SendMessage)
|
|
102
|
+
if (enableBusBridge) {
|
|
103
|
+
if (!hooks.PostToolUse) hooks.PostToolUse = [];
|
|
104
|
+
hooks.PostToolUse.push({
|
|
105
|
+
type: 'command',
|
|
106
|
+
matcher: 'SendMessage',
|
|
107
|
+
command: `node -e "require('./src/squad/bus-bridge').bridgeMailboxToBus(process.cwd(), '${squadSlug}', process.env.SESSION_ID || 'unknown', process.env.TOOL_INPUT || '{}').catch(() => {})"`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return hooks;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Write hooks to .claude/settings.json (merge with existing).
|
|
116
|
+
*
|
|
117
|
+
* @param {string} projectDir
|
|
118
|
+
* @param {string} squadSlug
|
|
119
|
+
* @param {object} [options]
|
|
120
|
+
* @returns {Promise<object>} — { settingsPath, agentPath, hooks }
|
|
121
|
+
*/
|
|
122
|
+
async function writeSquadHooks(projectDir, squadSlug, options = {}) {
|
|
123
|
+
const settingsDir = path.join(projectDir, CLAUDE_DIR);
|
|
124
|
+
const settingsPath = path.join(settingsDir, SETTINGS_FILE);
|
|
125
|
+
const agentsDir = path.join(projectDir, AGENTS_DIR);
|
|
126
|
+
|
|
127
|
+
// Ensure directories
|
|
128
|
+
await fs.mkdir(settingsDir, { recursive: true });
|
|
129
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
130
|
+
|
|
131
|
+
// Read existing settings
|
|
132
|
+
let existing = {};
|
|
133
|
+
try {
|
|
134
|
+
existing = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
|
|
135
|
+
} catch { /* no existing settings */ }
|
|
136
|
+
|
|
137
|
+
// Generate hooks
|
|
138
|
+
const hooks = generateHooksConfig(squadSlug, options);
|
|
139
|
+
|
|
140
|
+
// Merge hooks (squad hooks go into a namespaced section)
|
|
141
|
+
const merged = {
|
|
142
|
+
...existing,
|
|
143
|
+
hooks: {
|
|
144
|
+
...(existing.hooks || {}),
|
|
145
|
+
...hooks
|
|
146
|
+
},
|
|
147
|
+
_squadHooksFor: squadSlug,
|
|
148
|
+
_squadHooksAt: new Date().toISOString()
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
await fs.writeFile(settingsPath, JSON.stringify(merged, null, 2), 'utf8');
|
|
152
|
+
|
|
153
|
+
// Write quality gate agent template
|
|
154
|
+
let agentPath = null;
|
|
155
|
+
if (options.enableQualityGate !== false) {
|
|
156
|
+
agentPath = path.join(agentsDir, `squad-quality-gate-${squadSlug}.md`);
|
|
157
|
+
await fs.writeFile(agentPath, qualityGateAgentTemplate(squadSlug), 'utf8');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
settingsPath,
|
|
162
|
+
agentPath,
|
|
163
|
+
hooks,
|
|
164
|
+
merged: Object.keys(hooks).length
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Remove squad-specific hooks from .claude/settings.json.
|
|
170
|
+
*/
|
|
171
|
+
async function cleanupSquadHooks(projectDir, squadSlug) {
|
|
172
|
+
const settingsPath = path.join(projectDir, CLAUDE_DIR, SETTINGS_FILE);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const existing = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
|
|
176
|
+
|
|
177
|
+
if (existing._squadHooksFor === squadSlug) {
|
|
178
|
+
// Remove squad-specific hooks
|
|
179
|
+
delete existing.hooks;
|
|
180
|
+
delete existing._squadHooksFor;
|
|
181
|
+
delete existing._squadHooksAt;
|
|
182
|
+
await fs.writeFile(settingsPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Remove quality gate agent
|
|
186
|
+
const agentPath = path.join(projectDir, AGENTS_DIR, `squad-quality-gate-${squadSlug}.md`);
|
|
187
|
+
await fs.unlink(agentPath).catch(() => {});
|
|
188
|
+
} catch { /* no settings to clean */ }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
generateHooksConfig,
|
|
193
|
+
writeSquadHooks,
|
|
194
|
+
cleanupSquadHooks,
|
|
195
|
+
qualityGateAgentTemplate
|
|
196
|
+
};
|