@cyning/harness 2.2.0 → 2.3.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,36 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.3.0] - 2026-07-24
8
+
9
+ ### Added
10
+
11
+ - **`harness task lint`**:task md 结构闸(G1)+ 文本规则包(G3)。E 级(exit 2):E1 元信息/task_slug · E2 状态行 · E3 验收标准含勾选项 · E4 失败路径节 · E5 自检结论节 · E6 绝对本机路径(带行号)· E7 slug 一致性;W 级(仅 warn):W1 状态词表外 token · W2 缺人工闸节 · W3 自检结论占位。`--json` 统一契约 `{ok, errors[], warnings[], file, slug}`(errors 元素 `{rule, message, line?}`)。
12
+ - 独立命令 · 不接入 `verify`(不误伤存量在途 task);10 帽交接物新增「产出 task 须 task lint PASS」。
13
+
14
+ ### Changed
15
+
16
+ - `lib/task-meta.js` 上提共享规则(`STATUS_RE` / `PLACEHOLDER_RE` / `UNCHECKED_RE` / `CHECKBOX_RE` / 状态词表),`task-close.js` 改调共享层(行为不变)。
17
+ - **`extractSection` 起始标记锚定行首**:修复表格/正文中「\`## 验收标准\`」式文本提及被误判为节(dogfood 自身 task 时发现;close 的检查 2/3 同享更稳)。
18
+
19
+ ### Notes
20
+
21
+ - 背景:机械化率审计 `docs/rethink/2026-07-mechanization-rate/`(缺口 G1+G3 · 46% 规范零机械)。
22
+ - dogfood:工作区 17 个 active task 全量 lint,15 个存在真实结构缺口(E5×13 · E4×9 · E1×8 · E6×6 · E3×6 · E7×3 · E2×3)——矩阵预言被数据证实。
23
+ - minor · 无 CLI breaking · `npm test` 108 全绿 · SPEC `docs/spec/SPEC-task-lint-structure-gate_v1.md`
24
+
25
+ ## [2.2.1] - 2026-07-22
26
+
27
+ ### Fixed
28
+
29
+ - **slug 一致性误报**:`task close` 检查 4 与 `task lint-done` 的 slug 比较现在对下划线/连字符惯例等价(`task_cyning_harness_a5_*_v1.md` ↔ `cyning-harness-a5-*`,工作区实测命名惯例)。dogfood 本仓 task 时发现:2.2.0 严格相等比较会把全部工作区 task 误 BLOCKED。
30
+ - `lib/task-meta.js` 新增 `normalizeSlug`;两命令双侧规范化后比较。
31
+
32
+ ### Notes
33
+
34
+ - patch · 无 CLI breaking · `npm test` 94 全绿
35
+ - **已发布**:`@cyning/harness@2.2.1`(npm `latest` · 2026-07-22)· tag `v2.2.1` 已推送
36
+
7
37
  ## [2.2.0] - 2026-07-22
8
38
 
9
39
  ### Added
@@ -17,6 +47,7 @@
17
47
 
18
48
  - 背景:ops-desk-api 连续 4 任务 30 漏落 invoke(2026-07-20 根因分析)——invoke 留档原是纯 Prompt 层纪律,本版补上机械闸。
19
49
  - minor · 无 CLI breaking · `npm test` 91 全绿 · SPEC `docs/spec/SPEC-task-close-invoke-gate_v1.md`
50
+ - **已发布**:`@cyning/harness@2.2.0`(2026-07-22)· tag `v2.2.0` 已推送;**已被 2.2.1 取代**(slug 规范化修复,业务仓请直接升 2.2.1)
20
51
 
21
52
  ## [2.1.1] - 2026-06-30
22
53
 
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: rethink-mechanization-rate-01-big-directions
3
+ description: 大方向判断 · 机械化率框架的提出与四个改进方向排序 · 何时读:理解本系列的顶层逻辑
4
+ ---
5
+
6
+ # 01 · 大方向:从「事故驱动补闸」到「机械化率驱动改进」
7
+
8
+ > **简介**:本文是 2026-07-24 战略讨论的落盘版。核心命题:cyning-harness 的护城河是**纪律的机械化率**——明文纪律有多少被代码强制,而不是靠 Agent 自觉。当前它在被动挨打:每个缺口都要等一次线上事故来揭示。
9
+
10
+ ## 1. 核心判断
11
+
12
+ v2.2 的故事(invoke 留档连续 4 任务失守 → `task close` 补闸)的本质:
13
+
14
+ 1. 纪律是**声明**出来的(`harness/prompts/*.md` 的自然语言),enforce 是**个案**补出来的;
15
+ 2. invoke 绝不是唯一一条纯 Prompt 层纪律——reviews 留档、思考轮槽位、task 结构完整性、git 行为纪律……都没有覆盖矩阵;
16
+ 3. 缺口的发现机制是**用户追问**(2026-07-20「为何 invokes/by-task 无留档」),这是系统最差劲的传感器。
17
+
18
+ **推论**:改进的第一优先级不是任何单点功能,而是让缺口**系统性地自己浮出来**。
19
+
20
+ ## 2. 四个方向
21
+
22
+ ### 方向一 · 机械化率审计(本系列 02–04)
23
+
24
+ 盘点规范语句 × 强制状态,产出覆盖矩阵。每个 prompt-only 缺口自动成为候选 task。
25
+ **定位:改进路线的生成器。** 成本极低,产出即路线图。
26
+
27
+ ### 方向二 · 生命周期状态机(架构脊柱)
28
+
29
+ CLI 今天是一袋动词,背后实际是**一个** task 生命周期:`draft → R1 → approved → 30 → 40 → done → archived`。`verify` 是 30 转移的前置检查,`close` 是 done→archived 转移——状态机是隐式的。
30
+ 显式化后(如 `lifecycle.yaml`:状态/转移/前置条件),方向一产出的每个新闸都有**天然挂点**;「这个 task 现在能做什么」一条命令可答(`verify --json` 的 handoff 是胚胎)。
31
+ **原则**:闸只挡实质、宽容形式(slug 事件教训:现实有两种命名惯例,闸太死就误伤)。
32
+
33
+ ### 方向三 · Agent 一等公民接口(分发放大器)
34
+
35
+ 库的真实用户越来越是 Agent。`--json` / `VERIFY: PASS` / `CLOSE: PASS` 已出现但各命令各搞各的。
36
+ 统一机器契约(ok/blockers/gates/next_action 同 schema + exit code 语义一致),成熟后包 `harness mcp` —— 是远期「Agent 治理适配器层」的最小可行版。
37
+ **排序约束**:契约不稳时上 MCP = 把内部混乱固化成 API,故排在一、二之后。
38
+
39
+ ### 方向四 · HGM 消费者(按需拉动,暂缓)
40
+
41
+ G1(events/snapshot/axioms)与 invoke_index 犯同一种病:**基础设施先行,消费者缺席**。G2(timeline/patterns/SQLite)只在有具体待答问题时启动——而方向一会替你把问题问出来(「哪些 task 跳过 40?」「draft 滞留多久?」)。
42
+
43
+ ## 3. 排序与逻辑
44
+
45
+ ```
46
+ 方向一(审计) → 方向二(状态机) → 方向三(Agent 契约/MCP)
47
+ 产出生成器 让补闸变便宜 语义稳定后放大
48
+ 方向四 G2:被方向一的问题拉动后再启动
49
+ ```
50
+
51
+ 每一步让下一步更便宜,且全程可 dogfood——是库自身哲学(文件真值 → 机械执行 → 可选投影)的自洽延伸。
52
+
53
+ ## 4. 反方向纪律(同等重要)
54
+
55
+ 库最大的风险不是缺功能,而是**治理变成负担**——闸多到 Agent 绕过它。每个新闸须过三问(task-close 式检验):
56
+
57
+ 1. **挂点对吗**?(检查时刻必须晚于产物存在时刻——verify 查 invoke 永远 fail 的教训)
58
+ 2. **误报率可控吗**?(slug 教训:宽容形式差异,只挡实质)
59
+ 3. **有泄压阀吗**?(`--allow-unchecked` 模式:可豁免、须留痕)
60
+
61
+ ## 5. 与库自述路线的关系
62
+
63
+ - 不与 `ROADMAP_TO_AGENT_GOVERNANCE`(proposal)冲突:方向三是其阶段 1 的廉价前置;
64
+ - 不与 `methodology/ROADMAP_v1_zh.md`(L2 真值)冲突:G2 本就在 v2.x 序列内,本文只主张「消费者先行」;
65
+ - 方向一/二是对主轨的**质量投资**,不改变 semver 轨道划分。
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: rethink-mechanization-rate-02-discipline-inventory
3
+ description: 逐文件逐条盘点包内 prompts 规范语句 × 强制状态(mechanical/partial/prompt-only)· 何时读:查某条纪律是否有机械闸
4
+ ---
5
+
6
+ # 02 · 规范语句盘点:每条纪律有没有机械闸
7
+
8
+ > **简介**:对 `harness/prompts/`(npm 分发的 Starter 子集:10/22/30/40 四帽 + 2 FRAGMENT)与 `harness/invokes/TEMPLATE_invoke.md` 的全部规范语句逐条编号、分类。分类标准:**mechanical** = 有代码消费且可 fail;**partial** = 有代码辅助但关键语义靠自觉;**prompt-only** = 零代码消费(纯自然语言纪律)。机制编号见 README(M1–M10)。
9
+
10
+ ## 图例
11
+
12
+ | 标记 | 含义 |
13
+ |---|---|
14
+ | ✅ mechanical | 代码强制 · 可 exit≠0 |
15
+ | 🟡 partial | 有机制但挡不住核心违规 |
16
+ | ❌ prompt-only | 纯 Prompt 层(invoke 失守前的状态) |
17
+
18
+ ---
19
+
20
+ ## A · `30-execute-code.md`(执行帽 · 纪律密度最高)
21
+
22
+ | # | 规范语句(摘要) | 状态 | 机制 / 缺口分析 |
23
+ |---|---|---|---|
24
+ | A1 | HG pending → 拒开工、禁改码 | ✅ | M1 awk 三闸 + M2 verify exit 2(v2.1.1 已修 backtick 误判) |
25
+ | A2 | 开工前首输出 GATE_VERIFY 闸扫描表 | 🟡 | M2 提供机械判定,但「Agent 是否真先输出表再动码」无检查 |
26
+ | A3 | 真值在 task 表;声称 approved vs 表 pending → STOP | 🟡 | M1/M2 读表为真值;「用户声称」侧无法机械比对(也不应) |
27
+ | A4 | `test_strategy: required` → 先可失败测试再改实现 | 🟡 | M3 D5 只探测测试文件**存在**;「先红后绿」顺序无机制 |
28
+ | A5 | 运行验证命令 + 回填 `### 自检结论` | ✅(v2.2+) | M5 检查非占位符;「命令真跑过」内核仍靠 40 诚实(不可机械,见 G7) |
29
+ | A6 | invoke 快照落盘 `invokes/by-task/<slug>/` | ✅(v2.2+) | M5(归档闸)+ M6(CI 兜底)—— 本系列起点 |
30
+ | A7 | 归档只能 `task close` PASS 后进行 | ✅(v2.2+) | M5;手动 `mv` 旁路无法禁止,M6 兜底发现 |
31
+ | A8 | HG-GRAPH-MODULES pending → 禁改码 | ✅ | M1(gate-check --graph) |
32
+ | A9 | 缺验收/failure_paths/必读 → 仅输出阻塞清单 | ❌ | **无 task md 结构检查** → 缺口 G1 |
33
+ | A10 | 交接物 commit 仅本轮路径、禁 `git add -A` | ❌ | 无 git 行为层机制 → 缺口 G6(行为层,难) |
34
+
35
+ ## B · `40-self-check.md`(自检帽)
36
+
37
+ | # | 规范语句 | 状态 | 机制 / 缺口分析 |
38
+ |---|---|---|---|
39
+ | B1 | 逐条对照验收标准标记 pass/fail | 🟡 | M5 只查「无未勾选」;勾选真实性靠 40 |
40
+ | B2 | 必须运行验证命令并摘要退出码 | ❌(内核) | 「真跑过」无证据要求 → 缺口 G7(执行证据) |
41
+ | B3 | 必须回填 `### 自检结论(执行者)` | ✅(v2.2+) | M5 占位符检查 |
42
+ | B4 | 禁止改 `docs/tasks/`、`reviews/`、`invokes/by-task/`(S2) | ❌ | S2 无写保护;M5 是唯一合规写者但无法挡其他写者 → 缺口 G6 |
43
+ | B5 | 不凭记忆声称「测过」 | ❌ | 不可机械(诚实纪律,设计上留在 Prompt 层) |
44
+
45
+ ## C · `22-task-audit.md`(审核帽)
46
+
47
+ | # | 规范语句 | 状态 | 机制 / 缺口分析 |
48
+ |---|---|---|---|
49
+ | C1 | 必须落盘 `reviews/task_<slug>_audit_R<n>_<date>.md` | ❌ | **零机制**(v2.2 task 明确列为非范围)→ 缺口 G2 |
50
+ | C2 | HG-AUDIT-R1 pending 时禁止附 30 Prompt | ❌ | 审查文内容无检查 → G2 同族 |
51
+ | C3 | 思考轮审查不通过 → 退回 10 · 下一棒禁附 30 | ❌ | 流程纪律,prompt-only |
52
+ | C4 | 零阻塞写已核对项;终轮写签收/关闭 | ❌ | 审查文结构无 lint |
53
+
54
+ ## D · `10-requirements.md`(需求帽)
55
+
56
+ | # | 规范语句 | 状态 | 机制 / 缺口分析 |
57
+ |---|---|---|---|
58
+ | D1 | task 必含验收标准 / failure_paths / 非范围 / 依赖 | ❌ | **task md 无 schema**(M4 只验 sidecar JSON,不验 md 正文)→ 缺口 G1 |
59
+ | D2 | 不写绝对本机路径 | ❌ | 可机械(grep `/Users/`、`/home/` 等)→ 缺口 G3(易做) |
60
+ | D3 | 预置 R0–R5 思考轮槽 + 思考轮控制表 | ❌ | 槽位/控制表结构无检查 → 缺口 G4 |
61
+ | D4 | 缺验收/failure_paths → 仅输出阻塞清单 | ❌ | 同 A9 → G1 |
62
+
63
+ ## E · FRAGMENT_30(闸扫描与开工块)
64
+
65
+ | # | 规范语句 | 状态 | 机制 / 缺口分析 |
66
+ |---|---|---|---|
67
+ | E1 | invoke 中禁止预写 `HG-AUDIT-R1 approved` | ❌ | 可机械(grep invoke 文件字面句)→ 缺口 G3(易做) |
68
+ | E2 | 用户「确认 approved」= 须核验非事实 | 🟡 | M2 读表为真值;行为侧同 A3 |
69
+
70
+ ## F · `TEMPLATE_invoke.md` 纪律表
71
+
72
+ | # | 规范语句 | 状态 | 机制 / 缺口分析 |
73
+ |---|---|---|---|
74
+ | F1 | 落盘路径 `by-task/<task_slug>/` | ✅(v2.2+) | M5/M6 |
75
+ | F2 | 同帽追问不新增 invoke;打回用 `_r2` | ❌ | 命名纪律无检查(成本低收益低,可留 prompt 层) |
76
+ | F3 | 落盘 + task 回填后再 commit | ❌ | git 行为层 → G6 |
77
+
78
+ ## G · 机制已覆盖(对照组 · 证明模式有效)
79
+
80
+ | 规范 | 机制 |
81
+ |---|---|
82
+ | sidecar JSON schema | M4 `task check` ✅ |
83
+ | depends_on 禁环 | M4 `--no-circular` ✅ |
84
+ | graph YAML ↔ graph.json 一致 | M7 `graph yaml check` ✅ |
85
+ | manifest 版本可升级提示 | `check` / `upgrade` ✅ |
86
+ | S5 git-clean | M2/M3 warn(🟡 只警不挡,属刻意设计) |
87
+
88
+ ---
89
+
90
+ ## 缺口汇总(详表见 03)
91
+
92
+ | 缺口 | 内容 | 来源条目 | 可机械化度 |
93
+ |---|---|---|---|
94
+ | **G1** | task md 结构 lint(必填节:元信息/状态行/验收/failure_paths/自检结论/思考轮槽) | A9, D1, D4, D3(部分) | 高(解析与 M5 共享) |
95
+ | **G2** | reviews 留档存在性 + R 轮次命名 | C1–C4 | 高(glob 即可,挂 close 或 verify) |
96
+ | **G3** | 文本纪律 grep 闸(绝对路径、invoke 预写 approved) | D2, E1 | 高(trivial) |
97
+ | **G4** | 思考轮槽位/控制表结构检查 | D3 | 中(格式变体多,须宽容) |
98
+ | **G6** | git 行为层(仅本轮路径、S2 写保护、Git 仅 Lead) | A10, B4, F3 | 低(需 hook/action 层,属方向三) |
99
+ | **G7** | 执行证据(验证命令真跑过的留痕) | B2, A5 内核 | 低-中(需 runner 包装,属方向二/三) |
100
+
101
+ > 编号说明:G5 预留(首轮盘点未用上,留给复盘中新发现的同族缺口)。
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: rethink-mechanization-rate-03-coverage-matrix
3
+ description: 机械化率统计矩阵 + 缺口聚类 + 候选闸优先级排序 · 何时读:看覆盖率全貌数字、决定下一个补闸任务
4
+ ---
5
+
6
+ # 03 · 机械化率矩阵与缺口优先级
7
+
8
+ > **简介**:把 02 的逐条盘点聚合成数字全貌(按帽/按状态),并将 6 个缺口按「可机械化度 × 风险 × 成本」排序,给出候选闸形态与挂点建议。结论喂给 04(立项建议)。
9
+
10
+ ## 1. 覆盖率总览(Starter 子集 · 26 条规范语句)
11
+
12
+ | 状态 | 条数 | 占比 | 备注 |
13
+ |---|---|---|---|
14
+ | ✅ mechanical | 8 | 31% | 其中 4 条是 v2.2 本周刚补(A5/A6/A7/B3/F1 中 4 条) |
15
+ | 🟡 partial | 6 | 23% | 机制在场但核心语义靠自觉 |
16
+ | ❌ prompt-only | 12 | 46% | 零代码消费 |
17
+
18
+ **按帽分布**:
19
+
20
+ | 文件 | ✅ | 🟡 | ❌ | 评述 |
21
+ |---|---|---|---|---|
22
+ | 30-execute-code | 5 | 3 | 2 | v2.2 后机械化率最高(开工闸 + 归档闸双锚点) |
23
+ | 40-self-check | 1 | 1 | 3 | 「真跑过」无证据是最大黑洞(G7) |
24
+ | 22-task-audit | 0 | 0 | 4 | **全帽零机械**——reviews 留档与 invoke 失守同构 |
25
+ | 10-requirements | 0 | 0 | 4 | task md 无结构闸(G1),下游所有帽都假设它成立 |
26
+ | FRAGMENT_30 | 0 | 1 | 1 | E1 可 trivial 补 |
27
+ | TEMPLATE_invoke | 1 | 0 | 2 | 命名/commit 纪律可留 prompt 层 |
28
+
29
+ **读法**:22 和 10 两帽合计 8 条 ❌,且都处在链路**上游**——task 结构(G1)和审查留档(G2)是下游一切闸的输入假设。invoke 失守(A6)只是同构问题在下游的第一个显形。
30
+
31
+ ## 2. 缺口优先级
32
+
33
+ > 评分:风险 = 失守后流程失真程度;成本 = 实现+误报治理;均三档(高/中/低)。
34
+
35
+ | 优先级 | 缺口 | 风险 | 成本 | 候选闸形态 | 挂点 |
36
+ |---|---|---|---|---|---|
37
+ | ~~P0~~ ✅ | **G1 · task md 结构 lint** | 高(下游全部闸的输入假设) | 低(解析与 M5 共享) | **已落地 v2.3.0**:`harness task lint`(E1–E7/W1–W3)· 不接入 verify(dogfood 后另议) | 10 产出时 |
38
+ | **P0** | **G2 · reviews 留档闸** | 高(与 invoke 失守同构,且是 HG-AUDIT-R1 的签署依据) | 低(glob 命名模式) | `verify` 增检查:`reviews/task_<slug>_audit_R1_*.md` 存在(may_start_30 加条件);可选 `close` 增第 6 项 | verify(30 前)· close(归档前) |
39
+ | ~~P1~~ ✅(task 部分) | **G3 · 文本纪律 grep 闸** | 中 | 极低(正则) | **已落地 v2.3.0**(E6 绝对路径);invoke 预写 approved grep 缓做(SPEC R2:误报不可控) | 同 G1 |
40
+ | **P1** | **G4 · 思考轮结构检查** | 中(阶段 C 纪律,漏槽=思考轮形同虚设) | 中(格式变体多,须宽容:只查槽位存在与控制表字段,不查内容质量) | 并入 `task lint`:R0–R5 槽 + `actual_last_round`/`early_stop`/`residual_risks` 字段存在性 | 10 产出时 |
41
+ | **P2** | **G7 · 执行证据** | 高但难(「真跑过」本质要 runner 见证) | 高 | `harness run -- <验证命令>`:包装执行、留 exit code + 输出摘要到 `.cyning-harness/runs/`,40 回填引用 run id | 方向二状态机内再做 |
42
+ | **P3** | **G6 · git 行为层** | 中 | 高(需 hook/平台集成) | 暂留 prompt 层;远期 Agent 契约(方向三)的行为审计范畴 | 方向三 |
43
+
44
+ ## 3. 排序逻辑
45
+
46
+ 1. **G1+G2 先行**:两者是「文档存在性与结构」闸,与 v2.2 已验证的模式完全同构(解析 md + glob + exit 2),风险最高、模式最熟、误报治理经验已有(slug 规范化教训直接复用:形式宽容、实质严格)。
47
+ 2. **G3 搭 G1 的车**:同为 `task lint` 的正则规则包,边际成本趋零。
48
+ 3. **G4 与 G1 同命令但分开交付**:宽容度设计需要一轮 dogfood,不与 G1 抢同一版本。
49
+ 4. **G7/G6 不硬做**:它们的机械化本质是「见证执行」与「约束行为」,超出文档闸范畴——这正是方向二(状态机)与方向三(Agent 契约)的立项理由,不要降维成 grep。
50
+
51
+ ## 4. 反模式提醒(写进每个候选闸的验收)
52
+
53
+ - **挂点检查**:闸的触发时刻必须晚于其检查物的存在时刻(verify 查 invoke 的错位教训);
54
+ - **形式宽容**:命名/大小写/下划线-连字符差异不挡(slug 教训);
55
+ - **泄压阀**:每个 BLOCKED 须有对应 `--allow-*` 豁免 + warn 留痕;
56
+ - **不误伤存量**:新闸默认只拦新流转,存量由 `lint-done` 式集合 diff 兜底(不 retroactive 阻塞)。
57
+
58
+ ## 5. 数字的诚实边界
59
+
60
+ - 26 条是**人工盘点**(2026-07-24),分类判断含主观成分(尤其 🟡/❌ 边界);矩阵本身应落盘为 YAML 成为可维护资产,后续随版本更新(→ 04 建议);
61
+ - 「mechanical」只代表**存在**机制,不代表机制**有效**(如 D5 只探测测试文件存在而非真失败驱动)——下一层是机制质量审计,本轮不做。
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: rethink-mechanization-rate-04-next-steps
3
+ description: 结论与立项建议 · 缺口到 SPEC/task 的转化路径 + 与状态机/Agent 契约的接口 · 何时读:评审是否立项、起草 SPEC 前
4
+ ---
5
+
6
+ # 04 · 结论:从矩阵到立项
7
+
8
+ > **简介**:基于 01 的框架与 03 的矩阵,给出可执行的立项建议:哪些缺口立刻转 SPEC、以什么节奏、与方向二(状态机)/方向三(Agent 契约)如何衔接。**本文不产生任何闸的变更**;立项须走正式链(10-spec → 人签 → 00 起草 task → HG 闸)。
9
+
10
+ ## 1. 建议立项序列
11
+
12
+ ### 第一波 · `task lint`(G1 + G3)· 建议 v2.3.0
13
+
14
+ **一句话**:给 task md 一个结构闸——下游所有帽的输入假设从此可机械验证。
15
+
16
+ - 必填节检查:Harness 元信息(含 task_slug)、`> **状态**` 行、`## 验收标准`、`failure_paths`、`### 自检结论`、(阶段 C)R0–R5 槽 + 思考轮控制表字段
17
+ - 文本规则包(G3):禁绝对本机路径(`/Users/`、`/home/`、`C:\`);invoke 文件禁预写 `HG-AUDIT-R1 approved` 字面句
18
+ - 解析层与 `task-close.js` 共享(`extractSection`/`extractTaskSlug` 已在 task-meta.js)
19
+ - 挂点:10 产出时自查 + `verify` 聚合(30 前)
20
+ - 验收要点:形式宽容(slug 规范化同款)+ `--allow-*` 泄压 + 不误伤存量(只 lint 指定文件)
21
+
22
+ ### 第二波 · reviews 留档闸(G2)· 建议 v2.3.0 同波或 v2.3.1
23
+
24
+ **一句话**:22 帽目前是「全帽零机械」——invoke 失守的同构问题,趁模式还热补掉。
25
+
26
+ - `verify` 增检查:`docs/harness/reviews/task_<slug>_audit_R1_*.md` 存在(`findReviewPath` 已存在于 task-meta.js,零新解析成本)→ 不满足则 `may_start_30: false`
27
+ - `close` 可选增第 6 项检查(归档时 R1 审查文应仍在)
28
+ - 注意与现有流程的兼容:`findReviewPath` 已被 `verify --json` 消费(handoff.review_path),本次是把它从「信息」升级为「闸」
29
+
30
+ ### 第三波 · 思考轮结构检查(G4)· 建议 v2.4.0
31
+
32
+ - 并入 `task lint` 但作为独立规则组;宽容度设计需 dogfood 一轮(工作区 task 的槽位写法变体多)
33
+ - 只查结构(槽位存在、控制表字段),**不查**内容质量——质量判定永远留在 22 帽
34
+
35
+ ### 暂缓(写好立项理由,别顺手做)
36
+
37
+ | 缺口 | 暂缓理由 | 归属 |
38
+ |---|---|---|
39
+ | G7 执行证据 | 本质是 runner 见证(`harness run -- <cmd>`),需要方向二状态机的「执行」概念先行 | 方向二落地时 |
40
+ | G6 git 行为层 | 本质是行为约束(hook/平台集成),grep 降维做会误报成灾 | 方向三 Agent 契约 |
41
+
42
+ ## 2. 与方向二(状态机)的接口
43
+
44
+ 第一波/第二波**不依赖**状态机先行——它们是现有命令的自然扩展(lint/verify/close)。但有一个前置动作建议同波做:
45
+
46
+ - **`lifecycle.yaml` 最小版**:只声明状态与转移清单(draft→R1→approved→30→40→done→archived),不实现引擎;让每个新闸在文件里登记自己守卫的转移。这是方向二的「文档先行」形态,成本一行 YAML,收益是后续所有闸的挂点不再靠临场设计。
47
+
48
+ ## 3. 与方向三(Agent 契约)的接口
49
+
50
+ - 第一波起,所有新命令/新检查的 `--json` 输出**直接按统一契约设计**(`{ok, blockers[], warnings[], gates?, next_action?}`),不等方向三立项再返工;
51
+ - `CLOSE:`/`VERIFY:` 末行协议保留(人读 + grep 双通道),JSON 走 `--json`。
52
+
53
+ ## 4. 机械化率矩阵的资产化
54
+
55
+ - 把 03 的矩阵落成 `discipline-coverage.yaml`(语句 id / 出处 / 状态 / 机制 / 缺口 / 备注),随版本维护;
56
+ - 每个新闸落地 = 矩阵里一行 ❌→✅——这就是方向一承诺的「改进路线自动生成」的实体;
57
+ - 远期可给 `harness audit` 加 `--discipline` 视图直接渲染该 YAML(机制质量审计也挂这里)。
58
+
59
+ ## 5. 不做什么(本轮明确排除)
60
+
61
+ - 不做工作区 Extended 帽(00/10-spec/20-spec/50/handoff)盘点——方法已验证,需要时照搬 02 格式另开一轮;
62
+ - 不做机制**质量**审计(mechanical ≠ effective,如 D5 只探测测试文件存在)——矩阵 YAML 预留 `mechanism_quality` 字段即可;
63
+ - 不改任何 `harness/prompts/` 正文——本系列是审计不是修订;纪律措辞问题随各立项 task 顺带修。
64
+
65
+ ## 6. 下一棒
66
+
67
+ 若维护者认可本结论:
68
+
69
+ 1. 对**第一波(task lint · G1+G3)**走 10-spec:复制 `SPEC_TEMPLATE_v1_zh.md` → `docs/spec/SPEC-task-lint-structure-gate_v1.md`,R0 直接引用本系列 02/03;
70
+ 2. 第二波可同 SPEC 或独立 SPEC(建议独立,闸的验收各自可 dogfood);
71
+ 3. 本目录归档为思考留档,不随 task 关闭而删除——它是后续「为什么做这个闸」的上下文。
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: rethink-mechanization-rate-readme
3
+ description: 索引与背景 · 2026-07 机械化率审计思考系列 · 何时读:想了解本系列全貌/回溯思考过程
4
+ ---
5
+
6
+ # 机械化率审计 · 思考系列索引(2026-07)
7
+
8
+ > **简介**:本目录记录 cyning-harness「如何改进」的一次完整战略思考 —— 从 invoke 留档失守的根因出发,提出「机械化率」框架,盘点包内全部规范语句的强制状态,产出缺口清单与改进方向。**性质:思考留档(rethink),非 SPEC、非 task**;后续若立项,按 10-spec → 00 起草 task 链走正式流程。
9
+
10
+ ## 背景(30 秒版)
11
+
12
+ - 2026-07-20:ops-desk-api 连续 4 任务 30 漏落 invoke → 根因 = 纯 Prompt 层纪律零机械闸
13
+ - 2026-07-22:v2.2.0/2.2.1 补上 `task close` / `task lint-done` 机械闸(dogfood 自身关账)
14
+ - 2026-07-24:追问「还有多少条这样的纪律?」→ 启动机械化率审计(本系列)
15
+
16
+ ## 文档地图
17
+
18
+ | 文档 | 内容 | 读它当你想… |
19
+ |---|---|---|
20
+ | [01_big_directions.md](01_big_directions.md) | 大方向判断:机械化率框架 + 四个改进方向 + 排序 | 理解「为什么是机械化率」 |
21
+ | [02_discipline_inventory.md](02_discipline_inventory.md) | 逐文件逐条盘点 harness/prompts/ 规范语句 × 强制状态 | 查某条纪律有没有机械闸 |
22
+ | [03_coverage_matrix.md](03_coverage_matrix.md) | 覆盖率统计 + 缺口聚类 + 候选闸优先级 | 看全貌数字与下一步做什么 |
23
+ | [04_next_steps.md](04_next_steps.md) | 结论:缺口 → SPEC/task 候选 · 与状态机/Agent 契约的接口 | 立项前评审 |
24
+
25
+ ## 过程记录(回溯用)
26
+
27
+ | 日期 | 动作 | 产出 |
28
+ |---|---|---|
29
+ | 2026-07-24 | 大方向讨论(会话) | 01 |
30
+ | 2026-07-24 | 读包内 prompts 全部 5 帽 + 2 FRAGMENT + TEMPLATE_invoke;映射 M1–M10 机制 | 02 |
31
+ | 2026-07-24 | 矩阵聚合 + 缺口聚类 | 03 |
32
+ | 2026-07-24 | 结论与立项建议 | 04 |
33
+ | 2026-07-24 | **第一波落地**:G1+G3(task 部分)→ `task lint` v2.3.0(03 已勾 ✅);dogfood 数据:17 个 active task 中 15 个存在真实结构缺口 | 03 更新 |
34
+
35
+ **机制编号约定**(02/03 引用):M1 gate-check.sh · M2 verify · M3 audit · M4 task check · M5 task close · M6 task lint-done · M7 graph yaml · M8 graph HGM · M9 harness-sync · M10 package-scripts
36
+
37
+ **范围声明**:盘点对象为 **npm 包分发的 Starter 子集**(`harness/prompts/`)。工作区 Extended 帽(00/10-spec/20-spec/50/handoff)不在本轮,方法可复用。
@@ -0,0 +1,153 @@
1
+ # SPEC:task lint · task md 结构闸 + 文本规则包(v1)
2
+
3
+ > **状态**:`done`(10-spec R0–R5 已回填 · **维护者已签收 2026-07-24** · → 00 起草 task)
4
+ > **track**:`feature`
5
+ > **关联图谱**:无(纯 Harness 工具链)
6
+ > **上游思考**:[`docs/rethink/2026-07-mechanization-rate/`](../rethink/2026-07-mechanization-rate/)(缺口 G1 + G3 · P0/P1)
7
+ > **下游**:SPEC 签收 → 00 起草 task → 10-task
8
+
9
+ ---
10
+
11
+ ## Harness 元信息
12
+
13
+ | 字段 | 值 |
14
+ |------|-----|
15
+ | **spec_slug** | `task-lint-structure-gate` |
16
+ | **test_strategy** | `required` |
17
+ | **test_strategy_note** | 每条 lint 规则均可 fixture 驱动 fail/pass 路径;机械闸无测试不上线 |
18
+ | **entry_invoke_10_spec** | `Projects/docs/harness/invokes/by-task/cyning-harness-task-lint-structure-gate/invoke_20260724_10_spec_task_lint_structure_gate.md` |
19
+ | **entry_invoke_00_draft** | 工作区 `docs/harness/prompts/PROMPT_00_draft_spec_or_task_v1_zh.md` |
20
+
21
+ ---
22
+
23
+ ## 1. 背景与目标
24
+
25
+ 机械化率审计(rethink 02/03)发现:**task md 正文没有任何结构闸**——`task check` 只验 sidecar JSON,10 帽规定的必填节(元信息/状态行/验收标准/failure_paths/自检结论)缺失或占位时,下游所有帽(22 审、verify、close)都在消费一个未验证的输入假设。22/10 两帽合计 8 条规范全帽零机械,属「invoke 失守」同构问题,只是还没爆。
26
+
27
+ **目标**:新增 `harness task lint` —— task md 的结构与文本规则检查器(G1 + G3),让 10 产出、22 审查、维护者自查有一条机械命令可跑;每条规则有 fail 路径测试,形式宽容、实质严格。
28
+
29
+ **第一波刻意小步**:只做独立命令,不改 `verify` 聚合行为(不误伤存量在途 task,见 R2 裁定)。
30
+
31
+ ## 2. 范围
32
+
33
+ - **D1 · `lib/task-lint.js`(新增)**:规则引擎 + 规则集(§4 对照表),输出结构化 `{ok, errors[], warnings[], file, slug}`。
34
+ - **D2 · CLI 路由(`lib/cli.js`)**:`task lint --file PATH [--json]`;`LINT: PASS/FAIL · <file>` 末行协议;help/usage 同步。
35
+ - **D3 · 测试**:`test/task-lint.test.js` fixture 覆盖每条规则的 fail/pass + warn 路径。
36
+ - **D4 · dogfood 报告**:对工作区 `Projects/docs/harness/tasks/active/*.md` 全量跑一遍,结果(通过/违规分布)写进 task 的自检结论——**只报告,不要求存量合规**。
37
+ - **D5 · 文档**:CHANGELOG + 版本 **v2.3.0**(minor · 新增子命令);`harness/prompts/10-requirements.md` 交接物节补一行「产出 task 须 `task lint` PASS」。
38
+
39
+ ## 3. 非范围
40
+
41
+ - **不改 `verify` 聚合**(lint 结果不接入 may_start_30;dogfood 后另议,见 R2 弃选项)
42
+ - **不做 G2**(reviews 留档闸)——第二波独立 SPEC
43
+ - **不做 G4**(思考轮槽位/控制表结构检查)——第三波,宽容度需独立 dogfood(对 04 列表的修正,见 R1)
44
+ - **不做 invoke 文件「预写 approved」grep**——误报不可控(见 R2 分析)
45
+ - 不自动修复(no autofix);不 lint 工作区 Extended 帽模板;不改 sidecar `task check` 行为
46
+
47
+ ---
48
+
49
+ ## 4. 验收标准(规则集即验收表)
50
+
51
+ **E 级(error · exit 2)**:
52
+
53
+ - [ ] E1 缺 `## Harness 元信息` 节或表内无 `task_slug`
54
+ - [ ] E2 缺 `> **状态**` 行(首 token 提取规则与 task-close 一致)
55
+ - [ ] E3 缺 `## 验收标准` 节,或节内无任何 `- [ ]`/`- [x]`/`- [X]` 勾选项
56
+ - [ ] E4 缺失败路径节(接受 `## 失败路径` 或含 `failure_paths` 的标题 —— 形式宽容)
57
+ - [ ] E5 缺 `### 自检结论` 节(内容为占位符**不**算 error —— draft 期占位合法)
58
+ - [ ] E6 正文含绝对本机路径(`/Users/`、`/home/`、`/root/`、`[A-Za-z]:\Users\`),输出行号
59
+ - [ ] E7 文件名 slug ≠ 元信息 task_slug(`normalizeSlug` 双侧规范化 · 复用 task-close 同款)
60
+
61
+ **W 级(warn · 不影响 exit)**:
62
+
63
+ - [ ] W1 状态行首 token 不在已知词表(draft/pending/in_progress/active/deferred/done/completed)
64
+ - [ ] W2 缺 `### 人工闸` 节(轻量 task 可豁免,提醒而非阻塞)
65
+ - [ ] W3 `### 自检结论` 为占位符(提示:close 前须回填)
66
+
67
+ **协议与行为**:
68
+
69
+ - [ ] 全过 exit 0 + `LINT: PASS · <file basename>`;任一 E 级 exit 2 + `LINT: FAIL · <file>` + 逐条列表(含行号可得时)
70
+ - [ ] `--json` 输出统一契约 `{ok, errors[], warnings[], file, slug}`(方向三契约的首个落地实例)
71
+ - [ ] `npm test` 全绿(每条 E/W 规则有 fail/pass 用例)
72
+ - [ ] dogfood:工作区 active tasks 全量 lint 报告落 task 自检结论
73
+ - [ ] CHANGELOG 记 v2.3.0
74
+
75
+ ---
76
+
77
+ ## 5. failure_paths
78
+
79
+ | 触发条件 | 系统行为 | 可重试 |
80
+ |----------|----------|--------|
81
+ | --file 缺失/文件不存在 | exit 1 · usage 错误(与 task close 一致) | 修正参数 |
82
+ | 任一 E 级违规 | exit 2 · `LINT: FAIL` · 逐条列出 · 不改文件 | 修复后重跑 |
83
+ | 仅 W 级 | exit 0 · warn 行列出 | — |
84
+ | 存量 task 大量违规 | 不影响(独立命令 · 无 verify 联动) | dogfood 报告记录 |
85
+ | 标题写法变体(如 `## 失败路径表`) | E4 判定含 `失败路径`/`failure_paths` 子串即过(宽容) | 误报则 dogfood 期调正则 |
86
+ | 业务仓未升级到 2.3.0 | 旧版 CLI 无此命令 · 行为不变 | upgrade |
87
+
88
+ ---
89
+
90
+ ## 6. 依赖与引用
91
+
92
+ - 复用:`lib/task-meta.js`(`parseHarnessMeta` / `extractSection` / `extractTaskSlug` / `normalizeSlug`)、`lib/paths.js`
93
+ - 挂点先例:`lib/task-close.js`(状态行/占位符/勾选解析规则同源——**抽到共享层而非复制**,见 R2)
94
+ - 缺口出处:`docs/rethink/2026-07-mechanization-rate/02_discipline_inventory.md`(A9/D1–D4/E1 条目)、`03_coverage_matrix.md` §2(G1/G3 行)
95
+ - 测试模式:`node --test` + mkdtemp fixture(`test/task-close.test.js` 模式)
96
+
97
+ ---
98
+
99
+ ## 7. 思考轮(10-spec 回填 · R0–R5)
100
+
101
+ ### R0 · 读入与约束
102
+
103
+ 读入:rethink 系列 02/03/04(G1+G3 缺口定义与优先级);task-close v2.2 实现(可复用解析与已踩过的坑:slug 规范化、占位符正则、状态词表);04 的波次划分。约束:本阶段只产 SPEC;第一波不改 verify 行为是维护者可见的保守选择。
104
+
105
+ ### R1 · 范围 / 非范围 / 场景
106
+
107
+ 场景:① 10 帽产出 task 后自查(主);② 22 R1 审查时辅助(审查文可引用 lint 结果作「已核对项」);③ 维护者批量体检存量。**对 04 的一处修正**:04 把「(阶段 C)R0–R5 槽 + 控制表字段」列入第一波必填节,但 03 把 G4 排 P1/第三波——两者张力按 **03 为准**裁定:思考轮结构检查整体归第三波。理由:工作区 task 的思考轮写法变体多(有的内嵌 §5、有的独立文件、有的只在 SPEC),宽容度设计需要独立 dogfood,不应搭上 P0 的车。
108
+
109
+ ### R2 · 方案对比
110
+
111
+ | 决策点 | 选项 | 裁定 | 理由 |
112
+ |---|---|---|---|
113
+ | 命令形态 | 独立 `task lint` / 并入 `task check` | **独立** | check=sidecar JSON · lint=md 正文,关注点不同;改名会 break check 现有用户 |
114
+ | verify 聚合 | 接入并阻塞 / 接入仅 warn / 不接入 | **不接入(本波)** | 存量在途 task 若不合规会被误 BLOCKED(违反不误伤存量);dogfood 报告出来后由维护者定 warn 或 block |
115
+ | invoke 预写 approved grep | 本波做 / 缓做 | **缓做** | 误报不可控:合规的 GATE_VERIFY invoke 快照**天然含** approved 记录(本仓 30/40 invoke 即如此),机械区分「预写指令」与「事后记录」脆弱,需单独设计 |
116
+ | 解析复用 | 共享 task-meta/抽公共函数 / task-lint 自写 | **共享,必要时把 task-close 内联逻辑上提** | 状态行/占位符/勾选三条规则与 close 同源,双份必然漂移(v2.2.1 slug 教训) |
117
+ | 状态词表 | 写死 / 可配置 | **写死 + W1 降级** | 词表来自实测 13 个 task;未知 token 只 warn 不挡 |
118
+ | E4 标题匹配 | 精确 `## 失败路径` / 子串宽容 | **子串宽容**(`失败路径` 或 `failure_paths`) | 实测两种写法都在用 |
119
+
120
+ ### R3 · 边界 / 失败语义 / 安全
121
+
122
+ - **E/W 分级**是第一波的核心防误报设计:结构缺失(E)挡,措辞/可选节(W)提醒;W 永不影响 exit code。
123
+ - **占位符语义分阶段**:`### 自检结论` 在 draft 期占位**合法**(W3 提醒),在 close 时刻才必须回填(M5 已挡)——同一内容在两个时刻不同判定,lint 只查存在性。
124
+ - **E6 绝对路径**:正则覆盖 macOS/Linux/Windows 三类;命中即报行号,不区分代码块内外(task 正文出现本机绝对路径无合法场景;dogfood 若发现反例再调)。
125
+ - **安全**:lint 纯只读,不写任何文件;无 git 动作。
126
+ - **存量**:dogfood 只报告不要求合规——预期工作区部分 legacy task 会 E3/E4 不过,这正是矩阵的下一批数据。
127
+
128
+ ### R4 · 验收 / 可测性 / test_strategy
129
+
130
+ `test_strategy: required`。fixture 驱动:每条 E 规则一个 fail fixture + 一个全过 fixture + W 规则各一。E4 变体(`## 失败路径` / `## failure_paths` / `## 失败路径表`)各一用例。E6 三类路径各一用例。dogfood 命令与输出落 task 自检结论(含违规计数,不要求为零)。
131
+
132
+ ### R5 · SPEC 签收就绪 · 是否可交 00 出 task
133
+
134
+ SPEC 自足:规则集逐条可测、范围/非范围清晰、两处与 04 的偏差(思考轮归第三波、invoke grep 缓做、verify 不接入)均留痕。**可交 00 起草 task**。图谱:纯工具链,无需 bootstrap。版本建议 v2.3.0。dogfood 产出将直接决定第二波(G2 reviews 闸)与 verify 联动的设计输入。
135
+
136
+ ### 思考轮控制
137
+
138
+ | 字段 | 值 |
139
+ |------|-----|
140
+ | `actual_last_round` | `R5` |
141
+ | `early_stop` | `no` |
142
+ | `early_stop_reason` | — |
143
+ | `residual_risks` | ① 存量 task 违规面未知(dogfood 前无法预估;已通过「不接入 verify」隔离风险);② E4/E6 正则可能有未预见误报(宽容设计 + dogfood 期可调);③ 与 task check 的职责边界需文档说清,避免用户混淆 |
144
+ | `round_extension_note` | — |
145
+
146
+ ---
147
+
148
+ ## 修订记录
149
+
150
+ | 日期 | 摘要 |
151
+ |------|------|
152
+ | 2026-07-24 | 10-spec R0–R5 同会话回填(维护者委派)· 基于 rethink 系列 G1+G3;修正 04 两处(思考轮归第三波 · invoke grep 缓做 · verify 不接入) |
153
+ | 2026-07-24 | **维护者签收** · 进入 00 起草 task |
@@ -31,6 +31,7 @@
31
31
  ## 交接物
32
32
 
33
33
  - 可粘贴进 `docs/tasks/active/task_*.md` 的正文块;并注明建议 `test_strategy`。
34
+ - 产出 task 须 `npx @cyning/harness task lint --file <task>` PASS(v2.3+ · 结构闸 E1–E7 / W1–W3)。
34
35
  - 承接 **22 审查**:按 `docs/harness/reviews/*_audit_*.md` 回填 task。
35
36
 
36
37
  ## OSS 阶段 C · 思考轮(Starter 摘要)
package/lib/cli.js CHANGED
@@ -12,6 +12,7 @@ import { runBash } from './run.js';
12
12
  import { checkTaskFile } from './task.js';
13
13
  import { closeTaskFile } from './task-close.js';
14
14
  import { lintDoneInvokes } from './task-lint-done.js';
15
+ import { lintTaskFile } from './task-lint.js';
15
16
  import { auditTarget } from './audit.js';
16
17
  import { mergePackageScripts } from './package-scripts.js';
17
18
  import {
@@ -45,6 +46,7 @@ function usage(version = 'unknown') {
45
46
  npx @cyning/harness task check --file PATH [--no-circular] [--registry DIR]...
46
47
  npx @cyning/harness task close --file PATH [--target PATH] [--yes] [--allow-unchecked]
47
48
  npx @cyning/harness task lint-done [--target PATH]
49
+ npx @cyning/harness task lint --file PATH [--json]
48
50
  npx @cyning/harness graph yaml compile --graph-id ID [--input DIR] [--output FILE]
49
51
  npx @cyning/harness graph yaml check --graph-id ID [--input DIR] [--graph-json FILE]
50
52
  npx @cyning/harness graph yaml compile --all [--input DIR]
@@ -64,6 +66,7 @@ function usage(version = 'unknown') {
64
66
  task check 校验 task.harness.v1.json sidecar · --no-circular 检测 depends_on 环
65
67
  task close 受闸归档:5 项机械校验(invoke/自检结论/勾选/slug/状态)后 mv active→done(v2.2+)
66
68
  task lint-done done 与 invokes/by-task slug 集合 diff · 缺失 exit 2(v2.2+)
69
+ task lint task md 结构闸:必填节/勾选/状态行/绝对路径/slug(v2.3+)
67
70
  graph yaml Inform 图谱 YAML 编译 / 校验(v1.1+)
68
71
  graph ingest 扫描业务仓 → 追加 HGM 事件(v2.0+)
69
72
  graph snapshot 事件重放 → graph/snapshot.json(v2.0+)
@@ -507,8 +510,12 @@ async function cmdTask(args) {
507
510
  await cmdTaskLintDone(rest);
508
511
  return;
509
512
  }
513
+ if (sub === 'lint') {
514
+ await cmdTaskLint(rest);
515
+ return;
516
+ }
510
517
 
511
- const err = new Error(`task 子命令未知: ${sub ?? '(空)'}\n用法: task check --file PATH · task close --file PATH [--target PATH] [--yes] [--allow-unchecked] · task lint-done [--target PATH]`);
518
+ const err = new Error(`task 子命令未知: ${sub ?? '(空)'}\n用法: task check --file PATH · task close --file PATH [--target PATH] [--yes] [--allow-unchecked] · task lint-done [--target PATH] · task lint --file PATH [--json]`);
512
519
  err.exitCode = 1;
513
520
  throw err;
514
521
  }
@@ -615,6 +622,57 @@ done 有而 invokes 无 → exit 2 列缺失;invokes 多出仅 warn。
615
622
  console.log('LINT-DONE: PASS');
616
623
  }
617
624
 
625
+ async function cmdTaskLint(args) {
626
+ if (args.includes('--help') || args.includes('-h')) {
627
+ console.log(`用法: npx @cyning/harness task lint --file PATH [--json]
628
+
629
+ task md 结构闸(G1)+ 文本规则包(G3)· 纯只读:
630
+ E 级(exit 2):E1 元信息/task_slug · E2 状态行 · E3 验收标准含勾选项
631
+ E4 失败路径节 · E5 自检结论节 · E6 绝对本机路径 · E7 slug 一致
632
+ W 级(仅 warn):W1 状态 token 词表外 · W2 缺人工闸节 · W3 自检结论占位
633
+ 末行协议:LINT: PASS · <file> / LINT: FAIL · <file>
634
+ --json:{ok, errors[], warnings[], file, slug}(errors 元素 {rule, message, line?})
635
+ `);
636
+ return;
637
+ }
638
+
639
+ const json = args.includes('--json');
640
+ let rest = args.filter((a) => a !== '--json');
641
+ const { value: fileArg, rest: r1 } = takeOption(rest, '--file');
642
+ rest = r1;
643
+
644
+ if (rest.length > 0) {
645
+ const err = new Error(`task lint 未知参数: ${rest.join(' ')}`);
646
+ err.exitCode = 1;
647
+ throw err;
648
+ }
649
+ if (!fileArg) {
650
+ const err = new Error('task lint 须指定 --file PATH');
651
+ err.exitCode = 1;
652
+ throw err;
653
+ }
654
+
655
+ const result = lintTaskFile(fileArg, { cwd: process.cwd() });
656
+
657
+ if (json) {
658
+ console.log(JSON.stringify(result, null, 2));
659
+ } else {
660
+ for (const e of result.errors) {
661
+ console.log(` - [${e.rule}${e.line ? `:L${e.line}` : ''}] ${e.message}`);
662
+ }
663
+ for (const w of result.warnings) {
664
+ console.log(`warn: [${w.rule}${w.line ? `:L${w.line}` : ''}] ${w.message}`);
665
+ }
666
+ console.log(`LINT: ${result.ok ? 'PASS' : 'FAIL'} · ${path.basename(result.file)}`);
667
+ }
668
+
669
+ if (!result.ok) {
670
+ const err = new Error('');
671
+ err.exitCode = 2;
672
+ throw err;
673
+ }
674
+ }
675
+
618
676
  async function cmdTaskCheck(rest) {
619
677
  const noCircular = rest.includes('--no-circular');
620
678
  let filePath;
package/lib/task-close.js CHANGED
@@ -1,11 +1,15 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { extractSection, extractTaskSlug, parseHarnessMeta } from './task-meta.js';
4
-
5
- const PLACEHOLDER_RE = /^([^)]*(回填|待填)[^)]*)$/;
6
- const STATUS_RE = /\*\*状态\*\*:?\s*`?([a-z_]+)/i;
7
- const UNCHECKED_RE = /^\s*- \[ \]/;
8
- const CLOSE_STATUSES = new Set(['done', 'completed']);
3
+ import {
4
+ CLOSE_STATUSES,
5
+ PLACEHOLDER_RE,
6
+ STATUS_RE,
7
+ UNCHECKED_RE,
8
+ extractSection,
9
+ extractTaskSlug,
10
+ normalizeSlug,
11
+ parseHarnessMeta,
12
+ } from './task-meta.js';
9
13
 
10
14
  /**
11
15
  * task close 机械校验 + 归档执行。
@@ -42,10 +46,10 @@ export function closeTaskFile(filePath, options = {}) {
42
46
  const fileSlug = extractTaskSlug(abs);
43
47
  const slug = meta.task_slug ?? null;
44
48
 
45
- // 检查 4 · task_slug 一致
49
+ // 检查 4 · task_slug 一致(下划线/连字符惯例等价)
46
50
  if (!slug) {
47
51
  blockers.push('Harness 元信息表缺 task_slug');
48
- } else if (slug !== fileSlug) {
52
+ } else if (normalizeSlug(slug) !== normalizeSlug(fileSlug)) {
49
53
  blockers.push(`slug 不一致: 文件名 ${fileSlug} ≠ 元信息 task_slug ${slug}`);
50
54
  }
51
55
 
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { extractTaskSlug } from './task-meta.js';
3
+ import { extractTaskSlug, normalizeSlug } from './task-meta.js';
4
4
 
5
5
  const DONE_DIR_CANDIDATES = ['docs/tasks/done', 'docs/harness/tasks/done'];
6
6
 
@@ -15,7 +15,7 @@ export function lintDoneInvokes(target) {
15
15
  const dir = path.join(target, rel);
16
16
  if (!fs.existsSync(dir)) continue;
17
17
  for (const file of collectMarkdown(dir)) {
18
- const slug = extractTaskSlug(path.basename(file));
18
+ const slug = normalizeSlug(extractTaskSlug(path.basename(file)));
19
19
  if (!doneSlugs.has(slug)) {
20
20
  doneSlugs.set(slug, path.relative(target, file).replace(/\\/g, '/'));
21
21
  }
@@ -26,7 +26,7 @@ export function lintDoneInvokes(target) {
26
26
  const invokeSlugs = new Set();
27
27
  if (fs.existsSync(invokeRoot)) {
28
28
  for (const ent of fs.readdirSync(invokeRoot, { withFileTypes: true })) {
29
- if (ent.isDirectory()) invokeSlugs.add(ent.name);
29
+ if (ent.isDirectory()) invokeSlugs.add(normalizeSlug(ent.name));
30
30
  }
31
31
  }
32
32
 
@@ -0,0 +1,124 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {
4
+ CHECKBOX_RE,
5
+ KNOWN_STATUS_TOKENS,
6
+ PLACEHOLDER_RE,
7
+ STATUS_RE,
8
+ extractSection,
9
+ extractTaskSlug,
10
+ normalizeSlug,
11
+ parseHarnessMeta,
12
+ } from './task-meta.js';
13
+
14
+ /**
15
+ * 绝对本机路径:/Users/<seg> · /home/<seg> · /root/<seg> · C:\Users\<seg>
16
+ * 要求 <seg> 至少一个非 `/\s`` 字符 —— 规则文档里的泛型写法(`/Users/`)不触发。
17
+ */
18
+ const ABS_PATH_RE = /(\/(?:Users|home|root)\/[^\s/`\\]|[A-Za-z]:\\Users\\[^\s`\\])/;
19
+ const FAILURE_HEADING_RE = /^#{2,4}\s.*(失败路径|failure_paths)/i;
20
+
21
+ function err(rule, message, line) {
22
+ return line ? { rule, message, line } : { rule, message };
23
+ }
24
+ function warn(rule, message, line) {
25
+ return line ? { rule, message, line } : { rule, message };
26
+ }
27
+
28
+ /**
29
+ * task lint · task md 结构闸(G1)+ 文本规则包(G3)。
30
+ * 纯只读;E 级 → ok:false(CLI exit 2),W 级仅提醒。
31
+ */
32
+ export function lintTaskFile(filePath, options = {}) {
33
+ const { cwd = process.cwd() } = options;
34
+ const abs = path.resolve(cwd, filePath);
35
+
36
+ if (!fs.existsSync(abs)) {
37
+ const e = new Error(`task 文件不存在: ${filePath}`);
38
+ e.exitCode = 1;
39
+ throw e;
40
+ }
41
+
42
+ const content = fs.readFileSync(abs, 'utf8');
43
+ const lines = content.split('\n');
44
+ const errors = [];
45
+ const warnings = [];
46
+
47
+ // E1 · Harness 元信息 + task_slug
48
+ const meta = parseHarnessMeta(content);
49
+ if (!content.includes('## Harness 元信息')) {
50
+ errors.push(err('E1', '缺 ## Harness 元信息 节'));
51
+ } else if (!meta.task_slug) {
52
+ errors.push(err('E1', 'Harness 元信息表缺 task_slug'));
53
+ }
54
+
55
+ // E2 / W1 · 状态行
56
+ const statusIdx = lines.findIndex((l) => STATUS_RE.test(l));
57
+ if (statusIdx === -1) {
58
+ errors.push(err('E2', '缺 > **状态** 行'));
59
+ } else {
60
+ const token = lines[statusIdx].match(STATUS_RE)[1].toLowerCase();
61
+ if (!KNOWN_STATUS_TOKENS.has(token)) {
62
+ warnings.push(warn('W1', `状态 token 不在已知词表: ${token}`, statusIdx + 1));
63
+ }
64
+ }
65
+
66
+ // E3 · 验收标准 + 勾选项
67
+ const acceptance = extractSection(content, '## 验收标准', '\n##');
68
+ if (!acceptance) {
69
+ errors.push(err('E3', '缺 ## 验收标准 节'));
70
+ } else if (!CHECKBOX_RE.test(acceptance)) {
71
+ errors.push(err('E3', '## 验收标准 节内无任何勾选项(- [ ] / - [x])'));
72
+ }
73
+
74
+ // E4 · 失败路径节(标题含 失败路径 或 failure_paths · 形式宽容)
75
+ if (!lines.some((l) => FAILURE_HEADING_RE.test(l))) {
76
+ errors.push(err('E4', '缺失败路径节(## 失败路径 或 failure_paths)'));
77
+ }
78
+
79
+ // E5 / W3 · 自检结论
80
+ const selfCheck = extractSection(content, '### 自检结论', '\n##');
81
+ if (!selfCheck) {
82
+ errors.push(err('E5', '缺 ### 自检结论 节'));
83
+ } else {
84
+ const substantive = selfCheck
85
+ .split('\n')
86
+ .slice(1)
87
+ .map((l) => l.trim())
88
+ .filter(Boolean)
89
+ .filter((l) => !PLACEHOLDER_RE.test(l));
90
+ if (substantive.length === 0) {
91
+ warnings.push(warn('W3', '自检结论为占位符(draft 期合法 · close 前须回填)'));
92
+ }
93
+ }
94
+
95
+ // E6 · 绝对本机路径(带行号)
96
+ lines.forEach((l, i) => {
97
+ if (ABS_PATH_RE.test(l)) {
98
+ errors.push(err('E6', `绝对本机路径: ${l.trim().slice(0, 100)}`, i + 1));
99
+ }
100
+ });
101
+
102
+ // E7 · slug 一致(下划线/连字符等价)
103
+ if (meta.task_slug) {
104
+ const fileSlug = extractTaskSlug(abs);
105
+ if (normalizeSlug(meta.task_slug) !== normalizeSlug(fileSlug)) {
106
+ errors.push(
107
+ err('E7', `slug 不一致: 文件名 ${fileSlug} ≠ 元信息 task_slug ${meta.task_slug}`),
108
+ );
109
+ }
110
+ }
111
+
112
+ // W2 · 人工闸节
113
+ if (!content.includes('### 人工闸')) {
114
+ warnings.push(warn('W2', '缺 ### 人工闸 节(轻量 task 可忽略本提醒)'));
115
+ }
116
+
117
+ return {
118
+ ok: errors.length === 0,
119
+ errors,
120
+ warnings,
121
+ file: abs,
122
+ slug: meta.task_slug ?? extractTaskSlug(abs),
123
+ };
124
+ }
package/lib/task-meta.js CHANGED
@@ -206,9 +206,16 @@ export function listActiveTasks(target) {
206
206
  .sort();
207
207
  }
