= {
- {{ session.projectName || session.projectPath || '未命名项目' }}
+ {{ session.analysisTitle || session.projectName || session.projectPath || '未命名项目' }}
{{ statusLabel }}
本文档与仓库根目录 `CHANGELOG.md` 的版本一一对应,保留**实现向**表述(字段名、路径、数据结构等),供开发、排查与文档对照使用。
+> **面向最终用户的说明请以根目录 `CHANGELOG.md` 为准。**
+
+---
+
+## 0.1.3(2026-03-23)
+
+### 新增与优化
+
+- **CodeBuddy 采集与展示**
+ - 从工作区目录 `index.json` 的 `conversations[].name` 写入会话展示标题(`analysis_title`),与 IDE 会话列表一致;列表/详情在未提炼笔记前即可显示可读标题
+ - 侧栏「项目」分组名:由 `Workspace Folder` / `Workspace Path` 推导;若路径末级为纯数字或长 hex 等无辨识度目录名,则尝试使用上一级目录名
+ - 助手消息中的工具调用与思考过程统一为 `[Tool: …]`、`…`,便于对话回放与 Markdown 渲染
+- **旧版 CodeBuddy 消息**:兼容 `content` 为**单字符字符串数组**(拼接后与新版等价)、以及旧版字段名 **`Workspace Path:`**(与新版 `Workspace Folder:` 并存,优先匹配 Folder)
+- **修复**:首条用户消息中 `content` 为**整段字符串**(非 text 块数组)时,可正确解析工作区路径,避免侧栏项目名退化为 `history` 下哈希目录名
+
+### 其他
+
+- 会话列表未分析时优先展示上述采集标题;会话详情顶栏支持 `analysisTitle`;对话回放中工具标签匹配更宽松(含下划线等工具名)
+
+---
+
+## 0.1.2(2026-03-22)
+
+### 新增与优化
+
+- **采集**:支持 **CodeBuddy** 对话记录导入,扫描路径与问渠 `scanner/codebuddy.py` 一致:macOS `~/Library/Application Support/CodeBuddyExtension/Data`、Linux `~/.local/share/CodeBuddyExtension`,递归识别其下含 `index.json` 与 `messages/` 的会话目录;需在设置 → 数据源中开启「CodeBuddy」,默认关闭以免未安装时无效扫盘
+- **配置**:旧版配置文件在启动时会自动补充 CodeBuddy 数据源项(若尚无);若仍仅为旧默认 `~/.codebuddy`,启动时会迁移为上述 CodeBuddyExtension 根目录
+
+### 修正
+
+- **CodeBuddy 路径**:此前按 `~/.codebuddy/projects/*.jsonl` 采集与产品实际会话存储不符,已改为扩展数据目录(见上)
+
+---
+
+## 0.1.1(2026-03-22)
+
+### 新增与优化
+
+- **会话整理**:在对话记录侧栏可进入「会话整理」,在目录树中多选数据源、主机或项目后,一次性清理所选范围内的会话记录;清理的是应用内数据,**不会删除**本机上的原始对话文件;若一次选了多个范围,同一对话只会被删一次
+- **关于**:在设置中可查看应用版本、运行环境与本次更新说明
+- **导出**:笔记可导出为 Markdown,支持单条、多选或整库导出;整库导出不受当前列表筛选影响
+- **会话与笔记**:列表与详情可查看会话路径与会话标识;列表标题优先展示首条用户消息摘要
+- **分析**:分析失败时可看到原因;分析队列中可查看任务详情
+- **阅读**:笔记正文 Markdown 显示效果优化
+- **体验**:搜索与同步等相关提示与文案优化
+
+### 修复与调整
+
+- 侧栏点选「未关联项目」时,列表能正确显示该分组下的会话
+- 批量删除会话仅通过「会话整理」勾选分组后进行,避免误操作
+
+---
+
+## 0.1.0
+
+**发布说明**:首个正式版本,可在本机完成从采集对话、整理列表、提炼笔记到知识库浏览与搜索的完整流程。
+
+### 新增
+
+- **应用**:桌面端应用,支持浅色与深色外观
+- **采集**:支持从 Claude Code、Cursor 等来源导入对话到本地
+- **整理**:对话列表、会话详情(含笔记与对话回放)、按数据源分组的侧栏目录
+- **提炼**:对会话做价值判断并生成结构化笔记,接入本地模型或命令行工具
+- **知识库**:笔记列表、全文搜索、标签与类型筛选
+- **其他**:同步与设置、分析任务队列
+
+### 已知限制
+
+- 部分导出入口仍为占位或能力受限;个别数据源仅可配置名称与图标,尚不能采集
+- 同步需手动触发;与其他工具的深度联动仍在完善中
diff --git "a/docs/CodeBuddy \344\274\232\350\257\235\350\256\260\345\275\225\346\217\220\345\217\226\351\200\273\350\276\221.md" "b/docs/CodeBuddy \344\274\232\350\257\235\350\256\260\345\275\225\346\217\220\345\217\226\351\200\273\350\276\221.md"
new file mode 100644
index 0000000..4bedebf
--- /dev/null
+++ "b/docs/CodeBuddy \344\274\232\350\257\235\350\256\260\345\275\225\346\217\220\345\217\226\351\200\273\350\276\221.md"
@@ -0,0 +1,344 @@
+# CodeBuddy 会话记录提取逻辑
+
+> 本文档详细说明 CodeBuddy IDE 的本地会话记录文件结构,以及如何从中提取**项目名(project_name)**、**工作区路径(workspace_folder)** 等关键信息。
+> 适用于需要在非 Python 技术栈中复用此逻辑的场景。
+
+---
+
+## 1. 存储位置
+
+CodeBuddy 的会话数据存储在以下路径:
+
+| 平台 | 路径 |
+|------|------|
+| **macOS** | `~/Library/Application Support/CodeBuddyExtension/Data/{profile_id}/CodeBuddyIDE/{profile_id}/history/` |
+| **Windows** | `%APPDATA%/CodeBuddyExtension/Data/{profile_id}/CodeBuddyIDE/{profile_id}/history/` |
+| **Linux** | `~/.config/CodeBuddyExtension/Data/{profile_id}/CodeBuddyIDE/{profile_id}/history/` |
+
+其中 `{profile_id}` 是一个 UUID 格式的字符串,如 `363d8e66-2fd1-4b66-9de2-072ff116f842`。
+
+---
+
+## 2. 目录结构
+
+```
+{history_dir}/
+ └── {workspace_id}/ # 32 位 hex hash 字符串
+ ├── index.json # workspace 级索引(含会话列表)
+ └── {conversation_id}/ # 32 位 hex 字符串
+ ├── index.json # 会话级索引(含消息列表)
+ └── messages/
+ └── {msg_id}.json # 单条消息文件
+```
+
+### 各级说明
+
+| 层级 | 说明 | 示例 |
+|------|------|------|
+| `{workspace_id}/` | 一个工作区(项目),hash 标识 | `389bc7591f797f51a238cf478d48407c` |
+| `{conversation_id}/` | 一次对话会话 | `3f161540dfa14ccf948c7f46b53bdc2c` |
+| `messages/{msg_id}.json` | 对话中的单条消息 | `d50d0df94d114d9cbd6eab11f32853f2.json` |
+
+---
+
+## 3. 索引文件格式
+
+### 3.1 Workspace 级 `index.json`
+
+```json
+{
+ "version": 1,
+ "conversations": [
+ {
+ "id": "c44ebb41a3f24265a5484b3116b9a96d",
+ "name": "activity-service-php 项目分析",
+ "createdAt": 1731036781123,
+ "lastMessageAt": 1731036890456
+ }
+ ]
+}
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `conversations[].id` | string | 会话 ID,对应子目录名 |
+| `conversations[].name` | string | 会话标题(由 AI 自动生成) |
+| `conversations[].createdAt` | number | 创建时间戳(毫秒) |
+| `conversations[].lastMessageAt` | number | 最后消息时间戳(毫秒) |
+
+### 3.2 Conversation 级 `index.json`
+
+```json
+{
+ "version": 1,
+ "messages": [
+ {
+ "id": "d50d0df94d114d9cbd6eab11f32853f2",
+ "role": "user",
+ "type": "say",
+ "isComplete": true
+ },
+ {
+ "id": "a1b2c3d4e5f6...",
+ "role": "assistant",
+ "type": "say",
+ "isComplete": true
+ }
+ ]
+}
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `messages[].id` | string | 消息 ID,对应 `messages/` 下的文件名(不含 `.json`) |
+| `messages[].role` | string | `"user"` 或 `"assistant"` |
+| `messages[].type` | string | 消息类型,通常是 `"say"` |
+| `messages[].isComplete` | boolean | 消息是否完整 |
+
+---
+
+## 4. 消息文件格式(两次 JSON 解析)
+
+消息文件 `messages/{msg_id}.json` 需要**两次 JSON 解析**:
+
+### 第一层:文件本身
+
+```json
+{
+ "role": "user",
+ "message": "",
+ "id": "d50d0df94d114d9cbd6eab11f32853f2",
+ "references": [],
+ "extra": {}
+}
+```
+
+> ⚠️ `message` 字段是一个 **JSON 字符串**(string),不是对象!需要二次 `JSON.parse()`。
+
+### 第二层:`message` 字段解析后
+
+```json
+{
+ "role": "user",
+ "content": [...]
+}
+```
+
+> ⚠️ `content` 在新旧版本中格式不同!详见下一节。
+
+---
+
+## 5. Content 字段的两种格式(新版 vs 旧版)
+
+### 5.1 新版格式(约 2025-12 之后)
+
+```json
+{
+ "content": [
+ {
+ "type": "text",
+ "text": "\nOS Version: darwin\nShell: zsh\nWorkspace Folder: /Users/steve/Codes/workspace/ssv/ssv-ai/wenqu\nCurrent date: Sunday, Mar 22, 2026\n\n\n\n你好\n"
+ }
+ ]
+}
+```
+
+- `content` 是**对象数组**,每个元素有 `type` 和 `text` 字段
+- 工作区字段名为 `Workspace Folder`
+
+### 5.2 旧版格式(约 2025-11 及之前)
+
+```json
+{
+ "content": ["<", "u", "s", "e", "r", "_", "i", "n", "f", "o", ">", "\n", ...]
+}
+```
+
+- `content` 是**单字符字符串数组**,每个元素是一个字符
+- 需要 `join("")` 拼接成完整文本
+- 工作区字段名为 `Workspace Path`
+
+### 5.3 如何判断版本
+
+```
+if content[0] 是 string 且 length == 1:
+ → 旧版(单字符数组)
+else if content[0] 是 object 且有 type 字段:
+ → 新版(block 对象数组)
+```
+
+---
+
+## 6. 项目名提取逻辑
+
+### 6.1 核心原理
+
+项目名**不是**存在某个配置或元数据文件里的,而是从**每条会话的第一条 user 消息内容**中,通过正则匹配提取出来的。
+
+CodeBuddy 在每次用户发消息时,会自动在消息中注入一段 `` 标签,其中包含 `Workspace Folder`(或旧版的 `Workspace Path`)。
+
+### 6.2 提取伪代码
+
+```
+function extractWorkspaceFolder(msgFilePath):
+ // ═══ Step 1: 两次 JSON 解析 ═══
+ raw = JSON.parse(readFile(msgFilePath))
+ msgBody = JSON.parse(raw.message || "{}")
+
+ if msgBody.role != "user":
+ return null
+
+ content = msgBody.content
+
+ // ═══ Step 2: 从 content 提取文本(兼容两种格式)═══
+ text = ""
+
+ if content is array AND content.length > 0:
+ firstElement = content[0]
+
+ if typeof firstElement == "string" AND firstElement.length <= 1:
+ // 旧版:单字符数组 → 拼接
+ text = content.join("")
+
+ else if typeof firstElement == "object" AND firstElement.type == "text":
+ // 新版:block 对象数组 → 遍历取 text
+ for block in content:
+ if block.type == "text":
+ text += block.text
+
+ // ═══ Step 3: 正则提取 Workspace 路径(兼容两个字段名)═══
+ match = regex(text, /Workspace Folder:\s*(.+)/)
+ if !match:
+ match = regex(text, /Workspace Path:\s*(.+)/)
+
+ if match:
+ return match.group(1).trim()
+
+ return null
+
+
+function extractProjectName(workspaceFolder):
+ if workspaceFolder:
+ return basename(workspaceFolder) // 取路径最后一级目录名
+ return "CodeBuddy" // 默认值
+```
+
+### 6.3 遍历整个 history 的伪代码
+
+```
+function extractAllSessions(historyDir):
+ results = []
+
+ for workspaceDir in listDirs(historyDir):
+ // 读取 workspace 级 index.json(可选,含会话名称等元数据)
+ wsIndex = JSON.parse(readFile(workspaceDir + "/index.json"))
+ convMeta = {}
+ for c in wsIndex.conversations:
+ convMeta[c.id] = c // { id, name, createdAt, lastMessageAt }
+
+ for convDir in listDirs(workspaceDir):
+ // 跳过 index.json 文件
+ if convDir is not directory:
+ continue
+
+ // 读取 conversation 级 index.json
+ convIndex = JSON.parse(readFile(convDir + "/index.json"))
+ msgList = convIndex.messages
+
+ // 找到第一条 role="user" 的消息,提取 workspace folder
+ workspaceFolder = null
+ for msgMeta in msgList:
+ if msgMeta.role != "user":
+ continue
+ msgFile = convDir + "/messages/" + msgMeta.id + ".json"
+ workspaceFolder = extractWorkspaceFolder(msgFile)
+ if workspaceFolder:
+ break
+
+ projectName = extractProjectName(workspaceFolder)
+ convName = convMeta[convDir.name]?.name || ""
+
+ results.push({
+ workspaceId: workspaceDir.name,
+ conversationId: convDir.name,
+ workspaceFolder: workspaceFolder,
+ projectName: projectName,
+ convName: convName,
+ createdAt: convMeta[convDir.name]?.createdAt,
+ lastMessageAt: convMeta[convDir.name]?.lastMessageAt
+ })
+
+ return results
+```
+
+---
+
+## 7. 新旧版本差异速查表
+
+| 项目 | 旧版(~2025-11 及之前) | 新版(~2025-12 之后) |
+|------|------------------------|---------------------|
+| `content` 格式 | `["<","u","s","e","r",...]` 单字符数组 | `[{"type":"text","text":"..."}]` 对象数组 |
+| `content` 处理方式 | `content.join("")` 拼接 | 遍历取 `block.text` |
+| 工作区字段名 | `Workspace Path:` | `Workspace Folder:` |
+| 日期格式 | `2025-11-07` | `Sunday, Mar 22, 2026` |
+
+---
+
+## 8. 实际数据示例
+
+以本机实际数据为例,提取多个 workspace 的项目名:
+
+| workspace_id(前12位) | 消息中的 Workspace 路径 | 提取出的项目名 |
+|------------------------|------------------------|---------------|
+| `1694a20c0947` | `/Users/steve/资料/学习资料/KM 文章/知识库` | **知识库** |
+| `1f2b3bada274` | `/Users/steve/资料/学习资料/AI/AI编程资料包` | **AI编程资料包** |
+| `337f7f20cfdc` | `/Users/steve/CodeBuddy/AutoPublishStory` | **AutoPublishStory** |
+| `389bc7591f79` | `/Users/steve/Codes/workspace/ssv/ssv-ai/wenqu` | **wenqu** |
+| `4879786f00cd` | `/Users/steve/杂七杂八/tech4good_docs` | **tech4good_docs** |
+| `538ffc8b3206` | `/Users/steve/Codes/workspace/ssv/ssv-ai/统计/ssv_docs` | **ssv_docs** |
+
+---
+
+## 9. 注意事项
+
+1. **两次 JSON 解析**:消息文件的 `message` 字段本身是 JSON 字符串,必须解析两次。
+2. **只取第一条 user 消息**:`Workspace Folder` 通常在第一条 user 消息中注入,找到后即停止搜索。
+3. **部分会话可能没有 Workspace Folder**:例如没有打开项目目录时的会话,此时 fallback 为 `"CodeBuddy"`。
+4. **字符编码**:路径可能包含中文等 Unicode 字符,注意编码处理。
+5. **跨平台路径**:macOS/Linux 用 `/`,Windows 用 `\`,提取 basename 时需适配。
+
+---
+
+## 10. 数据流总结
+
+```
+消息文件 messages/{id}.json
+ │
+ │ 第一次 JSON.parse → raw 对象
+ │
+ ▼
+raw.message(JSON 字符串)
+ │
+ │ 第二次 JSON.parse → msgBody 对象
+ │
+ ▼
+msgBody.content(数组)
+ │
+ │ 判断 content[0] 的类型
+ │ ├── string(len=1) → 旧版,join("") 拼接
+ │ └── object → 新版,取 block.text
+ │
+ ▼
+完整文本
+ │
+ │ 正则匹配:Workspace Folder: (.+)
+ │ 或:Workspace Path: (.+)
+ │
+ ▼
+workspaceFolder = "/Users/steve/.../wenqu"
+ │
+ │ basename()
+ │
+ ▼
+projectName = "wenqu"
+```
diff --git "a/docs/PRD/\345\257\273\350\277\271\357\274\210XunJi\357\274\211\344\272\247\345\223\201\351\234\200\346\261\202\346\226\207\346\241\243 PRD.md" "b/docs/PRD/\345\257\273\350\277\271\357\274\210XunJi\357\274\211\344\272\247\345\223\201\351\234\200\346\261\202\346\226\207\346\241\243 PRD.md"
index 7b32161..2596926 100644
--- "a/docs/PRD/\345\257\273\350\277\271\357\274\210XunJi\357\274\211\344\272\247\345\223\201\351\234\200\346\261\202\346\226\207\346\241\243 PRD.md"
+++ "b/docs/PRD/\345\257\273\350\277\271\357\274\210XunJi\357\274\211\344\272\247\345\223\201\351\234\200\346\261\202\346\226\207\346\241\243 PRD.md"
@@ -59,8 +59,8 @@
|--------|--------|---------|------|---------|
| P0 | **Claude Code(内网版)** | `~/.claude-internal/projects/{路径hash}/{session-uuid}.jsonl`
全局历史:`~/.claude-internal/history.jsonl`
子代理:`sessions/{id}/subagents/agent-*.jsonl` | JSONL | ✅ 低 |
| P0 | **Cursor** | 全局:`~/Library/Application Support/Cursor/User/globalStorage/state.vscdb`(~208MB)
工作区:`workspaceStorage//state.vscdb`(约 196 个)
Plan 文件:`~/.cursor/plans/*.plan.md` | SQLite + Lexical 富文本 | ⚠️ 高 |
-| P1 | **CodeBuddy CLI** | `~/.codebuddy/projects/{项目名}/{session-uuid}.jsonl` | JSONL | ✅ 低 |
-| P1 | **CodeBuddy JetBrains 插件** | 完整会话:`~/Library/Application Support/CodeBuddyExtension/Data/{USER_ID}/CodeBuddyIDE/{USER_ID}/history/`
索引库:`~/Library/Application Support/com.tencent.copilot/copilot_index.db`
问题历史:`~/Library/Application Support/JetBrains/{IDE版本}/options/tencent-copilot-question-history.xml` | JSON + SQLite + XML | ✅ 中等 |
+| P1 | **CodeBuddy(扩展 / IDE,与问渠扫描一致)** | 根目录:macOS `~/Library/Application Support/CodeBuddyExtension/Data/`,Linux `~/.local/share/CodeBuddyExtension/`(见问渠 `scanner/codebuddy.py`)。会话落盘在目录树中名为 `history` 的路径之下:`.../history/{workspace_id}/{conversation_id}/`,含 `index.json`(消息索引)与 `messages/*.json`(单条消息)。**不作为**主会话来源:`~/.codebuddy/projects/*.jsonl`(CLI JSONL,与扩展存储不同) | JSON | ✅ 中等 |
+| P1 | **CodeBuddy 补充数据** | 索引库:`~/Library/Application Support/com.tencent.copilot/copilot_index.db`
问题历史:`~/Library/Application Support/JetBrains/{IDE版本}/options/tencent-copilot-question-history.xml` | SQLite + XML | ✅ 中等 |
| P2 | **Windsurf** | `~/Library/Application Support/Windsurf/User/workspaceStorage/` | SQLite | 中 |
| P2 | **GitHub Copilot** | VSCode workspaceStorage | SQLite | 中 |
@@ -94,7 +94,9 @@
- Plan 文件结构清晰:YAML frontmatter 含 `name`、`overview`、`todos`(含 status/dependencies)
- ❌ 无 token 统计,时间戳为毫秒时间戳
-**CodeBuddy CLI**
+**CodeBuddy CLI(独立 JSONL 流,与扩展会话目录不同)**
+
+> 寻迹当前版本对 CodeBuddy 的采集实现为 **CodeBuddyExtension** 根目录下的会话目录(`index.json` + `messages/`,见上表);下表为 CLI 侧 JSONL 行格式,供对照,**非**当前采集主路径。
```json
{
diff --git "a/docs/plans/2026-03-20-\345\257\273\350\277\271\346\225\264\344\275\223\346\236\266\346\236\204\350\256\276\350\256\241.md" "b/docs/plans/2026-03-20-\345\257\273\350\277\271\346\225\264\344\275\223\346\236\266\346\236\204\350\256\276\350\256\241.md"
index b7d8bdd..2c9db12 100644
--- "a/docs/plans/2026-03-20-\345\257\273\350\277\271\346\225\264\344\275\223\346\236\266\346\236\204\350\256\276\350\256\241.md"
+++ "b/docs/plans/2026-03-20-\345\257\273\350\277\271\346\225\264\344\275\223\346\236\266\346\236\204\350\256\276\350\256\241.md"
@@ -103,7 +103,7 @@ xunji/
│ │ │ │ ├── mod.rs
│ │ │ │ ├── claude_code.rs
│ │ │ │ ├── cursor.rs
-│ │ │ │ ├── codebuddy_cli.rs
+│ │ │ │ ├── codebuddy.rs
│ │ │ │ ├── codebuddy_jetbrains.rs
│ │ │ │ ├── watcher.rs
│ │ │ │ └── normalizer.rs
diff --git a/docs/plans/2026-03-22-v0.1.2-tasks.md b/docs/plans/2026-03-22-v0.1.2-tasks.md
index ae2db6e..55614c4 100644
--- a/docs/plans/2026-03-22-v0.1.2-tasks.md
+++ b/docs/plans/2026-03-22-v0.1.2-tasks.md
@@ -9,7 +9,7 @@
| ID | 任务 | 状态 | 说明 |
|----|------|------|------|
-| **T01** | CodeBuddy CLI:`~/.codebuddy/projects/**.jsonl` 解析,接入 `scheduler` | 已完成 | `codebuddy_cli.rs`、`scheduler.rs` |
+| **T01** | CodeBuddy:问渠路径(CodeBuddyExtension → 会话 `index.json` + `messages/`)解析,接入 `scheduler` | 已完成 | `codebuddy.rs`、`scheduler.rs` |
| **T02** | 默认配置 + 升级合并 `codebuddy-cli`(默认关闭采集) | 已完成 | `config.rs` |
| **T03** | 设置页:已接入采集器说明含 CodeBuddy CLI | 已完成 | `SettingsModal.vue` |
| **T04** | 版本号 0.1.2、CHANGELOG | 已完成 | 各 `package.json`、`Cargo.toml`、`tauri.conf.json` |
diff --git "a/docs/\346\225\260\346\215\256\345\272\223/\345\257\273\350\277\271 SQLite \346\225\260\346\215\256\346\250\241\345\236\213.md" "b/docs/\346\225\260\346\215\256\345\272\223/\345\257\273\350\277\271 SQLite \346\225\260\346\215\256\346\250\241\345\236\213.md"
new file mode 100644
index 0000000..6a66f23
--- /dev/null
+++ "b/docs/\346\225\260\346\215\256\345\272\223/\345\257\273\350\277\271 SQLite \346\225\260\346\215\256\346\250\241\345\236\213.md"
@@ -0,0 +1,227 @@
+# 寻迹(XunJi)SQLite 数据模型与表关系
+
+本文档描述桌面端(Tauri)本地库当前 Schema,依据 `apps/desktop/src-tauri/src/storage/migrations.rs` 中的迁移逻辑整理,并用 Mermaid 辅助说明实体关系。
+
+## 1. 概述
+
+| 项目 | 说明 |
+|------|------|
+| 引擎 | SQLite,连接时开启 `PRAGMA foreign_keys=ON`、`journal_mode=WAL` |
+| 默认路径 | `~/.xunji/db/xunji.db`(见应用配置) |
+| 版本 | `PRAGMA user_version`,当前迁移最高为 **v6** |
+| 权威来源 | `storage/migrations.rs`(DDL)+ `storage/models.rs`(Rust 结构体与业务语义) |
+
+**外键策略**:仅在 `messages`、`cards`、`card_tags`、`categories`(自关联)、`token_usage` 上声明 SQLite `FOREIGN KEY`。`sessions.source_id`、`sync_log.source_id`、`cards.category_id` 等在库中为**逻辑关联**,应用层负责一致性,便于数据源与分类的灵活演进。
+
+---
+
+## 2. 实体关系图(ER)
+
+下列图中标注「逻辑」的连线在数据库中**没有** `FOREIGN KEY` 约束。
+
+```mermaid
+erDiagram
+ sources {
+ TEXT id PK
+ TEXT name
+ INTEGER enabled
+ TEXT scan_paths
+ TEXT last_sync
+ TEXT config
+ }
+
+ sessions {
+ TEXT id PK
+ TEXT source_id "逻辑→sources.id"
+ TEXT session_id
+ TEXT source_host
+ TEXT project_path
+ TEXT project_name
+ INTEGER message_count
+ TEXT content_hash
+ TEXT raw_path
+ TEXT created_at
+ TEXT updated_at
+ TEXT status
+ TEXT value
+ INTEGER has_updates
+ TEXT analyzed_at
+ TEXT error_message
+ TEXT analysis_note "v3"
+ TEXT analysis_title "v4"
+ TEXT analysis_type "v4"
+ }
+
+ messages {
+ TEXT id PK
+ TEXT session_id FK
+ TEXT role
+ TEXT content
+ TEXT timestamp
+ INTEGER tokens_in
+ INTEGER tokens_out
+ INTEGER seq_order
+ }
+
+ cards {
+ TEXT id PK
+ TEXT session_id FK
+ TEXT title
+ TEXT type
+ TEXT value
+ TEXT summary
+ TEXT note
+ TEXT category_id "逻辑→categories.id"
+ TEXT memory
+ TEXT skill
+ TEXT source_name
+ TEXT project_name
+ INTEGER prompt_tokens
+ INTEGER completion_tokens
+ REAL cost_yuan
+ TEXT feedback
+ TEXT created_at
+ TEXT updated_at
+ TEXT tech_stack "v5"
+ }
+
+ tags {
+ TEXT id PK
+ TEXT name UK
+ TEXT type
+ INTEGER count
+ }
+
+ card_tags {
+ TEXT card_id PK_FK
+ TEXT tag_id PK_FK
+ }
+
+ categories {
+ TEXT id PK
+ TEXT name
+ TEXT parent_id FK
+ INTEGER sort_order
+ }
+
+ sync_log {
+ TEXT id PK
+ TEXT source_id "逻辑→sources.id"
+ TEXT started_at
+ TEXT finished_at
+ INTEGER sessions_found
+ INTEGER sessions_new
+ INTEGER sessions_updated
+ TEXT status
+ }
+
+ token_usage {
+ TEXT id PK
+ TEXT card_id FK_nullable
+ TEXT provider
+ TEXT model
+ INTEGER prompt_tokens
+ INTEGER completion_tokens
+ REAL cost_yuan
+ TEXT created_at
+ }
+
+ sources ||--o{ sessions : "source_id 逻辑"
+ sources ||--o{ sync_log : "source_id 逻辑"
+ sessions ||--o{ messages : "1:N"
+ sessions ||--o{ cards : "1:N"
+ cards }o--o{ tags : "N:M card_tags"
+ categories ||--o| categories : "parent_id 自关联"
+ cards }o--o| categories : "category_id 逻辑"
+ cards ||--o{ token_usage : "card_id 可空"
+```
+
+---
+
+## 3. 关系说明(业务视角)
+
+```text
+sources(数据源)
+ │ source_id(逻辑)
+ ├── sessions(会话,去重键:session_id + source_host UNIQUE)
+ │ ├── messages(对话消息)
+ │ └── cards(知识卡片)
+ │ ├── card_tags ── tags(标签,多对多)
+ │ └── token_usage(提炼费用,可关联卡片)
+ └── sync_log(同步任务日志)
+
+categories(分类树,自引用 parent_id)
+
+cards_fts(FTS5 虚拟表,见下节,非业务实体表)
+```
+
+- **会话去重**:同一数据源侧 `session_id` 在不同 `source_host` 下可共存;组合 `(session_id, source_host)` 唯一。
+- **卡片与标签**:标签名在 `tags.name` 上唯一;`card_tags` 为联合主键 `(card_id, tag_id)`。
+- **全文搜索**:`cards_fts` 为 FTS5 独立索引表,字段 `title, summary, note, tags`;`tags` 列内容由 `card_tags` + `tags` 拼接后写入,与 `cards` 表无 `content=` 回链(见迁移 v2 说明)。
+
+---
+
+## 4. 各表字段概要
+
+### 4.1 `sources`
+
+AI IDE 数据源配置(名称、启用、扫描路径 JSON、上次同步时间、扩展配置 JSON)。
+
+### 4.2 `sessions`
+
+一条记录对应一次采集到的 AI 对话(如一个 JSONL 文件)。含分析流水线字段:`status`、`value`、`analyzed_at`、`error_message`;v3/v4 起增加低价值会话展示用 `analysis_note`、`analysis_title`、`analysis_type`。
+
+### 4.3 `messages`
+
+会话内消息,`seq_order` 为会话内顺序;`role` 如 user / assistant / tool 等。
+
+### 4.4 `cards`
+
+由 LLM 从会话提炼的笔记;`type` / `value` 等为业务枚举字符串;`tech_stack`(v5)为逗号分隔技术栈字符串;展示用标签来自关联查询,非 `cards` 表列。
+
+### 4.5 `tags` / `card_tags`
+
+标签字典与卡片—标签多对多桥表。
+
+### 4.6 `categories`
+
+分类树,`parent_id` 指向同表(可为空表示根)。
+
+### 4.7 `sync_log`
+
+某次同步任务的统计与状态,按 `source_id` 与数据源对应(逻辑关联)。
+
+### 4.8 `token_usage`
+
+LLM 调用计费流水;`card_id` 可空,声明外键指向 `cards(id)`。
+
+### 4.9 `cards_fts`(虚拟表)
+
+FTS5,用于卡片侧栏/列表等全文检索;应用层在卡片写入、更新、删除及标签变更时维护与 `cards` 一致。
+
+---
+
+## 5. 迁移版本与 Schema 增量
+
+| 版本 | 内容摘要 |
+|------|----------|
+| v1 | 创建全部业务表、`cards_fts`、索引 |
+| v2 | 重建 `cards_fts` 为独立 FTS5,并从现有 `cards` + 标签关联回填 |
+| v3 | `sessions.analysis_note` |
+| v4 | `sessions.analysis_title`、`sessions.analysis_type` |
+| v5 | `cards.tech_stack` |
+| v6 | 数据修复:解码 `sessions` 中仍含 URL 编码的 `project_path` / `project_name` |
+
+执行顺序见 `run()`:按 `user_version` 递增直至当前最高版本。
+
+---
+
+## 6. 代码索引
+
+| 模块 | 路径 |
+|------|------|
+| 迁移 DDL | `apps/desktop/src-tauri/src/storage/migrations.rs` |
+| 模型与注释 | `apps/desktop/src-tauri/src/storage/models.rs` |
+| 连接与错误类型 | `apps/desktop/src-tauri/src/storage/db.rs` |
+
+文档随迁移变更时请同步更新本文与 `CHANGELOG`(若项目约定记录 Schema 变更)。
diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json
index 6eb8e60..8947fb1 100644
--- a/packages/mcp-server/package.json
+++ b/packages/mcp-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@xunji/mcp-server",
- "version": "0.1.2",
+ "version": "0.1.3",
"private": true,
"type": "module",
"scripts": {
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 10ee209..bad2103 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -1,6 +1,6 @@
{
"name": "@xunji/shared",
- "version": "0.1.2",
+ "version": "0.1.3",
"private": true,
"type": "module",
"main": "./src/index.ts",
diff --git a/packages/sidecar/package.json b/packages/sidecar/package.json
index dbada0a..a5eaded 100644
--- a/packages/sidecar/package.json
+++ b/packages/sidecar/package.json
@@ -1,6 +1,6 @@
{
"name": "@xunji/sidecar",
- "version": "0.1.2",
+ "version": "0.1.3",
"private": true,
"type": "module",
"scripts": {
From 2bde31482d6e3ef99b0553c0673f769b2e1e21dd Mon Sep 17 00:00:00 2001
From: stevelliu
Date: Sun, 22 Mar 2026 22:08:31 +0800
Subject: [PATCH 3/5] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=20CHANGELOG?=
=?UTF-8?q?=EF=BC=8C=E5=90=88=E5=B9=B6=20v0.1.1=20=E5=92=8C=20v0.1.2=20?=
=?UTF-8?q?=E7=9A=84=E5=8F=98=E6=9B=B4=E8=AE=B0=E5=BD=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将版本号更新至 0.1.2,新增 CodeBuddy 数据源支持及配置迁移功能
- 优化会话分组和对话回放体验
- 修正 CodeBuddy 存储路径对齐问题,确保兼容旧版数据
- 整理并更新文档,确保用户了解新功能和修复内容
---
CHANGELOG.md | 35 ++--
apps/desktop/src/data/app-changelog.md | 35 ++--
docs/CHANGELOG.md | 21 +-
...60\346\215\256\346\250\241\345\236\213.md" | 190 ++++++++++--------
4 files changed, 147 insertions(+), 134 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9290a7..76ca9dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,12 @@
> 面向使用者的更新说明。实现细节见 `docs/CHANGELOG.md`。
-## 0.1.3(2026-03-23)
+## 0.1.2(2026-03-22 – 2026-03-23)
+
+### 新增
+
+- **CodeBuddy**:支持从 CodeBuddy 导入对话记录,与本机扩展数据位置一致(Mac / Linux 常见路径见设置说明)。需在 **设置 → 数据源** 中打开「CodeBuddy」;默认关闭,避免未安装时白白扫盘。
+- **配置迁移**:若你用过旧版配置,应用启动时会**自动补全** CodeBuddy 相关选项,并把过时的扫描路径迁到当前扩展目录。
### 体验改进
@@ -10,8 +15,9 @@
- **项目分组更直观**:按你当时在本机打开的项目文件夹来归类;若文件夹名字是一长串数字或看不懂的编码,会尽量用**上一层文件夹的名字**显示,减少整屏「乱码」分组。
- **对话回放更清晰**:回看 CodeBuddy 对话时,AI **调用工具**、**思考过程**的展示更整齐,阅读起来更自然。
-### 兼容与修复
+### 修正与调整
+- **CodeBuddy**:存储位置已按**实际产品**对齐;此前若按旧路径采集会对不上,现已修正。
- **老版本 CodeBuddy 存的对话**也能正确识别项目与标题,减少「只能看到内部编号」的情况。
- **修复**部分会话在侧边栏只显示一长串内部 ID、**看不到真实项目名**的问题(与 CodeBuddy 保存消息的方式差异有关)。
@@ -21,27 +27,22 @@
---
-## 0.1.1 · 0.1.2(2026-03-22)
-
-> 同日两次小版本:**0.1.1** 以会话整理、导出与体验为主;**0.1.2** 增加 CodeBuddy 数据源并修正路径。以下合并记述。
+## 0.1.1(2026-03-22)
### 新增
-- **CodeBuddy(0.1.2)**:支持从 CodeBuddy 导入对话记录,与本机扩展数据位置一致(Mac / Linux 常见路径见设置说明)。需在 **设置 → 数据源** 中打开「CodeBuddy」;默认关闭,避免未安装时白白扫盘。
-- **配置迁移(0.1.2)**:若你用过旧版配置,应用启动时会**自动补全** CodeBuddy 相关选项,并把过时的扫描路径迁到当前扩展目录。
-- **会话整理(0.1.1)**:在对话记录侧栏可进入「会话整理」,在目录树里多选数据源、本机或项目后,一次性清理**应用里**的记录;**不会删除**你电脑上的原始对话文件;多选时同一条对话只删一次。
-- **关于(0.1.1)**:在设置中可查看版本、运行环境和更新说明。
-- **导出(0.1.1)**:笔记可导出为 Markdown,支持单条、多选或整库;整库导出不受当前列表筛选影响。
-- **会话与笔记(0.1.1)**:列表与详情可查看来源路径和会话标识;列表标题优先用首条用户消息的摘要。
-- **分析(0.1.1)**:失败时会提示原因;可在分析队列里查看任务详情。
-- **阅读(0.1.1)**:笔记正文在界面上的阅读体验优化。
-- **体验(0.1.1)**:搜索、同步等提示与文案优化。
+- **会话整理**:在对话记录侧栏可进入「会话整理」,在目录树里多选数据源、本机或项目后,一次性清理**应用里**的记录;**不会删除**你电脑上的原始对话文件;多选时同一条对话只删一次。
+- **关于**:在设置中可查看版本、运行环境和更新说明。
+- **导出**:笔记可导出为 Markdown,支持单条、多选或整库;整库导出不受当前列表筛选影响。
+- **会话与笔记**:列表与详情可查看来源路径和会话标识;列表标题优先用首条用户消息的摘要。
+- **分析**:失败时会提示原因;可在分析队列里查看任务详情。
+- **阅读**:笔记正文在界面上的阅读体验优化。
+- **体验**:搜索、同步等提示与文案优化。
### 修正与调整
-- **CodeBuddy(0.1.2)**:存储位置已按**实际产品**对齐;此前若按旧路径采集会对不上,现已修正。
-- **侧栏(0.1.1)**:选择「未关联项目」时,列表能正确显示该分组下的会话。
-- **删除方式(0.1.1)**:批量删除会话需通过「会话整理」勾选分组后进行,降低误删风险。
+- **侧栏**:选择「未关联项目」时,列表能正确显示该分组下的会话。
+- **删除方式**:批量删除会话需通过「会话整理」勾选分组后进行,降低误删风险。
---
diff --git a/apps/desktop/src/data/app-changelog.md b/apps/desktop/src/data/app-changelog.md
index d9290a7..76ca9dd 100644
--- a/apps/desktop/src/data/app-changelog.md
+++ b/apps/desktop/src/data/app-changelog.md
@@ -2,7 +2,12 @@
> 面向使用者的更新说明。实现细节见 `docs/CHANGELOG.md`。
-## 0.1.3(2026-03-23)
+## 0.1.2(2026-03-22 – 2026-03-23)
+
+### 新增
+
+- **CodeBuddy**:支持从 CodeBuddy 导入对话记录,与本机扩展数据位置一致(Mac / Linux 常见路径见设置说明)。需在 **设置 → 数据源** 中打开「CodeBuddy」;默认关闭,避免未安装时白白扫盘。
+- **配置迁移**:若你用过旧版配置,应用启动时会**自动补全** CodeBuddy 相关选项,并把过时的扫描路径迁到当前扩展目录。
### 体验改进
@@ -10,8 +15,9 @@
- **项目分组更直观**:按你当时在本机打开的项目文件夹来归类;若文件夹名字是一长串数字或看不懂的编码,会尽量用**上一层文件夹的名字**显示,减少整屏「乱码」分组。
- **对话回放更清晰**:回看 CodeBuddy 对话时,AI **调用工具**、**思考过程**的展示更整齐,阅读起来更自然。
-### 兼容与修复
+### 修正与调整
+- **CodeBuddy**:存储位置已按**实际产品**对齐;此前若按旧路径采集会对不上,现已修正。
- **老版本 CodeBuddy 存的对话**也能正确识别项目与标题,减少「只能看到内部编号」的情况。
- **修复**部分会话在侧边栏只显示一长串内部 ID、**看不到真实项目名**的问题(与 CodeBuddy 保存消息的方式差异有关)。
@@ -21,27 +27,22 @@
---
-## 0.1.1 · 0.1.2(2026-03-22)
-
-> 同日两次小版本:**0.1.1** 以会话整理、导出与体验为主;**0.1.2** 增加 CodeBuddy 数据源并修正路径。以下合并记述。
+## 0.1.1(2026-03-22)
### 新增
-- **CodeBuddy(0.1.2)**:支持从 CodeBuddy 导入对话记录,与本机扩展数据位置一致(Mac / Linux 常见路径见设置说明)。需在 **设置 → 数据源** 中打开「CodeBuddy」;默认关闭,避免未安装时白白扫盘。
-- **配置迁移(0.1.2)**:若你用过旧版配置,应用启动时会**自动补全** CodeBuddy 相关选项,并把过时的扫描路径迁到当前扩展目录。
-- **会话整理(0.1.1)**:在对话记录侧栏可进入「会话整理」,在目录树里多选数据源、本机或项目后,一次性清理**应用里**的记录;**不会删除**你电脑上的原始对话文件;多选时同一条对话只删一次。
-- **关于(0.1.1)**:在设置中可查看版本、运行环境和更新说明。
-- **导出(0.1.1)**:笔记可导出为 Markdown,支持单条、多选或整库;整库导出不受当前列表筛选影响。
-- **会话与笔记(0.1.1)**:列表与详情可查看来源路径和会话标识;列表标题优先用首条用户消息的摘要。
-- **分析(0.1.1)**:失败时会提示原因;可在分析队列里查看任务详情。
-- **阅读(0.1.1)**:笔记正文在界面上的阅读体验优化。
-- **体验(0.1.1)**:搜索、同步等提示与文案优化。
+- **会话整理**:在对话记录侧栏可进入「会话整理」,在目录树里多选数据源、本机或项目后,一次性清理**应用里**的记录;**不会删除**你电脑上的原始对话文件;多选时同一条对话只删一次。
+- **关于**:在设置中可查看版本、运行环境和更新说明。
+- **导出**:笔记可导出为 Markdown,支持单条、多选或整库;整库导出不受当前列表筛选影响。
+- **会话与笔记**:列表与详情可查看来源路径和会话标识;列表标题优先用首条用户消息的摘要。
+- **分析**:失败时会提示原因;可在分析队列里查看任务详情。
+- **阅读**:笔记正文在界面上的阅读体验优化。
+- **体验**:搜索、同步等提示与文案优化。
### 修正与调整
-- **CodeBuddy(0.1.2)**:存储位置已按**实际产品**对齐;此前若按旧路径采集会对不上,现已修正。
-- **侧栏(0.1.1)**:选择「未关联项目」时,列表能正确显示该分组下的会话。
-- **删除方式(0.1.1)**:批量删除会话需通过「会话整理」勾选分组后进行,降低误删风险。
+- **侧栏**:选择「未关联项目」时,列表能正确显示该分组下的会话。
+- **删除方式**:批量删除会话需通过「会话整理」勾选分组后进行,降低误删风险。
---
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index c37cda2..1112e9d 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -5,10 +5,12 @@
---
-## 0.1.3(2026-03-23)
+## 0.1.2(2026-03-22 – 2026-03-23)
### 新增与优化
+- **采集**:支持 **CodeBuddy** 对话记录导入,扫描路径与问渠 `scanner/codebuddy.py` 一致:macOS `~/Library/Application Support/CodeBuddyExtension/Data`、Linux `~/.local/share/CodeBuddyExtension`,递归识别其下含 `index.json` 与 `messages/` 的会话目录;需在设置 → 数据源中开启「CodeBuddy」,默认关闭以免未安装时无效扫盘
+- **配置**:旧版配置文件在启动时会自动补充 CodeBuddy 数据源项(若尚无);若仍仅为旧默认 `~/.codebuddy`,启动时会迁移为上述 CodeBuddyExtension 根目录
- **CodeBuddy 采集与展示**
- 从工作区目录 `index.json` 的 `conversations[].name` 写入会话展示标题(`analysis_title`),与 IDE 会话列表一致;列表/详情在未提炼笔记前即可显示可读标题
- 侧栏「项目」分组名:由 `Workspace Folder` / `Workspace Path` 推导;若路径末级为纯数字或长 hex 等无辨识度目录名,则尝试使用上一级目录名
@@ -16,23 +18,14 @@
- **旧版 CodeBuddy 消息**:兼容 `content` 为**单字符字符串数组**(拼接后与新版等价)、以及旧版字段名 **`Workspace Path:`**(与新版 `Workspace Folder:` 并存,优先匹配 Folder)
- **修复**:首条用户消息中 `content` 为**整段字符串**(非 text 块数组)时,可正确解析工作区路径,避免侧栏项目名退化为 `history` 下哈希目录名
-### 其他
-
-- 会话列表未分析时优先展示上述采集标题;会话详情顶栏支持 `analysisTitle`;对话回放中工具标签匹配更宽松(含下划线等工具名)
-
----
-
-## 0.1.2(2026-03-22)
-
-### 新增与优化
-
-- **采集**:支持 **CodeBuddy** 对话记录导入,扫描路径与问渠 `scanner/codebuddy.py` 一致:macOS `~/Library/Application Support/CodeBuddyExtension/Data`、Linux `~/.local/share/CodeBuddyExtension`,递归识别其下含 `index.json` 与 `messages/` 的会话目录;需在设置 → 数据源中开启「CodeBuddy」,默认关闭以免未安装时无效扫盘
-- **配置**:旧版配置文件在启动时会自动补充 CodeBuddy 数据源项(若尚无);若仍仅为旧默认 `~/.codebuddy`,启动时会迁移为上述 CodeBuddyExtension 根目录
-
### 修正
- **CodeBuddy 路径**:此前按 `~/.codebuddy/projects/*.jsonl` 采集与产品实际会话存储不符,已改为扩展数据目录(见上)
+### 其他
+
+- 会话列表未分析时优先展示上述采集标题;会话详情顶栏支持 `analysisTitle`;对话回放中工具标签匹配更宽松(含下划线等工具名)
+
---
## 0.1.1(2026-03-22)
diff --git "a/docs/\346\225\260\346\215\256\345\272\223/\345\257\273\350\277\271 SQLite \346\225\260\346\215\256\346\250\241\345\236\213.md" "b/docs/\346\225\260\346\215\256\345\272\223/\345\257\273\350\277\271 SQLite \346\225\260\346\215\256\346\250\241\345\236\213.md"
index 6a66f23..e4b3e2b 100644
--- "a/docs/\346\225\260\346\215\256\345\272\223/\345\257\273\350\277\271 SQLite \346\225\260\346\215\256\346\250\241\345\236\213.md"
+++ "b/docs/\346\225\260\346\215\256\345\272\223/\345\257\273\350\277\271 SQLite \346\225\260\346\215\256\346\250\241\345\236\213.md"
@@ -19,123 +19,141 @@
下列图中标注「逻辑」的连线在数据库中**没有** `FOREIGN KEY` 约束。
+**图例**:字段类型在图中使用 Mermaid 支持的写法(`string` / `int` / `float`),与 SQLite 的 `TEXT` / `INTEGER` / `REAL` 一一对应;字段行尾双引号内为「中文说明 + 英文字段名」。
+**说明**:部分预览器对 `erDiagram` 的 `实体["别名"] { ... }` 或 SQL 风格类型名解析不兼容,会导致实体框只显示 “(no attributes)”。下图使用**纯表名 + 标准类型**,以保证属性能渲染;中文表名见下表。
+
+| 表名 `identifier` | 中文含义 |
+|---------------------|----------|
+| `sources` | 数据源 |
+| `sessions` | 会话 |
+| `messages` | 对话消息 |
+| `cards` | 知识卡片 |
+| `tags` | 标签 |
+| `card_tags` | 卡片与标签关联(多对多) |
+| `categories` | 分类 |
+| `sync_log` | 同步日志 |
+| `token_usage` | Token 用量 / 计费流水 |
+
```mermaid
erDiagram
sources {
- TEXT id PK
- TEXT name
- INTEGER enabled
- TEXT scan_paths
- TEXT last_sync
- TEXT config
+ string id PK "主键 id"
+ string name "显示名称 name"
+ int enabled "是否启用 enabled"
+ string scan_paths "扫描目录列表 JSON scan_paths"
+ string last_sync "上次同步时间 last_sync"
+ string config "扩展配置 JSON config"
}
sessions {
- TEXT id PK
- TEXT source_id "逻辑→sources.id"
- TEXT session_id
- TEXT source_host
- TEXT project_path
- TEXT project_name
- INTEGER message_count
- TEXT content_hash
- TEXT raw_path
- TEXT created_at
- TEXT updated_at
- TEXT status
- TEXT value
- INTEGER has_updates
- TEXT analyzed_at
- TEXT error_message
- TEXT analysis_note "v3"
- TEXT analysis_title "v4"
- TEXT analysis_type "v4"
+ string id PK "主键 id(应用内 UUID)"
+ string source_id "数据源 id source_id, 逻辑关联 sources.id"
+ string session_id "数据源侧会话标识 session_id"
+ string source_host "来源主机 source_host, 去重维度之一"
+ string project_path "项目路径 project_path"
+ string project_name "项目名称 project_name"
+ int message_count "消息条数 message_count"
+ string content_hash "内容摘要哈希 content_hash"
+ string raw_path "原始文件路径 raw_path"
+ string created_at "创建时间 created_at"
+ string updated_at "更新时间 updated_at"
+ string status "分析状态 status"
+ string value "价值等级 value"
+ int has_updates "是否有新消息 has_updates"
+ string analyzed_at "分析完成时间 analyzed_at"
+ string error_message "分析错误信息 error_message"
+ string analysis_note "低价值说明 analysis_note (v3)"
+ string analysis_title "列表展示标题 analysis_title (v4)"
+ string analysis_type "分析类型徽章 analysis_type (v4)"
}
messages {
- TEXT id PK
- TEXT session_id FK
- TEXT role
- TEXT content
- TEXT timestamp
- INTEGER tokens_in
- INTEGER tokens_out
- INTEGER seq_order
+ string id PK "主键 id"
+ string session_id "所属会话 id session_id, FK sessions.id"
+ string role "角色 role"
+ string content "正文 content"
+ string timestamp "时间戳 timestamp"
+ int tokens_in "输入 token tokens_in"
+ int tokens_out "输出 token tokens_out"
+ int seq_order "会话内顺序 seq_order"
}
cards {
- TEXT id PK
- TEXT session_id FK
- TEXT title
- TEXT type
- TEXT value
- TEXT summary
- TEXT note
- TEXT category_id "逻辑→categories.id"
- TEXT memory
- TEXT skill
- TEXT source_name
- TEXT project_name
- INTEGER prompt_tokens
- INTEGER completion_tokens
- REAL cost_yuan
- TEXT feedback
- TEXT created_at
- TEXT updated_at
- TEXT tech_stack "v5"
+ string id PK "主键 id"
+ string session_id "来源会话 id session_id, FK sessions.id"
+ string title "标题 title"
+ string type "知识类型 type"
+ string value "价值等级 value"
+ string summary "一句话摘要 summary"
+ string note "笔记正文 Markdown note"
+ string category_id "分类 id category_id, 逻辑 categories.id"
+ string memory "Memory 规则 memory"
+ string skill "Skill 定义 skill"
+ string source_name "数据源名称冗余 source_name"
+ string project_name "项目名称冗余 project_name"
+ int prompt_tokens "提炼提示 token prompt_tokens"
+ int completion_tokens "提炼补全 token completion_tokens"
+ float cost_yuan "费用(元) cost_yuan"
+ string feedback "用户反馈 feedback"
+ string created_at "创建时间 created_at"
+ string updated_at "更新时间 updated_at"
+ string tech_stack "技术栈逗号串 tech_stack (v5)"
}
tags {
- TEXT id PK
- TEXT name UK
- TEXT type
- INTEGER count
+ string id PK "主键 id"
+ string name "标签名 name, 唯一"
+ string type "标签来源 type"
+ int count "引用计数 count"
}
card_tags {
- TEXT card_id PK_FK
- TEXT tag_id PK_FK
+ string card_id "卡片 id card_id, FK cards.id"
+ string tag_id "标签 id tag_id, FK tags.id"
}
categories {
- TEXT id PK
- TEXT name
- TEXT parent_id FK
- INTEGER sort_order
+ string id PK "主键 id"
+ string name "分类名称 name"
+ string parent_id "父分类 id parent_id, FK categories.id"
+ int sort_order "排序 sort_order"
}
sync_log {
- TEXT id PK
- TEXT source_id "逻辑→sources.id"
- TEXT started_at
- TEXT finished_at
- INTEGER sessions_found
- INTEGER sessions_new
- INTEGER sessions_updated
- TEXT status
+ string id PK "主键 id"
+ string source_id "数据源 id source_id, 逻辑 sources.id"
+ string started_at "开始时间 started_at"
+ string finished_at "结束时间 finished_at"
+ int sessions_found "扫描到会话数 sessions_found"
+ int sessions_new "新导入会话数 sessions_new"
+ int sessions_updated "检测到更新数 sessions_updated"
+ string status "任务状态 status"
}
token_usage {
- TEXT id PK
- TEXT card_id FK_nullable
- TEXT provider
- TEXT model
- INTEGER prompt_tokens
- INTEGER completion_tokens
- REAL cost_yuan
- TEXT created_at
+ string id PK "主键 id"
+ string card_id "关联卡片 id card_id, 可空, FK cards.id"
+ string provider "模型提供商 provider"
+ string model "模型名 model"
+ int prompt_tokens "提示 token prompt_tokens"
+ int completion_tokens "补全 token completion_tokens"
+ float cost_yuan "费用(元) cost_yuan"
+ string created_at "记录时间 created_at"
}
- sources ||--o{ sessions : "source_id 逻辑"
- sources ||--o{ sync_log : "source_id 逻辑"
- sessions ||--o{ messages : "1:N"
- sessions ||--o{ cards : "1:N"
- cards }o--o{ tags : "N:M card_tags"
- categories ||--o| categories : "parent_id 自关联"
- cards }o--o| categories : "category_id 逻辑"
- cards ||--o{ token_usage : "card_id 可空"
+ sources ||--o{ sessions : "按数据源归类(逻辑关联 source_id)"
+ sources ||--o{ sync_log : "同步任务归属(逻辑关联 source_id)"
+ sessions ||--o{ messages : "会话包含多条消息(一对多)"
+ sessions ||--o{ cards : "会话可有多张卡片(一对多)"
+ cards ||--o{ card_tags : "卡片到关联表"
+ card_tags }o--|| tags : "关联表到标签"
+ categories ||--o| categories : "父子分类(自关联 parent_id)"
+ cards }o--o| categories : "卡片归属分类(逻辑 category_id)"
+ cards ||--o{ token_usage : "提炼计费流水(card_id 可空)"
```
+**字段名说明**:`tags` 表中业务列在 SQLite 里名为 `type`;Mermaid 属性名若使用 `type` 易与语法关键字冲突,图中写作 `tag_type`,并在注释中标明「列名 type」。
+
---
## 3. 关系说明(业务视角)
From a8cc87374f028d33e8a52def1421b056b969e40b Mon Sep 17 00:00:00 2001
From: stevelliu
Date: Sun, 22 Mar 2026 22:14:18 +0800
Subject: [PATCH 4/5] =?UTF-8?q?ci:=20=E6=B7=BB=E5=8A=A0=20GitHub=20Actions?=
=?UTF-8?q?=EF=BC=88PR/master=20=E6=9E=84=E5=BB=BA=E6=A3=80=E6=9F=A5?=
=?UTF-8?q?=E3=80=81tag=20=E8=87=AA=E5=8A=A8=20Release=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Made-with: Cursor
---
.github/workflows/ci.yml | 60 +++++++++++++++++++++++++++++++++++
.github/workflows/release.yml | 23 ++++++++++++++
2 files changed, 83 insertions(+)
create mode 100644 .github/workflows/ci.yml
create mode 100644 .github/workflows/release.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..0f2662d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,60 @@
+# 在 PR / master 推送时做依赖安装、前端与 Rust 侧编译检查,避免明显破坏合并进主分支。
+name: CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ # 保证 CHANGELOG 与关于页数据源一致(脚本见 scripts/sync-changelog.mjs)
+ - name: Verify changelog sync
+ run: |
+ bun run sync:changelog
+ git diff --exit-code apps/desktop/src/data/app-changelog.md
+
+ - name: Build sidecar
+ run: cd packages/sidecar && bun run build
+
+ - name: Build desktop frontend
+ run: cd apps/desktop && bun run build
+
+ # Tauri 在 Linux 上链接需系统库,否则 cargo check 会失败
+ - name: Install Linux build dependencies (Tauri)
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libwebkit2gtk-4.1-dev \
+ build-essential \
+ curl \
+ libssl-dev \
+ libgtk-3-dev \
+ libayatana-appindicator3-dev \
+ librsvg2-dev \
+ patchelf
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cargo check (desktop)
+ run: cd apps/desktop/src-tauri && cargo check --all-targets
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..689c49d
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,23 @@
+# 推送形如 v0.1.2 的 tag 时自动创建 GitHub Release(含自动生成的提交说明)。
+# 若后续需要上传各平台安装包,可在此 job 中追加 matrix 与 tauri build。
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v[0-9]+.[0-9]+.[0-9]+'
+
+permissions:
+ contents: write
+
+jobs:
+ github-release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ generate_release_notes: true
From 8dd1526444dc9d0b5f443dceabef0e598d2413b8 Mon Sep 17 00:00:00 2001
From: stevelliu
Date: Sun, 22 Mar 2026 22:21:36 +0800
Subject: [PATCH 5/5] fix(sessions): Preserve analysis_title during resync when
collector sends None
- Updated `update_session_resync_metadata` to use COALESCE for analysis_title, preventing it from being overwritten with NULL during resync.
- Added tests to ensure that existing analysis_title is retained when None is provided, and updated when a valid title is sent.
- Documented the issue and solution in a new markdown file to clarify the behavior and prevent future regressions.
---
.../desktop/src-tauri/src/storage/sessions.rs | 78 ++++++++++++++++++-
...\350\246\206\347\233\226analysis_title.md" | 38 +++++++++
2 files changed, 115 insertions(+), 1 deletion(-)
create mode 100644 "docs/\350\270\251\345\235\221/\346\241\214\351\235\242\345\272\224\347\224\250-sessions-\351\207\215\345\220\214\346\255\245\350\246\206\347\233\226analysis_title.md"
diff --git a/apps/desktop/src-tauri/src/storage/sessions.rs b/apps/desktop/src-tauri/src/storage/sessions.rs
index c758f07..71b10fb 100644
--- a/apps/desktop/src-tauri/src/storage/sessions.rs
+++ b/apps/desktop/src-tauri/src/storage/sessions.rs
@@ -480,6 +480,9 @@ impl Database {
}
/// 增量同步后刷新会话元数据(CodeBuddy 等数据源重扫时更新项目名与展示标题)
+ ///
+ /// `analysis_title`:采集端有值(如 CodeBuddy 工作区 index)时写入;为 `None` 时不覆盖库内已有列,
+ /// 避免 Claude/Cursor 等恒为 `None` 的重同步把 `update_session_analysis_meta` 写入的展示标题抹成 NULL。
pub fn update_session_resync_metadata(
&self,
id: &str,
@@ -489,7 +492,7 @@ impl Database {
analysis_title: Option<&str>,
) -> DbResult<()> {
let n = self.conn().execute(
- "UPDATE sessions SET message_count = ?1, project_path = ?2, project_name = ?3, analysis_title = ?4 WHERE id = ?5",
+ "UPDATE sessions SET message_count = ?1, project_path = ?2, project_name = ?3, analysis_title = COALESCE(?4, analysis_title) WHERE id = ?5",
params![message_count, project_path, project_name, analysis_title, id],
)?;
if n == 0 {
@@ -546,3 +549,76 @@ impl Database {
rows.collect::, _>>().map_err(DbError::from)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::Database;
+
+ /// 回归:Claude/Cursor 重同步时采集端 `analysis_title` 为 None,不得抹掉
+ /// `update_session_analysis_meta` 已写入的展示标题(见 docs/踩坑 同名文档)。
+ #[test]
+ fn resync_metadata_preserves_analysis_title_when_collector_sends_none() {
+ let dir = tempfile::tempdir().expect("tempdir");
+ let db_path = dir.path().join("t.db");
+ let db = Database::open(&db_path).expect("open db");
+
+ let id = db
+ .insert_session(
+ "cursor",
+ "sess-1",
+ "local",
+ None,
+ None,
+ 1,
+ None,
+ "/tmp/raw",
+ "2025-01-01T00:00:00Z",
+ "2025-01-01T00:00:00Z",
+ None,
+ )
+ .expect("insert");
+
+ db.update_session_analysis_meta(&id, "分析写入的标题", "low", "note")
+ .expect("analysis meta");
+
+ db.update_session_resync_metadata(&id, 3, None, None, None)
+ .expect("resync");
+
+ let s = db.get_session(&id).expect("get");
+ assert_eq!(
+ s.analysis_title.as_deref(),
+ Some("分析写入的标题"),
+ "COALESCE(NULL, analysis_title) 应保留库内标题"
+ );
+ }
+
+ /// 采集端显式提供标题时仍应更新(如 CodeBuddy index 名称)。
+ #[test]
+ fn resync_metadata_updates_analysis_title_when_collector_provides_some() {
+ let dir = tempfile::tempdir().expect("tempdir");
+ let db_path = dir.path().join("t2.db");
+ let db = Database::open(&db_path).expect("open db");
+
+ let id = db
+ .insert_session(
+ "codebuddy-cli",
+ "sess-2",
+ "local",
+ None,
+ None,
+ 1,
+ None,
+ "/tmp/raw2",
+ "2025-01-01T00:00:00Z",
+ "2025-01-01T00:00:00Z",
+ None,
+ )
+ .expect("insert");
+
+ db.update_session_resync_metadata(&id, 2, None, None, Some("来自采集的标题"))
+ .expect("resync");
+
+ let s = db.get_session(&id).expect("get");
+ assert_eq!(s.analysis_title.as_deref(), Some("来自采集的标题"));
+ }
+}
diff --git "a/docs/\350\270\251\345\235\221/\346\241\214\351\235\242\345\272\224\347\224\250-sessions-\351\207\215\345\220\214\346\255\245\350\246\206\347\233\226analysis_title.md" "b/docs/\350\270\251\345\235\221/\346\241\214\351\235\242\345\272\224\347\224\250-sessions-\351\207\215\345\220\214\346\255\245\350\246\206\347\233\226analysis_title.md"
new file mode 100644
index 0000000..9211f73
--- /dev/null
+++ "b/docs/\350\270\251\345\235\221/\346\241\214\351\235\242\345\272\224\347\224\250-sessions-\351\207\215\345\220\214\346\255\245\350\246\206\347\233\226analysis_title.md"
@@ -0,0 +1,38 @@
+# 踩坑:增量重同步把 `sessions.analysis_title` 写成 NULL
+
+## 问题/背景
+
+会话经「低/无价值」分析后,由 `update_session_analysis_meta` 写入 `analysis_title` 等字段;随后若该会话在 Claude Code / Cursor 侧有新消息并触发采集重同步,列表里标题会消失。
+
+## 现象
+
+- 分析完成后列表有展示标题,同步一次后标题变空或恢复为默认逻辑。
+- 数据库中该行的 `analysis_title` 变为 `NULL`。
+
+## 根因
+
+```text
+dedup_and_write → message_count 增加
+ → update_session_resync_metadata(..., analysis_title = session.analysis_title)
+ NormalizedSession.analysis_title 对 claude-code / cursor 恒为 None
+ → SQL: UPDATE ... SET analysis_title = ?4 绑定 NULL
+ → 覆盖掉此前 analysis 步骤写入的标题
+```
+
+原先仅调用 `update_session_message_count` 时不会碰 `analysis_title`,引入「重同步元数据」后未区分「采集未提供」与「显式清空」。
+
+## 解决方法
+
+在 `update_session_resync_metadata` 的 `UPDATE` 中使用:
+
+`analysis_title = COALESCE(?4, analysis_title)`
+
+- 采集端传入非 NULL(如 CodeBuddy 工作区 index 中的名称)时仍更新;
+- 传入 NULL 时保留库内已有 `analysis_title`。
+
+实现位置:`apps/desktop/src-tauri/src/storage/sessions.rs` 中 `update_session_resync_metadata`。
+
+## 后续如何避免
+
+- 凡「可选字段」若语义为「未提供则不改」,在 SQL 层用 `COALESCE(新值, 列)` 或拆成两条 `UPDATE`,避免把 `NULL` 绑进无条件 `SET col = ?`。
+- 回归:`cargo test resync_metadata`(`storage::sessions` 内单元测试);或手工先跑分析写入标题,再触发该会话增量同步,确认标题仍在。