@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 +40 -2
- package/README.md +22 -4
- package/docs/ONBOARDING.md +10 -2
- package/docs/README.md +14 -4
- package/docs/ROADMAP_TO_AGENT_GOVERNANCE.md +7 -7
- package/docs/USER_GUIDE_v1.0_zh.md +22 -1
- package/docs/spec/SPEC-lifecycle-dry-run-close_v1.md +63 -0
- package/harness/discipline-coverage.yaml +3 -3
- package/harness/invokes/TEMPLATE_invoke.md +1 -1
- package/harness/lifecycle.yaml +3 -6
- package/harness/prompts/10-task-requirements.md +1 -1
- package/lib/cli.js +14 -3
- package/lib/lifecycle.js +35 -2
- package/lib/task-close.js +196 -97
- package/lib/task-meta.js +5 -5
- package/package.json +1 -1
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
|
|
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 后按
|
|
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
|
-
| 当前 |
|
|
175
|
-
|
|
|
176
|
-
| 许可 | **MIT** · [`LICENSE`](LICENSE) ·
|
|
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) ·
|
|
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
|
|
package/docs/ONBOARDING.md
CHANGED
|
@@ -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
|
|
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<n> 审查文存在 | `--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-
|
|
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
|
|
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/
|
|
43
|
-
-
|
|
44
|
-
-
|
|
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` |
|
|
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.
|
|
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
|
|
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
|
---
|
package/harness/lifecycle.yaml
CHANGED
|
@@ -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
|
-
#
|
|
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
|
|
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+ ·
|
|
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: {
|
|
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
|
|
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?: {
|
|
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
|
-
*
|
|
19
|
-
*
|
|
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
|
|
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
|
-
|
|
33
|
-
const
|
|
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
|
-
//
|
|
65
|
+
// close_slug
|
|
54
66
|
if (!slug) {
|
|
55
|
-
|
|
67
|
+
checks.close_slug = { status: 'fail', detail: 'Harness 元信息表缺 task_slug' };
|
|
56
68
|
} else if (normalizeSlug(slug) !== normalizeSlug(fileSlug)) {
|
|
57
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
//
|
|
115
|
+
// close_self_check
|
|
103
116
|
const selfCheck = extractSection(content, '### 自检结论', '\n##');
|
|
104
117
|
if (!selfCheck) {
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
136
|
+
// close_acceptance
|
|
119
137
|
const acceptance = extractSection(content, '## 验收标准', '\n##');
|
|
120
138
|
if (!acceptance) {
|
|
121
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
156
|
+
checks.close_status = { status: 'fail', detail: '未找到 > **状态** 行' };
|
|
142
157
|
} else if (!CLOSE_STATUSES.has(status)) {
|
|
143
|
-
|
|
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
|
-
//
|
|
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 (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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:
|
|
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)
|