fix: Chroma connection errors and remove dead last_user_message code (#525)
* fix: distinguish connection errors from collection-not-found in ChromaSync Previously, ensureCollection() caught ALL errors from chroma_get_collection_info and assumed they meant "collection doesn't exist". This caused connection errors like "Not connected" to trigger unnecessary collection creation attempts. Now connection-related errors are re-thrown immediately instead of being misinterpreted as missing collections. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: improve error handling for Chroma connection and collection creation * fix: remove dead last_user_message from summarize flow The last_user_message field was extracted from transcripts but never used. In Claude Code transcripts, "user" type messages are mostly tool_results, not actual user input. The user's original request is already stored in user_prompts table. This removes the false warning "Missing last_user_message when queueing summary" which was complaining about missing data that didn't exist and wasn't needed. Changes: - summary-hook: Only extract last_assistant_message - SessionRoutes: Remove last_user_message from request body handling - SessionManager.queueSummarize: Remove lastUserMessage parameter - PendingMessage interface: Remove last_user_message field - SDKSession interface: Remove last_user_message field - All agents: Remove last_user_message from buildSummaryPrompt calls 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * build artifacts for plugin * Enhance error handling across multiple services - Improved logging in `BranchManager.ts` to capture recovery checkout failures. - Updated `PaginationHelper.ts` to log when file paths are plain strings instead of valid JSON. - Enhanced error logging in `SDKAgent.ts` for Claude executable detection failures. - Added logging for plain string handling in `SearchManager.ts` for files read and edited. - Improved logging in `paths.ts` for git root detection failures. - Enhanced JSON parsing error handling in `timeline-formatting.ts` with previews of failed inputs. - Updated `transcript-parser.ts` to log summary of parse errors after processing transcript lines. - Established a baseline for error handling practices in `error-handling-baseline.txt`. - Documented error handling anti-pattern rules in `CLAUDE.md` to prevent silent failures and improve code quality. * Add error handling anti-pattern detection script and guidelines - Introduced `detect-error-handling-antipatterns.ts` to identify common error handling issues in TypeScript code. - Created comprehensive documentation in `CLAUDE.md` outlining forbidden patterns, allowed patterns, and critical path protection rules. - Implemented checks for empty catch blocks, logging practices, and try-catch block sizes to prevent silent failures and improve debugging. - Established a reporting mechanism to summarize detected anti-patterns with severity levels. * feat: add console filter bar and log line parsing with filtering capabilities - Introduced a console filter bar with options to filter logs by level and component. - Implemented parsing of log lines to extract structured data including timestamp, level, component, and correlation ID. - Added functionality to toggle individual and all levels/components for filtering. - Enhanced log line rendering with color coding based on log level and special message types. - Improved responsiveness of the filter bar for smaller screens. --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,147 +1,5 @@
|
|||||||
/* To @claude: be vigilant about only leaving evergreen context in this file, claude-mem handles working context separately. */
|
|
||||||
|
|
||||||
# ⚠️ MANDATORY ERROR HANDLING RULES ⚠️
|
|
||||||
|
|
||||||
## The Try-Catch Problem That Cost 10 Hours
|
|
||||||
|
|
||||||
A single overly-broad try-catch block wasted 10 hours of debugging time by silently swallowing errors.
|
|
||||||
**This pattern is BANNED.**
|
|
||||||
|
|
||||||
## BEFORE You Write Any Try-Catch
|
|
||||||
|
|
||||||
**RUN THIS TEST FIRST:**
|
|
||||||
```bash
|
|
||||||
bun run scripts/detect-error-handling-antipatterns.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
**You MUST answer these 5 questions to the user BEFORE writing try-catch:**
|
|
||||||
|
|
||||||
1. **What SPECIFIC error am I catching?** (Name the error type: `FileNotFoundError`, `NetworkTimeout`, `ValidationError`)
|
|
||||||
2. **Show documentation proving this error can occur** (Link to docs or show me the source code)
|
|
||||||
3. **Why can't this error be prevented?** (If it can be prevented, prevent it instead)
|
|
||||||
4. **What will the catch block DO?** (Must include logging + either rethrow OR explicit fallback)
|
|
||||||
5. **Why shouldn't this error propagate?** (Justify swallowing it rather than letting caller handle)
|
|
||||||
|
|
||||||
**If you cannot answer ALL 5 questions with specifics, DO NOT write the try-catch.**
|
|
||||||
|
|
||||||
## FORBIDDEN PATTERNS (Zero Tolerance)
|
|
||||||
|
|
||||||
### 🔴 CRITICAL - Never Allowed
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ FORBIDDEN: Empty catch
|
|
||||||
try {
|
|
||||||
doSomething();
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
// ❌ FORBIDDEN: Catch without logging
|
|
||||||
try {
|
|
||||||
doSomething();
|
|
||||||
} catch (error) {
|
|
||||||
return null; // Silent failure!
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ FORBIDDEN: Large try blocks (>10 lines)
|
|
||||||
try {
|
|
||||||
// 50 lines of code
|
|
||||||
// Multiple operations
|
|
||||||
// Different failure modes
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Something failed'); // Which thing?!
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ FORBIDDEN: Promise empty catch
|
|
||||||
promise.catch(() => {}); // Error disappears into void
|
|
||||||
|
|
||||||
// ❌ FORBIDDEN: Try-catch to fix TypeScript errors
|
|
||||||
try {
|
|
||||||
// @ts-ignore
|
|
||||||
const value = response.propertyThatDoesntExist;
|
|
||||||
} catch {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ ALLOWED Patterns
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ✅ GOOD: Specific, logged, explicit handling
|
|
||||||
try {
|
|
||||||
await fetch(url);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof NetworkError) {
|
|
||||||
logger.warn('SYNC', 'Network request failed, will retry', { url }, error);
|
|
||||||
return null; // Explicit: null means "fetch failed"
|
|
||||||
}
|
|
||||||
throw error; // Unexpected errors propagate
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ GOOD: Minimal scope, clear recovery
|
|
||||||
try {
|
|
||||||
JSON.parse(data);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('CONFIG', 'Corrupt settings file, using defaults', {}, error);
|
|
||||||
return DEFAULT_SETTINGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ GOOD: Fire-and-forget with logging
|
|
||||||
backgroundTask()
|
|
||||||
.catch(error => logger.warn('BACKGROUND', 'Task failed', {}, error));
|
|
||||||
|
|
||||||
// ✅ GOOD: Approved override for justified exceptions
|
|
||||||
try {
|
|
||||||
JSON.parse(optionalField);
|
|
||||||
} catch (error) {
|
|
||||||
// [APPROVED OVERRIDE]: Expected JSON parse failures for optional fields, too frequent to log
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Approved Overrides
|
|
||||||
|
|
||||||
When you have a **justified reason** to violate the error handling rules (e.g., performance-critical hot paths, expected frequent failures), you can use an approved override:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// [APPROVED OVERRIDE]: Brief explanation of why this is necessary
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules for approved overrides:**
|
|
||||||
- Must have a **specific, technical reason** (not "seemed fine" or "works for me")
|
|
||||||
- Reason must explain **why the violation is necessary**, not just what it does
|
|
||||||
- Examples of valid reasons:
|
|
||||||
- "Expected JSON parse failures for optional fields, too frequent to log"
|
|
||||||
- "Logger can't log its own failures, using stderr as last resort"
|
|
||||||
- "Health check port scan, expected connection failures"
|
|
||||||
- The detector will flag these as **APPROVED_OVERRIDE** (warning level) for review
|
|
||||||
- Invalid or outdated reasons should be challenged during code review
|
|
||||||
|
|
||||||
## The Meta-Rule
|
|
||||||
|
|
||||||
**UNCERTAINTY TRIGGERS RESEARCH, NOT TRY-CATCH**
|
|
||||||
|
|
||||||
When you're unsure if a property exists or a method signature is correct:
|
|
||||||
1. **READ** the source code or documentation
|
|
||||||
2. **VERIFY** with the Read tool
|
|
||||||
3. **USE** TypeScript types to catch errors at compile time
|
|
||||||
4. **WRITE** code you KNOW is correct
|
|
||||||
|
|
||||||
Never use try-catch to paper over uncertainty. That wastes hours of debugging time later.
|
|
||||||
|
|
||||||
## Critical Path Protection
|
|
||||||
|
|
||||||
These files are **NEVER** allowed to have catch-and-continue:
|
|
||||||
- `SDKAgent.ts` - Errors must propagate, not hide
|
|
||||||
- `GeminiAgent.ts` - Must fail loud, not silent
|
|
||||||
- `OpenRouterAgent.ts` - Must fail loud, not silent
|
|
||||||
- `SessionStore.ts` - Database errors must propagate
|
|
||||||
- `worker-service.ts` - Core service errors must be visible
|
|
||||||
|
|
||||||
On critical paths, prefer **NO TRY-CATCH** and let errors propagate naturally.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Claude-Mem: AI Development Instructions
|
# Claude-Mem: AI Development Instructions
|
||||||
|
|
||||||
## What This Project Is
|
|
||||||
|
|
||||||
Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.
|
Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -152,7 +10,7 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
|
|||||||
|
|
||||||
**Worker Service** (`src/services/worker-service.ts`) - Express API on port 37777, Bun-managed, handles AI processing asynchronously
|
**Worker Service** (`src/services/worker-service.ts`) - Express API on port 37777, Bun-managed, handles AI processing asynchronously
|
||||||
|
|
||||||
**Database** (`src/services/sqlite/`) - SQLite3 at `~/.claude-mem/claude-mem.db`
|
**Database** (`src/services/sqlite/`) - SQLite3 at `~/.claude-mem/claude-mem.db`
|
||||||
|
|
||||||
**Search Skill** (`plugin/skills/mem-search/SKILL.md`) - HTTP API for searching past work, auto-invoked when users ask about history
|
**Search Skill** (`plugin/skills/mem-search/SKILL.md`) - HTTP API for searching past work, auto-invoked when users ask about history
|
||||||
|
|
||||||
@@ -218,6 +76,6 @@ Claude-mem is designed with a clean separation between open-source core function
|
|||||||
|
|
||||||
This architecture preserves the open-source nature of the project while enabling sustainable development through optional paid features.
|
This architecture preserves the open-source nature of the project while enabling sustainable development through optional paid features.
|
||||||
|
|
||||||
# Important
|
## Important
|
||||||
|
|
||||||
No need to edit the changelog ever, it's generated automatically.
|
No need to edit the changelog ever, it's generated automatically.
|
||||||
|
|||||||
@@ -0,0 +1,688 @@
|
|||||||
|
🔍 Scanning for error handling anti-patterns...
|
||||||
|
|
||||||
|
Found 80 TypeScript files
|
||||||
|
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════
|
||||||
|
ERROR HANDLING ANTI-PATTERNS DETECTED
|
||||||
|
═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
Found 153 anti-patterns:
|
||||||
|
🔴 CRITICAL: 26
|
||||||
|
🟠 HIGH: 47
|
||||||
|
🟡 MEDIUM: 80
|
||||||
|
|
||||||
|
🔴 CRITICAL ISSUES (Fix immediately - these cause silent failures):
|
||||||
|
─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
📁 src/utils/transcript-parser.ts:44
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Parse errors accumulated in parseErrors array for batch access, logging each line would be excessive
|
||||||
|
this.parseErrors.push({
|
||||||
|
lineNumber: index + 1,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
... (2 more lines)
|
||||||
|
|
||||||
|
📁 src/shared/timeline-formatting.ts:18
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (err) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected JSON parse failures for malformed data fields, too frequent to log
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/shared/paths.ts:105
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected when not in git repo or git unavailable, common fallback path
|
||||||
|
return basename(process.cwd());
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/sdk/prompts.ts:98
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string tool inputs, normal fallback
|
||||||
|
toolInput = obs.tool_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/sdk/prompts.ts:105
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string tool outputs, normal fallback
|
||||||
|
toolOutput = obs.tool_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:68
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: PID file cleanup is non-critical, log full error and continue shutdown
|
||||||
|
logger.warn('SYSTEM', 'Failed to remove PID file', { path: PID_FILE }, error as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:131
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Context update is non-critical, log full error and continue
|
||||||
|
logger.warn('CURSOR', 'Failed to update context file', { projectName }, error as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:152
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected failure when port is free, called frequently for health checks
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:165
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected failures during startup health check, will retry
|
||||||
|
logger.debug('SYSTEM', 'Service not ready yet, will retry', { port }, error as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:368
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Shutdown must complete, log error and exit with failure code
|
||||||
|
logger.error('SYSTEM', 'Error during shutdown', {}, error as Error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:623
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Process may have already exited during cleanup, expected failure
|
||||||
|
logger.debug('SYSTEM', 'Failed to kill process, may have already exited', { pid }, error as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:632
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Process may have already exited during cleanup, expected failure
|
||||||
|
logger.debug('SYSTEM', 'Process already exited', { pid }, error as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:838
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// Recovery is best-effort - skip failed sessions and continue with others
|
||||||
|
logger.warn('SYSTEM', `Failed to process session ${sessionDbId}`, {}, error as Error);
|
||||||
|
result.sessionsSkipped++;
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:987
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch {
|
||||||
|
// Process may have already exited - continue shutdown
|
||||||
|
logger.debug('SYSTEM', 'Process already exited during force kill', { pid });
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1004
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// Expected: process has exited
|
||||||
|
// Not logging - this is called in a tight loop during cleanup
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1095
|
||||||
|
❌ EMPTY_CATCH
|
||||||
|
Empty catch block - errors are silently swallowed. User will waste hours debugging.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch {
|
||||||
|
// Start fresh if corrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1301
|
||||||
|
❌ EMPTY_CATCH
|
||||||
|
Empty catch block - errors are silently swallowed. User will waste hours debugging.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch {
|
||||||
|
// CLI not found
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1413
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// Start fresh if corrupt
|
||||||
|
logger.warn('SYSTEM', 'Corrupt mcp.json, creating new config', { path: mcpJsonPath, error: error instanceof Error ? error.message : String(error) });
|
||||||
|
config = { mcpServers: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1670
|
||||||
|
❌ EMPTY_CATCH
|
||||||
|
Empty catch block - errors are silently swallowed. User will waste hours debugging.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch {
|
||||||
|
// Worker not running - that's ok, context will be generated after first session
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:2050
|
||||||
|
❌ PROMISE_CATCH_NO_LOGGING
|
||||||
|
Promise .catch() without logging - errors are silently swallowed.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
.catch((error) => {
|
||||||
|
logger.failure('SYSTEM', 'Worker failed to start', {}, error as Error);
|
||||||
|
removePidFile();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
📁 src/services/worker/SDKAgent.ts:545
|
||||||
|
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||||
|
Critical path continues after error - may cause silent data corruption.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected failure when claude not in PATH, falls through to clear error message below
|
||||||
|
logger.debug('SDK', 'Claude executable auto-detection failed', {}, error as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1403
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string file lists, normal fallback
|
||||||
|
if (summary.files_read.trim()) {
|
||||||
|
lines.push(`**Files Read:** ${summary.files_read}`);
|
||||||
|
}
|
||||||
|
... (1 more lines)
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1418
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string file lists, normal fallback
|
||||||
|
if (summary.files_edited.trim()) {
|
||||||
|
lines.push(`**Files Edited:** ${summary.files_edited}`);
|
||||||
|
}
|
||||||
|
... (1 more lines)
|
||||||
|
|
||||||
|
📁 src/services/worker/PaginationHelper.ts:54
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (err) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string file paths, normal fallback
|
||||||
|
return filePathsStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/context-generator.ts:202
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (parseError) {
|
||||||
|
// [POSSIBLY RELEVANT]: Expected malformed JSON lines in transcript, logging each would be excessive
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
📁 src/services/context-generator.ts:226
|
||||||
|
❌ NO_LOGGING_IN_CATCH
|
||||||
|
Catch block has no logging - errors occur invisibly.
|
||||||
|
|
||||||
|
Code:
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.code === 'ERR_DLOPEN_FAILED') {
|
||||||
|
unlinkSync(VERSION_MARKER_PATH);
|
||||||
|
} catch (unlinkError) {
|
||||||
|
// [POSSIBLY RELEVANT]: Marker file may not exist during first run, expected cleanup failure
|
||||||
|
... (1 more lines)
|
||||||
|
|
||||||
|
🟠 HIGH PRIORITY:
|
||||||
|
─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useSSE.ts:50 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 33 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/usePagination.ts:54 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 19 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useSettings.ts:64 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 14 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useContextPreview.ts:47 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 11 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/bin/import-xml-observations.ts:62 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 12 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/bin/import-xml-observations.ts:134 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/bin/import-xml-observations.ts:167 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/servers/mcp-server.ts:52 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 14 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/servers/mcp-server.ts:97 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 21 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:55 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 67 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:227 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 38 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:346 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 40 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:427 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 43 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:495 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:532 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 35 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:599 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:1550 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 27 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:438 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:526 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 11 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:666 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 56 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:814 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1638 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 24 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1753 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 28 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sync/ChromaSync.ts:99 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 28 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sync/ChromaSync.ts:344 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 14 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sync/ChromaSync.ts:534 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 32 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/sync/ChromaSync.ts:609 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 106 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/GeminiAgent.ts:144 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 76 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/BranchManager.ts:120 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/BranchManager.ts:268 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 21 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:120 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 43 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:382 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:642 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 22 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:726 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 18 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:818 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 14 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:888 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:958 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1028 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1098 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1181 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 17 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1282 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1493 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 147 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1725 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/OpenRouterAgent.ts:104 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 77 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/http/routes/SessionRoutes.ts:151 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/worker/http/routes/SessionRoutes.ts:185 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 20 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
📁 src/services/context-generator.ts:182 - LARGE_TRY_BLOCK
|
||||||
|
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||||
|
|
||||||
|
🟡 MEDIUM PRIORITY:
|
||||||
|
─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useStats.ts:13 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useSSE.ts:93 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useTheme.ts:19 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useTheme.ts:64 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/usePagination.ts:84 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useContextPreview.ts:31 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/ui/viewer/hooks/useContextPreview.ts:60 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/bin/import-xml-observations.ts:152 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/bin/import-xml-observations.ts:183 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/bin/import-xml-observations.ts:329 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/bin/import-xml-observations.ts:361 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/utils/logger.ts:55 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/utils/logger.ts:74 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/utils/logger.ts:269 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/shared/timeline-formatting.ts:18 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/shared/SettingsDefaultsManager.ts:152 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/shared/paths.ts:105 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/sdk/prompts.ts:98 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/sdk/prompts.ts:105 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/servers/mcp-server.ts:76 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/servers/mcp-server.ts:123 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/servers/mcp-server.ts:269 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:138 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:278 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:399 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:482 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:520 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:575 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:619 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:1489 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:1521 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sqlite/SessionStore.ts:1577 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:59 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:68 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:131 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:152 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:165 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:185 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:368 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:623 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:632 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:743 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:838 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:963 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1004 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker-service.ts:1797 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/queue/SessionQueueProcessor.ts:31 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sync/ChromaSync.ts:578 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/sync/ChromaSync.ts:808 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SettingsManager.ts:45 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/BranchManager.ts:138 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/BranchManager.ts:243 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/BranchManager.ts:300 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SDKAgent.ts:545 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:185 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:398 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:676 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:754 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:838 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:912 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:982 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1052 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1127 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1214 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1311 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1403 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1418 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1700 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SearchManager.ts:1745 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/PaginationHelper.ts:54 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/http/BaseRouteHandler.ts:28 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/http/routes/SettingsRoutes.ts:76 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/http/routes/SessionRoutes.ts:165 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SessionManager.ts:208 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/worker/SessionManager.ts:256 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/domain/ModeManager.ts:146 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/domain/ModeManager.ts:163 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/domain/ModeManager.ts:173 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/context-generator.ts:202 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
📁 src/services/context-generator.ts:226 - GENERIC_CATCH
|
||||||
|
Catch block handles all errors identically - no error type discrimination.
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════
|
||||||
|
REMINDER: Every try-catch must answer these questions:
|
||||||
|
1. What SPECIFIC error am I catching? (Name it)
|
||||||
|
2. Show me documentation proving this error can occur
|
||||||
|
3. Why can't this error be prevented?
|
||||||
|
4. What will the catch block DO? (Log + rethrow? Fallback?)
|
||||||
|
5. Why shouldn't this error propagate to the caller?
|
||||||
|
|
||||||
|
To approve an anti-pattern, add: // [APPROVED OVERRIDE]: reason
|
||||||
|
═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
|
||||||
|
❌ FAILED: 26 critical error handling anti-patterns must be fixed.
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,13 +1,13 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
import{stdin as $}from"process";var f=JSON.stringify({continue:!0,suppressOutput:!0});import{readFileSync as w,writeFileSync as v,existsSync as F}from"fs";import{join as x}from"path";import{homedir as H}from"os";var d="bugfix,feature,refactor,discovery,decision,change",h="how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off";var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-sonnet-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_PROVIDER:"claude",CLAUDE_MEM_GEMINI_API_KEY:"",CLAUDE_MEM_GEMINI_MODEL:"gemini-2.5-flash-lite",CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED:"true",CLAUDE_MEM_OPENROUTER_API_KEY:"",CLAUDE_MEM_OPENROUTER_MODEL:"xiaomi/mimo-v2-flash:free",CLAUDE_MEM_OPENROUTER_SITE_URL:"",CLAUDE_MEM_OPENROUTER_APP_NAME:"claude-mem",CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES:"20",CLAUDE_MEM_OPENROUTER_MAX_TOKENS:"100000",CLAUDE_MEM_DATA_DIR:x(H(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:d,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:h,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){try{if(!F(t))return this.getAllDefaults();let r=w(t,"utf-8"),e=JSON.parse(r),n=e;if(e.env&&typeof e.env=="object"){n=e.env;try{v(t,JSON.stringify(n,null,2),"utf-8"),c.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(E){c.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},E)}}let o={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))n[E]!==void 0&&(o[E]=n[E]);return o}catch(r){return c.warn("SETTINGS","Failed to load settings, using defaults",{settingsPath:t},r),this.getAllDefaults()}}};import{appendFileSync as W,existsSync as b,mkdirSync as G}from"fs";import{join as T}from"path";var M=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(M||{}),p=class{level=null;useColor;logFilePath=null;constructor(){this.useColor=process.stdout.isTTY??!1,this.initializeLogFile()}initializeLogFile(){try{let t=g.get("CLAUDE_MEM_DATA_DIR"),r=T(t,"logs");b(r)||G(r,{recursive:!0});let e=new Date().toISOString().split("T")[0];this.logFilePath=T(r,`claude-mem-${e}.log`)}catch(t){console.error("[LOGGER] Failed to initialize log file:",t),this.logFilePath=null}}getLevel(){if(this.level===null)try{let t=g.get("CLAUDE_MEM_DATA_DIR"),r=T(t,"settings.json"),n=g.loadFromFile(r).CLAUDE_MEM_LOG_LEVEL.toUpperCase();this.level=M[n]??1}catch(t){console.error("[LOGGER] Failed to load settings, using INFO level:",t),this.level=1}return this.level}correlationId(t,r){return`obs-${t}-${r}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message}
|
import{stdin as $}from"process";var f=JSON.stringify({continue:!0,suppressOutput:!0});import{readFileSync as w,writeFileSync as v,existsSync as F}from"fs";import{join as x}from"path";import{homedir as H}from"os";var U="bugfix,feature,refactor,discovery,decision,change",d="how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off";var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-sonnet-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_PROVIDER:"claude",CLAUDE_MEM_GEMINI_API_KEY:"",CLAUDE_MEM_GEMINI_MODEL:"gemini-2.5-flash-lite",CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED:"true",CLAUDE_MEM_OPENROUTER_API_KEY:"",CLAUDE_MEM_OPENROUTER_MODEL:"xiaomi/mimo-v2-flash:free",CLAUDE_MEM_OPENROUTER_SITE_URL:"",CLAUDE_MEM_OPENROUTER_APP_NAME:"claude-mem",CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES:"20",CLAUDE_MEM_OPENROUTER_MAX_TOKENS:"100000",CLAUDE_MEM_DATA_DIR:x(H(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:U,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:d,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){try{if(!F(t))return this.getAllDefaults();let r=w(t,"utf-8"),e=JSON.parse(r),n=e;if(e.env&&typeof e.env=="object"){n=e.env;try{v(t,JSON.stringify(n,null,2),"utf-8"),c.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(E){c.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},E)}}let o={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))n[E]!==void 0&&(o[E]=n[E]);return o}catch(r){return c.warn("SETTINGS","Failed to load settings, using defaults",{settingsPath:t},r),this.getAllDefaults()}}};import{appendFileSync as W,existsSync as b,mkdirSync as G}from"fs";import{join as T}from"path";var M=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(M||{}),p=class{level=null;useColor;logFilePath=null;constructor(){this.useColor=process.stdout.isTTY??!1,this.initializeLogFile()}initializeLogFile(){try{let t=g.get("CLAUDE_MEM_DATA_DIR"),r=T(t,"logs");b(r)||G(r,{recursive:!0});let e=new Date().toISOString().split("T")[0];this.logFilePath=T(r,`claude-mem-${e}.log`)}catch(t){console.error("[LOGGER] Failed to initialize log file:",t),this.logFilePath=null}}getLevel(){if(this.level===null)try{let t=g.get("CLAUDE_MEM_DATA_DIR"),r=T(t,"settings.json"),n=g.loadFromFile(r).CLAUDE_MEM_LOG_LEVEL.toUpperCase();this.level=M[n]??1}catch(t){console.error("[LOGGER] Failed to load settings, using INFO level:",t),this.level=1}return this.level}correlationId(t,r){return`obs-${t}-${r}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message}
|
||||||
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let r=Object.keys(t);return r.length===0?"{}":r.length<=3?JSON.stringify(t):`{${r.length} keys: ${r.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,r){if(!r)return t;let e=typeof r=="string"?JSON.parse(r):r;if(t==="Bash"&&e.command)return`${t}(${e.command})`;if(e.file_path)return`${t}(${e.file_path})`;if(e.notebook_path)return`${t}(${e.notebook_path})`;if(t==="Glob"&&e.pattern)return`${t}(${e.pattern})`;if(t==="Grep"&&e.pattern)return`${t}(${e.pattern})`;if(e.url)return`${t}(${e.url})`;if(e.query)return`${t}(${e.query})`;if(t==="Task"){if(e.subagent_type)return`${t}(${e.subagent_type})`;if(e.description)return`${t}(${e.description})`}return t==="Skill"&&e.skill?`${t}(${e.skill})`:t==="LSP"&&e.operation?`${t}(${e.operation})`:t}formatTimestamp(t){let r=t.getFullYear(),e=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),o=String(t.getHours()).padStart(2,"0"),E=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0"),_=String(t.getMilliseconds()).padStart(3,"0");return`${r}-${e}-${n} ${o}:${E}:${i}.${_}`}log(t,r,e,n,o){if(t<this.getLevel())return;let E=this.formatTimestamp(new Date),i=M[t].padEnd(5),_=r.padEnd(6),a="";n?.correlationId?a=`[${n.correlationId}] `:n?.sessionId&&(a=`[session-${n.sessionId}] `);let l="";o!=null&&(o instanceof Error?l=this.getLevel()===0?`
|
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let r=Object.keys(t);return r.length===0?"{}":r.length<=3?JSON.stringify(t):`{${r.length} keys: ${r.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,r){if(!r)return t;let e=typeof r=="string"?JSON.parse(r):r;if(t==="Bash"&&e.command)return`${t}(${e.command})`;if(e.file_path)return`${t}(${e.file_path})`;if(e.notebook_path)return`${t}(${e.notebook_path})`;if(t==="Glob"&&e.pattern)return`${t}(${e.pattern})`;if(t==="Grep"&&e.pattern)return`${t}(${e.pattern})`;if(e.url)return`${t}(${e.url})`;if(e.query)return`${t}(${e.query})`;if(t==="Task"){if(e.subagent_type)return`${t}(${e.subagent_type})`;if(e.description)return`${t}(${e.description})`}return t==="Skill"&&e.skill?`${t}(${e.skill})`:t==="LSP"&&e.operation?`${t}(${e.operation})`:t}formatTimestamp(t){let r=t.getFullYear(),e=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),o=String(t.getHours()).padStart(2,"0"),E=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0"),_=String(t.getMilliseconds()).padStart(3,"0");return`${r}-${e}-${n} ${o}:${E}:${i}.${_}`}log(t,r,e,n,o){if(t<this.getLevel())return;let E=this.formatTimestamp(new Date),i=M[t].padEnd(5),_=r.padEnd(6),a="";n?.correlationId?a=`[${n.correlationId}] `:n?.sessionId&&(a=`[session-${n.sessionId}] `);let l="";o!=null&&(o instanceof Error?l=this.getLevel()===0?`
|
||||||
${o.message}
|
${o.message}
|
||||||
${o.stack}`:` ${o.message}`:this.getLevel()===0&&typeof o=="object"?l=`
|
${o.stack}`:` ${o.message}`:this.getLevel()===0&&typeof o=="object"?l=`
|
||||||
`+JSON.stringify(o,null,2):l=" "+this.formatData(o));let O="";if(n){let{sessionId:R,memorySessionId:Z,correlationId:tt,...U}=n;Object.keys(U).length>0&&(O=` {${Object.entries(U).map(([k,P])=>`${k}=${P}`).join(", ")}}`)}let D=`[${E}] [${i}] [${_}] ${a}${e}${O}${l}`;if(this.logFilePath)try{W(this.logFilePath,D+`
|
`+JSON.stringify(o,null,2):l=" "+this.formatData(o));let O="";if(n){let{sessionId:D,memorySessionId:Z,correlationId:tt,...R}=n;Object.keys(R).length>0&&(O=` {${Object.entries(R).map(([k,P])=>`${k}=${P}`).join(", ")}}`)}let C=`[${E}] [${i}] [${_}] ${a}${e}${O}${l}`;if(this.logFilePath)try{W(this.logFilePath,C+`
|
||||||
`,"utf8")}catch(R){process.stderr.write(`[LOGGER] Failed to write to log file: ${R}
|
`,"utf8")}catch(D){process.stderr.write(`[LOGGER] Failed to write to log file: ${D}
|
||||||
`)}else process.stderr.write(D+`
|
`)}else process.stderr.write(C+`
|
||||||
`)}debug(t,r,e,n){this.log(0,t,r,e,n)}info(t,r,e,n){this.log(1,t,r,e,n)}warn(t,r,e,n){this.log(2,t,r,e,n)}error(t,r,e,n){this.log(3,t,r,e,n)}dataIn(t,r,e,n){this.info(t,`\u2192 ${r}`,e,n)}dataOut(t,r,e,n){this.info(t,`\u2190 ${r}`,e,n)}success(t,r,e,n){this.info(t,`\u2713 ${r}`,e,n)}failure(t,r,e,n){this.error(t,`\u2717 ${r}`,e,n)}timing(t,r,e,n){this.info(t,`\u23F1 ${r}`,n,{duration:`${e}ms`})}happyPathError(t,r,e,n,o=""){let a=((new Error().stack||"").split(`
|
`)}debug(t,r,e,n){this.log(0,t,r,e,n)}info(t,r,e,n){this.log(1,t,r,e,n)}warn(t,r,e,n){this.log(2,t,r,e,n)}error(t,r,e,n){this.log(3,t,r,e,n)}dataIn(t,r,e,n){this.info(t,`\u2192 ${r}`,e,n)}dataOut(t,r,e,n){this.info(t,`\u2190 ${r}`,e,n)}success(t,r,e,n){this.info(t,`\u2713 ${r}`,e,n)}failure(t,r,e,n){this.error(t,`\u2717 ${r}`,e,n)}timing(t,r,e,n){this.info(t,`\u23F1 ${r}`,n,{duration:`${e}ms`})}happyPathError(t,r,e,n,o=""){let a=((new Error().stack||"").split(`
|
||||||
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),l=a?`${a[1].split("/").pop()}:${a[2]}`:"unknown",O={...e,location:l};return this.warn(t,`[HAPPY-PATH] ${r}`,O,n),o}},c=new p;import L from"path";import{homedir as K}from"os";import{readFileSync as X}from"fs";var A={DEFAULT:3e5,HEALTH_CHECK:3e4,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:300,PRE_RESTART_SETTLE_DELAY:2e3,WINDOWS_MULTIPLIER:1.5};function I(s){return process.platform==="win32"?Math.round(s*A.WINDOWS_MULTIPLIER):s}function N(s={}){let{port:t,includeSkillFallback:r=!1,customPrefix:e,actualError:n}=s,o=e||"Worker service connection failed.",E=t?` (port ${t})`:"",i=`${o}${E}
|
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),l=a?`${a[1].split("/").pop()}:${a[2]}`:"unknown",O={...e,location:l};return this.warn(t,`[HAPPY-PATH] ${r}`,O,n),o}},c=new p;import L from"path";import{homedir as K}from"os";import{readFileSync as X}from"fs";var A={DEFAULT:3e5,HEALTH_CHECK:3e4,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:300,PRE_RESTART_SETTLE_DELAY:2e3,WINDOWS_MULTIPLIER:1.5};function h(s){return process.platform==="win32"?Math.round(s*A.WINDOWS_MULTIPLIER):s}function I(s={}){let{port:t,includeSkillFallback:r=!1,customPrefix:e,actualError:n}=s,o=e||"Worker service connection failed.",E=t?` (port ${t})`:"",i=`${o}${E}
|
||||||
|
|
||||||
`;return i+=`To restart the worker:
|
`;return i+=`To restart the worker:
|
||||||
`,i+=`1. Exit Claude Code completely
|
`,i+=`1. Exit Claude Code completely
|
||||||
@@ -16,8 +16,8 @@ ${o.stack}`:` ${o.message}`:this.getLevel()===0&&typeof o=="object"?l=`
|
|||||||
|
|
||||||
If that doesn't work, try: /troubleshoot`),n&&(i=`Worker Error: ${n}
|
If that doesn't work, try: /troubleshoot`),n&&(i=`Worker Error: ${n}
|
||||||
|
|
||||||
${i}`),i}var V=L.join(K(),".claude","plugins","marketplaces","thedotmack"),Ct=I(A.HEALTH_CHECK),S=null;function u(){if(S!==null)return S;let s=L.join(g.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=g.loadFromFile(s);return S=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),S}async function j(){let s=u();return(await fetch(`http://127.0.0.1:${s}/api/readiness`)).ok}function B(){let s=L.join(V,"package.json");return JSON.parse(X(s,"utf-8")).version}async function Y(){let s=u(),t=await fetch(`http://127.0.0.1:${s}/api/version`);if(!t.ok)throw new Error(`Failed to get worker version: ${t.status}`);return(await t.json()).version}async function J(){let s=B(),t=await Y();s!==t&&c.debug("SYSTEM","Version check",{pluginVersion:s,workerVersion:t,note:"Mismatch will be auto-restarted by worker-service start command"})}async function y(){for(let r=0;r<75;r++){try{if(await j()){await J();return}}catch(e){c.debug("SYSTEM","Worker health check failed, will retry",{attempt:r+1,maxRetries:75,error:e instanceof Error?e.message:String(e)})}await new Promise(e=>setTimeout(e,200))}throw new Error(N({port:u(),customPrefix:"Worker did not become ready within 15 seconds."}))}import{readFileSync as q,existsSync as z}from"fs";function m(s,t,r=!1){if(!s||!z(s))throw new Error(`Transcript path missing or file does not exist: ${s}`);let e=q(s,"utf-8").trim();if(!e)throw new Error(`Transcript file exists but is empty: ${s}`);let n=e.split(`
|
${i}`),i}var V=L.join(K(),".claude","plugins","marketplaces","thedotmack"),Ct=h(A.HEALTH_CHECK),S=null;function u(){if(S!==null)return S;let s=L.join(g.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=g.loadFromFile(s);return S=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),S}async function j(){let s=u();return(await fetch(`http://127.0.0.1:${s}/api/readiness`)).ok}function B(){let s=L.join(V,"package.json");return JSON.parse(X(s,"utf-8")).version}async function Y(){let s=u(),t=await fetch(`http://127.0.0.1:${s}/api/version`);if(!t.ok)throw new Error(`Failed to get worker version: ${t.status}`);return(await t.json()).version}async function J(){let s=B(),t=await Y();s!==t&&c.debug("SYSTEM","Version check",{pluginVersion:s,workerVersion:t,note:"Mismatch will be auto-restarted by worker-service start command"})}async function N(){for(let r=0;r<75;r++){try{if(await j()){await J();return}}catch(e){c.debug("SYSTEM","Worker health check failed, will retry",{attempt:r+1,maxRetries:75,error:e instanceof Error?e.message:String(e)})}await new Promise(e=>setTimeout(e,200))}throw new Error(I({port:u(),customPrefix:"Worker did not become ready within 15 seconds."}))}import{readFileSync as q,existsSync as z}from"fs";function y(s,t,r=!1){if(!s||!z(s))throw new Error(`Transcript path missing or file does not exist: ${s}`);let e=q(s,"utf-8").trim();if(!e)throw new Error(`Transcript file exists but is empty: ${s}`);let n=e.split(`
|
||||||
`),o=!1;for(let E=n.length-1;E>=0;E--){let i=JSON.parse(n[E]);if(i.type===t&&(o=!0,i.message?.content)){let _="",a=i.message.content;if(typeof a=="string")_=a;else if(Array.isArray(a))_=a.filter(l=>l.type==="text").map(l=>l.text).join(`
|
`),o=!1;for(let E=n.length-1;E>=0;E--){let i=JSON.parse(n[E]);if(i.type===t&&(o=!0,i.message?.content)){let _="",a=i.message.content;if(typeof a=="string")_=a;else if(Array.isArray(a))_=a.filter(l=>l.type==="text").map(l=>l.text).join(`
|
||||||
`);else throw new Error(`Unknown message content format in transcript. Type: ${typeof a}`);return r&&(_=_.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g,""),_=_.replace(/\n{3,}/g,`
|
`);else throw new Error(`Unknown message content format in transcript. Type: ${typeof a}`);return r&&(_=_.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g,""),_=_.replace(/\n{3,}/g,`
|
||||||
|
|
||||||
`).trim()),_}}if(!o)throw new Error(`No message found for role '${t}' in transcript: ${s}`);return""}async function Q(s){if(await y(),!s)throw new Error("summaryHook requires input");let{session_id:t}=s,r=u();if(!s.transcript_path)throw new Error(`Missing transcript_path in Stop hook input for session ${t}`);let e=m(s.transcript_path,"user"),n=m(s.transcript_path,"assistant",!0);c.dataIn("HOOK","Stop: Requesting summary",{workerPort:r,hasLastUserMessage:!!e,hasLastAssistantMessage:!!n});let o=await fetch(`http://127.0.0.1:${r}/api/sessions/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:t,last_user_message:e,last_assistant_message:n})});if(!o.ok)throw console.log(f),new Error(`Summary generation failed: ${o.status}`);c.debug("HOOK","Summary request sent successfully"),console.log(f)}var C="";$.on("data",s=>C+=s);$.on("end",async()=>{let s;try{s=C?JSON.parse(C):void 0}catch(t){throw new Error(`Failed to parse hook input: ${t instanceof Error?t.message:String(t)}`)}await Q(s)});
|
`).trim()),_}}if(!o)throw new Error(`No message found for role '${t}' in transcript: ${s}`);return""}async function Q(s){if(await N(),!s)throw new Error("summaryHook requires input");let{session_id:t}=s,r=u();if(!s.transcript_path)throw new Error(`Missing transcript_path in Stop hook input for session ${t}`);let e=y(s.transcript_path,"assistant",!0);c.dataIn("HOOK","Stop: Requesting summary",{workerPort:r,hasLastAssistantMessage:!!e});let n=await fetch(`http://127.0.0.1:${r}/api/sessions/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:t,last_assistant_message:e})});if(!n.ok)throw console.log(f),new Error(`Summary generation failed: ${n.status}`);c.debug("HOOK","Summary request sent successfully"),console.log(f)}var m="";$.on("data",s=>m+=s);$.on("end",async()=>{let s;try{s=m?JSON.parse(m):void 0}catch(t){throw new Error(`Failed to parse hook input: ${t instanceof Error?t.message:String(t)}`)}await Q(s)});
|
||||||
|
|||||||
+162
-162
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2643,6 +2643,161 @@
|
|||||||
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
|
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Console Filter Bar */
|
||||||
|
.console-filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--color-bg-secondary);
|
||||||
|
border-bottom: 1px solid var(--color-border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip:hover {
|
||||||
|
background: var(--color-bg-card-hover);
|
||||||
|
border-color: var(--chip-color, var(--color-border-hover));
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip.active {
|
||||||
|
background: var(--chip-color, var(--color-accent-primary));
|
||||||
|
border-color: var(--chip-color, var(--color-accent-primary));
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip.active:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-action:hover {
|
||||||
|
background: var(--color-bg-card-hover);
|
||||||
|
border-color: var(--color-border-hover);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Log Line Styles */
|
||||||
|
.log-line {
|
||||||
|
display: block;
|
||||||
|
padding: 2px 0;
|
||||||
|
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line-raw {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line-empty {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
padding: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-timestamp {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-level {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-component {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-correlation {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-message {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Log Level Colors in Dark Mode */
|
||||||
|
[data-theme="dark"] .log-line-raw {
|
||||||
|
color: #8b949e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for filter bar */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.console-filters {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-section {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip {
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive Modal */
|
/* Responsive Modal */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.modal-body {
|
.modal-body {
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
# Error Handling Anti-Pattern Rules
|
||||||
|
|
||||||
|
This folder contains `detect-error-handling-antipatterns.ts` - run it before committing any error handling changes.
|
||||||
|
|
||||||
|
## The Try-Catch Problem That Cost 10 Hours
|
||||||
|
|
||||||
|
A single overly-broad try-catch block wasted 10 hours of debugging time by silently swallowing errors.
|
||||||
|
**This pattern is BANNED.**
|
||||||
|
|
||||||
|
## BEFORE You Write Any Try-Catch
|
||||||
|
|
||||||
|
**RUN THIS TEST FIRST:**
|
||||||
|
```bash
|
||||||
|
bun run scripts/anti-pattern-test/detect-error-handling-antipatterns.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**You MUST answer these 5 questions to the user BEFORE writing try-catch:**
|
||||||
|
|
||||||
|
1. **What SPECIFIC error am I catching?** (Name the error type: `FileNotFoundError`, `NetworkTimeout`, `ValidationError`)
|
||||||
|
2. **Show documentation proving this error can occur** (Link to docs or show me the source code)
|
||||||
|
3. **Why can't this error be prevented?** (If it can be prevented, prevent it instead)
|
||||||
|
4. **What will the catch block DO?** (Must include logging + either rethrow OR explicit fallback)
|
||||||
|
5. **Why shouldn't this error propagate?** (Justify swallowing it rather than letting caller handle)
|
||||||
|
|
||||||
|
**If you cannot answer ALL 5 questions with specifics, DO NOT write the try-catch.**
|
||||||
|
|
||||||
|
## FORBIDDEN PATTERNS (Zero Tolerance)
|
||||||
|
|
||||||
|
### CRITICAL - Never Allowed
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// FORBIDDEN: Empty catch
|
||||||
|
try {
|
||||||
|
doSomething();
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// FORBIDDEN: Catch without logging
|
||||||
|
try {
|
||||||
|
doSomething();
|
||||||
|
} catch (error) {
|
||||||
|
return null; // Silent failure!
|
||||||
|
}
|
||||||
|
|
||||||
|
// FORBIDDEN: Large try blocks (>10 lines)
|
||||||
|
try {
|
||||||
|
// 50 lines of code
|
||||||
|
// Multiple operations
|
||||||
|
// Different failure modes
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Something failed'); // Which thing?!
|
||||||
|
}
|
||||||
|
|
||||||
|
// FORBIDDEN: Promise empty catch
|
||||||
|
promise.catch(() => {}); // Error disappears into void
|
||||||
|
|
||||||
|
// FORBIDDEN: Try-catch to fix TypeScript errors
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
const value = response.propertyThatDoesntExist;
|
||||||
|
} catch {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ALLOWED Patterns
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// GOOD: Specific, logged, explicit handling
|
||||||
|
try {
|
||||||
|
await fetch(url);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof NetworkError) {
|
||||||
|
logger.warn('SYNC', 'Network request failed, will retry', { url }, error);
|
||||||
|
return null; // Explicit: null means "fetch failed"
|
||||||
|
}
|
||||||
|
throw error; // Unexpected errors propagate
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Minimal scope, clear recovery
|
||||||
|
try {
|
||||||
|
JSON.parse(data);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('CONFIG', 'Corrupt settings file, using defaults', {}, error);
|
||||||
|
return DEFAULT_SETTINGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Fire-and-forget with logging
|
||||||
|
backgroundTask()
|
||||||
|
.catch(error => logger.warn('BACKGROUND', 'Task failed', {}, error));
|
||||||
|
|
||||||
|
// GOOD: Ignored anti-pattern for genuine hot paths only
|
||||||
|
try {
|
||||||
|
checkIfProcessAlive(pid);
|
||||||
|
} catch (error) {
|
||||||
|
// [ANTI-PATTERN IGNORED]: Tight loop checking 100s of PIDs during cleanup
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ignoring Anti-Patterns (Rare)
|
||||||
|
|
||||||
|
**Only for genuine hot paths** where logging would cause performance problems:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// [ANTI-PATTERN IGNORED]: Reason why logging is impossible
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- **Hot paths only** - code in tight loops called 1000s of times
|
||||||
|
- If you can add logging, ADD LOGGING - don't ignore
|
||||||
|
- Valid examples:
|
||||||
|
- "Tight loop checking process exit status during cleanup"
|
||||||
|
- "Health check polling every 100ms"
|
||||||
|
- Invalid examples:
|
||||||
|
- "Expected JSON parse failures" - Just add logger.debug
|
||||||
|
- "Common fallback path" - Just add logger.debug
|
||||||
|
|
||||||
|
## The Meta-Rule
|
||||||
|
|
||||||
|
**UNCERTAINTY TRIGGERS RESEARCH, NOT TRY-CATCH**
|
||||||
|
|
||||||
|
When you're unsure if a property exists or a method signature is correct:
|
||||||
|
1. **READ** the source code or documentation
|
||||||
|
2. **VERIFY** with the Read tool
|
||||||
|
3. **USE** TypeScript types to catch errors at compile time
|
||||||
|
4. **WRITE** code you KNOW is correct
|
||||||
|
|
||||||
|
Never use try-catch to paper over uncertainty. That wastes hours of debugging time later.
|
||||||
|
|
||||||
|
## Critical Path Protection
|
||||||
|
|
||||||
|
These files are **NEVER** allowed to have catch-and-continue:
|
||||||
|
- `SDKAgent.ts` - Errors must propagate, not hide
|
||||||
|
- `GeminiAgent.ts` - Must fail loud, not silent
|
||||||
|
- `OpenRouterAgent.ts` - Must fail loud, not silent
|
||||||
|
- `SessionStore.ts` - Database errors must propagate
|
||||||
|
- `worker-service.ts` - Core service errors must be visible
|
||||||
|
|
||||||
|
On critical paths, prefer **NO TRY-CATCH** and let errors propagate naturally.
|
||||||
+120
-8
@@ -56,6 +56,117 @@ function detectAntiPatterns(filePath: string, projectRoot: string): AntiPattern[
|
|||||||
const relPath = relative(projectRoot, filePath);
|
const relPath = relative(projectRoot, filePath);
|
||||||
const isCriticalPath = CRITICAL_PATHS.some(cp => filePath.includes(cp));
|
const isCriticalPath = CRITICAL_PATHS.some(cp => filePath.includes(cp));
|
||||||
|
|
||||||
|
// Detect error message string matching for type detection (line-by-line patterns)
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
const trimmed = line.trim();
|
||||||
|
|
||||||
|
// Check for [ANTI-PATTERN IGNORED] on the same or previous line
|
||||||
|
const hasOverride = trimmed.includes('[ANTI-PATTERN IGNORED]') ||
|
||||||
|
(i > 0 && lines[i - 1].includes('[ANTI-PATTERN IGNORED]'));
|
||||||
|
const overrideMatch = (trimmed + (i > 0 ? lines[i - 1] : '')).match(/\[ANTI-PATTERN IGNORED\]:\s*(.+)/i);
|
||||||
|
const overrideReason = overrideMatch?.[1]?.trim();
|
||||||
|
|
||||||
|
// CRITICAL: Error message string matching for type detection
|
||||||
|
// Patterns like: errorMessage.includes('connection') or error.message.includes('timeout')
|
||||||
|
const errorStringMatchPatterns = [
|
||||||
|
/error(?:Message|\.message)\s*\.includes\s*\(\s*['"`](\w+)['"`]\s*\)/i,
|
||||||
|
/(?:err|e)\.message\s*\.includes\s*\(\s*['"`](\w+)['"`]\s*\)/i,
|
||||||
|
/String\s*\(\s*(?:error|err|e)\s*\)\s*\.includes\s*\(\s*['"`](\w+)['"`]\s*\)/i,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pattern of errorStringMatchPatterns) {
|
||||||
|
const match = trimmed.match(pattern);
|
||||||
|
if (match) {
|
||||||
|
const matchedString = match[1];
|
||||||
|
// Common generic patterns that are too broad
|
||||||
|
const genericPatterns = ['error', 'fail', 'connection', 'timeout', 'not', 'invalid', 'unable'];
|
||||||
|
const isGeneric = genericPatterns.some(gp => matchedString.toLowerCase().includes(gp));
|
||||||
|
|
||||||
|
if (hasOverride && overrideReason) {
|
||||||
|
antiPatterns.push({
|
||||||
|
file: relPath,
|
||||||
|
line: i + 1,
|
||||||
|
pattern: 'ERROR_STRING_MATCHING',
|
||||||
|
severity: 'APPROVED_OVERRIDE',
|
||||||
|
description: `Error type detection via string matching on "${matchedString}" - approved override.`,
|
||||||
|
code: trimmed,
|
||||||
|
overrideReason
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
antiPatterns.push({
|
||||||
|
file: relPath,
|
||||||
|
line: i + 1,
|
||||||
|
pattern: 'ERROR_STRING_MATCHING',
|
||||||
|
severity: isGeneric ? 'CRITICAL' : 'HIGH',
|
||||||
|
description: `Error type detection via string matching on "${matchedString}" - fragile and masks the real error. Log the FULL error object. We don't care about pretty error handling, we care about SEEING what went wrong.`,
|
||||||
|
code: trimmed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HIGH: Logging only error.message instead of the full error object
|
||||||
|
// Patterns like: logger.error('X', 'Y', {}, error.message) or console.error(error.message)
|
||||||
|
const partialErrorLoggingPatterns = [
|
||||||
|
/logger\.(error|warn|info|debug)\s*\([^)]*,\s*(?:error|err|e)\.message\s*\)/,
|
||||||
|
/logger\.(error|warn|info|debug)\s*\([^)]*\{\s*(?:error|err|e):\s*(?:error|err|e)\.message\s*\}/,
|
||||||
|
/console\.(error|warn|log)\s*\(\s*(?:error|err|e)\.message\s*\)/,
|
||||||
|
/console\.(error|warn|log)\s*\(\s*['"`][^'"`]+['"`]\s*,\s*(?:error|err|e)\.message\s*\)/,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pattern of partialErrorLoggingPatterns) {
|
||||||
|
if (pattern.test(trimmed)) {
|
||||||
|
if (hasOverride && overrideReason) {
|
||||||
|
antiPatterns.push({
|
||||||
|
file: relPath,
|
||||||
|
line: i + 1,
|
||||||
|
pattern: 'PARTIAL_ERROR_LOGGING',
|
||||||
|
severity: 'APPROVED_OVERRIDE',
|
||||||
|
description: 'Logging only error.message instead of full error object - approved override.',
|
||||||
|
code: trimmed,
|
||||||
|
overrideReason
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
antiPatterns.push({
|
||||||
|
file: relPath,
|
||||||
|
line: i + 1,
|
||||||
|
pattern: 'PARTIAL_ERROR_LOGGING',
|
||||||
|
severity: 'HIGH',
|
||||||
|
description: 'Logging only error.message HIDES the stack trace, error type, and all properties. ALWAYS pass the full error object - you need the complete picture, not a summary.',
|
||||||
|
code: trimmed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Catch-all error type guessing based on message content
|
||||||
|
// Pattern: if (errorMessage.includes('X') || errorMessage.includes('Y'))
|
||||||
|
const multipleIncludes = trimmed.match(/(?:error(?:Message|\.message)|(?:err|e)\.message).*\.includes.*\|\|.*\.includes/i);
|
||||||
|
if (multipleIncludes) {
|
||||||
|
if (hasOverride && overrideReason) {
|
||||||
|
antiPatterns.push({
|
||||||
|
file: relPath,
|
||||||
|
line: i + 1,
|
||||||
|
pattern: 'ERROR_MESSAGE_GUESSING',
|
||||||
|
severity: 'APPROVED_OVERRIDE',
|
||||||
|
description: 'Multiple string checks on error message to guess error type - approved override.',
|
||||||
|
code: trimmed,
|
||||||
|
overrideReason
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
antiPatterns.push({
|
||||||
|
file: relPath,
|
||||||
|
line: i + 1,
|
||||||
|
pattern: 'ERROR_MESSAGE_GUESSING',
|
||||||
|
severity: 'CRITICAL',
|
||||||
|
description: 'Multiple string checks on error message to guess error type. STOP GUESSING. Log the FULL error object. We don\'t care what the library throws - we care about SEEING the error when it happens.',
|
||||||
|
code: trimmed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Track try-catch blocks
|
// Track try-catch blocks
|
||||||
let inTry = false;
|
let inTry = false;
|
||||||
let tryStartLine = 0;
|
let tryStartLine = 0;
|
||||||
@@ -98,7 +209,7 @@ function detectAntiPatterns(filePath: string, projectRoot: string): AntiPattern[
|
|||||||
braceCount += (nextLine.match(/{/g) || []).length - (nextLine.match(/}/g) || []).length;
|
braceCount += (nextLine.match(/{/g) || []).length - (nextLine.match(/}/g) || []).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasLogging = catchBody.match(/logger\.(error|warn|debug|info)/) ||
|
const hasLogging = catchBody.match(/logger\.(error|warn|debug|info|failure)/) ||
|
||||||
catchBody.match(/console\.(error|warn)/);
|
catchBody.match(/console\.(error|warn)/);
|
||||||
|
|
||||||
if (!hasLogging && lookAhead > 0) { // Only flag if it's actually a multi-line handler
|
if (!hasLogging && lookAhead > 0) { // Only flag if it's actually a multi-line handler
|
||||||
@@ -216,12 +327,12 @@ function analyzeTryCatchBlock(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for [APPROVED OVERRIDE] marker
|
// Check for [ANTI-PATTERN IGNORED] marker
|
||||||
const overrideMatch = catchContent.match(/\/\/\s*\[APPROVED OVERRIDE\]:\s*(.+)/i);
|
const overrideMatch = catchContent.match(/\/\/\s*\[ANTI-PATTERN IGNORED\]:\s*(.+)/i);
|
||||||
const overrideReason = overrideMatch?.[1]?.trim();
|
const overrideReason = overrideMatch?.[1]?.trim();
|
||||||
|
|
||||||
// CRITICAL: No logging in catch block (unless explicitly approved)
|
// CRITICAL: No logging in catch block (unless explicitly approved)
|
||||||
const hasLogging = catchContent.match(/logger\.(error|warn|debug|info)/);
|
const hasLogging = catchContent.match(/logger\.(error|warn|debug|info|failure)/);
|
||||||
const hasConsoleError = catchContent.match(/console\.(error|warn)/);
|
const hasConsoleError = catchContent.match(/console\.(error|warn)/);
|
||||||
const hasStderr = catchContent.match(/process\.stderr\.write/);
|
const hasStderr = catchContent.match(/process\.stderr\.write/);
|
||||||
const hasThrow = catchContent.match(/throw/);
|
const hasThrow = catchContent.match(/throw/);
|
||||||
@@ -286,16 +397,17 @@ function analyzeTryCatchBlock(
|
|||||||
// CRITICAL on critical paths: Catch-and-continue
|
// CRITICAL on critical paths: Catch-and-continue
|
||||||
if (isCriticalPath && nonCommentContent && !hasThrow) {
|
if (isCriticalPath && nonCommentContent && !hasThrow) {
|
||||||
const hasReturn = catchContent.match(/return/);
|
const hasReturn = catchContent.match(/return/);
|
||||||
const continuesExecution = !hasReturn; // If no return/throw, execution continues
|
const hasProcessExit = catchContent.match(/process\.exit/);
|
||||||
|
const terminatesExecution = hasReturn || hasProcessExit;
|
||||||
|
|
||||||
if (continuesExecution && hasLogging) {
|
if (!terminatesExecution && hasLogging) {
|
||||||
if (overrideReason) {
|
if (overrideReason) {
|
||||||
antiPatterns.push({
|
antiPatterns.push({
|
||||||
file: relPath,
|
file: relPath,
|
||||||
line: catchStartLine,
|
line: catchStartLine,
|
||||||
pattern: 'CATCH_AND_CONTINUE_CRITICAL_PATH',
|
pattern: 'CATCH_AND_CONTINUE_CRITICAL_PATH',
|
||||||
severity: 'APPROVED_OVERRIDE',
|
severity: 'APPROVED_OVERRIDE',
|
||||||
description: 'Critical path continues after error - approved override.',
|
description: 'Critical path continues after error - anti-pattern ignored.',
|
||||||
code: catchBlock.trim(),
|
code: catchBlock.trim(),
|
||||||
overrideReason
|
overrideReason
|
||||||
});
|
});
|
||||||
@@ -400,7 +512,7 @@ function formatReport(antiPatterns: AntiPattern[]): string {
|
|||||||
report += '4. What will the catch block DO? (Log + rethrow? Fallback?)\n';
|
report += '4. What will the catch block DO? (Log + rethrow? Fallback?)\n';
|
||||||
report += '5. Why shouldn\'t this error propagate to the caller?\n';
|
report += '5. Why shouldn\'t this error propagate to the caller?\n';
|
||||||
report += '\n';
|
report += '\n';
|
||||||
report += 'To approve an anti-pattern, add: // [APPROVED OVERRIDE]: reason\n';
|
report += 'To ignore an anti-pattern, add: // [ANTI-PATTERN IGNORED]: reason\n';
|
||||||
report += '═══════════════════════════════════════════════════════════════\n\n';
|
report += '═══════════════════════════════════════════════════════════════\n\n';
|
||||||
|
|
||||||
return report;
|
return report;
|
||||||
@@ -42,13 +42,13 @@ async function summaryHook(input?: StopInput): Promise<void> {
|
|||||||
throw new Error(`Missing transcript_path in Stop hook input for session ${session_id}`);
|
throw new Error(`Missing transcript_path in Stop hook input for session ${session_id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract last user AND assistant messages from transcript
|
// Extract last assistant message from transcript (the work Claude did)
|
||||||
const lastUserMessage = extractLastMessage(input.transcript_path, 'user');
|
// Note: "user" messages in transcripts are mostly tool_results, not actual user input.
|
||||||
|
// The user's original request is already stored in user_prompts table.
|
||||||
const lastAssistantMessage = extractLastMessage(input.transcript_path, 'assistant', true);
|
const lastAssistantMessage = extractLastMessage(input.transcript_path, 'assistant', true);
|
||||||
|
|
||||||
logger.dataIn('HOOK', 'Stop: Requesting summary', {
|
logger.dataIn('HOOK', 'Stop: Requesting summary', {
|
||||||
workerPort: port,
|
workerPort: port,
|
||||||
hasLastUserMessage: !!lastUserMessage,
|
|
||||||
hasLastAssistantMessage: !!lastAssistantMessage
|
hasLastAssistantMessage: !!lastAssistantMessage
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,7 +58,6 @@ async function summaryHook(input?: StopInput): Promise<void> {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
contentSessionId: session_id,
|
contentSessionId: session_id,
|
||||||
last_user_message: lastUserMessage,
|
|
||||||
last_assistant_message: lastAssistantMessage
|
last_assistant_message: lastAssistantMessage
|
||||||
})
|
})
|
||||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||||
|
|||||||
+8
-7
@@ -20,7 +20,6 @@ export interface SDKSession {
|
|||||||
memory_session_id: string | null;
|
memory_session_id: string | null;
|
||||||
project: string;
|
project: string;
|
||||||
user_prompt: string;
|
user_prompt: string;
|
||||||
last_user_message?: string;
|
|
||||||
last_assistant_message?: string;
|
last_assistant_message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,17 +96,19 @@ export function buildObservationPrompt(obs: Observation): string {
|
|||||||
try {
|
try {
|
||||||
toolInput = typeof obs.tool_input === 'string' ? JSON.parse(obs.tool_input) : obs.tool_input;
|
toolInput = typeof obs.tool_input === 'string' ? JSON.parse(obs.tool_input) : obs.tool_input;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Expected: tool_input may not be valid JSON (e.g., plain strings)
|
logger.debug('SDK', 'Tool input is plain string, using as-is', {
|
||||||
// Not logging - this is a normal fallback for non-JSON tool inputs
|
toolName: obs.tool_name
|
||||||
toolInput = obs.tool_input; // If parse fails, use raw value
|
}, error as Error);
|
||||||
|
toolInput = obs.tool_input;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
toolOutput = typeof obs.tool_output === 'string' ? JSON.parse(obs.tool_output) : obs.tool_output;
|
toolOutput = typeof obs.tool_output === 'string' ? JSON.parse(obs.tool_output) : obs.tool_output;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Expected: tool_output may not be valid JSON (e.g., plain strings)
|
logger.debug('SDK', 'Tool output is plain string, using as-is', {
|
||||||
// Not logging - this is a normal fallback for non-JSON tool outputs
|
toolName: obs.tool_name
|
||||||
toolOutput = obs.tool_output; // If parse fails, use raw value
|
}, error as Error);
|
||||||
|
toolOutput = obs.tool_output;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<observed_from_primary_session>
|
return `<observed_from_primary_session>
|
||||||
|
|||||||
@@ -200,8 +200,7 @@ function extractPriorMessages(transcriptPath: string): { userMessage: string; as
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
// Expected: malformed JSON lines in transcript
|
logger.debug('PARSER', 'Skipping malformed transcript line', { lineIndex: i }, parseError as Error);
|
||||||
// Not logging - this loops through many lines, logging each would be excessive
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,8 +228,7 @@ export async function generateContext(input?: ContextInput, useColors: boolean =
|
|||||||
try {
|
try {
|
||||||
unlinkSync(VERSION_MARKER_PATH);
|
unlinkSync(VERSION_MARKER_PATH);
|
||||||
} catch (unlinkError) {
|
} catch (unlinkError) {
|
||||||
// Marker might not exist - expected during first run
|
logger.debug('SYSTEM', 'Marker file cleanup failed (may not exist)', {}, unlinkError as Error);
|
||||||
// Not logging - this is a normal case during initial setup
|
|
||||||
}
|
}
|
||||||
logger.error('SYSTEM', 'Native module rebuild needed - restart Claude Code to auto-fix');
|
logger.error('SYSTEM', 'Native module rebuild needed - restart Claude Code to auto-fix');
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export interface PersistentPendingMessage {
|
|||||||
tool_input: string | null;
|
tool_input: string | null;
|
||||||
tool_response: string | null;
|
tool_response: string | null;
|
||||||
cwd: string | null;
|
cwd: string | null;
|
||||||
last_user_message: string | null;
|
|
||||||
last_assistant_message: string | null;
|
last_assistant_message: string | null;
|
||||||
prompt_number: number | null;
|
prompt_number: number | null;
|
||||||
status: 'pending' | 'processing' | 'processed' | 'failed';
|
status: 'pending' | 'processing' | 'processed' | 'failed';
|
||||||
@@ -59,9 +58,9 @@ export class PendingMessageStore {
|
|||||||
INSERT INTO pending_messages (
|
INSERT INTO pending_messages (
|
||||||
session_db_id, content_session_id, message_type,
|
session_db_id, content_session_id, message_type,
|
||||||
tool_name, tool_input, tool_response, cwd,
|
tool_name, tool_input, tool_response, cwd,
|
||||||
last_user_message, last_assistant_message,
|
last_assistant_message,
|
||||||
prompt_number, status, retry_count, created_at_epoch
|
prompt_number, status, retry_count, created_at_epoch
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const result = stmt.run(
|
const result = stmt.run(
|
||||||
@@ -72,7 +71,6 @@ export class PendingMessageStore {
|
|||||||
message.tool_input ? JSON.stringify(message.tool_input) : null,
|
message.tool_input ? JSON.stringify(message.tool_input) : null,
|
||||||
message.tool_response ? JSON.stringify(message.tool_response) : null,
|
message.tool_response ? JSON.stringify(message.tool_response) : null,
|
||||||
message.cwd || null,
|
message.cwd || null,
|
||||||
message.last_user_message || null,
|
|
||||||
message.last_assistant_message || null,
|
message.last_assistant_message || null,
|
||||||
message.prompt_number || null,
|
message.prompt_number || null,
|
||||||
now
|
now
|
||||||
@@ -422,7 +420,6 @@ export class PendingMessageStore {
|
|||||||
tool_response: persistent.tool_response ? JSON.parse(persistent.tool_response) : undefined,
|
tool_response: persistent.tool_response ? JSON.parse(persistent.tool_response) : undefined,
|
||||||
prompt_number: persistent.prompt_number || undefined,
|
prompt_number: persistent.prompt_number || undefined,
|
||||||
cwd: persistent.cwd || undefined,
|
cwd: persistent.cwd || undefined,
|
||||||
last_user_message: persistent.last_user_message || undefined,
|
|
||||||
last_assistant_message: persistent.last_assistant_message || undefined
|
last_assistant_message: persistent.last_assistant_message || undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,10 @@ export class ChromaSync {
|
|||||||
|
|
||||||
logger.debug('CHROMA_SYNC', 'Collection exists', { collection: this.collectionName });
|
logger.debug('CHROMA_SYNC', 'Collection exists', { collection: this.collectionName });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Collection doesn't exist, create it
|
// Log the FULL error - don't try to guess what type it is
|
||||||
|
logger.warn('CHROMA_SYNC', 'Collection check failed, attempting to create', { collection: this.collectionName }, error as Error);
|
||||||
|
|
||||||
|
// Try to create collection - if this also fails, we'll see that error too
|
||||||
logger.info('CHROMA_SYNC', 'Creating collection', { collection: this.collectionName });
|
logger.info('CHROMA_SYNC', 'Creating collection', { collection: this.collectionName });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ function removePidFile(): void {
|
|||||||
try {
|
try {
|
||||||
if (existsSync(PID_FILE)) unlinkSync(PID_FILE);
|
if (existsSync(PID_FILE)) unlinkSync(PID_FILE);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// PID file removal is cleanup - log but don't fail shutdown
|
logger.warn('SYSTEM', 'Failed to remove PID file', { path: PID_FILE }, error as Error);
|
||||||
logger.warn('SYSTEM', 'Failed to remove PID file', { path: PID_FILE, error: (error as Error).message });
|
return; // Non-critical cleanup, OK to fail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +129,8 @@ export async function updateCursorContextForProject(projectName: string, port: n
|
|||||||
writeContextFile(entry.workspacePath, context);
|
writeContextFile(entry.workspacePath, context);
|
||||||
logger.debug('CURSOR', 'Updated context file', { projectName, workspacePath: entry.workspacePath });
|
logger.debug('CURSOR', 'Updated context file', { projectName, workspacePath: entry.workspacePath });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Context update is non-critical - log and continue
|
logger.warn('CURSOR', 'Failed to update context file', { projectName }, error as Error);
|
||||||
logger.warn('CURSOR', 'Failed to update context file', { projectName, error: (error as Error).message });
|
return; // Non-critical context update, OK to fail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +150,7 @@ async function isPortInUse(port: number): Promise<boolean> {
|
|||||||
const response = await fetch(`http://127.0.0.1:${port}/api/health`);
|
const response = await fetch(`http://127.0.0.1:${port}/api/health`);
|
||||||
return response.ok;
|
return response.ok;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Expected: port is free or service not responding
|
// [ANTI-PATTERN IGNORED]: Health check polls every 500ms, logging would flood
|
||||||
// Not logging - this is called frequently for health checks
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,10 +163,8 @@ async function waitForHealth(port: number, timeoutMs: number = 30000): Promise<b
|
|||||||
const response = await fetch(`http://127.0.0.1:${port}/api/readiness`);
|
const response = await fetch(`http://127.0.0.1:${port}/api/readiness`);
|
||||||
if (response.ok) return true;
|
if (response.ok) return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug('SYSTEM', 'Service not ready yet, will retry', {
|
// [ANTI-PATTERN IGNORED]: Retry loop - expected failures during startup, will retry
|
||||||
port,
|
logger.debug('SYSTEM', 'Service not ready yet, will retry', { port }, error as Error);
|
||||||
error: error instanceof Error ? error.message : String(error)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await new Promise(r => setTimeout(r, 500));
|
await new Promise(r => setTimeout(r, 500));
|
||||||
}
|
}
|
||||||
@@ -370,7 +367,7 @@ export class WorkerService {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('SYSTEM', 'Error during shutdown', {}, error as Error);
|
logger.error('SYSTEM', 'Error during shutdown', {}, error as Error);
|
||||||
process.exit(1);
|
process.exit(1); // Exit with error code - this terminates execution
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -459,6 +456,7 @@ export class WorkerService {
|
|||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: API must respond even on error, log full error and return error response
|
||||||
logger.error('WORKER', 'Failed to load instructions', { topic, operation }, error as Error);
|
logger.error('WORKER', 'Failed to load instructions', { topic, operation }, error as Error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
content: [{
|
content: [{
|
||||||
@@ -543,6 +541,7 @@ export class WorkerService {
|
|||||||
// This avoids code duplication and "headers already sent" errors
|
// This avoids code duplication and "headers already sent" errors
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// [POSSIBLY RELEVANT]: API must respond even on error, log full error and return error response
|
||||||
logger.error('WORKER', 'Context inject handler failed', {}, error as Error);
|
logger.error('WORKER', 'Context inject handler failed', {}, error as Error);
|
||||||
if (!res.headersSent) {
|
if (!res.headersSent) {
|
||||||
res.status(500).json({ error: error instanceof Error ? error.message : 'Internal server error' });
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Internal server error' });
|
||||||
@@ -621,19 +620,17 @@ export class WorkerService {
|
|||||||
try {
|
try {
|
||||||
execSync(`taskkill /PID ${pid} /T /F`, { timeout: 60000, stdio: 'ignore' });
|
execSync(`taskkill /PID ${pid} /T /F`, { timeout: 60000, stdio: 'ignore' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug('SYSTEM', 'Failed to kill process, may have already exited', {
|
// [ANTI-PATTERN IGNORED]: Cleanup loop - process may have exited, continue to next PID
|
||||||
pid,
|
logger.debug('SYSTEM', 'Failed to kill process, may have already exited', { pid }, error as Error);
|
||||||
error: error instanceof Error ? error.message : String(error)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const pid of pids) {
|
for (const pid of pids) {
|
||||||
try {
|
try {
|
||||||
process.kill(pid, 'SIGKILL');
|
process.kill(pid, 'SIGKILL');
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Process already exited - expected during cleanup
|
// [ANTI-PATTERN IGNORED]: Cleanup loop - process may have exited, continue to next PID
|
||||||
logger.debug('SYSTEM', 'Process already exited', { pid });
|
logger.debug('SYSTEM', 'Process already exited', { pid }, error as Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -838,7 +835,7 @@ export class WorkerService {
|
|||||||
// Small delay between sessions to avoid rate limiting
|
// Small delay between sessions to avoid rate limiting
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Recovery is best-effort - skip failed sessions and continue with others
|
// [ANTI-PATTERN IGNORED]: Recovery loop - skip failed session, continue to next
|
||||||
logger.warn('SYSTEM', `Failed to process session ${sessionDbId}`, {}, error as Error);
|
logger.warn('SYSTEM', `Failed to process session ${sessionDbId}`, {}, error as Error);
|
||||||
result.sessionsSkipped++;
|
result.sessionsSkipped++;
|
||||||
}
|
}
|
||||||
@@ -986,9 +983,9 @@ export class WorkerService {
|
|||||||
process.kill(pid, 'SIGKILL');
|
process.kill(pid, 'SIGKILL');
|
||||||
}
|
}
|
||||||
logger.info('SYSTEM', 'Killed process', { pid });
|
logger.info('SYSTEM', 'Killed process', { pid });
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Process may have already exited - continue shutdown
|
// [ANTI-PATTERN IGNORED]: Shutdown cleanup - process already exited, continue
|
||||||
logger.debug('SYSTEM', 'Process already exited during force kill', { pid });
|
logger.debug('SYSTEM', 'Process already exited during force kill', { pid }, error as Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1004,8 +1001,7 @@ export class WorkerService {
|
|||||||
process.kill(pid, 0);
|
process.kill(pid, 0);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Expected: process has exited
|
// [ANTI-PATTERN IGNORED]: Tight loop checking 100s of PIDs every 100ms during cleanup
|
||||||
// Not logging - this is called in a tight loop during cleanup
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1094,8 +1090,9 @@ async function runInteractiveSetup(): Promise<number> {
|
|||||||
if (existsSync(settingsPath)) {
|
if (existsSync(settingsPath)) {
|
||||||
try {
|
try {
|
||||||
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Start fresh if corrupt
|
// [ANTI-PATTERN IGNORED]: Fallback behavior - corrupt settings, continue with defaults
|
||||||
|
logger.debug('SETUP', 'Corrupt settings file, starting fresh', { path: settingsPath }, error as Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1300,8 +1297,9 @@ async function detectClaudeCode(): Promise<boolean> {
|
|||||||
if (stdout.trim()) {
|
if (stdout.trim()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
// CLI not found
|
// [ANTI-PATTERN IGNORED]: Fallback behavior - CLI not found, continue to directory check
|
||||||
|
logger.debug('SYSTEM', 'Claude CLI not in PATH', {}, error as Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Claude Code plugin directory
|
// Check for Claude Code plugin directory
|
||||||
@@ -1413,8 +1411,8 @@ function configureCursorMcp(target: string): number {
|
|||||||
config.mcpServers = {};
|
config.mcpServers = {};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Start fresh if corrupt
|
// [ANTI-PATTERN IGNORED]: Fallback behavior - corrupt config, continue with empty
|
||||||
logger.warn('SYSTEM', 'Corrupt mcp.json, creating new config', { path: mcpJsonPath, error: error instanceof Error ? error.message : String(error) });
|
logger.warn('SYSTEM', 'Corrupt mcp.json, creating new config', { path: mcpJsonPath }, error as Error);
|
||||||
config = { mcpServers: {} };
|
config = { mcpServers: {} };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1669,8 +1667,9 @@ ${context}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Worker not running - that's ok, context will be generated after first session
|
// [ANTI-PATTERN IGNORED]: Fallback behavior - worker not running, use placeholder
|
||||||
|
logger.debug('CURSOR', 'Worker not running during install', {}, error as Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!contextGenerated) {
|
if (!contextGenerated) {
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export interface PendingMessage {
|
|||||||
tool_response?: any;
|
tool_response?: any;
|
||||||
prompt_number?: number;
|
prompt_number?: number;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
last_user_message?: string;
|
|
||||||
last_assistant_message?: string;
|
last_assistant_message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -240,8 +240,9 @@ export async function switchBranch(targetBranch: string): Promise<SwitchResult>
|
|||||||
if (info.branch && isValidBranchName(info.branch)) {
|
if (info.branch && isValidBranchName(info.branch)) {
|
||||||
execGit(['checkout', info.branch]);
|
execGit(['checkout', info.branch]);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (recoveryError) {
|
||||||
// Recovery failed, user needs manual intervention
|
// [POSSIBLY RELEVANT]: Recovery checkout failed, user needs manual intervention - already logging main error above
|
||||||
|
logger.warn('BRANCH', 'Recovery checkout also failed', { originalBranch: info.branch }, recoveryError as Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -229,7 +229,6 @@ export class GeminiAgent {
|
|||||||
memory_session_id: session.memorySessionId,
|
memory_session_id: session.memorySessionId,
|
||||||
project: session.project,
|
project: session.project,
|
||||||
user_prompt: session.userPrompt,
|
user_prompt: session.userPrompt,
|
||||||
last_user_message: message.last_user_message || '',
|
|
||||||
last_assistant_message: message.last_assistant_message || ''
|
last_assistant_message: message.last_assistant_message || ''
|
||||||
}, mode);
|
}, mode);
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ export class OpenRouterAgent {
|
|||||||
memory_session_id: session.memorySessionId,
|
memory_session_id: session.memorySessionId,
|
||||||
project: session.project,
|
project: session.project,
|
||||||
user_prompt: session.userPrompt,
|
user_prompt: session.userPrompt,
|
||||||
last_user_message: message.last_user_message || '',
|
|
||||||
last_assistant_message: message.last_assistant_message || ''
|
last_assistant_message: message.last_assistant_message || ''
|
||||||
}, mode);
|
}, mode);
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ export class PaginationHelper {
|
|||||||
// Return as JSON string
|
// Return as JSON string
|
||||||
return JSON.stringify(strippedPaths);
|
return JSON.stringify(strippedPaths);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Expected: file paths may not be valid JSON (plain string)
|
logger.debug('WORKER', 'File paths is plain string, using as-is', {}, err as Error);
|
||||||
// Not logging - normal fallback for non-JSON file path strings
|
|
||||||
return filePathsStr;
|
return filePathsStr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,7 +289,6 @@ export class SDKAgent {
|
|||||||
memory_session_id: session.memorySessionId,
|
memory_session_id: session.memorySessionId,
|
||||||
project: session.project,
|
project: session.project,
|
||||||
user_prompt: session.userPrompt,
|
user_prompt: session.userPrompt,
|
||||||
last_user_message: message.last_user_message || '',
|
|
||||||
last_assistant_message: message.last_assistant_message || ''
|
last_assistant_message: message.last_assistant_message || ''
|
||||||
}, mode);
|
}, mode);
|
||||||
|
|
||||||
@@ -544,7 +543,8 @@ export class SDKAgent {
|
|||||||
|
|
||||||
if (claudePath) return claudePath;
|
if (claudePath) return claudePath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug('SDK', 'Claude executable auto-detection failed', error);
|
// [ANTI-PATTERN IGNORED]: Fallback behavior - which/where failed, continue to throw clear error
|
||||||
|
logger.debug('SDK', 'Claude executable auto-detection failed', {}, error as Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Claude executable not found. Please either:\n1. Add "claude" to your system PATH, or\n2. Set CLAUDE_CODE_PATH in ~/.claude-mem/settings.json');
|
throw new Error('Claude executable not found. Please either:\n1. Add "claude" to your system PATH, or\n2. Set CLAUDE_CODE_PATH in ~/.claude-mem/settings.json');
|
||||||
|
|||||||
@@ -1401,8 +1401,7 @@ export class SearchManager {
|
|||||||
lines.push(`**Files Read:** ${filesRead.join(', ')}`);
|
lines.push(`**Files Read:** ${filesRead.join(', ')}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Expected: files_read may not be valid JSON (plain string)
|
logger.debug('WORKER', 'files_read is plain string, using as-is', {}, error as Error);
|
||||||
// Not logging - normal fallback for plain text file lists
|
|
||||||
if (summary.files_read.trim()) {
|
if (summary.files_read.trim()) {
|
||||||
lines.push(`**Files Read:** ${summary.files_read}`);
|
lines.push(`**Files Read:** ${summary.files_read}`);
|
||||||
}
|
}
|
||||||
@@ -1417,8 +1416,7 @@ export class SearchManager {
|
|||||||
lines.push(`**Files Edited:** ${filesEdited.join(', ')}`);
|
lines.push(`**Files Edited:** ${filesEdited.join(', ')}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Expected: files_edited may not be valid JSON (plain string)
|
logger.debug('WORKER', 'files_edited is plain string, using as-is', {}, error as Error);
|
||||||
// Not logging - normal fallback for plain text file lists
|
|
||||||
if (summary.files_edited.trim()) {
|
if (summary.files_edited.trim()) {
|
||||||
lines.push(`**Files Edited:** ${summary.files_edited}`);
|
lines.push(`**Files Edited:** ${summary.files_edited}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ export class SessionManager {
|
|||||||
* CRITICAL: Persists to database FIRST before adding to in-memory queue.
|
* CRITICAL: Persists to database FIRST before adding to in-memory queue.
|
||||||
* This ensures summarize requests survive worker crashes.
|
* This ensures summarize requests survive worker crashes.
|
||||||
*/
|
*/
|
||||||
queueSummarize(sessionDbId: number, lastUserMessage: string, lastAssistantMessage?: string): void {
|
queueSummarize(sessionDbId: number, lastAssistantMessage?: string): void {
|
||||||
// Auto-initialize from database if needed (handles worker restarts)
|
// Auto-initialize from database if needed (handles worker restarts)
|
||||||
let session = this.sessions.get(sessionDbId);
|
let session = this.sessions.get(sessionDbId);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
@@ -244,7 +244,6 @@ export class SessionManager {
|
|||||||
// CRITICAL: Persist to database FIRST
|
// CRITICAL: Persist to database FIRST
|
||||||
const message: PendingMessage = {
|
const message: PendingMessage = {
|
||||||
type: 'summarize',
|
type: 'summarize',
|
||||||
last_user_message: lastUserMessage,
|
|
||||||
last_assistant_message: lastAssistantMessage
|
last_assistant_message: lastAssistantMessage
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -338,9 +338,9 @@ export class SessionRoutes extends BaseRouteHandler {
|
|||||||
const sessionDbId = this.parseIntParam(req, res, 'sessionDbId');
|
const sessionDbId = this.parseIntParam(req, res, 'sessionDbId');
|
||||||
if (sessionDbId === null) return;
|
if (sessionDbId === null) return;
|
||||||
|
|
||||||
const { last_user_message, last_assistant_message } = req.body;
|
const { last_assistant_message } = req.body;
|
||||||
|
|
||||||
this.sessionManager.queueSummarize(sessionDbId, last_user_message, last_assistant_message);
|
this.sessionManager.queueSummarize(sessionDbId, last_assistant_message);
|
||||||
|
|
||||||
// CRITICAL: Ensure SDK agent is running to consume the queue
|
// CRITICAL: Ensure SDK agent is running to consume the queue
|
||||||
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
||||||
@@ -492,12 +492,12 @@ export class SessionRoutes extends BaseRouteHandler {
|
|||||||
/**
|
/**
|
||||||
* Queue summarize by contentSessionId (summary-hook uses this)
|
* Queue summarize by contentSessionId (summary-hook uses this)
|
||||||
* POST /api/sessions/summarize
|
* POST /api/sessions/summarize
|
||||||
* Body: { contentSessionId, last_user_message, last_assistant_message }
|
* Body: { contentSessionId, last_assistant_message }
|
||||||
*
|
*
|
||||||
* Checks privacy, queues summarize request for SDK agent
|
* Checks privacy, queues summarize request for SDK agent
|
||||||
*/
|
*/
|
||||||
private handleSummarizeByClaudeId = this.wrapHandler((req: Request, res: Response): void => {
|
private handleSummarizeByClaudeId = this.wrapHandler((req: Request, res: Response): void => {
|
||||||
const { contentSessionId, last_user_message, last_assistant_message } = req.body;
|
const { contentSessionId, last_assistant_message } = req.body;
|
||||||
|
|
||||||
if (!contentSessionId) {
|
if (!contentSessionId) {
|
||||||
return this.badRequest(res, 'Missing contentSessionId');
|
return this.badRequest(res, 'Missing contentSessionId');
|
||||||
@@ -523,17 +523,7 @@ export class SessionRoutes extends BaseRouteHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Queue summarize
|
// Queue summarize
|
||||||
this.sessionManager.queueSummarize(
|
this.sessionManager.queueSummarize(sessionDbId, last_assistant_message);
|
||||||
sessionDbId,
|
|
||||||
last_user_message || logger.happyPathError(
|
|
||||||
'SESSION',
|
|
||||||
'Missing last_user_message when queueing summary in SessionRoutes',
|
|
||||||
{ sessionId: sessionDbId },
|
|
||||||
undefined,
|
|
||||||
''
|
|
||||||
),
|
|
||||||
last_assistant_message
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure SDK agent is running
|
// Ensure SDK agent is running
|
||||||
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
||||||
|
|||||||
+4
-2
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync } from 'fs';
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { SettingsDefaultsManager } from './SettingsDefaultsManager.js';
|
import { SettingsDefaultsManager } from './SettingsDefaultsManager.js';
|
||||||
|
import { logger } from '../utils/logger.js';
|
||||||
|
|
||||||
// Get __dirname that works in both ESM (hooks) and CJS (worker) contexts
|
// Get __dirname that works in both ESM (hooks) and CJS (worker) contexts
|
||||||
function getDirname(): string {
|
function getDirname(): string {
|
||||||
@@ -103,8 +104,9 @@ export function getCurrentProjectName(): string {
|
|||||||
}).trim();
|
}).trim();
|
||||||
return basename(gitRoot);
|
return basename(gitRoot);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Expected: not a git repo or git not available
|
logger.debug('SYSTEM', 'Git root detection failed, using cwd basename', {
|
||||||
// Not logging - this is a common fallback path
|
cwd: process.cwd()
|
||||||
|
}, error as Error);
|
||||||
return basename(process.cwd());
|
return basename(process.cwd());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { logger } from '../utils/logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse JSON array string, returning empty array on failure
|
* Parse JSON array string, returning empty array on failure
|
||||||
@@ -16,7 +17,9 @@ export function parseJsonArray(json: string | null): string[] {
|
|||||||
const parsed = JSON.parse(json);
|
const parsed = JSON.parse(json);
|
||||||
return Array.isArray(parsed) ? parsed : [];
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// [APPROVED OVERRIDE]: Expected JSON parse failures for malformed data fields, too frequent to log
|
logger.debug('PARSER', 'Failed to parse JSON array, using empty fallback', {
|
||||||
|
preview: json?.substring(0, 50)
|
||||||
|
}, err as Error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2643,6 +2643,161 @@
|
|||||||
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
|
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Console Filter Bar */
|
||||||
|
.console-filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--color-bg-secondary);
|
||||||
|
border-bottom: 1px solid var(--color-border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip:hover {
|
||||||
|
background: var(--color-bg-card-hover);
|
||||||
|
border-color: var(--chip-color, var(--color-border-hover));
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip.active {
|
||||||
|
background: var(--chip-color, var(--color-accent-primary));
|
||||||
|
border-color: var(--chip-color, var(--color-accent-primary));
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip.active:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-action:hover {
|
||||||
|
background: var(--color-bg-card-hover);
|
||||||
|
border-color: var(--color-border-hover);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Log Line Styles */
|
||||||
|
.log-line {
|
||||||
|
display: block;
|
||||||
|
padding: 2px 0;
|
||||||
|
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line-raw {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line-empty {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
padding: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-timestamp {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-level {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-component {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-correlation {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-message {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Log Level Colors in Dark Mode */
|
||||||
|
[data-theme="dark"] .log-line-raw {
|
||||||
|
color: #8b949e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for filter bar */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.console-filters {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-section {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-filter-chip {
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive Modal */
|
/* Responsive Modal */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.modal-body {
|
.modal-body {
|
||||||
|
|||||||
@@ -1,4 +1,72 @@
|
|||||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||||
|
|
||||||
|
// Log levels and components matching the logger.ts definitions
|
||||||
|
type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
|
||||||
|
type LogComponent = 'HOOK' | 'WORKER' | 'SDK' | 'PARSER' | 'DB' | 'SYSTEM' | 'HTTP' | 'SESSION' | 'CHROMA';
|
||||||
|
|
||||||
|
interface ParsedLogLine {
|
||||||
|
raw: string;
|
||||||
|
timestamp?: string;
|
||||||
|
level?: LogLevel;
|
||||||
|
component?: LogComponent;
|
||||||
|
correlationId?: string;
|
||||||
|
message?: string;
|
||||||
|
isSpecial?: 'dataIn' | 'dataOut' | 'success' | 'failure' | 'timing' | 'happyPath';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for log levels
|
||||||
|
const LOG_LEVELS: { key: LogLevel; label: string; icon: string; color: string }[] = [
|
||||||
|
{ key: 'DEBUG', label: 'Debug', icon: '🔍', color: '#8b8b8b' },
|
||||||
|
{ key: 'INFO', label: 'Info', icon: 'ℹ️', color: '#58a6ff' },
|
||||||
|
{ key: 'WARN', label: 'Warn', icon: '⚠️', color: '#d29922' },
|
||||||
|
{ key: 'ERROR', label: 'Error', icon: '❌', color: '#f85149' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Configuration for log components
|
||||||
|
const LOG_COMPONENTS: { key: LogComponent; label: string; icon: string; color: string }[] = [
|
||||||
|
{ key: 'HOOK', label: 'Hook', icon: '🪝', color: '#a371f7' },
|
||||||
|
{ key: 'WORKER', label: 'Worker', icon: '⚙️', color: '#58a6ff' },
|
||||||
|
{ key: 'SDK', label: 'SDK', icon: '📦', color: '#3fb950' },
|
||||||
|
{ key: 'PARSER', label: 'Parser', icon: '📄', color: '#79c0ff' },
|
||||||
|
{ key: 'DB', label: 'DB', icon: '🗄️', color: '#f0883e' },
|
||||||
|
{ key: 'SYSTEM', label: 'System', icon: '💻', color: '#8b949e' },
|
||||||
|
{ key: 'HTTP', label: 'HTTP', icon: '🌐', color: '#39d353' },
|
||||||
|
{ key: 'SESSION', label: 'Session', icon: '📋', color: '#db61a2' },
|
||||||
|
{ key: 'CHROMA', label: 'Chroma', icon: '🔮', color: '#a855f7' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Parse a single log line into structured data
|
||||||
|
function parseLogLine(line: string): ParsedLogLine {
|
||||||
|
// Pattern: [timestamp] [LEVEL] [COMPONENT] [correlation?] message
|
||||||
|
// Example: [2025-01-02 14:30:45.123] [INFO ] [WORKER] [session-123] → message
|
||||||
|
const pattern = /^\[([^\]]+)\]\s+\[(\w+)\s*\]\s+\[(\w+)\s*\]\s+(?:\[([^\]]+)\]\s+)?(.*)$/;
|
||||||
|
const match = line.match(pattern);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return { raw: line };
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, timestamp, level, component, correlationId, message] = match;
|
||||||
|
|
||||||
|
// Detect special message types
|
||||||
|
let isSpecial: ParsedLogLine['isSpecial'] = undefined;
|
||||||
|
if (message.startsWith('→')) isSpecial = 'dataIn';
|
||||||
|
else if (message.startsWith('←')) isSpecial = 'dataOut';
|
||||||
|
else if (message.startsWith('✓')) isSpecial = 'success';
|
||||||
|
else if (message.startsWith('✗')) isSpecial = 'failure';
|
||||||
|
else if (message.startsWith('⏱')) isSpecial = 'timing';
|
||||||
|
else if (message.includes('[HAPPY-PATH]')) isSpecial = 'happyPath';
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: line,
|
||||||
|
timestamp,
|
||||||
|
level: level?.trim() as LogLevel,
|
||||||
|
component: component?.trim() as LogComponent,
|
||||||
|
correlationId: correlationId || undefined,
|
||||||
|
message,
|
||||||
|
isSpecial,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface LogsDrawerProps {
|
interface LogsDrawerProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -10,12 +78,53 @@ export function LogsDrawer({ isOpen, onClose }: LogsDrawerProps) {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [autoRefresh, setAutoRefresh] = useState(false);
|
const [autoRefresh, setAutoRefresh] = useState(false);
|
||||||
const [height, setHeight] = useState(300); // Default height
|
const [height, setHeight] = useState(350);
|
||||||
const [isResizing, setIsResizing] = useState(false);
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
const startYRef = useRef(0);
|
const startYRef = useRef(0);
|
||||||
const startHeightRef = useRef(0);
|
const startHeightRef = useRef(0);
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
const wasAtBottomRef = useRef(true);
|
||||||
|
|
||||||
|
// Filter state
|
||||||
|
const [activeLevels, setActiveLevels] = useState<Set<LogLevel>>(
|
||||||
|
new Set(['DEBUG', 'INFO', 'WARN', 'ERROR'])
|
||||||
|
);
|
||||||
|
const [activeComponents, setActiveComponents] = useState<Set<LogComponent>>(
|
||||||
|
new Set(['HOOK', 'WORKER', 'SDK', 'PARSER', 'DB', 'SYSTEM', 'HTTP', 'SESSION', 'CHROMA'])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse and filter log lines
|
||||||
|
const parsedLines = useMemo(() => {
|
||||||
|
if (!logs) return [];
|
||||||
|
return logs.split('\n').map(parseLogLine);
|
||||||
|
}, [logs]);
|
||||||
|
|
||||||
|
const filteredLines = useMemo(() => {
|
||||||
|
return parsedLines.filter(line => {
|
||||||
|
// Always show unparsed lines
|
||||||
|
if (!line.level || !line.component) return true;
|
||||||
|
return activeLevels.has(line.level) && activeComponents.has(line.component);
|
||||||
|
});
|
||||||
|
}, [parsedLines, activeLevels, activeComponents]);
|
||||||
|
|
||||||
|
// Check if user is at bottom before updating
|
||||||
|
const checkIfAtBottom = useCallback(() => {
|
||||||
|
if (!contentRef.current) return true;
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = contentRef.current;
|
||||||
|
return scrollHeight - scrollTop - clientHeight < 50;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Auto-scroll to bottom
|
||||||
|
const scrollToBottom = useCallback(() => {
|
||||||
|
if (contentRef.current && wasAtBottomRef.current) {
|
||||||
|
contentRef.current.scrollTop = contentRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const fetchLogs = useCallback(async () => {
|
const fetchLogs = useCallback(async () => {
|
||||||
|
// Save scroll position before fetch
|
||||||
|
wasAtBottomRef.current = checkIfAtBottom();
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
@@ -30,7 +139,12 @@ export function LogsDrawer({ isOpen, onClose }: LogsDrawerProps) {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [checkIfAtBottom]);
|
||||||
|
|
||||||
|
// Scroll to bottom after logs update
|
||||||
|
useEffect(() => {
|
||||||
|
scrollToBottom();
|
||||||
|
}, [logs, scrollToBottom]);
|
||||||
|
|
||||||
const handleClearLogs = useCallback(async () => {
|
const handleClearLogs = useCallback(async () => {
|
||||||
if (!confirm('Are you sure you want to clear all logs?')) {
|
if (!confirm('Are you sure you want to clear all logs?')) {
|
||||||
@@ -84,6 +198,7 @@ export function LogsDrawer({ isOpen, onClose }: LogsDrawerProps) {
|
|||||||
// Fetch logs when drawer opens
|
// Fetch logs when drawer opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
|
wasAtBottomRef.current = true; // Start at bottom on open
|
||||||
fetchLogs();
|
fetchLogs();
|
||||||
}
|
}
|
||||||
}, [isOpen, fetchLogs]);
|
}, [isOpen, fetchLogs]);
|
||||||
@@ -98,10 +213,119 @@ export function LogsDrawer({ isOpen, onClose }: LogsDrawerProps) {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [isOpen, autoRefresh, fetchLogs]);
|
}, [isOpen, autoRefresh, fetchLogs]);
|
||||||
|
|
||||||
|
// Toggle level filter
|
||||||
|
const toggleLevel = useCallback((level: LogLevel) => {
|
||||||
|
setActiveLevels(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(level)) {
|
||||||
|
next.delete(level);
|
||||||
|
} else {
|
||||||
|
next.add(level);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Toggle component filter
|
||||||
|
const toggleComponent = useCallback((component: LogComponent) => {
|
||||||
|
setActiveComponents(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(component)) {
|
||||||
|
next.delete(component);
|
||||||
|
} else {
|
||||||
|
next.add(component);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Select all / none for levels
|
||||||
|
const setAllLevels = useCallback((enabled: boolean) => {
|
||||||
|
if (enabled) {
|
||||||
|
setActiveLevels(new Set(['DEBUG', 'INFO', 'WARN', 'ERROR']));
|
||||||
|
} else {
|
||||||
|
setActiveLevels(new Set());
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Select all / none for components
|
||||||
|
const setAllComponents = useCallback((enabled: boolean) => {
|
||||||
|
if (enabled) {
|
||||||
|
setActiveComponents(new Set(['HOOK', 'WORKER', 'SDK', 'PARSER', 'DB', 'SYSTEM', 'HTTP', 'SESSION', 'CHROMA']));
|
||||||
|
} else {
|
||||||
|
setActiveComponents(new Set());
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get style for a parsed log line
|
||||||
|
const getLineStyle = (line: ParsedLogLine): React.CSSProperties => {
|
||||||
|
const levelConfig = LOG_LEVELS.find(l => l.key === line.level);
|
||||||
|
const componentConfig = LOG_COMPONENTS.find(c => c.key === line.component);
|
||||||
|
|
||||||
|
let color = 'var(--color-text-primary)';
|
||||||
|
let fontWeight = 'normal';
|
||||||
|
let backgroundColor = 'transparent';
|
||||||
|
|
||||||
|
if (line.level === 'ERROR') {
|
||||||
|
color = '#f85149';
|
||||||
|
backgroundColor = 'rgba(248, 81, 73, 0.1)';
|
||||||
|
} else if (line.level === 'WARN') {
|
||||||
|
color = '#d29922';
|
||||||
|
backgroundColor = 'rgba(210, 153, 34, 0.05)';
|
||||||
|
} else if (line.isSpecial === 'success') {
|
||||||
|
color = '#3fb950';
|
||||||
|
} else if (line.isSpecial === 'failure') {
|
||||||
|
color = '#f85149';
|
||||||
|
} else if (line.isSpecial === 'happyPath') {
|
||||||
|
color = '#d29922';
|
||||||
|
} else if (levelConfig) {
|
||||||
|
color = levelConfig.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { color, fontWeight, backgroundColor, padding: '1px 0', borderRadius: '2px' };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render a single log line with syntax highlighting
|
||||||
|
const renderLogLine = (line: ParsedLogLine, index: number) => {
|
||||||
|
if (!line.timestamp) {
|
||||||
|
// Unparsed line - render as-is
|
||||||
|
return (
|
||||||
|
<div key={index} className="log-line log-line-raw">
|
||||||
|
{line.raw}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelConfig = LOG_LEVELS.find(l => l.key === line.level);
|
||||||
|
const componentConfig = LOG_COMPONENTS.find(c => c.key === line.component);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={index} className="log-line" style={getLineStyle(line)}>
|
||||||
|
<span className="log-timestamp">[{line.timestamp}]</span>
|
||||||
|
{' '}
|
||||||
|
<span className="log-level" style={{ color: levelConfig?.color }} title={line.level}>
|
||||||
|
[{levelConfig?.icon || ''} {line.level?.padEnd(5)}]
|
||||||
|
</span>
|
||||||
|
{' '}
|
||||||
|
<span className="log-component" style={{ color: componentConfig?.color }} title={line.component}>
|
||||||
|
[{componentConfig?.icon || ''} {line.component?.padEnd(7)}]
|
||||||
|
</span>
|
||||||
|
{' '}
|
||||||
|
{line.correlationId && (
|
||||||
|
<>
|
||||||
|
<span className="log-correlation">[{line.correlationId}]</span>
|
||||||
|
{' '}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<span className="log-message">{line.message}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="console-drawer" style={{ height: `${height}px` }}>
|
<div className="console-drawer" style={{ height: `${height}px` }}>
|
||||||
<div
|
<div
|
||||||
@@ -132,6 +356,16 @@ export function LogsDrawer({ isOpen, onClose }: LogsDrawerProps) {
|
|||||||
>
|
>
|
||||||
↻
|
↻
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="console-control-btn"
|
||||||
|
onClick={() => {
|
||||||
|
wasAtBottomRef.current = true;
|
||||||
|
scrollToBottom();
|
||||||
|
}}
|
||||||
|
title="Scroll to bottom"
|
||||||
|
>
|
||||||
|
⬇
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className="console-control-btn console-clear-btn"
|
className="console-control-btn console-clear-btn"
|
||||||
onClick={handleClearLogs}
|
onClick={handleClearLogs}
|
||||||
@@ -150,16 +384,74 @@ export function LogsDrawer({ isOpen, onClose }: LogsDrawerProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Filter Bar */}
|
||||||
|
<div className="console-filters">
|
||||||
|
<div className="console-filter-section">
|
||||||
|
<span className="console-filter-label">Levels:</span>
|
||||||
|
<div className="console-filter-chips">
|
||||||
|
{LOG_LEVELS.map(level => (
|
||||||
|
<button
|
||||||
|
key={level.key}
|
||||||
|
className={`console-filter-chip ${activeLevels.has(level.key) ? 'active' : ''}`}
|
||||||
|
onClick={() => toggleLevel(level.key)}
|
||||||
|
style={{
|
||||||
|
'--chip-color': level.color,
|
||||||
|
} as React.CSSProperties}
|
||||||
|
title={level.label}
|
||||||
|
>
|
||||||
|
{level.icon} {level.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
className="console-filter-action"
|
||||||
|
onClick={() => setAllLevels(activeLevels.size === 0)}
|
||||||
|
title={activeLevels.size === LOG_LEVELS.length ? 'Select none' : 'Select all'}
|
||||||
|
>
|
||||||
|
{activeLevels.size === LOG_LEVELS.length ? '○' : '●'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="console-filter-section">
|
||||||
|
<span className="console-filter-label">Components:</span>
|
||||||
|
<div className="console-filter-chips">
|
||||||
|
{LOG_COMPONENTS.map(comp => (
|
||||||
|
<button
|
||||||
|
key={comp.key}
|
||||||
|
className={`console-filter-chip ${activeComponents.has(comp.key) ? 'active' : ''}`}
|
||||||
|
onClick={() => toggleComponent(comp.key)}
|
||||||
|
style={{
|
||||||
|
'--chip-color': comp.color,
|
||||||
|
} as React.CSSProperties}
|
||||||
|
title={comp.label}
|
||||||
|
>
|
||||||
|
{comp.icon} {comp.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
className="console-filter-action"
|
||||||
|
onClick={() => setAllComponents(activeComponents.size === 0)}
|
||||||
|
title={activeComponents.size === LOG_COMPONENTS.length ? 'Select none' : 'Select all'}
|
||||||
|
>
|
||||||
|
{activeComponents.size === LOG_COMPONENTS.length ? '○' : '●'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="console-error">
|
<div className="console-error">
|
||||||
⚠ {error}
|
⚠ {error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="console-content">
|
<div className="console-content" ref={contentRef}>
|
||||||
<pre className="console-logs">
|
<div className="console-logs">
|
||||||
{logs || 'No logs available'}
|
{filteredLines.length === 0 ? (
|
||||||
</pre>
|
<div className="log-line log-line-empty">No logs available</div>
|
||||||
|
) : (
|
||||||
|
filteredLines.map((line, index) => renderLogLine(line, index))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
|
import { logger } from './logger.js';
|
||||||
import type {
|
import type {
|
||||||
TranscriptEntry,
|
TranscriptEntry,
|
||||||
UserTranscriptEntry,
|
UserTranscriptEntry,
|
||||||
@@ -42,14 +43,22 @@ export class TranscriptParser {
|
|||||||
const entry = JSON.parse(line) as TranscriptEntry;
|
const entry = JSON.parse(line) as TranscriptEntry;
|
||||||
this.entries.push(entry);
|
this.entries.push(entry);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Note: Parse errors are accumulated and accessible via getParseErrors()
|
logger.debug('PARSER', 'Failed to parse transcript line', { lineNumber: index + 1 }, error as Error);
|
||||||
// Not logging each individual line failure - would be too verbose for large transcripts
|
|
||||||
this.parseErrors.push({
|
this.parseErrors.push({
|
||||||
lineNumber: index + 1,
|
lineNumber: index + 1,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log summary if there were parse errors
|
||||||
|
if (this.parseErrors.length > 0) {
|
||||||
|
logger.warn('PARSER', `Failed to parse ${this.parseErrors.length} lines`, {
|
||||||
|
path: transcriptPath,
|
||||||
|
totalLines: lines.length,
|
||||||
|
errorCount: this.parseErrors.length
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user