planning-with-files 静默故障自检指南:/plan-doctor 命令原理与实战
2026/9/10 18:52:33 网站建设 项目流程

planning-with-files 静默故障自检指南:/plan-doctor 命令原理与实战

【免费下载链接】planning-with-filesPersistent file-based planning for AI coding agents and long-running tasks. Crash-proof markdown plans, session recovery after /clear and compaction, per-turn re-injection against context rot, deterministic completion gate. Manus-style. Install from npm, the Claude Code plugin marketplace, or npx skills. Codex, Cursor, OpenCode, 60+ agents.项目地址: https://gitcode.com/GitHub_Trending/pl/planning-with-files

本指南围绕 planning-with-files 自 v3.6.0 起提供的/plan-doctor自检命令展开,讲解它如何一次性暴露 hook 注入、计划解析、路径规范化、防篡改认证、安装面与单次 hook 延迟六类"静默失败"机制的真实状态。读完你将掌握在 hooks 突然"哑火"或新机器安装后如何运行自检、如何解读 PASS/WARN/FAIL 输出、以及每类 FAIL 对应的修复路径。

为什么需要 plan-doctor:静默失败是默认设计

planning-with-files 的核心机制——hook 注入与计划解析——在设计上有一个反直觉的特性:出错时它们照样以退出码 0 静默返回。这是刻意的("exit 0 and stay silent by design"),目的是任何情况下都不让 hook 打断 Agent 的主循环。但副作用是:当安装损坏时,坏掉的迹象与"还没有计划"看起来完全一致,用户无法区分"hooks 正常工作只是没有计划"和"hooks 已经彻底失效"。

正如 plan-doctor 命令文档 所述,这类故障分布在多个层面:

  • 计划解析(plan resolution)失败;
  • hook 注入(hook injection)没有发出任何计划上下文;
  • 路径规范化器(canonicalizer)输出的路径形态不可比较(Windows 原生 coreutils 输出C:\风格路径);
  • 认证状态(attestation)缺失或不匹配;
  • 安装面(install surface)不存在;
  • 单次 hook 触发延迟异常。

/plan-doctor就是为这个"看不见的故障类别"而生的:一次运行,把这些机制的真实状态全部摊开。该命令自 v3.6.0 起可用,其定位在 CHANGELOG.md 中有明确记载:它来自 2026 年 7 月基准测试的改进积压,属于"静默失败机制的一次性自检"。

运行方式:一条命令,全平台

在项目根目录执行(Linux / macOS / Git Bash):

sh ${CLAUDE_PLUGIN_ROOT}/scripts/plan-doctor.sh

在未通过插件安装、直接使用仓库源码的场景下,等价写法是:

sh scripts/plan-doctor.sh

Windows 上如果 Git Bash 不在 PATH 中,需要通过git.exe定位其usr\bin\sh.exe兄弟程序,再用它运行同一个脚本。底层实现见 scripts/plan-doctor.sh:脚本以#!/bin/sh编写,只依赖 POSIX sh 与常见 coreutils,无 Python 硬依赖;它"只读诊断",唯一会写入的内容是inject-plan.sh自身的 SHA 缓存,且无论检查结果如何总是以退出码 0 结束exit 0),延续了"绝不打断 Agent 循环"的项目契约。

命令前端约定

/plan-doctor是一个"禁用模型自发调用"(disable-model-invocation: true)的命令,且只允许使用 Bash 工具。这意味着它由用户在需要时显式触发,而不是让模型在任意时刻自行调用。触发场景有两个:hooks 看起来变安静时,以及在新机器上安装之后

输出协议:PASS / WARN / FAIL / info

脚本定义了四级输出(scripts/plan-doctor.sh 第 24-27 行):

ok() { printf 'PASS %s\n' "$1"; } warn() { printf 'WARN %s\n' "$1"; } fail() { printf 'FAIL %s\n' "$1"; } info() { printf 'info %s\n' "$1"; }

命令文档要求:把 PASS/WARN/FAIL 行原样报告给用户,因为这些文本是为"按原样阅读"设计的,不要转述或改写。另有一个约定:若出现 FAIL 行,需要给出对应的修复说明(见下文"故障与修复对照")。

六大检查项逐项拆解

/plan-doctor一次运行完成六项检查,对应脚本的六个编号段。

1. 规范化器探测(canonicalizer probe)

CANON="$(realpath . 2>/dev/null)" || CANON="" [ -z "${CANON}" ] && { CANON="$(readlink -f . 2>/dev/null)" || CANON=""; }

