涉及源码 :
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序列)
rust
// 88:98:rust/crates/compat-harness/src/lib.rs
pub fn extract_manifest(paths: &UpstreamPaths) -> std::io::Result<ExtractedManifest> {
let commands_source = fs::read_to_string(paths.commands_path())?;
let tools_source = fs::read_to_string(paths.tools_path())?;
let cli_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 等路径
rust
// 34:47:rust/crates/compat-harness/src/lib.rs
pub fn commands_path(&self) -> PathBuf {
self.repo_root.join("src/commands.ts")
}
pub fn tools_path(&self) -> PathBuf {
self.repo_root.join("src/tools.ts")
}
pub fn cli_path(&self) -> PathBuf {
self.repo_root.join("src/entrypoints/cli.tsx")
}
吞掉什么 :编辑器/单仓/多仓布局里「命令聚合文件叫啥、放在哪」的历史决策。Rust 侧不必在 claw 主逻辑里到处 join("src/commands.ts");只认 UpstreamPaths 与 CLAW_CODE_UPSTREAM。
2.2 仓库定位启发式:谁在「上游根」
rust
// 57:86:rust/crates/compat-harness/src/lib.rs
fn resolve_upstream_repo_root(primary_repo_root: &Path) -> PathBuf {
let candidates = 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())
}
fn upstream_repo_candidates(primary_repo_root: &Path) -> Vec<PathBuf> {
let mut candidates = vec![primary_repo_root.to_path_buf()];
if let Some(explicit) = std::env::var_os("CLAW_CODE_UPSTREAM") {
candidates.push(PathBuf::from(explicit));
}
for ancestor in primary_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
rust
// 100:147:rust/crates/compat-harness/src/lib.rs
pub fn extract_commands(source: &str) -> CommandRegistry {
let mut entries = Vec::new();
let mut in_internal_block = false;
for raw_line in source.lines() {
let line = raw_line.trim();
if line.starts_with("export const INTERNAL_ONLY_COMMANDS = [") {
in_internal_block = true;
continue;
}
...
if line.starts_with("import ") {
for imported in imported_symbols(line) {
entries.push(CommandManifestEntry {
name: imported,
source: CommandSource::Builtin,
});
}
}
if line.contains("feature('") && line.contains("./commands/") {
if let Some(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:
rust
// 182:217:rust/crates/compat-harness/src/lib.rs
pub fn extract_bootstrap_plan(source: &str) -> BootstrapPlan {
let mut phases = vec![BootstrapPhase::CliEntry];
if source.contains("--version") {
phases.push(BootstrapPhase::FastPathVersion);
}
if source.contains("startupProfiler") {
phases.push(BootstrapPhase::StartupProfiler);
}
if source.contains("--dump-system-prompt") {
phases.push(BootstrapPhase::SystemPromptFastPath);
}
if source.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()),与「从上游扫出来的计划」分离:
rust
// 22:38:rust/crates/runtime/src/bootstrap.rs
pub fn claw_default() -> Self {
Self::from_phases(vec![
BootstrapPhase::CliEntry,
BootstrapPhase::FastPathVersion,
...
BootstrapPhase::MainRuntime,
])
}
学习点 :兼容层允许 扫描结果 与 本仓 canonical 计划 并存;编辑器生态只依赖「能对比」,不强迫合并成一份真相。
2.5 调用侧:把 I/O 失败与「无上游」挡在 CLI 子命令里
rust
// 469:482:rust/crates/claw-cli/src/main.rs
fn dump_manifests() {
let workspace_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let paths = UpstreamPaths::from_workspace_dir(&workspace_dir);
match extract_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. 测试策略:无上游时优雅跳过
rust
// 311:321:rust/crates/compat-harness/src/lib.rs
#[test]
fn extracts_non_empty_manifests_from_upstream_repo() {
let paths = fixture_paths();
if !has_upstream_fixture(&paths) {
return;
}
let manifest = 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 实现中。