opencode-fractal-memory 0.2.0

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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +493 -0
  3. package/agent/memory-hints.md +98 -0
  4. package/agent/memory-researcher.md +56 -0
  5. package/commands/memory-auto-test.md +10 -0
  6. package/commands/memory-cache-status.md +13 -0
  7. package/commands/memory-check-context.md +4 -0
  8. package/commands/memory-compress.md +13 -0
  9. package/commands/memory-dashboard.md +23 -0
  10. package/commands/memory-delete.md +24 -0
  11. package/commands/memory-detect-topics.md +28 -0
  12. package/commands/memory-distill.md +35 -0
  13. package/commands/memory-drilldown-query.md +28 -0
  14. package/commands/memory-drilldown.md +11 -0
  15. package/commands/memory-extract-patterns.md +4 -0
  16. package/commands/memory-generate-embeddings.md +26 -0
  17. package/commands/memory-get.md +26 -0
  18. package/commands/memory-help.md +55 -0
  19. package/commands/memory-injection-feedback.md +26 -0
  20. package/commands/memory-injection-stats.md +11 -0
  21. package/commands/memory-list.md +4 -0
  22. package/commands/memory-llm-compress.md +34 -0
  23. package/commands/memory-mcp.md +20 -0
  24. package/commands/memory-prune.md +4 -0
  25. package/commands/memory-rate.md +48 -0
  26. package/commands/memory-reflect.md +37 -0
  27. package/commands/memory-replace.md +26 -0
  28. package/commands/memory-retrieve.md +34 -0
  29. package/commands/memory-search.md +28 -0
  30. package/commands/memory-session-stats.md +4 -0
  31. package/commands/memory-set.md +31 -0
  32. package/commands/memory-stats.md +11 -0
  33. package/commands/memory-summarize.md +29 -0
  34. package/commands/memory-tool-stats.md +4 -0
  35. package/commands/memory-total-tokens.md +10 -0
  36. package/commands/memory-verify.md +4 -0
  37. package/commands/memory-version.md +9 -0
  38. package/dist/cache.js +39 -0
  39. package/dist/config.js +120 -0
  40. package/dist/embeddings.js +125 -0
  41. package/dist/ensure-models.js +70 -0
  42. package/dist/file-summary.js +143 -0
  43. package/dist/frontmatter.js +28 -0
  44. package/dist/hnsw-index.js +138 -0
  45. package/dist/hooks/auto-discover.js +4 -0
  46. package/dist/hooks/auto-distill.js +120 -0
  47. package/dist/hooks/auto-retrieve/content.js +47 -0
  48. package/dist/hooks/auto-retrieve/detection.js +50 -0
  49. package/dist/hooks/auto-retrieve/formatting.js +19 -0
  50. package/dist/hooks/auto-retrieve/index.js +163 -0
  51. package/dist/hooks/auto-retrieve/scoring.js +56 -0
  52. package/dist/hooks/auto-retrieve.js +1 -0
  53. package/dist/hooks/index.js +4 -0
  54. package/dist/hooks/predictive-rating.js +87 -0
  55. package/dist/journal.js +279 -0
  56. package/dist/logging.js +147 -0
  57. package/dist/management/helpers.js +227 -0
  58. package/dist/management/router.js +48 -0
  59. package/dist/management/routes.js +197 -0
  60. package/dist/management-server.js +4 -0
  61. package/dist/management-standalone.js +31 -0
  62. package/dist/mcp/logging.js +57 -0
  63. package/dist/mcp/server.js +251 -0
  64. package/dist/mcp/transform.js +48 -0
  65. package/dist/mcp-server.js +18 -0
  66. package/dist/memory.js +2 -0
  67. package/dist/ollama.js +74 -0
  68. package/dist/plugin/hooks.js +168 -0
  69. package/dist/plugin/index.js +28 -0
  70. package/dist/plugin/init.js +109 -0
  71. package/dist/plugin/state.js +75 -0
  72. package/dist/plugin/tools.js +45 -0
  73. package/dist/plugin.js +2 -0
  74. package/dist/procedural/store.js +1 -0
  75. package/dist/procedural/types.js +1 -0
  76. package/dist/seed-nodes.js +804 -0
  77. package/dist/storage/compress-ops.js +129 -0
  78. package/dist/storage/compression/formatters.js +243 -0
  79. package/dist/storage/compression/index.js +107 -0
  80. package/dist/storage/compression/patterns.js +138 -0
  81. package/dist/storage/expiration.js +66 -0
  82. package/dist/storage/index.js +1 -0
  83. package/dist/storage/injection-events.js +82 -0
  84. package/dist/storage/lifecycle.js +65 -0
  85. package/dist/storage/maintenance.js +60 -0
  86. package/dist/storage/migrations/definitions.js +374 -0
  87. package/dist/storage/migrations/index.js +21 -0
  88. package/dist/storage/navigation.js +98 -0
  89. package/dist/storage/queries/base.js +44 -0
  90. package/dist/storage/queries/links.js +32 -0
  91. package/dist/storage/queries/nodes.js +189 -0
  92. package/dist/storage/queries/search-helpers.js +239 -0
  93. package/dist/storage/scoring.js +36 -0
  94. package/dist/storage/search.js +233 -0
  95. package/dist/storage/session-tracking.js +180 -0
  96. package/dist/storage/sqlite.js +329 -0
  97. package/dist/storage/tool-usage.js +56 -0
  98. package/dist/storage/types.js +1 -0
  99. package/dist/storage/utils.js +94 -0
  100. package/dist/tools/auto-test.js +24 -0
  101. package/dist/tools/cache-status.js +36 -0
  102. package/dist/tools/compress.js +186 -0
  103. package/dist/tools/core.js +307 -0
  104. package/dist/tools/dashboard.js +97 -0
  105. package/dist/tools/help.js +59 -0
  106. package/dist/tools/index.js +12 -0
  107. package/dist/tools/inject.js +91 -0
  108. package/dist/tools/injection-debug.js +48 -0
  109. package/dist/tools/journal.js +105 -0
  110. package/dist/tools/llm-compress.js +41 -0
  111. package/dist/tools/middle-term.js +68 -0
  112. package/dist/tools/playbook.js +64 -0
  113. package/dist/tools/reflect.js +291 -0
  114. package/dist/tools/search.js +188 -0
  115. package/dist/tools/session.js +189 -0
  116. package/dist/tools/shared.js +74 -0
  117. package/dist/tools/skill.js +37 -0
  118. package/dist/tools/stats.js +256 -0
  119. package/dist/tools/version.js +13 -0
  120. package/dist/tools.js +18 -0
  121. package/dist/utils/hybridScore.js +67 -0
  122. package/management/public/app.js +1529 -0
  123. package/management/public/index.html +486 -0
  124. package/management/public/three.min.js +6 -0
  125. package/package.json +65 -0
  126. package/scripts/download-models.ts +16 -0
  127. package/scripts/postinstall.cjs +30 -0