脚本先尝试realpath,再回退readlink -f。探测结果有三种:

  • 两者都不存在 →WARN:遏制(containment)检查将回退到每次 spawn 一个 Python 进程,开销更高;
  • 输出含反斜杠(*\\*)→info:规范化器输出 Windows 风格路径。脚本会提示"v3.6.0 之后已处理;更老的 pwf 版本在这台机器上什么也解析不出来";
  • 其他情况 →info:正常输出规范路径。

这一项直接对应 v3.6.0 修复的经典故障:Windows 原生 coreutils(如C:\Program Files\coreutils出现在 PATH 中且优先于 Git 的usr/bin)会把 MSYS 风格的/c/...输入规范化成C:\反斜杠输出,而遏制前缀匹配是用正斜杠书写的,于是每次规范化的结果对都不匹配,解析在静默中失效。脚本对反斜杠路径的处理见 scripts/resolve-plan-dir.sh 中的norm_slashes(纯 sh 参数展开循环,把反斜杠逐个归一为正斜杠,不 fork 子进程),以及is_windowsapps_path对 Microsoft Store WindowsApps 别名目录的识别——Store 应用别名不是稳定的解释器二进制,可能显示为可执行却拒绝运行脚本,因此必须排除。

2. 计划解析(plan resolution)

脚本调用同目录的 scripts/resolve-plan-dir.sh 并捕获其 stdout:

  • 解析成功 →PASS,并输出活动计划目录(active plan dir = ...);
  • 解析为空但项目根存在task_plan.mdPASS,报告 legacy 根计划(./task_plan.md);
  • .planning目录存在但什么都解析不出来 →FAIL,提示检查.planning/.active_plan内容和计划目录名;
  • 完全没有计划 →info:本目录没有计划(提示先运行init-session.sh创建);
  • resolve-plan-dir.sh不在脚本旁边 →WARN,提示安装布局异常。

要理解这条检查在验证什么,需要知道解析器的完整决议顺序(scripts/resolve-plan-dir.sh 头部注释):

  1. $PLAN_ID环境变量 →./.planning/$PLAN_ID/(若存在);
  2. ./.planning/.active_plan文件内容 → 对应目录(若存在);
  3. 按 mtime 取最新的./.planning/<dir>/
  4. 否则输出空,调用方回退 legacy 根./task_plan.md

