From 61e9fa94aca6284bc549c332664aedff620e341e Mon Sep 17 00:00:00 2001 From: Huarch Date: Mon, 11 May 2026 17:04:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .opencode/tools/skill_manager.ts | 2 ++ src/memory/store.ts | 3 +++ src/utils/fileStore.ts | 3 +++ 3 files changed, 8 insertions(+) diff --git a/.opencode/tools/skill_manager.ts b/.opencode/tools/skill_manager.ts index d88632a..9b181aa 100644 --- a/.opencode/tools/skill_manager.ts +++ b/.opencode/tools/skill_manager.ts @@ -18,6 +18,7 @@ const initializePromise = Promise.all([ toolContextStore.initialize(), ]); const SKILLS_ROOT_DIR = ".opencode/skills"; +// learned skill 与正式技能树同路径组织,但历史版本单独落到 data/history/skills 下。 const SKILLS_HISTORY_DIR = join(config.PERSISTENCE_HISTORY_DIR, "skills"); const LEARNED_PATTERNS_MARKER = "## Learned Patterns"; let writeQueue: Promise = Promise.resolve(); @@ -120,6 +121,7 @@ const appendLearnedSkillPattern = async (skillPath: string, pattern: string) => : `${current.trimEnd()}\n\n${LEARNED_PATTERNS_MARKER}\n- ${pattern}\n`; await ensureDirectory(join(SKILLS_ROOT_DIR, skillPath)); + // 追加 learned pattern 前先备份旧版 SKILL.md,避免共享技能被异常写坏。 await atomicWriteFileWithHistory(target, next, { historyDir: SKILLS_HISTORY_DIR, rootDir: SKILLS_ROOT_DIR, diff --git a/src/memory/store.ts b/src/memory/store.ts index 9141e8d..0b0f115 100644 --- a/src/memory/store.ts +++ b/src/memory/store.ts @@ -35,6 +35,7 @@ const SUSPICIOUS_MEMORY_PATTERNS = [ ]; export class MemoryStore { + // Memory 文件可能被多次连续追加,串行化可避免并发覆盖掉刚写入的条目。 private writeQueue: Promise = Promise.resolve(); constructor( @@ -46,6 +47,7 @@ export class MemoryStore { await ensureDirectory(this.baseDir); await ensureDirectory(join(this.baseDir, "users")); await ensureDirectory(join(this.baseDir, "workspaces")); + // 历史备份与正式数据分目录存放,便于排查和手工恢复。 await ensureDirectory(this.historyDir); } @@ -64,6 +66,7 @@ export class MemoryStore { const entry: MemoryEntry = { content }; entries.unshift(entry); + // 每次覆盖 memory 文件前先保留上一版,写入失败时由底层工具恢复。 await atomicWriteFileWithHistory( this.filePath(scope, key), renderMemoryMarkdown(scope, entries), diff --git a/src/utils/fileStore.ts b/src/utils/fileStore.ts index dac66ce..3f04c71 100644 --- a/src/utils/fileStore.ts +++ b/src/utils/fileStore.ts @@ -36,12 +36,14 @@ export const atomicWriteFileWithHistory = async ( let backupPath: string | null = null; if (previous !== null) { + // 仅在覆盖已有文件时保留历史版本,避免为首次创建产生空备份。 backupPath = buildHistoryBackupPath(path, options); await atomicWriteFile(backupPath, previous); } try { await atomicWriteFile(path, content); + // 给调用方预留一个写后钩子;若后续步骤失败,这里仍会回滚到旧内容。 await options.afterWrite?.(); } catch (error) { try { @@ -158,6 +160,7 @@ const buildHistoryBackupPath = (path: string, options: HistoricalWriteOptions) = const relativePath = relative(options.rootDir, path); const scopedPath = relativePath && !relativePath.startsWith("..") ? relativePath : basename(path); + // 备份目录尽量复用原始相对路径,便于按业务目录回看历史。 const backupName = `${basename(path)}.${Date.now().toString(36)}.bak`; return join(options.historyDir, dirname(scopedPath), backupName); };