specmem-hardwicksoftware 3.7.35 → 3.7.36

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +11 -15
  3. package/bin/specmem-console.cjs +839 -51
  4. package/claude-hooks/agent-chooser-hook.js +6 -6
  5. package/claude-hooks/agent-loading-hook.cjs +16 -16
  6. package/claude-hooks/agent-loading-hook.js +18 -18
  7. package/claude-hooks/agent-type-matcher.js +1 -1
  8. package/claude-hooks/background-completion-silencer.js +1 -1
  9. package/claude-hooks/file-claim-enforcer.cjs +37 -36
  10. package/claude-hooks/output-cleaner.cjs +1 -1
  11. package/claude-hooks/settings.json +27 -3
  12. package/claude-hooks/specmem-search-enforcer.cjs +2 -11
  13. package/claude-hooks/specmem-team-member-inject.js +1 -1
  14. package/claude-hooks/specmem-unified-hook.py +1 -1
  15. package/claude-hooks/subagent-loading-hook.cjs +1 -1
  16. package/claude-hooks/task-progress-hook.cjs +7 -7
  17. package/claude-hooks/task-progress-hook.js +3 -3
  18. package/claude-hooks/team-comms-enforcer.cjs +49 -47
  19. package/dist/claude-sessions/sessionParser.js +5 -0
  20. package/dist/codebase/codebaseIndexer.js +48 -17
  21. package/dist/codebase/exclusions.js +3 -4
  22. package/dist/codebase/index.js +4 -0
  23. package/dist/codebase/pdfExtractor.js +298 -0
  24. package/dist/dashboard/api/taskTeamMembers.js +2 -2
  25. package/dist/db/bigBrainMigrations.js +29 -0
  26. package/dist/hooks/hookManager.js +4 -4
  27. package/dist/hooks/teamFramingCli.js +1 -1
  28. package/dist/hooks/teamMemberPrepromptHook.js +5 -5
  29. package/dist/init/claudeConfigInjector.js +2 -2
  30. package/dist/mcp/compactionProxy.js +834 -186
  31. package/dist/mcp/compactionProxyDaemon.js +112 -37
  32. package/dist/mcp/contextVault.js +439 -0
  33. package/dist/mcp/embeddingServerManager.js +61 -1
  34. package/dist/mcp/mcpProtocolHandler.js +6 -1
  35. package/dist/mcp/miniCOTServerManager.js +82 -8
  36. package/dist/mcp/specMemServer.js +45 -10
  37. package/dist/mcp/toolRegistry.js +6 -0
  38. package/dist/startup/startupIndexing.js +14 -0
  39. package/dist/team-members/taskOrchestrator.js +3 -3
  40. package/dist/team-members/taskTeamMemberLogger.js +2 -2
  41. package/dist/tools/goofy/deployTeamMember.js +3 -3
  42. package/dist/tools/goofy/digInTheVault.js +81 -0
  43. package/dist/tools/goofy/stashTheGoods.js +56 -0
  44. package/dist/tools/teamMemberDeployer.js +2 -2
  45. package/dist/watcher/changeHandler.js +65 -8
  46. package/dist/watcher/changeQueue.js +20 -1
  47. package/embedding-sandbox/mini-cot-service.py +11 -13
  48. package/embedding-sandbox/pdf-text-extract.py +208 -0
  49. package/package.json +1 -1
  50. package/scripts/deploy-hooks.cjs +2 -2
  51. package/scripts/global-postinstall.cjs +2 -2
  52. package/scripts/specmem-init.cjs +130 -36
  53. package/specmem/model-config.json +6 -6
  54. package/specmem/supervisord.conf +1 -1
  55. package/svg-sections/readme-token-compaction.svg +246 -0
@@ -3,7 +3,7 @@
3
3
  * Agent Chooser Hook - Interactive Agent Deployment
4
4
  * ==================================================
5
5
  *
6
- * PreToolUse hook that INTERCEPTS Task tool calls and:
6
+ * PreToolUse hook that INTERCEPTS Agent tool calls and:
7
7
  * 1. BLOCKS automatic deployment
8
8
  * 2. Injects instructions for to ask user for preferences
9
9
  * 3. User chooses agent type, model, settings PER DEPLOYMENT
@@ -11,7 +11,7 @@
11
11
  * This gives users FULL CONTROL over every agent that gets deployed!
12
12
  *
13
13
  * Hook Event: PreToolUse
14
- * Matcher: Task
14
+ * Matcher: Agent
15
15
  *
