specmem-hardwicksoftware 3.7.10 → 3.7.12
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/bootstrap.cjs +43 -23
- package/claude-hooks/settings.json +73 -123
- package/claude-hooks/team-comms-enforcer.cjs +66 -55
- package/dist/codebase/fileReadWorker.js +90 -0
- package/dist/codebase/ingestion.js +231 -50
- package/dist/index.js +32 -0
- package/dist/mcp/embeddingServerManager.js +154 -0
- package/dist/team-members/teamCommsService.js +14 -0
- package/embedding-sandbox/frankenstein-embeddings.py +30 -6
- package/package.json +1 -1
- package/scripts/specmem-init.cjs +483 -160
package/bootstrap.cjs
CHANGED
|
@@ -357,8 +357,10 @@ function killStaleBootstraps() {
|
|
|
357
357
|
const currentPid = process.pid;
|
|
358
358
|
const projectPath = process.env.SPECMEM_PROJECT_PATH;
|
|
359
359
|
|
|
360
|
+
if (!projectPath) return; // Can't scope without a project path
|
|
361
|
+
|
|
360
362
|
try {
|
|
361
|
-
// Find all node processes running bootstrap.cjs
|
|
363
|
+
// Find all node processes running bootstrap.cjs
|
|
362
364
|
const result = execSync(
|
|
363
365
|
`ps aux | grep -E "node.*bootstrap\\.cjs" | grep -v grep | awk '{print $2}'`,
|
|
364
366
|
{ encoding: 'utf8', timeout: 5000 }
|
|
@@ -371,37 +373,55 @@ function killStaleBootstraps() {
|
|
|
371
373
|
const pid = parseInt(pidStr.trim(), 10);
|
|
372
374
|
if (isNaN(pid) || pid === currentPid) continue;
|
|
373
375
|
|
|
374
|
-
// Check if this process
|
|
376
|
+
// Check if this process serves the SAME project by reading its
|
|
377
|
+
// SPECMEM_PROJECT_PATH env var — this is the definitive project identity,
|
|
378
|
+
// not cwd which can differ from the actual project being served.
|
|
375
379
|
try {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
startupLog(`CLEANUP: Skipping young process ${pid} (age: ${Math.round(processAge/1000)}s < 30s)`);
|
|
385
|
-
continue;
|
|
380
|
+
let otherProjectPath = null;
|
|
381
|
+
try {
|
|
382
|
+
const environ = fs.readFileSync(`/proc/${pid}/environ`, 'utf8');
|
|
383
|
+
const envVars = environ.split('\0');
|
|
384
|
+
for (const v of envVars) {
|
|
385
|
+
if (v.startsWith('SPECMEM_PROJECT_PATH=')) {
|
|
386
|
+
otherProjectPath = v.substring('SPECMEM_PROJECT_PATH='.length);
|
|
387
|
+
break;
|
|
386
388
|
}
|
|
387
|
-
} catch (e) {
|
|
388
|
-
// Can't determine age, skip to be safe
|
|
389
|
-
continue;
|
|
390
389
|
}
|
|
390
|
+
} catch (e) {
|
|
391
|
+
// Can't read environ (permissions), fall back to cwd check
|
|
392
|
+
try {
|
|
393
|
+
otherProjectPath = fs.readlinkSync(`/proc/${pid}/cwd`);
|
|
394
|
+
} catch { continue; } // Can't determine project — skip to be safe
|
|
395
|
+
}
|
|
391
396
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
397
|
+
if (!otherProjectPath) continue; // Can't determine — skip
|
|
398
|
+
|
|
399
|
+
// STRICT: Only kill if EXACT same project path
|
|
400
|
+
if (otherProjectPath !== projectPath) continue;
|
|
401
|
+
|
|
402
|
+
// Only kill processes older than 30 seconds to avoid race conditions
|
|
403
|
+
try {
|
|
404
|
+
const stat = fs.statSync(`/proc/${pid}`);
|
|
405
|
+
const processAge = Date.now() - stat.ctimeMs;
|
|
406
|
+
if (processAge < 30000) {
|
|
407
|
+
startupLog(`CLEANUP: Skipping young process ${pid} (age: ${Math.round(processAge/1000)}s < 30s)`);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
} catch (e) {
|
|
411
|
+
// Can't determine age, skip to be safe
|
|
412
|
+
continue;
|
|
398
413
|
}
|
|
414
|
+
|
|
415
|
+
startupLog(`CLEANUP: Killing stale bootstrap for same project ${pid} (project: ${otherProjectPath})`);
|
|
416
|
+
process.kill(pid, 'SIGTERM');
|
|
417
|
+
setTimeout(() => {
|
|
418
|
+
try { process.kill(pid, 'SIGKILL'); } catch (e) { /* already dead */ }
|
|
419
|
+
}, 1000);
|
|
399
420
|
} catch (e) {
|
|
400
|
-
// Process might have died or we can't read its
|
|
421
|
+
// Process might have died or we can't read its info - skip
|
|
401
422
|
}
|
|
402
423
|
}
|
|
403
424
|
} catch (e) {
|
|
404
|
-
// Cleanup is best-effort - don't fail startup if it doesn't work
|
|
405
425
|
startupLog(`CLEANUP: Could not check for stale processes: ${e.message}`);
|
|
406
426
|
}
|
|
407
427
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"type": "command",
|
|
20
20
|
"command": "node /root/.claude/hooks/specmem-drilldown-hook.js",
|
|
21
21
|
"timeout": 30,
|
|
22
|
-
"statusMessage": "
|
|
22
|
+
"statusMessage": "Searching SpecMem...",
|
|
23
23
|
"env": {
|
|
24
24
|
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
25
25
|
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
@@ -56,22 +56,22 @@
|
|
|
56
56
|
{
|
|
57
57
|
"type": "command",
|
|
58
58
|
"command": "node /root/.claude/hooks/team-comms-enforcer.cjs",
|
|
59
|
-
"timeout":
|
|
60
|
-
"env": {
|
|
61
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
62
|
-
}
|
|
59
|
+
"timeout": 2,
|
|
60
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
63
61
|
},
|
|
64
62
|
{
|
|
65
63
|
"type": "command",
|
|
66
64
|
"command": "node /root/.claude/hooks/smart-context-hook.js",
|
|
67
|
-
"timeout":
|
|
65
|
+
"timeout": 8,
|
|
68
66
|
"env": {
|
|
69
|
-
"
|
|
67
|
+
"SPECMEM_HOME": "/root/.specmem",
|
|
68
|
+
"SPECMEM_PKG": "/specmem",
|
|
70
69
|
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
71
70
|
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock",
|
|
72
|
-
"
|
|
71
|
+
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
72
|
+
"SPECMEM_SEARCH_LIMIT": "3",
|
|
73
73
|
"SPECMEM_THRESHOLD": "0.25",
|
|
74
|
-
"SPECMEM_MAX_CONTENT": "
|
|
74
|
+
"SPECMEM_MAX_CONTENT": "150"
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
]
|
|
@@ -82,10 +82,8 @@
|
|
|
82
82
|
{
|
|
83
83
|
"type": "command",
|
|
84
84
|
"command": "node /root/.claude/hooks/team-comms-enforcer.cjs",
|
|
85
|
-
"timeout":
|
|
86
|
-
"env": {
|
|
87
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
88
|
-
}
|
|
85
|
+
"timeout": 2,
|
|
86
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
89
87
|
}
|
|
90
88
|
]
|
|
91
89
|
},
|
|
@@ -95,10 +93,8 @@
|
|
|
95
93
|
{
|
|
96
94
|
"type": "command",
|
|
97
95
|
"command": "node /root/.claude/hooks/team-comms-enforcer.cjs",
|
|
98
|
-
"timeout":
|
|
99
|
-
"env": {
|
|
100
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
101
|
-
}
|
|
96
|
+
"timeout": 2,
|
|
97
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
102
98
|
}
|
|
103
99
|
]
|
|
104
100
|
},
|
|
@@ -107,31 +103,29 @@
|
|
|
107
103
|
"hooks": [
|
|
108
104
|
{
|
|
109
105
|
"type": "command",
|
|
110
|
-
"command": "node /root/.claude/hooks/
|
|
111
|
-
"timeout":
|
|
112
|
-
"env": {
|
|
113
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
114
|
-
}
|
|
106
|
+
"command": "node /root/.claude/hooks/team-comms-enforcer.cjs",
|
|
107
|
+
"timeout": 2,
|
|
108
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
115
109
|
},
|
|
116
110
|
{
|
|
117
111
|
"type": "command",
|
|
118
|
-
"command": "node /root/.claude/hooks/
|
|
119
|
-
"timeout":
|
|
120
|
-
"env": {
|
|
121
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
122
|
-
}
|
|
112
|
+
"command": "node /root/.claude/hooks/use-code-pointers.cjs",
|
|
113
|
+
"timeout": 3,
|
|
114
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
123
115
|
},
|
|
124
116
|
{
|
|
125
117
|
"type": "command",
|
|
126
118
|
"command": "node /root/.claude/hooks/smart-context-hook.js",
|
|
127
|
-
"timeout":
|
|
119
|
+
"timeout": 8,
|
|
128
120
|
"env": {
|
|
129
|
-
"
|
|
121
|
+
"SPECMEM_HOME": "/root/.specmem",
|
|
122
|
+
"SPECMEM_PKG": "/specmem",
|
|
130
123
|
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
131
124
|
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock",
|
|
132
|
-
"
|
|
125
|
+
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
126
|
+
"SPECMEM_SEARCH_LIMIT": "3",
|
|
133
127
|
"SPECMEM_THRESHOLD": "0.25",
|
|
134
|
-
"SPECMEM_MAX_CONTENT": "
|
|
128
|
+
"SPECMEM_MAX_CONTENT": "150"
|
|
135
129
|
}
|
|
136
130
|
}
|
|
137
131
|
]
|
|
@@ -141,58 +135,46 @@
|
|
|
141
135
|
"hooks": [
|
|
142
136
|
{
|
|
143
137
|
"type": "command",
|
|
144
|
-
"command": "node /root/.claude/hooks/
|
|
145
|
-
"timeout":
|
|
146
|
-
"env": {
|
|
147
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
148
|
-
}
|
|
138
|
+
"command": "node /root/.claude/hooks/team-comms-enforcer.cjs",
|
|
139
|
+
"timeout": 2,
|
|
140
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
149
141
|
},
|
|
150
142
|
{
|
|
151
143
|
"type": "command",
|
|
152
|
-
"command": "node /root/.claude/hooks/
|
|
153
|
-
"timeout":
|
|
154
|
-
"env": {
|
|
155
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
156
|
-
}
|
|
144
|
+
"command": "node /root/.claude/hooks/use-code-pointers.cjs",
|
|
145
|
+
"timeout": 3,
|
|
146
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
157
147
|
},
|
|
158
148
|
{
|
|
159
149
|
"type": "command",
|
|
160
150
|
"command": "node /root/.claude/hooks/smart-context-hook.js",
|
|
161
|
-
"timeout":
|
|
151
|
+
"timeout": 8,
|
|
162
152
|
"env": {
|
|
163
|
-
"
|
|
153
|
+
"SPECMEM_HOME": "/root/.specmem",
|
|
154
|
+
"SPECMEM_PKG": "/specmem",
|
|
164
155
|
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
165
156
|
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock",
|
|
166
|
-
"
|
|
157
|
+
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
158
|
+
"SPECMEM_SEARCH_LIMIT": "3",
|
|
167
159
|
"SPECMEM_THRESHOLD": "0.25",
|
|
168
|
-
"SPECMEM_MAX_CONTENT": "
|
|
160
|
+
"SPECMEM_MAX_CONTENT": "150"
|
|
169
161
|
}
|
|
170
162
|
}
|
|
171
163
|
]
|
|
172
164
|
},
|
|
173
165
|
{
|
|
174
|
-
"matcher": "
|
|
166
|
+
"matcher": "Bash",
|
|
175
167
|
"hooks": [
|
|
176
168
|
{
|
|
177
169
|
"type": "command",
|
|
178
170
|
"command": "node /root/.claude/hooks/team-comms-enforcer.cjs",
|
|
179
|
-
"timeout":
|
|
180
|
-
"env": {
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
]
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
"matcher": "mcp__specmem__find_code_pointers",
|
|
188
|
-
"hooks": [
|
|
171
|
+
"timeout": 2,
|
|
172
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
173
|
+
},
|
|
189
174
|
{
|
|
190
175
|
"type": "command",
|
|
191
|
-
"command": "node /root/.claude/hooks/
|
|
192
|
-
"timeout":
|
|
193
|
-
"env": {
|
|
194
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
195
|
-
}
|
|
176
|
+
"command": "node /root/.claude/hooks/agent-output-interceptor.js",
|
|
177
|
+
"timeout": 3
|
|
196
178
|
}
|
|
197
179
|
]
|
|
198
180
|
},
|
|
@@ -218,62 +200,34 @@
|
|
|
218
200
|
]
|
|
219
201
|
},
|
|
220
202
|
{
|
|
221
|
-
"matcher": "
|
|
203
|
+
"matcher": "TaskOutput",
|
|
222
204
|
"hooks": [
|
|
223
205
|
{
|
|
224
206
|
"type": "command",
|
|
225
207
|
"command": "node /root/.claude/hooks/agent-output-interceptor.js",
|
|
226
|
-
"timeout": 3
|
|
227
|
-
"env": {
|
|
228
|
-
"SPECMEM_HOME": "/root/.specmem",
|
|
229
|
-
"SPECMEM_PKG": "/specmem",
|
|
230
|
-
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
231
|
-
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock",
|
|
232
|
-
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
233
|
-
"SPECMEM_SEARCH_LIMIT": "5",
|
|
234
|
-
"SPECMEM_THRESHOLD": "0.30",
|
|
235
|
-
"SPECMEM_MAX_CONTENT": "200"
|
|
236
|
-
}
|
|
208
|
+
"timeout": 3
|
|
237
209
|
}
|
|
238
210
|
]
|
|
239
211
|
},
|
|
240
212
|
{
|
|
241
|
-
"matcher": "
|
|
213
|
+
"matcher": "mcp__specmem__find_memory",
|
|
242
214
|
"hooks": [
|
|
243
215
|
{
|
|
244
216
|
"type": "command",
|
|
245
|
-
"command": "node /root/.claude/hooks/
|
|
246
|
-
"timeout":
|
|
247
|
-
"env": {
|
|
248
|
-
"SPECMEM_HOME": "/root/.specmem",
|
|
249
|
-
"SPECMEM_PKG": "/specmem",
|
|
250
|
-
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
251
|
-
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock",
|
|
252
|
-
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
253
|
-
"SPECMEM_SEARCH_LIMIT": "5",
|
|
254
|
-
"SPECMEM_THRESHOLD": "0.30",
|
|
255
|
-
"SPECMEM_MAX_CONTENT": "200"
|
|
256
|
-
}
|
|
217
|
+
"command": "node /root/.claude/hooks/team-comms-enforcer.cjs",
|
|
218
|
+
"timeout": 2,
|
|
219
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
257
220
|
}
|
|
258
221
|
]
|
|
259
222
|
},
|
|
260
223
|
{
|
|
261
|
-
"matcher": "
|
|
224
|
+
"matcher": "mcp__specmem__find_code_pointers",
|
|
262
225
|
"hooks": [
|
|
263
226
|
{
|
|
264
227
|
"type": "command",
|
|
265
|
-
"command": "node /root/.claude/hooks/
|
|
266
|
-
"timeout":
|
|
267
|
-
"env": {
|
|
268
|
-
"SPECMEM_HOME": "/root/.specmem",
|
|
269
|
-
"SPECMEM_PKG": "/specmem",
|
|
270
|
-
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
271
|
-
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock",
|
|
272
|
-
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
273
|
-
"SPECMEM_SEARCH_LIMIT": "5",
|
|
274
|
-
"SPECMEM_THRESHOLD": "0.30",
|
|
275
|
-
"SPECMEM_MAX_CONTENT": "200"
|
|
276
|
-
}
|
|
228
|
+
"command": "node /root/.claude/hooks/team-comms-enforcer.cjs",
|
|
229
|
+
"timeout": 2,
|
|
230
|
+
"env": { "SPECMEM_PROJECT_PATH": "${cwd}" }
|
|
277
231
|
}
|
|
278
232
|
]
|
|
279
233
|
}
|
|
@@ -285,7 +239,7 @@
|
|
|
285
239
|
"type": "command",
|
|
286
240
|
"command": "node /root/.claude/hooks/specmem-session-start.cjs",
|
|
287
241
|
"timeout": 30,
|
|
288
|
-
"statusMessage": "
|
|
242
|
+
"statusMessage": "Loading SpecMem context...",
|
|
289
243
|
"env": {
|
|
290
244
|
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
291
245
|
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
@@ -308,13 +262,19 @@
|
|
|
308
262
|
"env": {
|
|
309
263
|
"SPECMEM_PROJECT_PATH": "${cwd}",
|
|
310
264
|
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
311
|
-
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock"
|
|
312
|
-
"SPECMEM_SEARCH_LIMIT": "5",
|
|
313
|
-
"SPECMEM_THRESHOLD": "0.25",
|
|
314
|
-
"SPECMEM_MAX_CONTENT": "300"
|
|
265
|
+
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock"
|
|
315
266
|
}
|
|
316
267
|
}
|
|
317
268
|
]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"hooks": [
|
|
272
|
+
{
|
|
273
|
+
"type": "command",
|
|
274
|
+
"command": "node /root/.claude/hooks/specmem-drilldown-hook.js",
|
|
275
|
+
"timeout": 15
|
|
276
|
+
}
|
|
277
|
+
]
|
|
318
278
|
}
|
|
319
279
|
],
|
|
320
280
|
"PostToolUse": [
|
|
@@ -331,10 +291,7 @@
|
|
|
331
291
|
"SPECMEM_PKG": "/specmem",
|
|
332
292
|
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
333
293
|
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock",
|
|
334
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
335
|
-
"SPECMEM_SEARCH_LIMIT": "5",
|
|
336
|
-
"SPECMEM_THRESHOLD": "0.30",
|
|
337
|
-
"SPECMEM_MAX_CONTENT": "200"
|
|
294
|
+
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
338
295
|
}
|
|
339
296
|
}
|
|
340
297
|
]
|
|
@@ -387,10 +344,7 @@
|
|
|
387
344
|
"SPECMEM_PKG": "/specmem",
|
|
388
345
|
"SPECMEM_RUN_DIR": "${cwd}/specmem/sockets",
|
|
389
346
|
"SPECMEM_EMBEDDING_SOCKET": "${cwd}/specmem/sockets/embeddings.sock",
|
|
390
|
-
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
391
|
-
"SPECMEM_SEARCH_LIMIT": "5",
|
|
392
|
-
"SPECMEM_THRESHOLD": "0.30",
|
|
393
|
-
"SPECMEM_MAX_CONTENT": "200"
|
|
347
|
+
"SPECMEM_PROJECT_PATH": "${cwd}"
|
|
394
348
|
}
|
|
395
349
|
}
|
|
396
350
|
]
|
|
@@ -423,23 +377,19 @@
|
|
|
423
377
|
},
|
|
424
378
|
"permissions": {
|
|
425
379
|
"allow": [
|
|
426
|
-
"*",
|
|
427
380
|
"mcp__specmem__*",
|
|
428
381
|
"Skill(specmem)",
|
|
429
382
|
"Skill(specmem-*)",
|
|
383
|
+
"Read",
|
|
430
384
|
"Read(*)",
|
|
385
|
+
"Grep",
|
|
431
386
|
"Grep(*)",
|
|
387
|
+
"Glob",
|
|
432
388
|
"Glob(*)",
|
|
433
389
|
"Bash",
|
|
434
|
-
"Read",
|
|
435
390
|
"Write",
|
|
436
|
-
"Edit"
|
|
437
|
-
"Glob",
|
|
438
|
-
"Grep"
|
|
391
|
+
"Edit"
|
|
439
392
|
],
|
|
440
|
-
"deny": []
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
},
|
|
444
|
-
"model": "opus"
|
|
445
|
-
}
|
|
393
|
+
"deny": []
|
|
394
|
+
}
|
|
395
|
+
}
|
|
@@ -29,6 +29,20 @@ const fs = require('fs');
|
|
|
29
29
|
const path = require('path');
|
|
30
30
|
const crypto = require('crypto');
|
|
31
31
|
|
|
32
|
+
// AGENT DETECTION: Use shared is-agent.cjs for reliable env-var-based detection
|
|
33
|
+
// This checks CLAUDE_SUBAGENT, CLAUDE_AGENT_ID, SPECMEM_AGENT_MODE, etc.
|
|
34
|
+
let isAgentFn;
|
|
35
|
+
try {
|
|
36
|
+
isAgentFn = require('./is-agent.cjs').isAgent;
|
|
37
|
+
} catch {
|
|
38
|
+
// Fallback: try from specmem package
|
|
39
|
+
try {
|
|
40
|
+
isAgentFn = require('/usr/lib/node_modules/specmem-hardwicksoftware/claude-hooks/is-agent.cjs').isAgent;
|
|
41
|
+
} catch {
|
|
42
|
+
isAgentFn = () => false; // Can't detect agents, skip enforcement
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
32
46
|
// Project paths - CRITICAL: Use cwd() FIRST, not inherited env vars
|
|
33
47
|
// This prevents cross-project enforcement (specmem agents blocking /newServer)
|
|
34
48
|
const _projectPath = process.cwd(); // ALWAYS use current working directory
|
|
@@ -169,66 +183,55 @@ function isSpecmemProject() {
|
|
|
169
183
|
return indicators.some(p => fs.existsSync(p));
|
|
170
184
|
}
|
|
171
185
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Detect if this hook is running inside an agent context.
|
|
188
|
+
*
|
|
189
|
+
* THREE detection methods (any one = agent):
|
|
190
|
+
* 1. Environment vars: CLAUDE_SUBAGENT, CLAUDE_AGENT_ID, SPECMEM_AGENT_MODE, etc.
|
|
191
|
+
* (set by Claude Code for subagents, or by deployTeamMember for team members)
|
|
192
|
+
* 2. Subagent tracking: agents.json written by subagent-loading-hook.js on SubagentStart
|
|
193
|
+
* has active agents with no endTime = currently running subagent
|
|
194
|
+
* 3. Active agents file: active-agents.json written by agent-loading-hook.js
|
|
195
|
+
* has recently spawned agents
|
|
196
|
+
*
|
|
197
|
+
* CRITICAL: session_id is NOT reliable because subagents share the parent's session_id.
|
|
198
|
+
*/
|
|
199
|
+
function isRunningAsAgent() {
|
|
200
|
+
// Method 1: Environment variable detection (most reliable)
|
|
201
|
+
if (isAgentFn()) return true;
|
|
176
202
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
203
|
+
// Method 2: Check subagent tracking (from subagent-loading-hook.js SubagentStart)
|
|
204
|
+
try {
|
|
205
|
+
const agentsFile = `${PROJECT_TMP_DIR}/agents.json`;
|
|
206
|
+
if (fs.existsSync(agentsFile)) {
|
|
207
|
+
const data = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
for (const agent of Object.values(data.agents || {})) {
|
|
210
|
+
// Agent started within last 10 min AND has no endTime = still running
|
|
211
|
+
if (!agent.endTime && agent.startTime && (now - agent.startTime < 600000)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
183
215
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
216
|
+
} catch {}
|
|
217
|
+
|
|
218
|
+
return false;
|
|
188
219
|
}
|
|
189
220
|
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
// FIX: Since sessionIds aren't stored when agents spawn, we need a different approach:
|
|
195
|
-
// 1. Check if this is explicitly the MAIN session (stored in main-session.json)
|
|
196
|
-
// 2. If not main AND agents are active, treat as agent
|
|
221
|
+
function isSpecmemEnabled() {
|
|
222
|
+
if (!isSpecmemProject()) return false;
|
|
223
|
+
// Check that enforcement makes sense (agents exist or we are one)
|
|
224
|
+
if (isAgentFn()) return true;
|
|
197
225
|
try {
|
|
226
|
+
// Check active-agents.json for recently spawned agents
|
|
198
227
|
if (!fs.existsSync(ACTIVE_AGENTS_FILE)) return false;
|
|
199
228
|
const agents = JSON.parse(fs.readFileSync(ACTIVE_AGENTS_FILE, 'utf8'));
|
|
200
|
-
|
|
201
|
-
// Check if sessionId is explicitly registered (legacy check)
|
|
202
|
-
for (const [agentId, agent] of Object.entries(agents)) {
|
|
203
|
-
if (agent.sessionId === sessionId || agentId === sessionId) {
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// NEW: Check if this is the MAIN session
|
|
209
|
-
const mainSessionFile = `${PROJECT_TMP_DIR}/main-session.json`;
|
|
210
|
-
if (fs.existsSync(mainSessionFile)) {
|
|
211
|
-
const mainData = JSON.parse(fs.readFileSync(mainSessionFile, 'utf8'));
|
|
212
|
-
if (mainData.sessionId === sessionId) {
|
|
213
|
-
return false; // This IS the main session, not an agent
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// If agents are active and we're not the main session, assume we're an agent
|
|
218
|
-
// This catches spawned agents whose sessionIds weren't captured at spawn time
|
|
219
229
|
const now = Date.now();
|
|
220
230
|
for (const agent of Object.values(agents)) {
|
|
221
|
-
if (now - agent.spawnedAt < 600000)
|
|
222
|
-
// There ARE active agents, and we're not the main session
|
|
223
|
-
// So we must be one of the agents
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
231
|
+
if (now - agent.spawnedAt < 600000) return true;
|
|
226
232
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
} catch (e) {
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
233
|
+
} catch {}
|
|
234
|
+
return false;
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
// ============================================================================
|
|
@@ -258,7 +261,8 @@ function allowWithReminder(reminder) {
|
|
|
258
261
|
// MAIN HOOK
|
|
259
262
|
// ============================================================================
|
|
260
263
|
|
|
261
|
-
// FAST TIMEOUT - Never block main
|
|
264
|
+
// FAST TIMEOUT - Never block main Claude
|
|
265
|
+
// If stdin takes too long, bail and allow the tool call
|
|
262
266
|
setTimeout(() => {
|
|
263
267
|
console.log(JSON.stringify({ continue: true }));
|
|
264
268
|
process.exit(0);
|
|
@@ -274,11 +278,18 @@ process.stdin.on('end', () => {
|
|
|
274
278
|
const sessionId = data.session_id || 'unknown';
|
|
275
279
|
|
|
276
280
|
// ========================================================================
|
|
277
|
-
//
|
|
278
|
-
//
|
|
281
|
+
// AGENT DETECTION - ONLY ENFORCE ON AGENT SESSIONS
|
|
282
|
+
// Uses env vars (CLAUDE_SUBAGENT, CLAUDE_AGENT_ID, etc.) + subagent tracking
|
|
283
|
+
// Main Claude window is NEVER blocked, only subagents/team members
|
|
279
284
|
// ========================================================================
|
|
280
|
-
if (!
|
|
281
|
-
//
|
|
285
|
+
if (!isRunningAsAgent()) {
|
|
286
|
+
// Not an agent - pass through immediately
|
|
287
|
+
console.log(JSON.stringify({ continue: true }));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Verify this is a specmem-enabled project
|
|
292
|
+
if (!isSpecmemProject()) {
|
|
282
293
|
console.log(JSON.stringify({ continue: true }));
|
|
283
294
|
return;
|
|
284
295
|
}
|