specmem-hardwicksoftware 3.7.9 → 3.7.11

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.
@@ -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": "🔍 Searching SpecMem...",
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": 5,
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": 60,
65
+ "timeout": 8,
68
66
  "env": {
69
- "SPECMEM_PROJECT_PATH": "${cwd}",
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
- "SPECMEM_SEARCH_LIMIT": "5",
71
+ "SPECMEM_PROJECT_PATH": "${cwd}",
72
+ "SPECMEM_SEARCH_LIMIT": "3",
73
73
  "SPECMEM_THRESHOLD": "0.25",
74
- "SPECMEM_MAX_CONTENT": "200"
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": 5,
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": 5,
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/use-code-pointers.cjs",
111
- "timeout": 3,
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/team-comms-enforcer.cjs",
119
- "timeout": 5,
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": 60,
119
+ "timeout": 8,
128
120
  "env": {
129
- "SPECMEM_PROJECT_PATH": "${cwd}",
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
- "SPECMEM_SEARCH_LIMIT": "5",
125
+ "SPECMEM_PROJECT_PATH": "${cwd}",
126
+ "SPECMEM_SEARCH_LIMIT": "3",
133
127
  "SPECMEM_THRESHOLD": "0.25",
134
- "SPECMEM_MAX_CONTENT": "200"
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/use-code-pointers.cjs",
145
- "timeout": 3,
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/team-comms-enforcer.cjs",
153
- "timeout": 5,
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": 60,
151
+ "timeout": 8,
162
152
  "env": {
163
- "SPECMEM_PROJECT_PATH": "${cwd}",
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
- "SPECMEM_SEARCH_LIMIT": "5",
157
+ "SPECMEM_PROJECT_PATH": "${cwd}",
158
+ "SPECMEM_SEARCH_LIMIT": "3",
167
159
  "SPECMEM_THRESHOLD": "0.25",
168
- "SPECMEM_MAX_CONTENT": "200"
160
+ "SPECMEM_MAX_CONTENT": "150"
169
161
  }
170
162
  }
171
163
  ]
172
164
  },
173
165
  {
174
- "matcher": "mcp__specmem__find_memory",
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": 5,
180
- "env": {
181
- "SPECMEM_PROJECT_PATH": "${cwd}"
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/team-comms-enforcer.cjs",
192
- "timeout": 5,
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": "Read",
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": "Bash",
213
+ "matcher": "mcp__specmem__find_memory",
242
214
  "hooks": [
243
215
  {
244
216
  "type": "command",
245
- "command": "node /root/.claude/hooks/agent-output-interceptor.js",
246
- "timeout": 3,
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": "TaskOutput",
224
+ "matcher": "mcp__specmem__find_code_pointers",
262
225
  "hooks": [
263
226
  {
264
227
  "type": "command",
265
- "command": "node /root/.claude/hooks/agent-output-interceptor.js",
266
- "timeout": 3,
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": "🚀 Loading SpecMem context...",
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
- "ask": [],
442
- "defaultMode": "acceptEdits"
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
- function hasActiveAgents() {
173
- try {
174
- // CRITICAL: Only enforce on specmem-enabled projects
175
- if (!isSpecmemProject()) return false;
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
- if (!fs.existsSync(ACTIVE_AGENTS_FILE)) return false;
178
- const agents = JSON.parse(fs.readFileSync(ACTIVE_AGENTS_FILE, 'utf8'));
179
- const now = Date.now();
180
- // Only count agents spawned in the last 10 minutes
181
- for (const agent of Object.values(agents)) {
182
- if (now - agent.spawnedAt < 600000) return true;
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
- return false;
185
- } catch (e) {
186
- return false;
187
- }
216
+ } catch {}
217
+
218
+ return false;
188
219
  }
189
220
 
190
- function isAgentSession(sessionId) {
191
- // Check if THIS session is a spawned agent (vs main window)
192
- // Main should NEVER be blocked - only enforce on subagents
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
- return false;
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
- // MAIN WINDOW CHECK - ONLY ENFORCE ON AGENT SESSIONS
278
- // Main window should NEVER be blocked, only subagents
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 (!hasActiveAgents() || !isAgentSession(sessionId)) {
281
- // No agents running OR this is the main window - pass through
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
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * fileReadWorker.js - Worker thread for parallel file I/O
3
+ *
4
+ * Used by the ingestion pipeline to parallelize file reading across CPU cores.
5
+ * Each worker reads files, detects if binary, computes hashes, and returns results.
6
+ *
7
+ * Protocol:
8
+ * - Receives: { files: string[], maxFileSizeBytes: number }
9
+ * - Returns: { results: FileReadResult[] }
10
+ */
11
+ import { parentPort, workerData } from 'worker_threads';
12
+ import * as fs from 'fs/promises';
13
+ import * as path from 'path';
14
+ import * as crypto from 'crypto';
15
+
16
+ async function isBinaryBuffer(buffer) {
17
+ // Check first 512 bytes for null bytes
18
+ const checkLength = Math.min(buffer.length, 512);
19
+ for (let i = 0; i < checkLength; i++) {
20
+ if (buffer[i] === 0) return true;
21
+ }
22
+ return false;
23
+ }
24
+
25
+ function hashContent(content) {
26
+ return crypto.createHash('sha256').update(content).digest('hex');
27
+ }
28
+
29
+ async function processFiles(files, rootPath, maxFileSizeBytes) {
30
+ const results = [];
31
+
32
+ for (const filePath of files) {
33
+ try {
34
+ const stats = await fs.stat(filePath);
35
+
36
+ if (stats.size > maxFileSizeBytes) {
37
+ results.push({ filePath, skipped: true, reason: 'too_large' });
38
+ continue;
39
+ }
40
+
41
+ // Read raw buffer first for binary check
42
+ const rawBuffer = await fs.readFile(filePath);
43
+ if (await isBinaryBuffer(rawBuffer)) {
44
+ results.push({ filePath, skipped: true, reason: 'binary' });
45
+ continue;
46
+ }
47
+
48
+ const content = rawBuffer.toString('utf-8');
49
+ const contentHash = hashContent(content);
50
+ const relativePath = path.relative(rootPath, filePath);
51
+
52
+ results.push({
53
+ filePath,
54
+ relativePath,
55
+ skipped: false,
56
+ content,
57
+ contentHash,
58
+ sizeBytes: stats.size,
59
+ lineCount: content.split('\n').length,
60
+ charCount: content.length,
61
+ lastModified: stats.mtime.toISOString(),
62
+ fileName: path.basename(filePath),
63
+ extension: path.extname(filePath).toLowerCase()
64
+ });
65
+ } catch (err) {
66
+ results.push({
67
+ filePath,
68
+ skipped: true,
69
+ reason: 'error',
70
+ error: err.message || String(err)
71
+ });
72
+ }
73
+ }
74
+
75
+ return results;
76
+ }
77
+
78
+ // Worker entry point
79
+ if (parentPort) {
80
+ parentPort.on('message', async (msg) => {
81
+ if (msg.type === 'process') {
82
+ try {
83
+ const results = await processFiles(msg.files, msg.rootPath, msg.maxFileSizeBytes);
84
+ parentPort.postMessage({ type: 'results', results });
85
+ } catch (err) {
86
+ parentPort.postMessage({ type: 'error', error: err.message || String(err) });
87
+ }
88
+ }
89
+ });
90
+ }