涉及源码:
rust/crates/compat-harness/src/lib.rs、rust/crates/claw-cli/src/main.rs(dump_manifests);关联rust/crates/commands、rust/crates/tools、rust/crates/runtime的BootstrapPlan。
1. compat-harness 在本仓库里实际做什么
compat-harness是一个小而专的 crate:在本地若存在「上游式」目录布局时,读取旧版 TypeScript 入口附近的源文件,用行级启发式抽出:
- 命令清单→
commands::CommandRegistry(Builtin/InternalOnly/FeatureGated) - 工具清单→
tools::ToolRegistry(Base/Conditional) - 启动阶段草图→
runtime::BootstrapPlan(BootstrapPhase序列)
// 88:98:rust/crates/compat-harness/src/lib.rspubfnextract_manifest(paths:&UpstreamPaths)->std::io::Result<ExtractedManifest>{letcommands_source=fs::read_to_string(paths.commands_path())?;lettools_source=fs::read_to_string(paths.tools_path())?;letcli_source=fs::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{letcandidates=upstream_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)->Vec<PathBuf>{letmutcandidates=vec![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{letmutentries=Vec::new();letmutin_internal_block=false;forraw_lineinsource.lines(){letline=raw_line.trim();ifline.starts_with("export const INTERNAL_ONLY_COMMANDS = ["){in_internal_block=true;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结尾 →Base;feature+Tool→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{letmutphases=vec![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_dir=PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");letpaths=UpstreamPaths::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 | 应落在何处 |
|---|---|
| 真实工具执行、权限、沙箱 | runtime+tools |
| 面向用户的稳定 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(){letpaths=fixture_paths();if!has_upstream_fixture(&paths){return;}letmanifest=extract_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 实现中。