Skip to content
Merged
34 changes: 34 additions & 0 deletions docs/architecture/adr-004-mcp-trait-decoupling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ADR-004: MCP Tool Layer Trait Decoupling

- **状态**: accepted
- **日期**: 2026-05-11
- **作者**: devbase 架构优化会话

## 上下文

`src/mcp/tools/` 中的 MCP 工具实现直接内联调用 `crate::health::`、`crate::search::`、`crate::registry::` 等底层模块,导致:
- 工具层与业务层硬耦合,无法独立测试
- `repo.rs` 等文件 `crate::` 内联引用超过 10 处,违反架构红线
- 新增工具时容易引入隐式依赖

## 决策

为每个业务领域定义 trait(`ScanClient`、`HealthClient`、`RegistryClient`、`KnowledgeClient`、`SearchClient`、`RepoAnalyzer` 等),由 `AppContext` 统一实现,MCP 工具只依赖 trait。

## 后果

- **正面**: `repo.rs` `crate::` 引用从 11 降至 8(全部集中在 use 语句);工具层可独立单元测试;新增领域只需扩展 trait
- **负面**: trait 定义与实现分属不同文件,跳转成本略增;简单查询也需 trait 封装
- **风险**: 过度抽象可能导致 trait 膨胀;需定期审查 trait 方法是否仍被使用

## 备选方案

| 方案 | 不选原因 |
|------|---------|
| 保持现状,仅清理 use 语句 | 未解决测试隔离问题 |
| 每个工具独立 service struct | 与现有 `AppContext` 模式冲突,引入更多类型 |

## 相关决策

- 依赖:ADR-001(单 crate 模型使 trait 定义零成本)
- 被依赖:ADR-005(AppContext Clone 是 trait 在 spawn_blocking 中使用的前提)
34 changes: 34 additions & 0 deletions docs/architecture/adr-005-appcontext-clone.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ADR-005: AppContext Clone for Async Context Propagation

- **状态**: accepted
- **日期**: 2026-05-11
- **作者**: devbase 架构优化会话

## 上下文

MCP 工具频繁使用 `tokio::task::spawn_blocking` 执行 I/O 密集型操作(Tantivy 索引、SQLite 查询、文件系统遍历)。此前 `AppContext` 未实现 `Clone`,导致:
- 闭包内无法调用 `ctx.list_vault_notes()` 等 trait 方法
- 被迫在 `spawn_blocking` 外获取 `conn` 再 move 进闭包,增加生命周期复杂度
- `VaultClient`、`WorkflowClient` 等 trait 难以在异步上下文中使用

## 决策

将 `AppContext.env_cache` 从 `std::sync::Mutex<EnvVersionCache>` 改为 `Arc<std::sync::Mutex<EnvVersionCache>>`,并为 `AppContext` 添加 `#[derive(Clone)]`。

## 后果

- **正面**: `spawn_blocking` 闭包内可直接 `ctx.clone()` 后调用任意 trait 方法;统一 async/sync 边界处理模式
- **负面**: `Clone` 后多个上下文共享同一 `Mutex`,并发修改 env_cache 的竞争概率微增(当前仅 daemon 定期刷新,可忽略)
- **风险**: 未来若向 `AppContext` 添加非 Clone 字段,需回退到显式字段 clone 模式

## 备选方案

| 方案 | 不选原因 |
|------|---------|
| 为每个 trait 定义无状态 Impl (ZST) | `RegistryClient` 等方法需 `conn`,无状态 impl 需传入 `Connection`,违反 T11 红线 |
| 使用 `Arc<AppContext>` 包装 | 增加一层间接,所有调用点需改为 `arc.ctx.method()`,改动面过大 |
| 将 `Pool` 单独 clone move 进闭包 | 已在使用,但无法调用 `DigestClient` 等需要 config/i18n 的 trait 方法 |

## 相关决策

- 依赖:ADR-004(trait 化后,clone 成为 spawn_blocking 中使用 trait 的基础设施)
3 changes: 3 additions & 0 deletions docs/architecture/adr-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
|------|------|------|------|
| ADR-001 | 单 crate 模型(defer split)| accepted | 2026-04-26 |
| ADR-002 | Candle CPU BERT 单条编码(batch 回滚)| accepted | 2026-05-04 |
| ADR-003 | Tantivy + SQLite 双写一致性策略 | proposed | 2026-05-11 |
| ADR-004 | MCP Tool Layer Trait Decoupling | accepted | 2026-05-11 |
| ADR-005 | AppContext Clone for Async Context Propagation | accepted | 2026-05-11 |

