@cyning/harness 1.1.0 → 2.0.1

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.
@@ -0,0 +1,301 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://cyning.dev/schemas/cyning-harness/hgm_event.v1.json",
4
+ "title": "cyning-harness HGM Event v1",
5
+ "description": "Harness Graph Model 过程轨事件 schema · append-only JSONL · v2.0+",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["event_id", "type", "occurred_at", "actor", "subject", "data"],
9
+ "properties": {
10
+ "event_id": {
11
+ "type": "string",
12
+ "pattern": "^evt:[0-9]{8}T[0-9]{6}Z:[0-9]{3,}$",
13
+ "description": "全局唯一事件 ID · 格式 evt:{YYYYMMDD}T{HHMMSS}Z:{seq}"
14
+ },
15
+ "type": {
16
+ "type": "string",
17
+ "enum": [
18
+ "RepositoryAdopted",
19
+ "TaskCreated",
20
+ "TaskStatusChanged",
21
+ "GateStatusChanged",
22
+ "HumanGateRejected",
23
+ "AuditReviewProduced",
24
+ "InvokeSnapshotCreated",
25
+ "FailureReportProduced",
26
+ "GateCheckRunCompleted",
27
+ "SyncOperationCompleted",
28
+ "InformArtifactUpdated",
29
+ "CorrectionEvent"
30
+ ],
31
+ "description": "事件类型"
32
+ },
33
+ "occurred_at": {
34
+ "type": "string",
35
+ "format": "date-time",
36
+ "description": "事件发生 UTC 时间 · ISO 8601"
37
+ },
38
+ "actor": {
39
+ "type": "string",
40
+ "pattern": "^(maintainer|gate-check|sync|ci|agent:[a-zA-Z0-9_-]+|manual|system)$",
41
+ "description": "动作主体"
42
+ },
43
+ "subject": {
44
+ "type": "string",
45
+ "minLength": 1,
46
+ "description": "事件主题节点 ID · 如 task:{slug} / gate:{slug}:{gate_id}"
47
+ },
48
+ "data": {
49
+ "description": "事件 payload"
50
+ },
51
+ "source": {
52
+ "type": "string",
53
+ "enum": ["file_watch", "gate-check.sh", "harness-sync.sh", "manual", "cli", "agent"],
54
+ "description": "事件采集来源"
55
+ },
56
+ "supersedes": {
57
+ "type": "string",
58
+ "pattern": "^evt:[0-9]{8}T[0-9]{6}Z:[0-9]{3,}$",
59
+ "description": "CorrectionEvent 指向被修正的事件 ID(可选)"
60
+ }
61
+ },
62
+ "$defs": {
63
+ "statusValue": {
64
+ "type": "string",
65
+ "enum": ["draft", "pending", "in_progress", "approved", "rejected", "done", "archived"]
66
+ },
67
+ "taskSubject": {
68
+ "type": "string",
69
+ "pattern": "^task:[a-zA-Z0-9_-]+$"
70
+ },
71
+ "gateSubject": {
72
+ "type": "string",
73
+ "pattern": "^gate:[a-zA-Z0-9_-]+:[a-zA-Z0-9_-]+$"
74
+ }
75
+ },
76
+ "allOf": [
77
+ {
78
+ "if": { "properties": { "type": { "const": "RepositoryAdopted" } } },
79
+ "then": {
80
+ "properties": {
81
+ "subject": { "pattern": "^repo:[a-zA-Z0-9_-]+$" },
82
+ "data": {
83
+ "type": "object",
84
+ "additionalProperties": false,
85
+ "required": ["manifest_version", "preset"],
86
+ "properties": {
87
+ "manifest_version": { "type": "string" },
88
+ "preset": { "type": "string" },
89
+ "ide": { "type": "array", "items": { "type": "string" } }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ },
95
+ {
96
+ "if": { "properties": { "type": { "const": "TaskCreated" } } },
97
+ "then": {
98
+ "properties": {
99
+ "subject": { "$ref": "#/$defs/taskSubject" },
100
+ "data": {
101
+ "type": "object",
102
+ "additionalProperties": false,
103
+ "required": ["task_slug", "title", "status"],
104
+ "properties": {
105
+ "task_slug": { "type": "string" },
106
+ "title": { "type": "string" },
107
+ "status": { "$ref": "#/$defs/statusValue" },
108
+ "path": { "type": "string" },
109
+ "tags": { "type": "array", "items": { "type": "string" } }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ },
115
+ {
116
+ "if": { "properties": { "type": { "const": "TaskStatusChanged" } } },
117
+ "then": {
118
+ "properties": {
119
+ "subject": { "$ref": "#/$defs/taskSubject" },
120
+ "data": {
121
+ "type": "object",
122
+ "additionalProperties": false,
123
+ "required": ["old_status", "new_status"],
124
+ "properties": {
125
+ "old_status": { "$ref": "#/$defs/statusValue" },
126
+ "new_status": { "$ref": "#/$defs/statusValue" },
127
+ "artifact": { "type": "string" }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ },
133
+ {
134
+ "if": { "properties": { "type": { "const": "GateStatusChanged" } } },
135
+ "then": {
136
+ "properties": {
137
+ "subject": { "$ref": "#/$defs/gateSubject" },
138
+ "data": {
139
+ "type": "object",
140
+ "additionalProperties": false,
141
+ "required": ["old_status", "new_status", "task_slug", "human_gate_id"],
142
+ "properties": {
143
+ "old_status": { "$ref": "#/$defs/statusValue" },
144
+ "new_status": { "$ref": "#/$defs/statusValue" },
145
+ "task_slug": { "type": "string" },
146
+ "human_gate_id": { "type": "string" },
147
+ "blocks_hats": { "type": "array", "items": { "type": "string" } }
148
+ }
149
+ }
150
+ }
151
+ }
152
+ },
153
+ {
154
+ "if": { "properties": { "type": { "const": "HumanGateRejected" } } },
155
+ "then": {
156
+ "properties": {
157
+ "subject": { "$ref": "#/$defs/gateSubject" },
158
+ "data": {
159
+ "type": "object",
160
+ "additionalProperties": false,
161
+ "required": ["task_slug", "human_gate_id", "reason"],
162
+ "properties": {
163
+ "task_slug": { "type": "string" },
164
+ "human_gate_id": { "type": "string" },
165
+ "reason": { "type": "string" },
166
+ "returns_to_status": { "$ref": "#/$defs/statusValue" }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ },
172
+ {
173
+ "if": { "properties": { "type": { "const": "AuditReviewProduced" } } },
174
+ "then": {
175
+ "properties": {
176
+ "subject": { "$ref": "#/$defs/taskSubject" },
177
+ "data": {
178
+ "type": "object",
179
+ "additionalProperties": false,
180
+ "required": ["review_path", "round"],
181
+ "properties": {
182
+ "review_path": { "type": "string" },
183
+ "round": { "type": "string", "enum": ["R1", "R2", "CLOSE"] },
184
+ "hat_id": { "type": "string" }
185
+ }
186
+ }
187
+ }
188
+ }
189
+ },
190
+ {
191
+ "if": { "properties": { "type": { "const": "InvokeSnapshotCreated" } } },
192
+ "then": {
193
+ "properties": {
194
+ "subject": { "$ref": "#/$defs/taskSubject" },
195
+ "data": {
196
+ "type": "object",
197
+ "additionalProperties": false,
198
+ "required": ["invoke_path"],
199
+ "properties": {
200
+ "invoke_path": { "type": "string" },
201
+ "hat_id": { "type": "string" }
202
+ }
203
+ }
204
+ }
205
+ }
206
+ },
207
+ {
208
+ "if": { "properties": { "type": { "const": "FailureReportProduced" } } },
209
+ "then": {
210
+ "properties": {
211
+ "subject": { "$ref": "#/$defs/taskSubject" },
212
+ "data": {
213
+ "type": "object",
214
+ "additionalProperties": false,
215
+ "required": ["failure_path", "at_gate"],
216
+ "properties": {
217
+ "failure_path": { "type": "string" },
218
+ "at_gate": { "type": "string" },
219
+ "reason": { "type": "string" }
220
+ }
221
+ }
222
+ }
223
+ }
224
+ },
225
+ {
226
+ "if": { "properties": { "type": { "const": "GateCheckRunCompleted" } } },
227
+ "then": {
228
+ "properties": {
229
+ "subject": { "$ref": "#/$defs/taskSubject" },
230
+ "data": {
231
+ "type": "object",
232
+ "additionalProperties": false,
233
+ "required": ["exit_code", "task_slug"],
234
+ "properties": {
235
+ "exit_code": { "type": "integer" },
236
+ "task_slug": { "type": "string" },
237
+ "graph_checked": { "type": "boolean" },
238
+ "audit_checked": { "type": "boolean" }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ },
244
+ {
245
+ "if": { "properties": { "type": { "const": "SyncOperationCompleted" } } },
246
+ "then": {
247
+ "properties": {
248
+ "subject": { "pattern": "^repo:[a-zA-Z0-9_-]+$" },
249
+ "data": {
250
+ "type": "object",
251
+ "additionalProperties": false,
252
+ "required": ["mode", "version"],
253
+ "properties": {
254
+ "mode": { "type": "string", "enum": ["plan", "apply"] },
255
+ "version": { "type": "string" },
256
+ "files_touched": { "type": "array", "items": { "type": "string" } }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ },
262
+ {
263
+ "if": { "properties": { "type": { "const": "InformArtifactUpdated" } } },
264
+ "then": {
265
+ "properties": {
266
+ "subject": { "pattern": "^inform:.+" },
267
+ "data": {
268
+ "type": "object",
269
+ "additionalProperties": false,
270
+ "required": ["path", "inform_schema"],
271
+ "properties": {
272
+ "path": { "type": "string" },
273
+ "inform_schema": { "type": "string" },
274
+ "compiled_from": { "type": "string" }
275
+ }
276
+ }
277
+ }
278
+ }
279
+ },
280
+ {
281
+ "if": { "properties": { "type": { "const": "CorrectionEvent" } } },
282
+ "then": {
283
+ "required": ["supersedes"],
284
+ "properties": {
285
+ "subject": { "minLength": 1 },
286
+ "data": {
287
+ "type": "object",
288
+ "additionalProperties": false,
289
+ "required": ["field", "old_value", "new_value"],
290
+ "properties": {
291
+ "field": { "type": "string" },
292
+ "old_value": {},
293
+ "new_value": {},
294
+ "reason": { "type": "string" }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+ ]
301
+ }
@@ -1,8 +1,8 @@
1
1
  # SDD-Compliance micro-bench · v1
2
2
 
3
3
  > **标题**:SDD-Compliance · **非** Agent Score / pass@1 类模型 benchmark。
4
- > **范围**:S1–S4 · 公理 D2/D3/S2 可机械检查部分 · 不覆盖 D4/D5 与 Terminal-Bench。
5
- > **状态**:v1.0 · B8 冻结 · 4/4 期望合规率 **100%**。
4
+ > **范围**:S1–S5 · 公理 D2/D3/S2/rejected→draft 可机械检查部分 · 不覆盖 D5 与 Terminal-Bench。
5
+ > **状态**:v1.1 · G1.1 · 5/5 期望合规率 **100%**。
6
6
 
7
7
  ---
8
8
 
@@ -12,7 +12,7 @@
12
12
  | --- | --- |
13
13
  | **测什么** | `gate-check.sh` 与 `harness-sync.sh` 是否符合 SDD 公理(合成夹具) |
14
14
  | **不测什么** | LLM 会不会写代码 · 业务测试绿不绿 · 真实项目胜率 |
15
- | **100 是什么** | 合规率 % = PASS 场景 ÷ 总场景 × 100;4/4 全过即 **100** |
15
+ | **100 是什么** | 合规率 % = PASS 场景 ÷ 总场景 × 100;5/5 全过即 **100** |
16
16
  | **和 B2 关系** | B2 Part A 是真实试点 retro;bench 是可机械复现的 **机制回归** |
17
17
 
18
18
  ---
@@ -48,8 +48,7 @@ cd /path/to/cyning-harness
48
48
  | **S2** | R1 approved 无 review | D2/D3 | `S2_r1_no_review/` | 无 `*_audit_R1_*` 文件 | 只改 task 表不算真审核 |
49
49
  | **S3** | R1 approved + review | D2/D3 | `S3_r1_with_review/` | exit=0 且 review 存在 | 审核闭环 Happy Path |
50
50
  | **S4** | sync 域边界 | S2 | `S4_sync_domain/` + 临时仓 | plan 无 `docs/tasks/`、`reviews/` | upgrade 不覆盖用户 task |
51
-
52
- **v1 未纳入**:**S5** rejected→draft(待 gate-check 行为稳定后加夹具)。
51
+ | **S5** | rejected→draft | rejected→draft | `S5_rejected_draft/` | gate rejected + task status=draft | 否决后须回 draft(与 HGM axioms 同语义) |
53
52
 
54
53
  ---
55
54
 
@@ -66,9 +65,9 @@ cd /path/to/cyning-harness
66
65
  场景 结果 详情
67
66
  S1 ✅ PASS gate-check exit=1 · 30 被拒
68
67
  ...
69
- 合规率 4/4 = 100%
68
+ 合规率 5/5 = 100%
70
69
 
71
- 解读 100 = 全部 4 个场景行为符合 SDD 公理
70
+ 解读 100 = 全部 5 个场景行为符合 SDD 公理
72
71
  不是 LLM 解题分数 · 不可外推为「AI 胜率」
73
72
  ```
74
73
 
@@ -78,4 +77,5 @@ S1 ✅ PASS gate-check exit=1 · 30 被拒
78
77
 
79
78
  | 日期 | 说明 |
80
79
  | --- | --- |
80
+ | 2026-06-17 | G1.1:新增 S5 rejected→draft · 5/5 合规率 |
81
81
  | 2026-06-16 | 丰富脚本输出 · 公理/夹具/解读 · quiet 摘要走 stderr |
@@ -0,0 +1,33 @@
1
+ # Task fixture · S5 · rejected→draft 合规态
2
+
3
+ > 用途:SDD-Compliance micro-bench S5 夹具 · gate rejected 后 task 须回 draft(与 HGM axioms 同语义)
4
+
5
+ ---
6
+
7
+ ## Harness 元信息
8
+
9
+ | 字段 | 值 |
10
+ | --- | --- |
11
+ | **task_slug** | `s5_rejected_draft` |
12
+ | **test_strategy** | `not_applicable` |
13
+ | **audit_profile** | `full` |
14
+
15
+ > **状态**:`draft`
16
+
17
+ ### 人工闸
18
+
19
+ | human_gate_id | status | blocks_hats | 说明 |
20
+ | --- | --- | --- | --- |
21
+ | HG-TASK-DRAFT | approved | 22, 30 | 10 完成 |
22
+ | HG-AUDIT-R1 | rejected | 30 | 22 R1 否决 · 已回 draft |
23
+
24
+ ---
25
+
26
+ ## 背景与目标
27
+
28
+ 验证 **rejected→draft** 公理:维护者否决 R1 后,task 须回到 **draft** 状态(非 in_progress)。
29
+
30
+ ## 验收标准
31
+
32
+ - task 表 **status = draft** · **HG-AUDIT-R1 = rejected**
33
+ - bench 判 **合规**(与 `graph axioms check` rejected→draft 语义一致)
package/lib/cli.js CHANGED
@@ -19,6 +19,13 @@ import {
19
19
  GraphYamlError,
20
20
  yamlPathFor,
21
21
  } from './graph-yaml.js';
22
+ import {
23
+ buildSnapshot,
24
+ checkAxioms,
25
+ ingestRepoIdempotent,
26
+ loadEvents,
27
+ writeSnapshot,
28
+ } from './graph-hgm.js';
22
29
 
23
30
  function usage(version = 'unknown') {
24
31
  console.log(`@cyning/harness · cyning-harness CLI (v${version})
@@ -38,6 +45,9 @@ function usage(version = 'unknown') {
38
45
  npx @cyning/harness graph yaml check --graph-id ID [--input DIR] [--graph-json FILE]
39
46
  npx @cyning/harness graph yaml compile --all [--input DIR]
40
47
  npx @cyning/harness graph yaml check --all [--input DIR] [--graph-json FILE]
48
+ npx @cyning/harness graph ingest [--target PATH] [--actor ACTOR] [--dry-run]
49
+ npx @cyning/harness graph snapshot [--target PATH]
50
+ npx @cyning/harness graph axioms check [--target PATH] [--json]
41
51
 
42
52
  说明:
43
53
  init 首次接入 · 内部调用 wizard/install.sh + harness-sync.sh apply
@@ -48,7 +58,10 @@ function usage(version = 'unknown') {
48
58
  verify 30 前聚合:gate-check + audit D5 + S5 warn + 可选 --graph
49
59
  sync index 生成 .cyning-harness/invoke_index.json
50
60
  task check 校验 task.harness.v1.json sidecar · --no-circular 检测 depends_on 环
51
- graph yaml Inform 图谱 YAML 编译 / 校验(v1.1+)
61
+ graph yaml Inform 图谱 YAML 编译 / 校验(v1.1+)
62
+ graph ingest 扫描业务仓 → 追加 HGM 事件(v2.0+)
63
+ graph snapshot 事件重放 → graph/snapshot.json(v2.0+)
64
+ graph axioms 跑 HGM 公理检查(v2.0+)
52
65
 
53
66
  环境:
54
67
  CYNING_HARNESS 覆盖产品包根(维护者本地 clone 开发)
@@ -514,31 +527,54 @@ async function cmdTask(args) {
514
527
 
515
528
  async function cmdGraph(args) {
516
529
  if (args.includes('--help') || args.includes('-h')) {
517
- console.log(`用法: npx @cyning/harness graph yaml compile|check [选项]
530
+ console.log(`用法: npx @cyning/harness graph <子命令> [选项]
518
531
 
519
532
  子命令:
520
533
  graph yaml compile --graph-id ID [--input DIR] [--output FILE]
521
534
  graph yaml compile --all [--input DIR]
522
535
  graph yaml check --graph-id ID [--input DIR] [--graph-json FILE]
523
536
  graph yaml check --all [--input DIR] [--graph-json FILE]
537
+ graph ingest [--target PATH] [--actor ACTOR] [--dry-run]
538
+ graph snapshot [--target PATH]
539
+ graph axioms check [--target PATH] [--json]
524
540
 
525
541
  选项:
526
- --input DIR 输入目录(默认 target/docs/_tech_graph)
527
- --output FILE 输出 MD 路径(仅单图 compile)
528
- --graph-json FILE 对比的 graph.json 路径(默认 input/graph.json)
529
- --all 处理目录下所有 *.graph.yaml
542
+ --input DIR graph yaml 输入目录(默认 target/docs/_tech_graph)
543
+ --output FILE graph yaml 输出 MD 路径(仅单图 compile)
544
+ --graph-json FILE graph yaml 对比的 graph.json 路径
545
+ --target PATH 业务仓根目录(默认当前目录)
546
+ --actor ACTOR ingest actor(默认 system)
547
+ --dry-run ingest 仅输出事件数 · 不写入
548
+ --json axioms 输出 JSON
530
549
  `);
531
550
  return;
532
551
  }
533
552
 
534
553
  const [sub, ...subRest] = args;
535
- if (sub !== 'yaml') {
536
- const err = new Error(`graph 子命令未知: ${sub ?? '(空)'}`);
537
- err.exitCode = 1;
538
- throw err;
554
+ if (sub === 'yaml') {
555
+ await cmdGraphYaml(subRest);
556
+ return;
557
+ }
558
+ if (sub === 'ingest') {
559
+ await cmdGraphIngest(subRest);
560
+ return;
561
+ }
562
+ if (sub === 'snapshot') {
563
+ await cmdGraphSnapshot(subRest);
564
+ return;
565
+ }
566
+ if (sub === 'axioms') {
567
+ await cmdGraphAxioms(subRest);
568
+ return;
539
569
  }
540
570
 
541
- const [action, ...actionRest] = subRest;
571
+ const err = new Error(`graph 子命令未知: ${sub ?? '(空)'}`);
572
+ err.exitCode = 1;
573
+ throw err;
574
+ }
575
+
576
+ async function cmdGraphYaml(args) {
577
+ const [action, ...actionRest] = args;
542
578
  if (action !== 'compile' && action !== 'check') {
543
579
  const err = new Error(`graph yaml 动作未知: ${action ?? '(空)'}`);
544
580
  err.exitCode = 1;
@@ -638,3 +674,97 @@ async function cmdGraph(args) {
638
674
  throw err;
639
675
  }
640
676
  }
677
+
678
+ async function cmdGraphIngest(args) {
679
+ let rest = args;
680
+ const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
681
+ rest = r1;
682
+ const { value: actor, rest: r2 } = takeOption(rest, '--actor');
683
+ rest = r2;
684
+ const dryRun = rest.includes('--dry-run');
685
+ rest = rest.filter((a) => a !== '--dry-run');
686
+
687
+ if (rest.length > 0) {
688
+ const err = new Error(`graph ingest 未知参数: ${rest.join(' ')}`);
689
+ err.exitCode = 1;
690
+ throw err;
691
+ }
692
+
693
+ const target = resolveTarget(process.cwd(), targetArg);
694
+ const result = ingestRepoIdempotent(target, {
695
+ actor: actor || 'system',
696
+ source: 'cli',
697
+ dryRun,
698
+ });
699
+
700
+ console.log(`目标: ${target}`);
701
+ console.log(`新事件: ${result.count}`);
702
+ console.log(`跳过(已存在): ${result.skipped}`);
703
+ if (dryRun) {
704
+ console.log('mode: dry-run(未写入)');
705
+ }
706
+ }
707
+
708
+ async function cmdGraphSnapshot(args) {
709
+ let rest = args;
710
+ const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
711
+ rest = r1;
712
+
713
+ if (rest.length > 0) {
714
+ const err = new Error(`graph snapshot 未知参数: ${rest.join(' ')}`);
715
+ err.exitCode = 1;
716
+ throw err;
717
+ }
718
+
719
+ const target = resolveTarget(process.cwd(), targetArg);
720
+ const events = loadEvents(target);
721
+ const snapshot = buildSnapshot(events);
722
+ const out = writeSnapshot(target, snapshot);
723
+
724
+ console.log(`events: ${events.length}`);
725
+ console.log(`nodes: ${Object.keys(snapshot.nodes).length}`);
726
+ console.log(`edges: ${snapshot.edges.length}`);
727
+ console.log(`snapshot: ${out}`);
728
+ }
729
+
730
+ async function cmdGraphAxioms(args) {
731
+ const [sub, ...rest] = args;
732
+ if (sub !== 'check') {
733
+ const err = new Error(`graph axioms 动作未知: ${sub ?? '(空)'}`);
734
+ err.exitCode = 1;
735
+ throw err;
736
+ }
737
+
738
+ let remaining = rest;
739
+ const { value: targetArg, rest: r1 } = takeOption(remaining, '--target');
740
+ remaining = r1;
741
+ const json = remaining.includes('--json');
742
+ remaining = remaining.filter((a) => a !== '--json');
743
+
744
+ if (remaining.length > 0) {
745
+ const err = new Error(`graph axioms check 未知参数: ${remaining.join(' ')}`);
746
+ err.exitCode = 1;
747
+ throw err;
748
+ }
749
+
750
+ const target = resolveTarget(process.cwd(), targetArg);
751
+ const events = loadEvents(target);
752
+ const snapshot = buildSnapshot(events);
753
+ const result = checkAxioms(snapshot, events);
754
+
755
+ if (json) {
756
+ console.log(JSON.stringify(result, null, 2));
757
+ } else {
758
+ console.log(`axioms: ${result.ok ? 'PASS' : 'FAIL'}`);
759
+ console.log(`violations: ${result.violations.length}`);
760
+ for (const v of result.violations) {
761
+ console.log(` [${v.axiom}/${v.severity}] ${v.message}`);
762
+ }
763
+ }
764
+
765
+ if (!result.ok) {
766
+ const err = new Error('HGM axioms 未通过');
767
+ err.exitCode = 2;
768
+ throw err;
769
+ }
770
+ }