forked from kuitos/opencode-claude-memory
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmemoryScan.ts
More file actions
130 lines (117 loc) · 3.74 KB
/
Copy pathmemoryScan.ts
File metadata and controls
130 lines (117 loc) · 3.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { readdirSync, readFileSync, statSync } from "fs"
import { basename, join } from "path"
import {
getMemoryDir,
ENTRYPOINT_NAME,
MAX_MEMORY_FILES,
FRONTMATTER_MAX_LINES,
} from "./paths.js"
import type { MemoryType } from "./memory.js"
export type MemoryHeader = {
filename: string
filePath: string
mtimeMs: number
name: string | null
description: string | null
type: MemoryType | undefined
}
const MEMORY_TYPES: readonly string[] = ["user", "feedback", "project", "reference"]
function parseMemoryType(raw: string | undefined): MemoryType | undefined {
if (!raw) return undefined
return MEMORY_TYPES.includes(raw) ? (raw as MemoryType) : undefined
}
function readFileHeader(filePath: string, maxLines: number): { content: string; mtimeMs: number } {
try {
const raw = readFileSync(filePath, "utf-8")
const stat = statSync(filePath)
const lines = raw.split("\n")
const header = lines.slice(0, maxLines).join("\n")
return { content: header, mtimeMs: stat.mtimeMs }
} catch {
return { content: "", mtimeMs: 0 }
}
}
function parseFrontmatterHeader(raw: string): Record<string, string> {
const trimmed = raw.trim()
if (!trimmed.startsWith("---")) {
return {}
}
const lines = trimmed.split("\n")
let closingLineIdx = -1
for (let i = 1; i < lines.length; i++) {
if (lines[i].trimEnd() === "---") {
closingLineIdx = i
break
}
}
if (closingLineIdx === -1) {
return {}
}
const frontmatter: Record<string, string> = {}
for (let i = 1; i < closingLineIdx; i++) {
const line = lines[i]
const colonIdx = line.indexOf(":")
if (colonIdx === -1) continue
const key = line.slice(0, colonIdx).trim()
const value = line.slice(colonIdx + 1).trim()
if (key && value) {
frontmatter[key] = value
}
}
return frontmatter
}
/**
* Recursive scan of memory directory. Reads only frontmatter (first N lines),
* returns headers sorted by mtime desc, capped at MAX_MEMORY_FILES.
* Port of Claude Code's scanMemoryFiles().
*/
export function scanMemoryFiles(memoryDir: string): MemoryHeader[] {
try {
const entries = readdirSync(memoryDir, { recursive: true, encoding: "utf-8" }) as string[]
const mdFiles = entries.filter(
(f: string) => f.endsWith(".md") && basename(f) !== ENTRYPOINT_NAME,
)
const headers: MemoryHeader[] = []
for (const relativePath of mdFiles) {
const filePath = join(memoryDir, relativePath)
try {
const { content, mtimeMs } = readFileHeader(filePath, FRONTMATTER_MAX_LINES)
const frontmatter = parseFrontmatterHeader(content)
headers.push({
filename: relativePath,
filePath,
mtimeMs,
name: frontmatter.name || null,
description: frontmatter.description || null,
type: parseMemoryType(frontmatter.type),
})
} catch {
// skip unreadable files
}
}
return headers
.sort((a, b) => b.mtimeMs - a.mtimeMs)
.slice(0, MAX_MEMORY_FILES)
} catch {
return []
}
}
// Port of Claude Code's formatMemoryManifest():
// `- [type] filename (ISO timestamp): description` per line
export function formatMemoryManifest(memories: MemoryHeader[]): string {
return memories
.map((m) => {
const tag = m.type ? `[${m.type}] ` : ""
const ts = new Date(m.mtimeMs).toISOString()
return m.description
? `- ${tag}${m.filename} (${ts}): ${m.description}`
: `- ${tag}${m.filename} (${ts})`
})
.join("\n")
}
export function getMemoryManifest(worktree: string): { headers: MemoryHeader[]; manifest: string } {
const memoryDir = getMemoryDir(worktree)
const headers = scanMemoryFiles(memoryDir)
const manifest = formatMemoryManifest(headers)
return { headers, manifest }
}