208
208
 
209
+ function escapeRegExp(s) {
210
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
211
+ }
212
+
209
213
  export function extractSection(content, startMarker, endMarker) {
210
- const start = content.indexOf(startMarker);
211
- if (start === -1) return null;
214
+ // startMarker 锚定行首:避免命中表格/正文里的「`## 验收标准`」式文本提及
215
+ const startRe = new RegExp(`^${escapeRegExp(startMarker)}`, 'm');
216
+ const startMatch = content.match(startRe);
217
+ if (!startMatch) return null;
218
+ const start = startMatch.index;
212
219
 
213
220
  let end = content.length;
214
221
  if (endMarker) {
@@ -230,6 +237,40 @@ export function extractTaskSlug(fileName) {
230
237
  return base;
231
238
  }
232
239
 
240
+ /** 自检结论占位符:整行匹配 (…回填/待填…) */
241
+ export const PLACEHOLDER_RE = /^([^)]*(回填|待填)[^)]*)$/;
242
+
243
+ /** 状态行:`> **状态**:`done` …(首个 [a-z_] token,反引号可选) */
244
+ export const STATUS_RE = /\*\*状态\*\*:?\s*`?([a-z_]+)/i;
245
+
246
+ /** 验收标准未勾选项 */
247
+ export const UNCHECKED_RE = /^\s*- \[ \]/;
248
+
249
+ /** 验收标准勾选项(已勾/未勾均可 · 多行模式) */
250
+ export const CHECKBOX_RE = /^\s*- \[[ xX]\]/m;
251
+
252
+ /** close 可接受的状态 token */
253
+ export const CLOSE_STATUSES = new Set(['done', 'completed']);
254
+
255
+ /** 已知状态词表(lint W1 用 · 实测工作区 13 个 task) */
256
+ export const KNOWN_STATUS_TOKENS = new Set([
257
+ 'draft',
258
+ 'pending',
259
+ 'in_progress',
260
+ 'active',
261
+ 'deferred',
262
+ 'done',
263
+ 'completed',
264
+ ]);
265
+
266
+ /**
267
+ * slug 规范化比较:文件名惯例用下划线、task_slug/invoke 目录惯例用连字符
268
+ * (实测工作区:task_cyning_harness_a5_*_v1.md ↔ cyning-harness-a5-*)。
269
+ */
270
+ export function normalizeSlug(slug) {
271
+ return String(slug).replace(/_/g, '-');
272
+ }
273
+
233
274
  function normalizeCell(cell) {
234
275
  return cell.replace(/[`\\*]/g, '').trim();
235
276
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyning/harness",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "cyning-harness discipline package · init / upgrade / check CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",