需求派生树与收口真源
用途:解决「需求一开始只有 1、2、3,做到 A 时引出 1.1、1.2、1.3,推进 1.3 又引出 1.3.1、1.3.2……最后谁也说不清哪条为什么存在、哪些还没收口」。
把需求及其派生关系、处理与收口状态收进一份机器可校验的真源,其余视图全部是派生物。
典型触发:「记一下这个需求/待办」「这条需求为什么会有」「哪些还没做完/没收口」「需求又扩散了」「把待办和收口情况理一下」,或推进项目时你刚发现了一个新问题、新边界、新约束。
一、铁律(不遵守则系统立刻失去价值)
- 所有写入只走 CLI。真源是
tree.json,由todoctl唯一管理。
⛔ 不要手改tree.json,⛔ 更不要手改TREE.md/ACTIVE.md/FOCUS.md/dashboard.html(它们是render的产物,会被覆盖)。出现「两个真源」是这套系统最大的威胁。 - 身份与路径分离。节点身份是不可变 ID(
R-0007),1.3.2只是render算出来的位置编号。
引用节点一律用 ID,不要用1.3.2—— 一旦插入/移动节点,编号会整体错位,所有历史引用同时失效。 - 新增节点必须回答「为什么会有它」。
--why记录触发它的具体事实(在哪条需求、做什么动作时发现)。
没有why的节点等于断掉追溯链,等于回到表格时代。 - 收口必须附证据。
close强制要求至少一条--evidence。
没有证据的「已关闭」是假关闭,会直接污染真源。 done≠closed。done= 我改完了;closed= 已验证/已确认收口。两步分开,防止自己给自己盖章。- 报告成功 ≠ 真的生效。写入后必须回读校验(
todoctl show <id>/check/render产物 mtime)。 - 不可撤销的语义变更先确认:
drop(放弃)、带--override-reason的收口、--force跳过前置检查 —— 这三类必须由用户本人明确同意后才执行。
二、真源与派生物
| 文件 | 性质 | 说明 |
|---|---|---|
tree.json |
真源 | 扁平节点表,派生关系由 parent 指针表达 |
journal.jsonl |
真源(追加式) | 变更日志,每条带归属线与字段级 before/after;log 的唯一依据。只增不改、不手改、不进脱敏导出 |
baselines.json |
真源(追加式) | 基线列表 —— 不可变里程碑。baseline 写入,只增不改 |
baselines.json 的 ID |
— | 形如 B-YYYYMMDD-HHMMSS;同秒连续封板会追加 -02/-03 保证唯一。旧数据里的重复 ID 会由 baseline --list 显式提示 |
snapshots/ |
真源(自动留档) | 写入前自动快照:tree.json 的 last.json / prev.json / 按天一份;基线封板前另存 baselines-last.json。无 git 时的回滚依据 |
tree.lock |
运行时 | 跨进程写锁。常驻不删(进程退出自动释放),不要手动清理 |
TREE.md |
派生 | 全树视图,已全部收口的子树折叠为一行 |
ACTIVE.md |
派生 | 活跃视图,只列未收口节点 + 祖先上下文 |
FOCUS.md |
派生 | 聚焦视图,当前推进分支及其派生 |
RESUME.md |
派生 | 恢复简报:上次做到哪、下一步、期间变动、未收口阻塞项 |
BENCH.md |
派生 | 台面:手头全部进行中任务,按线分组 + 下一步 + 搁置天数 + 绕行标记 |
BASELINE.md |
派生 | 最新基线摘要 + 各线进度 + 基线历史 |
dashboard.html |
派生 | 单文件看板,可按状态/关键字/告警筛选 |
> 派生物会自动刷新:任何一次真源写入(add / start / close / focus …)成功后,只要该目录已存在 TREE.md,七个视图会被自动重新生成 —— 不需要你记得手动跑 render。
> 视图过期是「记录对不上」的老根源,所以它不由人负责。要临时关掉用 --no-render。
> 全新真源要先手动跑一次 render(那时还没有 TREE.md 可判断),之后才自动跟随。
> 渲染耗时与节点数近似线性:800 节点一次写入约 0.3 s,其中约 0.24 s 是 Python 进程启动本身 ——
> 也就是说规模基本不再是成本项。
存放位置(todoctl 自动判定,可用 --root 或 $TODOCTL_ROOT 覆盖):
--root显式指定- 环境变量
TODOCTL_ROOT - 当前目录已是真源:
docs/requirements//.req-tree/requirements//requirements/下已有tree.json - 兼容:旧版宿主专有路径
.workbuddy/requirements/下已有tree.json(仅识别,不再新建) - 当前目录已预建:
docs/requirements/或.req-tree/requirements/(空目录即生效 —— 只限这两个本系统自建命名空间) - 检测到 git 仓库 →
<仓库根>/docs/requirements/(推荐:与代码同版本,git log即审计日志) - 未检测到 git 仓库 →
<当前目录>/.req-tree/requirements/
> ⚠️ 第 3、5 条只认上列名字。裸 requirements/ 必须已经是真源(含 tree.json)才会被优先命中 ——
> 否则一个存放依赖清单的 requirements/ 目录会把真源劫持到仓库子目录里,静默产生第二棵树。
> init 会打印「根定位方式 / 当前目录 / 真源目录」三行,落点不在当前目录下时还会额外告警;请对照确认。
>
> 迁移兼容:真源若落在旧版路径 .workbuddy/requirements/,仍会被认出(第 4 条),
> 但 check 会主动提示你搬到 .req-tree/requirements/ 或 docs/requirements/ ——
> 搬的时候把 tree.json / journal.jsonl / baselines.json / snapshots/ 一起带走即可,不会丢数据。
>
> 本工具不绑定任何宿主:默认落点、启动脚本、文档里都没有写死的宿主私有目录。
> 公开版/脱敏镜像库场景:用 todoctl export 导出 —— 恒脱敏,没有「不脱敏」模式
> (一个以安全为目的的命令不该带危险开关;要完整副本直接读真源目录即可)。
> 它清空 why/notes,并把标了 internal 的节点连同整条子树剔除。
> ⚠ 唯一无法自动脱敏的是 title —— 每次导出都会提示「本次将导出 N 个节点,公开前请人工复核标题」。
>
> 更省事的一条路:如果你的公开版根本不需要待办树,直接把 docs/requirements/ 排除掉,
> 压根不用 export。export 只服务于「要公开、但要公开一个子集」这个场景。
三、怎么用、去哪看(入口索引)
完整的功能与入口索引见 references/entry-map.md —— 按「我想做 X → 打哪条命令 → 结果去哪看」组织,
含全部子命令与各自选项。这里只给最常用的骨架:
| 我要做的事 | 命令 | 去哪看 |
|---|---|---|
| 开工:拿回上次现场 | resume(每天第一条命令) |
RESUME.md |
| 记一条新需求 / 派生需求 | add --parent <ID> --title … --why … --done-when … |
TREE.md、ACTIVE.md |
| 开始做 | start <ID>(需 done_when) |
ACTIVE.md |
| 随手记进度 | note <ID> 做到哪 发现了什么 |
RESUME.md 的进度轨迹 |
| 看手头同时有几件事 | bench |
BENCH.md |
| 实现完成 → 验证后收口 | done <ID> → close <ID> --evidence "…" |
TREE.md(✅) |
| 体检 | check(有错返回 1) |
终端 |
| 出全部视图 | render |
7 个派生物 |
| 把各线成果封成一个时点 | baseline --name "…" |
BASELINE.md |
| 本次提交到底写了什么 | log --since "…" [--line <线ID>] |
终端 / --commit-msg |
- 五个工作流(首次建真源 / 派生新需求 / 推进与收口 / 出视图与巡检 / 每周 rebalance)见
entry-map.md第五节 - 触发时机与写入纪律见
references/discipline.md - ⚠️
--force与--override-reason是两个独立的确认,常被混用 ——--force跳不过假收口检查。
连同退出码约定,都在entry-map.md的「全局约定」里
四、会话恢复(跨次交接)
需求会记在树里,但「我上次做到哪一步」不会自动留在树里 —— 除非你留痕。这一节解决的就是「隔了一天/一周再回来,怎么接上」。
三个必须养成的动作
开工:todoctl resume # 一条命令拿回现场
过程中:todoctl note R-0007 这次做到哪 发现了什么
收工:todoctl note R-0007 收尾 --next-step "明天先跑一遍 XX 用例"
note 是唯一能留住「过程」的手段。没有 note,下次 resume 只能告诉你「这条需求还在推进中」,却说不出卡在哪一句话上。
⚠️ note / add 的长文本只能走位置参数,没有 --file / stdin 通道 ⇒ 文本里的
反引号会被 shell 当命令替换吃掉($ 同理),且不会报错、事后极难发现
(本机实测两次:R-0080 与 R-0088 的一批取证 note 里,代码片段整段消失)。
对策:① 文本里不要用反引号,标识符直接写、或用 「」;② 已经写坏了就补一条校正 note
(note 追加语义,不改历史);③ 需要引用代码就描述位置(app/x.py:123)而非贴片段。
⚠️ 已收口的节点仍可 note —— 这是被鼓励的做法:收口后才发现的新证据/失效的旧断言,
用 note 补记而不是把节点 restore 重开。状态不受影响(实测 R-0059/0060/0061/0062/0087)。
交错推进(跳着做是常态,不是错误)
真实工作几乎不会按派生顺序走:在做 1.1.2、下一步该做 1.1.3,但你被拉去做 2.1、2.2,然后才回头做 1.1.3。
这本身没有数据风险(单人顺序操作,不会写坏真源)。风险在于三个「看不见」:
| 风险 | 工具给的解法 |
|---|---|
| 忘了回来 | bench 的搁置天数;W5(doing 超过 7 天未动) |
| 不知道自己在绕 | update <id> --next <ID> 声明计划中的下一步 → W6 提示「正在绕行」,且 start 别处时当场打印绕行提示 |
| 看不到手头有几件事 | bench / BENCH.md:全部 doing 按线分组 + 各自下一步 + 最近进度 |
用法就两步:
todoctl update R-0007 --next R-0008 --next-step "先跑一遍用例" # 声明计划下一步
todoctl bench # 随时看全手头
--next 是节点指针(可被校验、能算出绕行);--next-step 是文字说明。两者可并存、互不替代。
跳过计划下一步不阻断 —— 绕行是正常决策,工具只负责让你「知道自己在绕」,不替你判断该不该绕。
resume 会给你什么
| 区块 | 回答的问题 |
|---|---|
| 你在做什么 | 当前聚焦节点 + 从根到它的完整路径 |
| 为什么做 / 收口条件 | 这条需求的出发点与验收标准 |
| 下一步 | 上次记下的下一动作(没记会明确提示你补) |
| 进度轨迹 | 该节点最近 5 条 note(最近发生了什么) |
| 该节点告警 | W1~W4(如:正在推进但依赖未收口) |
| 由它派生 | 从它长出来的子项及各自状态 |
| 自上次恢复以来的变动 | 上次 resume 之后,哪些节点被新增/推进/收口/阻塞 |
| 别忘了 | 所有未收口的阻塞项(跨分支,防止卡点被遗忘) |
恢复锚点
resume 会把「现在」写成锚点(tree.json 的 last_resume),之后每次写入都会让 RESUME.md 里的变动清单累积。
所以:早上 resume 一次,明天打开时 RESUME.md 就是「昨天到今天发生了什么」。
- 锚点是秒级精度:与锚点同一秒内发生的改动不计入变动清单。真实使用间隔是分钟/小时级,无影响。
- 没设
focus时,resume会推断恢复目标(最近变动的doing节点)并标注「(推断)」,避免空手而归。
变动按需求线分流(防止跨需求交叉)
一天里你往往同时碰几条需求。如果 resume 把期间所有变动平铺出来,恢复现场就变成一锅粥,
提交时也说不清这个 commit 到底属于哪条线。
所以每条变更都带归属线(line = 该节点所属的最顶层根需求),恢复时自动分流:
| 类别 | 处理 |
|---|---|
| 本条线(与当前聚焦同属一个顶层根) | 明细列出,进现场 |
| 旁支(其他需求线) | 只给「哪条线 / 几个节点变了 / 一条命令接手」,不混入现场 |
关键点:旁支成果不会丢 —— 它已经在真源里了,只是不摆到当前现场。
提交时用 log 按线抽出叙述,commit message 就不会是一锅粥:
todoctl log # 全部变更,按线分组
todoctl log --line R-0031 # 只要 OCR 这条线(可给该线上任意节点)
todoctl log --since "2026-09-22 09:00" --commit-msg # 上次提交以来,直接出草稿
journal.jsonl 是追加式变更日志(只增不改),也是 log 的唯一依据。它是真源的一部分,要一起提交、不要手改;也不要进脱敏导出。
与其他机制的分工
| 想知道 | 看哪里 |
|---|---|
| 现在有哪些事没做完 | ACTIVE.md |
| 手头正在做哪几件事、哪件搁置最久、有没有在绕行 | bench / BENCH.md |
| 我上次在做什么、下一步 | resume / RESUME.md |
| 这条需求为什么存在 | TREE.md + show |
| 近 7 天的整体进出 | report --days 7 |
| 跨次累积的变动(按线分流) | RESUME.md 的「自上次恢复以来的变动」 |
| 期间哪些需求线被碰过、各改了什么 | log / log --commit-msg |
⚠️ RESUME.md 与 FOCUS.md 不进脱敏导出(含内部进度轨迹与工作现场)。
五、封板、回滚与并发保护
封板(baseline)—— 把散在各线的成果合成一个可交付时点
交错推进一段时间后,你需要一个「到此为止」的时点:此刻各条线分别走到哪、哪几件正在进行。
todoctl baseline --name "v0.3 首版" --note "首版功能齐了"
它做三件事:
- 冻结一份不可变快照(节点规模、各线进度、当时正在进行的项)写入
baselines.json;此后无论怎么改,历史基线都不变 - 打印相对上一基线的差异(节点 / 已收口 / 未收口 / 进行中 / 阻塞的增减 + 各线进度变化)
- 刷新
BASELINE.md
- 真源有错时拒绝封板 —— 基线必须是干净的时点
- 比对用
todoctl baseline --diff(不带值 = 与上一个比) - ⚠️
--diff输出的右侧 ID 不是基线:它形如对比:`B-甲` → `B-乙`,其中B-乙是打印时刻现算的临时标签,代表「当前实时状态」,
不会被写进baselines.json/BASELINE.md,--list里也查不到。「看到两个 ID 但只查到 1 条基线」是正常现象,不是记录丢失。
想比对两个真实基线,必须显式给--diff <B-xxx>。
回滚(restore)—— 没有 git 也能退回上一步
每次写入前都会自动留档到 snapshots/:last.json(上一版)、prev.json(上上版)、按天一份(保留 30 天)。
todoctl restore --list # 先看有什么
todoctl restore --last --yes # 退回上一次写入之前
todoctl restore --date 2026-09-22 --yes # 退回该日首写之前
- 必须显式
--yes:回滚是不可逆操作;未加--yes只会打印「将要发生什么」然后返回2 - 执行前会把当前状态另存为
pre-restore-<时间>.json—— 所以回滚本身也可逆 - 回滚会自动抬高 ID 水位(扫
journal与baselines里出现过的最大 ID),保证「ID 永不复用」这条不变量不被回滚破坏
> 分工:整体回退用快照,逐字段追溯用 journal 的 before/after(todoctl log)。
并发保护(tree.lock)—— 只防「同时写」
同机多开进程同时写会静默丢数据(读-改-写的后写覆盖先写)。写入命令会先取 <root>/tree.lock 的非阻塞字节锁:
| 情况 | 行为 |
|---|---|
| 拿到锁 | 正常写入 |
| 拿不到 | 拒绝并告知(附原因与 --wait 建议),而不是覆盖别人 |
| 只读命令 | check / log / bench / report / show 是真·不写盘,不加锁,写入进行中也能用 |
| 写盘命令 | render(写 7 个视图)与 export(写 public/)也持锁 —— 判据是「是否写盘」,不是「是否改真源」。否则两个并发 render 会写出互相撕裂的视图(TREE.md 来自版本 A、ACTIVE.md 来自版本 B),而视图正是人做判断的依据 |
| 想等一会儿 | --wait 5 |
| 锁文件本身 | 常驻不删(进程退出由系统自动释放),避免每次写入都往回收站丢文件 |
⚠️ 这把锁只防「同时写」,不限制「并行推进多条需求」。交错推进一直是允许的、也是常态。
六、强校验清单(check 会阻断的错误)
| 码 | 规则 |
|---|---|
| E1 | ID 格式非法(须 R-0001 形式) |
| E2 | 父节点不存在 |
| E3 / E10 | 父子关系成环 / depends_on 成环 |
| E4 / E5 | 状态或类型不在枚举内 |
| E6 | closed 缺少收口证据 |
| E7 | doing/done/closed 缺少 done_when |
| E8 | closed 缺少 closed_at |
| E9 | 依赖指向不存在的节点 |
| E11 | ID 重复 |
| E12 | 假收口:已 closed 但仍有未被显式接受的未收口后代。--override-reason 只接受收口当时存在的那批(记入 close_override_ids);收口后新增的未收口子项照旧报错 |
| E13 | blocked 缺少阻塞理由 |
| E14 | 标题为空 |
| E15 | 「计划中的下一步」(next)指向不存在的节点 |
| E16 | next 指向自身 |
不阻断但必须看的告警:
W1 深度超限、W2 同父未收口过多、W3 推进中依赖未收口、
W4 open/blocked/done 长期未更新(> 14 天)、
W5 doing 搁置(> 7 天未动)、W6 计划中的下一步尚未开工(正在绕行)、
W7 祖先已收口/已放弃但它仍在 doing/done(--force 放行 start 后的兜底告警)、
W8 计划中的下一步已终结(closed/dropped) —— 该计划已失效,需更新或清除 --next。
> W6 与 W8 的区别:W6 是「绕行」(下一步还没开始,你还能回来);W8 是「计划失效」
> (指向的节点已经收口/放弃,原计划不再成立)。bench 上分别是 ↷绕行中 与 ⌫计划已失效。
> W4 与 W5 互斥:doing 只出 W5,其余状态只出 W4,不会重复告警。
start / unblock 的两道前置门(除了需要 done_when):
① depends_on 未收口 → 拒;② 祖先已 closed/dropped → 拒。
两者都可用 --force 显式放行(疏而有痕:放行后 W3/W7 会持续提示,而不是静默)。
在已收口的祖先下开工,等于正在制造一个假收口 —— 这是 E12 在时间轴上的前置版本。
七、常见错误
| 现象 | 正确做法 |
|---|---|
用 1.3.2 当身份引用 |
用 R-0007。编号会变,ID 永不变 |
手改 TREE.md 补一行 |
改真源(todoctl update / add)后重跑 render |
| 攒一堆需求最后一起录 | 发现即落库,why 才能写准 |
任务做完直接 close |
先 done,验证通过再 close 并附证据 |
从 open 直接 done |
被拒("只有 doing 才能标记 done")⇒ 顺序只能是 start → done → close。想少两步可以用 close --force 跳过 done 前置检查,但那会跳过 done 语义、少一条留痕;线根/需求节点收官建议老实走三连(journal 留有完整生命周期) |
| 收口后不回头看祖先 | close 后必查祖先验收条件是否失效 |
| 树太深就删节点 | 不要删。用 drop --reason 归档,或 close 后由折叠机制收进一行 |
「判定不做」的需求直接 close |
用 drop --reason。✅ closed 会被后来读树的人误读成「该功能已实现」;判定不做/不实现/不接入一律记 ⏹ dropped,理由必须写进 --reason(用户面口径,需用户拍板) |
drop / close 完就走人 |
两者都会让两处引用失效,必须同批清掉,否则视图会撒谎:① 被终结的节点若被别的节点 --next 指向 → 触发 W8(计划失效)→ update <父ID> --next "" 清指针;② 它若正是 focus 目标 → FOCUS.md 的「当前分支」会继续显示这条已放弃/已收口的分支 → focus <别的ID> 复位。<br>⚠️ close 同样会悬空,且 check 不会报(focus 从不进强校验)—— 2026-09-23 实测:R-0088 已 closed、其父 R-0045 也已 closed,FOCUS.md 仍以「7 开发环境与工具链 / 7.3 …app_settings」为当前分支。<br>⇒ 收口一条线的根节点时,若刚聚焦过该线的任一节点,务必顺手复位(聚焦目标优先选「该线声明的 next」,与 W6 口径一致) |
只改状态、不留 note |
下次 resume 只能说「还在推进」,说不出卡在哪一句话 → 做一步记一句 |
| 收口时证据写「整理自既有记录」这类占位串 | 证据在收口后事实上不可变 —— close 对已 closed 节点直接 Fail("已收口"),update 也没有 --evidence ⇒ 没有事后补写通道(只能 restore 快照整体回滚)。所以收口当刻就要写可复核的物证:commit:<sha> / test:<用例名> / doc:<路径> / script:<路径> / user:<谁>确认。写「整理自某处」等于给未来留一个退不掉的假收口 |
收工不记 --next-step |
下次开工要多花十分钟重新进入上下文 → 收工时一句话搞定 |
note 文本里用反引号 |
从 shell 传参时反引号会被当命令替换吃掉($ 同理),静默、事后极难发现(实测两次)。长文本只用位置参数、没有 --file/stdin ⇒ 文本里别用反引号,标识符直写或用 「」;已写坏就补一条校正 note |
直接按 why(触发理由)开工 |
why 是立案当刻的判断,可能已过期。开工第一步应是先验证前提是否仍成立 —— 本机实测 R-0083:why 写「判据未定、门禁拦不住」,实测判据早已定、真正的问题在别处(豁免清单两份副本漂出死项)。前提变了就先更正 why/note,再动手,否则会去修一个不存在的问题 |
| 收口时只收编自己的发现 | 顺带挖出的其它缺陷(哪怕同根因)要另立节点,不要塞进本条 --reason/--evidence:混在一起会让「单一真源」破功,且关掉本条就等于把新缺陷一起埋了(实测:做 R-0083 时挖出同根因的 R-0088/R-0089) |
| 汇报时用「已定位/已归因」暗示「已修复」 | 两者是不同状态,必须分开写清:归因(做完对照实验、定位到单行)≠ 已修(代码已改 + 读数已变绿)。实测翻车:快车道红点只做了归因与记录(节点仍 open),但因回复里写了「已用对照实验排除 + 定位真因」,用户直接追问「刚才的报错怎么修的?」⇒ 只好先纠正「没修」。自检:给用户结论前问一句「这条的 status 是什么?」——open/doing 就不能说「已处理」 |
| 跳着做了却不说 | 绕行会变成「静默丢失」——你只知道自己在忙,不知道 1.1.3 被跳过了 → 声明 --next <ID>,bench 会一直提示 |
用 --next 写文字 |
--next 是节点 ID 指针;文字说明用 --next-step |
| 把开发库待办树推到公开版 | 走 export(恒脱敏、剔除 internal 子树);RESUME.md / BENCH.md / BASELINE.md / FOCUS.md 默认不导出。不需要公开就把目录整排除 |
手动删除 snapshots/ 或 tree.lock |
前者的删除量很小(每天最多 1 个文件,自动保留 30 天);后者常驻是设计如此,删了也没意义 |
| 用回滚来「清理」树 | 回滚只是应急;要清理用 drop --reason + 折叠机制。回滚会连带撤掉之后的全部合法改动 |
不看 --yes 提示就重跑 |
未加 --yes 时返回码是 2、真源未被改动 —— 这是给你的确认机会,不是报错 |
| 同时开两个会话写同一个真源 | 第二个会被写锁拒绝(这是对的)。真要并行,让它们各写各自的项目真源 |
| 在已收口的父需求下继续开工 | start 会直接拒绝。要么把这项工作改建为自己的节点,要么说明父项收早了(update 修正父项),确认可接受才加 --force(会留 W7 告警) |
| 收口后才想起来还有活儿 | 收口时显式接受的那批是封存的;之后新增的子项一定报 E12。这说明父项该重新推进,而不是再 override 一次 |
直接改 baselines.json |
封板前会先解析校验,解析失败时拒绝封板(不再按空处理、覆盖掉全部历史);损坏时从 snapshots/baselines-last.json 取回上一版 |
| 把真源建在了意料之外的位置 | 先看 init 打印的「真源目录」与「当前目录」;落点不在当前目录下时会额外告警。裸 requirements/ 只有已是真源才会被优先命中 |
⚠️ --root 传成了「仓库根」(它要的是真源目录,如 <repo>/docs/requirements) |
✅ 2026-09-22 已加守卫:真源不存在时,除 init(负责创建)/ restore(从快照恢复)外的所有命令一律 exit 2 拒绝,并打印「落点判定 + 两条处理建议」。此前 render 会在错误位置静默生成 8 个空派生物(TREE/ACTIVE/FOCUS/RESUME/BENCH/BASELINE.md + dashboard.html + tree.lock)而 check 只显示「节点 0」不报错 —— 把「参数指错目录」伪装成「树是空的」。回归用例 L16(7 条)已做变异验证:禁用守卫后 4 条立刻变红。<br>⚠️ 治本纪律仍适用:批量写前先跑一条试探;写完看节点数是否等于预期,而不是只看「✅ 通过」 |
八、细节页
| 需要什么 | 读哪个 |
|---|---|
| 功能与入口索引(我要做 X → 打哪条命令 / 结果去哪看;全部子命令与选项 + 五个工作流) | references/entry-map.md |
| 字段定义、状态机语义、ID 与编号规则 | references/schema.md |
| 触发时机、写入纪律、rebalance 与脱敏流程 | references/discipline.md |
工具本体:scripts/todoctl.py(零第三方依赖,仅标准库)。三个入口完全等价,都只从 PATH 找 Python、
不含任何写死的安装路径(换机器、换平台、换用户都不用改):
| 入口 | 适用平台 |
|---|---|
scripts/todoctl |
macOS / Linux / WSL / Git Bash |
scripts/todoctl.cmd |
Windows 命令行 |
scripts/todoctl.py |
通用 —— 任何 python3 可用的地方 |