docs: docs/ 里 13 条 cli.ts 行号引用改钉符号锚(22 → 9,改前 11/11 全错) - #857
Conversation
#852 量过:docs/ 下按 blob/main 钉 cli.ts 行号的引用,锚文本带符号名、可机器判定的 11 条里 **漂移 11、仍对 0**。这次把能确定唯一锚串的都改掉。 改法照 #845 已确立的形状: 改前 [`cli.ts:228 loadProfile`](…/blob/main/agent-network/bin/cli.ts#L228) 改后 [`cli.ts`](…/blob/main/agent-network/bin/cli.ts) —— 搜 `function loadProfile(` 13 条的真实位置(改之前它们全都指错了): adminUtokPath 文档说 28 实际 138 saveGlobal 文档说 77-81 实际 1000 saveServerConfig 文档说 89-95 实际 1047 saveAdminUtok 文档说 105-111 实际 1062 loadProfile 文档说 228 实际 1208 saveProfile 文档说 246-273 实际 1272 setupCommand 文档说 556 实际 1860 ensureMcpJson 文档说 1644 实际 4300 runCommand 文档说 2044 实际 5641 renameCommand 文档说 2583 / 2629 实际 6911 deleteCommand 文档说 2800-2840 实际另处 dashboardReleaseTag 文档说 347 实际另处 每一条新锚串都逐条核过在 cli.ts 里**唯一**(13 条,非唯一 0 条)。 `RuntimeName` 那条**没有改**:它在 cli.ts 里出现 13 次,做不出唯一锚 —— 与其钉一个 含糊的锚,不如留着行号,等有人给它一个能唯一定位的写法。 剩下 9 条锚文本里没有符号名(形如 `[cli.ts:1724](…#L1724)`),机械改不了, 需要人读源码判断它当初想指的是什么。留给 #852。
接上一提交。这 6 条的锚文本里没有符号名,机械改不了,是逐条读上下文判出它当初
想指什么、再去源码里定位的:
node-lifecycle.md:206 正文说 notifyServerOffline → 搜 `async function notifyServerOffline(`
node-lifecycle.md:213 正文说「确认流程」 → 搜 `This will delete "${displayName}" (node_id:`
node-lifecycle.md:383 正文直接写了 resolveNodeRef → 搜 `function resolveNodeRef(`
architecture.md:316 正文说写 .mcp.json → 搜 `.mcp.json: commhub → .anet/node-server.js`
architecture.md:326 正文引了 compare-by-content → 搜 `if (src !== dst)`
architecture.md:328 RuntimeName type → 🔴 它已经不在 cli.ts 里了
最后一条值得单说:文档写「RuntimeName type cli.ts:145」,但 cli.ts 里
`type RuntimeName =` 出现 **0 次** —— 这个类型已经搬到
`agent-network/src/normalize-runtime.ts:16`。这不是行号漂移,是文件都换了。
所以这条改的是链接目标,不只是锚。
「确认流程」那条要小心:`Run again with --force to confirm.` 在 cli.ts 里出现 **2 次**
(deleteCommand 8143 / networkCommand 10173),不能拿它当锚。读上下文确认文档说的是
节点删除,才选了 deleteCommand 里唯一的那句。
每条锚串都核过唯一。node-lifecycle.md 的行号 pin 已归零。
|
追加一个提交( 新修的 6 条,每条都是读上下文判出它当初想指什么、再去源码定位的:
🔴 RuntimeName 那条不是行号漂移,是文件都换了。 文档写「RuntimeName type cli.ts:145」,但 cli.ts 里 「确认流程」那条差点选错锚:
剩余 3 条(锚文本无任何线索,连正文都没说它指什么函数): pitfalls 那条我已定位到候选: |
这 3 条的锚文本里没有符号名,是逐条读正文判出来的:
architecture.md:520 正文自己就写着「`sub === "dashboard"` 分支」
→ 链接去掉行号即可,锚已经在正文里(唯一,cli.ts:6092)
RFC-002:37 「参考实现」指的是 anet channel add/ls
→ 搜 `async function channelCommand(`(唯一,cli.ts:8159)
pitfalls.md:80 「candidates 数组:源文件搜索顺序」
→ cli.ts 里 `const candidates` 有 4 处,其中 1024 与 4333
都是 node-server.js 解析顺序。判据是 4333 那处的注释
`// dist/src/node-server.js(npm 包混淆后产物,优先)`
与 pitfalls 正文第 1 条逐字对应,1024 那处不是。
→ 搜那条注释(唯一)
至此 docs/ 下 blob/main 形式的 cli.ts 行号引用为 0。每条锚串都核过在源码里唯一。
|
再追加一个提交( 这 3 条锚文本里没有符号名,是逐条读正文判出来的:
这是这批里唯一一条两个候选都长得像的,如果按「找个差不多的就钉上」处理,就会钉错一个 —— 而钉错之后它仍然是「锚串存在、链接能开」,没有任何门会报。 仓内其余 8 条(不在本 PR 范围)#851 合了之后只剩 |
复核:本条结论仍成立,但范围之外还有同类的 6 条,全部也是错的一、结论仍成立(2026-08-14 复核)
🔴 二、我按更宽的范围数了一遍:
|
| 引用位置 | 声称 | 该行现在是什么 | 那个符号实际在第几行 |
|---|---|---|---|
docs/architecture.md:48 |
cli.ts:558-598 settingSources: [] |
// "Reached maximum number of turns (5)" … |
2238 |
docs/message-lifecycle.md:41 |
cli.ts:864 msgType !== "task" … |
inboxDir: string; |
该表达式已不存在 |
docs/message-lifecycle.md:142 |
cli.ts:864 同上 |
inboxDir: string; |
同上 |
docs/message-lifecycle.md:198 |
cli.ts:1102 ["new_task","broadcast"].includes(ev.type) |
if (AUTH_TOKEN) headers["Authorization"] = … |
5687 |
docs/message-lifecycle.md:199 |
cli.ts:836 shouldSkipMessage |
warn(\cleared ${key} …`)` |
4610 / 4695 |
docs/node-lifecycle.md:196 |
cli.ts:1159 心跳 setInterval |
try { |
1231 / 5942 / 5993 |
漂移幅度不小:settingSources 声称 558,实际 2238;includes(ev.type) 声称 1102,实际 5687。
其中 msgType !== "task" 这个表达式在当前代码里已经找不到了 ——
那两处引用不只是行号漂了,是它描述的判断本身可能已经改写,需要人看一眼再决定钉哪。
建议
不必扩大本条的范围(小范围是它能做完的原因),但标题/正文最好把范围写死成
「agent-network/bin/cli.ts 的 22 条」,否则读者会以为 docs/ 下的 cli.ts 引用都处理过了 ——
改一半比不改更糟,因为看起来像已经修过了。
那 6 条我不在这里动,建议另开一条跟踪(或在本条里显式写"不含 agent-node/src/cli.ts 的 6 条")。
(只读:全部读 origin/main,临时 worktree 用完即 remove;未改任何文档。)
|
核过了,合。前提比 PR 描述说得还严重,而新锚点我逐条验了。 ① 旧行号 pin:我抽查的 13 条,13 条全错拿
没有一条是「差几行」—— 全是指向完全无关的代码。 而它们都长得像有效引用(格式对、行号在文件范围内、点开能打开),所以读的人不会怀疑。 🔴 ② 新锚点:21 条,我逐条 grep 过把 diff 里所有 21/21 有效。 🔴 这一步不能省:把一个错的行号换成一个错的搜索串,读起来会更可信,坏得更彻底。 ③ 一个这个 PR 没解决、也不该由它解决的问题行号 pin 有 #843 那道门在数(守住不再变多)。符号锚点没有任何门在看。 它们确实不会因为「上面插了几行」而失效 —— 但会因为改名而失效,而且失效之后同样没有任何东西会喊。我上面那段 grep 就是一次性写的,不在仓里。 建议单开一道门:扫 |
冲突全是同一形状:本分支把 `[cli.ts:228 loadProfile](…#L228)` 换成 `[cli.ts](…) —— 搜 \`function loadProfile(\``,而 main 上 #869 改了同文件别处 (runtime 表、目录树),git 判成重叠。 八处一律取本分支侧。核过没有连带丢东西: merged 与 origin/main 的逐行 diff = 12 删 / 12 增,**每一条被删的 main 行都含 `cli.ts` 引用** —— 也就是说改动只落在 pin 上,#869 的 runtime 表和目录树改动 一个字都没动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#857 把 docs 里的行号 pin 换成了符号锚点。换得对:那 13 条行号逐条对下来 **13 条全错** —— `loadProfile` 实际在 cli.ts:1274(doc 写 228),`runCommand` 在 5812(doc 写 2044),`ensureMcpJson` 指的那一行是**空行**。而它们全都长得像 有效引用:格式对、行号在文件范围内、点开能打开,所以读的人不会怀疑。 但换完之后留了一格没人看:**符号锚点不会因为「上面插了几行」失效,却会因为 改名而失效,而失效之后同样没有任何东西会喊。** #843 那道门在数行号 pin(守住 不再变多),符号锚点在变多,一直没有对应的门。 判据:每一条 `搜 \`X\`` 里的 X,必须在**它左边最近的那个源码链接**指向的文件里 逐字存在。两类失败都报 —— 找不到(改名/删除/写错),以及前面根本没有链接 (无法判定它指哪个文件,这本身就是缺陷)。 ## 起点(与手工核对一致) checked 21 symbol anchor(s) across 262 tracked doc(s); 21 resolved every symbol anchor exists in the file it names. 21 这个数是先手工数出来的,再让脚本跑 —— 两边对上才用。 ## 见红(三种坏法,三种输出,互不相同) A 改一个锚点为源码里不存在的名字 → rc=1 "not found in 'agent-network/bin/cli.ts'" B 去掉锚点前面的链接 → rc=1 "no source link precedes this anchor" C 把扫描范围改成一个不存在的目录 → rc=2 "0 tracked .md … 扫描范围塌了" 三份输出的 md5 两两不同。C 是分母承重:🔴 这道门最可能的坏法不是判据写错, 是**一条都没扫到然后打印一片绿** —— 那种假绿和真绿逐字相同。所以扫到 0 个 md 或 0 条锚点一律 exit 2,让「没问题」和「没有看」在输出上长得不一样。 变异做完全部还原:`cmp docs/architecture.md` 与 main 逐字节相同。 ## 🔴 刻意不加 paths 过滤 这道门的主要失效场景是**有人在 cli.ts 里把一个函数改名**,不是有人改了 doc。 按 `docs/**` 过滤的话,改源码的 PR 不会触发它 —— 门在、判据也对,但在最需要它 的那一类改动上永远不会被触发。整个脚本跑完不到一秒,省这点没有意义。 `--selftest` 8 条,含「无锚点文本 → 计数必须为 0」。job 名 `doc-symbol-anchors` 全仓唯一(25 个 job,25 个不同的名字)。 Co-authored-by: t <t@x> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
main 上已经动过这两行,方向和本 PR **不同**: main: 链接改成 blob/main/…(不带 #L),行号退回正文「(当时在 61 行)」 #851: 链接钉到当时的提交 blob/3a387204/…#L61 两个都成立,但各解决了一半: - main 那版**链接永远打得开**,但点开是今天 13753 行的 cli.ts —— 第 61 行现在 是一句 import,读者拿不到那句话说的东西; - #851 那版**点开就是** `const PINNED_SERVER_VERSION = "0.8.0";`,但链接文字 还是裸的 `cli.ts:2589`,不说明那处是干什么的。 取并集:**URL 用 #851 的提交钉法,链接文字用 main 的描述性写法**,并在正文里 写清「钉在当时的提交 3a38720,第 61 行」。 实跑核过(该提交下 cli.ts 共 7203 行): L61 const PINNED_SERVER_VERSION = "0.8.0"; L2589 const serverArgs = ["--bun", `@sleep2agi/commhub-server@${PINNED_SERVER_VERSION}`]; 对照 main 上同样两行:L61 是 `} from "../src/opencode-preset";`,L2589 是 `}`。 🔴 顺带记一条判断:同样是「行号 pin 失效」,#857 那批指向**当前**代码,正确修法是 换符号锚点(跟着代码走);本 PR 这两条在 **changelog** 里,描述的是当时的状态, 正确修法是**钉提交**。`PINNED_SERVER_VERSION = "0.8.0"` 这个事实只在那个提交上 成立 —— 换符号锚点反而会指向一个今天已经不是 0.8.0 的常量。 **症状一样,修法取决于那句话说的是「现在」还是「当时」。** 合并后跑过: check-docs-integrity.py no problems(6 个 changelog 的 main 行锚点 = 0) check-doc-symbol-anchors.py 21/21 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
doc claim freshness 红了,红得对:我在 agent-node/src/cli.ts ~:462 插了 12 行, docs/stale-issue-review.md 里三条按行号 pin 的断言整体下移 12 行。 3468 → 3480 video_gen 4291 → 4303 Expose CURRENT_TASK_ID 2388 → 2400 total_cost_usd 三个都恰好 +12,等于我的插入量 —— 是纯位移,不是内容变了。 顺带在清单上方留了一条注释说明这次为什么会漂,并指出:这正是 #857 用「符号 锚点」替换行号 pin 想解决的事,而这份清单仍是行号制,**任何在上方的插入都会 让它红,而红的原因和「文档说错了」在输出上长得一样**。换成符号锚点要动 tests/test846-doc-claims 的判据,属于另一条。 scripts/check-doc-claims.py rc=0 claims_checked=8 claims_failed=0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(agent-node): 拒绝 claude-code-cli 时说清它住在哪 (#909) #917 修好了 `--help`:`--runtime` 的取值列表不再列 claude-code-cli,Runtime 段里标明它由 `anet` 提供。但同一句错话留在了**用户真正撞上的那一处**: $ agent-node --alias t --runtime claude-code-cli [t] Unsupported runtime "claude-code-cli". Supported: claude-agent-sdk, ... "unsupported" 对这个二进制是真的,对产品是假的 —— claude-code-cli 已实现、 有 e2e(qa-180-rename-ghost)、在出货,只是执行道在 launcher 里 (agent-network/bin/cli.ts ~:5096,`anet node start` → launchAgent → 真的 claude CLI)。跟一个用户说"unsupported",是把他推去找一个不存在的 runtime, 而不是推去隔壁那条命令。 现在: [t] "claude-code-cli" does not run through agent-node — it is provided and launched by `anet` itself. [t] Use: anet node start <node> (with runtime "claude-code-cli" in that node's config.json) [t] Runtimes agent-node can take directly: claude-agent-sdk, ... 行为没变(仍然 exit 1,RUNTIME_MAP 一个键都没加 —— 加键会让 CLI-login 用户 被静默降级到 SDK 通道,那正是上面注释警告过的)。 测试加在 #917 建的那个文件里,红绿都亲手见过: 用 origin/main 的 cli.ts 跑 → 1 fail(打印出旧那句 Unsupported runtime) 用本 PR 的 cli.ts 跑 → 4 pass 带一条负对照:真正不存在的 runtime 仍走原来那句,且不泄漏 `anet node start`。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(docs): 同步被本 PR 撞漂的三条行号 pin (#909) doc claim freshness 红了,红得对:我在 agent-node/src/cli.ts ~:462 插了 12 行, docs/stale-issue-review.md 里三条按行号 pin 的断言整体下移 12 行。 3468 → 3480 video_gen 4291 → 4303 Expose CURRENT_TASK_ID 2388 → 2400 total_cost_usd 三个都恰好 +12,等于我的插入量 —— 是纯位移,不是内容变了。 顺带在清单上方留了一条注释说明这次为什么会漂,并指出:这正是 #857 用「符号 锚点」替换行号 pin 想解决的事,而这份清单仍是行号制,**任何在上方的插入都会 让它红,而红的原因和「文档说错了」在输出上长得一样**。换成符号锚点要动 tests/test846-doc-claims 的判据,属于另一条。 scripts/check-doc-claims.py rc=0 claims_checked=8 claims_failed=0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: t <t@t> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
共存运行时把 Grok CLI 钉在精确 build `grok 0.2.93 (f00f96316d)`,而官方不带版本的 安装器给的是当前 stable —— #876 立案时是 1.0.4,2026-08-18 实测已是 **1.0.5**。 它一直在走,钉子不走。 原文的问题在于它是一句**现在时断言**: - The exact Grok CLI build `grok 0.2.93 (f00f96316d)`; the known stable installer may append ` [stable]` to that output. 写下时 stable 就是 0.2.93,这句是对的;今天它让读者以为跑一遍官方安装器就能得到 钉死那一版。而全文**没有任何一处**说怎么装到 0.2.93(grep 核过)。 排障段的 `Install the pinned build` 同样只说了要装,没说怎么装。 修法不是那条 issue 里建议的手拼产物 URL —— 官方安装器**自己就收版本参数** (`TARGET="$1"`),所以走它支持的路: curl -fsSL https://x.ai/cli/install.sh | bash -s 0.2.93 grok --version # grok 0.2.93 (f00f96316d) 实测(隔离 HOME,2026-08-18): Grok 0.2.93 installed to …/.grok/bin/grok grok --version → grok 0.2.93 (f00f96316d) ← 与钉死的串逐字相同 同时补了两件原文没说的事: - 那个安装器**不做校验和**(对它 grep sha256/checksum 零命中)。是 xAI 的安装器不是 我们的,写出来让信任边界显式,而不是默认。 - `docs/grok-build-runtime.md` 教的不带版本安装**对它自己讲的 grok-build-acp 是对的** (那条路不受钉子约束),加了交叉引用说明共存那条路不一样。两份文档各自没写错, 合起来读才会把人带沟里 —— 这正是 #876 的形状。 引用用符号锚点不用行号:#857 把这个仓的 13 条行号 pin 换掉时,实测 **13 条全部已漂**。 Co-authored-by: t <t@t> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
CI 上两条红,都是这个 PR 自己造成的,且都被仓里已有的门当场抓住: ① home-path-baseline 新夹具里写了 `/home/dev` 和 `/home/d`。改成 `/home/user`(该名字在 check-home-path-baseline.py 的 NOT_A_PERSON 里),201 → 198,rc=0。 ② doc claim freshness(test846) docs/stale-issue-review.md 的 doc-claims 清单里三条 agent-node/src/cli.ts 的行号漂了:3480→3481 / 4303→4302 / 2400→2401。 🔴 值得记的是量级:本 PR 对 cli.ts 只有 +12/-11、净 +1 行(两个 denyPaths 调用点从内联字面量换成 grokCliDenyPaths(...)),却让三条本来正确的文档引用 同时变错,而且**两个方向都有**——增删发生在不同位置。 这正是 #857 用符号锚点替换行号 pin 想解决的那件事。那份清单是行号制, 任何在上方的增删都会让它红,而**红的原因和「文档说错了」在输出上长得一样**。 注释里把这次的位移量记下来了,和上一次 #950 的 +12 放在一起。
* test(grok-cli): put a gate on the sandbox deny list cli.ts passes (#885) #885 verified that the isolated GROK_HOME is absent from every sandbox deny profile, tried the one-line fix, and then withdrew it — because nothing in the repo watches this list: grok-copresence suite 165 pass ← never touches cli.ts or denyPaths grok-build-cli-home suite 41 pass ← tests the callee, which faithfully writes whatever it is handed That is the right call and the right reason. A caller that forgets a path sails through every test of the mechanism, so the fix could not be reviewed as a behaviour change. This commit builds the missing gate so it can be. - `grokCliDenyPaths()` is now the single place the list is decided; both call sites in cli.ts (the co-presence leader and prepareRuntime) go through it. The list itself is byte-for-byte what it was, including the `configFilePath || ""` placeholder, so prepareGrokCliHome still receives the same arity. - The call-site test asserts on shape rather than on an import: zero `denyPaths: [` literals, exactly two `denyPaths: grokCliDenyPaths(`. Merely checking that the helper is imported would pass with a literal array sitting next to it. - One test states the open gap out loud — the isolated GROK_HOME is still not denied. It fails the moment someone adds it, which forces the decision to be written down instead of landing silently. Witnessed red first: before cli.ts was rewired, the call-site test failed with 2 literals found where 0 were expected. Full agent-node suite: 1285 pass / 0 fail across 92 files (TMPDIR=/run/user/1000/anet-tests, see #884/#865). Not done here: the deny entry itself. Reading the code, sandbox.toml constrains Grok's *tool* sandbox and the isolated home holds the generated sandbox.toml, requirements.toml and an agent_id credential link — so denying it looks both safe and correct. I have not verified that at runtime, and a sandbox change should not land on a code reading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(#984): 夹具改用占位家目录名;同步被本 PR 撞漂的三条文档行号 pin CI 上两条红,都是这个 PR 自己造成的,且都被仓里已有的门当场抓住: ① home-path-baseline 新夹具里写了 `/home/dev` 和 `/home/d`。改成 `/home/user`(该名字在 check-home-path-baseline.py 的 NOT_A_PERSON 里),201 → 198,rc=0。 ② doc claim freshness(test846) docs/stale-issue-review.md 的 doc-claims 清单里三条 agent-node/src/cli.ts 的行号漂了:3480→3481 / 4303→4302 / 2400→2401。 🔴 值得记的是量级:本 PR 对 cli.ts 只有 +12/-11、净 +1 行(两个 denyPaths 调用点从内联字面量换成 grokCliDenyPaths(...)),却让三条本来正确的文档引用 同时变错,而且**两个方向都有**——增删发生在不同位置。 这正是 #857 用符号锚点替换行号 pin 想解决的那件事。那份清单是行号制, 任何在上方的增删都会让它红,而**红的原因和「文档说错了」在输出上长得一样**。 注释里把这次的位移量记下来了,和上一次 #950 的 +12 放在一起。 --------- Co-authored-by: t <t@t> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
推进 #852。
docs/下的cli.ts行号引用 22 → 9,13 条改钉符号锚。为什么必须改
#852 量过:
docs/下锚文本带符号名、因而可机器判定的引用有 11 条 ——漂移 11 条,仍然对的 0 条。这批引用现有的任何一道门都照不到:
DOC_ROOT = "docs-site",不覆盖docs/;指着一行正常代码、只是不是声称的那个符号」只能碰到 2/11。
改法(照 #845 已确立的形状)
行号会漂,符号不会。读者用
git grep一次定位。改之前它们错到什么程度
origin/main实际adminUtokPathsaveGlobalsaveServerConfigsaveAdminUtokloadProfilesaveProfilesetupCommandensureMcpJsonrunCommandrenameCommand不是漂了几行,是差了几千行 —— 点进去看到的是完全无关的代码。
承重复核:每条新锚串在
cli.ts里唯一RuntimeName那条故意没改:它在cli.ts里出现 13 次,做不出唯一锚。与其钉一个含糊的锚(那正是 #845 踩过的坑:锚串确实存在,只是落在别的地方),
不如留着行号,等有人给它一个能唯一定位的写法。
剩下的 9 条:机械改不了,留给 #852
它们的锚文本里根本没有符号名:
要修它们得人读源码,判断当初那段话想指的是什么 —— 没有机械办法。
这正是 #852 说的「剩下 9/11 要符号级校验」的那部分工作量。
顺带发现(不在本 PR 范围)
docs/qa/weekly/2026-W19.md不是 UTF-8(position 2467 处 invalid continuation byte),任何按 UTF-8 读全目录的脚本都会在它上面炸。本 PR 的处理是跳过它。