@cyning/harness 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,43 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.13.0] - 2026-07-26
8
+
9
+ ### Added
10
+
11
+ - **`lifecycle dry-run` 接线 `close_*`**:`--task` + `close` + `from=done` 时求值 `close_invoke` / `close_self_check` / `close_acceptance` / `close_slug` / `close_status` / `close_review`(与 `task close` 同语义 · 旁路 · 不 mv)。
12
+ - `evaluateCloseChecks`(`lib/task-close.js`):close 与 dry-run 共用检查;CLI 透传 `--allow-invoke-gap` / `--allow-unchecked`。
13
+
14
+ ### Changed
15
+
16
+ - README / USER_GUIDE / ONBOARDING / docs 索引:对齐 **2.12–2.13**(invoke hats · dry-run close)。
17
+ - `discipline-coverage.yaml` `as_of` → **2.13.0**。
18
+
19
+ ### Notes
20
+
21
+ - SPEC:`docs/spec/SPEC-lifecycle-dry-run-close_v1.md`
22
+ - minor · 仍无 `--apply` / G7;`to_00` 仍可 unevaluated
23
+ - dry-run **不替代** `task close` 硬闸
24
+
25
+ ## [2.12.1] - 2026-07-26
26
+
27
+ ### Fixed
28
+
29
+ - **invoke 文件名 hat 扫描**:改为日期后的**前缀 hat run**;slug 中的 `10`/`40` 等不再误计为已覆盖;去掉整名 `/CLOSE/i` 裸匹配。
30
+ - `lifecycle.yaml`:删除 `invoke_hats_retention` 别名守卫,仅保留 `close_invoke`。
31
+
32
+ ### Changed
33
+
34
+ - `verify` / 单测收紧:缺口 WARN 用例断言 exit 0;补误计负例与 lint **W6**。
35
+ - USER_GUIDE §6.0 · TEMPLATE_invoke 命名约束 · 10-task 提及 W6。
36
+ - `discipline-coverage.yaml` `as_of` → **2.12.1**。
37
+
38
+ ### Notes
39
+
40
+ - patch · 对规范命名无影响;依赖 slug 误计的畸形文件名 close 可能新 BLOCK
41
+ - 迁移指引:[`docs/USER_GUIDE_v1.0_zh.md`](docs/USER_GUIDE_v1.0_zh.md) §6.0(不另造 RUNBOOK)
42
+ - **已发布**:`@cyning/harness@2.12.1`(npm `latest` · 2026-07-26)· tag `v2.12.1` 已推送
43
+
7
44
  ## [2.12.0] - 2026-07-26
8
45
 
9
46
  ### Added
@@ -15,14 +52,15 @@
15
52
 
16
53
  ### Changed
17
54
 
18
- - `lifecycle.yaml` close 守卫登记 `invoke_hats_retention`;`discipline-coverage.yaml` `as_of` → **2.12.0** · gaps `INVOKE-HATS`。
55
+ - `lifecycle.yaml` close 守卫升级 `close_invoke`(集合覆盖);`discipline-coverage.yaml` `as_of` → **2.12.0** · gaps `INVOKE-HATS`。
19
56
  - TASK_TEMPLATE / TEMPLATE_invoke / 10·20·30 帽文同步字段与纪律。
20
57
 
21
58
  ### Notes
22
59
 
23
60
  - SPEC:`docs/spec/SPEC-invoke-hats-retention-gate_v1.md`
24
61
  - **破坏性(close)**:无字段时按 default 要求 10+30+40;存量仅 30 的 active 须补档、改 `minimal` 或 `--allow-invoke-gap`
25
- - minor · 建议业务仓 upgrade 后按 RUNBOOK 勾选 invoke 集合
62
+ - minor · 建议业务仓 upgrade 后按 [`USER_GUIDE` §6.0](docs/USER_GUIDE_v1.0_zh.md) 勾选 invoke 集合
63
+ - **已发布**:`@cyning/harness@2.12.0`(npm `latest` · 2026-07-26)· tag `v2.12.0` 已推送
26
64
 
27
65
  ## [2.11.1] - 2026-07-25
28
66
 