16
16
  * To disable interactive mode, set SPECMEM_AGENT_AUTO=1
17
17
  */
@@ -75,7 +75,7 @@ function buildChooserContext(description, currentType, currentModel, config) {
75
75
  [AGENT-CHOOSER]
76
76
  🎛️ AGENT DEPLOYMENT PAUSED - Confirm settings
77
77
 
78
- Task: "${description}"
78
+ Mission: "${description}"
79
79
  Agent: ${currentType || 'general-purpose'} | Model: ${currentModel || config.defaults?.model || 'sonnet'}
80
80
 
81
81
  Call AskUserQuestion NOW:
@@ -105,7 +105,7 @@ Call AskUserQuestion NOW:
105
105
  ]
106
106
  }
107
107
 
108
- After response: Re-deploy Task with choices + "${CHOOSER_MARKER}" in prompt.
108
+ After response: Re-deploy Agent with choices + "${CHOOSER_MARKER}" in prompt.
109
109
  [/AGENT-CHOOSER]
110
110
  `;
111
111
  }
@@ -122,8 +122,8 @@ async function main() {
122
122
  const toolName = data.tool_name || '';
123
123
  const toolInput = data.tool_input || {};
124
124
 
125
- // Only intercept Task tool
126
- if (toolName !== 'Task') {
125
+ // Only intercept Agent tool
126
+ if (toolName !== 'Agent') {
127
127
  process.exit(0);
128
128
  }
129
129
 
@@ -3,7 +3,7 @@
3
3
  * Agent Loading Hook for Claude Code
4
4
  * ===================================
5
5
  *
6
- * PreToolUse hook that intercepts Task tool calls and:
6
+ * PreToolUse hook that intercepts Agent tool calls and:
7
7
  * 1. Shows a clean loading indicator instead of mega prompts
8
8
  * 2. Silently injects team member context
9
9
  * 3. Forces background execution
@@ -11,7 +11,7 @@
11
11
  * 5. [NEW] Applies user-configurable agent settings from .specmem/agent-config.json
12
12
  *
13
13
  * Hook Event: PreToolUse
14
- * Matcher: Task
14
+ * Matcher: Agent
15
15
  *
16
16
  * Output goes to STDERR (visible in terminal) not STDOUT (tool output)
17
17
  *
@@ -93,7 +93,7 @@ const FORCE_CHOOSER = process.env.SPECMEM_FORCE_CHOOSER === '1' || process.env.S
93
93
  * ===========================================================================
94
94
  *
95
95
  * PROBLEM THIS SOLVES:
96
- * When FORCE_CHOOSER is enabled, this hook denies Task tool calls and tells
96
+ * When FORCE_CHOOSER is enabled, this hook denies Agent tool calls and tells
97
97
  * Claude to ask the user for confirmation first. Without a marker system,
98
98
  * Claude would:
99
99
  * 1. Try to deploy agent -> Hook denies, says "ask user first"
@@ -101,22 +101,22 @@ const FORCE_CHOOSER = process.env.SPECMEM_FORCE_CHOOSER === '1' || process.env.S
101
101
  * 3. Claude tries to deploy agent again -> Hook denies AGAIN (infinite loop!)
102
102
  *
103
103
  * HOW THE MARKER SYSTEM WORKS:
104
- * 1. First Task call: Hook denies with message telling Claude to use AskUserQuestion
104
+ * 1. First Agent call: Hook denies with message telling Claude to use AskUserQuestion
105
105
  * 2. Claude asks user via AskUserQuestion tool
106
106
  * 3. User confirms deployment settings
107
- * 4. Claude re-calls Task with "[SR-DEV-APPROVED]" in the prompt parameter
107
+ * 4. Claude re-calls Agent with "[SR-DEV-APPROVED]" in the prompt parameter
108
108
  * 5. Hook sees marker -> Allows deployment without re-asking
109
109
  *
110
110
  * MARKER DETECTION:
111
111
  * The marker is checked in BOTH the prompt AND description fields because
112
- * Claude sometimes places it in the task title instead of the prompt body.
112
+ * Claude sometimes places it in the agent title instead of the prompt body.
113
113
  *
114
114
  * FLOW DIAGRAM:
115
- * Task(prompt: "do X")
115
+ * Agent(prompt: "do X")
116
116
  * -> Hook: DENY, "ask user first"
117
117
  * AskUserQuestion(...)
118
118
  * -> User confirms
119
- * Task(prompt: "do X [SR-DEV-APPROVED]")
119
+ * Agent(prompt: "do X [SR-DEV-APPROVED]")
120
120
  * -> Hook: ALLOW (marker found)
121
121
  */
122
122
  const SR_DEV_APPROVED_MARKER = '[SR-DEV-APPROVED]';
@@ -287,7 +287,7 @@ const MODELS = [
287
287
 
288
288
  /**
289
289
  * Maps our custom agent type names to valid Claude Code subagent_type values
290
- * The Task tool only accepts: Bash, general-purpose, statusline-setup, Explore, Plan, claude-code-guide
290
+ * The Agent tool only accepts: Bash, general-purpose, statusline-setup, Explore, Plan, claude-code-guide
291
291
  */
292
292
  const SUBAGENT_TYPE_MAP = {
293
293
  // Built-in types (pass through)
@@ -467,13 +467,13 @@ function buildChooserInstructions(description, currentType, settings, config) {
467
467
  ).join(' | ');
468
468
 
469
469
  // FLATTENED: Single line format to avoid breaking Claude's context formatting
470
- return '[AGENT-CHOOSER-MANUAL] Task:"' + shortDesc + '" | ' +
470
+ return '[AGENT-CHOOSER-MANUAL] Agent:"' + shortDesc + '" | ' +
471
471
  'STEP1:AskUserQuestion({question:"Category?",options:[' + categoryOptions + ']}) | ' +
472
472
  'STEP2:Based on category ask TYPE(' + categoryTypeMap + ') | ' +
473
473
  'STEP3:Ask MODEL(opus/sonnet/haiku) | ' +
474
474
  'STEP4:Ask EXTRAS(ultrathink,thorough,quick) multiSelect:true | ' +
475
475
  'STEP5:Ask CHANNEL(main,swarm-1,swarm-2,swarm-3) | ' +
476
- 'THEN:Re-call Task with subagent_type+model+prompt+"' + SR_DEV_APPROVED_MARKER + '" [/AGENT-CHOOSER-MANUAL]';
476
+ 'THEN:Re-call Agent with subagent_type+model+prompt+"' + SR_DEV_APPROVED_MARKER + '" [/AGENT-CHOOSER-MANUAL]';
477
477
  }
478
478
 
479
479
  /**
@@ -491,7 +491,7 @@ function buildAutoManualChoice(description) {
491
491
 
492
492
  // Human-readable format using bracket notation
493
493
  return `[AGENT-DEPLOY-REQUIRED]
