opencode-claude-memory 1.7.6 → 1.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -213
- package/dist/agents.d.ts +12 -0
- package/dist/agents.js +69 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.js +79 -0
- package/dist/extraction/ExtractionCoordinator.d.ts +62 -0
- package/dist/extraction/ExtractionCoordinator.js +386 -0
- package/dist/extraction/autodream.d.ts +33 -0
- package/dist/extraction/autodream.js +85 -0
- package/dist/extraction/forkSession.d.ts +37 -0
- package/dist/extraction/forkSession.js +133 -0
- package/dist/extraction/lock.d.ts +29 -0
- package/dist/extraction/lock.js +191 -0
- package/dist/extraction/prompts.d.ts +5 -0
- package/dist/extraction/prompts.js +78 -0
- package/dist/extraction/state.d.ts +38 -0
- package/dist/extraction/state.js +159 -0
- package/dist/hooks/ignore.d.ts +6 -0
- package/dist/hooks/ignore.js +53 -0
- package/dist/hooks/messages.d.ts +14 -0
- package/dist/hooks/messages.js +96 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +50 -633
- package/dist/prompt/sections.d.ts +7 -0
- package/dist/{prompt.js → prompt/sections.js} +16 -72
- package/dist/prompt/systemPrompt.d.ts +7 -0
- package/dist/prompt/systemPrompt.js +64 -0
- package/dist/recall/RecallCoordinator.d.ts +36 -0
- package/dist/recall/RecallCoordinator.js +140 -0
- package/dist/recall/format.d.ts +17 -0
- package/dist/{recall.js → recall/format.js} +13 -36
- package/dist/{recallSelector.d.ts → recall/selector.d.ts} +26 -16
- package/dist/recall/selector.js +92 -0
- package/dist/sdk.d.ts +12 -0
- package/dist/sdk.js +8 -0
- package/dist/store/MemoryStore.d.ts +41 -0
- package/dist/store/MemoryStore.js +105 -0
- package/dist/store/frontmatter.d.ts +25 -0
- package/dist/store/frontmatter.js +61 -0
- package/dist/store/indexFile.d.ts +15 -0
- package/dist/store/indexFile.js +153 -0
- package/dist/{paths.d.ts → store/paths.d.ts} +7 -6
- package/dist/store/paths.js +187 -0
- package/dist/store/scan.d.ts +29 -0
- package/dist/store/scan.js +112 -0
- package/dist/tools.d.ts +9 -0
- package/dist/tools.js +142 -0
- package/dist/util/exclusiveFile.d.ts +11 -0
- package/dist/util/exclusiveFile.js +108 -0
- package/dist/util/legacyShellHook.d.ts +3 -0
- package/dist/util/legacyShellHook.js +23 -0
- package/dist/util/log.d.ts +6 -0
- package/dist/util/log.js +29 -0
- package/dist/util/ownedSessions.d.ts +7 -0
- package/dist/util/ownedSessions.js +39 -0
- package/dist/util/timeout.d.ts +5 -0
- package/dist/util/timeout.js +39 -0
- package/package.json +15 -8
- package/bin/opencode-memory +0 -1436
- package/dist/memory.d.ts +0 -25
- package/dist/memory.js +0 -203
- package/dist/memoryScan.d.ts +0 -20
- package/dist/memoryScan.js +0 -106
- package/dist/nativeExtraction.d.ts +0 -2
- package/dist/nativeExtraction.js +0 -38
- package/dist/paths.js +0 -141
- package/dist/prompt.d.ts +0 -4
- package/dist/recall.d.ts +0 -12
- package/dist/recallSelector.js +0 -154
package/dist/memory.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export declare const MEMORY_TYPES: readonly ["user", "feedback", "project", "reference"];
|
|
2
|
-
export type MemoryType = (typeof MEMORY_TYPES)[number];
|
|
3
|
-
export type MemoryEntry = {
|
|
4
|
-
filePath: string;
|
|
5
|
-
fileName: string;
|
|
6
|
-
name: string;
|
|
7
|
-
description: string;
|
|
8
|
-
type: MemoryType;
|
|
9
|
-
content: string;
|
|
10
|
-
rawContent: string;
|
|
11
|
-
};
|
|
12
|
-
export declare function listMemories(worktree: string): MemoryEntry[];
|
|
13
|
-
export declare function readMemory(worktree: string, fileName: string): MemoryEntry | null;
|
|
14
|
-
export declare function saveMemory(worktree: string, fileName: string, name: string, description: string, type: MemoryType, content: string): string;
|
|
15
|
-
export declare function deleteMemory(worktree: string, fileName: string): boolean;
|
|
16
|
-
export declare function searchMemories(worktree: string, query: string): MemoryEntry[];
|
|
17
|
-
export declare function readIndex(worktree: string): string;
|
|
18
|
-
export type EntrypointTruncation = {
|
|
19
|
-
content: string;
|
|
20
|
-
lineCount: number;
|
|
21
|
-
byteCount: number;
|
|
22
|
-
wasLineTruncated: boolean;
|
|
23
|
-
wasByteTruncated: boolean;
|
|
24
|
-
};
|
|
25
|
-
export declare function truncateEntrypoint(raw: string): EntrypointTruncation;
|
package/dist/memory.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, readdirSync, unlinkSync } from "fs";
|
|
2
|
-
import { join, basename } from "path";
|
|
3
|
-
import { getMemoryDir, getMemoryEntrypoint, ENTRYPOINT_NAME, validateMemoryFileName, MAX_MEMORY_FILES, MAX_MEMORY_FILE_BYTES, MAX_ENTRYPOINT_LINES, MAX_ENTRYPOINT_BYTES, FRONTMATTER_MAX_LINES, } from "./paths.js";
|
|
4
|
-
export const MEMORY_TYPES = ["user", "feedback", "project", "reference"];
|
|
5
|
-
function parseFrontmatter(raw) {
|
|
6
|
-
const trimmed = raw.trim();
|
|
7
|
-
if (!trimmed.startsWith("---")) {
|
|
8
|
-
return { frontmatter: {}, content: trimmed };
|
|
9
|
-
}
|
|
10
|
-
const lines = trimmed.split("\n");
|
|
11
|
-
let closingLineIdx = -1;
|
|
12
|
-
for (let i = 1; i < Math.min(lines.length, FRONTMATTER_MAX_LINES); i++) {
|
|
13
|
-
if (lines[i].trimEnd() === "---") {
|
|
14
|
-
closingLineIdx = i;
|
|
15
|
-
break;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
if (closingLineIdx === -1) {
|
|
19
|
-
return { frontmatter: {}, content: trimmed };
|
|
20
|
-
}
|
|
21
|
-
const endIndex = lines.slice(0, closingLineIdx).join("\n").length + 1;
|
|
22
|
-
const frontmatterBlock = trimmed.slice(3, endIndex).trim();
|
|
23
|
-
const content = trimmed.slice(endIndex + 3).trim();
|
|
24
|
-
const frontmatter = {};
|
|
25
|
-
for (const line of frontmatterBlock.split("\n")) {
|
|
26
|
-
const colonIdx = line.indexOf(":");
|
|
27
|
-
if (colonIdx === -1)
|
|
28
|
-
continue;
|
|
29
|
-
const key = line.slice(0, colonIdx).trim();
|
|
30
|
-
const value = line.slice(colonIdx + 1).trim();
|
|
31
|
-
if (key && value) {
|
|
32
|
-
frontmatter[key] = value;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return { frontmatter, content };
|
|
36
|
-
}
|
|
37
|
-
function buildFrontmatter(name, description, type) {
|
|
38
|
-
return `---\nname: ${name}\ndescription: ${description}\ntype: ${type}\n---`;
|
|
39
|
-
}
|
|
40
|
-
function parseMemoryType(raw) {
|
|
41
|
-
if (!raw)
|
|
42
|
-
return undefined;
|
|
43
|
-
return MEMORY_TYPES.find((t) => t === raw);
|
|
44
|
-
}
|
|
45
|
-
export function listMemories(worktree) {
|
|
46
|
-
const memDir = getMemoryDir(worktree);
|
|
47
|
-
const entries = [];
|
|
48
|
-
let files;
|
|
49
|
-
try {
|
|
50
|
-
files = readdirSync(memDir, { encoding: "utf-8" })
|
|
51
|
-
.filter((f) => f.endsWith(".md") && f !== ENTRYPOINT_NAME)
|
|
52
|
-
.sort()
|
|
53
|
-
.slice(0, MAX_MEMORY_FILES);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return entries;
|
|
57
|
-
}
|
|
58
|
-
for (const fileName of files) {
|
|
59
|
-
const filePath = join(memDir, fileName);
|
|
60
|
-
try {
|
|
61
|
-
const rawContent = readFileSync(filePath, "utf-8");
|
|
62
|
-
const { frontmatter, content } = parseFrontmatter(rawContent);
|
|
63
|
-
entries.push({
|
|
64
|
-
filePath,
|
|
65
|
-
fileName,
|
|
66
|
-
name: frontmatter.name ?? fileName.replace(/\.md$/, ""),
|
|
67
|
-
description: frontmatter.description ?? "",
|
|
68
|
-
type: parseMemoryType(frontmatter.type) ?? "user",
|
|
69
|
-
content,
|
|
70
|
-
rawContent,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return entries;
|
|
77
|
-
}
|
|
78
|
-
export function readMemory(worktree, fileName) {
|
|
79
|
-
const safeName = validateMemoryFileName(fileName);
|
|
80
|
-
const memDir = getMemoryDir(worktree);
|
|
81
|
-
const filePath = join(memDir, safeName);
|
|
82
|
-
try {
|
|
83
|
-
const rawContent = readFileSync(filePath, "utf-8");
|
|
84
|
-
const { frontmatter, content } = parseFrontmatter(rawContent);
|
|
85
|
-
return {
|
|
86
|
-
filePath,
|
|
87
|
-
fileName: basename(filePath),
|
|
88
|
-
name: frontmatter.name ?? fileName.replace(/\.md$/, ""),
|
|
89
|
-
description: frontmatter.description ?? "",
|
|
90
|
-
type: parseMemoryType(frontmatter.type) ?? "user",
|
|
91
|
-
content,
|
|
92
|
-
rawContent,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
export function saveMemory(worktree, fileName, name, description, type, content) {
|
|
100
|
-
const safeName = validateMemoryFileName(fileName);
|
|
101
|
-
if (typeof name !== "string" || !name.trim()) {
|
|
102
|
-
throw new Error("Memory name is required");
|
|
103
|
-
}
|
|
104
|
-
const memDir = getMemoryDir(worktree);
|
|
105
|
-
const filePath = join(memDir, safeName);
|
|
106
|
-
const fileContent = `${buildFrontmatter(name, description, type)}\n\n${content.trim()}\n`;
|
|
107
|
-
if (Buffer.byteLength(fileContent, "utf-8") > MAX_MEMORY_FILE_BYTES) {
|
|
108
|
-
throw new Error(`Memory file content exceeds the ${MAX_MEMORY_FILE_BYTES}-byte limit`);
|
|
109
|
-
}
|
|
110
|
-
writeFileSync(filePath, fileContent, "utf-8");
|
|
111
|
-
updateIndex(worktree, safeName, name, description);
|
|
112
|
-
return filePath;
|
|
113
|
-
}
|
|
114
|
-
export function deleteMemory(worktree, fileName) {
|
|
115
|
-
const safeName = validateMemoryFileName(fileName);
|
|
116
|
-
const memDir = getMemoryDir(worktree);
|
|
117
|
-
const filePath = join(memDir, safeName);
|
|
118
|
-
try {
|
|
119
|
-
unlinkSync(filePath);
|
|
120
|
-
removeFromIndex(worktree, safeName);
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
export function searchMemories(worktree, query) {
|
|
128
|
-
const all = listMemories(worktree);
|
|
129
|
-
const lowerQuery = query.toLowerCase();
|
|
130
|
-
return all.filter((entry) => entry.name.toLowerCase().includes(lowerQuery) ||
|
|
131
|
-
entry.description.toLowerCase().includes(lowerQuery) ||
|
|
132
|
-
entry.content.toLowerCase().includes(lowerQuery));
|
|
133
|
-
}
|
|
134
|
-
export function readIndex(worktree) {
|
|
135
|
-
const entrypoint = getMemoryEntrypoint(worktree);
|
|
136
|
-
try {
|
|
137
|
-
return readFileSync(entrypoint, "utf-8");
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
return "";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
function updateIndex(worktree, fileName, name, description) {
|
|
144
|
-
const entrypoint = getMemoryEntrypoint(worktree);
|
|
145
|
-
const existing = readIndex(worktree);
|
|
146
|
-
const lines = existing.split("\n").filter((l) => l.trim());
|
|
147
|
-
const pointer = `- [${name}](${fileName}) — ${description}`;
|
|
148
|
-
const existingIdx = lines.findIndex((l) => l.includes(`(${fileName})`));
|
|
149
|
-
if (existingIdx >= 0) {
|
|
150
|
-
lines[existingIdx] = pointer;
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
lines.push(pointer);
|
|
154
|
-
}
|
|
155
|
-
writeFileSync(entrypoint, lines.join("\n") + "\n", "utf-8");
|
|
156
|
-
}
|
|
157
|
-
function removeFromIndex(worktree, fileName) {
|
|
158
|
-
const entrypoint = getMemoryEntrypoint(worktree);
|
|
159
|
-
const existing = readIndex(worktree);
|
|
160
|
-
const lines = existing
|
|
161
|
-
.split("\n")
|
|
162
|
-
.filter((l) => l.trim() && !l.includes(`(${fileName})`));
|
|
163
|
-
writeFileSync(entrypoint, lines.length > 0 ? lines.join("\n") + "\n" : "", "utf-8");
|
|
164
|
-
}
|
|
165
|
-
function formatFileSize(bytes) {
|
|
166
|
-
if (bytes < 1024)
|
|
167
|
-
return `${bytes}B`;
|
|
168
|
-
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
169
|
-
}
|
|
170
|
-
// Port of Claude Code's truncateEntrypointContent() from memdir.ts.
|
|
171
|
-
// Uses .length (char count, same as Claude Code) for byte measurement.
|
|
172
|
-
export function truncateEntrypoint(raw) {
|
|
173
|
-
const trimmed = raw.trim();
|
|
174
|
-
if (!trimmed)
|
|
175
|
-
return { content: "", lineCount: 0, byteCount: 0, wasLineTruncated: false, wasByteTruncated: false };
|
|
176
|
-
const contentLines = trimmed.split("\n");
|
|
177
|
-
const lineCount = contentLines.length;
|
|
178
|
-
const byteCount = trimmed.length;
|
|
179
|
-
const wasLineTruncated = lineCount > MAX_ENTRYPOINT_LINES;
|
|
180
|
-
const wasByteTruncated = byteCount > MAX_ENTRYPOINT_BYTES;
|
|
181
|
-
if (!wasLineTruncated && !wasByteTruncated) {
|
|
182
|
-
return { content: trimmed, lineCount, byteCount, wasLineTruncated, wasByteTruncated };
|
|
183
|
-
}
|
|
184
|
-
let truncated = wasLineTruncated
|
|
185
|
-
? contentLines.slice(0, MAX_ENTRYPOINT_LINES).join("\n")
|
|
186
|
-
: trimmed;
|
|
187
|
-
if (truncated.length > MAX_ENTRYPOINT_BYTES) {
|
|
188
|
-
const cutAt = truncated.lastIndexOf("\n", MAX_ENTRYPOINT_BYTES);
|
|
189
|
-
truncated = truncated.slice(0, cutAt > 0 ? cutAt : MAX_ENTRYPOINT_BYTES);
|
|
190
|
-
}
|
|
191
|
-
const reason = wasByteTruncated && !wasLineTruncated
|
|
192
|
-
? `${formatFileSize(byteCount)} (limit: ${formatFileSize(MAX_ENTRYPOINT_BYTES)}) — index entries are too long`
|
|
193
|
-
: wasLineTruncated && !wasByteTruncated
|
|
194
|
-
? `${lineCount} lines (limit: ${MAX_ENTRYPOINT_LINES})`
|
|
195
|
-
: `${lineCount} lines and ${formatFileSize(byteCount)}`;
|
|
196
|
-
return {
|
|
197
|
-
content: truncated + `\n\n> WARNING: ${ENTRYPOINT_NAME} is ${reason}. Only part of it was loaded. Keep index entries to one line under ~200 chars; move detail into topic files.`,
|
|
198
|
-
lineCount,
|
|
199
|
-
byteCount,
|
|
200
|
-
wasLineTruncated,
|
|
201
|
-
wasByteTruncated,
|
|
202
|
-
};
|
|
203
|
-
}
|
package/dist/memoryScan.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { MemoryType } from "./memory.js";
|
|
2
|
-
export type MemoryHeader = {
|
|
3
|
-
filename: string;
|
|
4
|
-
filePath: string;
|
|
5
|
-
mtimeMs: number;
|
|
6
|
-
name: string | null;
|
|
7
|
-
description: string | null;
|
|
8
|
-
type: MemoryType | undefined;
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Recursive scan of memory directory. Reads only frontmatter (first N lines),
|
|
12
|
-
* returns headers sorted by mtime desc, capped at MAX_MEMORY_FILES.
|
|
13
|
-
* Port of Claude Code's scanMemoryFiles().
|
|
14
|
-
*/
|
|
15
|
-
export declare function scanMemoryFiles(memoryDir: string): MemoryHeader[];
|
|
16
|
-
export declare function formatMemoryManifest(memories: MemoryHeader[]): string;
|
|
17
|
-
export declare function getMemoryManifest(worktree: string): {
|
|
18
|
-
headers: MemoryHeader[];
|
|
19
|
-
manifest: string;
|
|
20
|
-
};
|
package/dist/memoryScan.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, statSync } from "fs";
|
|
2
|
-
import { basename, join } from "path";
|
|
3
|
-
import { getMemoryDir, ENTRYPOINT_NAME, MAX_MEMORY_FILES, FRONTMATTER_MAX_LINES, } from "./paths.js";
|
|
4
|
-
const MEMORY_TYPES = ["user", "feedback", "project", "reference"];
|
|
5
|
-
function parseMemoryType(raw) {
|
|
6
|
-
if (!raw)
|
|
7
|
-
return undefined;
|
|
8
|
-
return MEMORY_TYPES.includes(raw) ? raw : undefined;
|
|
9
|
-
}
|
|
10
|
-
function readFileHeader(filePath, maxLines) {
|
|
11
|
-
try {
|
|
12
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
13
|
-
const stat = statSync(filePath);
|
|
14
|
-
const lines = raw.split("\n");
|
|
15
|
-
const header = lines.slice(0, maxLines).join("\n");
|
|
16
|
-
return { content: header, mtimeMs: stat.mtimeMs };
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
return { content: "", mtimeMs: 0 };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function parseFrontmatterHeader(raw) {
|
|
23
|
-
const trimmed = raw.trim();
|
|
24
|
-
if (!trimmed.startsWith("---")) {
|
|
25
|
-
return {};
|
|
26
|
-
}
|
|
27
|
-
const lines = trimmed.split("\n");
|
|
28
|
-
let closingLineIdx = -1;
|
|
29
|
-
for (let i = 1; i < lines.length; i++) {
|
|
30
|
-
if (lines[i].trimEnd() === "---") {
|
|
31
|
-
closingLineIdx = i;
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (closingLineIdx === -1) {
|
|
36
|
-
return {};
|
|
37
|
-
}
|
|
38
|
-
const frontmatter = {};
|
|
39
|
-
for (let i = 1; i < closingLineIdx; i++) {
|
|
40
|
-
const line = lines[i];
|
|
41
|
-
const colonIdx = line.indexOf(":");
|
|
42
|
-
if (colonIdx === -1)
|
|
43
|
-
continue;
|
|
44
|
-
const key = line.slice(0, colonIdx).trim();
|
|
45
|
-
const value = line.slice(colonIdx + 1).trim();
|
|
46
|
-
if (key && value) {
|
|
47
|
-
frontmatter[key] = value;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return frontmatter;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Recursive scan of memory directory. Reads only frontmatter (first N lines),
|
|
54
|
-
* returns headers sorted by mtime desc, capped at MAX_MEMORY_FILES.
|
|
55
|
-
* Port of Claude Code's scanMemoryFiles().
|
|
56
|
-
*/
|
|
57
|
-
export function scanMemoryFiles(memoryDir) {
|
|
58
|
-
try {
|
|
59
|
-
const entries = readdirSync(memoryDir, { recursive: true, encoding: "utf-8" });
|
|
60
|
-
const mdFiles = entries.filter((f) => f.endsWith(".md") && basename(f) !== ENTRYPOINT_NAME);
|
|
61
|
-
const headers = [];
|
|
62
|
-
for (const relativePath of mdFiles) {
|
|
63
|
-
const filePath = join(memoryDir, relativePath);
|
|
64
|
-
try {
|
|
65
|
-
const { content, mtimeMs } = readFileHeader(filePath, FRONTMATTER_MAX_LINES);
|
|
66
|
-
const frontmatter = parseFrontmatterHeader(content);
|
|
67
|
-
headers.push({
|
|
68
|
-
filename: relativePath,
|
|
69
|
-
filePath,
|
|
70
|
-
mtimeMs,
|
|
71
|
-
name: frontmatter.name || null,
|
|
72
|
-
description: frontmatter.description || null,
|
|
73
|
-
type: parseMemoryType(frontmatter.type),
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// skip unreadable files
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return headers
|
|
81
|
-
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
82
|
-
.slice(0, MAX_MEMORY_FILES);
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// Port of Claude Code's formatMemoryManifest():
|
|
89
|
-
// `- [type] filename (ISO timestamp): description` per line
|
|
90
|
-
export function formatMemoryManifest(memories) {
|
|
91
|
-
return memories
|
|
92
|
-
.map((m) => {
|
|
93
|
-
const tag = m.type ? `[${m.type}] ` : "";
|
|
94
|
-
const ts = new Date(m.mtimeMs).toISOString();
|
|
95
|
-
return m.description
|
|
96
|
-
? `- ${tag}${m.filename} (${ts}): ${m.description}`
|
|
97
|
-
: `- ${tag}${m.filename} (${ts})`;
|
|
98
|
-
})
|
|
99
|
-
.join("\n");
|
|
100
|
-
}
|
|
101
|
-
export function getMemoryManifest(worktree) {
|
|
102
|
-
const memoryDir = getMemoryDir(worktree);
|
|
103
|
-
const headers = scanMemoryFiles(memoryDir);
|
|
104
|
-
const manifest = formatMemoryManifest(headers);
|
|
105
|
-
return { headers, manifest };
|
|
106
|
-
}
|
package/dist/nativeExtraction.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const DEFAULT_NATIVE_EXTRACT_TIMEOUT_MS = 120_000;
|
|
2
|
-
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
3
|
-
function getErrorMessage(error) {
|
|
4
|
-
if (error instanceof Error)
|
|
5
|
-
return error.message;
|
|
6
|
-
if (error && typeof error === "object") {
|
|
7
|
-
const message = error.message;
|
|
8
|
-
if (typeof message === "string")
|
|
9
|
-
return message;
|
|
10
|
-
}
|
|
11
|
-
return String(error);
|
|
12
|
-
}
|
|
13
|
-
export function getNativeExtractTimeoutMs(raw = process.env.OPENCODE_MEMORY_EXTRACT_TIMEOUT_MS) {
|
|
14
|
-
const timeout = Number(raw);
|
|
15
|
-
return Number.isSafeInteger(timeout) && timeout > 0 && timeout <= MAX_TIMER_DELAY_MS
|
|
16
|
-
? timeout
|
|
17
|
-
: DEFAULT_NATIVE_EXTRACT_TIMEOUT_MS;
|
|
18
|
-
}
|
|
19
|
-
export function logNativeExtractionFailure(client, directory, sessionID, error) {
|
|
20
|
-
const c = client;
|
|
21
|
-
if (!c?.app?.log)
|
|
22
|
-
return;
|
|
23
|
-
try {
|
|
24
|
-
const message = getErrorMessage(error);
|
|
25
|
-
void c.app.log({
|
|
26
|
-
body: {
|
|
27
|
-
service: "opencode-claude-memory",
|
|
28
|
-
level: "error",
|
|
29
|
-
message: "Native extraction failed",
|
|
30
|
-
extra: { error: message, sessionID },
|
|
31
|
-
},
|
|
32
|
-
query: { directory },
|
|
33
|
-
}).catch(() => { });
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
// Logging must stay best-effort: stderr is rendered into the OpenCode chat UI.
|
|
37
|
-
}
|
|
38
|
-
}
|
package/dist/paths.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
// Claude Code compatible memory directory path resolution.
|
|
2
|
-
// Directory: ~/.claude/projects/<sanitizePath(canonicalGitRoot)>/memory/
|
|
3
|
-
// Ensures bidirectional memory sharing between Claude Code and OpenCode.
|
|
4
|
-
import { homedir } from "os";
|
|
5
|
-
import { join, dirname, resolve, sep } from "path";
|
|
6
|
-
import { mkdirSync, existsSync, readFileSync, statSync, realpathSync } from "fs";
|
|
7
|
-
export const ENTRYPOINT_NAME = "MEMORY.md";
|
|
8
|
-
export const MAX_ENTRYPOINT_LINES = 200;
|
|
9
|
-
export const MAX_ENTRYPOINT_BYTES = 25_000;
|
|
10
|
-
export const MAX_MEMORY_FILES = 200;
|
|
11
|
-
export const MAX_MEMORY_FILE_BYTES = 40_000;
|
|
12
|
-
export const FRONTMATTER_MAX_LINES = 30;
|
|
13
|
-
export function validateMemoryFileName(fileName) {
|
|
14
|
-
const base = fileName.endsWith(".md") ? fileName.slice(0, -3) : fileName;
|
|
15
|
-
if (base.length === 0) {
|
|
16
|
-
throw new Error("Memory file name cannot be empty");
|
|
17
|
-
}
|
|
18
|
-
if (base.includes("/") || base.includes("\\")) {
|
|
19
|
-
throw new Error(`Memory file name must not contain path separators: ${fileName}`);
|
|
20
|
-
}
|
|
21
|
-
if (base.includes("..")) {
|
|
22
|
-
throw new Error(`Memory file name must not contain path traversal: ${fileName}`);
|
|
23
|
-
}
|
|
24
|
-
if (base.includes("\0")) {
|
|
25
|
-
throw new Error(`Memory file name must not contain null bytes: ${fileName}`);
|
|
26
|
-
}
|
|
27
|
-
if (base.startsWith(".")) {
|
|
28
|
-
throw new Error(`Memory file name must not start with '.': ${fileName}`);
|
|
29
|
-
}
|
|
30
|
-
if (base.toUpperCase() === "MEMORY") {
|
|
31
|
-
throw new Error(`'MEMORY' is a reserved name and cannot be used as a memory file name`);
|
|
32
|
-
}
|
|
33
|
-
return `${base}.md`;
|
|
34
|
-
}
|
|
35
|
-
const MAX_SANITIZED_LENGTH = 200;
|
|
36
|
-
// Exact copy of Claude Code's djb2Hash() from utils/hash.ts
|
|
37
|
-
function djb2Hash(str) {
|
|
38
|
-
let hash = 0;
|
|
39
|
-
for (let i = 0; i < str.length; i++) {
|
|
40
|
-
hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
|
|
41
|
-
}
|
|
42
|
-
return hash;
|
|
43
|
-
}
|
|
44
|
-
function simpleHash(str) {
|
|
45
|
-
return Math.abs(djb2Hash(str)).toString(36);
|
|
46
|
-
}
|
|
47
|
-
// Exact copy of Claude Code's sanitizePath() from utils/sessionStoragePortable.ts
|
|
48
|
-
export function sanitizePath(name) {
|
|
49
|
-
const sanitized = name.replace(/[^a-zA-Z0-9]/g, "-");
|
|
50
|
-
if (sanitized.length <= MAX_SANITIZED_LENGTH) {
|
|
51
|
-
return sanitized;
|
|
52
|
-
}
|
|
53
|
-
const hash = simpleHash(name);
|
|
54
|
-
return `${sanitized.slice(0, MAX_SANITIZED_LENGTH)}-${hash}`;
|
|
55
|
-
}
|
|
56
|
-
// Matches Claude Code's findGitRoot() from utils/git.ts
|
|
57
|
-
function findGitRoot(startPath) {
|
|
58
|
-
let current = resolve(startPath);
|
|
59
|
-
const root = current.substring(0, current.indexOf(sep) + 1) || sep;
|
|
60
|
-
while (current !== root) {
|
|
61
|
-
try {
|
|
62
|
-
const gitPath = join(current, ".git");
|
|
63
|
-
const s = statSync(gitPath);
|
|
64
|
-
if (s.isDirectory() || s.isFile()) {
|
|
65
|
-
return current.normalize("NFC");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch { }
|
|
69
|
-
const parent = dirname(current);
|
|
70
|
-
if (parent === current)
|
|
71
|
-
break;
|
|
72
|
-
current = parent;
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
const gitPath = join(root, ".git");
|
|
76
|
-
const s = statSync(gitPath);
|
|
77
|
-
if (s.isDirectory() || s.isFile()) {
|
|
78
|
-
return root.normalize("NFC");
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch { }
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
// Matches Claude Code's resolveCanonicalRoot() from utils/git.ts
|
|
85
|
-
// Resolves worktrees to the main repo root via .git -> gitdir -> commondir chain
|
|
86
|
-
function resolveCanonicalRoot(gitRoot) {
|
|
87
|
-
try {
|
|
88
|
-
const gitContent = readFileSync(join(gitRoot, ".git"), "utf-8").trim();
|
|
89
|
-
if (!gitContent.startsWith("gitdir:")) {
|
|
90
|
-
return gitRoot;
|
|
91
|
-
}
|
|
92
|
-
const worktreeGitDir = resolve(gitRoot, gitContent.slice("gitdir:".length).trim());
|
|
93
|
-
const commonDir = resolve(worktreeGitDir, readFileSync(join(worktreeGitDir, "commondir"), "utf-8").trim());
|
|
94
|
-
// SECURITY: validate worktreeGitDir is a direct child of <commonDir>/worktrees/
|
|
95
|
-
if (resolve(dirname(worktreeGitDir)) !== join(commonDir, "worktrees")) {
|
|
96
|
-
return gitRoot;
|
|
97
|
-
}
|
|
98
|
-
// SECURITY: validate gitdir back-link points to our .git
|
|
99
|
-
const backlink = realpathSync(readFileSync(join(worktreeGitDir, "gitdir"), "utf-8").trim());
|
|
100
|
-
if (backlink !== join(realpathSync(gitRoot), ".git")) {
|
|
101
|
-
return gitRoot;
|
|
102
|
-
}
|
|
103
|
-
if (commonDir.endsWith(`${sep}.git`) || commonDir.endsWith("/.git")) {
|
|
104
|
-
return dirname(commonDir).normalize("NFC");
|
|
105
|
-
}
|
|
106
|
-
return commonDir.normalize("NFC");
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
return gitRoot;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
export function findCanonicalGitRoot(startPath) {
|
|
113
|
-
const root = findGitRoot(startPath);
|
|
114
|
-
if (!root)
|
|
115
|
-
return null;
|
|
116
|
-
return resolveCanonicalRoot(root);
|
|
117
|
-
}
|
|
118
|
-
function getClaudeConfigHomeDir() {
|
|
119
|
-
return (process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude")).normalize("NFC");
|
|
120
|
-
}
|
|
121
|
-
export function getProjectDir(worktree) {
|
|
122
|
-
const canonicalRoot = findCanonicalGitRoot(worktree) ?? worktree;
|
|
123
|
-
return join(getClaudeConfigHomeDir(), "projects", sanitizePath(canonicalRoot));
|
|
124
|
-
}
|
|
125
|
-
export function getMemoryDir(worktree) {
|
|
126
|
-
const memoryDir = join(getProjectDir(worktree), "memory");
|
|
127
|
-
ensureDir(memoryDir);
|
|
128
|
-
return memoryDir;
|
|
129
|
-
}
|
|
130
|
-
export function getMemoryEntrypoint(worktree) {
|
|
131
|
-
return join(getMemoryDir(worktree), ENTRYPOINT_NAME);
|
|
132
|
-
}
|
|
133
|
-
export function isMemoryPath(absolutePath, worktree) {
|
|
134
|
-
const memDir = getMemoryDir(worktree);
|
|
135
|
-
return absolutePath.startsWith(memDir);
|
|
136
|
-
}
|
|
137
|
-
export function ensureDir(dir) {
|
|
138
|
-
if (!existsSync(dir)) {
|
|
139
|
-
mkdirSync(dir, { recursive: true });
|
|
140
|
-
}
|
|
141
|
-
}
|
package/dist/prompt.d.ts
DELETED
package/dist/recall.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { MemoryHeader } from "./memoryScan.js";
|
|
2
|
-
export type RecalledMemory = {
|
|
3
|
-
fileName: string;
|
|
4
|
-
filePath: string;
|
|
5
|
-
name: string;
|
|
6
|
-
type: string;
|
|
7
|
-
description: string;
|
|
8
|
-
content: string;
|
|
9
|
-
ageInDays: number;
|
|
10
|
-
};
|
|
11
|
-
export declare function recallSelectedMemories(headers: readonly MemoryHeader[], selectedFilenames: readonly string[], alreadySurfaced?: ReadonlySet<string>): RecalledMemory[];
|
|
12
|
-
export declare function formatRecalledMemories(memories: RecalledMemory[]): string;
|