From 5edac8b214ece5d608cc8db7a58cdfc92b248995 Mon Sep 17 00:00:00 2001 From: juice094 <160722440+juice094@users.noreply.github.com> Date: Mon, 11 May 2026 09:43:27 +0800 Subject: [PATCH 1/4] =?UTF-8?q?docs(AGENTS):=20=E6=A0=87=E8=AE=B0=E5=8D=95?= =?UTF-8?q?=E4=BD=93=E8=81=8C=E8=B4=A3=E8=86=A8=E8=83=80=E6=8A=80=E6=9C=AF?= =?UTF-8?q?=E5=80=BA=EF=BC=8C=E6=9B=B4=E6=96=B0=E6=8B=86=E5=88=86=E8=AF=84?= =?UTF-8?q?=E4=BC=B0=E5=86=B3=E7=AD=96=E8=87=B3=20v0.16.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 9aa607f..0c11191 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -242,6 +242,7 @@ grep -rn "unwrap()\|expect()\|panic!(" src/ \ | tree-sitter 编译成本 | 🟢 | ~15-20s grammar C compilation | 可控 | 已完成 feature-gate:`lang-rust`/`lang-python`/`lang-js-ts`/`lang-go` 四个 feature,默认全启,可选关闭减少编译;`--no-default-features` 编译通过 | 8 | | Feature flags 缺失 | 🟢 | 4 个可选 feature (tui, watch, mcp, embedding) | ≥3 | 已完成:`tui`/`watch`/`mcp`/`embedding` 均为 optional;`--no-default-features` 编译通过 | ≤15 | | `LOCALAPPDATA` 测试模式残留 | 🟢 | 0 处 | 0 | 全面废弃 `LOCALAPPDATA` 环境变量覆盖,统一为 `DEVBASE_DATA_DIR`;mcp/tests.rs 修复 cleanup 逻辑(remove_var 目标从 LOCALAPPDATA 修正为 DEVBASE_DATA_DIR) | 47 | +| 单体职责膨胀(代码智能+知识库+仓库管理+工作流+Skill+Syncthing) | 🟡 | 6 个核心领域耦合于单一二进制(31MB);`workflow`/`skill` 与 Claude Code Agent 能力重叠 | 按领域拆分为 `devbase-core`(代码+vault)+ `devbase-sync`(仓库管理)+ `devbase-bridge`(Syncthing);冻结 workflow/skill 新增 | 外部审查 2026-05-11 | **清偿原则**: 1. 禁止在清偿现有 🔴 债务前新增同类别债务。 @@ -390,7 +391,7 @@ v0.11.3 已交付(tagged)。v0.12.0-alpha 全部功能已完成,进入发 - `.devbase` 目录规范(无外部采纳者) - MCP 协议扩展提案(Star = 0,不会被采纳) - 商业化 / 付费版 -- 拆分 crate(50+ tools 后再评估) +- ~~拆分 crate(50+ tools 后再评估)~~ → **重新评估**:已触发外部架构审查(§九 耦合检查,6 领域耦合),`workflow`/`skill` 与 Claude Code Agent 重叠,v0.16.0 需输出拆分方案(`devbase-core` / `devbase-sync` / `devbase-bridge`) ### Future / Icebox(无排期) From ebb3cab22758452d24494dc588a3f95ba3238fe2 Mon Sep 17 00:00:00 2001 From: juice094 <160722440+juice094@users.noreply.github.com> Date: Mon, 11 May 2026 10:14:11 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(clippy):=20=E6=B8=85=E9=9B=B6=20workspa?= =?UTF-8?q?ce=20warnings=20+=20docs(crates):=20=E8=A1=A5=E5=86=99=2018=20?= =?UTF-8?q?=E4=B8=AA=E7=BC=BA=E5=A4=B1=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Clippy 修复 (3 处手动 + cargo fix 自动)**: - symbol_index.rs:89: needless_borrows_for_generic_args → - health.rs:511: collapsible_if 嵌套 → let-chains - devbase-embedding/src/lib.rs:364: redundant_closure → - cargo fix 自动应用剩余可修复项 **文档治理 (RF-7 三层摘要)**: - 为 18 个缺失 README 的 workspace crate 生成最小可用 README - 包含:一句话职责、边界说明、深度链接、许可证 - 覆盖率: 1/19 → 19/19 (100%) 验证: - cargo check --workspace: 0 errors - cargo clippy --workspace --all-targets: 0 warnings - cargo fmt --check: pass --- crates/devbase-core-types/README.md | 20 +++++++++++++++++++ crates/devbase-embedding/src/lib.rs | 2 +- crates/devbase-registry-call-graph/README.md | 20 +++++++++++++++++++ .../devbase-registry-code-symbols/README.md | 20 +++++++++++++++++++ crates/devbase-registry-dead-code/README.md | 20 +++++++++++++++++++ crates/devbase-registry-entity/README.md | 20 +++++++++++++++++++ crates/devbase-registry-health/README.md | 20 +++++++++++++++++++ crates/devbase-registry-metrics/README.md | 20 +++++++++++++++++++ crates/devbase-registry-relation/README.md | 20 +++++++++++++++++++ crates/devbase-registry-workspace/README.md | 20 +++++++++++++++++++ crates/devbase-skill-runtime-parser/README.md | 20 +++++++++++++++++++ crates/devbase-skill-runtime-types/README.md | 20 +++++++++++++++++++ crates/devbase-symbol-links/README.md | 20 +++++++++++++++++++ crates/devbase-sync-protocol/README.md | 20 +++++++++++++++++++ crates/devbase-syncthing-client/README.md | 20 +++++++++++++++++++ crates/devbase-vault-frontmatter/README.md | 20 +++++++++++++++++++ crates/devbase-vault-wikilink/README.md | 20 +++++++++++++++++++ crates/devbase-workflow-interpolate/README.md | 20 +++++++++++++++++++ crates/devbase-workflow-model/README.md | 20 +++++++++++++++++++ src/health.rs | 8 ++++---- src/search/symbol_index.rs | 2 +- 21 files changed, 366 insertions(+), 6 deletions(-) create mode 100644 crates/devbase-core-types/README.md create mode 100644 crates/devbase-registry-call-graph/README.md create mode 100644 crates/devbase-registry-code-symbols/README.md create mode 100644 crates/devbase-registry-dead-code/README.md create mode 100644 crates/devbase-registry-entity/README.md create mode 100644 crates/devbase-registry-health/README.md create mode 100644 crates/devbase-registry-metrics/README.md create mode 100644 crates/devbase-registry-relation/README.md create mode 100644 crates/devbase-registry-workspace/README.md create mode 100644 crates/devbase-skill-runtime-parser/README.md create mode 100644 crates/devbase-skill-runtime-types/README.md create mode 100644 crates/devbase-symbol-links/README.md create mode 100644 crates/devbase-sync-protocol/README.md create mode 100644 crates/devbase-syncthing-client/README.md create mode 100644 crates/devbase-vault-frontmatter/README.md create mode 100644 crates/devbase-vault-wikilink/README.md create mode 100644 crates/devbase-workflow-interpolate/README.md create mode 100644 crates/devbase-workflow-model/README.md diff --git a/crates/devbase-core-types/README.md b/crates/devbase-core-types/README.md new file mode 100644 index 0000000..e4ca734 --- /dev/null +++ b/crates/devbase-core-types/README.md @@ -0,0 +1,20 @@ +# devbase-core-types + +Core knowledge graph types: Node, Edge, NodeType + +## 一句话职责 + +Core knowledge graph types: Node, Edge, NodeType + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-embedding/src/lib.rs b/crates/devbase-embedding/src/lib.rs index 70d8b11..f816c48 100644 --- a/crates/devbase-embedding/src/lib.rs +++ b/crates/devbase-embedding/src/lib.rs @@ -361,7 +361,7 @@ mod tests { fn generate_python_embedding(text: &str) -> anyhow::Result> { let candidates: Vec = ["python", "python3"] .iter() - .map(|c| std::path::PathBuf::from(c)) + .map(std::path::PathBuf::from) .filter(|p| { std::process::Command::new(p) .arg("-c") diff --git a/crates/devbase-registry-call-graph/README.md b/crates/devbase-registry-call-graph/README.md new file mode 100644 index 0000000..a718f60 --- /dev/null +++ b/crates/devbase-registry-call-graph/README.md @@ -0,0 +1,20 @@ +# devbase-registry-call-graph + +devbase-registry-call-graph workspace crate. + +## 一句话职责 + +devbase-registry-call-graph workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-registry-code-symbols/README.md b/crates/devbase-registry-code-symbols/README.md new file mode 100644 index 0000000..d6d78a5 --- /dev/null +++ b/crates/devbase-registry-code-symbols/README.md @@ -0,0 +1,20 @@ +# devbase-registry-code-symbols + +devbase-registry-code-symbols workspace crate. + +## 一句话职责 + +devbase-registry-code-symbols workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-registry-dead-code/README.md b/crates/devbase-registry-dead-code/README.md new file mode 100644 index 0000000..ff7b345 --- /dev/null +++ b/crates/devbase-registry-dead-code/README.md @@ -0,0 +1,20 @@ +# devbase-registry-dead-code + +devbase-registry-dead-code workspace crate. + +## 一句话职责 + +devbase-registry-dead-code workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-registry-entity/README.md b/crates/devbase-registry-entity/README.md new file mode 100644 index 0000000..8af09da --- /dev/null +++ b/crates/devbase-registry-entity/README.md @@ -0,0 +1,20 @@ +# devbase-registry-entity + +devbase-registry-entity workspace crate. + +## 一句话职责 + +devbase-registry-entity workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-registry-health/README.md b/crates/devbase-registry-health/README.md new file mode 100644 index 0000000..db4dd3a --- /dev/null +++ b/crates/devbase-registry-health/README.md @@ -0,0 +1,20 @@ +# devbase-registry-health + +devbase-registry-health workspace crate. + +## 一句话职责 + +devbase-registry-health workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-registry-metrics/README.md b/crates/devbase-registry-metrics/README.md new file mode 100644 index 0000000..d5b131f --- /dev/null +++ b/crates/devbase-registry-metrics/README.md @@ -0,0 +1,20 @@ +# devbase-registry-metrics + +devbase-registry-metrics workspace crate. + +## 一句话职责 + +devbase-registry-metrics workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-registry-relation/README.md b/crates/devbase-registry-relation/README.md new file mode 100644 index 0000000..1fd2f53 --- /dev/null +++ b/crates/devbase-registry-relation/README.md @@ -0,0 +1,20 @@ +# devbase-registry-relation + +devbase-registry-relation workspace crate. + +## 一句话职责 + +devbase-registry-relation workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-registry-workspace/README.md b/crates/devbase-registry-workspace/README.md new file mode 100644 index 0000000..dadccee --- /dev/null +++ b/crates/devbase-registry-workspace/README.md @@ -0,0 +1,20 @@ +# devbase-registry-workspace + +devbase-registry-workspace workspace crate. + +## 一句话职责 + +devbase-registry-workspace workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-skill-runtime-parser/README.md b/crates/devbase-skill-runtime-parser/README.md new file mode 100644 index 0000000..a80b69c --- /dev/null +++ b/crates/devbase-skill-runtime-parser/README.md @@ -0,0 +1,20 @@ +# devbase-skill-runtime-parser + +devbase-skill-runtime-parser workspace crate. + +## 一句话职责 + +devbase-skill-runtime-parser workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-skill-runtime-types/README.md b/crates/devbase-skill-runtime-types/README.md new file mode 100644 index 0000000..c2a9ed1 --- /dev/null +++ b/crates/devbase-skill-runtime-types/README.md @@ -0,0 +1,20 @@ +# devbase-skill-runtime-types + +devbase-skill-runtime-types workspace crate. + +## 一句话职责 + +devbase-skill-runtime-types workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-symbol-links/README.md b/crates/devbase-symbol-links/README.md new file mode 100644 index 0000000..980a46a --- /dev/null +++ b/crates/devbase-symbol-links/README.md @@ -0,0 +1,20 @@ +# devbase-symbol-links + +Code symbol link generator: similar-signature and co-located relationship discovery + +## 一句话职责 + +Code symbol link generator: similar-signature and co-located relationship discovery + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-sync-protocol/README.md b/crates/devbase-sync-protocol/README.md new file mode 100644 index 0000000..178143e --- /dev/null +++ b/crates/devbase-sync-protocol/README.md @@ -0,0 +1,20 @@ +# devbase-sync-protocol + +Lightweight directory sync protocol with version vectors + +## 一句话职责 + +Lightweight directory sync protocol with version vectors + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-syncthing-client/README.md b/crates/devbase-syncthing-client/README.md new file mode 100644 index 0000000..0cfd017 --- /dev/null +++ b/crates/devbase-syncthing-client/README.md @@ -0,0 +1,20 @@ +# devbase-syncthing-client + +Syncthing REST API client for folder management + +## 一句话职责 + +Syncthing REST API client for folder management + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-vault-frontmatter/README.md b/crates/devbase-vault-frontmatter/README.md new file mode 100644 index 0000000..e60805e --- /dev/null +++ b/crates/devbase-vault-frontmatter/README.md @@ -0,0 +1,20 @@ +# devbase-vault-frontmatter + +Markdown frontmatter parser for vault notes + +## 一句话职责 + +Markdown frontmatter parser for vault notes + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-vault-wikilink/README.md b/crates/devbase-vault-wikilink/README.md new file mode 100644 index 0000000..973b585 --- /dev/null +++ b/crates/devbase-vault-wikilink/README.md @@ -0,0 +1,20 @@ +# devbase-vault-wikilink + +WikiLink parser and resolver for Markdown vaults + +## 一句话职责 + +WikiLink parser and resolver for Markdown vaults + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-workflow-interpolate/README.md b/crates/devbase-workflow-interpolate/README.md new file mode 100644 index 0000000..3a1730a --- /dev/null +++ b/crates/devbase-workflow-interpolate/README.md @@ -0,0 +1,20 @@ +# devbase-workflow-interpolate + +Workflow variable interpolation for devbase + +## 一句话职责 + +Workflow variable interpolation for devbase + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/crates/devbase-workflow-model/README.md b/crates/devbase-workflow-model/README.md new file mode 100644 index 0000000..ebcc825 --- /dev/null +++ b/crates/devbase-workflow-model/README.md @@ -0,0 +1,20 @@ +# devbase-workflow-model + +devbase-workflow-model workspace crate. + +## 一句话职责 + +devbase-workflow-model workspace crate. + +## 边界说明 + +零内部耦合 workspace crate。禁止直接调用 devbase 主 crate 模块。 + +## 深度链接 + +- [devbase 主文档](../README.md) +- [AGENTS.md](../AGENTS.md) + +## 许可证 + +AGPL-3.0-or-later diff --git a/src/health.rs b/src/health.rs index 6234b73..900371e 100644 --- a/src/health.rs +++ b/src/health.rs @@ -508,10 +508,10 @@ fn fmt_version(raw: Option, i18n: &crate::i18n::I18n) -> String { Some(s) => { let s = s.trim(); // Java outputs: java version "1.8.0_31" — extract quoted version - if let Some(start) = s.find('"') { - if let Some(end) = s[start + 1..].find('"') { - return s[start + 1..start + 1 + end].to_string(); - } + if let Some(start) = s.find('"') + && let Some(end) = s[start + 1..].find('"') + { + return s[start + 1..start + 1 + end].to_string(); } let parts: Vec<&str> = s.split_whitespace().collect(); if parts.len() >= 2 { diff --git a/src/search/symbol_index.rs b/src/search/symbol_index.rs index d198bbb..b902f5a 100644 --- a/src/search/symbol_index.rs +++ b/src/search/symbol_index.rs @@ -86,7 +86,7 @@ pub fn add_symbol_doc( doc.add_text(sig_f, s); } doc.add_text(path_f, file_path); - doc.add_text(line_f, &line_start.to_string()); + doc.add_text(line_f, line_start.to_string()); writer.add_document(doc)?; Ok(()) } From 731bb66b63836876587684792e0ac007e743d221 Mon Sep 17 00:00:00 2001 From: juice094 <160722440+juice094@users.noreply.github.com> Date: Mon, 11 May 2026 10:16:59 +0800 Subject: [PATCH 3/4] fix(feature-flags): eliminate --no-default-features dead_code warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit semantic_index/symbol.rs and call_graph.rs contain language-specific functions that are unused when all lang-* features are disabled. Add conditional #![allow(dead_code, unused_imports, unused_variables)] that only activates when no tree-sitter grammar features are enabled: This preserves zero-warning builds for: - cargo clippy --workspace --all-targets (0 warnings) - cargo clippy --no-default-features (0 warnings) - cargo clippy --features tui (0 warnings) - cargo clippy --features mcp (0 warnings) 验证: cargo check --workspace 0 errors --- Cargo.toml | 5 +++++ src/semantic_index/call_graph.rs | 9 +++++++++ src/semantic_index/symbol.rs | 9 +++++++++ 3 files changed, 23 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a568424..8b495d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,11 @@ predicates = "3" [profile.dev] opt-level = 1 +[profile.release] +lto = true +codegen-units = 1 +strip = true + [workspace] members = ["crates/*"] resolver = "2" diff --git a/src/semantic_index/call_graph.rs b/src/semantic_index/call_graph.rs index 7ae8d80..40877c8 100644 --- a/src/semantic_index/call_graph.rs +++ b/src/semantic_index/call_graph.rs @@ -1,5 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 juice094 +#![cfg_attr( + not(any( + feature = "lang-rust", + feature = "lang-python", + feature = "lang-js-ts", + feature = "lang-go" + )), + allow(dead_code, unused_imports, unused_variables) +)] use std::path::Path; use tracing::warn; diff --git a/src/semantic_index/symbol.rs b/src/semantic_index/symbol.rs index 416e982..0a5b359 100644 --- a/src/semantic_index/symbol.rs +++ b/src/semantic_index/symbol.rs @@ -1,5 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 juice094 +#![cfg_attr( + not(any( + feature = "lang-rust", + feature = "lang-python", + feature = "lang-js-ts", + feature = "lang-go" + )), + allow(dead_code, unused_imports, unused_variables) +)] use std::path::Path; use tracing::{debug, warn}; From fcd19b4316357ebae578caf062495c990c005d82 Mon Sep 17 00:00:00 2001 From: juice094 <160722440+juice094@users.noreply.github.com> Date: Mon, 11 May 2026 12:39:46 +0800 Subject: [PATCH 4/4] =?UTF-8?q?refactor(health):=20extract=20env=5Fcache?= =?UTF-8?q?=20submod=20+=20reduce=20crate::=20refs=2029=E2=86=927?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **问题**: health.rs 是项目中 crate:: 引用最多的文件 (29 处), 违反 RF-7 的耦合健康度约束。 **方案**: 1. 提取 / / 到 子模块 2. health.rs 通过 向后兼容 re-export 3. 集中导入外部模块,消除行内 crate:: 引用: - - - - **效果**: - health.rs crate:: 引用: 29 → 7 (-76%) - env_cache.rs 职责单一:环境工具链版本检测 + 格式化 - 零运行时行为变更 验证: - cargo check --workspace: 0 errors - cargo clippy --workspace --all-targets: 0 warnings - cargo test --workspace: 503 passed / 0 failed / 4 ignored --- src/health.rs | 187 +++++++++++----------------------------- src/health/env_cache.rs | 84 ++++++++++++++++++ 2 files changed, 134 insertions(+), 137 deletions(-) create mode 100644 src/health/env_cache.rs diff --git a/src/health.rs b/src/health.rs index 900371e..aede2d1 100644 --- a/src/health.rs +++ b/src/health.rs @@ -1,12 +1,22 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 juice094 +use crate::clients::HealthClient; +use crate::i18n::I18n; use crate::registry::repo; -use crate::registry::{HealthEntry, OplogEntry, WorkspaceSnapshot}; +use crate::registry::{ + HealthEntry, OplogEntry, OplogEventType, WorkspaceSnapshot, health as reg_health, + workspace as reg_workspace, +}; +use crate::storage::{AppContext, EnvVersionCache}; use chrono::Utc; use git2::Repository; use std::path::Path; use tracing::info; +pub use self::env_cache::{fmt_version, refresh_env_cache}; + +mod env_cache; + const IGNORED_DIRS: &[&str] = &[ ".git", "node_modules", @@ -59,8 +69,8 @@ pub async fn run_json( limit: usize, page: usize, ttl_seconds: i64, - i18n: &crate::i18n::I18n, - env_cache: &crate::storage::EnvVersionCache, + i18n: &I18n, + env_cache: &EnvVersionCache, ) -> anyhow::Result { let start = std::time::Instant::now(); let (total_repos, dirty_repos, behind_upstream, no_upstream_count, repo_details) = { @@ -92,8 +102,7 @@ pub async fn run_json( .filter(|r| r.workspace_type == "git") .map(|r| r.id.as_str()) .collect(); - let health_batch = - crate::registry::health::get_health_batch(conn, &repo_ids).unwrap_or_default(); + let health_batch = reg_health::get_health_batch(conn, &repo_ids).unwrap_or_default(); for repo in repos { total_repos += 1; @@ -121,9 +130,7 @@ pub async fn run_json( behind, checked_at: Utc::now(), }; - if let Err(e) = - crate::registry::health::save_health(conn, &repo.id, &new_health) - { + if let Err(e) = reg_health::save_health(conn, &repo.id, &new_health) { tracing::warn!("Failed to save health for {}: {}", repo.id, e); } (status, ahead, behind) @@ -168,28 +175,24 @@ pub async fn run_json( continue; } }; - let status = - match crate::registry::workspace::get_latest_workspace_snapshot(conn, &repo.id) - { - Ok(Some(prev)) if prev.file_hash == current_hash => "ok".to_string(), - _ => { - let snapshot = WorkspaceSnapshot { - repo_id: repo.id.clone(), - file_hash: current_hash, - checked_at: Utc::now(), - }; - if let Err(e) = - crate::registry::workspace::save_workspace_snapshot(conn, &snapshot) - { - tracing::warn!( - "Failed to save workspace snapshot for {}: {}", - repo.id, - e - ); - } - "changed".to_string() + let status = match reg_workspace::get_latest_workspace_snapshot(conn, &repo.id) { + Ok(Some(prev)) if prev.file_hash == current_hash => "ok".to_string(), + _ => { + let snapshot = WorkspaceSnapshot { + repo_id: repo.id.clone(), + file_hash: current_hash, + checked_at: Utc::now(), + }; + if let Err(e) = reg_workspace::save_workspace_snapshot(conn, &snapshot) { + tracing::warn!( + "Failed to save workspace snapshot for {}: {}", + repo.id, + e + ); } - }; + "changed".to_string() + } + }; (status, 0, 0) }; @@ -245,11 +248,11 @@ pub async fn run_json( "behind_upstream": behind_upstream, "no_upstream": no_upstream_count }); - let _ = crate::registry::workspace::save_oplog( + let _ = reg_workspace::save_oplog( conn, &OplogEntry { id: None, - event_type: crate::registry::OplogEventType::HealthCheck, + event_type: OplogEventType::HealthCheck, repo_id: None, details: Some(details.to_string()), status: "success".to_string(), @@ -291,8 +294,8 @@ pub async fn run( limit: usize, page: usize, ttl_seconds: i64, - i18n: &crate::i18n::I18n, - env_cache: &crate::storage::EnvVersionCache, + i18n: &I18n, + env_cache: &EnvVersionCache, ) -> anyhow::Result<()> { let result = run_json(conn, detail, limit, page, ttl_seconds, i18n, env_cache).await?; @@ -457,115 +460,25 @@ fn calc_ahead_behind( repo.graph_ahead_behind(local_oid, remote_oid).map_err(|e| anyhow::anyhow!(e)) } -/// Refresh environment version cache by spawning all tool subprocesses in parallel. -pub async fn refresh_env_cache() -> crate::storage::EnvVersionCache { - let (rustc, cargo, node, go, cmake, python, bun, zig, java) = tokio::join!( - get_tool_version("rustc", &["--version"]), - get_tool_version("cargo", &["--version"]), - get_tool_version("node", &["--version"]), - get_tool_version("go", &["version"]), - get_tool_version("cmake", &["--version"]), - get_tool_version("python", &["--version"]), - get_tool_version("bun", &["--version"]), - get_tool_version("zig", &["version"]), - get_tool_version("java", &["-version"]), - ); - crate::storage::EnvVersionCache { - rustc, - cargo, - node, - go, - cmake, - python, - bun, - zig, - java, - fetched_at: Some(std::time::Instant::now()), - } -} - -async fn get_tool_version(cmd: &str, args: &[&str]) -> Option { - let output = tokio::process::Command::new(cmd).args(args).output().await.ok()?; - - if !output.status.success() { - return None; - } - - let raw = if !output.stdout.is_empty() { - String::from_utf8_lossy(&output.stdout) - } else { - String::from_utf8_lossy(&output.stderr) - }; - let line = raw.lines().next()?.trim(); - if line.is_empty() { - return None; - } - Some(line.to_string()) -} - -fn fmt_version(raw: Option, i18n: &crate::i18n::I18n) -> String { - match raw { - Some(s) => { - let s = s.trim(); - // Java outputs: java version "1.8.0_31" — extract quoted version - if let Some(start) = s.find('"') - && let Some(end) = s[start + 1..].find('"') - { - return s[start + 1..start + 1 + end].to_string(); - } - let parts: Vec<&str> = s.split_whitespace().collect(); - if parts.len() >= 2 { - match parts[0] { - "rustc" | "cargo" | "bun" | "zig" | "Python" => { - parts.get(1).unwrap_or(&"unknown").to_string() - } - "cmake" | "version" => parts.get(2).unwrap_or(&"unknown").to_string(), - "go" if parts.len() >= 3 => parts[2].to_string(), - "Docker" if parts.len() >= 3 && parts[1] == "version" => parts[2..].join(" "), - _ => { - // Heuristic: if second word is "version", skip first two - if parts.len() >= 3 && parts[1] == "version" { - parts[2..].join(" ") - } else { - s.to_string() - } - } - } - } else { - s.to_string() - } - } - None => i18n.log.not_installed.to_string(), - } -} - -impl crate::clients::HealthClient for crate::storage::AppContext { +impl HealthClient for AppContext { async fn check_health(&self, detail: bool) -> anyhow::Result { let conn = self.conn()?; let cache = self.env_cache()?; let env_cache = if cache.is_fresh() { cache } else { - let fresh = crate::health::refresh_env_cache().await; + let fresh = refresh_env_cache().await; self.set_env_cache(fresh.clone())?; fresh }; - crate::health::run_json( - &conn, - detail, - 0, - 1, - self.config.cache.ttl_seconds, - &self.i18n, - &env_cache, - ) - .await + run_json(&conn, detail, 0, 1, self.config.cache.ttl_seconds, &self.i18n, &env_cache).await } } #[cfg(test)] mod tests { use super::*; + use crate::i18n::from_language; #[test] fn test_compute_workspace_hash_empty_dir() { @@ -616,61 +529,61 @@ mod tests { #[test] fn test_fmt_version_rustc() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("rustc 1.70.0".to_string()), &i18n), "1.70.0"); } #[test] fn test_fmt_version_cargo() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("cargo 1.70.0".to_string()), &i18n), "1.70.0"); } #[test] fn test_fmt_version_cmake() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("cmake version 3.26".to_string()), &i18n), "3.26"); } #[test] fn test_fmt_version_go() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("go version go1.20".to_string()), &i18n), "go1.20"); } #[test] fn test_fmt_version_unknown() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("foo bar".to_string()), &i18n), "foo bar"); } #[test] fn test_fmt_version_single_word() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("v1.0".to_string()), &i18n), "v1.0"); } #[test] fn test_fmt_version_python() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("Python 3.12.13".to_string()), &i18n), "3.12.13"); } #[test] fn test_fmt_version_bun() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("1.3.11".to_string()), &i18n), "1.3.11"); } #[test] fn test_fmt_version_java() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!(fmt_version(Some("java version \"1.8.0_31\"".to_string()), &i18n), "1.8.0_31"); } #[test] fn test_fmt_version_docker() { - let i18n = crate::i18n::from_language("en"); + let i18n = from_language("en"); assert_eq!( fmt_version(Some("Docker version 24.0.7, build afdd53b".to_string()), &i18n), "24.0.7, build afdd53b" diff --git a/src/health/env_cache.rs b/src/health/env_cache.rs new file mode 100644 index 0000000..9643218 --- /dev/null +++ b/src/health/env_cache.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 juice094 + +use crate::i18n::I18n; + +/// Refresh environment version cache by spawning all tool subprocesses in parallel. +pub async fn refresh_env_cache() -> crate::storage::EnvVersionCache { + let (rustc, cargo, node, go, cmake, python, bun, zig, java) = tokio::join!( + get_tool_version("rustc", &["--version"]), + get_tool_version("cargo", &["--version"]), + get_tool_version("node", &["--version"]), + get_tool_version("go", &["version"]), + get_tool_version("cmake", &["--version"]), + get_tool_version("python", &["--version"]), + get_tool_version("bun", &["--version"]), + get_tool_version("zig", &["version"]), + get_tool_version("java", &["-version"]), + ); + crate::storage::EnvVersionCache { + rustc, + cargo, + node, + go, + cmake, + python, + bun, + zig, + java, + fetched_at: Some(std::time::Instant::now()), + } +} + +async fn get_tool_version(cmd: &str, args: &[&str]) -> Option { + let output = tokio::process::Command::new(cmd).args(args).output().await.ok()?; + + if !output.status.success() { + return None; + } + + let raw = if !output.stdout.is_empty() { + String::from_utf8_lossy(&output.stdout) + } else { + String::from_utf8_lossy(&output.stderr) + }; + let line = raw.lines().next()?.trim(); + if line.is_empty() { + return None; + } + Some(line.to_string()) +} + +pub fn fmt_version(raw: Option, i18n: &I18n) -> String { + match raw { + Some(s) => { + let s = s.trim(); + if let Some(start) = s.find('"') + && let Some(end) = s[start + 1..].find('"') + { + return s[start + 1..start + 1 + end].to_string(); + } + let parts: Vec<&str> = s.split_whitespace().collect(); + if parts.len() >= 2 { + match parts[0] { + "rustc" | "cargo" | "bun" | "zig" | "Python" => { + parts.get(1).unwrap_or(&"unknown").to_string() + } + "cmake" | "version" => parts.get(2).unwrap_or(&"unknown").to_string(), + "go" if parts.len() >= 3 => parts[2].to_string(), + "Docker" if parts.len() >= 3 && parts[1] == "version" => parts[2..].join(" "), + _ => { + if parts.len() >= 3 && parts[1] == "version" { + parts[2..].join(" ") + } else { + s.to_string() + } + } + } + } else { + s.to_string() + } + } + None => i18n.log.not_installed.to_string(), + } +}