@cyning/harness 1.0.2 → 2.0.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.
@@ -0,0 +1,111 @@
1
+ {
2
+ "schema_version": "graph_v2",
3
+ "freeze_id": "TECH_GRAPH_S2_FREEZE_20260519_V2_3",
4
+ "generated_at": "2026-06-17T00:00:00Z",
5
+ "graphs": [
6
+ {
7
+ "id": "00_main",
8
+ "title": "Harness P0 金样流程",
9
+ "source_yaml_path": "00_main.graph.yaml"
10
+ }
11
+ ],
12
+ "nodes": [
13
+ {
14
+ "graph_id": "00_main",
15
+ "id": "DONE",
16
+ "label": "P0 通过"
17
+ },
18
+ {
19
+ "graph_id": "00_main",
20
+ "id": "EXEC",
21
+ "label": "30 执行帽"
22
+ },
23
+ {
24
+ "graph_id": "00_main",
25
+ "id": "GATE",
26
+ "label": "gate-check"
27
+ },
28
+ {
29
+ "graph_id": "00_main",
30
+ "id": "INIT",
31
+ "label": "harness init"
32
+ },
33
+ {
34
+ "graph_id": "00_main",
35
+ "id": "R1",
36
+ "label": "22 审核帽"
37
+ },
38
+ {
39
+ "graph_id": "00_main",
40
+ "id": "START",
41
+ "label": "空 Git 仓"
42
+ },
43
+ {
44
+ "graph_id": "00_main",
45
+ "id": "TASK",
46
+ "label": "task_demo_p0_golden"
47
+ }
48
+ ],
49
+ "edges": [
50
+ {
51
+ "anchors": [],
52
+ "from": "EXEC",
53
+ "graph_id": "00_main",
54
+ "label": "40 自检",
55
+ "mark": "->",
56
+ "sync": true,
57
+ "to": "DONE",
58
+ "type": "depends_on"
59
+ },
60
+ {
61
+ "anchors": [],
62
+ "from": "GATE",
63
+ "graph_id": "00_main",
64
+ "label": "exit 0",
65
+ "mark": "->",
66
+ "sync": true,
67
+ "to": "EXEC",
68
+ "type": "depends_on"
69
+ },
70
+ {
71
+ "anchors": [],
72
+ "from": "INIT",
73
+ "graph_id": "00_main",
74
+ "label": "复制金样 task",
75
+ "mark": "->",
76
+ "sync": true,
77
+ "to": "TASK",
78
+ "type": "depends_on"
79
+ },
80
+ {
81
+ "anchors": [],
82
+ "from": "R1",
83
+ "graph_id": "00_main",
84
+ "label": "HG-AUDIT-R1 approved",
85
+ "mark": "->",
86
+ "sync": true,
87
+ "to": "GATE",
88
+ "type": "depends_on"
89
+ },
90
+ {
91
+ "anchors": [],
92
+ "from": "START",
93
+ "graph_id": "00_main",
94
+ "label": "install.sh",
95
+ "mark": "->",
96
+ "sync": true,
97
+ "to": "INIT",
98
+ "type": "depends_on"
99
+ },
100
+ {
101
+ "anchors": [],
102
+ "from": "TASK",
103
+ "graph_id": "00_main",
104
+ "label": "10 → 22",
105
+ "mark": "->",
106
+ "sync": true,
107
+ "to": "R1",
108
+ "type": "depends_on"
109
+ }
110
+ ]
111
+ }
package/lib/cli.js CHANGED
@@ -12,9 +12,23 @@ import { runBash } from './run.js';
12
12
  import { checkTaskFile } from './task.js';
13
13
  import { auditTarget } from './audit.js';
14
14
  import { mergePackageScripts } from './package-scripts.js';
15
+ import {
16
+ allGraphIds,
17
+ checkGraph,
18
+ compileGraph,
19
+ GraphYamlError,
20
+ yamlPathFor,
21
+ } from './graph-yaml.js';
22
+ import {
23
+ buildSnapshot,
24
+ checkAxioms,
25
+ ingestRepoIdempotent,
26
+ loadEvents,
27
+ writeSnapshot,
28
+ } from './graph-hgm.js';
15
29
 
