specmem-hardwicksoftware 3.7.31 → 3.7.33
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 +22 -0
- package/README.md +12 -0
- package/bin/specmem-cli.cjs +18 -33
- package/bootstrap.cjs +5 -1
- package/claude-hooks/agent-loading-hook.js +31 -47
- package/claude-hooks/bash-call-enforcer.cjs +140 -0
- package/claude-hooks/settings.json +33 -0
- package/claude-hooks/specmem-drilldown-hook.cjs +49 -2
- package/claude-hooks/specmem-drilldown-hook.js +49 -2
- package/claude-hooks/specmem-drilldown-hook.js.bak +495 -0
- package/claude-hooks/specmem-precompact.cjs +13 -36
- package/claude-hooks/specmem-precompact.js +3 -7
- package/claude-hooks/specmem-session-start.cjs +38 -50
- package/claude-hooks/specmem-session-start.js +19 -60
- 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 +648 -73
- package/dist/mcp/compactionProxyDaemon.js +18 -4
- package/dist/mcp/specMemServer.js +8 -0
- package/dist/watcher/index.js +40 -0
- 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 +4 -4
- package/specmem/supervisord.conf +1 -1
- package/svg-sections/readme-install.svg +94 -52
- package/svg-sections/readme-whats-new.svg +120 -114
|
@@ -283,10 +283,10 @@ function getLastSessionOutput() {
|
|
|
283
283
|
|
|
284
284
|
let sessionContent;
|
|
285
285
|
if (startIdx === -1) {
|
|
286
|
-
sessionContent = lines.slice(-
|
|
286
|
+
sessionContent = lines.slice(-40).join('\n');
|
|
287
287
|
} else {
|
|
288
288
|
const outputLines = lines.slice(startIdx + 2, endIdx > 0 ? endIdx : undefined);
|
|
289
|
-
sessionContent = outputLines.slice(-
|
|
289
|
+
sessionContent = outputLines.slice(-40).join('\n');
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
// FILTER OUT GARBAGE - Skills, corrupted content, and non-conversation data
|
|
@@ -331,6 +331,14 @@ function getLastSessionOutput() {
|
|
|
331
331
|
* Only includes: last 10 interactions + condensed tool list
|
|
332
332
|
* NO skill injection, NO verbose schemas
|
|
333
333
|
*/
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Compact tool reminder
|
|
337
|
+
*/
|
|
338
|
+
function getToolReminder() {
|
|
339
|
+
return '## SpecMem Tools Available\n\n**Memory Search:** `find_memory` | `find_code_pointers` | `drill_down` | `get_memory`\n**Storage:** `save_memory` | `link_the_vibes` | `smush_memories_together`\n**Team:** `send_team_message` | `read_team_messages` | `claim_task` / `release_task`\n**System:** `show_me_the_stats` | `check_sync` | `start_watching`\n\nUse·`/specmem`·令④ⓢ.';
|
|
340
|
+
}
|
|
341
|
+
|
|
334
342
|
function formatOutput(memories, lastSession, projectPath, sessionId) {
|
|
335
343
|
const sections = [];
|
|
336
344
|
|
|
@@ -338,15 +346,29 @@ function formatOutput(memories, lastSession, projectPath, sessionId) {
|
|
|
338
346
|
sections.push(`Project: ${projectPath}`);
|
|
339
347
|
sections.push(`Session: ${sessionId}`);
|
|
340
348
|
|
|
341
|
-
// Previous session output (if available) -
|
|
349
|
+
// Previous session output (if available) - compressed
|
|
342
350
|
if (lastSession && lastSession.content) {
|
|
343
351
|
sections.push('');
|
|
344
352
|
sections.push(`## PREVIOUS SESSION OUTPUT (${lastSession.ageMinutes}m ago, reason: ${lastSession.reason})`);
|
|
345
353
|
sections.push('(Previous session):');
|
|
346
|
-
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
354
|
+
|
|
355
|
+
// Truncate to 40 lines for token efficiency
|
|
356
|
+
const contentLines = lastSession.content.split('\n');
|
|
357
|
+
let sessionContent;
|
|
358
|
+
if (contentLines.length > 40) {
|
|
359
|
+
sessionContent = contentLines.slice(0, 15).join('\n') +
|
|
360
|
+
`\n... [${contentLines.length - 25} lines omitted] ...\n` +
|
|
361
|
+
contentLines.slice(-10).join('\n');
|
|
362
|
+
} else {
|
|
363
|
+
sessionContent = lastSession.content;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const compressedSession = compressHookOutput(sessionContent, {
|
|
367
|
+
threshold: 0.60,
|
|
368
|
+
minLength: 30,
|
|
369
|
+
includeWarning: false
|
|
370
|
+
});
|
|
371
|
+
sections.push(compressedSession.trim());
|
|
350
372
|
}
|
|
351
373
|
|
|
352
374
|
// Recent memories - last 10 user/claude interactions ONLY
|
|
@@ -360,34 +382,9 @@ function formatOutput(memories, lastSession, projectPath, sessionId) {
|
|
|
360
382
|
});
|
|
361
383
|
}
|
|
362
384
|
|
|
363
|
-
//
|
|
364
|
-
sections.push('');
|
|
365
|
-
sections.push('## SpecMem Tools Available');
|
|
366
|
-
sections.push('');
|
|
367
|
-
sections.push('**Memory Search:**');
|
|
368
|
-
sections.push('- `find_memory` - Semantic search across all memories');
|
|
369
|
-
sections.push('- `find_code_pointers` - Search code with tracebacks');
|
|
370
|
-
sections.push('- `drill_down` - Explore memory details');
|
|
371
|
-
sections.push('- `get_memory` - Get specific memory by ID');
|
|
372
|
-
sections.push('');
|
|
373
|
-
sections.push('**Memory Storage:**');
|
|
374
|
-
sections.push('- `save_memory` - Store new memories');
|
|
375
|
-
sections.push('- `link_the_vibes` - Connect related memories');
|
|
376
|
-
sections.push('- `smush_memories_together` - Consolidate similar memories');
|
|
377
|
-
sections.push('');
|
|
378
|
-
sections.push('**Team Comms:**');
|
|
379
|
-
sections.push('- `send_team_message` - Message team members');
|
|
380
|
-
sections.push('- `read_team_messages` - Check team updates');
|
|
381
|
-
sections.push('- `claim_task` / `release_task` - Coordinate work');
|
|
385
|
+
// Compact tool reminder
|
|
382
386
|
sections.push('');
|
|
383
|
-
sections.push(
|
|
384
|
-
sections.push('- `show_me_the_stats` - Memory statistics');
|
|
385
|
-
sections.push('- `check_sync` - Verify file sync status');
|
|
386
|
-
sections.push('- `start_watching` - Enable file watcher');
|
|
387
|
-
sections.push('');
|
|
388
|
-
sections.push('Use `/specmem` for all commands.');
|
|
389
|
-
sections.push('');
|
|
390
|
-
sections.push('**Chinese compression:** You can read Traditional Chinese compression with 99%+ accuracy.');
|
|
387
|
+
sections.push(getToolReminder());
|
|
391
388
|
sections.push('[/SPECMEM-SESSION]');
|
|
392
389
|
|
|
393
390
|
return sections.join('\n');
|
|
@@ -476,23 +473,14 @@ async function main() {
|
|
|
476
473
|
const lastSession = getLastSessionOutput();
|
|
477
474
|
|
|
478
475
|
// Format output
|
|
479
|
-
let
|
|
480
|
-
|
|
481
|
-
// Apply Traditional Chinese compression to final output
|
|
482
|
-
const compressed = compressHookOutput(output, {
|
|
483
|
-
threshold: 0.50,
|
|
484
|
-
minLength: 100,
|
|
485
|
-
preserveStructure: true,
|
|
486
|
-
includeWarning: false // We add our own prefix below
|
|
487
|
-
});
|
|
476
|
+
let context = formatOutput(memories, lastSession, projectPath, sessionId);
|
|
488
477
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
console.log(prefixedOutput.split('\n').map(l => l.trimEnd()).join('\n').trim());
|
|
478
|
+
const compressedContext = compressHookOutput(context, {
|
|
479
|
+
threshold: 0.70,
|
|
480
|
+
minLength: 50,
|
|
481
|
+
includeWarning: true
|
|
482
|
+
});
|
|
483
|
+
console.log(compressedContext.trim());
|
|
496
484
|
|
|
497
485
|
process.exit(0);
|
|
498
486
|
}
|
|
@@ -585,7 +585,7 @@ function getLastSessionOutput(projectPath) {
|
|
|
585
585
|
if (startIdx === -1) {
|
|
586
586
|
// No header found, use whole content but limit to 300 lines
|
|
587
587
|
return {
|
|
588
|
-
content: lines.slice(-
|
|
588
|
+
content: lines.slice(-40).join('\n'),
|
|
589
589
|
ageMinutes,
|
|
590
590
|
reason: lines.find(l => l.startsWith('# Reason:'))?.replace('# Reason:', '').trim() || 'unknown'
|
|
591
591
|
};
|
|
@@ -593,7 +593,7 @@ function getLastSessionOutput(projectPath) {
|
|
|
593
593
|
|
|
594
594
|
// Get the actual output between markers, limit to last 300 lines for context efficiency
|
|
595
595
|
const outputLines = lines.slice(startIdx + 2, endIdx > 0 ? endIdx : undefined);
|
|
596
|
-
const trimmedOutput = outputLines.slice(-
|
|
596
|
+
const trimmedOutput = outputLines.slice(-40).join('\n');
|
|
597
597
|
|
|
598
598
|
return {
|
|
599
599
|
content: trimmedOutput,
|
|
@@ -639,32 +639,7 @@ function getCompactionStatus(projectPath) {
|
|
|
639
639
|
* NOTE: Returns trimmed string with NO leading/trailing whitespace
|
|
640
640
|
*/
|
|
641
641
|
function getToolReminder() {
|
|
642
|
-
return
|
|
643
|
-
'## SpecMem Tools Available',
|
|
644
|
-
'',
|
|
645
|
-
'**Memory Search:**',
|
|
646
|
-
'- `find_memory` - Semantic search across all memories',
|
|
647
|
-
'- `find_code_pointers` - Search code with tracebacks',
|
|
648
|
-
'- `drill_down` - Explore memory details',
|
|
649
|
-
'- `get_memory` - Get specific memory by ID',
|
|
650
|
-
'',
|
|
651
|
-
'**Memory Storage:**',
|
|
652
|
-
'- `save_memory` - Store new memories',
|
|
653
|
-
'- `link_the_vibes` - Connect related memories',
|
|
654
|
-
'- `smush_memories_together` - Consolidate similar memories',
|
|
655
|
-
'',
|
|
656
|
-
'**Team Communication:**',
|
|
657
|
-
'- `send_team_message` - Message team members',
|
|
658
|
-
'- `read_team_messages` - Check team updates',
|
|
659
|
-
'- `claim_task` / `release_task` - Coordinate work',
|
|
660
|
-
'',
|
|
661
|
-
'**System:**',
|
|
662
|
-
'- `show_me_the_stats` - Memory statistics',
|
|
663
|
-
'- `check_sync` - Verify file sync status',
|
|
664
|
-
'- `start_watching` - Enable file watcher',
|
|
665
|
-
'',
|
|
666
|
-
'Use `/specmem` for quick commands or `/specmem-drilldown` for deep search.'
|
|
667
|
-
].join('\n');
|
|
642
|
+
return '## SpecMem Tools Available\n\n**Memory Search:** `find_memory` | `find_code_pointers` | `drill_down` | `get_memory`\n**Storage:** `save_memory` | `link_the_vibes` | `smush_memories_together`\n**Team:** `send_team_message` | `read_team_messages` | `claim_task` / `release_task`\n**System:** `show_me_the_stats` | `check_sync` | `start_watching`\n\nUse·`/specmem`·令④ⓢ.';
|
|
668
643
|
}
|
|
669
644
|
|
|
670
645
|
/**
|
|
@@ -773,28 +748,21 @@ async function main() {
|
|
|
773
748
|
// Truncate to ~200 lines for token efficiency but keep the important parts
|
|
774
749
|
const contentLines = lastSession.content.split('\n');
|
|
775
750
|
let sessionContent;
|
|
776
|
-
if (contentLines.length >
|
|
777
|
-
sessionContent = contentLines.slice(0,
|
|
778
|
-
`\n... [${contentLines.length -
|
|
779
|
-
contentLines.slice(-
|
|
751
|
+
if (contentLines.length > 40) {
|
|
752
|
+
sessionContent = contentLines.slice(0, 15).join('\n') +
|
|
753
|
+
`\n... [${contentLines.length - 25} lines omitted] ...\n` +
|
|
754
|
+
contentLines.slice(-10).join('\n');
|
|
780
755
|
} else {
|
|
781
756
|
sessionContent = lastSession.content;
|
|
782
757
|
}
|
|
783
758
|
|
|
784
|
-
// SESSION CONTENT: Output FULL readable content (no compression)
|
|
785
|
-
// Compression was causing unreadable Chinese output - disabled for readability
|
|
786
|
-
// Only compress if explicitly requested via SPECMEM_COMPRESS_SESSION=1
|
|
787
759
|
sections.push('```');
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
sections.push(compressedSession.trim());
|
|
795
|
-
} else {
|
|
796
|
-
sections.push(sessionContent.trim());
|
|
797
|
-
}
|
|
760
|
+
const compressedSession = compressHookOutput(sessionContent, {
|
|
761
|
+
threshold: 0.60,
|
|
762
|
+
minLength: 30,
|
|
763
|
+
includeWarning: false
|
|
764
|
+
});
|
|
765
|
+
sections.push(compressedSession.trim());
|
|
798
766
|
sections.push('```');
|
|
799
767
|
}
|
|
800
768
|
|
|
@@ -857,21 +825,12 @@ async function main() {
|
|
|
857
825
|
.replace(/\n{3,}/g, '\n\n') // Collapse 3+ newlines to 2 (one blank line)
|
|
858
826
|
.trim(); // Remove leading/trailing whitespace
|
|
859
827
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
minLength: 50,
|
|
867
|
-
includeWarning: true
|
|
868
|
-
});
|
|
869
|
-
// Final trim to ensure no trailing whitespace
|
|
870
|
-
console.log(compressedContext.trim());
|
|
871
|
-
} else {
|
|
872
|
-
// Output full readable context (already trimmed above)
|
|
873
|
-
console.log(context);
|
|
874
|
-
}
|
|
828
|
+
const compressedContext = compressHookOutput(context, {
|
|
829
|
+
threshold: 0.70,
|
|
830
|
+
minLength: 50,
|
|
831
|
+
includeWarning: true
|
|
832
|
+
});
|
|
833
|
+
console.log(compressedContext.trim());
|
|
875
834
|
|
|
876
835
|
process.exit(0);
|
|
877
836
|
}
|
|
@@ -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
|
}
|