@cyning/harness 1.0.0 → 1.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.
- package/CHANGELOG.md +22 -0
- package/README.md +21 -17
- package/bin/harness.js +1 -1
- package/docs/ONBOARDING.md +14 -11
- package/docs/README.md +2 -2
- package/docs/RELEASE_v1.0.0.md +1 -0
- package/docs/RELEASE_v1.0.1.md +87 -0
- package/docs/USER_GUIDE_v1.0_zh.md +284 -0
- package/docs/methodology/ROADMAP_v1_zh.md +22 -1
- package/docs/methodology/graph/HARNESS_GRAPH_MODEL_design_v0_zh.md +1 -0
- package/docs/methodology/graph/HGM_UPGRADE_OUTLINE_v1_zh.md +248 -0
- package/examples/compliance_bench/README.md +64 -11
- package/harness/prompts/FRAGMENT_30_gate_verify_v1_zh.md +1 -1
- package/harness/templates/QUICKREF_v1_zh.md +47 -0
- package/ide/adapters/AGENTS.md.fragment.example +1 -0
- package/ide/adapters/CLAUDE.md.fragment.example +1 -0
- package/ide/adapters/cursor-harness-starter.mdc.example +1 -0
- package/lib/audit.js +4 -6
- package/lib/cli.js +195 -5
- package/lib/package-scripts.js +65 -0
- package/lib/paths.js +27 -0
- package/lib/verify.js +103 -0
- package/ontology.yaml +1 -1
- package/package.json +1 -1
- package/wizard/README.md +4 -0
- package/wizard/compliance-bench.sh +150 -55
- package/wizard/install.sh +5 -3
- package/wizard/upgrade.sh +7 -2
package/lib/cli.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { readManifest, isNewerVersion } from './manifest.js';
|
|
2
4
|
import {
|
|
3
5
|
resolveHarnessRoot,
|
|
6
|
+
resolveHarnessRootForTarget,
|
|
4
7
|
resolvePackageVersion,
|
|
5
8
|
resolveTarget,
|
|
6
9
|
wizardScript,
|
|
@@ -8,15 +11,21 @@ import {
|
|
|
8
11
|
import { runBash } from './run.js';
|
|
9
12
|
import { checkTaskFile } from './task.js';
|
|
10
13
|
import { auditTarget } from './audit.js';
|
|
14
|
+
import { mergePackageScripts } from './package-scripts.js';
|
|
11
15
|
|
|
12
16
|
function usage() {
|
|
13
|
-
console.log(`@cyning/harness · cyning-harness CLI (
|
|
17
|
+
console.log(`@cyning/harness · cyning-harness CLI (v1.0.1)
|
|
14
18
|
|
|
15
19
|
用法:
|
|
16
|
-
npx @cyning/harness
|
|
17
|
-
npx @cyning/harness
|
|
20
|
+
npx @cyning/harness --version | -V
|
|
21
|
+
npx @cyning/harness --help | -h
|
|
22
|
+
npx @cyning/harness init [--preset NAME] [--ide LIST] [--target PATH] [--yes] [--with-scripts] [--pm pnpm|npm|yarn|auto]
|
|
23
|
+
npx @cyning/harness upgrade [--target PATH] [--ide LIST] [--yes] [--force] [--gate-check] [--with-scripts] [--pm pnpm|npm|yarn|auto]
|
|
18
24
|
npx @cyning/harness check [--target PATH]
|
|
19
25
|
npx @cyning/harness audit [--target PATH] [--task FILE]
|
|
26
|
+
npx @cyning/harness gate-check [--target PATH] [--task FILE] [--graph] [--json]
|
|
27
|
+
npx @cyning/harness verify [--target PATH] [--task FILE] [--graph]
|
|
28
|
+
npx @cyning/harness sync index [--target PATH]
|
|
20
29
|
npx @cyning/harness task check --file PATH [--no-circular] [--registry DIR]...
|
|
21
30
|
|
|
22
31
|
说明:
|
|
@@ -24,6 +33,9 @@ function usage() {
|
|
|
24
33
|
upgrade 升级已接入仓 · 等价 wizard/upgrade.sh --yes
|
|
25
34
|
check 读 manifest.json · 对比当前包版本是否有可升级版本
|
|
26
35
|
audit ICVO 机械审计:复用 gate-check · D5 测试声明检查 · S5 git-clean 提醒
|
|
36
|
+
gate-check 调用 wizard/gate-check.sh(业务仓无需 clone 即可用)
|
|
37
|
+
verify 30 前聚合:gate-check + audit D5 + S5 warn + 可选 --graph
|
|
38
|
+
sync index 生成 .cyning-harness/invoke_index.json
|
|
27
39
|
task check 校验 task.harness.v1.json sidecar · --no-circular 检测 depends_on 环
|
|
28
40
|
|
|
29
41
|
环境:
|
|
@@ -51,6 +63,11 @@ export async function runCli(argv) {
|
|
|
51
63
|
return;
|
|
52
64
|
}
|
|
53
65
|
|
|
66
|
+
if (argv.includes('--version') || argv.includes('-V')) {
|
|
67
|
+
console.log(pkgVersion);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
const [cmd, ...rest] = argv;
|
|
55
72
|
|
|
56
73
|
if (cmd === 'init') {
|
|
@@ -69,6 +86,18 @@ export async function runCli(argv) {
|
|
|
69
86
|
await cmdAudit(rest);
|
|
70
87
|
return;
|
|
71
88
|
}
|
|
89
|
+
if (cmd === 'gate-check') {
|
|
90
|
+
await cmdGateCheck(rest);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (cmd === 'verify') {
|
|
94
|
+
await cmdVerify(rest);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (cmd === 'sync') {
|
|
98
|
+
await cmdSync(rest);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
72
101
|
if (cmd === 'task') {
|
|
73
102
|
await cmdTask(rest);
|
|
74
103
|
return;
|
|
@@ -81,13 +110,16 @@ export async function runCli(argv) {
|
|
|
81
110
|
|
|
82
111
|
async function cmdInit(args, harnessRoot, pkgVersion) {
|
|
83
112
|
const yes = args.includes('--yes');
|
|
84
|
-
|
|
113
|
+
const withScripts = args.includes('--with-scripts');
|
|
114
|
+
let rest = args.filter((a) => a !== '--yes' && a !== '--with-scripts');
|
|
85
115
|
const { value: preset, rest: r1 } = takeOption(rest, '--preset');
|
|
86
116
|
rest = r1;
|
|
87
117
|
const { value: ide, rest: r2 } = takeOption(rest, '--ide');
|
|
88
118
|
rest = r2;
|
|
89
119
|
const { value: targetArg, rest: r3 } = takeOption(rest, '--target');
|
|
90
120
|
rest = r3;
|
|
121
|
+
const { value: pm, rest: r4 } = takeOption(rest, '--pm');
|
|
122
|
+
rest = r4;
|
|
91
123
|
|
|
92
124
|
if (rest.length > 0) {
|
|
93
125
|
const err = new Error(`init 未知参数: ${rest.join(' ')}`);
|
|
@@ -109,6 +141,18 @@ async function cmdInit(args, harnessRoot, pkgVersion) {
|
|
|
109
141
|
inherit: true,
|
|
110
142
|
});
|
|
111
143
|
|
|
144
|
+
if (withScripts) {
|
|
145
|
+
const ps = mergePackageScripts(target, pkgVersion, pm || 'auto');
|
|
146
|
+
if (ps.skipped) {
|
|
147
|
+
console.log(`warn: ${ps.reason}`);
|
|
148
|
+
} else if (ps.changed) {
|
|
149
|
+
console.log(`scripts: ${ps.reason}(pm: ${ps.pm})`);
|
|
150
|
+
console.log('提示: 运行 `pnpm install` 或对应 install 使 devDep 生效');
|
|
151
|
+
} else {
|
|
152
|
+
console.log(`scripts: ${ps.reason}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
112
156
|
console.log(`manifest: ${target}/.cyning-harness/manifest.json`);
|
|
113
157
|
if (!yes) {
|
|
114
158
|
console.log('init 完成。');
|
|
@@ -120,13 +164,23 @@ async function cmdUpgrade(args, harnessRoot, pkgVersion) {
|
|
|
120
164
|
const force = args.includes('--force');
|
|
121
165
|
const gateCheck = args.includes('--gate-check');
|
|
122
166
|
const yes = args.includes('--yes') || !checkOnly;
|
|
167
|
+
const withScripts = args.includes('--with-scripts');
|
|
123
168
|
let rest = args.filter(
|
|
124
|
-
(a) =>
|
|
169
|
+
(a) =>
|
|
170
|
+
![
|
|
171
|
+
'--check',
|
|
172
|
+
'--force',
|
|
173
|
+
'--gate-check',
|
|
174
|
+
'--yes',
|
|
175
|
+
'--with-scripts',
|
|
176
|
+
].includes(a),
|
|
125
177
|
);
|
|
126
178
|
const { value: ide, rest: r1 } = takeOption(rest, '--ide');
|
|
127
179
|
rest = r1;
|
|
128
180
|
const { value: targetArg, rest: r2 } = takeOption(rest, '--target');
|
|
129
181
|
rest = r2;
|
|
182
|
+
const { value: pm, rest: r3 } = takeOption(rest, '--pm');
|
|
183
|
+
rest = r3;
|
|
130
184
|
|
|
131
185
|
if (rest.length > 0) {
|
|
132
186
|
const err = new Error(`upgrade 未知参数: ${rest.join(' ')}`);
|
|
@@ -156,6 +210,18 @@ async function cmdUpgrade(args, harnessRoot, pkgVersion) {
|
|
|
156
210
|
},
|
|
157
211
|
inherit: true,
|
|
158
212
|
});
|
|
213
|
+
|
|
214
|
+
if (withScripts) {
|
|
215
|
+
const ps = mergePackageScripts(target, pkgVersion, pm || 'auto');
|
|
216
|
+
if (ps.skipped) {
|
|
217
|
+
console.log(`warn: ${ps.reason}`);
|
|
218
|
+
} else if (ps.changed) {
|
|
219
|
+
console.log(`scripts: ${ps.reason}(pm: ${ps.pm})`);
|
|
220
|
+
console.log('提示: 运行 `pnpm install` 或对应 install 使 devDep 生效');
|
|
221
|
+
} else {
|
|
222
|
+
console.log(`scripts: ${ps.reason}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
159
225
|
}
|
|
160
226
|
|
|
161
227
|
async function cmdCheck(args, harnessRoot, pkgVersion) {
|
|
@@ -236,6 +302,130 @@ async function cmdAudit(args) {
|
|
|
236
302
|
}
|
|
237
303
|
}
|
|
238
304
|
|
|
305
|
+
async function cmdGateCheck(args) {
|
|
306
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
307
|
+
console.log(`用法: npx @cyning/harness gate-check [--target PATH] [--task FILE] [--graph] [--json]`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let rest = args;
|
|
312
|
+
const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
|
|
313
|
+
rest = r1;
|
|
314
|
+
const { value: taskFile, rest: r2 } = takeOption(rest, '--task');
|
|
315
|
+
rest = r2;
|
|
316
|
+
const graph = rest.includes('--graph');
|
|
317
|
+
const json = rest.includes('--json');
|
|
318
|
+
rest = rest.filter((a) => a !== '--graph' && a !== '--json');
|
|
319
|
+
|
|
320
|
+
if (rest.length > 0) {
|
|
321
|
+
const err = new Error(`gate-check 未知参数: ${rest.join(' ')}`);
|
|
322
|
+
err.exitCode = 1;
|
|
323
|
+
throw err;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const target = resolveTarget(process.cwd(), targetArg);
|
|
327
|
+
const harnessRoot = resolveHarnessRootForTarget(target);
|
|
328
|
+
const script = wizardScript(harnessRoot, 'gate-check.sh');
|
|
329
|
+
const childArgs = ['--target', target];
|
|
330
|
+
if (taskFile) childArgs.push('--task', taskFile);
|
|
331
|
+
if (graph) childArgs.push('--graph');
|
|
332
|
+
if (json) childArgs.push('--json');
|
|
333
|
+
|
|
334
|
+
const result = spawnSync('bash', [script, ...childArgs], {
|
|
335
|
+
stdio: 'inherit',
|
|
336
|
+
env: { ...process.env, CYNING_HARNESS: harnessRoot },
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const exitCode = result.status ?? 1;
|
|
340
|
+
if (exitCode !== 0) {
|
|
341
|
+
const err = new Error('');
|
|
342
|
+
err.exitCode = exitCode;
|
|
343
|
+
throw err;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function cmdVerify(args) {
|
|
348
|
+
// D3 · implemented in lib/verify.js
|
|
349
|
+
const { verifyTarget } = await import('./verify.js');
|
|
350
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
351
|
+
console.log(`用法: npx @cyning/harness verify [--target PATH] [--task FILE] [--graph]`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let rest = args;
|
|
356
|
+
const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
|
|
357
|
+
rest = r1;
|
|
358
|
+
const { value: taskFile, rest: r2 } = takeOption(rest, '--task');
|
|
359
|
+
rest = r2;
|
|
360
|
+
const graph = rest.includes('--graph');
|
|
361
|
+
rest = rest.filter((a) => a !== '--graph');
|
|
362
|
+
|
|
363
|
+
if (rest.length > 0) {
|
|
364
|
+
const err = new Error(`verify 未知参数: ${rest.join(' ')}`);
|
|
365
|
+
err.exitCode = 1;
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const target = resolveTarget(process.cwd(), targetArg);
|
|
370
|
+
const result = verifyTarget(target, { taskFile, graph });
|
|
371
|
+
|
|
372
|
+
// forward gate-check/audit/graph stdout if any
|
|
373
|
+
if (result.stdout) {
|
|
374
|
+
process.stdout.write(result.stdout);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const summary = taskFile
|
|
378
|
+
? `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''} · ${path.basename(taskFile)}`
|
|
379
|
+
: `VERIFY: ${result.ok ? 'PASS' : 'BLOCKED'}${result.reason ? ` · ${result.reason}` : ''}`;
|
|
380
|
+
console.log(summary);
|
|
381
|
+
|
|
382
|
+
if (!result.ok) {
|
|
383
|
+
const err = new Error('');
|
|
384
|
+
err.exitCode = result.exitCode ?? 2;
|
|
385
|
+
throw err;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function cmdSync(args) {
|
|
390
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
391
|
+
console.log(`用法: npx @cyning/harness sync index [--target PATH]`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const [sub, ...rest] = args;
|
|
396
|
+
if (sub !== 'index') {
|
|
397
|
+
const err = new Error(`sync 子命令未知: ${sub ?? '(空)'}\n用法: sync index [--target PATH]`);
|
|
398
|
+
err.exitCode = 1;
|
|
399
|
+
throw err;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let remaining = rest;
|
|
403
|
+
const { value: targetArg, rest: r1 } = takeOption(remaining, '--target');
|
|
404
|
+
remaining = r1;
|
|
405
|
+
|
|
406
|
+
if (remaining.length > 0) {
|
|
407
|
+
const err = new Error(`sync index 未知参数: ${remaining.join(' ')}`);
|
|
408
|
+
err.exitCode = 1;
|
|
409
|
+
throw err;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const target = resolveTarget(process.cwd(), targetArg);
|
|
413
|
+
const harnessRoot = resolveHarnessRootForTarget(target);
|
|
414
|
+
const script = wizardScript(harnessRoot, 'harness-sync.sh');
|
|
415
|
+
|
|
416
|
+
const result = spawnSync('bash', [script, '--index', '--target', target], {
|
|
417
|
+
stdio: 'inherit',
|
|
418
|
+
env: { ...process.env, CYNING_HARNESS: harnessRoot },
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const exitCode = result.status ?? 1;
|
|
422
|
+
if (exitCode !== 0) {
|
|
423
|
+
const err = new Error('');
|
|
424
|
+
err.exitCode = exitCode;
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
239
429
|
async function cmdTask(args) {
|
|
240
430
|
const [sub, ...rest] = args;
|
|
241
431
|
if (sub !== 'check') {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_SCRIPTS = {
|
|
5
|
+
'harness:verify': 'harness verify',
|
|
6
|
+
'harness:gate': 'harness gate-check',
|
|
7
|
+
'harness:audit': 'harness audit',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function detectPackageManager(target) {
|
|
11
|
+
if (fs.existsSync(path.join(target, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
12
|
+
if (fs.existsSync(path.join(target, 'yarn.lock'))) return 'yarn';
|
|
13
|
+
if (fs.existsSync(path.join(target, 'package-lock.json'))) return 'npm';
|
|
14
|
+
return 'npm';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 在 target 仓的 package.json 中 merge devDep + scripts(不覆盖已有 key)。
|
|
19
|
+
* @returns {{ok: boolean, skipped?: boolean, reason?: string, changed: boolean}}
|
|
20
|
+
*/
|
|
21
|
+
export function mergePackageScripts(target, version, pm = 'auto') {
|
|
22
|
+
const pkgPath = path.join(target, 'package.json');
|
|
23
|
+
if (!fs.existsSync(pkgPath)) {
|
|
24
|
+
return {
|
|
25
|
+
ok: true,
|
|
26
|
+
skipped: true,
|
|
27
|
+
reason: '未找到 package.json,跳过 --with-scripts',
|
|
28
|
+
changed: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
33
|
+
const original = JSON.stringify(pkg);
|
|
34
|
+
|
|
35
|
+
if (!pkg.devDependencies) pkg.devDependencies = {};
|
|
36
|
+
if (pkg.devDependencies['@cyning/harness'] !== version) {
|
|
37
|
+
pkg.devDependencies['@cyning/harness'] = version;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
41
|
+
for (const [key, value] of Object.entries(DEFAULT_SCRIPTS)) {
|
|
42
|
+
if (!Object.prototype.hasOwnProperty.call(pkg.scripts, key)) {
|
|
43
|
+
pkg.scripts[key] = value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (JSON.stringify(pkg) !== original) {
|
|
48
|
+
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
49
|
+
return {
|
|
50
|
+
ok: true,
|
|
51
|
+
skipped: false,
|
|
52
|
+
changed: true,
|
|
53
|
+
pm: pm === 'auto' ? detectPackageManager(target) : pm,
|
|
54
|
+
reason: `已写入 @cyning/harness@${version} 与 scripts`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
skipped: false,
|
|
61
|
+
changed: false,
|
|
62
|
+
pm: pm === 'auto' ? detectPackageManager(target) : pm,
|
|
63
|
+
reason: 'package.json 已包含所需 devDep/scripts',
|
|
64
|
+
};
|
|
65
|
+
}
|
package/lib/paths.js
CHANGED
|
@@ -12,6 +12,33 @@ export function resolveHarnessRoot() {
|
|
|
12
12
|
return path.resolve(__dirname, '..');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* 对指定业务仓 target 解析可用产品包根。
|
|
17
|
+
* 顺序:env CYNING_HARNESS → target/.cyning-harness/local.json → npm 包根
|
|
18
|
+
*/
|
|
19
|
+
export function resolveHarnessRootForTarget(target) {
|
|
20
|
+
if (process.env.CYNING_HARNESS) {
|
|
21
|
+
return path.resolve(process.env.CYNING_HARNESS);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const localJson = path.join(target, '.cyning-harness', 'local.json');
|
|
25
|
+
if (fs.existsSync(localJson)) {
|
|
26
|
+
try {
|
|
27
|
+
const data = JSON.parse(fs.readFileSync(localJson, 'utf8'));
|
|
28
|
+
if (data.cyning_harness_root) {
|
|
29
|
+
const candidate = path.resolve(target, data.cyning_harness_root);
|
|
30
|
+
if (fs.existsSync(path.join(candidate, 'wizard', 'gate-check.sh'))) {
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// fall through
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return path.resolve(__dirname, '..');
|
|
40
|
+
}
|
|
41
|
+
|
|
15
42
|
export function resolvePackageVersion(harnessRoot) {
|
|
16
43
|
if (process.env.HARNESS_VERSION) {
|
|
17
44
|
return process.env.HARNESS_VERSION;
|
package/lib/verify.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolveHarnessRootForTarget, wizardScript } from './paths.js';
|
|
5
|
+
import { auditTarget } from './audit.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 30 前聚合验证:gate-check + audit D5(仅 --task)+ S5 git-clean warn + 可选 --graph
|
|
9
|
+
*/
|
|
10
|
+
export function verifyTarget(target, options = {}) {
|
|
11
|
+
const { taskFile, graph } = options;
|
|
12
|
+
const harnessRoot = resolveHarnessRootForTarget(target);
|
|
13
|
+
|
|
14
|
+
// 1. gate-check(含 --graph)
|
|
15
|
+
const gateResult = runGateCheck(target, taskFile, graph, harnessRoot);
|
|
16
|
+
if (!gateResult.ok) {
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
exitCode: 2,
|
|
20
|
+
reason: gateResult.reason,
|
|
21
|
+
stdout: gateResult.stdout,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 2. audit D5(仅当指定 task)
|
|
26
|
+
if (taskFile) {
|
|
27
|
+
const audit = auditTarget(target, { taskFile });
|
|
28
|
+
if (!audit.ok) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
exitCode: 2,
|
|
32
|
+
reason: audit.test.ok ? 'gate-check 或 audit 未通过' : audit.test.reason,
|
|
33
|
+
stdout: gateResult.stdout,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 3. S5 git-clean:warn 不挡
|
|
39
|
+
const gitResult = runGitCleanCheck(target);
|
|
40
|
+
if (gitResult.dirty) {
|
|
41
|
+
// 把 warn 拼到 stdout 里让 CLI 透传
|
|
42
|
+
const warn = `WARN: 工作区未 clean(S5):建议 commit 后再执行 apply\n`;
|
|
43
|
+
gateResult.stdout += warn;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
exitCode: 0,
|
|
49
|
+
stdout: gateResult.stdout,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function runGateCheck(target, taskFile, graph, harnessRoot) {
|
|
54
|
+
const script = wizardScript(harnessRoot, 'gate-check.sh');
|
|
55
|
+
const args = [script, '--target', target];
|
|
56
|
+
if (taskFile) args.push('--task', taskFile);
|
|
57
|
+
if (graph) args.push('--graph');
|
|
58
|
+
|
|
59
|
+
const result = spawnSync('bash', args, {
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
env: { ...process.env, CYNING_HARNESS: harnessRoot },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (result.status !== 0) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
status: result.status,
|
|
68
|
+
stdout: result.stdout,
|
|
69
|
+
reason: extractBlockReason(result.stdout, taskFile),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
status: 0,
|
|
76
|
+
stdout: result.stdout,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractBlockReason(stdout) {
|
|
81
|
+
const lines = stdout.split('\n');
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (line.includes('→ 30 不可开工')) {
|
|
84
|
+
return line.trim().replace(/^→\s*/, '');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
if (line.includes('❌ 拒 30')) {
|
|
89
|
+
return line.trim();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return 'gate-check blocked';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function runGitCleanCheck(target) {
|
|
96
|
+
const result = spawnSync('git', ['status', '--porcelain'], {
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
cwd: target,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const dirty = result.status === 0 && result.stdout.trim().length > 0;
|
|
102
|
+
return { dirty };
|
|
103
|
+
}
|
package/ontology.yaml
CHANGED
package/package.json
CHANGED
package/wizard/README.md
CHANGED
|
@@ -15,6 +15,9 @@ npx @cyning/harness@latest init --preset harness-only --ide cursor,agents
|
|
|
15
15
|
npx @cyning/harness upgrade
|
|
16
16
|
npx @cyning/harness upgrade --target /path/to/project --yes --force
|
|
17
17
|
|
|
18
|
+
# 30 前验证
|
|
19
|
+
npx @cyning/harness verify --target /path/to/project
|
|
20
|
+
|
|
18
21
|
# 仅检查版本
|
|
19
22
|
npx @cyning/harness check
|
|
20
23
|
```
|
|
@@ -22,6 +25,7 @@ npx @cyning/harness check
|
|
|
22
25
|
- 内部单源:`wizard/install.sh` → `harness-sync.sh apply`(S2 写路径不变)
|
|
23
26
|
- manifest schema:[`schema/manifest.v1.schema.json`](../schema/manifest.v1.schema.json)
|
|
24
27
|
- 本地开发:`node bin/harness.js init --target /path/to/project --preset harness-only --ide cursor,agents --yes`
|
|
28
|
+
- Node 仓可选:`npx @cyning/harness init --with-scripts --pm pnpm`
|
|
25
29
|
|
|
26
30
|
---
|
|
27
31
|
|