package/README.md CHANGED
@@ -97,6 +97,22 @@ Inform-YAML:`npx @cyning/harness graph yaml compile|check --input docs/_tech_g
97
97
  HGM 过程轨:`npx @cyning/harness graph ingest|snapshot|axioms check --target /path/to/your-repo`(v2.0+)
98
98
  Invoke 索引:`npx @cyning/harness sync index --target /path/to/your-repo`(不覆盖 S2 域)
99
99
 
100
+ ### 近期纪律闸(v2.5–2.13)
101
+
102
+ | 能力 | 版本 | 命令 / 要点 |
103
+ |------|------|-------------|
104
+ | reviews 留档 | 2.5+ / 全量 2.9 | `verify` · `task close` 查 R\<n\> 审查文 |
105
+ | `task lint` | 2.3+ · W6=2.12 | 结构闸;缺 `required_invoke_hats` / profile 提醒 |
106
+ | `lifecycle show` / `dry-run` | 2.7 / 2.10+ | 登记真值;旁路资格报告(**不**替代 verify/close) |
107
+ | dry-run `to_30` 守卫 | 2.11 | HG · reviews · D5 · lint |
108
+ | 多帽 invoke 留档 | **2.12+** | close 硬闸缺省 `10,30,40`;`--allow-invoke-gap`;见 USER_GUIDE §6.0 |
109
+ | dry-run `close_*` | **2.13** | 与 `task close` 同语义求值;仍无 `--apply` |
110
+
111
+ ```bash
112
+ npx @cyning/harness lifecycle dry-run --transition close --from done --task docs/tasks/active/task_xxx.md
113
+ npx @cyning/harness task close --file docs/tasks/active/task_xxx.md # 硬闸归档
114
+ ```
115
+
100
116
  Node 仓可选:`npx @cyning/harness init --with-scripts` → `pnpm harness:verify`。
101
117
  详见 [`docs/USER_GUIDE_v1.0_zh.md`](docs/USER_GUIDE_v1.0_zh.md) · [`docs/ONBOARDING.md`](docs/ONBOARDING.md) §2.2。
102
118
 
@@ -171,9 +187,11 @@ cyning-harness/
171
187
 
172
188
  | 项 | 值 |
173
189
  |----|-----|
174
- | 当前 | **v2.0.0**HGM G1 · `graph ingest|snapshot|axioms` · Inform-YAML · ICVO audit · invoke_index · gate-check --graph · B2 证据 |
175
- | 上一档 | v1.1.0 — Inform-YAML · `graph yaml compile|check` · ICVO audit · invoke_index · gate-check --graph · B2 证据 |
176
- | 许可 | **MIT** · [`LICENSE`](LICENSE) · Release [`docs/RELEASE_v1.0.0.md`](docs/RELEASE_v1.0.0.md) |
190
+ | 当前 npm | **`@cyning/harness@2.13.0`**dry-run `close_*` · invoke hats 硬闸(2.12)· lifecycle / verify 扩面(2.9–2.11) |
191
+ | 里程碑 | v2.0 — HGM G1 · Inform-YAML · ICVO audit · invoke_index |
192
+ | 许可 | **MIT** · [`LICENSE`](LICENSE) · 变更 [`CHANGELOG.md`](CHANGELOG.md) |
193
+
194
+ > README 历史「v2.0.0」表述为 HGM 里程碑标签;**以 `package.json` / npm `latest` / CHANGELOG 为准**。
177
195
 
178
196
  ---
179
197
 
@@ -183,7 +201,7 @@ cyning-harness/
183
201
  | --- | --- |
184
202
  | **总指引** | [`methodology/README.md`](docs/methodology/README.md) · [关系图](docs/methodology/DOCUMENT_MAP_v1_zh.md) · [路线 ROADMAP](docs/methodology/ROADMAP_v1_zh.md) |
185
203
  | **本体** | [DESIGN_ONTOLOGY v1.2](docs/methodology/product/DESIGN_ONTOLOGY_v1_zh.md) |
186
- | **使用** | [**USER_GUIDE v1.0**](docs/USER_GUIDE_v1.0_zh.md) · [P0 差距](docs/methodology/execution/P0_V0.2_GAP.md) · [金样验收](examples/demo_checkout/ACCEPTANCE.md) · [ETCLOVG 映射](docs/ETCLOVG_MAPPING_v1_zh.md) |
204
+ | **使用** | [**USER_GUIDE v1.0**](docs/USER_GUIDE_v1.0_zh.md)(§6.0 invoke 留档 · lifecycle dry-run)· [金样验收](examples/demo_checkout/ACCEPTANCE.md) |
187
205
  | **远期** | [HGM 草案](docs/methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md) |
188
206
  | **接入** | [ONBOARDING](docs/ONBOARDING.md) · [ARCHITECTURE](docs/ARCHITECTURE.md) |
189
207
 
@@ -66,13 +66,20 @@ npx @cyning/harness verify --spec docs/spec/SPEC-xxx_v1.md \
66
66
  # 只读生命周期登记(方向二 · harness/lifecycle.yaml · 非引擎)
67
67
  npx @cyning/harness lifecycle show
68
68
  npx @cyning/harness lifecycle show --json
69
- # 转移资格 dry-run(v2.10+ · 旁路报告 · 不替代 verify)
70
- # v2.11+ to_30 已接线:HG-* · reviews · audit_D5 · task_lint(close_* 仍可 unevaluated)
69
+ # 转移资格 dry-run(v2.10+ · 旁路报告 · 不替代 verify / task close
70
+ # v2.11+ to_30 已接线:HG-* · reviews · audit_D5 · task_lint
71
+ # v2.13+ close_* 已接线(与 task close 同语义 · 仍无 --apply)
71
72
  npx @cyning/harness lifecycle dry-run --transition to_30 --from draft \
72
73
  --task docs/tasks/active/task_xxx.md
74
+ npx @cyning/harness lifecycle dry-run --transition close --from done \
75
+ --task docs/tasks/active/task_xxx.md
73
76
  # 无 --task:仅结构 + 守卫清单 unevaluated
74
77
  npx @cyning/harness lifecycle dry-run --transition to_30 --from draft
75
78
 
79
+ # 受闸归档(v2.12+ 多帽 invoke 集合 · 缺省 10,30,40 · 见 USER_GUIDE §6.0)
80
+ npx @cyning/harness task close --file docs/tasks/active/task_xxx.md
81
+ # 豁免示例:--allow-invoke-gap · --allow-unchecked · --allow-no-review
82
+
76
83
  # 机械化率资产只读(v2.11+ · SoT=discipline-coverage.yaml)
77
84
  npx @cyning/harness discipline show
78
85
  npx @cyning/harness discipline show --json
@@ -94,6 +101,7 @@ npx @cyning/harness sync index --target /path/to/your-repo
94
101
  | **D3** | 30 前置人闸 | 复用 `gate-check.sh`,HG-AUDIT-R1 非 approved 时 verify 非 0 |
95
102
  | **D5** | 改码任务测试声明 | 仅 `--task`:`test_strategy=required` 但无测试/CI 引用时 verify 非 0 |
96
103
  | **reviews** | R&lt;n&gt; 审查文存在 | `--task` 与全量(v2.9+)· 缺文 BLOCKED · `--allow-no-review` |
104
+ | **invoke hats** | 多帽 invoke 集合 | `task close` 硬闸(v2.12+)· `verify --task` 仅 WARN · `lifecycle dry-run close` 旁路(v2.13) |
97
105
  | **active** | 任务发现 | 双路径 `docs/tasks/active` ∪ `docs/harness/tasks/active`(v2.9+) |
98
106
  | **S5** | Git 工作区干净 | dirty 时 warn(不直接 fail verify,但 apply 须 `--force`) |
99
107
  | **lint** | task 结构(仅 `--task`) | v2.7+ E 级 → `WARN: task lint`(不改 exit / `may_start_30`) |
package/docs/README.md CHANGED
@@ -3,8 +3,9 @@
3
3
  | 项 | 内容 |
4
4
  | --- | --- |
5
5
  | **入口** | 本页 → **[方法论总指引](./methodology/README.md)** |
6
- | **接入** | [**USER_GUIDE v1.0**](./USER_GUIDE_v1.0_zh.md) · [`ONBOARDING.md`](./ONBOARDING.md) · [`../wizard/README.md`](../wizard/README.md) |
6
+ | **接入** | [**USER_GUIDE v1.0**](./USER_GUIDE_v1.0_zh.md)(§6.0 invoke 留档 · lifecycle)· [`ONBOARDING.md`](./ONBOARDING.md) · [`../wizard/README.md`](../wizard/README.md) |
7
7
  | **架构** | [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
8
+ | **变更** | [`../CHANGELOG.md`](../CHANGELOG.md) · 当前包 **2.13.0** |
8
9
 
9
10
  ---
10
11
 
@@ -13,9 +14,16 @@
13
14
  | 读者 | 先读 |
14
15
  | --- | --- |
15
16
  | **新接入** | **USER_GUIDE v1.0** → ONBOARDING → [`methodology/execution/P0_V0.2_GAP.md`](./methodology/execution/P0_V0.2_GAP.md) |
16
- | **实现 / 扩展** | [`methodology/product/DESIGN_ONTOLOGY_v1_zh.md`](./methodology/product/DESIGN_ONTOLOGY_v1_zh.md) |
17
+ | **实现 / 扩展** | [`methodology/product/DESIGN_ONTOLOGY_v1_zh.md`](./methodology/product/DESIGN_ONTOLOGY_v1_zh.md) · [`spec/`](./spec/)(闸 SPEC) |
17
18
  | **战略 / 开源** | [`methodology/pointers/`](./methodology/pointers/README.md) → 工作区 STRATEGY_* |
18
- | **HGM 远期** | [`methodology/graph/`](./methodology/graph/README.md) |
19
+ | **HGM 远期** | [`methodology/graph/`](./methodology/graph/README.md) · 愿景 [`ROADMAP_TO_AGENT_GOVERNANCE.md`](./ROADMAP_TO_AGENT_GOVERNANCE.md) |
20
+
21
+ ### 近期产品说明(2.12–2.13)
22
+
23
+ | 主题 | 文档 |
24
+ |------|------|
25
+ | 多帽 invoke 留档硬闸 | USER_GUIDE §6.0 · [`SPEC-invoke-hats-retention-gate_v1.md`](./spec/SPEC-invoke-hats-retention-gate_v1.md) |
26
+ | dry-run 接线 close | [`SPEC-lifecycle-dry-run-close_v1.md`](./spec/SPEC-lifecycle-dry-run-close_v1.md) · ONBOARDING §2.2 |
19
27
 
20
28
  ---
21
29
 
@@ -26,6 +34,8 @@ docs/
26
34
  ├── README.md # 本页
27
35
  ├── ARCHITECTURE.md # 五轨 · 与业务仓关系
28
36
  ├── ONBOARDING.md # 接入指南
37
+ ├── USER_GUIDE_v1.0_zh.md # 陌生人手册
38
+ ├── spec/ # 产品闸 SPEC(signed)
29
39
  ├── DESIGN_ONTOLOGY_v1_zh.md # POINTER → methodology/product/
30
40
  ├── P0_V0.2_GAP.md # POINTER → methodology/execution/
31
41
  ├── HARNESS_GRAPH_MODEL_*.md # POINTER → methodology/graph/
@@ -35,4 +45,4 @@ docs/
35
45
  └── prompts/ # Agent 写作 / 讨论 Prompt
36
46
  ```
