diff --git a/AGENTS.md b/AGENTS.md index fa1fd92..d3e5668 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,9 +4,9 @@ > 它将本地数字资产的原始数据(代码库、笔记、Skill、工作流)编译为 AI 可决策的结构化情境,不负责思考,不负责执行,只负责感知、编码、持久化、检索。 -- **当前阶段**:阶段九 → v0.18.0 进行中(ClaudeCode 工作流深度集成) -- **当前版本**:v0.18.0-dev(Schema 34,64 MCP tools,446 tests) -- **已完成里程碑**:Registry God Object 完全拆解(10 子模块提取)+ 18 workspace crates 提取 + MCP Python SDK 1.16.0 兼容修复 + repo.rs trait 化 + flaky 测试根治(RF-2.1/2.2/2.3)+ 许可证迁移 + health 性能优化(-44%)+ index skip-embeddings + batch encoding 实验 + RF-6 清零 + 架构治理文档(ADR/不变量清单)+ Tantivy BM25 代码符号搜索(P1)+ AppContext 职责拆分 Phase 1/2(storage.rs 860→430 行)+ 架构不变量 CI(G5/T11/T12)+ Embedding 多后端(Candle/Ollama 配置切换, P3)+ EnvVersionCache 扩展(9 工具链检测, P4)+ **v0.16.0 Agent Contexts(P1/P2/P3)**:`agent_contexts`/`agent_memories`/`context_entity_links` Schema + 9 个 Session MCP tools + Context-aware Skill Runtime(`DEVBASE_ACTIVE_CONTEXT` 注入)+ **v0.16.1 Workflow-Session Binding**:`workflow_executions.context_id` + 执行自动绑定 Active Context + **v0.17.0 Embedding Externalization**:`embedding` 从 default features 移除(Candle/Ollama 降级为 opt-in `llm-backend`)+ Schema 34 向量存储 + `cosine_similarity` SQLite UDF + `devkit_session_recall` / `devkit_session_index`(60 tools)+ **v0.18.0-dev ClaudeCode Integration**:`devkit_project_brief`(Markdown 项目简报)+ `devkit_impact_analysis`(修改影响范围分析)+ `devkit_session_export` / `devkit_session_import` + `scripts/devbase-claude.ps1` 启动器(自动注入 `.claude/CLAUDE.md`)+ RFC `docs/RFC/claudecode-workflow-integration.md`(64 tools) +- **当前阶段**:阶段十 → v0.19.0 进行中(知识基础设施可靠性加固) +- **当前版本**:v0.19.0-dev(Schema 34,64 MCP tools,446 tests) +- **已完成里程碑**:Registry God Object 完全拆解(10 子模块提取)+ 18 workspace crates 提取 + MCP Python SDK 1.16.0 兼容修复 + repo.rs trait 化 + flaky 测试根治(RF-2.1/2.2/2.3)+ 许可证迁移 + health 性能优化(-44%)+ index skip-embeddings + batch encoding 实验 + RF-6 清零 + 架构治理文档(ADR/不变量清单)+ Tantivy BM25 代码符号搜索(P1)+ AppContext 职责拆分 Phase 1/2(storage.rs 860→430 行)+ 架构不变量 CI(G5/T11/T12)+ Embedding 多后端(Candle/Ollama 配置切换, P3)+ EnvVersionCache 扩展(9 工具链检测, P4)+ **v0.16.0 Agent Contexts(P1/P2/P3)**:`agent_contexts`/`agent_memories`/`context_entity_links` Schema + 9 个 Session MCP tools + Context-aware Skill Runtime(`DEVBASE_ACTIVE_CONTEXT` 注入)+ **v0.16.1 Workflow-Session Binding**:`workflow_executions.context_id` + 执行自动绑定 Active Context + **v0.17.0 Embedding Externalization**:`embedding` 从 default features 移除(Candle/Ollama 降级为 opt-in `llm-backend`)+ Schema 34 向量存储 + `cosine_similarity` SQLite UDF + `devkit_session_recall` / `devkit_session_index`(60 tools)+ **v0.18.0 ClaudeCode Integration**:`devkit_project_brief`(Markdown 项目简报)+ `devkit_impact_analysis`(修改影响范围分析)+ `devkit_session_export` / `devkit_session_import` + `scripts/devbase-claude.ps1` 启动器(自动注入 `.claude/CLAUDE.md`)+ RFC `docs/RFC/claudecode-workflow-integration.md`(64 tools)+ **v0.18.0 发布收尾**:PR 合并 + 双平台二进制构建 + GitHub Release + 根目录治理 + 世界模型战略认知沉淀(Vault + AGENTS 双向联动)+ NotebookLM 生态消化(5 项目注册)+ GreptimeDB 互补分析 - **核心方向**:让 Kimi CLI 在调用文件工具之前,先通过 devbase 获得"该读哪些文件、为什么读、它们之间的关系" - **本质分析**:见 `vault/99-Meta/devbase-essence-analysis-20260430.md` 与 `docs/architecture/redefinition.md` - **设计文档**: @@ -23,7 +23,7 @@ Skill Runtime 全生命周期已落地(含依赖管理 Schema v15),Schema - **Workspace**:`%LOCALAPPDATA%\devbase\workspace/` —— 文件系统 = source of truth - `vault/` —— PARA 结构:00-Inbox, 01-Projects, 02-Areas, 03-Resources, 04-Archives, 99-Meta - `assets/` —— 二进制资源 -- **MCP Server**:stdio only,**64 个 tools**(含 5 个 vault tools + 8 个代码分析工具 + 4 个 embedding/搜索工具 + 4 个 Skill Runtime tools + 3 个 Workflow/评分 tools + 1 个报告工具 + 1 个 arXiv 工具 + 2 个 KnownLimit tools + 3 个 Relation tools + 11 个 Agent Context tools + 2 个 ClaudeCode 集成工具 + 1 个 streaming index 工具 + 1 个 oplog 工具);配置见 `mcp.json` +- **MCP Server**:stdio only,**65 个 tools**(含 5 个 vault tools + 8 个代码分析工具 + 4 个 embedding/搜索工具 + 4 个 Skill Runtime tools + 3 个 Workflow/评分 tools + 1 个报告工具 + 1 个 arXiv 工具 + 2 个 KnownLimit tools + 3 个 Relation tools + 11 个 Agent Context tools + 2 个 ClaudeCode 集成工具 + 1 个 streaming index 工具 + 1 个 oplog 工具 + **1 个 Index Health 工具**);配置见 `mcp.json` - **Kimi CLI 集成**:MCP server 已通过 `kimi mcp add` 注册,端到端验证通过(`kimi --print` 成功调用 `devkit_health`);项目级 skill 位于 `.kimi/skills/devbase-project/SKILL.md` - **统一节点模型**:`core::node::{Node, NodeType, Edge}` —— GitRepo / VaultNote / Asset / ExternalLink - **当前测试**:446+ lib passed / 0 failed / 3 ignored + 11/11 integration passed(`tests/cli.rs`) @@ -552,6 +552,73 @@ cargo metadata --format-version 1 | jq '.workspace_members' grep -rc 'crate::' src/*.rs | sort -t: -k2 -n | tail -5 ``` +## 知识库生产级缺口与补齐路线(Knowledge Base Production Gap) + +> 该章节记录 devbase 作为知识基础设施与生产级要求之间的真实差距,以及消除"玩具感"的补齐路径。 +> **核心原则**:devbase 首先是一个可靠的本地知识基础设施,然后才是一个 World Model Compiler。AI 层是编译器的输出接口,但如果存储层不可靠,AI 就是沙上建塔。 + +### 缺口诊断(与生产级知识库对比) + +| 能力维度 | 当前现状 | 生产级要求 | 缺口等级 | +|:---|:---|:---|:---:| +| **存储可靠性** | SQLite 单文件;Schema 迁移前自动快照 | WAL 并发模式、增量备份、索引损坏自动检测重建、点对点恢复 | 🔴 **严重** | +| **检索质量** | BM25 + 768-dim `cosine_similarity` SQL UDF | Hybrid RRF 调优、Re-rank、多路召回、查询延迟可观测 | 🔴 **严重** | +| **知识图谱** | `relation_store/query` 简单三元组 | 双向链接图遍历、Transitive Closure、社区发现、本体约束 | 🟠 **显著** | +| **版本历史** | 代码有 Git;Vault 笔记无版本 | 笔记块级历史、分支、冲突合并策略 | 🟠 **显著** | +| **规模化** | 单机 Rayon;未验证 >100 仓库 / >10k 文档场景 | 索引分片、增量更新、查询缓存、内存上限保护 | 🟠 **显著** | +| **互操作性** | Vault 读写 Markdown | Obsidian 兼容(frontmatter/wikilink)、标准导入导出、避免 Vendor Lock-in | 🟡 **中等** | +| **多模态** | 文本为主 | PDF 解析、图片 OCR、音频转录 | 🟡 **可延期** | +| **协作** | 单用户 + Syncthing 文件级同步 | 冲突解决(CRDT/OT 或至少 last-write-win)、多设备状态一致性 | 🟠 **显著** | + +### 补齐路线图 + +#### 🔴 v0.19.0:存储可靠性加固(消除"玩具感"的最快路径) + +| 任务 | 优先级 | 验收标准 | +|:---|:---:|:---| +| SQLite WAL 模式默认启用 | P1 | 并发写入无锁定冲突;`PRAGMA journal_mode=WAL` 持久化 | +| Tantivy 索引健康检查 `devkit_index_health` | P1 | 检测索引损坏、版本不匹配、孤儿文档;返回健康评分 0-100 | +| 自动重建策略 | P1 | 索引损坏时自动 fallback 全量重建,而非静默失败;重建过程写入 OpLog | +| 查询性能基线测试 | P1 | CI 中测试 1k/10k/100k 文档量级的检索延迟;建立性能回归红线 | +| Vault 批量导出(Markdown + frontmatter) | P2 | `devkit_vault_export` 支持 PARA 结构完整导出;消除 Vendor Lock-in 焦虑 | +| Redis 缓存评估 | P2 | 完成 Session/向量缓存需求分析;决策:引入 / 自建 / 放弃 | + +#### 🟠 v0.20.0:知识完备性(从"能存"到"好用") + +| 任务 | 优先级 | 验收标准 | +|:---|:---:|:---| +| Vault 双向链接图遍历 | P1 | `vault_backlinks` 升级为图查询:最短路径、共同引用、引用频次 | +| 笔记变更追踪 | P1 | Vault 笔记历史基于 Git 追踪(vault 目录作为 Git 子模块)或 SQLite 增量历史表 | +| 混合检索质量监控 | P1 | RRF 参数可调(`k`、`weights`)、召回率/精确率指标、`devkit_search_quality` 工具 | +| 笔记块级引用 `[[note#block]]` | P2 | 从文档级粒度下沉到块级;支持标题块、列表块、代码块引用 | +| middleware.ts 错误修复 | P2 | 解决已知未解决错误,见技术债登记簿 | + +#### 🟡 v0.21.0+:外部能力嫁接(不重复造轮子) + +| 任务 | 来源 | 集成方式 | +|:---|:---|:---| +| 多说话人播客/测验生成管道 | Open Notebook | 提取生成模块作为外部 MCP Tool,devbase 提供文档输入 | +| Agent 协作与多 LLM 路由 | SurfSense | 参考 Agent 架构,融入 Clarity 三角色世界模型 | +| 时序观测基础设施 | GreptimeDB | Standalone 模式起步,替代 Prometheus,监控索引和查询健康度 | +| 向量索引统一(远期) | GreptimeDB v1.1 | 评估替代 Tantivy+SQLite 双写架构的可行性 | + +### 技术债关联更新 + +| 债项 | 严重 | 状态变更 | 清理路径 | +|:---|:---:|:---|:---| +| Tantivy+SQLite 双写一致性 | 🟡 | **从长期降级至 v0.19.0 P1** | WAL + 补偿机制 + `devkit_index_health` | +| SQLite 单文件并发 | 🔴 | **新增** | v0.19.0 WAL 模式启用 | +| 查询性能不可观测 | 🔴 | **新增** | v0.19.0 CI 性能基线 + OpLog 延迟指标 | +| Vault 无版本历史 | 🟠 | **新增** | v0.20.0 Git 追踪或增量表 | + +### 决策约束 + +1. **v0.19.0 禁止新增非可靠性相关的 MCP Tool**。所有新增 Tool 必须与存储健康、可观测性、或索引修复直接相关。 +2. **v0.19.0 禁止引入外部数据库依赖**(包括 GreptimeDB、Redis、PostgreSQL)。可靠性加固必须在现有 SQLite + Tantivy 技术栈内完成。 +3. **世界模型研究(Spark/Flink/时序图神经网络)保持独立仓库**,主仓库继续执行"不得引入 Spark/Flink 依赖"红线。 + +--- + ## 架构演进方向:世界模型战略(World Model Strategy) > 该章节记录 devbase 从"静态情境编译器"向"动态世界模型"演进的战略认知。 diff --git a/README.md b/README.md index d831400..1f11e7d 100644 --- a/README.md +++ b/README.md @@ -24,24 +24,32 @@ devbase 是开发者的**世界模型编译器**。它将代码库、笔记、 | **项目维护者** | `devbase skill discover .` 一键将项目封装为 Skill,让 AI 用户能够发现和调用 | ``` -┌─────────────────────────────────────────────────────────────┐ -│ devbase │ -│ World Model Compiler for Workspaces │ -├─────────────────────────────┬───────────────────────────────┤ -│ Human Layer │ AI Layer │ -│ ┌─────────────────────┐ │ ┌─────────────────────┐ │ -│ │ TUI Dashboard │ │ │ MCP Server │ │ -│ │ 终端交互仪表盘 │ │ │ 64 Tools │ │ -│ │ • 多仓库健康总览 │ │ │ stdio + streaming │ │ -│ │ • 跨仓库代码搜索 │ │ │ │ │ -│ │ • 一键启动 gitui │ │ │ • devkit_scan │ │ -│ │ • Skill / Workflow │ │ │ • devkit_skill_run│ │ -│ │ • Vault / Session │ │ │ • devkit_hybrid_search│ │ -│ └─────────────────────┘ │ └─────────────────────┘ │ -├─────────────────────────────┴───────────────────────────────┤ -│ Data Layer │ -│ Filesystem (Source of Truth) │ SQLite │ Tantivy (Search) │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────────┐ +│ Interaction Layer (人类与 AI 的接口) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ +│ │ TUI 仪表盘 │ │ MCP Server │ │ Workflow Engine │ │ +│ │ (ratatui) │ │ 64 Tools │ │ YAML + 拓扑调度 │ │ +│ └──────────────┘ └──────────────┘ └──────────────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ Compilation Layer (World Model Compiler Core) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ +│ │ Perception │ │ Knowledge │ │ Policy / Action │ │ +│ │ · tree-sitter│ │ · Graph DB │ │ · Sync Strategy │ │ +│ │ · Tantivy │ │ · Vector UDF│ │ · Workflow Rules │ │ +│ │ · Git 状态 │ │ · Relation │ │ · Health Guardrails │ │ +│ └──────────────┘ └──────────────┘ └──────────────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ Reliability Layer (生产级底线) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ +│ │ SQLite WAL │ │ Index Health│ │ Observability │ │ +│ │ 并发安全 │ │ · 损坏检测 │ │ · OpLog 审计 │ │ +│ │ · 增量备份 │ │ · 自动重建 │ │ · 查询延迟指标 │ │ +│ │ · 迁移回滚 │ │ · 性能基线 │ │ · 数据质量评分 │ │ +│ └──────────────┘ └──────────────┘ └──────────────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ Source of Truth (持久化真相源) │ +│ Git 代码库 · Vault PARA 笔记 · 外部论文 · 二进制资源 │ +└─────────────────────────────────────────────────────────────────┘ ``` --- @@ -126,15 +134,20 @@ cd devbase && cargo install --path . > 完整 Tool 矩阵见下文 [MCP Tool 矩阵](#mcp-tool-矩阵)。 -### Data Layer — 本地优先知识库 +### Storage & Reliability Layer — 生产级本地知识基础设施 -| 组件 | 技术 | 说明 | +> **devbase 首先是一个可靠的本地知识基础设施,然后才是一个 World Model Compiler。** AI 层是编译器的输出接口,但如果存储层不可靠,AI 就是沙上建塔。 + +| 组件 | 技术 | 生产级特性 | |:---|:---|:---| -| 索引 | SQLite + Tantivy | 仓库元数据 + 全文检索 | -| 语义 | SQLite BLOB (768-dim) + UDF | 外置 Embedding 存储 + `cosine_similarity` 纯 SQL 比对 | -| Agent 记忆 | `agent_contexts` + `agent_memories` | 会话生命周期 + 语义记忆召回 + 向量索引 | -| AST | tree-sitter | Rust / Python / TS / Go 多语言符号提取 | -| 审计 | SQLite `oplog` | 所有 `scan`/`sync`/`health` 自动记录,schema 迁移前自动快照 | +| 关系存储 | SQLite (WAL mode) | 并发安全、增量备份、Schema 迁移前自动快照、回滚保障 | +| 全文检索 | Tantivy | BM25 评分、索引健康检测、损坏自动重建、孤儿文档清理 | +| 语义检索 | SQLite BLOB (768-dim) + `cosine_similarity` UDF | 外置 Embedding 存储、纯 SQL 向量比对、零 ML 运行时依赖 | +| Agent 记忆 | `agent_contexts` + `agent_memories` | 会话生命周期管理、语义记忆召回、向量索引持久化 | +| AST 感知 | tree-sitter | Rust / Python / TS / Go 多语言符号提取 + 调用图构建 | +| 可观测性 | SQLite `oplog` + 性能基线 | 全操作审计追踪、查询延迟指标、数据质量评分 | + +**可靠性红线**:所有对 Registry 的写入操作必须留下不可变审计痕迹(OpLog);Schema 迁移前自动生成 `backup-YYYYMMDD-HHMMSS.db`;索引层具备反向一致性扫描与自动修复能力。详见 [AGENTS.md](./AGENTS.md) §知识库生产级缺口与补齐路线。 --- diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index a590ca1..89abcee 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,10 +1,10 @@ # devbase Roadmap -> **当前阶段**:阶段六 — v0.16.0 分发就绪(进行中) +> **当前阶段**:阶段十 — v0.19.0 知识基础设施硬化(进行中) > -> **最后更新**:2026-04-26 +> **最后更新**:2026-05-14 > -> **版本状态**:`0.16.0-dev`(P2 Workspace crate 第二批提取 #1 完成:`devbase-workflow-interpolate`) +> **版本状态**:`0.19.0-dev`(Schema 34,64 MCP tools,446 tests) --- @@ -22,136 +22,86 @@ Schema v16 统一实体模型、Skill 自动封装、Workflow Engine、Mind Mark L0-L4 五层知识模型 MVP:entities 统一模型、known_limits 风险层、knowledge_meta 元认知层、PARA vault 结构。 -### 阶段四:工程健康与解耦(v0.12.0–v0.14.0)— ✅ 已交付 +### 阶段四:工程健康与解耦(v0.12.0–v0.14.0)— ✅ -| 任务 | 交付物 | 状态 | -|------|--------|------| -| Registry God Object 拆解 | 10 子模块提取为 free-function 模块 | ✅ v0.12.0 | -| AppContext Pool 化 | `r2d2::Pool` 替代单 Connection,22 处调用点迁移 | ✅ v0.12.0 | -| 生产 unwrap 清零 | 0 个生产 unwrap,`clippy -D warnings` 全绿 | ✅ v0.13.0 | -| 测试覆盖率收尾 | 437 workspace tests passed,零测试文件清零 | ✅ v0.13.0 | -| Workspace 骨架搭建 | `crates/` 目录 + 3 个零耦合模块提取 | ✅ v0.14.0 | -| 全模块耦合地图 | 按 `crate::` 引用数扫描,🟢/🟡/🔴 分级 | ✅ v0.14.0 | +Registry God Object 拆解、AppContext Pool 化、生产 unwrap 清零、Workspace 骨架搭建(18 crates)、全模块耦合地图。 -### 阶段五:v0.15.0 数据层 + 可靠性 + Agent 体验 — ✅ 已交付 +### 阶段五:数据层 + 可靠性 + Agent 体验(v0.15.0)— ✅ -| Sprint | 核心交付 | 状态 | -|--------|---------|------| -| Sprint A — 数据层 + 性能 | v28 三维 embedding 主键;rayon 并行化(130s→~20s) | ✅ `dfdc1cc` | -| Sprint B — 可靠性 | Tantivy-SQLite Saga 一致性扫描 + orphan 懒清理 | ✅ `dcbe256` | -| Sprint C — Agent 体验 | `devbase status` + `DevkitStatusTool` + MCP streaming | ✅ `e8860ba` | +三维 embedding 主键、Tantivy-SQLite Saga 一致性扫描、`devbase status` + MCP streaming。 ---- +### 阶段六:分发就绪与 Embedding 外迁(v0.16.0–v0.17.0)— ✅ -## 当前阶段:阶段六 — v0.16.0 分发就绪(进行中) +Workspace 扩展至 18 crates、Embedding Externalization(Candle/Ollama 降级为 opt-in)、Schema 34 向量存储 + `cosine_similarity` SQLite UDF、Agent Contexts / Session 记忆体系。 -**核心目标**:让 devbase 的通用组件达到"可独立发布"标准,同时保持主 crate 的健康度。 +### 阶段七:ClaudeCode 集成与世界模型定位(v0.18.0)— ✅ -> 分发 ≠ 必须发布到 crates.io。分发标准是耦合健康度的检验手段:能拆 = 健康,不能拆 = 有债。 +`devkit_project_brief` / `impact_analysis`、Session 导出/导入、`devbase-claude.ps1` 启动器、World Model Compiler 定位升级、根目录治理、NotebookLM 生态消化(5 项目注册)、GreptimeDB 互补分析。 --- -## 待办清单(按优先级) - -### P0 — Workspace 扩展(本周–本月) - -提取 🟢 健康模块(0-3 个 `crate::` refs)为独立 crate。 - -| 候选模块 | 行数 | 测试 | 内部耦合 | 估计工时 | -|----------|------|------|----------|----------| - -| ~~`syncthing_client`~~ | ~~85~~ | ~~2~~ | ~~0~~ | ~~✅ 已完成~~ | -| ~~`registry/health`~~ | ~~156~~ | ~~3~~ | ~~0~~ | ~~✅ 已完成~~ | -| ~~`registry/metrics`~~ | ~~153~~ | ~~4~~ | ~~0~~ | ~~✅ 已完成~~ | - -| ~~`vault/frontmatter`~~ | ~~175~~ | ~~5~~ | ~~0~~ | ~~✅ 已完成~~ | -| ~~`vault/wikilink`~~ | ~~130~~ | ~~5~~ | ~~0~~ | ~~✅ 已完成~~ | -| ~~`workflow/interpolate`~~ | ~~239~~ | ~~9~~ | ~~0~~ | ~~✅ 已完成~~ | -| ~~`workflow/model`~~ | ~~330~~ | ~~2~~ | ~~0~~ | ~~✅ 已完成~~ | -| ~~`embedding`~~ | ~~299~~ | ~~5~~ | ~~0~~ | ~~✅ 已完成~~ | - - - -| ~~`registry/workspace`~~ | ~~215~~ | ~~5~~ | ~~0~~ | ~~✅ 已完成~~ | - - - -**目标**:workspace 成员达到 8-10 个。当前:13 个(✅ 已超额达成)。 -**验收**:`cargo check --workspace` 0 errors,`cargo test --workspace` 全绿。 - ---- +## 当前阶段:阶段十 — v0.19.0 知识基础设施硬化(进行中) -### P1 — MCP trait 化(本月–下月) - -**问题**:`mcp/tools/repo.rs` 有 **70 个 `crate::` 引用**,是 devbase 最大耦合黑洞。 - -**方案**: -1. 定义 `RegistryClient` trait: - ```rust - pub trait RegistryClient { - fn list_repos(&self, conn: &rusqlite::Connection, filter: &str) -> Vec; - fn get_repo(&self, conn: &rusqlite::Connection, id: &str) -> Option; - // ... 其他 repo 相关操作 - } - ``` -2. 定义 `SearchClient` trait: - ```rust - pub trait SearchClient { - fn hybrid_search(&self, query: &str, limit: usize) -> Vec; - } - ``` -3. `mcp/tools` 从 `crate::registry::*` 改为 `trait` 调用 -4. devbase 主 crate 实现这些 trait - -**阻塞**:需要确定 trait 边界——哪些操作属于 RegistryClient,哪些属于 SearchClient。 -**验收**:`mcp/` 目录的 `crate::` 引用数从 70 降至 <10。 +**核心目标**:消除"玩具感",将 devbase 从"功能演示级"推进到"日常生产力级"。**存储可靠性 > AI 炫技**。 ---- +> **核心原则**:devbase 首先是一个可靠的本地知识基础设施,然后才是一个 World Model Compiler。详见 [AGENTS.md](../AGENTS.md) §知识库生产级缺口与补齐路线。 -### P2 — registry 子模块拆分(本月) +### v0.19.0 Sprint 规划 -`registry/health`, `registry/metrics`, `registry/workspace`, `registry/entity`, `registry/relation` 已零耦合,可直接提取为 workspace crate 或保持为子模块但消除 `crate::` 引用。 +| Sprint | 主题 | 关键交付 | 目标日期 | +|--------|------|---------|----------| +| **Sprint A — SQLite 可靠性** | WAL 模式 + 并发安全 | `PRAGMA journal_mode=WAL` 默认启用;并发写入测试覆盖;迁移回滚硬化 | 2026-05 | +| **Sprint B — 索引健康度** | Tantivy 可观测与自愈 | `devkit_index_health` tool(健康评分 0-100);损坏检测;自动重建策略 | 2026-05 | +| **Sprint C — 性能基线** | 查询延迟可观测 | CI 性能回归测试(1k/10k/100k 文档);OpLog 查询延迟指标;Redis 缓存决策文档 | 2026-06 | +| **Sprint D — 数据自由** | Vault 导出与互操作 | `devkit_vault_export` 完整 PARA 导出;frontmatter 兼容性验证;Vendor Lock-in 消除 | 2026-06 | -**决策点**:registry 子模块是否值得独立为 crate? -- 若作为独立 crate:`devbase-registry-health` 等 -- 若保持子模块:确保它们只对 `rusqlite::Connection` 有依赖 +**v0.19.0 验收标准**: +1. `cargo test` 全绿 + CI 新增性能回归红线(查询延迟 P99 < 200ms @ 10k 文档) +2. `devkit_index_health` 可返回所有注册仓库的索引健康评分 +3. SQLite WAL 模式在所有新创建/迁移的数据库上默认启用 +4. Vault 导出可通过标准 Markdown 工具链(如 Obsidian)无损重新导入 -**推荐**:暂时保持子模块结构(避免 crate 数量爆炸),但消除所有 `crate::` 引用,使它们达到"随时可提取"状态。 +**v0.19.0 约束**: +- ❌ 禁止新增非可靠性相关的 MCP Tool +- ❌ 禁止引入外部数据库依赖(GreptimeDB、Redis、PostgreSQL 仅评估,不集成) +- ✅ 世界模型研究继续独立仓库推进 --- -### P3 — migrate.rs 拆解(✅ 已完成,文档滞后) - -| 属性 | 值 | -|------|-----| -| 实际行数 | **487**(非 1273,文档已过时) | -| 耦合 | `crate::storage::StorageBackend`, `crate::backup::auto_backup_before_migration`, `crate::registry::migrations::run_all` | -| 状态 | 迁移逻辑已全部拆分至 `migrations/` 目录(29 个独立文件,1118 行) | -| 当前角色 | 入口门面:`init_db_at()` + `run_migrations()` 委托 | +## 技术债务(清偿中) -**结论**:`migrate.rs` 不再是巨石文件,无需进一步拆分。P3 关闭。 +| 债项 | 严重 | 当前值 | 目标 | 清理路径 | 版本 | +|------|------|--------|------|----------|------| +| Tantivy+SQLite 双写一致性 | 🔴 | 无事务协调,反向检测已落地 | 补偿机制 + 健康评分 | `devkit_index_health` + WAL | v0.19.0 | +| SQLite 单文件并发锁定 | 🔴 | DELETE journal_mode | WAL mode | `PRAGMA journal_mode=WAL` | v0.19.0 | +| 查询性能不可观测 | 🔴 | 无基线 | P99 < 200ms @ 10k | CI 性能回归 + OpLog 指标 | v0.19.0 | +| tree-sitter 编译成本 | 🟡 | ~15-20s | <10s | ccache 或 grammar 预编译 | v0.20.0 | +| Vault 无版本历史 | 🟠 | 无 | Git 追踪或增量表 | vault 目录 Git 子模块 | v0.20.0 | +| Feature flags 完善 | 🟡 | 4 个(tui, watch, mcp, embedding) | ≥5 | `llm-backend` feature 细分 | v0.20.0 | +| `init_db()` 全局路径 | 🟢 | 5 处 grandfathered | 0 新增 | StorageBackend trait 已奠基 | 持续 | --- -## 技术债务(清偿中) +## 版本规划 -| 债项 | 严重 | 当前值 | 目标 | 清理路径 | -|------|------|--------|------|----------| -| Tantivy+SQLite 双写一致性 | 🟡 | 无事务协调 | 补偿机制或 FTS5 替代 | 评估 `sync_index_to_db()` 两阶段提交 | -| tree-sitter 编译成本 | 🟡 | ~15-20s | <10s | ccache 或 grammar 预编译 | -| Feature flags 缺失 | 🟡 | 2/3(tui, watch) | ≥3 | 评估 mcp 是否独立 feature | -| `init_db()` 全局路径 | 🟢 | 5 处 grandfathered | 0 新增 | StorageBackend trait 已奠基,迁移中 | -| `SortMode` unused import | 🟢 | 1 warning | 0 | `cargo fix` 或手动移除 | +| 版本 | 主题 | 关键交付 | 预计时间 | +|------|------|----------|----------| +| v0.19.0 | **知识基础设施硬化** | SQLite WAL + Tantivy 健康评分 + CI 性能基线 + Vault 导出 | 2026-06 | +| v0.20.0 | **知识完备性** | 双向链接图遍历 + 笔记历史追踪 + 混合检索质量监控 + block 引用 | 2026-07 | +| v0.21.0 | **外部能力嫁接** | GreptimeDB 观测层评估 + Open Notebook 管道对接 + SurfSense Agent 参考 | 2026-08 | +| v0.22.0 | **规模化验证** | >100 仓库场景测试 + 索引分片评估 + 查询缓存 | 2026-Q3 | +| v0.25.0 | **分发发布** | 首个 crate (`devbase-mcp` 或 `devbase-core`) 发布到 crates.io | 2026-Q4 | --- -## Future / Icebox(无排期) +## Future / Icebox(无排期,但已注册参考项目) -- 跨设备注册表同步(syncthing-rust 集成,REST API 待就绪) -- 形式化验证 / TEE 集成(长期,无排期) -- Workflow 引擎细化(Loop body Retry/Fallback、TUI 执行进度条) -- 生长信号与遗忘机制(L0-L4 知识模型的自动衰减) -- `devbase-mcp` 独立发布(待 MCP trait 化完成后) +- **GreptimeDB 集成**:时序观测层、Flow Engine 流式知识加工、向量索引统一评估(待 v1.1 向量索引成熟) +- **Open Notebook 嫁接**:多说话人播客/测验生成管道作为外部 MCP Tool +- **SurfSense 参考**:Agent 协作与多 LLM 路由模式融入 Clarity 三角色 +- **跨设备注册表同步**:syncthing-rust 集成(REST API 待就绪) +- **形式化验证 / TEE 集成**:长期,无排期 +- **生长信号与遗忘机制**:L0-L4 知识模型的自动衰减 --- @@ -163,21 +113,10 @@ L0-L4 五层知识模型 MVP:entities 统一模型、known_limits 风险层、 | `.devbase` 目录规范 | 无外部采纳者 | ❌ 排除 | | MCP 协议扩展提案 | Star = 0,不会被采纳 | ❌ 排除 | | 商业化 / 付费版 | 与本地优先原则冲突 | ❌ 排除 | -| ~~拆分 crate~~ | ~~22.7 KLOC 单 crate 仍最优~~ | ~~→ 已推翻,v0.14.0 已启动拆分~~ | - ---- - -## 版本规划 - -| 版本 | 主题 | 关键交付 | 预计时间 | -|------|------|----------|----------| -| v0.15.0 | 数据层 + 可靠性 + Agent 体验 | Workspace 成员 6 个,三维 embedding + Saga 一致性 + MCP Streaming | ✅ 2026-05 | -| v0.16.0 | Workspace 扩展 Phase 2 | Workspace 成员 8-10 个,debug 稳定性修复 | 2026-05 | -| v0.16.1 | MCP 解耦收尾 | mcp/tools/repo.rs `crate::` 引用 <10 | 2026-05 | -| v0.17.0 | Registry 清洁 + Tantivy 一致性 | 所有 registry 子模块零 `crate::` 引用,FTS5 评估 | 2026-06 | -| v0.20.0 | 分发发布 | 首个 crate (`devbase-mcp`) 发布到 crates.io | 2026-07+ | +| 主仓库引入 Spark/Flink | 研究性质,独立仓库处理 | ❌ 排除(红线) | +| v0.19.0 引入 Redis/GreptimeDB | 可靠性加固需在现有栈内完成 | ❌ 排除(阶段约束) | --- *本 Roadmap 替代 `plans/roadmap-2026.md` 成为唯一活跃主路线图。* -*历史计划见 `docs/archive/`。* +*历史计划见 `docs/_archive/`。* diff --git a/src/mcp/mod.rs b/src/mcp/mod.rs index f7746f6..a28f933 100644 --- a/src/mcp/mod.rs +++ b/src/mcp/mod.rs @@ -62,6 +62,7 @@ pub enum McpToolEnum { Query(DevkitQueryTool), QueryRepos(DevkitQueryReposTool), Index(DevkitIndexTool), + IndexHealth(DevkitIndexHealthTool), IndexStream(DevkitIndexStreamTool), Note(DevkitNoteTool), Status(DevkitStatusTool), @@ -158,6 +159,7 @@ impl McpToolEnum { McpToolEnum::Sync(_) => ToolTier::Beta, McpToolEnum::Query(_) => ToolTier::Beta, McpToolEnum::Index(_) => ToolTier::Beta, + McpToolEnum::IndexHealth(_) => ToolTier::Beta, McpToolEnum::IndexStream(_) => ToolTier::Beta, McpToolEnum::Status(_) => ToolTier::Beta, McpToolEnum::Note(_) => ToolTier::Beta, @@ -225,6 +227,7 @@ impl McpTool for McpToolEnum { McpToolEnum::Query(t) => t.name(), McpToolEnum::QueryRepos(t) => t.name(), McpToolEnum::Index(t) => t.name(), + McpToolEnum::IndexHealth(t) => t.name(), McpToolEnum::IndexStream(t) => t.name(), McpToolEnum::Status(t) => t.name(), McpToolEnum::Note(t) => t.name(), @@ -294,6 +297,7 @@ impl McpTool for McpToolEnum { McpToolEnum::Query(t) => t.schema(), McpToolEnum::QueryRepos(t) => t.schema(), McpToolEnum::Index(t) => t.schema(), + McpToolEnum::IndexHealth(t) => t.schema(), McpToolEnum::IndexStream(t) => t.schema(), McpToolEnum::Status(t) => t.schema(), McpToolEnum::Note(t) => t.schema(), @@ -367,6 +371,7 @@ impl McpTool for McpToolEnum { McpToolEnum::Query(t) => t.invoke(args, ctx).await, McpToolEnum::QueryRepos(t) => t.invoke(args, ctx).await, McpToolEnum::Index(t) => t.invoke(args, ctx).await, + McpToolEnum::IndexHealth(t) => t.invoke(args, ctx).await, McpToolEnum::IndexStream(t) => t.invoke(args, ctx).await, McpToolEnum::Status(t) => t.invoke(args, ctx).await, McpToolEnum::Note(t) => t.invoke(args, ctx).await, @@ -630,6 +635,7 @@ pub fn build_server_with_tiers(tiers: Option<&HashSet>) -> McpServer { McpToolEnum::Query(DevkitQueryTool), McpToolEnum::QueryRepos(DevkitQueryReposTool), McpToolEnum::Index(DevkitIndexTool), + McpToolEnum::IndexHealth(DevkitIndexHealthTool), McpToolEnum::IndexStream(DevkitIndexStreamTool), McpToolEnum::Status(DevkitStatusTool), McpToolEnum::Note(DevkitNoteTool), diff --git a/src/mcp/tests.rs b/src/mcp/tests.rs index ffb404f..f5e757d 100644 --- a/src/mcp/tests.rs +++ b/src/mcp/tests.rs @@ -39,8 +39,9 @@ async fn test_tools_list() { let (mut ctx, _tmp) = test_ctx(); let resp = server.handle_request(req, &mut ctx).await.unwrap(); let tools = resp.get("result").unwrap().get("tools").unwrap().as_array().unwrap(); - assert_eq!(tools.len(), 64); + assert_eq!(tools.len(), 65); let names: Vec<&str> = tools.iter().map(|t| t.get("name").unwrap().as_str().unwrap()).collect(); + assert!(names.contains(&"devkit_index_health")); assert!(names.contains(&"devkit_session_save")); assert!(names.contains(&"devkit_session_list")); assert!(names.contains(&"devkit_session_resume")); diff --git a/src/mcp/tools/index_health.rs b/src/mcp/tools/index_health.rs new file mode 100644 index 0000000..6597edc --- /dev/null +++ b/src/mcp/tools/index_health.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 juice094 +//! MCP tool: devkit_index_health — Tantivy + SQLite 索引健康度诊断。 + +use crate::mcp::McpTool; +use crate::registry::ENTITY_TYPE_REPO; +use crate::search::list_indexed_repo_ids_at; +use crate::storage::AppContext; +use std::collections::HashSet; +use tantivy::{Index, ReloadPolicy}; + +#[derive(Clone)] +pub struct DevkitIndexHealthTool; + +impl McpTool for DevkitIndexHealthTool { + fn name(&self) -> &'static str { + "devkit_index_health" + } + + fn schema(&self) -> serde_json::Value { + serde_json::json!({ + "description": r#"Diagnose the health of devbase search indexes (Tantivy + SQLite). + +Returns an overall health score (0-100) and detailed metrics for: +- Tantivy repo index: document count, schema validity, orphan detection +- Tantivy symbol index: document count, schema validity +- SQLite registry: repo count, journal mode, orphan records + +Use this when: +- Search results seem incomplete or stale +- Before/after running devkit_index to verify consistency +- Troubleshooting "missing repo" or "orphan document" issues + +Parameters: none (inspects all registered indexes automatically)."#, + "inputSchema": { + "type": "object", + "properties": {} + } + }) + } + + async fn invoke( + &self, + _args: serde_json::Value, + ctx: &mut AppContext, + ) -> anyhow::Result { + run_index_health(ctx) + } +} + +fn build_repo_schema() -> tantivy::schema::Schema { + let mut schema_builder = tantivy::schema::Schema::builder(); + schema_builder.add_text_field("id", tantivy::schema::STRING | tantivy::schema::STORED); + schema_builder.add_text_field("title", tantivy::schema::TEXT | tantivy::schema::STORED); + schema_builder.add_text_field("content", tantivy::schema::TEXT); + schema_builder.add_text_field("tags", tantivy::schema::TEXT); + schema_builder.add_text_field("doc_type", tantivy::schema::TEXT | tantivy::schema::STORED); + schema_builder.build() +} + +fn build_symbol_schema() -> tantivy::schema::Schema { + let mut sb = tantivy::schema::Schema::builder(); + sb.add_text_field("repo_id", tantivy::schema::TEXT | tantivy::schema::STORED); + sb.add_text_field("name", tantivy::schema::TEXT | tantivy::schema::STORED); + sb.add_text_field("signature", tantivy::schema::TEXT | tantivy::schema::STORED); + sb.add_text_field("file_path", tantivy::schema::TEXT | tantivy::schema::STORED); + sb.add_text_field("line_start", tantivy::schema::STORED); + sb.build() +} + +fn check_index_at( + path: &std::path::Path, + expected_schema: &tantivy::schema::Schema, +) -> anyhow::Result<(bool, usize)> { + if !path.exists() { + return Ok((true, 0)); + } + let idx = match Index::open_in_dir(path) { + Ok(i) => i, + Err(_) => return Ok((false, 0)), + }; + let schema_valid = idx.schema() == *expected_schema; + let reader = idx.reader_builder().reload_policy(ReloadPolicy::Manual).try_into()?; + let num_docs = reader.searcher().num_docs() as usize; + Ok((schema_valid, num_docs)) +} + +pub fn run_index_health(ctx: &mut AppContext) -> anyhow::Result { + let index_path = ctx.storage.index_path()?; + let symbol_index_path = ctx.storage.symbol_index_path()?; + + // 1. Tantivy repo index + let repo_schema = build_repo_schema(); + let (repo_schema_valid, repo_docs) = check_index_at(&index_path, &repo_schema)?; + + // 2. Tantivy symbol index + let sym_schema = build_symbol_schema(); + let (sym_schema_valid, sym_docs) = check_index_at(&symbol_index_path, &sym_schema)?; + + // 3. SQLite repo count + journal mode + let conn = ctx.conn_mut()?; + let sqlite_repo_count: i64 = conn + .query_row( + "SELECT COUNT(*) FROM entities WHERE entity_type = ?1", + [ENTITY_TYPE_REPO], + |row| row.get(0), + ) + .unwrap_or(0); + + let journal_mode: String = conn + .query_row("PRAGMA journal_mode", [], |row| row.get(0)) + .unwrap_or_else(|_| "unknown".to_string()); + + // 4. Orphans from orphan_tantivy_docs table + let orphan_rows: Vec = { + let mut stmt = conn.prepare("SELECT repo_id FROM orphan_tantivy_docs")?; + let rows = stmt.query_map([], |row| row.get::<_, String>(0))?; + rows.filter_map(Result::ok).collect() + }; + let recorded_orphans = orphan_rows.len(); + + // 5. Live consistency: Tantivy IDs vs SQLite IDs + let (live_orphans, missing_from_index) = { + let tantivy_ids: HashSet = match list_indexed_repo_ids_at(&index_path) { + Ok(ids) => ids.into_iter().collect(), + Err(_) => HashSet::new(), + }; + let sqlite_ids: HashSet = { + let mut stmt = conn.prepare("SELECT id FROM entities WHERE entity_type = ?1")?; + let rows = stmt.query_map([ENTITY_TYPE_REPO], |row| row.get::<_, String>(0))?; + rows.filter_map(Result::ok).collect() + }; + let orphans = tantivy_ids.difference(&sqlite_ids).count(); + let missing = sqlite_ids.difference(&tantivy_ids).count(); + (orphans, missing) + }; + + drop(conn); + + // 6. Health score calculation + let mut score = 100i64; + if !repo_schema_valid { + score = 0; + } else { + score -= (live_orphans as i64).min(6) * 5; + score -= (missing_from_index as i64).min(10) * 3; + if journal_mode != "wal" { + score -= 10; + } + } + let score = score.max(0) as u8; + + Ok(serde_json::json!({ + "overall_score": score, + "journal_mode": journal_mode, + "tantivy_repo_index": { + "path": index_path.to_string_lossy(), + "schema_valid": repo_schema_valid, + "num_docs": repo_docs, + }, + "tantivy_symbol_index": { + "path": symbol_index_path.to_string_lossy(), + "schema_valid": sym_schema_valid, + "num_docs": sym_docs, + }, + "sqlite_registry": { + "num_repos": sqlite_repo_count, + "recorded_orphans": recorded_orphans, + }, + "consistency": { + "live_orphans": live_orphans, + "missing_from_index": missing_from_index, + "orphan_repo_ids": orphan_rows, + } + })) +} diff --git a/src/mcp/tools/mod.rs b/src/mcp/tools/mod.rs index 2daf337..3bcf083 100644 --- a/src/mcp/tools/mod.rs +++ b/src/mcp/tools/mod.rs @@ -4,6 +4,7 @@ pub mod brief; pub mod context; pub mod evaluate; pub mod impact; +pub mod index_health; pub mod known_limit; pub mod oplog; pub mod query; @@ -23,6 +24,7 @@ pub mod search; pub use brief::*; pub use context::*; pub use impact::*; +pub use index_health::*; pub use known_limit::*; pub use oplog::*; pub use query::*; diff --git a/src/registry/agent_context.rs b/src/registry/agent_context.rs index 7153070..23fc95b 100644 --- a/src/registry/agent_context.rs +++ b/src/registry/agent_context.rs @@ -224,7 +224,7 @@ pub fn list_memories(conn: &Connection, context_id: &str) -> anyhow::Result = row.get(7)?; - let indexed_at = indexed_at.map(|s| parse_datetime(s)).transpose().map_err(|e| { + let indexed_at = indexed_at.map(parse_datetime).transpose().map_err(|e| { rusqlite::Error::FromSqlConversionFailure( 7, rusqlite::types::Type::Text, @@ -343,7 +343,7 @@ pub fn search_memories( ) })?; let indexed_at: Option = row.get(7)?; - let indexed_at = indexed_at.map(|s| parse_datetime(s)).transpose().map_err(|e| { + let indexed_at = indexed_at.map(parse_datetime).transpose().map_err(|e| { rusqlite::Error::FromSqlConversionFailure( 7, rusqlite::types::Type::Text, @@ -400,7 +400,7 @@ pub fn register_vector_functions(conn: &Connection) -> anyhow::Result<()> { move |ctx| { let a_blob: Vec = ctx.get(0)?; let b_blob: Vec = ctx.get(1)?; - if a_blob.len() != b_blob.len() || a_blob.len() % 4 != 0 { + if a_blob.len() != b_blob.len() || !a_blob.len().is_multiple_of(4) { return Err(rusqlite::Error::UserFunctionError( "embedding blobs must have equal length and be multiples of 4 bytes".into(), )); @@ -472,7 +472,7 @@ pub fn search_memories_semantic( ) })?; let indexed_at: Option = row.get(7)?; - let indexed_at = indexed_at.map(|s| parse_datetime(s)).transpose().map_err(|e| { + let indexed_at = indexed_at.map(parse_datetime).transpose().map_err(|e| { rusqlite::Error::FromSqlConversionFailure( 7, rusqlite::types::Type::Text, diff --git a/src/registry/migrate.rs b/src/registry/migrate.rs index 362aa07..913ecf6 100644 --- a/src/registry/migrate.rs +++ b/src/registry/migrate.rs @@ -49,6 +49,7 @@ impl WorkspaceRegistry { pub fn init_db_at(path: &std::path::Path) -> anyhow::Result { let mut conn = rusqlite::Connection::open(path)?; + conn.pragma_update(None, "journal_mode", "WAL")?; conn.execute("PRAGMA foreign_keys = ON", [])?; // Prevent TOCTOU races when multiple threads/processes open the same DB // concurrently (e.g. workflow executor's parallel step threads). diff --git a/src/skill_runtime/executor.rs b/src/skill_runtime/executor.rs index 15c2800..98be4bc 100644 --- a/src/skill_runtime/executor.rs +++ b/src/skill_runtime/executor.rs @@ -231,12 +231,11 @@ fn recall_context_memories( let query_text = build_recall_query(skill_id, args); // Tier 1: semantic recall - if let Ok(embedding) = generate_query_embedding_external(&query_text) { - if let Ok(results) = try_semantic_recall(conn, context_id, &embedding) { - if !results.is_empty() { - return Ok((results, "semantic".to_string())); - } - } + if let Ok(embedding) = generate_query_embedding_external(&query_text) + && let Ok(results) = try_semantic_recall(conn, context_id, &embedding) + && !results.is_empty() + { + return Ok((results, "semantic".to_string())); } // Tier 2: keyword fallback diff --git a/src/storage.rs b/src/storage.rs index db90611..ac46b47 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -184,6 +184,7 @@ impl AppContext { fn build_pool(path: &std::path::Path) -> anyhow::Result> { let manager = SqliteConnectionManager::file(path).with_init(|c| { + c.pragma_update(None, "journal_mode", "WAL")?; c.execute("PRAGMA foreign_keys = ON", [])?; Ok(()) });