From 4efc3d267c8eb736749df208b5818e12b72f70ab Mon Sep 17 00:00:00 2001 From: hc Date: Mon, 13 Apr 2026 16:48:22 +0800 Subject: [PATCH] =?UTF-8?q?memory=20v2,=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E8=AE=B0=E5=BF=86memory=20md=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 3 +- SKILLS/cli-core.md | 3 +- src/cc_slim/engine.py | 2 +- src/cc_slim/memory.py | 64 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5baa759..95d2396 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,8 @@ ## Memory 与验证 -- 当前项目支持最小 memory:使用 `/remember` 保存长期信息,使用 `/memory` 查看。 +- `AGENTS.md` 是项目规则文件,不用于保存长期项目知识或用户偏好。 +- 当前项目支持结构化 memory:使用 `/remember` 保存长期知识,使用 `/memory` 查看。 - session 是原始对话历史,不直接拼进 system prompt;memory 才作为长期补充进入 prompt。 - 默认语言:中文优先。 - 默认验证:优先做最小可验证检查,不夸大成功状态。 diff --git a/SKILLS/cli-core.md b/SKILLS/cli-core.md index 0e390a8..9c257c5 100644 --- a/SKILLS/cli-core.md +++ b/SKILLS/cli-core.md @@ -26,7 +26,8 @@ - 需要创建或覆盖文件时,优先使用 `Write` 工具,而不是 `Bash` - 会话与 memory 管理优先使用 slash command,而不是自然语言或 `Bash` 探查对应文件 - 优先通过 `/help` 查看当前命令,而不是猜测命令格式 -- 长期有价值的项目约束或偏好,优先使用 `/remember` 保存 +- 长期有效的项目知识或用户偏好,优先使用 `/remember` 保存 +- 项目规则和工作方式仍应写在 `AGENTS.md` - 高风险操作会触发确认,优先先读再改,减少无意义审批 - 复杂任务可先切换到 `/mode plan` 进行规划,再切换回 `/mode build` 执行 - workspace 是当前默认操作边界,先在 workspace 内定位和操作,避免无关路径探索 diff --git a/src/cc_slim/engine.py b/src/cc_slim/engine.py index 97ad077..0f7fc02 100644 --- a/src/cc_slim/engine.py +++ b/src/cc_slim/engine.py @@ -216,7 +216,7 @@ class Agent: memory = MemoryStore(workspace).read() if not memory: return "" - return f"# Memory\n\n{memory}" + return memory def _build_runtime_summary(self, workspace: Path) -> str: tool_names = ", ".join(self.tools.keys()) or "(none)" diff --git a/src/cc_slim/memory.py b/src/cc_slim/memory.py index fd3ffef..fe13376 100644 --- a/src/cc_slim/memory.py +++ b/src/cc_slim/memory.py @@ -10,23 +10,67 @@ class MemoryStore: self.cwd = cwd.resolve() self.root = Path.home() / ".config" / "cc-slim" / "memory" / self._sanitize_cwd(self.cwd) self.root.mkdir(parents=True, exist_ok=True) + self.ensure_structure() - def append(self, text: str) -> Path: - path = self.path() - existing = self.read() - content = f"{existing}\n\n{text.strip()}" if existing else text.strip() - path.write_text(content + "\n", encoding="utf-8") - return path - - def read(self) -> str: + def ensure_structure(self) -> None: path = self.path() if not path.exists(): - return "" - return path.read_text(encoding="utf-8").strip() + path.write_text(self._default_template(), encoding="utf-8") + return + + existing = path.read_text(encoding="utf-8").strip() + if self._is_structured(existing): + return + + migrated = self._default_template(existing) + path.write_text(migrated, encoding="utf-8") + + def append(self, text: str) -> str: + content = self.read().rstrip() + entry = self._format_scratch_entry(text) + updated = f"{content}\n\n{entry}\n" + self.path().write_text(updated, encoding="utf-8") + return "Scratch Notes" + + def read(self) -> str: + self.ensure_structure() + return self.path().read_text(encoding="utf-8").strip() def path(self) -> Path: return self.root / "MEMORY.md" + def _default_template(self, scratch_notes: str = "") -> str: + scratch = self._format_scratch_entry(scratch_notes) if scratch_notes.strip() else "" + template = ( + "# Memory\n\n" + "## User Memory\n\n" + "## Project Memory\n\n" + "## Constraints\n\n" + "## Consolidated Facts\n\n" + "## Scratch Notes\n" + ) + return f"{template}\n{scratch}\n" if scratch else f"{template}\n" + + def _format_scratch_entry(self, text: str) -> str: + lines = [line.rstrip() for line in text.strip().splitlines() if line.strip()] + if not lines: + return "" + if len(lines) == 1: + return f"- {lines[0]}" + tail = "\n".join(f" {line}" for line in lines[1:]) + return f"- {lines[0]}\n{tail}" + + def _is_structured(self, text: str) -> bool: + required_sections = [ + "# Memory", + "## User Memory", + "## Project Memory", + "## Constraints", + "## Consolidated Facts", + "## Scratch Notes", + ] + return all(section in text for section in required_sections) + def _sanitize_cwd(self, cwd: Path) -> str: text = re.sub(r"[^A-Za-z0-9._-]+", "_", str(cwd)) digest = hashlib.sha1(str(cwd).encode("utf-8")).hexdigest()[:8]