opencodekit 0.17.13 → 0.18.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.
- package/dist/index.js +4 -6
- package/dist/template/.opencode/dcp.jsonc +81 -81
- package/dist/template/.opencode/memory/memory.db +0 -0
- package/dist/template/.opencode/memory.db +0 -0
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +199 -23
- package/dist/template/.opencode/opencode.json.tui-migration.bak +1380 -0
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/lib/capture.ts +177 -0
- package/dist/template/.opencode/plugin/lib/context.ts +194 -0
- package/dist/template/.opencode/plugin/lib/curator.ts +234 -0
- package/dist/template/.opencode/plugin/lib/db/maintenance.ts +312 -0
- package/dist/template/.opencode/plugin/lib/db/observations.ts +299 -0
- package/dist/template/.opencode/plugin/lib/db/pipeline.ts +520 -0
- package/dist/template/.opencode/plugin/lib/db/schema.ts +356 -0
- package/dist/template/.opencode/plugin/lib/db/types.ts +211 -0
- package/dist/template/.opencode/plugin/lib/distill.ts +376 -0
- package/dist/template/.opencode/plugin/lib/inject.ts +126 -0
- package/dist/template/.opencode/plugin/lib/memory-admin-tools.ts +188 -0
- package/dist/template/.opencode/plugin/lib/memory-db.ts +54 -936
- package/dist/template/.opencode/plugin/lib/memory-helpers.ts +202 -0
- package/dist/template/.opencode/plugin/lib/memory-hooks.ts +240 -0
- package/dist/template/.opencode/plugin/lib/memory-tools.ts +341 -0
- package/dist/template/.opencode/plugin/memory.ts +56 -60
- package/dist/template/.opencode/plugin/sessions.ts +372 -93
- package/dist/template/.opencode/tui.json +15 -0
- package/package.json +1 -1
- package/dist/template/.opencode/tool/action-queue.ts +0 -313
- package/dist/template/.opencode/tool/memory-admin.ts +0 -445
- package/dist/template/.opencode/tool/memory-get.ts +0 -143
- package/dist/template/.opencode/tool/memory-read.ts +0 -45
- package/dist/template/.opencode/tool/memory-search.ts +0 -264
- package/dist/template/.opencode/tool/memory-timeline.ts +0 -105
- package/dist/template/.opencode/tool/memory-update.ts +0 -63
- package/dist/template/.opencode/tool/observation.ts +0 -357
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
import {
|
|
5
|
-
type ObservationType,
|
|
6
|
-
type SearchIndexResult,
|
|
7
|
-
checkFTS5Available,
|
|
8
|
-
searchObservationsFTS,
|
|
9
|
-
} from "../plugin/lib/memory-db";
|
|
10
|
-
|
|
11
|
-
// Fallback file-based search for non-SQLite content
|
|
12
|
-
interface FileSearchResult {
|
|
13
|
-
file: string;
|
|
14
|
-
matches: { line: number; content: string }[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function searchDirectory(
|
|
18
|
-
dir: string,
|
|
19
|
-
pattern: RegExp,
|
|
20
|
-
results: FileSearchResult[],
|
|
21
|
-
baseDir: string,
|
|
22
|
-
): Promise<void> {
|
|
23
|
-
try {
|
|
24
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
25
|
-
|
|
26
|
-
for (const entry of entries) {
|
|
27
|
-
const fullPath = path.join(dir, entry.name);
|
|
28
|
-
|
|
29
|
-
if (entry.isDirectory()) {
|
|
30
|
-
// Skip hidden directories and vector_db
|
|
31
|
-
if (entry.name.startsWith(".") || entry.name === "vector_db") {
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
await searchDirectory(fullPath, pattern, results, baseDir);
|
|
35
|
-
} else if (entry.name.endsWith(".md")) {
|
|
36
|
-
const content = await fs.readFile(fullPath, "utf-8");
|
|
37
|
-
const lines = content.split("\n");
|
|
38
|
-
const matches: { line: number; content: string }[] = [];
|
|
39
|
-
|
|
40
|
-
for (let index = 0; index < lines.length; index++) {
|
|
41
|
-
const line = lines[index];
|
|
42
|
-
if (pattern.test(line)) {
|
|
43
|
-
matches.push({
|
|
44
|
-
line: index + 1,
|
|
45
|
-
content: line.trim().substring(0, 150),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (matches.length > 0) {
|
|
51
|
-
const relativePath = path.relative(baseDir, fullPath);
|
|
52
|
-
results.push({ file: relativePath, matches });
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
} catch {
|
|
57
|
-
// Directory doesn't exist or not readable
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function fallbackKeywordSearch(
|
|
62
|
-
query: string,
|
|
63
|
-
type: string | undefined,
|
|
64
|
-
limit: number,
|
|
65
|
-
): Promise<FileSearchResult[]> {
|
|
66
|
-
const memoryDir = path.join(process.cwd(), ".opencode/memory");
|
|
67
|
-
const beadsDir = path.join(process.cwd(), ".beads/artifacts");
|
|
68
|
-
const globalMemoryDir = path.join(
|
|
69
|
-
process.env.HOME || "",
|
|
70
|
-
".config/opencode/memory",
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
// Create case-insensitive regex from query
|
|
74
|
-
let pattern: RegExp;
|
|
75
|
-
try {
|
|
76
|
-
pattern = new RegExp(query, "i");
|
|
77
|
-
} catch {
|
|
78
|
-
// Escape special chars if not valid regex
|
|
79
|
-
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
80
|
-
pattern = new RegExp(escaped, "i");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const results: FileSearchResult[] = [];
|
|
84
|
-
|
|
85
|
-
// Handle type filtering
|
|
86
|
-
if (type === "beads") {
|
|
87
|
-
await searchDirectory(beadsDir, pattern, results, beadsDir);
|
|
88
|
-
} else if (type && type !== "all" && type !== "observations") {
|
|
89
|
-
const typeMap: Record<string, string> = {
|
|
90
|
-
handoffs: "handoffs",
|
|
91
|
-
research: "research",
|
|
92
|
-
templates: "_templates",
|
|
93
|
-
};
|
|
94
|
-
const subdir = typeMap[type];
|
|
95
|
-
if (subdir) {
|
|
96
|
-
const searchDir = path.join(memoryDir, subdir);
|
|
97
|
-
await searchDirectory(searchDir, pattern, results, memoryDir);
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
// Search all: memory + beads
|
|
101
|
-
await searchDirectory(memoryDir, pattern, results, memoryDir);
|
|
102
|
-
await searchDirectory(beadsDir, pattern, results, beadsDir);
|
|
103
|
-
await searchDirectory(globalMemoryDir, pattern, results, globalMemoryDir);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return results.slice(0, limit);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const TYPE_ICONS: Record<string, string> = {
|
|
110
|
-
decision: "🎯",
|
|
111
|
-
bugfix: "🐛",
|
|
112
|
-
feature: "✨",
|
|
113
|
-
pattern: "🔄",
|
|
114
|
-
discovery: "💡",
|
|
115
|
-
learning: "📚",
|
|
116
|
-
warning: "⚠️",
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
function formatCompactIndex(
|
|
120
|
-
results: SearchIndexResult[],
|
|
121
|
-
query: string,
|
|
122
|
-
): string {
|
|
123
|
-
if (results.length === 0) {
|
|
124
|
-
return `No observations found for "${query}".\n\nTip: Use memory-search without query to see recent observations.`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
let output = `# Search Results: "${query}"\n\n`;
|
|
128
|
-
output += `Found **${results.length}** observation(s). Use \`memory-get\` for full details.\n\n`;
|
|
129
|
-
output += "| ID | Type | Title | Date |\n";
|
|
130
|
-
output += "|---|---|---|---|\n";
|
|
131
|
-
|
|
132
|
-
for (const result of results) {
|
|
133
|
-
const icon = TYPE_ICONS[result.type] || "📝";
|
|
134
|
-
const date = result.created_at.split("T")[0];
|
|
135
|
-
const title =
|
|
136
|
-
result.title.length > 50
|
|
137
|
-
? `${result.title.substring(0, 47)}...`
|
|
138
|
-
: result.title;
|
|
139
|
-
output += `| #${result.id} | ${icon} ${result.type} | ${title} | ${date} |\n`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
output += "\n## Snippets\n\n";
|
|
143
|
-
for (const result of results) {
|
|
144
|
-
const icon = TYPE_ICONS[result.type] || "📝";
|
|
145
|
-
output += `**#${result.id}** ${icon} ${result.title}\n`;
|
|
146
|
-
if (result.snippet) {
|
|
147
|
-
output += `> ${result.snippet}...\n`;
|
|
148
|
-
}
|
|
149
|
-
output += "\n";
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
output += "\n---\n";
|
|
153
|
-
output += `💡 **Next steps:**\n`;
|
|
154
|
-
output += `- \`memory-get({ ids: "${results
|
|
155
|
-
.map((r) => r.id)
|
|
156
|
-
.slice(0, 3)
|
|
157
|
-
.join(",")}" })\` - Get full details\n`;
|
|
158
|
-
output += `- \`memory-timeline({ anchor_id: ${results[0].id} })\` - See chronological context\n`;
|
|
159
|
-
|
|
160
|
-
return output;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function formatFallbackResults(
|
|
164
|
-
query: string,
|
|
165
|
-
results: FileSearchResult[],
|
|
166
|
-
limit: number,
|
|
167
|
-
): string {
|
|
168
|
-
if (results.length === 0) {
|
|
169
|
-
return `No matches found for "${query}" in non-observation files.`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
let output = `# File Search: "${query}"\n\n`;
|
|
173
|
-
output += `Found ${results.length} file(s) with matches.\n\n`;
|
|
174
|
-
|
|
175
|
-
for (const result of results) {
|
|
176
|
-
output += `## ${result.file}\n\n`;
|
|
177
|
-
const matchesToShow = result.matches.slice(0, limit);
|
|
178
|
-
for (const match of matchesToShow) {
|
|
179
|
-
output += `- **Line ${match.line}:** ${match.content}\n`;
|
|
180
|
-
}
|
|
181
|
-
if (result.matches.length > limit) {
|
|
182
|
-
output += `- ... and ${result.matches.length - limit} more matches\n`;
|
|
183
|
-
}
|
|
184
|
-
output += "\n";
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return output;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export default tool({
|
|
191
|
-
description: `Search memory across observations and markdown archives.
|
|
192
|
-
|
|
193
|
-
Purpose:
|
|
194
|
-
- Fast, ranked search across all observations in SQLite (when FTS5 is available)
|
|
195
|
-
- Returns compact index (~50-100 tokens per result) for progressive disclosure
|
|
196
|
-
- Use memory-get for full details after identifying relevant observations
|
|
197
|
-
|
|
198
|
-
FTS5 availability:
|
|
199
|
-
- Auto-detected at runtime; if unavailable, observation searches fall back to file scan
|
|
200
|
-
|
|
201
|
-
Search modes and hints:
|
|
202
|
-
- "observations" (default): Best for decisions, bugs, learnings; uses FTS5 ranking when available
|
|
203
|
-
- "handoffs": Use for past session handoffs and summaries
|
|
204
|
-
- "research": Use for research notes and external findings
|
|
205
|
-
- "templates": Use for memory templates and boilerplate references
|
|
206
|
-
- "beads": Use for task artifacts in .beads/artifacts
|
|
207
|
-
- "all": Use when you are unsure where info lives; searches SQLite + markdown + beads
|
|
208
|
-
|
|
209
|
-
Example:
|
|
210
|
-
memory-search({ query: "authentication" })
|
|
211
|
-
memory-search({ query: "auth", type: "decision", limit: 5 })`,
|
|
212
|
-
args: {
|
|
213
|
-
query: tool.schema.string().describe("Search query: keywords or phrase"),
|
|
214
|
-
type: tool.schema
|
|
215
|
-
.string()
|
|
216
|
-
.optional()
|
|
217
|
-
.describe(
|
|
218
|
-
"Filter by observation type (decision, bugfix, feature, pattern, discovery, learning, warning) or search scope (observations, handoffs, research, templates, beads, all)",
|
|
219
|
-
),
|
|
220
|
-
limit: tool.schema
|
|
221
|
-
.number()
|
|
222
|
-
.optional()
|
|
223
|
-
.describe("Max results (default: 10)"),
|
|
224
|
-
},
|
|
225
|
-
execute: async (args: { query: string; type?: string; limit?: number }) => {
|
|
226
|
-
const limit = args.limit || 10;
|
|
227
|
-
|
|
228
|
-
// Determine if we should use SQLite FTS5 or fallback
|
|
229
|
-
const observationTypes = [
|
|
230
|
-
"decision",
|
|
231
|
-
"bugfix",
|
|
232
|
-
"feature",
|
|
233
|
-
"pattern",
|
|
234
|
-
"discovery",
|
|
235
|
-
"learning",
|
|
236
|
-
"warning",
|
|
237
|
-
];
|
|
238
|
-
const isObservationType =
|
|
239
|
-
args.type && observationTypes.includes(args.type.toLowerCase());
|
|
240
|
-
const isObservationsScope =
|
|
241
|
-
!args.type || args.type === "observations" || isObservationType;
|
|
242
|
-
|
|
243
|
-
// Try SQLite FTS5 for observations
|
|
244
|
-
if (isObservationsScope && checkFTS5Available()) {
|
|
245
|
-
try {
|
|
246
|
-
const obsType = isObservationType
|
|
247
|
-
? (args.type?.toLowerCase() as ObservationType)
|
|
248
|
-
: undefined;
|
|
249
|
-
const results = searchObservationsFTS(args.query, {
|
|
250
|
-
type: obsType,
|
|
251
|
-
limit,
|
|
252
|
-
});
|
|
253
|
-
return formatCompactIndex(results, args.query);
|
|
254
|
-
} catch {
|
|
255
|
-
// FTS5 failed, fall through to file search
|
|
256
|
-
// Silently fall back to file-based search
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Fallback to file-based search for non-observation types or FTS5 failure
|
|
261
|
-
const results = await fallbackKeywordSearch(args.query, args.type, limit);
|
|
262
|
-
return formatFallbackResults(args.query, results, limit);
|
|
263
|
-
},
|
|
264
|
-
});
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import {
|
|
3
|
-
type SearchIndexResult,
|
|
4
|
-
getTimelineAroundObservation,
|
|
5
|
-
} from "../plugin/lib/memory-db";
|
|
6
|
-
|
|
7
|
-
const TYPE_ICONS: Record<string, string> = {
|
|
8
|
-
decision: "🎯",
|
|
9
|
-
bugfix: "🐛",
|
|
10
|
-
feature: "✨",
|
|
11
|
-
pattern: "🔄",
|
|
12
|
-
discovery: "💡",
|
|
13
|
-
learning: "📚",
|
|
14
|
-
warning: "⚠️",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
function formatTimelineResult(
|
|
18
|
-
anchor: {
|
|
19
|
-
id: number;
|
|
20
|
-
type: string;
|
|
21
|
-
title: string;
|
|
22
|
-
created_at: string;
|
|
23
|
-
} | null,
|
|
24
|
-
before: SearchIndexResult[],
|
|
25
|
-
after: SearchIndexResult[],
|
|
26
|
-
): string {
|
|
27
|
-
if (!anchor) {
|
|
28
|
-
return "Anchor observation not found.";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let output = "# Timeline Context\n\n";
|
|
32
|
-
|
|
33
|
-
// Before (older observations)
|
|
34
|
-
if (before.length > 0) {
|
|
35
|
-
output += "## Earlier Observations\n\n";
|
|
36
|
-
for (const obs of before) {
|
|
37
|
-
const icon = TYPE_ICONS[obs.type] || "📝";
|
|
38
|
-
const date = obs.created_at.split("T")[0];
|
|
39
|
-
output += `- **#${obs.id}** ${icon} ${obs.title} _(${date})_\n`;
|
|
40
|
-
if (obs.snippet) {
|
|
41
|
-
output += ` ${obs.snippet.substring(0, 80)}...\n`;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
output += "\n";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Anchor
|
|
48
|
-
const anchorIcon = TYPE_ICONS[anchor.type] || "📝";
|
|
49
|
-
const anchorDate = anchor.created_at.split("T")[0];
|
|
50
|
-
output += `## ▶ Current: #${anchor.id}\n\n`;
|
|
51
|
-
output += `${anchorIcon} **${anchor.title}** _(${anchorDate})_\n\n`;
|
|
52
|
-
|
|
53
|
-
// After (newer observations)
|
|
54
|
-
if (after.length > 0) {
|
|
55
|
-
output += "## Later Observations\n\n";
|
|
56
|
-
for (const obs of after) {
|
|
57
|
-
const icon = TYPE_ICONS[obs.type] || "📝";
|
|
58
|
-
const date = obs.created_at.split("T")[0];
|
|
59
|
-
output += `- **#${obs.id}** ${icon} ${obs.title} _(${date})_\n`;
|
|
60
|
-
if (obs.snippet) {
|
|
61
|
-
output += ` ${obs.snippet.substring(0, 80)}...\n`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return output;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export default tool({
|
|
70
|
-
description: `Get chronological context around an observation.
|
|
71
|
-
|
|
72
|
-
Purpose:
|
|
73
|
-
- Progressive disclosure: see what was happening before/after a specific observation
|
|
74
|
-
- Understand decision context over time
|
|
75
|
-
- Navigate memory timeline
|
|
76
|
-
|
|
77
|
-
Example:
|
|
78
|
-
memory-timeline({ anchor_id: 42, depth_before: 5, depth_after: 5 })`,
|
|
79
|
-
args: {
|
|
80
|
-
anchor_id: tool.schema
|
|
81
|
-
.number()
|
|
82
|
-
.describe("ID of the observation to get context around"),
|
|
83
|
-
depth_before: tool.schema
|
|
84
|
-
.number()
|
|
85
|
-
.optional()
|
|
86
|
-
.describe("Number of earlier observations to include (default: 5)"),
|
|
87
|
-
depth_after: tool.schema
|
|
88
|
-
.number()
|
|
89
|
-
.optional()
|
|
90
|
-
.describe("Number of later observations to include (default: 5)"),
|
|
91
|
-
},
|
|
92
|
-
execute: async (args: {
|
|
93
|
-
anchor_id: number;
|
|
94
|
-
depth_before?: number;
|
|
95
|
-
depth_after?: number;
|
|
96
|
-
}) => {
|
|
97
|
-
const { anchor, before, after } = getTimelineAroundObservation(
|
|
98
|
-
args.anchor_id,
|
|
99
|
-
args.depth_before ?? 5,
|
|
100
|
-
args.depth_after ?? 5,
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
return formatTimelineResult(anchor, before, after);
|
|
104
|
-
},
|
|
105
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { upsertMemoryFile } from "../plugin/lib/memory-db.js";
|
|
3
|
-
|
|
4
|
-
export default tool({
|
|
5
|
-
description: `Update memory files with new learnings, progress, or context.
|
|
6
|
-
|
|
7
|
-
Purpose:
|
|
8
|
-
- Write or append to project memory in SQLite
|
|
9
|
-
- Supports subdirectories (e.g., 'research/2024-01-topic')
|
|
10
|
-
- Two modes: 'replace' (overwrite) or 'append' (add to end)
|
|
11
|
-
|
|
12
|
-
Example:
|
|
13
|
-
memory-update({ file: "research/session-findings", content: "..." })
|
|
14
|
-
memory-update({ file: "handoffs/phase-2", content: "...", mode: "append" })`,
|
|
15
|
-
args: {
|
|
16
|
-
file: tool.schema
|
|
17
|
-
.string()
|
|
18
|
-
.describe(
|
|
19
|
-
"Memory file to update: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic",
|
|
20
|
-
),
|
|
21
|
-
content: tool.schema
|
|
22
|
-
.string()
|
|
23
|
-
.describe("Content to write or append to the memory file"),
|
|
24
|
-
mode: tool.schema
|
|
25
|
-
.string()
|
|
26
|
-
.optional()
|
|
27
|
-
.default("replace")
|
|
28
|
-
.describe(
|
|
29
|
-
"Update mode: 'replace' (overwrite file) or 'append' (add to end).",
|
|
30
|
-
),
|
|
31
|
-
},
|
|
32
|
-
execute: async (args: { file: string; content: string; mode?: string }) => {
|
|
33
|
-
// Normalize file path: strip existing .md extension
|
|
34
|
-
const normalizedFile = args.file.replace(/\.md$/i, "");
|
|
35
|
-
const mode = (args.mode || "replace") as "replace" | "append";
|
|
36
|
-
|
|
37
|
-
const timestamp = new Date().toISOString();
|
|
38
|
-
let finalContent: string;
|
|
39
|
-
|
|
40
|
-
if (mode === "append") {
|
|
41
|
-
finalContent = `\n\n---\n**Updated:** ${timestamp}\n\n${args.content}`;
|
|
42
|
-
} else {
|
|
43
|
-
// Replace mode - update timestamp placeholder if present
|
|
44
|
-
finalContent = args.content.replace(
|
|
45
|
-
/\*\*Last Updated:\*\* \[Timestamp\]/,
|
|
46
|
-
`**Last Updated:** ${timestamp}`,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
// Store in SQLite (single source of truth)
|
|
52
|
-
upsertMemoryFile(normalizedFile, finalContent, mode);
|
|
53
|
-
|
|
54
|
-
const action = mode === "append" ? "Appended to" : "Updated";
|
|
55
|
-
return `✓ ${action} ${normalizedFile}`;
|
|
56
|
-
} catch (error) {
|
|
57
|
-
if (error instanceof Error) {
|
|
58
|
-
return `Error updating memory: ${error.message}`;
|
|
59
|
-
}
|
|
60
|
-
return "Unknown error updating memory file";
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
});
|