37
47
 
38
- **修订**:2026-06-15 · 方法论归集至 `methodology/`
48
+ **修订**:2026-07-26 · 对齐 2.12–2.13 · 索引 SPEC
@@ -31,23 +31,23 @@ cyning-harness 已经通过 HumanGate、AuditReview、InvokeSnapshot 等实体
31
31
 
32
32
  ## 2. 三阶段演进路径
33
33
 
34
- ### 阶段 0:当前状态(v0.2 v0.4
34
+ ### 阶段 0:当前状态(纪律包 · npm 2.x
35
35
 
36
36
  | 项 | 内容 |
37
37
  | --- | --- |
38
38
  | **形态** | 项目纪律包,需手动绑定到仓库,要求开发者遵循 SDD 帽链 |
39
+ | **当前包** | `@cyning/harness@2.13+`(以 npm / CHANGELOG 为准;本文档为愿景,非 semver 承诺) |
39
40
 
40
- **能力**:
41
+ **能力**(摘要):
41
42
 
42
- - ProcessTrack + StarterHat(10/22/30/40
43
- - HumanGate AuditReview 落盘
44
- - Sync 机制(S1–S6)
45
- - 基础 ICVO 映射
43
+ - ProcessTrack + StarterHat(10/20/30/40)+ HumanGate / AuditReview / InvokeSnapshot
44
+ - Sync(S1–S6)· ICVO verify / audit · lifecycle dry-run(to_30 · close_*)
45
+ - 多帽 invoke 留档硬闸(close · v2.12+)
46
46
 
47
47
  **限制**:
48
48
 
49
49
  - 必须有人参与闸门审批(HumanGate)
50
- - 未与 Agent 运行时集成
50
+ - 未与 Agent 运行时集成;dry-run **无** `--apply` / G7 runner
51
51
 
52
52
  ---
53
53
 
@@ -190,7 +190,7 @@ Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.sche
190
190
  | `npx @cyning/harness verify` | 全量:双路径 + reviews(v2.9+);`--task`:30 前聚合 + invoke hats WARN(v2.12+);`--spec`:SPEC→00(v2.8+ · 互斥) |
191
191
  | `npx @cyning/harness task close` | 受闸归档;v2.12+ 按 `required_invoke_hats` / `invoke_retention_profile` 校验多帽 invoke(缺省 `10,30,40`;`--allow-invoke-gap`) |
192
192
  | `npx @cyning/harness lifecycle show` | 只读展示 `harness/lifecycle.yaml`(登记 · v2.7+) |
193
- | `npx @cyning/harness lifecycle dry-run` | 转移资格判定(引擎消费 yaml · v2.10+ · 旁路 · 非 G7) |
193
+ | `npx @cyning/harness lifecycle dry-run` | 转移资格判定(v2.10+ · `to_30` v2.11 · **`close_*` v2.13** · 旁路 · 非 G7) |
194
194
  | `npx @cyning/harness discipline show` | 只读展示 `harness/discipline-coverage.yaml`(v2.11+ · 非 audit UI) |
195
195
  | `npx @cyning/harness gate-check` | 仅人工闸(`--graph` / `--json`) |
196
196
  | `npx @cyning/harness audit` | ICVO 机械审计(D3/D5/S5) |
@@ -207,6 +207,27 @@ Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.sche
207
207
 
208
208
  Audit **不替代** 维护者判断;22 内容质量仍须人读 review。详见 [`ONBOARDING.md`](./ONBOARDING.md) §2.2。
209
209
 
210
+ ### 6.0 多帽 invoke 留档(v2.12+)
211
+
212
+ `task close` 按 task 元信息校验 `by-task/<slug>/` 下 invoke 文件名所覆盖的 **hat 集合**(缺省要求 `10,30,40`)。
213
+
214
+ | 字段 / 手段 | 说明 |
215
+ | --- | --- |
216
+ | `invoke_retention_profile` | `default`=`10,30,40` · `minimal`=`30` · `full`=`00,10,20,30,40,CLOSE` |
217
+ | `required_invoke_hats` | 显式列表,**优先于** profile |
218
+ | 命名 | `invoke_YYYYMMDD_<hat>[_<hat>...]_<slug>.md`;hat 只在日期后连续前缀(例 `30_40` 可双计);**slug 段勿夹** `10`/`40` 等(v2.12.1 起不误计) |
219
+ | `--allow-invoke-gap` | close / `verify --task` 缺帽豁免并留痕 |
220
+ | 存量仅 30 | upgrade 后:补 `10`/`40` 档、或改 `minimal`、或显式 `--allow-invoke-gap` |
221
+
222
+ `verify --task` 对缺口仅 **WARN**,不挡 `may_start_30`;硬闸在 close。
223
+
224
+ 归档前可用旁路资格报告(**不** mv、**不**替代 `task close`):
225
+
226
+ ```bash
227
+ npx @cyning/harness lifecycle dry-run --transition close --from done --task docs/tasks/active/task_xxx.md
228
+ # 可选:--allow-invoke-gap · --allow-unchecked · --allow-no-review
229
+ ```
230
+
210
231
  ### 6.1 SDD-Compliance bench(维护者 · 可选)
211
232
 
212
233
  > **业务仓日常不必跑**;用于验证 `gate-check` / `sync` 公理行为,或对照 README「试点证据」中的 bench 数字。
@@ -0,0 +1,63 @@
1
+ # SPEC:lifecycle dry-run 接线 close_*(v1)
2
+
3
+ > **状态**:`signed`
4
+ > **track**:`feature`
5
+ > **前置**:`@cyning/harness@2.12.1`(invoke hats 硬闸)· dry-run 骨架 `2.10` · to_30 扩面 `2.11`
6
+ > **下游**:30 实现 → 测试 → docs → **2.13.0**
7
+ > **关联**:[`SPEC-lifecycle-guard-expand_v1.md`](./SPEC-lifecycle-guard-expand_v1.md) residual · [`SPEC-invoke-hats-retention-gate_v1.md`](./SPEC-invoke-hats-retention-gate_v1.md)
8
+
9
+ ---
10
+
11
+ ## Harness 元信息
12
+
13
+ | 字段 | 值 |
14
+ |------|-----|
15
+ | **spec_slug** | `lifecycle-dry-run-close` |
16
+ | **test_strategy** | `required` |
17
+ | **test_strategy_note** | close 转移各 close_* 守卫 pass/fail/warn;豁免旗;无 --apply |
18
+ | **skip_spec_audit** | `true` |
19
+
20
+ ---
21
+
22
+ ## 1. 背景与目标
23
+
24
+ `lifecycle dry-run` 对 `close` 转移的 `close_*` 仍全部 `unevaluated`,与 `task close` 硬闸脱节。
25
+ **一句话**:`--task` + `close` + `from=done` 时,把 yaml 登记的 close 守卫接到与 `task close` **同语义**的薄 adapter(旁路报告 · 不 mv · 无 `--apply`)。
26
+
27
+ ---
28
+
29
+ ## 2. 范围
30
+
31
+ | guard id | 语义 | allow |
32
+ |----------|------|-------|
33
+ | `close_invoke` | invoke hats 集合覆盖(v2.12+) | `--allow-invoke-gap` |
34
+ | `close_self_check` | 自检结论已回填 | — |
35
+ | `close_acceptance` | 验收勾选 | `--allow-unchecked` |
36
+ | `close_slug` | task_slug 与文件名一致 | — |
37
+ | `close_status` | 状态 done/completed | — |
38
+ | `close_review` | R\<n\> 审查文存在 | `--allow-no-review` |
39
+
40
+ - 复用 `task-close` 检查逻辑(抽 `evaluateCloseChecks`,禁止复制分叉)
41
+ - CLI `lifecycle dry-run` 透传上述 allow 旗
42
+ - CHANGELOG **2.13.0** · README / USER_GUIDE / ONBOARDING / docs 索引同步
43
+
44
+ ---
45
+
46
+ ## 3. 非范围
47
+
48
+ - `--apply` / 写盘 / G7 runner
49
+ - 改 `task close` / `verify` 硬闸语义
50
+ - N2-C · `to_00` 接线 · `audit --discipline` UI
51
+
52
+ ---
53
+
54
+ ## 4. 验收
55
+
56
+ - [x] `close` + 齐全 fixture → 全部 close_* 非 `unevaluated` · `unevaluated_count=0` · exit 0
57
+ - [x] 仅缺 invoke hats → `close_invoke=fail` · blocked
58
+ - [x] `--allow-invoke-gap` → `close_invoke=warn` · 不 block
59
+ - [x] 状态非 done → `close_status=fail`
60
+ - [x] 无 `--task`:仍 unevaluated
61
+ - [x] `npm test` 全绿 · README 版本表与 CLI 说明已更新
62
+
63
+ > **实现**:`@cyning/harness@2.13.0` · `evaluateCloseChecks` + lifecycle adapters。
@@ -6,7 +6,7 @@
6
6
  # 禁止:把本文件当「每周全量重盘」流程;不做 audit --discipline UI(本波)
7
7
 
8
8
  version: "1"
9
- as_of_package_version: "2.12.0"
9
+ as_of_package_version: "2.13.0"
10
10
  scope: "Starter · harness/prompts (10/22/30/40 + FRAGMENT)+ TEMPLATE_invoke"
11
11
  source_audit: "rethink/2026-07-mechanization-rate · 02 转录 + 03 聚合 · 状态已按 G1–G4/N1–N4 刷新(非第二轮通读盘点)"
12
12
 
@@ -114,7 +114,7 @@ statements:
114
114
  status: mechanical
115
115
  mechanism: "M5 close evaluateInvokeHatsRetention + M6 lint-done"
116
116
  gap: null
117
- notes: "v2.12.0 默认 10,30,40;profile minimal/full;--allow-invoke-gap"
117
+ notes: "v2.12.0 默认 10,30,40;profile minimal/full;--allow-invoke-gap;v2.12.1 前缀 hat run"
118
118
  mechanism_quality: null
119
119
  - id: A7
120
120
  source: 30-execute-code.md
@@ -274,7 +274,7 @@ statements:
274
274
  status: mechanical
275
275
  mechanism: "M5 evaluateInvokeHatsRetention / M6"
276
276
  gap: null
277
- notes: "v2.12.0 文件名 hat token · 合并 30_40 可双计"
277
+ notes: "v2.12.0+ 文件名 hat token · 合并 30_40 可双计 · v2.12.1 前缀 run 防 slug 误计"
278
278
  mechanism_quality: null
279
279
  - id: F2
280
280
  source: TEMPLATE_invoke.md
@@ -45,7 +45,7 @@
45
45
  | 打回重开 | 新文件或 `_r2` 后缀 |
46
46
  | 与 reviews 分工 | reviews = 结论;invoke = **当时指令** |
47
47
  | close 机械闸 | `harness task close` 按 task 元信息 `required_invoke_hats` / `invoke_retention_profile` 校验帽集合覆盖(v2.12+;缺省 `10,30,40`;文件名含 `30_40` 可双计;`--allow-invoke-gap` 豁免) |
48
- | 命名 | `invoke_YYYYMMDD_<hat>_<slug>.md` `invoke_YYYYMMDD_30_40_<slug>.md` |
48
+ | 命名 | `invoke_YYYYMMDD_<hat>[_<hat>...]_<slug>.md`(hat token **仅**在日期后、slug 前连续段;slug 勿夹 `10`/`40` 等白名单段 · v2.12.1) |
49
49
  | commit | 落盘 + task 回填后再 commit(见 HANDOFF) |
50
50
 
51
51
  ---
@@ -3,7 +3,8 @@
3
3
  # 登记真值:状态 / 转移 / 守卫。yaml 本身不是引擎。
4
4
  # 由 `lifecycle dry-run` 引擎消费(v2.10+ · 资格判定旁路报告 · 不写盘)。
5
5
  # v2.11+ to_30 已接线:HG-AUDIT-R1 · HG-TASK-DRAFT · reviews_retention · audit_D5 · task_lint
6
- # (close_* / 可选 to_00 仍可 unevaluated)
6
+ # v2.13+ close 已接线:close_invoke · close_self_check · close_acceptance · close_slug · close_status · close_review
7
+ # (to_00 / spec_reviews_retention 仍可 unevaluated)
7
8
  # 只读展示:npx @cyning/harness lifecycle show [--json]
8
9
  # Schema:schema/lifecycle.v1.schema.json
9
10
 
@@ -69,11 +70,7 @@ transitions:
69
70
  command_or_check: "task close 检查1 · invoke hats 集合覆盖(required_invoke_hats / profile · v2.12+)"
70
71
  severity: block
71
72
  allow_flag: "--allow-invoke-gap"
72
- note: "缺省 default=10,30,40;minimal=30;full 含 CLOSE;40 可与 30 合并文件名"
73
- - id: invoke_hats_retention
74
- command_or_check: "evaluateInvokeHatsRetention · 同 close_invoke(登记别名)"
75
- severity: block
76
- allow_flag: "--allow-invoke-gap"
73
+ note: "缺省 default=10,30,40;minimal=30;full 含 CLOSE;40 可与 30 合并文件名;v2.12.1 去掉 invoke_hats_retention 别名"
77
74
  - id: close_self_check
78
75
  command_or_check: "task close 检查2 · 自检结论已回填"
79
76
  severity: block
@@ -33,7 +33,7 @@
33
33
  ## 交接物
34
34
 
35
35
  - 可粘贴进 `docs/tasks/active/task_*.md` 的正文块;并注明建议 `test_strategy`。
36
- - 产出 task 须 `npx @cyning/harness task lint --file <task>` PASS(v2.3+ · 结构闸 E1–E7 / W1–W3)。
36
+ - 产出 task 须 `npx @cyning/harness task lint --file <task>` PASS(v2.3+ · 结构闸 E1–E7 / W1+;v2.12+ 含 **W6** invoke 留档字段提醒)。
37
37
  - 承接 **20-task-audit 审查**:按 `docs/harness/reviews/*_audit_*.md` 回填 task。
38
38
 
39
39
  ## OSS 阶段 C · 思考轮(Starter 摘要)
package/lib/cli.js CHANGED
@@ -531,9 +531,10 @@ async function cmdLifecycle(args) {
531
531
  npx @cyning/harness lifecycle show [--json]
532
532
  npx @cyning/harness lifecycle dry-run --transition ID --from STATE [--task PATH] [--json]
533
533
  [--allow-no-review] [--allow-lint-fail] [--allow-no-spec-review]
534
+ [--allow-invoke-gap] [--allow-unchecked]
534
535
 
535
536
  show · 只读展示 harness/lifecycle.yaml(登记真值 · v2.7+)
536
- dry-run · 转移资格判定(引擎消费 yaml · v2.10+ · 旁路报告 · runner / 非 G7)
537
+ dry-run · 转移资格判定(引擎消费 yaml · v2.10+ · to_30 v2.11+ · close_* v2.13+ · 旁路 · 非 G7)
537
538
  `);
538
539
  return;
539
540
  }
@@ -567,12 +568,16 @@ dry-run · 转移资格判定(引擎消费 yaml · v2.10+ · 旁路报告 ·
567
568
  const allowNoReview = remaining.includes('--allow-no-review');
568
569
  const allowLintFail = remaining.includes('--allow-lint-fail');
569
570
  const allowNoSpecReview = remaining.includes('--allow-no-spec-review');
571
+ const allowInvokeGap = remaining.includes('--allow-invoke-gap');
572
+ const allowUnchecked = remaining.includes('--allow-unchecked');
570
573
  remaining = remaining.filter(
571
574
  (a) =>
572
575
  a !== '--json' &&
573
576
  a !== '--allow-no-review' &&
574
577
  a !== '--allow-lint-fail' &&
575
- a !== '--allow-no-spec-review',
578
+ a !== '--allow-no-spec-review' &&
579
+ a !== '--allow-invoke-gap' &&
580
+ a !== '--allow-unchecked',
576
581
  );
577
582
  if (remaining.length > 0) {
578
583
  const err = new Error(`lifecycle dry-run 未知参数: ${remaining.join(' ')}`);
@@ -591,7 +596,13 @@ dry-run · 转移资格判定(引擎消费 yaml · v2.10+ · 旁路报告 ·
591
596
  transitionId,
592
597
  fromState,
593
598
  taskPath,
594
- flags: { allowNoReview, allowLintFail, allowNoSpecReview },
599
+ flags: {
600
+ allowNoReview,
601
+ allowLintFail,
602
+ allowNoSpecReview,
603
+ allowInvokeGap,
604
+ allowUnchecked,
605
+ },
595
606
  cwd: process.cwd(),
596
607
  });
597
608
 
package/lib/lifecycle.js CHANGED
@@ -5,6 +5,7 @@ import { runTestCheck } from './audit.js';
5
5
  import { resolveHarnessRoot } from './paths.js';
6
6
  import { findReview, parseHumanGates } from './task-meta.js';
7
7
  import { lintTaskFile } from './task-lint.js';
8
+ import { evaluateCloseChecks } from './task-close.js';
8
9
 
9
10
  const SEVERITIES = new Set(['block', 'warn']);
10
11
 
@@ -123,13 +124,19 @@ export function formatLifecycleShow(data) {
123
124
  return lines.join('\n').trimEnd();
124
125
  }
125
126
 
126
- /** 已接线的守卫(未登记者 → unevaluated;close_* 本波仍未接线) */
127
+ /** 已接线的守卫(未登记者 → unevaluated */
127
128
  const GUARD_ADAPTERS = {
128
129
  'HG-AUDIT-R1': evaluateHgGate('HG-AUDIT-R1'),
129
130
  'HG-TASK-DRAFT': evaluateHgGate('HG-TASK-DRAFT'),
130
131
  reviews_retention: evaluateReviewsRetention,
131
132
  audit_D5: evaluateAuditD5,
132
133
  task_lint: evaluateTaskLint,
134
+ close_invoke: evaluateCloseGuard('close_invoke'),
135
+ close_self_check: evaluateCloseGuard('close_self_check'),
136
+ close_acceptance: evaluateCloseGuard('close_acceptance'),
137
+ close_slug: evaluateCloseGuard('close_slug'),
138
+ close_status: evaluateCloseGuard('close_status'),
139
+ close_review: evaluateCloseGuard('close_review'),
133
140
  };
134
141
 
135
142
  function findRepoRootFromTask(absTask) {
@@ -146,6 +153,8 @@ function flagsAllow(flags, allowFlag) {
146
153
  if (allowFlag === '--allow-no-review') return Boolean(flags.allowNoReview);
147
154
  if (allowFlag === '--allow-lint-fail') return Boolean(flags.allowLintFail);
148
155
  if (allowFlag === '--allow-no-spec-review') return Boolean(flags.allowNoSpecReview);
156
+ if (allowFlag === '--allow-invoke-gap') return Boolean(flags.allowInvokeGap);
157
+ if (allowFlag === '--allow-unchecked') return Boolean(flags.allowUnchecked);
149
158
  return false;
150
159
  }
151
160
 
@@ -207,6 +216,19 @@ function evaluateTaskLint({ taskPath, target }) {
207
216
  }
208
217
  }
209
218
 
219
+ /** close_* · 复用 evaluateCloseChecks(与 task close 同语义) */
220
+ function evaluateCloseGuard(checkId) {
221
+ return ({ taskPath, closeEval, target, cwd }) => {
222
+ const ev =
223
+ closeEval ?? evaluateCloseChecks(taskPath, { cwd: target ?? cwd });
224
+ const c = ev.checks[checkId];
225
+ if (!c) {
226
+ return { status: 'fail', detail: `未知 close 检查: ${checkId}` };
227
+ }
228
+ return { status: c.status, detail: c.detail };
229
+ };
230
+ }
231
+
210
232
  /**
211
233
  * 转移 dry-run(引擎最小骨架 · v2.10+)。
212
234
  * 默认不写盘;无 --apply。
@@ -216,7 +238,13 @@ function evaluateTaskLint({ taskPath, target }) {
216
238
  * fromState: string,
217
239
  * taskPath?: string,
218
240
  * harnessRoot?: string,
219
- * flags?: { allowNoReview?: boolean, allowLintFail?: boolean, allowNoSpecReview?: boolean },
241
+ * flags?: {
242
+ * allowNoReview?: boolean,
243
+ * allowLintFail?: boolean,
244
+ * allowNoSpecReview?: boolean,
245
+ * allowInvokeGap?: boolean,
246
+ * allowUnchecked?: boolean,
247
+ * },
220
248
  * cwd?: string,
221
249
  * }} options
222
250
  */
@@ -282,6 +310,10 @@ export function dryRunTransition(options = {}) {
282
310
 
283
311
  const canEval = Boolean(absTask);
284
312
  const target = absTask ? findRepoRootFromTask(absTask) ?? cwd : cwd;
313
+ const closeEval =
314
+ canEval && transition.id === 'close'
315
+ ? evaluateCloseChecks(absTask, { cwd: target })
316
+ : null;
285
317
  const guards = [];
286
318
 
287
319
  for (const g of transition.guards) {
@@ -303,6 +335,7 @@ export function dryRunTransition(options = {}) {
303
335
  flags,
304
336
  guard: g,
305
337
  cwd,
338
+ closeEval,
306
339
  });
307
340
 
308
341
  let status = evaluated.status;
package/lib/task-close.js CHANGED
@@ -14,95 +14,108 @@ import {
14
14
  } from './task-meta.js';
15
15
 
16
16
  /**
17
- * task close 机械校验 + 归档执行。
18
- * 任一校验失败 ok:false + blockers[],绝不 mv。
19
- * 默认 dry-run(只检不 mv);yes=true 才执行归档。
17
+ * close 机械校验(结构化 · 供 task close lifecycle dry-run 共用)。
18
+ * 不应用 --allow-*(豁免由调用方处理);不 mv。
19
+ *
20
+ * @returns {{
21
+ * file: string,
22
+ * slug: string|null,
23
+ * fileSlug: string,
24
+ * repoRoot: string|null,
25
+ * inActive: boolean,
26
+ * invoke_hats: object|null,
27
+ * checks: Record<string, { status: 'pass'|'fail', detail: string }>,
28
+ * }}
20
29
  */
21
- export function closeTaskFile(filePath, options = {}) {
22
- const {
23
- yes = false,
24
- target: targetArg,
25
- allowUnchecked = false,
26
- allowNoReview = false,
27
- allowInvokeGap = false,
28
- cwd = process.cwd(),
29
- } = options;
30
-
30
+ export function evaluateCloseChecks(filePath, options = {}) {
31
+ const { cwd = process.cwd() } = options;
31
32
  const abs = path.resolve(cwd, filePath);
32
- const blockers = [];
33
- const warnings = [];
33
+
34
+ const emptyFail = (detail) => ({
35
+ file: abs,
36
+ slug: null,
37
+ fileSlug: extractTaskSlug(abs),
38
+ repoRoot: null,
39
+ inActive: false,
40
+ invoke_hats: null,
41
+ checks: {
42
+ close_invoke: { status: 'fail', detail },
43
+ close_self_check: { status: 'fail', detail },
44
+ close_acceptance: { status: 'fail', detail },
45
+ close_slug: { status: 'fail', detail },
46
+ close_status: { status: 'fail', detail },
47
+ close_review: { status: 'fail', detail },
48
+ },
49
+ });
34
50
 
35
51
  if (!fs.existsSync(abs)) {
36
- return {
37
- ok: false,
38
- blockers: [`task 文件不存在: ${filePath}`],
39
- warnings,
40
- file: abs,
41
- slug: extractTaskSlug(abs),
42
- dest: null,
43
- dryRun: !yes,
44
- moved: false,
45
- };
52
+ return emptyFail(`task 文件不存在: ${filePath}`);
46
53
  }
47
54
 
48
55
  const content = fs.readFileSync(abs, 'utf8');
49
56
  const meta = parseHarnessMeta(content);
50
57
  const fileSlug = extractTaskSlug(abs);
51
58
  const slug = meta.task_slug ?? null;
59
+ const activeDir = path.dirname(abs);
60
+ const inActive = path.basename(activeDir) === 'active';
61
+ const repoRoot = findRepoRoot(activeDir);
62
+
63
+ const checks = {};
52
64
 
53
- // 检查 4 · task_slug 一致(下划线/连字符惯例等价)
65
+ // close_slug
54
66
  if (!slug) {
55
- blockers.push('Harness 元信息表缺 task_slug');
67
+ checks.close_slug = { status: 'fail', detail: 'Harness 元信息表缺 task_slug' };
56
68
  } else if (normalizeSlug(slug) !== normalizeSlug(fileSlug)) {
57
- blockers.push(`slug 不一致: 文件名 ${fileSlug} ≠ 元信息 task_slug ${slug}`);
69
+ checks.close_slug = {
70
+ status: 'fail',
71
+ detail: `slug 不一致: 文件名 ${fileSlug} ≠ 元信息 task_slug ${slug}`,
72
+ };
73
+ } else {
74
+ checks.close_slug = { status: 'pass', detail: `slug=${slug}` };
58
75
  }
59
76
 
60
- // 布局推导:active/ 同级 done/ · repo root(docs 祖先)
61
- const activeDir = path.dirname(abs);
62
- const inActive = path.basename(activeDir) === 'active';
63
- const repoRoot = findRepoRoot(activeDir);
64
-
65
- // 检查 1 · invoke 留档(v2.12+:按 required_invoke_hats / profile 集合覆盖)
77
+ // close_invoke
66
78
  let invokeHatsEval = null;
67
79
  if (!repoRoot) {
68
- blockers.push(`无法定位 repo root(${activeDir} 无 docs 祖先)`);
69
- } else if (slug) {
70
- const invokeDir = path.join(
71
- repoRoot,
72
- 'docs/harness/invokes/by-task',
73
- slug,
74
- );
80
+ checks.close_invoke = {
81
+ status: 'fail',
82
+ detail: `无法定位 repo root(${activeDir} 无 docs 祖先)`,
83
+ };
84
+ } else if (!slug) {
85
+ checks.close_invoke = { status: 'fail', detail: '无 task_slug · 无法解析 invoke 目录' };
86
+ } else {
87
+ const invokeDir = path.join(repoRoot, 'docs/harness/invokes/by-task', slug);
75
88
  invokeHatsEval = evaluateInvokeHatsRetention(meta, invokeDir);
76
89
  const relDir = path.relative(repoRoot, invokeDir).replace(/\\/g, '/');
77
90
  if (invokeHatsEval.missingDir || invokeHatsEval.files.length === 0) {
78
- const msg = `missing invoke snapshots: ${relDir} 不存在或无 .md(要求 hats: ${invokeHatsEval.required.join(',')})`;
79
- if (allowInvokeGap) {
80
- warnings.push(`warn: ${msg}(--allow-invoke-gap 豁免 · 留痕)`);
81
- } else {
82
- blockers.push(msg);
83
- }
91
+ checks.close_invoke = {
92
+ status: 'fail',
93
+ detail: `missing invoke snapshots: ${relDir} 不存在或无 .md(要求 hats: ${invokeHatsEval.required.join(',')})`,
94
+ };
84
95
  } else if (invokeHatsEval.missing.length > 0) {
85
- const msg =
86
- `missing invoke hats: ${invokeHatsEval.missing.join(',')} ` +
87
- `(要求 ${invokeHatsEval.required.join(',')} · 已有 ${invokeHatsEval.covered.join(',') || '∅'} · ` +
88
- `source=${invokeHatsEval.resolution.source}` +
89
- `${invokeHatsEval.resolution.unknown_profile ? ` · unknown_profile=${invokeHatsEval.resolution.unknown_profile}` : ''})`;
90
- if (allowInvokeGap) {
91
- warnings.push(`warn: ${msg}(--allow-invoke-gap 豁免 · 留痕)`);
92
- } else {
93
- blockers.push(msg);
94
- }
95
- } else if (invokeHatsEval.resolution.unknown_profile) {
96
- warnings.push(
97
- `warn: unknown invoke_retention_profile '${invokeHatsEval.resolution.unknown_profile}' · 已回退 default`,
98
- );
96
+ checks.close_invoke = {
97
+ status: 'fail',
98
+ detail:
99
+ `missing invoke hats: ${invokeHatsEval.missing.join(',')} ` +
100
+ `(要求 ${invokeHatsEval.required.join(',')} · 已有 ${invokeHatsEval.covered.join(',') || ''} · ` +
101
+ `source=${invokeHatsEval.resolution.source}` +
102
+ `${invokeHatsEval.resolution.unknown_profile ? ` · unknown_profile=${invokeHatsEval.resolution.unknown_profile}` : ''})`,
103
+ };
104
+ } else {
105
+ const note = invokeHatsEval.resolution.unknown_profile
106
+ ? ` · unknown_profile=${invokeHatsEval.resolution.unknown_profile} 已回退 default`
107
+ : '';
108
+ checks.close_invoke = {
109
+ status: 'pass',
110
+ detail: `hats ok · ${invokeHatsEval.required.join(',')}${note}`,
111
+ };
99
112
  }
100
113
  }
101
114
 
102
- // 检查 2 · 自检结论回填
115
+ // close_self_check
103
116
  const selfCheck = extractSection(content, '### 自检结论', '\n##');
104
117
  if (!selfCheck) {
105
- blockers.push('缺 ### 自检结论 节');
118
+ checks.close_self_check = { status: 'fail', detail: '缺 ### 自检结论 节' };
106
119
  } else {
107
120
  const bodyLines = selfCheck
108
121
  .split('\n')
@@ -111,53 +124,146 @@ export function closeTaskFile(filePath, options = {}) {
111
124
  .filter(Boolean);
112
125
  const substantive = bodyLines.filter((l) => !PLACEHOLDER_RE.test(l));
113
126
  if (substantive.length === 0) {
114
- blockers.push('自检结论未回填(空或纯占位符)');
127
+ checks.close_self_check = {
128
+ status: 'fail',
129
+ detail: '自检结论未回填(空或纯占位符)',
130
+ };
131
+ } else {
132
+ checks.close_self_check = { status: 'pass', detail: '自检结论已回填' };
115
133
  }
116
134
  }
117
135
 
118
- // 检查 3 · 验收标准勾选
136
+ // close_acceptance
119
137
  const acceptance = extractSection(content, '## 验收标准', '\n##');
120
138
  if (!acceptance) {
121
- blockers.push('缺 ## 验收标准 节');
139
+ checks.close_acceptance = { status: 'fail', detail: '缺 ## 验收标准 节' };
122
140
  } else {
123
- const unchecked = acceptance
124
- .split('\n')
125
- .filter((l) => UNCHECKED_RE.test(l));
141
+ const unchecked = acceptance.split('\n').filter((l) => UNCHECKED_RE.test(l));
126
142
  if (unchecked.length > 0) {
127
- if (allowUnchecked) {
128
- warnings.push(`warn: 验收标准 ${unchecked.length} 项未勾选(--allow-unchecked 豁免)`);
129
- } else {
130
- blockers.push(
131
- `验收标准 ${unchecked.length} 项未勾选(或 --allow-unchecked 显式豁免)`,
132
- );
133
- }
143
+ checks.close_acceptance = {
144
+ status: 'fail',
145
+ detail: `验收标准 ${unchecked.length} 项未勾选`,
146
+ };
147
+ } else {
148
+ checks.close_acceptance = { status: 'pass', detail: '验收标准已勾选' };
134
149
  }
135
150
  }
136
151
 
137
- // 检查 5 · 状态字段
152
+ // close_status
138
153
  const statusLine = content.split('\n').find((l) => STATUS_RE.test(l));
139
154
  const status = statusLine ? statusLine.match(STATUS_RE)[1].toLowerCase() : null;
140
155
  if (!status) {
141
- blockers.push('未找到 > **状态** 行');
156
+ checks.close_status = { status: 'fail', detail: '未找到 > **状态** 行' };
142
157
  } else if (!CLOSE_STATUSES.has(status)) {
143
- blockers.push(`状态非 done/completed(当前: ${status})`);
158
+ checks.close_status = {
159
+ status: 'fail',
160
+ detail: `状态非 done/completed(当前: ${status})`,
161
+ };
162
+ } else {
163
+ checks.close_status = { status: 'pass', detail: `status=${status}` };
144
164
  }
145
165
 
146
- // 检查 6 · R<n> 审查文留档(G2 · v2.5+)
147
- if (repoRoot) {
166
+ // close_review
167
+ if (!repoRoot) {
168
+ checks.close_review = {
169
+ status: 'fail',
170
+ detail: '无法定位 repo root · 无法查 reviews',
171
+ };
172
+ } else {
148
173
  const review = findReview(repoRoot, abs);
149
- if (!review.found) {
150
- if (allowNoReview) {
151
- warnings.push('warn: missing R<n> review(--allow-no-review 豁免 · 留痕)');
152
- } else {
153
- blockers.push(
154
- 'missing R<n> review(docs/harness/reviews/ 无该 task 的 _audit_R 文件 · 或 --allow-no-review)',
174
+ if (review.found) {
175
+ checks.close_review = { status: 'pass', detail: review.latest };
176
+ } else {
177
+ checks.close_review = { status: 'fail', detail: 'missing R<n> review' };
178
+ }
179
+ }
180
+
181
+ return {
182
+ file: abs,
183
+ slug: slug ?? fileSlug,
184
+ fileSlug,
185
+ repoRoot,
186
+ inActive,
187
+ invoke_hats: invokeHatsEval
188
+ ? {
189
+ required: invokeHatsEval.required,
190
+ covered: invokeHatsEval.covered,
191
+ missing: invokeHatsEval.missing,
192
+ source: invokeHatsEval.resolution.source,
193
+ }
194
+ : null,
195
+ checks,
196
+ };
197
+ }
198
+
199
+ /**
200
+ * task close 机械校验 + 归档执行。
201
+ * 任一校验失败 → ok:false + blockers[],绝不 mv。
202
+ * 默认 dry-run(只检不 mv);yes=true 才执行归档。
203
+ */
204
+ export function closeTaskFile(filePath, options = {}) {
205
+ const {
206
+ yes = false,
207
+ target: targetArg,
208
+ allowUnchecked = false,
209
+ allowNoReview = false,
210
+ allowInvokeGap = false,
211
+ cwd = process.cwd(),
212
+ } = options;
213
+
214
+ const abs = path.resolve(cwd, filePath);
215
+ const blockers = [];
216
+ const warnings = [];
217
+
218
+ if (!fs.existsSync(abs)) {
219
+ return {
220
+ ok: false,
221
+ blockers: [`task 文件不存在: ${filePath}`],
222
+ warnings,
223
+ file: abs,
224
+ slug: extractTaskSlug(abs),
225
+ dest: null,
226
+ dryRun: !yes,
227
+ moved: false,
228
+ };
229
+ }
230
+
231
+ const evaluated = evaluateCloseChecks(filePath, { cwd });
232
+ const { checks, inActive, slug, fileSlug, invoke_hats: invokeHats } = evaluated;
233
+
234
+ const pushCheck = (id, allow, allowLabel) => {
235
+ const c = checks[id];
236
+ if (!c) return;
237
+ if (c.status === 'pass') {
238
+ if (id === 'close_invoke' && /unknown_profile=/.test(c.detail)) {
239
+ warnings.push(
240
+ `warn: unknown invoke_retention_profile(已回退 default)· ${c.detail}`,
155
241
  );
156
242
  }
243
+ return;
157
244
  }
158
- }
245
+ if (allow) {
246
+ warnings.push(`warn: ${c.detail}(${allowLabel} 豁免 · 留痕)`);
247
+ } else {
248
+ blockers.push(
249
+ id === 'close_acceptance'
250
+ ? `${c.detail}(或 --allow-unchecked 显式豁免)`
251
+ : id === 'close_review'
252
+ ? 'missing R<n> review(docs/harness/reviews/ 无该 task 的 _audit_R 文件 · 或 --allow-no-review)'
253
+ : c.detail,
254
+ );
255
+ }
256
+ };
159
257
 
160
- // 归档目标
258
+ pushCheck('close_slug', false);
259
+ pushCheck('close_invoke', allowInvokeGap, '--allow-invoke-gap');
260
+ pushCheck('close_self_check', false);
261
+ pushCheck('close_acceptance', allowUnchecked, '--allow-unchecked');
262
+ pushCheck('close_status', false);
263
+ pushCheck('close_review', allowNoReview, '--allow-no-review');
264
+
265
+ // 归档目标(布局约束 · 非 yaml 守卫)
266
+ const activeDir = path.dirname(abs);
161
267
  let dest = null;
162
268
  if (targetArg) {
163
269
  dest = path.resolve(cwd, targetArg);
@@ -187,14 +293,7 @@ export function closeTaskFile(filePath, options = {}) {
187
293
  dest,
188
294
  dryRun: !yes,
189
295
  moved,
190
- invoke_hats: invokeHatsEval
191
- ? {
192
- required: invokeHatsEval.required,
193
- covered: invokeHatsEval.covered,
194
- missing: invokeHatsEval.missing,
195
- source: invokeHatsEval.resolution.source,
196
- }
197
- : null,
296
+ invoke_hats: invokeHats,
198
297
  };
199
298
  }
200
299
 
package/lib/task-meta.js CHANGED
@@ -456,18 +456,15 @@ export function resolveRequiredInvokeHats(meta = {}) {
456
456
  }
457
457
 
458
458
  /**
459
- * 从 invoke 文件名提取 hat token 集合。
459
+ * 从 invoke 文件名提取 hat token 集合(v2.12.1+:前缀 hat run)。
460
460
  * 兼容:invoke_YYYYMMDD_30_slug.md · invoke_YYYYMMDD_30_40_slug.md · invoke_*_CLOSE_*.md
461
+ * 只消费日期后的**连续前缀** hat token;遇第一个非 hat 段即停(其后为 slug,不误计)。
461
462
  * 亦认 22→20(V2 改名等价,计为 20)。
462
463
  */
463
464
  export function extractHatsFromInvokeFilename(fileName) {
464
465
  const base = path.basename(fileName, '.md');
465
466
  const hats = new Set();
466
467
 
467
- if (/CLOSE/i.test(base)) {
468
- hats.add('CLOSE');
469
- }
470
-
471
468
  // 去 invoke_ 前缀与可选日期段
472
469
  let rest = base.replace(/^invoke[_-]?/i, '');
473
470
  rest = rest.replace(/^\d{8}[_-]?/, '');
@@ -482,7 +479,10 @@ export function extractHatsFromInvokeFilename(fileName) {
482
479
  }
483
480
  if (KNOWN_INVOKE_HAT_TOKENS.has(p)) {
484
481
  hats.add(normalizeHatToken(p));
482
+ continue;
485
483
  }
484
+ // 首个非 hat 段起视为 slug · 停止扫描(防 slug 含 10/40 误计)
485
+ break;
486
486
  }
487
487
 
488
488
  // V2:22 与 20 等价(审 task)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyning/harness",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "cyning-harness discipline package · init / upgrade / check CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",