Refactor silent debugging to happy path error handling
- Replaced instances of silentDebug with happy_path_error__with_fallback across multiple files to improve error logging and handling. - Updated the utility function to provide clearer semantics for error handling when expected values are missing. - Introduced a script to find potential silent failures in the codebase that may need to be addressed with the new error handling approach.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|
||||
import { stdin } from 'process';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { silentDebug } from '../utils/silent-debug.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
|
||||
export interface SessionEndInput {
|
||||
@@ -26,7 +26,7 @@ async function cleanupHook(input?: SessionEndInput): Promise<void> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
silentDebug('[cleanup-hook] Hook fired', {
|
||||
happy_path_error__with_fallback('[cleanup-hook] Hook fired', {
|
||||
session_id: input?.session_id,
|
||||
cwd: input?.cwd,
|
||||
reason: input?.reason
|
||||
@@ -64,14 +64,14 @@ async function cleanupHook(input?: SessionEndInput): Promise<void> {
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
silentDebug('[cleanup-hook] Session cleanup completed', result);
|
||||
happy_path_error__with_fallback('[cleanup-hook] Session cleanup completed', result);
|
||||
} else {
|
||||
// Non-fatal - session might not exist
|
||||
silentDebug('[cleanup-hook] Session not found or already cleaned up');
|
||||
happy_path_error__with_fallback('[cleanup-hook] Session not found or already cleaned up');
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Worker might not be running - that's okay
|
||||
silentDebug('[cleanup-hook] Worker not reachable (non-critical)', {
|
||||
happy_path_error__with_fallback('[cleanup-hook] Worker not reachable (non-critical)', {
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import path from 'path';
|
||||
import { stdin } from 'process';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { silentDebug } from '../utils/silent-debug.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
|
||||
export interface UserPromptSubmitInput {
|
||||
session_id: string;
|
||||
@@ -61,7 +61,7 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
const { session_id, cwd, prompt } = input;
|
||||
|
||||
// Debug: Log what we received
|
||||
silentDebug('[new-hook] Input received', {
|
||||
happy_path_error__with_fallback('[new-hook] Input received', {
|
||||
session_id,
|
||||
cwd,
|
||||
cwd_type: typeof cwd,
|
||||
@@ -72,7 +72,7 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
|
||||
const project = path.basename(cwd);
|
||||
|
||||
silentDebug('[new-hook] Project extracted', {
|
||||
happy_path_error__with_fallback('[new-hook] Project extracted', {
|
||||
project,
|
||||
project_type: typeof project,
|
||||
project_length: project?.length,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { createHookResponse } from './hook-response.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
|
||||
export interface PostToolUseInput {
|
||||
session_id: string;
|
||||
@@ -66,7 +67,11 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
tool_name,
|
||||
tool_input,
|
||||
tool_response,
|
||||
cwd: cwd || ''
|
||||
cwd: happy_path_error__with_fallback(
|
||||
'Missing cwd in PostToolUse hook input',
|
||||
{ session_id, tool_name },
|
||||
cwd || ''
|
||||
)
|
||||
}),
|
||||
signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT)
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import { createHookResponse } from './hook-response.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
|
||||
export interface StopInput {
|
||||
session_id: string;
|
||||
@@ -143,8 +144,13 @@ async function summaryHook(input?: StopInput): Promise<void> {
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Extract last user AND assistant messages from transcript
|
||||
const lastUserMessage = extractLastUserMessage(input.transcript_path || '');
|
||||
const lastAssistantMessage = extractLastAssistantMessage(input.transcript_path || '');
|
||||
const transcriptPath = happy_path_error__with_fallback(
|
||||
'Missing transcript_path in Stop hook input',
|
||||
{ session_id },
|
||||
input.transcript_path || ''
|
||||
);
|
||||
const lastUserMessage = extractLastUserMessage(transcriptPath);
|
||||
const lastAssistantMessage = extractLastAssistantMessage(transcriptPath);
|
||||
|
||||
logger.dataIn('HOOK', 'Stop: Requesting summary', {
|
||||
workerPort: port,
|
||||
|
||||
+7
-1
@@ -3,6 +3,8 @@
|
||||
* Generates prompts for the Claude Agent SDK memory worker
|
||||
*/
|
||||
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
|
||||
export interface Observation {
|
||||
id: number;
|
||||
tool_name: string;
|
||||
@@ -175,7 +177,11 @@ export function buildObservationPrompt(obs: Observation): string {
|
||||
* Build prompt to generate progress summary
|
||||
*/
|
||||
export function buildSummaryPrompt(session: SDKSession): string {
|
||||
const lastAssistantMessage = session.last_assistant_message || '';
|
||||
const lastAssistantMessage = happy_path_error__with_fallback(
|
||||
'Missing last_assistant_message in session for summary prompt',
|
||||
session,
|
||||
session.last_assistant_message || ''
|
||||
);
|
||||
|
||||
return `PROGRESS SUMMARY CHECKPOINT
|
||||
===========================
|
||||
|
||||
+11
-11
@@ -14,7 +14,7 @@ import {
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { z } from 'zod';
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||
import { silentDebug } from '../utils/silent-debug.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
import { getWorkerPort } from '../shared/worker-utils.js';
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ async function callWorkerAPI(
|
||||
endpoint: string,
|
||||
params: Record<string, any>
|
||||
): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
|
||||
silentDebug('[mcp-server] → Worker API', { endpoint, params });
|
||||
happy_path_error__with_fallback('[mcp-server] → Worker API', { endpoint, params });
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
@@ -72,12 +72,12 @@ async function callWorkerAPI(
|
||||
|
||||
const data = await response.json() as { content: Array<{ type: 'text'; text: string }>; isError?: boolean };
|
||||
|
||||
silentDebug('[mcp-server] ← Worker API success', { endpoint });
|
||||
happy_path_error__with_fallback('[mcp-server] ← Worker API success', { endpoint });
|
||||
|
||||
// Worker returns { content: [...] } format directly
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
silentDebug('[mcp-server] ← Worker API error', { endpoint, error: error.message });
|
||||
happy_path_error__with_fallback('[mcp-server] ← Worker API error', { endpoint, error: error.message });
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
@@ -412,7 +412,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
|
||||
// Cleanup function
|
||||
async function cleanup() {
|
||||
silentDebug('[mcp-server] Shutting down...');
|
||||
happy_path_error__with_fallback('[mcp-server] Shutting down...');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -425,22 +425,22 @@ async function main() {
|
||||
// Start the MCP server
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
silentDebug('[mcp-server] Claude-mem search server started');
|
||||
happy_path_error__with_fallback('[mcp-server] Claude-mem search server started');
|
||||
|
||||
// Check Worker availability in background
|
||||
setTimeout(async () => {
|
||||
const workerAvailable = await verifyWorkerConnection();
|
||||
if (!workerAvailable) {
|
||||
silentDebug('[mcp-server] WARNING: Worker not available at', WORKER_BASE_URL);
|
||||
silentDebug('[mcp-server] Tools will fail until Worker is started');
|
||||
silentDebug('[mcp-server] Start Worker with: npm run worker:restart');
|
||||
happy_path_error__with_fallback('[mcp-server] WARNING: Worker not available at', WORKER_BASE_URL);
|
||||
happy_path_error__with_fallback('[mcp-server] Tools will fail until Worker is started');
|
||||
happy_path_error__with_fallback('[mcp-server] Start Worker with: npm run worker:restart');
|
||||
} else {
|
||||
silentDebug('[mcp-server] Worker available at', WORKER_BASE_URL);
|
||||
happy_path_error__with_fallback('[mcp-server] Worker available at', WORKER_BASE_URL);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
silentDebug('[mcp-server] Fatal error:', error);
|
||||
happy_path_error__with_fallback('[mcp-server] Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SessionStore } from '../sqlite/SessionStore.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { SettingsDefaultsManager } from '../worker/settings/SettingsDefaultsManager.js';
|
||||
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
|
||||
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
@@ -766,7 +767,11 @@ export class ChromaSync {
|
||||
arguments: arguments_obj
|
||||
});
|
||||
|
||||
const resultText = result.content[0]?.text || '';
|
||||
const resultText = happy_path_error__with_fallback(
|
||||
'Missing text in MCP chroma_query_documents result',
|
||||
{ project: this.project, query_text: query },
|
||||
result.content[0]?.text || ''
|
||||
);
|
||||
|
||||
// Parse JSON response
|
||||
let parsed: any;
|
||||
|
||||
@@ -14,7 +14,7 @@ import path from 'path';
|
||||
import { DatabaseManager } from './DatabaseManager.js';
|
||||
import { SessionManager } from './SessionManager.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { silentDebug } from '../../utils/silent-debug.js';
|
||||
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
|
||||
import { parseObservations, parseSummary } from '../../sdk/parser.js';
|
||||
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js';
|
||||
import { SettingsDefaultsManager } from './settings/SettingsDefaultsManager.js';
|
||||
@@ -233,8 +233,16 @@ export class SDKAgent {
|
||||
sdk_session_id: session.sdkSessionId,
|
||||
project: session.project,
|
||||
user_prompt: session.userPrompt,
|
||||
last_user_message: message.last_user_message || '',
|
||||
last_assistant_message: message.last_assistant_message || ''
|
||||
last_user_message: happy_path_error__with_fallback(
|
||||
'Missing last_user_message for summary in SDKAgent',
|
||||
{ sessionDbId: session.sessionDbId, sdkSessionId: session.sdkSessionId },
|
||||
message.last_user_message || ''
|
||||
),
|
||||
last_assistant_message: happy_path_error__with_fallback(
|
||||
'Missing last_assistant_message for summary in SDKAgent',
|
||||
{ sessionDbId: session.sessionDbId, sdkSessionId: session.sdkSessionId },
|
||||
message.last_assistant_message || ''
|
||||
)
|
||||
})
|
||||
},
|
||||
session_id: session.claudeSessionId,
|
||||
@@ -268,16 +276,16 @@ export class SDKAgent {
|
||||
sessionId: session.sessionDbId,
|
||||
obsId,
|
||||
type: obs.type,
|
||||
title: obs.title || silentDebug('obs.title is null', { obsId, type: obs.type }, '(untitled)'),
|
||||
filesRead: obs.files_read?.length ?? (silentDebug('obs.files_read is null/undefined', { obsId }), 0),
|
||||
filesModified: obs.files_modified?.length ?? (silentDebug('obs.files_modified is null/undefined', { obsId }), 0),
|
||||
concepts: obs.concepts?.length ?? (silentDebug('obs.concepts is null/undefined', { obsId }), 0)
|
||||
title: obs.title || happy_path_error__with_fallback('obs.title is null', { obsId, type: obs.type }, '(untitled)'),
|
||||
filesRead: obs.files_read?.length ?? (happy_path_error__with_fallback('obs.files_read is null/undefined', { obsId }), 0),
|
||||
filesModified: obs.files_modified?.length ?? (happy_path_error__with_fallback('obs.files_modified is null/undefined', { obsId }), 0),
|
||||
concepts: obs.concepts?.length ?? (happy_path_error__with_fallback('obs.concepts is null/undefined', { obsId }), 0)
|
||||
});
|
||||
|
||||
// Sync to Chroma with error logging
|
||||
const chromaStart = Date.now();
|
||||
const obsType = obs.type;
|
||||
const obsTitle = obs.title || silentDebug('obs.title is null for Chroma sync', { obsId, type: obs.type }, '(untitled)');
|
||||
const obsTitle = obs.title || happy_path_error__with_fallback('obs.title is null for Chroma sync', { obsId, type: obs.type }, '(untitled)');
|
||||
this.dbManager.getChromaSync().syncObservation(
|
||||
obsId,
|
||||
session.claudeSessionId,
|
||||
@@ -345,14 +353,14 @@ export class SDKAgent {
|
||||
logger.info('SDK', 'Summary saved', {
|
||||
sessionId: session.sessionDbId,
|
||||
summaryId,
|
||||
request: summary.request || silentDebug('summary.request is null', { summaryId }, '(no request)'),
|
||||
request: summary.request || happy_path_error__with_fallback('summary.request is null', { summaryId }, '(no request)'),
|
||||
hasCompleted: !!summary.completed,
|
||||
hasNextSteps: !!summary.next_steps
|
||||
});
|
||||
|
||||
// Sync to Chroma with error logging
|
||||
const chromaStart = Date.now();
|
||||
const summaryRequest = summary.request || silentDebug('summary.request is null for Chroma sync', { summaryId }, '(no request)');
|
||||
const summaryRequest = summary.request || happy_path_error__with_fallback('summary.request is null for Chroma sync', { summaryId }, '(no request)');
|
||||
this.dbManager.getChromaSync().syncSummary(
|
||||
summaryId,
|
||||
session.claudeSessionId,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ChromaSync } from '../sync/ChromaSync.js';
|
||||
import { FormattingService } from './FormattingService.js';
|
||||
import { TimelineService, TimelineItem } from './TimelineService.js';
|
||||
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
|
||||
import { silentDebug } from '../../utils/silent-debug.js';
|
||||
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
|
||||
|
||||
const COLLECTION_NAME = 'cm__claude-mem';
|
||||
|
||||
@@ -97,7 +97,7 @@ export class SearchManager {
|
||||
// PATH 1: FILTER-ONLY (no query text) - Skip Chroma/FTS5, use direct SQLite filtering
|
||||
// This path enables date filtering which Chroma cannot do (requires direct SQLite access)
|
||||
if (!query) {
|
||||
silentDebug(`[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)`);
|
||||
const obsOptions = { ...options, type: obs_type, concepts, files };
|
||||
if (searchObservations) {
|
||||
observations = this.sessionSearch.searchObservations(undefined, obsOptions);
|
||||
@@ -113,7 +113,7 @@ export class SearchManager {
|
||||
else if (this.chromaSync) {
|
||||
let chromaSucceeded = false;
|
||||
try {
|
||||
silentDebug(`[mcp-server] Using ChromaDB semantic search (type filter: ${type || 'all'})`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Using ChromaDB semantic search (type filter: ${type || 'all'})`);
|
||||
|
||||
// Build Chroma where filter for doc_type
|
||||
let whereFilter: Record<string, any> | undefined;
|
||||
@@ -128,7 +128,7 @@ export class SearchManager {
|
||||
// Step 1: Chroma semantic search with optional type filter
|
||||
const chromaResults = await this.queryChroma(query, 100, whereFilter);
|
||||
chromaSucceeded = true; // Chroma didn't throw error
|
||||
silentDebug(`[mcp-server] ChromaDB returned ${chromaResults.ids.length} semantic matches`);
|
||||
happy_path_error__with_fallback(`[mcp-server] ChromaDB returned ${chromaResults.ids.length} semantic matches`);
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Step 2: Filter by recency (90 days)
|
||||
@@ -139,7 +139,7 @@ export class SearchManager {
|
||||
isRecent: meta && meta.created_at_epoch > ninetyDaysAgo
|
||||
})).filter(item => item.isRecent);
|
||||
|
||||
silentDebug(`[mcp-server] ${recentMetadata.length} results within 90-day window`);
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentMetadata.length} results within 90-day window`);
|
||||
|
||||
// Step 3: Categorize IDs by document type
|
||||
const obsIds: number[] = [];
|
||||
@@ -157,7 +157,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
silentDebug(`[mcp-server] Categorized: ${obsIds.length} obs, ${sessionIds.length} sessions, ${promptIds.length} prompts`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Categorized: ${obsIds.length} obs, ${sessionIds.length} sessions, ${promptIds.length} prompts`);
|
||||
|
||||
// Step 4: Hydrate from SQLite with additional filters
|
||||
if (obsIds.length > 0) {
|
||||
@@ -172,14 +172,14 @@ export class SearchManager {
|
||||
prompts = this.sessionStore.getUserPromptsByIds(promptIds, { orderBy: 'date_desc', limit: options.limit });
|
||||
}
|
||||
|
||||
silentDebug(`[mcp-server] Hydrated ${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts from SQLite`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts from SQLite`);
|
||||
} else {
|
||||
// Chroma returned 0 results - this is the correct answer, don't fall back to FTS5
|
||||
silentDebug(`[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)`);
|
||||
happy_path_error__with_fallback(`[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)`);
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):', chromaError.message);
|
||||
silentDebug('[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/');
|
||||
happy_path_error__with_fallback('[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/');
|
||||
// Return empty results - no fallback
|
||||
observations = [];
|
||||
sessions = [];
|
||||
@@ -188,8 +188,8 @@ export class SearchManager {
|
||||
}
|
||||
// ChromaDB not initialized - return empty results (no fallback)
|
||||
else {
|
||||
silentDebug(`[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)`);
|
||||
silentDebug(`[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/`);
|
||||
happy_path_error__with_fallback(`[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/`);
|
||||
observations = [];
|
||||
sessions = [];
|
||||
prompts = [];
|
||||
@@ -312,9 +312,9 @@ export class SearchManager {
|
||||
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using hybrid semantic search for timeline query');
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
|
||||
const chromaResults = await this.queryChroma(query, 100);
|
||||
silentDebug(`[mcp-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`);
|
||||
|
||||
if (chromaResults?.ids && chromaResults.ids.length > 0) {
|
||||
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
|
||||
@@ -328,7 +328,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ export class SearchManager {
|
||||
const topResult = results[0];
|
||||
anchorId = topResult.id;
|
||||
anchorEpoch = topResult.created_at_epoch;
|
||||
silentDebug(`[mcp-server] Query mode: Using observation #${topResult.id} as timeline anchor`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Query mode: Using observation #${topResult.id} as timeline anchor`);
|
||||
timelineData = this.sessionStore.getTimelineAroundObservation(topResult.id, topResult.created_at_epoch, depth_before, depth_after, project);
|
||||
}
|
||||
// MODE 2: Anchor-based timeline
|
||||
@@ -621,7 +621,7 @@ export class SearchManager {
|
||||
try {
|
||||
if (query) {
|
||||
// Semantic search filtered to decision type
|
||||
silentDebug('[mcp-server] Using Chroma semantic search with type=decision filter');
|
||||
happy_path_error__with_fallback('[mcp-server] Using Chroma semantic search with type=decision filter');
|
||||
const chromaResults = await this.queryChroma(query, Math.min((filters.limit || 20) * 2, 100), { type: 'decision' });
|
||||
const obsIds = chromaResults.ids;
|
||||
|
||||
@@ -632,7 +632,7 @@ export class SearchManager {
|
||||
}
|
||||
} else {
|
||||
// No query: get all decisions, rank by "decision" keyword
|
||||
silentDebug('[mcp-server] Using metadata-first + semantic ranking for decisions');
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for decisions');
|
||||
const metadataResults = this.sessionSearch.findByType('decision', filters);
|
||||
|
||||
if (metadataResults.length > 0) {
|
||||
@@ -653,7 +653,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma search failed, using SQLite fallback:', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma search failed, using SQLite fallback:', chromaError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ export class SearchManager {
|
||||
// Search for change-type observations and change-related concepts
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using hybrid search for change-related observations');
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid search for change-related observations');
|
||||
|
||||
// Get all observations with type="change" or concepts containing change
|
||||
const typeResults = this.sessionSearch.findByType('change', filters);
|
||||
@@ -737,7 +737,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,7 +807,7 @@ export class SearchManager {
|
||||
// Search for how-it-works concept observations
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using metadata-first + semantic ranking for how-it-works');
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for how-it-works');
|
||||
const metadataResults = this.sessionSearch.findByConcept('how-it-works', filters);
|
||||
|
||||
if (metadataResults.length > 0) {
|
||||
@@ -827,7 +827,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,11 +883,11 @@ export class SearchManager {
|
||||
// Vector-first search via ChromaDB
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using hybrid semantic search (Chroma + SQLite)');
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search (Chroma + SQLite)');
|
||||
|
||||
// Step 1: Chroma semantic search (top 100)
|
||||
const chromaResults = await this.queryChroma(query, 100);
|
||||
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Step 2: Filter by recency (90 days)
|
||||
@@ -897,17 +897,17 @@ export class SearchManager {
|
||||
return meta && meta.created_at_epoch > ninetyDaysAgo;
|
||||
});
|
||||
|
||||
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
|
||||
// Step 3: Hydrate from SQLite in temporal order
|
||||
if (recentIds.length > 0) {
|
||||
const limit = options.limit || 20;
|
||||
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit });
|
||||
silentDebug(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,11 +960,11 @@ export class SearchManager {
|
||||
// Vector-first search via ChromaDB
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using hybrid semantic search for sessions');
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for sessions');
|
||||
|
||||
// Step 1: Chroma semantic search (top 100)
|
||||
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'session_summary' });
|
||||
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Step 2: Filter by recency (90 days)
|
||||
@@ -974,17 +974,17 @@ export class SearchManager {
|
||||
return meta && meta.created_at_epoch > ninetyDaysAgo;
|
||||
});
|
||||
|
||||
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
|
||||
// Step 3: Hydrate from SQLite in temporal order
|
||||
if (recentIds.length > 0) {
|
||||
const limit = options.limit || 20;
|
||||
results = this.sessionStore.getSessionSummariesByIds(recentIds, { orderBy: 'date_desc', limit });
|
||||
silentDebug(`[mcp-server] Hydrated ${results.length} sessions from SQLite`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} sessions from SQLite`);
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,11 +1037,11 @@ export class SearchManager {
|
||||
// Vector-first search via ChromaDB
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using hybrid semantic search for user prompts');
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for user prompts');
|
||||
|
||||
// Step 1: Chroma semantic search (top 100)
|
||||
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'user_prompt' });
|
||||
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Step 2: Filter by recency (90 days)
|
||||
@@ -1051,17 +1051,17 @@ export class SearchManager {
|
||||
return meta && meta.created_at_epoch > ninetyDaysAgo;
|
||||
});
|
||||
|
||||
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
|
||||
// Step 3: Hydrate from SQLite in temporal order
|
||||
if (recentIds.length > 0) {
|
||||
const limit = options.limit || 20;
|
||||
results = this.sessionStore.getUserPromptsByIds(recentIds, { orderBy: 'date_desc', limit });
|
||||
silentDebug(`[mcp-server] Hydrated ${results.length} user prompts from SQLite`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} user prompts from SQLite`);
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1114,11 +1114,11 @@ export class SearchManager {
|
||||
// Metadata-first, semantic-enhanced search
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using metadata-first + semantic ranking for concept search');
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for concept search');
|
||||
|
||||
// Step 1: SQLite metadata filter (get all IDs with this concept)
|
||||
const metadataResults = this.sessionSearch.findByConcept(concept, filters);
|
||||
silentDebug(`[mcp-server] Found ${metadataResults.length} observations with concept "${concept}"`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with concept "${concept}"`);
|
||||
|
||||
if (metadataResults.length > 0) {
|
||||
// Step 2: Chroma semantic ranking (rank by relevance to concept)
|
||||
@@ -1133,7 +1133,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
|
||||
|
||||
// Step 3: Hydrate in semantic rank order
|
||||
if (rankedIds.length > 0) {
|
||||
@@ -1143,14 +1143,14 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
// Fall through to SQLite fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to SQLite-only if Chroma unavailable or failed
|
||||
if (results.length === 0) {
|
||||
silentDebug('[mcp-server] Using SQLite-only concept search');
|
||||
happy_path_error__with_fallback('[mcp-server] Using SQLite-only concept search');
|
||||
results = this.sessionSearch.findByConcept(concept, filters);
|
||||
}
|
||||
|
||||
@@ -1204,11 +1204,11 @@ export class SearchManager {
|
||||
// Metadata-first, semantic-enhanced search for observations
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using metadata-first + semantic ranking for file search');
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for file search');
|
||||
|
||||
// Step 1: SQLite metadata filter (get all results with this file)
|
||||
const metadataResults = this.sessionSearch.findByFile(filePath, filters);
|
||||
silentDebug(`[mcp-server] Found ${metadataResults.observations.length} observations, ${metadataResults.sessions.length} sessions for file "${filePath}"`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.observations.length} observations, ${metadataResults.sessions.length} sessions for file "${filePath}"`);
|
||||
|
||||
// Sessions: Keep as-is (already summarized, no semantic ranking needed)
|
||||
sessions = metadataResults.sessions;
|
||||
@@ -1227,7 +1227,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} observations by semantic relevance`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} observations by semantic relevance`);
|
||||
|
||||
// Step 3: Hydrate in semantic rank order
|
||||
if (rankedIds.length > 0) {
|
||||
@@ -1237,14 +1237,14 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
// Fall through to SQLite fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to SQLite-only if Chroma unavailable or failed
|
||||
if (observations.length === 0 && sessions.length === 0) {
|
||||
silentDebug('[mcp-server] Using SQLite-only file search');
|
||||
happy_path_error__with_fallback('[mcp-server] Using SQLite-only file search');
|
||||
const results = this.sessionSearch.findByFile(filePath, filters);
|
||||
observations = results.observations;
|
||||
sessions = results.sessions;
|
||||
@@ -1323,11 +1323,11 @@ export class SearchManager {
|
||||
// Metadata-first, semantic-enhanced search
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using metadata-first + semantic ranking for type search');
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for type search');
|
||||
|
||||
// Step 1: SQLite metadata filter (get all IDs with this type)
|
||||
const metadataResults = this.sessionSearch.findByType(type, filters);
|
||||
silentDebug(`[mcp-server] Found ${metadataResults.length} observations with type "${typeStr}"`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with type "${typeStr}"`);
|
||||
|
||||
if (metadataResults.length > 0) {
|
||||
// Step 2: Chroma semantic ranking (rank by relevance to type)
|
||||
@@ -1342,7 +1342,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
silentDebug(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
|
||||
|
||||
// Step 3: Hydrate in semantic rank order
|
||||
if (rankedIds.length > 0) {
|
||||
@@ -1352,14 +1352,14 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
// Fall through to SQLite fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to SQLite-only if Chroma unavailable or failed
|
||||
if (results.length === 0) {
|
||||
silentDebug('[mcp-server] Using SQLite-only type search');
|
||||
happy_path_error__with_fallback('[mcp-server] Using SQLite-only type search');
|
||||
results = this.sessionSearch.findByType(type, filters);
|
||||
}
|
||||
|
||||
@@ -1815,9 +1815,9 @@ export class SearchManager {
|
||||
// Use hybrid search if available
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
silentDebug('[mcp-server] Using hybrid semantic search for timeline query');
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
|
||||
const chromaResults = await this.queryChroma(query, 100);
|
||||
silentDebug(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Filter by recency (90 days)
|
||||
@@ -1827,15 +1827,15 @@ export class SearchManager {
|
||||
return meta && meta.created_at_epoch > ninetyDaysAgo;
|
||||
});
|
||||
|
||||
silentDebug(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
|
||||
if (recentIds.length > 0) {
|
||||
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit: mode === 'auto' ? 1 : limit });
|
||||
silentDebug(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
silentDebug('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1886,7 +1886,7 @@ export class SearchManager {
|
||||
} else {
|
||||
// Auto mode: Use top result as timeline anchor
|
||||
const topResult = results[0];
|
||||
silentDebug(`[mcp-server] Auto mode: Using observation #${topResult.id} as timeline anchor`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Auto mode: Using observation #${topResult.id} as timeline anchor`);
|
||||
|
||||
// Get timeline around this observation
|
||||
const timelineData = this.sessionStore.getTimelineAroundObservation(
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { DatabaseManager } from './DatabaseManager.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { silentDebug } from '../../utils/silent-debug.js';
|
||||
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
|
||||
import type { ActiveSession, PendingMessage, ObservationData } from '../worker-types.js';
|
||||
|
||||
export class SessionManager {
|
||||
@@ -43,7 +43,7 @@ export class SessionManager {
|
||||
// in the database but the in-memory session still has the stale empty value
|
||||
const dbSession = this.dbManager.getSessionById(sessionDbId);
|
||||
if (dbSession.project && dbSession.project !== session.project) {
|
||||
silentDebug('[SessionManager] Updating project from database', {
|
||||
happy_path_error__with_fallback('[SessionManager] Updating project from database', {
|
||||
sessionDbId,
|
||||
oldProject: session.project,
|
||||
newProject: dbSession.project
|
||||
@@ -53,7 +53,7 @@ export class SessionManager {
|
||||
|
||||
// Update userPrompt for continuation prompts
|
||||
if (currentUserPrompt) {
|
||||
silentDebug('[SessionManager] Updating userPrompt for continuation', {
|
||||
happy_path_error__with_fallback('[SessionManager] Updating userPrompt for continuation', {
|
||||
sessionDbId,
|
||||
promptNumber,
|
||||
oldPrompt: session.userPrompt.substring(0, 80),
|
||||
@@ -62,7 +62,7 @@ export class SessionManager {
|
||||
session.userPrompt = currentUserPrompt;
|
||||
session.lastPromptNumber = promptNumber || session.lastPromptNumber;
|
||||
} else {
|
||||
silentDebug('[SessionManager] No currentUserPrompt provided for existing session', {
|
||||
happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for existing session', {
|
||||
sessionDbId,
|
||||
promptNumber,
|
||||
usingCachedPrompt: session.userPrompt.substring(0, 80)
|
||||
@@ -78,13 +78,13 @@ export class SessionManager {
|
||||
const userPrompt = currentUserPrompt || dbSession.user_prompt;
|
||||
|
||||
if (!currentUserPrompt) {
|
||||
silentDebug('[SessionManager] No currentUserPrompt provided for new session, using database', {
|
||||
happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for new session, using database', {
|
||||
sessionDbId,
|
||||
promptNumber,
|
||||
dbPrompt: dbSession.user_prompt.substring(0, 80)
|
||||
});
|
||||
} else {
|
||||
silentDebug('[SessionManager] Initializing session with fresh userPrompt', {
|
||||
happy_path_error__with_fallback('[SessionManager] Initializing session with fresh userPrompt', {
|
||||
sessionDbId,
|
||||
promptNumber,
|
||||
userPrompt: currentUserPrompt.substring(0, 80)
|
||||
|
||||
@@ -9,6 +9,7 @@ import express, { Request, Response } from 'express';
|
||||
import { getWorkerPort } from '../../../../shared/worker-utils.js';
|
||||
import { logger } from '../../../../utils/logger.js';
|
||||
import { stripMemoryTagsFromJson, stripMemoryTagsFromPrompt } from '../../../../utils/tag-stripping.js';
|
||||
import { happy_path_error__with_fallback } from '../../../../utils/silent-debug.js';
|
||||
import { SessionManager } from '../../SessionManager.js';
|
||||
import { DatabaseManager } from '../../DatabaseManager.js';
|
||||
import { SDKAgent } from '../../SDKAgent.js';
|
||||
@@ -307,7 +308,11 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
tool_input: cleanedToolInput,
|
||||
tool_response: cleanedToolResponse,
|
||||
prompt_number: promptNumber,
|
||||
cwd: cwd || ''
|
||||
cwd: happy_path_error__with_fallback(
|
||||
'Missing cwd when queueing observation in SessionRoutes',
|
||||
{ sessionDbId, tool_name },
|
||||
cwd || ''
|
||||
)
|
||||
});
|
||||
|
||||
// Ensure SDK agent is running
|
||||
@@ -353,7 +358,15 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
}
|
||||
|
||||
// Queue summarize
|
||||
this.sessionManager.queueSummarize(sessionDbId, last_user_message || '', last_assistant_message);
|
||||
this.sessionManager.queueSummarize(
|
||||
sessionDbId,
|
||||
happy_path_error__with_fallback(
|
||||
'Missing last_user_message when queueing summary in SessionRoutes',
|
||||
{ sessionDbId },
|
||||
last_user_message || ''
|
||||
),
|
||||
last_assistant_message
|
||||
);
|
||||
|
||||
// Ensure SDK agent is running
|
||||
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { silentDebug } from '../utils/silent-debug.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
|
||||
const SETTINGS_PATH = join(homedir(), '.claude-mem', 'settings.json');
|
||||
|
||||
@@ -25,7 +25,7 @@ export function loadEarlySetting(key: keyof EarlySettings, defaultValue: string)
|
||||
if (fileValue !== undefined) return fileValue;
|
||||
}
|
||||
} catch (error) {
|
||||
silentDebug('Failed to load settings file', { error, settingsPath: SETTINGS_PATH, key });
|
||||
happy_path_error__with_fallback('Failed to load settings file', { error, settingsPath: SETTINGS_PATH, key });
|
||||
}
|
||||
|
||||
return process.env[key] || defaultValue;
|
||||
|
||||
+18
-10
@@ -1,25 +1,27 @@
|
||||
/**
|
||||
* Silent Debug Logger
|
||||
* Happy Path Error With Fallback
|
||||
*
|
||||
* Semantic meaning: "When the happy path fails, this is an error, but we have a fallback."
|
||||
*
|
||||
* NOTE: This utility is to be used like Frank's Red Hot, we put that shit on everything.
|
||||
*
|
||||
* USE THIS INSTEAD OF SILENT FAILURES!
|
||||
* Stop doing this: `const value = something || '';`
|
||||
* Start doing this: `const value = something || silentDebug('something was undefined');`
|
||||
* Start doing this: `const value = something || happy_path_error__with_fallback('something was undefined');`
|
||||
*
|
||||
* Logs to ~/.claude-mem/silent.log and returns a fallback value.
|
||||
* Check logs with `npm run logs:silent`
|
||||
*
|
||||
* Usage:
|
||||
* import { silentDebug } from '../utils/silent-debug.js';
|
||||
* import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
*
|
||||
* const title = obs.title || silentDebug('obs.title missing', { obs });
|
||||
* const name = user.name || silentDebug('user.name missing', { user }, 'Anonymous');
|
||||
* const title = obs.title || happy_path_error__with_fallback('obs.title missing', { obs });
|
||||
* const name = user.name || happy_path_error__with_fallback('user.name missing', { user }, 'Anonymous');
|
||||
*
|
||||
* try {
|
||||
* doSomething();
|
||||
* } catch (error) {
|
||||
* silentDebug('doSomething failed', { error });
|
||||
* happy_path_error__with_fallback('doSomething failed', { error });
|
||||
* }
|
||||
*/
|
||||
|
||||
@@ -30,13 +32,13 @@ import { join } from 'path';
|
||||
const LOG_FILE = join(homedir(), '.claude-mem', 'silent.log');
|
||||
|
||||
/**
|
||||
* Write a debug message to silent.log and return fallback value
|
||||
* @param message - The message to log
|
||||
* Write an error message to silent.log and return fallback value
|
||||
* @param message - Error message describing what went wrong
|
||||
* @param data - Optional data to include (will be JSON stringified)
|
||||
* @param fallback - Value to return (defaults to empty string)
|
||||
* @returns The fallback value (for use in || fallbacks)
|
||||
*/
|
||||
export function silentDebug(message: string, data?: any, fallback: string = ''): string {
|
||||
export function happy_path_error__with_fallback(message: string, data?: any, fallback: string = ''): string {
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Capture stack trace to get caller location
|
||||
@@ -51,7 +53,7 @@ export function silentDebug(message: string, data?: any, fallback: string = ''):
|
||||
? `${callerMatch[1].split('/').pop()}:${callerMatch[2]}`
|
||||
: 'unknown';
|
||||
|
||||
let logLine = `[${timestamp}] [${location}] ${message}`;
|
||||
let logLine = `[${timestamp}] [HAPPY-PATH-ERROR] [${location}] ${message}`;
|
||||
|
||||
if (data !== undefined) {
|
||||
try {
|
||||
@@ -84,3 +86,9 @@ export function clearSilentLog(): void {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use happy_path_error__with_fallback instead
|
||||
* Backward compatibility alias for silentDebug
|
||||
*/
|
||||
export const silentDebug = happy_path_error__with_fallback;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* This keeps the worker service simple and follows one-way data stream.
|
||||
*/
|
||||
|
||||
import { silentDebug } from './silent-debug.js';
|
||||
import { happy_path_error__with_fallback } from './silent-debug.js';
|
||||
|
||||
/**
|
||||
* Maximum number of tags allowed in a single content block
|
||||
@@ -41,14 +41,14 @@ function countTags(content: string): number {
|
||||
*/
|
||||
export function stripMemoryTagsFromJson(content: string): string {
|
||||
if (typeof content !== 'string') {
|
||||
silentDebug('[tag-stripping] received non-string for JSON context:', { type: typeof content });
|
||||
happy_path_error__with_fallback('[tag-stripping] received non-string for JSON context:', { type: typeof content });
|
||||
return '{}'; // Safe default for JSON context
|
||||
}
|
||||
|
||||
// ReDoS protection: limit tag count before regex processing
|
||||
const tagCount = countTags(content);
|
||||
if (tagCount > MAX_TAG_COUNT) {
|
||||
silentDebug('[tag-stripping] tag count exceeds limit, truncating:', {
|
||||
happy_path_error__with_fallback('[tag-stripping] tag count exceeds limit, truncating:', {
|
||||
tagCount,
|
||||
maxAllowed: MAX_TAG_COUNT,
|
||||
contentLength: content.length
|
||||
@@ -73,14 +73,14 @@ export function stripMemoryTagsFromJson(content: string): string {
|
||||
*/
|
||||
export function stripMemoryTagsFromPrompt(content: string): string {
|
||||
if (typeof content !== 'string') {
|
||||
silentDebug('[tag-stripping] received non-string for prompt context:', { type: typeof content });
|
||||
happy_path_error__with_fallback('[tag-stripping] received non-string for prompt context:', { type: typeof content });
|
||||
return ''; // Safe default for prompt content
|
||||
}
|
||||
|
||||
// ReDoS protection: limit tag count before regex processing
|
||||
const tagCount = countTags(content);
|
||||
if (tagCount > MAX_TAG_COUNT) {
|
||||
silentDebug('[tag-stripping] tag count exceeds limit, truncating:', {
|
||||
happy_path_error__with_fallback('[tag-stripping] tag count exceeds limit, truncating:', {
|
||||
tagCount,
|
||||
maxAllowed: MAX_TAG_COUNT,
|
||||
contentLength: content.length
|
||||
|
||||
Reference in New Issue
Block a user