claw-code 源码详细分析:compat-harness——对接编辑器生态时,兼容层该吞掉哪些「历史包袱」?

张开发
2026/4/11 2:55:34 15 分钟阅读

分享文章

claw-code 源码详细分析:compat-harness——对接编辑器生态时,兼容层该吞掉哪些「历史包袱」?
涉及源码rust/crates/compat-harness/src/lib.rs、rust/crates/claw-cli/src/main.rsdump_manifests关联rust/crates/commands、rust/crates/tools、rust/crates/runtime的BootstrapPlan。1. compat-harness 在本仓库里实际做什么compat-harness是一个小而专的 crate在本地若存在「上游式」目录布局时读取旧版 TypeScript 入口附近的源文件用行级启发式抽出命令清单→commands::CommandRegistryBuiltin/InternalOnly/FeatureGated工具清单→tools::ToolRegistryBase/Conditional启动阶段草图→runtime::BootstrapPlanBootstrapPhase序列// 88:98:rust/crates/compat-harness/src/lib.rspubfnextract_manifest(paths:UpstreamPaths)-std::io::ResultExtractedManifest{letcommands_sourcefs::read_to_string(paths.commands_path())?;lettools_sourcefs::read_to_string(paths.tools_path())?;letcli_sourcefs::read_to_string(paths.cli_path())?;Ok(ExtractedManifest{commands:extract_commands(commands_source),tools:extract_tools(tools_source),bootstrap:extract_bootstrap_plan(cli_source),})}它不是通用「编辑器插件协议」实现它是把历史树形与源文件习惯挡在 Rust 产品之外的适配器让 CLI 能在--dump-manifests一类路径上对比/调试清单而不强迫主实现依赖 TS 构建链。2. 兼容层应吞掉的包袱按类别2.1 目录与命名约定写死的src/commands.ts等路径// 34:47:rust/crates/compat-harness/src/lib.rspubfncommands_path(self)-PathBuf{self.repo_root.join(src/commands.ts)}pubfntools_path(self)-PathBuf{self.repo_root.join(src/tools.ts)}pubfncli_path(self)-PathBuf{self.repo_root.join(src/entrypoints/cli.tsx)}吞掉什么编辑器/单仓/多仓布局里「命令聚合文件叫啥、放在哪」的历史决策。Rust 侧不必在claw主逻辑里到处join(src/commands.ts)只认UpstreamPaths与CLAW_CODE_UPSTREAM。2.2 仓库定位启发式谁在「上游根」// 57:86:rust/crates/compat-harness/src/lib.rsfnresolve_upstream_repo_root(primary_repo_root:Path)-PathBuf{letcandidatesupstream_repo_candidates(primary_repo_root);candidates.into_iter().find(|candidate|candidate.join(src/commands.ts).is_file()).unwrap_or_else(||primary_repo_root.to_path_buf())}fnupstream_repo_candidates(primary_repo_root:Path)-VecPathBuf{letmutcandidatesvec![primary_repo_root.to_path_buf()];ifletSome(explicit)std::env::var_os(CLAW_CODE_UPSTREAM){candidates.push(PathBuf::from(explicit));}forancestorinprimary_repo_root.ancestors().take(4){candidates.push(ancestor.join(claw-code));}candidates.push(primary_repo_root.join(reference-source).join(claw-code));candidates.push(primary_repo_root.join(vendor).join(claw-code));...}吞掉什么开发机上前端/插件仓与claw-code的相对位置../claw-code、vendor/、reference-source/显式覆盖环境变量「找不到就当 workspace 父目录」的失败兜底。主产品代码保持「我只问extract_manifest不关心你磁盘上怎么摆」。2.3 TS/JS 模块语法与 feature 门用正则级解析而非 TypeScript 编译器extract_commands识别INTERNAL_ONLY_COMMANDS数组块 →CommandSource::InternalOnlyimport ... from→Builtinfeature(...)且路径含./commands/→FeatureGated// 100:147:rust/crates/compat-harness/src/lib.rspubfnextract_commands(source:str)-CommandRegistry{letmutentriesVec::new();letmutin_internal_blockfalse;forraw_lineinsource.lines(){letlineraw_line.trim();ifline.starts_with(export const INTERNAL_ONLY_COMMANDS [){in_internal_blocktrue;continue;}...ifline.starts_with(import ){forimportedinimported_symbols(line){entries.push(CommandManifestEntry{name:imported,source:CommandSource::Builtin,});}}ifline.contains(feature()line.contains(./commands/){ifletSome(name)first_assignment_identifier(line){entries.push(CommandManifestEntry{name,source:CommandSource::FeatureGated,});}}}dedupe_commands(entries)}工具侧同理import且./tools/且名以Tool结尾 →BasefeatureTool→Conditional。吞掉什么bundler/feature-flag 的历史写法不把tsc/swc拉进 Rust 构建不完整语义只做清单级近似不做类型检查。代价与边界上游若改字符串模式extractor 会静默漂移——这正是「包袱应留在 compat crate 测试」的原因。2.4 CLI 启动链的字符串考古extract_bootstrap_plan从cli.tsx里搜子串推断存在哪些 fast path再拼BootstrapPlan// 182:217:rust/crates/compat-harness/src/lib.rspubfnextract_bootstrap_plan(source:str)-BootstrapPlan{letmutphasesvec![BootstrapPhase::CliEntry];ifsource.contains(--version){phases.push(BootstrapPhase::FastPathVersion);}ifsource.contains(startupProfiler){phases.push(BootstrapPhase::StartupProfiler);}ifsource.contains(--dump-system-prompt){phases.push(BootstrapPhase::SystemPromptFastPath);}ifsource.contains(--claude-in-chrome-mcp){phases.push(BootstrapPhase::ChromeMcpFastPath);}...phases.push(BootstrapPhase::MainRuntime);BootstrapPlan::from_phases(phases)}吞掉什么旧 CLI 里flag 名称与分支实现细节含品牌/历史命名如--claude-in-chrome-mcp「启动profiler / daemon / remote-control / bg session」等产品史在源码里的痕迹。Rust 自己的默认计划可单独维护BootstrapPlan::claw_default()与「从上游扫出来的计划」分离// 22:38:rust/crates/runtime/src/bootstrap.rspubfnclaw_default()-Self{Self::from_phases(vec![BootstrapPhase::CliEntry,BootstrapPhase::FastPathVersion,...BootstrapPhase::MainRuntime,])}学习点兼容层允许扫描结果与本仓 canonical 计划并存编辑器生态只依赖「能对比」不强迫合并成一份真相。2.5 调用侧把 I/O 失败与「无上游」挡在 CLI 子命令里// 469:482:rust/crates/claw-cli/src/main.rsfndump_manifests(){letworkspace_dirPathBuf::from(env!(CARGO_MANIFEST_DIR)).join(../..);letpathsUpstreamPaths::from_workspace_dir(workspace_dir);matchextract_manifest(paths){Ok(manifest){println!(commands: {},manifest.commands.entries().len());println!(tools: {},manifest.tools.entries().len());println!(bootstrap phases: {},manifest.bootstrap.phases().len());}Err(error){eprintln!(failed to extract manifests: {error});std::process::exit(1);}}}吞掉什么文件不存在、路径错、编码问题——不污染tools/commands主注册表路径只在 dump/对比工具里失败退出。3. 兼容层不该吞掉的东西边界不应塞进 compat-harness应落在何处真实工具执行、权限、沙箱runtimetools面向用户的稳定 CLI 契约claw-cli/commands与编辑器通信的 LSP/协议细节lsp 上层集成长期唯一的命令/工具真相源Rust registry 或生成管线而非永久依赖 TS 文本扫描compat-harness 的定位是桥接旧表面不是第二套 runtime。4. 测试策略无上游时优雅跳过// 311:321:rust/crates/compat-harness/src/lib.rs#[test]fnextracts_non_empty_manifests_from_upstream_repo(){letpathsfixture_paths();if!has_upstream_fixture(paths){return;}letmanifestextract_manifest(paths).expect(manifest should load);assert!(!manifest.commands.entries().is_empty());...}学习点公开 CI 未必有上游树兼容层测试允许缺席避免绑架贡献者环境。5. 小结compat-harness 应吞掉的历史包袱可概括为路径与仓库拓扑固定文件名、CLAW_CODE_UPSTREAM、vendor/reference 约定。TS 模块与 feature-flag 行文用轻量解析换清单不引编译器。旧 CLI 字符串与 flag 考古bootstrap fast path 的启发式还原。失败与无上游局限在 dump/对比入口不污染核心产品路径。不应吞掉执行语义、权限、会话、长期 registry 真相——那些应留在runtime/tools/commands的 definitive 实现中。

更多文章