494
- Task: "${shortDesc}"
494
+ Agent: "${shortDesc}"
495
495
  Status: Awaiting senior dev approval
496
496
 
497
497
  [STEP-1] Deploy Mode
@@ -499,7 +499,7 @@ Use AskUserQuestion with options:
499
499
  - Auto | Deploy with defaults (sonnet, main channel)
500
500
  - Manual | Configure model, features, channel
501
501
 
502
- IF AUTO: Re-call Task with defaults + "${SR_DEV_APPROVED_MARKER}"
502
+ IF AUTO: Re-call Agent with defaults + "${SR_DEV_APPROVED_MARKER}"
503
503
 
504
504
  [STEP-2] Model Selection (Manual only)
505
505
  Options: opus (deep reasoning) | sonnet (balanced) | haiku (fast/cheap)
@@ -514,7 +514,7 @@ Options: main | swarm-1 | swarm-2 | swarm-3
514
514
  For 2+ agents, ask per-agent: Auto (defaults) | Configure
515
515
 
516
516
  [FINAL-STEP]
517
- Re-call Task with: prompt containing [FEATURES:...] [CHANNEL:...] ${SR_DEV_APPROVED_MARKER}
517
+ Re-call Agent with: prompt containing [FEATURES:...] [CHANNEL:...] ${SR_DEV_APPROVED_MARKER}
518
518
  [/AGENT-DEPLOY-REQUIRED]`;
519
519
  }
520
520
 
@@ -808,8 +808,8 @@ async function main() {
808
808
  const toolName = hookData.tool_name || '';
809
809
  const toolInput = hookData.tool_input || {};
810
810
 
811
- // Only handle Task tool
812
- if (toolName !== 'Task') {
811
+ // Only handle Agent tool
812
+ if (toolName !== 'Agent') {
813
813
  process.exit(0);
814
814
  }
815
815
 
@@ -3,7 +3,7 @@
3
3
  * Agent Loading Hook for Code
4
4
  * ===================================
5
5
  *
6
- * PreToolUse hook that intercepts Task tool calls and:
6
+ * PreToolUse hook that intercepts Agent tool calls and:
7
7
  * 1. Shows a clean loading indicator instead of mega prompts
8
8
  * 2. Silently injects team member context
9
9
  * 3. Forces background execution
@@ -11,7 +11,7 @@
11
11
  * 5. [NEW] Applies user-configurable agent settings from .specmem/agent-config.json
12
12
  *
13
13
  * Hook Event: PreToolUse
14
- * Matcher: Task
14
+ * Matcher: Agent
15
15
  *
16
16
  * Output goes to STDERR (visible in terminal) not STDOUT (tool output)
17
17
  *
@@ -100,7 +100,7 @@ const FORCE_CHOOSER = process.env.SPECMEM_FORCE_CHOOSER === '1' || process.env.S
100
100
  * ===========================================================================
101
101
  *
102
102
  * PROBLEM THIS SOLVES:
103
- * When FORCE_CHOOSER is enabled, this hook denies Task tool calls and tells
103
+ * When FORCE_CHOOSER is enabled, this hook denies Agent tool calls and tells
104
104
  * to ask the user for confirmation first. Without a marker system,
105
105
  * would:
106
106
  * 1. Try to deploy agent -> Hook denies, says "ask user first"
@@ -108,22 +108,22 @@ const FORCE_CHOOSER = process.env.SPECMEM_FORCE_CHOOSER === '1' || process.env.S
108
108
  * 3. tries to deploy agent again -> Hook denies AGAIN (infinite loop!)
109
109
  *
110
110
  * HOW THE MARKER SYSTEM WORKS:
111
- * 1. First Task call: Hook denies with message telling to use AskUserQuestion
111
+ * 1. First Agent call: Hook denies with message telling to use AskUserQuestion
112
112
  * 2. asks user via AskUserQuestion tool
113
113
  * 3. User confirms deployment settings
114
- * 4. re-calls Task with "[SR-DEV-APPROVED]" in the prompt parameter
114
+ * 4. re-calls Agent with "[SR-DEV-APPROVED]" in the prompt parameter
115
115
  * 5. Hook sees marker -> Allows deployment without re-asking
116
116
  *
117
117
  * MARKER DETECTION:
118
118
  * The marker is checked in BOTH the prompt AND description fields because
119
- * sometimes places it in the task title instead of the prompt body.
119
+ * sometimes places it in the agent title instead of the prompt body.
120
120
  *
121
121
  * FLOW DIAGRAM:
122
- * Task(prompt: "do X")
122
+ * Agent(prompt: "do X")
123
123
  * -> Hook: DENY, "ask user first"
124
124
  * AskUserQuestion(...)
125
125
  * -> User confirms
126
- * Task(prompt: "do X [SR-DEV-APPROVED]")
126
+ * Agent(prompt: "do X [SR-DEV-APPROVED]")
127
127
  * -> Hook: ALLOW (marker found)
128
128
  */
129
129
  const SR_DEV_APPROVED_MARKER = '[SR-DEV-APPROVED]';
@@ -294,7 +294,7 @@ const MODELS = [
294
294
 
295
295
  /**
296
296
  * Maps our custom agent type names to valid Code subagent_type values
297
- * The Task tool only accepts: Bash, general-purpose, statusline-setup, Explore, Plan, claude-code-guide
297
+ * The Agent tool only accepts: Bash, general-purpose, statusline-setup, Explore, Plan, claude-code-guide
298
298
  */
299
299
  const SUBAGENT_TYPE_MAP = {
300
300
  // Built-in types (pass through)
@@ -474,13 +474,13 @@ function buildChooserInstructions(description, currentType, settings, config) {
474
474
  ).join(' | ');
475
475
 
476
476
  // FLATTENED: Single line format to avoid breaking 's context formatting
477
- return '[AGENT-CHOOSER-MANUAL] Task:"' + shortDesc + '" | ' +
477
+ return '[AGENT-CHOOSER-MANUAL] Agent:"' + shortDesc + '" | ' +
478
478
  'STEP1:AskUserQuestion({question:"Category?",options:[' + categoryOptions + ']}) | ' +
479
479
  'STEP2:Based on category ask TYPE(' + categoryTypeMap + ') | ' +
480
480
  'STEP3:Ask MODEL(opus/sonnet/haiku) | ' +
481
481
  'STEP4:Ask EXTRAS(ultrathink,thorough,quick) multiSelect:true | ' +
482
482
  'STEP5:Ask CHANNEL(main,swarm-1,swarm-2,swarm-3) | ' +
483
- 'THEN:Re-call Task with subagent_type+model+prompt+"' + SR_DEV_APPROVED_MARKER + '" [/AGENT-CHOOSER-MANUAL]';
483
+ 'THEN:Re-call Agent with subagent_type+model+prompt+"' + SR_DEV_APPROVED_MARKER + '" [/AGENT-CHOOSER-MANUAL]';
484
484
  }
485
485
 
486
486
  /**
@@ -498,7 +498,7 @@ function buildAutoManualChoice(description) {
498
498
 
499
499
  // BULLETPROOF format - Claude CANNOT misinterpret this.
500
500
  // All instructions are a single action: call AskUserQuestion with ALL options bundled.
501
- // After user responds, re-call Task with the marker.
501
+ // After user responds, re-call Agent with the marker.
502
502
  return `[AGENT-DEPLOY-REQUIRED]
