specmem-hardwicksoftware 3.7.32 → 3.7.34
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 +32 -0
- package/README.md +4 -2
- package/bin/specmem-cli.cjs +18 -33
- package/bootstrap.cjs +5 -1
- package/claude-hooks/agent-loading-hook.js +31 -47
- package/claude-hooks/subagent-loading-hook.js +1 -1
- package/claude-hooks/team-comms-enforcer.cjs +112 -93
- package/dist/config/configSync.js +4 -1
- package/dist/index.js +44 -1
- package/dist/init/claudeConfigInjector.js +4 -1
- package/dist/installer/autoInstall.js +4 -1
- package/dist/mcp/compactionProxy.js +634 -72
- package/dist/mcp/compactionProxyDaemon.js +18 -4
- package/dist/mcp/specMemServer.js +8 -0
- package/dist/watcher/index.js +16 -2
- package/package.json +1 -1
- package/scripts/deploy-hooks.cjs +4 -1
- package/scripts/specmem-init.cjs +31 -35
- package/scripts/specmem-uninstall.cjs +4 -1
- package/specmem/model-config.json +3 -3
- package/specmem/supervisord.conf +1 -1
- package/svg-sections/readme-how-to-install.svg +145 -0
- package/svg-sections/readme-install.svg +89 -53
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,38 @@ All notable changes to SpecMem - we keep it real with semantic versioning. Deada
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [3.7.34] - 2026-02-26
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- System reminder dedup bug — `String.replace(string, '')` only removes first occurrence; duplicate identical reminders in same message block now fully nuked via `while(includes)` loop
|
|
11
|
+
- Edit tool stripping bug — `smartStripEdit(block)` was passing the whole tool_use block instead of `block.input`, causing null return and no stripping of Edit payloads in old messages
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## [3.7.33] - 2026-02-26
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Token compaction proxy reliability — resolved connection drops and socket failures under sustained load
|
|
19
|
+
- Multi-project registry for translation socket resolution — daemon now iterates registered projects to find active socket instead of hardcoded path
|
|
20
|
+
- System reminder stripping — strips redundant `<system-reminder>` blocks (keeps first occurrence), reduces token overhead per request
|
|
21
|
+
- Compaction proxy minimum translate length removed — all content now eligible for compression regardless of size
|
|
22
|
+
|
|
23
|
+
### Improved
|
|
24
|
+
- Token usage efficiency — advanced stenographic compression now applied to all tool results and system prompts, measurably lower token consumption per session
|
|
25
|
+
- Agent behavior modification hooks — agents now properly communicate via team messaging tools and use SpecMem tool calls as expected
|
|
26
|
+
- Tool call enforcement — bash call enforcer upgraded with better debouncing and team comms validation
|
|
27
|
+
- Team communications enforcer — improved reliability of inter-agent message routing
|
|
28
|
+
- Multi-project resource sharing — compaction daemon tracks project registry with cache invalidation on changes
|
|
29
|
+
- README updated with new "How to Install on Linux Systems" SVG featuring Debian, Ubuntu, Ubuntu LTS, Mint, and Kali Linux
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- Project registry system in compaction proxy — tracks all registered projects for shared translation memory and synonym caches
|
|
33
|
+
- Dynamic translate socket resolution across registered projects (sorted by last-seen)
|
|
34
|
+
- `SYSTEM_REMINDER_STRIPPING` live config toggle
|
|
35
|
+
- New skill commands for agent orchestration and autoclaude workflows
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
7
39
|
## [3.7.32] - 2026-02-24
|
|
8
40
|
|
|
9
41
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
## Token compression currently broken - please run specmem proxy off before running specmem init - until this is fixed!
|
|
2
|
+
|
|
1
3
|
<!-- Debian/Ubuntu/Mint/Kali notice -->
|
|
2
4
|
<div align="center">
|
|
3
5
|
<table><tr><td align="center" style="background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:12px 20px">
|
|
@@ -12,8 +14,8 @@
|
|
|
12
14
|
|
|
13
15
|
<div align="center">
|
|
14
16
|
|
|
15
|
-
<!--
|
|
16
|
-
<img src="./
|
|
17
|
+
<!-- How to Install SVG -->
|
|
18
|
+
<img src="./svg-sections/readme-how-to-install.svg" alt="How to Install SpecMem on Linux" width="800">
|
|
17
19
|
|
|
18
20
|
<br/>
|
|
19
21
|
<br/>
|
package/bin/specmem-cli.cjs
CHANGED
|
@@ -779,7 +779,7 @@ function manageProxy(args) {
|
|
|
779
779
|
switch (sub) {
|
|
780
780
|
case 'disable':
|
|
781
781
|
case 'off': {
|
|
782
|
-
// Create disabled flag
|
|
782
|
+
// Create disabled flag — daemon stays running but in passthrough mode
|
|
783
783
|
try {
|
|
784
784
|
if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
|
|
785
785
|
fs.writeFileSync(disabledFile, new Date().toISOString(), { mode: 0o644 });
|
|
@@ -788,40 +788,17 @@ function manageProxy(args) {
|
|
|
788
788
|
process.exit(1);
|
|
789
789
|
}
|
|
790
790
|
|
|
791
|
-
//
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
// Kill the running proxy if it's up
|
|
795
|
-
const port = process.env.COMPACTION_PROXY_PORT || '4080';
|
|
791
|
+
// Switch the running proxy to paused (passthrough) mode — don't kill it
|
|
792
|
+
const disablePort = process.env.COMPACTION_PROXY_PORT || '4080';
|
|
796
793
|
try {
|
|
797
|
-
execSync(`curl -sf http://127.0.0.1:${
|
|
798
|
-
|
|
799
|
-
try {
|
|
800
|
-
const pid = execSync(`lsof -ti tcp:${port} -s tcp:listen 2>/dev/null`, { encoding: 'utf8' }).trim();
|
|
801
|
-
if (pid) {
|
|
802
|
-
process.kill(parseInt(pid), 'SIGTERM');
|
|
803
|
-
console.log(`${GREEN}✓${RESET} Killed running proxy (PID ${pid})`);
|
|
804
|
-
}
|
|
805
|
-
} catch (e) { /* couldn't find pid, that's ok */ }
|
|
794
|
+
execSync(`curl -sf -X POST http://127.0.0.1:${disablePort}/pause`, { timeout: 2000, stdio: 'pipe' });
|
|
795
|
+
console.log(`${GREEN}✓${RESET} Proxy switched to ${YELLOW}passthrough${RESET} mode`);
|
|
806
796
|
} catch (e) {
|
|
807
|
-
// Proxy not running
|
|
797
|
+
// Proxy not running — that's fine, flag will keep next start paused
|
|
808
798
|
}
|
|
809
799
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
const bashrc = path.join(os.homedir(), '.bashrc');
|
|
813
|
-
if (fs.existsSync(bashrc)) {
|
|
814
|
-
const content = fs.readFileSync(bashrc, 'utf8');
|
|
815
|
-
const cleaned = content.replace(/\n?# specmem-proxy-env\n(?:# [^\n]*\n)*if \[ -f "\$HOME\/\.claude\/\.compaction-proxy-port" \];[\s\S]*?fi\n?/g, '\n');
|
|
816
|
-
if (cleaned !== content) {
|
|
817
|
-
fs.writeFileSync(bashrc, cleaned);
|
|
818
|
-
console.log(`${GREEN}✓${RESET} Removed proxy env from .bashrc`);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
} catch (e) { /* non-fatal */ }
|
|
822
|
-
|
|
823
|
-
console.log(`${GREEN}✓${RESET} Compaction proxy ${RED}disabled${RESET}`);
|
|
824
|
-
console.log(` ${DIM}Proxy won't start on next session${RESET}`);
|
|
800
|
+
console.log(`${GREEN}✓${RESET} Compaction proxy ${RED}disabled${RESET} (passthrough — traffic still flows)`);
|
|
801
|
+
console.log(` ${DIM}Proxy stays running but passes all requests through untouched${RESET}`);
|
|
825
802
|
console.log(`\n ${DIM}Re-enable with: ${CYAN}specmem proxy enable${RESET}`);
|
|
826
803
|
break;
|
|
827
804
|
}
|
|
@@ -831,9 +808,17 @@ function manageProxy(args) {
|
|
|
831
808
|
// Remove disabled flag
|
|
832
809
|
try { if (fs.existsSync(disabledFile)) fs.unlinkSync(disabledFile); } catch (e) { /* ok */ }
|
|
833
810
|
|
|
811
|
+
// Unpause the running proxy — activate compression immediately
|
|
812
|
+
const enablePort = process.env.COMPACTION_PROXY_PORT || '4080';
|
|
813
|
+
try {
|
|
814
|
+
execSync(`curl -sf -X POST http://127.0.0.1:${enablePort}/resume`, { timeout: 2000, stdio: 'pipe' });
|
|
815
|
+
console.log(`${GREEN}✓${RESET} Proxy compression ${GREEN}activated${RESET}`);
|
|
816
|
+
} catch (e) {
|
|
817
|
+
// Proxy not running — will start active on next session
|
|
818
|
+
}
|
|
819
|
+
|
|
834
820
|
console.log(`${GREEN}✓${RESET} Compaction proxy ${GREEN}enabled${RESET}`);
|
|
835
|
-
console.log(` ${DIM}
|
|
836
|
-
console.log(` ${DIM}Or restart Claude now to activate immediately${RESET}`);
|
|
821
|
+
console.log(` ${DIM}Compression active immediately (no restart needed)${RESET}`);
|
|
837
822
|
break;
|
|
838
823
|
}
|
|
839
824
|
|
package/bootstrap.cjs
CHANGED
|
@@ -955,7 +955,11 @@ function syncProjectConfigs() {
|
|
|
955
955
|
// Backup and write
|
|
956
956
|
const backupPath = `${claudeJsonPath}.backup.${Date.now()}`;
|
|
957
957
|
fs.copyFileSync(claudeJsonPath, backupPath);
|
|
958
|
-
|
|
958
|
+
// ATOMIC WRITE: write to temp file then rename to prevent corruption
|
|
959
|
+
// fs.writeFileSync truncates first, so Claude Code can read a partial file mid-write
|
|
960
|
+
const tmpPath = claudeJsonPath + '.tmp.' + process.pid;
|
|
961
|
+
fs.writeFileSync(tmpPath, JSON.stringify(claudeConfig, null, 2), 'utf-8');
|
|
962
|
+
fs.renameSync(tmpPath, claudeJsonPath);
|
|
959
963
|
startupLog(`CONFIG SYNC: Fixed ${fixedProjects.length} stale project configs: ${fixedProjects.join(', ')}`);
|
|
960
964
|
|
|
961
965
|
// Clean up old backups (keep last 3)
|
|
@@ -734,53 +734,37 @@ async function loadSkills() {
|
|
|
734
734
|
function getMinimalTeamContext() {
|
|
735
735
|
return `
|
|
736
736
|
|
|
737
|
-
[
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
MEMORY TOOLS (MANDATORY - enforced by hooks):
|
|
769
|
-
- find_memory({query, limit:10}) - semantic search for past conversations and decisions
|
|
770
|
-
- find_code_pointers({query, limit:10, includeTracebacks:true}) - semantic code search with callers/callees
|
|
771
|
-
- save_memory({content, importance, memoryType, tags}) - save important findings
|
|
772
|
-
- drill_down({drilldownID}) - get full context on a memory result
|
|
773
|
-
- getMemoryFull({id}) - get full memory with live code
|
|
774
|
-
|
|
775
|
-
WORKFLOW (enforced - you cannot skip steps):
|
|
776
|
-
1. START: send_team_message({type:"status", message:"Starting: [task]"})
|
|
777
|
-
2. CLAIM: claim_task({description, files}) - REQUIRED before any writes
|
|
778
|
-
3. SEARCH: find_memory/find_code_pointers FIRST, then Grep/Glob if needed
|
|
779
|
-
4. EVERY 4 CALLS: read_team_messages({include_swarms:true, limit:5}) - MANDATORY, you WILL be blocked
|
|
780
|
-
5. EVERY 5 CALLS: read_team_messages({include_broadcasts:true, include_swarms:true, limit:10}) - MANDATORY
|
|
781
|
-
6. EVERY 8 CALLS: get_team_status() - check if anyone needs help!
|
|
782
|
-
6. DONE: release_task({claimId:"all"}), send completion status
|
|
783
|
-
[/TEAM CONTEXT]`;
|
|
737
|
+
[TEAM COLLABORATION]
|
|
738
|
+
Welcome! You're joining a team of developers working on this project together. To keep things running smoothly and avoid stepping on each other's toes, here's how the team stays coordinated:
|
|
739
|
+
|
|
740
|
+
GETTING STARTED:
|
|
741
|
+
First, let the team know you're here and what you'll be working on:
|
|
742
|
+
send_team_message({type:"status", message:"Starting: [your task summary]"})
|
|
743
|
+
|
|
744
|
+
SEARCHING FOR CODE & CONTEXT:
|
|
745
|
+
The project has semantic search tools that are much more effective than grep — use these first:
|
|
746
|
+
find_code_pointers({query:"what you're looking for"}) — semantic code search with tracebacks, shows who calls what
|
|
747
|
+
find_memory({query:"topic"}) — finds past discussions, decisions, and context from the user and other team members
|
|
748
|
+
drill_down({drilldownID: N}) — expand a result from find_code_pointers or find_memory for full detail
|
|
749
|
+
If semantic search doesn't cover it, Grep/Glob are still available as a fallback.
|
|
750
|
+
|
|
751
|
+
EDITING FILES:
|
|
752
|
+
To avoid merge conflicts with other team members:
|
|
753
|
+
1. claim_task({description:"what you're doing", files:["path/to/file"]}) — claim before editing
|
|
754
|
+
2. Make your edits
|
|
755
|
+
3. release_task({claimId:"all"}) — release so others can work on those files
|
|
756
|
+
If a file is already claimed by someone else, coordinate via send_team_message first.
|
|
757
|
+
|
|
758
|
+
STAYING IN SYNC:
|
|
759
|
+
Check in with the team periodically — other team members may have updates that affect your work:
|
|
760
|
+
read_team_messages({include_swarms:true, limit:5}) — see recent team activity
|
|
761
|
+
get_team_status() — see who's working on what, and if anyone needs help
|
|
762
|
+
respond_to_help({requestId, response}) — if you can help a teammate, do it!
|
|
763
|
+
|
|
764
|
+
WHEN YOU'RE DONE:
|
|
765
|
+
release_task({claimId:"all"}) — free your claimed files
|
|
766
|
+
send_team_message({type:"status", message:"Completed: [summary of what you did]"})
|
|
767
|
+
[/TEAM COLLABORATION]`;
|
|
784
768
|
}
|
|
785
769
|
|
|
786
770
|
// ============================================================================
|
|
@@ -141,7 +141,7 @@ function handleSubagentStart(data) {
|
|
|
141
141
|
// CRITICAL: The announcement requirement is MANDATORY and ENFORCED by team-comms-enforcer.cjs
|
|
142
142
|
// Make it PROMINENT and FIRST in the context so agents don't miss it
|
|
143
143
|
const shortPurpose = purpose.slice(0, 30).replace(/"/g, "'");
|
|
144
|
-
const loadingContext = '[
|
|
144
|
+
const loadingContext = '[TEAM-MEMBER-' + agentNum + '] Hey! First, let the team know you are here: send_team_message({type:"status", message:"Starting: ' + shortPurpose + '"}) | For code search use find_code_pointers(), for past discussions use find_memory(), for detail use drill_down() | Before editing: claim_task(), after editing: release_task() | When done: send_team_message({type:"status", message:"Completed: [summary]"})';
|
|
145
145
|
|
|
146
146
|
// suppressOutput: true = Context gets processed but doesn't spam terminal
|
|
147
147
|
// The agent will output its OWN progress via team messages
|
|
@@ -127,7 +127,7 @@ const WRITE_TOOLS = ['Edit', 'Write', 'NotebookEdit'];
|
|
|
127
127
|
// - Task: can spawn sub-agents to bypass limits
|
|
128
128
|
const FULL_COMPLIANCE_TOOLS = ['Bash', 'Task'];
|
|
129
129
|
|
|
130
|
-
// Tools that are always allowed (reading team state + cross-swarm help)
|
|
130
|
+
// Tools that are always allowed (reading team state + cross-swarm help + research)
|
|
131
131
|
const ALWAYS_ALLOWED = [
|
|
132
132
|
'mcp__specmem__read_team_messages',
|
|
133
133
|
'mcp__specmem__get_team_status',
|
|
@@ -144,7 +144,12 @@ const ALWAYS_ALLOWED = [
|
|
|
144
144
|
'mcp__specmem__save_memory',
|
|
145
145
|
// Cross-swarm help - helping hands make the world go round!
|
|
146
146
|
'mcp__specmem__request_help',
|
|
147
|
-
'mcp__specmem__respond_to_help'
|
|
147
|
+
'mcp__specmem__respond_to_help',
|
|
148
|
+
// Research tools — read-only, never block these
|
|
149
|
+
'WebFetch',
|
|
150
|
+
'WebSearch',
|
|
151
|
+
'ToolSearch',
|
|
152
|
+
'Read',
|
|
148
153
|
];
|
|
149
154
|
|
|
150
155
|
// ============================================================================
|
|
@@ -214,25 +219,36 @@ function isSpecmemProject() {
|
|
|
214
219
|
*/
|
|
215
220
|
function isRunningAsAgent() {
|
|
216
221
|
// Method 1: Environment variable detection (most reliable)
|
|
217
|
-
//
|
|
218
|
-
// Built-in subagents have CLAUDE_SUBAGENT=1 but no MCP tools, so enforcement blocks them permanently.
|
|
222
|
+
// Deployed team members — always enforce
|
|
219
223
|
if (isTeamMemberFn()) return true;
|
|
220
224
|
|
|
221
|
-
// Method 2:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
// Method 2: General-purpose subagents (CLAUDE_SUBAGENT=1)
|
|
226
|
+
// These DO have MCP tools and SHOULD be enforced.
|
|
227
|
+
// Exclude Explore/Plan agents — they don't have MCP tools and can't comply.
|
|
228
|
+
// We check agents.json to see if the active subagent has MCP tools.
|
|
229
|
+
if (process.env.CLAUDE_SUBAGENT === '1' || process.env.CLAUDE_AGENT_ID) {
|
|
230
|
+
try {
|
|
231
|
+
const agentsFile = `${PROJECT_TMP_DIR}/agents.json`;
|
|
232
|
+
if (fs.existsSync(agentsFile)) {
|
|
233
|
+
const data = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
|
|
234
|
+
const now = Date.now();
|
|
235
|
+
for (const agent of Object.values(data.agents || {})) {
|
|
236
|
+
// Active agent (started within 10 min, no endTime)
|
|
237
|
+
if (!agent.endTime && agent.startTime && (now - agent.startTime < 600000)) {
|
|
238
|
+
// Check if this agent has MCP tools (general-purpose agents do)
|
|
239
|
+
const tools = agent.tools || [];
|
|
240
|
+
const hasMcpTools = tools.some(t => t.startsWith('mcp__specmem__'));
|
|
241
|
+
if (hasMcpTools) return true;
|
|
242
|
+
}
|
|
231
243
|
}
|
|
232
244
|
}
|
|
233
|
-
}
|
|
234
|
-
|
|
245
|
+
} catch {}
|
|
246
|
+
// No agents.json or no MCP tools found — this is likely Explore/Plan, skip enforcement
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
235
249
|
|
|
250
|
+
// Method 3: Check subagent tracking as fallback (parent context seeing active agents)
|
|
251
|
+
// This does NOT enforce on the parent — only on processes with CLAUDE_SUBAGENT=1
|
|
236
252
|
return false;
|
|
237
253
|
}
|
|
238
254
|
|
|
@@ -255,12 +271,15 @@ function isSpecmemEnabled() {
|
|
|
255
271
|
// ============================================================================
|
|
256
272
|
// BLOCK RESPONSE BUILDERS
|
|
257
273
|
// ============================================================================
|
|
258
|
-
function blockResponse(reason) {
|
|
274
|
+
function blockResponse(tool, reason) {
|
|
275
|
+
// Each denial specifies the EXACT tool to call. Framed as team collaboration,
|
|
276
|
+
// not enforcement. "team member" language, never "agent".
|
|
277
|
+
const msg = `[TEAM COLLABORATION - call ${tool} then retry] ${reason}`;
|
|
259
278
|
return JSON.stringify({
|
|
260
279
|
hookSpecificOutput: {
|
|
261
280
|
hookEventName: 'PreToolUse',
|
|
262
281
|
permissionDecision: 'deny',
|
|
263
|
-
permissionDecisionReason:
|
|
282
|
+
permissionDecisionReason: msg
|
|
264
283
|
}
|
|
265
284
|
});
|
|
266
285
|
}
|
|
@@ -397,12 +416,8 @@ process.stdin.on('end', () => {
|
|
|
397
416
|
state.blockedCount++;
|
|
398
417
|
saveTracking(tracking);
|
|
399
418
|
console.log(blockResponse(
|
|
400
|
-
|
|
401
|
-
`
|
|
402
|
-
`send_team_message({type:"status", message:"Starting: [describe your task]"})\n\n` +
|
|
403
|
-
`Note: If you were assigned to a specific channel (e.g. swarm-1, swarm-2), use that channel.\n` +
|
|
404
|
-
`Otherwise, the message will go to the main channel.\n\n` +
|
|
405
|
-
`After announcing, you can proceed with other tools.`
|
|
419
|
+
'mcp__specmem__send_team_message',
|
|
420
|
+
`Let other team members know what you're working on so nobody duplicates effort. Call: send_team_message({type:"status", message:"Starting: [your task]"})`
|
|
406
421
|
));
|
|
407
422
|
return;
|
|
408
423
|
}
|
|
@@ -424,10 +439,8 @@ process.stdin.on('end', () => {
|
|
|
424
439
|
state.blockedCount++;
|
|
425
440
|
saveTracking(tracking);
|
|
426
441
|
console.log(blockResponse(
|
|
427
|
-
|
|
428
|
-
`
|
|
429
|
-
`You MUST check team messages every 4 tool calls. This is non-negotiable.\n` +
|
|
430
|
-
`Other agents may have critical updates for you. CHECK NOW.`
|
|
442
|
+
'mcp__specmem__read_team_messages',
|
|
443
|
+
`Quick check-in — other team members may have updates that affect your work. Call: read_team_messages({include_swarms: true, limit: 5})`
|
|
431
444
|
));
|
|
432
445
|
return;
|
|
433
446
|
}
|
|
@@ -441,10 +454,8 @@ process.stdin.on('end', () => {
|
|
|
441
454
|
state.blockedCount++;
|
|
442
455
|
saveTracking(tracking);
|
|
443
456
|
console.log(blockResponse(
|
|
444
|
-
|
|
445
|
-
`
|
|
446
|
-
`You MUST check broadcasts every 5 tool calls. This is non-negotiable.\n` +
|
|
447
|
-
`Team-wide announcements and status updates require your attention. CHECK NOW.`
|
|
457
|
+
'mcp__specmem__read_team_messages',
|
|
458
|
+
`Time to check for team-wide announcements — there may be important updates. Call: read_team_messages({include_broadcasts: true, include_swarms: true, limit: 10})`
|
|
448
459
|
));
|
|
449
460
|
return;
|
|
450
461
|
}
|
|
@@ -457,11 +468,8 @@ process.stdin.on('end', () => {
|
|
|
457
468
|
state.blockedCount++;
|
|
458
469
|
saveTracking(tracking);
|
|
459
470
|
console.log(blockResponse(
|
|
460
|
-
|
|
461
|
-
`
|
|
462
|
-
`This shows open help requests from ALL swarms. Helping hands make the world go round!\n` +
|
|
463
|
-
`If you see a request you can help with, use respond_to_help().\n` +
|
|
464
|
-
`After checking, you can continue working.`
|
|
471
|
+
'mcp__specmem__get_team_status',
|
|
472
|
+
`Quick check — another team member might need a hand. Call: get_team_status() — if someone needs help, respond_to_help().`
|
|
465
473
|
));
|
|
466
474
|
return;
|
|
467
475
|
}
|
|
@@ -480,7 +488,7 @@ process.stdin.on('end', () => {
|
|
|
480
488
|
// ========================================================================
|
|
481
489
|
if (state.commsToolCount === TEAM_COMMS_CHECK_INTERVAL - 1) {
|
|
482
490
|
console.log(allowWithReminder(
|
|
483
|
-
`
|
|
491
|
+
`Heads up — good time to check in with the team: read_team_messages({include_swarms: true, limit: 5})`
|
|
484
492
|
));
|
|
485
493
|
// Don't return - continue to other checks
|
|
486
494
|
}
|
|
@@ -499,12 +507,8 @@ process.stdin.on('end', () => {
|
|
|
499
507
|
state.blockedCount++;
|
|
500
508
|
saveTracking(tracking);
|
|
501
509
|
console.log(blockResponse(
|
|
502
|
-
|
|
503
|
-
`
|
|
504
|
-
`• find_memory({query:"your search"}) - semantic memory search\n` +
|
|
505
|
-
`• find_code_pointers({query:"your search"}) - semantic code search with tracebacks\n\n` +
|
|
506
|
-
`These are MORE POWERFUL than Grep/Glob. USE THEM NOW.\n` +
|
|
507
|
-
`This resets every ${MAX_SEARCHES_BEFORE_BLOCK} searches — you must keep using them.`
|
|
510
|
+
'mcp__specmem__find_code_pointers',
|
|
511
|
+
`Grep/Glob haven't found what you need — try semantic search instead, it's much more effective. Call: find_code_pointers({query:"what you're looking for"}) — if you need more detail on a result, use drill_down(). To find what another team member or the user said, use find_memory({query:"topic"}).`
|
|
508
512
|
));
|
|
509
513
|
return;
|
|
510
514
|
}
|
|
@@ -513,7 +517,7 @@ process.stdin.on('end', () => {
|
|
|
513
517
|
if (state.searchCount === MAX_SEARCHES_BEFORE_BLOCK) {
|
|
514
518
|
saveTracking(tracking);
|
|
515
519
|
console.log(allowWithReminder(
|
|
516
|
-
`
|
|
520
|
+
`Tip: find_code_pointers() and find_memory() are much faster for semantic searches — try them next.`
|
|
517
521
|
));
|
|
518
522
|
return;
|
|
519
523
|
}
|
|
@@ -532,50 +536,63 @@ process.stdin.on('end', () => {
|
|
|
532
536
|
const toolInput = data.tool_input || {};
|
|
533
537
|
const filePath = toolInput.file_path || '';
|
|
534
538
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
issues.push(`find_memory() or find_code_pointers() to understand the code first`);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Check file claims — agent must have THIS file in their own claim
|
|
543
|
-
// Uses GLOBAL claims file so cross-channel/cross-swarm agents see each other
|
|
539
|
+
// Check file claims — to avoid conflicts with other team members
|
|
540
|
+
let fileInOtherClaim = false;
|
|
541
|
+
let otherClaimDesc = '';
|
|
542
|
+
let fileInOwnClaim = false;
|
|
544
543
|
if (filePath && state.claimed) {
|
|
545
544
|
try {
|
|
546
545
|
let claims = {};
|
|
547
546
|
if (fs.existsSync(GLOBAL_CLAIMS_FILE)) {
|
|
548
547
|
claims = JSON.parse(fs.readFileSync(GLOBAL_CLAIMS_FILE, 'utf8'));
|
|
549
548
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
} else {
|
|
558
|
-
fileInOtherClaim = true;
|
|
559
|
-
otherClaimDesc = claim.description || claim.agentId || 'another agent';
|
|
560
|
-
}
|
|
549
|
+
for (const [claimId, claim] of Object.entries(claims)) {
|
|
550
|
+
if (claim.files && claim.files.some(f => filePath.endsWith(f) || f.endsWith(filePath) || f === filePath)) {
|
|
551
|
+
if (claim.sessionId === sessionId || claim.agentId === sessionId) {
|
|
552
|
+
fileInOwnClaim = true;
|
|
553
|
+
} else {
|
|
554
|
+
fileInOtherClaim = true;
|
|
555
|
+
otherClaimDesc = claim.description || claim.agentId || 'another team member';
|
|
561
556
|
}
|
|
562
557
|
}
|
|
563
|
-
|
|
564
|
-
issues.push(`File "${filePath}" is claimed by: ${otherClaimDesc}\n Wait for them to release_task() or coordinate via send_team_message()`);
|
|
565
|
-
} else if (!fileInOwnClaim) {
|
|
566
|
-
issues.push(`File "${filePath}" is NOT in your claim!\n Update your claim: claim_task({description:"your task", files:["${filePath}"]})`);
|
|
567
|
-
}
|
|
558
|
+
}
|
|
568
559
|
} catch (e) {}
|
|
569
560
|
}
|
|
570
561
|
|
|
571
|
-
|
|
562
|
+
// Determine the specific blocker and give a targeted message
|
|
563
|
+
if (fileInOtherClaim) {
|
|
572
564
|
state.blockedCount++;
|
|
573
565
|
saveTracking(tracking);
|
|
574
566
|
console.log(blockResponse(
|
|
575
|
-
|
|
576
|
-
`
|
|
577
|
-
|
|
578
|
-
|
|
567
|
+
'mcp__specmem__send_team_message',
|
|
568
|
+
`"${filePath}" is currently being edited by ${otherClaimDesc}. To avoid merge conflicts, coordinate with them first. Call: send_team_message({type:"status", message:"Waiting on ${filePath} — is it free yet?"})`
|
|
569
|
+
));
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (!state.claimed) {
|
|
573
|
+
state.blockedCount++;
|
|
574
|
+
saveTracking(tracking);
|
|
575
|
+
console.log(blockResponse(
|
|
576
|
+
'mcp__specmem__claim_task',
|
|
577
|
+
`To prevent edit conflicts with other team members, claim the file before editing. Call: claim_task({description:"${(filePath || 'editing').slice(0, 40)}", files:["${filePath || 'file'}"]})`
|
|
578
|
+
));
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (!fileInOwnClaim && filePath) {
|
|
582
|
+
state.blockedCount++;
|
|
583
|
+
saveTracking(tracking);
|
|
584
|
+
console.log(blockResponse(
|
|
585
|
+
'mcp__specmem__claim_task',
|
|
586
|
+
`"${filePath}" isn't in your current claim. Update it so other team members know you're working here. Call: claim_task({description:"your task", files:["${filePath}"]})`
|
|
587
|
+
));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (!state.usedMemoryTools) {
|
|
591
|
+
state.blockedCount++;
|
|
592
|
+
saveTracking(tracking);
|
|
593
|
+
console.log(blockResponse(
|
|
594
|
+
'mcp__specmem__find_code_pointers',
|
|
595
|
+
`Before editing, check if there's existing context about this code. Call: find_code_pointers({query:"${(filePath || 'what you are editing').slice(0, 50)}"}) — or find_memory() to see what the user/team discussed about it.`
|
|
579
596
|
));
|
|
580
597
|
return;
|
|
581
598
|
}
|
|
@@ -601,11 +618,8 @@ process.stdin.on('end', () => {
|
|
|
601
618
|
state.blockedCount++;
|
|
602
619
|
saveTracking(tracking);
|
|
603
620
|
console.log(blockResponse(
|
|
604
|
-
|
|
605
|
-
`
|
|
606
|
-
`REQUIRED: release_task({claimId:"${state.currentClaimId || 'your-claim-id'}"})\n\n` +
|
|
607
|
-
`Protocol: claim_task → Edit/Write → release_task (every time).\n` +
|
|
608
|
-
`Release now, then claim_task again for your next edit.`
|
|
621
|
+
'mcp__specmem__release_task',
|
|
622
|
+
`You're done editing ${state.editedFiles[state.editedFiles.length - 1]} — release the claim so other team members can work on it. Call: release_task({claimId:"${state.currentClaimId || 'your-claim-id'}"})`
|
|
609
623
|
));
|
|
610
624
|
return;
|
|
611
625
|
}
|
|
@@ -618,26 +632,31 @@ process.stdin.on('end', () => {
|
|
|
618
632
|
if (FULL_COMPLIANCE_TOOLS.includes(toolName)) {
|
|
619
633
|
const issues = [];
|
|
620
634
|
|
|
635
|
+
// Give the FIRST missing step as a specific tool call
|
|
621
636
|
if (!state.announced) {
|
|
622
|
-
|
|
637
|
+
state.blockedCount++;
|
|
638
|
+
saveTracking(tracking);
|
|
639
|
+
console.log(blockResponse(
|
|
640
|
+
'mcp__specmem__send_team_message',
|
|
641
|
+
`Let the team know what you're working on first. Call: send_team_message({type:"status", message:"Starting: [your task]"})`
|
|
642
|
+
));
|
|
643
|
+
return;
|
|
623
644
|
}
|
|
624
645
|
if (!state.claimed) {
|
|
625
|
-
|
|
646
|
+
state.blockedCount++;
|
|
647
|
+
saveTracking(tracking);
|
|
648
|
+
console.log(blockResponse(
|
|
649
|
+
'mcp__specmem__claim_task',
|
|
650
|
+
`Claim your task so other team members don't overlap. Call: claim_task({description:"what you're doing"})`
|
|
651
|
+
));
|
|
652
|
+
return;
|
|
626
653
|
}
|
|
627
654
|
if (!state.usedMemoryTools) {
|
|
628
|
-
issues.push(`find_memory() or find_code_pointers() - USE SEMANTIC SEARCH FIRST`);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
if (issues.length > 0) {
|
|
632
655
|
state.blockedCount++;
|
|
633
656
|
saveTracking(tracking);
|
|
634
|
-
const toolType = toolName === 'Task' ? 'sub-agents' : 'Bash';
|
|
635
657
|
console.log(blockResponse(
|
|
636
|
-
|
|
637
|
-
`
|
|
638
|
-
`YOU MUST DO ALL OF THESE FIRST:\n` +
|
|
639
|
-
issues.map((i, idx) => `${idx + 1}. ${i}`).join('\n') + `\n\n` +
|
|
640
|
-
`NO SHORTCUTS. Follow the protocol.`
|
|
658
|
+
'mcp__specmem__find_code_pointers',
|
|
659
|
+
`Check existing context first — it'll save time. Call: find_code_pointers({query:"what you need"}) or find_memory({query:"topic"})`
|
|
641
660
|
));
|
|
642
661
|
return;
|
|
643
662
|
}
|
|
@@ -654,7 +673,7 @@ process.stdin.on('end', () => {
|
|
|
654
673
|
|
|
655
674
|
if (!state.usedMemoryTools && state.searchCount > 0) {
|
|
656
675
|
console.log(allowWithReminder(
|
|
657
|
-
`
|
|
676
|
+
`Tip: find_code_pointers() gives semantic code search with tracebacks, and find_memory() finds what the user or team discussed — both faster than grep.`
|
|
658
677
|
));
|
|
659
678
|
return;
|
|
660
679
|
}
|
|
@@ -321,7 +321,10 @@ function syncProjectConfigs() {
|
|
|
321
321
|
catch {
|
|
322
322
|
// Ignore cleanup errors
|
|
323
323
|
}
|
|
324
|
-
|
|
324
|
+
// ATOMIC WRITE: write to temp file then rename to prevent corruption
|
|
325
|
+
const tmpPath = claudeJsonPath + '.tmp.' + process.pid;
|
|
326
|
+
fs.writeFileSync(tmpPath, JSON.stringify(claudeConfig, null, 2), 'utf-8');
|
|
327
|
+
fs.renameSync(tmpPath, claudeJsonPath);
|
|
325
328
|
logger.info({ projectsFixed }, '[ConfigSync] Project-level configs updated');
|
|
326
329
|
return { fixed: true, projectsFixed };
|
|
327
330
|
}
|