@cyning/harness 2.5.0 → 2.7.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,39 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.7.0] - 2026-07-24
8
+
9
+ ### Added
10
+
11
+ - **`harness/lifecycle.yaml`(方向二 · 文档先行)**:登记 task 状态 / 转移 / 守卫(`to_30` · `close`);`task_lint` severity=`warn`。Schema:`schema/lifecycle.v1.schema.json`。
12
+ - **`harness lifecycle show [--json]`**:只读渲染包内 yaml(**不做**转移引擎、不写盘)。
13
+ - **`verify --task` ↔ task lint(N2)**:E 级仅 `WARN: task lint FAIL`(不改 exit / 不改 `may_start_30`);`--allow-lint-fail` 抑制 WARN;handoff JSON 增加 `lint: { ok, errors, warnings, suppressed? }`;无 `--task` 全量模式不跑 lint。
14
+
15
+ ### Notes
16
+
17
+ - 背景:Post-G4 方案 N1+N2 · rethink 方向二骨架;dogfood active lint FAIL 率高故本波不做 block。
18
+ - SPEC:`docs/spec/SPEC-lifecycle-and-verify-lint_v1.md`
19
+ - minor · `npm test` 含 lifecycle + verify-lint-warn
20
+
21
+ ## [2.6.0] - 2026-07-24
22
+
23
+ ### Added
24
+
25
+ - **task lint · 思考轮结构规则组(G4)**:条件触发(行首 `### R0` 或 `##/###` 标题含「思考轮」)后检查:
26
+ - **E8** R0–R5 槽位标题齐全(`^### R\d` 前缀宽容)
27
+ - **E9** 控制表字段 `actual_last_round` / `early_stop` / `residual_risks`
28
+ - **E10** `early_stop=yes` 时 `early_stop_reason` 非空
29
+ - **W4** 无思考轮节(SPEC 承载 / bugfix 合法豁免提示)
30
+ - **W5** 存在 `### R6+` 但缺 `round_extension_note`
31
+ - 槽内容 `(待填)` / `(跳过 · 见思考轮控制)` 合法;**不**查思考内容质量(20 帽职责)。
32
+ - SPEC:`docs/spec/SPEC-task-lint-thinking-rounds_v1.md`
33
+
34
+ ### Notes
35
+
36
+ - 背景:机械化率审计 G4(rethink 矩阵 P1 · 第三波);与 G1 同命令、分开交付。
37
+ - minor · 无 verify 行为变更 · `npm test` 含既有回归全绿
38
+ - **已发布**:`@cyning/harness@2.6.0`(npm `latest` · 2026-07-24)· tag `v2.6.0` 已推送
39
+
7
40
  ## [2.5.0] - 2026-07-24
8
41
 
9
42
  ### Changed
@@ -18,6 +51,7 @@
18
51
  - 背景:机械化率审计 G2(rethink 矩阵 P0 最后一块);22/20 帽自此脱离「全帽零机械」。
19
52
  - dogfood:工作区 approved 任务全过(含版本后缀变体 ops_clarify · 恰好是 R1 审查抓出的案例)。
20
53
  - minor · verify 行为变更(本版核心 · 见上)· `npm test` 118 全绿 · SPEC `docs/spec/SPEC-reviews-retention-gate_v1.md`
54
+ - **已发布**:`@cyning/harness@2.5.0`(npm `latest` · 2026-07-24)· tag `v2.5.0` 已推送
21
55
 
22
56
  ## [2.4.0] - 2026-07-24
23
57
 
@@ -45,10 +45,16 @@ cd your-project
45
45
  `npx @cyning/harness verify` 在 30 执行前聚合扫描人工闸与测试声明,确保 ICVO 公理可机械检查:
46
46
 
47
47
  ```bash
48
- # 30 前聚合验证(gate-check + audit D5 + S5 warn + 可选 --graph)
48
+ # 30 前聚合验证(gate-check + audit D5 + reviews + S5 warn + 可选 --graph)
49
49
  npx @cyning/harness verify --target /path/to/your-repo
50
50
  npx @cyning/harness verify --target /path/to/your-repo \
51
51
  --task docs/tasks/active/task_xxx.md
52
+ # --task 另跑 task lint(v2.7+ · E 级仅 WARN,不挡 may_start_30)
53
+ # 抑制 lint WARN:加 --allow-lint-fail
54
+
55
+ # 只读生命周期登记(方向二 · 非引擎)
56
+ npx @cyning/harness lifecycle show
57
+ npx @cyning/harness lifecycle show --json
52
58
 
53
59
  # 仅人工闸
54
60
  npx @cyning/harness gate-check --target /path/to/your-repo
@@ -67,6 +73,9 @@ npx @cyning/harness sync index --target /path/to/your-repo
67
73
  | **D3** | 30 前置人闸 | 复用 `gate-check.sh`,HG-AUDIT-R1 非 approved 时 verify 非 0 |
68
74
  | **D5** | 改码任务测试声明 | `test_strategy=required` 但无测试/CI 引用时 verify 非 0 |
69
75
  | **S5** | Git 工作区干净 | dirty 时 warn(不直接 fail verify,但 apply 须 `--force`) |
76
+ | **lint** | task 结构(仅 `--task`) | v2.7+ E 级 → `WARN: task lint`(不改 exit / `may_start_30`) |
77
+
78
+ 生命周期真值:[`harness/lifecycle.yaml`](../harness/lifecycle.yaml)(`lifecycle show` 只读)。
70
79
 
71
80
  Audit **不替代** 维护者最终判断;Agent 首输出仍须人工复核。
72
81
 
@@ -111,8 +111,9 @@ npx @cyning/harness@1.0.1 init --preset harness-only --ide cursor,agents --yes
111
111
  **原则**:Agent 可以写 task 和 review,**人工闸只有维护者能签**。
112
112
 
113
113
  ```bash
114
- # 30 前聚合验证(gate-check + audit D5 + S5 warn + 可选 --graph)
114
+ # 30 前聚合验证(gate-check + audit D5 + reviews + task lint WARN + S5 · 可选 --graph)
115
115
  npx @cyning/harness verify --target . --task docs/tasks/active/task_xxx.md
116
+ # lint FAIL 仅 WARN(v2.7+ · 不挡 30);抑制:--allow-lint-fail
116
117
 
117
118
  # Agent handoff(v2.0.2+):JSON 路由 + 下一帽提示
118
119
  npx @cyning/harness verify --target . \
@@ -120,6 +121,9 @@ npx @cyning/harness verify --target . \
120
121
  [--json] [--agent-hint] \
121
122
  [--workspace-root /path/to/Projects]
122
123
 
124
+ # 只读生命周期登记(方向二 · harness/lifecycle.yaml · 非引擎)
125
+ npx @cyning/harness lifecycle show [--json]
126
+
123
127
  # 仅人工闸
124
128
  npx @cyning/harness gate-check --target . --task docs/tasks/active/task_xxx.md
125
129
  npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
@@ -136,6 +140,7 @@ npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
136
140
  | `HG-TASK-DRAFT` | task 初稿维护者签 | pending 且 blocks 含 30 → 拒 30 |
137
141
  | `HG-GRAPH-MODULES` | 架构模块表人签 | pending → 拒改码 30 |
138
142
  | `HG-RELEASE` | 发版闸(产品仓) | 一般业务仓不涉及 |
143
+ | `task_lint`(v2.7+) | `verify --task` 结构检查 | **仅 WARN** · 不改 `may_start_30` |
139
144
 
140
145
  ### 5.1 Agent handoff(v2.0.2+)
141
146
 
@@ -150,6 +155,7 @@ npx @cyning/harness gate-check --graph --target . # Inform 图谱闸
150
155
  | `entry_invoke_30_resolved` | 绝对路径(`Projects/` 前缀须 `--workspace-root`) |
151
156
  | `next_hat` | `"30"` 或 `null` |
152
157
  | `agent_preamble` | 短句提醒首输出 GATE_VERIFY |
158
+ | `lint`(v2.7+ · 仅 `--task`) | `{ ok, errors, warnings, suppressed? }` · 不参与 `may_start_30` |
153
159
 
154
160
  Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.schema.json)
155
161
 
