* feat(memory): memory-save tool with auto-setup * feat(memory): memory-search with keyword and semantic fallback * feat(memory): memory-list and memory-access tools * feat(memory): memory-doctor diagnostic tool * docs(handoff): add handoff and ADR for ts memory tools * docs(handoff): set PR number * docs(handoff): finalize pr-101 frontmatter and pending * docs(project-map): add 5 memory TS tools after PR#101 * refactor(memory): extract shared helpers to _memory-shared.ts * refactor(memory): split oversized execute functions * fix(memory): handle git checkout and catch blocks --------- Co-authored-by: opencode-agent <agent@opencode.local>
66 lines
No EOL
2.6 KiB
TypeScript
66 lines
No EOL
2.6 KiB
TypeScript
import fs from "fs"
|
|
import path from "path"
|
|
import { tool } from "@opencode-ai/plugin"
|
|
import { FRONTMATTER_RE, resolveMemoryDir } from "./_memory-shared"
|
|
|
|
function todayISO(): string {
|
|
return new Date().toISOString().slice(0, 10)
|
|
}
|
|
|
|
function bumpAccessFields(yaml: string, dateStr: string): { yaml: string; newCount: number } {
|
|
const countMatch = yaml.match(/^access_count:\s*(\d+)/m)
|
|
const currentCount = countMatch ? parseInt(countMatch[1], 10) : 0
|
|
const newCount = currentCount + 1
|
|
let updated = yaml
|
|
if (updated.match(/^last_accessed:/m)) {
|
|
updated = updated.replace(/^last_accessed:.*$/m, `last_accessed: ${dateStr}`)
|
|
} else {
|
|
updated += `\nlast_accessed: ${dateStr}`
|
|
}
|
|
if (updated.match(/^access_count:/m)) {
|
|
updated = updated.replace(/^access_count:.*$/m, `access_count: ${newCount}`)
|
|
} else {
|
|
updated += `\naccess_count: ${newCount}`
|
|
}
|
|
return { yaml: updated, newCount }
|
|
}
|
|
|
|
export default tool({
|
|
description:
|
|
"Record that a memory file was accessed (read and used). Updates last_accessed date and increments access_count in frontmatter. " +
|
|
"Call this AFTER reading a memory file that you actually used to inform your work — not for casual browsing. " +
|
|
"This helps the memory system track which memories are actively useful vs. stale. " +
|
|
"Atomic write (tmp + rename). Does NOT commit — the next memory_save will sync the change.",
|
|
args: {
|
|
path: tool.schema
|
|
.string()
|
|
.describe("Relative path within the memory directory (e.g. 'technical/build-tooling.md')"),
|
|
},
|
|
async execute(args) {
|
|
const memoryDir = resolveMemoryDir()
|
|
const filePath = path.join(memoryDir, args.path)
|
|
if (!fs.existsSync(filePath)) return `Could not update ${args.path} (file not found)`
|
|
let content: string
|
|
try {
|
|
content = fs.readFileSync(filePath, "utf-8")
|
|
} catch (e) {
|
|
process.stderr.write(`[memory-access] read failed ${args.path}: ${(e as Error).message}\n`)
|
|
return `Could not update ${args.path}`
|
|
}
|
|
const fmMatch = content.match(FRONTMATTER_RE)
|
|
if (!fmMatch) return `No frontmatter in ${args.path} — skipped`
|
|
const yaml = fmMatch[1]
|
|
const body = fmMatch[2]
|
|
const { yaml: updatedYaml, newCount } = bumpAccessFields(yaml, todayISO())
|
|
const newContent = `---\n${updatedYaml}\n---\n${body}`
|
|
try {
|
|
const tmp = filePath + ".tmp"
|
|
fs.writeFileSync(tmp, newContent, "utf-8")
|
|
fs.renameSync(tmp, filePath)
|
|
} catch (e) {
|
|
process.stderr.write(`[memory-access] write failed ${args.path}: ${(e as Error).message}\n`)
|
|
return `Could not update ${args.path}`
|
|
}
|
|
return `Accessed: ${args.path}, count=${newCount}`
|
|
},
|
|
}) |