specmem-hardwicksoftware 3.7.42 → 3.7.43
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.
|
@@ -24,10 +24,21 @@ function isAgent() {
|
|
|
24
24
|
if (process.env.CLAUDE_SUBAGENT === '1') return true;
|
|
25
25
|
if (process.env.CLAUDE_AGENT_ID) return true;
|
|
26
26
|
|
|
27
|
+
// CLAUDE_CODE_ENTRYPOINT=task is set by Agent tool when spawning subagents
|
|
28
|
+
if (process.env.CLAUDE_CODE_ENTRYPOINT === 'task') return true;
|
|
29
|
+
|
|
27
30
|
// Check if this is a team member screen session (has specmem-tm- prefix)
|
|
28
|
-
// Regular
|
|
31
|
+
// Regular Claude screens are claude-<project>, not specmem-tm-
|
|
29
32
|
if (process.env.STY && process.env.STY.includes('specmem-tm-')) return true;
|
|
30
33
|
|
|
34
|
+
// Debug: log env vars for troubleshooting
|
|
35
|
+
const relevantEnvVars = Object.keys(process.env)
|
|
36
|
+
.filter(k => k.includes('AGENT') || k.includes('SUBAGENT') || k.includes('TASK') || k.includes('ENTRYPOINT'))
|
|
37
|
+
.reduce((acc, k) => ({ ...acc, [k]: process.env[k] }), {});
|
|
38
|
+
if (Object.keys(relevantEnvVars).length > 0) {
|
|
39
|
+
console.error('[is-agent] Relevant env vars found:', relevantEnvVars);
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
// Check working directory for agent-specific paths
|
|
32
43
|
try {
|
|
33
44
|
const cwd = process.cwd();
|
|
@@ -37,7 +48,7 @@ function isAgent() {
|
|
|
37
48
|
}
|
|
38
49
|
} catch (e) {}
|
|
39
50
|
|
|
40
|
-
// Default: NOT an agent (main
|
|
51
|
+
// Default: NOT an agent (main session gets unrestricted access)
|
|
41
52
|
return false;
|
|
42
53
|
}
|
|
43
54
|
|
|
@@ -46,7 +57,8 @@ function isAgent() {
|
|
|
46
57
|
*/
|
|
47
58
|
function isSubagent() {
|
|
48
59
|
return process.env.CLAUDE_SUBAGENT === '1' ||
|
|
49
|
-
process.env.CLAUDE_AGENT_ID !== undefined
|
|
60
|
+
process.env.CLAUDE_AGENT_ID !== undefined ||
|
|
61
|
+
process.env.CLAUDE_CODE_ENTRYPOINT === 'task';
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
/**
|
|
@@ -54,6 +66,7 @@ function isSubagent() {
|
|
|
54
66
|
*/
|
|
55
67
|
function isTeamMember() {
|
|
56
68
|
return process.env.SPECMEM_TEAM_MEMBER_ID !== undefined ||
|
|
69
|
+
process.env.CLAUDE_CODE_ENTRYPOINT === 'task' ||
|
|
57
70
|
(process.env.STY && process.env.STY.includes('specmem-tm-'));
|
|
58
71
|
}
|
|
59
72
|
|
|
@@ -76,10 +76,11 @@ try {
|
|
|
76
76
|
// ============================================================================
|
|
77
77
|
// CONFIGURATION
|
|
78
78
|
// ============================================================================
|
|
79
|
-
const MAX_SEARCHES_BEFORE_BLOCK =
|
|
80
|
-
const TEAM_COMMS_CHECK_INTERVAL = 3; // MUST send_team_message every 3 tool
|
|
81
|
-
const BROADCAST_CHECK_INTERVAL = 5; // MUST read_team_messages w/ include_broadcasts every 5
|
|
82
|
-
const HELP_CHECK_INTERVAL = 8; // Check help requests every 8
|
|
79
|
+
const MAX_SEARCHES_BEFORE_BLOCK = 1; // STRICT: Every 1 Grep/Glob/Read must use find_code_pointers/find_memory
|
|
80
|
+
const TEAM_COMMS_CHECK_INTERVAL = 3; // MUST send_team_message every 3 Grep/Glob/Read (not every tool)
|
|
81
|
+
const BROADCAST_CHECK_INTERVAL = 5; // MUST read_team_messages w/ include_broadcasts every 5 Grep/Glob/Read
|
|
82
|
+
const HELP_CHECK_INTERVAL = 8; // Check help requests every 8 Grep/Glob/Read
|
|
83
|
+
const MEMORY_USAGE_INTERVAL = 3; // MUST use memory tools every 3 Grep/Glob/Read
|
|
83
84
|
|
|
84
85
|
// Tools that count as "announcing"
|
|
85
86
|
const ANNOUNCE_TOOLS = [
|
|
@@ -115,8 +116,8 @@ const HELP_CHECK_TOOLS = [
|
|
|
115
116
|
];
|
|
116
117
|
|
|
117
118
|
// Basic search tools (limited before requiring memory tools)
|
|
118
|
-
// NOTE: Read
|
|
119
|
-
const BASIC_SEARCH_TOOLS = ['Grep', 'Glob'];
|
|
119
|
+
// NOTE: Read IS included now - agents were abusing it to bypass limits
|
|
120
|
+
const BASIC_SEARCH_TOOLS = ['Grep', 'Glob', 'Read'];
|
|
120
121
|
|
|
121
122
|
// READ + SEARCH combined — forces team msg every 3 reads OR searches
|
|
122
123
|
const READ_SEARCH_TOOLS = ['Read', 'Grep', 'Glob'];
|
|
@@ -184,6 +185,7 @@ function getAgentState(tracking, sessionId) {
|
|
|
184
185
|
commsToolCount: 0, // Tool calls since last team comms check (every 4)
|
|
185
186
|
broadcastToolCount: 0, // Tool calls since last broadcast check (every 5)
|
|
186
187
|
helpToolUsageCount: 0, // Tool calls since last help check (every 8)
|
|
188
|
+
memoryToolCount: 0, // Tool calls since last memory tool usage (every 3)
|
|
187
189
|
lastCommsCheck: Date.now(),
|
|
188
190
|
lastBroadcastCheck: Date.now(),
|
|
189
191
|
lastHelpCheck: Date.now(),
|
|
@@ -258,6 +260,7 @@ function isSpecmemEnabled() {
|
|
|
258
260
|
function blockResponse(tool, reason) {
|
|
259
261
|
// Each denial specifies the EXACT tool to call. Framed as team collaboration,
|
|
260
262
|
// not enforcement. "team member" language, never "agent".
|
|
263
|
+
console.error(`[TEAM COMMS] BLOCKING: ${tool} - ${reason}`);
|
|
261
264
|
const msg = `[TEAM COLLABORATION - call ${tool} then retry] ${reason}`;
|
|
262
265
|
return JSON.stringify({
|
|
263
266
|
hookSpecificOutput: {
|
|
@@ -298,19 +301,24 @@ process.stdin.on('end', () => {
|
|
|
298
301
|
const toolName = data.tool_name;
|
|
299
302
|
const sessionId = data.session_id || 'unknown';
|
|
300
303
|
|
|
304
|
+
// DEBUG: Log detection status
|
|
305
|
+
const runningAsAgent = isRunningAsAgent();
|
|
306
|
+
const isSpecmem = isSpecmemProject();
|
|
307
|
+
console.error(`[TEAM COMMS] Tool: ${toolName}, RunningAsAgent: ${runningAsAgent}, IsSpecmemProject: ${isSpecmem}`);
|
|
308
|
+
|
|
301
309
|
// ========================================================================
|
|
302
310
|
// AGENT DETECTION - ONLY ENFORCE ON AGENT SESSIONS
|
|
303
311
|
// Uses env vars (CLAUDE_SUBAGENT, CLAUDE_AGENT_ID, etc.) + subagent tracking
|
|
304
312
|
// Main Claude window is NEVER blocked, only subagents/team members
|
|
305
313
|
// ========================================================================
|
|
306
|
-
if (!
|
|
314
|
+
if (!runningAsAgent) {
|
|
307
315
|
// Not an agent - pass through immediately
|
|
308
316
|
console.log(JSON.stringify({ continue: true }));
|
|
309
317
|
return;
|
|
310
318
|
}
|
|
311
319
|
|
|
312
320
|
// Verify this is a specmem-enabled project
|
|
313
|
-
if (!
|
|
321
|
+
if (!isSpecmem) {
|
|
314
322
|
console.log(JSON.stringify({ continue: true }));
|
|
315
323
|
return;
|
|
316
324
|
}
|
|
@@ -386,6 +394,7 @@ process.stdin.on('end', () => {
|
|
|
386
394
|
if (MEMORY_TOOLS.includes(toolName)) {
|
|
387
395
|
state.usedMemoryTools = true;
|
|
388
396
|
state.searchCount = 0; // Reset search counter — allows next 2 searches
|
|
397
|
+
state.memoryToolCount = 0; // Reset memory tool counter - they just used one!
|
|
389
398
|
// usedMemoryTools resets to false after 2 more searches (see BASIC_SEARCH_TOOLS block)
|
|
390
399
|
}
|
|
391
400
|
// Track Read/Search count for team comms cadence
|
|
@@ -430,9 +439,25 @@ process.stdin.on('end', () => {
|
|
|
430
439
|
state.commsToolCount = (state.commsToolCount || 0) + 1;
|
|
431
440
|
state.broadcastToolCount = (state.broadcastToolCount || 0) + 1;
|
|
432
441
|
state.helpToolUsageCount = (state.helpToolUsageCount || 0) + 1;
|
|
442
|
+
// Count all tools for memory usage enforcement
|
|
443
|
+
state.memoryToolCount = (state.memoryToolCount || 0) + 1;
|
|
444
|
+
|
|
445
|
+
// ========================================================================
|
|
446
|
+
// HARD BLOCK: Must use memory tools every 3 tool usages
|
|
447
|
+
// find_code_pointers, find_memory, drill_down satisfy this
|
|
448
|
+
// ========================================================================
|
|
449
|
+
if (state.memoryToolCount >= MEMORY_USAGE_INTERVAL && !MEMORY_TOOLS.includes(toolName)) {
|
|
450
|
+
state.blockedCount++;
|
|
451
|
+
saveTracking(tracking);
|
|
452
|
+
console.log(blockResponse(
|
|
453
|
+
'mcp__specmem__find_code_pointers',
|
|
454
|
+
`Use semantic search to find context! Call: find_code_pointers({query:"what you need"}) or find_memory({query:"topic from conversation"})`
|
|
455
|
+
));
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
433
458
|
|
|
434
459
|
// ========================================================================
|
|
435
|
-
// HARD BLOCK: Must send team message every
|
|
460
|
+
// HARD BLOCK: Must send team message every 4 tool usages
|
|
436
461
|
// send_team_message() or broadcast_to_team() satisfies this
|
|
437
462
|
// ========================================================================
|
|
438
463
|
if (state.commsToolCount >= TEAM_COMMS_CHECK_INTERVAL && !ANNOUNCE_TOOLS.includes(toolName)) {
|
|
@@ -824,27 +824,50 @@ export class EmbeddingServerManager extends EventEmitter {
|
|
|
824
824
|
}
|
|
825
825
|
/**
|
|
826
826
|
* Release the file-based start lock
|
|
827
|
+
* FIX: Use rename-to-temp + unlink for atomic release (prevents race condition)
|
|
827
828
|
*/
|
|
828
829
|
releaseStartLock() {
|
|
829
830
|
try {
|
|
830
831
|
if (existsSync(this.startLockPath)) {
|
|
831
832
|
// FIX 2.14: Verify ownership before releasing - only the lock owner should release
|
|
833
|
+
// FIX 2.15: Use atomic rename-then-unlink to prevent race conditions
|
|
834
|
+
let isOwner = false;
|
|
832
835
|
try {
|
|
833
836
|
const lockContent = readFileSync(this.startLockPath, 'utf8').trim();
|
|
834
837
|
const parts = lockContent.split(':');
|
|
835
838
|
const lockPid = parseInt(parts[1], 10);
|
|
836
|
-
if (!isNaN(lockPid) && lockPid
|
|
839
|
+
if (!isNaN(lockPid) && lockPid === process.pid) {
|
|
840
|
+
isOwner = true;
|
|
841
|
+
} else if (!isNaN(lockPid) && lockPid !== process.pid) {
|
|
837
842
|
logger.warn({ lockPid, myPid: process.pid }, '[EmbeddingServerManager] Lock release by non-owner - ignoring');
|
|
838
843
|
return false;
|
|
839
844
|
}
|
|
845
|
+
// If no valid PID in lock, consider ourselves owner to clean up
|
|
846
|
+
isOwner = true;
|
|
840
847
|
}
|
|
841
848
|
catch (readErr) {
|
|
842
849
|
// Can't verify ownership - proceed with release (lock may be corrupt)
|
|
843
850
|
logger.debug({ error: readErr }, '[EmbeddingServerManager] Cannot read lock for ownership check, releasing anyway');
|
|
851
|
+
isOwner = true;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (isOwner) {
|
|
855
|
+
// Atomic release: rename to temp path, then unlink
|
|
856
|
+
// This prevents race where another process acquires lock between read and delete
|
|
857
|
+
const tempPath = this.startLockPath + '.releasing.' + process.pid;
|
|
858
|
+
try {
|
|
859
|
+
// First, try atomic rename (will fail if lock was taken)
|
|
860
|
+
renameSync(this.startLockPath, tempPath);
|
|
861
|
+
// If rename succeeded, we own it - now safely unlink
|
|
862
|
+
unlinkSync(tempPath);
|
|
863
|
+
logger.debug('[EmbeddingServerManager] Released start lock (atomic)');
|
|
864
|
+
return true;
|
|
865
|
+
} catch (renameErr) {
|
|
866
|
+
// Rename failed - another process acquired the lock
|
|
867
|
+
logger.debug({ error: renameErr }, '[EmbeddingServerManager] Lock was taken during release, ignoring');
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
844
870
|
}
|
|
845
|
-
unlinkSync(this.startLockPath);
|
|
846
|
-
logger.debug('[EmbeddingServerManager] Released start lock');
|
|
847
|
-
return true;
|
|
848
871
|
}
|
|
849
872
|
return true; // No lock to release
|
|
850
873
|
}
|
|
@@ -731,10 +731,12 @@ Examples:
|
|
|
731
731
|
storage: isDBAvailable() ? 'postgresql' : 'memory'
|
|
732
732
|
});
|
|
733
733
|
// Human-readable response
|
|
734
|
+
const typeIcon = { status: '🔄', question: '❓', update: '📝', broadcast: '📢', help_request: '🆘', help_response: '💡' }[type] || '💬';
|
|
735
|
+
const priIcon = { urgent: ' ‼️URGENT', high: ' ❗', normal: '', low: '' }[priority] || '';
|
|
734
736
|
return {
|
|
735
737
|
content: [{
|
|
736
738
|
type: 'text',
|
|
737
|
-
text:
|
|
739
|
+
text: `✅ ${typeIcon} Message sent to #${channel}${priIcon}\n "${message.slice(0, 100)}${message.length > 100 ? '...' : ''}"`
|
|
738
740
|
}]
|
|
739
741
|
};
|
|
740
742
|
}
|
|
@@ -1091,28 +1093,28 @@ Returns messages sorted by newest first.`;
|
|
|
1091
1093
|
help_request: '🆘',
|
|
1092
1094
|
help_response: '💡'
|
|
1093
1095
|
};
|
|
1094
|
-
const priorityTag = { urgent: '‼️', high: '❗', normal: '', low: '' };
|
|
1096
|
+
const priorityTag = { urgent: ' ‼️URGENT', high: ' ❗', normal: '', low: '' };
|
|
1095
1097
|
const formatTimeAgo = (date) => {
|
|
1096
1098
|
const now = Date.now();
|
|
1097
1099
|
const then = new Date(date).getTime();
|
|
1098
1100
|
const diffSec = Math.floor((now - then) / 1000);
|
|
1099
|
-
if (diffSec < 60) return `${diffSec}s
|
|
1101
|
+
if (diffSec < 60) return `${diffSec}s`;
|
|
1100
1102
|
const diffMin = Math.floor(diffSec / 60);
|
|
1101
|
-
if (diffMin < 60) return `${diffMin}m
|
|
1103
|
+
if (diffMin < 60) return `${diffMin}m`;
|
|
1102
1104
|
const diffHr = Math.floor(diffMin / 60);
|
|
1103
|
-
if (diffHr < 24) return `${diffHr}h
|
|
1104
|
-
return `${Math.floor(diffHr / 24)}d
|
|
1105
|
+
if (diffHr < 24) return `${diffHr}h`;
|
|
1106
|
+
return `${Math.floor(diffHr / 24)}d`;
|
|
1105
1107
|
};
|
|
1106
1108
|
const formatMessage = (m) => {
|
|
1107
1109
|
const emoji = typeEmoji[m.type] || '💬';
|
|
1108
|
-
const unread = m.is_unread ? '
|
|
1110
|
+
const unread = m.is_unread ? ' ✨' : '';
|
|
1109
1111
|
const pri = priorityTag[m.priority] || '';
|
|
1110
1112
|
const time = formatTimeAgo(m.timestamp);
|
|
1111
1113
|
const from = m.sender_name || m.sender || '?';
|
|
1112
1114
|
// Content already compressed by verified compressor above
|
|
1113
|
-
const content = m.content.slice(0,
|
|
1114
|
-
const truncated = m.content.length >
|
|
1115
|
-
return `
|
|
1115
|
+
const content = m.content.slice(0, 300).replace(/\n/g, ' ');
|
|
1116
|
+
const truncated = m.content.length > 300 ? '…' : '';
|
|
1117
|
+
return ` ${emoji}${unread}${pri} ${from} [${time}]: ${content}${truncated}`;
|
|
1116
1118
|
};
|
|
1117
1119
|
// Group messages by channel for sortable output
|
|
1118
1120
|
const byChannel = {};
|
|
@@ -1133,15 +1135,15 @@ Returns messages sorted by newest first.`;
|
|
|
1133
1135
|
const msgs = byChannel[ch];
|
|
1134
1136
|
const chUnread = msgs.filter(m => m.is_unread).length;
|
|
1135
1137
|
const chLabel = chUnread > 0 ? `#${ch} (${chUnread} new)` : `#${ch}`;
|
|
1136
|
-
msgList += `\n
|
|
1138
|
+
msgList += `\n┌─ ${chLabel} ─\n`;
|
|
1137
1139
|
msgList += msgs.map(m => formatMessage(m)).join('\n');
|
|
1138
1140
|
msgList += '\n';
|
|
1139
1141
|
}
|
|
1140
|
-
const header =
|
|
1141
|
-
const footer =
|
|
1142
|
+
const header = `📬 Team Messages (${messages.length} total, ${unreadCount} unread)`;
|
|
1143
|
+
const footer = '─────────────────────────';
|
|
1142
1144
|
const output = messages.length > 0
|
|
1143
|
-
? `${header}${msgList}${footer}`
|
|
1144
|
-
: `${header}\n
|
|
1145
|
+
? `${header}${msgList}\n${footer}`
|
|
1146
|
+
: `${header}\n\n No messages yet\n${footer}`;
|
|
1145
1147
|
return { content: [{ type: 'text', text: output }] };
|
|
1146
1148
|
}
|
|
1147
1149
|
}
|
|
@@ -1260,10 +1262,11 @@ Use cross_project: true for system-wide announcements (use sparingly).`;
|
|
|
1260
1262
|
storage: isDBAvailable() ? 'postgresql' : 'memory'
|
|
1261
1263
|
});
|
|
1262
1264
|
// Human-readable response
|
|
1265
|
+
const bcastIcon = { status: '🔄', update: '📢', alert: '🚨', milestone: '🎉' }[broadcast_type] || '📣';
|
|
1263
1266
|
return {
|
|
1264
1267
|
content: [{
|
|
1265
1268
|
type: 'text',
|
|
1266
|
-
text:
|
|
1269
|
+
text: `📣 ${bcastIcon} Broadcast sent to ALL teams\n "${message.slice(0, 80)}${message.length > 80 ? '...' : ''}"`
|
|
1267
1270
|
}]
|
|
1268
1271
|
};
|
|
1269
1272
|
}
|
|
@@ -1374,17 +1377,17 @@ Best practice: Always claim before starting work on a task.`;
|
|
|
1374
1377
|
warnings,
|
|
1375
1378
|
storage: isDBAvailable() ? 'postgresql' : 'memory'
|
|
1376
1379
|
});
|
|
1377
|
-
|
|
1380
|
+
});
|
|
1381
|
+
// Human-readable return
|
|
1382
|
+
const fileList = files && files.length > 0 ? `\n Files: ${files.join(', ')}` : '';
|
|
1383
|
+
const warningText = warnings && warnings.length > 0 ? `\n ⚠️ ${warnings.join(' | ')}` : '';
|
|
1378
1384
|
return {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
timestamp: claimedAt,
|
|
1384
|
-
warnings
|
|
1385
|
+
content: [{
|
|
1386
|
+
type: 'text',
|
|
1387
|
+
text: `✅ Task claimed: ${description.slice(0, 50)}${description.length > 50 ? '...' : ''}\n ID: ${claimId.slice(0, 8)}${fileList}${warningText}`
|
|
1388
|
+
}]
|
|
1385
1389
|
};
|
|
1386
1390
|
}
|
|
1387
|
-
}
|
|
1388
1391
|
export class ReleaseTask {
|
|
1389
1392
|
name = 'release_task';
|
|
1390
1393
|
description = `Release files or entire task claims.
|
|
@@ -1537,14 +1540,16 @@ Best practice: Release files as you finish them, release entire claim when task
|
|
|
1537
1540
|
memberId,
|
|
1538
1541
|
storage: isDBAvailable() ? 'postgresql' : 'memory'
|
|
1539
1542
|
});
|
|
1540
|
-
//
|
|
1543
|
+
// Human-readable
|
|
1544
|
+
const filesMsg = releasedFiles.length > 0 ? ` (${releasedFiles.length} files)` : '';
|
|
1541
1545
|
const message = releasedClaims.length === 1
|
|
1542
|
-
?
|
|
1543
|
-
:
|
|
1546
|
+
? `✅ Released: ${releasedClaims[0].slice(0, 8)}${filesMsg}`
|
|
1547
|
+
: `✅ Released ${releasedClaims.length} claims${filesMsg}`;
|
|
1544
1548
|
return {
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1549
|
+
content: [{
|
|
1550
|
+
type: 'text',
|
|
1551
|
+
text: message
|
|
1552
|
+
}]
|
|
1548
1553
|
};
|
|
1549
1554
|
}
|
|
1550
1555
|
}
|
|
@@ -1737,12 +1742,12 @@ respond using the respond_to_help tool.`;
|
|
|
1737
1742
|
skills_needed,
|
|
1738
1743
|
storage: isDBAvailable() ? 'postgresql' : 'memory'
|
|
1739
1744
|
});
|
|
1740
|
-
//
|
|
1745
|
+
// Human-readable
|
|
1741
1746
|
return {
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1747
|
+
content: [{
|
|
1748
|
+
type: 'text',
|
|
1749
|
+
text: `🆘 Help request sent to team\n "${question.slice(0, 60)}${question.length > 60 ? '...' : ''}"`
|
|
1750
|
+
}]
|
|
1746
1751
|
};
|
|
1747
1752
|
}
|
|
1748
1753
|
}
|
|
@@ -1859,13 +1864,12 @@ will be notified.`;
|
|
|
1859
1864
|
respondedBy,
|
|
1860
1865
|
storage: isDBAvailable() ? 'postgresql' : 'memory'
|
|
1861
1866
|
});
|
|
1862
|
-
//
|
|
1867
|
+
// Human-readable
|
|
1863
1868
|
return {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
message: `Response sent to ${requester}`
|
|
1869
|
+
content: [{
|
|
1870
|
+
type: 'text',
|
|
1871
|
+
text: `💡 Response sent to @${requester}\n "${response.slice(0, 60)}${response.length > 60 ? '...' : ''}"`
|
|
1872
|
+
}]
|
|
1869
1873
|
};
|
|
1870
1874
|
}
|
|
1871
1875
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specmem-hardwicksoftware",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.43",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Your Claude Code sessions don't have to start from scratch anymore — SpecMem gives your AI real memory. It won't forget your conversations, your code, or your architecture decisions between sessions. That's the whole point. Semantic code indexing that actually works: TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, C, C++, HTML and more. It doesn't just track functions — it gets classes, methods, fields, constants, enums, macros, imports, structs, the whole codebase graph. There's chat memory too, powered by pgvector embeddings. You've also got token compression, team coordination, multi-agent comms, and file watching built in. 74+ MCP tools. Runs on PostgreSQL + Docker. It's kind of a big deal. justcalljon.pro",
|
|
6
6
|
"main": "dist/index.js",
|
package/scripts/specmem-init.cjs
CHANGED
|
@@ -9058,6 +9058,18 @@ CREATE INDEX IF NOT EXISTS idx_embedding_queue_project ON embedding_queue (proje
|
|
|
9058
9058
|
const claudeDir = path.join(projectPath, '.claude');
|
|
9059
9059
|
safeMkdir(path.join(claudeDir, 'commands'));
|
|
9060
9060
|
|
|
9061
|
+
// Deploy HOW_TO_USE.md to project root
|
|
9062
|
+
const howToUseSrc = path.join(__dirname, '..', 'HOW_TO_USE_SPECMEM_MCP.md');
|
|
9063
|
+
const howToUseDest = path.join(projectPath, 'HOW_TO_USE.md');
|
|
9064
|
+
if (fs.existsSync(howToUseSrc)) {
|
|
9065
|
+
try {
|
|
9066
|
+
fs.copyFileSync(howToUseSrc, howToUseDest);
|
|
9067
|
+
initLog('[SETUP] Deployed HOW_TO_USE.md to project root');
|
|
9068
|
+
} catch (e) {
|
|
9069
|
+
initLog('[SETUP] Failed to deploy HOW_TO_USE.md: ' + e.message);
|
|
9070
|
+
}
|
|
9071
|
+
}
|
|
9072
|
+
|
|
9061
9073
|
setupUI.setSubProgress(1);
|
|
9062
9074
|
setupUI.setSubStatus('✓ Project dirs ready');
|
|
9063
9075
|
await qqms();
|