16
- function usage() {
17
- console.log(`@cyning/harness · cyning-harness CLI (v1.0.2)
30
+ function usage(version = 'unknown') {
31
+ console.log(`@cyning/harness · cyning-harness CLI (v${version})
18
32
 
19
33
  用法:
20
34
  npx @cyning/harness --version | -V
@@ -27,6 +41,13 @@ function usage() {
27
41
  npx @cyning/harness verify [--target PATH] [--task FILE] [--graph]
28
42
  npx @cyning/harness sync index [--target PATH]
29
43
  npx @cyning/harness task check --file PATH [--no-circular] [--registry DIR]...
44
+ npx @cyning/harness graph yaml compile --graph-id ID [--input DIR] [--output FILE]
45
+ npx @cyning/harness graph yaml check --graph-id ID [--input DIR] [--graph-json FILE]
46
+ npx @cyning/harness graph yaml compile --all [--input DIR]
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]
30
51
 
31
52
  说明:
32
53
  init 首次接入 · 内部调用 wizard/install.sh + harness-sync.sh apply
@@ -37,6 +58,10 @@ function usage() {
37
58
  verify 30 前聚合:gate-check + audit D5 + S5 warn + 可选 --graph
38
59
  sync index 生成 .cyning-harness/invoke_index.json
39
60
  task check 校验 task.harness.v1.json sidecar · --no-circular 检测 depends_on 环
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+)
40
65
 
41
66
  环境:
42
67
  CYNING_HARNESS 覆盖产品包根(维护者本地 clone 开发)
@@ -59,7 +84,7 @@ export async function runCli(argv) {
59
84
  const pkgVersion = resolvePackageVersion(harnessRoot);
60
85
 
61
86
  if (argv.length === 0 || argv.includes('-h') || argv.includes('--help')) {
62
- usage();
87
+ usage(pkgVersion);
63
88
  return;
64
89
  }
65
90
 
@@ -102,6 +127,10 @@ export async function runCli(argv) {
102
127
  await cmdTask(rest);
103
128
  return;
104
129
  }
130
+ if (cmd === 'graph') {
131
+ await cmdGraph(rest);
132
+ return;
133
+ }
105
134
 
106
135
  const err = new Error(`未知命令: ${cmd}\n`);
107
136
  err.exitCode = 1;
@@ -495,3 +524,247 @@ async function cmdTask(args) {
495
524
  console.log('depends_on: acyclic');
496
525
  }
497
526
  }
527
+
528
+ async function cmdGraph(args) {
529
+ if (args.includes('--help') || args.includes('-h')) {
530
+ console.log(`用法: npx @cyning/harness graph <子命令> [选项]
531
+
532
+ 子命令:
533
+ graph yaml compile --graph-id ID [--input DIR] [--output FILE]
534
+ graph yaml compile --all [--input DIR]
535
+ graph yaml check --graph-id ID [--input DIR] [--graph-json FILE]
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]
540
+
541
+ 选项:
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
549
+ `);
550
+ return;
551
+ }
552
+
553
+ const [sub, ...subRest] = args;
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;
569
+ }
570
+
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;
578
+ if (action !== 'compile' && action !== 'check') {
579
+ const err = new Error(`graph yaml 动作未知: ${action ?? '(空)'}`);
580
+ err.exitCode = 1;
581
+ throw err;
582
+ }
583
+
584
+ const all = actionRest.includes('--all');
585
+ let rest = actionRest.filter((a) => a !== '--all');
586
+
587
+ const { value: graphId, rest: r1 } = takeOption(rest, '--graph-id');
588
+ rest = r1;
589
+ const { value: inputArg, rest: r2 } = takeOption(rest, '--input');
590
+ rest = r2;
591
+ const { value: outputArg, rest: r3 } = takeOption(rest, '--output');
592
+ rest = r3;
593
+ const { value: graphJsonArg, rest: r4 } = takeOption(rest, '--graph-json');
594
+ rest = r4;
595
+
596
+ if (rest.length > 0) {
597
+ const err = new Error(`graph yaml ${action} 未知参数: ${rest.join(' ')}`);
598
+ err.exitCode = 1;
599
+ throw err;
600
+ }
601
+
602
+ if (!all && !graphId) {
603
+ const err = new Error('须指定 --graph-id ID 或 --all');
604
+ err.exitCode = 1;
605
+ throw err;
606
+ }
607
+
608
+ const cwd = process.cwd();
609
+ const inputRoot = inputArg ? path.resolve(cwd, inputArg) : path.resolve(cwd, 'docs', '_tech_graph');
610
+ const graphJsonPath = graphJsonArg
611
+ ? path.resolve(cwd, graphJsonArg)
612
+ : path.join(inputRoot, 'graph.json');
613
+
614
+ try {
615
+ if (action === 'compile') {
616
+ if (all) {
617
+ const ids = allGraphIds(inputRoot);
618
+ if (ids.length === 0) {
619
+ console.log('未找到 *.graph.yaml');
620
+ return;
621
+ }
622
+ for (const id of ids) {
623
+ const out = compileGraph(id, inputRoot);
624
+ console.log(`Generated: ${out}`);
625
+ }
626
+ } else {
627
+ const out = compileGraph(graphId, inputRoot, outputArg ? path.resolve(cwd, outputArg) : null);
628
+ console.log(`Generated: ${out}`);
629
+ }
630
+ return;
631
+ }
632
+
633
+ // action === 'check'
634
+ if (all) {
635
+ const ids = allGraphIds(inputRoot);
636
+ if (ids.length === 0) {
637
+ console.log('未找到 *.graph.yaml');
638
+ return;
639
+ }
640
+ let failed = false;
641
+ for (const id of ids) {
642
+ const result = checkGraph(id, inputRoot, graphJsonPath);
643
+ if (result.ok) {
644
+ console.log(`OK: ${id}`);
645
+ } else {
646
+ failed = true;
647
+ console.error(`ERROR: ${id}\n${result.diff}`);
648
+ }
649
+ }
650
+ if (failed) {
651
+ const err = new Error('graph yaml check 发现差异');
652
+ err.exitCode = 1;
653
+ throw err;
654
+ }
655
+ return;
656
+ }
657
+
658
+ const result = checkGraph(graphId, inputRoot, graphJsonPath);
659
+ if (result.ok) {
660
+ console.log(`OK: YAML matches graph.json ${graphId} slice`);
661
+ } else {
662
+ console.error(`ERROR: Diff detected for ${graphId}:\n${result.diff}`);
663
+ const err = new Error('graph yaml check 发现差异');
664
+ err.exitCode = 1;
665
+ throw err;
666
+ }
667
+ } catch (err) {
668
+ if (err instanceof GraphYamlError) {
669
+ console.error(err.message);
670
+ const exitErr = new Error('');
671
+ exitErr.exitCode = 1;
672
+ throw exitErr;
673
+ }
674
+ throw err;
675
+ }
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);
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
+ }