@cyning/harness 2.6.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,20 @@
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
+
7
21
  ## [2.6.0] - 2026-07-24
8
22
 
9
23
  ### Added
@@ -21,7 +35,7 @@
21
35
 
22
36
  - 背景:机械化率审计 G4(rethink 矩阵 P1 · 第三波);与 G1 同命令、分开交付。
23
37
  - minor · 无 verify 行为变更 · `npm test` 含既有回归全绿
24
- - **待发布**:`@cyning/harness@2.6.0`(维护者 CLOSE npm publish + tag)
38
+ - **已发布**:`@cyning/harness@2.6.0`(npm `latest` · 2026-07-24)· tag `v2.6.0` 已推送
25
39
 
26
40
  ## [2.5.0] - 2026-07-24
27
41
 
@@ -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 一等公民接口(分发放大器)
@@ -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,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"
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]`);
@@ -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/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.6.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": {