# https://github.com/rickbassham/commonplace 项目说明书

生成时间：2026-05-13 17:34:29 UTC

## 目录

- [项目概览](#page-overview)
- [快速入门与安装](#page-quickstart)
- [系统架构](#page-architecture)
- [技术栈详解](#page-tech-stack)
- [嵌入模型与语义搜索](#page-embedder)
- [内存格式与侧载文件](#page-memory-format)
- [搜索功能详解](#page-search)
- [图功能与关系管理](#page-graph-features)
- [存储系统](#page-storage)
- [作用域与多存储](#page-scopes)

<a id='page-overview'></a>

## 项目概览

### 相关页面

相关主题：[快速入门与安装](#page-quickstart), [系统架构](#page-architecture)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/rickbassham/commonplace/blob/main/README.md)
- [CLAUDE.md](https://github.com/rickbassham/commonplace/blob/main/CLAUDE.md)
- [CONTRIBUTING.md](https://github.com/rickbassham/commonplace/blob/main/CONTRIBUTING.md)
- [src/cli/migrate.ts](https://github.com/rickbassham/commonplace/blob/main/src/cli/migrate.ts)
- [src/store/memory.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)
- [src/store/memory-store.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory-store.ts)
- [src/store/mentions.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/mentions.ts)
- [src/index.ts](https://github.com/rickbassham/commonplace/blob/main/src/index.ts)
</details>

# 项目概览

## 项目简介

Commonplace 是一个基于 MCP（Model Context Protocol）的持久化记忆系统，旨在为 AI 代理提供跨会话的上下文记忆能力。该项目使 AI 能够记住用户的偏好、项目的架构决策、技术反馈等重要信息，从而在长时间交互中保持一致性和连续性。

资料来源：[README.md:1-10]()

## 核心设计原则

### 记忆文件即真相

Commonplace 采用 Markdown 文件作为记忆的持久化存储介质。每个记忆以 `.md` 文件形式存储，YAML frontmatter 携带结构化元数据，文件主体承载具体内容。这种设计使得记忆可以被版本控制、人类可读、手动编辑，且不依赖专有格式。

资料来源：[CLAUDE.md:15-20]()

### 边车文件可重建

`.embedding` 等边车文件（sidecar）是从 `.md` 源文件派生的，可以随时从源码重新生成。这确保了数据的安全性——即使边车文件丢失或损坏，也能通过扫描重建索引。

资料来源：[CLAUDE.md:20-22]()

## 内存类型系统

Commonplace 定义了四种内存类型，构成记忆分类的基础：

| 类型 | 用途 | 典型场景 |
|------|------|----------|
| `user` | 个人规则、偏好、身份事实 | 编程语言偏好、沟通风格、联系方式 |
| `feedback` | 来自代理行为的修正和教训 | 避免的模式、成功的策略、工作习惯 |
| `project` | 项目特定上下文 | 架构笔记、代码规范、决策记录 |
| `reference` | 持久化中性知识 | API 形状、公式、引用、需要按语义查询的内容 |

资料来源：[README.md:45-60]()

## 记忆存储架构

### 双作用域设计

系统支持同时加载两个独立的记忆存储：

```mermaid
graph TD
    A[Commonplace] --> B[用户存储]
    A --> C[项目存储]
    B --> D[COMMONPLACE_USER_DIR<br/>~/.commonplace/memory]
    C --> E[COMMONPLACE_PROJECT_DIR<br/>或 .commonplace/memory]
    
    F[MCP roots/list检测] --> E
    G[当前工作目录] --> E
```

- **用户存储（User Store）**：始终加载，存储跨项目共享的个人规则和反馈
- **项目存储（Project Store）**：仅在检测到项目根目录时加载，存储项目特定的上下文

资料来源：[README.md:140-165]()

### 目录结构

每个记忆存储遵循以下结构：

```
<store-dir>/
├── <name>.md           # 记忆文件
├── <name>.embedding    # 向量嵌入边车（自动生成）
└── .index/             # 索引缓存（可选）
```

资料来源：[src/store/memory.ts:20-35]()

### 记忆文件格式

记忆文件采用标准 Markdown + YAML frontmatter 格式：

```yaml
---
name: feedback_scope
description: 不要单方面缩减范围
type: feedback
relations:          # 关系边（DAR-925）
  - to: other_name
    type: builds-on
supersedes:         # 替代关系
  - old_name
---
<body>
```

资料来源：[src/store/memory.ts:40-60]()

## 核心模块

### CLI 模块

入口文件 `src/index.ts` 负责命令行参数解析和子命令分发：

```mermaid
graph LR
    A[commonplace CLI] --> B[migrate]
    A --> C[graph]
    B --> D[scan - 扫描并嵌入]
    B --> E[import - 导入外部记忆]
    B --> F[prune - 清理悬挂引用]
```

资料来源：[src/index.ts:1-50]()

### 内存存储层

`MemoryStore`（`src/store/memory-store.ts`）是核心存储引擎，提供以下能力：

| 方法 | 功能 |
|------|------|
| `scan()` | 扫描目录，检测/生成边车嵌入 |
| `save()` | 保存新记忆 |
| `delete()` | 删除记忆 |
| `search()` | 语义搜索 |
| `list()` | 列出所有记忆 |
| `linkEdge()` | 添加记忆间关系 |
| `unlinkEdge()` | 移除记忆间关系 |

资料来源：[src/store/memory-store.ts:1-100]()

### 内容哈希机制

`contentSha` 函数计算记忆内容的规范哈希：

```
SHA256(`${type}\n${name}\n${description}\n${body}`)
```

注意：关系字段（`relations`、`supersedes`）不参与哈希计算，添加或移除关系边不会使嵌入失效。这确保了边的修改不会触发不必要的重新嵌入。

资料来源：[src/store/memory.ts:80-95]()

### 提及提取

`src/store/mentions.ts` 实现 `[[name]]` 语法提取：

- 扫描记忆文件 body 中的 `[[name]]` 模式
- 仅提取符合 `^[a-z0-9_]+$` 的名称
- 通过 `MemoryGraph.addMentionsEdge` 建立提及关系
- 可通过 `COMMONPLACE_EXTRACT_MENTIONS=false` 环境变量禁用

资料来源：[src/store/mentions.ts:1-40]()

### 原子写入

`atomicWrite`（`src/store/atomic-write.ts`）确保写入原子性：

1. 在同目录创建临时文件
2. 写入数据
3. 使用 `rename(2)` 原子替换目标
4. 验证源和目标在同一文件系统

资料来源：[src/store/atomic-write.ts:1-50]()

## 关系图系统（DAR-925）

记忆之间可以建立有向关系边：

| 关系类型 | 语义 | 搜索默认包含 |
|----------|------|--------------|
| `related-to` | 一般关联 | 是 |
| `builds-on` | 基于某记忆构建 | 是 |
| `contradicts` | 与某记忆矛盾 | 否 |
| `child-of` | 某记忆的子项 | 否 |
| `supersedes` | 替代某旧记忆 | 否 |
| `mentions` | 正文中提及 | 否 |

`MemoryGraph` 维护内存中的邻接表，支持 BFS 遍历和路径查询。

资料来源：[src/server/handlers.ts:10-25]()

## MCP 工具接口

系统通过 MCP 暴露四个核心工具：

### memory_save

保存新记忆，参数包括名称、类型、描述和内容。拒绝覆盖已有记忆，合约要求先删除再保存。

### memory_search

语义搜索记忆，返回按相关性排序的匹配列表。支持按类型过滤和作用域限定。

### memory_list

列出记忆名称，支持按类型过滤。

### memory_path

图遍历查询，返回两记忆间的路径。

资料来源：[README.md:75-150]()

## 迁移与导入

### migrate 命令

`commonplace migrate <dir>` 命令用于迁移现有记忆目录：

1. 扫描 `.md` 文件，嵌入缺失或过时的边车
2. 清理孤立边车（无对应 `.md` 的 `.embedding`）
3. 可选：剪除悬挂引用（`--prune-dangling`）

资料来源：[src/cli/migrate.ts:50-100]()

### Claude Code 导入

支持从 Claude Code 的自动记忆目录导入兼容文件：

```
~/.claude/projects/*/memory/*.md
```

导入逻辑：检测候选文件，如目标 `<name>.md` 已存在则跳过，否则复制源码并执行扫描/嵌入。

资料来源：[src/cli/migrate.ts:120-160]()

## 配置与部署

### 环境变量

| 变量 | 默认值 | 用途 |
|------|--------|------|
| `COMMONPLACE_USER_DIR` | `~/.commonplace/memory` | 用户存储路径 |
| `COMMONPLACE_PROJECT_DIR` | - | 项目存储路径（覆盖检测） |
| `COMMONPLACE_MODEL_ID` | - | 指定嵌入模型 |
| `COMMONPLACE_EXTRACT_MENTIONS` | `true` | 启用提及提取 |

资料来源：[CLAUDE.md:25-35]()

### 依赖技术栈

- **运行时**：Node.js 20+
- **包管理**：pnpm
- **嵌入引擎**：transformers.js
- **协议**：MCP (Model Context Protocol)
- **测试**：make test
- **类型检查**：make typecheck
- **构建**：make build

## 开发工作流

```mermaid
graph TD
    A[从 main 创建功能分支] --> B[开发与测试]
    B --> C[提交 PR]
    C --> D[CI 检查<br/>typecheck + lint + build + test]
    D --> E{通过?}
    E -->|是| F[Squash 合并到 main]
    E -->|否| B
    F --> G[推送 tag 触发 Release]
    G --> H[自动发布 npm 包]
```

资料来源：[CONTRIBUTING.md:1-50]()

## 状态与里程碑

| 阶段 | 状态 | 说明 |
|------|------|------|
| v0.1 | 进行中 | 基础记忆 CRUD、语义搜索 |
| DAR-911 | 完成 | 内存文件 I/O |
| DAR-910 | 完成 | 嵌入生成 |
| DAR-925 | 完成 | 关系图系统 |
| DAR-926 | 进行中 | 悬挂引用处理 |
| DAR-927 | 完成 | 提及提取 |
| DAR-928 | 待开始 | MCP 编辑工具 |

## 快速入门

1. 安装并配置 MCP 服务器
2. 重启 Claude Code 会话
3. 使用 `memory_save` 保存第一条记忆
4. 通过 `memory_search` 和 `memory_list` 检索记忆

资料来源：[README.md:25-40]()

---

**最后更新**：基于 repository main 分支，内容涵盖核心架构、存储模型、工具接口和开发流程。

---

<a id='page-quickstart'></a>

## 快速入门与安装

### 相关页面

相关主题：[项目概览](#page-overview)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/rickbassham/commonplace/blob/main/README.md)
- [CLAUDE.md](https://github.com/rickbassham/commonplace/blob/main/CLAUDE.md)
- [CONTRIBUTING.md](https://github.com/rickbassham/commonplace/blob/main/CONTRIBUTING.md)
- [package.json](https://github.com/rickbassham/commonplace/blob/main/package.json)
- [.nvmrc](https://github.com/rickbassham/commonplace/blob/main/.nvmrc)
</details>

# 快速入门与安装

## 概述

Commonplace 是一个本地优先的个人知识管理工具，以 MCP（Model Context Protocol）服务器形式运行。它将笔记存储为带有 YAML 前缀的纯 Markdown 文件，嵌入向量存储在 `.embedding` 侧边文件中，语义搜索完全离线运行，底层使用 `transformers.js` + `bge-base-en-v1.5` 模型实现。

本节介绍 Commonplace 的完整安装流程、系统要求、环境配置以及与 Claude Code 的集成方法。

## 系统要求

### Node.js 环境

Commonplace 要求 Node.js 版本 **20 或更高**。项目通过 `.nvmrc` 文件声明了版本要求：

```bash
20
```

同时 `package.json` 的 `engines` 字段也做了约束声明。

### 包管理器

| 包管理器 | 支持状态 |
|---------|---------|
| pnpm | ✅ 唯一支持 |
| npm | ❌ 不支持 |
| yarn | ❌ 不支持 |

> 资料来源：[CLAUDE.md:30]()

### 技术栈

| 技术 | 版本/要求 | 用途 |
|------|----------|------|
| TypeScript | 严格模式, ES2022 目标 | 源码编写 |
| ESM | NodeNext 模块输出 | 模块系统 |
| vitest | 最新版 | 单元和集成测试 |
| @huggingface/transformers | 最新版 | 本地嵌入推理 |
| @modelcontextprotocol/sdk | 最新版 | MCP stdio 服务器接口 |

> 资料来源：[CLAUDE.md:24-37]()

## 安装流程

### 步骤一：克隆仓库

```bash
git clone https://github.com/rickbassham/commonplace.git
cd commonplace
```

### 步骤二：安装依赖

```bash
pnpm install
```

### 步骤三：构建项目

```bash
pnpm build
```

构建过程会：
- 编译 TypeScript 源码到 ESM 格式
- 输出到 `dist/` 目录
- 生成两个可执行入口：`commonplace` 和 `commonplace-mcp`

> 资料来源：[CLAUDE.md:48-50]()

## Claude Code 集成配置

### 添加 MCP 服务器

在 Claude Code 配置中添加以下服务器配置：

```json
{
  "mcpServers": {
    "commonplace": {
      "command": "node",
      "args": ["/path/to/commonplace/dist/bin/commonplace-mcp.js"]
    }
  }
}
```

完成配置后，重启所有正在运行的 Claude Code 会话，四个记忆工具即可使用：

- `memory_save`
- `memory_list`
- `memory_delete`
- `memory_search`

> 资料来源：[README.md:48-58]()

## 环境变量配置

### 存储路径配置

| 环境变量 | 默认值 | 说明 |
|---------|-------|------|
| `COMMONPLACE_USER_DIR` | `~/.commonplace/memory` | 用户级记忆存储路径，始终加载 |
| `COMMONPLACE_PROJECT_DIR` | 项目根目录下的 `.commonplace/memory` | 项目级记忆存储路径，按需加载 |

### 功能开关

| 环境变量 | 默认值 | 说明 |
|---------|-------|------|
| `COMMONPLACE_EXTRACT_MENTIONS` | `'true'` | 控制 `[[name]]` 提及提取功能，设为 `'false'` 可禁用 |

> 资料来源：[CLAUDE.md:53-57]()

### 记忆存储架构

```mermaid
graph TD
    subgraph 用户存储
        USER_DIR["COMMONPLACE_USER_DIR<br/>(~/.commonplace/memory)"]
    end
    
    subgraph 项目存储
        PROJ_DIR["COMMONPLACE_PROJECT_DIR<br/>(按需加载)"]
    end
    
    subgraph 记忆文件
        MD["<name>.md<br/>YAML 前缀 + Markdown 正文"]
        EMB["<name>.embedding<br/>二进制嵌入向量"]
    end
    
    USER_DIR --> MD
    PROJ_DIR --> MD
    MD --> EMB
```

## 记忆类型系统

### 四种记忆类型

| 类型 | 用途 | 示例 |
|------|------|------|
| `user` | 个人规则、偏好和身份信息 | 工作时间偏好、写作风格 |
| `feedback` | 修正和从以往行为中学到的教训 | 不要擅自缩减范围 |
| `project` | 项目特定的上下文信息 | 架构笔记、代码约定 |
| `reference` | 持久化的中立知识 | API 形状、公式、引用 |

> 资料来源：[README.md:60-73]()

## CLI 命令行工具

### 可执行文件

| 命令 | 路径 | 用途 |
|------|------|------|
| `commonplace` | `dist/index.js` | CLI 子命令接口 |
| `commonplace-mcp` | `dist/bin/commonplace-mcp.js` | MCP stdio 服务器 |

### 迁移命令

```bash
# 检测已知外部记忆源
commonplace migrate

# 从指定源导入记忆
commonplace migrate --from claude-code

# 预览模式（不写入）
commonplace migrate --from claude-code --dry-run

# 脚本化运行
commonplace migrate --from claude-code --auto

# 为现有记忆目录重建侧边文件
commonplace migrate <dir>
commonplace migrate <dir> --dry-run
commonplace migrate <dir> --prune-dangling
```

> 资料来源：[README.md:17-23]()

## 验证安装

### 构建验证

```bash
make build
make typecheck
make lint
```

### 运行测试

```bash
make test
```

### 审计依赖

```bash
make audit
```

> 资料来源：[CONTRIBUTING.md:24-26]()

## 目录结构概览

```
commonplace/
├── dist/                    # 构建输出
│   ├── index.js            # CLI 入口
│   └── bin/
│       └── commonplace-mcp.js  # MCP 服务器入口
├── src/
│   ├── embedder/           # transformers.js 封装
│   ├── store/              # Markdown + 侧边文件 I/O
│   └── server/             # MCP 服务器处理程序
├── package.json
└── .nvmrc                  # Node.js 版本声明
```

## 常见问题

### Q: npm 或 yarn 可以使用吗？

不可以。项目仅支持 pnpm 作为包管理器。使用其他包管理器会导致依赖安装失败或构建错误。

### Q: Node.js 18 可以使用吗？

不可以。必须使用 Node.js 20 或更高版本。

### Q: 记忆文件存储在哪里？

默认情况下，用户级记忆存储在 `~/.commonplace/memory`，项目级记忆存储在项目根目录的 `.commonplace/memory` 下。

### Q: 如何重建嵌入向量？

使用 `commonplace migrate <dir>` 命令可重新扫描目录并重建所有 `.embedding` 侧边文件。

---

<a id='page-architecture'></a>

## 系统架构

### 相关页面

相关主题：[项目概览](#page-overview), [嵌入模型与语义搜索](#page-embedder), [存储系统](#page-storage)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [CLAUDE.md](https://github.com/rickbassham/commonplace/blob/main/CLAUDE.md)
- [src/index.ts](https://github.com/rickbassham/commonplace/blob/main/src/index.ts)
- [src/store/memory-store.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory-store.ts)
- [src/store/memory.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)
- [src/store/mentions.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/mentions.ts)
- [src/cli/migrate.ts](https://github.com/rickbassham/commonplace/blob/main/src/cli/migrate.ts)
</details>

# 系统架构

## 概述

Commonplace 是一个为 Claude Code 提供持久化记忆功能的 MCP (Model Context Protocol) 工具系统。它通过 markdown 文件存储记忆内容，支持语义搜索和关系图谱管理。资料来源：[CLAUDE.md:1]()

该系统具有以下核心设计原则：

- **单一真相来源** — `.md` 文件是数据的唯一真实来源，`.embedding` 等辅助文件可随时从源文件重新生成
- **无覆盖写入** — `memory_save` 工具拒绝覆盖已有条目，必须先删除再保存
- **双存储架构** — 支持用户级存储和项目级存储分离
- **图关系管理** — 支持在记忆之间建立显式关系边

资料来源：[CLAUDE.md:16-19]()

---

## 整体架构

```mermaid
graph TB
    subgraph "CLI 层"
        CLI[命令行入口<br/>src/index.ts]
        MIGRATE[迁移命令<br/>src/cli/migrate.ts]
    end

    subgraph "MCP 服务器层"
        SERVER[MCP Server<br/>src/server/server.ts]
        HANDLERS[请求处理器<br/>src/server/handlers.ts]
    end

    subgraph "核心存储层"
        STORE[MemoryStore<br/>src/store/memory-store.ts]
        MEMORY[Memory I/O<br/>src/store/memory.ts]
        GRAPH[MemoryGraph<br/>关系图谱]
        MENTIONS[Mentions 提取器<br/>src/store/mentions.ts]
    end

    subgraph "嵌入层"
        EMBEDDER[Embedder<br/>src/embedder/index.ts]
        EMBEDDINGS[.embedding 侧文件]
    end

    subgraph "存储后端"
        USER_DIR[用户存储<br/>~/.commonplace/memory]
        PROJECT_DIR[项目存储<br/>.commonplace/memory]
    end

    CLI --> MIGRATE
    CLI --> STORE
    SERVER --> HANDLERS
    HANDLERS --> STORE
    STORE --> MEMORY
    STORE --> GRAPH
    STORE --> MENTIONS
    STORE --> EMBEDDER
    EMBEDDER --> EMBEDDINGS
    MEMORY --> USER_DIR
    MEMORY --> PROJECT_DIR
    GRAPH --> USER_DIR
    GRAPH --> PROJECT_DIR
```

---

## 核心组件

### 1. CLI 入口层

#### 命令行入口 (`src/index.ts`)

负责命令解析和分发，是系统的最外层接口。

**支持的子命令：**

| 子命令 | 功能 | 源码位置 |
|--------|------|----------|
| `migrate` | 扫描、嵌入、清理记忆目录 | src/index.ts:28-50 |
| `graph` | 图关系查询 | src/index.ts:34-42 |

CLI 采用延迟加载策略：只有在确认需要执行时才构造 `Embedder` 实例，避免不必要的模型加载开销。

资料来源：[src/index.ts:38-47]()

```typescript
// 延迟 embedder 工厂函数
embedderFactory: () => new Embedder(resolveModelId(process.env))
```

#### 迁移命令 (`src/cli/migrate.ts`)

提供记忆目录的维护功能：

1. **扫描与嵌入** — 为缺失或过时的 `.embedding` 侧文件重新生成向量
2. **孤立文件清理** — 移除无对应 `.md` 的 `.embedding` 文件
3. **悬空边修剪** — 可选功能，移除指向不存在记忆的关系引用

资料来源：[src/cli/migrate.ts:58-85]()

---

### 2. MCP 服务器层

#### 服务器 (`src/server/server.ts`)

MCP 服务器负责与 Claude Code 会话建立通信，提供四个核心工具：

| 工具名 | 功能 |
|--------|------|
| `memory_save` | 保存记忆到 markdown 文件 |
| `memory_search` | 语义搜索记忆 |
| `memory_list` | 列出/过滤记忆 |
| `memory_path` | 获取记忆文件路径 |

资料来源：[CLAUDE.md:60-100]()

#### 请求处理器 (`src/server/handlers.ts`)

处理 MCP 工具调用的核心逻辑，包括：

- 参数验证与类型检查
- 搜索结果的聚合与排序
- 默认扩展类型的配置

```typescript
export const DEFAULT_EXPAND_TYPES: readonly EdgeType[] = ['builds-on', 'related-to'] as const;
```

资料来源：[src/server/handlers.ts:8-12]()

---

### 3. 核心存储层

#### MemoryStore (`src/store/memory-store.ts`)

系统的核心存储引擎，管理内存索引和文件系统同步。

**主要职责：**

- 加载与缓存记忆条目
- 增量图索引维护
- 原子写入操作
- 扫描与嵌入管理

**关键方法：**

| 方法 | 功能 |
|------|------|
| `scan()` | 扫描目录，同步嵌入状态 |
| `save()` | 保存新记忆 |
| `delete()` | 删除记忆 |
| `search()` | 语义向量搜索 |
| `list()` | 列出记忆 |
| `linkEdge()` | 添加关系边 |
| `unlinkEdge()` | 移除关系边 |

资料来源：[src/store/memory-store.ts:1-50]()

#### Memory I/O (`src/store/memory.ts`)

负责 markdown 文件的序列化与反序列化。

**文件格式规范：**

```yaml
---
name: memory_name
description: 简短描述
type: user | feedback | project | reference
relations:       # 可选，关系列表
  - to: other_name
    type: builds-on
supersedes:      # 可选，替代列表
  - old_name
---
<body>
```

**内容哈希计算：**

```typescript
export const contentSha = (memory: Memory): string =>
  createHash('sha256')
    .update(`${memory.type}\n${memory.name}\n${memory.description}\n${memory.body}`, 'utf8')
    .digest('hex');
```

注意：`contentSha` 仅基于类型、名称、描述和正文计算，**不包括** `relations` 和 `supersedes` 字段。这意味着关系的增删不会导致嵌入失效。

资料来源：[src/store/memory.ts:90-97]()

#### MemoryGraph (`src/store/memory-store.ts`)

内存中的关系图数据结构，支持：

- **边的类型**：`builds-on`、`related-to`、`contradicts`、`child-of`、`supersedes`、`mentions`
- **BFS 遍历**：支持深度限制和边类型过滤
- **增量更新**：通过 `addEdge()` / `removeEdge()` 增量维护

#### Mentions 提取器 (`src/store/mentions.ts`)

从记忆正文中提取 `[[name]]` 形式的内联引用。

- 默认启用，可通过 `COMMONPLACE_EXTRACT_MENTIONS=false` 环境变量禁用
- 仅提取符合 `[a-z0-9_]+` 命名规则的引用
- 在 `scan()` 和 `save()` 时自动调用，结果自动写入图的 `mentions` 边

资料来源：[src/store/mentions.ts:1-30]()

---

### 4. 嵌入层

#### Embedder (`src/embedder/index.ts`)

使用 Transformers.js 进行本地向量嵌入生成。

**嵌入流程：**

1. 接收文本内容
2. 使用预训练模型生成向量表示
3. 将向量序列化写入 `.embedding` 侧文件

**侧文件新鲜度判断标准：**

- `.embedding` 文件存在且可解码
- `modelId` 与当前 embedder 一致
- `dim` 维度匹配
- `contentSha` 与源文件哈希一致

任一条件不满足都会触发重新嵌入。

资料来源：[src/store/memory-store.ts:180-195]()

---

## 双存储架构

```mermaid
graph LR
    subgraph "用户存储"
        USER[~/.commonplace/memory]
        USER_TYPE[user / feedback<br/>类型]
    end

    subgraph "项目存储"
        PROJECT[<project>/.commonplace/memory]
        PROJECT_TYPE[project / reference<br/>类型]
    end

    USER --> USER_TYPE
    PROJECT --> PROJECT_TYPE
```

### 存储位置

| 存储类型 | 路径 | 加载时机 |
|----------|------|----------|
| 用户存储 | `COMMONPLACE_USER_DIR` (默认 `~/.commonplace/memory`) | 始终加载 |
| 项目存储 | `COMMONPLACE_PROJECT_DIR` 或 `<project-root>/.commonplace/memory` | 检测到项目根目录时加载 |

### 检测优先级

项目存储的路径按以下优先级确定：

1. `COMMONPLACE_PROJECT_DIR` 环境变量（显式覆盖，始终优先）
2. MCP `roots/list` 协议检测到的项目根目录
3. 当前工作目录

资料来源：[CLAUDE.md:130-150]()

### 搜索行为

搜索时，结果会跨两个存储聚合，按相似度降序排列。返回结果中包含 `scope: 'user' | 'project'` 字段标识来源。

---

## 原子写入机制

```mermaid
graph TD
    A[写入请求] --> B[创建临时文件<br/>`<name>.<random>.tmp`]
    B --> C{同文件系统?}
    C -->|否| D[抛出错误]
    C -->|是| E[写入数据到临时文件]
    E --> F[fsync 刷新]
    F --> G[rename 到目标路径]
    G --> H[完成]
    
    D --> I[原子性保证失败<br/>拒绝跨文件系统操作]
```

`atomicWrite` 实现通过临时文件 + rename 序列保证写入原子性，防止系统崩溃导致文件损坏。

资料来源：[src/store/atomic-write.ts:1-25]()

---

## 内存类型体系

| 类型 | 用途 | 存储位置 |
|------|------|----------|
| `user` | 个人规则、偏好、身份事实 | 用户存储 |
| `feedback` | 来自以往行为的修正和经验教训 | 用户存储 |
| `project` | 项目级上下文，如架构笔记、约定 | 项目存储 |
| `reference` | 持久性知识，如 API 形状、公式、引用 | 项目存储 |

所有四个工具都接受 `type` 参数进行过滤。

资料来源：[CLAUDE.md:30-45]()

---

## 关系边类型

| 边类型 | 语义含义 | 默认展开 |
|--------|----------|----------|
| `builds-on` | 基于某记忆构建 | ✓ |
| `related-to` | 相关但不明确 | ✓ |
| `mentions` | 正文中提及 | ✗ |
| `supersedes` | 替代某记忆 | ✗ |
| `contradicts` | 与某记忆矛盾 | ✗ |
| `child-of` | 从属关系 | ✗ |

`builds-on` 和 `related-to` 被设为默认展开类型，因为它们最可能提供有用的上下文。

资料来源：[src/server/handlers.ts:8-12]()

---

## 扫描与嵌入流程

```mermaid
graph TD
    A[migrate 命令] --> B[遍历 .md 文件]
    B --> C{侧文件新鲜?}
    C -->|是| D[跳过]
    C -->|否| E[调用 Embedder]
    E --> F[写入 .embedding]
    D --> G{还有文件?}
    F --> G
    G -->|是| B
    G -->|否| H[孤立文件清理]
    H --> I[输出统计报告]
```

扫描完成后会遍历目录，移除任何无对应 `.md` 的 `.embedding` 文件（孤立侧文件清理）。

资料来源：[src/store/memory-store.ts:165-180]()

---

## 数据流向

```mermaid
graph LR
    A[MCP 工具调用] --> B[参数验证]
    B --> C[MemoryStore 操作]
    C --> D{操作类型}
    D -->|save| E[序列化 Markdown]
    D -->|search| F[向量搜索]
    D -->|linkEdge| G[更新关系图]
    E --> H[原子写入]
    F --> I[匹配结果]
    G --> J[图索引更新]
    H --> K[文件系统]
    I --> L[结果聚合]
    J --> K
    L --> M[返回结果]
```

---

## 环境变量配置

| 变量 | 默认值 | 作用 |
|------|--------|------|
| `COMMONPLACE_USER_DIR` | `~/.commonplace/memory` | 用户存储路径 |
| `COMMONPLACE_PROJECT_DIR` | - | 项目存储路径（覆盖检测） |
| `COMMONPLACE_EXTRACT_MENTIONS` | `true` | 启用 `[[name]]` 提取 |

资料来源：[src/store/mentions.ts:18-20]()

---

## 关键设计决策

### 1. 嵌入哈希稳定性

`contentSha` 故意排除 `relations` 和 `supersedes` 字段。这意味着修改记忆之间的关系不会导致嵌入重新计算，保证嵌入的稳定性。

资料来源：[src/store/memory.ts:82-91]()

### 2. 写入安全

使用原子写入（临时文件 + rename）确保数据一致性，防止崩溃导致的文件损坏。

### 3. 图索引增量维护

`linkEdge` 和 `unlinkEdge` 方法直接操作内存中的图结构，无需完整重新扫描目录。目录修改时间基准在操作后刷新，防止不必要的重新扫描。

资料来源：[src/store/memory-store.ts:220-230]()

### 4. 延迟模型加载

CLI 层的 `embedderFactory` 工厂函数采用延迟实例化策略，只有在实际需要嵌入时才加载 Transformers.js 模型。

资料来源：[src/index.ts:44-47]()

---

<a id='page-tech-stack'></a>

## 技术栈详解

### 相关页面

相关主题：[系统架构](#page-architecture)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [package.json](https://github.com/rickbassham/commonplace/blob/main/package.json)
- [tsconfig.json](https://github.com/rickbassham/commonplace/blob/main/tsconfig.json)
- [tsconfig.build.json](https://github.com/rickbassham/commonplace/blob/main/tsconfig.build.json)
- [vitest.config.ts](https://github.com/rickbassham/commonplace/blob/main/vitest.config.ts)
- [eslint.config.js](https://github.com/rickbassham/commonplace/blob/main/eslint.config.js)
- [src/embedder/](https://github.com/rickbassham/commonplace/blob/main/src/embedder/)
- [src/store/](https://github.com/rickbassham/commonplace/blob/main/src/store/)
- [src/server/](https://github.com/rickbassham/commonplace/blob/main/src/server/)
</details>

# 技术栈详解

## 概述

Commonplace 是一个基于 TypeScript 构建的记忆管理工具，通过 MCP（Model Context Protocol）协议与 Claude Code 等 AI 代理集成。其技术栈围绕本地向量嵌入推理、Markdown 文件存储和 MCP 协议通信三大核心能力展开。

项目采用 Node.js 作为运行时环境，要求 Node >=20，代码以 ESM（ECMAScript Modules）格式输出，不使用 CommonJS。资料来源：[CLAUDE.md]()

## 核心架构分层

```mermaid
graph TD
    subgraph "表现层"
        CLI[CLI 命令行]
        MCP[MCP 服务器]
    end
    
    subgraph "服务层"
        HANDLERS[MCP Handlers]
        EMBEDDER[嵌入器]
    end
    
    subgraph "存储层"
        MEMORY_STORE[MemoryStore]
        GRAPH[MemoryGraph]
    end
    
    subgraph "文件系统"
        MD[.md 文件]
        EMBEDDING[.embedding 侧文件]
    end
    
    CLI --> HANDLERS
    MCP --> HANDLERS
    HANDLERS --> EMBEDDER
    HANDLERS --> MEMORY_STORE
    MEMORY_STORE --> GRAPH
    MEMORY_STORE --> MD
    MEMORY_STORE --> EMBEDDING
```

## 编程语言与工具链

### TypeScript

项目使用 TypeScript 进行开发，采用严格模式（strict mode）和 ES2022 编译目标。

| 配置项 | 值 |
|--------|-----|
| 目标版本 | ES2022 |
| 模块系统 | ESM (NodeNext) |
| 类型检查 | strict mode |
| 编译输出 | ESM 格式 |

资料来源：[CLAUDE.md]()

### 代码质量工具

| 工具 | 用途 |
|------|------|
| ESLint | 代码静态分析 |
| vitest | 单元测试和集成测试 |
| TypeScript Compiler | 类型检查与编译 |

项目配置了多个 Make 目标来执行质量检查：

- `make typecheck` - TypeScript 类型检查
- `make lint` - ESLint 代码检查
- `make test` - vitest 测试执行
- `make build` - 项目构建
- `make audit` - 安全审计（非阻塞）

资料来源：[CONTRIBUTING.md]()

## 核心依赖库

### 向量嵌入推理

| 依赖 | 版本 | 用途 |
|------|------|------|
| @huggingface/transformers | 最新 | 本地 embedding 模型推理 |

`src/embedder/` 目录封装了 transformers.js 的调用，提供类型化的 `embed(text) -> Float32Array` 接口，并隔离模型加载逻辑与业务代码的耦合。

```mermaid
sequenceDiagram
    participant 调用方
    participant Embedder
    participant Transformers
    
    调用方->>Embedder: embed(text)
    Embedder->>Transformers: 加载模型
    Transformers-->>Embedder: 模型就绪
    Embedder->>Transformers: 执行推理
    Transformers-->>Embedder: Float32Array
    Embedder-->>调用方: 返回嵌入向量
```

资料来源：[CLAUDE.md]()

### MCP 协议通信

| 依赖 | 用途 |
|------|------|
| @modelcontextprotocol/sdk | stdio MCP 服务器实现 |

`src/server/` 目录包含 MCP 工具处理器和资源处理器，负责将 MemoryStore 和 Embedder 的能力暴露给外部 MCP 客户端。

项目定义了两个独立的 bin 入口：

- `commonplace` -> `dist/index.js`：CLI 命令行界面
- `commonplace-mcp` -> `dist/bin/commonplace-mcp.js`：stdio MCP 服务器

资料来源：[src/index.ts]()

### 文件处理

| 依赖 | 用途 |
|------|------|
| yaml | YAML frontmatter 序列化/反序列化 |
| crypto (内置) | SHA256 内容哈希计算 |
| fs/promises | 原子写入文件系统操作 |

## 数据存储架构

### 存储目录结构

```mermaid
graph LR
    subgraph "用户存储 ~/\.commonplace/memory"
        MD1[name1.md]
        EMB1[name1.embedding]
        MD2[name2.md]
        EMB2[name2.embedding]
    end
    
    subgraph "项目存储 \<project\>/\.commonplace/memory"
        MD3[proj_name1.md]
        EMB3[proj_name1.embedding]
    end
```

### Memory 文件格式

每个记忆以 Markdown 文件形式存储，包含 YAML frontmatter 和正文内容：

```yaml
---
name: memory_name
description: 描述文本
type: user | feedback | project | reference
relations:       # 可选，定义关系图
  - to: other_name
    type: builds-on
supersedes:      # 可选，定义替代关系
  - old_name
---
<body>
```

记忆类型说明：

| 类型 | 用途 |
|------|------|
| user | 个人规则、偏好和身份事实 |
| feedback | 代理行为纠正和经验教训 |
| project | 项目特定上下文 |
| reference | 持久性中性知识 |

资料来源：[src/store/memory.ts]()

### 内容哈希机制

`contentSha` 函数计算记忆内容的 SHA256 哈希值，用于判断是否需要重新生成 embedding。哈希计算范围包括：

- `type`
- `name`
- `description`
- `body`

图关系字段（`relations`、`supersedes`）**不参与**哈希计算，确保图结构的修改不会使已有的 embedding 失效。

```typescript
export const contentSha = (memory: Memory): string =>
  createHash('sha256')
    .update(`${memory.type}\n${memory.name}\n${memory.description}\n${memory.body}`, 'utf8')
    .digest('hex');
```

资料来源：[src/store/memory.ts]()

### 原子写入

`src/store/atomic-write.ts` 实现了跨文件系统的原子写入保护，确保并发写入时的数据完整性：

1. 在目标目录创建临时文件（`.basename.<random>.tmp`）
2. 验证临时文件和目标目录在同一文件系统
3. 原子重命名临时文件到目标文件

资料来源：[src/store/atomic-write.ts]()

## 包管理

### pnpm

项目使用 pnpm 作为唯一的包管理器，不支持 npm 或 yarn。

```bash
# 安装依赖
pnpm install

# 添加依赖
pnpm add <package>

# 运行测试
pnpm test
```

### CI 自动化

项目在 Node 20 和 Node 22 两个版本上运行 CI，确保兼容性。

```mermaid
graph LR
    A[PR 创建] --> B[CI 触发]
    B --> C{make typecheck}
    B --> D{make lint}
    B --> E{make build}
    B --> F{make test}
    B --> G{make audit}
    C --> H{全部通过?}
    D --> H
    E --> H
    F --> H
    H -->|是| I[可合并]
    H -->|否| J[阻塞合并]
```

资料来源：[CONTRIBUTING.md]()

## 关键模块职责

| 模块路径 | 职责 |
|---------|------|
| `src/embedder/` | transformers.js 封装，embedding 推理 |
| `src/store/` | Markdown I/O，YAML frontmatter 编解码，vector index |
| `src/server/` | MCP 协议处理器，工具/资源暴露 |
| `src/cli/migrate.ts` | CLI 命令行参数解析和迁移逻辑 |
| `src/index.ts` | CLI 分发器，双 bin 入口协调 |

## 环境配置

项目使用环境变量进行配置管理，由 DAR-913 统一管理：

| 环境变量 | 说明 |
|---------|------|
| COMMONPLACE_USER_DIR | 用户记忆存储路径（默认 `~/.commonplace/memory`） |
| COMMONPLACE_PROJECT_DIR | 项目记忆存储路径 |
| COMMONPLACE_EXTRACT_MENTIONS | 是否提取 `[[name]]` 提及（默认开启） |

## 测试框架

vitest 用于项目的单元测试和集成测试：

```typescript
// 测试示例结构
import { describe, it, expect } from 'vitest';

describe('MemoryStore', () => {
  it('should load memories from directory', async () => {
    // 测试逻辑
  });
});
```

## 总结

Commonplace 的技术栈设计遵循以下原则：

1. **本地优先**：使用 transformers.js 实现本地 embedding 推理，无需外部 API
2. **简单持久化**：以 Markdown + YAML 作为存储格式，确保数据可读性和可移植性
3. **协议解耦**：通过 MCP SDK 暴露能力，支持多种 MCP 客户端集成
4. **类型安全**：TypeScript 严格模式配合 ESLint 确保代码质量

---

<a id='page-embedder'></a>

## 嵌入模型与语义搜索

### 相关页面

相关主题：[搜索功能详解](#page-search), [系统架构](#page-architecture)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/store/memory.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)
- [src/store/memory-store.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory-store.ts)
- [src/store/atomic-write.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/atomic-write.ts)
- [src/cli/migrate.ts](https://github.com/rickbassham/commonplace/blob/main/src/cli/migrate.ts)
- [src/index.ts](https://github.com/rickbassham/commonplace/blob/main/src/index.ts)
- [src/server/handlers.ts](https://github.com/rickbassham/commonplace/blob/main/src/server/handlers.ts)
- [CLAUDE.md](https://github.com/rickbassham/commonplace/blob/main/CLAUDE.md)
- [README.md](https://github.com/rickbassham/commonplace/blob/main/README.md)
- [CHANGELOG.md](https://github.com/rickbassham/commonplace/blob/main/CHANGELOG.md)
</details>

# 嵌入模型与语义搜索

## 概述

Commonplace 的嵌入模型与语义搜索子系统是该应用的核心能力之一。它使 MCP 服务器能够对用户的笔记进行语义相似度搜索，而非简单的关键词匹配。整个系统完全离线运行，不依赖任何云端 API，通过本地向量计算实现语义检索。

核心设计原则：

- **嵌入完全本地化**：使用 `@huggingface/transformers` 库配合 `bge-base-en-v1.5` 模型
- **边车文件（Sidecar）模式**：`.embedding` 二进制文件与 `.md` 笔记一一对应，作为派生物可随时重建
- **内容哈希校验**：通过 `contentSha` 确保嵌入与笔记内容的一致性

资料来源：[CHANGELOG.md]()

## 架构概览

```mermaid
graph TD
    A["📄 Memory (.md)"] --> B["contentSha() 计算哈希"]
    A --> C["序列化内存对象"]
    B --> D["嵌入模型 bge-base-en-v1.5"]
    C --> E[".embedding 边车文件"]
    D --> E
    E --> F["向量数据库"]
    G["语义搜索查询"] --> H["计算查询向量"]
    H --> I["余弦相似度计算"]
    F --> I
    I --> J["Top-K 结果排序"]
    J --> K["返回 MemorySearchMatch"]
```

## 内存文件结构与内容哈希

### YAML Frontmatter 格式

每篇笔记都是一个 Markdown 文件，前置 YAML 元数据：

```yaml
---
name: feedback_scope
description: Don't shrink scope unilaterally
type: feedback
relations:
  - to: other_name
    type: builds-on
supersedes:
  - old_name
---
<body>
```

资料来源：[src/store/memory.ts:44-60]()

### contentSha 哈希算法

`contentSha` 函数计算内存内容的规范表示，用于判断边车文件是否需要重建：

```typescript
export const contentSha = (memory: Memory): string =>
  createHash('sha256')
    .update(`${memory.type}\n${memory.name}\n${memory.description}\n${memory.body}`, 'utf8')
    .digest('hex');
```

**关键设计决策**：哈希仅基于 v0.1 基线 frontmatter（`type`、`name`、`description`）和正文 `body`。图字段 `relations` 和 `supersedes` **不参与**哈希计算。

> 添加或删除图关系边**不应**导致嵌入失效，这是 DAR-925 的核心设计要求。

资料来源：[src/store/memory.ts:25-35]()

## 边车文件（Sidecar）机制

### 边车文件格式

`.embedding` 是二进制文件，包含以下结构：

| 字段 | 描述 |
|------|------|
| Magic Bytes | 格式魔数 |
| Version | 版本号 |
| Length | 数据长度 |
| modelId | 使用的嵌入模型标识 |
| dim | 向量维度 |
| contentSha | 对应笔记的哈希值 |
| vector | 实际的嵌入向量数据 |

资料来源：[src/store/memory-store.ts:140-150]()

### 边车新鲜度判断

`scan()` 方法在加载时判断每个边车文件是否可复用。满足以下**全部条件**时，边车被认为是"新鲜的"：

| 条件 | 说明 |
|------|------|
| 文件存在 | `.embedding` 文件存在于磁盘 |
| 解码成功 | Magic + Version + Length 校验通过 |
| 模型匹配 | `decoded.modelId === embedder.modelId` |
| 维度匹配 | `decoded.dim === embedder.dim` |
| 哈希匹配 | `decoded.contentSha === contentSha(memoryAsRead)` |

任一条件不满足都会触发重新嵌入并重写边车文件。

资料来源：[src/store/memory-store.ts:140-160]()

### 孤立边车清理

扫描完成后，`scan()` 会进行第二轮目录遍历，移除任何没有对应 `.md` 文件的 `.embedding` 孤立边车。这些孤立文件在内存索引中不可达，保留它们只会无声地积累无效数据。

```typescript
// 孤立边车计数报告
result.orphaned  // 被清理的孤立边车数量
```

资料来源：[src/store/memory-store.ts:175-185]()

## 扫描流程（Scan Pipeline）

```mermaid
sequenceDiagram
    participant FS as 文件系统
    participant Store as MemoryStore
    participant Scan as scan()
    participant Embedder as Embedder
    participant Sidecar as .embedding

    Scan->>FS: 遍历目录中的 *.md 文件
    Scan->>FS: 检查对应的 .embedding 边车
    alt 边车新鲜
        Scan->>Scan: 复用现有边车
    else 边车过期
        Scan->>Embedder: 调用 embed(body)
        Embedder-->>Scan: 返回向量
        Scan->>FS: 原子写入新边车
    end
    Scan->>FS: 清理孤立的 .embedding 文件
    Scan-->>Store: 更新内存索引
```

### 扫描选项

```typescript
interface ScanOptions {
  /** 是否为预演模式 */
  dryRun?: boolean;
  /** 是否删除悬挂边 */
  pruneDangling?: boolean;
}
```

在 `dryRun` 模式下，孤立边车计数会报告但不实际删除。

资料来源：[src/store/memory-store.ts:130-145]()

## 原子写入机制

嵌入向量通过原子写入操作保存到边车文件，防止并发写入导致文件损坏：

```mermaid
graph LR
    A["写入请求"] --> B["生成临时文件<br/>base.<8位随机hex>.tmp"]
    B --> C["写入临时文件"]
    C --> D{"写入成功?"}
    D--否--> E["删除临时文件<br/>抛出错误"]
    D--是--> F["原子重命名<br/>tmp → target"]
    F --> G["完成"]
```

关键特性：

- **文件系统检查**：验证目标目录与临时文件目录在同一文件系统（`dev` 匹配）
- **16位熵随机数**：充足的空间避免并发冲突
- **原子重命名**：利用操作系统原子操作保证一致性

资料来源：[src/store/atomic-write.ts:1-30]()

## 语义搜索实现

### Top-K 余弦相似度搜索

搜索流程：

1. 接收用户查询文本
2. 使用嵌入模型将查询转换为向量
3. 在内存索引中计算所有笔记的余弦相似度
4. 按相似度降序排列
5. 返回前 K 条结果

```typescript
interface MemorySearchMatch {
  name: string;
  type: MemoryType;
  description: string;
  body: string;        // 完整正文，不截断
  score: number;       // 余弦相似度，保留3位小数
  relations: Relation[]; // 出边关系
}
```

资料来源：[src/server/handlers.ts:35-55]()

### 搜索结果丰富化

搜索结果会额外返回笔记的图关系信息，但默认只包含 `builds-on` 和 `related-to` 两种边类型：

```typescript
export const DEFAULT_EXPAND_TYPES: readonly EdgeType[] = 
  ['builds-on', 'related-to'] as const;
```

这是经过设计的决策——这两种边类型最可能暴露对代理有用的邻近节点。其他边类型（`mentions`、`supersedes`、`contradicts`、`child-of`）需要显式 opt-in。

资料来源：[src/server/handlers.ts:15-20]()

### 废弃笔记排除

默认情况下，被 `supersedes` 字段引用的旧笔记会被排除在搜索结果之外。搜索结果的 `relations` 数组中**不包含** `supersedes` 边，该字段仅在内存的 YAML frontmatter 中作为单向声明存在。

资料来源：[src/server/handlers.ts:55-65]()

## MCP 工具接口

系统通过 MCP stdio 服务器暴露以下与搜索相关的工具：

### memory_search

语义搜索工具，返回匹配的笔记及其相似度分数。

### memory_list

列出所有笔记，返回名称、类型、描述信息。

### memory_save

保存新笔记，自动触发嵌入生成。

### memory_delete

删除笔记及其关联的边车文件。

### memory_link / memory_unlink

图关系链接工具，用于建立笔记间的 `builds-on`、`related-to`、`contradicts`、`child-of` 关系。

资料来源：[src/index.ts:1-30]()

## 配置与环境变量

| 环境变量 | 默认值 | 用途 |
|----------|--------|------|
| `COMMONPLACE_USER_DIR` | `~/.commonplace/memory` | 用户记忆存储根目录 |
| `COMMONPLACE_PROJECT_DIR` | 动态检测 | 项目级记忆存储目录 |
| `COMMONPLACE_EXTRACT_MENTIONS` | `'true'` | 是否启用 `[[name]]` 提及提取 |

嵌入模型 ID 通过 `resolveModelId(process.env)` 解析，环境变量配置由 DAR-913 规范。

资料来源：[src/store/mentions.ts:20-30]()

## CLI 命令行接口

`migrate` 子命令提供边车管理的命令行入口：

| 命令 | 功能 |
|------|------|
| `commonplace migrate --from claude-code` | 从 Claude Code 导入记忆 |
| `commonplace migrate --dry-run` | 预演模式，不实际写入 |
| `commonplace migrate --scan <dir>` | 扫描目录重建边车 |
| `commonplace migrate --prune-dangling` | 清理悬挂边 |

资料来源：[src/cli/migrate.ts:1-50]()

## 性能考量

### 懒加载嵌入器

嵌入器（Embedder）构造函数本身很轻量，真正的模型加载发生在首次 `embed()` 调用时。这使得 `migrateMain` 可以先完成参数解析和目录验证，再按需构造嵌入器。

```typescript
embedderFactory: () => new Embedder(resolveModelId(process.env))
```

### mtime 基线刷新

每次边车写入后，系统会刷新目录 mtime 基线，确保后续 `search()` / `list()` 调用不会误判为需要重新扫描：

```typescript
this.#refreshMtimeBaseline();
```

资料来源：[src/store/memory-store.ts:280-295]()

## 版本历史

| 版本 | 变更内容 |
|------|----------|
| 0.1.0 | 初始发布：本地嵌入管道、bge-base-en-v1.5 模型、`.embedding` 边车、brute-force top-k 余弦搜索 |

资料来源：[CHANGELOG.md]()

## 相关模块

| 模块 | 职责 |
|------|------|
| `src/embedder/index.ts` | 嵌入模型封装、向量生成 |
| `src/store/memory.ts` | 内存对象序列化、contentSha 计算 |
| `src/store/memory-store.ts` | 存储抽象、scan/save/search 核心逻辑 |
| `src/store/atomic-write.ts` | 原子文件写入 |
| `src/server/handlers.ts` | MCP 工具处理器 |

---

<a id='page-memory-format'></a>

## 内存格式与侧载文件

### 相关页面

相关主题：[存储系统](#page-storage), [搜索功能详解](#page-search)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/store/memory.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)
- [src/store/sidecar.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/sidecar.ts)
- [src/store/memory-store.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory-store.ts)
- [src/store/mentions.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/mentions.ts)
- [src/cli/migrate.ts](https://github.com/rickbassham/commonplace/blob/main/src/cli/migrate.ts)
</details>

# 内存格式与侧载文件

## 概述

Commonplace 系统中的"内存"(Memory)是项目的核心数据单元。每个内存以 Markdown 文件形式持久化存储，配合同名的二进制侧载文件(`.embedding`)存放嵌入向量。这种设计确保了 Markdown 文件为唯一真相来源，侧载文件可随时从源文件重新生成。

系统通过 `contentSha` 哈希值实现内容变更检测，确保嵌入向量与源文件保持同步。当检测到内容变化时，系统会自动重新生成嵌入并更新侧载文件。

资料来源：[src/store/memory.ts:1-30]()

## 内存 Markdown 格式

### 文件结构

内存文件采用标准 Markdown+YAML frontmatter 格式，文件结构如下：

```yaml
---
name: feedback_scope
description: Don't shrink scope unilaterally
type: feedback
relations:
  - to: other_name
    type: builds-on
supersedes:
  - old_name
---
<body>
```

资料来源：[src/store/memory.ts:1-30]()

### YAML Frontmatter 字段说明

| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `name` | string | 是 | 内存名称，必须匹配 `^[a-z0-9_]+$`，将成为文件名 |
| `description` | string | 是 | 简短的人类可读描述 |
| `type` | enum | 是 | 内存类型：`user`、`feedback`、`project`、`reference` 之一 |
| `relations` | Relation[] | 否 | 图关系列表，支持 `builds-on`、`related-to`、`contradicts`、`child-of` |
| `supersedes` | string[] | 否 | 被当前内存取代的其他内存名称列表 |

资料来源：[src/store/memory.ts:50-80]()

### Relation 对象结构

```typescript
interface Relation {
  to: string;      // 目标内存名称
  type: RelationType; // builds-on | related-to | contradicts | child-of
}
```

资料来源：[src/store/memory.ts:40-45]()

### 内存类型(MemoryType)

| 类型 | 用途 |
|------|------|
| `user` | 个人规则、偏好和身份事实 |
| `feedback` | 来自先前代理行为的纠正和经验教训 |
| `project` | 项目级上下文，如架构笔记、仓库约定 |
| `reference` | 持久化、中立的知识，如 API 形状、公式、引用 |

资料来源：[README.md](https://github.com/rickbassham/commonplace/blob/main/README.md)

### Body 内容

Markdown body 可以包含任意文本内容，支持标准 Markdown 语法。特别地，系统支持 `[[name]]` 格式的内存提及语法，用于在内存间建立隐式关系。

资料来源：[src/store/mentions.ts:1-20]()

## 规范化内容哈希(contentSha)

### 计算范围

`contentSha` 是内存内容的规范化 SHA-256 哈希值，用于检测内容变更。哈希计算范围**仅限于** v0.1 基线 frontmatter 字段：

```
${type}\n${name}\n${description}\n${body}
```

**重要**：`relations` 和 `supersedes` 图字段**不参与**哈希计算。这意味着添加或删除图边不会使嵌入向量失效。

资料来源：[src/store/memory.ts:70-85]()

### 设计原则

```
graph TD
    A[内存 Markdown 文件] --> B[提取规范内容]
    B --> C["type\nname\ndescription\nbody"]
    C --> D[SHA-256 哈希]
    D --> E[64位十六进制字符串]
    
    F[图字段: relations/supersedes] -.->|不参与| C
    
    style F fill:#ffcccc
```

这种设计确保了：
1. 图关系的修改不会触发嵌入重新计算
2. 仅当核心内容变化时才需要重新嵌入
3. 侧载文件与 Markdown 保持松耦合关系

资料来源：[src/store/memory.ts:75-85]()

## 二进制侧载文件格式

### 文件命名

每个内存文件 `<name>.md` 配对一个侧载文件 `<name>.embedding`，存放在同一目录下。

### Wire Format 规范

侧载文件采用小端序二进制格式，结构如下：

```
偏移   大小    字段
0      4      magic        "CMEM" (ASCII)
4      1      version      0x01
5      1      model_len    model_id 的 UTF-8 字节长度
6      L      model_id     UTF-8 字节
6+L    4      dim          uint32 小端序
10+L   32     content_sha  原始 sha256 (从十六进制解码)
42+L   D*4    vector       float32 小端序值
```

**总大小** = 42 + model_len + dim × 4 字节

对于 bge-base 模型（model_id: `Xenova/bge-base-en-v1.5`，dim: 768），每个侧载文件约 3 KB。

资料来源：[src/store/sidecar.ts:1-50]()

### 侧载文件结构图

```mermaid
graph LR
    subgraph "Binary .embedding file"
        A["magic<br/>4 bytes<br/>CMEM"] --> B["version<br/>1 byte<br/>0x01"]
        B --> C["model_len<br/>1 byte"]
        C --> D["model_id<br/>L bytes<br/>UTF-8"]
        D --> E["dim<br/>4 bytes<br/>uint32 LE"]
        E --> F["content_sha<br/>32 bytes<br/>raw sha256"]
        F --> G["vector<br/>dim×4 bytes<br/>float32 LE"]
    end
```

资料来源：[src/store/sidecar.ts:25-45]()

## 内存存储操作

### MemoryStore 架构

```mermaid
graph TD
    subgraph "MemoryStore"
        A["#entries[]<br/>内存索引数组"] --> B["scan()"]
        A --> C["linkEdge()"]
        A --> D["unlinkEdge()"]
        A --> E["search()"]
    end
    
    F["文件系统<br/>*.md + *.embedding"] --> B
    B --> G["生成嵌入<br/>创建侧载"]
    G --> F
    
    H["MemoryGraph<br/>内存图"] --> C
    H --> D
    C -->|addEdge| H
    D -->|removeEdge| H
```

资料来源：[src/store/memory-store.ts:1-100]()

### 扫描与嵌入生成

`scan()` 方法执行以下操作：

1. 遍历目录中所有 `.md` 文件
2. 对每个文件，检查对应的 `.embedding` 是否可复用
3. 满足以下条件时复用现有侧载：
   - `.embedding` 文件存在
   - 解码通过（magic + version + length 检查）
   - `decoded.modelId === embedder.modelId`
   - `decoded.dim === embedder.dim`
   - `decoded.contentSha === contentSha(memoryAsRead)`

资料来源：[src/store/memory-store.ts:80-120]()

### 孤儿侧载清理

扫描完成后，`scan()` 会再次遍历目录，删除任何缺少对应 `.md` 文件的 `.embedding` 孤儿文件。这防止了孤立侧载文件在磁盘上无声积累。

```mermaid
graph TD
    A["扫描完成"] --> B{"存在孤儿子载文件?"}
    B -->|是| C{"dryRun 模式?"}
    C -->|是| D["报告孤立数量<br/>不删除"]
    C -->|否| E["删除孤儿文件"]
    B -->|否| F["扫描完成"]
    D --> F
    E --> F
```

资料来源：[src/store/memory-store.ts:120-140]()

### 边链接操作

#### linkEdge - 添加关系

```typescript
public async linkEdge(args: {
  from: string;
  to: string;
  type: RelationType | 'supersedes';
}): Promise<{ relations: Relation[]; supersedes: string[] }>
```

流程：
1. 验证源内存存在
2. 检查是否重复边
3. 更新内存对象
4. 通过 `atomicWrite` 写入 `.md` 文件
5. 更新内存图 `MemoryGraph`
6. 刷新 mtime 基线

资料来源：[src/store/memory-store.ts:150-200]()

#### unlinkEdge - 移除关系

```typescript
public async unlinkEdge(args: {
  from: string;
  to: string;
  type?: RelationType | 'supersedes';
}): Promise<{ relations: Relation[]; supersedes: string[]; note?: string }>
```

- 当 `type` 省略时，移除所有从 `from` 到 `to` 的边
- 当边不存在时为静默操作，返回 `note` 说明原因
- 涉及原子写入和图更新

资料来源：[src/store/memory-store.ts:200-260]()

## 内容变更检测与重新嵌入

```mermaid
sequenceDiagram
    participant MD as Markdown 文件
    participant SC as Sidecar 文件
    participant SS as MemoryStore
    participant EMB as Embedder

    SS->>MD: 读取内容
    SS->>MD: 计算 contentSha
    SS->>SC: 读取 sidecar
    SC->>SS: 返回 content_sha
    Note over SS: contentSha === content_sha?
    
    alt 内容一致
        SS->>SC: 复用现有侧载
    else 内容变更
        SS->>EMB: 请求嵌入
        EMB->>SS: 返回向量
        SS->>SC: 写入新侧载
    end
```

## 迁移工具中的扫描操作

`migrate` 命令支持对现有目录执行扫描：

```bash
commonplace migrate <dir> [--dry-run] [--prune-dangling]
```

参数说明：

| 参数 | 说明 |
|------|------|
| `dir` | 内存目录路径 |
| `--dry-run` | 预览模式，不实际写入 |
| `--prune-dangling` | 清理指向不存在内存的悬空边 |

资料来源：[src/cli/migrate.ts:100-150]()

## 数据完整性保证

| 操作 | 原子性 | 锁机制 |
|------|--------|--------|
| 内存写入 | `atomicWrite` (临时文件+重命名) | `acquireNameLock` |
| 边添加/移除 | 原子写入 | 名称锁 |
| 扫描 | 非原子 | mtime 基线避免浪费性重扫描 |
| 孤儿清理 | `fs.unlinkSync` | - |

资料来源：[src/store/memory-store.ts:180-220]()

## 命名验证规则

内存名称必须满足以下正则表达式：

```typescript
/^[a-z0-9_]+$/
```

- 只能包含小写字母、数字和下划线
- 不允许路径分隔符
- 成为文件名主干

此规则确保了文件系统兼容性和 URI 安全。

资料来源：[src/store/memory.ts:20-40]()

---

<a id='page-search'></a>

## 搜索功能详解

### 相关页面

相关主题：[嵌入模型与语义搜索](#page-embedder), [图功能与关系管理](#page-graph-features)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/rickbassham/commonplace/blob/main/README.md)
- [src/store/memory.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)
- [src/store/memory-store.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory-store.ts)
- [src/store/mentions.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/mentions.ts)
- [src/server/handlers.ts](https://github.com/rickbassham/commonplace/blob/main/src/server/handlers.ts)
</details>

# 搜索功能详解

## 概述

Commonplace 的搜索功能是一个本地优先的语义搜索系统，基于余弦相似度（Cosine Similarity）算法实现完全离线的向量搜索。系统使用 `@huggingface/transformers` 库和 `bge-base-en-v1.5` 模型生成嵌入向量，支持对记忆进行语义匹配检索。

搜索功能的核心设计原则是**记忆文件（`.md`）是唯一真相来源**，`.embedding` 侧边文件是从记忆内容派生的，可以随时删除和重建。

## 核心架构

### 搜索流程图

```mermaid
graph TD
    A[用户发起 memory_search] --> B[解析查询参数]
    B --> C[生成查询向量]
    C --> D[遍历所有记忆文件]
    D --> E[加载 .embedding 侧边文件]
    E --> F[计算余弦相似度]
    F --> G[按分数排序取 Top-K]
    G --> H[合并 User/Project 商店结果]
    H --> I[排除已废弃记忆]
    I --> J[丰富关系图数据]
    J --> K[返回搜索结果]
```

### 关键组件

| 组件 | 文件位置 | 职责 |
|------|----------|------|
| Embedder | `src/store/embedder.ts` | 负责将文本转换为 768 维向量 |
| MemoryStore | `src/store/memory-store.ts` | 管理记忆存储、扫描和搜索 |
| MemoryGraph | `src/store/graph.ts` | 维护记忆间的图关系 |
| Mentions | `src/store/mentions.ts` | 提取正文中 `[[name]]` 引用 |

## 嵌入向量生成

### 嵌入模型

系统使用 `bge-base-en-v1.5` 模型作为默认嵌入模型，该模型输出 768 维的向量表示。

```mermaid
graph LR
    A[记忆文本] --> B[contentSha 计算]
    B --> C[模型推理]
    C --> D[768维向量]
    D --> E[.embedding 侧边文件]
```

### contentSha 计算

为了检测记忆内容是否发生变化，系统为每个记忆计算 SHA-256 哈希值：

```typescript
export const contentSha = (memory: Memory): string =>
  createHash('sha256')
    .update(`${memory.type}\n${memory.name}\n${memory.description}\n${memory.body}`, 'utf8')
    .digest('hex');
```

**重要设计决策**：哈希值的计算范围仅包括 `type`、`name`、`description` 和 `body` 四个字段。**图字段（`relations` 和 `supersedes`）不参与哈希计算**，这确保了添加或删除图关系不会导致嵌入向量失效。

资料来源：[src/store/memory.ts:47-51](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)

### 惰性重新嵌入

在保存记忆时，如果 `contentSha` 与侧边文件中存储的哈希值不匹配，系统会自动触发重新嵌入：

```typescript
const needsReembed =
  isMissing || stale.embedding.corrupt || stale.embedding.contentShaMismatch;
```

这确保了搜索结果始终与最新的记忆内容同步。

## 记忆文件结构

### Markdown 格式

记忆以 Markdown 文件形式存储，包含 YAML 前置数据：

```yaml
---
name: feedback_scope
description: Don't shrink scope unilaterally
type: feedback
relations:
  - to: other_name
    type: builds-on
supersedes:
  - old_name
---
<正文内容>
```

资料来源：[src/store/memory.ts:30-46](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)

### 记忆类型

| 类型 | 说明 | 适用场景 |
|------|------|----------|
| `user` | 个人规则和偏好 | 跨项目的通用知识 |
| `feedback` | 纠正和经验教训 | 持久的行为调整 |
| `project` | 项目特定上下文 | 架构笔记、代码规范 |
| `reference` | 持久知识 | API 形状、公式、引用 |

资料来源：[README.md](https://github.com/rickassham/commonplace/blob/main/README.md)

## 搜索 API

### memory_search 工具

搜索工具是最常用的查询接口，支持以下参数：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `query` | string | 是 | 搜索查询文本 |
| `type` | string | 否 | 过滤特定记忆类型 |
| `limit` | number | 否 | 返回结果数量上限 |
| `scope` | `user \| project` | 否 | 指定搜索范围 |
| `expandTypes` | string[] | 否 | 展开的边类型 |
| `includeSuperseded` | boolean | 否 | 是否包含已废弃记忆 |

### 搜索响应结构

```typescript
export interface MemorySearchResult {
  matches: MemorySearchMatch[];
  query: string;
}

export interface MemorySearchMatch {
  name: string;
  type: MemoryType;
  description: string;
  body: string;
  score: number;  // 余弦相似度，保留3位小数
  relations: Relation[];
}
```

**重要约束**：根据设计规范，body 字段**永远不会被截断、摘要或转换**，调用者获得完全原始的存储内容。

资料来源：[src/server/handlers.ts:35-56](https://github.com/rickbassham/commonplace/blob/main/src/server/handlers.ts)

## 搜索范围与合并

### 双商店架构

Commonplace 支持同时加载两个记忆商店：

```mermaid
graph TD
    A[memory_search 请求] --> B{是否指定 scope?}
    B -->|是| C[仅搜索指定商店]
    B -->|否| D[并行搜索两个商店]
    C --> E[返回结果]
    D --> F[User Store 搜索]
    D --> G[Project Store 搜索]
    F --> H[按分数合并排序]
    G --> H
    H --> I[返回合并结果]
```

### 商店位置

| 商店类型 | 默认路径 | 说明 |
|----------|----------|------|
| User Store | `~/.commonplace/memory` | 始终加载 |
| Project Store | `<project-root>/.commonplace/memory` | 仅在检测到项目根目录时加载 |

### 检测优先级

项目商店的位置按以下优先级确定：

1. `COMMONPLACE_PROJECT_DIR` 环境变量（最高优先级）
2. MCP `roots/list` 返回的项目根目录
3. 当前工作目录

## 关系图扩展

### 边类型

搜索结果可以包含记忆之间的关系信息，支持以下边类型：

| 边类型 | 说明 | 默认展开 |
|--------|------|----------|
| `builds-on` | 建立于其他记忆之上 | ✓ |
| `related-to` | 相关关系 | ✓ |
| `mentions` | 正文中引用 | ✗ |
| `supersedes` | 废弃替代 | ✗ |
| `contradicts` | 矛盾关系 | ✗ |
| `child-of` | 父子关系 | ✗ |

**默认展开类型**：`builds-on` 和 `related-to` 是最可能产生有用邻居的边类型，因此默认展开。

资料来源：[src/server/handlers.ts:19-25](https://github.com/rickbassham/commonplace/blob/main/src/server/handlers.ts)

### 废弃记忆排除

默认情况下，搜索结果会排除已被废弃的记忆。通过 `supersedes` 字段，系统自动识别并过滤：

```typescript
// memory_path 工具的默认行为
if (!includeSuperseded) {
  results = results.filter((m) => !supersededNames.has(m.name));
}
```

## 提及提取功能

### [[name]] 语法

系统支持从记忆正文中提取 `[[name]]` 格式的提及，生成隐式的关系边：

```mermaid
graph TD
    A[记忆正文] --> B[正则匹配 [[name]]
    B --> C{匹配成功?}
    C -->|是| D[提取 name]
    C -->|否| E[跳过]
    D --> F[调用 graph.addMentionsEdge]
    E --> G[继续处理]
```

### 启用控制

提及提取功能通过环境变量控制：

- **默认状态**：启用
- **禁用方式**：设置 `COMMONPLACE_EXTRACT_MENTIONS=false`

```typescript
// 提取受环境变量 COMMONPLACE_EXTRACT_MENTIONS 控制
const enabled = mentionsExtractionEnabled();
if (enabled) {
  const mentions = extractMentions(body);
  for (const name of mentions) {
    this.#graph.addMentionsEdge({ from: name, to });
  }
}
```

资料来源：[src/store/mentions.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/mentions.ts)

## 搜索性能特性

### 暴力搜索算法

系统采用暴力搜索（Brute-Force）算法计算余弦相似度：

```typescript
// 伪代码示意
for (const memory of allMemories) {
  const embedding = loadEmbedding(memory.embeddingPath);
  const score = cosineSimilarity(queryEmbedding, embedding);
  results.push({ memory, score });
}
return results.sort((a, b) => b.score - a.score).slice(0, limit);
```

### 目录扫描优化

MemoryStore 维护目录修改时间基线，避免不必要的重新扫描：

```typescript
// 刷新 mtime 基线以避免浪费性重新扫描
this.#refreshMtimeBaseline();
```

### 原子写入保证

文件写入使用原子写入模式，防止并发操作导致数据损坏：

```typescript
// 临时文件 + rename 确保写入原子性
const tmpName = `${base}.${randomBytes(8).toString('hex')}.tmp`;
const tmpPath = join(dir, tmpName);
// ... 写入临时文件 ...
await fs.rename(tmpPath, target);
```

## MCP 工具注册

搜索功能通过 MCP（Model Context Protocol）协议暴露给 AI 代理：

```mermaid
graph LR
    A[AI 代理] -->|MCP 调用| B[mcp__commonplace__memory_search]
    B --> C[handlers.ts 分发]
    C --> D[MemoryStore.search]
    D --> E[返回 MemorySearchResult]
    E --> F[AI 代理]
```

## 搜索与记忆生命周期

```mermaid
graph LR
    A[memory_save] --> B[写入 .md]
    B --> C[计算 contentSha]
    C --> D[生成嵌入向量]
    D --> E[写入 .embedding]
    E --> F[scan 更新索引]
    F --> G[提取 mentions]
    G --> H[更新关系图]
    
    I[memory_search] --> J[查询向量]
    J --> K[遍历 .embedding]
    K --> L[计算余弦相似度]
    L --> M[合并排序结果]
    M --> N[丰富关系数据]
    N --> O[返回结果]
```

## 相关命令

| 命令 | 说明 |
|------|------|
| `commonplace migrate <dir>` | 扫描并嵌入目录中的所有记忆 |
| `commonplace migrate --from claude-code` | 从 Claude Code 导入记忆 |
| `mcp__commonplace__memory_search` | MCP 搜索接口 |

## 配置参考

### 环境变量

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `COMMONPLACE_USER_DIR` | `~/.commonplace/memory` | 用户记忆目录 |
| `COMMONPLACE_PROJECT_DIR` | - | 项目记忆目录（覆盖检测） |
| `COMMONPLACE_EXTRACT_MENTIONS` | `true` | 启用提及提取 |

### CLI 选项

| 选项 | 说明 |
|------|------|
| `--dry-run` | 预览操作不写入文件 |
| `--auto` | 脚本化自动运行 |
| `--prune-dangling` | 清理悬空引用 |

## 版本历史

| 版本 | 日期 | 搜索相关特性 |
|------|------|--------------|
| 0.1.0 | 2026-05-10 | 初始发布：基础搜索、向量嵌入、图关系 |

资料来源：[CHANGELOG.md](https://github.com/rickbassham/commonplace/blob/main/CHANGELOG.md)

---

<a id='page-graph-features'></a>

## 图功能与关系管理

### 相关页面

相关主题：[搜索功能详解](#page-search), [内存格式与侧载文件](#page-memory-format)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/store/graph.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/graph.ts)
- [src/store/mentions.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/mentions.ts)
- [src/store/memory.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)
- [src/store/memory-store.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory-store.ts)
- [src/server/handlers.ts](https://github.com/rickbassham/commonplace/blob/main/src/server/handlers.ts)
- [src/cli/graph.ts](https://github.com/rickbassham/commonplace/blob/main/src/cli/graph.ts)
</details>

# 图功能与关系管理

## 概述

图功能与关系管理是 Commonplace 项目中用于管理记忆（Memory）之间语义关联的核心模块。该模块基于 YAML frontmatter 中的 `relations` 和 `supersedes` 字段构建内存图，并支持从正文中提取 `[[name]]` 形式的提及关系。通过这些机制，用户可以构建记忆之间的有向图，实现知识关联、一跳扩展搜索等功能。

该功能主要服务于以下场景：

- 显式声明记忆之间的关系（如"此记忆建立在另一个记忆之上"）
- 自动检测正文中的提及关系
- 悬空边检测与清理
- 图可视化与路径查询

资料来源：[src/store/graph.ts:1-20]()

## 数据模型

### 边的类型

图模块定义了六种边类型，分为两类：

**授权边类型（Authored Relation Types）**

| 边类型 | 说明 |
|--------|------|
| `related-to` | 相关关系 |
| `builds-on` | 建立关系（继承/基于） |
| `contradicts` | 矛盾关系 |
| `child-of` | 子级关系 |

**派生边类型（Derived Edge Types）**

| 边类型 | 来源 |
|--------|------|
| `supersedes` | 来自 frontmatter 的 `supersedes[]` 字段 |
| `mentions` | 来自正文 `[[name]]` 提取（DAR-927） |

资料来源：[src/store/graph.ts:19-22]()

```typescript
export type EdgeType = RelationType | 'supersedes' | 'mentions';
```

### 边接口

```typescript
export interface Edge {
  from: string;    // 源记忆名称
  to: string;      // 目标记忆名称
  type: EdgeType;  // 边类型
}
```

### 悬空边接口

当边指向的目标记忆不存在时，该边称为悬空边（Dangling Edge）：

```typescript
export interface DanglingEdge {
  from: string;
  to: string;
  type: string;  // 类型被放宽为 string，允许扩展
}
```

资料来源：[src/store/graph.ts:24-44]()

### 记忆前端matter关系字段

每个记忆的 YAML frontmatter 可包含以下图字段：

```yaml
---
name: feedback_scope
type: feedback
relations:        # DAR-925，可选，默认 []
  - to: other_name
    type: builds-on
supersedes:      # DAR-925，可选，默认 []
  - old_name
---
```

资料来源：[src/store/memory.ts:7-23]()

### 关系解析验证

`relations` 字段中的每条记录必须包含 `to` 和 `type` 两个必填键：

```typescript
const parseRelation = (entry: unknown, ctx: string): Relation => {
  if (!isPlainObject(entry)) {
    throw new Error(`${ctx} must be a mapping with \`to\` and \`type\``);
  }
  if (!('to' in entry)) {
    throw new Error(`${ctx} is missing required key \`to\``);
  }
  if (!('type' in entry)) {
    throw new Error(`${ctx} is missing required key \`type\``);
  }
  const to = validateName(entry.to, `${ctx}.to`);
  const type = entry.type;
  if (!isRelationType(type)) {
    throw new Error(`${ctx}.type must be one of ${RELATION_TYPES.join(', ')}`);
  }
  return { to, type };
};
```

资料来源：[src/store/memory.ts:82-102]()

### 内容哈希与图字段独立性

内容 SHA256 仅基于 v0.1 基线字段计算：`type`、`name`、`description` 和 `body`。图字段（`relations`、`supersedes`）**不参与**哈希计算，这意味着添加或删除图边不会使对应的 embedding 失效。

```typescript
export const contentSha = (memory: Memory): string =>
  createHash('sha256')
    .update(`${memory.type}\n${memory.name}\n${memory.description}\n${body}`, 'utf8')
    .digest('hex');
```

资料来源：[src/store/memory.ts:48-55]()

## 提及提取（DAR-927）

### 概述

`[[name]]` 提及提取器负责从记忆正文中解析所有提及的目标名称，返回去重后的集合，保持首次出现顺序。

### 提取规则

- 仅接受符合 `^[a-z0-9_]+$` 规则的名称（与记忆文件名命名规则一致）
- 不符合规则的 `[[...]]` 形式会被静默忽略
- 代码块和行内代码中的提及**不会**被特殊处理（按需求说明 #1 排除）

资料来源：[src/store/mentions.ts:1-30]()

### 配置

提及提取由环境变量 `COMMONPLACE_EXTRACT_MENTIONS` 控制：

| 值 | 行为 |
|----|------|
| 未设置 / `'true'` | 启用提取 |
| `'false'` | 禁用提取 |

资料来源：[src/store/mentions.ts:16-20]()

### 工作流程

```mermaid
graph TD
    A[记忆正文] --> B{提取开关}
    B -->|启用| C[正则匹配 [[...]]]
    B -->|禁用| D[返回空集]
    C --> E[提取内容]
    E --> F{符合 NAME_PATTERN?}
    F -->|是| G[加入结果集]
    F -->|否| H[忽略]
    G --> I[去重并排序]
    H --> I
    I --> J[返回唯一名称列表]
    D --> J
```

## MemoryStore 关系管理

### 添加关系

`linkEdge` 方法用于向记忆添加新的关系边：

```typescript
public async linkEdge(args: {
  from: string;
  to: string;
  type: RelationType | 'supersedes';
}): Promise<Memory>
```

**行为说明：**

1. 验证源记忆存在
2. 检查重复边（相同 `from`、`to`、`type` 的边已存在则报错）
3. 更新 `relations[]` 或 `supersedes[]` 数组
4. 通过 `atomicWrite` 原子写入 `.md` 文件
5. 更新内存中的 entry
6. 同步更新 `MemoryGraph`

**处理逻辑：**

```typescript
const nextRelations: Relation[] =
  type === 'supersedes' ? entry.relations.slice() : [...entry.relations, { to, type }];
const nextSupersedes: string[] =
  type === 'supersedes' ? [...entry.supersedes, to] : entry.supersedes.slice();
```

资料来源：[src/store/memory-store.ts:1-100]()

### 移除关系

`unlinkEdge` 方法用于移除关系边：

```typescript
public async unlinkEdge(args: {
  from: string;
  to: string;
  type?: RelationType | 'supersedes';  // 可选，不指定则移除所有类型
}): Promise<{ relations: Relation[]; supersedes: string[]; note?: string }>
```

**行为说明：**

- 当 `type` 指定时，仅移除匹配的边
- 当 `type` 省略时，移除所有从 `from` 到 `to` 的边（无论类型）
- 边不存在时为静默操作，返回 `note` 说明原因

资料来源：[src/store/memory-store.ts:150-200]()

### 原子写入保证

所有图修改通过 `atomicWrite` 确保一致性：

```typescript
const mdPath = join(this.#dir, `${from}.md`);
const release = await acquireNameLock(this.#dir, from);
try {
  const mdBytes = Buffer.from(serializeMemory(updated), 'utf8');
  await atomicWrite(mdPath, mdBytes);
  // 更新内存索引...
} finally {
  release();
}
```

## MCP 工具接口

### memory_link

建立记忆之间的边：

```jsonc
// memory_link
{
  "from": "architecture_overview",
  "to": "feedback_scope",
  "type": "builds-on"
}
```

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `from` | string | 是 | 源记忆名称 |
| `to` | string | 是 | 目标记忆名称 |
| `type` | 边类型 | 是 | 关系类型 |
| `scope` | `user\|project` | 否 | 作用域 |

### memory_unlink

移除记忆之间的边：

```jsonc
// memory_unlink
{
  "from": "architecture_overview",
  "to": "feedback_scope",
  "type": "builds-on"  // 可选，忽略则移除所有类型
}
```

资料来源：[src/server/handlers.ts:1-50]()

### 搜索中的一跳扩展

`memory_search` 工具支持 `expand: 'one-hop'` 参数，从直接命中出发扩展邻居节点：

| 参数 | 默认值 | 说明 |
|------|--------|------|
| `expand` | `'none'` | 扩展模式：`none` 或 `one-hop` |
| `expandTypes` | `['builds-on', 'related-to']` | 允许的边类型 |
| `expandLimit` | `2` | 每个直接命中的最大邻居数 |

```jsonc
// 带一跳扩展的搜索
{
  "query": "architecture",
  "expand": "one-hop",
  "expandTypes": ["builds-on", "related-to", "mentions"],
  "expandLimit": 3
}
```

扩展条目携带 `via` 字段标识来源：

```jsonc
{
  "name": "neighbor_name",
  "via": {
    "source": "direct_hit_name",
    "edge": "builds-on"
  }
}
```

资料来源：[src/server/handlers.ts:60-90]()

### 默认扩展类型

默认仅扩展 `builds-on` 和 `related-to`，因为这两种边类型最可能产生有用的邻居。其他类型（`mentions`、`supersedes`、`contradicts`、`child-of`）需要显式 opt-in。

```typescript
export const DEFAULT_EXPAND_TYPES: readonly EdgeType[] = ['builds-on', 'related-to'] as const;
```

资料来源：[src/server/handlers.ts:52-55]()

## CLI 命令行工具

### graph 子命令

`commonplace graph` 命令用于可视化图结构：

```bash
commonplace graph <name> [options]
```

| 选项 | 默认值 | 说明 |
|------|--------|------|
| `--depth, -d` | `1` | 遍历深度 |
| `--types, -t` | 全部 | 逗号分隔的边类型过滤 |
| `--direction` | `outbound` | `outbound`、`inbound` 或 `both` |
| `--format, -f` | `mermaid` | 输出格式：`mermaid`、`json`、`dot` |
| `--scope, -s` | 自动 | `user`、`project` 或 `both` |

### 输出格式

**Mermaid 格式（默认）：**

````markdown
```mermaid
flowchart LR
    A[architecture_overview] -->|builds-on| B[feedback_scope]
```
````

**JSON 格式：**

```json
{
  "root": "architecture_overview",
  "nodes": [...],
  "edges": [...]
}
```

**DOT 格式：** 用于 Graphviz 工具链处理大规模图。

资料来源：[src/cli/graph.ts:1-40]()

### 实现细节

CLI 使用与 MCP `memory_graph` 工具相同的遍历辅助函数，因此图遍历行为、循环处理、深度/类型过滤完全一致：

```typescript
const result = await graphMain({
  argv,
  embedderFactory: () => new Embedder(resolveModelId(process.env)),
  stdout: (chunk: string) => process.stdout.write(chunk),
  stderr: (chunk: string) => process.stderr.write(chunk),
  env: process.env,
  cwd: process.cwd(),
});
```

资料来源：[src/index.ts:50-65]()

## 存储层架构

### 双存储模式

Commonplace 支持同时加载两个记忆存储：

```mermaid
graph LR
    subgraph 用户存储
        U[User Memory Store]
    end
    subgraph 项目存储
        P[Project Memory Store]
    end
    G[MemoryGraph] --- U
    G[MemoryGraph] --- P
```

- **用户存储**：始终加载，位于 `~/.commonplace/memory`
- **项目存储**：仅在检测到项目根目录时加载

每个存储维护独立的 `#entries` 数组和 `MemoryGraph` 实例。

资料来源：[src/store/memory-store.ts:1-30]()

### 目录扫描与图构建

在 `scan()` 和 `save()` 过程中，`MemoryStore` 调用 `extractMentions` 提取正文提及，并将其转发给 `MemoryGraph.addMentionsEdge`：

```typescript
// 伪代码示意
for (const memory of memories) {
  const mentions = extractMentions(memory.body);
  for (const name of mentions) {
    this.#graph.addMentionsEdge({ from: memory.name, to: name });
  }
}
```

## 功能边界

以下功能明确不在当前实现范围内：

| 功能 | 负责人 |
|------|--------|
| 验证引用的记忆名称是否存在于磁盘（DAR-926） | 待实现 |
| 构建内存邻接表/图（DAR-926） | 待实现 |
| Wiki 风格反向链接渲染/自动补全 | 排除范围 |
| 代码块/行内代码中的提及感知 | 排除范围 |
| MCP 工具响应中的提及展示（DAR-929/DAR-932） | 待实现 |
| 中心性/PageRank 计算（DAR-931） | 待实现 |
| 遍历/路径查询（DAR-932） | 待实现 |

资料来源：[src/store/graph.ts:6-15]()

## 迁移与清理

### migrate 命令的图清理

`commonplace migrate` 命令在导入文件时会检测并清理悬空边：

```
  pruned:       5 dangling edges across 2 files (would be removed)
    memory_a: 3
    memory_b: 2
```

悬空边指向前端 matter 中声明但不存在于磁盘的目标记忆。

资料来源：[src/cli/migrate.ts:1-30]()

### 悬空边检测

图模块暴露 `DanglingEdge` 接口用于悬空边报告：

```typescript
export interface DanglingEdge {
  from: string;
  to: string;
  type: string;
}
```

返回时 `type` 被放宽为 `string`，以便未来支持扩展边类型。

---

<a id='page-storage'></a>

## 存储系统

### 相关页面

相关主题：[内存格式与侧载文件](#page-memory-format), [作用域与多存储](#page-scopes)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/store/memory-store.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory-store.ts)
- [src/store/atomic-write.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/atomic-write.ts)
- [src/store/graph.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/graph.ts)
- [src/store/memory.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)
- [src/store/mentions.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/mentions.ts)
</details>

# 存储系统

## 概述

存储系统是 Commonplace 项目的核心模块，负责将记忆（Memory）以 Markdown 文件形式持久化到本地磁盘，同时管理向量嵌入（Embedding）sidecar 文件、图关系以及内存索引。该系统采用本地优先（Local-First）架构，所有数据存储在用户本地目录，无需云端依赖，实现了完全的离线语义搜索能力。

存储系统的设计目标包括：

- **文件即存储**：每个记忆是一个独立的 `.md` 文件，YAML frontmatter 存储元数据
- **嵌入派生**：`.embedding` sidecar 文件由 `.md` 内容派生，可随时重建
- **原子操作**：通过 temp file + rename 模式保证写操作的原子性
- **图关系管理**：支持记忆之间的多种关系类型（builds-on、related-to 等）
- **作用域隔离**：支持 `user` 和 `project` 两种存储作用域

资料来源：[src/store/memory-store.ts:1-50]()

## 架构概览

```mermaid
graph TB
    subgraph "存储层 (Disk)"
        MD[".md 文件<br/>YAML Frontmatter + Body"]
        EMB[".embedding 文件<br/>Binary Sidecar"]
    end
    
    subgraph "内存索引 (In-Memory)"
        IDX["#entries[]<br/>MemoryEntry 数组"]
        GRAPH["#graph<br/>MemoryGraph"]
    end
    
    subgraph "核心模块"
        MS["MemoryStore<br/>主存储类"]
        AW["atomicWrite<br/>原子写入工具"]
        MEM["memory.ts<br/>序列化/反序列化"]
        MENT["mentions.ts<br/>提及提取器"]
    end
    
    MD --> MS
    EMB --> MS
    MS --> IDX
    MS --> GRAPH
    MS --> AW
    MS --> MEM
    MS --> MENT
```

## 核心组件

### MemoryStore

`MemoryStore` 是存储系统的主类，负责管理特定目录下的所有记忆文件。它维护两个核心内部状态：

- `#entries: MemoryEntry[]` — 从磁盘扫描加载的内存索引
- `#graph: MemoryGraph | undefined` — 可选的内存图结构

资料来源：[src/store/memory-store.ts:80-85]()

#### 关键方法

| 方法 | 用途 |
|------|------|
| `save()` | 创建新记忆，写入 `.md` 和 `.embedding` |
| `list()` | 返回所有记忆的摘要信息 |
| `get()` | 获取单个记忆的完整内容 |
| `delete()` | 删除记忆文件和关联的 embedding |
| `search()` | 基于向量相似度的语义搜索 |
| `linkEdge()` | 添加记忆间的关系边 |
| `unlinkEdge()` | 移除记忆间的关系边 |
| `scan()` | 重新扫描目录，更新内存索引 |

资料来源：[src/store/memory-store.ts:200-450]()

### Atomic Write

`atomicWrite` 模块提供基于 temp file + rename 的原子写入模式，确保在写入过程中发生崩溃或断电时不会产生部分写入的损坏文件。

```mermaid
sequenceDiagram
    participant Caller
    participant AW as atomicWrite
    participant FS as FileSystem
    
    Caller->>AW: atomicWrite(target, data)
    AW->>AW: 生成随机 tmp 文件名<br/>base.16hexchars.tmp
    AW->>FS: 写入 tmp 文件
    AW->>FS: rename(tmp, target)
    Note over FS: rename(2) 原子操作
    AW->>Caller: 完成
```

#### 安全特性

- **跨文件系统防护**：通过比较源目录和临时文件目录的 `dev` 设备号，拒绝跨文件系统重命名
- **足够熵值**：使用 8 字节（16 十六进制字符）随机数生成临时文件名，避免并发写入冲突
- **同目录原则**：临时文件与目标文件位于同一目录，确保 rename 操作在同一文件系统内

资料来源：[src/store/atomic-write.ts:1-40]()

### Memory 数据模型

每个记忆由一个 Markdown 文件表示，结构如下：

```yaml
---
name: feedback_scope
description: Don't shrink scope unilaterally
type: feedback
relations:
  - to: other_name
    type: builds-on
supersedes:
  - old_name
---
<body>
```

#### 字段说明

| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `name` | string | 是 | 记忆名称，需匹配 `^[a-z0-9_]+$` |
| `description` | string | 是 | 简短描述，用于搜索结果展示 |
| `type` | MemoryType | 是 | 记忆类型：`user` \| `feedback` \| `project` \| `reference` |
| `relations` | Relation[] | 否 | 关系列表，默认空数组 |
| `supersedes` | string[] | 否 | 被替代的记忆名称列表 |

资料来源：[src/store/memory.ts:1-60]()

### 记忆类型 (MemoryType)

```mermaid
graph LR
    A[MemoryType] --> B["user<br/>用户偏好规则"]
    A --> C["feedback<br/>反馈纠正"]
    A --> D["project<br/>项目上下文"]
    A --> E["reference<br/>参考知识"]
```

| 类型 | 用途 | 使用场景 |
|------|------|----------|
| `user` | 个人规则、偏好、身份信息 | 跨项目适用的用户特征 |
| `feedback` | 纠正和经验教训 | 对 agent 行为的持久修正 |
| `project` | 项目特定上下文 | 架构笔记、代码规范、特定决策 |
| `reference` | 持久性中性知识 | API 形状、公式、引用资料 |

资料来源：[README.md:1-50]()

## 图关系系统

### 边类型 (RelationType)

| 类型 | 说明 | 行为 |
|------|------|------|
| `builds-on` | 建立在另一个记忆之上 | 加入 `relations[]` |
| `related-to` | 与另一个记忆相关 | 加入 `relations[]` |
| `contradicts` | 与另一个记忆矛盾 | 加入 `relations[]` |
| `child-of` | 是另一个记忆的子项 | 加入 `relations[]` |
| `supersedes` | 替代另一个记忆 | 加入 `supersedes[]` |

资料来源：[src/store/memory-store.ts:150-200]()

### 关系写入流程

```mermaid
graph TD
    START[linkEdge from, to, type] --> CHECK{from === to?}
    CHECK -->|是| ERR1[抛出错误:<br/>禁止自环]
    CHECK -->|否| LOAD[从内存索引加载 from 条目]
    LOAD --> EXISTS{to 条目存在?}
    EXISTS -->|否| ERR2[抛出错误:<br/>目标记忆不存在]
    EXISTS -->|是| DUPE{重复边?}
    DUPE -->|是| ERR3[抛出错误:<br/>边已存在]
    DUPE -->|否| BUILD[构建更新后的 Memory]
    BUILD --> LOCK[acquireNameLock 获取锁]
    LOCK --> WRITE[atomicWrite 写入 .md]
    WRITE --> MUTATE[原地更新内存条目]
    MUTATE --> GRAPH{有 #graph?}
    GRAPH -->|是| ADD[graph.addEdge]
    GRAPH -->|否| REFRESH[刷新 mtime 基线]
    ADD --> REFRESH
    REFRESH --> DONE[返回新 relations/supersedes]
```

资料来源：[src/store/memory-store.ts:280-350]()

## 扫描与嵌入

### Scan 流程

`scan()` 方法执行完整的目录重新扫描：

1. 遍历目录中所有 `.md` 文件
2. 解析每个文件的 YAML frontmatter
3. 检查对应的 `.embedding` sidecar 是否可复用
4. 不可用时触发重新嵌入
5. 清理孤立的 embedding 文件

```mermaid
flowchart TD
    START[scan] --> SCAN[扫描目录 *.md]
    SCAN --> LOOP{遍历每个 .md}
    LOOP -->|有文件| READ[读取 .md]
    READ --> PARSE[解析 frontmatter]
    PARSE --> CHECK{sidecar 存在?}
    CHECK -->|否| EMBED[重新嵌入]
    CHECK -->|是| VALIDATE[验证 sidecar]
    VALIDATE -->|有效| REUSE[复用 sidecar]
    VALIDATE -->|无效| EMBED
    REUSE --> NEXT[下一个文件]
    EMBED --> WRITE[写入新 .embedding]
    WRITE --> NEXT
    LOOP -->|完成| ORPHAN[清理孤立 sidecar]
    ORPHAN --> DONE[返回 ScanResult]
```

#### Sidecar 复用条件

一个 `.embedding` sidecar 被认为是"新鲜"的，当且仅当：

| 条件 | 说明 |
|------|------|
| 文件存在 | sidecar 文件存在 |
| 解码有效 | magic + version + length 检查通过 |
| 模型匹配 | `decoded.modelId === embedder.modelId` |
| 维度匹配 | `decoded.dim === embedder.dim` |
| SHA 匹配 | `decoded.contentSha === contentSha(memoryAsRead)` |

资料来源：[src/store/memory-store.ts:400-450]()

### Content SHA

Content SHA 用于验证记忆内容是否发生变化，进而决定是否需要重新嵌入。计算范围仅限于 v0.1 基线 frontmatter：

```typescript
export const contentSha = (memory: Memory): string =>
  createHash('sha256')
    .update(`${memory.type}\n${memory.name}\n${memory.description}\n${memory.body}`, 'utf8')
    .digest('hex');
```

**重要**：`relations` 和 `supersedes` 图字段不参与 SHA 计算。这意味着添加或删除图关系不会使 embedding 失效。

资料来源：[src/store/memory.ts:80-95]()

## 搜索功能

### 搜索流程

```mermaid
graph TD
    START[search query] --> RESCAN{需要 rescan?}
    RESCAN -->|是| SCAN[调用 scan]
    RESCAN -->|否| EMBED[嵌入查询文本]
    SCAN --> EMBED
    EMBED --> SIM[计算余弦相似度]
    SIM --> FILTER[过滤已删除记忆]
    FILTER --> RANK[按得分排序]
    RANK --> EXPAND[expandRelations 扩展邻居]
    EXPAND --> RESULT[返回结果信封]
```

### 搜索结果结构

```typescript
interface MemorySearchResult {
  query: string;           // 原始查询
  memories: MemorySearchMatch[];  // 匹配结果
  expanded: boolean;       // 是否包含扩展关系
}
```

资料来源：[src/store/handlers.ts:50-80]()

## 并发与锁定

### Per-Name 锁定机制

为防止并发写入同一记忆名称导致的竞态条件，系统实现了 `acquireNameLock` 机制：

- 锁定粒度：每个记忆名称独立锁
- 锁文件位置：`.locks/<name>.lock`
- 超时回收：超过 5 秒的过期锁自动回收
- 锁范围：覆盖 `.md` 和 `.embedding` 的完整写入过程

资料来源：[src/store/memory-store.ts:500-550]()

### 原子写入序列

```mermaid
sequenceDiagram
    participant Store
    participant Lock
    participant AW as atomicWrite
    participant FS
    
    Store->>Lock: acquireNameLock(name)
    Lock-->>Store: release function
    
    Store->>AW: atomicWrite(mdPath, mdBytes)
    AW->>FS: writeFile(tmpPath)
    AW->>FS: rename(tmpPath, mdPath)
    
    Store->>AW: atomicWrite(embPath, embBytes)
    AW->>FS: writeFile(tmpPath)
    AW->>FS: rename(tmpPath, embPath)
    
    Store->>Lock: release()
```

## 作用域系统

存储系统支持两种作用域（Scope），实现项目级别和用户级别的记忆隔离：

```mermaid
graph LR
    subgraph "user scope"
        UD[~/.commonplace/]
    end
    subgraph "project scope"
        PD[<project>/.commonplace/]
    end
    
    UD --> UA[user/*.md]
    UD --> UE[user/*.embedding]
    PD --> PA[project/*.md]
    PD --> PE[project/*.embedding]
```

| 作用域 | 存储路径 | 用途 |
|--------|----------|------|
| `user` | `~/.commonplace/` | 跨项目适用的规则、偏好 |
| `project` | `<project>/.commonplace/` | 仅当前项目有效的上下文 |

资料来源：[src/store/memory-store.ts:90-120]()

## 提及提取器

`mentions.ts` 模块负责从记忆正文中提取 `[[name]]` 格式的提及：

- 仅识别匹配 `^[a-z0-9_]+$` 的名称
- 返回唯一集合，按首次出现顺序排列
- 可通过 `COMMONPLACE_EXTRACT_MENTIONS=false` 环境变量禁用
- 在 `scan()` 和 `save()` 时自动调用
- 提取的提及会添加到内存图的 `mentions` 边

资料来源：[src/store/mentions.ts:1-50]()

## 关键设计决策

### 为什么不使用数据库

- **简单性**：文件系统是最终的持久化介质，无需额外依赖
- **可移植性**：记忆文件可独立使用任意文本编辑器查看和编辑
- **可重建**：所有派生数据（embedding）可从 `.md` 重新生成
- **版本控制**：可直接将 `.md` 文件纳入 Git 管理

### Sidecar 文件策略

`.embedding` 文件作为 sidecar 存在有以下优势：

- 与 `.md` 文件解耦，删除 `.md` 时自动清理
- 嵌入模型升级时可批量重新生成
- 避免了数据库迁移的复杂性

### 图字段不参与 SHA

图关系（`relations`、`supersedes`）不参与 `contentSha` 计算是刻意的设计选择：

> Adding or removing graph edges does not invalidate the embedding.

这允许在不重新嵌入的情况下快速调整关系网络。

资料来源：[src/store/memory.ts:20-30]()

## 错误处理

| 错误场景 | 处理方式 |
|----------|----------|
| 目标文件已存在 | 抛出错误，提示先删除 |
| 源记忆不存在 | 抛出错误，阻止操作 |
| 目标记忆不存在 | 抛出错误，阻止操作 |
| 自环引用 | 抛出错误，禁止 |
| 重复边 | 抛出错误，需先解绑 |
| 磁盘读取失败 | 传播到调用方 |
| 磁盘写入失败 | 传播到调用方 |

## 相关子模块

| 模块 | 职责 |
|------|------|
| `memory.ts` | Memory 序列化/反序列化、Content SHA 计算 |
| `mentions.ts` | `[[name]]` 提及提取 |
| `graph.ts` | 内存图数据结构 |
| `atomic-write.ts` | 原子文件写入 |

---

<a id='page-scopes'></a>

## 作用域与多存储

### 相关页面

相关主题：[存储系统](#page-storage)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/rickbassham/commonplace/blob/main/README.md)
- [src/store/memory-store.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory-store.ts)
- [src/store/memory.ts](https://github.com/rickbassham/commonplace/blob/main/src/store/memory.ts)
- [src/cli/migrate.ts](https://github.com/rickbassham/commonplace/blob/main/src/cli/migrate.ts)
- [src/index.ts](https://github.com/rickbassham/commonplace/blob/main/src/index.ts)
- [CLAUDE.md](https://github.com/rickbassham/commonplace/blob/main/CLAUDE.md)
</details>

# 作用域与多存储

## 概述

Commonplace 采用**双存储架构**，支持用户级别（user）和项目级别（project）两套独立的记忆存储。这种设计允许用户在不同的项目中维护各自的上下文，同时在全局层面保留跨项目的个人规则和偏好。

**核心价值：**

- **持久化反馈**：跨项目积累的纠正和学习经验不会因项目切换而丢失
- **项目隔离**：每个项目可以拥有独立的架构笔记和约定规则
- **作用域感知**：工具调用可精确指定读写目标存储

资料来源：[README.md:1-20]()

## 架构设计

### 双存储模型

```mermaid
graph TD
    subgraph 用户存储 ["用户存储 (User Store)"]
        U[~/.commonplace/memory]
        U1[用户类型记忆]
        U2[反馈类型记忆]
    end
    
    subgraph 项目存储 ["项目存储 (Project Store)"]
        P[.commonplace/memory]
        P1[项目类型记忆]
        P2[引用类型记忆]
    end
    
    subgraph MCP工具 ["MCP 工具层"]
        SAVE[memory_save]
        SEARCH[memory_search]
        LIST[memory_list]
        DELETE[memory_delete]
    end
    
    SAVE -->|scope: user| U
    SAVE -->|scope: project| P
    SEARCH -->|跨存储合并| RESULT[按评分排序结果]
    
    style 用户存储 fill:#e1f5fe
    style 项目存储 fill:#fff3e0
```

### 记忆类型与存储映射

| 记忆类型 | 建议存储位置 | 典型用途 |
|---------|------------|---------|
| `user` | 用户存储 | 个人规则、偏好、身份信息 |
| `feedback` | 用户存储 | 来自 Agent 的纠正和经验教训 |
| `project` | 项目存储 | 架构笔记、仓库约定、特定决策 |
| `reference` | 项目存储 | API 形状、公式、引用性知识 |

资料来源：[README.md:60-75]()

## 作用域检测优先级

项目存储的检测按照以下优先级顺序进行，从高到低：

```mermaid
graph LR
    A["1. COMMONPLACE_PROJECT_DIR 环境变量"] -->|最高优先级| Z[显式覆盖]
    B["2. MCP roots/list 协议"] -->|次高| P1[项目根目录]
    C["3. 当前工作目录 (cwd)"] -->|兜底| P2[自动检测]
    
    style A fill:#ffcdd2
    style Z fill:#c8e6c9
```

### 优先级详情

1. **环境变量 `COMMONPLACE_PROJECT_DIR`**：显式覆盖，最高优先级。路径无需预先存在，Commonplace 会自动创建所需的目录结构。
2. **MCP `roots/list` 协议**：通过 MCP 协议发现的项目根目录。
3. **当前工作目录**：自动检测，将 `<cwd>/.commonplace/memory` 作为项目存储路径。

资料来源：[README.md:140-155]()

## 存储目录结构

### 用户存储

默认路径：`~/.commonplace/memory`（可通过 `COMMONPLACE_USER_DIR` 覆盖）

```
~/.commonplace/memory/
├── feedback_learned_patterns.md
├── feedback_avoid_shrink_scope.md
├── user_preferences.md
└── .embeddings/
    ├── feedback_learned_patterns.embedding
    └── ...
```

### 项目存储

项目存储位置取决于检测优先级：

```
# 方式1: 环境变量
${COMMONPLACE_PROJECT_DIR}/memory/

# 方式2: 项目根目录
<project-root>/.commonplace/memory/
```

项目存储只在该项目被 Commonplace 加载时才会被初始化和扫描。

资料来源：[CLAUDE.md:1-30]()

## 工具接口中的作用域参数

所有四个核心工具都接受可选的 `scope` 参数：

```typescript
interface ToolArguments {
  name?: string;
  type?: 'user' | 'feedback' | 'project' | 'reference';
  scope?: 'user' | 'project';  // 作用域参数
  query?: string;
  // ... 其他参数
}
```

### 作用域行为

| 操作 | `scope` 省略 | `scope: 'user'` | `scope: 'project'` |
|-----|-------------|-----------------|-------------------|
| **读取** | 合并两存储，按评分排序 | 仅用户存储 | 仅项目存储 |
| **写入** | 默认写入用户存储 | 写入用户存储 | 写入项目存储 |

```mermaid
graph TD
    subgraph 读取流程
        R1{scope 参数?}
        R2[合并两存储结果]
        R3[仅用户存储]
        R4[仅项目存储]
        
        R1 -->|省略| R2
        R1 -->|user| R3
        R1 -->|project| R4
    end
    
    subgraph 写入流程
        W1{scope 参数?}
        W2[写入用户存储]
        W3[写入项目存储]
        
        W1 -->|省略/空| W2
        W1 -->|project| W3
    end
```

资料来源：[README.md:80-95]()

## 搜索结果合并

当 `scope` 参数省略时，搜索会跨两个存储执行，并通过向量相似度评分合并结果：

```typescript
interface MemorySearchMatch {
  name: string;
  type: MemoryType;
  description: string;
  body: string;
  score: number;      // 余弦相似度，保留3位小数
  scope: 'user' | 'project';  // 结果来源标识
}
```

**合并规则：**

- 按 `score` 降序排列
- 每个匹配结果携带 `scope` 标签，标识其来源存储
- 项目存储的结果可能在某些查询中排名更高

资料来源：[src/server/handlers.ts:1-50]()

## 迁移命令与作用域

`commonplace migrate` 命令支持跨作用域的记忆迁移：

```bash
# 扫描用户存储（默认）
commonplace migrate

# 扫描指定目录
commonplace migrate ~/.commonplace/memory

# 预演模式（不实际写入）
commonplace migrate ~/.commonplace/memory --dry-run
```

### 迁移操作类型

| 操作 | 说明 |
|-----|------|
| **嵌入** | 为缺少 `.embedding` 的 `.md` 文件生成向量 |
| **重新嵌入** | 重新生成过时的（内容哈希/模型不匹配）或损坏的 sidecar |
| **清理孤立文件** | 删除没有对应 `.md` 的孤立 `.embedding` 文件 |
| **修剪悬空引用** | 移除指向不存在记忆的关系和 supersedes 条目 |

资料来源：[src/cli/migrate.ts:1-80]()

## 环境变量配置

| 变量名 | 默认值 | 说明 |
|-------|--------|------|
| `COMMONPLACE_USER_DIR` | `~/.commonplace/memory` | 用户存储根目录 |
| `COMMONPLACE_PROJECT_DIR` | 未设置 | 项目存储根目录（显式覆盖） |
| `COMMONPLACE_EXTRACT_MENTIONS` | `'true'` | 启用 `[[name]]` 提及提取 |

### 配置示例

```bash
# 使用自定义用户存储位置
export COMMONPLACE_USER_DIR=/data/commonplace/memory

# 指定项目存储位置（优先级最高）
export COMMONPLACE_PROJECT_DIR=/workspace/my-project/.commonplace/memory
```

资料来源：[src/index.ts:1-30]()

## 开发指南：记忆存储实践

在开发 Commonplace 项目本身时，应遵循以下规则：

```mermaid
graph TD
    subgraph 记忆保存决策
        D1{记忆范围}
        D2[scope: user] --> U1[跨项目规则、偏好]
        D3[scope: project] --> P1[架构笔记、仓库约定]
        
        D1 -->|跨项目教训| D2
        D1 -->|特定代码库| D3
    end
    
    subgraph 禁止事项
        B1[禁止写入 harness 内置路径]
        B2[禁止删除历史记忆]
    end
    
    P1 --> B1
```

**核心原则：**

- 通过 `mcp__commonplace__memory_save` 保存记忆，选择合适的 `type`
- 通过 `mcp__commonplace__memory_search` 和 `mcp__commonplace__memory_list` 召回记忆
- **禁止**向 `~/.claude/projects/<slug>/memory/` 写入（使用 Commonplace 产品自身）
- **禁止**删除已有的历史记忆

资料来源：[CLAUDE.md:20-45]()

## 记忆文件格式与作用域

每条记忆存储为独立的 Markdown 文件，带有 YAML frontmatter：

```yaml
---
name: feedback_scope          # 记忆唯一标识
description: 不要单方面缩减范围  # 简短描述
type: feedback                # 记忆类型
relations:                    # 可选：关系图边
  - to: architecture_decision
    type: builds-on
supersedes:                   # 可选：标记被替代的记忆
  - old_scope_rule
---
记忆正文内容，支持 [[name]] 语法引用其他记忆。
```

**作用域隐式由文件所在目录决定**，而非 frontmatter 中的字段。

资料来源：[src/store/memory.ts:1-60]()

## 总结

作用域与多存储系统为 Commonplace 提供了灵活的记忆组织能力：

- **用户存储**（`~/.commonplace/memory`）：跨项目的个人知识和反馈
- **项目存储**（`.commonplace/memory`）：项目特定的上下文和决策
- **作用域感知 API**：精确控制记忆的读写目标
- **自动合并搜索**：跨存储检索，按相关性排序

---

---

## Doramagic 踩坑日志

项目：rickbassham/commonplace

摘要：发现 8 个潜在踩坑项，其中 0 个为 high/blocking；最高优先级：能力坑 - 能力判断依赖假设。

## 1. 能力坑 · 能力判断依赖假设

- 严重度：medium
- 证据强度：source_linked
- 发现：README/documentation is current enough for a first validation pass.
- 对用户的影响：假设不成立时，用户拿不到承诺的能力。
- 建议检查：将假设转成下游验证清单。
- 防护动作：假设必须转成验证项；没有验证结果前不能写成事实。
- 证据：capability.assumptions | github_repo:1232879661 | https://github.com/rickbassham/commonplace | README/documentation is current enough for a first validation pass.

## 2. 运行坑 · 运行可能依赖外部服务

- 严重度：medium
- 证据强度：source_linked
- 发现：项目说明出现 external service/cloud/webhook/database 等运行依赖关键词。
- 对用户的影响：本地安装成功不等于能力可用，外部服务不可用会阻断体验。
- 建议检查：确认是否有离线 demo、mock 数据或可替代服务。
- 防护动作：外部服务依赖未明确时，不把本地安装成功等同于能力可用。
- 证据：packet_text.keyword_scan | github_repo:1232879661 | https://github.com/rickbassham/commonplace | matched external service / cloud / webhook / database keyword

## 3. 维护坑 · 维护活跃度未知

- 严重度：medium
- 证据强度：source_linked
- 发现：未记录 last_activity_observed。
- 对用户的影响：新项目、停更项目和活跃项目会被混在一起，推荐信任度下降。
- 建议检查：补 GitHub 最近 commit、release、issue/PR 响应信号。
- 防护动作：维护活跃度未知时，推荐强度不能标为高信任。
- 证据：evidence.maintainer_signals | github_repo:1232879661 | https://github.com/rickbassham/commonplace | last_activity_observed missing

## 4. 安全/权限坑 · 下游验证发现风险项

- 严重度：medium
- 证据强度：source_linked
- 发现：no_demo
- 对用户的影响：下游已经要求复核，不能在页面中弱化。
- 建议检查：进入安全/权限治理复核队列。
- 防护动作：下游风险存在时必须保持 review/recommendation 降级。
- 证据：downstream_validation.risk_items | github_repo:1232879661 | https://github.com/rickbassham/commonplace | no_demo; severity=medium

## 5. 安全/权限坑 · 存在安全注意事项

- 严重度：medium
- 证据强度：source_linked
- 发现：No sandbox install has been executed yet; downstream must verify before user use.
- 对用户的影响：用户安装前需要知道权限边界和敏感操作。
- 建议检查：转成明确权限清单和安全审查提示。
- 防护动作：安全注意事项必须面向用户前置展示。
- 证据：risks.safety_notes | github_repo:1232879661 | https://github.com/rickbassham/commonplace | No sandbox install has been executed yet; downstream must verify before user use.

## 6. 安全/权限坑 · 存在评分风险

- 严重度：medium
- 证据强度：source_linked
- 发现：no_demo
- 对用户的影响：风险会影响是否适合普通用户安装。
- 建议检查：把风险写入边界卡，并确认是否需要人工复核。
- 防护动作：评分风险必须进入边界卡，不能只作为内部分数。
- 证据：risks.scoring_risks | github_repo:1232879661 | https://github.com/rickbassham/commonplace | no_demo; severity=medium

## 7. 维护坑 · issue/PR 响应质量未知

- 严重度：low
- 证据强度：source_linked
- 发现：issue_or_pr_quality=unknown。
- 对用户的影响：用户无法判断遇到问题后是否有人维护。
- 建议检查：抽样最近 issue/PR，判断是否长期无人处理。
- 防护动作：issue/PR 响应未知时，必须提示维护风险。
- 证据：evidence.maintainer_signals | github_repo:1232879661 | https://github.com/rickbassham/commonplace | issue_or_pr_quality=unknown

## 8. 维护坑 · 发布节奏不明确

- 严重度：low
- 证据强度：source_linked
- 发现：release_recency=unknown。
- 对用户的影响：安装命令和文档可能落后于代码，用户踩坑概率升高。
- 建议检查：确认最近 release/tag 和 README 安装命令是否一致。
- 防护动作：发布节奏未知或过期时，安装说明必须标注可能漂移。
- 证据：evidence.maintainer_signals | github_repo:1232879661 | https://github.com/rickbassham/commonplace | release_recency=unknown

<!-- canonical_name: rickbassham/commonplace; human_manual_source: deepwiki_human_wiki -->
