涉及源码 :
src/command_graph.py、src/commands.py、src/main.py、src/reference_data/commands_snapshot.json、tests/test_porting_workspace.py。
1. 为什么需要"把命令关系写出来"
当 CLI 产品的命令数量上百时,开发者脑中通常会混着三种东西:
- 命令清单:有哪些名字?(help/补全层面)
- 命令分组:哪些是内置、哪些来自插件、哪些来自技能?(产品/治理层面)
- 命令依赖/路径:一个命令背后来自哪份模块、会触发哪些初始化?(排障/扩展层面)
如果这些关系只存在于人的记忆里:新增命令会破坏一致性、插件命令会悄悄"混进核心"、排障时没人能快速回答"这条命令属于哪一类"。
claw-code 的 Command Graph 做的是一个很实用的第一步:把"分段规则"代码化并输出为 Markdown,让分组从"口头共识"变成"可重复报告"。
2. Command Graph 在仓库里是什么形状
2.1 图不是邻接表,而是"分段后的三列清单"
python
# 9:26:src/command_graph.py
@dataclass(frozen=True)
class CommandGraph:
builtins: tuple[PortingModule, ...]
plugin_like: tuple[PortingModule, ...]
skill_like: tuple[PortingModule, ...]
def flattened(self) -> tuple[PortingModule, ...]:
return self.builtins + self.plugin_like + self.skill_like
def as_markdown(self) -> str:
lines = [
'# Command Graph',
'',
f'Builtins: {len(self.builtins)}',
f'Plugin-like commands: {len(self.plugin_like)}',
f'Skill-like commands: {len(self.skill_like)}',
]
return '\\n'.join(lines)
关键点:这里的 "Graph" 更像"分段视图(segmentation view)"。它并不表达"命令 A 调用命令 B",而是表达"命令集合在治理维度上如何切块"。对移植期/治理期来说,这类"粗粒度图"往往比邻接表更先落地、更常用。
2.2 分段规则:只用 source_hint 的子串判断
python
# 29:34:src/command_graph.py
def build_command_graph() -> CommandGraph:
commands = get_commands()
builtins = tuple(module for module in commands if 'plugin' not in module.source_hint.lower() and 'skills' not in module.source_hint.lower())
plugin_like = tuple(module for module in commands if 'plugin' in module.source_hint.lower())
skill_like = tuple(module for module in commands if 'skills' in module.source_hint.lower())
return CommandGraph(builtins=builtins, plugin_like=plugin_like, skill_like=skill_like)
含义 :把"来源分类"完全寄托在镜像清单里的 source_hint(归档路径提示)上。这样有两个强优势:
- 零侵入:不用修改每条命令条目,只要快照中已有路径,规则即可运行。
- 可解释 :任何人都能从
source_hint理解为什么被分到 plugin-like。
但也有一个成熟产品常见的演进方向:将来会把 plugin_like/skill_like 从"路径猜测"升级为 显式字段 (例如 origin: builtin|plugin|skill),否则路径命名变化会破坏分段。
3. 分段的数据源:命令宇宙来自镜像快照
CommandGraph 的输入来自 get_commands(),而 get_commands 的底层是 commands_snapshot.json:
python
# 22:37:src/commands.py
@lru_cache(maxsize=1)
def load_command_snapshot() -> tuple[PortingModule, ...]:
raw_entries = json.loads(SNAPSHOT_PATH.read_text())
return tuple(
PortingModule(
name=entry['name'],
responsibility=entry['responsibility'],
source_hint=entry['source_hint'],
status='mirrored',
)
for entry in raw_entries
)
PORTED_COMMANDS = load_command_snapshot()
get_commands 还提供了进一步过滤开关(是否包含 plugin/skill 命令),这和 CommandGraph 形成一对"治理旋钮":
python
# 60:66:src/commands.py
def get_commands(cwd: str | None = None, include_plugin_commands: bool = True, include_skill_commands: bool = True) -> tuple[PortingModule, ...]:
commands = list(PORTED_COMMANDS)
if not include_plugin_commands:
commands = [module for module in commands if 'plugin' not in module.source_hint.lower()]
if not include_skill_commands:
commands = [module for module in commands if 'skills' not in module.source_hint.lower()]
return tuple(commands)
学习点 :命令关系"从脑子到纸上"的第一步不是画大 UML,而是把命令清单变成 可枚举数据结构 (快照 → PortingModule),再在此之上叠加 确定性分段。
4. 纸面化输出:CLI 子命令 command-graph
main.py 直接把 as_markdown() 打到 stdout:
python
# 110:112:src/main.py
if args.command == 'command-graph':
print(build_command_graph().as_markdown())
return 0
这看似朴素,但对复杂 CLI 产品很关键:
- 输出是 Markdown:天然适合贴到 PR、Issue、设计文档里。
- 输出是统计而非长列表:避免 200+ 命令刷屏,把关注点放在"分段比例是否健康"。
同时测试把它钉成"长期存在的可观察面":
python
# 213:217:tests/test_porting_workspace.py
def test_command_graph_and_tool_pool_cli_run(self) -> None:
command_graph = subprocess.run([sys.executable, '-m', 'src.main', 'command-graph'], check=True, capture_output=True, text=True)
tool_pool = subprocess.run([sys.executable, '-m', 'src.main', 'tool-pool'], check=True, capture_output=True, text=True)
self.assertIn('Command Graph', command_graph.stdout)
self.assertIn('Tool Pool', tool_pool.stdout)
学习点:当你把"命令关系"做成一个 CLI 报表,再用测试保证它不被删掉,你就拥有了一个持续可用的"纸面仪表盘"。
5. 从"分段"走向"关系图"的可演进路线(成熟产品做法)
claw-code 目前的 CommandGraph 只回答"来源分段"。如果要让"命令关系"更像真正的图谱,同时仍保持可维护,可以按这个顺序演进:
- 把分段规则显式化 :在快照里加入
origin字段,避免依赖路径子串。 - 补齐"命令元信息" :如
aliases、flags、requires_trust、side_effects、category。 - 加入"阶段依赖"边 :例如每个命令声明
requires: [setup, deferred_init, mcp_prefetch],图就从"三列清单"升级为"阶段→命令"的 bipartite 图。 - 加入"子命令树"边 :把
/session list、/session export等归并成树状命名空间(Rust 侧已有更接近 slash command 的体系,可对照)。 - 输出多视图:统计视图(现在)、树视图(help/文档)、边视图(排障/影响面分析)。
这套路线的核心仍是同一个工程原则:先让关系成为数据,再让数据成为报告。
6. 小结
Command Graph把命令的"来源分段"从脑内共识变成了:快照数据 → 确定性规则 → Markdown 报表。- 图式化的价值不在于一次画大图,而在于让团队拥有一个可重复的"纸面仪表盘",并通过 CLI + 测试长期保留。
- 未来若要更像成熟 CLI 产品,可从"路径猜测分段"演进到"显式 origin + 阶段依赖 + 子命令树",逐步把更多关系搬到纸上。