503
503
  BLOCKED. You MUST do the following. Do NOT skip this. Do NOT do the work yourself. Do NOT ask the user in plain text.
504
504
 
@@ -513,9 +513,9 @@ Question 3 (only if Manual): header:"Features", question:"Enable which features?
513
513
  Question 4 (only if Manual): header:"Channel", question:"Which team channel?", options:[{"label":"main (Recommended)","description":"Default channel, visible to all"},{"label":"swarm-1","description":"Private swarm channel 1"},{"label":"swarm-2","description":"Private swarm channel 2"},{"label":"swarm-3","description":"Private swarm channel 3"}], multiSelect:false
514
514
 
515
515
  AFTER USER RESPONDS:
516
- - If Auto: Re-call Task with SAME prompt + " ${SR_DEV_APPROVED_MARKER}" appended
517
- - If Manual: Re-call Task with SAME prompt + " [FEATURES:chosen] [CHANNEL:chosen] ${SR_DEV_APPROVED_MARKER}" appended
518
- - If Cancel: Do NOT call Task. Tell user you'll handle it differently.
516
+ - If Auto: Re-call Agent with SAME prompt + " ${SR_DEV_APPROVED_MARKER}" appended
517
+ - If Manual: Re-call Agent with SAME prompt + " [FEATURES:chosen] [CHANNEL:chosen] ${SR_DEV_APPROVED_MARKER}" appended
518
+ - If Cancel: Do NOT call Agent. Tell user you'll handle it differently.
519
519
 