@@ -0,0 +1,233 @@
1
+ import { rowToNode } from "./queries/base";
2
+ import { getHNSWIndex } from "../hnsw-index";
3
+ import { generateEmbedding } from "../embeddings";
4
+ import { cosineSimilarity, computeBM25ScoresSQL, computeFinalScores, rerankResults } from "./queries/search-helpers";
5
+ import { tokenize, blobToEmbedding } from "./utils";
6
+ export async function searchByEmbedding(getDb, query, limit = 5, options) {
7
+ const weights = options?.levelWeights ?? {};
8
+ const bm25Weight = options?.bm25Weight ?? 0;
9
+ const doRerank = options?.rerank ?? false;
10
+ const queryText = options?.queryText ?? "";
11
+ const hnsw = getHNSWIndex();
12
+ const hnswResults = await hnsw.search(query, limit * 5);
13
+ let scoredNodes = [];
14
+ if (hnswResults.length > 0) {
15
+ const candidateIds = new Set(hnswResults.map(r => r.id));
16
+ const hnswScoreMap = new Map(hnswResults.map(r => [r.id, r.score]));
17
+ for (const scope of ["global", "project"]) {
18
+ const db = await getDb(scope);
19
+ const placeholders = Array.from(candidateIds).map(() => "?").join(",");
20
+ const rows = db.query(`SELECT * FROM memory_nodes WHERE id IN (${placeholders}) AND (embedding IS NOT NULL OR embedding_blob IS NOT NULL) AND (expires_at IS NULL OR expires_at > ?)`).all(...Array.from(candidateIds), Date.now());
21
+ for (const row of rows) {
22
+ const level = row.level;
23
+ const node = rowToNode(row);
24
+ if (!node.embedding)
25
+ continue;
26
+ if (options?.minLevel !== undefined && level < options.minLevel)
27
+ continue;
28
+ if (options?.maxLevel !== undefined && level > options.maxLevel)
29
+ continue;
30
+ if (options?.minUsefulness !== undefined && (node.usefulnessScore ?? 0) < options.minUsefulness)
31
+ continue;
32
+ let embedding = node.embedding;
33
+ if (row.embedding_blob) {
34
+ embedding = blobToEmbedding(row.embedding_blob);
35
+ }
36
+ const hnswScore = hnswScoreMap.get(node.id) ?? 0;
37
+ const levelWeight = weights[level] ?? 1;
38
+ const confidence = node.confidence ?? 0.5;
39
+ const confidenceWeight = 0.5 + 0.5 * confidence;
40
+ scoredNodes.push({
41
+ ...node,
42
+ importance: hnswScore * levelWeight * confidenceWeight * (1 + (node.usefulnessScore ?? 0) * 0.1),
43
+ });
44
+ }
45
+ }
46
+ }
47
+ else {
48
+ const minLevel = options?.minLevel ?? 0;
49
+ const maxLevel = options?.maxLevel ?? 5;
50
+ const minUsefulness = options?.minUsefulness ?? 0;
51
+ for (const scope of ["global", "project"]) {
52
+ const db = await getDb(scope);
53
+ const rows = db.query(`SELECT * FROM memory_nodes WHERE (embedding IS NOT NULL OR embedding_blob IS NOT NULL) AND level >= ? AND level <= ? AND usefulness_score >= ? AND (expires_at IS NULL OR expires_at > ?)`).all(minLevel, maxLevel, minUsefulness, Date.now());
54
+ for (const row of rows) {
55
+ const level = row.level;
56
+ const node = rowToNode(row);
57
+ if (!node.embedding)
58
+ continue;
59
+ let embedding = node.embedding;
60
+ if (row.embedding_blob) {
61
+ embedding = blobToEmbedding(row.embedding_blob);
62
+ }
63
+ const semanticScore = cosineSimilarity(query, embedding);
64
+ const levelWeight = weights[level] ?? 1;
65
+ const confidence = node.confidence ?? 0.5;
66
+ const confidenceWeight = 0.5 + 0.5 * confidence;
67
+ scoredNodes.push({
68
+ ...node,
69
+ importance: semanticScore * levelWeight * confidenceWeight * (1 + (node.usefulnessScore ?? 0) * 0.1),
70
+ });
71
+ }
72
+ }
73
+ }
74
+ if (bm25Weight > 0 && queryText) {
75
+ const queryTerms = tokenize(queryText);
76
+ const bm25Scores = new Map();
77
+ for (const scope of ["global", "project"]) {
78
+ const db = await getDb(scope);
79
+ const scopeNodeIds = scoredNodes.filter(n => n.scope === scope).map(n => n.id);
80
+ if (scopeNodeIds.length === 0)
81
+ continue;
82
+ const scopeScores = computeBM25ScoresSQL(db, scope, queryTerms, scopeNodeIds);
83
+ for (const [id, score] of scopeScores) {
84
+ bm25Scores.set(id, score);
85
+ }
86
+ }
87
+ options = { ...options, bm25Scores };
88
+ }
89
+ const finalNodes = computeFinalScores(scoredNodes, options);
90
+ let finalResults = finalNodes.slice(0, limit);
91
+ if (doRerank && queryText.length > 0 && finalResults.length > 3) {
92
+ const reranked = rerankResults(queryText, finalResults, limit);
93
+ finalResults = reranked.map(r => ({
94
+ ...r.node,
95
+ importance: r.finalScore
96
+ }));
97
+ }
98
+ const now = Date.now();
99
+ for (const node of finalResults) {
100
+ const db = await getDb(node.scope);
101
+ db.run(`UPDATE memory_nodes SET times_used = times_used + 1, last_accessed = ? WHERE id = ?`, [now, node.id]);
102
+ }
103
+ return finalResults;
104
+ }
105
+ export async function getDrilldownPath(getNode, nodeId, maxDepth) {
106
+ const path = [];
107
+ let currentId = nodeId;
108
+ let depth = 0;
109
+ const visited = new Set();
110
+ while (currentId && depth < maxDepth) {
111
+ if (visited.has(currentId))
112
+ break;
113
+ visited.add(currentId);
114
+ try {
115
+ const node = await getNode(currentId);
116
+ path.push(node);
117
+ if (!node.parentIds || node.parentIds.length === 0)
118
+ break;
119
+ currentId = node.parentIds[0] ?? null;
120
+ depth++;
121
+ }
122
+ catch {
123
+ break;
124
+ }
125
+ }
126
+ return path;
127
+ }
128
+ export async function drilldownQuery(deps, query, maxResults = 20) {
129
+ const queryEmbedding = await generateEmbedding(query);
130
+ const results = [];
131
+ const seenIds = new Set();
132
+ const summaries = await deps.searchByEmbedding(queryEmbedding, 5, { minLevel: 2, maxLevel: 4 });
133
+ for (const node of summaries) {
134
+ if (seenIds.has(node.id))
135
+ continue;
136
+ seenIds.add(node.id);
137
+ const path = await deps.getDrilldownPath(node.id, 3);
138
+ results.push({
139
+ node,
140
+ relevance: node.importance,
141
+ path,
142
+ level: "summary",
143
+ });
144
+ }
145
+ const intermediates = await deps.searchByEmbedding(queryEmbedding, 10, { minLevel: 1, maxLevel: 1 });
146
+ for (const node of intermediates) {
147
+ if (seenIds.has(node.id))
148
+ continue;
149
+ if (results.length >= maxResults)
150
+ break;
151
+ seenIds.add(node.id);
152
+ const path = await deps.getDrilldownPath(node.id, 2);
153
+ results.push({
154
+ node,
155
+ relevance: node.importance * 0.8,
156
+ path,
157
+ level: "intermediate",
158
+ });
159
+ }
160
+ const details = await deps.searchByEmbedding(queryEmbedding, maxResults, { minLevel: 0, maxLevel: 0 });
161
+ for (const node of details) {
162
+ if (seenIds.has(node.id))
163
+ continue;
164
+ if (results.length >= maxResults)
165
+ break;
166
+ seenIds.add(node.id);
167
+ results.push({
168
+ node,
169
+ relevance: node.importance * 0.6,
170
+ path: [node],
171
+ level: "detail",
172
+ });
173
+ }
174
+ if (results.length < maxResults) {
175
+ for (const scope of ["global", "project"]) {
176
+ const db = await deps.getDb(scope);
177
+ const rows = db.query("SELECT * FROM memory_nodes WHERE (label LIKE ? OR content LIKE ?) AND (embedding IS NULL AND embedding_blob IS NULL)").all(`%${query}%`, `%${query}%`);
178
+ for (const row of rows) {
179
+ if (results.length >= maxResults)
180
+ break;
181
+ const node = rowToNode(row);
182
+ if (seenIds.has(node.id))
183
+ continue;
184
+ seenIds.add(node.id);
185
+ results.push({
186
+ node,
187
+ relevance: 0.5,
188
+ path: [node],
189
+ level: "detail",
190
+ });
191
+ }
192
+ if (results.length >= maxResults)
193
+ break;
194
+ }
195
+ }
196
+ return results.sort((a, b) => b.relevance - a.relevance).slice(0, maxResults);
197
+ }
198
+ export async function detectTopicBoundaries(getDb, scope, minSimilarity = 0.7) {
199
+ const scopes = scope === "all" ? ["global", "project"] : [scope];
200
+ const nodesWithEmbeddings = [];
201
+ for (const s of scopes) {
202
+ const db = await getDb(s);
203
+ const rows = db.query("SELECT * FROM memory_nodes WHERE (embedding IS NOT NULL OR embedding_blob IS NOT NULL) AND length(content) > 50 ORDER BY created_at DESC").all();
204
+ for (const row of rows) {
205
+ nodesWithEmbeddings.push(rowToNode(row));
206
+ }
207
+ }
208
+ if (nodesWithEmbeddings.length < 2)
209
+ return [];
210
+ const sorted = nodesWithEmbeddings;
211
+ const boundaries = [];
212
+ let currentCluster = [sorted[0]];
213
+ for (let i = 1; i < sorted.length; i++) {
214
+ const prev = sorted[i - 1];
215
+ const curr = sorted[i];
216
+ if (!prev.embedding || !curr.embedding)
217
+ continue;
218
+ const similarity = cosineSimilarity(prev.embedding, curr.embedding);
219
+ if (similarity >= minSimilarity) {
220
+ currentCluster.push(curr);
221
+ }
222
+ else {
223
+ if (currentCluster.length >= 2) {
224
+ boundaries.push(currentCluster);
225
+ }
226
+ currentCluster = [curr];
227
+ }
228
+ }
229
+ if (currentCluster.length >= 2) {
230
+ boundaries.push(currentCluster);
231
+ }
232
+ return boundaries;
233
+ }
@@ -0,0 +1,180 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { withRetry } from "./utils";
3
+ export async function insertAgentToolCall(db, sessionId, toolName, args, output, success, durationMs, toolCategory) {
4
+ const timestamp = Date.now();
5
+ const id = randomUUID();
6
+ let filePath = null;
7
+ let command = null;
8
+ if (args) {
9
+ if (args.filePath && typeof args.filePath === "string") {
10
+ filePath = args.filePath;
11
+ }
12
+ else if (args.path && typeof args.path === "string") {
13
+ filePath = args.path;
14
+ }
15
+ if (args.command && typeof args.command === "string") {
16
+ command = args.command.substring(0, 500);
17
+ }
18
+ }
19
+ const outputPreview = output ? output.substring(0, 500) : null;
20
+ await withRetry(() => {
21
+ db.run(`INSERT INTO agent_tool_calls
22
+ (id, session_id, timestamp, tool_name, args_json, output_preview, success, duration_ms, tool_category, file_path, command)
23
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
24
+ id,
25
+ sessionId,
26
+ timestamp,
27
+ toolName,
28
+ args ? JSON.stringify(args) : null,
29
+ outputPreview,
30
+ success !== null ? (success ? 1 : 0) : null,
31
+ durationMs ?? null,
32
+ toolCategory,
33
+ filePath,
34
+ command,
35
+ ]);
36
+ });
37
+ }
38
+ export async function createSessionMetrics(db, sessionId, startedAt) {
39
+ const timestamp = startedAt ?? Date.now();
40
+ await withRetry(() => {
41
+ db.run(`INSERT OR REPLACE INTO session_metrics (session_id, started_at, status) VALUES (?, ?, 'active')`, [sessionId, timestamp]);
42
+ });
43
+ }
44
+ export async function updateSessionMetrics(db, sessionId, updates) {
45
+ const setClauses = [];
46
+ const values = [];
47
+ if (updates.endedAt !== undefined) {
48
+ setClauses.push("ended_at = ?");
49
+ values.push(updates.endedAt);
50
+ }
51
+ if (updates.totalToolCalls !== undefined) {
52
+ setClauses.push("total_tool_calls = ?");
53
+ values.push(updates.totalToolCalls);
54
+ }
55
+ if (updates.fileReads !== undefined) {
56
+ setClauses.push("file_reads = ?");
57
+ values.push(updates.fileReads);
58
+ }
59
+ if (updates.fileEdits !== undefined) {
60
+ setClauses.push("file_edits = ?");
61
+ values.push(updates.fileEdits);
62
+ }
63
+ if (updates.bashCommands !== undefined) {
64
+ setClauses.push("bash_commands = ?");
65
+ values.push(updates.bashCommands);
66
+ }
67
+ if (updates.memoryTools !== undefined) {
68
+ setClauses.push("memory_tools = ?");
69
+ values.push(updates.memoryTools);
70
+ }
71
+ if (updates.failedTools !== undefined) {
72
+ setClauses.push("failed_tools = ?");
73
+ values.push(updates.failedTools);
74
+ }
75
+ if (updates.uniqueFilesTouched !== undefined) {
76
+ setClauses.push("unique_files_touched = ?");
77
+ values.push(JSON.stringify(updates.uniqueFilesTouched));
78
+ }
79
+ if (updates.injectionCount !== undefined) {
80
+ setClauses.push("injection_count = ?");
81
+ values.push(updates.injectionCount);
82
+ }
83
+ if (updates.injectedTokens !== undefined) {
84
+ setClauses.push("injected_tokens = ?");
85
+ values.push(updates.injectedTokens);
86
+ }
87
+ if (updates.taskDescription !== undefined) {
88
+ setClauses.push("task_description = ?");
89
+ values.push(updates.taskDescription);
90
+ }
91
+ if (updates.status !== undefined) {
92
+ setClauses.push("status = ?");
93
+ values.push(updates.status);
94
+ }
95
+ if (setClauses.length === 0)
96
+ return;
97
+ values.push(sessionId);
98
+ await withRetry(() => {
99
+ db.run(`UPDATE session_metrics SET ${setClauses.join(", ")} WHERE session_id = ?`, values);
100
+ });
101
+ }
102
+ export async function incrementSessionToolCall(db, sessionId, toolName, success, filePath, toolCategory) {
103
+ const session = db.query("SELECT * FROM session_metrics WHERE session_id = ?").get(sessionId);
104
+ if (!session) {
105
+ await createSessionMetrics(db, sessionId);
106
+ await incrementSessionToolCall(db, sessionId, toolName, success, filePath, toolCategory);
107
+ return;
108
+ }
109
+ const category = toolCategory ?? getToolCategory(toolName);
110
+ const updates = {
111
+ totalToolCalls: session.total_tool_calls + 1,
112
+ };
113
+ if (category === "file") {
114
+ if (toolName === "read")
115
+ updates.fileReads = session.file_reads + 1;
116
+ else if (toolName === "edit")
117
+ updates.fileEdits = session.file_edits + 1;
118
+ }
119
+ else if (category === "shell") {
120
+ updates.bashCommands = session.bash_commands + 1;
121
+ }
122
+ else if (category === "memory") {
123
+ updates.memoryTools = session.memory_tools + 1;
124
+ }
125
+ if (!success) {
126
+ updates.failedTools = session.failed_tools + 1;
127
+ }
128
+ if (filePath) {
129
+ const currentFiles = session.unique_files_touched
130
+ ? JSON.parse(session.unique_files_touched)
131
+ : [];
132
+ if (!currentFiles.includes(filePath)) {
133
+ updates.uniqueFilesTouched = [...currentFiles, filePath];
134
+ }
135
+ }
136
+ await updateSessionMetrics(db, sessionId, updates);
137
+ }
138
+ function getToolCategory(toolName) {
139
+ if (toolName.startsWith("memory_") || toolName.startsWith("journal_"))
140
+ return "memory";
141
+ if (["read", "edit", "write", "glob", "grep", "search"].includes(toolName))
142
+ return "file";
143
+ if (["bash", "shell"].includes(toolName))
144
+ return "shell";
145
+ return "other";
146
+ }
147
+ export function getSessionStats(db, sessionId) {
148
+ const session = db.query("SELECT * FROM session_metrics WHERE session_id = ?").get(sessionId);
149
+ if (!session)
150
+ return null;
151
+ const toolCalls = db.query(`SELECT tool_name, timestamp, tool_category, file_path, command, success
152
+ FROM agent_tool_calls
153
+ WHERE session_id = ?
154
+ ORDER BY timestamp ASC`).all(sessionId);
155
+ return {
156
+ sessionId: session.session_id,
157
+ startedAt: session.started_at,
158
+ endedAt: session.ended_at,
159
+ status: session.status,
160
+ totalToolCalls: session.total_tool_calls,
161
+ fileReads: session.file_reads,
162
+ fileEdits: session.file_edits,
163
+ bashCommands: session.bash_commands,
164
+ memoryTools: session.memory_tools,
165
+ failedTools: session.failed_tools,
166
+ uniqueFilesTouched: session.unique_files_touched
167
+ ? JSON.parse(session.unique_files_touched)
168
+ : [],
169
+ injectionCount: session.injection_count,
170
+ injectedTokens: session.injected_tokens,
171
+ toolCalls: toolCalls.map((tc) => ({
172
+ toolName: tc.tool_name,
173
+ timestamp: tc.timestamp,
174
+ toolCategory: tc.tool_category,
175
+ filePath: tc.file_path,
176
+ command: tc.command,
177
+ success: tc.success !== null ? tc.success === 1 : null,
178
+ })),
179
+ };
180
+ }