@@ -166,7 +172,8 @@ Schema:[`schema/verify_result.v1.schema.json`](../schema/verify_result.v1.sche
166
172
  | `npx @cyning/harness init` | 首次安装模板与 manifest(可选 `--with-scripts`) |
167
173
  | `npx @cyning/harness upgrade` | 同步产品包更新(可加 `--gate-check` 先 audit) |
168
174
  | `npx @cyning/harness check` | 检查是否有新版本 |
169
- | `npx @cyning/harness verify` | 30 前聚合:gate-check + audit D5 + S5 warn + 可选 `--graph` · v2.0.2+ `--json` / `--agent-hint` / `--workspace-root` |
175
+ | `npx @cyning/harness verify` | 30 前聚合:gate-check + audit D5 + reviews +(`--task`)lint WARN + S5 · 可选 `--graph` / `--json` / `--allow-lint-fail` |
176
+ | `npx @cyning/harness lifecycle show` | 只读展示 `harness/lifecycle.yaml`(状态/转移/守卫 · v2.7+ · 非引擎) |
170
177
  | `npx @cyning/harness gate-check` | 仅人工闸(`--graph` / `--json`) |
171
178
  | `npx @cyning/harness audit` | ICVO 机械审计(D3/D5/S5) |
172
179
  | `npx @cyning/harness sync index` | 生成 `.cyning-harness/invoke_index.json` |
@@ -28,6 +28,7 @@ v2.2 的故事(invoke 留档连续 4 任务失守 → `task close` 补闸)
28
28
 
29
29
  CLI 今天是一袋动词,背后实际是**一个** task 生命周期:`draft → R1 → approved → 30 → 40 → done → archived`。`verify` 是 30 转移的前置检查,`close` 是 done→archived 转移——状态机是隐式的。
30
30
  显式化后(如 `lifecycle.yaml`:状态/转移/前置条件),方向一产出的每个新闸都有**天然挂点**;「这个 task 现在能做什么」一条命令可答(`verify --json` 的 handoff 是胚胎)。
31
+ **落地(v2.7.0 · 文档先行)**:产品包真值 [`harness/lifecycle.yaml`](../../../harness/lifecycle.yaml) + `npx @cyning/harness lifecycle show [--json]`(只读 · **不做**转移引擎);`verify --task` 已挂 `task_lint`(severity=warn)。
31
32
  **原则**:闸只挡实质、宽容形式(slug 事件教训:现实有两种命名惯例,闸太死就误伤)。
32
33
 
33
34
  ### 方向三 · Agent 一等公民接口(分发放大器)
@@ -37,7 +37,7 @@ description: 机械化率统计矩阵 + 缺口聚类 + 候选闸优先级排序
37
37
  | ~~P0~~ ✅ | **G1 · task md 结构 lint** | 高(下游全部闸的输入假设) | 低(解析与 M5 共享) | **已落地 v2.3.0**:`harness task lint`(E1–E7/W1–W3)· 不接入 verify(dogfood 后另议) | 10 产出时 |
38
38
  | ~~P0~~ ✅ | **G2 · reviews 留档闸** | 高(与 invoke 失守同构,且是 HG-AUDIT-R1 的签署依据) | 低(glob 命名模式) | **已落地 v2.5.0**:verify blocking(`missing R<n> review`)+ close 检查 6 · 豁免 `--allow-no-review` · 匹配两侧剥离版本后缀 | verify(30 前)· close(归档前) |
39
39
  | ~~P1~~ ✅(task 部分) | **G3 · 文本纪律 grep 闸** | 中 | 极低(正则) | **已落地 v2.3.0**(E6 绝对路径);invoke 预写 approved grep 缓做(SPEC R2:误报不可控) | 同 G1 |
40
- | **P1** | **G4 · 思考轮结构检查** | 中(阶段 C 纪律,漏槽=思考轮形同虚设) | 中(格式变体多,须宽容:只查槽位存在与控制表字段,不查内容质量) | 并入 `task lint`:R0R5 + `actual_last_round`/`early_stop`/`residual_risks` 字段存在性 | 10 产出时 |
40
+ | ~~P1~~ | **G4 · 思考轮结构检查** | 中(阶段 C 纪律,漏槽=思考轮形同虚设) | 中(格式变体多,须宽容:只查槽位存在与控制表字段,不查内容质量) | **已落地 v2.6.0**:`task lint` 条件触发 E8E10 / W4–W5(有节才查 · 形态 B/C 仅 W4) | 10 产出时 |
41
41
  | **P2** | **G7 · 执行证据** | 高但难(「真跑过」本质要 runner 见证) | 高 | `harness run -- <验证命令>`:包装执行、留 exit code + 输出摘要到 `.cyning-harness/runs/`,40 回填引用 run id | 方向二状态机内再做 |
42
42
  | **P3** | **G6 · git 行为层** | 中 | 高(需 hook/平台集成) | 暂留 prompt 层;远期 Agent 契约(方向三)的行为审计范畴 | 方向三 |
43
43
 
@@ -27,7 +27,7 @@ description: 结论与立项建议 · 缺口到 SPEC/task 的转化路径 + 与
27
27
  - `close` 可选增第 6 项检查(归档时 R1 审查文应仍在)
28
28
  - 注意与现有流程的兼容:`findReviewPath` 已被 `verify --json` 消费(handoff.review_path),本次是把它从「信息」升级为「闸」
29
29
 
30
- ### 第三波 · 思考轮结构检查(G4)· 建议 v2.4.0
30
+ ### 第三波 · 思考轮结构检查(G4)· v2.6.0
31
31
 
32
32
  - 并入 `task lint` 但作为独立规则组;宽容度设计需 dogfood 一轮(工作区 task 的槽位写法变体多)
33
33
  - 只查结构(槽位存在、控制表字段),**不查**内容质量——质量判定永远留在 22 帽
@@ -0,0 +1,174 @@
1
+ # SPEC:lifecycle.yaml 最小版 + verify↔lint 挂点(N1+N2)(v1)
2
+
3
+ > **状态**:`signed`(维护者签收 2026-07-24 · 对话「签收」)
4
+ > **track**:`epic`
5
+ > **关联图谱**:无(纯 Harness 工具链 / 过程轨)
6
+ > **上游**:工作区 [`PLAN_post_g4_next_mechanization_v1_zh.md`](../../../docs/harness/guides/PLAN_post_g4_next_mechanization_v1_zh.md) · rethink [`01_big_directions`](../rethink/2026-07-mechanization-rate/01_big_directions.md) 方向二
7
+ > **前置**:G1–G4 文档闸 ✅ · `@cyning/harness@2.6.0`
8
+ > **下游**:00 已起草 task → 20-task-audit → HG-AUDIT-R1 → 30
9
+
10
+ ---
11
+
12
+ ## Harness 元信息
13
+
14
+ | 字段 | 值 |
15
+ |------|-----|
16
+ | **spec_slug** | `lifecycle-and-verify-lint` |
17
+ | **test_strategy** | `required` |
18
+ | **test_strategy_note** | N1:schema 校验夹具 + fixture yaml;N2:verify 三态(无 lint 错 / warn / 豁免)+ 既有 verify 回归 |
19
+ | **entry_invoke_10_spec** | `Projects/docs/harness/invokes/by-task/cyning-harness-lifecycle-and-verify-lint/invoke_20260724_10_spec_lifecycle_and_verify_lint.md` |
20
+ | **entry_invoke_00_draft** | 工作区 `docs/harness/prompts/PROMPT_00_draft_spec_or_task_v1_zh.md` |
21
+
22
+ ---
23
+
24
+ ## 1. 背景与目标
25
+
26
+ G1–G4 把「文档存在性/结构」闸补齐后,CLI 仍是一袋动词:新闸的挂点靠临场设计(verify?close?独立命令?)。rethink 方向二主张用 **`lifecycle.yaml`** 显式登记状态/转移/守卫,让补闸变便宜。
27
+
28
+ 同时 G1 遗留决策未闭合:`task lint` 是否接入 `verify`。2026-07-24 dogfood:**active 16 · PASS 1 · FAIL 15**——全量 block 会立即误伤工作区。
29
+
30
+ **本 Epic 双目标**:
31
+
32
+ 1. **N1**:产品仓落地 `lifecycle.yaml` 最小真值(文档 + schema;可选只读 CLI),**不**实现状态机引擎。
33
+ 2. **N2**:在 lifecycle 上裁定并实现 **verify↔lint** 挂点语义——本波 **warn-only**(见 R2),为将来升 block 留泄压阀与登记位。
34
+
35
+ ---
36
+
37
+ ## 2. 范围
38
+
39
+ ### N1 · lifecycle.yaml 最小版
40
+
41
+ - **D1 · 真值文件**:`harness/lifecycle.yaml`(随包分发)描述:
42
+ - `states[]`:至少覆盖 `draft` · `in_progress` · `done` · `archived`(与 task 状态词表 / close 对齐;允许注释映射 `pending` 等别名)
43
+ - `transitions[]`:至少 `→30`(开工)与 `done→archived`(close);每条含 `id` · `from` · `to` · `guards[]`
44
+ - `guards[]` 元素:`{ id, command_or_check, severity: block|warn, allow_flag? }`
45
+ - 本波须把 **既有** 守卫登记进去:`HG-AUDIT-R1` / reviews 留档 / audit D5 / `task close` 五~六检(描述级 · 不要求引擎执行)
46
+ - **D2 · Schema**:`schema/lifecycle.v1.schema.json`(或等价 YAML schema)+ 非法 fixture 校验测试
47
+ - **D3 · 只读 CLI(最小)**:`harness lifecycle show [--json]` —— 读包内 yaml · 打印状态/转移/守卫表;**不做**转移执行、不做持久化状态
48
+ - **D4 · 文档**:USER_GUIDE 或 ONBOARDING 一小节 + CHANGELOG;rethink 01 方向二链到本文件
49
+
50
+ ### N2 · verify 接入 task lint(本波语义)
51
+
52
+ - **D5 · 裁定落地(R2)**:**severity = warn**(非 block)
53
+ - `verify --task`:在 gate-check + audit + reviews 之后调用 `lintTaskFile`;有 E 级 → `WARN: task lint FAIL · …` 拼入 stdout,**不**改 exit / **不**改 `may_start_30`
54
+ - `--json` handoff 增字段:`lint_ok` · `lint_errors[]`(或 `lint: { ok, errors, warnings }`)
55
+ - 豁免:`--allow-lint-fail`(即使未来升 block 也复用此旗;本波 warn 模式下作用为「抑制 WARN 行」或留痕 `lint_suppressed`)
56
+ - **D6 · lifecycle 登记**:`to_30` 守卫含 `task_lint` · `severity: warn` · `allow_flag: --allow-lint-fail`;注释写明升 `block` 的前置条件(见 §非范围 / residual)
57
+ - **D7 · 测试**:verify fixture 三态(lint pass 无 warn / lint fail 有 warn 且 exit 0 / `--allow-lint-fail`);既有 verify 用例不回归
58
+ - **D8 · 版本**:**v2.7.0**(N1 文档+只读 CLI + N2 warn 行为;无硬 block)
59
+
60
+ ---
61
+
62
+ ## 3. 非范围
63
+
64
+ - **状态机引擎**(按转移执行、写回 status、事件溯源)—— 方向二下一波
65
+ - **N2 选项 C(verify block lint)**—— 本波不做;待 active lint FAIL 率下降或另立「仅新流转」策略后再开
66
+ - `harness run` / G7 执行证据
67
+ - G6 git 行为层 · HGM G2 查询
68
+ - SPEC 审查文闸(N3)· verify 无 `--task` 全量模式纳入 reviews
69
+ - 强制修复工作区存量 15 个 FAIL task(可另开 hygiene task)
70
+ - 改变既有 E1–E10 规则集语义
71
+
72
+ ---
73
+
74
+ ## 4. 验收标准
75
+
76
+ ### N1
77
+
78
+ - [ ] `harness/lifecycle.yaml` 可被 schema 校验通过;含 `to_30` 与 `close` 两条主转移及既有守卫登记
79
+ - [ ] 非法 yaml(缺 states / 非法 severity)→ schema 或 `lifecycle show` 可失败可定位
80
+ - [ ] `harness lifecycle show` 退出 0 · 人读表含状态与守卫;`--json` 输出稳定字段
81
+ - [ ] 文档章节可从 README/ONBOARDING/USER_GUIDE 链到
82
+
83
+ ### N2
84
+
85
+ - [ ] `verify --task` 对 lint FAIL 的 task:exit **0**(在其余闸通过时)且 stdout 含 `WARN: task lint`
86
+ - [ ] lint PASS:无该 WARN
87
+ - [ ] `--json` 含 lint 结果字段;`may_start_30` **不**因 lint FAIL 变 false(本波)
88
+ - [ ] `--allow-lint-fail` 可抑制 WARN(或显式 `lint_suppressed: true`)
89
+ - [ ] `npm test` 全绿(含既有 verify 回归)
90
+
91
+ ### 发布
92
+
93
+ - [ ] CHANGELOG v2.7.0;PLAN / rethink 回链本 SPEC
94
+
95
+ ---
96
+
97
+ ## 5. failure_paths
98
+
99
+ | 触发条件 | 系统行为 | 可重试 |
100
+ |----------|----------|--------|
101
+ | lifecycle.yaml 损坏 / 不符 schema | `lifecycle show` exit ≠0 · 信息可定位 | 修 yaml |
102
+ | verify 时 task 文件不可读 | 既有 verify 行为不变 | 修正路径 |
103
+ | lint FAIL(本波) | WARN · exit 仍由其余闸决定 | 修 task 或 `--allow-lint-fail` |
104
+ | 业务仓未升级 2.7.0 | 无 lifecycle 子命令 · verify 无 lint WARN | upgrade |
105
+ | 误以为 lint FAIL 会挡 30 | 文档 + lifecycle 表 severity=warn 明示 | — |
106
+
107
+ ---
108
+
109
+ ## 6. 依赖与引用
110
+
111
+ - rethink 01 方向二;PLAN_post_g4 N1/N2
112
+ - `lib/task-lint.js` · `lib/verify.js` · `lib/task-meta.js`(handoff)
113
+ - G1 SPEC:刻意不接入 verify 的历史裁定 → 本 SPEC **修订**为 warn 挂点(非沉默独立)
114
+ - dogfood 证据:2026-07-24 active `PASS=1 FAIL=15`
115
+
116
+ ---
117
+
118
+ ## 7. 思考轮(10-spec 回填 · R0–R5)
119
+
120
+ ### R0 · 读入与约束
121
+
122
+ 读入:PLAN_post_g4(点菜 N1+N2)· rethink 01 方向二原文 · G1/G4 SPEC 非范围(verify 不接入)· verify.js 当前插点顺序(gate → audit D5 → reviews → S5 warn)· dogfood active 15/16 lint FAIL。约束:本波 **不**做引擎;闸三问(挂点/误报/泄压)必须过;不误伤存量。
123
+
124
+ ### R1 · 范围 / 非范围 / 场景
125
+
126
+ **场景**:① 维护者/Agent 查「to_30 有哪些守卫」→ lifecycle show;② 30 前 verify 看到结构问题但不被存量堵死 → lint WARN;③ 未来升 block 时有登记位与 `--allow-lint-fail`。
127
+ **同 Epic 理由**:N2 的挂点必须写进 N1 的转移表,拆开会再临场一次。
128
+ **非范围**:引擎、C 硬挡、N3/N4/G6/G7——避免 Epic 膨胀。
129
+
130
+ ### R2 · 方案对比
131
+
132
+ | 决策点 | 选项 | 裁定 | 理由 |
133
+ |---|---|---|---|
134
+ | N1 形态 | 仅 md 文档 / yaml+schema / yaml+引擎 | **yaml+schema+只读 show** | 真值可校验;引擎无消费者则违反「消费者先行」 |
135
+ | N1 CLI | 无 / show / doctor+建议 | **show** | doctor 暗示修复策略,本波无库存治理承诺 |
136
+ | N2 A 维持独立 | 永不进 verify | **否** | 丢掉机械化挂点;与方向二冲突 |
137
+ | N2 B warn-only | verify 提示不挡 | **是 · 本波** | FAIL 率 15/16,block=治理负担 |
138
+ | N2 C block+allow | 硬挡 + 豁免 | **推迟** | 须 hygiene 或「仅新流转」另 SPEC;本波在 yaml 预留 severity 升级路径 |
139
+ | 版本 | 拆 2.7 docs + 2.8 lint | **同发 2.7.0** | 同 Epic 同挂点叙事;warn 非 breaking |
140
+
141
+ ### R3 · 边界 / 失败语义 / 安全
142
+
143
+ - **挂点**:lint 检查时刻 = verify `--task`(产物 task md 已存在)✓;全量无 `--task` 模式本波 **不**跑 lint(与 G2 全量策略一致,避免扫库爆炸)。
144
+ - **误报**:沿用 lint 形式宽容;WARN 须带 rule 列表便于修。
145
+ - **泄压**:`--allow-lint-fail`;升 block 后同旗变豁免。
146
+ - **安全**:只读 lint;不改 task 文件;lifecycle show 不写盘。
147
+ - **兼容**:未升级仓行为不变。
148
+
149
+ ### R4 · 验收 / 可测性 / test_strategy
150
+
151
+ `test_strategy: required`。N1:schema 正反例 + show JSON 快照字段。N2:verify 夹具三态 + 全量模式「不出现 lint WARN」断言。回归:既有 verify/close/lint 套件。
152
+
153
+ ### R5 · SPEC 签收就绪 · 是否可交 00 出 task
154
+
155
+ SPEC 自足:双 D 包边界清晰,N2 裁定写死为 warn,C 升级条件在 residual。**可交 00**:建议单 task `cyning-harness-lifecycle-and-verify-lint`(D1–D8 同 PR),或拆 `…-lifecycle` + `…-verify-lint` 两 task 串行(lifecycle 先 merge)。图谱无需 bootstrap。版本 **v2.7.0**。
156
+
157
+ ### 思考轮控制
158
+
159
+ | 字段 | 值 |
160
+ |------|-----|
161
+ | `actual_last_round` | `R5` |
162
+ | `early_stop` | `no` |
163
+ | `early_stop_reason` | — |
164
+ | `residual_risks` | ① 升 C(block)时机依赖存量 FAIL 率或「新流转」定义未在本 SPEC 冻结;② lifecycle 描述级守卫与代码路径可能漂移——须 30 实现时对照 verify/close 源码回填 yaml;③ `lifecycle show` 若被误解为引擎,需文档醒目标「只读」 |
165
+ | `round_extension_note` | — |
166
+
167
+ ---
168
+
169
+ ## 修订记录
170
+
171
+ | 日期 | 摘要 |
172
+ |------|------|
173
+ | 2026-07-24 | 10-spec R0–R5(维护者点菜「开 N1+N2」)· 分支清理后同会话落盘 |
174
+ | 2026-07-24 | 维护者签收(对话「签收」)· 下游 00 起草 task |
@@ -0,0 +1,144 @@
1
+ # SPEC:task lint 思考轮结构规则组(G4)(v1)
2
+
3
+ > **状态**:`signed`(维护者签收 2026-07-24 · 对话「签收,继续」)
4
+ > **track**:`feature`
5
+ > **关联图谱**:无(纯 Harness 工具链)
6
+ > **上游思考**:[`docs/rethink/2026-07-mechanization-rate/`](../rethink/2026-07-mechanization-rate/)(缺口 G4 · P1)
7
+ > **前置**:v2.3.0 `task lint`(G1+G3)· 本 SPEC 为其规则组扩展
8
+ > **下游**:00 已起草 task → 20-task-audit → HG-AUDIT-R1 → 30
9
+
10
+ ---
11
+
12
+ ## Harness 元信息
13
+
14
+ | 字段 | 值 |
15
+ |------|-----|
16
+ | **spec_slug** | `task-lint-thinking-rounds` |
17
+ | **test_strategy** | `required` |
18
+ | **test_strategy_note** | 条件触发三形态 + 每条 E/W 规则的 fail/pass fixture;`npm test` 全绿含回归 |
19
+ | **entry_invoke_10_spec** | `Projects/docs/harness/invokes/by-task/cyning-harness-task-lint-thinking-rounds/invoke_20260724_10_spec_task_lint_thinking_rounds.md` |
20
+ | **entry_invoke_00_draft** | 工作区 `docs/harness/prompts/PROMPT_00_draft_spec_or_task_v1_zh.md` |
21
+
22
+ ---
23
+
24
+ ## 1. 背景与目标
25
+
26
+ rethink 02 D3:10 帽「预置 R0–R5 思考轮槽 + 思考轮控制表」是纯 Prompt 纪律(零机械)。v2.3.0 `task lint` 落地时本项被显式推迟(SPEC R1:写法变体多 · 宽容度需独立 dogfood)——本 SPEC 即第三波。
27
+
28
+ **变体实测(2026-07-24 · SPEC 设计依据)**:
29
+
30
+ | 形态 | 实例 | 占比(active 16) |
31
+ |---|---|---|
32
+ | **A · task 内嵌**(### R0–R5 + 控制表) | hat_chain_assessment · v0_2_d3_ide · ops_desk_kimi_code_spec_10_refine | ~2-3/16 |
33
+ | **B · SPEC 承载**(task 无槽位 · 链 SPEC) | 近三波 task(lint/reviews-gate 等)· 3 份 SPEC 均有完整结构 | ~2/16 |
34
+ | **C · 无思考轮**(bugfix 轨 / Issue≈SPEC) | hat_v2_split 等 | ~12/16 |
35
+
36
+ **目标**:`task lint` 新增思考轮规则组——**条件触发**(有思考轮节才查结构完整性),只查结构不查内容质量;形态 B/C 不挡(W 级提示)。
37
+
38
+ ## 2. 范围
39
+
40
+ - **D1 · `lib/task-lint.js` 规则组扩展**:
41
+ - 触发条件:文件含思考轮节(标题含「思考轮」或存在 `### R0` 槽标题)
42
+ - **E 级**(触发后):E8 R0–R5 槽位标题齐全 · E9 控制表必填字段齐全(`actual_last_round` / `early_stop` / `residual_risks`)· E10 `early_stop=yes` 时 `early_stop_reason` 非空
43
+ - **W 级**:W4 无思考轮节(提示 · 注明 SPEC 承载/bugfix 轨合法豁免)· W5 存在 `### R6`+ 但 `round_extension_note` 缺失或为空
44
+ - 槽位内容为 `(待填)`/`(跳过 · 见思考轮控制)` **合法**(draft 期语义 · 同自检结论占位原则)
45
+ - **D2 · 测试**:三形态(A 完整 pass / A 缺槽或缺字段 fail / B·C 仅 W4)+ E10 逻辑用例 + W5 用例;`npm test` 全绿含 118 例回归
46
+ - **D3 · dogfood**:工作区 active + done tasks 全量 lint,三形态分布与规则命中落自检结论(只报告)
47
+ - **D4 · 文档**:`harness/prompts/10-task-requirements.md` 思考轮摘要节补「v2.6+ task lint 机械检查结构」;CHANGELOG + 版本 **v2.6.0**;rethink 03 G4 行 ❌→✅
48
+
49
+ ## 3. 非范围
50
+
51
+ - 思考轮**内容质量**判定(是否「真思考」)—— 20 帽职责,永不在 lint
52
+ - SPEC 文件的 lint(E1 元信息字段不同 · 规则组设计为可复用,SPEC lint 下一轮)
53
+ - `verify` 接入 lint(维持 v2.3 裁定 · 独立命令)
54
+ - 强制所有 task 必须有思考轮(形态 B/C 合法 · 本闸不消灭变体)
55
+
56
+ ---
57
+
58
+ ## 4. 验收标准
59
+
60
+ - [ ] 形态 A 完整(R0–R5 + 三字段 + early_stop 逻辑一致)→ 无 E · exit 0
61
+ - [ ] 形态 A 缺槽(如缺 `### R3`)→ E8 exit 2;缺控制表字段 → E9;`early_stop=yes` 无 reason → E10
62
+ - [ ] 形态 B/C(无思考轮节)→ 仅 W4 · exit 0
63
+ - [ ] `### R6` 存在且 `round_extension_note` 空 → W5 · exit 0
64
+ - [ ] 槽位 `(待填)`/`(跳过 · 见思考轮控制)` 不触发任何 E
65
+ - [ ] `npm test` 全绿(新增用例 + 118 例回归)
66
+ - [ ] dogfood 三形态分布落自检结论
67
+ - [ ] 10-task Prompt + CHANGELOG(v2.6.0);rethink G4 行 ❌→✅
68
+
69
+ ---
70
+
71
+ ## 5. failure_paths
72
+
73
+ | 触发条件 | 系统行为 | 可重试 |
74
+ |----------|----------|--------|
75
+ | 有思考轮节但结构不全 | exit 2 · 逐条列出(E8/E9/E10)· 不改文件 | 补齐后重跑 |
76
+ | 形态 B/C | W4 提示 · exit 0 | — |
77
+ | 槽标题变体(如 `### R3 · 边界`) | 槽匹配按 `^### R\d` 前缀(形式宽容)| 误报则 dogfood 期调整 |
78
+ | 既有 task 大量 W4 | 不影响 exit · dogfood 记录分布 | — |
79
+ | 业务仓未升级 2.6.0 | 旧 lint 无本规则组 · 行为不变 | upgrade |
80
+
81
+ ---
82
+
83
+ ## 6. 依赖与引用
84
+
85
+ - 复用:`lib/task-lint.js`(规则组框架 · E/W 分级)· `lib/task-meta.js`(`extractSection` 行首锚定)
86
+ - 纪律原文:`harness/prompts/10-task-requirements.md`「OSS 阶段 C · 思考轮」节;工作区 `FRAGMENT_rethink_backfill_spec_v1_zh.md`(控制表字段定义)
87
+ - 变体实测:2026-07-24 扫描(§1 表)
88
+ - 缺口出处:rethink 02(D3)· 03(G4 行)· SPEC-task-lint-structure-gate R1(推迟裁定)
89
+
90
+ ---
91
+
92
+ ## 7. 思考轮(10-spec 回填 · R0–R5)
93
+
94
+ ### R0 · 读入与约束
95
+
96
+ 读入:rethink 02 D3 / 03 G4 / 04 第三波定义;v2.3 SPEC 的推迟裁定;变体实测数据(§1);10-task 新帽思考轮摘要节(v2.4.0)。约束:只查结构不查内容是 04 定下的硬边界;不消灭形态 B/C(它们是 R1 审查已接受的合法轨)。
97
+
98
+ ### R1 · 范围 / 非范围 / 场景
99
+
100
+ 变体实测驱动设计:active 16 个中仅 ~3 个形态 A——**无条件强制槽位 = 误伤 80% 存量**,故核心设计是**条件触发**(有节才查)。场景:① 形态 A task 的 10 产出自查(主);② 22/20 审查时辅助核对控制表;③ 维护者批量体检。SPEC 文件的结构与本规则组同构,但 E1 元信息字段不同(spec_slug ≠ task_slug),SPEC lint 不在本波(非范围已列)。
101
+
102
+ ### R2 · 方案对比
103
+
104
+ | 决策点 | 选项 | 裁定 | 理由 |
105
+ |---|---|---|---|
106
+ | 触发方式 | 全员强制 / 条件触发 / 只对阶段 C 标记 task | **条件触发(有节才查)** | 变体实测:强制 = 80% 误伤;「阶段 C 标记」本身无机械标记可锚 |
107
+ | 无思考轮节 | 沉默 / W4 提示 / E 级 | **W4** | 形态 B/C 合法但值得可见(提示里也写明豁免路径,教育新用户) |
108
+ | 槽内容 | 查非占位 / 不查 | **不查**(`(待填)`/`(跳过)` 合法) | draft 期语义与自检结论占位原则一致;填没填的思考质量是 20 帽职责 |
109
+ | early_stop 逻辑 | 查 / 不查 | **查(E10)** | `yes` 必须配 reason 是控制表自身的逻辑完整性,属结构非内容 |
110
+ | R6+ | 不查 / W5 / E | **W5** | 扩展理由缺失值得提示,但 R6 使用率低,E 级过重 |
111
+ | 槽标题匹配 | 精确 `### R0` | **`^### R\d` 前缀宽容**(允许 `### R3 · 边界` 后缀) | 实测槽标题带主题后缀是惯例 |
112
+
113
+ ### R3 · 边界 / 失败语义 / 安全
114
+
115
+ - **E/W 分界原则**:触发后结构残缺 = E(有节就等于承诺了结构);未触发 = 最多 W(不承诺不查)。
116
+ - **触发条件假阳性**:文件正文提到「思考轮」三字但无槽位 → 触发条件要求「标题含思考轮 **或** 存在 `### R0` 槽标题」——纯文本提及(如本段)不触发,因为 `### R0` 必须行首锚定(extractSection 同款锚定)。「标题含思考轮」单独触发会不会误伤?一个写着「## 思考轮说明」但无槽的文件 → E8 fail。裁定:触发 = 存在 `^### R0` 槽标题 **或** `^#{2,3}.*思考轮` 标题;两者任一即按形态 A 要求。dogfood 验证误伤面(D3)。
117
+ - **安全**:纯只读;无 git 动作;不影响既有 E1–E7 行为(118 例回归兜底)。
118
+
119
+ ### R4 · 验收 / 可测性 / test_strategy
120
+
121
+ `test_strategy: required`。fixture:形态 A 完整 / 缺槽 / 缺字段 / early_stop 无 reason / R6 无扩展理由 / 形态 B / 形态 C / 槽标题带后缀宽容 / 正文提及「思考轮」不触发。dogfood 落三形态分布与 W4 计数。
122
+
123
+ ### R5 · SPEC 签收就绪 · 是否可交 00 出 task
124
+
125
+ SPEC 自足:条件触发模型有三形态实测支撑,E/W 边界清晰,宽容条款逐条在案。**可交 00 起草 task**。图谱:纯工具链。版本 v2.6.0(lint 规则组扩展 · 无 verify 行为变更)。落地后 rethink 矩阵 ❌ 8 → 7;P1 队列余 SPEC 审查闸与 verify 全量模式决策。
126
+
127
+ ### 思考轮控制
128
+
129
+ | 字段 | 值 |
130
+ |------|-----|
131
+ | `actual_last_round` | `R5` |
132
+ | `early_stop` | `no` |
133
+ | `early_stop_reason` | — |
134
+ | `residual_risks` | ① 触发条件的假阳性(文件有「思考轮」标题但槽在别处)——dogfood D3 实测,必要时触发条件收紧为仅 `### R0`;② 历史形态 A 文件可能结构不全被 E 挡(存量仅 ~3 个 · 影响面小且可豁免豁免阀之外直接修复);③ SPEC lint 复用需求可能提前到来(规则组已按可复用设计) |
135
+ | `round_extension_note` | — |
136
+
137
+ ---
138
+
139
+ ## 修订记录
140
+
141
+ | 日期 | 摘要 |
142
+ |------|------|
143
+ | 2026-07-24 | 10-spec R0–R5 同会话回填(维护者点菜「1」)· 基于 rethink G4 + 变体实测(形态 A/B/C 分布) |
144
+ | 2026-07-24 | 维护者签收(对话「签收,继续」)· 下游 00 起草 task |
@@ -0,0 +1,69 @@
1
+ # Task lifecycle · 最小真值(方向二文档先行 · v1)
2
+ #
3
+ # 只读登记:状态 / 转移 / 守卫。不由引擎执行。
4
+ # CLI:npx @cyning/harness lifecycle show [--json]
5
+ # Schema:schema/lifecycle.v1.schema.json
6
+
7
+ version: "1"
8
+
9
+ states:
10
+ - id: draft
11
+ note: "含 pending / active 等词表别名 · 见 KNOWN_STATUS_TOKENS"
12
+ - id: in_progress
13
+ - id: done
14
+ note: "close 可接受 completed 别名"
15
+ - id: archived
16
+ note: "task close 归档后 · 物理上在 done/ 目录"
17
+
18
+ transitions:
19
+ - id: to_30
20
+ from: ["draft", "in_progress"]
21
+ to: in_progress
22
+ hat: "30"
23
+ description: "执行帽开工(verify --task 聚合)"
24
+ guards:
25
+ - id: HG-AUDIT-R1
26
+ command_or_check: "gate-check · human_gate HG-AUDIT-R1=approved"
27
+ severity: block
28
+ - id: HG-TASK-DRAFT
29
+ command_or_check: "gate-check · HG-TASK-DRAFT=approved(blocks 22,30)"
30
+ severity: block
31
+ - id: audit_D5
32
+ command_or_check: "verify/audit · test_strategy vs 测试文件存在"
33
+ severity: block
34
+ - id: reviews_retention
35
+ command_or_check: "verify · findReview R<n> 存在性(v2.5+)"
36
+ severity: block
37
+ allow_flag: "--allow-no-review"
38
+ - id: task_lint
39
+ command_or_check: "verify --task · lintTaskFile(v2.7+)"
40
+ severity: warn
41
+ allow_flag: "--allow-lint-fail"
42
+ note: "升 block 前置:active lint FAIL 率下降或另立「仅新流转」SPEC(N2-C)"
43
+
44
+ - id: close
45
+ from: ["done"]
46
+ to: archived
47
+ hat: "close"
48
+ description: "task close · active→done 归档"
49
+ guards:
50
+ - id: close_invoke
51
+ command_or_check: "task close 检查1 · invoke 快照存在"
52
+ severity: block
53
+ - id: close_self_check
54
+ command_or_check: "task close 检查2 · 自检结论已回填"
55
+ severity: block
56
+ - id: close_acceptance
57
+ command_or_check: "task close 检查3 · 验收勾选"
58
+ severity: block
59
+ allow_flag: "--allow-unchecked"
60
+ - id: close_slug
61
+ command_or_check: "task close 检查4 · slug 一致"
62
+ severity: block
63
+ - id: close_status
64
+ command_or_check: "task close 检查5 · 状态 done/completed"
65
+ severity: block
66
+ - id: close_review
67
+ command_or_check: "task close 检查6 · R<n> 审查文(v2.5+)"
68
+ severity: block
69
+ allow_flag: "--allow-no-review"
@@ -40,12 +40,14 @@
40
40
  - 10-task 草稿 **须预置 R0 + R1–R5** 五槽 + **思考轮控制** 表(见 `wizard/templates/TASK_TEMPLATE_upstream_pr_v1.md`)。
41
41
  - **可提前停 / 可增 R6+**;须填 reason · residual_risks(无则 `none`)。
42
42
  - **20-task-audit 审思考是否充分**;不通过 → **退回 10-task** 补轮后再审 R+1。
43
+ - **v2.6+ 机械检查结构**(条件触发):`task lint` 在存在 `### R0` 或思考轮标题时查 E8–E10(槽位/控制表/`early_stop` 逻辑);无节仅 W4(SPEC 承载 / bugfix 合法);不查内容质量。
43
44
 
44
45
  ## 修订记录
45
46
 
46
47
  | 日期 | 摘要 |
47
48
  |------|------|
48
49
  | 2026-07-24 | V2 拆分收编:自 `10-requirements.md` 改名为 `10-task-requirements.md`(来源:工作区 `10-task-requirements.md` · 2026-06-12 v2) |
50
+ | 2026-07-24 | G4:思考轮摘要节补 v2.6+ `task lint` 条件触发结构检查 |
49
51
 
50
52
  ## 给 Cursor
51
53
 
package/lib/cli.js CHANGED
@@ -42,6 +42,7 @@ function usage(version = 'unknown') {
42
42
  npx @cyning/harness audit [--target PATH] [--task FILE]
43
43
  npx @cyning/harness gate-check [--target PATH] [--task FILE] [--graph] [--json]
44
44
  npx @cyning/harness verify [--target PATH] [--task FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH]
45
+ npx @cyning/harness lifecycle show [--json]
45
46
  npx @cyning/harness sync index [--target PATH]
46
47
  npx @cyning/harness task check --file PATH [--no-circular] [--registry DIR]...
47
48
  npx @cyning/harness task close --file PATH [--target PATH] [--yes] [--allow-unchecked]
@@ -61,7 +62,8 @@ function usage(version = 'unknown') {
61
62
  check 读 manifest.json · 对比当前包版本是否有可升级版本
62
63
  audit ICVO 机械审计:复用 gate-check · D5 测试声明检查 · S5 git-clean 提醒
63
64
  gate-check 调用 wizard/gate-check.sh(业务仓无需 clone 即可用)
64
- verify 30 前聚合:gate-check + audit D5 + S5 warn + 可选 --graph
65
+ verify 30 前聚合:gate-check + audit D5 + reviews + task lint WARN(v2.7+)+ S5 warn + 可选 --graph
66
+ lifecycle 只读展示 harness/lifecycle.yaml(状态/转移/守卫 · v2.7+)
65
67
  sync index 生成 .cyning-harness/invoke_index.json
66
68
  task check 校验 task.harness.v1.json sidecar · --no-circular 检测 depends_on 环
67
69
  task close 受闸归档:5 项机械校验(invoke/自检结论/勾选/slug/状态)后 mv active→done(v2.2+)
@@ -128,6 +130,10 @@ export async function runCli(argv) {
128
130
  await cmdVerify(rest);
129
131
  return;
130
132
  }
133
+ if (cmd === 'lifecycle') {
134
+ await cmdLifecycle(rest);
135
+ return;
136
+ }
131
137
  if (cmd === 'sync') {
132
138
  await cmdSync(rest);
133
139
  return;
@@ -386,11 +392,12 @@ async function cmdVerify(args) {
386
392
  // D3 · implemented in lib/verify.js
387
393
  const { verifyTarget, buildVerifyHandoff, formatAgentHint } = await import('./verify.js');
388
394
  if (args.includes('--help') || args.includes('-h')) {
389
- console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH]
395
+ console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE] [--graph] [--json] [--agent-hint] [--workspace-root PATH] [--allow-no-review] [--allow-lint-fail]
390
396
 
391
- 无 --task 时:扫描 docs/tasks/active/task_*.md 并逐项列出闸表(同 gate-check)。
397
+ 无 --task 时:扫描 docs/tasks/active/task_*.md 并逐项列出闸表(同 gate-check);本波不跑 task lint。
392
398
  任一 task 阻塞则 VERIFY: BLOCKED · exit 2;全部可 30 则 VERIFY: PASS。
393
- --json:输出 Agent handoff JSONmay_start_30 · entry_invoke_30 · review_path 等)。
399
+ --task:另跑 task lint;E 级仅 WARNv2.7+ · 不改 may_start_30);--allow-lint-fail 抑制 WARN。
400
+ --json:输出 Agent handoff JSON(may_start_30 · entry_invoke_30 · review_path · lint 等)。
394
401
  --agent-hint:人类可读 handoff 摘要(可与 --json 同用)。
395
402
  --workspace-root:解析 task 中 Projects/ 前缀的 entry_invoke 路径。
396
403
  `);
@@ -408,7 +415,15 @@ async function cmdVerify(args) {
408
415
  const json = rest.includes('--json');
409
416
  const agentHint = rest.includes('--agent-hint');
410
417
  const allowNoReview = rest.includes('--allow-no-review');
411
- rest = rest.filter((a) => a !== '--graph' && a !== '--json' && a !== '--agent-hint' && a !== '--allow-no-review');
418
+ const allowLintFail = rest.includes('--allow-lint-fail');
419
+ rest = rest.filter(
420
+ (a) =>
421
+ a !== '--graph' &&
422
+ a !== '--json' &&
423
+ a !== '--agent-hint' &&
424
+ a !== '--allow-no-review' &&
425
+ a !== '--allow-lint-fail',
426
+ );
412
427
 
413
428
  if (rest.length > 0) {
414
429
  const err = new Error(`verify 未知参数: ${rest.join(' ')}`);
@@ -417,7 +432,7 @@ async function cmdVerify(args) {
417
432
  }
418
433
 
419
434
  const target = resolveTarget(process.cwd(), targetArg);
420
- const result = verifyTarget(target, { taskFile, graph, allowNoReview });
435
+ const result = verifyTarget(target, { taskFile, graph, allowNoReview, allowLintFail });
421
436
 
422
437
  // forward gate-check/audit/graph stdout if any(--json 时仍透传机械闸表)
423
438
  if (result.stdout && !json) {
@@ -431,6 +446,7 @@ async function cmdVerify(args) {
431
446
  taskFile,
432
447
  workspaceRoot: workspaceRoot ? path.resolve(workspaceRoot) : undefined,
433
448
  allowNoReview,
449
+ allowLintFail,
434
450
  });
435
451
 
436
452
  if (json) {
@@ -458,6 +474,37 @@ async function cmdVerify(args) {
458
474
  }
459
475
  }
460
476
 
477
+ async function cmdLifecycle(args) {
478
+ const { loadLifecycle, formatLifecycleShow } = await import('./lifecycle.js');
479
+ const [sub, ...rest] = args;
480
+ if (!sub || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
481
+ console.log(`用法: npx @cyning/harness lifecycle show [--json]
482
+
483
+ 只读展示产品包 harness/lifecycle.yaml(状态 / 转移 / 守卫登记)。
484
+ 不做转移执行、不写盘(方向二文档先行 · v2.7+)。
485
+ `);
486
+ return;
487
+ }
488
+ if (sub !== 'show') {
489
+ const err = new Error(`lifecycle 子命令未知: ${sub}\n用法: lifecycle show [--json]`);
490
+ err.exitCode = 1;
491
+ throw err;
492
+ }
493
+ const json = rest.includes('--json');
494
+ const unknown = rest.filter((a) => a !== '--json');
495
+ if (unknown.length > 0) {
496
+ const err = new Error(`lifecycle show 未知参数: ${unknown.join(' ')}`);
497
+ err.exitCode = 1;
498
+ throw err;
499
+ }
500
+ const { data } = loadLifecycle();
501
+ if (json) {
502
+ console.log(JSON.stringify(data, null, 2));
503
+ } else {
504
+ console.log(formatLifecycleShow(data));
505
+ }
506
+ }
507
+
461
508
  async function cmdSync(args) {
462
509
  if (args.includes('--help') || args.includes('-h')) {
463
510
  console.log(`用法: npx @cyning/harness sync index [--target PATH]`);
@@ -630,10 +677,12 @@ async function cmdTaskLint(args) {
630
677
  if (args.includes('--help') || args.includes('-h')) {
631
678
  console.log(`用法: npx @cyning/harness task lint --file PATH [--json]
632
679
 
633
- task md 结构闸(G1)+ 文本规则包(G3)· 纯只读:
680
+ task md 结构闸(G1)+ 文本规则包(G3)+ 思考轮结构(G4)· 纯只读:
634
681
  E 级(exit 2):E1 元信息/task_slug · E2 状态行 · E3 验收标准含勾选项
635
682
  E4 失败路径节 · E5 自检结论节 · E6 绝对本机路径 · E7 slug 一致
683
+ E8–E10(条件触发:有 ### R0 或思考轮标题):槽位齐全 · 控制表字段 · early_stop 逻辑
636
684
  W 级(仅 warn):W1 状态 token 词表外 · W2 缺人工闸节 · W3 自检结论占位
685
+ W4 无思考轮节(形态 B/C 豁免提示)· W5 R6+ 缺 round_extension_note
637
686
  末行协议:LINT: PASS · <file> / LINT: FAIL · <file>
638
687
  --json:{ok, errors[], warnings[], file, slug}(errors 元素 {rule, message, line?})
639
688
  `);
@@ -0,0 +1,121 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import yaml from 'js-yaml';
4
+ import { resolveHarnessRoot } from './paths.js';
5
+
6
+ const SEVERITIES = new Set(['block', 'warn']);
7
+
8
+ /**
9
+ * 轻量结构校验(不引入 Ajv):对齐 schema/lifecycle.v1.schema.json 必填约束。
10
+ * @returns {{ ok: boolean, errors: string[] }}
11
+ */
12
+ export function validateLifecycle(data) {
13
+ const errors = [];
14
+ if (!data || typeof data !== 'object') {
15
+ return { ok: false, errors: ['lifecycle 根须为 object'] };
16
+ }
17
+ if (typeof data.version !== 'string' || !data.version.trim()) {
18
+ errors.push('缺 version(string)');
19
+ }
20
+ if (!Array.isArray(data.states) || data.states.length === 0) {
21
+ errors.push('缺 states[] 或为空');
22
+ } else {
23
+ data.states.forEach((s, i) => {
24
+ if (!s || typeof s.id !== 'string' || !s.id.trim()) {
25
+ errors.push(`states[${i}] 缺 id`);
26
+ }
27
+ });
28
+ }
29
+ if (!Array.isArray(data.transitions) || data.transitions.length === 0) {
30
+ errors.push('缺 transitions[] 或为空');
31
+ } else {
32
+ data.transitions.forEach((t, i) => {
33
+ if (!t || typeof t.id !== 'string' || !t.id.trim()) {
34
+ errors.push(`transitions[${i}] 缺 id`);
35
+ }
36
+ if (!Array.isArray(t?.from) || t.from.length === 0) {
37
+ errors.push(`transitions[${i}] 缺 from[]`);
38
+ }
39
+ if (typeof t?.to !== 'string' || !t.to.trim()) {
40
+ errors.push(`transitions[${i}] 缺 to`);
41
+ }
42
+ if (!Array.isArray(t?.guards)) {
43
+ errors.push(`transitions[${i}] 缺 guards[]`);
44
+ } else {
45
+ t.guards.forEach((g, j) => {
46
+ if (!g || typeof g.id !== 'string') {
47
+ errors.push(`transitions[${i}].guards[${j}] 缺 id`);
48
+ }
49
+ if (!g || typeof g.command_or_check !== 'string') {
50
+ errors.push(`transitions[${i}].guards[${j}] 缺 command_or_check`);
51
+ }
52
+ if (!g || !SEVERITIES.has(g.severity)) {
53
+ errors.push(
54
+ `transitions[${i}].guards[${j}] severity 须为 block|warn(当前: ${g?.severity})`,
55
+ );
56
+ }
57
+ });
58
+ }
59
+ });
60
+ }
61
+ return { ok: errors.length === 0, errors };
62
+ }
63
+
64
+ /**
65
+ * 加载并校验 lifecycle.yaml。
66
+ * @param {{ harnessRoot?: string, filePath?: string }} [options]
67
+ */
68
+ export function loadLifecycle(options = {}) {
69
+ const harnessRoot = options.harnessRoot ?? resolveHarnessRoot();
70
+ const filePath =
71
+ options.filePath ?? path.join(harnessRoot, 'harness', 'lifecycle.yaml');
72
+
73
+ if (!fs.existsSync(filePath)) {
74
+ const e = new Error(`lifecycle.yaml 不存在: ${filePath}`);
75
+ e.exitCode = 1;
76
+ throw e;
77
+ }
78
+
79
+ let data;
80
+ try {
81
+ data = yaml.load(fs.readFileSync(filePath, 'utf8'));
82
+ } catch (err) {
83
+ const e = new Error(`lifecycle.yaml 解析失败: ${err.message}`);
84
+ e.exitCode = 1;
85
+ throw e;
86
+ }
87
+
88
+ const v = validateLifecycle(data);
89
+ if (!v.ok) {
90
+ const e = new Error(`lifecycle.yaml 校验失败:\n - ${v.errors.join('\n - ')}`);
91
+ e.exitCode = 1;
92
+ e.validationErrors = v.errors;
93
+ throw e;
94
+ }
95
+
96
+ return { data, filePath, harnessRoot };
97
+ }
98
+
99
+ /**
100
+ * 人读表。
101
+ */
102
+ export function formatLifecycleShow(data) {
103
+ const lines = [
104
+ `lifecycle v${data.version}`,
105
+ '',
106
+ '## states',
107
+ ...data.states.map((s) => `- ${s.id}${s.note ? ` · ${s.note}` : ''}`),
108
+ '',
109
+ '## transitions',
110
+ ];
111
+ for (const t of data.transitions) {
112
+ lines.push(`### ${t.id} · ${t.from.join('|')} → ${t.to}${t.hat ? ` · hat=${t.hat}` : ''}`);
113
+ if (t.description) lines.push(` ${t.description}`);
114
+ for (const g of t.guards) {
115
+ const allow = g.allow_flag ? ` · allow=${g.allow_flag}` : '';
116
+ lines.push(` - [${g.severity}] ${g.id}: ${g.command_or_check}${allow}`);
117
+ }
118
+ lines.push('');
119
+ }
120
+ return lines.join('\n').trimEnd();
121
+ }
package/lib/task-lint.js CHANGED
@@ -17,6 +17,14 @@ import {
17
17
  */
18
18
  const ABS_PATH_RE = /(\/(?:Users|home|root)\/[^\s/`\\]|[A-Za-z]:\\Users\\[^\s`\\])/;
19
19
  const FAILURE_HEADING_RE = /^#{2,4}\s.*(失败路径|failure_paths)/i;
20
+ /** G4 触发:行首 ### R0(可带后缀) */
21
+ const R0_HEADING_RE = /^###\s+R0(\b|[^\d]|$)/m;
22
+ /** G4 触发(叠加):行首 ##/### 标题含「思考轮」 */
23
+ const THINK_HEADING_RE = /^#{2,3}\s+.*思考轮/m;
24
+ /** 槽位标题:### Rn(前缀宽容) */
25
+ const RN_HEADING_RE = /^###\s+R(\d+)\b/gm;
26
+ /** 控制表字段行:| `field` | / | **field** | / | field | */
27
+ const CTRL_FIELD_RE = /^\|\s*`?\*{0,2}([a-z_]+)\*{0,2}`?\s*\|\s*(.*?)\s*\|/i;
20
28
 
21
29
  function err(rule, message, line) {
22
30
  return line ? { rule, message, line } : { rule, message };
@@ -25,8 +33,79 @@ function warn(rule, message, line) {
25
33
  return line ? { rule, message, line } : { rule, message };
26
34
  }
27
35
 
36
+ function parseControlFields(lines) {
37
+ const fields = {};
38
+ for (const l of lines) {
39
+ const m = l.match(CTRL_FIELD_RE);
40
+ if (!m) continue;
41
+ const key = m[1].toLowerCase();
42
+ let val = m[2].trim();
43
+ if (val.startsWith('`') && val.endsWith('`')) val = val.slice(1, -1).trim();
44
+ fields[key] = val;
45
+ }
46
+ return fields;
47
+ }
48
+
49
+ function isEmptyCtrlValue(val) {
50
+ if (val == null) return true;
51
+ const v = String(val).trim();
52
+ return v === '' || v === '—' || v === '-' || v === '–';
53
+ }
54
+
55
+ /**
56
+ * G4 · 思考轮结构规则组(条件触发)。
57
+ * 触发优先:存在 ### R0;叠加:行首 ##/### 含「思考轮」。
58
+ * 正文提及「思考轮」不触发。
59
+ */
60
+ function lintThinkingRounds(content, lines, errors, warnings) {
61
+ const hasR0 = R0_HEADING_RE.test(content);
62
+ const hasThinkHeading = THINK_HEADING_RE.test(content);
63
+ const triggered = hasR0 || hasThinkHeading;
64
+
65
+ if (!triggered) {
66
+ warnings.push(
67
+ warn('W4', '无思考轮节(SPEC 承载 / bugfix 轨合法豁免 · 有节则查 R0–R5 与控制表)'),
68
+ );
69
+ return;
70
+ }
71
+
72
+ // E8 · R0–R5 槽位齐全
73
+ const found = new Set();
74
+ const rnRe = new RegExp(RN_HEADING_RE.source, 'gm');
75
+ let m;
76
+ while ((m = rnRe.exec(content)) !== null) {
77
+ found.add(Number(m[1]));
78
+ }
79
+ const missing = [];
80
+ for (let i = 0; i <= 5; i += 1) {
81
+ if (!found.has(i)) missing.push(`R${i}`);
82
+ }
83
+ if (missing.length) {
84
+ errors.push(err('E8', `思考轮槽位不全,缺:${missing.join(', ')}`));
85
+ }
86
+
87
+ // E9 / E10 · 控制表字段
88
+ const fields = parseControlFields(lines);
89
+ const required = ['actual_last_round', 'early_stop', 'residual_risks'];
90
+ const missFields = required.filter((k) => !(k in fields));
91
+ if (missFields.length) {
92
+ errors.push(err('E9', `思考轮控制表缺字段:${missFields.join(', ')}`));
93
+ }
94
+
95
+ const early = (fields.early_stop || '').toLowerCase();
96
+ if (early === 'yes' && isEmptyCtrlValue(fields.early_stop_reason)) {
97
+ errors.push(err('E10', 'early_stop=yes 时 early_stop_reason 须非空'));
98
+ }
99
+
100
+ // W5 · R6+ 须有 round_extension_note
101
+ const hasR6Plus = [...found].some((n) => n >= 6);
102
+ if (hasR6Plus && isEmptyCtrlValue(fields.round_extension_note)) {
103
+ warnings.push(warn('W5', '存在 ### R6+ 但 round_extension_note 缺失或为空'));
104
+ }
105
+ }
106
+
28
107
  /**
29
- * task lint · task md 结构闸(G1)+ 文本规则包(G3)。
108
+ * task lint · task md 结构闸(G1)+ 文本规则包(G3)+ 思考轮结构(G4)。
30
109
  * 纯只读;E 级 → ok:false(CLI exit 2),W 级仅提醒。
31
110
  */
32
111
  export function lintTaskFile(filePath, options = {}) {
@@ -114,6 +193,9 @@ export function lintTaskFile(filePath, options = {}) {
114
193
  warnings.push(warn('W2', '缺 ### 人工闸 节(轻量 task 可忽略本提醒)'));
115
194
  }
116
195
 
196
+ // G4 · 思考轮结构(条件触发)
197
+ lintThinkingRounds(content, lines, errors, warnings);
198
+
117
199
  return {
118
200
  ok: errors.length === 0,
119
201
  errors,
package/lib/verify.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
  import { resolveHarnessRootForTarget, wizardScript } from './paths.js';
5
4
  import { auditTarget } from './audit.js';
6
5
  import { buildTaskHandoff, findReview, listActiveTasks } from './task-meta.js';
6
+ import { lintTaskFile } from './task-lint.js';
7
7
 
8
8
  /**
9
- * 30 前聚合验证:gate-check + audit D5(仅 --task)+ reviews 留档闸(仅 --task)+ S5 git-clean warn + 可选 --graph
9
+ * 30 前聚合验证:gate-check + audit D5(仅 --task)+ reviews 留档闸(仅 --task
10
+ * + task lint WARN(仅 --task · v2.7+)+ S5 git-clean warn + 可选 --graph
10
11
  */
11
12
  export function verifyTarget(target, options = {}) {
12
- const { taskFile, graph, allowNoReview = false } = options;
13
+ const { taskFile, graph, allowNoReview = false, allowLintFail = false } = options;
13
14
  const harnessRoot = resolveHarnessRootForTarget(target);
14
15
 
15
16
  // 1. gate-check(含 --graph)
@@ -49,12 +50,22 @@ export function verifyTarget(target, options = {}) {
49
50
  };
50
51
  }
51
52
  }
53
+
54
+ // 3b. task lint(N2 · v2.7+):severity=warn · 不改 exit / may_start_30
55
+ const lint = runTaskLintForVerify(target, taskFile);
56
+ if (!lint.ok) {
57
+ if (allowLintFail) {
58
+ gateResult.stdout += `WARN: task lint FAIL suppressed(--allow-lint-fail · 留痕)\n`;
59
+ } else {
60
+ const rules = lint.errors.map((e) => e.rule).join(',');
61
+ gateResult.stdout += `WARN: task lint FAIL · ${rules || 'errors'}(不挡 may_start_30 · 修结构或 --allow-lint-fail)\n`;
62
+ }
63
+ }
52
64
  }
53
65
 
54
66
  // 4. S5 git-clean:warn 不挡
55
67
  const gitResult = runGitCleanCheck(target);
56
68
  if (gitResult.dirty) {
57
- // 把 warn 拼到 stdout 里让 CLI 透传
58
69
  const warn = `WARN: 工作区未 clean(S5):建议 commit 后再执行 apply\n`;
59
70
  gateResult.stdout += warn;
60
71
  }
@@ -66,6 +77,39 @@ export function verifyTarget(target, options = {}) {
66
77
  };
67
78
  }
68
79
 
80
+ function runTaskLintForVerify(target, taskFile) {
81
+ try {
82
+ return lintTaskFile(taskFile, { cwd: target });
83
+ } catch (err) {
84
+ return {
85
+ ok: false,
86
+ errors: [{ rule: 'E_IO', message: err.message }],
87
+ warnings: [],
88
+ };
89
+ }
90
+ }
91
+
92
+ /** 规范化 lint 结果供 handoff */
93
+ export function lintSnapshotForHandoff(target, taskFile, { allowLintFail = false } = {}) {
94
+ if (!taskFile) return null;
95
+ let result;
96
+ try {
97
+ result = lintTaskFile(taskFile, { cwd: target });
98
+ } catch (err) {
99
+ result = {
100
+ ok: false,
101
+ errors: [{ rule: 'E_IO', message: err.message }],
102
+ warnings: [],
103
+ };
104
+ }
105
+ return {
106
+ ok: result.ok,
107
+ errors: result.errors ?? [],
108
+ warnings: result.warnings ?? [],
109
+ suppressed: Boolean(allowLintFail && !result.ok),
110
+ };
111
+ }
112
+
69
113
  function runGateCheck(target, taskFile, graph, harnessRoot) {
70
114
  const script = wizardScript(harnessRoot, 'gate-check.sh');
71
115
  const args = [script, '--target', target];
@@ -171,13 +215,14 @@ function runGitCleanCheck(target) {
171
215
  * 构建 verify --json / --agent-hint 的 Agent handoff 载荷。
172
216
  */
173
217
  export function buildVerifyHandoff(target, options = {}) {
174
- const { taskFile, workspaceRoot, allowNoReview = false } = options;
218
+ const { taskFile, workspaceRoot, allowNoReview = false, allowLintFail = false } = options;
175
219
  const handoffOpts = { workspaceRoot };
176
220
 
177
221
  if (taskFile) {
178
222
  const handoff = buildTaskHandoff(target, taskFile, handoffOpts);
179
223
  const reviewOk = handoff.review_found || allowNoReview;
180
224
  const verifyOk = handoff.may_start_30 && reviewOk;
225
+ const lint = lintSnapshotForHandoff(target, taskFile, { allowLintFail });
181
226
  return {
182
227
  schema_version: '1',
183
228
  verify_ok: verifyOk,
@@ -185,6 +230,7 @@ export function buildVerifyHandoff(target, options = {}) {
185
230
  may_start_30: verifyOk,
186
231
  blocked_reason: handoff.blocked_reason ?? (reviewOk ? null : 'missing R<n> review'),
187
232
  next_hat: verifyOk ? '30' : null,
233
+ lint,
188
234
  };
189
235
  }
190
236
 
@@ -231,6 +277,11 @@ export function formatAgentHint(payload) {
231
277
  if (item.next_hat) {
232
278
  lines.push(` next_hat: ${item.next_hat}`);
233
279
  }
280
+ if (item.lint) {
281
+ const n = item.lint.errors?.length ?? 0;
282
+ const tag = item.lint.suppressed ? 'suppressed' : item.lint.ok ? 'ok' : `FAIL(${n})`;
283
+ lines.push(` lint: ${tag}`);
284
+ }
234
285
  for (const w of item.warnings ?? []) {
235
286
  lines.push(` warn: ${w}`);
236
287
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyning/harness",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "cyning-harness discipline package · init / upgrade / check CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://cyning.dev/schemas/cyning-harness/lifecycle.v1.json",
4
+ "title": "cyning-harness Task Lifecycle (minimal)",
5
+ "description": "状态/转移/守卫登记 · 只读真值 · 非引擎(v2.7+)",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["version", "states", "transitions"],
9
+ "properties": {
10
+ "version": {
11
+ "type": "string",
12
+ "description": "lifecycle 文档 schema 版本"
13
+ },
14
+ "states": {
15
+ "type": "array",
16
+ "minItems": 1,
17
+ "items": {
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "required": ["id"],
21
+ "properties": {
22
+ "id": { "type": "string", "minLength": 1 },
23
+ "note": { "type": "string" }
24
+ }
25
+ }
26
+ },
27
+ "transitions": {
28
+ "type": "array",
29
+ "minItems": 1,
30
+ "items": {
31
+ "type": "object",
32
+ "additionalProperties": false,
33
+ "required": ["id", "from", "to", "guards"],
34
+ "properties": {
35
+ "id": { "type": "string", "minLength": 1 },
36
+ "from": {
37
+ "type": "array",
38
+ "minItems": 1,
39
+ "items": { "type": "string", "minLength": 1 }
40
+ },
41
+ "to": { "type": "string", "minLength": 1 },
42
+ "hat": { "type": "string" },
43
+ "description": { "type": "string" },
44
+ "guards": {
45
+ "type": "array",
46
+ "items": {
47
+ "type": "object",
48
+ "additionalProperties": false,
49
+ "required": ["id", "command_or_check", "severity"],
50
+ "properties": {
51
+ "id": { "type": "string", "minLength": 1 },
52
+ "command_or_check": { "type": "string", "minLength": 1 },
53
+ "severity": { "type": "string", "enum": ["block", "warn"] },
54
+ "allow_flag": { "type": "string" },
55
+ "note": { "type": "string" }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
@@ -35,6 +35,16 @@
35
35
  "type": "array",
36
36
  "items": { "type": "string" }
37
37
  },
38
+ "lint": {
39
+ "description": "v2.7+ · 仅 --task;不参与 may_start_30",
40
+ "type": ["object", "null"],
41
+ "properties": {
42
+ "ok": { "type": "boolean" },
43
+ "errors": { "type": "array" },
44
+ "warnings": { "type": "array" },
45
+ "suppressed": { "type": "boolean" }
46
+ }
47
+ },
38
48
  "gates": {
39
49
  "type": "array",
40
50
  "items": {