520
520
  For Auto: ONLY ask Question 1. For Manual: Ask Questions 1-4 together.
521
521
  [/AGENT-DEPLOY-REQUIRED]`;
@@ -805,8 +805,8 @@ async function main() {
805
805
  const toolName = hookData.tool_name || '';
806
806
  const toolInput = hookData.tool_input || {};
807
807
 
808
- // Only handle Task tool
809
- if (toolName !== 'Task') {
808
+ // Only handle Agent tool
809
+ if (toolName !== 'Agent') {
810
810
  process.exit(0);
811
811
  }
812
812
 
@@ -851,7 +851,7 @@ async function main() {
851
851
  // Compact denial with inline Auto/Manual/Cancel - same flow the old hook had
852
852
  // but without the massive 500-token AskUserQuestion template dump
853
853
  const shortDesc = description.slice(0, 40);
854
- const denyMsg = `[AGENT-DEPLOY-REQUIRED] BLOCKED. You MUST call AskUserQuestion with: question:"Deploy agent: ${shortDesc}?", header:"Deploy", options:[{label:"Auto (Recommended)",description:"sonnet, main channel, defaults"},{label:"Manual",description:"Choose model/channel/features"},{label:"Cancel",description:"Don't deploy"}]. If Auto: re-call Task with same prompt + " ${SR_DEV_APPROVED_MARKER}". If Manual: ask model(sonnet/opus/haiku), channel(main/swarm-1/swarm-2), then re-call Task with " [CHANNEL:chosen] ${SR_DEV_APPROVED_MARKER}". If Cancel: stop. Do NOT do the work yourself - Grep/Read/Glob will be BLOCKED. [/AGENT-DEPLOY-REQUIRED]`;
854
+ const denyMsg = `[AGENT-DEPLOY-REQUIRED] BLOCKED. You MUST call AskUserQuestion with: question:"Deploy agent: ${shortDesc}?", header:"Deploy", options:[{label:"Auto (Recommended)",description:"sonnet, main channel, defaults"},{label:"Manual",description:"Choose model/channel/features"},{label:"Cancel",description:"Don't deploy"}]. If Auto: re-call Agent with same prompt + " ${SR_DEV_APPROVED_MARKER}". If Manual: ask model(sonnet/opus/haiku), channel(main/swarm-1/swarm-2), then re-call Agent with " [CHANNEL:chosen] ${SR_DEV_APPROVED_MARKER}". If Cancel: stop. Do NOT do the work yourself - Grep/Read/Glob will be BLOCKED. [/AGENT-DEPLOY-REQUIRED]`;
855
855
 
856
856
  console.log(JSON.stringify({
857
857
  hookSpecificOutput: {
@@ -311,7 +311,7 @@ async function main() {
311
311
  const toolInput = data.tool_input || {};
312
312
 
313
313
  // Only process Task tool
314
- if (toolName !== 'Task') {
314
+ if (toolName !== 'Agent') {
315
315
  process.exit(0);
316
316
  }
317
317
 
@@ -71,7 +71,7 @@ async function main() {
71
71
 
72
72
  // Only intercept background completions
73
73
  const isBackgroundTask = toolInput.run_in_background === true;
74
- const isTaskTool = toolName === 'Task';
74
+ const isTaskTool = toolName === 'Agent';
75
75
  const isBashTool = toolName === 'Bash';
76
76
 
77
77
  if (!isBackgroundTask && !isTaskTool) {
@@ -193,38 +193,8 @@ function readStdinWithTimeout(timeoutMs = 5000) {
193
193
  });
194
194
  }
195
195
 
196
- /**
197
- * Check if we're running as an agent (subagent from Task tool)
198
- * Main sessions should NOT be restricted
199
- *
200
- * IMPORTANT: Only explicit agent indicators count!
201
- * Screen sessions (STY) and process trees are NOT reliable because
202
- * main also runs in screen sessions via specmem-init.
203
- */
204
- function isAgent() {
205
- // ONLY use explicit env vars set by agent spawning code
206
- // These are set by deployTeamMember or Task tool when spawning agents
207
- if (process.env.SPECMEM_AGENT_MODE === '1') return true;
208
- if (process.env.SPECMEM_TEAM_MEMBER_ID) return true;
209
- if (process.env.CLAUDE_SUBAGENT === '1') return true;
210
- if (process.env.CLAUDE_AGENT_ID) return true;
211
-
212
- // Check if this is a team member screen session (has specmem-tm- prefix)
213
- // Regular screens are claude-<project>, not specmem-tm-
214
- if (process.env.STY && process.env.STY.includes('specmem-tm-')) return true;
215
-
216
- // Check working directory for agent-specific paths
217
- try {
218
- const cwd = process.cwd();
219
- // Only agent scratchpads, not main project
220
- if (cwd.includes('/tmp/claude/') && cwd.includes('/scratchpad/')) {
221
- return true;
222
- }
223
- } catch (e) {}
224
-
225
- // Default: NOT an agent (main session gets unrestricted access)
226
- return false;
227
- }
196
+ // --- Agent detection (shared module — detects CLAUDE_SUBAGENT=1 correctly) ---
197
+ const { isAgent } = require('./is-agent.cjs');
228
198
 
229
199
  async function main() {
230
200
  const inputData = await readStdinWithTimeout(5000);
@@ -239,13 +209,44 @@ async function main() {
239
209
  process.exit(0);
240
210
  }
241
211
 
242
- // Only enforce on Read, Edit, Write
243
- if (!['Read', 'Edit', 'Write'].includes(toolName)) {
212
+ // Only enforce on Edit, Write, Bash (NOT Read — agents need to read to know what to claim)
213
+ if (!['Edit', 'Write', 'Bash'].includes(toolName)) {
244
214
  process.exit(0);
245
215
  }
246
216
 
247
- // Get the file path from tool input
248
- const filePath = toolInput.file_path || toolInput.path || '';
217
+ // Get the file path based on tool type
218
+ let filePath;
219
+ if (toolName === 'Bash') {
220
+ const command = toolInput.command || '';
221
+ // Check if this Bash command modifies files
222
+ const MODIFY_PATTERNS = [
223
+ /\bsed\s+-i\b/, // sed -i (in-place edit)
224
+ /\bawk\b.*>/, // awk with output redirect
225
+ /\becho\b.*>/, // echo > file
226
+ /\bcat\b.*>/, // cat > file
227
+ /\btee\s/, // tee file
228
+ /\bmv\s/, // mv old new
229
+ /\bcp\s/, // cp src dst
230
+ /\brm\s/, // rm file
231
+ /\bchmod\s/, // chmod
232
+ /\bchown\s/, // chown
233
+ /\btruncate\s/, // truncate
234
+ ];
235
+ const isModifying = MODIFY_PATTERNS.some(p => p.test(command));
236
+ if (!isModifying) {
237
+ process.exit(0); // Not a file-modifying command, allow through
238
+ }
239
+ // Extract target file path (best-effort)
240
+ let m;
241
+ m = command.match(/\bsed\s+.*-i[^\s]*\s+(?:'[^']*'|"[^"]*"|[^\s]+)\s+(\S+)/);
242
+ if (!m) m = command.match(/>\s*(\S+)/);
243
+ if (!m) m = command.match(/\btee\s+(?:-a\s+)?(\S+)/);
244
+ if (!m) m = command.match(/\b(?:mv|cp)\s+(?:-\S+\s+)*\S+\s+(\S+)/);
245
+ if (!m) m = command.match(/\brm\s+(?:-\S+\s+)*(\S+)/);
246
+ filePath = m ? m[1] : '';
247
+ } else {
248
+ filePath = toolInput.file_path || toolInput.path || '';
249
+ }
249
250
 
250
251
  if (!filePath) {
251
252
  process.exit(0);
@@ -270,7 +270,7 @@ async function hookMode() {
270
270
  const toolResult = data.tool_result || {};
271
271
 
272
272
  // Only handle Task tool completions
273
- if (toolName !== 'Task') {
273
+ if (toolName !== 'Agent') {
274
274
  process.exit(0);
275
275
  }
276
276
 
@@ -107,6 +107,14 @@
107
107
  "env": {
108
108
  "SPECMEM_PROJECT_PATH": "${cwd}"
109
109
  }
110
+ },
111
+ {
112
+ "type": "command",
113
+ "command": "node /root/.claude/hooks/file-claim-enforcer.cjs",
114
+ "timeout": 2,
115
+ "env": {
116
+ "SPECMEM_PROJECT_PATH": "${cwd}"
117
+ }
110
118
  }
111
119
  ]
112
120
  },
@@ -125,6 +133,14 @@
125
133
  "env": {
126
134
  "SPECMEM_PROJECT_PATH": "${cwd}"
127
135
  }
136
+ },
137
+ {
138
+ "type": "command",
139
+ "command": "node /root/.claude/hooks/file-claim-enforcer.cjs",
140
+ "timeout": 2,
141
+ "env": {
142
+ "SPECMEM_PROJECT_PATH": "${cwd}"
143
+ }
128
144
  }
129
145
  ]
130
146
  },
@@ -243,11 +259,19 @@
243
259
  "type": "command",
244
260
  "command": "node /root/.claude/hooks/agent-output-interceptor.js",
245
261
  "timeout": 3
262
+ },
263
+ {
264
+ "type": "command",
265
+ "command": "node /root/.claude/hooks/file-claim-enforcer.cjs",
266
+ "timeout": 2,
267
+ "env": {
268
+ "SPECMEM_PROJECT_PATH": "${cwd}"
269
+ }
246
270
  }
247
271
  ]
248
272
  },
249
273
  {
250
- "matcher": "Task",
274
+ "matcher": "Agent",
251
275
  "hooks": [
252
276
  {
253
277
  "type": "command",
@@ -439,7 +463,7 @@
439
463
  ]
440
464
  },
441
465
  {
442
- "matcher": "Task",
466
+ "matcher": "Agent",
443
467
  "hooks": [
444
468
  {
445
469
  "type": "command",
@@ -556,4 +580,4 @@
556
580
  ],
557
581
  "deny": []
558
582
  }
559
- }
583
+ }
@@ -20,17 +20,8 @@
20
20
  const fs = require('fs');
21
21
  const path = require('path');
22
22
 
23
- // --- Agent detection (inline, no require chain issues) ---
24
- function isAgent() {
25
- const markers = [
26
- process.env.CLAUDE_AGENT === 'true',
27
- process.env.CLAUDE_AGENT_TYPE,
28
- process.env.TASK_ID,
29
- (process.env.CLAUDE_WORKTREE || '').length > 0,
30
- (process.env.CLAUDE_SESSION_ID || '').includes('task-'),
31
- ];
32
- return markers.some(Boolean);
33
- }
23
+ // --- Agent detection (shared module detects CLAUDE_SUBAGENT=1 correctly) ---
24
+ const { isAgent } = require('./is-agent.cjs');
34
25
 
35
26
  // --- Config ---
36
27
  const SEARCH_TOOLS = ['Grep', 'Glob', 'Read', 'Bash'];
@@ -219,7 +219,7 @@ async function main() {
219
219
  const toolInput = hookData.tool_input || {};
220
220
 
221
221
  // Only intercept Task tool calls
222
- if (toolName !== 'Task') {
222
+ if (toolName !== 'Agent') {
223
223
  // Not a Task tool - exit without modification
224
224
  process.exit(0);
225
225
  }
@@ -580,7 +580,7 @@ def handle_pre_tool_use(input_data):
580
580
  """Handle PreToolUse - inject API reference into Task calls."""
581
581
  tool_name = input_data.get('tool_name', '')
582
582
 
583
- if tool_name != 'Task':
583
+ if tool_name != 'Agent':
584
584
  return None
585
585
 
586
586
  tool_input = input_data.get('tool_input', {})
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * SUBAGENT LOADING HOOK - Visual Progress for Task Agents
3
+ * SUBAGENT LOADING HOOK - Visual Progress for Agents
4
4
  *
5
5
  * Based on MCP Runtime Guide (Hardwick Software Services):
6
6
  * - SubagentStart: Injects context, tracks spawning
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * TASK PROGRESS HOOK - Real loading bars for Task tool agents
3
+ * AGENT PROGRESS HOOK - Real loading bars for Agent tool agents
4
4
  *
5
5
  * Writes DIRECTLY to /dev/tty to bypass Claude's stdout capture
6
6
  * This actually shows content in the terminal!
@@ -92,8 +92,8 @@ process.stdin.on('end', async () => {
92
92
  const data = JSON.parse(input);
93
93
  const { hookEventName, toolName } = data;
94
94
 
95
- // Only handle Task tool
96
- if (toolName !== 'Task') {
95
+ // Only handle Agent tool
96
+ if (toolName !== 'Agent') {
97
97
  console.log(JSON.stringify({ continue: true }));
98
98
  return;
99
99
  }
@@ -114,7 +114,7 @@ process.stdin.on('end', async () => {
114
114
 
115
115
  function handlePreTask(data) {
116
116
  const { toolInput } = data;
117
- const description = toolInput?.description || 'Task';
117
+ const description = toolInput?.description || 'Agent';
118
118
  const runInBackground = toolInput?.run_in_background !== false;
119
119
 
120
120
  // Track task
@@ -148,11 +148,11 @@ function handlePreTask(data) {
148
148
  hookEventName: 'PreToolUse',
149
149
  additionalContext: `
