@triedotdev/mcp 1.0.136 → 1.0.138
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/README.md +6 -6
- package/dist/{autonomy-config-QA6ATWLJ.js → autonomy-config-TZ6HF4FA.js} +3 -3
- package/dist/{chat-store-HFOOWZYN.js → chat-store-OJLJCJFI.js} +3 -3
- package/dist/{chunk-DFPVUMVE.js → chunk-23RJT5WT.js} +5 -4
- package/dist/chunk-23RJT5WT.js.map +1 -0
- package/dist/{chunk-4YJ6KLGI.js → chunk-3MUCUZ46.js} +8 -8
- package/dist/chunk-3MUCUZ46.js.map +1 -0
- package/dist/{chunk-6VIMBFUZ.js → chunk-3RRXWX3V.js} +21 -17
- package/dist/chunk-3RRXWX3V.js.map +1 -0
- package/dist/{chunk-WHIQAGB7.js → chunk-4C67GV3O.js} +2 -2
- package/dist/{chunk-WS6OA7H6.js → chunk-4MJ52WBH.js} +2 -3
- package/dist/chunk-4MJ52WBH.js.map +1 -0
- package/dist/{chunk-AJ34GCMD.js → chunk-67GSG2ST.js} +41 -38
- package/dist/chunk-67GSG2ST.js.map +1 -0
- package/dist/{chunk-UHX4462X.js → chunk-6LLH3TBZ.js} +24 -25
- package/dist/chunk-6LLH3TBZ.js.map +1 -0
- package/dist/{chunk-DFHMB44X.js → chunk-D3AS5LY7.js} +6 -10
- package/dist/chunk-D3AS5LY7.js.map +1 -0
- package/dist/{chunk-6OUWNVLX.js → chunk-EDDT4ZIH.js} +8 -8
- package/dist/chunk-EDDT4ZIH.js.map +1 -0
- package/dist/{chunk-Z4DN527J.js → chunk-FG467PDD.js} +156 -39
- package/dist/chunk-FG467PDD.js.map +1 -0
- package/dist/{chunk-T4THB2OR.js → chunk-FOCXXIXY.js} +49 -28
- package/dist/chunk-FOCXXIXY.js.map +1 -0
- package/dist/{goal-validator-PDKYZSNP.js → chunk-GFFUDJMK.js} +97 -40
- package/dist/chunk-GFFUDJMK.js.map +1 -0
- package/dist/{chunk-ZEXMMTIQ.js → chunk-J5EMP4XW.js} +2 -2
- package/dist/{chunk-UHMMANC2.js → chunk-LT6VUZG2.js} +21 -18
- package/dist/chunk-LT6VUZG2.js.map +1 -0
- package/dist/{chunk-55CBWOEZ.js → chunk-QSWUPSLK.js} +2 -2
- package/dist/{chunk-45Y5TLQZ.js → chunk-SH7H3WRU.js} +3 -6
- package/dist/chunk-SH7H3WRU.js.map +1 -0
- package/dist/{chunk-VRLMTOB6.js → chunk-TIMIKBY2.js} +1 -1
- package/dist/chunk-TIMIKBY2.js.map +1 -0
- package/dist/{chunk-POHBQUG7.js → chunk-X3F5QDER.js} +1224 -448
- package/dist/chunk-X3F5QDER.js.map +1 -0
- package/dist/{chunk-O6OTJI3W.js → chunk-Y32FM3MR.js} +2 -2
- package/dist/{chunk-G5PRBQIQ.js → chunk-YOKQ25IW.js} +102 -82
- package/dist/chunk-YOKQ25IW.js.map +1 -0
- package/dist/{chunk-JAKMZI5S.js → chunk-Z2P4WST6.js} +291 -180
- package/dist/chunk-Z2P4WST6.js.map +1 -0
- package/dist/cli/create-agent.js +1 -1
- package/dist/cli/main.js +113 -86
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/yolo-daemon.js +19 -19
- package/dist/cli/yolo-daemon.js.map +1 -1
- package/dist/{client-BZHI675W.js → client-JTU5TRLB.js} +3 -3
- package/dist/{codebase-index-CR6Q2HEI.js → codebase-index-FNJ4GCBE.js} +3 -3
- package/dist/{goal-manager-FAK7H4RR.js → goal-manager-6BJQ36AH.js} +7 -8
- package/dist/goal-validator-GISXYANK.js +22 -0
- package/dist/{graph-PAUZ5EMP.js → graph-X2FMRQLG.js} +3 -3
- package/dist/{hypothesis-L5446W36.js → hypothesis-K3KQJOXJ.js} +7 -8
- package/dist/{incident-index-ZCDSJ42L.js → incident-index-BWW2UEY7.js} +3 -3
- package/dist/index.js +343 -288
- package/dist/index.js.map +1 -1
- package/dist/{insight-store-F5KDBY5Y.js → insight-store-A5XXMFD6.js} +6 -6
- package/dist/issue-store-BO5OWLJW.js +32 -0
- package/dist/{output-manager-BOTMXSND.js → output-manager-DZO5LGSG.js} +2 -2
- package/dist/{tiered-storage-QW2G7GSG.js → tiered-storage-VZL7KK64.js} +3 -3
- package/dist/trie-agent-XMSGMD7E.js +26 -0
- package/dist/trie-agent-XMSGMD7E.js.map +1 -0
- package/dist/ui/chat.html +260 -67
- package/dist/ui/goals.html +246 -3
- package/dist/ui/hypotheses.html +248 -5
- package/dist/ui/ledger.html +252 -9
- package/dist/ui/nudges.html +244 -1
- package/package.json +1 -1
- package/dist/chunk-45Y5TLQZ.js.map +0 -1
- package/dist/chunk-4YJ6KLGI.js.map +0 -1
- package/dist/chunk-6OUWNVLX.js.map +0 -1
- package/dist/chunk-6VIMBFUZ.js.map +0 -1
- package/dist/chunk-AJ34GCMD.js.map +0 -1
- package/dist/chunk-DFHMB44X.js.map +0 -1
- package/dist/chunk-DFPVUMVE.js.map +0 -1
- package/dist/chunk-G5PRBQIQ.js.map +0 -1
- package/dist/chunk-JAKMZI5S.js.map +0 -1
- package/dist/chunk-PEJEYWVR.js +0 -135
- package/dist/chunk-PEJEYWVR.js.map +0 -1
- package/dist/chunk-POHBQUG7.js.map +0 -1
- package/dist/chunk-T4THB2OR.js.map +0 -1
- package/dist/chunk-UHMMANC2.js.map +0 -1
- package/dist/chunk-UHX4462X.js.map +0 -1
- package/dist/chunk-VRLMTOB6.js.map +0 -1
- package/dist/chunk-WS6OA7H6.js.map +0 -1
- package/dist/chunk-Z4DN527J.js.map +0 -1
- package/dist/goal-validator-PDKYZSNP.js.map +0 -1
- package/dist/guardian-agent-4RHGIXUD.js +0 -27
- package/dist/ledger-WKVJWHBX.js +0 -17
- /package/dist/{autonomy-config-QA6ATWLJ.js.map → autonomy-config-TZ6HF4FA.js.map} +0 -0
- /package/dist/{chat-store-HFOOWZYN.js.map → chat-store-OJLJCJFI.js.map} +0 -0
- /package/dist/{chunk-WHIQAGB7.js.map → chunk-4C67GV3O.js.map} +0 -0
- /package/dist/{chunk-ZEXMMTIQ.js.map → chunk-J5EMP4XW.js.map} +0 -0
- /package/dist/{chunk-55CBWOEZ.js.map → chunk-QSWUPSLK.js.map} +0 -0
- /package/dist/{chunk-O6OTJI3W.js.map → chunk-Y32FM3MR.js.map} +0 -0
- /package/dist/{client-BZHI675W.js.map → client-JTU5TRLB.js.map} +0 -0
- /package/dist/{codebase-index-CR6Q2HEI.js.map → codebase-index-FNJ4GCBE.js.map} +0 -0
- /package/dist/{goal-manager-FAK7H4RR.js.map → goal-manager-6BJQ36AH.js.map} +0 -0
- /package/dist/{graph-PAUZ5EMP.js.map → goal-validator-GISXYANK.js.map} +0 -0
- /package/dist/{guardian-agent-4RHGIXUD.js.map → graph-X2FMRQLG.js.map} +0 -0
- /package/dist/{hypothesis-L5446W36.js.map → hypothesis-K3KQJOXJ.js.map} +0 -0
- /package/dist/{incident-index-ZCDSJ42L.js.map → incident-index-BWW2UEY7.js.map} +0 -0
- /package/dist/{insight-store-F5KDBY5Y.js.map → insight-store-A5XXMFD6.js.map} +0 -0
- /package/dist/{ledger-WKVJWHBX.js.map → issue-store-BO5OWLJW.js.map} +0 -0
- /package/dist/{output-manager-BOTMXSND.js.map → output-manager-DZO5LGSG.js.map} +0 -0
- /package/dist/{tiered-storage-QW2G7GSG.js.map → tiered-storage-VZL7KK64.js.map} +0 -0
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ The agent learns from every incident, decision, and fix. When you ship, Trie rem
|
|
|
18
18
|
|
|
19
19
|
## Goal
|
|
20
20
|
|
|
21
|
-
**Trie is your personal AI agent that
|
|
21
|
+
**Trie is your personal AI agent that carries context across tools.**
|
|
22
22
|
|
|
23
23
|
Every bug you fix teaches the agent a pattern that protects all your projects. Every incident you report becomes institutional knowledge that travels with your code. Every decision you make—and the tradeoffs you considered—gets extracted and stored automatically by the agent.
|
|
24
24
|
|
|
@@ -108,12 +108,12 @@ Trie remembers your decisions across tools, tracks the tradeoffs you've made, an
|
|
|
108
108
|
|
|
109
109
|
## How It Works
|
|
110
110
|
|
|
111
|
-
### The
|
|
111
|
+
### The Trie Agent
|
|
112
112
|
|
|
113
|
-
Trie uses a
|
|
113
|
+
Trie uses a proactive agent architecture:
|
|
114
114
|
|
|
115
115
|
- Analyzes your code for potential issues based on your incident history.
|
|
116
|
-
-
|
|
116
|
+
- The agent decides what matters based on your history.
|
|
117
117
|
- Memory Tree stores incident patterns using a trie data structure for fast lookups.
|
|
118
118
|
- Learning Loop improves predictions based on your feedback.
|
|
119
119
|
|
|
@@ -312,7 +312,7 @@ trie watch
|
|
|
312
312
|
# Reattach with: tmux attach -t trie-watch
|
|
313
313
|
```
|
|
314
314
|
|
|
315
|
-
This allows you to keep the
|
|
315
|
+
This allows you to keep the agent running while using other terminals. The interactive dashboard will be available when you reattach.
|
|
316
316
|
|
|
317
317
|
### JIT Defect Prediction ("Gotchas")
|
|
318
318
|
|
|
@@ -635,7 +635,7 @@ What happens at the 10,000 issue cap: Trie will deduplicate new repeats, compact
|
|
|
635
635
|
|
|
636
636
|
**Hooks not working**: Reinstall with `trie init`. Make sure you have write permissions to .git/hooks/.
|
|
637
637
|
|
|
638
|
-
**Goals showing but not detecting violations**: This happens when goals appear in the UI but aren't active in
|
|
638
|
+
**Goals showing but not detecting violations**: This happens when goals appear in the UI but aren't active in project state. The Goals view will show a yellow warning box. To fix:
|
|
639
639
|
```bash
|
|
640
640
|
# Quick fix - activate a specific goal
|
|
641
641
|
node fix-goals.js "NO EMOJIS"
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
shouldAutoFix,
|
|
11
11
|
shouldBlockPush,
|
|
12
12
|
trackIssueOccurrence
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-J5EMP4XW.js";
|
|
14
|
+
import "./chunk-SH7H3WRU.js";
|
|
15
15
|
import "./chunk-APMV77PU.js";
|
|
16
16
|
import "./chunk-DGUM43GV.js";
|
|
17
17
|
export {
|
|
@@ -27,4 +27,4 @@ export {
|
|
|
27
27
|
shouldBlockPush,
|
|
28
28
|
trackIssueOccurrence
|
|
29
29
|
};
|
|
30
|
-
//# sourceMappingURL=autonomy-config-
|
|
30
|
+
//# sourceMappingURL=autonomy-config-TZ6HF4FA.js.map
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
ChatStore,
|
|
3
3
|
clearChatStores,
|
|
4
4
|
getChatStore
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-23RJT5WT.js";
|
|
6
6
|
import "./chunk-43X6JBEM.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-SH7H3WRU.js";
|
|
8
8
|
import "./chunk-APMV77PU.js";
|
|
9
9
|
import "./chunk-DGUM43GV.js";
|
|
10
10
|
export {
|
|
@@ -12,4 +12,4 @@ export {
|
|
|
12
12
|
clearChatStores,
|
|
13
13
|
getChatStore
|
|
14
14
|
};
|
|
15
|
-
//# sourceMappingURL=chat-store-
|
|
15
|
+
//# sourceMappingURL=chat-store-OJLJCJFI.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-43X6JBEM.js";
|
|
4
4
|
import {
|
|
5
5
|
getTrieDirectory
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SH7H3WRU.js";
|
|
7
7
|
|
|
8
8
|
// src/cli/dashboard/chat-store.ts
|
|
9
9
|
import { mkdir, readFile, unlink } from "fs/promises";
|
|
@@ -20,11 +20,12 @@ var ChatMessageSchema = z.object({
|
|
|
20
20
|
input: z.record(z.any())
|
|
21
21
|
})).optional(),
|
|
22
22
|
pendingFix: z.object({
|
|
23
|
-
id: z.string(),
|
|
23
|
+
id: z.string().optional(),
|
|
24
24
|
file: z.string(),
|
|
25
25
|
goal: z.string(),
|
|
26
26
|
violation: z.string(),
|
|
27
|
-
suggestedFix: z.string().optional()
|
|
27
|
+
suggestedFix: z.string().optional(),
|
|
28
|
+
directory: z.string().optional()
|
|
28
29
|
}).optional()
|
|
29
30
|
});
|
|
30
31
|
var ChatSessionSchema = z.object({
|
|
@@ -344,4 +345,4 @@ export {
|
|
|
344
345
|
getChatStore,
|
|
345
346
|
clearChatStores
|
|
346
347
|
};
|
|
347
|
-
//# sourceMappingURL=chunk-
|
|
348
|
+
//# sourceMappingURL=chunk-23RJT5WT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/dashboard/chat-store.ts"],"sourcesContent":["/**\n * Chat Store - Persistent storage for chat history\n * \n * Features:\n * - Save and load chat sessions\n * - List all chat sessions\n * - Archive management (rename, delete, export)\n * - Atomic writes with backup rotation\n * - Zod validation for data integrity\n * \n * Persists to: .trie/memory/chats/\n */\n\nimport { mkdir, readFile, unlink } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { getTrieDirectory } from '../../utils/workspace.js';\nimport { z } from 'zod';\nimport { atomicWriteJSON } from '../../utils/atomic-write.js';\nimport type { ChatMessage } from './types.js';\n\n// ============================================================================\n// Schemas\n// ============================================================================\n\nconst ChatMessageSchema = z.object({\n role: z.enum(['user', 'assistant']),\n content: z.string(),\n timestamp: z.number(),\n toolCalls: z.array(z.object({\n id: z.string(),\n name: z.string(),\n input: z.record(z.any()),\n })).optional(),\n pendingFix: z.object({\n id: z.string().optional(),\n file: z.string(),\n goal: z.string(),\n violation: z.string(),\n suggestedFix: z.string().optional(),\n directory: z.string().optional(),\n }).optional(),\n});\n\nconst ChatSessionSchema = z.object({\n id: z.string(),\n title: z.string(),\n messages: z.array(ChatMessageSchema),\n createdAt: z.number(),\n updatedAt: z.number(),\n archived: z.boolean(),\n});\n\nexport type ChatSession = z.infer<typeof ChatSessionSchema>;\n\nconst ChatStoreIndexSchema = z.object({\n version: z.literal(1),\n sessions: z.array(z.object({\n id: z.string(),\n title: z.string(),\n createdAt: z.number(),\n updatedAt: z.number(),\n archived: z.boolean(),\n messageCount: z.number(),\n })),\n lastUpdated: z.string(),\n});\n\ntype ChatStoreIndex = z.infer<typeof ChatStoreIndexSchema>;\n\n// ============================================================================\n// ChatStore Class\n// ============================================================================\n\nexport class ChatStore {\n private projectPath: string;\n private index: ChatStoreIndex | null = null;\n \n constructor(projectPath: string) {\n this.projectPath = projectPath;\n }\n \n /**\n * Get the chats directory path\n */\n private getChatsDir(): string {\n return join(getTrieDirectory(this.projectPath), 'memory', 'chats');\n }\n \n /**\n * Get the index file path\n */\n private getIndexPath(): string {\n return join(this.getChatsDir(), 'index.json');\n }\n \n /**\n * Get the path for a specific session file\n */\n private getSessionPath(sessionId: string): string {\n return join(this.getChatsDir(), `${sessionId}.json`);\n }\n \n /**\n * Load the index file\n */\n private async loadIndex(): Promise<ChatStoreIndex> {\n if (this.index) {\n return this.index;\n }\n \n const indexPath = this.getIndexPath();\n \n if (existsSync(indexPath)) {\n try {\n const content = await readFile(indexPath, 'utf-8');\n const parsed = JSON.parse(content);\n const result = ChatStoreIndexSchema.safeParse(parsed);\n \n if (result.success) {\n this.index = result.data;\n return this.index;\n }\n } catch (error) {\n console.error('Failed to load chat index:', error);\n }\n }\n \n // Create empty index\n this.index = {\n version: 1,\n sessions: [],\n lastUpdated: new Date().toISOString(),\n };\n \n return this.index;\n }\n \n /**\n * Save the index file\n */\n private async saveIndex(): Promise<void> {\n if (!this.index) return;\n \n const indexPath = this.getIndexPath();\n const chatsDir = this.getChatsDir();\n \n // Ensure directory exists\n await mkdir(chatsDir, { recursive: true });\n \n // Update timestamp\n this.index.lastUpdated = new Date().toISOString();\n \n // Atomic write\n await atomicWriteJSON(indexPath, this.index);\n }\n \n /**\n * Generate a unique session ID\n */\n private generateSessionId(): string {\n return `chat-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n }\n \n /**\n * Generate a default title from the first user message\n */\n private generateTitle(messages: ChatMessage[]): string {\n const firstUser = messages.find(m => m.role === 'user');\n if (firstUser) {\n const text = firstUser.content.trim();\n const maxLen = 50;\n if (text.length > maxLen) {\n return text.slice(0, maxLen) + '...';\n }\n return text;\n }\n return 'New Chat';\n }\n \n /**\n * Save a chat session\n */\n async saveSession(messages: ChatMessage[], sessionId?: string, title?: string): Promise<string> {\n await this.loadIndex();\n \n const id = sessionId || this.generateSessionId();\n const now = Date.now();\n \n // Load existing session if updating\n let createdAt = now;\n if (sessionId) {\n const existing = this.index!.sessions.find(s => s.id === sessionId);\n if (existing) {\n createdAt = existing.createdAt;\n }\n }\n \n const session: ChatSession = {\n id,\n title: title || this.generateTitle(messages),\n messages,\n createdAt,\n updatedAt: now,\n archived: false,\n };\n \n // Validate\n const result = ChatSessionSchema.safeParse(session);\n if (!result.success) {\n throw new Error(`Invalid chat session: ${result.error.message}`);\n }\n \n // Save session file\n const sessionPath = this.getSessionPath(id);\n await mkdir(this.getChatsDir(), { recursive: true });\n await atomicWriteJSON(sessionPath, session);\n \n // Update index\n const existingIndex = this.index!.sessions.findIndex(s => s.id === id);\n const indexEntry = {\n id,\n title: session.title,\n createdAt: session.createdAt,\n updatedAt: session.updatedAt,\n archived: session.archived,\n messageCount: messages.length,\n };\n \n if (existingIndex >= 0) {\n this.index!.sessions[existingIndex] = indexEntry;\n } else {\n this.index!.sessions.unshift(indexEntry);\n }\n \n await this.saveIndex();\n \n return id;\n }\n \n /**\n * Load a chat session\n */\n async loadSession(sessionId: string): Promise<ChatSession | null> {\n const sessionPath = this.getSessionPath(sessionId);\n \n if (!existsSync(sessionPath)) {\n return null;\n }\n \n try {\n const content = await readFile(sessionPath, 'utf-8');\n const parsed = JSON.parse(content);\n const result = ChatSessionSchema.safeParse(parsed);\n \n if (result.success) {\n return result.data;\n }\n \n console.error('Invalid chat session:', result.error.message);\n return null;\n } catch (error) {\n console.error('Failed to load chat session:', error);\n return null;\n }\n }\n \n /**\n * List all chat sessions\n */\n async listSessions(includeArchived: boolean = false): Promise<ChatStoreIndex['sessions']> {\n await this.loadIndex();\n \n let sessions = this.index!.sessions;\n \n if (!includeArchived) {\n sessions = sessions.filter(s => !s.archived);\n }\n \n // Sort by most recent first\n return sessions.sort((a, b) => b.updatedAt - a.updatedAt);\n }\n \n /**\n * Delete a chat session\n */\n async deleteSession(sessionId: string): Promise<boolean> {\n await this.loadIndex();\n \n const sessionPath = this.getSessionPath(sessionId);\n \n if (!existsSync(sessionPath)) {\n return false;\n }\n \n try {\n await unlink(sessionPath);\n \n // Remove from index\n this.index!.sessions = this.index!.sessions.filter(s => s.id !== sessionId);\n await this.saveIndex();\n \n return true;\n } catch (error) {\n console.error('Failed to delete chat session:', error);\n return false;\n }\n }\n \n /**\n * Rename a chat session\n */\n async renameSession(sessionId: string, newTitle: string): Promise<boolean> {\n await this.loadIndex();\n \n const session = await this.loadSession(sessionId);\n if (!session) {\n return false;\n }\n \n session.title = newTitle;\n session.updatedAt = Date.now();\n \n // Save updated session\n const sessionPath = this.getSessionPath(sessionId);\n await atomicWriteJSON(sessionPath, session);\n \n // Update index\n const indexEntry = this.index!.sessions.find(s => s.id === sessionId);\n if (indexEntry) {\n indexEntry.title = newTitle;\n indexEntry.updatedAt = session.updatedAt;\n await this.saveIndex();\n }\n \n return true;\n }\n \n /**\n * Archive a chat session\n */\n async archiveSession(sessionId: string): Promise<boolean> {\n await this.loadIndex();\n \n const session = await this.loadSession(sessionId);\n if (!session) {\n return false;\n }\n \n session.archived = true;\n session.updatedAt = Date.now();\n \n // Save updated session\n const sessionPath = this.getSessionPath(sessionId);\n await atomicWriteJSON(sessionPath, session);\n \n // Update index\n const indexEntry = this.index!.sessions.find(s => s.id === sessionId);\n if (indexEntry) {\n indexEntry.archived = true;\n indexEntry.updatedAt = session.updatedAt;\n await this.saveIndex();\n }\n \n return true;\n }\n \n /**\n * Unarchive a chat session\n */\n async unarchiveSession(sessionId: string): Promise<boolean> {\n await this.loadIndex();\n \n const session = await this.loadSession(sessionId);\n if (!session) {\n return false;\n }\n \n session.archived = false;\n session.updatedAt = Date.now();\n \n // Save updated session\n const sessionPath = this.getSessionPath(sessionId);\n await atomicWriteJSON(sessionPath, session);\n \n // Update index\n const indexEntry = this.index!.sessions.find(s => s.id === sessionId);\n if (indexEntry) {\n indexEntry.archived = false;\n indexEntry.updatedAt = session.updatedAt;\n await this.saveIndex();\n }\n \n return true;\n }\n \n /**\n * Export a chat session as JSON\n */\n async exportSession(sessionId: string): Promise<string | null> {\n const session = await this.loadSession(sessionId);\n if (!session) {\n return null;\n }\n \n return JSON.stringify(session, null, 2);\n }\n \n /**\n * Get statistics about chat sessions\n */\n async getStats(): Promise<{\n total: number;\n active: number;\n archived: number;\n totalMessages: number;\n }> {\n await this.loadIndex();\n \n const total = this.index!.sessions.length;\n const archived = this.index!.sessions.filter(s => s.archived).length;\n const active = total - archived;\n const totalMessages = this.index!.sessions.reduce((sum, s) => sum + s.messageCount, 0);\n \n return { total, active, archived, totalMessages };\n }\n \n /**\n * Prune old sessions (older than N days)\n */\n async pruneOldSessions(daysToKeep: number = 90): Promise<number> {\n await this.loadIndex();\n \n const cutoff = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);\n const toDelete = this.index!.sessions.filter(s => s.updatedAt < cutoff);\n \n for (const session of toDelete) {\n await this.deleteSession(session.id);\n }\n \n return toDelete.length;\n }\n}\n\n// ============================================================================\n// Singleton Management\n// ============================================================================\n\nconst chatStores: Map<string, ChatStore> = new Map();\n\n/**\n * Get the ChatStore for a project (singleton per project)\n */\nexport function getChatStore(projectPath: string): ChatStore {\n let store = chatStores.get(projectPath);\n if (!store) {\n store = new ChatStore(projectPath);\n chatStores.set(projectPath, store);\n }\n return store;\n}\n\n/**\n * Clear all ChatStore instances (for testing)\n */\nexport function clearChatStores(): void {\n chatStores.clear();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BackupManager,
|
|
3
3
|
safeParseAndValidate
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4MJ52WBH.js";
|
|
5
5
|
import {
|
|
6
6
|
atomicWriteJSON
|
|
7
7
|
} from "./chunk-43X6JBEM.js";
|
|
8
8
|
import {
|
|
9
9
|
getTrieDirectory
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-SH7H3WRU.js";
|
|
11
11
|
|
|
12
|
-
// src/
|
|
12
|
+
// src/agent/insight-store.ts
|
|
13
13
|
import { mkdir, readFile } from "fs/promises";
|
|
14
14
|
import { existsSync } from "fs";
|
|
15
15
|
import { join } from "path";
|
|
@@ -37,7 +37,7 @@ var InsightDetailsSchema = z.object({
|
|
|
37
37
|
patternsCount: z.number().optional(),
|
|
38
38
|
topPattern: z.string().optional()
|
|
39
39
|
});
|
|
40
|
-
var
|
|
40
|
+
var InsightSchema = z.object({
|
|
41
41
|
id: z.string(),
|
|
42
42
|
type: z.enum(["observation", "warning", "suggestion", "celebration", "question"]),
|
|
43
43
|
message: z.string(),
|
|
@@ -53,7 +53,7 @@ var GuardianInsightSchema = z.object({
|
|
|
53
53
|
});
|
|
54
54
|
var InsightStoreDataSchema = z.object({
|
|
55
55
|
version: z.literal(1),
|
|
56
|
-
insights: z.array(
|
|
56
|
+
insights: z.array(InsightSchema),
|
|
57
57
|
cooldowns: z.record(z.string(), z.number()),
|
|
58
58
|
// insightKey -> timestamp
|
|
59
59
|
dismissedIds: z.array(z.string()),
|
|
@@ -96,7 +96,7 @@ var InsightStore = class _InsightStore {
|
|
|
96
96
|
* Get the storage file path
|
|
97
97
|
*/
|
|
98
98
|
getStorePath() {
|
|
99
|
-
return join(getTrieDirectory(this.projectPath), "memory", "
|
|
99
|
+
return join(getTrieDirectory(this.projectPath), "memory", "insights.json");
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
102
|
* Create empty data structure
|
|
@@ -431,10 +431,10 @@ function clearInsightStores() {
|
|
|
431
431
|
|
|
432
432
|
export {
|
|
433
433
|
InsightDetailsSchema,
|
|
434
|
-
|
|
434
|
+
InsightSchema,
|
|
435
435
|
InsightStoreDataSchema,
|
|
436
436
|
InsightStore,
|
|
437
437
|
getInsightStore,
|
|
438
438
|
clearInsightStores
|
|
439
439
|
};
|
|
440
|
-
//# sourceMappingURL=chunk-
|
|
440
|
+
//# sourceMappingURL=chunk-3MUCUZ46.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agent/insight-store.ts"],"sourcesContent":["/**\n * Insight Store - Persistent storage for insights\n * \n * Persists to: .trie/memory/insights.json\n * \n * Features:\n * - Insight persistence across restarts\n * - Cooldown state persistence (prevents duplicate insights)\n * - Dismissed insight tracking\n * - Atomic writes with backup rotation\n * - Zod validation for data integrity\n */\n\nimport { mkdir, readFile } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { getTrieDirectory } from '../utils/workspace.js';\nimport { z } from 'zod';\nimport { atomicWriteJSON } from '../utils/atomic-write.js';\nimport { BackupManager } from '../utils/backup-manager.js';\nimport { safeParseAndValidate } from '../memory/validation.js';\n\n// ============================================================================\n// Schemas\n// ============================================================================\n\n/**\n * Schema for insight details\n */\nexport const InsightDetailsSchema = z.object({\n affectedFiles: z.array(z.string()).optional(),\n issueBreakdown: z.record(z.string(), z.number()).optional(),\n examples: z.array(z.string()).optional(),\n trend: z.enum(['improving', 'stable', 'worsening']).optional(),\n comparison: z.string().optional(),\n resolvedCount: z.number().optional(),\n resolvedIssues: z.array(z.object({\n file: z.string(),\n line: z.number().optional(),\n issue: z.string(),\n agent: z.string(),\n resolvedAt: z.string().optional(),\n })).optional(),\n summary: z.string().optional(),\n // Hypothesis-related fields\n hypothesis: z.string().optional(),\n testCriteria: z.string().optional(),\n confidence: z.number().optional(),\n // Pattern-related fields\n patternsCount: z.number().optional(),\n topPattern: z.string().optional(),\n});\n\n/**\n * Schema for an insight\n */\nexport const InsightSchema = z.object({\n id: z.string(),\n type: z.enum(['observation', 'warning', 'suggestion', 'celebration', 'question']),\n message: z.string(),\n context: z.string().optional(),\n suggestedAction: z.string().optional(),\n actionCommand: z.string().optional(),\n relatedIssues: z.array(z.string()),\n priority: z.number().min(1).max(10),\n timestamp: z.number(),\n dismissed: z.boolean(),\n category: z.enum(['security', 'quality', 'performance', 'pattern', 'progress', 'general']),\n details: InsightDetailsSchema.optional(),\n});\n\nexport type Insight = z.infer<typeof InsightSchema>;\n\n/**\n * Schema for the entire insight store file\n */\nexport const InsightStoreDataSchema = z.object({\n version: z.literal(1),\n insights: z.array(InsightSchema),\n cooldowns: z.record(z.string(), z.number()), // insightKey -> timestamp\n dismissedIds: z.array(z.string()), // Track dismissed insight IDs permanently\n lastUpdated: z.string(),\n});\n\nexport type InsightStoreData = z.infer<typeof InsightStoreDataSchema>;\n\n// ============================================================================\n// InsightStore Class\n// ============================================================================\n\n/**\n * Persistent store for insights\n * \n * Usage:\n * ```typescript\n * const store = new InsightStore(projectPath);\n * await store.load();\n * \n * await store.addInsight(insight);\n * const active = store.getActiveInsights();\n * await store.dismissInsight(insightId);\n * ```\n */\nexport class InsightStore {\n private projectPath: string;\n private data: InsightStoreData;\n private loaded: boolean = false;\n private dirty: boolean = false;\n \n // Default cooldown periods (in ms)\n static readonly COOLDOWNS: Record<string, number> = {\n 'pre-push-warning': 60000, // 1 min between pre-push warnings\n 'security-warning': 30000, // 30s between security warnings\n 'new-issues': 30000, // 30s between new issue observations\n 'celebration': 60000, // 1 min between celebrations\n 'pattern-suggestion': 120000, // 2 min between pattern suggestions\n 'accessibility-visual-qa': 300000, // 5 min between visual QA suggestions\n 'goal-suggestion': 300000, // 5 min between goal suggestions\n 'risk-prediction': 180000, // 3 min between risk predictions\n 'hypothesis-update': 600000, // 10 min between hypothesis updates\n 'auto-escalation': 300000, // 5 min between auto-escalations\n };\n \n constructor(projectPath: string) {\n this.projectPath = projectPath;\n this.data = this.createEmptyData();\n }\n \n /**\n * Get the storage file path\n */\n private getStorePath(): string {\n return join(getTrieDirectory(this.projectPath), 'memory', 'insights.json');\n }\n \n /**\n * Create empty data structure\n */\n private createEmptyData(): InsightStoreData {\n return {\n version: 1,\n insights: [],\n cooldowns: {},\n dismissedIds: [],\n lastUpdated: new Date().toISOString(),\n };\n }\n \n /**\n * Load insights from disk\n * \n * If the file is corrupted, attempts recovery from backup.\n * Returns empty data if no valid file/backup exists.\n */\n async load(): Promise<InsightStoreData> {\n if (this.loaded) {\n return this.data;\n }\n \n const storePath = this.getStorePath();\n \n try {\n if (existsSync(storePath)) {\n const content = await readFile(storePath, 'utf-8');\n const result = safeParseAndValidate(content, InsightStoreDataSchema);\n \n if (result.success) {\n this.data = result.data;\n this.loaded = true;\n // Deduplicate any existing duplicate insights\n this.deduplicateInsights();\n return this.data;\n }\n \n // Validation failed - attempt recovery from backup\n console.error(` Insight store corrupted: ${result.error}`);\n const backupManager = new BackupManager(storePath);\n \n if (await backupManager.recoverFromBackup()) {\n console.error(' Recovered from backup');\n const recovered = await readFile(storePath, 'utf-8');\n const recoveredResult = safeParseAndValidate(recovered, InsightStoreDataSchema);\n if (recoveredResult.success) {\n this.data = recoveredResult.data;\n this.loaded = true;\n // Deduplicate any existing duplicate insights\n this.deduplicateInsights();\n return this.data;\n }\n }\n \n console.error(' No valid backup found, starting fresh');\n }\n } catch (error) {\n // File doesn't exist or recovery failed - start fresh\n console.error(` Could not load insight store: ${error}`);\n }\n \n this.data = this.createEmptyData();\n this.loaded = true;\n return this.data;\n }\n \n /**\n * Deduplicate existing insights on load\n * Keeps the most recent instance of each unique insight\n */\n private deduplicateInsights(): void {\n const seen = new Map<string, number>(); // contentKey -> index of first occurrence\n const toRemove: number[] = [];\n \n // Process from newest to oldest (insights are sorted by recency)\n for (let i = 0; i < this.data.insights.length; i++) {\n const insight = this.data.insights[i];\n if (!insight) continue;\n \n const contentKey = this.getContentKey(insight);\n \n if (seen.has(contentKey)) {\n // This is a duplicate - mark for removal\n toRemove.push(i);\n } else {\n seen.set(contentKey, i);\n }\n }\n \n // Remove duplicates (reverse order to preserve indices)\n if (toRemove.length > 0) {\n for (let i = toRemove.length - 1; i >= 0; i--) {\n const idx = toRemove[i];\n if (idx !== undefined) {\n this.data.insights.splice(idx, 1);\n }\n }\n this.dirty = true;\n }\n }\n \n /**\n * Save insights to disk\n * \n * Creates backup before writing, uses atomic write.\n */\n async save(): Promise<void> {\n if (!this.dirty && this.loaded) {\n return; // No changes to save\n }\n \n const storePath = this.getStorePath();\n const memoryDir = join(getTrieDirectory(this.projectPath), 'memory');\n \n // Ensure directory exists\n await mkdir(memoryDir, { recursive: true });\n \n // Create backup before writing\n const backupManager = new BackupManager(storePath);\n await backupManager.createBackup();\n \n // Update timestamp\n this.data.lastUpdated = new Date().toISOString();\n \n // Atomic write\n await atomicWriteJSON(storePath, this.data);\n \n this.dirty = false;\n }\n \n /**\n * Generate a content-based key for deduplication\n * Insights with the same content key are considered duplicates\n */\n private getContentKey(insight: Insight): string {\n // Normalize the message by removing specific numbers/counts that might vary slightly\n // e.g., \"Found 30 security issues\" and \"Found 31 security issues\" are the same insight\n const normalizedMessage = insight.message\n .replace(/\\d+/g, 'N') // Replace all numbers with N\n .toLowerCase()\n .trim();\n \n return `${insight.type}:${insight.category}:${normalizedMessage}`;\n }\n \n /**\n * Add an insight to the store\n * \n * Checks for duplicates using both insight ID and content similarity.\n * If a similar insight already exists (same type, category, and normalized message),\n * updates its timestamp instead of creating a duplicate.\n * Respects cooldowns to prevent insight spam.\n */\n async addInsight(insight: Insight): Promise<boolean> {\n await this.load();\n \n // Check if already exists by ID\n if (this.data.insights.some(i => i.id === insight.id)) {\n return false;\n }\n \n // Check if ID was previously dismissed (don't re-add)\n if (this.data.dismissedIds.includes(insight.id)) {\n return false;\n }\n \n // Check for content-based duplicates (same type, category, similar message)\n const contentKey = this.getContentKey(insight);\n const existingIndex = this.data.insights.findIndex(i => \n !i.dismissed && this.getContentKey(i) === contentKey\n );\n \n if (existingIndex >= 0) {\n // Update existing insight instead of adding duplicate\n const existing = this.data.insights[existingIndex];\n if (existing) {\n // Update with new data but keep the original ID\n existing.timestamp = insight.timestamp;\n existing.message = insight.message; // Update with current counts\n existing.details = insight.details; // Update details\n existing.relatedIssues = insight.relatedIssues;\n existing.suggestedAction = insight.suggestedAction;\n \n // Move to front of list\n this.data.insights.splice(existingIndex, 1);\n this.data.insights.unshift(existing);\n \n this.dirty = true;\n await this.save();\n }\n return false; // Return false to indicate no new insight was created\n }\n \n // Add to front of list\n this.data.insights.unshift(insight);\n \n // Keep only last 100 insights\n if (this.data.insights.length > 100) {\n this.data.insights = this.data.insights.slice(0, 100);\n }\n \n this.dirty = true;\n await this.save();\n \n return true;\n }\n \n /**\n * Check if a cooldown has expired for an insight type\n */\n canCreateInsight(insightKey: string): boolean {\n const lastTime = this.data.cooldowns[insightKey];\n const cooldown = InsightStore.COOLDOWNS[insightKey] || 30000;\n \n if (!lastTime) return true;\n return Date.now() - lastTime > cooldown;\n }\n \n /**\n * Mark that an insight type was created (set cooldown)\n */\n async markInsightCreated(insightKey: string): Promise<void> {\n await this.load();\n this.data.cooldowns[insightKey] = Date.now();\n this.dirty = true;\n await this.save();\n }\n \n /**\n * Get active (non-dismissed) insights\n * \n * Returns insights sorted by priority (highest first),\n * limited to the specified count.\n */\n getActiveInsights(limit: number = 5): Insight[] {\n return this.data.insights\n .filter(i => !i.dismissed)\n .sort((a, b) => b.priority - a.priority)\n .slice(0, limit);\n }\n \n /**\n * Get all insights (including dismissed)\n */\n getAllInsights(): Insight[] {\n return [...this.data.insights];\n }\n \n /**\n * Dismiss an insight by ID\n */\n async dismissInsight(insightId: string): Promise<boolean> {\n await this.load();\n \n const insight = this.data.insights.find(i => i.id === insightId);\n if (!insight) {\n return false;\n }\n \n insight.dismissed = true;\n \n // Track permanently dismissed IDs\n if (!this.data.dismissedIds.includes(insightId)) {\n this.data.dismissedIds.push(insightId);\n \n // Keep only last 500 dismissed IDs\n if (this.data.dismissedIds.length > 500) {\n this.data.dismissedIds = this.data.dismissedIds.slice(-500);\n }\n }\n \n this.dirty = true;\n await this.save();\n \n return true;\n }\n \n /**\n * Remove an insight entirely\n */\n async removeInsight(insightId: string): Promise<boolean> {\n await this.load();\n \n const index = this.data.insights.findIndex(i => i.id === insightId);\n if (index === -1) {\n return false;\n }\n \n this.data.insights.splice(index, 1);\n this.dirty = true;\n await this.save();\n \n return true;\n }\n \n /**\n * Clear all cooldowns\n */\n async clearCooldowns(): Promise<void> {\n await this.load();\n this.data.cooldowns = {};\n this.dirty = true;\n await this.save();\n }\n \n /**\n * Get insight by ID\n */\n getInsight(insightId: string): Insight | undefined {\n return this.data.insights.find(i => i.id === insightId);\n }\n \n /**\n * Update an existing insight\n */\n async updateInsight(insightId: string, updates: Partial<Insight>): Promise<boolean> {\n await this.load();\n \n const insight = this.data.insights.find(i => i.id === insightId);\n if (!insight) {\n return false;\n }\n \n Object.assign(insight, updates);\n this.dirty = true;\n await this.save();\n \n return true;\n }\n \n /**\n * Get insights by category\n */\n getInsightsByCategory(category: Insight['category']): Insight[] {\n return this.data.insights.filter(i => i.category === category && !i.dismissed);\n }\n \n /**\n * Get insights by type\n */\n getInsightsByType(type: Insight['type']): Insight[] {\n return this.data.insights.filter(i => i.type === type && !i.dismissed);\n }\n \n /**\n * Get insights from the last N hours\n */\n getRecentInsights(hours: number = 24): Insight[] {\n const cutoff = Date.now() - (hours * 60 * 60 * 1000);\n return this.data.insights.filter(i => i.timestamp >= cutoff);\n }\n \n /**\n * Get statistics about insights\n */\n getStats(): {\n total: number;\n active: number;\n dismissed: number;\n byCategory: Record<string, number>;\n byType: Record<string, number>;\n } {\n const stats = {\n total: this.data.insights.length,\n active: 0,\n dismissed: 0,\n byCategory: {} as Record<string, number>,\n byType: {} as Record<string, number>,\n };\n \n for (const insight of this.data.insights) {\n if (insight.dismissed) {\n stats.dismissed++;\n } else {\n stats.active++;\n }\n \n stats.byCategory[insight.category] = (stats.byCategory[insight.category] || 0) + 1;\n stats.byType[insight.type] = (stats.byType[insight.type] || 0) + 1;\n }\n \n return stats;\n }\n \n /**\n * Prune old insights (older than N days)\n */\n async pruneOldInsights(daysToKeep: number = 30): Promise<number> {\n await this.load();\n \n const cutoff = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);\n const originalCount = this.data.insights.length;\n \n this.data.insights = this.data.insights.filter(i => i.timestamp >= cutoff);\n \n const pruned = originalCount - this.data.insights.length;\n if (pruned > 0) {\n this.dirty = true;\n await this.save();\n }\n \n return pruned;\n }\n \n /**\n * Check if the store has been loaded\n */\n isLoaded(): boolean {\n return this.loaded;\n }\n \n /**\n * Force reload from disk\n */\n async reload(): Promise<InsightStoreData> {\n this.loaded = false;\n this.dirty = false;\n return this.load();\n }\n}\n\n// ============================================================================\n// Singleton Management\n// ============================================================================\n\nconst insightStores: Map<string, InsightStore> = new Map();\n\n/**\n * Get the InsightStore for a project (singleton per project)\n */\nexport function getInsightStore(projectPath: string): InsightStore {\n let store = insightStores.get(projectPath);\n if (!store) {\n store = new InsightStore(projectPath);\n insightStores.set(projectPath, store);\n }\n return store;\n}\n\n/**\n * Clear all InsightStore instances (for testing)\n */\nexport function clearInsightStores(): void {\n insightStores.clear();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -6,20 +6,18 @@ import {
|
|
|
6
6
|
} from "./chunk-43X6JBEM.js";
|
|
7
7
|
import {
|
|
8
8
|
getTrieDirectory
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import {
|
|
11
|
-
__require
|
|
12
|
-
} from "./chunk-DGUM43GV.js";
|
|
9
|
+
} from "./chunk-SH7H3WRU.js";
|
|
13
10
|
|
|
14
11
|
// src/context/codebase-index.ts
|
|
15
12
|
import { readFile, stat } from "fs/promises";
|
|
16
13
|
import { createHash } from "crypto";
|
|
17
14
|
import { join } from "path";
|
|
18
|
-
import { existsSync } from "fs";
|
|
15
|
+
import { existsSync, readFileSync } from "fs";
|
|
19
16
|
var CodebaseIndex = class {
|
|
20
17
|
trie = new Trie();
|
|
21
18
|
projectPath;
|
|
22
19
|
indexPath;
|
|
20
|
+
lastUpdated = null;
|
|
23
21
|
constructor(projectPath) {
|
|
24
22
|
this.projectPath = projectPath;
|
|
25
23
|
this.indexPath = join(getTrieDirectory(projectPath), "codebase-index.json");
|
|
@@ -31,10 +29,13 @@ var CodebaseIndex = class {
|
|
|
31
29
|
load() {
|
|
32
30
|
if (!existsSync(this.indexPath)) return;
|
|
33
31
|
try {
|
|
34
|
-
const raw = JSON.parse(
|
|
32
|
+
const raw = JSON.parse(readFileSync(this.indexPath, "utf-8"));
|
|
35
33
|
if (raw.trie) {
|
|
36
34
|
this.trie = Trie.fromJSON(raw.trie);
|
|
37
35
|
}
|
|
36
|
+
if (raw.lastUpdated) {
|
|
37
|
+
this.lastUpdated = raw.lastUpdated;
|
|
38
|
+
}
|
|
38
39
|
} catch (error) {
|
|
39
40
|
console.error("Failed to load codebase index:", error);
|
|
40
41
|
}
|
|
@@ -43,9 +44,10 @@ var CodebaseIndex = class {
|
|
|
43
44
|
* Save index to disk
|
|
44
45
|
*/
|
|
45
46
|
async save() {
|
|
47
|
+
this.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
46
48
|
const data = {
|
|
47
49
|
version: 1,
|
|
48
|
-
lastUpdated:
|
|
50
|
+
lastUpdated: this.lastUpdated,
|
|
49
51
|
fileCount: this.trie.getWithPrefix("").length,
|
|
50
52
|
trie: this.trie.toJSON()
|
|
51
53
|
};
|
|
@@ -76,8 +78,8 @@ var CodebaseIndex = class {
|
|
|
76
78
|
const existing = this.trie.search(filePath);
|
|
77
79
|
if (existing.found && existing.value) {
|
|
78
80
|
if (existing.value.hash === hash) {
|
|
79
|
-
metadata.violations = existing.value.violations;
|
|
80
|
-
metadata.lastScanned = existing.value.lastScanned;
|
|
81
|
+
if (existing.value.violations) metadata.violations = existing.value.violations;
|
|
82
|
+
if (existing.value.lastScanned != null) metadata.lastScanned = existing.value.lastScanned;
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
this.trie.insert(filePath, metadata);
|
|
@@ -133,7 +135,7 @@ var CodebaseIndex = class {
|
|
|
133
135
|
*/
|
|
134
136
|
getFilesByType(type) {
|
|
135
137
|
const allFiles = this.trie.getWithPrefix("");
|
|
136
|
-
return allFiles.map((m) => m.value).filter((v) => v
|
|
138
|
+
return allFiles.map((m) => m.value).filter((v) => v != null && v.type === type);
|
|
137
139
|
}
|
|
138
140
|
/**
|
|
139
141
|
* Record goal violation scan result for a file
|
|
@@ -144,14 +146,15 @@ var CodebaseIndex = class {
|
|
|
144
146
|
metadata.lastScanned = Date.now();
|
|
145
147
|
if (!metadata.violations) metadata.violations = [];
|
|
146
148
|
metadata.violations = metadata.violations.filter((v) => v.goalId !== goalId);
|
|
147
|
-
|
|
149
|
+
const entry = {
|
|
148
150
|
goalId,
|
|
149
151
|
goalDescription,
|
|
150
152
|
found,
|
|
151
|
-
details,
|
|
152
|
-
confidence,
|
|
153
153
|
timestamp: Date.now()
|
|
154
|
-
}
|
|
154
|
+
};
|
|
155
|
+
if (details !== void 0) entry.details = details;
|
|
156
|
+
if (confidence !== void 0) entry.confidence = confidence;
|
|
157
|
+
metadata.violations.push(entry);
|
|
155
158
|
this.trie.insert(filePath, metadata);
|
|
156
159
|
}
|
|
157
160
|
/**
|
|
@@ -181,7 +184,8 @@ var CodebaseIndex = class {
|
|
|
181
184
|
totalSize: allFiles.reduce((sum, f) => sum + f.size, 0),
|
|
182
185
|
filesByType: {},
|
|
183
186
|
scannedFiles: allFiles.filter((f) => f.lastScanned).length,
|
|
184
|
-
filesWithViolations: allFiles.filter((f) => f.violations && f.violations.some((v) => v.found)).length
|
|
187
|
+
filesWithViolations: allFiles.filter((f) => f.violations && f.violations.some((v) => v.found)).length,
|
|
188
|
+
lastUpdated: this.lastUpdated
|
|
185
189
|
};
|
|
186
190
|
for (const file of allFiles) {
|
|
187
191
|
stats.filesByType[file.type] = (stats.filesByType[file.type] || 0) + 1;
|
|
@@ -201,7 +205,7 @@ var CodebaseIndex = class {
|
|
|
201
205
|
file.violations = file.violations.filter((v) => v.timestamp > cutoff);
|
|
202
206
|
cleared += before - file.violations.length;
|
|
203
207
|
if (file.violations.length === 0) {
|
|
204
|
-
file.lastScanned
|
|
208
|
+
delete file.lastScanned;
|
|
205
209
|
}
|
|
206
210
|
this.trie.insert(file.path, file);
|
|
207
211
|
}
|
|
@@ -213,4 +217,4 @@ var CodebaseIndex = class {
|
|
|
213
217
|
export {
|
|
214
218
|
CodebaseIndex
|
|
215
219
|
};
|
|
216
|
-
//# sourceMappingURL=chunk-
|
|
220
|
+
//# sourceMappingURL=chunk-3RRXWX3V.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context/codebase-index.ts"],"sourcesContent":["/**\n * Codebase Index - Fast file indexing for goal violation detection\n * \n * Uses Trie structure for efficient file lookup and caching to avoid\n * re-scanning unchanged files.\n * \n * For large codebases, this dramatically improves goal check performance:\n * - Indexes file hashes to detect changes\n * - Caches AI analysis results\n * - Uses Trie for O(m) lookups where m = path length\n * - Stores file metadata (size, type, last modified, etc.)\n */\n\nimport { readFile, stat } from 'fs/promises';\nimport { createHash } from 'crypto';\nimport { join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\nimport { Trie } from '../trie/trie.js';\nimport { getTrieDirectory } from '../utils/workspace.js';\nimport { atomicWriteJSON } from '../utils/atomic-write.js';\n\nexport interface FileMetadata {\n path: string;\n hash: string; // SHA-256 of content\n size: number;\n lastModified: number;\n type: string; // Extension\n lastScanned?: number; // Timestamp of last AI scan\n violations?: {\n goalId: string;\n goalDescription: string;\n found: boolean;\n details?: string;\n confidence?: number;\n timestamp: number;\n }[];\n}\n\nexport interface CodebaseIndexData {\n version: number;\n lastUpdated: string;\n fileCount: number;\n trie: any; // Serialized Trie\n}\n\nexport class CodebaseIndex {\n private trie: Trie<FileMetadata> = new Trie();\n private projectPath: string;\n private indexPath: string;\n private lastUpdated: string | null = null;\n\n constructor(projectPath: string) {\n this.projectPath = projectPath;\n this.indexPath = join(getTrieDirectory(projectPath), 'codebase-index.json');\n this.load();\n }\n\n /**\n * Load index from disk if it exists\n */\n private load(): void {\n if (!existsSync(this.indexPath)) return;\n\n try {\n const raw = JSON.parse(readFileSync(this.indexPath, 'utf-8'));\n if (raw.trie) {\n this.trie = Trie.fromJSON<FileMetadata>(raw.trie);\n }\n if (raw.lastUpdated) {\n this.lastUpdated = raw.lastUpdated;\n }\n } catch (error) {\n console.error('Failed to load codebase index:', error);\n }\n }\n\n /**\n * Save index to disk\n */\n async save(): Promise<void> {\n this.lastUpdated = new Date().toISOString();\n const data: CodebaseIndexData = {\n version: 1,\n lastUpdated: this.lastUpdated,\n fileCount: this.trie.getWithPrefix('').length,\n trie: this.trie.toJSON(),\n };\n\n await atomicWriteJSON(this.indexPath, data);\n }\n\n /**\n * Add or update a file in the index\n * Returns null if the file doesn't exist or can't be read\n */\n async indexFile(filePath: string): Promise<FileMetadata | null> {\n // Safety check: if filePath is already absolute and doesn't start with projectPath,\n // this is likely a bug (stale cache entry from different project)\n if (filePath.startsWith('/')) {\n // Already absolute - check if it's within our project\n if (!filePath.toLowerCase().startsWith(this.projectPath.toLowerCase())) {\n return null; // Wrong project, skip silently\n }\n // It's absolute but within our project - use as-is\n }\n \n // Build absolute path: if filePath is relative, join with projectPath\n // If filePath is absolute (and within project), use it directly\n const absolutePath = filePath.startsWith('/') ? filePath : join(this.projectPath, filePath);\n \n try {\n const content = await readFile(absolutePath, 'utf-8');\n const stats = await stat(absolutePath);\n const hash = createHash('sha256').update(content).digest('hex');\n\n const metadata: FileMetadata = {\n path: filePath,\n hash,\n size: stats.size,\n lastModified: stats.mtimeMs,\n type: filePath.split('.').pop() || '',\n };\n\n // Check if file exists in index\n const existing = this.trie.search(filePath);\n if (existing.found && existing.value) {\n // File exists - preserve violations if hash matches\n if (existing.value.hash === hash) {\n if (existing.value.violations) metadata.violations = existing.value.violations;\n if (existing.value.lastScanned != null) metadata.lastScanned = existing.value.lastScanned;\n }\n }\n\n this.trie.insert(filePath, metadata);\n return metadata;\n } catch (error) {\n // File doesn't exist, was deleted, or can't be read - return null\n return null;\n }\n }\n\n /**\n * Get file metadata from index\n */\n getFile(filePath: string): FileMetadata | null {\n const result = this.trie.search(filePath);\n return result.found && result.value ? result.value : null;\n }\n\n /**\n * Check if file has changed since last index\n */\n async hasChanged(filePath: string): Promise<boolean> {\n // Safety check for absolute paths from wrong project\n if (filePath.startsWith('/') && !filePath.toLowerCase().startsWith(this.projectPath.toLowerCase())) {\n return true; // Wrong project, treat as changed (will be skipped later)\n }\n \n const metadata = this.getFile(filePath);\n if (!metadata) return true; // Not indexed = changed\n\n // Build absolute path correctly\n const absolutePath = filePath.startsWith('/') ? filePath : join(this.projectPath, filePath);\n try {\n const stats = await stat(absolutePath);\n if (stats.mtimeMs !== metadata.lastModified) return true;\n\n // Double-check with hash for certainty\n const content = await readFile(absolutePath, 'utf-8');\n const hash = createHash('sha256').update(content).digest('hex');\n return hash !== metadata.hash;\n } catch {\n return true; // Error = assume changed\n }\n }\n\n /**\n * Get files in a directory\n */\n getDirectoryFiles(directoryPath: string): FileMetadata[] {\n const prefix = directoryPath.endsWith('/') ? directoryPath : `${directoryPath}/`;\n const matches = this.trie.getWithPrefix(prefix);\n return matches.map(m => m.value).filter((v): v is FileMetadata => v !== null);\n }\n\n /**\n * Get files that match a pattern (uses Trie prefix matching)\n */\n getFilesWithPrefix(prefix: string): FileMetadata[] {\n const matches = this.trie.getWithPrefix(prefix);\n return matches.map(m => m.value).filter((v): v is FileMetadata => v !== null);\n }\n\n /**\n * Get files by type (extension)\n */\n getFilesByType(type: string): FileMetadata[] {\n const allFiles = this.trie.getWithPrefix('');\n return allFiles\n .map(m => m.value)\n .filter((v): v is FileMetadata => v != null && v.type === type);\n }\n\n /**\n * Record goal violation scan result for a file\n */\n recordViolation(\n filePath: string,\n goalId: string,\n goalDescription: string,\n found: boolean,\n details?: string,\n confidence?: number\n ): void {\n const metadata = this.getFile(filePath);\n if (!metadata) return;\n\n metadata.lastScanned = Date.now();\n if (!metadata.violations) metadata.violations = [];\n\n // Remove existing violation for this goal\n metadata.violations = metadata.violations.filter(v => v.goalId !== goalId);\n\n // Add new violation result\n const entry: { goalId: string; goalDescription: string; found: boolean; timestamp: number; details?: string; confidence?: number } = {\n goalId,\n goalDescription,\n found,\n timestamp: Date.now(),\n };\n if (details !== undefined) entry.details = details;\n if (confidence !== undefined) entry.confidence = confidence;\n metadata.violations.push(entry);\n\n this.trie.insert(filePath, metadata);\n }\n\n /**\n * Get cached violation results for a file\n */\n getCachedViolations(filePath: string, goalId?: string): FileMetadata['violations'] {\n const metadata = this.getFile(filePath);\n if (!metadata || !metadata.violations) return undefined;\n\n if (goalId) {\n return metadata.violations.filter(v => v.goalId === goalId);\n }\n\n return metadata.violations;\n }\n\n /**\n * Check if the index is empty (needs initial indexing)\n */\n isEmpty(): boolean {\n return this.trie.getWithPrefix('').length === 0;\n }\n\n /**\n * Get statistics about the index\n */\n getStats(): {\n totalFiles: number;\n totalSize: number;\n filesByType: Record<string, number>;\n scannedFiles: number;\n filesWithViolations: number;\n lastUpdated: string | null;\n } {\n const allFiles = this.trie.getWithPrefix('').map(m => m.value).filter((v): v is FileMetadata => v !== null);\n\n const stats = {\n totalFiles: allFiles.length,\n totalSize: allFiles.reduce((sum, f) => sum + f.size, 0),\n filesByType: {} as Record<string, number>,\n scannedFiles: allFiles.filter(f => f.lastScanned).length,\n filesWithViolations: allFiles.filter(f => f.violations && f.violations.some(v => v.found)).length,\n lastUpdated: this.lastUpdated,\n };\n\n for (const file of allFiles) {\n stats.filesByType[file.type] = (stats.filesByType[file.type] || 0) + 1;\n }\n\n return stats;\n }\n\n /**\n * Clear stale cached results (older than maxAgeMs)\n */\n clearStaleCache(maxAgeMs: number = 7 * 24 * 60 * 60 * 1000): number {\n const allFiles = this.trie.getWithPrefix('').map(m => m.value).filter((v): v is FileMetadata => v !== null);\n const cutoff = Date.now() - maxAgeMs;\n let cleared = 0;\n\n for (const file of allFiles) {\n if (file.violations) {\n const before = file.violations.length;\n file.violations = file.violations.filter(v => v.timestamp > cutoff);\n cleared += before - file.violations.length;\n if (file.violations.length === 0) {\n delete file.lastScanned;\n }\n this.trie.insert(file.path, file);\n }\n }\n\n return cleared;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-6NLHFIYA.js";
|
|
4
4
|
import {
|
|
5
5
|
getTrieDirectory
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SH7H3WRU.js";
|
|
7
7
|
|
|
8
8
|
// src/context/incident-index.ts
|
|
9
9
|
import path2 from "path";
|
|
@@ -170,4 +170,4 @@ var IncidentIndex = class _IncidentIndex {
|
|
|
170
170
|
export {
|
|
171
171
|
IncidentIndex
|
|
172
172
|
};
|
|
173
|
-
//# sourceMappingURL=chunk-
|
|
173
|
+
//# sourceMappingURL=chunk-4C67GV3O.js.map
|
|
@@ -209,10 +209,9 @@ var ProjectSummarySchema = z.object({
|
|
|
209
209
|
name: z.string(),
|
|
210
210
|
path: z.string(),
|
|
211
211
|
lastScan: z.string(),
|
|
212
|
-
healthScore: z.number(),
|
|
213
212
|
totalIssues: z.number(),
|
|
214
213
|
patterns: z.array(z.string())
|
|
215
|
-
});
|
|
214
|
+
}).passthrough();
|
|
216
215
|
var PatternSummarySchema = z.object({
|
|
217
216
|
pattern: z.string(),
|
|
218
217
|
count: z.number(),
|
|
@@ -263,4 +262,4 @@ export {
|
|
|
263
262
|
CompactedSummariesIndexSchema,
|
|
264
263
|
safeParseAndValidate
|
|
265
264
|
};
|
|
266
|
-
//# sourceMappingURL=chunk-
|
|
265
|
+
//# sourceMappingURL=chunk-4MJ52WBH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/backup-manager.ts","../src/memory/validation.ts"],"sourcesContent":["/**\n * Backup Manager\n * \n * Maintains rotational backups of critical data files.\n * Provides automatic recovery from corrupted files.\n * \n * Features:\n * - N rotational backups (default: 5)\n * - Automatic pruning of old backups\n * - Validation before recovery\n * - Timestamp-based backup naming\n */\n\nimport { copyFile, readdir, unlink, readFile, stat } from 'fs/promises';\nimport { existsSync } from 'fs';\nimport { dirname, basename, join } from 'path';\n\nexport interface BackupOptions {\n /**\n * Maximum number of backups to keep\n * @default 5\n */\n maxBackups?: number;\n \n /**\n * Custom validator function for backup validation\n * Returns true if the backup is valid\n */\n validator?: (content: string) => boolean;\n}\n\nexport interface BackupInfo {\n path: string;\n timestamp: number;\n size: number;\n}\n\n/**\n * Manages rotational backups for a file\n * \n * @example\n * ```typescript\n * const manager = new BackupManager('/path/to/issues.json');\n * \n * // Create backup before modifying\n * await manager.createBackup();\n * \n * // If corruption detected, recover\n * if (await manager.recoverFromBackup()) {\n * console.log('Recovered from backup');\n * }\n * ```\n */\nexport class BackupManager {\n private readonly filePath: string;\n private readonly maxBackups: number;\n private readonly validator: ((content: string) => boolean) | undefined;\n private readonly backupDir: string;\n private readonly baseFileName: string;\n\n constructor(filePath: string, options: BackupOptions = {}) {\n this.filePath = filePath;\n this.maxBackups = options.maxBackups ?? 5;\n this.validator = options.validator;\n this.backupDir = dirname(filePath);\n this.baseFileName = basename(filePath);\n }\n\n /**\n * Create a backup of the current file\n * \n * @returns The backup file path, or null if source doesn't exist\n */\n async createBackup(): Promise<string | null> {\n if (!existsSync(this.filePath)) {\n return null;\n }\n\n const timestamp = Date.now();\n const backupPath = this.getBackupPath(timestamp);\n\n await copyFile(this.filePath, backupPath);\n await this.pruneOldBackups();\n\n return backupPath;\n }\n\n /**\n * List all backups sorted by timestamp (newest first)\n */\n async listBackups(): Promise<BackupInfo[]> {\n if (!existsSync(this.backupDir)) {\n return [];\n }\n\n const files = await readdir(this.backupDir);\n const backupPattern = new RegExp(\n `^${this.escapeRegex(this.baseFileName)}\\\\.backup\\\\.(\\\\d+)$`\n );\n\n const backups: BackupInfo[] = [];\n\n for (const file of files) {\n const match = file.match(backupPattern);\n if (match) {\n const timestamp = parseInt(match[1]!, 10);\n const backupPath = join(this.backupDir, file);\n \n try {\n const stats = await stat(backupPath);\n backups.push({\n path: backupPath,\n timestamp,\n size: stats.size,\n });\n } catch {\n // Skip files we can't stat\n }\n }\n }\n\n // Sort by timestamp, newest first\n return backups.sort((a, b) => b.timestamp - a.timestamp);\n }\n\n /**\n * Find the first valid backup\n * \n * Iterates through backups from newest to oldest,\n * returning the first one that passes validation.\n * \n * @returns Path to valid backup, or null if none found\n */\n async findValidBackup(): Promise<string | null> {\n const backups = await this.listBackups();\n\n for (const backup of backups) {\n if (await this.validateBackup(backup.path)) {\n return backup.path;\n }\n }\n\n return null;\n }\n\n /**\n * Validate a backup file\n * \n * If a custom validator was provided, uses that.\n * Otherwise, attempts JSON parse for .json files.\n * \n * @returns true if backup is valid\n */\n async validateBackup(backupPath: string): Promise<boolean> {\n try {\n const content = await readFile(backupPath, 'utf-8');\n\n // Use custom validator if provided\n if (this.validator) {\n return this.validator(content);\n }\n\n // Default validation: check if it's valid JSON for .json files\n if (this.filePath.endsWith('.json')) {\n JSON.parse(content);\n return true;\n }\n\n // For non-JSON files, just check it's readable\n return content.length > 0;\n } catch {\n return false;\n }\n }\n\n /**\n * Recover from the most recent valid backup\n * \n * @returns true if recovery was successful\n */\n async recoverFromBackup(): Promise<boolean> {\n const validBackup = await this.findValidBackup();\n \n if (!validBackup) {\n return false;\n }\n\n await copyFile(validBackup, this.filePath);\n return true;\n }\n\n /**\n * Remove old backups beyond the max limit\n */\n private async pruneOldBackups(): Promise<void> {\n const backups = await this.listBackups();\n\n // Remove backups beyond the limit\n const toRemove = backups.slice(this.maxBackups);\n \n for (const backup of toRemove) {\n try {\n await unlink(backup.path);\n } catch {\n // Ignore errors - backup might already be removed\n }\n }\n }\n\n /**\n * Get the backup path for a given timestamp\n */\n private getBackupPath(timestamp: number): string {\n return join(this.backupDir, `${this.baseFileName}.backup.${timestamp}`);\n }\n\n /**\n * Escape special regex characters in a string\n */\n private escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n\n /**\n * Get the number of existing backups\n */\n async getBackupCount(): Promise<number> {\n const backups = await this.listBackups();\n return backups.length;\n }\n\n /**\n * Get the most recent backup timestamp\n */\n async getLatestBackupTime(): Promise<number | null> {\n const backups = await this.listBackups();\n return backups[0]?.timestamp ?? null;\n }\n\n /**\n * Delete all backups\n */\n async clearBackups(): Promise<number> {\n const backups = await this.listBackups();\n let deleted = 0;\n\n for (const backup of backups) {\n try {\n await unlink(backup.path);\n deleted++;\n } catch {\n // Ignore errors\n }\n }\n\n return deleted;\n }\n}\n\n/**\n * Default JSON validator\n * \n * Validates that content is valid JSON and optionally\n * checks for required fields.\n */\nexport function createJSONValidator(requiredFields?: string[]): (content: string) => boolean {\n return (content: string): boolean => {\n try {\n const parsed = JSON.parse(content);\n \n if (requiredFields && Array.isArray(parsed)) {\n // For arrays, check first element has required fields\n if (parsed.length > 0) {\n const first = parsed[0];\n return requiredFields.every(field => field in first);\n }\n // Empty array is valid\n return true;\n }\n \n if (requiredFields && typeof parsed === 'object' && parsed !== null) {\n return requiredFields.every(field => field in parsed);\n }\n \n return true;\n } catch {\n return false;\n }\n };\n}\n","/**\n * Memory Validation\n * \n * Schema validation for memory storage files using Zod.\n * Catches corruption early and provides clear error messages.\n * \n * Used by:\n * - issue-store.ts for issues.json\n * - global-memory.ts for global-patterns.json\n * - compactor.ts for compacted-summaries.json\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Issue Schemas\n// ============================================================================\n\n/**\n * Schema for a stored issue in issues.json\n */\nexport const StoredIssueSchema = z.object({\n id: z.string(),\n hash: z.string(),\n severity: z.string(),\n issue: z.string(),\n fix: z.string(),\n file: z.string(),\n line: z.number().optional(),\n agent: z.string(),\n category: z.string().optional(),\n timestamp: z.string(),\n project: z.string(),\n resolved: z.boolean().optional(),\n resolvedAt: z.string().optional(),\n});\n\nexport type ValidatedStoredIssue = z.infer<typeof StoredIssueSchema>;\n\n/**\n * Schema for the issues.json file (array of issues)\n */\nexport const IssueIndexSchema = z.array(StoredIssueSchema);\n\n// ============================================================================\n// Global Pattern Schemas\n// ============================================================================\n\n/**\n * Schema for a fix applied record\n */\nexport const FixAppliedSchema = z.object({\n project: z.string(),\n timestamp: z.string(),\n fix: z.string(),\n});\n\n/**\n * Schema for a global pattern in global-patterns.json\n */\nexport const GlobalPatternSchema = z.object({\n id: z.string(),\n pattern: z.string(),\n description: z.string(),\n severity: z.string(),\n agent: z.string(),\n occurrences: z.number(),\n projects: z.array(z.string()),\n firstSeen: z.string(),\n lastSeen: z.string(),\n fixApplied: FixAppliedSchema.optional(),\n});\n\nexport type ValidatedGlobalPattern = z.infer<typeof GlobalPatternSchema>;\n\n/**\n * Schema for global-patterns.json (array of patterns)\n */\nexport const GlobalPatternsIndexSchema = z.array(GlobalPatternSchema);\n\n// ============================================================================\n// Project Summary Schemas\n// ============================================================================\n\n/**\n * Schema for a project summary\n */\nexport const ProjectSummarySchema = z.object({\n name: z.string(),\n path: z.string(),\n lastScan: z.string(),\n totalIssues: z.number(),\n patterns: z.array(z.string()),\n}).passthrough(); // Allow legacy healthScore in old project files\n\nexport type ValidatedProjectSummary = z.infer<typeof ProjectSummarySchema>;\n\n// ============================================================================\n// Compacted Summary Schemas\n// ============================================================================\n\n/**\n * Schema for a pattern summary in compacted data\n */\nexport const PatternSummarySchema = z.object({\n pattern: z.string(),\n count: z.number(),\n severity: z.string(),\n agent: z.string(),\n exampleFix: z.string(),\n});\n\n/**\n * Schema for a hot file entry\n */\nexport const HotFileSchema = z.object({\n file: z.string(),\n count: z.number(),\n});\n\n/**\n * Schema for a compacted summary\n */\nexport const CompactedSummarySchema = z.object({\n period: z.string(),\n startDate: z.string(),\n endDate: z.string(),\n totalIssues: z.number(),\n resolvedCount: z.number(),\n bySeverity: z.record(z.string(), z.number()),\n byAgent: z.record(z.string(), z.number()),\n topPatterns: z.array(PatternSummarySchema),\n hotFiles: z.array(HotFileSchema),\n compactedAt: z.string(),\n});\n\nexport type ValidatedCompactedSummary = z.infer<typeof CompactedSummarySchema>;\n\n/**\n * Schema for compacted-summaries.json (array of summaries)\n */\nexport const CompactedSummariesIndexSchema = z.array(CompactedSummarySchema);\n\n// ============================================================================\n// Validation Error\n// ============================================================================\n\n/**\n * Custom error for validation failures\n */\nexport class ValidationError extends Error {\n constructor(\n message: string,\n public readonly zodError: z.ZodError,\n public readonly filePath?: string\n ) {\n super(message);\n this.name = 'ValidationError';\n }\n\n /**\n * Get a human-readable summary of validation errors\n */\n getSummary(): string {\n const issues = this.zodError.issues.slice(0, 5);\n const lines = issues.map(issue => {\n const path = issue.path.join('.');\n return ` - ${path}: ${issue.message}`;\n });\n \n if (this.zodError.issues.length > 5) {\n lines.push(` ... and ${this.zodError.issues.length - 5} more errors`);\n }\n \n return lines.join('\\n');\n }\n}\n\n// ============================================================================\n// Validation Functions\n// ============================================================================\n\n/**\n * Validate issue index data\n * \n * @throws ValidationError if data is invalid\n * @returns Validated issue array\n */\nexport function validateIssueIndex(\n data: unknown,\n filePath?: string\n): ValidatedStoredIssue[] {\n const result = IssueIndexSchema.safeParse(data);\n \n if (!result.success) {\n throw new ValidationError(\n `Issue index validation failed${filePath ? ` (${filePath})` : ''}`,\n result.error,\n filePath\n );\n }\n \n return result.data;\n}\n\n/**\n * Validate global patterns data\n * \n * @throws ValidationError if data is invalid\n * @returns Validated patterns array\n */\nexport function validateGlobalPatterns(\n data: unknown,\n filePath?: string\n): ValidatedGlobalPattern[] {\n const result = GlobalPatternsIndexSchema.safeParse(data);\n \n if (!result.success) {\n throw new ValidationError(\n `Global patterns validation failed${filePath ? ` (${filePath})` : ''}`,\n result.error,\n filePath\n );\n }\n \n return result.data;\n}\n\n/**\n * Validate project summary data\n * \n * @throws ValidationError if data is invalid\n * @returns Validated project summary\n */\nexport function validateProjectSummary(\n data: unknown,\n filePath?: string\n): ValidatedProjectSummary {\n const result = ProjectSummarySchema.safeParse(data);\n \n if (!result.success) {\n throw new ValidationError(\n `Project summary validation failed${filePath ? ` (${filePath})` : ''}`,\n result.error,\n filePath\n );\n }\n \n return result.data;\n}\n\n/**\n * Validate compacted summaries data\n * \n * @throws ValidationError if data is invalid\n * @returns Validated summaries array\n */\nexport function validateCompactedSummaries(\n data: unknown,\n filePath?: string\n): ValidatedCompactedSummary[] {\n const result = CompactedSummariesIndexSchema.safeParse(data);\n \n if (!result.success) {\n throw new ValidationError(\n `Compacted summaries validation failed${filePath ? ` (${filePath})` : ''}`,\n result.error,\n filePath\n );\n }\n \n return result.data;\n}\n\n/**\n * Safely parse and validate JSON\n * \n * Combines JSON parsing with schema validation.\n * Returns null on any error instead of throwing.\n */\nexport function safeParseAndValidate<T>(\n content: string,\n schema: z.ZodSchema<T>\n): { success: true; data: T } | { success: false; error: string } {\n try {\n const parsed = JSON.parse(content);\n const result = schema.safeParse(parsed);\n \n if (result.success) {\n return { success: true, data: result.data };\n }\n \n return {\n success: false,\n error: `Validation failed: ${result.error.issues[0]?.message || 'Unknown error'}`,\n };\n } catch (error) {\n return {\n success: false,\n error: `JSON parse failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n}\n\n/**\n * Check if data matches a schema (non-throwing)\n */\nexport function isValidSchema<T>(data: unknown, schema: z.ZodSchema<T>): boolean {\n return schema.safeParse(data).success;\n}\n"],"mappings":"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","names":[]}
|