specmem-hardwicksoftware 3.7.29 → 3.7.31
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/bootstrap.cjs +19 -0
- package/claude-hooks/settings.json +99 -0
- package/claude-hooks/specmem-search-enforcer.cjs +229 -0
- package/claude-hooks/specmem-search-tracker.cjs +71 -0
- package/dist/config.js +11 -16
- package/dist/db/connectionPoolGoBrrr.js +3 -3
- package/dist/index.js +21 -4
- package/dist/mcp/compactionProxy.js +52 -17
- package/dist/mcp/embeddingServerManager.js +15 -1
- package/dist/mcp/mcpProtocolHandler.js +22 -4
- package/dist/mcp/specMemServer.js +16 -3
- package/dist/mcp/toolRegistry.js +19 -21
- package/dist/tools/goofy/checkSyncStatus.js +14 -7
- package/dist/watcher/fileWatcher.js +57 -20
- package/dist/watcher/syncChecker.js +11 -7
- package/package.json +1 -1
- package/scripts/global-postinstall.cjs +7 -2
- package/scripts/specmem-init.cjs +91 -111
- package/specmem/model-config.json +26 -6
- package/specmem/supervisord.conf +1 -1
- package/specmem/user-config.json +12 -0
|
@@ -2313,8 +2313,22 @@ export class EmbeddingServerManager extends EventEmitter {
|
|
|
2313
2313
|
logger.warn({
|
|
2314
2314
|
failures: this.config.maxFailuresBeforeRestart,
|
|
2315
2315
|
restartCount: this.restartCount,
|
|
2316
|
-
}, '[EmbeddingServerManager] Too many consecutive failures in container mode -
|
|
2316
|
+
}, '[EmbeddingServerManager] Too many consecutive failures in container mode - attempting container restart');
|
|
2317
2317
|
this.emit('unhealthy_container', { failures: this.config.maxFailuresBeforeRestart });
|
|
2318
|
+
// FIX: Auto-restart brain container when embedding is dead
|
|
2319
|
+
try {
|
|
2320
|
+
const { getContainerManager } = require('../container/containerManager.js');
|
|
2321
|
+
const projectPath = process.env['SPECMEM_PROJECT_PATH'] || process.cwd();
|
|
2322
|
+
const cm = getContainerManager(projectPath);
|
|
2323
|
+
logger.info({ projectPath }, '[EmbeddingServerManager] Restarting brain container...');
|
|
2324
|
+
await cm.start();
|
|
2325
|
+
logger.info('[EmbeddingServerManager] Brain container restarted successfully');
|
|
2326
|
+
this.isRunning = true;
|
|
2327
|
+
this.startTime = Date.now();
|
|
2328
|
+
} catch (containerErr) {
|
|
2329
|
+
logger.error({ error: containerErr?.message || containerErr },
|
|
2330
|
+
'[EmbeddingServerManager] Failed to restart brain container');
|
|
2331
|
+
}
|
|
2318
2332
|
}
|
|
2319
2333
|
else {
|
|
2320
2334
|
logger.warn({
|
|
@@ -424,12 +424,30 @@ export class MCPProtocolHandler {
|
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
/**
|
|
427
|
-
* batch handle multiple tool calls -
|
|
427
|
+
* batch handle multiple tool calls - with concurrency limit
|
|
428
|
+
* prevents overwhelming the db pool when claude fires 5+ calls at once
|
|
428
429
|
*/
|
|
429
430
|
async handleBatchToolCalls(calls) {
|
|
430
431
|
const results = [];
|
|
431
|
-
//
|
|
432
|
-
|
|
432
|
+
// inline concurrency limiter - no npm deps needed
|
|
433
|
+
// max 2 concurrent to leave headroom on 4-core/8GB systems
|
|
434
|
+
const _limitConcurrency = (concurrency) => {
|
|
435
|
+
let active = 0;
|
|
436
|
+
const queue = [];
|
|
437
|
+
const next = () => {
|
|
438
|
+
while (active < concurrency && queue.length > 0) {
|
|
439
|
+
active++;
|
|
440
|
+
const { fn, resolve, reject } = queue.shift();
|
|
441
|
+
fn().then(resolve, reject).finally(() => { active--; next(); });
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
return (fn) => new Promise((resolve, reject) => {
|
|
445
|
+
queue.push({ fn, resolve, reject });
|
|
446
|
+
next();
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
const limit = _limitConcurrency(2);
|
|
450
|
+
const promises = calls.map((call) => limit(async () => {
|
|
433
451
|
try {
|
|
434
452
|
const result = await this.handleToolCall(call.name, call.args);
|
|
435
453
|
return { name: call.name, result };
|
|
@@ -440,7 +458,7 @@ export class MCPProtocolHandler {
|
|
|
440
458
|
error: error instanceof Error ? error.message : 'unknown error'
|
|
441
459
|
};
|
|
442
460
|
}
|
|
443
|
-
});
|
|
461
|
+
}));
|
|
444
462
|
const settled = await Promise.allSettled(promises);
|
|
445
463
|
for (const result of settled) {
|
|
446
464
|
if (result.status === 'fulfilled') {
|
|
@@ -236,8 +236,9 @@ export class SpecMemServer {
|
|
|
236
236
|
this.announceToOnStartup();
|
|
237
237
|
// Auto-start Codebook Learner (resource-capped background service)
|
|
238
238
|
this._startCodebookLearner();
|
|
239
|
-
//
|
|
240
|
-
//
|
|
239
|
+
// NOTE: _triggerCodebaseIndexing() is also called from deferredInitPromise.then()
|
|
240
|
+
// after DB migrations complete (which create codebase_files table).
|
|
241
|
+
// This early call may no-op if DB not ready yet — the post-DB call is the critical one.
|
|
241
242
|
this._triggerCodebaseIndexing();
|
|
242
243
|
};
|
|
243
244
|
// get that db connection no cap
|
|
@@ -1285,6 +1286,14 @@ export class SpecMemServer {
|
|
|
1285
1286
|
await this.initializeMiniCOTServerManager();
|
|
1286
1287
|
startupLog('Mini COT server manager initialized');
|
|
1287
1288
|
logger.info('SpecMem MCP server fully initialized — all components ready');
|
|
1289
|
+
// FIX: Trigger codebase indexing AFTER DB init (migrations create codebase_files table)
|
|
1290
|
+
// Previously only called in oninitialized which fires BEFORE deferred DB init,
|
|
1291
|
+
// causing checkCodebaseIndexStatus to see missing table → needsReindex=false → skip
|
|
1292
|
+
try {
|
|
1293
|
+
await this._triggerCodebaseIndexing();
|
|
1294
|
+
} catch (indexErr) {
|
|
1295
|
+
logger.warn({ error: indexErr?.message }, 'Post-DB-init codebase indexing failed (non-fatal)');
|
|
1296
|
+
}
|
|
1288
1297
|
// Run initial sync on startup — ensures codebase is fresh when Claude Code launches
|
|
1289
1298
|
await this._runStartupSync();
|
|
1290
1299
|
// Start idle sync timer — auto-syncs when no tool calls for 60s
|
|
@@ -1354,7 +1363,11 @@ export class SpecMemServer {
|
|
|
1354
1363
|
const checkResult = await CheckSyncStatus.execute({ detailed: false }, wm);
|
|
1355
1364
|
const syncScore = checkResult?.syncScore ?? 100;
|
|
1356
1365
|
// Only resync if drift detected (score < 100)
|
|
1357
|
-
if (syncScore
|
|
1366
|
+
// Skip resync if indexing is still pending (syncScore === -1)
|
|
1367
|
+
if (checkResult?.indexingPending) {
|
|
1368
|
+
process.stderr.write(`[SPECMEM IDLE-SYNC] Indexing still in progress, skipping resync\n`);
|
|
1369
|
+
}
|
|
1370
|
+
else if (syncScore < 100) {
|
|
1358
1371
|
process.stderr.write(`[SPECMEM IDLE-SYNC] Drift detected (${syncScore}%), resyncing...\n`);
|
|
1359
1372
|
const resyncResult = await ForceResync.execute({ dryRun: false }, wm);
|
|
1360
1373
|
const added = resyncResult?.stats?.filesAdded ?? 0;
|
package/dist/mcp/toolRegistry.js
CHANGED
|
@@ -94,32 +94,30 @@ _cacheCleanupTimer.unref();
|
|
|
94
94
|
/**
|
|
95
95
|
* Get the project-scoped embedding cache
|
|
96
96
|
*/
|
|
97
|
-
// HIGH-4:
|
|
98
|
-
|
|
97
|
+
// HIGH-4: Eviction uses while-loop to guarantee room before creating new entry.
|
|
98
|
+
// Old boolean _evictionInProgress flag was broken: when flag was true, eviction was
|
|
99
|
+
// skipped but new entry was still created at line 125, exceeding the 20-project limit.
|
|
99
100
|
function getProjectEmbeddingCache() {
|
|
100
101
|
const project = getProjectPath();
|
|
101
102
|
_EMBEDDING_CACHE_ACCESS_TIMES.set(project, Date.now());
|
|
102
103
|
if (!_EMBEDDING_CACHE_BY_PROJECT.has(project)) {
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (t < oldestTime) {
|
|
112
|
-
oldestTime = t;
|
|
113
|
-
oldestProject = p;
|
|
114
|
-
}
|
|
104
|
+
// Evict until there's room - loop guarantees we never exceed limit
|
|
105
|
+
while (_EMBEDDING_CACHE_BY_PROJECT.size >= 20) {
|
|
106
|
+
let oldestProject = null;
|
|
107
|
+
let oldestTime = Infinity;
|
|
108
|
+
for (const [p, t] of _EMBEDDING_CACHE_ACCESS_TIMES) {
|
|
109
|
+
if (t < oldestTime && p !== project) {
|
|
110
|
+
oldestTime = t;
|
|
111
|
+
oldestProject = p;
|
|
115
112
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
113
|
+
}
|
|
114
|
+
if (oldestProject) {
|
|
115
|
+
_EMBEDDING_CACHE_BY_PROJECT.delete(oldestProject);
|
|
116
|
+
_EMBEDDING_CACHE_ACCESS_TIMES.delete(oldestProject);
|
|
117
|
+
__debugLog('[MCP DEBUG]', Date.now(), 'CACHE_PROJECT_EVICTED', { evictedProject: oldestProject, reason: 'max_projects_reached' });
|
|
118
|
+
} else {
|
|
119
|
+
// Safety: no evictable project found (all entries are current project?), break to avoid infinite loop
|
|
120
|
+
break;
|
|
123
121
|
}
|
|
124
122
|
}
|
|
125
123
|
_EMBEDDING_CACHE_BY_PROJECT.set(project, new Map());
|
|
@@ -27,7 +27,10 @@ export class CheckSyncStatus {
|
|
|
27
27
|
const driftReport = await watcherManager.checkSync();
|
|
28
28
|
// build summary message
|
|
29
29
|
let summary;
|
|
30
|
-
if (driftReport.
|
|
30
|
+
if (driftReport.indexingPending) {
|
|
31
|
+
summary = `Codebase indexing in progress — sync score not yet available. ${driftReport.totalFiles} files on disk awaiting indexing.`;
|
|
32
|
+
}
|
|
33
|
+
else if (driftReport.inSync) {
|
|
31
34
|
summary = `Everything is in sync! ${driftReport.upToDate} files are up to date.`;
|
|
32
35
|
}
|
|
33
36
|
else {
|
|
@@ -45,7 +48,8 @@ export class CheckSyncStatus {
|
|
|
45
48
|
}
|
|
46
49
|
const result = {
|
|
47
50
|
inSync: driftReport.inSync,
|
|
48
|
-
syncScore: driftReport.syncScore,
|
|
51
|
+
syncScore: driftReport.indexingPending ? -1 : driftReport.syncScore,
|
|
52
|
+
indexingPending: !!driftReport.indexingPending,
|
|
49
53
|
driftPercentage: driftReport.driftPercentage,
|
|
50
54
|
summary,
|
|
51
55
|
stats: {
|
|
@@ -70,10 +74,12 @@ export class CheckSyncStatus {
|
|
|
70
74
|
contentMismatch: driftReport.contentMismatch
|
|
71
75
|
};
|
|
72
76
|
}
|
|
73
|
-
// Update statusbar sync score live
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
// Update statusbar sync score live (skip if indexing pending — don't write -1)
|
|
78
|
+
if (!driftReport.indexingPending) {
|
|
79
|
+
try {
|
|
80
|
+
await watcherManager.writeSyncScore(driftReport.syncScore);
|
|
81
|
+
} catch (e) { /* non-critical */ }
|
|
82
|
+
}
|
|
77
83
|
logger.info({ inSync: driftReport.inSync, syncScore: driftReport.syncScore }, 'sync check complete');
|
|
78
84
|
// Build human readable response
|
|
79
85
|
const drifted = driftReport.missingFromMcp.length + driftReport.missingFromDisk.length + driftReport.contentMismatch.length;
|
|
@@ -99,7 +105,8 @@ export class CheckSyncStatus {
|
|
|
99
105
|
if (more > 0) detailLines += `\n ... and ${more} more`;
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
|
-
const
|
|
108
|
+
const displayScore = driftReport.indexingPending ? 'Pending' : `${Math.round(driftReport.syncScore * 100)}%`;
|
|
109
|
+
const message = `Sync Score: ${displayScore}
|
|
103
110
|
${summary}
|
|
104
111
|
|
|
105
112
|
Stats:
|
|
@@ -55,6 +55,11 @@ export class WatchForChangesNoCap {
|
|
|
55
55
|
debounceCleanupTimer = null;
|
|
56
56
|
// FIX 7.14: Track pending flush promises so stop() can await them
|
|
57
57
|
pendingFlushPromises = new Set();
|
|
58
|
+
// PERF: Batch-level debounce — collect per-file handler results into batches
|
|
59
|
+
// so git operations changing many files don't fire hundreds of individual handler calls
|
|
60
|
+
_batchTimer = null;
|
|
61
|
+
_batchQueue = [];
|
|
62
|
+
_batchDebounceMs = 500; // collect events for 500ms before dispatching batch
|
|
58
63
|
// stats tracking
|
|
59
64
|
stats = {
|
|
60
65
|
filesWatched: 0,
|
|
@@ -75,6 +80,37 @@ export class WatchForChangesNoCap {
|
|
|
75
80
|
verbose: config.verbose ?? false
|
|
76
81
|
};
|
|
77
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* _enqueueBatchEvent - batch-level debounce for handler calls
|
|
85
|
+
*
|
|
86
|
+
* Instead of calling changeHandler() immediately per-file, queue events
|
|
87
|
+
* and dispatch the entire batch after 500ms of quiet. This prevents
|
|
88
|
+
* git operations (checkout, merge, rebase) from firing hundreds of
|
|
89
|
+
* individual handler calls that each trigger sync/DB work.
|
|
90
|
+
*/
|
|
91
|
+
_enqueueBatchEvent(event) {
|
|
92
|
+
this._batchQueue.push(event);
|
|
93
|
+
if (this._batchTimer) clearTimeout(this._batchTimer);
|
|
94
|
+
this._batchTimer = setTimeout(async () => {
|
|
95
|
+
this._batchTimer = null;
|
|
96
|
+
const batch = this._batchQueue.splice(0);
|
|
97
|
+
if (batch.length === 0 || !this.changeHandler) return;
|
|
98
|
+
const batchSize = batch.length;
|
|
99
|
+
if (batchSize > 5) {
|
|
100
|
+
logger.info({ batchSize }, `batch debounce: dispatching ${batchSize} file events as batch`);
|
|
101
|
+
}
|
|
102
|
+
// Process each event in the batch sequentially
|
|
103
|
+
// (changeHandler expects individual events)
|
|
104
|
+
for (const evt of batch) {
|
|
105
|
+
try {
|
|
106
|
+
await this.changeHandler(evt);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
logger.error({ error: err, path: evt.path }, 'batch handler error for file');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}, this._batchDebounceMs);
|
|
113
|
+
}
|
|
78
114
|
/**
|
|
79
115
|
* startWatching - fires up the file watcher
|
|
80
116
|
*
|
|
@@ -189,7 +225,7 @@ export class WatchForChangesNoCap {
|
|
|
189
225
|
depth: undefined, // watch all depths
|
|
190
226
|
// debouncing built into chokidar
|
|
191
227
|
awaitWriteFinish: {
|
|
192
|
-
stabilityThreshold:
|
|
228
|
+
stabilityThreshold: 500, // wait 500ms for file to stop changing (reduced CPU from rapid fire events)
|
|
193
229
|
pollInterval: 100 // check every 100ms
|
|
194
230
|
},
|
|
195
231
|
// dont follow symlinks (security)
|
|
@@ -240,6 +276,18 @@ export class WatchForChangesNoCap {
|
|
|
240
276
|
await Promise.allSettled([...this.pendingFlushPromises]);
|
|
241
277
|
this.pendingFlushPromises.clear();
|
|
242
278
|
}
|
|
279
|
+
// PERF: Clear batch timer and flush pending batch events
|
|
280
|
+
if (this._batchTimer) {
|
|
281
|
+
clearTimeout(this._batchTimer);
|
|
282
|
+
this._batchTimer = null;
|
|
283
|
+
}
|
|
284
|
+
if (this._pendingBatchEvents.length > 0 && this.changeHandler) {
|
|
285
|
+
// Flush remaining batch events before shutdown
|
|
286
|
+
const batch = this._pendingBatchEvents.splice(0);
|
|
287
|
+
for (const evt of batch) {
|
|
288
|
+
try { await this.changeHandler(evt); } catch { /* shutting down */ }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
243
291
|
// FIX MED-13: Cancel all debounced handlers before clearing to prevent memory leaks
|
|
244
292
|
// The debounce library's clear() method cancels pending timer execution
|
|
245
293
|
for (const handler of this.debouncedHandlers.values()) {
|
|
@@ -381,7 +429,9 @@ export class WatchForChangesNoCap {
|
|
|
381
429
|
if (latestEvent) {
|
|
382
430
|
// Update timestamp to reflect when we actually process the event
|
|
383
431
|
latestEvent.timestamp = new Date();
|
|
384
|
-
|
|
432
|
+
// PERF: Route through batch debounce instead of calling handler directly
|
|
433
|
+
// This prevents git operations (200+ files) from firing 200 individual handler calls
|
|
434
|
+
this._enqueueBatchEvent(latestEvent);
|
|
385
435
|
this.stats.eventsProcessed++;
|
|
386
436
|
this.stats.lastEventTime = new Date();
|
|
387
437
|
}
|
|
@@ -435,7 +485,8 @@ export class WatchForChangesNoCap {
|
|
|
435
485
|
const flushPromise = Promise.resolve().then(async () => {
|
|
436
486
|
try {
|
|
437
487
|
latestEvent.timestamp = new Date();
|
|
438
|
-
|
|
488
|
+
// PERF: Route through batch debounce
|
|
489
|
+
this._enqueueBatchEvent(latestEvent);
|
|
439
490
|
this.stats.eventsProcessed++;
|
|
440
491
|
this.stats.lastEventTime = new Date();
|
|
441
492
|
}
|
|
@@ -479,23 +530,9 @@ export class WatchForChangesNoCap {
|
|
|
479
530
|
const latestEvent = this.pendingEventData.get(key);
|
|
480
531
|
if (latestEvent && this.changeHandler) {
|
|
481
532
|
this.pendingEventData.delete(key);
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
latestEvent.timestamp = new Date();
|
|
486
|
-
await this.changeHandler(latestEvent);
|
|
487
|
-
this.stats.eventsProcessed++;
|
|
488
|
-
this.stats.lastEventTime = new Date();
|
|
489
|
-
}
|
|
490
|
-
catch (error) {
|
|
491
|
-
this.stats.errors++;
|
|
492
|
-
logger.error({ error, event: latestEvent }, 'error processing stale debounce entry');
|
|
493
|
-
}
|
|
494
|
-
finally {
|
|
495
|
-
this.pendingFlushPromises.delete(flushPromise);
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
this.pendingFlushPromises.add(flushPromise);
|
|
533
|
+
// PERF: Route stale entries through batch debounce too
|
|
534
|
+
latestEvent.timestamp = new Date();
|
|
535
|
+
this._enqueueBatchEvent(latestEvent);
|
|
499
536
|
}
|
|
500
537
|
else {
|
|
501
538
|
this.pendingEventData.delete(key);
|
|
@@ -119,22 +119,26 @@ export class AreWeStillInSync {
|
|
|
119
119
|
const totalFiles = diskFiles.length;
|
|
120
120
|
const totalMemories = mcpFiles.length;
|
|
121
121
|
const totalDrift = missingFromMcp.length + missingFromDisk.length + contentMismatch.length;
|
|
122
|
+
// FIX: If codebase_files is empty but disk files exist, indexing hasn't completed yet.
|
|
123
|
+
// Don't report 0% sync — that's misleading. Report indexing-pending state instead.
|
|
124
|
+
const indexingPending = totalMemories === 0 && totalFiles > 0;
|
|
122
125
|
// Sync score = what % of disk files are correctly synced in MCP
|
|
123
126
|
// Deleted-from-disk files are cleanup work, not sync failures
|
|
124
127
|
const totalItems = totalFiles || 1;
|
|
125
|
-
const driftPercentage = totalItems > 0 ? (totalDrift / totalItems) * 100 : 0;
|
|
126
|
-
const syncScore = totalItems > 0 ? upToDate / totalItems : 1;
|
|
128
|
+
const driftPercentage = indexingPending ? 0 : (totalItems > 0 ? (totalDrift / totalItems) * 100 : 0);
|
|
129
|
+
const syncScore = indexingPending ? -1 : (totalItems > 0 ? upToDate / totalItems : 1);
|
|
127
130
|
const report = {
|
|
128
|
-
inSync: totalDrift === 0,
|
|
131
|
+
inSync: indexingPending ? false : totalDrift === 0,
|
|
129
132
|
lastChecked: new Date(),
|
|
130
133
|
totalFiles,
|
|
131
134
|
totalMemories,
|
|
132
|
-
missingFromMcp,
|
|
135
|
+
missingFromMcp: indexingPending ? [] : missingFromMcp,
|
|
133
136
|
missingFromDisk,
|
|
134
|
-
contentMismatch,
|
|
135
|
-
upToDate,
|
|
137
|
+
contentMismatch: indexingPending ? [] : contentMismatch,
|
|
138
|
+
upToDate: indexingPending ? 0 : upToDate,
|
|
136
139
|
driftPercentage,
|
|
137
|
-
syncScore
|
|
140
|
+
syncScore,
|
|
141
|
+
indexingPending
|
|
138
142
|
};
|
|
139
143
|
this.lastSyncCheck = report.lastChecked;
|
|
140
144
|
this.lastCheckTime = startTime; // FIX 7.03: Record check time for mtime optimization
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specmem-hardwicksoftware",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.31",
|
|
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",
|
|
@@ -947,9 +947,14 @@ function adjustPgAuth() {
|
|
|
947
947
|
// Backup and modify
|
|
948
948
|
run(`sudo cp ${pgHbaPath} ${pgHbaPath}.backup`);
|
|
949
949
|
|
|
950
|
-
// Add password auth for our user
|
|
950
|
+
// Add password auth for our user — check if already exists to prevent duplicates
|
|
951
951
|
const authLine = `host ${DB_CONFIG.name} ${DB_CONFIG.user} 127.0.0.1/32 md5`;
|
|
952
|
-
run(`
|
|
952
|
+
const alreadyExists = run(`sudo grep -qF '${authLine}' ${pgHbaPath}`, { silent: true });
|
|
953
|
+
if (!alreadyExists.success) {
|
|
954
|
+
run(`echo '${authLine}' | sudo tee -a ${pgHbaPath}`);
|
|
955
|
+
} else {
|
|
956
|
+
log.info('pg_hba.conf auth line already present, skipping');
|
|
957
|
+
}
|
|
953
958
|
|
|
954
959
|
// Reload PostgreSQL
|
|
955
960
|
run('sudo systemctl reload postgresql 2>/dev/null || sudo -u postgres pg_ctl reload');
|