三个关键语义值得注意:PLAN_ID是绑定而非提示——一旦设置,无论被拒绝(slug 形状非法、目录不存在、遏制失败)都会在此终止决议,绝不回退到另一个计划,防止一个字符的拼写错误静默切换到别的计划(issue #237);PWF_PLAN_ROOT是最高优先级绑定,用于 cwd 位于真实项目共享父目录的线程,一个坏的 pin 会"失败关闭"(fail closed),stdout 保持干净(stdout 是数据通道,通知文本由注入路由负责);slug 校验用纯 sh case 模式匹配^[A-Za-z0-9_][A-Za-z0-9._-]*$,以过滤.active_plan中的垃圾内容(纯空白或乱码)而不强制日期前缀。此外还有遏制守卫:解析出的计划目录必须规范化到项目根之下,符号链接逃逸(如 slug 目录内指向/etc的软链)会被拒绝,两侧路径都先做反斜杠归一化再比较。

3. hook 注入(hook injection)

这是 doctor 最核心、也最讲究的一项。它运行:

sh "${SCRIPT_DIR}/inject-plan.sh" --context=userprompt

然后对输出做基于数据帧(data framing)的结构化分类,而不是对整段输出做子串匹配(issue #236)。为什么必须这样:inject-plan.sh的输出把计划正文原样放在===BEGIN-PWF-DATA===栅栏内,如果对整段 blob 做子串测试,计划正文里的普通文字就可能误触发分类——历史上确实发生过:某计划的一个阶段写着"fix the false PLAN TAMPERED warning",导致 doctor 对一个正确认证的计划误报哈希不匹配。

分类逻辑(scripts/plan-doctor.sh 第 94-121 行):

输出特征判定含义
===BEGIN-PWF-DATAPASS+ 字节数注入真实发生,计划上下文到达模型。因为inject-plan.sh的所有拒绝路径都会在frame_file运行前打印横幅并退出,所以输出中出现帧就能证明注入成功,并排除所有拒绝路径
[PLAN TAMPEREDWARN计划已认证但哈希不匹配,需运行/plan-attestattest-plan.sh重新批准
requires attested planWARNv3 模式但未认证,运行一次 attest-plan 以武装注入
Session isolation is armedWARN会话隔离拒绝该会话,用PWF_SESSION_ID=<id>.planning/sessions/<id>.attached附着,或删除过期的.planning/sessions/目录关闭隔离
Ambiguous planWARN嵌套计划歧义:cwd 的正下方项目自带计划,hooks 拒绝猜测,用PWF_PLAN_ROOT=<绝对项目根>PLAN_ID=<slug>钉住线程
PWF_PLAN_ROOT is not a supported absolute local directoryWARNpin 指向的不是绝对本地目录,修复或取消 pin(坏 pin 失败关闭,什么都不注入)
PLAN_ID does not name a plan directoryWARNPLAN_ID未命名.planning下任何计划目录,修复或取消 pin(设置即绑定,失败关闭而非另选计划)
其他情况WARN+ 字节数输出了内容但没有帧,说明计划上下文没有到达模型,这是 doctor 无法识别的拒绝通知,需直接运行sh scripts/inject-plan.sh --context=userprompt阅读原文

这个"默认 arm 警告而非通过"的设计是刻意的:横幅措辞漂移时应退化为响亮的警告,而不是静默的 PASS。这正是旧版 bug 的教训——旧代码里PWF_PLAN_ROOT is not a directory字面量从未是inject-plan.sh实际输出内容的子串,该 arm 是死代码,执行落入成功臂,于是一个完全"暗 hooks"的状态被报告成 PASS,还带着拒绝通知自身的字节数。参见 tests/test_plan_doctor_classification.py 中的test_dark_hooks_never_report_passtest_an_unrecognized_refusal_banner_warns_instead_of_passing

另外两个注入相关的边界情况:有可解析计划但注入输出为空 →FAIL,并列出已知静默原因(v3.6.0 之前 + PATH 上的 Windows 原生 realpath、PLANNING_DISABLED=1、计划目录位于项目根之外、过期.planning/sessions/目录没有附着会话——后者会让 pretool/precompact 触发整体静默);没有计划时注入静默 →PASS,这是正确行为。若inject-plan.sh缺失则WARN,并提示查看 docs/installation.md 的安装矩阵。

4. 认证状态(attestation)

doctor 查找认证文件:优先${RES}/.attestation(平行计划模式,与计划目录同目录),其次.plan-attestation(legacy 根计划模式)。找到则info报告路径,否则info说明"legacy 模式可选、v3 模式默认开启,批准计划后运行/plan-attest"。

认证机制的完整语义见 commands/plan-attest.md 与 scripts/attest-plan.sh:attest-plan.sh对解析出的task_plan.md计算 SHA-256,把十六进制摘要写入.planning/<active-plan>/.attestation(平行模式)或./.plan-attestation(legacy 模式),随后每个 UserPromptSubmit / PreToolUse hook 触发时都会把当前文件与存储哈希比对,一旦发散就输出[PLAN TAMPERED — injection blocked]而不是向模型喂计划内容。支持--show(打印已存哈希与存储位置)和--clear(移除认证、重新开放编辑)。

5. 安装面(install surface)

doctor 逐一探测四个技能目录安装面是否存在:

  • .claude/skills/planning-with-files(项目级)
  • ${HOME}/.claude/skills/planning-with-files(用户级)
  • .agents/skills/planning-with-files(项目级,OpenCode 等读取)
  • ${HOME}/.agents/skills/planning-with-files(用户级)

一个都没找到时输出info(插件路线的安装位于插件缓存下,而不是技能目录)。随后还有一条"路线提醒":插件路线随包提供 commands/ 与 hooks;npx-skills 路线只提供技能本身。项目级技能安装后 hooks 静默?需要检查项目信任(hasTrustDialogAccepted)以及 docs/installation.md 的安装矩阵。

6. 单次 hook 延迟(hook latency)

doctor 用纳秒时钟测量一次inject-plan.sh --context=userprompt的墙钟耗时:

T0="$(date +%s%N 2>/dev/null)" || T0="" sh "${INJ}" --context=userprompt >/dev/null 2>&1 T1="$(date +%s%N 2>/dev/null)" || T1="" MS=$(( (T1 - T0) / 1000000 ))

date二进制不支持纳秒时钟(T0/T1非数字),则info说明跳过。这条检查对排查"每次 hook 触发是否明显拖慢对话"非常直接:每轮 UserPromptSubmit、每个匹配的 PreToolUse 都各触发一次注入,单次成本乘以触发次数就是可见的感知延迟。

故障与修复对照

命令文档明确给出了 FAIL 行的修复指引:

故障行修复路径
resolver FAIL检查.planning/.active_plan内容与计划目录名
有可解析计划但 injection FAILhooks 处于静默暗态。若属 v3.6.0 之前 + Windows 原生 coreutils realpath 的根因,升级到 v3.6.0 以上修复;同时检查PLANNING_DISABLED
tamper WARN若计划编辑是有意为之,用/plan-attest重新批准

doctor 的另一个实用信号在脚本开头:PLANNING_DISABLED=1已设置时,会提前输出WARN——该环境下每个 hook 都会立即退出(这是 issue #195 引入的按次调用退出开关,供与计划共享 cwd 但从未选择加入的一次性/CI 会话使用,见 scripts/inject-plan.sh 第 77-79 行)。

诊断原则:只报告,不修复

命令文档与脚本都强调:doctor 是纯诊断工具,不做任何自动修复。遇到 FAIL 时,正确的动作是解释对应的修复步骤(上述对照表),然后让用户或配套命令(/plan-attestinit-session.sh、环境变量修正)来执行。这与项目整体的"绝不破坏 Agent 循环、失败关闭"哲学一致:诊断信号走人可读的输出通道,修复动作走显式的用户批准流程。

源码与测试佐证

  • 命令定义:commands/plan-doctor.md——frontmatter 声明disable-model-invocation: trueallowed-tools: "Bash",正文给出执行步骤、输出报告约定与修复指引。
  • 实现:scripts/plan-doctor.sh——六段检查的完整 sh 实现,约 170 行,无外部依赖,set -u防未定义变量。
  • 解析依赖:scripts/resolve-plan-dir.sh——PLAN_ID / .active_plan / newest-by-mtime 三级决议 + slug 校验 + 遏制守卫 + 反斜杠归一化。
  • 注入依赖:scripts/inject-plan.sh——按--context=userprompt|pretool|precompact|preflight|validate区分注入形态,含会话附着守卫与多根消歧。
  • 认证依赖:scripts/attest-plan.sh 与 commands/plan-attest.md——SHA-256 锁定计划内容,--show/--clear两个旗标。
  • 回归测试:tests/test_plan_doctor_classification.py——覆盖"真实篡改必报 WARN"、"计划正文引用全部控制字面量仍必须 PASS"、"暗 hooks 绝不报 PASS"、"被拒 PLAN_ID 绑定绝不报 PASS"、"无计划时的静默即正确"以及"无法识别的拒绝横幅降级为 WARN"六类场景,其中第三条与第六条正是历史缺陷 2(假 PASS)的回归锁。
  • 发布记录:CHANGELOG.md 3.6.0 条目——记载 doctor 的引入动机(该故障类别全部静默、损坏安装与"尚无计划"外观一致)与六项检查内容。
  • 安装矩阵:docs/installation.md——不同路线(插件 / npx-skills / 手工技能拷贝 / OpenCode / Hermes)分别提供哪些面(SKILL.md、命令、hooks),以及两条会导致独立技能路线无活跃 hook 的条件(项目信任未接受、技能未被调用)。

何时该跑一次 doctor

/plan-doctor当作安装后与故障期的固定动作:

  • 新机器 / 新项目装完插件或技能后跑一次,确认六项全绿再开始长任务;
  • hooks 突然变安静——计划明明存在,模型却不再看到计划上下文——立刻跑一次,用 injection 行的分类结果定位静默原因;
  • Windows 环境升级后跑一次,确认规范化器输出的路径形态被正确识别(v3.6.0 起已处理,旧版本在这类机器上会整体失效);
  • 怀疑会话隔离 / 嵌套项目 / pin 失效时,doctor 会用对应的 WARN 文案直接把根因指出来。

一句话总结:在 planning-with-files 里,"没有报错"不等于"一切正常";/plan-doctor用一次只读的、永不失败的运行,把那些设计上就静默的机制照出原形——PASS 让你安心,WARN 告诉你该修什么,FAIL 告诉你修复从哪查起。

【免费下载链接】planning-with-filesPersistent file-based planning for AI coding agents and long-running tasks. Crash-proof markdown plans, session recovery after /clear and compaction, per-turn re-injection against context rot, deterministic completion gate. Manus-style. Install from npm, the Claude Code plugin marketplace, or npx skills. Codex, Cursor, OpenCode, 60+ agents.项目地址: https://gitcode.com/GitHub_Trending/pl/planning-with-files

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询