specmem-hardwicksoftware 3.7.0 → 3.7.2
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/dist/init/claudeConfigInjector.js +103 -37
- package/package.json +1 -1
|
@@ -41,27 +41,72 @@ const HOOKS_DIR = path.join(CLAUDE_CONFIG_DIR, 'hooks');
|
|
|
41
41
|
const COMMANDS_DIR = path.join(CLAUDE_CONFIG_DIR, 'commands');
|
|
42
42
|
// stores per-project MCP configs in ~/.claude.json under "projects" key
|
|
43
43
|
const CLAUDE_JSON_PATH = path.join(HOME_DIR, '.claude.json');
|
|
44
|
-
// SpecMem directory detection -
|
|
44
|
+
// SpecMem directory detection - dynamically resolves from how specmem was launched
|
|
45
|
+
// Handles bootstrap.cjs AND bootstrap.js, works with ANY install location
|
|
46
|
+
function hasBootstrap(dir) {
|
|
47
|
+
return fs.existsSync(path.join(dir, 'bootstrap.cjs')) ||
|
|
48
|
+
fs.existsSync(path.join(dir, 'bootstrap.js'));
|
|
49
|
+
}
|
|
45
50
|
function getSpecmemRoot() {
|
|
46
|
-
// Check environment variable
|
|
47
|
-
if (process.env.SPECMEM_ROOT) {
|
|
51
|
+
// 1. Check environment variable (explicit override)
|
|
52
|
+
if (process.env.SPECMEM_ROOT && hasBootstrap(process.env.SPECMEM_ROOT)) {
|
|
48
53
|
return process.env.SPECMEM_ROOT;
|
|
49
54
|
}
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
// 2. Detect from current file location (__dirname is most reliable)
|
|
56
|
+
// dist/init/claudeConfigInjector.js -> go up 2 levels to package root
|
|
57
|
+
const fromThisFile = path.resolve(__dirname, '..', '..');
|
|
58
|
+
if (hasBootstrap(fromThisFile)) {
|
|
59
|
+
return fromThisFile;
|
|
60
|
+
}
|
|
61
|
+
// 3. Detect from process.argv - what script launched us
|
|
62
|
+
// e.g. node /usr/local/lib/.../bootstrap.cjs or /usr/lib/.../bin/specmem-cli.cjs
|
|
63
|
+
for (const arg of process.argv) {
|
|
64
|
+
if (typeof arg === 'string' && arg.includes('specmem')) {
|
|
65
|
+
// Resolve symlinks to get real path
|
|
66
|
+
try {
|
|
67
|
+
const realArg = fs.realpathSync(arg);
|
|
68
|
+
// Walk up from the script to find package root
|
|
69
|
+
let candidate = path.dirname(realArg);
|
|
70
|
+
for (let i = 0; i < 4; i++) {
|
|
71
|
+
if (hasBootstrap(candidate)) return candidate;
|
|
72
|
+
if (fs.existsSync(path.join(candidate, 'package.json'))) {
|
|
73
|
+
try {
|
|
74
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(candidate, 'package.json'), 'utf-8'));
|
|
75
|
+
if (pkg.name === 'specmem-hardwicksoftware') return candidate;
|
|
76
|
+
} catch { /* ignore */ }
|
|
77
|
+
}
|
|
78
|
+
candidate = path.dirname(candidate);
|
|
79
|
+
}
|
|
80
|
+
} catch { /* ignore resolve errors */ }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// 4. Try resolving the `specmem` command via PATH (execSync imported at top)
|
|
84
|
+
try {
|
|
85
|
+
const whichResult = execSync('which specmem 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
86
|
+
if (whichResult) {
|
|
87
|
+
const realBin = fs.realpathSync(whichResult);
|
|
88
|
+
// specmem binary is at <root>/bin/specmem-cli.cjs -> go up 2 levels
|
|
89
|
+
const candidate = path.resolve(path.dirname(realBin), '..');
|
|
90
|
+
if (hasBootstrap(candidate)) return candidate;
|
|
91
|
+
}
|
|
92
|
+
} catch { /* which not available or specmem not in PATH */ }
|
|
93
|
+
// 5. Fallback to cwd (dev mode - running from source)
|
|
94
|
+
if (hasBootstrap(process.cwd())) {
|
|
95
|
+
return process.cwd();
|
|
96
|
+
}
|
|
97
|
+
// 6. Last resort - return __dirname-based path even without bootstrap
|
|
98
|
+
return fromThisFile;
|
|
99
|
+
}
|
|
100
|
+
// Find the actual bootstrap file (cjs or js)
|
|
101
|
+
function findBootstrapPath(root) {
|
|
102
|
+
for (const name of ['bootstrap.cjs', 'bootstrap.js']) {
|
|
103
|
+
const p = path.join(root, name);
|
|
104
|
+
if (fs.existsSync(p)) return p;
|
|
105
|
+
}
|
|
106
|
+
return path.join(root, 'bootstrap.cjs'); // default
|
|
62
107
|
}
|
|
63
108
|
const SPECMEM_ROOT = getSpecmemRoot();
|
|
64
|
-
const BOOTSTRAP_PATH =
|
|
109
|
+
const BOOTSTRAP_PATH = findBootstrapPath(SPECMEM_ROOT);
|
|
65
110
|
const SOURCE_HOOKS_DIR = path.join(SPECMEM_ROOT, 'claude-hooks');
|
|
66
111
|
const SOURCE_COMMANDS_DIR = path.join(SPECMEM_ROOT, 'commands');
|
|
67
112
|
// ============================================================================
|
|
@@ -72,10 +117,10 @@ const SOURCE_COMMANDS_DIR = path.join(SPECMEM_ROOT, 'commands');
|
|
|
72
117
|
const HOOK_ENV = {
|
|
73
118
|
SPECMEM_HOME: path.join(HOME_DIR, '.specmem'), // Dynamic path using os.homedir()
|
|
74
119
|
SPECMEM_PKG: SPECMEM_ROOT, // Use detected package root
|
|
75
|
-
// Per-project socket paths - ${
|
|
120
|
+
// Per-project socket paths - ${PWD} is expanded at MCP server startup
|
|
76
121
|
SPECMEM_RUN_DIR: '${cwd}/specmem/sockets',
|
|
77
122
|
SPECMEM_EMBEDDING_SOCKET: '${cwd}/specmem/sockets/embeddings.sock',
|
|
78
|
-
SPECMEM_PROJECT_PATH: '${
|
|
123
|
+
SPECMEM_PROJECT_PATH: '${PWD}', // Dynamically set by Code
|
|
79
124
|
SPECMEM_SEARCH_LIMIT: '5',
|
|
80
125
|
SPECMEM_THRESHOLD: '0.30',
|
|
81
126
|
SPECMEM_MAX_CONTENT: '200'
|
|
@@ -155,7 +200,7 @@ export function isSpecmemMcpConfigured(projectPath) {
|
|
|
155
200
|
return false;
|
|
156
201
|
}
|
|
157
202
|
const specmem = config.mcpServers.specmem;
|
|
158
|
-
// Check if it points to a valid bootstrap
|
|
203
|
+
// Check if it points to a valid bootstrap file (cjs or js)
|
|
159
204
|
if (!specmem.args || specmem.args.length < 2) {
|
|
160
205
|
return false;
|
|
161
206
|
}
|
|
@@ -167,8 +212,8 @@ export function isSpecmemMcpConfigured(projectPath) {
|
|
|
167
212
|
// If projectPath specified, check if env has correct project path
|
|
168
213
|
if (projectPath) {
|
|
169
214
|
const configuredPath = specmem.env?.SPECMEM_PROJECT_PATH;
|
|
170
|
-
// ${PWD}
|
|
171
|
-
if (configuredPath && configuredPath !== '${PWD}' && configuredPath !== projectPath) {
|
|
215
|
+
// ${PWD} and ${cwd} are expanded at runtime by Claude Code, so they're valid
|
|
216
|
+
if (configuredPath && configuredPath !== '${PWD}' && configuredPath !== '${PWD}' && configuredPath !== projectPath) {
|
|
172
217
|
return false;
|
|
173
218
|
}
|
|
174
219
|
}
|
|
@@ -179,11 +224,11 @@ export function isSpecmemMcpConfigured(projectPath) {
|
|
|
179
224
|
* Returns true if changes were made
|
|
180
225
|
*/
|
|
181
226
|
function configureMcpServer() {
|
|
182
|
-
// Verify bootstrap
|
|
227
|
+
// Verify bootstrap file exists (cjs or js)
|
|
183
228
|
if (!fs.existsSync(BOOTSTRAP_PATH)) {
|
|
184
229
|
return {
|
|
185
230
|
configured: false,
|
|
186
|
-
error: `bootstrap
|
|
231
|
+
error: `bootstrap not found at ${BOOTSTRAP_PATH}`
|
|
187
232
|
};
|
|
188
233
|
}
|
|
189
234
|
const config = safeReadJson(CONFIG_PATH, {});
|
|
@@ -191,17 +236,16 @@ function configureMcpServer() {
|
|
|
191
236
|
config.mcpServers = {};
|
|
192
237
|
}
|
|
193
238
|
// Build the SpecMem MCP server config
|
|
194
|
-
// CRITICAL: ${
|
|
195
|
-
// NOTE: ${PWD} resolves at MCP server startup, ${cwd} resolves per-invocation
|
|
239
|
+
// CRITICAL: ${PWD} is expanded at MCP server startup to current working directory
|
|
196
240
|
const specmemConfig = {
|
|
197
241
|
command: 'node',
|
|
198
242
|
args: ['--max-old-space-size=250', BOOTSTRAP_PATH],
|
|
199
243
|
env: {
|
|
200
|
-
// Core paths - ${
|
|
244
|
+
// Core paths - ${PWD} gives us project isolation (dynamic per-directory)
|
|
201
245
|
HOME: HOME_DIR,
|
|
202
|
-
SPECMEM_PROJECT_PATH: '${
|
|
203
|
-
SPECMEM_WATCHER_ROOT_PATH: '${
|
|
204
|
-
SPECMEM_CODEBASE_PATH: '${
|
|
246
|
+
SPECMEM_PROJECT_PATH: '${PWD}',
|
|
247
|
+
SPECMEM_WATCHER_ROOT_PATH: '${PWD}',
|
|
248
|
+
SPECMEM_CODEBASE_PATH: '${PWD}',
|
|
205
249
|
// Database (use environment values or defaults)
|
|
206
250
|
SPECMEM_DB_HOST: process.env.SPECMEM_DB_HOST || 'localhost',
|
|
207
251
|
SPECMEM_DB_PORT: process.env.SPECMEM_DB_PORT || '5432',
|
|
@@ -265,8 +309,9 @@ function fixProjectMcpConfigs() {
|
|
|
265
309
|
// Scan all project entries
|
|
266
310
|
for (const [projectPath, projectConfig] of Object.entries(claudeJson.projects)) {
|
|
267
311
|
const config = projectConfig;
|
|
268
|
-
|
|
269
|
-
|
|
312
|
+
if (!config) continue;
|
|
313
|
+
// Case 1: Project has specmem MCP config but with outdated path
|
|
314
|
+
if (config.mcpServers?.specmem) {
|
|
270
315
|
const specmem = config.mcpServers.specmem;
|
|
271
316
|
const args = specmem.args || [];
|
|
272
317
|
// Check if the args contain an outdated specmem path
|
|
@@ -275,28 +320,49 @@ function fixProjectMcpConfigs() {
|
|
|
275
320
|
if (typeof arg === 'string' &&
|
|
276
321
|
arg.includes('specmem') &&
|
|
277
322
|
arg !== BOOTSTRAP_PATH &&
|
|
278
|
-
(arg.endsWith('index.js') || arg.endsWith('bootstrap.js'))) {
|
|
323
|
+
(arg.endsWith('index.js') || arg.endsWith('bootstrap.js') || arg.endsWith('bootstrap.cjs'))) {
|
|
279
324
|
needsUpdate = true;
|
|
280
325
|
return BOOTSTRAP_PATH;
|
|
281
326
|
}
|
|
282
327
|
return arg;
|
|
283
328
|
});
|
|
284
329
|
if (needsUpdate) {
|
|
285
|
-
// Update the args
|
|
286
330
|
specmem.args = updatedArgs;
|
|
287
|
-
// Ensure SPECMEM_PROJECT_PATH is set to actual project path
|
|
288
|
-
// CRITICAL: ${PWD} doesn't get expanded by Code, use literal path
|
|
289
331
|
if (!specmem.env) {
|
|
290
332
|
specmem.env = {};
|
|
291
333
|
}
|
|
292
|
-
if (!specmem.env.SPECMEM_PROJECT_PATH || specmem.env.SPECMEM_PROJECT_PATH === '${PWD}' || specmem.env.SPECMEM_PROJECT_PATH === '${
|
|
293
|
-
specmem.env.SPECMEM_PROJECT_PATH = projectPath;
|
|
334
|
+
if (!specmem.env.SPECMEM_PROJECT_PATH || specmem.env.SPECMEM_PROJECT_PATH === '${PWD}' || specmem.env.SPECMEM_PROJECT_PATH === '${PWD}') {
|
|
335
|
+
specmem.env.SPECMEM_PROJECT_PATH = projectPath;
|
|
294
336
|
}
|
|
295
337
|
logger.info({ projectPath, oldArgs: args, newArgs: updatedArgs }, '[ConfigInjector] Fixed outdated specmem path in project config');
|
|
296
338
|
fixed++;
|
|
297
339
|
modified = true;
|
|
298
340
|
}
|
|
299
341
|
}
|
|
342
|
+
// Case 2: Project has mcpServers but NO specmem entry (empty {} or missing key)
|
|
343
|
+
// This empty override hides the global config.json MCP server, so we inject it
|
|
344
|
+
else if (config.mcpServers && !config.mcpServers.specmem) {
|
|
345
|
+
// Don't clobber other MCP servers - only add specmem
|
|
346
|
+
config.mcpServers.specmem = {
|
|
347
|
+
command: 'node',
|
|
348
|
+
args: ['--max-old-space-size=250', BOOTSTRAP_PATH],
|
|
349
|
+
env: {
|
|
350
|
+
HOME: HOME_DIR,
|
|
351
|
+
SPECMEM_PROJECT_PATH: '${PWD}',
|
|
352
|
+
SPECMEM_WATCHER_ROOT_PATH: '${PWD}',
|
|
353
|
+
SPECMEM_CODEBASE_PATH: '${PWD}',
|
|
354
|
+
SPECMEM_DB_HOST: process.env.SPECMEM_DB_HOST || 'localhost',
|
|
355
|
+
SPECMEM_DB_PORT: process.env.SPECMEM_DB_PORT || '5432',
|
|
356
|
+
SPECMEM_SESSION_WATCHER_ENABLED: 'true',
|
|
357
|
+
SPECMEM_WATCHER_ENABLED: 'true',
|
|
358
|
+
SPECMEM_DASHBOARD_ENABLED: 'true',
|
|
359
|
+
SPECMEM_DASHBOARD_PORT: process.env.SPECMEM_DASHBOARD_PORT || '8595',
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
logger.info({ projectPath }, '[ConfigInjector] Injected specmem MCP server into project with empty mcpServers');
|
|
363
|
+
fixed++;
|
|
364
|
+
modified = true;
|
|
365
|
+
}
|
|
300
366
|
}
|
|
301
367
|
// Write back if modified
|
|
302
368
|
if (modified) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specmem-hardwicksoftware",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory system for coding sessions - semantic search with pgvector, token compression, team coordination, file watching. Needs root: installs system-wide hooks, manages docker/PostgreSQL, writes global configs, handles screen sessions. justcalljon.pro",
|
|
6
6
|
"main": "dist/index.js",
|