claude-devtools-rs / crates
CLAUDE.md1 starsChanged 6 months agoMITView on GitHub
仅在 Claude 读写 crates/** 下的文件时由 Claude Code 自动加载。跨域共识在根 CLAUDE.md;IPC payload 模式在 src-tauri/CLAUDE.md;spec 工作流在 openspec/CLAUDE.md。本文合并自原 .claude/rules/rust.md。 unsafe workspace-wide 禁用(#![forbid(unsafe_code)])。 通用规范跟 Rust API Guidelines + clippy pedantic(详 §clippy pedantic 速查)。以下是*项目特有或易忽略*:
In this repository31 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- openspec/CLAUDE.mdCLAUDE.md
- src-tauri/CLAUDE.mdCLAUDE.md
- ui/CLAUDE.mdCLAUDE.md
- bug-huntSkill
- bump-versionSkill
- debug-firstSkill
- dep-updateSkill
- e2e-http-verifySkill
- insta-reviewSkill
- mark-tasks-doneSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-bulk-archive-changeSkill
- openspec-continue-changeSkill
- openspec-exploreSkill
- openspec-ff-changeSkill
- openspec-new-changeSkill
- openspec-onboardSkill
- openspec-proposeSkill
- openspec-sync-specsSkill
- openspec-verify-changeSkill
- perfSkill
- port-dashboardSkill
- preflightSkill
- refactorSkill
- release-runbookSkill
- ts-parity-checkSkill
- ts-ui-compareSkill
- wait-ciSkill
The file
Files
crates/ — Rust workspace(cdt-*)
仅在 Claude 读写 crates/** 下的文件时由 Claude Code 自动加载。跨域共识在根 CLAUDE.md;IPC payload 模式在 src-tauri/CLAUDE.md;spec 工作流在 openspec/CLAUDE.md。本文合并自原 .claude/rules/rust.md。
crate 边界(capability → crate)
cdt-core:共享类型 + traits,no runtime deps(不依赖tokio/axum/notify/ssh2等)cdt-parse:session-parsing(含 JSONL 流式 + dedupe)cdt-analyze:chunk-building / tool-linking / context-tracking / team-metadata(sync,不引 tokio)cdt-discover:project-discovery / session-search / path-decoder(跨平台路径工具单一源)cdt-watch:file-watching(自实现 tokio debounce,不用 notify-debouncer-mini)cdt-config:configuration-management / notification-triggerscdt-ssh:ssh-remote-contextcdt-api:ipc-data-api + http-data-api facadecdt-telemetry:application-telemetry — Counter / Histogram / Event Signal Registry + tracing bridge layer(hot path < 0.2% 增量;详openspec/specs/application-telemetry/spec.md)cdt-cli:binary entrypoint(bin = cdt,anyhow::Result+tracing_subscriberinit)
unsafe workspace-wide 禁用(#![forbid(unsafe_code)])。
Rust 约定(仅列项目偏离 / 强调点)
通用规范跟 Rust API Guidelines + clippy pedantic(详 §clippy pedantic 速查)。以下是项目特有或易忽略:
- crate 边界:
cdt-core/cdt-analyze保 sync + no runtime deps;tokio只加到真做 I/O 的 leaf crate;跨 crate import 只走 public API(pub usefromlib.rs),不伸进crate::internal::...;共用类型放cdt-core,不跨 leaf re-export - Error 二分:library crate 用
thiserror定pub enum <Crate>Error,非 test 路径禁panic!/unwrap();binarycdt-cli用anyhow::Result<()>+.context()。验证只在系统边界(external input / fs / IPC / HTTP / SSH) tracing_subscriber仅在cdt-cli::main初始化一次——library crate 不装 global subscriber;用结构化字段tracing::info!(session_id = %id, ...)- 日志级别纪律(边界 + 语义双闸门,防 CLI/MCP 噪音回归):
- 边界:
cdt-cli默认全静默(init_logging默认 filteroff),诊断日志是 opt-in(-v/-vv/-vvv= warn/info/debug,或RUST_LOG)。这保证--format json/jsonl、mcp serve(stdio JSON-RPC)、管道、终端永不被 tracing 污染,且与 library 各处用何级别无关——不靠"默认值恰好够低"赌。命令自身错误走anyhow::Result由 main 打印,与诊断日志两条路,staticoff不吞它。禁止把这个默认改成info/warn基线(v0.6/0.7stats刷 321 行 WARN 的根因就是默认info基线泄漏 library WARN)。 - 语义:处理外部数据的预期内瑕疵(坏 JSONL 行、重复 tool_use/tool_result id、schema 漂移)SHALL 记
debug!不记warn!——这类是常态不是异常,逐条 warn 是 warning fatigue 反模式。有计数器的(如ToolLinkingResult::duplicates_dropped)走聚合,不逐条刷。warn!只留给"意外且需要有人采取行动"的事件。参照已对齐的cdt-parse::file/cdt-discover::project_scanner。
- 边界:
- 解析双形态:
parse_entry(line) -> Result<_>(sync per-line)+parse_file(path) -> impl Stream(async per-file) - 依赖版本住 workspace 根:
[workspace.dependencies],crate-level 用dep = { workspace = true };新依赖要 justify - 测试:每 capability spec scenario 至少一个
#[test],命名用 prose(如fn user_question_then_ai_response_emits_two_chunks);snapshot-heavy 用insta - 注释:默认无——名字 + 类型承载意义;
pub不 self-explanatory 时///(trait contract / 不变量),模块头//!链openspec/specs/<cap>/spec.md;写 WHY 不写 WHAT
测试基础设施陷阱
- macOS
TempDirvs FSEvents:TempDir返/var/...但notify/FSEvents 返/private/var/...(symlink canonicalization)。涉及路径比较时必须canonicalize()。 notify-debouncer-minitimer 不受tokio::time::pause()控制,测试不确定。优先用notify裸接 + 自实现 tokio debounce。cdt-watchtests/file_watching.rs在 macOS flaky(FSEvents 时序依赖);just test单线程补跑也可能 5/6 timeout(不只是burst_of_writes_debounced)。判真回归:cargo test -p cdt-watch <test_name>单 case 跑——单跑能过即视为环境 flake,可继续 archive;改 watcher 行为时才纠结全套通过。tokio::time::pause测试的 send-advance 顺序:#[tokio::test(start_paused = true)]+tokio::time::advance时,send → advance直觉顺序会失败——loop task 尚未 poll,pending 仍空,advance不触发 flush。正确:tx.send(...) → yield_now (loop 收 event 写 pending) → advance(duration) → yield_now (sleep_until wake + flush)。需tokiodev-dep 带test-utilfeature。例:cdt-watch::watcher::tests5 个 debounce 单元测。- insta 快照接受:没装
cargo-insta就用INSTA_UPDATE=always cargo test -p <crate>;提交生成的tests/snapshots/*.snap。 - 同步解析入口:
cdt-analyze集成测试不引入 tokio——用cdt_parse::parse_entry_at(line, n)逐行解析 fixture,再跑dedupe_by_request_id。 - 后台服务的本机路径参数化:涉及
~/.claude/projects/的后台服务(notifier、未来的 history scanner)不要在函数内直接path_decoder::get_projects_base_path(),显式从构造器传projects_dir: PathBuf,否则集成测试会命中真实本机路径。
Serde / IPC 契约
- camelCase:所有面向前端(Tauri IPC)的 struct 必须
#[serde(rename_all = "camelCase")];enum 用rename_all_fields = "camelCase"给字段、rename_all = "snake_case"给 tag 值。例外:TokenUsage保持 snake_case(与 Anthropic API 原始格式一致)。 - 缩写处理:
auto_expand_ai_groups→autoExpandAiGroups(缩写当普通词,不会大写成AIGroups)。所有xx_ai_yy/xx_http_yy/xx_ssh_yy类字段在前后端两侧的 key 都按xxAiYy写;用 ipc_contract round-trip test 拦截大小写错配(历史 bug:autoExpandAIGroups与前端autoExpandAiGroups错配,toggle 历久不持久化)。 ConfigManager::update_<section>是手写白名单 dispatch:未列出 key 走_ => {}静默丢弃——加GeneralConfig/DisplayConfig/UpdaterConfig等字段时 SHALL 同步在crates/cdt-config/src/manager.rs::update_<section>加 match 分支(含 enum 字符串校验),并在crates/cdt-api/tests/ipc_contract.rs加update_config_<section>_<field>_round_trip测试(默认值 + 改写 + 改回 + 非法值拒绝)。否则 SettingsView 改完看似生效(前端乐观更新),重启后丢失。ContextInjectionserde 格式:#[serde(tag = "category", rename_all = "kebab-case")]是 internally-tagged,JSON 为{ "category": "claude-md", "id": "...", ... }(不是{ "ClaudeMd": {...} })。前端按inj.category字段 switch 匹配。AppConfig::keyboard_shortcuts: HashMap<String, String>(changeadd-keyboard-shortcut-system):camelCasekeyboardShortcuts;仅#[serde(default)]不加skip_serializing_if——empty HashMap SHALL 序列化为{}同时满足 IPC 字段必含 + 文件持久化简洁双约束。
chunk-building 语义契约(详 openspec/specs/chunk-building/spec.md)
is_meta / slash / interruption / teammate-message 四类消息的完整行为契约在 spec(Scenario 级覆盖)。port 专属踩坑:
is_meta过滤:跳过产UserChunk,但tool_result仍合并到 assistant buffer(spec 待补)- Slash 双产出 + 紧邻约束:slash user 消息既要产
UserChunk(UI 气泡)又要挂到下一个AIChunk.slash_commands;instructions来自is_meta=true + parent_uuid=slash.uuid的 follow-up;普通 user 消息产UserChunk前必须pending_slashes.clear()。TS 原版通过"只看紧邻前 UserGroup"实现,勿回退 - Interruption 分类:
[Request interrupted by user起首的 user 消息是MessageCategory::Interruption(非 hard noise),产SemanticStep::Interruption追加到前一 AIChunk。TS 侧曾当 hard noise 过滤,port 已反向修复,勿回退 - Teammate-message 多 block + 嵌入 AIChunk:
<teammate-message teammate_id="..." ...>body</teammate-message>的 user 消息不产UserChunk,转化为 N 条(一条 user msg 含多块时各产一条)TeammateMessage注入下一个 flush 出的AIChunk.teammate_messages。禁止用朴素text.find('>')+strip_suffix("</teammate-message>")解析——多 block 时会把所有块的 body 串成一段丢失。SHALL 用cdt-analyze::team::detection::parse_all_teammate_attrs(global regex)。回滚开关EMBED_TEAMMATES: bool在chunk::builder顶部
port 状态判定要查兜底
原版纯算法 ts(sessionStateDetection.ts / tokenFormatting.ts 等)只定结构性判定;最终落到 UI 的字段(isOngoing / messageCount / gitBranch...)常在 src/main/services/discovery/ProjectScanner.ts 等调用方叠加 mtime / count / threshold 兜底。port 时只看算法文件会漏,必须 grep 调用方"该字段被赋值的地方"——本仓 isOngoing 缺 5min STALE_SESSION_THRESHOLD_MS 的根因(详见 change session-ongoing-stale-check)。
subagent JSONL 跨 project_dir 关联当前不支持
Claude Code 父 process cwd 被切(worktree / EnterWorktree)后,subagent JSONL 写到 cwd 编码的 project_dir,不是父 session 的 project_dir。scan_subagent_candidates 当前只扫同 project,跨 dir 找不到 candidate → UI 上 Agent 工具显示为 ToolItem 无明细。普通用户不触发;dev / worktree 场景下会复现。关联线索(值得日后修):父 sessionId 在路径 <project>/<父sessionId>/subagents/agent-*.jsonl 目录名里 + agent-<id>.meta.json 含 {agentType, description} 可二次匹配父 session 的 Agent tool_use.input.description。已在 change worktree-support-and-cross-project-subagent 修复(scan_subagent_candidates_cross_project + CROSS_PROJECT_SUBAGENT_SCAN: bool gate)。
LocalDataApi 构造器扩展
注入新基础设施(FileWatcher、SSH pool 等)时新增 new_with_<xxx>() 构造器,不改 new() 签名——旧构造器被 crates/cdt-api/tests/*.rs 依赖,改签名会批量破坏集成测试。
cdt-core 核心 struct 加字段先 grep 全构造点
在 AIChunk / ToolExecution 等核心 struct 加非 Option 非 #[serde(default)] 字段会让 workspace 所有 Foo { ... } 构造点编译失败(典型 AIChunk 11 处、ToolExecution 9 处)。
工作流:
grep -rn "<StructName> {" crates --include="*.rs"先列全清单- 一轮 Edit 全部补齐再
cargo check——避免 PostToolUse clippy hook 在单文件 Edit 间反复阻塞 - 新字段尽量
Option<T> + #[serde(default, skip_serializing_if = "Option::is_none")]让加字段对老 fixture / 老前端无破坏
IPC vs HTTP 行为分叉
trait 加默认方法 fallback 到通用版本,LocalDataApi 自己 override 真版本——其他实现安全降级。例:DataApi::list_sessions_sync 默认调 list_sessions(骨架),LocalDataApi override 为同步全扫;list_sessions_sync 保留作为 trait fallback,但 axum HTTP route 已切换到与 IPC 共用的 list_sessions(骨架 + SSE push)实现(change unify-session-list-loading-strategy)。
后台任务 per-key 取消
触发新一轮后台扫描前需 abort 同 key 的旧任务时,用:
Arc<std::sync::Mutex<HashMap<K, ScanEntry { generation: u64, handle: AbortHandle }>>>
+ Arc<AtomicU64> 计数器abort 旧 + 分配 generation + tokio::spawn + insert 必须在同一把 sync lock 临界区内——tokio::spawn 不 await(只 enqueue task),sync lock 持有期间调用安全;分两次 lock acquire 会让并发 A/B 调用的 insert 互相覆盖,孤立 task 没人能 abort(PR #38 codex 二轮 race)。任务尾部 cleanup 时只在 entry.generation == my_generation 才 remove,避免旧 task 误删新 entry。例见 LocalDataApi::list_sessions 的 active_scans。
Windows 兼容硬约束(详 change windows-platform-support)
- home 解析:凡需
~/.claude/或用户 home 的代码都调cdt_discover::home_dir(),不要直接dirs::home_dir()。前者四级 fallbackHOME → USERPROFILE → HOMEDRIVE+HOMEPATH → dirs::home_dir(),对齐 TSpathDecoder.ts::getHomeDir;后者在 Windows 上若USERPROFILE未设但HOMEDRIVE+HOMEPATH设了会返 None,fallback 到.导致找不到数据目录。 - 绝对路径判断:凡校验/接受绝对路径的代码都用
cdt_discover::looks_like_absolute_path(&str),不要直接Path::is_absolute()。前者跨平台识别 POSIX/foo、WindowsC:[\/]...、UNC\\...;后者只认当前平台风格,Windows 上拒 POSIX(但 SSH 远端 / WSL / JSONLcwd字段都可能是 POSIX)。 - 路径编解码:
encode_path/decode_path/is_valid_encoded_path是跨 crate 唯一实现源,在cdt_discover::path_decoder。cdt-config::claude_md、cdt-api/tests/agent_configs.rs等调用方use cdt_discover::encode_path,禁止再写私有副本(历史有两份分叉副本,是 Windows auto-memory 找不到文件的根因之一)。 - Windows NTFS 目录名禁用字符:
< > : " / \ | ? *不能做文件/目录名。测试 fixture 里用encode_path(r"C:\Users\...")会产-C:-Users-...含:,Windows CI 上create_dir_all报 error 267 NotADirectory。凡需"真在磁盘上建 encoded project 目录"的集成测试(见crates/cdt-api/tests/agent_configs.rs),用纯字母/数字/-的 hardcoded 名(如-ws-my-proj),cwd 真实磁盘路径由 JSONLcwd字段提供,scanner 依赖字段不依赖 encoded 名与磁盘路径的对应。
clippy pedantic 速查
workspace 开启 pedantic,PostToolUse hook 每次 .rs 编辑后自动跑 clippy 报错。最常踩的:
doc_markdown(注释里标识符要反引号)cast_possible_wrap(u64 as i64→i64::try_from)uninlined_format_args(format!("{}", x)→format!("{x}"))map_unwrap_or(x.map(f).unwrap_or(d)→x.map_or(d, f))is_some_and(opt.map(f).unwrap_or(false)→opt.is_some_and(f))if_not_else(if x != y { A } else { B }→ 倒顺序)manual_let_else(match X { Ok(v)=>v, Err(_)=>return ... }→let Ok(v) = X else { return ... })
其余照 clippy 输出修即可。
Spec fidelity
- capability 实现以
openspec/specs/<cap>/spec.md为真相源 - 每个
SHALL至少一个测试 - TS 与 spec 冲突时(见
openspec/TS_BASELINE_DEVIATIONS.md)跟 spec,不跟 TS;在 change 的 tasks.md 里记下 deliberate divergence
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

