docs(cli): classify the agent-directory command in the manual - #4613
Conversation
`loopx agent-directory` shipped in #4544 without a manual-visibility decision, so `examples/cli-help-manpage-smoke.py` fails on every `main` head with `{'unclassified': ['agent-directory']}` and takes the full public smoke sweep down with it. The command produces the local, goal-scoped peer agent directory this host can hand work to, so it belongs with the other maintainer-facing agent commands instead of the help-only surface. `man/loopx.1` is regenerated from `render_manpage()`, because the smoke asserts the checked-in manual equals the renderer output. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
examples/cli-help-manpage-smoke.py 要求每个顶层 loopx 命令要么进入手册目录,要么显式声明只存在于命令自身的 help 面。PR #4544(commit 818db75,分发点在 loopx/cli.py:773)新增了 loopx agent-directory 却没有做这个可见性决定,于是此后每个 main head 都在这条断言上失败:{'unclassified': ['agent-directory']}。这正是 run 35164002565 中 full public smokes 分片 0/2/3/4 全红的原因,与被它看起来挡住的那些 PR 无关。
改动思路
不放宽断言,而是补上这个决定:agent-directory 的职责是产出本机可派活的、goal 作用域的 peer agent 目录,因此它和 register-agent 同属“Maintainer and adapter commands”,而不是 help-only 面。手册文本由渲染器生成,而 smoke 断言签入的 man/loopx.1 必须等于渲染结果,所以两者一起更新。
具体改动
loopx/help_surface.py 增加一条目录项(命令 + 用途);man/loopx.1 按渲染器重新生成,只多出对应的三行(.TP、粗体命令、用途)。除此之外没有其它改动。
对主干的风险
只动公开 help 面,单用途、可回滚。tests/control_plane/test_cli_output_budget.py 的预算只覆盖 agent 面行(Start here、Daily operator commands、loopx heartbeat-prompt),维护者分组新增行不进入该预算,22 项测试通过;默认 help 行数上限、命令引用面断言均不变。改动前可在本分支复现同一条 unclassified 失败,改动后 cli-help-manpage-smoke ok。
我的整体评价
修的是 main 上真实存在的公开 smoke 红灯,改动面最小且没有绕过任何断言;分类位置与命令用途一致,证据是先复现后修复。建议在该 head 的必过检查转绿后合并,并据此把 Todo#todo_28e0823d6392 的 public smoke 修复范围收敛为已修复状态。
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Head: f02b3fa
English verdict: APPROVE
…irectory-20260917 Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
examples/cli-help-manpage-smoke.py 要求每个 top-level loopx 命令要么在 manual 里有代表,要么被显式留在 command-specific help 面上。agent-directory(#4544 引入,loopx/cli.py:773 分派)从来没有被归类,于是从那次合并开始,每一个 main head 都挂在这条断言上:
AssertionError: {'unclassified': ['agent-directory'], 'stale_manual': [], 'stale_help_only': []}
今天 main 的整轮 public smoke(shards 0/2/3/4)就停在这里,看起来像在拦一堆与它无关的 PR,实际原因只有这一个未分类命令。
改动思路
做可见性决定,而不是放松断言:把 agent-directory 归到 manual 的 maintainer-facing agent 命令组,挨着 register-agent——理由是这个命令的职责正是产出这台 host 可以派活的、本地 goal-scoped 的 peer agent 目录。man/loopx.1 由 render_manpage() 重新生成;smoke 断言签入的 manual 等于渲染输出,所以两者必须一起动,而本 PR 是一起动的。
具体改动
loopx/help_surface.py:新增agent-directory的 manual 条目(四行),归入 agent 命令组。man/loopx.1:从渲染函数重新生成。- 没有别的源改动,也没有把任何命令加进跳过列表。
对主干的风险
把命令放进 manual 会让人在帮助页里看到它,这正是本 PR 的目的;需要留意的是 agent-facing 行预算,因为 manual 变长有时会顺带撑大那些行。本次复审在更新后的 head 上实测(该 head 只是把 origin/main 合进来,两个被评审文件在旧 head 与新 head 之间逐字节一致,已用内容哈希核对):
uv run --extra test python examples/cli-help-manpage-smoke.py→cli-help-manpage-smoke ok- 该改动落在 maintainer 分组,不在
tests/control_plane/test_cli_output_budget.py预算的 agent-facing 行里(PR 记录该套件 22 passed 不变)
另需如实记一笔:这条分支上一次跑 CI 时 stage2c (mutants 0) 出现过一个存活 mutant(fence_unshared_state_lock),当时在 main 的两次同 job 运行里没有复现;本次复审以新 head 的必过检查结果为准。
我的整体评价
用一个明确的可见性决定消掉“自 #4544 起 main 每个 head 都红”的这个原因,并且是改事实而不是改断言,方向正确。
合并边界这条要说清楚:本 PR 改的是 loopx/help_surface.py,属于控制面/CLI 契约面,按仓库当前规则不由作者自合并——本 head 的审查记录只用于把决定交给维护者,实际合并由维护者执行。
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Head: d10d7f6
English verdict: APPROVE
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewed exact head: c13df19dae7f5e4e51224ebc08dba4391db74992 (re-review after merging origin/main; previous review covered the pre-merge content).
loopx pr-review --check-result on the matching packet returned ok: true with no approval_blockers; all 19 evidence rows for this control-plane plan are verified.
动机
loopx agent-directory 是真实的顶层命令(实测它存在于 130 个顶层命令中,loopx agent-directory --help 正常),但既不在手册命令组里,也不在显式 help-only 集合里。examples/cli-help-manpage-smoke.py::assert_top_level_commands_are_explicitly_classified 要求 parser 命令集合 == 手册命令集合 ∪ help-only 集合,于是 main 上这条必过 shard 0 报:
AssertionError: {'unclassified': ['agent-directory'], 'stale_manual': [], 'stale_help_only': []}
这是 main 必过套件里最后一条红项,不修则所有开放 PR 都拿不到必过检查全绿。
这个 PR 的价值:它补的是真实存在的帮助面缺口(命令有实现、手册无分类),而且没有用「少写文档」的方式换取绿灯。
改动思路
关键在于选择哪一类归属。smoke 的一致性等式只允许两种落点:进手册的 COMMAND_GROUPS,或进显式的 MANPAGE_COMMAND_HELP_ONLY。把命令丢进后者虽然同样能让集合等式成立,却会让它从手册正文消失——等于用削文档换绿灯,所以选择按既有条目形状补进手册组;man/loopx.1 是被 scripts/render-manpage.py 从同一张表渲染出来的产物,因此必须同步更新。
具体改动
loopx/help_surface.py:262:在「运行与编排」组追加一条{'command': 'loopx agent-directory', 'purpose': 'Produce the local, goal-scoped peer agent directory this host can hand work to.'},与相邻条目(register-agent、lark-kanban、presentation)形状完全同构。man/loopx.1:追加对应的.TP段落(转义与生成器一致)。
两个文件之外零改动:不改命令实现、不改 smoke 断言、不新增 help-only 例外。
关键代码讲解
def manpage_top_level_commands() -> frozenset[str]:
commands = {"commands"}
for group in COMMAND_GROUPS:
for entry in group.get("commands", []):
command = str(entry.get("command") or "")
commands.update(re.findall(r"(?:^| / )loopx ([a-z0-9][a-z0-9-]*)", command))分类不是靠子串匹配判定的,而是靠集合相等:正则只负责从既有条目文本里提取命令 id,归属由「在哪张表里」决定。因此唯一真实的书写风险是形态(例如写成下划线就解析不出来),这一点由 smoke 实测覆盖。
def assert_checked_in_manpage_surface() -> None:
assert (REPO_ROOT / "man" / "loopx.1").read_text(encoding="utf-8") == render_manpage()man page 必须与渲染结果逐字节相等,所以「只改表不改 man page」会立刻失败——这也是我这次单独复核的地方:实测 checked == render_manpage() 为 True。
对主干的风险
纯帮助面数据改动,风险面只有一个:分类表与 man page 可能不同步。该风险由 assert_checked_in_manpage_surface 的逐字节比对强制,本次已实测通过。命令行为、权限、registry、quota、todo 均未触碰;loopx agent-directory 的成员校验语义(未注册调用方得到 scope gap)只被描述、未被修改。
本次在更新后的 head 上实测(该 head 把 origin/main —— 含已合并的 #4593 —— 合入;本分支自身 diff 仍为 2 文件 +7/-0):
env -u PYTHONPATH uv run --extra test python examples/cli-help-manpage-smoke.py→ ok(exit 0)man/loopx.1 == render_manpage()→ True- 干净 main worktree(
/tmp/main-green.CEZZNu)上同一 smoke → 失败(unclassified: ['agent-directory']),确认基线确为红 env -u PYTHONPATH uv run --extra test loopx canary premerge --from-git-diff→ 0 failures / 0 advisories(diff hygiene、changed-python compile、3/3 catalog canary)
未验证 / 环境说明(如实标注,不当通过):semantic-vocabulary-drift-smoke 在本机需要仓库的 TypeScript npm 依赖,缺依赖时会以 TypeScript production parser failed; run npm ci --ignore-scripts 退出。该失败与本次改动无关——在同一台机器的干净 main worktree 上以完全相同的方式失败——补齐本地未跟踪的根 node_modules 后该 smoke 与 premerge 均通过。未手工在安装后的 man1 环境翻阅页面(该路径由 smoke 的安装断言覆盖)。
边界声明:本 PR 改动 loopx/** 下的帮助面数据,按仓库规则属控制面改动,只提 PR、由维护者合并,作者不做自合并。
我的整体评价
按既有分类把帮助面补完整、并同步生成产物,是解除这条 main 红项的最小且不削弱契约的改动。无阻断性发现,残余风险只有「分类表与 man page 必须同时更新」这一既有约定。
与 #4620(main 上另外三条红 smoke,属控制面运行时改动)合并后,main 的 Full Public Smokes 应恢复全绿,随后重跑 canary promotion-readiness 预检以替换陈旧证据。
English verdict: APPROVE - re-verified on exact head c13df19: loopx agent-directory is a real top-level command that was missing from both the manual command groups and the explicit help-only set, so the full-public-smoke assertion failed on main. This adds it to the existing group with the existing entry shape and syncs the rendered man page, instead of hiding it in the help-only set. cli-help-manpage-smoke passes, man/loopx.1 == render_manpage() is True, the same smoke still fails on a clean main worktree, and premerge canary reports 0 failures. No blocking finding.
huangruiteng
left a comment
There was a problem hiding this comment.
动机
examples/cli-help-manpage-smoke.py 要求每个 top-level loopx 命令要么在 manual 里有代表,要么被显式留在 command-specific help 面上。agent-directory(#4544 引入,loopx/cli.py:773 分派)从来没有被归类,于是从那次合并开始,每一个 main head 都挂在这条断言上:
AssertionError: {'unclassified': ['agent-directory'], 'stale_manual': [], 'stale_help_only': []}
今天 main 的整轮 public smoke(shards 0/2/3/4)就停在这里,看起来像在拦一堆与它无关的 PR,实际原因只有这一个未分类命令。
改动思路
做可见性决定,而不是放松断言:把 agent-directory 归到 manual 的 maintainer-facing agent 命令组,挨着 register-agent——理由是这个命令的职责正是产出这台 host 可以派活的、本地 goal-scoped 的 peer agent 目录。man/loopx.1 由 render_manpage() 重新生成;smoke 断言签入的 manual 等于渲染输出,所以两者必须一起动,而本 PR 是一起动的。
具体改动
loopx/help_surface.py:新增agent-directory的 manual 条目(四行),归入 agent 命令组。man/loopx.1:从渲染函数重新生成。- 没有别的源改动,也没有把任何命令加进跳过列表。
对主干的风险
把命令放进 manual 会让人在帮助页里看到它,这正是本 PR 的目的;需要留意的是 agent-facing 行预算,因为 manual 变长有时会顺带撑大那些行。本次复审在更新后的 head 上实测(该 head 只是把 origin/main 合进来,两个被评审文件在旧 head 与新 head 之间逐字节一致(loopx/help_surface.py 与 man/loopx.1 均用内容哈希核对)):
uv run --extra test python examples/cli-help-manpage-smoke.py→cli-help-manpage-smoke ok- 该改动落在 maintainer 分组,不在
tests/control_plane/test_cli_output_budget.py预算的 agent-facing 行里(PR 记录该套件 22 passed 不变)
另需如实记一笔:这条分支上一次跑 CI 时 stage2c (mutants 0) 出现过一个存活 mutant(fence_unshared_state_lock),当时在 main 的两次同 job 运行里没有复现;本次复审以新 head 的必过检查结果为准。
我的整体评价
用一个明确的可见性决定消掉“自 #4544 起 main 每个 head 都红”的这个原因,并且是改事实而不是改断言,方向正确。
合并边界这条要说清楚:本 PR 改的是 loopx/help_surface.py,属于控制面/CLI 契约面,按仓库当前规则不由作者自合并——本 head 的审查记录只用于把决定交给维护者,实际合并由维护者执行。
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Head: c13df19
English verdict: APPROVE
…irectory-20260917 Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
c13df19 to
fe27f18
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
动机
examples/cli-help-manpage-smoke.py 要求每个 top-level loopx 命令要么在 manual 里有代表,要么被显式留在 command-specific help 面上。agent-directory(#4544 引入,loopx/cli.py:773 分派)从来没有被归类,于是从那次合并开始,每一个 main head 都挂在这条断言上:
AssertionError: {'unclassified': ['agent-directory'], 'stale_manual': [], 'stale_help_only': []}
今天 main 的整轮 public smoke(shards 0/2/3/4)就停在这里,看起来像在拦一堆与它无关的 PR,实际原因只有这一个未分类命令。
改动思路
做可见性决定,而不是放松断言:把 agent-directory 归到 manual 的 maintainer-facing agent 命令组,挨着 register-agent——理由是这个命令的职责正是产出这台 host 可以派活的、本地 goal-scoped 的 peer agent 目录。man/loopx.1 由 render_manpage() 重新生成;smoke 断言签入的 manual 等于渲染输出,所以两者必须一起动,而本 PR 是一起动的。
具体改动
loopx/help_surface.py:新增agent-directory的 manual 条目(四行),归入 agent 命令组。man/loopx.1:从渲染函数重新生成。- 没有别的源改动,也没有把任何命令加进跳过列表。
对主干的风险
把命令放进 manual 会让人在帮助页里看到它,这正是本 PR 的目的;需要留意的是 agent-facing 行预算,因为 manual 变长有时会顺带撑大那些行。本次复审在更新后的 head 上实测(该 head 只是把 origin/main 合进来,两个被评审文件在旧 head 与新 head 之间逐字节一致(loopx/help_surface.py 与 man/loopx.1 均用内容哈希核对)):
uv run --extra test python examples/cli-help-manpage-smoke.py→cli-help-manpage-smoke ok- 该改动落在 maintainer 分组,不在
tests/control_plane/test_cli_output_budget.py预算的 agent-facing 行里(PR 记录该套件 22 passed 不变)
另需如实记一笔:这条分支上一次跑 CI 时 stage2c (mutants 0) 出现过一个存活 mutant(fence_unshared_state_lock),当时在 main 的两次同 job 运行里没有复现;本次复审以新 head 的必过检查结果为准。
我的整体评价
用一个明确的可见性决定消掉“自 #4544 起 main 每个 head 都红”的这个原因,并且是改事实而不是改断言,方向正确。
合并边界这条要说清楚:本 PR 改的是 loopx/help_surface.py,属于控制面/CLI 契约面,按仓库当前规则不由作者自合并——本 head 的审查记录只用于把决定交给维护者,实际合并由维护者执行。
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Head: fe27f18
English verdict: APPROVE
…irectory-20260917
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewed exact head: 90fd1806ea86f1f102181fc10d5608bbae48476f (re-review after merging origin/main through e160b1bbc / #4633; the two changed files are byte-identical to the earlier reviewed heads, and the main advance has no intersection with them).
loopx pr-review --check-result on the matching packet returned ok: true with no approval_blockers; all 19 evidence rows for this control-plane plan are verified.
动机
loopx agent-directory 是真实的顶层命令(实测它存在于 130 个顶层命令中,loopx agent-directory --help 正常),但既不在手册命令组里,也不在显式 help-only 集合里。examples/cli-help-manpage-smoke.py::assert_top_level_commands_are_explicitly_classified 要求 parser 命令集合 == 手册命令集合 ∪ help-only 集合,于是 main 上这条必过 shard 0 报:
AssertionError: {'unclassified': ['agent-directory'], 'stale_manual': [], 'stale_help_only': []}
这是 main 必过套件里最后一条红项,不修则所有开放 PR 都拿不到必过检查全绿。
这个 PR 的价值:它补的是真实存在的帮助面缺口(命令有实现、手册无分类),而且没有用「少写文档」的方式换取绿灯。
改动思路
关键在于选择哪一类归属。smoke 的一致性等式只允许两种落点:进手册的 COMMAND_GROUPS,或进显式的 MANPAGE_COMMAND_HELP_ONLY。把命令丢进后者虽然同样能让集合等式成立,却会让它从手册正文消失——等于用削文档换绿灯,所以选择按既有条目形状补进手册组;man/loopx.1 是被 scripts/render-manpage.py 从同一张表渲染出来的产物,因此必须同步更新。
具体改动
loopx/help_surface.py:262:在「运行与编排」组追加一条{'command': 'loopx agent-directory', 'purpose': 'Produce the local, goal-scoped peer agent directory this host can hand work to.'},与相邻条目(register-agent、lark-kanban、presentation)形状完全同构。man/loopx.1:追加对应的.TP段落(转义与生成器一致)。
两个文件之外零改动:不改命令实现、不改 smoke 断言、不新增 help-only 例外。
关键代码讲解
def manpage_top_level_commands() -> frozenset[str]:
commands = {"commands"}
for group in COMMAND_GROUPS:
for entry in group.get("commands", []):
command = str(entry.get("command") or "")
commands.update(re.findall(r"(?:^| / )loopx ([a-z0-9][a-z0-9-]*)", command))分类不是靠子串匹配判定的,而是靠集合相等:正则只负责从既有条目文本里提取命令 id,归属由「在哪张表里」决定。因此唯一真实的书写风险是形态(例如写成下划线就解析不出来),这一点由 smoke 实测覆盖。
def assert_checked_in_manpage_surface() -> None:
assert (REPO_ROOT / "man" / "loopx.1").read_text(encoding="utf-8") == render_manpage()man page 必须与渲染结果逐字节相等,所以「只改表不改 man page」会立刻失败——这也是我这次单独复核的地方:实测 checked == render_manpage() 为 True。
对主干的风险
纯帮助面数据改动,风险面只有一个:分类表与 man page 可能不同步。该风险由 assert_checked_in_manpage_surface 的逐字节比对强制,本次已实测通过。命令行为、权限、registry、quota、todo 均未触碰;loopx agent-directory 的成员校验语义(未注册调用方得到 scope gap)只被描述、未被修改。
本次在更新后的 head 上实测(该 head 把 origin/main —— 含已合并的 #4593 —— 合入;本分支自身 diff 仍为 2 文件 +7/-0):
env -u PYTHONPATH uv run --extra test python examples/cli-help-manpage-smoke.py→ ok(exit 0)man/loopx.1 == render_manpage()→ True- 干净 main worktree(
/tmp/main-green.CEZZNu)上同一 smoke → 失败(unclassified: ['agent-directory']),确认基线确为红 env -u PYTHONPATH uv run --extra test loopx canary premerge --from-git-diff→ 0 failures / 0 advisories(diff hygiene、changed-python compile、3/3 catalog canary)
未验证 / 环境说明(如实标注,不当通过):semantic-vocabulary-drift-smoke 在本机需要仓库的 TypeScript npm 依赖,缺依赖时会以 TypeScript production parser failed; run npm ci --ignore-scripts 退出。该失败与本次改动无关——在同一台机器的干净 main worktree 上以完全相同的方式失败——补齐本地未跟踪的根 node_modules 后该 smoke 与 premerge 均通过。未手工在安装后的 man1 环境翻阅页面(该路径由 smoke 的安装断言覆盖)。
边界声明:本 PR 改动 loopx/** 下的帮助面数据,按仓库规则属控制面改动,只提 PR、由维护者合并,作者不做自合并。
我的整体评价
按既有分类把帮助面补完整、并同步生成产物,是解除这条 main 红项的最小且不削弱契约的改动。无阻断性发现,残余风险只有「分类表与 man page 必须同时更新」这一既有约定。
与 #4620(main 上另外三条红 smoke,属控制面运行时改动)合并后,main 的 Full Public Smokes 应恢复全绿,随后重跑 canary promotion-readiness 预检以替换陈旧证据。
English verdict: APPROVE - re-verified on exact head 90fd180: loopx agent-directory is a real top-level command that was missing from both the manual command groups and the explicit help-only set, so the full-public-smoke assertion failed on main. This adds it to the existing group with the existing entry shape and syncs the rendered man page, instead of hiding it in the help-only set. cli-help-manpage-smoke passes, man/loopx.1 == render_manpage() is True, the same smoke still fails on a clean main worktree, and premerge canary reports 0 failures. No blocking finding.
|
Retained in the runtime/recovery PR reconciliation. This is a useful, complete command-discovery fix: it classifies the shipped |
|
Independent review at exact head Verified at this head:
No assertion relaxed; classification decision (maintainer-facing, next to |
What changed
examples/cli-help-manpage-smoke.pyrequires every top-levelloopxcommand to be either represented in the manual or explicitly kept on its command-specific help surface.loopx agent-directory, added by #4544 (commit818db7522) and dispatched atloopx/cli.py:773, was never classified, so everymainhead since then fails that check:On
maintoday this is what takes the full public smoke sweep down (run35164002565: shards(0,2,3,4)fail, shard 0 stops here), so the failure is unrelated to the PRs it looks like it is blocking.This PR makes the visibility decision instead of relaxing the assertion:
agent-directoryjoins the manual's maintainer-facing agent commands, next toregister-agent, because the command's job is to produce the local, goal-scoped peer agent directory this host can hand work to.man/loopx.1is regenerated fromrender_manpage(); the smoke asserts the checked-in manual equals the renderer output, so both move together.Validation
tests/control_plane/test_cli_output_budget.pybudgets the agent-facing rows ("Start here", "Daily operator commands",loopx heartbeat-prompt); a maintainer-group row is not part of that budget, and the suite passes unchanged.Before the fix, the same smoke fails on this tree with the
unclassifiedassertion above; the only source change is the four-line entry plus the regenerated manual.Boundary
Public help surface only: one catalog entry and its generated manual text. No runtime, control-plane, permission, scoring, or agent-facing output change, and no assertion is relaxed or skipped.
loopx commandsandloopx --helpline counts are unchanged in the assertion that bounds them.