@claude-flow/shared 3.0.0-alpha.7 → 3.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-flow/daemon-state.json +135 -0
- package/.claude-flow/data/pending-insights.jsonl +2 -0
- package/.claude-flow/data/ranked-context.json +5 -0
- package/.claude-flow/logs/daemon.log +45 -0
- package/.claude-flow/logs/headless/audit_1777379186972_h5un5x_prompt.log +3210 -0
- package/.claude-flow/logs/headless/audit_1777379186972_h5un5x_result.log +117 -0
- package/.claude-flow/logs/headless/audit_1777379816437_w0eaul_prompt.log +3210 -0
- package/.claude-flow/logs/headless/audit_1777379816437_w0eaul_result.log +53 -0
- package/.claude-flow/logs/headless/audit_1777380440097_621y8m_prompt.log +3210 -0
- package/.claude-flow/logs/headless/audit_1777380440097_621y8m_result.log +75 -0
- package/.claude-flow/logs/headless/optimize_1777379306973_an4lmy_prompt.log +3504 -0
- package/.claude-flow/logs/headless/optimize_1777379306973_an4lmy_result.log +166 -0
- package/.claude-flow/logs/headless/optimize_1777380274732_apxz3s_prompt.log +3504 -0
- package/.claude-flow/logs/headless/optimize_1777380274732_apxz3s_result.log +219 -0
- package/.claude-flow/logs/headless/testgaps_1777379546969_dvf2a1_prompt.log +3189 -0
- package/.claude-flow/logs/headless/testgaps_1777379546969_dvf2a1_result.log +155 -0
- package/.claude-flow/metrics/codebase-map.json +11 -0
- package/.claude-flow/metrics/consolidation.json +6 -0
- package/.claude-flow/sessions/current.json +13 -0
- package/.swarm/hnsw.index +0 -0
- package/.swarm/hnsw.metadata.json +1 -0
- package/.swarm/memory.db +0 -0
- package/.swarm/memory.db-shm +0 -0
- package/.swarm/memory.db-wal +0 -0
- package/.swarm/schema.sql +305 -0
- package/dist/core/config/schema.d.ts +96 -96
- package/dist/events/event-store.d.ts.map +1 -1
- package/dist/events/event-store.js +20 -9
- package/dist/events/event-store.js.map +1 -1
- package/dist/hooks/executor.d.ts.map +1 -1
- package/dist/hooks/executor.js +7 -4
- package/dist/hooks/executor.js.map +1 -1
- package/dist/hooks/verify-exports.test.js +6 -6
- package/dist/hooks/verify-exports.test.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +3 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/types.d.ts +4 -6
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/package.json +3 -2
- package/ruvector.db +0 -0
- package/src/events/event-store.ts +18 -9
- package/src/hooks/executor.ts +7 -5
- package/src/hooks/verify-exports.test.ts +6 -6
- package/src/mcp/server.ts +3 -6
- package/src/mcp/types.ts +4 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/.agentic-flow/intelligence.json +0 -16
- package/__tests__/coverage/base.css +0 -224
- package/__tests__/coverage/block-navigation.js +0 -87
- package/__tests__/coverage/coverage-final.json +0 -50
- package/__tests__/coverage/favicon.png +0 -0
- package/__tests__/coverage/index.html +0 -326
- package/__tests__/coverage/lcov-report/base.css +0 -224
- package/__tests__/coverage/lcov-report/block-navigation.js +0 -87
- package/__tests__/coverage/lcov-report/favicon.png +0 -0
- package/__tests__/coverage/lcov-report/index.html +0 -326
- package/__tests__/coverage/lcov-report/prettify.css +0 -1
- package/__tests__/coverage/lcov-report/prettify.js +0 -2
- package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/__tests__/coverage/lcov-report/sorter.js +0 -210
- package/__tests__/coverage/lcov-report/src/core/config/defaults.ts.html +0 -706
- package/__tests__/coverage/lcov-report/src/core/config/index.html +0 -161
- package/__tests__/coverage/lcov-report/src/core/config/loader.ts.html +0 -898
- package/__tests__/coverage/lcov-report/src/core/config/schema.ts.html +0 -649
- package/__tests__/coverage/lcov-report/src/core/config/validator.ts.html +0 -712
- package/__tests__/coverage/lcov-report/src/core/event-bus.ts.html +0 -793
- package/__tests__/coverage/lcov-report/src/core/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/core/interfaces/event.interface.ts.html +0 -886
- package/__tests__/coverage/lcov-report/src/core/interfaces/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/core/orchestrator/event-coordinator.ts.html +0 -451
- package/__tests__/coverage/lcov-report/src/core/orchestrator/health-monitor.ts.html +0 -727
- package/__tests__/coverage/lcov-report/src/core/orchestrator/index.html +0 -176
- package/__tests__/coverage/lcov-report/src/core/orchestrator/lifecycle-manager.ts.html +0 -874
- package/__tests__/coverage/lcov-report/src/core/orchestrator/session-manager.ts.html +0 -922
- package/__tests__/coverage/lcov-report/src/core/orchestrator/task-manager.ts.html +0 -1036
- package/__tests__/coverage/lcov-report/src/events/domain-events.ts.html +0 -1837
- package/__tests__/coverage/lcov-report/src/events/event-store.ts.html +0 -1849
- package/__tests__/coverage/lcov-report/src/events/example-usage.ts.html +0 -964
- package/__tests__/coverage/lcov-report/src/events/index.html +0 -176
- package/__tests__/coverage/lcov-report/src/events/projections.ts.html +0 -1768
- package/__tests__/coverage/lcov-report/src/events/state-reconstructor.ts.html +0 -1132
- package/__tests__/coverage/lcov-report/src/events.ts.html +0 -1186
- package/__tests__/coverage/lcov-report/src/hooks/example-usage.ts.html +0 -1582
- package/__tests__/coverage/lcov-report/src/hooks/executor.ts.html +0 -1222
- package/__tests__/coverage/lcov-report/src/hooks/index.html +0 -191
- package/__tests__/coverage/lcov-report/src/hooks/registry.ts.html +0 -1084
- package/__tests__/coverage/lcov-report/src/hooks/safety/bash-safety.ts.html +0 -1897
- package/__tests__/coverage/lcov-report/src/hooks/safety/file-organization.ts.html +0 -1504
- package/__tests__/coverage/lcov-report/src/hooks/safety/git-commit.ts.html +0 -1954
- package/__tests__/coverage/lcov-report/src/hooks/safety/index.html +0 -146
- package/__tests__/coverage/lcov-report/src/hooks/session-hooks.ts.html +0 -1762
- package/__tests__/coverage/lcov-report/src/hooks/task-hooks.ts.html +0 -1624
- package/__tests__/coverage/lcov-report/src/hooks/types.ts.html +0 -1156
- package/__tests__/coverage/lcov-report/src/index.html +0 -176
- package/__tests__/coverage/lcov-report/src/mcp/connection-pool.ts.html +0 -1399
- package/__tests__/coverage/lcov-report/src/mcp/index.html +0 -176
- package/__tests__/coverage/lcov-report/src/mcp/server.ts.html +0 -2407
- package/__tests__/coverage/lcov-report/src/mcp/session-manager.ts.html +0 -1369
- package/__tests__/coverage/lcov-report/src/mcp/tool-registry.ts.html +0 -1783
- package/__tests__/coverage/lcov-report/src/mcp/transport/http.ts.html +0 -1756
- package/__tests__/coverage/lcov-report/src/mcp/transport/index.html +0 -146
- package/__tests__/coverage/lcov-report/src/mcp/transport/stdio.ts.html +0 -1057
- package/__tests__/coverage/lcov-report/src/mcp/transport/websocket.ts.html +0 -1537
- package/__tests__/coverage/lcov-report/src/mcp/types.ts.html +0 -1780
- package/__tests__/coverage/lcov-report/src/plugin-interface.ts.html +0 -2074
- package/__tests__/coverage/lcov-report/src/plugin-loader.ts.html +0 -1999
- package/__tests__/coverage/lcov-report/src/plugin-registry.ts.html +0 -1897
- package/__tests__/coverage/lcov-report/src/plugins/official/hive-mind-plugin.ts.html +0 -1075
- package/__tests__/coverage/lcov-report/src/plugins/official/index.html +0 -131
- package/__tests__/coverage/lcov-report/src/plugins/official/maestro-plugin.ts.html +0 -1609
- package/__tests__/coverage/lcov-report/src/resilience/bulkhead.ts.html +0 -916
- package/__tests__/coverage/lcov-report/src/resilience/circuit-breaker.ts.html +0 -1063
- package/__tests__/coverage/lcov-report/src/resilience/index.html +0 -161
- package/__tests__/coverage/lcov-report/src/resilience/rate-limiter.ts.html +0 -1345
- package/__tests__/coverage/lcov-report/src/resilience/retry.ts.html +0 -757
- package/__tests__/coverage/lcov-report/src/security/index.html +0 -131
- package/__tests__/coverage/lcov-report/src/security/input-validation.ts.html +0 -880
- package/__tests__/coverage/lcov-report/src/security/secure-random.ts.html +0 -562
- package/__tests__/coverage/lcov-report/src/types/index.html +0 -131
- package/__tests__/coverage/lcov-report/src/types/swarm.types.ts.html +0 -850
- package/__tests__/coverage/lcov-report/src/types/task.types.ts.html +0 -700
- package/__tests__/coverage/lcov-report/src/types.ts.html +0 -1186
- package/__tests__/coverage/lcov-report/src/utils/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/utils/secure-logger.ts.html +0 -856
- package/__tests__/coverage/lcov.info +0 -19877
- package/__tests__/coverage/prettify.css +0 -1
- package/__tests__/coverage/prettify.js +0 -2
- package/__tests__/coverage/sort-arrow-sprite.png +0 -0
- package/__tests__/coverage/sorter.js +0 -210
- package/__tests__/coverage/src/core/config/defaults.ts.html +0 -706
- package/__tests__/coverage/src/core/config/index.html +0 -161
- package/__tests__/coverage/src/core/config/loader.ts.html +0 -898
- package/__tests__/coverage/src/core/config/schema.ts.html +0 -649
- package/__tests__/coverage/src/core/config/validator.ts.html +0 -712
- package/__tests__/coverage/src/core/event-bus.ts.html +0 -793
- package/__tests__/coverage/src/core/index.html +0 -116
- package/__tests__/coverage/src/core/interfaces/event.interface.ts.html +0 -886
- package/__tests__/coverage/src/core/interfaces/index.html +0 -116
- package/__tests__/coverage/src/core/orchestrator/event-coordinator.ts.html +0 -451
- package/__tests__/coverage/src/core/orchestrator/health-monitor.ts.html +0 -727
- package/__tests__/coverage/src/core/orchestrator/index.html +0 -176
- package/__tests__/coverage/src/core/orchestrator/lifecycle-manager.ts.html +0 -874
- package/__tests__/coverage/src/core/orchestrator/session-manager.ts.html +0 -922
- package/__tests__/coverage/src/core/orchestrator/task-manager.ts.html +0 -1036
- package/__tests__/coverage/src/events/domain-events.ts.html +0 -1837
- package/__tests__/coverage/src/events/event-store.ts.html +0 -1849
- package/__tests__/coverage/src/events/example-usage.ts.html +0 -964
- package/__tests__/coverage/src/events/index.html +0 -176
- package/__tests__/coverage/src/events/projections.ts.html +0 -1768
- package/__tests__/coverage/src/events/state-reconstructor.ts.html +0 -1132
- package/__tests__/coverage/src/events.ts.html +0 -1186
- package/__tests__/coverage/src/hooks/example-usage.ts.html +0 -1582
- package/__tests__/coverage/src/hooks/executor.ts.html +0 -1222
- package/__tests__/coverage/src/hooks/index.html +0 -191
- package/__tests__/coverage/src/hooks/registry.ts.html +0 -1084
- package/__tests__/coverage/src/hooks/safety/bash-safety.ts.html +0 -1897
- package/__tests__/coverage/src/hooks/safety/file-organization.ts.html +0 -1504
- package/__tests__/coverage/src/hooks/safety/git-commit.ts.html +0 -1954
- package/__tests__/coverage/src/hooks/safety/index.html +0 -146
- package/__tests__/coverage/src/hooks/session-hooks.ts.html +0 -1762
- package/__tests__/coverage/src/hooks/task-hooks.ts.html +0 -1624
- package/__tests__/coverage/src/hooks/types.ts.html +0 -1156
- package/__tests__/coverage/src/index.html +0 -176
- package/__tests__/coverage/src/mcp/connection-pool.ts.html +0 -1399
- package/__tests__/coverage/src/mcp/index.html +0 -176
- package/__tests__/coverage/src/mcp/server.ts.html +0 -2407
- package/__tests__/coverage/src/mcp/session-manager.ts.html +0 -1369
- package/__tests__/coverage/src/mcp/tool-registry.ts.html +0 -1783
- package/__tests__/coverage/src/mcp/transport/http.ts.html +0 -1756
- package/__tests__/coverage/src/mcp/transport/index.html +0 -146
- package/__tests__/coverage/src/mcp/transport/stdio.ts.html +0 -1057
- package/__tests__/coverage/src/mcp/transport/websocket.ts.html +0 -1537
- package/__tests__/coverage/src/mcp/types.ts.html +0 -1780
- package/__tests__/coverage/src/plugin-interface.ts.html +0 -2074
- package/__tests__/coverage/src/plugin-loader.ts.html +0 -1999
- package/__tests__/coverage/src/plugin-registry.ts.html +0 -1897
- package/__tests__/coverage/src/plugins/official/hive-mind-plugin.ts.html +0 -1075
- package/__tests__/coverage/src/plugins/official/index.html +0 -131
- package/__tests__/coverage/src/plugins/official/maestro-plugin.ts.html +0 -1609
- package/__tests__/coverage/src/resilience/bulkhead.ts.html +0 -916
- package/__tests__/coverage/src/resilience/circuit-breaker.ts.html +0 -1063
- package/__tests__/coverage/src/resilience/index.html +0 -161
- package/__tests__/coverage/src/resilience/rate-limiter.ts.html +0 -1345
- package/__tests__/coverage/src/resilience/retry.ts.html +0 -757
- package/__tests__/coverage/src/security/index.html +0 -131
- package/__tests__/coverage/src/security/input-validation.ts.html +0 -880
- package/__tests__/coverage/src/security/secure-random.ts.html +0 -562
- package/__tests__/coverage/src/types/index.html +0 -131
- package/__tests__/coverage/src/types/swarm.types.ts.html +0 -850
- package/__tests__/coverage/src/types/task.types.ts.html +0 -700
- package/__tests__/coverage/src/types.ts.html +0 -1186
- package/__tests__/coverage/src/utils/index.html +0 -116
- package/__tests__/coverage/src/utils/secure-logger.ts.html +0 -856
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
[2026-04-28T12:46:43.991Z] RESULT
|
|
2
|
+
============================================================
|
|
3
|
+
{
|
|
4
|
+
"success": true,
|
|
5
|
+
"output": "Now I have a complete picture. Here's the full performance analysis:\n\n---\n\n## Performance Analysis\n\nThis is a TypeScript backend orchestration system (no React). I've identified issues across memory leaks, redundant computation, missing caches, and one query pattern.\n\n---\n\n### 1. Memory Leaks\n\n#### `session-manager.ts` — Terminated sessions never removed\n`terminateSession()` marks a session terminated but never removes it from `this.sessions`. Terminated sessions accumulate indefinitely.\n\n```typescript\n// BUG: session stays in map forever after termination\nasync terminateSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n session.status = 'terminated';\n session.endTime = new Date();\n this.sessionProfiles.delete(sessionId); // only cleans up profile\n // this.sessions still holds the entry\n}\n```\n\n**Fix — remove from map at termination:**\n```typescript\nasync terminateSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n session.status = 'terminated';\n session.endTime = new Date();\n const duration = session.endTime.getTime() - session.startTime.getTime();\n\n this.eventBus.emit(SystemEventTypes.SESSION_TERMINATED, {\n sessionId, agentId: session.agentId, duration,\n });\n\n // Clean up both maps immediately\n this.sessions.delete(sessionId);\n this.sessionProfiles.delete(sessionId);\n\n this.persistSessions().catch(() => {});\n}\n```\n\n#### `event-coordinator.ts` — `shutdown()` leaves event bus handlers registered\n`registerHandler` subscribes to the event bus AND stores locally. `shutdown()` only clears the local map — the event bus keeps all subscriptions alive and will keep invoking them.\n\n```typescript\n// Only clears local map, NOT event bus subscriptions\nasync shutdown(): Promise<void> {\n this.handlers.clear(); // ← event bus is still subscribed\n this.initialized = false;\n}\n```\n\n**Fix — unregister from event bus on shutdown:**\n```typescript\nasync shutdown(): Promise<void> {\n for (const [type, handlers] of this.handlers.entries()) {\n for (const handler of handlers) {\n this.eventBus.off(type, handler);\n }\n }\n this.handlers.clear();\n this.initialized = false;\n}\n```\n\n#### `task-manager.ts` — Tasks accumulate without auto-pruning\n`this.tasks` (a Map) grows forever. `cleanup()` exists but is never called automatically. In a long-running process with thousands of tasks per day this is a significant leak.\n\n**Fix — add a periodic cleanup or prune on completion:**\n```typescript\nconstructor(private eventBus: IEventBus, queue?: ITaskQueue) {\n this.queue = queue ?? new TaskQueue();\n // Prune completed/failed tasks older than 1 hour every 10 minutes\n setInterval(() => {\n const cutoff = new Date(Date.now() - 3_600_000);\n this.cleanup(cutoff).catch(() => {});\n }, 600_000).unref();\n}\n```\n\n#### `health-monitor.ts` — `listeners` array not cleared on `stop()`\nWhen `stop()` is called the interval is cleared, but `this.listeners` keeps all callbacks referenced.\n\n```typescript\nstop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n this.running = false;\n // BUG: listeners array still holds external references\n}\n```\n\n**Fix:**\n```typescript\nstop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n this.listeners = [];\n this.running = false;\n}\n```\n\n---\n\n### 2. Redundant Computations\n\n#### `task-manager.ts` — `TaskQueue.enqueue()` re-sorts entire array on every insert\n\n`O(n log n)` per insertion; with a priority queue (min-heap) this is `O(log n)`.\n\n```typescript\n// Current: O(n log n) per insert\nasync enqueue(task: ITask): Promise<void> {\n this.tasks.push(task);\n this.tasks.sort((a, b) => b.priority - a.priority);\n}\n```\n\n**Fix — insertion into sorted position, `O(n)` worst case but avoids full re-sort:**\n```typescript\nasync enqueue(task: ITask): Promise<void> {\n // Binary search for insertion point\n let lo = 0, hi = this.tasks.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (this.tasks[mid].priority >= task.priority) lo = mid + 1;\n else hi = mid;\n }\n this.tasks.splice(lo, 0, task);\n}\n```\n\nFor very high enqueue rates, replace `ITask[]` with a proper binary heap. The current approach re-sorts even when the queue has hundreds of entries.\n\n#### `event-bus.ts` — `dispatchEvent()` allocates a new `Set` on every single event\n\n```typescript\nprivate dispatchEvent<T>(event: IEvent<T>): void {\n const typeHandlers = this.handlers.get(event.type);\n const wildcardHandlers = this.handlers.get('*');\n\n // NEW Set + copy on every dispatch — unnecessary when no wildcards\n const allHandlers = new Set<IEventHandler>();\n if (typeHandlers) {\n for (const handler of typeHandlers) allHandlers.add(handler);\n }\n if (wildcardHandlers) {\n for (const handler of wildcardHandlers) allHandlers.add(handler);\n }\n // ...\n}\n```\n\n**Fix — fast-path when no wildcards (the common case):**\n```typescript\nprivate dispatchEvent<T>(event: IEvent<T>): void {\n const typeHandlers = this.handlers.get(event.type);\n const wildcardHandlers = this.handlers.get('*');\n\n if (!wildcardHandlers || wildcardHandlers.size === 0) {\n // Fast path: no allocation needed\n if (typeHandlers) {\n for (const handler of typeHandlers) this.invokeHandler(handler, event);\n }\n return;\n }\n\n // Slow path: need dedup between type + wildcard handlers\n const allHandlers = new Set<IEventHandler>([\n ...(typeHandlers ?? []),\n ...wildcardHandlers,\n ]);\n for (const handler of allHandlers) this.invokeHandler(handler, event);\n}\n\nprivate invokeHandler<T>(handler: IEventHandler, event: IEvent<T>): void {\n try {\n const result = handler(event);\n if (result instanceof Promise) result.catch(err =>\n console.error(`Error in async event handler for ${event.type}:`, err)\n );\n } catch (error) {\n console.error(`Error in event handler for ${event.type}:`, error);\n }\n}\n```\n\n#### `health-monitor.ts` — `addToHistory` creates a new array on every trim\n\n```typescript\nprivate addToHistory(status: IHealthStatus): void {\n this.history.push(status);\n if (this.history.length > this.config.historyLimit) {\n this.history = this.history.slice(-this.config.historyLimit); // new array every time\n }\n}\n```\n\n**Fix — shift instead of slice (keeps array in place):**\n```typescript\nprivate addToHistory(status: IHealthStatus): void {\n this.history.push(status);\n if (this.history.length > this.config.historyLimit) {\n this.history.shift(); // O(n) but avoids allocation; or use a circular buffer\n }\n}\n```\n\nFor high-frequency health checks, a fixed-size circular buffer is ideal (`O(1)` insert/trim).\n\n#### `lifecycle-manager.ts` — multiple calls to `getAll()` create redundant copies\n\n`getByStatus()`, `getByType()`, and `getAvailable()` each call `getAll()`, which allocates a new array via `Array.from()`.\n\n```typescript\ngetByStatus(status: AgentStatus): IAgent[] {\n return this.getAll().filter(agent => agent.status === status); // new array\n}\ngetByType(type: string): IAgent[] {\n return this.getAll().filter(agent => agent.type === type); // new array\n}\n```\n\n**Fix — iterate the Map directly:**\n```typescript\ngetByStatus(status: AgentStatus): IAgent[] {\n const result: IAgent[] = [];\n for (const agent of this.agents.values()) {\n if (agent.status === status) result.push(agent);\n }\n return result;\n}\n```\n\n#### `task-manager.ts` — `getTasks()` makes multiple passes over the same array\n\n```typescript\ngetTasks(filter?) {\n let tasks = Array.from(this.tasks.values()); // copy\n if (filter?.status) tasks = tasks.filter(t => t.status === filter.status);\n if (filter?.type) tasks = tasks.filter(t => t.type === filter.type);\n if (filter?.assignedAgent) tasks = tasks.filter(t => t.assignedAgent === filter.assignedAgent);\n return tasks;\n}\n```\n\nThree separate `.filter()` passes, each producing an intermediate array.\n\n**Fix — single pass:**\n```typescript\ngetTasks(filter?: Partial<Pick<ITask, 'status' | 'type' | 'assignedAgent'>>): ITask[] {\n const result: ITask[] = [];\n for (const task of this.tasks.values()) {\n if (filter?.status && task.status !== filter.status) continue;\n if (filter?.type && task.type !== filter.type) continue;\n if (filter?.assignedAgent && task.assignedAgent !== filter.assignedAgent) continue;\n result.push(task);\n }\n return result;\n}\n```\n\n---\n\n### 3. Caching Opportunities\n\n#### `config/loader.ts` — no result caching; re-reads disk on every `load()` call\n\n`ConfigLoader.load()` hits the filesystem and validates on every invocation. In a CLI that calls it from multiple commands, this is wasteful.\n\n**Fix — memoize after first successful load:**\n```typescript\nexport class ConfigLoader {\n private cachedConfig: LoadedConfig | null = null;\n\n async load(forceReload = false): Promise<LoadedConfig> {\n if (this.cachedConfig && !forceReload) return this.cachedConfig;\n const result = await this._load();\n this.cachedConfig = result;\n return result;\n }\n\n invalidate(): void {\n this.cachedConfig = null;\n }\n}\n```\n\n#### `event-store.ts` — `getStats()` runs 5 sequential SQL queries\n\nEvery call to `getStats()` issues five separate SQL statements that could be combined or cached.\n\n```typescript\n// 5 separate round-trips to SQLite\nconst totalStmt = this.db!.prepare('SELECT COUNT(*) ...');\nconst typeStmt = this.db!.prepare('SELECT type, COUNT(*) ... GROUP BY type');\nconst aggStmt = this.db!.prepare('SELECT aggregate_id, COUNT(*) ... GROUP BY aggregate_id');\nconst rangeStmt = this.db!.prepare('SELECT MIN(timestamp), MAX(timestamp) ...');\nconst snapshotStmt = this.db!.prepare('SELECT COUNT(*) FROM snapshots');\n```\n\n**Fix — cache stats with a short TTL, or merge into fewer queries:**\n```typescript\nprivate statsCache: { stats: EventStoreStats; cachedAt: number } | null = null;\nprivate readonly STATS_TTL_MS = 5000;\n\nasync getStats(): Promise<EventStoreStats> {\n const now = Date.now();\n if (this.statsCache && now - this.statsCache.cachedAt < this.STATS_TTL_MS) {\n return this.statsCache.stats;\n }\n const stats = await this.computeStats();\n this.statsCache = { stats, cachedAt: now };\n return stats;\n}\n```\n\n#### `connection-pool.ts` — `getStats()` iterates all connections on every call\n\n```typescript\ngetStats(): ConnectionPoolStats {\n let idleCount = 0, busyCount = 0;\n for (const connection of this.connections.values()) { // full scan every call\n if (connection.state === 'idle') idleCount++;\n else if (connection.state === 'busy') busyCount++;\n }\n // ...\n}\n```\n\n**Fix — maintain separate counters updated on `acquire()`/`release()`/`destroy()`:**\n```typescript\nprivate idleCount = 0;\nprivate busyCount = 0;\n\n// In acquire(): this.idleCount--; this.busyCount++;\n// In release(): this.busyCount--; this.idleCount++;\n// In destroy(): if (conn.state === 'idle') this.idleCount--;\n// else if (conn.state === 'busy') this.busyCount--;\n\ngetStats(): ConnectionPoolStats {\n return {\n totalConnections: this.connections.size,\n idleConnections: this.idleCount,\n busyConnections: this.busyCount,\n // ...\n };\n}\n```\n\n---\n\n### 4. N+1 Query Pattern\n\n#### `event-store.ts` — `loadAggregateVersions()` loads ALL versions at startup\n\nOn startup the store loads every aggregate's max version into memory. With millions of events this becomes a large in-memory map. The actual problem arises in `append()` — the version is fetched from the in-memory map, but if the store is used concurrently (e.g. multiple workers) there's no serialization and the version tracker diverges.\n\nThe subtler N+1 is in how consumers typically use this: calling `getEvents(aggregateId)` per aggregate in a loop rather than batching via `query({ aggregateIds: [...] })`. The `query()` method already supports batching — consumers should prefer it.\n\n**Guidance:** Prefer `query({ aggregateIds: ['a','b','c'] })` over a loop of `getEvents()` calls. Add a JSDoc note to `getEvents()` calling this out.\n\n#### `event-store.ts` — `getSnapshot()` uses `getAsObject` incorrectly (latent bug)\n\n```typescript\nconst row = stmt.getAsObject([aggregateId]); // passes bind params to getAsObject, not bind()\n```\n\n`getAsObject()` doesn't accept bind params. This should be:\n```typescript\nstmt.bind([aggregateId]);\nconst row = stmt.step() ? stmt.getAsObject() : null;\nstmt.free();\n```\n\n---\n\n### 5. Missing React Re-renders\n\nThis codebase has no React — no action needed here.\n\n---\n\n### Priority Summary\n\n| Priority | Issue | File | Impact |\n|---|---|---|---|\n| **Critical** | Terminated sessions never removed | `session-manager.ts:terminateSession` | Memory grows unbounded |\n| **Critical** | `shutdown()` doesn't unsubscribe from event bus | `event-coordinator.ts:shutdown` | Phantom handlers fire after shutdown |\n| **High** | Tasks never auto-pruned | `task-manager.ts:constructor` | Memory grows unbounded |\n| **High** | `TaskQueue.enqueue()` full re-sort on every insert | `task-manager.ts:TaskQueue` | CPU, scales as O(n²) total |\n| **High** | `getStats()` iterates pool on every call | `connection-pool.ts:getStats` | Called on every MCP health check |\n| **Medium** | New `Set` allocated on every event dispatch | `event-bus.ts:dispatchEvent` | High-frequency hot path |\n| **Medium** | `getStats()` runs 5 SQL queries every call | `event-store.ts:getStats` | No caching |\n| **Medium** | Multi-pass filter in `getTasks()` | `task-manager.ts:getTasks` | 3× allocations per call |\n| **Medium** | Config re-read from disk on every `load()` | `config/loader.ts` | Repeated I/O |\n| **Low** | History trim allocates new array | `health-monitor.ts:addToHistory` | Minor, fixed interval |\n| **Low** | `getAll()` called separately per filter method | `lifecycle-manager.ts` | Minor duplicate allocation |\n| **Bug** | `getSnapshot()` passes bind params to `getAsObject` | `event-store.ts:getSnapshot` | Always returns null |\n",
|
|
6
|
+
"parsedOutput": {
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"title": "Performance Analysis",
|
|
10
|
+
"content": "\nThis is a TypeScript backend orchestration system (no React). I've identified issues across memory leaks, redundant computation, missing caches, and one query pattern.\n\n---\n\n",
|
|
11
|
+
"level": 2
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"title": "1. Memory Leaks",
|
|
15
|
+
"content": "\n",
|
|
16
|
+
"level": 3
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"title": "`session-manager.ts` — Terminated sessions never removed",
|
|
20
|
+
"content": "`terminateSession()` marks a session terminated but never removes it from `this.sessions`. Terminated sessions accumulate indefinitely.\n\n```typescript\n// BUG: session stays in map forever after termination\nasync terminateSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n session.status = 'terminated';\n session.endTime = new Date();\n this.sessionProfiles.delete(sessionId); // only cleans up profile\n // this.sessions still holds the entry\n}\n```\n\n**Fix — remove from map at termination:**\n```typescript\nasync terminateSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n session.status = 'terminated';\n session.endTime = new Date();\n const duration = session.endTime.getTime() - session.startTime.getTime();\n\n this.eventBus.emit(SystemEventTypes.SESSION_TERMINATED, {\n sessionId, agentId: session.agentId, duration,\n });\n\n // Clean up both maps immediately\n this.sessions.delete(sessionId);\n this.sessionProfiles.delete(sessionId);\n\n this.persistSessions().catch(() => {});\n}\n```\n\n",
|
|
21
|
+
"level": 4
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"title": "`event-coordinator.ts` — `shutdown()` leaves event bus handlers registered",
|
|
25
|
+
"content": "`registerHandler` subscribes to the event bus AND stores locally. `shutdown()` only clears the local map — the event bus keeps all subscriptions alive and will keep invoking them.\n\n```typescript\n// Only clears local map, NOT event bus subscriptions\nasync shutdown(): Promise<void> {\n this.handlers.clear(); // ← event bus is still subscribed\n this.initialized = false;\n}\n```\n\n**Fix — unregister from event bus on shutdown:**\n```typescript\nasync shutdown(): Promise<void> {\n for (const [type, handlers] of this.handlers.entries()) {\n for (const handler of handlers) {\n this.eventBus.off(type, handler);\n }\n }\n this.handlers.clear();\n this.initialized = false;\n}\n```\n\n",
|
|
26
|
+
"level": 4
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"title": "`task-manager.ts` — Tasks accumulate without auto-pruning",
|
|
30
|
+
"content": "`this.tasks` (a Map) grows forever. `cleanup()` exists but is never called automatically. In a long-running process with thousands of tasks per day this is a significant leak.\n\n**Fix — add a periodic cleanup or prune on completion:**\n```typescript\nconstructor(private eventBus: IEventBus, queue?: ITaskQueue) {\n this.queue = queue ?? new TaskQueue();\n // Prune completed/failed tasks older than 1 hour every 10 minutes\n setInterval(() => {\n const cutoff = new Date(Date.now() - 3_600_000);\n this.cleanup(cutoff).catch(() => {});\n }, 600_000).unref();\n}\n```\n\n",
|
|
31
|
+
"level": 4
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"title": "`health-monitor.ts` — `listeners` array not cleared on `stop()`",
|
|
35
|
+
"content": "When `stop()` is called the interval is cleared, but `this.listeners` keeps all callbacks referenced.\n\n```typescript\nstop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n this.running = false;\n // BUG: listeners array still holds external references\n}\n```\n\n**Fix:**\n```typescript\nstop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n this.listeners = [];\n this.running = false;\n}\n```\n\n---\n\n",
|
|
36
|
+
"level": 4
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"title": "2. Redundant Computations",
|
|
40
|
+
"content": "\n",
|
|
41
|
+
"level": 3
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"title": "`task-manager.ts` — `TaskQueue.enqueue()` re-sorts entire array on every insert",
|
|
45
|
+
"content": "\n`O(n log n)` per insertion; with a priority queue (min-heap) this is `O(log n)`.\n\n```typescript\n// Current: O(n log n) per insert\nasync enqueue(task: ITask): Promise<void> {\n this.tasks.push(task);\n this.tasks.sort((a, b) => b.priority - a.priority);\n}\n```\n\n**Fix — insertion into sorted position, `O(n)` worst case but avoids full re-sort:**\n```typescript\nasync enqueue(task: ITask): Promise<void> {\n // Binary search for insertion point\n let lo = 0, hi = this.tasks.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (this.tasks[mid].priority >= task.priority) lo = mid + 1;\n else hi = mid;\n }\n this.tasks.splice(lo, 0, task);\n}\n```\n\nFor very high enqueue rates, replace `ITask[]` with a proper binary heap. The current approach re-sorts even when the queue has hundreds of entries.\n\n",
|
|
46
|
+
"level": 4
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"title": "`event-bus.ts` — `dispatchEvent()` allocates a new `Set` on every single event",
|
|
50
|
+
"content": "\n```typescript\nprivate dispatchEvent<T>(event: IEvent<T>): void {\n const typeHandlers = this.handlers.get(event.type);\n const wildcardHandlers = this.handlers.get('*');\n\n // NEW Set + copy on every dispatch — unnecessary when no wildcards\n const allHandlers = new Set<IEventHandler>();\n if (typeHandlers) {\n for (const handler of typeHandlers) allHandlers.add(handler);\n }\n if (wildcardHandlers) {\n for (const handler of wildcardHandlers) allHandlers.add(handler);\n }\n // ...\n}\n```\n\n**Fix — fast-path when no wildcards (the common case):**\n```typescript\nprivate dispatchEvent<T>(event: IEvent<T>): void {\n const typeHandlers = this.handlers.get(event.type);\n const wildcardHandlers = this.handlers.get('*');\n\n if (!wildcardHandlers || wildcardHandlers.size === 0) {\n // Fast path: no allocation needed\n if (typeHandlers) {\n for (const handler of typeHandlers) this.invokeHandler(handler, event);\n }\n return;\n }\n\n // Slow path: need dedup between type + wildcard handlers\n const allHandlers = new Set<IEventHandler>([\n ...(typeHandlers ?? []),\n ...wildcardHandlers,\n ]);\n for (const handler of allHandlers) this.invokeHandler(handler, event);\n}\n\nprivate invokeHandler<T>(handler: IEventHandler, event: IEvent<T>): void {\n try {\n const result = handler(event);\n if (result instanceof Promise) result.catch(err =>\n console.error(`Error in async event handler for ${event.type}:`, err)\n );\n } catch (error) {\n console.error(`Error in event handler for ${event.type}:`, error);\n }\n}\n```\n\n",
|
|
51
|
+
"level": 4
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"title": "`health-monitor.ts` — `addToHistory` creates a new array on every trim",
|
|
55
|
+
"content": "\n```typescript\nprivate addToHistory(status: IHealthStatus): void {\n this.history.push(status);\n if (this.history.length > this.config.historyLimit) {\n this.history = this.history.slice(-this.config.historyLimit); // new array every time\n }\n}\n```\n\n**Fix — shift instead of slice (keeps array in place):**\n```typescript\nprivate addToHistory(status: IHealthStatus): void {\n this.history.push(status);\n if (this.history.length > this.config.historyLimit) {\n this.history.shift(); // O(n) but avoids allocation; or use a circular buffer\n }\n}\n```\n\nFor high-frequency health checks, a fixed-size circular buffer is ideal (`O(1)` insert/trim).\n\n",
|
|
56
|
+
"level": 4
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"title": "`lifecycle-manager.ts` — multiple calls to `getAll()` create redundant copies",
|
|
60
|
+
"content": "\n`getByStatus()`, `getByType()`, and `getAvailable()` each call `getAll()`, which allocates a new array via `Array.from()`.\n\n```typescript\ngetByStatus(status: AgentStatus): IAgent[] {\n return this.getAll().filter(agent => agent.status === status); // new array\n}\ngetByType(type: string): IAgent[] {\n return this.getAll().filter(agent => agent.type === type); // new array\n}\n```\n\n**Fix — iterate the Map directly:**\n```typescript\ngetByStatus(status: AgentStatus): IAgent[] {\n const result: IAgent[] = [];\n for (const agent of this.agents.values()) {\n if (agent.status === status) result.push(agent);\n }\n return result;\n}\n```\n\n",
|
|
61
|
+
"level": 4
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"title": "`task-manager.ts` — `getTasks()` makes multiple passes over the same array",
|
|
65
|
+
"content": "\n```typescript\ngetTasks(filter?) {\n let tasks = Array.from(this.tasks.values()); // copy\n if (filter?.status) tasks = tasks.filter(t => t.status === filter.status);\n if (filter?.type) tasks = tasks.filter(t => t.type === filter.type);\n if (filter?.assignedAgent) tasks = tasks.filter(t => t.assignedAgent === filter.assignedAgent);\n return tasks;\n}\n```\n\nThree separate `.filter()` passes, each producing an intermediate array.\n\n**Fix — single pass:**\n```typescript\ngetTasks(filter?: Partial<Pick<ITask, 'status' | 'type' | 'assignedAgent'>>): ITask[] {\n const result: ITask[] = [];\n for (const task of this.tasks.values()) {\n if (filter?.status && task.status !== filter.status) continue;\n if (filter?.type && task.type !== filter.type) continue;\n if (filter?.assignedAgent && task.assignedAgent !== filter.assignedAgent) continue;\n result.push(task);\n }\n return result;\n}\n```\n\n---\n\n",
|
|
66
|
+
"level": 4
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"title": "3. Caching Opportunities",
|
|
70
|
+
"content": "\n",
|
|
71
|
+
"level": 3
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"title": "`config/loader.ts` — no result caching; re-reads disk on every `load()` call",
|
|
75
|
+
"content": "\n`ConfigLoader.load()` hits the filesystem and validates on every invocation. In a CLI that calls it from multiple commands, this is wasteful.\n\n**Fix — memoize after first successful load:**\n```typescript\nexport class ConfigLoader {\n private cachedConfig: LoadedConfig | null = null;\n\n async load(forceReload = false): Promise<LoadedConfig> {\n if (this.cachedConfig && !forceReload) return this.cachedConfig;\n const result = await this._load();\n this.cachedConfig = result;\n return result;\n }\n\n invalidate(): void {\n this.cachedConfig = null;\n }\n}\n```\n\n",
|
|
76
|
+
"level": 4
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"title": "`event-store.ts` — `getStats()` runs 5 sequential SQL queries",
|
|
80
|
+
"content": "\nEvery call to `getStats()` issues five separate SQL statements that could be combined or cached.\n\n```typescript\n// 5 separate round-trips to SQLite\nconst totalStmt = this.db!.prepare('SELECT COUNT(*) ...');\nconst typeStmt = this.db!.prepare('SELECT type, COUNT(*) ... GROUP BY type');\nconst aggStmt = this.db!.prepare('SELECT aggregate_id, COUNT(*) ... GROUP BY aggregate_id');\nconst rangeStmt = this.db!.prepare('SELECT MIN(timestamp), MAX(timestamp) ...');\nconst snapshotStmt = this.db!.prepare('SELECT COUNT(*) FROM snapshots');\n```\n\n**Fix — cache stats with a short TTL, or merge into fewer queries:**\n```typescript\nprivate statsCache: { stats: EventStoreStats; cachedAt: number } | null = null;\nprivate readonly STATS_TTL_MS = 5000;\n\nasync getStats(): Promise<EventStoreStats> {\n const now = Date.now();\n if (this.statsCache && now - this.statsCache.cachedAt < this.STATS_TTL_MS) {\n return this.statsCache.stats;\n }\n const stats = await this.computeStats();\n this.statsCache = { stats, cachedAt: now };\n return stats;\n}\n```\n\n",
|
|
81
|
+
"level": 4
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"title": "`connection-pool.ts` — `getStats()` iterates all connections on every call",
|
|
85
|
+
"content": "\n```typescript\ngetStats(): ConnectionPoolStats {\n let idleCount = 0, busyCount = 0;\n for (const connection of this.connections.values()) { // full scan every call\n if (connection.state === 'idle') idleCount++;\n else if (connection.state === 'busy') busyCount++;\n }\n // ...\n}\n```\n\n**Fix — maintain separate counters updated on `acquire()`/`release()`/`destroy()`:**\n```typescript\nprivate idleCount = 0;\nprivate busyCount = 0;\n\n// In acquire(): this.idleCount--; this.busyCount++;\n// In release(): this.busyCount--; this.idleCount++;\n// In destroy(): if (conn.state === 'idle') this.idleCount--;\n// else if (conn.state === 'busy') this.busyCount--;\n\ngetStats(): ConnectionPoolStats {\n return {\n totalConnections: this.connections.size,\n idleConnections: this.idleCount,\n busyConnections: this.busyCount,\n // ...\n };\n}\n```\n\n---\n\n",
|
|
86
|
+
"level": 4
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"title": "4. N+1 Query Pattern",
|
|
90
|
+
"content": "\n",
|
|
91
|
+
"level": 3
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"title": "`event-store.ts` — `loadAggregateVersions()` loads ALL versions at startup",
|
|
95
|
+
"content": "\nOn startup the store loads every aggregate's max version into memory. With millions of events this becomes a large in-memory map. The actual problem arises in `append()` — the version is fetched from the in-memory map, but if the store is used concurrently (e.g. multiple workers) there's no serialization and the version tracker diverges.\n\nThe subtler N+1 is in how consumers typically use this: calling `getEvents(aggregateId)` per aggregate in a loop rather than batching via `query({ aggregateIds: [...] })`. The `query()` method already supports batching — consumers should prefer it.\n\n**Guidance:** Prefer `query({ aggregateIds: ['a','b','c'] })` over a loop of `getEvents()` calls. Add a JSDoc note to `getEvents()` calling this out.\n\n",
|
|
96
|
+
"level": 4
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"title": "`event-store.ts` — `getSnapshot()` uses `getAsObject` incorrectly (latent bug)",
|
|
100
|
+
"content": "\n```typescript\nconst row = stmt.getAsObject([aggregateId]); // passes bind params to getAsObject, not bind()\n```\n\n`getAsObject()` doesn't accept bind params. This should be:\n```typescript\nstmt.bind([aggregateId]);\nconst row = stmt.step() ? stmt.getAsObject() : null;\nstmt.free();\n```\n\n---\n\n",
|
|
101
|
+
"level": 4
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"title": "5. Missing React Re-renders",
|
|
105
|
+
"content": "\nThis codebase has no React — no action needed here.\n\n---\n\n",
|
|
106
|
+
"level": 3
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"title": "Priority Summary",
|
|
110
|
+
"content": "| Priority | Issue | File | Impact |\n|---|---|---|---|\n| **Critical** | Terminated sessions never removed | `session-manager.ts:terminateSession` | Memory grows unbounded |\n| **Critical** | `shutdown()` doesn't unsubscribe from event bus | `event-coordinator.ts:shutdown` | Phantom handlers fire after shutdown |\n| **High** | Tasks never auto-pruned | `task-manager.ts:constructor` | Memory grows unbounded |\n| **High** | `TaskQueue.enqueue()` full re-sort on every insert | `task-manager.ts:TaskQueue` | CPU, scales as O(n²) total |\n| **High** | `getStats()` iterates pool on every call | `connection-pool.ts:getStats` | Called on every MCP health check |\n| **Medium** | New `Set` allocated on every event dispatch | `event-bus.ts:dispatchEvent` | High-frequency hot path |\n| **Medium** | `getStats()` runs 5 SQL queries every call | `event-store.ts:getStats` | No caching |\n| **Medium** | Multi-pass filter in `getTasks()` | `task-manager.ts:getTasks` | 3× allocations per call |\n| **Medium** | Config re-read from disk on every `load()` | `config/loader.ts` | Repeated I/O |\n| **Low** | History trim allocates new array | `health-monitor.ts:addToHistory` | Minor, fixed interval |\n| **Low** | `getAll()` called separately per filter method | `lifecycle-manager.ts` | Minor duplicate allocation |\n| **Bug** | `getSnapshot()` passes bind params to `getAsObject` | `event-store.ts:getSnapshot` | Always returns null |",
|
|
111
|
+
"level": 3
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"codeBlocks": [
|
|
115
|
+
{
|
|
116
|
+
"language": "typescript",
|
|
117
|
+
"code": "// BUG: session stays in map forever after termination\nasync terminateSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n session.status = 'terminated';\n session.endTime = new Date();\n this.sessionProfiles.delete(sessionId); // only cleans up profile\n // this.sessions still holds the entry\n}"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"language": "typescript",
|
|
121
|
+
"code": "async terminateSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n session.status = 'terminated';\n session.endTime = new Date();\n const duration = session.endTime.getTime() - session.startTime.getTime();\n\n this.eventBus.emit(SystemEventTypes.SESSION_TERMINATED, {\n sessionId, agentId: session.agentId, duration,\n });\n\n // Clean up both maps immediately\n this.sessions.delete(sessionId);\n this.sessionProfiles.delete(sessionId);\n\n this.persistSessions().catch(() => {});\n}"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"language": "typescript",
|
|
125
|
+
"code": "// Only clears local map, NOT event bus subscriptions\nasync shutdown(): Promise<void> {\n this.handlers.clear(); // ← event bus is still subscribed\n this.initialized = false;\n}"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"language": "typescript",
|
|
129
|
+
"code": "async shutdown(): Promise<void> {\n for (const [type, handlers] of this.handlers.entries()) {\n for (const handler of handlers) {\n this.eventBus.off(type, handler);\n }\n }\n this.handlers.clear();\n this.initialized = false;\n}"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"language": "typescript",
|
|
133
|
+
"code": "constructor(private eventBus: IEventBus, queue?: ITaskQueue) {\n this.queue = queue ?? new TaskQueue();\n // Prune completed/failed tasks older than 1 hour every 10 minutes\n setInterval(() => {\n const cutoff = new Date(Date.now() - 3_600_000);\n this.cleanup(cutoff).catch(() => {});\n }, 600_000).unref();\n}"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"language": "typescript",
|
|
137
|
+
"code": "stop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n this.running = false;\n // BUG: listeners array still holds external references\n}"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"language": "typescript",
|
|
141
|
+
"code": "stop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n this.listeners = [];\n this.running = false;\n}"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"language": "typescript",
|
|
145
|
+
"code": "// Current: O(n log n) per insert\nasync enqueue(task: ITask): Promise<void> {\n this.tasks.push(task);\n this.tasks.sort((a, b) => b.priority - a.priority);\n}"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"language": "typescript",
|
|
149
|
+
"code": "async enqueue(task: ITask): Promise<void> {\n // Binary search for insertion point\n let lo = 0, hi = this.tasks.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (this.tasks[mid].priority >= task.priority) lo = mid + 1;\n else hi = mid;\n }\n this.tasks.splice(lo, 0, task);\n}"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"language": "typescript",
|
|
153
|
+
"code": "private dispatchEvent<T>(event: IEvent<T>): void {\n const typeHandlers = this.handlers.get(event.type);\n const wildcardHandlers = this.handlers.get('*');\n\n // NEW Set + copy on every dispatch — unnecessary when no wildcards\n const allHandlers = new Set<IEventHandler>();\n if (typeHandlers) {\n for (const handler of typeHandlers) allHandlers.add(handler);\n }\n if (wildcardHandlers) {\n for (const handler of wildcardHandlers) allHandlers.add(handler);\n }\n // ...\n}"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"language": "typescript",
|
|
157
|
+
"code": "private dispatchEvent<T>(event: IEvent<T>): void {\n const typeHandlers = this.handlers.get(event.type);\n const wildcardHandlers = this.handlers.get('*');\n\n if (!wildcardHandlers || wildcardHandlers.size === 0) {\n // Fast path: no allocation needed\n if (typeHandlers) {\n for (const handler of typeHandlers) this.invokeHandler(handler, event);\n }\n return;\n }\n\n // Slow path: need dedup between type + wildcard handlers\n const allHandlers = new Set<IEventHandler>([\n ...(typeHandlers ?? []),\n ...wildcardHandlers,\n ]);\n for (const handler of allHandlers) this.invokeHandler(handler, event);\n}\n\nprivate invokeHandler<T>(handler: IEventHandler, event: IEvent<T>): void {\n try {\n const result = handler(event);\n if (result instanceof Promise) result.catch(err =>\n console.error(`Error in async event handler for ${event.type}:`, err)\n );\n } catch (error) {\n console.error(`Error in event handler for ${event.type}:`, error);\n }\n}"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"language": "typescript",
|
|
161
|
+
"code": "private addToHistory(status: IHealthStatus): void {\n this.history.push(status);\n if (this.history.length > this.config.historyLimit) {\n this.history = this.history.slice(-this.config.historyLimit); // new array every time\n }\n}"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"language": "typescript",
|
|
165
|
+
"code": "private addToHistory(status: IHealthStatus): void {\n this.history.push(status);\n if (this.history.length > this.config.historyLimit) {\n this.history.shift(); // O(n) but avoids allocation; or use a circular buffer\n }\n}"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"language": "typescript",
|
|
169
|
+
"code": "getByStatus(status: AgentStatus): IAgent[] {\n return this.getAll().filter(agent => agent.status === status); // new array\n}\ngetByType(type: string): IAgent[] {\n return this.getAll().filter(agent => agent.type === type); // new array\n}"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"language": "typescript",
|
|
173
|
+
"code": "getByStatus(status: AgentStatus): IAgent[] {\n const result: IAgent[] = [];\n for (const agent of this.agents.values()) {\n if (agent.status === status) result.push(agent);\n }\n return result;\n}"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"language": "typescript",
|
|
177
|
+
"code": "getTasks(filter?) {\n let tasks = Array.from(this.tasks.values()); // copy\n if (filter?.status) tasks = tasks.filter(t => t.status === filter.status);\n if (filter?.type) tasks = tasks.filter(t => t.type === filter.type);\n if (filter?.assignedAgent) tasks = tasks.filter(t => t.assignedAgent === filter.assignedAgent);\n return tasks;\n}"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"language": "typescript",
|
|
181
|
+
"code": "getTasks(filter?: Partial<Pick<ITask, 'status' | 'type' | 'assignedAgent'>>): ITask[] {\n const result: ITask[] = [];\n for (const task of this.tasks.values()) {\n if (filter?.status && task.status !== filter.status) continue;\n if (filter?.type && task.type !== filter.type) continue;\n if (filter?.assignedAgent && task.assignedAgent !== filter.assignedAgent) continue;\n result.push(task);\n }\n return result;\n}"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"language": "typescript",
|
|
185
|
+
"code": "export class ConfigLoader {\n private cachedConfig: LoadedConfig | null = null;\n\n async load(forceReload = false): Promise<LoadedConfig> {\n if (this.cachedConfig && !forceReload) return this.cachedConfig;\n const result = await this._load();\n this.cachedConfig = result;\n return result;\n }\n\n invalidate(): void {\n this.cachedConfig = null;\n }\n}"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"language": "typescript",
|
|
189
|
+
"code": "// 5 separate round-trips to SQLite\nconst totalStmt = this.db!.prepare('SELECT COUNT(*) ...');\nconst typeStmt = this.db!.prepare('SELECT type, COUNT(*) ... GROUP BY type');\nconst aggStmt = this.db!.prepare('SELECT aggregate_id, COUNT(*) ... GROUP BY aggregate_id');\nconst rangeStmt = this.db!.prepare('SELECT MIN(timestamp), MAX(timestamp) ...');\nconst snapshotStmt = this.db!.prepare('SELECT COUNT(*) FROM snapshots');"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"language": "typescript",
|
|
193
|
+
"code": "private statsCache: { stats: EventStoreStats; cachedAt: number } | null = null;\nprivate readonly STATS_TTL_MS = 5000;\n\nasync getStats(): Promise<EventStoreStats> {\n const now = Date.now();\n if (this.statsCache && now - this.statsCache.cachedAt < this.STATS_TTL_MS) {\n return this.statsCache.stats;\n }\n const stats = await this.computeStats();\n this.statsCache = { stats, cachedAt: now };\n return stats;\n}"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"language": "typescript",
|
|
197
|
+
"code": "getStats(): ConnectionPoolStats {\n let idleCount = 0, busyCount = 0;\n for (const connection of this.connections.values()) { // full scan every call\n if (connection.state === 'idle') idleCount++;\n else if (connection.state === 'busy') busyCount++;\n }\n // ...\n}"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"language": "typescript",
|
|
201
|
+
"code": "private idleCount = 0;\nprivate busyCount = 0;\n\n// In acquire(): this.idleCount--; this.busyCount++;\n// In release(): this.busyCount--; this.idleCount++;\n// In destroy(): if (conn.state === 'idle') this.idleCount--;\n// else if (conn.state === 'busy') this.busyCount--;\n\ngetStats(): ConnectionPoolStats {\n return {\n totalConnections: this.connections.size,\n idleConnections: this.idleCount,\n busyConnections: this.busyCount,\n // ...\n };\n}"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"language": "typescript",
|
|
205
|
+
"code": "const row = stmt.getAsObject([aggregateId]); // passes bind params to getAsObject, not bind()"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"language": "typescript",
|
|
209
|
+
"code": "stmt.bind([aggregateId]);\nconst row = stmt.step() ? stmt.getAsObject() : null;\nstmt.free();"
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
"durationMs": 129259,
|
|
214
|
+
"model": "sonnet",
|
|
215
|
+
"sandboxMode": "permissive",
|
|
216
|
+
"workerType": "optimize",
|
|
217
|
+
"timestamp": "2026-04-28T12:46:43.991Z",
|
|
218
|
+
"executionId": "optimize_1777380274732_apxz3s"
|
|
219
|
+
}
|