specmem-hardwicksoftware 3.5.99 → 3.6.1
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/bin/specmem-statusbar.cjs +154 -298
- package/claude-hooks/agent-loading-hook.js +8 -4
- package/claude-hooks/team-comms-enforcer.cjs +109 -92
- package/dist/config/embeddingTimeouts.js +4 -4
- package/dist/database.js +52 -6
- package/dist/db/bigBrainMigrations.js +7 -6
- package/dist/db/memoryDrilldown.sql +1 -1
- package/dist/db/projectSchemaInit.sql +21 -0
- package/dist/index.js +238 -13
- package/dist/installer/firstRun.js +2 -2
- package/dist/mcp/embeddingServerManager.js +225 -7
- package/dist/mcp/healthMonitor.js +165 -32
- package/dist/mcp/tools/embeddingControl.js +31 -0
- package/dist/mcp/tools/teamComms.js +16 -0
- package/dist/mcp/watcherIntegration.js +50 -7
- package/dist/services/CameraZoomSearch.js +62 -5
- package/dist/services/DimensionService.js +73 -6
- package/dist/services/EmbeddingQueue.js +64 -0
- package/dist/services/MemoryDrilldown.js +19 -12
- package/dist/tools/goofy/findCodePointers.js +11 -7
- package/dist/tools/goofy/findWhatISaid.js +145 -53
- package/dist/utils/qoms.js +187 -4
- package/dist/watcher/changeHandler.js +54 -4
- package/dist/watcher/fileWatcher.js +121 -1
- package/dist/watcher/index.js +75 -31
- package/dist/watcher/syncChecker.js +248 -63
- package/embedding-sandbox/__pycache__/frankenstein-embeddings.cpython-313.pyc +0 -0
- package/embedding-sandbox/frankenstein-embeddings.py +175 -64
- package/package.json +1 -1
|
@@ -48,7 +48,8 @@ try {
|
|
|
48
48
|
// CONFIGURATION
|
|
49
49
|
// ============================================================================
|
|
50
50
|
const MAX_SEARCHES_BEFORE_BLOCK = 3;
|
|
51
|
-
const
|
|
51
|
+
const TEAM_COMMS_CHECK_INTERVAL = 4; // MUST read_team_messages every 4 tool usages
|
|
52
|
+
const BROADCAST_CHECK_INTERVAL = 5; // MUST read_team_messages w/ include_broadcasts every 5 tool usages
|
|
52
53
|
const HELP_CHECK_INTERVAL = 8; // Check help requests every 8 tool usages
|
|
53
54
|
|
|
54
55
|
// Tools that count as "announcing"
|
|
@@ -142,11 +143,14 @@ function getAgentState(tracking, sessionId) {
|
|
|
142
143
|
usedMemoryTools: false,
|
|
143
144
|
searchCount: 0,
|
|
144
145
|
blockedCount: 0,
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
commsToolCount: 0, // Tool calls since last team comms check (every 4)
|
|
147
|
+
broadcastToolCount: 0, // Tool calls since last broadcast check (every 5)
|
|
148
|
+
helpToolUsageCount: 0, // Tool calls since last help check (every 8)
|
|
149
|
+
lastCommsCheck: Date.now(),
|
|
147
150
|
lastBroadcastCheck: Date.now(),
|
|
148
151
|
lastHelpCheck: Date.now(),
|
|
149
|
-
|
|
152
|
+
needsCommsCheck: false, // HARD BLOCK until they read team messages
|
|
153
|
+
needsBroadcastCheck: false, // HARD BLOCK until they read broadcasts
|
|
150
154
|
needsHelpCheck: false, // Flag when they hit the limit
|
|
151
155
|
lastActivity: Date.now()
|
|
152
156
|
};
|
|
@@ -287,93 +291,78 @@ process.stdin.on('end', () => {
|
|
|
287
291
|
state.lastActivity = Date.now();
|
|
288
292
|
|
|
289
293
|
// ========================================================================
|
|
290
|
-
//
|
|
294
|
+
// TRACK STATE FOR ALL TOOLS (announcements, claims, memory, comms)
|
|
291
295
|
// ========================================================================
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
296
|
+
if (ANNOUNCE_TOOLS.includes(toolName)) {
|
|
297
|
+
state.announced = true;
|
|
298
|
+
}
|
|
299
|
+
if (CLAIM_TOOLS.includes(toolName)) {
|
|
300
|
+
state.claimed = true;
|
|
301
|
+
const params = data.tool_input || {};
|
|
302
|
+
const claimFiles = params.files || [];
|
|
303
|
+
const claimDesc = params.description || 'unnamed task';
|
|
304
|
+
const claimsFile = `${PROJECT_TMP_DIR}/active-claims.json`;
|
|
305
|
+
try {
|
|
306
|
+
let claims = {};
|
|
307
|
+
if (fs.existsSync(claimsFile)) {
|
|
308
|
+
claims = JSON.parse(fs.readFileSync(claimsFile, 'utf8'));
|
|
309
|
+
}
|
|
310
|
+
const claimId = `claim-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
311
|
+
claims[claimId] = {
|
|
312
|
+
sessionId, agentId: sessionId, files: claimFiles,
|
|
313
|
+
description: claimDesc, createdAt: Date.now()
|
|
314
|
+
};
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
for (const [id, claim] of Object.entries(claims)) {
|
|
317
|
+
if (now - claim.createdAt > 1800000) delete claims[id];
|
|
318
|
+
}
|
|
319
|
+
fs.writeFileSync(claimsFile, JSON.stringify(claims, null, 2));
|
|
320
|
+
state.currentClaimId = claimId;
|
|
321
|
+
} catch (e) {}
|
|
322
|
+
}
|
|
323
|
+
if (toolName === 'mcp__specmem__release_task') {
|
|
324
|
+
const claimsFile = `${PROJECT_TMP_DIR}/active-claims.json`;
|
|
325
|
+
try {
|
|
326
|
+
if (fs.existsSync(claimsFile)) {
|
|
327
|
+
let claims = JSON.parse(fs.readFileSync(claimsFile, 'utf8'));
|
|
321
328
|
for (const [id, claim] of Object.entries(claims)) {
|
|
322
|
-
if (
|
|
329
|
+
if (claim.sessionId === sessionId) delete claims[id];
|
|
323
330
|
}
|
|
324
331
|
fs.writeFileSync(claimsFile, JSON.stringify(claims, null, 2));
|
|
325
|
-
state.currentClaimId = claimId;
|
|
326
|
-
} catch (e) {}
|
|
327
|
-
}
|
|
328
|
-
// Track release_task - remove from shared claims
|
|
329
|
-
if (toolName === 'mcp__specmem__release_task') {
|
|
330
|
-
const claimsFile = `${PROJECT_TMP_DIR}/active-claims.json`;
|
|
331
|
-
try {
|
|
332
|
-
if (fs.existsSync(claimsFile)) {
|
|
333
|
-
let claims = JSON.parse(fs.readFileSync(claimsFile, 'utf8'));
|
|
334
|
-
// Remove claims from this session
|
|
335
|
-
for (const [id, claim] of Object.entries(claims)) {
|
|
336
|
-
if (claim.sessionId === sessionId) delete claims[id];
|
|
337
|
-
}
|
|
338
|
-
fs.writeFileSync(claimsFile, JSON.stringify(claims, null, 2));
|
|
339
|
-
}
|
|
340
|
-
} catch (e) {}
|
|
341
|
-
state.claimed = false;
|
|
342
|
-
state.editedFiles = [];
|
|
343
|
-
}
|
|
344
|
-
// Track memory tool usage
|
|
345
|
-
if (MEMORY_TOOLS.includes(toolName)) {
|
|
346
|
-
state.usedMemoryTools = true;
|
|
347
|
-
state.searchCount = 0; // Reset search count
|
|
348
|
-
}
|
|
349
|
-
// Track broadcast checks (read_team_messages with broadcasts)
|
|
350
|
-
if (BROADCAST_CHECK_TOOLS.includes(toolName)) {
|
|
351
|
-
// Check if they included broadcasts in the params
|
|
352
|
-
const params = data.tool_input || {};
|
|
353
|
-
if (params.include_broadcasts !== false) { // Default is true
|
|
354
|
-
state.toolUsageCount = 0; // Reset counter
|
|
355
|
-
state.lastBroadcastCheck = Date.now();
|
|
356
|
-
state.needsBroadcastCheck = false;
|
|
357
332
|
}
|
|
333
|
+
} catch (e) {}
|
|
334
|
+
state.claimed = false;
|
|
335
|
+
state.editedFiles = [];
|
|
336
|
+
}
|
|
337
|
+
if (MEMORY_TOOLS.includes(toolName)) {
|
|
338
|
+
state.usedMemoryTools = true;
|
|
339
|
+
state.searchCount = 0;
|
|
340
|
+
}
|
|
341
|
+
// Track team comms reads - resets comms counter
|
|
342
|
+
if (BROADCAST_CHECK_TOOLS.includes(toolName)) {
|
|
343
|
+
state.commsToolCount = 0;
|
|
344
|
+
state.lastCommsCheck = Date.now();
|
|
345
|
+
state.needsCommsCheck = false;
|
|
346
|
+
// Also reset broadcast counter IF they included broadcasts
|
|
347
|
+
const params = data.tool_input || {};
|
|
348
|
+
if (params.include_broadcasts !== false) {
|
|
349
|
+
state.broadcastToolCount = 0;
|
|
350
|
+
state.lastBroadcastCheck = Date.now();
|
|
351
|
+
state.needsBroadcastCheck = false;
|
|
358
352
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
saveTracking(tracking);
|
|
366
|
-
console.log(JSON.stringify({ continue: true }));
|
|
367
|
-
return;
|
|
353
|
+
}
|
|
354
|
+
if (HELP_CHECK_TOOLS.includes(toolName)) {
|
|
355
|
+
state.helpToolUsageCount = 0;
|
|
356
|
+
state.lastHelpCheck = Date.now();
|
|
357
|
+
state.needsHelpCheck = false;
|
|
368
358
|
}
|
|
369
359
|
|
|
370
360
|
// ========================================================================
|
|
371
|
-
// CHECK: Must announce first
|
|
361
|
+
// CHECK: Must announce first (before anything else)
|
|
372
362
|
// ========================================================================
|
|
373
|
-
if (!state.announced) {
|
|
363
|
+
if (!state.announced && !ALWAYS_ALLOWED.includes(toolName)) {
|
|
374
364
|
state.blockedCount++;
|
|
375
365
|
saveTracking(tracking);
|
|
376
|
-
// Make the announcement requirement very clear with proper channel guidance
|
|
377
366
|
console.log(blockResponse(
|
|
378
367
|
`[BLOCKED] You MUST ANNOUNCE yourself first!\n\n` +
|
|
379
368
|
`This is your MANDATORY FIRST ACTION before any other tool:\n\n` +
|
|
@@ -386,23 +375,43 @@ process.stdin.on('end', () => {
|
|
|
386
375
|
}
|
|
387
376
|
|
|
388
377
|
// ========================================================================
|
|
389
|
-
// INCREMENT TOOL
|
|
378
|
+
// INCREMENT ALL COUNTERS ON EVERY TOOL CALL (per-agent, per-session)
|
|
379
|
+
// This counts ALL tools including ALWAYS_ALLOWED - no dodging
|
|
390
380
|
// ========================================================================
|
|
391
|
-
state.
|
|
381
|
+
state.commsToolCount = (state.commsToolCount || 0) + 1;
|
|
382
|
+
state.broadcastToolCount = (state.broadcastToolCount || 0) + 1;
|
|
392
383
|
state.helpToolUsageCount = (state.helpToolUsageCount || 0) + 1;
|
|
393
384
|
|
|
394
385
|
// ========================================================================
|
|
395
|
-
//
|
|
386
|
+
// HARD BLOCK: Must read team messages every 4 tool usages
|
|
387
|
+
// read_team_messages() satisfies this - any mode
|
|
388
|
+
// ========================================================================
|
|
389
|
+
if (state.commsToolCount >= TEAM_COMMS_CHECK_INTERVAL && !BROADCAST_CHECK_TOOLS.includes(toolName)) {
|
|
390
|
+
state.needsCommsCheck = true;
|
|
391
|
+
state.blockedCount++;
|
|
392
|
+
saveTracking(tracking);
|
|
393
|
+
console.log(blockResponse(
|
|
394
|
+
`[BLOCKED] MANDATORY team comms check! (${state.commsToolCount} tools since last check)\n\n` +
|
|
395
|
+
`REQUIRED: read_team_messages({include_swarms: true, limit: 5})\n\n` +
|
|
396
|
+
`You MUST check team messages every 4 tool calls. This is non-negotiable.\n` +
|
|
397
|
+
`Other agents may have critical updates for you. CHECK NOW.`
|
|
398
|
+
));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
396
402
|
// ========================================================================
|
|
397
|
-
|
|
403
|
+
// HARD BLOCK: Must read broadcasts every 5 tool usages
|
|
404
|
+
// read_team_messages({include_broadcasts: true}) satisfies this
|
|
405
|
+
// ========================================================================
|
|
406
|
+
if (state.broadcastToolCount >= BROADCAST_CHECK_INTERVAL && !BROADCAST_CHECK_TOOLS.includes(toolName)) {
|
|
398
407
|
state.needsBroadcastCheck = true;
|
|
399
408
|
state.blockedCount++;
|
|
400
409
|
saveTracking(tracking);
|
|
401
410
|
console.log(blockResponse(
|
|
402
|
-
`[BLOCKED]
|
|
403
|
-
`REQUIRED: read_team_messages({include_broadcasts: true, limit: 10})\n\n` +
|
|
404
|
-
`
|
|
405
|
-
`
|
|
411
|
+
`[BLOCKED] MANDATORY broadcast check! (${state.broadcastToolCount} tools since last broadcast check)\n\n` +
|
|
412
|
+
`REQUIRED: read_team_messages({include_broadcasts: true, include_swarms: true, limit: 10})\n\n` +
|
|
413
|
+
`You MUST check broadcasts every 5 tool calls. This is non-negotiable.\n` +
|
|
414
|
+
`Team-wide announcements and status updates require your attention. CHECK NOW.`
|
|
406
415
|
));
|
|
407
416
|
return;
|
|
408
417
|
}
|
|
@@ -410,7 +419,7 @@ process.stdin.on('end', () => {
|
|
|
410
419
|
// ========================================================================
|
|
411
420
|
// CHECK: Must check help requests every 8 tool usages
|
|
412
421
|
// ========================================================================
|
|
413
|
-
if (state.helpToolUsageCount >= HELP_CHECK_INTERVAL) {
|
|
422
|
+
if (state.helpToolUsageCount >= HELP_CHECK_INTERVAL && !HELP_CHECK_TOOLS.includes(toolName)) {
|
|
414
423
|
state.needsHelpCheck = true;
|
|
415
424
|
state.blockedCount++;
|
|
416
425
|
saveTracking(tracking);
|
|
@@ -425,12 +434,20 @@ process.stdin.on('end', () => {
|
|
|
425
434
|
}
|
|
426
435
|
|
|
427
436
|
// ========================================================================
|
|
428
|
-
//
|
|
437
|
+
// ALWAYS ALLOWED TOOLS - pass through after counter checks
|
|
438
|
+
// ========================================================================
|
|
439
|
+
if (ALWAYS_ALLOWED.includes(toolName)) {
|
|
440
|
+
saveTracking(tracking);
|
|
441
|
+
console.log(JSON.stringify({ continue: true }));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ========================================================================
|
|
446
|
+
// WARN: Approaching comms check
|
|
429
447
|
// ========================================================================
|
|
430
|
-
if (state.
|
|
431
|
-
// Don't block, just warn
|
|
448
|
+
if (state.commsToolCount === TEAM_COMMS_CHECK_INTERVAL - 1) {
|
|
432
449
|
console.log(allowWithReminder(
|
|
433
|
-
`[HEADS UP] Next tool call will require
|
|
450
|
+
`[HEADS UP] Next tool call will require team comms check. Do it now: read_team_messages({include_swarms: true, limit: 5})`
|
|
434
451
|
));
|
|
435
452
|
// Don't return - continue to other checks
|
|
436
453
|
}
|
|
@@ -89,10 +89,10 @@ export const embeddingTimeouts = {
|
|
|
89
89
|
},
|
|
90
90
|
/**
|
|
91
91
|
* Timeout for embedding generation during memory search (find_memory)
|
|
92
|
-
* Env: SPECMEM_FIND_EMBEDDING_TIMEOUT_MS (default:
|
|
92
|
+
* Env: SPECMEM_FIND_EMBEDDING_TIMEOUT_MS (default: 120000 = 120s, increased from 60s to match cold-start)
|
|
93
93
|
*/
|
|
94
94
|
get search() {
|
|
95
|
-
return this.master ?? parseTimeoutMs('SPECMEM_FIND_EMBEDDING_TIMEOUT_MS',
|
|
95
|
+
return this.master ?? parseTimeoutMs('SPECMEM_FIND_EMBEDDING_TIMEOUT_MS', 120000);
|
|
96
96
|
},
|
|
97
97
|
/**
|
|
98
98
|
* Timeout for health check embedding tests
|
|
@@ -140,10 +140,10 @@ export const embeddingTimeouts = {
|
|
|
140
140
|
/**
|
|
141
141
|
* Code search/pointer lookup timeout
|
|
142
142
|
* Used by find_code_pointers tool
|
|
143
|
-
* Env: SPECMEM_CODE_SEARCH_TIMEOUT (default:
|
|
143
|
+
* Env: SPECMEM_CODE_SEARCH_TIMEOUT (default: 120000 - increased from 60s to match cold-start initial timeout)
|
|
144
144
|
*/
|
|
145
145
|
get codeSearch() {
|
|
146
|
-
return this.master ?? parseTimeoutMs('SPECMEM_CODE_SEARCH_TIMEOUT',
|
|
146
|
+
return this.master ?? parseTimeoutMs('SPECMEM_CODE_SEARCH_TIMEOUT', 120000);
|
|
147
147
|
},
|
|
148
148
|
/**
|
|
149
149
|
* Search timeout for database queries in find_memory
|
package/dist/database.js
CHANGED
|
@@ -172,28 +172,70 @@ export class DatabaseManager {
|
|
|
172
172
|
return undefined;
|
|
173
173
|
}
|
|
174
174
|
createPool() {
|
|
175
|
+
// Issue #11 FIX: All pool settings are env-var configurable with sensible defaults
|
|
176
|
+
const poolMax = parseInt(process.env['SPECMEM_DB_POOL_MAX'] || String(this.config.maxConnections || 20), 10);
|
|
177
|
+
const connectionTimeoutMillis = parseInt(process.env['SPECMEM_DB_CONNECTION_TIMEOUT_MS'] || String(this.config.connectionTimeout || 10000), 10);
|
|
178
|
+
const statementTimeoutMs = parseInt(process.env['SPECMEM_DB_STATEMENT_TIMEOUT_MS'] || '30000', 10);
|
|
179
|
+
const idleInTransactionTimeoutMs = parseInt(process.env['SPECMEM_DB_IDLE_TRANSACTION_TIMEOUT_MS'] || '60000', 10);
|
|
180
|
+
logger.info({
|
|
181
|
+
poolMax,
|
|
182
|
+
connectionTimeoutMillis,
|
|
183
|
+
statementTimeoutMs,
|
|
184
|
+
idleInTransactionTimeoutMs
|
|
185
|
+
}, 'Creating pool with configurable timeouts (Issue #11 fix)');
|
|
175
186
|
return new Pool({
|
|
176
187
|
host: this.config.host,
|
|
177
188
|
port: this.config.port,
|
|
178
189
|
database: this.config.database,
|
|
179
190
|
user: this.config.user,
|
|
180
191
|
password: this.config.password,
|
|
181
|
-
max:
|
|
192
|
+
max: poolMax,
|
|
182
193
|
idleTimeoutMillis: this.config.idleTimeout,
|
|
183
|
-
connectionTimeoutMillis:
|
|
184
|
-
ssl: this.config.ssl
|
|
194
|
+
connectionTimeoutMillis: connectionTimeoutMillis,
|
|
195
|
+
ssl: this.config.ssl,
|
|
196
|
+
// Issue #11: Set statement_timeout and idle_in_transaction_session_timeout
|
|
197
|
+
// These prevent hung queries from blocking the pool over long sessions
|
|
198
|
+
statement_timeout: statementTimeoutMs,
|
|
199
|
+
idle_in_transaction_session_timeout: idleInTransactionTimeoutMs
|
|
185
200
|
});
|
|
186
201
|
}
|
|
187
202
|
setupPoolEvents() {
|
|
203
|
+
// Issue #11 FIX: Enhanced pool error handler with connection details
|
|
188
204
|
this.pool.on('error', (err) => {
|
|
189
|
-
logger.error({
|
|
205
|
+
logger.error({
|
|
206
|
+
err,
|
|
207
|
+
totalCount: this.pool.totalCount,
|
|
208
|
+
idleCount: this.pool.idleCount,
|
|
209
|
+
waitingCount: this.pool.waitingCount
|
|
210
|
+
}, 'Unexpected pool error - logging pool health for diagnostics');
|
|
190
211
|
});
|
|
191
212
|
// SCHEMA ISOLATION FIX: pool.on('connect') doesn't wait for async callbacks!
|
|
192
213
|
// The client is returned to the caller before await completes.
|
|
193
214
|
// We still set search_path here for defense-in-depth, but critical paths
|
|
194
215
|
// must use ensureSearchPath() explicitly to guarantee isolation.
|
|
195
216
|
this.pool.on('connect', (client) => {
|
|
196
|
-
|
|
217
|
+
// Issue #11 FIX: Set statement_timeout and idle_in_transaction_session_timeout
|
|
218
|
+
// on each new connection as defense-in-depth (in addition to pool-level config).
|
|
219
|
+
// This ensures timeouts apply even if the pool config is ignored by the driver.
|
|
220
|
+
const statementTimeoutMs = parseInt(process.env['SPECMEM_DB_STATEMENT_TIMEOUT_MS'] || '30000', 10);
|
|
221
|
+
const idleInTransactionTimeoutMs = parseInt(process.env['SPECMEM_DB_IDLE_TRANSACTION_TIMEOUT_MS'] || '60000', 10);
|
|
222
|
+
logger.debug({
|
|
223
|
+
totalCount: this.pool.totalCount,
|
|
224
|
+
idleCount: this.pool.idleCount,
|
|
225
|
+
waitingCount: this.pool.waitingCount
|
|
226
|
+
}, 'New client connected to pool');
|
|
227
|
+
// Set statement_timeout and idle_in_transaction_session_timeout via SET command
|
|
228
|
+
// This is fire-and-forget (pg doesn't await connect handlers)
|
|
229
|
+
client.query('SET statement_timeout TO ' + statementTimeoutMs)
|
|
230
|
+
.then(() => {
|
|
231
|
+
return client.query('SET idle_in_transaction_session_timeout TO ' + idleInTransactionTimeoutMs);
|
|
232
|
+
})
|
|
233
|
+
.then(() => {
|
|
234
|
+
logger.debug({ statementTimeoutMs, idleInTransactionTimeoutMs }, 'Set query timeouts on new pool connection');
|
|
235
|
+
})
|
|
236
|
+
.catch((error) => {
|
|
237
|
+
logger.error({ error }, 'Failed to set query timeouts on new connection');
|
|
238
|
+
});
|
|
197
239
|
// If we have a current schema, set search_path on this new connection
|
|
198
240
|
// NOTE: This is fire-and-forget because pg doesn't await connect handlers
|
|
199
241
|
// Critical code paths should call ensureSearchPath() explicitly
|
|
@@ -211,7 +253,11 @@ export class DatabaseManager {
|
|
|
211
253
|
}
|
|
212
254
|
});
|
|
213
255
|
this.pool.on('remove', () => {
|
|
214
|
-
logger.debug(
|
|
256
|
+
logger.debug({
|
|
257
|
+
totalCount: this.pool.totalCount,
|
|
258
|
+
idleCount: this.pool.idleCount,
|
|
259
|
+
waitingCount: this.pool.waitingCount
|
|
260
|
+
}, 'Client removed from pool');
|
|
215
261
|
});
|
|
216
262
|
}
|
|
217
263
|
async initialize() {
|
|
@@ -636,8 +636,8 @@ export class BigBrainMigrations {
|
|
|
636
636
|
)
|
|
637
637
|
);
|
|
638
638
|
|
|
639
|
-
-- index for content hash
|
|
640
|
-
CREATE
|
|
639
|
+
-- index for content hash lookup (NOT unique - multiple files can share same hash)
|
|
640
|
+
CREATE INDEX IF NOT EXISTS idx_codebase_files_content_hash
|
|
641
641
|
ON codebase_files(content_hash);
|
|
642
642
|
|
|
643
643
|
-- full-text search index for code search
|
|
@@ -4203,10 +4203,11 @@ export class BigBrainMigrations {
|
|
|
4203
4203
|
CREATE INDEX IF NOT EXISTS idx_memories_project_path_type
|
|
4204
4204
|
ON memories(project_path, memory_type);
|
|
4205
4205
|
|
|
4206
|
-
--
|
|
4207
|
-
--
|
|
4208
|
-
|
|
4209
|
-
|
|
4206
|
+
-- UNIQUE composite index for codebase_files project + file_path
|
|
4207
|
+
-- MUST be UNIQUE for ON CONFLICT (file_path, project_path) upsert to work
|
|
4208
|
+
-- Used by: changeHandler.updateCodebaseFiles, codebaseTools queries
|
|
4209
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_codebase_files_path_project_unique
|
|
4210
|
+
ON codebase_files(file_path, project_path);
|
|
4210
4211
|
|
|
4211
4212
|
-- Composite index for codebase_files project + content_hash (dedup check)
|
|
4212
4213
|
-- Used by: codebaseIndexer for hash lookups per project
|
|
@@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS team_member_conversations (
|
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
CREATE INDEX IF NOT EXISTS idx_team_member_conversations_memory ON team_member_conversations(memory_id);
|
|
55
|
-
CREATE INDEX IF NOT EXISTS
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_team_member_conversations_team_member ON team_member_conversations(team_member_id);
|
|
56
56
|
CREATE INDEX IF NOT EXISTS idx_team_member_conversations_time ON team_member_conversations(timestamp);
|
|
57
57
|
|
|
58
58
|
-- HELPER FUNCTIONS
|
|
@@ -266,6 +266,27 @@ INSERT INTO team_channels (name, channel_type, created_by, project_path)
|
|
|
266
266
|
SELECT 'team-broadcast', 'broadcast', 'system', '/'
|
|
267
267
|
WHERE NOT EXISTS (SELECT 1 FROM team_channels WHERE name = 'team-broadcast' AND channel_type = 'broadcast');
|
|
268
268
|
|
|
269
|
+
-- ============================================================================
|
|
270
|
+
-- TEAM MEMBER CONVERSATIONS TABLE
|
|
271
|
+
-- Stores the conversation context that spawned a memory
|
|
272
|
+
-- Required by MemoryDrilldown.js for getMemoryFull / drill_down
|
|
273
|
+
-- ============================================================================
|
|
274
|
+
CREATE TABLE IF NOT EXISTS team_member_conversations (
|
|
275
|
+
id BIGSERIAL PRIMARY KEY,
|
|
276
|
+
memory_id UUID NOT NULL,
|
|
277
|
+
team_member_id VARCHAR(255) NOT NULL,
|
|
278
|
+
team_member_name VARCHAR(255),
|
|
279
|
+
timestamp TIMESTAMPTZ DEFAULT NOW(),
|
|
280
|
+
summary TEXT,
|
|
281
|
+
full_transcript TEXT,
|
|
282
|
+
message_count INTEGER,
|
|
283
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
CREATE INDEX IF NOT EXISTS idx_team_member_conversations_memory ON team_member_conversations(memory_id);
|
|
287
|
+
CREATE INDEX IF NOT EXISTS idx_team_member_conversations_team_member ON team_member_conversations(team_member_id);
|
|
288
|
+
CREATE INDEX IF NOT EXISTS idx_team_member_conversations_time ON team_member_conversations(timestamp);
|
|
289
|
+
|
|
269
290
|
-- ============================================================================
|
|
270
291
|
-- END - Core data uses PUBLIC schema with project_path filtering
|
|
271
292
|
-- ============================================================================
|