From f03b806092bef9e32d9c568156ce51e223cd944c Mon Sep 17 00:00:00 2001 From: hkwuks <25031515+hkwuks@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:13:48 +0800 Subject: [PATCH 1/3] fix: close a Step only when it says `- Status: done` The gate read the field as a blacklist: `pending`, `in_progress` and `blocked` were named, and everything else counted as finished. So `completed` archived an unfinished Plan, and so did a misspelling, an empty value, and a Step with no Status line at all. Measured 2026-10-27 by driving the gate directly: `completed`, `complted`, `skipped`, `wip`, `DONE`, an empty value and a missing line each returned exit 0. `hooks/task progress` has only ever accepted `pending|in_progress|done| blocked`, so none of those spellings could have been written by the tool. They are hand edits made because the two sides disagreed, and reading anything unknown as closed is that disagreement, not a fix for it. Closed is now a whitelist of the one word the writer accepts. The block is located by its `### Step ` heading anywhere in the Plan, which is how `task progress` finds the block it edits; scanning only `## Steps` would give the reader a different set of Steps than the writer changes. Knowingly still open and pinned by an assertion: a Plan with no `### Step` at all is not refused, because "must a Plan have Steps" is a separate rule. Five archived Plans read as unfinished under the new rule and are not rewritten -- `achieved/` is read-only in place, and editing history to pass a check that no longer applies to it would make the record untrue. Run over all 91, only two change verdict; the other three were already refused on main for unrelated reasons. smoke grew by 19 completion-gate assertions. Five mutations were run against them, each reddening a named assertion; the fourth showed the first version of the assertions did not pin the trailing-whitespace tolerance, so two fixtures were generated with printf to cover `\r` and trailing spaces. --- CHANGELOG.md | 8 ++ .../2026-10-27-close-step-status/plan.md | 120 ++++++++++++++++++ .../2026-10-27-close-step-status/prd.md | 74 +++++++++++ TaskFlowDocs/todo.md | 60 +++++++++ hooks/completion-gate | 45 ++++++- hooks/smoke-test | 69 ++++++++++ skills/taskflow/SKILL.md | 2 +- skills/taskflow/references/runtime.md | 4 +- 8 files changed, 377 insertions(+), 5 deletions(-) create mode 100644 TaskFlowDocs/2026-10-27-close-step-status/plan.md create mode 100644 TaskFlowDocs/2026-10-27-close-step-status/prd.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 754eb9c..a0f7ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +### Fixed + +- **A Step counts as closed only when it says `- Status: done`, so a word the gate did not know can no longer archive a Plan that is not finished.** `hooks/completion-gate` read the field as a blacklist — `pending`, `in_progress`, `blocked` were named and everything else was finished — so the word `completed`, a misspelling, an empty value, and a Step with no `- Status:` line at all all passed. Measured 2026-10-27 by driving the gate directly: `completed`, `complted`, `skipped`, `wip`, `DONE`, an empty value and a missing line each returned exit 0 and "finished". `hooks/task progress` has only ever accepted `pending|in_progress|done|blocked`, so those spellings could not have been written by the tool — they are the result of hand edits made precisely because the two sides disagreed. Closed is now a whitelist of one word, and it is the same word the writer accepts. The block is located by its `### Step ` heading anywhere in the Plan rather than only under `## Steps`, because that is how `task progress` finds the block it edits; a reader that skipped the other sections would see a different set of Steps than the writer changes. **Knowingly still open, and pinned by an assertion so it stays a decision**: a Plan with no `### Step` block at all is not refused, because "must a Plan have Steps" is a separate rule this change does not make. + +### Compatibility + +- **Five archived Plans read as unfinished under the new rule, and are not rewritten.** `achieved/` is read-only in place, so nothing here changes them and no task is blocked by them. They are: `2026-09-07-repository-standards-and-doc-root` (three Steps say `done (v1)`), `2026-10-11-gate-host-coverage` (four say `completed`), `2026-09-08-token-efficiency` and `2026-09-14-personal-docs-git-boundary` (their Steps still say `pending` while the Plan header says `completed` — the same disagreement running the other way), and `2026-10-22-taskflow-drift-guard`, whose `## Verification / Review` section reuses `### Step 1` as an ordinary subheading for a table. Recorded rather than repaired: editing history so it passes a check that no longer applies to it would make the record say something untrue. Only two of the five change verdict when the real gate is run over all 91 — the other three were already refused on `main` for reasons that have nothing to do with Steps, so this release does not newly refuse them. + ## [1.1.9] — 2026-10-26 ### Added diff --git a/TaskFlowDocs/2026-10-27-close-step-status/plan.md b/TaskFlowDocs/2026-10-27-close-step-status/plan.md new file mode 100644 index 0000000..c4153b7 --- /dev/null +++ b/TaskFlowDocs/2026-10-27-close-step-status/plan.md @@ -0,0 +1,120 @@ +# Plan — 一个 Step 什么时候算「关闭」 +> Task version: v1 +> Status: checking + +No spec required — small, self-contained task: one `awk` condition in one hook, its assertions, and three doc lines. No cross-layer contract, no new file, no interface change. + +## Reference Pointers + +- `hooks/completion-gate:67` — 当前的黑名单:`/^- Status: (pending|in_progress|blocked)$/ { unfinished = 1 }`。 +- `hooks/completion-gate:59-75` — 一次 `awk` 读完整个 Plan 的既有形状,与 `:22-31` 记的 `pipefail` / SIGPIPE 原因;本任务沿用,不引入管道。 +- `hooks/task:449-460` — 写侧定位 Step 的方式(`### Step ` 前缀 + 纯数字 + 数字后不接字母数字下划线),R2 要镜像的就是这一段。 +- `hooks/task:1099-1101` — 写侧取值集合 `pending|in_progress|done|blocked`。 +- `hooks/smoke-test:33-60` — `cg_case` 辅助函数与 `cg_approved` 夹具,新断言按它加。 +- `skills/taskflow/SKILL.md:313` — 待改的黑名单措辞。 +- `skills/taskflow/references/runtime.md:244-245` — 同上,两行。 +- `TaskFlowDocs/todo.md` → `TF-20261027-131c2b` — 本任务的来源条目与三条已裁定决定。 + +## Related Tasks + +- `TF-20261027-e4ba77`(长实现里保持在场)——同源但不同层:那条改注入节奏,本条改门的判据。本条不依赖它。 +- `TF-20261027-e8c6dc`(先问结论是否依赖外部事实)——同批记下的另一条,无依赖。 +- `TF-20261027-131c2b` — 本任务。 + +## Skills / Tools Used + +- [PRD] Unaided — no capability applied to this phase; considered: requirements elicitation and framing +- [Plan] Unaided — no capability applied to this phase; considered: work breakdown and task decomposition +- [Test] Unaided — no capability applied to this phase; considered: testing strategy + +## Preconditions + +- 工作树 `.worktrees/close-step-status`,分支 `fix/close-step-status`,base `main` @ `b47ea3f`。 +- `TaskFlowDocs/todo.md` 的未提交改动(三条 2026-10-27 条目)已随本任务进入本工作树。 +- 用户已裁定三个岔口(PRD 的 Open Questions)。 + +## Approval + +- Status: approved +- Approved by: user +- Approved at: 2026-10-27 21:59 +0800 +- Approved version: v1 +- Approved scope: PRD / Plan + +## Steps + +### Step 1 — 把门的 unfinished 判定改成块内的白名单,并用断言钉住 + +- Goal: `hooks/completion-gate` 只在一个 `### Step N` 块里确实写着列首 `- Status: done` 时才认为该块关闭;其余一切算未完成。行为变化只发生在这一处。 +- Dependencies: 无(第一步)。 +- Files: `hooks/completion-gate`、`hooks/smoke-test`。 +- Implementation checklist: + - [x] 删除 `hooks/completion-gate:67` 的列首黑名单行,改为块内白名单:`### Step <数字>` 处开启块,`## ` 或下一个 `### Step <数字>` 或文件尾关闭块;`END` 时也要结算最后一个块。 + - [x] 块的开启判定镜像 `hooks/task:449-460`:`### Step ` 前缀、纯数字、数字后不接 `[A-Za-z0-9_]`。不按 `## Steps` 段落过滤(PRD 第 5 条)。 + - [x] 关闭的判据只认列首 `- Status: done`,行尾空白容忍(`[[:space:]]` 在 `LC_ALL=C` 下含 `\r`)。一个块里出现多行 `- Status:` 时以最后一行为准,并写进注释。 + - [x] 保持 `unfinished` 的既有输出:`Plan has unfinished Steps`、`exit 1`;`--approval-only` 分支不检查 Step(R4)。 + - [x] 在 `awk` 的注释里写明 R5 的残留:没有 `### Step` 的 Plan 此项通过,这是本规则明确留下的 fail-open,属于另一个问题。 + - [x] `hooks/smoke-test` 在既有 completion-gate 段加 `cg_case`:A1(`done`→0)、A2(`completed`→1)、A3(`complted`/`skipped`/`wip`/`DONE`/`done (v1)`/空值/缺行→1)、A4(三个旧词→1)、A5(一 done 一 completed→1)、A6(`## Approval` 段与 `## Verification / Review` 段下的块,`completed`→1 且 `done`→0)、A7(无 `### Step`→0)、A8(`--approval-only` 对 A2 的 Plan→0),外加补 `\r` 与行尾空白的容忍。 + - [x] 断言里带一句注释,说明 A7 是**故意断言 fail-open**,不是漏写。 +- Acceptance: A1–A8 全绿;A4(`pending`/`in_progress`/`blocked` 仍被拒)与既有 `cg_case unfinished` 无回归;A10(既有 completion-gate 断言不改)。 +- Verification: 5 次变异全部验证过(记录在 `## Verification / Review`),`bash hooks/smoke-test` `ALL SMOKE PASSED`,`diff` 确认每次改完都按字节还原。 +- Rollback: `git checkout -- hooks/completion-gate hooks/smoke-test`(本步不碰其它文件,回滚无连带)。 +- Status: done + +### Step 2 — 让文档说白名单,并全量验证 + +- Goal: 三处文档不再描述黑名单;整仓检查通过。 +- Dependencies: Step 1(措辞要描述已实现的规则)。 +- Files: `skills/taskflow/SKILL.md`、`skills/taskflow/references/runtime.md`、`CHANGELOG.md`。 +- Implementation checklist: + - [x] `SKILL.md:313`:把「no Step still `pending`, `in_progress`, or `blocked`」改成白名单措辞——每个 Step 必须写着 `- Status: done`,其余一律算未完成,并点明这与 `task progress` 只肯写 `done` 是同一条规则的两侧。 + - [x] `runtime.md:244-245`:同义修改,两行都要动(一行描述 `archive`,一行描述 `completion-gate`)。 + - [x] `CHANGELOG.md`:`## [Unreleased]` 下加 `### Fixed` 与 `### Compatibility` 两段——症状(`completed` 在内的七种写法都放行)、根因(黑名单 fail-open)、块定位与写侧一致的理由、R5 的已知残留,以及 5 份归档 Plan 逐条点名不重写。 + - [x] 扫一遍是否还有别处复述黑名单:`grep -rn 'in_progress\`, or \`blocked\|pending, in_progress, or blocked' skills/ hooks/README.md`。 +- Acceptance: 三处文档与实现一致;`grep` 不再命中旧措辞。 +- Verification: `bash hooks/smoke-test`;`bash hooks/repository-check .`;`bash hooks/release-check .`;`git diff --check`;`python3 /scripts/quick_validate.py skills/taskflow`。 +- Rollback: `git checkout -- skills/ CHANGELOG.md`。 +- Status: done + +## Checkpoints + +- Step 1 之后:`git diff` 只含 `hooks/completion-gate` 与 `hooks/smoke-test`;A1–A8 逐条驱动过一次;至少 A2、A6 各做过一次变异验证(改坏→断言红→改回)。 +- Step 2 之后:全量验证四条命令的记录写进 `## Verification / Review`。 + +## Verification / Review + +- 2026-10-27 Step 2: bash hooks/smoke-test ALL SMOKE PASSED; 5 mutations each reddened a named assertion; release-check pass; quick_validate valid; git diff --check clean + +2026-10-27,全部在本工作树(`fix/close-step-status`)执行: + +- `bash hooks/smoke-test` — `ALL SMOKE PASSED`(含新增的 19 条 completion-gate 断言)。 +- `bash hooks/release-check .` — `STATUS: pass`;两侧 marketplace pin 仍是 `v1.1.9 -> 800e7f1…`。 +- `bash hooks/repository-check .` — `STATUS: needs-user-input`,三条原因都是本任务进行中的正常状态而不是缺陷:`Base: ambiguous`(新分支尚无 upstream)、`Working tree: has changes`、任务目录未跟踪。`Orphan task directories: none`、`Stale task worktrees: none`。 +- `git diff --check` — 干净。 +- `quick_validate.py skills/taskflow` — `Skill is valid!`。 +- 逐步驱动 `/tmp` 夹具跑过 A1–A8 与四个边界(`### Step 1b` 不算块、`### Step 12` 算块、` +` 尾、行尾空白)。 +- **五次变异验证,每次改坏后按字节还原**(`diff -q` 确认): + 1. 换回旧的黑名单行 → `FAIL completion-gate clean: rc 1 != 0 (Plan has unfinished Steps)`(白名单被删,`done` 的 Step 反而被拒)。 + 2. 把块定位限制在 `## Steps` 段内 → `FAIL completion-gate unfinished: rc 0 != 1`;并且单独证实新夹具 `block-outside-steps-section` 在变异下 return 0(应为 1),即它独立捕获了这一变异,不是搭了别的断言的便车。 + 3. 删掉 `END` 里的 `flush()` → `FAIL completion-gate unfinished: rc 0 != 1`(最后一个块不被结算)。 + 4. 把 `done` 判据收紧成整行精确相等 → 第一轮 `ALL SMOKE PASSED`,**断言没抓住**;补了 `printf` 生成的行尾空白与 `\r` 两条夹具后重跑,`FAIL completion-gate closed-done-trailing-space: rc 1 != 0`。 + 5. `done` 判据放宽成 `(done|completed)` → `FAIL completion-gate closed-completed: rc 0 != 1`(正是本任务要消除的那个词)。 + +第 4 次值得留档:第一版断言只写了字面上的 `done`,于是「容忍行尾空白」这条实现细节根本没有被钉住——变异验证发现了断言的漏洞,不是代码的漏洞。 + +**回归对照(`git show main:hooks/completion-gate` 对同一个 `achieved/` 跑一遍)**:91 份里 `main` 拒 17 份,改后拒 19 份,**新变化的只有 2 份**——`2026-10-11-gate-host-coverage` 与 `2026-10-22-taskflow-drift-guard`。另外 3 份(`repository-standards-and-doc-root`、`token-efficiency`、`personal-docs-git-boundary`)在 `main` 上就已经因为「current Task version is not approved」被拒,本次改动没有改变它们的判定。`achieved/` 不过门,所以这 2 份在运行时也没有后果;写进 CHANGELOG 的是事实而不是影响。 + +## Change Log + +- 2026-10-27 work revision — 变异验证发现第一版断言没钉住「行尾空白容忍」:实现里 `[[:space:]]` 允许 `done` 后面有空格与 `\r`,但夹具只写了字面的 `done`,把判据收紧成整行精确相等仍然全绿。补了两条用 `printf` 生成字节的夹具(行尾两空格、CRLF)后,同一变异变红。改的是断言,不是行为。 +- 2026-10-27 work revision — CHANGELOG 的 Compatibility 段补上回归对照的结论:91 份里 `main` 拒 17、改后拒 19,新变化只有 2 份,其余 3 份在 `main` 上已因别的条件被拒。原措辞只说「5 份算未完成」,会被读成「5 份的判定都变了」。 + +## Follow-ups + +- 「Plan 可以没有 `### Step`」这条残留口径(R5)不在本任务内,若要做需另立条目。 +- `2026-10-22-taskflow-drift-guard` 在 `## Verification / Review` 段下复用 `### Step 1` 作普通小标题——写侧本来就把它当 Step,本条只是让读侧与写侧一致;若要禁止这种复用,也另立条目。 + +## Version History + +- v1 — 初版:白名单 `done`、任意 `### Step N`、归档 5 份不动。 diff --git a/TaskFlowDocs/2026-10-27-close-step-status/prd.md b/TaskFlowDocs/2026-10-27-close-step-status/prd.md new file mode 100644 index 0000000..c97578b --- /dev/null +++ b/TaskFlowDocs/2026-10-27-close-step-status/prd.md @@ -0,0 +1,74 @@ +# PRD — 一个 Step 什么时候算「关闭」 +> Task version: v1 +> Status: checking + +## Goal + +让 `hooks/completion-gate` 和 `hooks/task progress` 对「Step 关闭」给出同一个答案:`- Status: done` 是唯一表示关闭的写法,其余一律算未完成。今天门对任何它不认识的写法都**放行**,所以 `completed`、`done (v1)`、`complted`、空值、甚至没有这一行,全都能把一个没做完的 Plan 送到 `achieved/`。 + +## Background / Confirmed Facts + +2026-10-27 在本仓库直接驱动 `hooks/completion-gate` 实测(`/tmp` 一次性夹具,跑的是 `b47ea3f` 的码): + +1. **门是 fail-open 的,缺陷比「两套词表」说法更宽。** 一个只带 `- Status: complted` / `skipped` / `wip` / 空值 / 裸 `x` 的 Plan,门一律 `exit 0`「finished」。真正被拒的只有 `pending`、`in_progress`、`blocked` 三个词——因为 `hooks/completion-gate:67` 是一条黑名单:`/^- Status: (pending|in_progress|blocked)$/ { unfinished = 1 }`。任何没被列出来的值都落进「完成」。 +2. **写侧只认四个词。** `hooks/task:1099-1101` 把 `- Status:` 的取值限定在 `pending|in_progress|done|blocked`,别的直接 `fail`。所以 `completed` 这类写法**不可能**由 `task progress` 写出,它只能是手改的结果——两处对同一个字段的分歧,正是那些手改的成因。 +3. **真实 Plan 里已经有非规范写法。** 91 份 achieved Plan 的列首 `- Status:` 分布:254 `done`、78 `approved`(Approval 段)、6 `pending`、6 `completed`、4 `requested`(Approval 段)、3 `done (v1)`、1 `not required for read-only research`(Approval 段)。 +4. **按新规则重扫这 91 份,5 份会被判为未完成**,原因分三类:`2026-09-07-repository-standards-and-doc-root` 三个 Step 是 `done (v1)`;`2026-10-11-gate-host-coverage` 四个 Step 是 `completed`;`2026-09-08-token-efficiency`、`2026-09-14-personal-docs-git-boundary` 是真的 `pending`(这两个 `> Status: completed`,Step 却从没被改过——另一个方向的同一件事);`2026-10-22-taskflow-drift-guard` 的 `## Verification / Review` 段里有一个同名小节 `### Step 1 — per-host contract`,内容是表格不是计划步骤,没有 Status 行。 +5. **写侧定位 Step 的方式是「只看标题、不分段落」。** `hooks/task:449` 扫全文件找 `### Step <数字>`,块尾是下一个 `### Step ` 或 `## `。`setr` 又只认列首 `- Status:`。所以门若按「`## Steps` 段内」来找,会和写侧看到不同的块——`2026-09-10-repository-governed-development` 就有两个 Step 块挂在 `## Approval` 段下,写侧能改、门读不到。 +6. **`--approval-only` 是另一条路径。** `task state in_progress` 和 `task progress` 用它问「当前 Task version 批准了吗」,它们本来就不该要求 Plan 已完成。 + +## Requirements + +**R1 — 关闭的判据是白名单。** 一个 Step 块算关闭,当且仅当块内**列首**存在一行 `- Status: done`(行尾空白容忍)。其余一切——`completed`、`done (v1)`、拼写错误、空值、没有这一行——都算未完成。 + +**R2 — Step 块的定位与写侧一致。** 块由 `plan.md` 里任意位置的列首 `### Step <数字>` 开启(数字后不能紧跟字母数字或下划线),到下一个同类标题、或下一个列首 `## `、或文件尾结束。**不按 `## Steps` 段落过滤**:过滤会重新制造本任务要消除的那种分叉(第 5 条实测)。 + +**R3 — 门仍然只报一句话。** 有未关闭的 Step 时仍是 `Plan has unfinished Steps`、`exit 1`。不加 Step 编号,不改既有措辞——四个调用方和既有断言都靠这句话。 + +**R4 — `--approval-only` 不变。** 它继续只回答批准问题,不检查 Step。 + +**R5 — 没有 Step 块时此项通过,且写进注释。** 一个连 `### Step` 都没有的 Plan 不因本规则被拒。这是本规则明确留下的 fail-open 残留,属于另一个问题(「Plan 可以没有 Step」),不在本任务范围内,必须在 `hooks/completion-gate` 的注释里写清,免得下一个人以为这里是全封闭的。 + +**R6 — 不写兼容代码,不回填历史。** 不放宽到接受 `completed` / `done (v1)`,不改 `TaskFlowDocs/achieved/` 里的任何文件。已归档 Plan 不过门,新规则对它们没有运行时后果,第 4 条的 5 份只在 CHANGELOG 里记录事实。 + +**R7 — `hooks/task progress` 一个字都不用改。** 它已经是这条规则的写侧与校验侧;本任务只是让读侧与它对齐。 + +**R8 — 文档同步。** `skills/taskflow/SKILL.md:313` 与 `skills/taskflow/references/runtime.md:244-245` 现在写的是「no Step still `pending`, `in_progress`, or `blocked`」,那是黑名单的措辞,必须改成白名单;`CHANGELOG.md` 增一条,Compatibility 段声明「归档文档不重写,其中 5 份按新规则算未完成」。 + +## Acceptance Criteria + +- **A1** 一个 Step 写 `- Status: done` → 门 `exit 0`。 +- **A2** 一个 Step 写 `- Status: completed` → 门 `exit 1`,消息含 `unfinished Steps`。(这是本条缺陷的原始症状。) +- **A3** 一个 Step 写 `complted` / `skipped` / `wip` / `DONE` / 空值 / 整行缺失 → 六种都 `exit 1`。 +- **A4** 一个 Step 写 `pending` / `in_progress` / `blocked` → 仍 `exit 1`(不得回归)。 +- **A5** 两个 Step 一个 `done` 一个 `completed` → `exit 1`(不能因为有一个对就整体放行)。 +- **A6** Step 块写在 `## Approval` 段或 `## Verification / Review` 段下,`- Status: completed` → `exit 1`;同位置写 `done` → `exit 0`。证明定位不分段落。 +- **A7** 一个没有 `### Step` 的 Plan → `exit 0`(R5 的残留,被断言钉住,而不是默默存在)。 +- **A8** `--approval-only` 对 A2 的 Plan 仍 `exit 0`(批准就够了,这是 `task progress` 问的问题)。 +- **A9** `bash hooks/smoke-test` 全绿,新增断言覆盖 A1–A8。 +- **A10** `bash hooks/smoke-test` 的既有 completion-gate 断言不变(`cg_case unfinished` 等)——它们的 Step 用的是 `pending`/`done`,本来就在规则内。 + +## In Scope + +- `hooks/completion-gate`:`unfinished` 判定改写为白名单 + 块定位,附注释说明 R5 的残留。 +- `hooks/smoke-test`:新增 `cg_case`(A1–A8)。 +- `skills/taskflow/SKILL.md`、`skills/taskflow/references/runtime.md`:措辞同步。 +- `CHANGELOG.md`。 + +## Out of Scope + +- `TaskFlowDocs/achieved/` 里 5 份 Plan 的内容(R6)。 +- `hooks/task progress` 的取值集合(R7:已经是 `pending|in_progress|done|blocked`)。 +- 「Plan 可以没有 Step」这条残留口径(R5)——只记录,不关掉。 +- `TF-20261027-e4ba77`(长实现里不打扰)与本条同源,但那条要改的是注入节奏,不是门的判据。 +- `TF-20261027-e8c6dc`(先问结论是否依赖外部事实)——同一个 session 里记下的另一个问题。 + +## Risks / Deferred Items + +- **这是收紧,会让某些今天能归档的 Plan 被拒。** 缓解:被拒的只有「Step 没写 `done`」的 Plan,而 `task progress` 一直只肯写 `done`,所以正常流程产出的 Plan 不受影响;受影响的是手改过的,而它本来就该被拒。 +- **`### Step N` 这个形状被别处复用作普通小标题会误判。** 实测 91 份里有 1 份(`2026-10-22-taskflow-drift-guard`)。选「任何 `### Step N`」而不是「`## Steps` 段内」是为了和写侧一致(第 5 条);代价是这种复用会被算成未关闭的 Step。写侧本来也把它当成 Step 来改——所以这不是新分叉,是同一个已存在的约定。 +- **CRLF。** 仓库 `.gitattributes` 是 `* text=auto eol=lf`,实测归档 Plan 全是 LF。行尾仍按空白容忍处理,`[[:space:]]` 在 `LC_ALL=C` 下含 `\r`,不需要额外分支。 + +## Open Questions + +无。三个岔口已于 2026-10-27 由用户裁定:白名单只认 `done`;块定位用任意 `### Step N`;归档的 5 份不动、只记录。 diff --git a/TaskFlowDocs/todo.md b/TaskFlowDocs/todo.md index 850eeb1..fa9a239 100644 --- a/TaskFlowDocs/todo.md +++ b/TaskFlowDocs/todo.md @@ -26,6 +26,66 @@ has an issue number or URL. +## Agree on what a closed Step is, so `completed` stops passing one check + +- ID: TF-20261027-131c2b +- Status: in_progress +- Priority: normal +- Owner: Claude +- Source: user request +- Added: 2026-10-27 +- Updated: 2026-10-27 +- Goal: Make `completion-gate` and `hooks/task progress` mean the same thing by a closed Step, so the word `completed` cannot be refused by one and accepted by the other. +- Task: `TaskFlowDocs/2026-10-27-close-step-status/` +- Next action: Push the branch, read CI, record the run in plan.md, then archive. +- Notes: **2026-10-27 measured, and the hole is wider than the two vocabularies.** The two writers disagree — `hooks/task progress` accepts exactly `pending|in_progress|done|blocked` (`hooks/task:1100`) while `hooks/completion-gate:67` treats only `pending|in_progress|blocked` as unfinished — but the real defect is that the gate **fails open on every unrecognized value**. Driven through the gate directly, a Plan whose only Step reads `- Status: complted` / `skipped` / `wip` / empty / a bare `x` returns **exit 0, "finished"**. `completed`, `done (v1)` and `DONE` do the same, so the case that started this item is one instance of a general shape rather than a special one. + + **Real Plans already carry the non-canonical spellings:** over 91 achieved Plans, Step statuses are 254 `done`, 6 `pending`, **4 `completed`** (`2026-10-11-gate-host-coverage`), **3 `done (v1)`** (`2026-09-07-repository-standards-and-doc-root`). Both spellings exist because `task progress` refused the word and the Step was hand-edited instead — the validator and the reader disagreeing is what produced them. + + The choice is a user decision, not a tidy-up: a whitelist would refuse those archived Plans' shapes and is the fail-closed side, while the blacklist is what the gate already is. Whichever wins, `task progress` must move with it, or the next hand edit recreates the split. + +## Ask where a conclusion should come from before converging on it + +- ID: TF-20261027-e8c6dc +- Status: inbox +- Priority: normal +- Owner: Claude +- Source: user request +- Added: 2026-10-27 +- Updated: 2026-10-27 +- Goal: Make TaskFlow ask, before a phase converges, whether the answer depends on a fact from outside this repository — and record the answer either way, instead of leaving external search to a decision that never gets made. +- Task: Not promoted. +- Next action: Decide the smallest change that forces the question: a line beside the `inspect` enumeration in `SKILL.md:54`, or a required PRD field. Do not add a gate. +- Notes: **2026-10-27 re-measured over 91 achieved tasks — worse than the 2026-10-11 figures on [[TF-20260919-90797c]], which owns the measurement.** `## Reference Pointers` holds **365 bullets, 0 containing a URL**. Whole-directory scan: **1 of 91 tasks** cites any third-party host (the 2026-09-10 audit task, 4 URLs). Only **3 of 91** have a `reference/` directory. + + **Three mechanisms, each pointing at a line** (inference, not causation): (1) `SKILL.md:54`'s `inspect` enumerates eight sources and **all eight are inside the repository or the Agent's own state** — nothing outbound; (2) "searching external sources" is only ever listed under Phase 2 (`SKILL.md:172`), and Phase 2 is *optional* with `reference/` as its artifact, so the mental model is "a writing phase" — you do not fetch facts until you have decided to write the note; (3) `hooks/capability-evidence:48-50` counts only `Skill`/`Agent`/`mcp__*`, so `WebSearch`/`WebFetch` are invisible to TaskFlow — excluded deliberately by R12 (`achieved/2026-09-27-capability-pre-write-gate/prd.md:61`) so the gate could not be satisfied by built-in tools. Net effect: search is neither blocked nor rewarded. + + **Why not a gate or a mandate:** the user has stated this is not a request to *require* network search. This repository is largely self-sufficient, which plausibly explains much of 1/91; a mandate would add a compulsory step to the majority of tasks that genuinely do not need one. + + **Coupled to [[TF-20261027-e4ba77]]:** both come from the same root — TaskFlow speaks only at turn boundaries and only about the phase it is in. If the inter-step reminder lands first, this one may be answerable with prose alone. + +## Stay present inside a long implementation run, not only at the prompt + +- ID: TF-20261027-e4ba77 +- Status: inbox +- Priority: normal +- Owner: Claude +- Source: user request +- Added: 2026-10-27 +- Updated: 2026-10-27 +- Goal: Keep TaskFlow present between the steps of one implementation run, so a long turn cannot leave the approved Plan behind while nothing in the workflow speaks up again. +- Task: Not promoted. +- Next action: Establish which hosts can inject context from a mid-turn hook (a `PostToolUse` hook on every tool, or the host's nearest equivalent) before weighing any design; the whole option set depends on that answer and it is a documentation fact, not a preference. +- Notes: **2026-10-27 measured on this machine:** across the three most recent session transcripts, tool calls per user turn are median **10–15**, max **197**. `hooks/prompt-reminder` speaks once at the *start* of each turn (`hooks/hooks.json`, `UserPromptSubmit`), and nothing TaskFlow-owned fires for the remaining 10–197 calls. The user's report — "跑着跑着不走流程" — lands in that gap. + + **Four openings named at file:line** (each verified today): (1) `hooks/capability-gate:126-134` returns 0 once a stage is released for this task version, and only matches the basenames `prd.md`/`spec.md`/`plan.md` (`:96-99`), so **no edit after the first Plan write is checked at all**; (2) the gate never reads `## Approval` — recorded history has the failure happening (`achieved/2026-10-23-archive-completion-gate/plan.md:132`, "Steps 1–3 were implemented before the Approval block was filled"); (3) `hooks/task progress` is the only writer of Step status and is bound to **no hook**, so a Step's `- Status:` changes only if the Agent chooses to call it; (4) the record the gate depends on is largely absent — **59 of 91 achieved Plans have zero `## Skills / Tools Used` stage lines**, and 8 have no `## Change Log` section. + + **External evidence for the frequency lever, not for a new gate:** arXiv 2604.12147 (IBM, 21,120 SWE-agent trajectories, SWE-bench Verified + Pro) measures that re-inserting the plan **every five steps** reduces plan violations and raises success, attributing the drift to the plan losing influence as the trajectory grows. That is the same mechanism `prompt-reminder` already uses, at the wrong cadence. + + **Deliberately *not* proposed: extending the gate into the implementation phase.** It would convert a reminder problem into a new hard gate over ordinary refactoring edits, and the false-refusal cost is borne per edit. If it is ever wanted it needs its own evidence, not this item's. + + **Open question the design turns on:** whether a host's mid-turn hook can actually inject text the model reads. If none can, the lever is not a new hook at all but the Plan carrying the current Step where the turn-start reminder can restate it — a smaller change with the same effect at turn granularity only. + ## task approve requires the first [Plan] line to name exactly the capability relea - ID: TF-20261026-726ec4 diff --git a/hooks/completion-gate b/hooks/completion-gate index e028a7e..010b8f6 100644 --- a/hooks/completion-gate +++ b/hooks/completion-gate @@ -56,16 +56,57 @@ plan="$1/plan.md" # The Approval fields are matched only inside the `## Approval` section, so a # `- Status: approved` written anywhere else cannot stand in for an approval. # An end-of-file section needs no special case: `inap` simply stays set. -result="$(awk -v approval_only="$approval_only" 'BEGIN { bad = 0 } +result="$(awk -v approval_only="$approval_only" ' + # A Step block is closed only when it says `- Status: done` in column 0. This + # is a whitelist, and that is the point: the previous shape listed the three + # words that meant unfinished, so every value it did not know about -- a typo, + # `completed`, `done (v1)`, an empty value -- counted as finished. Measured + # 2026-10-27: all of those archived. `hooks/task progress` has always accepted + # exactly `pending|in_progress|done|blocked`, so `done` is the only word that + # can mean closed, and reading anything else as closed is the disagreement + # this replaces rather than one it repeats. + # + # Block location mirrors the writer at `hooks/task` -- any `### Step ` with a + # number and no trailing word character opens one, and any `### Step ` or + # column-0 `## ` closes it. Deliberately NOT filtered to the `## Steps` + # section: two archived Plans carry Step blocks under other headings, and a + # reader that skipped them would see a different set of Steps than + # `task progress` writes to. That is the split this check exists to remove. + # + # What this still lets through, and knowingly: a Plan with no `### Step` at + # all has nothing to be unfinished, so this check passes it. Closing that + # needs a rule about whether a Plan must have Steps, which is a different + # question with its own arguments; it is not smuggled in here. + function flush() { + if (instep && !closed) unfinished = 1 + instep = 0; closed = 0 + } + BEGIN { bad = 0 } + /^## / { flush() } /^> Task version: v[0-9][0-9]*$/ { if (ver == "") { ver = $0; sub(/^> Task version: /, "", ver) } next } /^## Approval$/ { inap = 1; next } inap && /^## / { inap = 0 } inap && $0 == "- Status: approved" { st = 1 } inap && $0 == "- Approved by: pending" { pend = 1 } inap && index($0, "- Approved version: ") == 1 { av = substr($0, 21) } + /^### Step / { + flush() + rest = substr($0, 10) + if (match(rest, /^[0-9]+/)) { + if (substr(rest, RLENGTH + 1, 1) !~ /[A-Za-z0-9_]/) instep = 1 + } + next + } /^[[:space:]]*- \[ \]/ { unchecked = 1 } - /^- Status: (pending|in_progress|blocked)$/ { unfinished = 1 } + # Last one wins, which is what "the block says" means when a block carries + # two: `task progress` replaces the line it finds rather than adding one, so a + # second `- Status:` is a hand edit, and reading the last is the reading a + # person makes. + instep && /^- Status:/ { + closed = ($0 ~ /^- Status:[[:space:]]*done[[:space:]]*$/) + } END { + flush() if (ver == "") { print "missing Plan Task version"; bad = 1 } else if (!st || av != ver || pend) { print "current Task version is not approved"; bad = 1 } else if (!approval_only && unchecked) { print "Plan has unchecked items"; bad = 1 } diff --git a/hooks/smoke-test b/hooks/smoke-test index 9773d11..4a4d0c1 100755 --- a/hooks/smoke-test +++ b/hooks/smoke-test @@ -96,12 +96,81 @@ cg_case unchecked 1 'unchecked items' "${cg_approved}### Step 1 — a cg_case unfinished 1 'unfinished Steps' "${cg_approved}### Step 1 — a - Status: pending " +# Closed is a whitelist, so every word but `done` has to be refused. The list +# below is not decoration: `completed` was the one measured reaching `achieved/` +# (2026-10-27), the misspellings and the empty value are what the old blacklist +# let through for the same reason, and `done (v1)` is a shape two archived Plans +# still carry. One `cg_case` each, so deleting the whitelist reddens the one it +# actually removed rather than "something". +for cg_word in completed 'done (v1)' complted skipped wip DONE; do + cg_case "closed-$cg_word" 1 'unfinished Steps' "${cg_approved}### Step 1 — a +- Status: $cg_word +" +done +cg_case closed-empty 1 'unfinished Steps' "${cg_approved}### Step 1 — a +- Status: +" +# A block that never got a Status line is unfinished too: absent is not `done`. +cg_case closed-missing 1 'unfinished Steps' "${cg_approved}### Step 1 — a +- Goal: something +" +# ...and the words that already meant unfinished still do. `done` and `pending` +# are covered above; the other two are here. A whitelist that refused everything +# would pass all of these, which is why `closed-done` sits beside them. +cg_case open-in_progress 1 'unfinished Steps' "${cg_approved}### Step 1 — a +- Status: in_progress +" +cg_case open-blocked 1 'unfinished Steps' "${cg_approved}### Step 1 — a +- Status: blocked +" +cg_case closed-done 0 '' "${cg_approved}### Step 1 — a +- Status: done +" +# Trailing whitespace and a CRLF line ending are written with `printf` rather +# than typed, so the bytes are visible in the assertion instead of hiding at the +# end of a quoted line. Both have to count as closed: the repository's +# `.gitattributes` says `eol=lf`, but a Plan edited on Windows before it is +# committed carries the `\r`, and refusing it would report a finished Step as +# unfinished for a reason nobody could see. +cg_case closed-done-trailing-space 0 '' "$(printf '%s### Step 1 — a\n- Status: done \n' "$cg_approved")" +cg_case closed-done-crlf 0 '' "$(printf '%s### Step 1 — a\r\n- Status: done\r\n' "$cg_approved")" +# Two Steps, one closed and one not: the check is per block, not "does the file +# contain a `done` anywhere". +cg_case closed-but-sibling-open 1 'unfinished Steps' "${cg_approved}### Step 1 — a +- Status: done +### Step 2 — b +- Status: completed +" +# The block reader is not filtered to `## Steps`, on purpose: `hooks/task +# progress` locates a Step by its heading alone, so a reader that skipped the +# other sections would see a different set of Steps than the writer edits. Two +# archived Plans carry Step blocks under `## Approval` and `## Verification / +# Review`, which is why both sections are pinned here. +cg_case block-outside-steps-section 1 'unfinished Steps' "${cg_approved} +### Step 1 — a +- Status: completed +" +cg_case block-outside-steps-section-done 0 '' "${cg_approved} +### Step 1 — a +- Status: done +" +# Knowingly still fail-open, and pinned so it stays a decision rather than an +# oversight: a Plan with no `### Step` has nothing to be unfinished. Whether a +# Plan must have Steps is a different question and is not answered here. +cg_case no-step-blocks 0 '' "${cg_approved}## Steps + +## Checkpoints +" # `--approval-only` is the mid-task question: `state in_progress` and # `progress` must not demand a finished Plan, only an approved one. cg_case approval-only-unfinished 0 '' "${cg_approved}### Step 1 — a - [ ] still working - Status: in_progress " '--approval-only' +cg_case approval-only-not-done 0 '' "${cg_approved}## Steps +### Step 1 — a +- Status: completed +" '--approval-only' # A promotion that never got a Plan is the case `task complete` can reach, # because `task_paths` only requires the directory. The gate says so in words # rather than letting awk report a missing file. diff --git a/skills/taskflow/SKILL.md b/skills/taskflow/SKILL.md index f9e96e1..b502f5e 100644 --- a/skills/taskflow/SKILL.md +++ b/skills/taskflow/SKILL.md @@ -310,7 +310,7 @@ Run it as one command: `hooks/task complete --user-accepted`. It check Before that command, confirm all acceptance criteria pass, verification is recorded, the current Task version is consistent across existing core documents, unresolved items are explicit follow-ups, and no other agent is writing core documents. What the command performs: mark the task `completed`, move the entire directory, update the linked Todo item's `Task:` path to `TaskFlowDocs/achieved//`, close it, then verify the active path is absent, the achieved path exists, and the achieved root PRD and Plan both say `completed`. If any part fails, record the blocker and do not claim archival completion. -**`hooks/archive` refuses an unfinished task on its own.** It requires the Plan's `## Approval` to say `approved` for its current `> Task version:` with an approver other than `pending`, no unchecked `- [ ]` item, and no Step still `pending`, `in_progress`, or `blocked` — the same three gates `complete` applies, applied again where the move happens, because `archive` is also runnable by hand. All three are checked before the directory moves, so a refusal leaves the task active, the Todo untouched, and `achieved/` without it. +**`hooks/archive` refuses an unfinished task on its own.** It requires the Plan's `## Approval` to say `approved` for its current `> Task version:` with an approver other than `pending`, no unchecked `- [ ]` item, and every Step block closed — a Step is closed when it says `- Status: done`, and nothing else counts. That last one is a whitelist, and deliberately: it used to name the three words that meant unfinished, so any word it did not know (`completed`, a misspelling, an empty value, a missing line) read as finished and archived a Plan that was not. `hooks/task progress` has only ever accepted `pending|in_progress|done|blocked`, so `done` is the one word an author can write that means closed, and the two now read the same rule from both sides. The three gates are the same ones `complete` applies, applied again where the move happens, because `archive` is also runnable by hand. All of them are checked before the directory moves, so a refusal leaves the task active, the Todo untouched, and `achieved/` without it. **Closing the entry is part of the transaction, not a later tidying pass.** `hooks/archive` reduces the linked entry to the fields triage still needs — its heading plus `ID`, `Status`, `Goal`, `Task`, `Next action` — because a finished entry had become 78% of the file: eighty-five of them held 89 KB of the 114 KB `todo.md`. `Priority`, `Owner`, `Source`, `External`, `Added`, `Updated` and the free-form prose under any field go. A live entry keeps every field; only a closed one is reduced, and only `hooks/archive` does it, so `task next`, `task state` and `task reopen` never see a shortened entry they write to. An entry with no achieved plan.md to link — `- Task: Not promoted.`, or a `Task:` that is prose rather than a path — is left exactly as it is, because the prose has nowhere to go and cannot stay where it is: a `## Todo Record` heading at column 0 inside an entry is a new section to every hook that splits on `## `. diff --git a/skills/taskflow/references/runtime.md b/skills/taskflow/references/runtime.md index d161f37..dc68a91 100644 --- a/skills/taskflow/references/runtime.md +++ b/skills/taskflow/references/runtime.md @@ -241,8 +241,8 @@ A host with no hook wiring gets none of this, and that is not a gap to be filled | `hooks/task progress [verification] [--root ]` | Update one Plan Step and optionally append a verification line. | `done` rejects unchecked checklist items. | | `hooks/task unaided --considered "" [--root ]` | Record a deliberate unaided decision for a stage, which is what lets the pre-write gate release it and `approve` reconcile it. For `Test` it is also the stage's only carrier, so it spends the next unreleased evidence line when one is standing and reports which kind it wrote. | Exit `2` on a stage that is none of the four, a missing `--considered`, or a class outside the phase vocabulary, which it prints. It validates a class against the vocabulary and nothing else — it cannot judge whether the decision was right. | | `hooks/task complete --user-accepted [--root ]` | Run `completion-gate`, set core statuses, and invoke archive. | Requires explicit acceptance and leaves no active task on success. Refuses before the first write, so nothing has to be rolled back. | -| `hooks/archive ` | Run `completion-gate` — the Plan is approved for its current Task version, has no unchecked item and no unfinished Step — then move `TaskFlowDocs/` → `TaskFlowDocs/achieved/`, update the linked Todo item's `Task:` path and status to `done`, and verify the active path is absent, the achieved path exists, and the achieved root PRD and Plan both say `completed`. | Prints a check report; on any failure leaves the task active, the Todo unchanged, and exits nonzero. Both entry points into `achieved/` run the same file, so neither is a weaker way in. | -| `hooks/completion-gate ` | Decide whether the Plan may be treated as finished: approved for its current Task version, no unchecked item, no unfinished Step. `--approval-only` asks the narrower question `task state in_progress` and `task progress` need. | Exit `1` with one line naming the failing condition. Reads the Plan as a file in one `awk`; a `printf | grep -q` shape is silently disabled by `pipefail` on any Plan larger than the pipe buffer. | +| `hooks/archive ` | Run `completion-gate` — the Plan is approved for its current Task version, has no unchecked item and no unclosed Step — then move `TaskFlowDocs/` → `TaskFlowDocs/achieved/`, update the linked Todo item's `Task:` path and status to `done`, and verify the active path is absent, the achieved path exists, and the achieved root PRD and Plan both say `completed`. | Prints a check report; on any failure leaves the task active, the Todo unchanged, and exits nonzero. Both entry points into `achieved/` run the same file, so neither is a weaker way in. | +| `hooks/completion-gate ` | Decide whether the Plan may be treated as finished: approved for its current Task version, no unchecked item, no unclosed Step. A Step is closed only when it says `- Status: done` in column 0 — a whitelist, so an unrecognized or missing value counts as unfinished rather than as finished. A block is located by its `### Step ` heading anywhere in the Plan, not only under `## Steps`, because that is how `task progress` finds the block it edits. `--approval-only` asks the narrower question `task state in_progress` and `task progress` need. | Exit `1` with one line naming the failing condition. Reads the Plan as a file in one `awk`; a `printf | grep -q` shape is silently disabled by `pipefail` on any Plan larger than the pipe buffer. A Plan with no `### Step` block at all is not refused — that is a question about whether a Plan must have Steps, which this command does not answer. | | `hooks/reopen ` | Move `TaskFlowDocs/achieved/` → `TaskFlowDocs/`, record the Todo source/reopen reason in the Plan change log. | Prints a check report; exit nonzero on mismatch. | | `hooks/version ` | Copy only changed core documents plus `version.md` into `old/v/`, retain root docs, bump them to ``, and reset state/approval for review. | Prints archived paths + root version/state agreement; exit nonzero before mutation on invalid input. | From 84f04078b0298ea42e5753d301e81137e049c16b Mon Sep 17 00:00:00 2001 From: hkwuks <25031515+hkwuks@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:26:32 +0800 Subject: [PATCH 2/3] docs: record PR 102 CI for close-step-status --- TaskFlowDocs/2026-10-27-close-step-status/plan.md | 3 ++- TaskFlowDocs/todo.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/TaskFlowDocs/2026-10-27-close-step-status/plan.md b/TaskFlowDocs/2026-10-27-close-step-status/plan.md index c4153b7..a082e48 100644 --- a/TaskFlowDocs/2026-10-27-close-step-status/plan.md +++ b/TaskFlowDocs/2026-10-27-close-step-status/plan.md @@ -83,9 +83,10 @@ No spec required — small, self-contained task: one `awk` condition in one hook ## Verification / Review +- **`Hooks` workflow, run [40519266846](https://github.com/hkwuks/TaskFlow/actions/runs/40519266846)** at `f03b806` — **`success`, all seven jobs**: `changes`, `evals`, `release`, `todo-merge-audit`, and `smoke` on `ubuntu-latest` (20s), `windows-latest` (39s) and `macos-latest` (1m7s). Run time 1m25s. CI is the test authority per `CONTRIBUTING.md`, so the new assertions ran on all three hosts rather than only on this one; the local run recorded below was made before CI existed for this branch and stands as the same tree's result. - 2026-10-27 Step 2: bash hooks/smoke-test ALL SMOKE PASSED; 5 mutations each reddened a named assertion; release-check pass; quick_validate valid; git diff --check clean -2026-10-27,全部在本工作树(`fix/close-step-status`)执行: +2026-10-27,本地(本工作树 `fix/close-step-status`)执行: - `bash hooks/smoke-test` — `ALL SMOKE PASSED`(含新增的 19 条 completion-gate 断言)。 - `bash hooks/release-check .` — `STATUS: pass`;两侧 marketplace pin 仍是 `v1.1.9 -> 800e7f1…`。 diff --git a/TaskFlowDocs/todo.md b/TaskFlowDocs/todo.md index fa9a239..da3f1dc 100644 --- a/TaskFlowDocs/todo.md +++ b/TaskFlowDocs/todo.md @@ -37,7 +37,7 @@ has an issue number or URL. - Updated: 2026-10-27 - Goal: Make `completion-gate` and `hooks/task progress` mean the same thing by a closed Step, so the word `completed` cannot be refused by one and accepted by the other. - Task: `TaskFlowDocs/2026-10-27-close-step-status/` -- Next action: Push the branch, read CI, record the run in plan.md, then archive. +- Next action: Archive the task; the PR is ready once its documents are in it. - Notes: **2026-10-27 measured, and the hole is wider than the two vocabularies.** The two writers disagree — `hooks/task progress` accepts exactly `pending|in_progress|done|blocked` (`hooks/task:1100`) while `hooks/completion-gate:67` treats only `pending|in_progress|blocked` as unfinished — but the real defect is that the gate **fails open on every unrecognized value**. Driven through the gate directly, a Plan whose only Step reads `- Status: complted` / `skipped` / `wip` / empty / a bare `x` returns **exit 0, "finished"**. `completed`, `done (v1)` and `DONE` do the same, so the case that started this item is one instance of a general shape rather than a special one. **Real Plans already carry the non-canonical spellings:** over 91 achieved Plans, Step statuses are 254 `done`, 6 `pending`, **4 `completed`** (`2026-10-11-gate-host-coverage`), **3 `done (v1)`** (`2026-09-07-repository-standards-and-doc-root`). Both spellings exist because `task progress` refused the word and the Step was hand-edited instead — the validator and the reader disagreeing is what produced them. From 9e2395f2f2886821af4f5be1d8c0e64083f52f43 Mon Sep 17 00:00:00 2001 From: hkwuks <25031515+hkwuks@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:27:07 +0800 Subject: [PATCH 3/3] docs: archive close-step-status --- .../2026-10-27-close-step-status/plan.md | 12 +++++++++++- .../2026-10-27-close-step-status/prd.md | 2 +- TaskFlowDocs/todo.md | 16 +++------------- 3 files changed, 15 insertions(+), 15 deletions(-) rename TaskFlowDocs/{ => achieved}/2026-10-27-close-step-status/plan.md (87%) rename TaskFlowDocs/{ => achieved}/2026-10-27-close-step-status/prd.md (99%) diff --git a/TaskFlowDocs/2026-10-27-close-step-status/plan.md b/TaskFlowDocs/achieved/2026-10-27-close-step-status/plan.md similarity index 87% rename from TaskFlowDocs/2026-10-27-close-step-status/plan.md rename to TaskFlowDocs/achieved/2026-10-27-close-step-status/plan.md index a082e48..db0dde6 100644 --- a/TaskFlowDocs/2026-10-27-close-step-status/plan.md +++ b/TaskFlowDocs/achieved/2026-10-27-close-step-status/plan.md @@ -1,6 +1,6 @@ # Plan — 一个 Step 什么时候算「关闭」 > Task version: v1 -> Status: checking +> Status: completed No spec required — small, self-contained task: one `awk` condition in one hook, its assertions, and three doc lines. No cross-layer contract, no new file, no interface change. @@ -119,3 +119,13 @@ No spec required — small, self-contained task: one `awk` condition in one hook ## Version History - v1 — 初版:白名单 `done`、任意 `### Step N`、归档 5 份不动。 + +## Todo Record + +Moved verbatim from the TODO entry by `hooks/archive`, grouped by the field each block followed. + +### `- Notes:` + +- **2026-10-27 measured, and the hole is wider than the two vocabularies.** The two writers disagree — `hooks/task progress` accepts exactly `pending|in_progress|done|blocked` (`hooks/task:1100`) while `hooks/completion-gate:67` treats only `pending|in_progress|blocked` as unfinished — but the real defect is that the gate **fails open on every unrecognized value**. Driven through the gate directly, a Plan whose only Step reads `- Status: complted` / `skipped` / `wip` / empty / a bare `x` returns **exit 0, "finished"**. `completed`, `done (v1)` and `DONE` do the same, so the case that started this item is one instance of a general shape rather than a special one. + **Real Plans already carry the non-canonical spellings:** over 91 achieved Plans, Step statuses are 254 `done`, 6 `pending`, **4 `completed`** (`2026-10-11-gate-host-coverage`), **3 `done (v1)`** (`2026-09-07-repository-standards-and-doc-root`). Both spellings exist because `task progress` refused the word and the Step was hand-edited instead — the validator and the reader disagreeing is what produced them. + The choice is a user decision, not a tidy-up: a whitelist would refuse those archived Plans' shapes and is the fail-closed side, while the blacklist is what the gate already is. Whichever wins, `task progress` must move with it, or the next hand edit recreates the split. diff --git a/TaskFlowDocs/2026-10-27-close-step-status/prd.md b/TaskFlowDocs/achieved/2026-10-27-close-step-status/prd.md similarity index 99% rename from TaskFlowDocs/2026-10-27-close-step-status/prd.md rename to TaskFlowDocs/achieved/2026-10-27-close-step-status/prd.md index c97578b..2a9cd68 100644 --- a/TaskFlowDocs/2026-10-27-close-step-status/prd.md +++ b/TaskFlowDocs/achieved/2026-10-27-close-step-status/prd.md @@ -1,6 +1,6 @@ # PRD — 一个 Step 什么时候算「关闭」 > Task version: v1 -> Status: checking +> Status: completed ## Goal diff --git a/TaskFlowDocs/todo.md b/TaskFlowDocs/todo.md index da3f1dc..8d1d014 100644 --- a/TaskFlowDocs/todo.md +++ b/TaskFlowDocs/todo.md @@ -29,20 +29,10 @@ has an issue number or URL. ## Agree on what a closed Step is, so `completed` stops passing one check - ID: TF-20261027-131c2b -- Status: in_progress -- Priority: normal -- Owner: Claude -- Source: user request -- Added: 2026-10-27 -- Updated: 2026-10-27 +- Status: done - Goal: Make `completion-gate` and `hooks/task progress` mean the same thing by a closed Step, so the word `completed` cannot be refused by one and accepted by the other. -- Task: `TaskFlowDocs/2026-10-27-close-step-status/` -- Next action: Archive the task; the PR is ready once its documents are in it. -- Notes: **2026-10-27 measured, and the hole is wider than the two vocabularies.** The two writers disagree — `hooks/task progress` accepts exactly `pending|in_progress|done|blocked` (`hooks/task:1100`) while `hooks/completion-gate:67` treats only `pending|in_progress|blocked` as unfinished — but the real defect is that the gate **fails open on every unrecognized value**. Driven through the gate directly, a Plan whose only Step reads `- Status: complted` / `skipped` / `wip` / empty / a bare `x` returns **exit 0, "finished"**. `completed`, `done (v1)` and `DONE` do the same, so the case that started this item is one instance of a general shape rather than a special one. - - **Real Plans already carry the non-canonical spellings:** over 91 achieved Plans, Step statuses are 254 `done`, 6 `pending`, **4 `completed`** (`2026-10-11-gate-host-coverage`), **3 `done (v1)`** (`2026-09-07-repository-standards-and-doc-root`). Both spellings exist because `task progress` refused the word and the Step was hand-edited instead — the validator and the reader disagreeing is what produced them. - - The choice is a user decision, not a tidy-up: a whitelist would refuse those archived Plans' shapes and is the fail-closed side, while the blacklist is what the gate already is. Whichever wins, `task progress` must move with it, or the next hand edit recreates the split. +- Task: `TaskFlowDocs/achieved/2026-10-27-close-step-status/` +- Next action: None — completed and archived. ## Ask where a conclusion should come from before converging on it