ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

claw CLI 源码地图:解读 rusty-claude-cli 的架构、输出契约与黑盒测试策略

claw CLI 源码地图:解读 rusty-claude-cli 的架构、输出契约与黑盒测试策略 claw CLI 源码地图解读 rusty-claude-cli 的架构、输出契约与黑盒测试策略【免费下载链接】claw-codeAn agent-managed museum exhibit, built in Rust with Gajae-Code / LazyCodex — developed and maintained with no human intervention.项目地址: https://gitcode.com/gh_mirrors/claudeco/claw-codeclaw 是 claw-code 项目提供的核心命令行二进制由rusty-claude-clicrate 构建而来。本文以 rust/crates/rusty-claude-cli/AGENTS.md 为骨架结合 src/main.rs 源码与tests/测试套件完整还原claw的单文件巨型实现、25 个子命令的调度模型、text|json双输出契约、错误分类体系与黑盒测试策略。读完本文你将掌握claw的内部结构、每个子命令的 JSON 输出字段约定以及新增或修改命令时必须同步维护的测试红线。一、OVERVIEW一个 crate、一个二进制、约 25 个子命令rusty-claude-cli是 workspace 中的一个 bin crate唯一的产出物是claw二进制。这一点在 Cargo.toml 中有明确的[[bin]]声明[[bin]] name claw path src/main.rs它把整个系统的能力“焊接”在一起apiAnthropic / OpenAI-compatible 提供方客户端、消息与 SSE 流式解析、prompt cacheruntime会话Session、对话运行时ConversationRuntime、配置加载、权限策略、MCP 服务器管理、stale-base 预检tools工具执行器execute_tool、全局工具注册表GlobalToolRegistry、--allowedTools白名单规范化commands/agents、/mcp、/plugins、/skills等斜杠命令分派与resume命令支持plugins插件钩子PluginHooks、插件管理器与注册表终端交互层crossterm 0.28终端控制、rustyline 15行编辑/补全、syntect 5语法高亮、pulldown-cmark 0.13Markdown 渲染。值得强调的架构取向是参数解析是手写的没有引入 clap。parse_args是一个约 800 行的手工 token 循环虽然“原始”但它带来了完全可控的错误提示、别名解析时机与--output-format预扫描能力后文会逐一展开。命名注意crate 名为rusty-claude-cli二进制名为claw二者不一致是官方在 ANTI-PATTERNS 中明确记录的“坑”在路径书写与测试宏env!(CARGO_BIN_EXE_claw)中要格外小心。二、main.rs 源码地图一份约 19,800 行的文件如何组织src/main.rs当前约 19,800 行全部入口逻辑集中于此。AGENTS.md 提供了一份权威的地标表结合源码逐段核对如下行区间段落源码验证73–330Provenance / 模型溯源类型ModelSource、ModelProvenance、PermissionModeSource与构建常量src/main.rs330–682错误分类classify_error_kind、JSON/text 错误输出src/main.rs774–994全局 flags、--cwd/-C剥离、stdin 管道src/main.rs995–1158run()单个扁平 match 分派所有CliAction变体src/main.rs1162–1280CliAction枚举25 个结构变体各自携带output_formatsrc/main.rs1312–1477输出格式机制OnceLock静态量与重复 flag 追踪src/main.rs1478–2272parse_args约 800 行手动 token 循环src/main.rs2542–3389各子命令的 sub-parser见parse_args内部2902–3389模型 / 权限 / allowed-tools 解析src/main.rs3390–4696Doctor 子系统十余个check_*_health函数src/main.rs4697–6062Manifests、bootstrap-plan、system-prompt、version、resume_sessionsrc/main.rs5459–7047StatusContext、BinaryProvenance、broad-cwd 策略、stale-base 预检—7048–9423run_repl交互循环LiveCli、流式、heartbeat、HookAbortMonitorsrc/main.rs9424–14264快照打印器session-list、status、sandbox、models、help topics、acp、run_initL11027、run_exportL11715、print_helpL14055src/main.rs14266–19831文件内mod tests 3 个更小的测试模块其中一个内嵌 Python MCP fixture—这份地标表本身就是最好的导航工具当你在claw上报问题或阅读报错时可以先根据错误信息猜测属于哪一段再直接跳到对应行区间阅读。2.1 顶层结构与程序入口main → run文件顶部是 crate 级#![recursion_limit 256]以及 13 个#![allow(...)]详见反模式一节随后声明四个兄弟模块init.rs、input.rs、render.rs、setup_wizard.rs。程序入口的职责划分非常清晰main()L330只是“错误外壳”调用run()若返回错误则按--output-format决定输出 JSON 还是 text 错误随后std::process::exit(1)run()L995才是真正的调度核心预扫描 argvraw_args_request_json_output、剥离全局--cwd、调用parse_args得到CliAction再用一个扁平 match 将每个变体分派到对应的打印/执行函数。2.2 模型溯源flag / env / config / default 四级来源ModelSourceL79-L101记录模型字符串来自何处enum ModelSource { Flag, // 显式 --model / --model CLI 参数 Env, // 运行时模型环境变量 Config, // .claw.json / .claw/settings.json 中的 model 键 Default, // 编译期内置 DEFAULT_MODEL 兜底 }配套的ModelProvenanceL103-L115还额外记录原始输入、别名展开目标与提供模型的环境变量名。这套设计issue #148是为了让claw status的 JSON/text 输出能审计“用户传入的--model是否被真正采纳还是回退到了 env/config/default”下游 Agent 无需重新读取 argv 即可验证参数是否生效。编译期默认模型为anthropic/claude-opus-4-7DEFAULT_MODELL73。内置别名在resolve_model_aliasL2902中定义opus anthropic/claude-opus-4-7, sonnet anthropic/claude-sonnet-4-6, haiku anthropic/claude-haiku-4-5-20251213,而resolve_model_alias_with_configL2914先查用户配置中的自定义别名再回退到内置表是所有模型字符串进入 provider 前的统一入口。validate_model_syntaxL2925会在解析期拦截非法模型名拒绝空串、含空格字符串当配置了OLLAMA_HOST时放宽校验以支持qwen3:8b这类本地模型命名对gpt-*、qwen、grok等拼错格式还会给出带openai/、DASHSCOPE_API_KEY、xai/的定向提示。2.3 错误分类体系从 prose 到稳定的 snake_case 契约classify_error_kindL437 起是这套 CLI 最有特色的设计之一。它把错误消息前缀/关键词映射成稳定的snake_casekind 令牌使下游脚本和 Agent 可以switch而不是 regex-scrape 人类文案。可验证的分类包括会话类session_not_found、no_managed_sessions、session_path_is_directory#787、session_load_failed、legacy_session_no_workspace_binding#780必须排在通用session_load_failed之前参数类missing_argument、missing_flag_value、invalid_flag_value、invalid_model、invalid_model_syntax、invalid_cwd、invalid_output_path、invalid_output_format、invalid_tool_name、invalid_permission_mode、cli_parseAPI 类api_auth_error401/#781、api_rate_limit_error429/#781、api_http_error配置类malformed_mcp_config、config_parse_error#763其他missing_credentials、missing_manifests、missing_worker_state、unknown_slash_command、empty_prompt、interactive_only、plugin_not_found、unsupported_acp_invocation等。当--output-format json生效时错误以 JSON 输出到stdoutL337-L409这是 #819/#820/#823 确立的契约机器消费者从 stdout 第 0 字节即可解析失败字段固定为{ type: error, kind: snake_case_kind, status: error, error_kind: snake_case_kind, error: short reason, message: short reason, action: abort, hint: remediation hint, exit_code: 1 }其中hint支持从\n分隔的消息内联提取缺失时由fallback_hint_for_error_kind按 kind 兜底#781invalid_cwd、invalid_output_path、invalid_output_format、invalid_tool_name、missing_argument等特殊 kind 还会附加path/reason/value/expected/available/tool_aliases等结构化字段。文本模式下错误则统一走 stderr并带[error-kind: ...]前缀#156方便 stderr 观察者同样免去正则解析。三、全局 Flags 与--cwd剥离机制在正式进入parse_args之前run()先调用split_global_cwd_argsL774-L837对 argv 做一次“预过滤”把--cwd path、-C path、--directory path及其内联形式从参数流中抽出并立即env::set_current_dirapply_global_cwdL894-L899。这意味着--cwd可以出现在任何位置且对后续所有配置加载、git 探测、会话目录解析立即生效。全局 flag 分为三类均由专门的谓词函数判定带值 flagglobal_flag_takes_valueL839--model、--output-format、--permission-mode、--base-commit、--reasoning-effort、--allowedTools、--allowed-tools无值 flagglobal_flag_without_valueL862--help/-h、--version/-V、--dangerously-skip-permissions/--skip-permissions、--compact、--allow-broad-cwd、--print、--acp/-acp内联值 flagglobal_flag_is_value_inlineL852上述带值 flag 的--flagvalue写法。validate_global_cwdL879-L892会区分Empty、NotADirectory、NotFound三种失败原因并给出不同提示。同样在run()中完成的还有 stdin 管道集成read_piped_stdinL906仅在 stdin 非 TTY 时读取TTY 下返回None保证交互式权限确认不会因 stdin 被消费而全部拒绝merge_prompt_with_stdinL926把管道内容以空行分隔追加到 prompt 之后让模型先看到 prompt 再看到管道上下文。值得注意的细节是管道 stdin 仅在PermissionMode::DangerFullAccess全自动无人值守下被消费L1077-L1081。其余模式下 stdin 必须保留给交互式权限确认器否则确认逻辑的read_line()会直接读到 EOF 从而拒绝所有请求——这是从源码中能直接读到的安全取舍。四、run()调度与CliAction25 个子命令一览run()内是一个庞大的扁平 matchL1006-L1158把parse_args返回的CliAction逐一映射到执行函数。CliAction枚举L1162-L1280共 25 个结构变体每个变体都携带output_format: CliOutputFormat变体语义对应执行DumpManifests导出 manifests 目录dump_manifestsBootstrapPlan打印引导计划print_bootstrap_planAgents/Mcp/Skills/Plugins子系统的斜杠命令分派LiveCli::print_*PrintSystemPrompt打印 system promptprint_system_promptVersion版本与构建溯源print_versionL4986SessionList/ResumeSession会话列表 / 恢复会话run_session_list/resume_sessionStatus状态快照含模型溯源print_status_snapshotSandbox沙箱状态快照print_sandbox_status_snapshotPrompt单轮 prompt支持--compact、stdinLiveCli::run_turn_with_outputDoctor环境自检run_doctorAcpACP 状态print_acp_status随后exit(2)Stateworker 状态run_worker_stateInit初始化仓库幂等run_initL11027Setup引导向导run_setupConfig/Diff本地只读内省#146render_config_report/render_diff_reportModels模型列表print_modelsExport导出会话run_exportL11715Repl交互式 REPLrun_replL7048HelpTopic/Help分主题帮助 / 总帮助print_help_topic/print_helpL14055权限模式由parse_permission_mode_arg解析取值固定为read-only|workspace-write|danger-full-access缺失值错误信息 L1577 可证--dangerously-skip-permissions/--skip-permissions直接映射为DangerFullAccess。此外normalize_permission_modeL11077还为兼容 Claw Code 生态把default/plan→read-only、acceptEdits/auto→workspace-write、dontAsk/bypassPermissions→danger-full-access。--base-commit会校验 hex SHA7-64 位L1608-L1616issue #122--reasoning-effort只接受low|medium|highL1624-L1635-p只消费一个 token 作为 prompt#755确保后续--model/--output-format不会被吞进 prompt 字符串L1660-L1667。未知参数与拼写错误会收到 Levenshtein 距离建议CONVENTIONS 明示。4.1 输出格式机制OnceLock 静态量与 env 覆盖CliOutputFormat只有Text/Json两种取值L1312-L1316。格式选择的来源被显式建模为OutputFormatSource::{Default, Env, Flag}L1318-L1333连同原始字符串与“被覆盖的历史值”一起存进OutputFormatSelection并通过OnceLockMutex...静态量L1354-L1356在进程内共享。关键行为环境变量CLAW_OUTPUT_FORMAT可在无 flag 时把默认格式切换为 jsonoutput_format_selection_from_envL1428预扫描raw_args_request_json_outputL1398-L1426在参数正式解析前扫描 argv遇到--即停若末尾的--output-format值或CLAW_OUTPUT_FORMAT非text则run()会先调用runtime::suppress_config_warnings_for_json_mode()#824避免配置弃用警告污染 JSON stdout重复 flag同一个--output-format出现多次时向 stderr 打印warning: --output-format specified multiple times; using last value ...并把旧值记入overriddenL1440-L1458issue #468 的重复 flag 溯源重复的--model、--permission-mode同样被DUPLICATE_FLAGS静态量记录非法值CliOutputFormat::parseL1459不区分大小写地接受text/json其余值返回invalid_output_format错误并提示Expected: text, json。五、Doctor 子系统十余个 check 组成的自检矩阵claw doctorrun_doctor调用点位于 L3713-L3724 附近执行一组健康检查每个检查返回一个DiagnosticCheckL3411-L3421字段包括name、levelOk/Warn/FailL3390-L3409、summary、details、data与稳定补救提示hint#778。源码中可核对到 12 个检查函数check_auth_healthL3832认证凭据是否就绪check_base_url_healthL3961API base URL 配置check_config_healthL4004配置文件可解析性check_mcp_validation_healthL4112MCP 服务器配置校验check_hook_validation_healthL4162钩子配置校验check_permission_healthL4207权限模式是否安全可用check_install_source_healthL4269安装来源完整性check_workspace_healthL4299工作区状态check_memory_healthL4419记忆/上下文目录check_boot_preflight_healthL4475启动预检check_sandbox_healthL4568沙箱状态check_system_healthL4641系统环境。DiagnosticCheck::json_valueL3453给出了机器可读形态检查名被降为稳定的 snake_caseid#704details[]从旧版 prose 演进为结构化的{key, value}对象#701同时保留details_prose[]供旧调用方兼容——这是“结构化优先、兼容并存”的典型实现。Warn/Fail级别还会携带可机读的hint字段#778让自动化巡检可以直接读取补救方案而无需解析散文。六、REPL 与快照打印器交互与非交互两条路径6.1 run_replLiveCli 交互循环run_replL7048-L7114是交互式主循环核心结构是LiveCliL7135持有 model、权限、system prompt、BuiltRuntime与会话句柄。循环行为包括启动时打印 banner 与连接模型行每次迭代通过input::LineEditorrustyline 驱动读取输入并动态刷新补全候选repl_completion_candidates/exit、/quit退出前调用cli.persist_session()持久化会话输入先走SlashCommand::parse分派斜杠命令未命中则尝试 bare-word skill 分派首 token 命中已知 skill 名时按/skills input处理ROADMAP #36最后才作为普通 prompt 转发给 LLM每一轮都会记录 prompt 历史record_prompt_history。6.2 快照打印器与 run_init / run_export9424–14264 行集中了“纯本地、非交互”的快照打印器session-list、status、sandbox、models、help topics、acp以及run_initL11027与run_exportL11715。run_init调用 init.rs 的initialize_repo生成初始化报告并输出init_json_valueL11043status恒为ok当前无失败路径但通过already_initialized、created/updated/skipped/partial/deferred各工件状态桶与hint“Workspace already initialised…” 或 “Review and tailor CLAUDE.md…”让编排方无需子串匹配即可判断幂等场景#783/#436。print_versionL4986则输出完整的构建溯源version、git_sha、git_sha_short、is_dirty、branch、commit_date、commit_timestamp、rustc_version、target、build_date、executable_path。claw diffrender_diff_reportL11088会先执行git rev-parse --is-inside-work-tree判断是否在 git 仓库内避免git diff --cached在非 git 目录下的误导性报错再分别取 staged 与 unstaged 差异工作区干净时输出clean working tree。6.3 build.rs构建期注入的 provenance 元数据build.rs 通过cargo:rustc-env注入GIT_SHA、GIT_SHA_SHORT、GIT_DIRTY、GIT_BRANCH、GIT_COMMIT_DATE、GIT_COMMIT_TIMESTAMP、RUSTC_VERSION、TARGET与BUILD_DATESOURCE_DATE_EPOCH支持下实现可复现构建并通过cargo:rerun-if-changed监听.git/HEAD、refs、index使 git 状态变化触发重新注入。这些常量在 src/main.rs 以option_env!读取最终呈现在claw version与claw status中。七、CONVENTIONS全 CLI 的统一工程约定AGENTS.md 用一节明示了所有子命令必须遵守的约定源码中均可一一印证每个子命令都支持--output-format text|json且可被环境变量覆盖——CliAction每个变体都携带output_format字段L1162-L1280CLAW_OUTPUT_FORMAT提供 env 兜底raw_args_request_json_output预扫描 argv——在配置加载前抑制 JSON 模式下的 stderr 配置警告#824L1000-L1003每条输出路径都是双渲染器人类可读 text 与结构化*_json如version_json_value、init_json_value、plugin_command_json未知 flag / 拼写错误给出 Levenshtein 距离建议注释携带 issue 号#824、#146、#781、#787 等——这是团队追踪已知问题的工作方式读代码时等于读变更历史main()只是错误外壳真正工作都在run()错误输出必含五字段status、error_kind、action、hint、exit_codeclassify_error_kind把消息前缀映射为 snake_case kindJSON 错误走 stdout、text 错误走 stderr#819/#820/#823 契约兄弟模块职责init.rs仓库初始化、input.rsrustyline 行编辑、render.rsMarkdownStreamState、Spinner、syntect、setup_wizard.rs引导向导。八、ANTI-PATTERNS官方承认的代码债务与红线这份 AGENTS.md 罕见地“自曝”了代码库的债务这是接手者必须了解的约束文件顶部 13 个 crate 级#![allow(...)]抑制dead_code、unused_imports、clippy::too_many_lines等 13 个 lintsrc/main.rs。AGENTS.md 明确标注“Legacy. Do NOT extend this list.”——不要新增被禁 lint9 个函数带#[allow(clippy::too_many_lines)]如parse_argsL1478是历史遗留的容忍不是新增代码的许可证包名/二进制名不匹配crate 是rusty-claude-cli二进制是claw。涉及路径与测试宏时要反复核对。九、TESTS6 个黑盒测试文件钉死输出契约claw的测试全部是黑盒风格位于tests/下的 6 个集成测试文件通过env!(CARGO_BIN_EXE_claw)启动真实二进制在AtomicU64计数器生成的唯一临时目录中运行不与内部函数耦合。这是这套代码能承受大规模重构的根本保障。文件覆盖范围output_format_contract.rs约 105 个测试5,986 行为每一个子命令钉死kind/status/actionJSON 契约。添加或修改任何命令输出时必须同步更新。resume_slash_commands.rsresume 与斜杠命令行为cli_flags_and_config_defaults.rsflag 解析、配置文件默认值compact_output.rs紧凑输出模式compact_repl_panic.rs嵌套运行时 panic 回归mock_parity_harness.rs针对 mock-anthropic-service 的场景驱动测试由 rust/mock_parity_scenarios.json 驱动以 output_format_contract.rs 的实测为例第一个测试help_emits_json_when_requested断言claw --output-format json help的 JSON 中kind help、status ok、message含Usage:且必须包含--cwd PATH, -C PATH, --directory PATH的全局 cwd 覆盖说明issue #429——契约测试连帮助文案的关键措辞都钉死了。export_help_emits_bounded_json_when_requested_384则校验claw export --help --output-format json的usage字符串精确等于claw export [--session id|latest] [--output path] [--output-format format]同时确保 JSON 模式下message字段不存在bounded 输出避免把大段帮助文本灌进结构化结果。mock_parity_harness.rs 则拉起MockAnthropicService用ScenarioCase数组streaming_text、read_file_roundtrip、grep_chunk_assembly、write_file_allowed、write_file_denied……在不同permission_moderead-only/workspace-write与allowed_tools组合下验证端到端行为场景清单由仓库根的 rust/mock_parity_scenarios.json 描述实现位于 rust/crates/mock-anthropic-service。十、总结读这份源码地图能获得什么修改claw任何子命令的输出先看 output_format_contract.rs 是否已有该命令的契约测试改完必须同步更新这是 repo 官方在 AGENTS.md 中明示的硬性要求排查claw报错按[error-kind: ...]或 JSONerror_kind字段对照classify_error_kind的分支即可确定错误来自会话、参数、API 还是配置子系统新增全局 flag必须同时接入global_flag_takes_value/global_flag_is_value_inline/global_flag_without_value三个谓词与split_global_cwd_args的预过滤否则--cwd剥离与 JSON 预扫描会漏掉它保持 JSON/text 双输出一致遵循“每条路径双渲染器”约定并在 JSON 模式下把错误交给 stdout、警告交给被抑制的 stderr。rusty-claude-cli用一份约 2 万行的单文件承载了完整的 Agent CLI 表面又用手写参数解析、稳定错误 kind、text|json双渲染与 6 个黑盒契约测试把这套表面牢牢钉住——AGENTS.md 既是导航地图也是约束清单本文即是对这份地图的逐段展开与源码印证。【免费下载链接】claw-codeAn agent-managed museum exhibit, built in Rust with Gajae-Code / LazyCodex — developed and maintained with no human intervention.项目地址: https://gitcode.com/gh_mirrors/claudeco/claw-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表