### ADR-001: 单 crate 模型(defer split)

Expand Down
7 changes: 7 additions & 0 deletions src/daemon.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
//! Background daemon: periodic health checks, re-indexing, discovery,
//! daily digest generation, and relation graph maintenance.
//!
//! The daemon runs on a configurable schedule (`daemon.interval_seconds`)
//! and uses `tokio::spawn_blocking` for CPU- or I/O-heavy tasks to avoid
//! blocking the async runtime.

use crate::config::Config;
use crate::digest::generate_daily_digest;
use crate::discovery_engine::{discover_dependencies, discover_similar_projects};
Expand Down
8 changes: 6 additions & 2 deletions src/embedding.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
// RE-EXPORT ONLY — 实现已迁移至 devbase-embedding crate.
// 禁止在本文件中添加新代码。
//! Embedding generation: local Candle-based BERT inference for code symbols.
//!
//! **Re-export only** — implementation lives in the `devbase-embedding` crate.
//! Do not add new code here; extend the extracted crate instead.
//!
//! Feature-gated behind `embedding`; disabled by default to reduce binary size.

#[cfg(feature = "embedding")]
pub use devbase_embedding::*;
Expand Down
10 changes: 10 additions & 0 deletions src/i18n/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
//! Internationalization (i18n) layer for devbase.
//!
//! Provides language-specific UI strings for TUI, CLI, sync reports, and logs.
//! Supported languages: English (`en`) and Simplified Chinese (`zh_cn`).
//!
//! **Note on `#[allow(dead_code)]`**: Many string fields are accessed only when
//! the `tui` feature is enabled. Without this attribute, compiling without
//! `--features tui` would produce spurious dead-code warnings. The fields are
//! actively used in production builds with the default feature set.

#[derive(Clone, Copy)]
#[allow(dead_code)]
pub struct I18n {
Expand Down
10 changes: 10 additions & 0 deletions src/knowledge_engine/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
//! Knowledge engine: repository indexing, summary extraction, and module analysis.
//!
//! Orchestrates Tantivy full-text indexing, SQLite registry persistence,
//! semantic code indexing (AST + call graph), and optional embedding generation.
//!
//! Entry points:
//! - [`run_index`] — batch index all registered repos or a single path
//! - [`index_repo`] — index a single repo (standalone writer)
//! - [`index_repo_with_writer`] — index a single repo reusing an existing writer

pub mod fallback;
pub mod index;
pub mod index_state;
Expand Down
7 changes: 7 additions & 0 deletions src/registry.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
//! Registry layer: SQLite-backed entity storage and domain-specific submodules.
//!
//! Central types (`RepoEntry`, `VaultNote`, `PaperEntry`, etc.) and the
//! [`RegistryClient`] trait implementation on [`AppContext`].
//! Submodules cover repos, health, knowledge, code metrics, call graphs,
//! dead-code analysis, and migrations.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
Expand Down
8 changes: 8 additions & 0 deletions src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
//! Storage abstraction and application context (`AppContext`).
//!
//! [`StorageBackend`] decouples concrete paths from consumers, enabling
//! test isolation via [`TempStorageBackend`] and future remote backends.
//! [`AppContext`] is the central dependency-injection container: it holds
//! the storage backend, database pool, config, i18n, and environment cache,
//! and implements all MCP client traits (`ScanClient`, `HealthClient`, etc.).

use crate::config::Config;
use crate::i18n::{I18n, from_language};
use crate::registry::{ENTITY_TYPE_REPO, WorkspaceRegistry};
Expand Down
10 changes: 10 additions & 0 deletions src/workflow/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
//! Workflow automation engine: YAML-defined multi-step pipelines with parallel
//! execution, variable interpolation, and SQLite-backed execution tracking.
//!
//! A workflow consists of ordered steps, each referencing a registered Skill.
//! The scheduler builds independent batches; the executor runs each batch in
//! parallel while preserving step ordering across batches.
//!
//! Key traits:
//! - [`WorkflowClient`] — MCP-facing API for listing, running, and querying workflows

pub mod executor;
pub mod interpolate;
pub mod model;
Expand Down
Loading