@cyning/harness 2.11.0 → 2.12.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 +34 -0
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/ONBOARDING.md +4 -0
- package/docs/USER_GUIDE_v1.0_zh.md +14 -13
- package/docs/methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md +4 -2
- package/docs/methodology/graph/README.md +6 -4
- package/docs/spec/SPEC-invoke-hats-retention-gate_v1.md +59 -0
- package/harness/discipline-coverage.yaml +12 -7
- package/harness/invokes/TEMPLATE_invoke.md +3 -2
- package/harness/lifecycle.yaml +7 -1
- package/harness/prompts/10-task-requirements.md +3 -1
- package/harness/prompts/20-task-audit.md +3 -1
- package/harness/prompts/30-execute-code.md +2 -2
- package/harness/templates/TASK_TEMPLATE.md +2 -0
- package/lib/cli.js +29 -10
- package/lib/task-close.js +35 -7
- package/lib/task-lint.js +27 -0
- package/lib/task-meta.js +152 -0
- package/lib/verify.js +39 -1
- package/package.json +1 -1
- package/wizard/lib/common.sh +17 -0
- package/wizard/lib/manifest.sh +34 -0
- package/wizard/upgrade.sh +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.12.0] - 2026-07-26
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **多帽 invoke 留档硬闸**:task 元信息 `required_invoke_hats` / `invoke_retention_profile`(`default`=`10,30,40` · `minimal`=`30` · `full`=`00,10,20,30,40,CLOSE`)。
|
|
12
|
+
- `task close` 检查 1 按集合覆盖校验;文件名 `30_40` 可同时满足 30 与 40;`--allow-invoke-gap` 豁免留痕。
|
|
13
|
+
- `verify --task` 对 invoke hats 缺口 **WARN**(不挡 30);`task lint` **W6** 提醒未设字段。
|
|
14
|
+
- `lib/task-meta.js`:`resolveRequiredInvokeHats` · `scanInvokeHats` · `evaluateInvokeHatsRetention` · `extractHatsFromInvokeFilename`。
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- `lifecycle.yaml` close 守卫登记 `invoke_hats_retention`;`discipline-coverage.yaml` `as_of` → **2.12.0** · gaps `INVOKE-HATS`。
|
|
19
|
+
- TASK_TEMPLATE / TEMPLATE_invoke / 10·20·30 帽文同步字段与纪律。
|
|
20
|
+
|
|
21
|
+
### Notes
|
|
22
|
+
|
|
23
|
+
- SPEC:`docs/spec/SPEC-invoke-hats-retention-gate_v1.md`
|
|
24
|
+
- **破坏性(close)**:无字段时按 default 要求 10+30+40;存量仅 30 的 active 须补档、改 `minimal` 或 `--allow-invoke-gap`
|
|
25
|
+
- minor · 建议业务仓 upgrade 后按 RUNBOOK 勾选 invoke 集合
|
|
26
|
+
|
|
27
|
+
## [2.11.1] - 2026-07-25
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **F1 · `upgrade` 自绊 S5(ops-desk-web dogfood)**:`local.json` 改为 **sync apply 成功后** 写入(compare-before-write)。避免 npx 缓存路径变化时先脏工作区再被 git-clean 中止。
|
|
32
|
+
- **F2 · manifest 静默丢字段**:`write_manifest_upgrade` 重写前对 schema 外字段与 `ide` 裁剪打 **WARN** 清单(知情);**仍不**合并保留(`additionalProperties: false`)。
|
|
33
|
+
|
|
34
|
+
### Notes
|
|
35
|
+
|
|
36
|
+
- 摩擦报告:`Projects/docs/harness/guides/ANALYSIS_upgrade_wizard_friction_20260725_v1_zh.md`
|
|
37
|
+
- patch · 2.3+ manifest 仅五字段:`version` / `preset` / `ide` / `from_version` / `upgraded_at`(`name` / `tech_graph_dir` / `tasks_dir` / `hooks` 等已废弃)
|
|
38
|
+
- **已发布**:`@cyning/harness@2.11.1`(npm `latest` · 2026-07-26)· tag `v2.11.1` 已推送
|
|
39
|
+
|
|
7
40
|
## [2.11.0] - 2026-07-25
|
|
8
41
|
|
|
9
42
|
### Added
|
|
@@ -19,6 +52,7 @@
|
|
|
19
52
|
- SPEC:`docs/spec/SPEC-lifecycle-guard-expand_v1.md` · `docs/spec/SPEC-discipline-show_v1.md`
|
|
20
53
|
- minor · `lifecycle show` / verify 硬闸语义不变
|
|
21
54
|
- `discipline-coverage.yaml` `as_of_package_version` → **2.11.0**
|
|
55
|
+
- **已发布**:`@cyning/harness@2.11.0`(npm `latest` · 2026-07-25)· tag `v2.11.0` 已推送
|
|
22
56
|
|
|
23
57
|
## [2.10.0] - 2026-07-25
|
|
24
58
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -68,5 +68,5 @@ CLI/Worker 与模板 **同一仓库** 演进,避免多仓割裂。
|
|
|
68
68
|
Track / Hat / Sync / SDD 公理与扩展规则见 [`methodology/product/DESIGN_ONTOLOGY_v1_zh.md`](./methodology/product/DESIGN_ONTOLOGY_v1_zh.md)(**v1.3**)。
|
|
69
69
|
**帽链流程(人读)**:[`methodology/product/SDD_HAT_FLOW_v2_zh.md`](./methodology/product/SDD_HAT_FLOW_v2_zh.md)
|
|
70
70
|
**方法论总指引**:[`methodology/README.md`](./methodology/README.md) · 关系图 [`methodology/DOCUMENT_MAP_v1_zh.md`](./methodology/DOCUMENT_MAP_v1_zh.md)
|
|
71
|
-
远期 **过程实例图 + 事件历史** 见 [`methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md`](./methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md)
|
|
71
|
+
远期 **过程实例图 + 事件历史** 见 [`methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md`](./methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md)(**PIP(过程实例投影)** · **v2.0+** · v1.0 后)。
|
|
72
72
|
与 **战略本体**(工作区 `docs/harness/guides/STRATEGY_ONTOLOGY_*`)分层:本文件描述 **产品如何实现**,战略文档描述 **对外演进**。
|
package/docs/ONBOARDING.md
CHANGED
|
@@ -26,6 +26,10 @@ npx @cyning/harness check # 仅检查是否有新版本
|
|
|
26
26
|
写入 `.cyning-harness/manifest.json`(钉版本 · preset · ide)与 `profile.json`。
|
|
27
27
|
Schema:[`schema/manifest.v1.schema.json`](../schema/manifest.v1.schema.json)
|
|
28
28
|
|
|
29
|
+
**manifest(2.3+)仅五字段**:`version` / `preset` / `ide` / `from_version` / `upgraded_at`(`additionalProperties: false`)。
|
|
30
|
+
旧字段 `name` / `harness_version` / `tech_graph_dir` / `tasks_dir` / `hooks` 已废弃且无消费方;`upgrade` 会整体重写并(v2.11.1+)对将被移除的字段打 WARN,**不会**合并保留。
|
|
31
|
+
`upgrade` 的 `local.json` 簿记在 sync apply **成功之后**写入(v2.11.1+),避免自绊 S5 git-clean。
|
|
32
|
+
|
|
29
33
|
**维护者 / 离线 · clone 路径:**
|
|
30
34
|
|
|
31
35
|
```bash
|
|
@@ -185,9 +185,10 @@ Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.sche
|
|
|
185
185
|
| 命令 | 用途 |
|
|
186
186
|
| --- | --- |
|
|
187
187
|
| `npx @cyning/harness init` | 首次安装模板与 manifest(可选 `--with-scripts`) |
|
|
188
|
-
| `npx @cyning/harness upgrade` | 同步产品包更新(可加 `--gate-check`
|
|
188
|
+
| `npx @cyning/harness upgrade` | 同步产品包更新(可加 `--gate-check`);v2.11.1+:`local.json` 在 apply 后写入;manifest 重写前 WARN 非标准字段(2.3+ 仅五字段) |
|
|
189
189
|
| `npx @cyning/harness check` | 检查是否有新版本 |
|
|
190
|
-
| `npx @cyning/harness verify` | 全量:双路径 + reviews(v2.9+);`--task`:30
|
|
190
|
+
| `npx @cyning/harness verify` | 全量:双路径 + reviews(v2.9+);`--task`:30 前聚合 + invoke hats WARN(v2.12+);`--spec`:SPEC→00(v2.8+ · 互斥) |
|
|
191
|
+
| `npx @cyning/harness task close` | 受闸归档;v2.12+ 按 `required_invoke_hats` / `invoke_retention_profile` 校验多帽 invoke(缺省 `10,30,40`;`--allow-invoke-gap`) |
|
|
191
192
|
| `npx @cyning/harness lifecycle show` | 只读展示 `harness/lifecycle.yaml`(登记 · v2.7+) |
|
|
192
193
|
| `npx @cyning/harness lifecycle dry-run` | 转移资格判定(引擎消费 yaml · v2.10+ · 旁路 · 非 G7) |
|
|
193
194
|
| `npx @cyning/harness discipline show` | 只读展示 `harness/discipline-coverage.yaml`(v2.11+ · 非 audit UI) |
|
|
@@ -284,9 +285,9 @@ npm run harness -- check --target /tmp/foo
|
|
|
284
285
|
| --- | --- | --- | --- |
|
|
285
286
|
| **MD 人类轨** | `docs/_tech_graph/*.md` | 代码审阅、README 引用、Mermaid 渲染 | v1.0+ |
|
|
286
287
|
| **YAML 编辑源** | `docs/_tech_graph/*.graph.yaml` | 结构化编辑、diff、CI 校验 | **v1.1+** |
|
|
287
|
-
| **
|
|
288
|
+
| **PIP(过程实例投影)过程轨** | `.cyning-harness/events/*.jsonl` | Task / Gate / Review 实例与事件史 | **v2.0+** |
|
|
288
289
|
|
|
289
|
-
**原则**:YAML 为 Inform 编辑源,
|
|
290
|
+
**原则**:YAML 为 Inform 编辑源,PIP(过程实例投影)为过程事件图;YAML **不替代** task/review 真值,PIP(过程实例投影)**不替代** YAML/MD Inform 正文。
|
|
290
291
|
|
|
291
292
|
### 10.2 最小工作流
|
|
292
293
|
|
|
@@ -327,9 +328,9 @@ npx @cyning/harness graph yaml check --all --input docs/_tech_graph
|
|
|
327
328
|
| bench `100` | [SDD-Compliance](../examples/compliance_bench/README.md) 五场景合规率 · 见上文 §6.1 |
|
|
328
329
|
| Extended 帽 | 00/50/链式 PROMPT 不在 Starter 默认包 · 见 [`harness/prompts/README.md`](../harness/prompts/README.md) |
|
|
329
330
|
| Inform-YAML | **v1.1+** · 可选编辑源 · 须 `graph yaml check` 与 `graph.json` 一致 |
|
|
330
|
-
|
|
|
331
|
+
| PIP(过程实例投影)/ 图数据库 | **v2.0+ 已实现** `graph ingest|snapshot|axioms` · 本地 JSONL + snapshot;Neo4j / 远端同步 **仍提案** |
|
|
331
332
|
| Agent-shell | 研究轨 #9,非 npm 功能 |
|
|
332
|
-
| rejected→draft | **v2.0.1+** bench S5 +
|
|
333
|
+
| rejected→draft | **v2.0.1+** bench S5 + PIP(过程实例投影)axioms 事件流精确匹配 |
|
|
333
334
|
|
|
334
335
|
---
|
|
335
336
|
|
|
@@ -350,21 +351,21 @@ A:不会(S2 域)。若 prompts 模板有更新,apply 会更新 **模板
|
|
|
350
351
|
**Q:如何编辑 Inform 架构图?**
|
|
351
352
|
A:v1.1+ 可选 `docs/_tech_graph/*.graph.yaml` 作为编辑源,运行 `npx @cyning/harness graph yaml compile|check` 生成 MD / 校验 graph.json。
|
|
352
353
|
|
|
353
|
-
**Q:
|
|
354
|
-
A:Inform-YAML 是 **架构图谱** 的编辑源;
|
|
354
|
+
**Q:PIP(过程实例投影)与 Inform-YAML 是什么关系?**
|
|
355
|
+
A:Inform-YAML 是 **架构图谱** 的编辑源;PIP(过程实例投影,v2.0+)是 **过程协作** 的事件图。二者并列,PIP(过程实例投影)通过 `InformArtifact` 节点引用 Inform 产物,但不覆盖其正文。
|
|
355
356
|
|
|
356
357
|
**Q:如何贡献或报 issue?**
|
|
357
358
|
A:GitHub [Cyning12/cyning-harness](https://github.com/Cyning12/cyning-harness) · MIT。
|
|
358
359
|
|
|
359
360
|
---
|
|
360
361
|
|
|
361
|
-
## 13.
|
|
362
|
+
## 13. PIP(过程实例投影)过程轨(v2.0+)
|
|
362
363
|
|
|
363
|
-
|
|
364
|
+
PIP(过程实例投影;Process Instance Projection)把 task、gate、review、invoke、sync 等过程实例变成 **append-only 事件流** 与 **可重放图快照**,用于机械检查 SDD 公理。
|
|
364
365
|
|
|
365
366
|
### 13.1 事件文件
|
|
366
367
|
|
|
367
|
-
|
|
368
|
+
PIP(过程实例投影) 事件写入 `.cyning-harness/events/YYYY-MM.jsonl`,修正 = 追加 `CorrectionEvent`,**禁止删改历史**。
|
|
368
369
|
|
|
369
370
|
### 13.2 CLI
|
|
370
371
|
|
|
@@ -397,7 +398,7 @@ chmod +x .git/hooks/pre-commit
|
|
|
397
398
|
|
|
398
399
|
- `InformArtifact` 节点 ID:`inform:{repo_rel_path}`
|
|
399
400
|
- Task → InformArtifact 边:`MUST_READ`
|
|
400
|
-
-
|
|
401
|
+
- PIP(过程实例投影)**不存储** Inform 正文,只存路径指针与 schema 版本
|
|
401
402
|
|
|
402
403
|
### 13.5 局限
|
|
403
404
|
|
|
@@ -432,4 +433,4 @@ chmod +x .git/hooks/pre-commit
|
|
|
432
433
|
| 2026-06-16 | v1.0.1:verify / gate-check / sync index CLI · `--with-scripts` · QUICKREF |
|
|
433
434
|
| 2026-06-17 | v1.1.0:新增 §10 Inform-YAML · `graph yaml compile|check` · 三轨边界说明 |
|
|
434
435
|
| 2026-06-17 | v2.0.1:D4-a axioms · rejected→draft 精确化 · S5 bench · optional pre-commit hook |
|
|
435
|
-
| 2026-06-17 | v2.0.0:新增 §13
|
|
436
|
+
| 2026-06-17 | v2.0.0:新增 §13 PIP(过程实例投影)过程轨 · `graph ingest|snapshot|axioms` · InformArtifact 与 MUST_READ 边 |
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# PIP(过程实例投影;Process Instance Projection)· 设计草案 v0
|
|
2
|
+
|
|
3
|
+
> 文件名历史:`HARNESS_GRAPH_MODEL_*`(路径兼容)。
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
| 项 | 内容 |
|
|
@@ -20,7 +22,7 @@
|
|
|
20
22
|
### 0.1 一句话
|
|
21
23
|
|
|
22
24
|
```text
|
|
23
|
-
|
|
25
|
+
PIP(过程实例投影)
|
|
24
26
|
= OOP 的结构化对象(本体 Class 的实例)
|
|
25
27
|
+ 显式带类型的边(ObjectProperty · HatRef · 基数)
|
|
26
28
|
+ 不可变事件历史(append-only · 可重放投影)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# graph ·
|
|
1
|
+
# graph · PIP(过程实例投影;Process Instance Projection)
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **对外名**:PIP(过程实例投影)。对内历史文件名可含 `HGM` / `hgm_*`(路径兼容,正文不用曾称括注)。
|
|
4
|
+
|
|
5
|
+
> **Track G · G1 / v2.0+**:PIP(过程实例投影)本地 JSONL + `graph ingest|snapshot|axioms` **已实现**;Neo4j / 远端同步仍提案 · 详见 [`../ROADMAP_v1_zh.md`](../ROADMAP_v1_zh.md) §2.0 · §2.2。
|
|
4
6
|
|
|
5
7
|
| 文档 | 状态 | 说明 |
|
|
6
8
|
| --- | --- | --- |
|
|
@@ -10,7 +12,7 @@
|
|
|
10
12
|
| [`HARNESS_GRAPH_MODEL_dialogue_archive_v1_zh.md`](./HARNESS_GRAPH_MODEL_dialogue_archive_v1_zh.md) | archive | 对话参考 · **非真值** |
|
|
11
13
|
|
|
12
14
|
```text
|
|
13
|
-
|
|
15
|
+
PIP(过程实例投影) = 结构化对象 + 显式带类型的边 + 不可变事件历史 + 可推理的公理
|
|
14
16
|
```
|
|
15
17
|
|
|
16
18
|
**勿混淆**:
|
|
@@ -19,6 +21,6 @@ HGM = 结构化对象 + 显式带类型的边 + 不可变事件历史 + 可推
|
|
|
19
21
|
| --- | --- |
|
|
20
22
|
| **GraphTrack** | `docs/_tech_graph/` · Inform 架构地图 |
|
|
21
23
|
| **gate-check --graph** | Inform 模块依赖图(Q3 · v1.0) |
|
|
22
|
-
| **
|
|
24
|
+
| **PIP(过程实例投影)** | Process 实例图 · Task / Gate / Review 时序 |
|
|
23
25
|
|
|
24
26
|
**依赖**:[`../product/DESIGN_ONTOLOGY_v1_zh.md`](../product/DESIGN_ONTOLOGY_v1_zh.md) v1.2
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# SPEC:多帽 invoke 留档硬闸(可配置)(v1)
|
|
2
|
+
|
|
3
|
+
> **状态**:`signed`
|
|
4
|
+
> **track**:`feature`
|
|
5
|
+
> **关联图谱**:无(纯 Harness 工具链)
|
|
6
|
+
> **下游**:30 实现 → 40 → close
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Harness 元信息
|
|
11
|
+
|
|
12
|
+
| 字段 | 值 |
|
|
13
|
+
|------|-----|
|
|
14
|
+
| **spec_slug** | `invoke-hats-retention-gate` |
|
|
15
|
+
| **test_strategy** | `required` |
|
|
16
|
+
| **test_strategy_note** | close/lint/verify 集合校验与 profile 展开须可失败测试驱动 |
|
|
17
|
+
| **skip_spec_audit** | `true` |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 1. 背景与目标
|
|
22
|
+
|
|
23
|
+
close 检查 1 仅要求 `by-task/<slug>/` ≥1 个 `.md`,业务仓理性只交 30。
|
|
24
|
+
目标:按 task 元信息 `required_invoke_hats` / `invoke_retention_profile` 做**集合覆盖**硬闸;默认 `10,30,40`;40 允许与 30 合并文件名。
|
|
25
|
+
|
|
26
|
+
## 2. 范围
|
|
27
|
+
|
|
28
|
+
- `lib/task-meta.js`:profile 预置、解析、文件名 hat 扫描、缺口计算
|
|
29
|
+
- `lib/task-close.js`:检查 1 升级;`--allow-invoke-gap`
|
|
30
|
+
- `lib/task-lint.js`:缺字段 WARN
|
|
31
|
+
- `lib/verify.js`:缺口 WARN(不挡 30)
|
|
32
|
+
- lifecycle / discipline / 模板 / CHANGELOG · **v2.12.0**
|
|
33
|
+
|
|
34
|
+
## 3. 非范围
|
|
35
|
+
|
|
36
|
+
- 强制 CLOSE invoke 文件(关账= close 命令)
|
|
37
|
+
- 默认要求 20 invoke(reviews 已硬闸)
|
|
38
|
+
- 同步 Ink 全套 TEMPLATE-*-invoke.md
|
|
39
|
+
- 历史 done invoke 补录
|
|
40
|
+
|
|
41
|
+
## 4. 验收标准
|
|
42
|
+
|
|
43
|
+
- [x] 无字段 → 默认要求 `10,30,40`;仅有 30 → CLOSE BLOCKED
|
|
44
|
+
- [x] `invoke_retention_profile: minimal` → 仅需 30
|
|
45
|
+
- [x] `required_invoke_hats` 优先于 profile
|
|
46
|
+
- [x] `invoke_*_30_40_*.md` 同时满足 30 与 40
|
|
47
|
+
- [x] `--allow-invoke-gap` 豁免并留痕
|
|
48
|
+
- [x] verify --task 对缺口 WARN、不挡 may_start_30
|
|
49
|
+
- [x] `npm test` 全绿
|
|
50
|
+
|
|
51
|
+
> **复检**(2026-07-26 · cyning-harness `task/invoke-hats-retention-gate`):`npm test` **187/187** 绿;行为与上表一致。非阻塞备注见对话复检报告。
|
|
52
|
+
|
|
53
|
+
## 5. 预置
|
|
54
|
+
|
|
55
|
+
| profile | hats |
|
|
56
|
+
|---------|------|
|
|
57
|
+
| default | 10,30,40 |
|
|
58
|
+
| minimal | 30 |
|
|
59
|
+
| full | 00,10,20,30,40,CLOSE |
|
|
@@ -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.12.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
|
|
|
@@ -61,6 +61,11 @@ gaps:
|
|
|
61
61
|
status: closed
|
|
62
62
|
closed_in: "2.10.0"
|
|
63
63
|
note: "Epic T2 · 旁路报告"
|
|
64
|
+
- id: INVOKE-HATS
|
|
65
|
+
title: 多帽 invoke 留档集合闸
|
|
66
|
+
status: closed
|
|
67
|
+
closed_in: "2.12.0"
|
|
68
|
+
note: "close evaluateInvokeHatsRetention · default 10,30,40"
|
|
64
69
|
|
|
65
70
|
statements:
|
|
66
71
|
- id: A1
|
|
@@ -105,11 +110,11 @@ statements:
|
|
|
105
110
|
mechanism_quality: null
|
|
106
111
|
- id: A6
|
|
107
112
|
source: 30-execute-code.md
|
|
108
|
-
summary: invoke 快照落盘 by-task/<slug
|
|
113
|
+
summary: invoke 快照落盘 by-task/<slug>/(按 required_invoke_hats 集合)
|
|
109
114
|
status: mechanical
|
|
110
|
-
mechanism: "M5 close + M6 lint-done"
|
|
115
|
+
mechanism: "M5 close evaluateInvokeHatsRetention + M6 lint-done"
|
|
111
116
|
gap: null
|
|
112
|
-
notes:
|
|
117
|
+
notes: "v2.12.0 默认 10,30,40;profile minimal/full;--allow-invoke-gap"
|
|
113
118
|
mechanism_quality: null
|
|
114
119
|
- id: A7
|
|
115
120
|
source: 30-execute-code.md
|
|
@@ -265,11 +270,11 @@ statements:
|
|
|
265
270
|
mechanism_quality: null
|
|
266
271
|
- id: F1
|
|
267
272
|
source: TEMPLATE_invoke.md
|
|
268
|
-
summary: 落盘路径 by-task/<task_slug>/
|
|
273
|
+
summary: 落盘路径 by-task/<task_slug>/ 且帽子集合覆盖
|
|
269
274
|
status: mechanical
|
|
270
|
-
mechanism: "M5/M6"
|
|
275
|
+
mechanism: "M5 evaluateInvokeHatsRetention / M6"
|
|
271
276
|
gap: null
|
|
272
|
-
notes:
|
|
277
|
+
notes: "v2.12.0 文件名 hat token · 合并 30_40 可双计"
|
|
273
278
|
mechanism_quality: null
|
|
274
279
|
- id: F2
|
|
275
280
|
source: TEMPLATE_invoke.md
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
| 字段 | 值 |
|
|
11
11
|
|------|-----|
|
|
12
|
-
| hat_id | 10 / 22 / 30 / 40 / 50 / 00 / CLOSE |
|
|
12
|
+
| hat_id | 10 / 22 / 30 / 40 / 50 / 00 / CLOSE(合并可写 `30(含 40)`) |
|
|
13
13
|
| task_slug | `<slug>` |
|
|
14
14
|
| freeze_id | (可选) |
|
|
15
15
|
| task_paths | `docs/tasks/active/task_….md` |
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
| 同帽追问 | **不** 新增 invoke;沿用本节路径 |
|
|
45
45
|
| 打回重开 | 新文件或 `_r2` 后缀 |
|
|
46
46
|
| 与 reviews 分工 | reviews = 结论;invoke = **当时指令** |
|
|
47
|
-
| close 机械闸 | `harness task close`
|
|
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
49
|
| commit | 落盘 + task 回填后再 commit(见 HANDOFF) |
|
|
49
50
|
|
|
50
51
|
---
|
package/harness/lifecycle.yaml
CHANGED
|
@@ -66,8 +66,14 @@ transitions:
|
|
|
66
66
|
description: "task close · active→done 归档"
|
|
67
67
|
guards:
|
|
68
68
|
- id: close_invoke
|
|
69
|
-
command_or_check: "task close 检查1 · invoke
|
|
69
|
+
command_or_check: "task close 检查1 · invoke hats 集合覆盖(required_invoke_hats / profile · v2.12+)"
|
|
70
70
|
severity: block
|
|
71
|
+
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"
|
|
71
77
|
- id: close_self_check
|
|
72
78
|
command_or_check: "task close 检查2 · 自检结论已回填"
|
|
73
79
|
severity: block
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- 补齐 **`failure_paths`**(触发 → 行为 → 可重试 → 用户可见)
|
|
16
16
|
- 写清 **非范围**、**依赖**(相对路径链接)
|
|
17
17
|
- 建议 `test_strategy` + `code_quality_bar`
|
|
18
|
+
- **选定 invoke 留档集合**(v2.12+):写 `invoke_retention_profile`(`default`=`10,30,40` / `minimal`=`30` / `full`)或显式 `required_invoke_hats`;并落盘本帽 `invoke_*_10_*.md`
|
|
18
19
|
- 承接 **20-task-audit 审查**:按 `docs/harness/reviews/*_audit_*.md` 回填 task
|
|
19
20
|
|
|
20
21
|
## 禁止什么
|
|
@@ -48,7 +49,8 @@
|
|
|
48
49
|
|------|------|
|
|
49
50
|
| 2026-07-24 | V2 拆分收编:自 `10-requirements.md` 改名为 `10-task-requirements.md`(来源:工作区 `10-task-requirements.md` · 2026-06-12 v2) |
|
|
50
51
|
| 2026-07-24 | G4:思考轮摘要节补 v2.6+ `task lint` 条件触发结构检查 |
|
|
52
|
+
| 2026-07-26 | v2.12:起草时选定 `required_invoke_hats` / `invoke_retention_profile` |
|
|
51
53
|
|
|
52
54
|
## 给 Cursor
|
|
53
55
|
|
|
54
|
-
`Harness`、`10-task`、`验收`、`failure_paths`、`test_strategy`、`task lint`、`拒开工`
|
|
56
|
+
`Harness`、`10-task`、`验收`、`failure_paths`、`test_strategy`、`task lint`、`required_invoke_hats`、`拒开工`
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
|
|
63
63
|
## 交接物
|
|
64
64
|
|
|
65
|
-
- 审查 md
|
|
65
|
+
- 审查 md 路径(**必须** · `docs/harness/reviews/`)
|
|
66
|
+
- invoke 快照:仅当 task `required_invoke_hats` / profile **含 20** 时必须落盘;默认 default 集合不含 20(与 reviews 硬闸分工)
|
|
66
67
|
- 按 HANDOFF 分仓 commit
|
|
67
68
|
|
|
68
69
|
## 修订记录
|
|
@@ -70,6 +71,7 @@
|
|
|
70
71
|
| 日期 | 摘要 |
|
|
71
72
|
|------|------|
|
|
72
73
|
| 2026-07-24 | V2 拆分收编:自 `22-task-audit.md` 改名为 `20-task-audit.md`(来源:工作区 `20-task-audit.md` · 2026-06-21 v3 + 原包内人工闸联动节保留) |
|
|
74
|
+
| 2026-07-26 | v2.12:澄清 20 invoke 仅在 required 集合含 20 时强制;默认靠 reviews |
|
|
73
75
|
|
|
74
76
|
## 给 Cursor
|
|
75
77
|
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
- 读 task **必读列表** + `AGENTS.md` + `_tech_graph/` + L2(涉码)
|
|
21
21
|
- `test_strategy: required` → **先** 可失败测试再改实现
|
|
22
22
|
- 闸扫描通过后:运行 task **验证命令**;回填 `### 自检结论(执行者)`
|
|
23
|
-
- invoke 快照落盘 `docs/harness/invokes/by-task/<task_slug
|
|
24
|
-
- 归档(active→done)**只能**走 `npx @cyning/harness task close --file <task> --yes`:`CLOSE: PASS` 后方可归档;机械校验 invoke
|
|
23
|
+
- invoke 快照落盘 `docs/harness/invokes/by-task/<task_slug>/`(须覆盖 task 要求的 hats;40 可与 30 合并为 `invoke_*_30_40_*.md`)
|
|
24
|
+
- 归档(active→done)**只能**走 `npx @cyning/harness task close --file <task> --yes`:`CLOSE: PASS` 后方可归档;机械校验 **invoke hats 集合** / 自检结论 / 验收勾选 / slug / 状态 / R1 review,任一不过 **不执行** mv(v2.2+ · hats 集合 v2.12+)
|
|
25
25
|
|
|
26
26
|
## 禁止什么
|
|
27
27
|
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
| **chain_prompt** | `docs/harness/prompts/PROMPT_cursor_task_chain_serial_v1.md`(嵌入后路径) |
|
|
20
20
|
| **semi_auto** | **`false`**(禁止作总闸) |
|
|
21
21
|
| **audit_profile** | `full` / `post_close` / `human_only` |
|
|
22
|
+
| **invoke_retention_profile** | `default` / `minimal` / `full`(10 起草时选定) |
|
|
23
|
+
| **required_invoke_hats** | `10,30,40`(显式列表优先于 profile;缺省=default) |
|
|
22
24
|
| **git_branch** | `task/<slug>` |
|
|
23
25
|
| **worktree_root** | (并行时填独立 worktree 目录) |
|
|
24
26
|
| **experience_capture** | `required` / `recommended` / `not_applicable` |
|
package/lib/cli.js
CHANGED
|
@@ -404,11 +404,11 @@ async function cmdVerify(args) {
|
|
|
404
404
|
formatAgentHint,
|
|
405
405
|
} = await import('./verify.js');
|
|
406
406
|
if (args.includes('--help') || args.includes('-h')) {
|
|
407
|
-
console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE | --spec FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH] [--allow-no-review] [--allow-lint-fail] [--allow-no-spec-review]
|
|
407
|
+
console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE | --spec FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH] [--allow-no-review] [--allow-lint-fail] [--allow-no-spec-review] [--allow-invoke-gap]
|
|
408
408
|
|
|
409
409
|
无 --task/--spec:扫描 docs/tasks/active ∪ docs/harness/tasks/active(同 basename 优先 Starter);
|
|
410
410
|
闸表 + 全量 reviews 存在性(v2.9+)· 不跑 task lint / D5 · --allow-no-review。
|
|
411
|
-
--task:30 前聚合(gate + audit + reviews + lint WARN)· --allow-no-review / --allow-lint-fail。
|
|
411
|
+
--task:30 前聚合(gate + audit + reviews + lint WARN + invoke hats WARN)· --allow-no-review / --allow-lint-fail / --allow-invoke-gap。
|
|
412
412
|
--spec:SPEC→00 前查审查文存在性(v2.8+ · 与 --task 互斥)· --allow-no-spec-review。
|
|
413
413
|
推荐:--spec <产品仓 SPEC> --workspace-root <含 docs/harness/reviews 的工作区根>
|
|
414
414
|
--json / --agent-hint:Agent handoff。
|
|
@@ -431,6 +431,7 @@ async function cmdVerify(args) {
|
|
|
431
431
|
const allowNoReview = rest.includes('--allow-no-review');
|
|
432
432
|
const allowLintFail = rest.includes('--allow-lint-fail');
|
|
433
433
|
const allowNoSpecReview = rest.includes('--allow-no-spec-review');
|
|
434
|
+
const allowInvokeGap = rest.includes('--allow-invoke-gap');
|
|
434
435
|
rest = rest.filter(
|
|
435
436
|
(a) =>
|
|
436
437
|
a !== '--graph' &&
|
|
@@ -438,7 +439,8 @@ async function cmdVerify(args) {
|
|
|
438
439
|
a !== '--agent-hint' &&
|
|
439
440
|
a !== '--allow-no-review' &&
|
|
440
441
|
a !== '--allow-lint-fail' &&
|
|
441
|
-
a !== '--allow-no-spec-review'
|
|
442
|
+
a !== '--allow-no-spec-review' &&
|
|
443
|
+
a !== '--allow-invoke-gap',
|
|
442
444
|
);
|
|
443
445
|
|
|
444
446
|
if (rest.length > 0) {
|
|
@@ -469,7 +471,13 @@ async function cmdVerify(args) {
|
|
|
469
471
|
});
|
|
470
472
|
label = path.basename(specFile);
|
|
471
473
|
} else {
|
|
472
|
-
result = verifyTarget(target, {
|
|
474
|
+
result = verifyTarget(target, {
|
|
475
|
+
taskFile,
|
|
476
|
+
graph,
|
|
477
|
+
allowNoReview,
|
|
478
|
+
allowLintFail,
|
|
479
|
+
allowInvokeGap,
|
|
480
|
+
});
|
|
473
481
|
handoff = buildVerifyHandoff(target, {
|
|
474
482
|
taskFile,
|
|
475
483
|
workspaceRoot: ws,
|
|
@@ -709,13 +717,16 @@ async function cmdTask(args) {
|
|
|
709
717
|
|
|
710
718
|
async function cmdTaskClose(args) {
|
|
711
719
|
if (args.includes('--help') || args.includes('-h')) {
|
|
712
|
-
console.log(`用法: npx @cyning/harness task close --file PATH [--target PATH] [--yes] [--allow-unchecked]
|
|
720
|
+
console.log(`用法: npx @cyning/harness task close --file PATH [--target PATH] [--yes] [--allow-unchecked] [--allow-no-review] [--allow-invoke-gap]
|
|
713
721
|
|
|
714
|
-
受闸归档(方案 A · invoke
|
|
715
|
-
|
|
722
|
+
受闸归档(方案 A · invoke 留档机械闸 · v2.12+ 多帽集合):
|
|
723
|
+
机械校验:invoke hats 覆盖(required_invoke_hats / profile)· 自检结论 · 验收勾选 · slug · 状态 · R1 review
|
|
716
724
|
默认 dry-run(只检不 mv);--yes 才执行 mv 到 active/ 同级 done/(basename 保留)
|
|
717
|
-
--target PATH
|
|
718
|
-
--allow-unchecked
|
|
725
|
+
--target PATH 显式覆盖归档目标完整路径
|
|
726
|
+
--allow-unchecked 验收标准未勾选项 warn 放行
|
|
727
|
+
--allow-no-review 缺 R<n> 审查文 warn 放行
|
|
728
|
+
--allow-invoke-gap 缺 required invoke hats warn 放行(v2.12+)
|
|
729
|
+
缺省 required hats:10,30,40(可用 invoke_retention_profile=minimal|full 或显式列表覆盖)
|
|
719
730
|
任一校验失败 → exit 2 + CLOSE: BLOCKED · 不执行 mv。末行协议:CLOSE: PASS · <slug>。
|
|
720
731
|
`);
|
|
721
732
|
return;
|
|
@@ -724,7 +735,14 @@ async function cmdTaskClose(args) {
|
|
|
724
735
|
const yes = args.includes('--yes');
|
|
725
736
|
const allowUnchecked = args.includes('--allow-unchecked');
|
|
726
737
|
const allowNoReview = args.includes('--allow-no-review');
|
|
727
|
-
|
|
738
|
+
const allowInvokeGap = args.includes('--allow-invoke-gap');
|
|
739
|
+
let rest = args.filter(
|
|
740
|
+
(a) =>
|
|
741
|
+
a !== '--yes' &&
|
|
742
|
+
a !== '--allow-unchecked' &&
|
|
743
|
+
a !== '--allow-no-review' &&
|
|
744
|
+
a !== '--allow-invoke-gap',
|
|
745
|
+
);
|
|
728
746
|
const { value: fileArg, rest: r1 } = takeOption(rest, '--file');
|
|
729
747
|
rest = r1;
|
|
730
748
|
const { value: targetArg, rest: r2 } = takeOption(rest, '--target');
|
|
@@ -746,6 +764,7 @@ async function cmdTaskClose(args) {
|
|
|
746
764
|
target: targetArg,
|
|
747
765
|
allowUnchecked,
|
|
748
766
|
allowNoReview,
|
|
767
|
+
allowInvokeGap,
|
|
749
768
|
cwd: process.cwd(),
|
|
750
769
|
});
|
|
751
770
|
|
package/lib/task-close.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
PLACEHOLDER_RE,
|
|
6
6
|
STATUS_RE,
|
|
7
7
|
UNCHECKED_RE,
|
|
8
|
+
evaluateInvokeHatsRetention,
|
|
8
9
|
extractSection,
|
|
9
10
|
extractTaskSlug,
|
|
10
11
|
findReview,
|
|
@@ -23,6 +24,7 @@ export function closeTaskFile(filePath, options = {}) {
|
|
|
23
24
|
target: targetArg,
|
|
24
25
|
allowUnchecked = false,
|
|
25
26
|
allowNoReview = false,
|
|
27
|
+
allowInvokeGap = false,
|
|
26
28
|
cwd = process.cwd(),
|
|
27
29
|
} = options;
|
|
28
30
|
|
|
@@ -60,7 +62,8 @@ export function closeTaskFile(filePath, options = {}) {
|
|
|
60
62
|
const inActive = path.basename(activeDir) === 'active';
|
|
61
63
|
const repoRoot = findRepoRoot(activeDir);
|
|
62
64
|
|
|
63
|
-
// 检查 1 · invoke
|
|
65
|
+
// 检查 1 · invoke 留档(v2.12+:按 required_invoke_hats / profile 集合覆盖)
|
|
66
|
+
let invokeHatsEval = null;
|
|
64
67
|
if (!repoRoot) {
|
|
65
68
|
blockers.push(`无法定位 repo root(${activeDir} 无 docs 祖先)`);
|
|
66
69
|
} else if (slug) {
|
|
@@ -69,12 +72,29 @@ export function closeTaskFile(filePath, options = {}) {
|
|
|
69
72
|
'docs/harness/invokes/by-task',
|
|
70
73
|
slug,
|
|
71
74
|
);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`
|
|
75
|
+
invokeHatsEval = evaluateInvokeHatsRetention(meta, invokeDir);
|
|
76
|
+
const relDir = path.relative(repoRoot, invokeDir).replace(/\\/g, '/');
|
|
77
|
+
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
|
+
}
|
|
84
|
+
} 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`,
|
|
78
98
|
);
|
|
79
99
|
}
|
|
80
100
|
}
|
|
@@ -167,6 +187,14 @@ export function closeTaskFile(filePath, options = {}) {
|
|
|
167
187
|
dest,
|
|
168
188
|
dryRun: !yes,
|
|
169
189
|
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,
|
|
170
198
|
};
|
|
171
199
|
}
|
|
172
200
|
|
package/lib/task-lint.js
CHANGED
|
@@ -193,6 +193,33 @@ export function lintTaskFile(filePath, options = {}) {
|
|
|
193
193
|
warnings.push(warn('W2', '缺 ### 人工闸 节(轻量 task 可忽略本提醒)'));
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// W6 · invoke 留档集合字段(v2.12+ · 缺则 close 按 default=10,30,40)
|
|
197
|
+
if (
|
|
198
|
+
meta.task_slug &&
|
|
199
|
+
!meta.required_invoke_hats &&
|
|
200
|
+
!meta.invoke_retention_profile
|
|
201
|
+
) {
|
|
202
|
+
warnings.push(
|
|
203
|
+
warn(
|
|
204
|
+
'W6',
|
|
205
|
+
'未设 required_invoke_hats / invoke_retention_profile · close 将按 default=10,30,40(紧急 fix 可写 minimal)',
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
} else if (
|
|
209
|
+
meta.invoke_retention_profile &&
|
|
210
|
+
!['default', 'minimal', 'full'].includes(
|
|
211
|
+
String(meta.invoke_retention_profile).trim().toLowerCase(),
|
|
212
|
+
) &&
|
|
213
|
+
!meta.required_invoke_hats
|
|
214
|
+
) {
|
|
215
|
+
warnings.push(
|
|
216
|
+
warn(
|
|
217
|
+
'W6',
|
|
218
|
+
`未知 invoke_retention_profile '${meta.invoke_retention_profile}' · 将回退 default`,
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
196
223
|
// G4 · 思考轮结构(条件触发)
|
|
197
224
|
lintThinkingRounds(content, lines, errors, warnings);
|
|
198
225
|
|
package/lib/task-meta.js
CHANGED
|
@@ -396,6 +396,158 @@ export function normalizeSlug(slug) {
|
|
|
396
396
|
return String(slug).replace(/_/g, '-');
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
/**
|
|
400
|
+
* invoke 留档 profile → 帽集合(v2.12+)。
|
|
401
|
+
* default=10,30,40 · minimal=30 · full=00,10,20,30,40,CLOSE
|
|
402
|
+
*/
|
|
403
|
+
export const INVOKE_RETENTION_PROFILES = Object.freeze({
|
|
404
|
+
default: Object.freeze(['10', '30', '40']),
|
|
405
|
+
minimal: Object.freeze(['30']),
|
|
406
|
+
full: Object.freeze(['00', '10', '20', '30', '40', 'CLOSE']),
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
/** 已知 hat token(文件名扫描白名单) */
|
|
410
|
+
export const KNOWN_INVOKE_HAT_TOKENS = Object.freeze(
|
|
411
|
+
new Set([
|
|
412
|
+
'00',
|
|
413
|
+
'10',
|
|
414
|
+
'20',
|
|
415
|
+
'22',
|
|
416
|
+
'30',
|
|
417
|
+
'40',
|
|
418
|
+
'50',
|
|
419
|
+
'CLOSE',
|
|
420
|
+
'close',
|
|
421
|
+
]),
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* 解析 required_invoke_hats / invoke_retention_profile。
|
|
426
|
+
* 显式列表优先;皆无 → default。
|
|
427
|
+
* @returns {{ hats: string[], profile: string|null, source: 'explicit'|'profile'|'default', unknown_profile?: string }}
|
|
428
|
+
*/
|
|
429
|
+
export function resolveRequiredInvokeHats(meta = {}) {
|
|
430
|
+
const explicit = meta.required_invoke_hats;
|
|
431
|
+
if (explicit != null && String(explicit).trim() !== '') {
|
|
432
|
+
const hats = parseHatList(explicit);
|
|
433
|
+
return { hats, profile: meta.invoke_retention_profile ?? null, source: 'explicit' };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const profileRaw = meta.invoke_retention_profile;
|
|
437
|
+
if (profileRaw != null && String(profileRaw).trim() !== '') {
|
|
438
|
+
const key = String(profileRaw).trim().toLowerCase();
|
|
439
|
+
const preset = INVOKE_RETENTION_PROFILES[key];
|
|
440
|
+
if (preset) {
|
|
441
|
+
return { hats: [...preset], profile: key, source: 'profile' };
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
hats: [...INVOKE_RETENTION_PROFILES.default],
|
|
445
|
+
profile: key,
|
|
446
|
+
source: 'default',
|
|
447
|
+
unknown_profile: key,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
hats: [...INVOKE_RETENTION_PROFILES.default],
|
|
453
|
+
profile: 'default',
|
|
454
|
+
source: 'default',
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* 从 invoke 文件名提取 hat token 集合。
|
|
460
|
+
* 兼容:invoke_YYYYMMDD_30_slug.md · invoke_YYYYMMDD_30_40_slug.md · invoke_*_CLOSE_*.md
|
|
461
|
+
* 亦认 22→20(V2 改名等价,计为 20)。
|
|
462
|
+
*/
|
|
463
|
+
export function extractHatsFromInvokeFilename(fileName) {
|
|
464
|
+
const base = path.basename(fileName, '.md');
|
|
465
|
+
const hats = new Set();
|
|
466
|
+
|
|
467
|
+
if (/CLOSE/i.test(base)) {
|
|
468
|
+
hats.add('CLOSE');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// 去 invoke_ 前缀与可选日期段
|
|
472
|
+
let rest = base.replace(/^invoke[_-]?/i, '');
|
|
473
|
+
rest = rest.replace(/^\d{8}[_-]?/, '');
|
|
474
|
+
rest = rest.replace(/^\d{4}-\d{2}-\d{2}[_-]?/, '');
|
|
475
|
+
|
|
476
|
+
const parts = rest.split(/[_-]+/).filter(Boolean);
|
|
477
|
+
for (const p of parts) {
|
|
478
|
+
const upper = p.toUpperCase();
|
|
479
|
+
if (upper === 'CLOSE') {
|
|
480
|
+
hats.add('CLOSE');
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (KNOWN_INVOKE_HAT_TOKENS.has(p)) {
|
|
484
|
+
hats.add(normalizeHatToken(p));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// V2:22 与 20 等价(审 task)
|
|
489
|
+
if (hats.has('22')) {
|
|
490
|
+
hats.add('20');
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return hats;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* 扫描 by-task/<slug>/ 目录,聚合已覆盖 hat。
|
|
498
|
+
* @returns {{ covered: Set<string>, files: string[], missingDir: boolean }}
|
|
499
|
+
*/
|
|
500
|
+
export function scanInvokeHats(invokeDir) {
|
|
501
|
+
if (!invokeDir || !fs.existsSync(invokeDir)) {
|
|
502
|
+
return { covered: new Set(), files: [], missingDir: true };
|
|
503
|
+
}
|
|
504
|
+
const files = fs
|
|
505
|
+
.readdirSync(invokeDir)
|
|
506
|
+
.filter((f) => f.endsWith('.md'));
|
|
507
|
+
const covered = new Set();
|
|
508
|
+
for (const f of files) {
|
|
509
|
+
for (const h of extractHatsFromInvokeFilename(f)) {
|
|
510
|
+
covered.add(h);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return { covered, files, missingDir: false };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 计算 required vs covered 缺口。
|
|
518
|
+
* @returns {{ required: string[], covered: string[], missing: string[], ok: boolean, resolution }}
|
|
519
|
+
*/
|
|
520
|
+
export function evaluateInvokeHatsRetention(meta, invokeDir) {
|
|
521
|
+
const resolution = resolveRequiredInvokeHats(meta);
|
|
522
|
+
const { covered, files, missingDir } = scanInvokeHats(invokeDir);
|
|
523
|
+
const required = resolution.hats.map(normalizeHatToken);
|
|
524
|
+
const missing = required.filter((h) => !covered.has(h));
|
|
525
|
+
return {
|
|
526
|
+
required,
|
|
527
|
+
covered: [...covered].sort(),
|
|
528
|
+
missing,
|
|
529
|
+
ok: missing.length === 0 && !missingDir && files.length > 0,
|
|
530
|
+
missingDir,
|
|
531
|
+
files,
|
|
532
|
+
resolution,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function parseHatList(raw) {
|
|
537
|
+
return String(raw)
|
|
538
|
+
.split(/[,,\s]+/)
|
|
539
|
+
.map((s) => s.trim())
|
|
540
|
+
.filter(Boolean)
|
|
541
|
+
.map(normalizeHatToken);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function normalizeHatToken(token) {
|
|
545
|
+
const t = String(token).trim();
|
|
546
|
+
if (/^close$/i.test(t)) return 'CLOSE';
|
|
547
|
+
if (t === '22') return '22';
|
|
548
|
+
return t;
|
|
549
|
+
}
|
|
550
|
+
|
|
399
551
|
function normalizeCell(cell) {
|
|
400
552
|
return cell.replace(/[`\\*]/g, '').trim();
|
|
401
553
|
}
|
package/lib/verify.js
CHANGED
|
@@ -5,10 +5,12 @@ import { resolveHarnessRootForTarget, wizardScript } from './paths.js';
|
|
|
5
5
|
import { auditTarget } from './audit.js';
|
|
6
6
|
import {
|
|
7
7
|
buildTaskHandoff,
|
|
8
|
+
evaluateInvokeHatsRetention,
|
|
8
9
|
extractSpecSlug,
|
|
9
10
|
findReview,
|
|
10
11
|
findSpecReview,
|
|
11
12
|
listActiveTasks,
|
|
13
|
+
parseHarnessMeta,
|
|
12
14
|
shouldSkipSpecAudit,
|
|
13
15
|
} from './task-meta.js';
|
|
14
16
|
import { lintTaskFile } from './task-lint.js';
|
|
@@ -16,10 +18,17 @@ import { lintTaskFile } from './task-lint.js';
|
|
|
16
18
|
/**
|
|
17
19
|
* 30 前聚合验证:gate-check + audit D5(仅 --task)+ reviews 留档闸
|
|
18
20
|
*(--task 与全量 · N4/v2.9+)+ task lint WARN(仅 --task · v2.7+)
|
|
21
|
+
* + invoke hats 缺口 WARN(仅 --task · v2.12+ · 不挡 30)
|
|
19
22
|
* + S5 git-clean warn + 可选 --graph
|
|
20
23
|
*/
|
|
21
24
|
export function verifyTarget(target, options = {}) {
|
|
22
|
-
const {
|
|
25
|
+
const {
|
|
26
|
+
taskFile,
|
|
27
|
+
graph,
|
|
28
|
+
allowNoReview = false,
|
|
29
|
+
allowLintFail = false,
|
|
30
|
+
allowInvokeGap = false,
|
|
31
|
+
} = options;
|
|
23
32
|
const harnessRoot = resolveHarnessRootForTarget(target);
|
|
24
33
|
|
|
25
34
|
// 1. gate-check(含 --graph)
|
|
@@ -70,6 +79,35 @@ export function verifyTarget(target, options = {}) {
|
|
|
70
79
|
gateResult.stdout += `WARN: task lint FAIL · ${rules || 'errors'}(不挡 may_start_30 · 修结构或 --allow-lint-fail)\n`;
|
|
71
80
|
}
|
|
72
81
|
}
|
|
82
|
+
|
|
83
|
+
// 3d. invoke hats 缺口(v2.12+):WARN 不挡 30 · close 才 block
|
|
84
|
+
const absTask = path.isAbsolute(taskFile)
|
|
85
|
+
? taskFile
|
|
86
|
+
: path.join(target, taskFile);
|
|
87
|
+
if (fs.existsSync(absTask)) {
|
|
88
|
+
const content = fs.readFileSync(absTask, 'utf8');
|
|
89
|
+
const meta = parseHarnessMeta(content);
|
|
90
|
+
const slug = meta.task_slug;
|
|
91
|
+
if (slug) {
|
|
92
|
+
const invokeDir = path.join(
|
|
93
|
+
target,
|
|
94
|
+
'docs/harness/invokes/by-task',
|
|
95
|
+
slug,
|
|
96
|
+
);
|
|
97
|
+
const hatsEval = evaluateInvokeHatsRetention(meta, invokeDir);
|
|
98
|
+
if (hatsEval.missing.length > 0 || hatsEval.missingDir || hatsEval.files.length === 0) {
|
|
99
|
+
const detail =
|
|
100
|
+
hatsEval.missingDir || hatsEval.files.length === 0
|
|
101
|
+
? `目录缺失/空 · 要求 ${hatsEval.required.join(',')}`
|
|
102
|
+
: `缺 ${hatsEval.missing.join(',')}(要求 ${hatsEval.required.join(',')} · 已有 ${hatsEval.covered.join(',') || '∅'})`;
|
|
103
|
+
if (allowInvokeGap) {
|
|
104
|
+
gateResult.stdout += `WARN: invoke hats gap suppressed(--allow-invoke-gap · 留痕)· ${detail}\n`;
|
|
105
|
+
} else {
|
|
106
|
+
gateResult.stdout += `WARN: invoke hats gap · ${detail}(不挡 may_start_30 · close 前须补齐或 --allow-invoke-gap)\n`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
73
111
|
} else {
|
|
74
112
|
// 3c. 全量 reviews 闸(N4 · v2.9+):不跑 lint / D5
|
|
75
113
|
const taskFiles = listActiveTasks(target);
|
package/package.json
CHANGED
package/wizard/lib/common.sh
CHANGED
|
@@ -198,6 +198,23 @@ profile_ide_cursor_rel() {
|
|
|
198
198
|
echo "$rel"
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
# 写入 .cyning-harness/local.json(compare-before-write · 内容不变则跳过)
|
|
202
|
+
# 供 upgrade post-apply 簿记;避免在 sync apply 前改已跟踪文件触发 S5
|
|
203
|
+
write_local_json_if_changed() {
|
|
204
|
+
local target="$1" root="$2"
|
|
205
|
+
local dir="$target/.cyning-harness"
|
|
206
|
+
local path="$dir/local.json"
|
|
207
|
+
local payload
|
|
208
|
+
payload="$(printf '%s\n' "{\"cyning_harness_root\":\"$root\"}")"
|
|
209
|
+
mkdir -p "$dir"
|
|
210
|
+
if [[ -f "$path" ]] && [[ "$(cat "$path")" == "$payload" ]]; then
|
|
211
|
+
echo "local.json 未变(跳过写入)→ cyning_harness_root=$root"
|
|
212
|
+
return 0
|
|
213
|
+
fi
|
|
214
|
+
printf '%s\n' "$payload" > "$path"
|
|
215
|
+
echo "已更新 local.json → cyning_harness_root=$root"
|
|
216
|
+
}
|
|
217
|
+
|
|
201
218
|
# 从 CLAUDE.md / AGENTS.md 剥离 Harness marker 块;若仅剩空文件则删除
|
|
202
219
|
# S5 · sync/apply 前检测 Git 工作区干净(v0.3+)
|
|
203
220
|
# 非 git 仓跳过;--force 或 HARNESS_SYNC_FORCE=1 跳过
|
package/wizard/lib/manifest.sh
CHANGED
|
@@ -77,6 +77,38 @@ EOF
|
|
|
77
77
|
echo "已写入 manifest: $mf"
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
# upgrade 重写前:对 schema 外字段 / ide 裁剪打 warning(知情而非静默)
|
|
81
|
+
# 标准五字段:version / preset / ide / from_version / upgraded_at(additionalProperties: false)
|
|
82
|
+
warn_manifest_upgrade_drops() {
|
|
83
|
+
local mf="$1" new_ide_json="$2"
|
|
84
|
+
[[ -f "$mf" ]] || return 0
|
|
85
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
86
|
+
echo "WARN: 未找到 node,跳过 manifest 字段变更预检" >&2
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
node -e '
|
|
90
|
+
const fs = require("fs");
|
|
91
|
+
const mf = process.argv[1];
|
|
92
|
+
const newIde = JSON.parse(process.argv[2]);
|
|
93
|
+
const schema = new Set(["version", "preset", "ide", "from_version", "upgraded_at"]);
|
|
94
|
+
let old;
|
|
95
|
+
try { old = JSON.parse(fs.readFileSync(mf, "utf8")); } catch { process.exit(0); }
|
|
96
|
+
const extra = Object.keys(old).filter((k) => !schema.has(k));
|
|
97
|
+
if (extra.length) {
|
|
98
|
+
console.log(
|
|
99
|
+
"WARN: manifest 以下非标准字段将被移除(2.3+ 仅五字段 · 见 ONBOARDING): " +
|
|
100
|
+
extra.join(", "),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const oldIde = old.ide;
|
|
104
|
+
const oldS = JSON.stringify(oldIde ?? null);
|
|
105
|
+
const newS = JSON.stringify(newIde);
|
|
106
|
+
if (oldS !== newS) {
|
|
107
|
+
console.log("WARN: manifest.ide 将从 profile 重算: " + oldS + " → " + newS);
|
|
108
|
+
}
|
|
109
|
+
' "$mf" "$new_ide_json"
|
|
110
|
+
}
|
|
111
|
+
|
|
80
112
|
# upgrade apply 后更新 manifest
|
|
81
113
|
write_manifest_upgrade() {
|
|
82
114
|
local target="$1" version="$2"
|
|
@@ -96,6 +128,8 @@ write_manifest_upgrade() {
|
|
|
96
128
|
preset="$(grep '"preset"' "$mf" | head -1 | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/' || echo "$preset")"
|
|
97
129
|
fi
|
|
98
130
|
|
|
131
|
+
warn_manifest_upgrade_drops "$mf" "$ide_json"
|
|
132
|
+
|
|
99
133
|
mkdir -p "$target/.cyning-harness"
|
|
100
134
|
cat > "$mf" <<EOF
|
|
101
135
|
{
|
package/wizard/upgrade.sh
CHANGED
|
@@ -101,10 +101,8 @@ echo "preset: $(profile_preset_name "$PROFILE_FILE")"
|
|
|
101
101
|
echo "当前 IDE: $(profile_ide_summary "$PROFILE_FILE")"
|
|
102
102
|
echo ""
|
|
103
103
|
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
echo "已更新 local.json → cyning_harness_root=$CYNING_HARNESS"
|
|
107
|
-
echo ""
|
|
104
|
+
# 注意:local.json / manifest 簿记放在 sync apply **成功之后**(F1)。
|
|
105
|
+
# 勿在 apply 前改已跟踪的 .cyning-harness/local.json,否则 S5 git-clean 自绊。
|
|
108
106
|
|
|
109
107
|
if [[ -z "$IDE_LIST" ]] && [[ "$ASSUME_YES" -eq 0 ]]; then
|
|
110
108
|
if prompt_yes_no "是否更新 IDE 勾选(cursor / claude / agents)?" "n"; then
|
|
@@ -140,14 +138,16 @@ if [[ "$DO_APPLY" -eq 1 ]]; then
|
|
|
140
138
|
SYNC_ARGS=(apply --target "$TARGET")
|
|
141
139
|
[[ "$SYNC_FORCE" -eq 1 ]] && SYNC_ARGS+=(--force)
|
|
142
140
|
"$SCRIPT_DIR/harness-sync.sh" "${SYNC_ARGS[@]}"
|
|
141
|
+
# post-sync 簿记:apply 失败则不落(set -e 已中止)
|
|
142
|
+
write_local_json_if_changed "$TARGET" "$CYNING_HARNESS"
|
|
143
143
|
write_manifest_upgrade "$TARGET" "$VERSION"
|
|
144
144
|
cp "$CYNING_HARNESS/harness/templates/QUICKREF_v1_zh.md" "$TARGET/.cyning-harness/QUICKREF.md"
|
|
145
|
-
echo ""
|
|
146
|
-
echo "升级完成。"
|
|
147
|
-
echo "常用命令:"
|
|
148
|
-
echo " npx @cyning/harness verify --target $TARGET"
|
|
149
|
-
echo " npx @cyning/harness gate-check --target $TARGET"
|
|
150
|
-
echo " npx @cyning/harness upgrade --target $TARGET --yes"
|
|
145
|
+
echo ""
|
|
146
|
+
echo "升级完成。"
|
|
147
|
+
echo "常用命令:"
|
|
148
|
+
echo " npx @cyning/harness verify --target $TARGET"
|
|
149
|
+
echo " npx @cyning/harness gate-check --target $TARGET"
|
|
150
|
+
echo " npx @cyning/harness upgrade --target $TARGET --yes"
|
|
151
151
|
else
|
|
152
152
|
echo "已取消 apply(仅预览)。"
|
|
153
153
|
exit 0
|