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.
- package/CHANGELOG.md +34 -0
- package/README.md +11 -15
- package/bin/specmem-console.cjs +839 -51
- package/claude-hooks/agent-chooser-hook.js +6 -6
- package/claude-hooks/agent-loading-hook.cjs +16 -16
- package/claude-hooks/agent-loading-hook.js +18 -18
- package/claude-hooks/agent-type-matcher.js +1 -1
- package/claude-hooks/background-completion-silencer.js +1 -1
- package/claude-hooks/file-claim-enforcer.cjs +37 -36
- package/claude-hooks/output-cleaner.cjs +1 -1
- package/claude-hooks/settings.json +27 -3
- package/claude-hooks/specmem-search-enforcer.cjs +2 -11
- package/claude-hooks/specmem-team-member-inject.js +1 -1
- package/claude-hooks/specmem-unified-hook.py +1 -1
- package/claude-hooks/subagent-loading-hook.cjs +1 -1
- package/claude-hooks/task-progress-hook.cjs +7 -7
- package/claude-hooks/task-progress-hook.js +3 -3
- package/claude-hooks/team-comms-enforcer.cjs +49 -47
- package/dist/claude-sessions/sessionParser.js +5 -0
- package/dist/codebase/codebaseIndexer.js +48 -17
- package/dist/codebase/exclusions.js +3 -4
- package/dist/codebase/index.js +4 -0
- package/dist/codebase/pdfExtractor.js +298 -0
- package/dist/dashboard/api/taskTeamMembers.js +2 -2
- package/dist/db/bigBrainMigrations.js +29 -0
- package/dist/hooks/hookManager.js +4 -4
- package/dist/hooks/teamFramingCli.js +1 -1
- package/dist/hooks/teamMemberPrepromptHook.js +5 -5
- package/dist/init/claudeConfigInjector.js +2 -2
- package/dist/mcp/compactionProxy.js +834 -186
- package/dist/mcp/compactionProxyDaemon.js +112 -37
- package/dist/mcp/contextVault.js +439 -0
- package/dist/mcp/embeddingServerManager.js +61 -1
- package/dist/mcp/mcpProtocolHandler.js +6 -1
- package/dist/mcp/miniCOTServerManager.js +82 -8
- package/dist/mcp/specMemServer.js +45 -10
- package/dist/mcp/toolRegistry.js +6 -0
- package/dist/startup/startupIndexing.js +14 -0
- package/dist/team-members/taskOrchestrator.js +3 -3
- package/dist/team-members/taskTeamMemberLogger.js +2 -2
- package/dist/tools/goofy/deployTeamMember.js +3 -3
- package/dist/tools/goofy/digInTheVault.js +81 -0
- package/dist/tools/goofy/stashTheGoods.js +56 -0
- package/dist/tools/teamMemberDeployer.js +2 -2
- package/dist/watcher/changeHandler.js +65 -8
- package/dist/watcher/changeQueue.js +20 -1
- package/embedding-sandbox/mini-cot-service.py +11 -13
- package/embedding-sandbox/pdf-text-extract.py +208 -0
- package/package.json +1 -1
- package/scripts/deploy-hooks.cjs +2 -2
- package/scripts/global-postinstall.cjs +2 -2
- package/scripts/specmem-init.cjs +130 -36
- package/specmem/model-config.json +6 -6
- package/specmem/supervisord.conf +1 -1
- 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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
126
|
-
if (toolName !== '
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
112
|
+
* Claude sometimes places it in the agent title instead of the prompt body.
|
|
113
113
|
*
|
|
114
114
|
* FLOW DIAGRAM:
|
|
115
|
-
*
|
|
115
|
+
* Agent(prompt: "do X")
|
|
116
116
|
* -> Hook: DENY, "ask user first"
|
|
117
117
|
* AskUserQuestion(...)
|
|
118
118
|
* -> User confirms
|
|
119
|
-
*
|
|
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
|
|
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]
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
812
|
-
if (toolName !== '
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
119
|
+
* sometimes places it in the agent title instead of the prompt body.
|
|
120
120
|
*
|
|
121
121
|
* FLOW DIAGRAM:
|
|
122
|
-
*
|
|
122
|
+
* Agent(prompt: "do X")
|
|
123
123
|
* -> Hook: DENY, "ask user first"
|
|
124
124
|
* AskUserQuestion(...)
|
|
125
125
|
* -> User confirms
|
|
126
|
-
*
|
|
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
|
|
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]
|
|
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
|
|
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
|
|
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
|
|
517
|
-
- If Manual: Re-call
|
|
518
|
-
- If Cancel: Do NOT call
|
|
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
|
|
809
|
-
if (toolName !== '
|
|
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
|
|
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: {
|
|
@@ -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 === '
|
|
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
|
-
|
|
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
|
|
243
|
-
if (!['
|
|
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
|
|
248
|
-
|
|
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);
|
|
@@ -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": "
|
|
274
|
+
"matcher": "Agent",
|
|
251
275
|
"hooks": [
|
|
252
276
|
{
|
|
253
277
|
"type": "command",
|
|
@@ -439,7 +463,7 @@
|
|
|
439
463
|
]
|
|
440
464
|
},
|
|
441
465
|
{
|
|
442
|
-
"matcher": "
|
|
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 (
|
|
24
|
-
|
|
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 !== '
|
|
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 != '
|
|
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
|
-
*
|
|
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
|
|
96
|
-
if (toolName !== '
|
|
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 || '
|
|
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
|
-
|
|
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 || '
|
|
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 !== '
|
|
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 || '
|
|
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 || '
|
|
167
|
+
const description = toolInput?.description || 'Agent';
|
|
168
168
|
|
|
169
169
|
killSpinner();
|
|
170
170
|
|