150
150
  [AGENT #${taskNum} DEPLOYED]
151
- Task: ${description}
151
+ Agent: ${description}
152
152
  Status: Running in background
153
153
 
154
154
  OUTPUT PROGRESS using send_team_message():
155
- - When starting: send_team_message({message: "🔄 Starting: [task]"})
155
+ - When starting: send_team_message({message: "🔄 Starting: [agent task]"})
156
156
  - During work: send_team_message({message: "📝 Progress: [update]"})
157
157
  - When done: send_team_message({message: "✅ Completed: [summary]"})
158
158
  `
@@ -164,7 +164,7 @@ OUTPUT PROGRESS using send_team_message():
164
164
 
165
165
  function handlePostTask(data) {
166
166
  const { toolInput, toolOutput } = data;
167
- const description = toolInput?.description || 'Task';
167
+ const description = toolInput?.description || 'Agent';
168
168
 
169
169
  killSpinner();
170
170
 
@@ -93,7 +93,7 @@ process.stdin.on('end', async () => {
93
93
  const { hookEventName, toolName } = data;
94
94
 
95
95
  // Only handle Task tool
96
- if (toolName !== 'Task') {
96
+ if (toolName !== 'Agent') {
97
97
  console.log(JSON.stringify({ continue: true }));
98
98
  return;
99
99
  }
@@ -114,7 +114,7 @@ process.stdin.on('end', async () => {
114
114
 
115
115
  function handlePreTask(data) {
116
116
  const { toolInput } = data;
117
- const description = toolInput?.description || 'Task';
117
+ const description = toolInput?.description || 'Agent';
118
118
  const runInBackground = toolInput?.run_in_background !== false;
119
119
 
120
120
  // Track task
@@ -164,7 +164,7 @@ OUTPUT PROGRESS using send_team_message():
164
164
 
165
165
  function handlePostTask(data) {
166
166
  const { toolInput, toolOutput } = data;
167
- const description = toolInput?.description || 'Task';
167
+ const description = toolInput?.description || 'Agent';
168
168
 
169
169
  killSpinner();
170
170