@theihtisham/agent-shadow-brain 5.1.1 → 6.0.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/README.md +355 -1095
- package/dist/adapters/copilot.d.ts +14 -0
- package/dist/adapters/copilot.d.ts.map +1 -0
- package/dist/adapters/copilot.js +93 -0
- package/dist/adapters/copilot.js.map +1 -0
- package/dist/adapters/cursor.d.ts +13 -0
- package/dist/adapters/cursor.d.ts.map +1 -0
- package/dist/adapters/cursor.js +96 -0
- package/dist/adapters/cursor.js.map +1 -0
- package/dist/adapters/index.d.ts +4 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +7 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/windsurf.d.ts +14 -0
- package/dist/adapters/windsurf.d.ts.map +1 -0
- package/dist/adapters/windsurf.js +107 -0
- package/dist/adapters/windsurf.js.map +1 -0
- package/dist/brain/agent-firewall.d.ts +7 -0
- package/dist/brain/agent-firewall.d.ts.map +1 -0
- package/dist/brain/agent-firewall.js +152 -0
- package/dist/brain/agent-firewall.js.map +1 -0
- package/dist/brain/agent-handoff.d.ts +19 -0
- package/dist/brain/agent-handoff.d.ts.map +1 -0
- package/dist/brain/agent-handoff.js +127 -0
- package/dist/brain/agent-handoff.js.map +1 -0
- package/dist/brain/air-gap.d.ts +23 -0
- package/dist/brain/air-gap.d.ts.map +1 -0
- package/dist/brain/air-gap.js +137 -0
- package/dist/brain/air-gap.js.map +1 -0
- package/dist/brain/attention-heatmap.d.ts +24 -0
- package/dist/brain/attention-heatmap.d.ts.map +1 -0
- package/dist/brain/attention-heatmap.js +108 -0
- package/dist/brain/attention-heatmap.js.map +1 -0
- package/dist/brain/auto-setup.js +19 -14
- package/dist/brain/auto-setup.js.map +1 -1
- package/dist/brain/auto-update.js +1 -1
- package/dist/brain/brain-chat.d.ts +42 -0
- package/dist/brain/brain-chat.d.ts.map +1 -0
- package/dist/brain/brain-chat.js +162 -0
- package/dist/brain/brain-chat.js.map +1 -0
- package/dist/brain/brain-encryption.d.ts +14 -0
- package/dist/brain/brain-encryption.d.ts.map +1 -0
- package/dist/brain/brain-encryption.js +68 -0
- package/dist/brain/brain-encryption.js.map +1 -0
- package/dist/brain/brain-exchange.d.ts +30 -0
- package/dist/brain/brain-exchange.d.ts.map +1 -0
- package/dist/brain/brain-exchange.js +106 -0
- package/dist/brain/brain-exchange.js.map +1 -0
- package/dist/brain/brain-garden.d.ts +18 -0
- package/dist/brain/brain-garden.d.ts.map +1 -0
- package/dist/brain/brain-garden.js +81 -0
- package/dist/brain/brain-garden.js.map +1 -0
- package/dist/brain/brain-portability.js +1 -1
- package/dist/brain/branch-brain.d.ts +23 -0
- package/dist/brain/branch-brain.d.ts.map +1 -0
- package/dist/brain/branch-brain.js +159 -0
- package/dist/brain/branch-brain.js.map +1 -0
- package/dist/brain/calibration-monitor.d.ts +20 -0
- package/dist/brain/calibration-monitor.d.ts.map +1 -0
- package/dist/brain/calibration-monitor.js +144 -0
- package/dist/brain/calibration-monitor.js.map +1 -0
- package/dist/brain/causal-chains.d.ts +37 -0
- package/dist/brain/causal-chains.d.ts.map +1 -0
- package/dist/brain/causal-chains.js +250 -0
- package/dist/brain/causal-chains.js.map +1 -0
- package/dist/brain/collision-detective.d.ts +32 -0
- package/dist/brain/collision-detective.d.ts.map +1 -0
- package/dist/brain/collision-detective.js +233 -0
- package/dist/brain/collision-detective.js.map +1 -0
- package/dist/brain/cost-aware-spawner.d.ts +7 -0
- package/dist/brain/cost-aware-spawner.d.ts.map +1 -0
- package/dist/brain/cost-aware-spawner.js +77 -0
- package/dist/brain/cost-aware-spawner.js.map +1 -0
- package/dist/brain/dream-engine.d.ts +35 -0
- package/dist/brain/dream-engine.d.ts.map +1 -0
- package/dist/brain/dream-engine.js +273 -0
- package/dist/brain/dream-engine.js.map +1 -0
- package/dist/brain/embeddings.d.ts +27 -0
- package/dist/brain/embeddings.d.ts.map +1 -0
- package/dist/brain/embeddings.js +175 -0
- package/dist/brain/embeddings.js.map +1 -0
- package/dist/brain/forgetting-curve.d.ts +21 -0
- package/dist/brain/forgetting-curve.d.ts.map +1 -0
- package/dist/brain/forgetting-curve.js +174 -0
- package/dist/brain/forgetting-curve.js.map +1 -0
- package/dist/brain/formal-verification-bridge.d.ts +26 -0
- package/dist/brain/formal-verification-bridge.d.ts.map +1 -0
- package/dist/brain/formal-verification-bridge.js +219 -0
- package/dist/brain/formal-verification-bridge.js.map +1 -0
- package/dist/brain/global-brain.d.ts +63 -0
- package/dist/brain/global-brain.d.ts.map +1 -0
- package/dist/brain/global-brain.js +348 -0
- package/dist/brain/global-brain.js.map +1 -0
- package/dist/brain/hallucination-quarantine.d.ts +26 -0
- package/dist/brain/hallucination-quarantine.d.ts.map +1 -0
- package/dist/brain/hallucination-quarantine.js +135 -0
- package/dist/brain/hallucination-quarantine.js.map +1 -0
- package/dist/brain/hive-accelerator.d.ts +39 -0
- package/dist/brain/hive-accelerator.d.ts.map +1 -0
- package/dist/brain/hive-accelerator.js +152 -0
- package/dist/brain/hive-accelerator.js.map +1 -0
- package/dist/brain/json-output.d.ts +26 -0
- package/dist/brain/json-output.d.ts.map +1 -0
- package/dist/brain/json-output.js +112 -0
- package/dist/brain/json-output.js.map +1 -0
- package/dist/brain/l0-cache.d.ts +40 -0
- package/dist/brain/l0-cache.d.ts.map +1 -0
- package/dist/brain/l0-cache.js +176 -0
- package/dist/brain/l0-cache.js.map +1 -0
- package/dist/brain/local-llm.d.ts +20 -0
- package/dist/brain/local-llm.d.ts.map +1 -0
- package/dist/brain/local-llm.js +132 -0
- package/dist/brain/local-llm.js.map +1 -0
- package/dist/brain/mcp-server.d.ts +44 -0
- package/dist/brain/mcp-server.d.ts.map +1 -1
- package/dist/brain/mcp-server.js +486 -12
- package/dist/brain/mcp-server.js.map +1 -1
- package/dist/brain/model-registry.d.ts +96 -0
- package/dist/brain/model-registry.d.ts.map +1 -0
- package/dist/brain/model-registry.js +585 -0
- package/dist/brain/model-registry.js.map +1 -0
- package/dist/brain/orchestrator.js +1 -1
- package/dist/brain/pr-auto-review.d.ts +17 -0
- package/dist/brain/pr-auto-review.d.ts.map +1 -0
- package/dist/brain/pr-auto-review.js +73 -0
- package/dist/brain/pr-auto-review.js.map +1 -0
- package/dist/brain/pre-mortem.d.ts +21 -0
- package/dist/brain/pre-mortem.d.ts.map +1 -0
- package/dist/brain/pre-mortem.js +210 -0
- package/dist/brain/pre-mortem.js.map +1 -0
- package/dist/brain/prompt-cache.d.ts +54 -0
- package/dist/brain/prompt-cache.d.ts.map +1 -0
- package/dist/brain/prompt-cache.js +105 -0
- package/dist/brain/prompt-cache.js.map +1 -0
- package/dist/brain/reputation-ledger.d.ts +42 -0
- package/dist/brain/reputation-ledger.d.ts.map +1 -0
- package/dist/brain/reputation-ledger.js +326 -0
- package/dist/brain/reputation-ledger.js.map +1 -0
- package/dist/brain/session-hooks.d.ts +47 -0
- package/dist/brain/session-hooks.d.ts.map +1 -0
- package/dist/brain/session-hooks.js +706 -0
- package/dist/brain/session-hooks.js.map +1 -0
- package/dist/brain/subagent-bridge.d.ts +46 -0
- package/dist/brain/subagent-bridge.d.ts.map +1 -0
- package/dist/brain/subagent-bridge.js +364 -0
- package/dist/brain/subagent-bridge.js.map +1 -0
- package/dist/brain/subconscious.d.ts +34 -0
- package/dist/brain/subconscious.d.ts.map +1 -0
- package/dist/brain/subconscious.js +275 -0
- package/dist/brain/subconscious.js.map +1 -0
- package/dist/brain/swarm-debate.d.ts +22 -0
- package/dist/brain/swarm-debate.d.ts.map +1 -0
- package/dist/brain/swarm-debate.js +134 -0
- package/dist/brain/swarm-debate.js.map +1 -0
- package/dist/brain/team-brain-sync.d.ts +22 -0
- package/dist/brain/team-brain-sync.d.ts.map +1 -0
- package/dist/brain/team-brain-sync.js +132 -0
- package/dist/brain/team-brain-sync.js.map +1 -0
- package/dist/brain/token-economy.d.ts +25 -0
- package/dist/brain/token-economy.d.ts.map +1 -0
- package/dist/brain/token-economy.js +216 -0
- package/dist/brain/token-economy.js.map +1 -0
- package/dist/brain/voice-mode.d.ts +15 -0
- package/dist/brain/voice-mode.d.ts.map +1 -0
- package/dist/brain/voice-mode.js +97 -0
- package/dist/brain/voice-mode.js.map +1 -0
- package/dist/cli.js +990 -6
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/server.d.ts +6 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +1118 -368
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/v6-dashboard-html.d.ts +2 -0
- package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -0
- package/dist/dashboard/v6-dashboard-html.js +2474 -0
- package/dist/dashboard/v6-dashboard-html.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +644 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +77 -12
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
// src/brain/session-hooks.ts — Universal Session Hook Installer (v5.2.0)
|
|
2
|
+
//
|
|
3
|
+
// Installs SessionStart-equivalent hooks for every supported AI coding agent so
|
|
4
|
+
// that Shadow Brain is the FIRST thing each agent calls when a new session begins.
|
|
5
|
+
//
|
|
6
|
+
// Each agent has a different hook mechanism — this module abstracts them:
|
|
7
|
+
//
|
|
8
|
+
// Claude Code → ~/.claude/settings.json hooks.SessionStart
|
|
9
|
+
// Cursor → .cursor/rules + global ~/.cursor/mcp.json
|
|
10
|
+
// Cline → ~/.vscode/User/cline_mcp_settings.json
|
|
11
|
+
// Windsurf → .windsurfrules + ~/.windsurf/mcp.json
|
|
12
|
+
// Codex → ~/.codex/config.json
|
|
13
|
+
// Kilo Code → ~/.kilocode/settings.json
|
|
14
|
+
// OpenCode → ~/.opencode/settings.json
|
|
15
|
+
// Roo Code → ~/.roocode/mcp.json
|
|
16
|
+
// Aider → ~/.aider.conf.yml load_md
|
|
17
|
+
// Copilot → .github/copilot-instructions.md (workspace) + user settings.json
|
|
18
|
+
//
|
|
19
|
+
// Falls back gracefully when a specific agent's hook system isn't available.
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import * as path from 'path';
|
|
22
|
+
import * as os from 'os';
|
|
23
|
+
const SHADOW_BRAIN_HOOK_CMD = 'shadow-brain subconscious inject';
|
|
24
|
+
const SHADOW_BRAIN_MCP_NAME = 'shadow-brain';
|
|
25
|
+
const SHADOW_BRAIN_MCP_CONFIG = {
|
|
26
|
+
command: 'npx',
|
|
27
|
+
args: ['-y', '@theihtisham/agent-shadow-brain', 'mcp', '--stdio'],
|
|
28
|
+
};
|
|
29
|
+
export class SessionHookInstaller {
|
|
30
|
+
/** Detect installed agents on this machine + project */
|
|
31
|
+
async detectInstalled(projectDir) {
|
|
32
|
+
const detected = [];
|
|
33
|
+
const home = os.homedir();
|
|
34
|
+
const checks = [
|
|
35
|
+
{ agent: 'claude-code', paths: [path.join(home, '.claude'), path.join(projectDir, '.claude')] },
|
|
36
|
+
{ agent: 'cursor', paths: [path.join(home, '.cursor'), path.join(projectDir, '.cursor'), path.join(projectDir, '.cursorrules')] },
|
|
37
|
+
{ agent: 'cline', paths: [path.join(home, 'AppData', 'Roaming', 'Code', 'User'), path.join(home, '.vscode')] },
|
|
38
|
+
{ agent: 'windsurf', paths: [path.join(home, '.windsurf'), path.join(projectDir, '.windsurfrules')] },
|
|
39
|
+
{ agent: 'codex', paths: [path.join(home, '.codex'), path.join(projectDir, '.codex')] },
|
|
40
|
+
{ agent: 'kilo-code', paths: [path.join(home, '.kilocode'), path.join(projectDir, '.kilocode')] },
|
|
41
|
+
{ agent: 'opencode', paths: [path.join(home, '.opencode'), path.join(projectDir, '.opencode')] },
|
|
42
|
+
{ agent: 'roo-code', paths: [path.join(home, '.roocode'), path.join(home, '.rovodev')] },
|
|
43
|
+
{ agent: 'aider', paths: [path.join(home, '.aider.conf.yml'), path.join(projectDir, '.aider.conf.yml')] },
|
|
44
|
+
{ agent: 'copilot', paths: [path.join(projectDir, '.github', 'copilot-instructions.md'), path.join(home, '.config', 'copilot')] },
|
|
45
|
+
];
|
|
46
|
+
for (const { agent, paths } of checks) {
|
|
47
|
+
if (paths.some(p => fs.existsSync(p))) {
|
|
48
|
+
detected.push(agent);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return detected;
|
|
52
|
+
}
|
|
53
|
+
/** Install hooks for ALL detected agents in one shot */
|
|
54
|
+
async attachAll(projectDir) {
|
|
55
|
+
const start = Date.now();
|
|
56
|
+
const detected = await this.detectInstalled(projectDir);
|
|
57
|
+
const attached = [];
|
|
58
|
+
const failed = [];
|
|
59
|
+
const hooks = [];
|
|
60
|
+
for (const agent of detected) {
|
|
61
|
+
try {
|
|
62
|
+
const installed = await this.attach(agent, projectDir);
|
|
63
|
+
if (installed.length) {
|
|
64
|
+
attached.push(agent);
|
|
65
|
+
hooks.push(...installed);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
failed.push({ agent, reason: 'no hook installed (no supported mechanism)' });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
failed.push({ agent, reason: err instanceof Error ? err.message : String(err) });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
detected,
|
|
77
|
+
attached,
|
|
78
|
+
failed,
|
|
79
|
+
hooks,
|
|
80
|
+
totalAgents: detected.length,
|
|
81
|
+
durationMs: Date.now() - start,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Best-effort non-mutating audit of Shadow Brain hook artifacts. */
|
|
85
|
+
async audit(projectDir) {
|
|
86
|
+
const detected = [];
|
|
87
|
+
const agents = await this.detectInstalled(projectDir);
|
|
88
|
+
for (const agent of agents) {
|
|
89
|
+
detected.push(...this.findInstalledHooks(agent, projectDir));
|
|
90
|
+
}
|
|
91
|
+
return detected;
|
|
92
|
+
}
|
|
93
|
+
/** Detach Shadow Brain from every detected agent without creating new hooks. */
|
|
94
|
+
async detachAll(projectDir) {
|
|
95
|
+
const start = Date.now();
|
|
96
|
+
const detected = await this.detectInstalled(projectDir);
|
|
97
|
+
const detached = [];
|
|
98
|
+
const failed = [];
|
|
99
|
+
const hooks = [];
|
|
100
|
+
for (const agent of detected) {
|
|
101
|
+
try {
|
|
102
|
+
const before = this.findInstalledHooks(agent, projectDir);
|
|
103
|
+
const ok = await this.detach(agent, projectDir);
|
|
104
|
+
if (ok) {
|
|
105
|
+
detached.push(agent);
|
|
106
|
+
hooks.push(...before);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
failed.push({ agent, reason: 'no Shadow Brain hook found' });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
failed.push({ agent, reason: err instanceof Error ? err.message : String(err) });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
detected,
|
|
118
|
+
attached: detached,
|
|
119
|
+
failed,
|
|
120
|
+
hooks,
|
|
121
|
+
totalAgents: detected.length,
|
|
122
|
+
durationMs: Date.now() - start,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Install hooks for a single agent. Returns list of installed hooks. */
|
|
126
|
+
async attach(agent, projectDir) {
|
|
127
|
+
switch (agent) {
|
|
128
|
+
case 'claude-code': return this.attachClaudeCode(projectDir);
|
|
129
|
+
case 'cursor': return this.attachCursor(projectDir);
|
|
130
|
+
case 'cline': return this.attachCline(projectDir);
|
|
131
|
+
case 'windsurf': return this.attachWindsurf(projectDir);
|
|
132
|
+
case 'codex': return this.attachCodex(projectDir);
|
|
133
|
+
case 'kilo-code': return this.attachKiloCode(projectDir);
|
|
134
|
+
case 'opencode': return this.attachOpenCode(projectDir);
|
|
135
|
+
case 'roo-code': return this.attachRooCode(projectDir);
|
|
136
|
+
case 'aider': return this.attachAider(projectDir);
|
|
137
|
+
case 'copilot': return this.attachCopilot(projectDir);
|
|
138
|
+
default: return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Detach Shadow Brain from a specific agent */
|
|
142
|
+
async detach(agent, projectDir) {
|
|
143
|
+
const hooks = this.findInstalledHooks(agent, projectDir);
|
|
144
|
+
if (!hooks.length)
|
|
145
|
+
return false;
|
|
146
|
+
for (const hook of hooks) {
|
|
147
|
+
try {
|
|
148
|
+
if (!fs.existsSync(hook.installPath))
|
|
149
|
+
continue;
|
|
150
|
+
if (hook.hookType === 'workspace-rule') {
|
|
151
|
+
if (path.basename(hook.installPath).includes('shadow-brain')) {
|
|
152
|
+
fs.unlinkSync(hook.installPath);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
this.removeShadowBrainBlock(hook.installPath);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.removeFromJsonConfig(hook.installPath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch { /* best effort */ }
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
// ─── Per-agent installers ───────────────────────────────────────────────────
|
|
167
|
+
attachClaudeCode(projectDir) {
|
|
168
|
+
const home = os.homedir();
|
|
169
|
+
const settingsPath = path.join(home, '.claude', 'settings.json');
|
|
170
|
+
const hooks = [];
|
|
171
|
+
try {
|
|
172
|
+
let settings = {};
|
|
173
|
+
if (fs.existsSync(settingsPath)) {
|
|
174
|
+
try {
|
|
175
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')) ?? {};
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
settings = {};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// FIRST — repair any malformed entries from older Shadow Brain versions.
|
|
182
|
+
// Claude Code schema: hooks.SessionStart is an array of { matcher, hooks[] } blocks.
|
|
183
|
+
settings.hooks ?? (settings.hooks = {});
|
|
184
|
+
if (!Array.isArray(settings.hooks.SessionStart))
|
|
185
|
+
settings.hooks.SessionStart = [];
|
|
186
|
+
settings.hooks.SessionStart = this.repairClaudeHookBlocks(settings.hooks.SessionStart);
|
|
187
|
+
// Check if our hook already exists anywhere in a well-formed block.
|
|
188
|
+
let present = false;
|
|
189
|
+
for (const block of settings.hooks.SessionStart) {
|
|
190
|
+
if (!block || !Array.isArray(block.hooks))
|
|
191
|
+
continue;
|
|
192
|
+
if (block.hooks.some((h) => h && typeof h.command === 'string' && h.command.includes('shadow-brain'))) {
|
|
193
|
+
present = true;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!present) {
|
|
198
|
+
// Install into a matcher-less block so the hook runs for every session.
|
|
199
|
+
// Prefer merging into an existing empty-matcher block if one exists.
|
|
200
|
+
let target = settings.hooks.SessionStart.find((b) => b && (b.matcher === '' || b.matcher == null));
|
|
201
|
+
if (!target) {
|
|
202
|
+
target = { matcher: '', hooks: [] };
|
|
203
|
+
settings.hooks.SessionStart.push(target);
|
|
204
|
+
}
|
|
205
|
+
if (!Array.isArray(target.hooks))
|
|
206
|
+
target.hooks = [];
|
|
207
|
+
target.hooks.push({ type: 'command', command: SHADOW_BRAIN_HOOK_CMD, timeout: 5000 });
|
|
208
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
209
|
+
// Atomic write — prevents half-written settings breaking Claude Code
|
|
210
|
+
const tmp = settingsPath + '.tmp';
|
|
211
|
+
fs.writeFileSync(tmp, JSON.stringify(settings, null, 2));
|
|
212
|
+
fs.renameSync(tmp, settingsPath);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
// Still ensure the (previously malformed) file is now valid.
|
|
216
|
+
const tmp = settingsPath + '.tmp';
|
|
217
|
+
fs.writeFileSync(tmp, JSON.stringify(settings, null, 2));
|
|
218
|
+
fs.renameSync(tmp, settingsPath);
|
|
219
|
+
}
|
|
220
|
+
hooks.push({
|
|
221
|
+
agent: 'claude-code',
|
|
222
|
+
event: 'session-start',
|
|
223
|
+
hookType: 'settings-json',
|
|
224
|
+
installPath: settingsPath,
|
|
225
|
+
command: SHADOW_BRAIN_HOOK_CMD,
|
|
226
|
+
active: true,
|
|
227
|
+
installedAt: new Date(),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
throw new Error(`claude-code attach failed: ${err}`);
|
|
232
|
+
}
|
|
233
|
+
return hooks;
|
|
234
|
+
}
|
|
235
|
+
attachCursor(projectDir) {
|
|
236
|
+
const hooks = [];
|
|
237
|
+
const cursorDir = path.join(projectDir, '.cursor');
|
|
238
|
+
const rulesDir = path.join(cursorDir, 'rules');
|
|
239
|
+
const mcpPath = path.join(cursorDir, 'mcp.json');
|
|
240
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
241
|
+
// 1. Workspace rule that tells Cursor to call shadow-brain on session
|
|
242
|
+
const ruleFile = path.join(rulesDir, 'shadow-brain.md');
|
|
243
|
+
if (!fs.existsSync(ruleFile)) {
|
|
244
|
+
fs.writeFileSync(ruleFile, this.cursorRuleTemplate(), 'utf-8');
|
|
245
|
+
}
|
|
246
|
+
hooks.push({
|
|
247
|
+
agent: 'cursor',
|
|
248
|
+
event: 'session-start',
|
|
249
|
+
hookType: 'workspace-rule',
|
|
250
|
+
installPath: ruleFile,
|
|
251
|
+
command: SHADOW_BRAIN_HOOK_CMD,
|
|
252
|
+
active: true,
|
|
253
|
+
installedAt: new Date(),
|
|
254
|
+
});
|
|
255
|
+
// 2. MCP server registration
|
|
256
|
+
this.upsertMcpJson(mcpPath);
|
|
257
|
+
hooks.push({
|
|
258
|
+
agent: 'cursor',
|
|
259
|
+
event: 'session-start',
|
|
260
|
+
hookType: 'extension-config',
|
|
261
|
+
installPath: mcpPath,
|
|
262
|
+
command: SHADOW_BRAIN_MCP_CONFIG.command,
|
|
263
|
+
active: true,
|
|
264
|
+
installedAt: new Date(),
|
|
265
|
+
});
|
|
266
|
+
return hooks;
|
|
267
|
+
}
|
|
268
|
+
attachCline(projectDir) {
|
|
269
|
+
const home = os.homedir();
|
|
270
|
+
const candidates = [
|
|
271
|
+
path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'),
|
|
272
|
+
path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'),
|
|
273
|
+
path.join(home, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'),
|
|
274
|
+
];
|
|
275
|
+
const hooks = [];
|
|
276
|
+
for (const settingsPath of candidates) {
|
|
277
|
+
const dir = path.dirname(settingsPath);
|
|
278
|
+
if (!fs.existsSync(path.dirname(dir)))
|
|
279
|
+
continue;
|
|
280
|
+
try {
|
|
281
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
282
|
+
this.upsertMcpJson(settingsPath, 'mcpServers');
|
|
283
|
+
hooks.push({
|
|
284
|
+
agent: 'cline',
|
|
285
|
+
event: 'session-start',
|
|
286
|
+
hookType: 'extension-config',
|
|
287
|
+
installPath: settingsPath,
|
|
288
|
+
command: SHADOW_BRAIN_MCP_CONFIG.command,
|
|
289
|
+
active: true,
|
|
290
|
+
installedAt: new Date(),
|
|
291
|
+
});
|
|
292
|
+
break; // Only one location per OS
|
|
293
|
+
}
|
|
294
|
+
catch { /* try next */ }
|
|
295
|
+
}
|
|
296
|
+
// Workspace fallback rule
|
|
297
|
+
const projectRule = path.join(projectDir, '.clinerules');
|
|
298
|
+
if (!fs.existsSync(projectRule)) {
|
|
299
|
+
fs.writeFileSync(projectRule, this.genericRuleTemplate('Cline'), 'utf-8');
|
|
300
|
+
}
|
|
301
|
+
hooks.push({
|
|
302
|
+
agent: 'cline',
|
|
303
|
+
event: 'session-start',
|
|
304
|
+
hookType: 'workspace-rule',
|
|
305
|
+
installPath: projectRule,
|
|
306
|
+
command: SHADOW_BRAIN_HOOK_CMD,
|
|
307
|
+
active: true,
|
|
308
|
+
installedAt: new Date(),
|
|
309
|
+
});
|
|
310
|
+
return hooks;
|
|
311
|
+
}
|
|
312
|
+
attachWindsurf(projectDir) {
|
|
313
|
+
const home = os.homedir();
|
|
314
|
+
const hooks = [];
|
|
315
|
+
const ruleFile = path.join(projectDir, '.windsurfrules');
|
|
316
|
+
if (!fs.existsSync(ruleFile)) {
|
|
317
|
+
fs.writeFileSync(ruleFile, this.genericRuleTemplate('Windsurf'), 'utf-8');
|
|
318
|
+
}
|
|
319
|
+
hooks.push({
|
|
320
|
+
agent: 'windsurf',
|
|
321
|
+
event: 'session-start',
|
|
322
|
+
hookType: 'workspace-rule',
|
|
323
|
+
installPath: ruleFile,
|
|
324
|
+
command: SHADOW_BRAIN_HOOK_CMD,
|
|
325
|
+
active: true,
|
|
326
|
+
installedAt: new Date(),
|
|
327
|
+
});
|
|
328
|
+
const mcpPath = path.join(home, '.windsurf', 'mcp.json');
|
|
329
|
+
fs.mkdirSync(path.dirname(mcpPath), { recursive: true });
|
|
330
|
+
this.upsertMcpJson(mcpPath);
|
|
331
|
+
hooks.push({
|
|
332
|
+
agent: 'windsurf',
|
|
333
|
+
event: 'session-start',
|
|
334
|
+
hookType: 'extension-config',
|
|
335
|
+
installPath: mcpPath,
|
|
336
|
+
command: SHADOW_BRAIN_MCP_CONFIG.command,
|
|
337
|
+
active: true,
|
|
338
|
+
installedAt: new Date(),
|
|
339
|
+
});
|
|
340
|
+
return hooks;
|
|
341
|
+
}
|
|
342
|
+
attachCodex(projectDir) {
|
|
343
|
+
const home = os.homedir();
|
|
344
|
+
const cfgPath = path.join(home, '.codex', 'config.json');
|
|
345
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
346
|
+
let cfg = {};
|
|
347
|
+
if (fs.existsSync(cfgPath)) {
|
|
348
|
+
try {
|
|
349
|
+
cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
cfg = {};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
cfg.session_start_command = SHADOW_BRAIN_HOOK_CMD;
|
|
356
|
+
cfg.mcp_servers ?? (cfg.mcp_servers = {});
|
|
357
|
+
cfg.mcp_servers[SHADOW_BRAIN_MCP_NAME] = SHADOW_BRAIN_MCP_CONFIG;
|
|
358
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
359
|
+
return [{
|
|
360
|
+
agent: 'codex',
|
|
361
|
+
event: 'session-start',
|
|
362
|
+
hookType: 'config-file',
|
|
363
|
+
installPath: cfgPath,
|
|
364
|
+
command: SHADOW_BRAIN_HOOK_CMD,
|
|
365
|
+
active: true,
|
|
366
|
+
installedAt: new Date(),
|
|
367
|
+
}];
|
|
368
|
+
}
|
|
369
|
+
attachKiloCode(projectDir) {
|
|
370
|
+
const home = os.homedir();
|
|
371
|
+
const settingsPath = path.join(home, '.kilocode', 'settings.json');
|
|
372
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
373
|
+
let cfg = {};
|
|
374
|
+
if (fs.existsSync(settingsPath)) {
|
|
375
|
+
try {
|
|
376
|
+
cfg = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
cfg = {};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
cfg.mcpServers ?? (cfg.mcpServers = {});
|
|
383
|
+
cfg.mcpServers[SHADOW_BRAIN_MCP_NAME] = SHADOW_BRAIN_MCP_CONFIG;
|
|
384
|
+
cfg.sessionStartHook = SHADOW_BRAIN_HOOK_CMD;
|
|
385
|
+
fs.writeFileSync(settingsPath, JSON.stringify(cfg, null, 2));
|
|
386
|
+
return [{
|
|
387
|
+
agent: 'kilo-code',
|
|
388
|
+
event: 'session-start',
|
|
389
|
+
hookType: 'settings-json',
|
|
390
|
+
installPath: settingsPath,
|
|
391
|
+
command: SHADOW_BRAIN_HOOK_CMD,
|
|
392
|
+
active: true,
|
|
393
|
+
installedAt: new Date(),
|
|
394
|
+
}];
|
|
395
|
+
}
|
|
396
|
+
attachOpenCode(projectDir) {
|
|
397
|
+
const home = os.homedir();
|
|
398
|
+
const settingsPath = path.join(home, '.opencode', 'settings.json');
|
|
399
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
400
|
+
let cfg = {};
|
|
401
|
+
if (fs.existsSync(settingsPath)) {
|
|
402
|
+
try {
|
|
403
|
+
cfg = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
cfg = {};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
cfg.mcpServers ?? (cfg.mcpServers = {});
|
|
410
|
+
cfg.mcpServers[SHADOW_BRAIN_MCP_NAME] = SHADOW_BRAIN_MCP_CONFIG;
|
|
411
|
+
fs.writeFileSync(settingsPath, JSON.stringify(cfg, null, 2));
|
|
412
|
+
return [{
|
|
413
|
+
agent: 'opencode',
|
|
414
|
+
event: 'session-start',
|
|
415
|
+
hookType: 'settings-json',
|
|
416
|
+
installPath: settingsPath,
|
|
417
|
+
command: SHADOW_BRAIN_MCP_CONFIG.command,
|
|
418
|
+
active: true,
|
|
419
|
+
installedAt: new Date(),
|
|
420
|
+
}];
|
|
421
|
+
}
|
|
422
|
+
attachRooCode(projectDir) {
|
|
423
|
+
const home = os.homedir();
|
|
424
|
+
const candidates = [
|
|
425
|
+
path.join(home, '.roocode', 'mcp.json'),
|
|
426
|
+
path.join(home, '.rovodev', 'mcp.json'),
|
|
427
|
+
];
|
|
428
|
+
const hooks = [];
|
|
429
|
+
for (const mcpPath of candidates) {
|
|
430
|
+
try {
|
|
431
|
+
fs.mkdirSync(path.dirname(mcpPath), { recursive: true });
|
|
432
|
+
this.upsertMcpJson(mcpPath);
|
|
433
|
+
hooks.push({
|
|
434
|
+
agent: 'roo-code',
|
|
435
|
+
event: 'session-start',
|
|
436
|
+
hookType: 'extension-config',
|
|
437
|
+
installPath: mcpPath,
|
|
438
|
+
command: SHADOW_BRAIN_MCP_CONFIG.command,
|
|
439
|
+
active: true,
|
|
440
|
+
installedAt: new Date(),
|
|
441
|
+
});
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
catch { /* try next */ }
|
|
445
|
+
}
|
|
446
|
+
return hooks;
|
|
447
|
+
}
|
|
448
|
+
attachAider(projectDir) {
|
|
449
|
+
const home = os.homedir();
|
|
450
|
+
const cfgPath = path.join(home, '.aider.conf.yml');
|
|
451
|
+
const briefingPath = path.join(home, '.aider.shadow-brain.md');
|
|
452
|
+
// Write the briefing wrapper file Aider will load via load_md
|
|
453
|
+
fs.writeFileSync(briefingPath, '# Shadow Brain\n\nThis file is rewritten on each Shadow Brain session-start hook.\n');
|
|
454
|
+
let cfg = '';
|
|
455
|
+
if (fs.existsSync(cfgPath)) {
|
|
456
|
+
cfg = fs.readFileSync(cfgPath, 'utf-8');
|
|
457
|
+
}
|
|
458
|
+
if (!cfg.includes('.aider.shadow-brain.md')) {
|
|
459
|
+
cfg += `\n# Shadow Brain auto-injected\nread:\n - ${briefingPath}\n`;
|
|
460
|
+
fs.writeFileSync(cfgPath, cfg);
|
|
461
|
+
}
|
|
462
|
+
return [{
|
|
463
|
+
agent: 'aider',
|
|
464
|
+
event: 'session-start',
|
|
465
|
+
hookType: 'config-file',
|
|
466
|
+
installPath: cfgPath,
|
|
467
|
+
command: SHADOW_BRAIN_HOOK_CMD,
|
|
468
|
+
active: true,
|
|
469
|
+
installedAt: new Date(),
|
|
470
|
+
}];
|
|
471
|
+
}
|
|
472
|
+
attachCopilot(projectDir) {
|
|
473
|
+
// GitHub Copilot reads .github/copilot-instructions.md as workspace context.
|
|
474
|
+
const githubDir = path.join(projectDir, '.github');
|
|
475
|
+
fs.mkdirSync(githubDir, { recursive: true });
|
|
476
|
+
const instrPath = path.join(githubDir, 'copilot-instructions.md');
|
|
477
|
+
if (!fs.existsSync(instrPath)) {
|
|
478
|
+
fs.writeFileSync(instrPath, this.copilotInstructionsTemplate(), 'utf-8');
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
// Append marker if not present
|
|
482
|
+
const existing = fs.readFileSync(instrPath, 'utf-8');
|
|
483
|
+
if (!existing.includes('Shadow Brain')) {
|
|
484
|
+
fs.appendFileSync(instrPath, `\n\n---\n\n${this.copilotInstructionsTemplate()}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return [{
|
|
488
|
+
agent: 'copilot',
|
|
489
|
+
event: 'session-start',
|
|
490
|
+
hookType: 'workspace-rule',
|
|
491
|
+
installPath: instrPath,
|
|
492
|
+
command: SHADOW_BRAIN_HOOK_CMD,
|
|
493
|
+
active: true,
|
|
494
|
+
installedAt: new Date(),
|
|
495
|
+
}];
|
|
496
|
+
}
|
|
497
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
498
|
+
/** Add Shadow Brain to an MCP-server-style JSON config without clobbering existing servers */
|
|
499
|
+
upsertMcpJson(jsonPath, key = 'mcpServers') {
|
|
500
|
+
let cfg = {};
|
|
501
|
+
if (fs.existsSync(jsonPath)) {
|
|
502
|
+
try {
|
|
503
|
+
cfg = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
cfg = {};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
cfg[key] ?? (cfg[key] = {});
|
|
510
|
+
cfg[key][SHADOW_BRAIN_MCP_NAME] = SHADOW_BRAIN_MCP_CONFIG;
|
|
511
|
+
fs.mkdirSync(path.dirname(jsonPath), { recursive: true });
|
|
512
|
+
fs.writeFileSync(jsonPath, JSON.stringify(cfg, null, 2));
|
|
513
|
+
}
|
|
514
|
+
findInstalledHooks(agent, projectDir) {
|
|
515
|
+
const home = os.homedir();
|
|
516
|
+
const candidates = [];
|
|
517
|
+
switch (agent) {
|
|
518
|
+
case 'claude-code':
|
|
519
|
+
candidates.push({ path: path.join(home, '.claude', 'settings.json'), hookType: 'settings-json' });
|
|
520
|
+
break;
|
|
521
|
+
case 'cursor':
|
|
522
|
+
candidates.push({ path: path.join(projectDir, '.cursor', 'rules', 'shadow-brain.md'), hookType: 'workspace-rule' }, { path: path.join(projectDir, '.cursor', 'mcp.json'), hookType: 'extension-config' });
|
|
523
|
+
break;
|
|
524
|
+
case 'cline':
|
|
525
|
+
candidates.push({ path: path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), hookType: 'extension-config' }, { path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), hookType: 'extension-config' }, { path: path.join(home, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), hookType: 'extension-config' }, { path: path.join(projectDir, '.clinerules'), hookType: 'workspace-rule' });
|
|
526
|
+
break;
|
|
527
|
+
case 'windsurf':
|
|
528
|
+
candidates.push({ path: path.join(projectDir, '.windsurfrules'), hookType: 'workspace-rule' }, { path: path.join(home, '.windsurf', 'mcp.json'), hookType: 'extension-config' });
|
|
529
|
+
break;
|
|
530
|
+
case 'codex':
|
|
531
|
+
candidates.push({ path: path.join(home, '.codex', 'config.json'), hookType: 'config-file' });
|
|
532
|
+
break;
|
|
533
|
+
case 'kilo-code':
|
|
534
|
+
candidates.push({ path: path.join(home, '.kilocode', 'settings.json'), hookType: 'settings-json' });
|
|
535
|
+
break;
|
|
536
|
+
case 'opencode':
|
|
537
|
+
candidates.push({ path: path.join(home, '.opencode', 'settings.json'), hookType: 'settings-json' });
|
|
538
|
+
break;
|
|
539
|
+
case 'roo-code':
|
|
540
|
+
candidates.push({ path: path.join(home, '.roocode', 'mcp.json'), hookType: 'extension-config' }, { path: path.join(home, '.rovodev', 'mcp.json'), hookType: 'extension-config' });
|
|
541
|
+
break;
|
|
542
|
+
case 'aider':
|
|
543
|
+
candidates.push({ path: path.join(home, '.aider.conf.yml'), hookType: 'config-file' }, { path: path.join(home, '.aider.shadow-brain.md'), hookType: 'workspace-rule' });
|
|
544
|
+
break;
|
|
545
|
+
case 'copilot':
|
|
546
|
+
candidates.push({ path: path.join(projectDir, '.github', 'copilot-instructions.md'), hookType: 'workspace-rule' });
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
return candidates
|
|
550
|
+
.filter(c => this.containsShadowBrain(c.path))
|
|
551
|
+
.map(c => ({
|
|
552
|
+
agent,
|
|
553
|
+
event: 'session-start',
|
|
554
|
+
hookType: c.hookType,
|
|
555
|
+
installPath: c.path,
|
|
556
|
+
command: c.hookType === 'workspace-rule' ? SHADOW_BRAIN_HOOK_CMD : SHADOW_BRAIN_MCP_CONFIG.command,
|
|
557
|
+
active: true,
|
|
558
|
+
installedAt: new Date(),
|
|
559
|
+
}));
|
|
560
|
+
}
|
|
561
|
+
containsShadowBrain(filePath) {
|
|
562
|
+
try {
|
|
563
|
+
return fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf-8').includes('shadow-brain');
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
removeShadowBrainBlock(filePath) {
|
|
570
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
571
|
+
const cleaned = content
|
|
572
|
+
.replace(/\n?# Shadow Brain auto-injected\nread:\n\s+- .+?\.aider\.shadow-brain\.md\n?/gs, '\n')
|
|
573
|
+
.replace(/\n?---\n\n## Shadow Brain Context[\s\S]*?(?=\n---|\n## |\s*$)/g, '\n')
|
|
574
|
+
.replace(/\n?# Shadow Brain Integration \([^)]+\)[\s\S]*?Run `shadow-brain status` for state\.\n?/g, '\n')
|
|
575
|
+
.replace(/\n?# === Shadow Brain Critical Alerts ===[\s\S]*?(?=\n# |\s*$)/g, '\n')
|
|
576
|
+
.trimEnd() + '\n';
|
|
577
|
+
if (cleaned.trim())
|
|
578
|
+
fs.writeFileSync(filePath, cleaned, 'utf-8');
|
|
579
|
+
else
|
|
580
|
+
fs.unlinkSync(filePath);
|
|
581
|
+
}
|
|
582
|
+
removeFromJsonConfig(jsonPath) {
|
|
583
|
+
let cfg = {};
|
|
584
|
+
try {
|
|
585
|
+
cfg = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (cfg.mcpServers)
|
|
591
|
+
delete cfg.mcpServers[SHADOW_BRAIN_MCP_NAME];
|
|
592
|
+
if (cfg.mcp_servers)
|
|
593
|
+
delete cfg.mcp_servers[SHADOW_BRAIN_MCP_NAME];
|
|
594
|
+
if (cfg.servers)
|
|
595
|
+
delete cfg.servers[SHADOW_BRAIN_MCP_NAME];
|
|
596
|
+
if (cfg.sessionStartHook === SHADOW_BRAIN_HOOK_CMD)
|
|
597
|
+
delete cfg.sessionStartHook;
|
|
598
|
+
if (cfg.session_start_command === SHADOW_BRAIN_HOOK_CMD)
|
|
599
|
+
delete cfg.session_start_command;
|
|
600
|
+
// Remove ANY Shadow Brain entry from hooks.SessionStart — handle every shape.
|
|
601
|
+
if (cfg.hooks?.SessionStart && Array.isArray(cfg.hooks.SessionStart)) {
|
|
602
|
+
cfg.hooks.SessionStart = cfg.hooks.SessionStart
|
|
603
|
+
.map((block) => {
|
|
604
|
+
if (!block || typeof block !== 'object')
|
|
605
|
+
return null;
|
|
606
|
+
// Well-formed { matcher, hooks[] } — strip our entries from the inner array
|
|
607
|
+
if (Array.isArray(block.hooks)) {
|
|
608
|
+
const filtered = block.hooks.filter((h) => !(h && typeof h.command === 'string' && h.command.includes('shadow-brain')));
|
|
609
|
+
if (!filtered.length)
|
|
610
|
+
return null;
|
|
611
|
+
return { ...block, hooks: filtered };
|
|
612
|
+
}
|
|
613
|
+
// Legacy flat entry — drop if it references shadow-brain
|
|
614
|
+
if (typeof block.command === 'string' && block.command.includes('shadow-brain'))
|
|
615
|
+
return null;
|
|
616
|
+
return block;
|
|
617
|
+
})
|
|
618
|
+
.filter((b) => b !== null);
|
|
619
|
+
// After stripping, run the repair once more so the file is always valid Claude schema
|
|
620
|
+
cfg.hooks.SessionStart = this.repairClaudeHookBlocks(cfg.hooks.SessionStart);
|
|
621
|
+
}
|
|
622
|
+
// Atomic write — prevents leaving a broken settings file
|
|
623
|
+
const tmp = jsonPath + '.tmp';
|
|
624
|
+
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2));
|
|
625
|
+
fs.renameSync(tmp, jsonPath);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Claude Code schema requires each SessionStart entry to have shape:
|
|
629
|
+
* { matcher: string, hooks: [{ type: 'command', command: '...' }] }
|
|
630
|
+
* Older Shadow Brain versions mistakenly wrote flat { name, command, timeout }
|
|
631
|
+
* entries directly, which broke Claude Code's settings loader. This function:
|
|
632
|
+
* - drops null/undefined/non-object entries
|
|
633
|
+
* - rewraps flat "legacy" entries into a valid { matcher, hooks } block
|
|
634
|
+
* - ensures every block.hooks is a valid array of { type:'command', command }
|
|
635
|
+
* - strips any nested malformed entries
|
|
636
|
+
*/
|
|
637
|
+
repairClaudeHookBlocks(blocks) {
|
|
638
|
+
if (!Array.isArray(blocks))
|
|
639
|
+
return [];
|
|
640
|
+
const out = [];
|
|
641
|
+
for (const block of blocks) {
|
|
642
|
+
if (block == null || typeof block !== 'object')
|
|
643
|
+
continue;
|
|
644
|
+
if (Array.isArray(block.hooks)) {
|
|
645
|
+
const cleanHooks = block.hooks
|
|
646
|
+
.filter((h) => h && typeof h === 'object')
|
|
647
|
+
.map((h) => ({
|
|
648
|
+
type: typeof h.type === 'string' ? h.type : 'command',
|
|
649
|
+
command: typeof h.command === 'string' ? h.command : '',
|
|
650
|
+
...(typeof h.timeout === 'number' ? { timeout: h.timeout } : {}),
|
|
651
|
+
}))
|
|
652
|
+
.filter((h) => h.command);
|
|
653
|
+
if (cleanHooks.length) {
|
|
654
|
+
out.push({ matcher: typeof block.matcher === 'string' ? block.matcher : '', hooks: cleanHooks });
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
else if (typeof block.command === 'string') {
|
|
658
|
+
out.push({
|
|
659
|
+
matcher: '',
|
|
660
|
+
hooks: [{ type: 'command', command: block.command, ...(typeof block.timeout === 'number' ? { timeout: block.timeout } : {}) }],
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return out;
|
|
665
|
+
}
|
|
666
|
+
cursorRuleTemplate() {
|
|
667
|
+
return `# Shadow Brain — Always-On Context Layer
|
|
668
|
+
|
|
669
|
+
This project is augmented by Shadow Brain (https://github.com/theihtisham/agent-shadow-brain).
|
|
670
|
+
|
|
671
|
+
At the start of every session, Shadow Brain auto-injects a briefing of:
|
|
672
|
+
- Recent project decisions
|
|
673
|
+
- Active tasks
|
|
674
|
+
- Cross-agent insights from Claude/Cline/Codex
|
|
675
|
+
- Known warnings and patterns
|
|
676
|
+
|
|
677
|
+
To check what Shadow Brain knows about this project: \`shadow-brain recall\`
|
|
678
|
+
To view the global brain stats: \`shadow-brain global stats\`
|
|
679
|
+
`;
|
|
680
|
+
}
|
|
681
|
+
genericRuleTemplate(agentName) {
|
|
682
|
+
return `# Shadow Brain Integration (${agentName})
|
|
683
|
+
|
|
684
|
+
Shadow Brain is active for this project. It maintains a singleton global brain
|
|
685
|
+
across all your AI coding agents — what one agent learns, all agents know.
|
|
686
|
+
|
|
687
|
+
Auto-injects context on session start. Run \`shadow-brain status\` for state.
|
|
688
|
+
`;
|
|
689
|
+
}
|
|
690
|
+
copilotInstructionsTemplate() {
|
|
691
|
+
return `## Shadow Brain Context
|
|
692
|
+
|
|
693
|
+
This workspace is monitored by Shadow Brain — a cross-agent intelligence layer.
|
|
694
|
+
When making suggestions, prefer patterns documented in the Shadow Brain memory at \`.shadow-brain/\` over generic patterns.
|
|
695
|
+
|
|
696
|
+
Project-specific decisions, conventions, and warnings are recorded in \`.shadow-brain/SHADOW_BRAIN.md\`.
|
|
697
|
+
`;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
let defaultInstaller = null;
|
|
701
|
+
export function getHookInstaller() {
|
|
702
|
+
if (!defaultInstaller)
|
|
703
|
+
defaultInstaller = new SessionHookInstaller();
|
|
704
|
+
return defaultInstaller;
|
|
705
|
+
}
|
|
706
|
+
//# sourceMappingURL=session-hooks.js.map
|