release-skill 0.7.6 → 0.7.8

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.
Files changed (61) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +40 -0
  7. package/INSTALL.md +2 -2
  8. package/INSTALL.zh-CN.md +2 -2
  9. package/README.md +14 -11
  10. package/README.zh-CN.md +13 -10
  11. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/release-skill.bundle.mjs +1728 -1155
  14. package/adapters/claude/schemas/.render-manifest.json +6 -6
  15. package/adapters/claude/schemas/release-plan.schema.json +47 -0
  16. package/adapters/claude/schemas/release-project.schema.json +24 -0
  17. package/adapters/claude/schemas/release-run.schema.json +41 -1
  18. package/adapters/claude/skills/release-prepare/SKILL.md +4 -0
  19. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  20. package/adapters/codex/bin/release-skill.bundle.mjs +1728 -1155
  21. package/adapters/codex/schemas/.render-manifest.json +6 -6
  22. package/adapters/codex/schemas/release-plan.schema.json +47 -0
  23. package/adapters/codex/schemas/release-project.schema.json +24 -0
  24. package/adapters/codex/schemas/release-run.schema.json +41 -1
  25. package/adapters/codex/skills/release-prepare/SKILL.md +4 -0
  26. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  27. package/adapters/kimi/bin/release-skill.bundle.mjs +1728 -1155
  28. package/adapters/kimi/schemas/.render-manifest.json +6 -6
  29. package/adapters/kimi/schemas/release-plan.schema.json +47 -0
  30. package/adapters/kimi/schemas/release-project.schema.json +24 -0
  31. package/adapters/kimi/schemas/release-run.schema.json +41 -1
  32. package/adapters/kimi/skills/release-prepare/SKILL.md +4 -0
  33. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  34. package/adapters/workbuddy/bin/release-skill.bundle.mjs +1728 -1155
  35. package/adapters/workbuddy/schemas/.render-manifest.json +6 -6
  36. package/adapters/workbuddy/schemas/release-plan.schema.json +47 -0
  37. package/adapters/workbuddy/schemas/release-project.schema.json +24 -0
  38. package/adapters/workbuddy/schemas/release-run.schema.json +41 -1
  39. package/adapters/workbuddy/skills/release-prepare/SKILL.md +4 -0
  40. package/bin/release-skill-cli.mjs +116 -47
  41. package/bin/release-skill.bundle.mjs +1728 -1155
  42. package/package.json +1 -1
  43. package/platform-manifest.json +4 -4
  44. package/references/.render-manifest.json +6 -6
  45. package/references/01-state-machine.md +2 -2
  46. package/references/02-project-config.md +18 -0
  47. package/references/06-adapter-contract.md +4 -3
  48. package/schemas/.render-manifest.json +6 -6
  49. package/schemas/release-plan.schema.json +47 -0
  50. package/schemas/release-project.schema.json +24 -0
  51. package/schemas/release-run.schema.json +41 -1
  52. package/skills/release-prepare/SKILL.md +4 -0
  53. package/skills-src/release-prepare/SKILL.md +4 -0
  54. package/src/adapters/git-github.mjs +139 -26
  55. package/src/commands/prepare.mjs +72 -3
  56. package/src/commands/publish.mjs +51 -0
  57. package/src/commands/verify.mjs +53 -1
  58. package/src/core/config.mjs +31 -0
  59. package/src/core/plan.mjs +78 -0
  60. package/src/core/release-assets.mjs +54 -0
  61. package/src/core/source-authority.mjs +127 -3
@@ -26,16 +26,16 @@
26
26
  "bytes": 1744
27
27
  },
28
28
  "release-plan.schema.json": {
29
- "digest": "48b7755dfbeee88ccf78c3200000ab2f5dd06db42bc7b423aed0f81a91cc0418",
30
- "bytes": 54672
29
+ "digest": "3d24cb908cdfbfd5d89551b7c6fed5140f6f1448b9920f114264291ca4cf1c52",
30
+ "bytes": 55905
31
31
  },
32
32
  "release-project.schema.json": {
33
- "digest": "cc2061799c3a0dd6daea940148e3497b7a4ae8cafb987bf51de7c34c1fed921d",
34
- "bytes": 46932
33
+ "digest": "b0ba3c46692622f734a80d3f4e3904180bbfd1096ca2d9b6d6fd957917e96f73",
34
+ "bytes": 47688
35
35
  },
36
36
  "release-run.schema.json": {
37
- "digest": "9755a8caa58dd02509ac8e6f869aec5e74d8ae3f7339a3f944977bfad7b729ad",
38
- "bytes": 17897
37
+ "digest": "4a041b8f6488e7d61b7eb271ebdbc2e043731df41c4437783ef63e77f664c192",
38
+ "bytes": 19002
39
39
  }
40
40
  }
41
41
  }
@@ -53,6 +53,53 @@
53
53
  }
54
54
  }
55
55
  },
56
+ "publicSourceAuthorityReceipt": {
57
+ "type": "object",
58
+ "required": [
59
+ "coordinatorUnitId",
60
+ "subjectUnitIds",
61
+ "asset"
62
+ ],
63
+ "additionalProperties": false,
64
+ "properties": {
65
+ "coordinatorUnitId": {
66
+ "type": "string",
67
+ "pattern": "^(?!\\.{1,2}$)[A-Za-z0-9][A-Za-z0-9._-]*$"
68
+ },
69
+ "subjectUnitIds": {
70
+ "type": "array",
71
+ "minItems": 1,
72
+ "uniqueItems": true,
73
+ "items": {
74
+ "type": "string",
75
+ "pattern": "^(?!\\.{1,2}$)[A-Za-z0-9][A-Za-z0-9._-]*$"
76
+ }
77
+ },
78
+ "asset": {
79
+ "type": "object",
80
+ "required": [
81
+ "name",
82
+ "path",
83
+ "sha256"
84
+ ],
85
+ "additionalProperties": false,
86
+ "properties": {
87
+ "name": {
88
+ "const": "source-authority-receipt.json"
89
+ },
90
+ "path": {
91
+ "type": "string",
92
+ "minLength": 1,
93
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*\\\\).+$"
94
+ },
95
+ "sha256": {
96
+ "type": "string",
97
+ "pattern": "^[a-f0-9]{64}$"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ },
56
103
  "baseline": {
57
104
  "type": "object",
58
105
  "required": [
@@ -44,6 +44,30 @@
44
44
  }
45
45
  }
46
46
  },
47
+ "publicSourceAuthorityReceipt": {
48
+ "type": "object",
49
+ "description": "Optional public source-authority receipt uploaded to one coordinator GitHub Release after all subject npm tarballs are frozen.",
50
+ "required": [
51
+ "coordinatorUnitId",
52
+ "subjectUnitIds"
53
+ ],
54
+ "additionalProperties": false,
55
+ "properties": {
56
+ "coordinatorUnitId": {
57
+ "type": "string",
58
+ "pattern": "^(?!\\.{1,2}$)[A-Za-z0-9][A-Za-z0-9._-]*$"
59
+ },
60
+ "subjectUnitIds": {
61
+ "type": "array",
62
+ "minItems": 1,
63
+ "uniqueItems": true,
64
+ "items": {
65
+ "type": "string",
66
+ "pattern": "^(?!\\.{1,2}$)[A-Za-z0-9][A-Za-z0-9._-]*$"
67
+ }
68
+ }
69
+ }
70
+ },
47
71
  "releaseUnits": {
48
72
  "type": "array",
49
73
  "minItems": 1,
@@ -199,6 +199,11 @@
199
199
  },
200
200
  "url": {
201
201
  "type": "string"
202
+ },
203
+ "integrity": {
204
+ "type": "string",
205
+ "pattern": "^sha512-[A-Za-z0-9+/]{86}==$",
206
+ "description": "Observed npm dist.integrity when an already-consistent npm publish is skipped"
202
207
  }
203
208
  }
204
209
  },
@@ -280,7 +285,42 @@
280
285
  "minimum": 0,
281
286
  "description": "写入目标的载荷文件数"
282
287
  }
283
- }
288
+ },
289
+ "allOf": [
290
+ {
291
+ "if": {
292
+ "properties": {
293
+ "remoteRef": {
294
+ "type": "object",
295
+ "required": [
296
+ "integrity"
297
+ ]
298
+ }
299
+ },
300
+ "required": [
301
+ "remoteRef"
302
+ ]
303
+ },
304
+ "then": {
305
+ "properties": {
306
+ "actionType": {
307
+ "const": "npm-publish"
308
+ },
309
+ "status": {
310
+ "const": "skipped"
311
+ },
312
+ "preObserve": {
313
+ "const": "CONSISTENT"
314
+ }
315
+ },
316
+ "required": [
317
+ "actionType",
318
+ "status",
319
+ "preObserve"
320
+ ]
321
+ }
322
+ }
323
+ ]
284
324
  }
285
325
  },
286
326
  "sourceAuthorityReceipts": {
@@ -33,6 +33,10 @@ description: Freeze an immutable release plan with local configuration, document
33
33
 
34
34
  若用户明确要求 GitHub+npm 生产发布,加入 `--production`。该模式还会封存独立
35
35
  Git commit/tree 和 npm tarball,并把路径、SHA/integrity、branch/tag 写入计划。
36
+ 配置声明 `publicSourceAuthorityReceipt` 时,prepare 必须在所有 subject npm tarball
37
+ 冻结后生成 `source-authority-receipt.json`,并把该文件的路径与 SHA-256 绑定到
38
+ coordinator unit 的 `github-release` action。该能力不支持非生产 prepare;不得手工
39
+ 补写 receipt 或把私有 plan/run 字段复制进公开文件。
36
40
  每个 npm tarball 在计划落盘前必须静态验证 `package.json` 的具体
37
41
  `bin`/`main`/`module`/`types`/`typings`/`exports` 入口均为 tarball 内普通文件;
38
42
  该门禁不依赖项目是否配置 `requiredPublicFiles` 或 `smokeBin`。通配符 exports 不做
@@ -7,6 +7,23 @@ import { parseNodeMajor, meetsMinimum, computeReadinessStatus } from '../src/cor
7
7
  import { registerPathRedactor } from '../src/core/errors.mjs';
8
8
  import { redactSensitivePaths } from '../src/core/redact.mjs';
9
9
 
10
+ // Process output errors can be emitted immediately after a large console
11
+ // write, before command routing reaches its exit path. Guard both streams at
12
+ // module initialization so EPIPE and other destination failures cannot become
13
+ // uncaught exceptions that replace the command's selected business exit code.
14
+ const outputStreamStates = new WeakMap();
15
+
16
+ function guardOutputStream(stream) {
17
+ const state = { failed: false };
18
+ outputStreamStates.set(stream, state);
19
+ stream.on('error', () => {
20
+ state.failed = true;
21
+ });
22
+ }
23
+
24
+ guardOutputStream(process.stdout);
25
+ guardOutputStream(process.stderr);
26
+
10
27
  // Install the path-redaction choke point eagerly and synchronously (static
11
28
  // imports, no top-level await) so every ReleaseError constructed on any
12
29
  // command path is redacted from the very first statement — in source mode and
@@ -21,6 +38,58 @@ const execFile = promisify(execFileCb);
21
38
 
22
39
  const COMMANDS = new Set(['help', 'setup', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'ship', 'attest', 'hooks', 'artifacts', 'docs', 'distribute', 'route', 'lineage']);
23
40
 
41
+ /**
42
+ * Wait until writes already queued on a process output stream have reached
43
+ * the underlying destination. Stream failures do not replace the command's
44
+ * previously selected exit code.
45
+ *
46
+ * @param {NodeJS.WriteStream} stream
47
+ * @returns {Promise<void>}
48
+ */
49
+ function flushOutputStream(stream) {
50
+ if (
51
+ !stream?.writable
52
+ || stream.destroyed
53
+ || stream.writableEnded
54
+ || outputStreamStates.get(stream)?.failed
55
+ ) {
56
+ return Promise.resolve();
57
+ }
58
+ return new Promise((resolveFlush) => {
59
+ let settled = false;
60
+ const finish = () => {
61
+ if (settled) return;
62
+ settled = true;
63
+ stream.off('error', finish);
64
+ resolveFlush();
65
+ };
66
+ stream.once('error', finish);
67
+ try {
68
+ // The callback runs after this empty write and every earlier console
69
+ // write have been flushed. This also covers a stream waiting for drain.
70
+ stream.write('', finish);
71
+ } catch {
72
+ finish();
73
+ }
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Preserve CLI exit semantics while preventing buffered stdout/stderr from
79
+ * being discarded by process.exit().
80
+ *
81
+ * @param {number} exitCode
82
+ * @returns {Promise<never>}
83
+ */
84
+ async function exitAfterFlush(exitCode) {
85
+ process.exitCode = exitCode;
86
+ await Promise.all([
87
+ flushOutputStream(process.stdout),
88
+ flushOutputStream(process.stderr),
89
+ ]);
90
+ process.exit(exitCode);
91
+ }
92
+
24
93
  /**
25
94
  * Check if a command is available and get its version.
26
95
  *
@@ -346,7 +415,7 @@ if (!command && (args.includes('--version') || args.includes('-v'))) {
346
415
  } else {
347
416
  console.log(pkg.version);
348
417
  }
349
- process.exit(0);
418
+ await exitAfterFlush(0);
350
419
  }
351
420
 
352
421
  if (!command || command === 'help') {
@@ -442,10 +511,10 @@ if (!command || command === 'help') {
442
511
  // availability is informational and never blocks release readiness.
443
512
 
444
513
  console.log(JSON.stringify(output, null, 2));
445
- process.exit(readiness.status === 'READY' ? 0 : 1);
514
+ await exitAfterFlush(readiness.status === 'READY' ? 0 : 1);
446
515
  } else {
447
516
  printHelp();
448
- process.exit(0);
517
+ await exitAfterFlush(0);
449
518
  }
450
519
  }
451
520
 
@@ -461,7 +530,7 @@ if (!COMMANDS.has(command)) {
461
530
  console.error(`Error: Unknown command '${command}'`);
462
531
  console.error('Run "release-skill help" for available commands.');
463
532
  }
464
- process.exit(2);
533
+ await exitAfterFlush(2);
465
534
  }
466
535
 
467
536
  // --- Setup command routing ---
@@ -513,7 +582,7 @@ if (command === 'setup') {
513
582
  if (report.next) console.log(`Next: ${report.next}`);
514
583
  }
515
584
  const okStatuses = ['HOOKS_PROPOSAL_READY', 'HOOKS_APPENDED', 'HOOKS_NO_CHANGE', 'postpublish-discovery'];
516
- process.exit(okStatuses.includes(report.status ?? report.mode) ? 0 : 2);
585
+ await exitAfterFlush(okStatuses.includes(report.status ?? report.mode) ? 0 : 2);
517
586
  } catch (err) {
518
587
  if (hasJson) {
519
588
  console.log(JSON.stringify({
@@ -525,7 +594,7 @@ if (command === 'setup') {
525
594
  } else {
526
595
  console.error(`Error: ${err.message}`);
527
596
  }
528
- process.exit(err.exitCode ?? 1);
597
+ await exitAfterFlush(err.exitCode ?? 1);
529
598
  }
530
599
  }
531
600
 
@@ -540,7 +609,7 @@ if (command === 'setup') {
540
609
  if (report.configPath) console.log(`Config: ${report.configPath}`);
541
610
  if (report.next) console.log(`Next: ${report.next}`);
542
611
  }
543
- process.exit(['READY_TO_WRITE', 'CONFIG_CREATED', 'ALREADY_CONFIGURED'].includes(report.status) ? 0 : 2);
612
+ await exitAfterFlush(['READY_TO_WRITE', 'CONFIG_CREATED', 'ALREADY_CONFIGURED'].includes(report.status) ? 0 : 2);
544
613
  } catch (err) {
545
614
  if (hasJson) {
546
615
  console.log(JSON.stringify({
@@ -552,7 +621,7 @@ if (command === 'setup') {
552
621
  } else {
553
622
  console.error(`Error: ${err.message}`);
554
623
  }
555
- process.exit(err.exitCode ?? 1);
624
+ await exitAfterFlush(err.exitCode ?? 1);
556
625
  }
557
626
  }
558
627
 
@@ -575,7 +644,7 @@ if (command === 'assess') {
575
644
  console.log(report.summary);
576
645
  }
577
646
 
578
- process.exit(report.status === 'ASSESSED' ? 0 : 1);
647
+ await exitAfterFlush(report.status === 'ASSESSED' ? 0 : 1);
579
648
  } catch (err) {
580
649
  if (hasJson) {
581
650
  const errOutput = {
@@ -588,7 +657,7 @@ if (command === 'assess') {
588
657
  } else {
589
658
  console.error(`Error: ${err.message}`);
590
659
  }
591
- process.exit(err.exitCode ?? 1);
660
+ await exitAfterFlush(err.exitCode ?? 1);
592
661
  }
593
662
  }
594
663
 
@@ -601,7 +670,7 @@ if (command === 'hooks') {
601
670
  const message = 'hooks requires subcommand: hooks validate';
602
671
  if (hasJson) console.log(JSON.stringify({ error: 'MISSING_PARAMETERS', message }));
603
672
  else console.error(`Error: ${message}`);
604
- process.exit(1);
673
+ await exitAfterFlush(1);
605
674
  }
606
675
  try {
607
676
  const { validateDeclaredHooks } = await import('../src/commands/hooks.mjs');
@@ -616,7 +685,7 @@ if (command === 'hooks') {
616
685
  console.log(`Declared hooks: ${result.status}`);
617
686
  console.log(`Reusable receipt evidence: ${result.evidenceDir}`);
618
687
  }
619
- process.exit(0);
688
+ await exitAfterFlush(0);
620
689
  } catch (err) {
621
690
  if (hasJson) {
622
691
  console.log(JSON.stringify({
@@ -626,7 +695,7 @@ if (command === 'hooks') {
626
695
  exitCode: err.exitCode ?? 1,
627
696
  }));
628
697
  } else console.error(`Error: ${err.message}`);
629
- process.exit(err.exitCode ?? 1);
698
+ await exitAfterFlush(err.exitCode ?? 1);
630
699
  }
631
700
  }
632
701
 
@@ -710,7 +779,7 @@ if (command === 'ship') {
710
779
  console.log(`Manual requirement [${requirement.platform}]: ${requirement.requirementPath}`);
711
780
  }
712
781
  }
713
- process.exit(0);
782
+ await exitAfterFlush(0);
714
783
  } catch (err) {
715
784
  if (hasJson) {
716
785
  console.log(JSON.stringify({
@@ -722,7 +791,7 @@ if (command === 'ship') {
722
791
  } else {
723
792
  console.error(`Error: ${err.message}`);
724
793
  }
725
- process.exit(err.exitCode ?? 1);
794
+ await exitAfterFlush(err.exitCode ?? 1);
726
795
  }
727
796
  }
728
797
 
@@ -752,7 +821,7 @@ if (command === 'attest') {
752
821
  console.log(`Bound plan digest: ${result.planDigest}`);
753
822
  console.log(`Receipt: ${result.attestationPath}`);
754
823
  }
755
- process.exit(0);
824
+ await exitAfterFlush(0);
756
825
  } catch (err) {
757
826
  if (hasJson) {
758
827
  console.log(JSON.stringify({
@@ -764,7 +833,7 @@ if (command === 'attest') {
764
833
  } else {
765
834
  console.error(`Error: ${err.message}`);
766
835
  }
767
- process.exit(err.exitCode ?? 1);
836
+ await exitAfterFlush(err.exitCode ?? 1);
768
837
  }
769
838
  }
770
839
 
@@ -869,7 +938,7 @@ if (command === 'prepare') {
869
938
  }
870
939
  }
871
940
 
872
- process.exit(0);
941
+ await exitAfterFlush(0);
873
942
  } catch (err) {
874
943
  if (hasJson) {
875
944
  const errOutput = {
@@ -882,7 +951,7 @@ if (command === 'prepare') {
882
951
  } else {
883
952
  console.error(`Error: ${err.message}`);
884
953
  }
885
- process.exit(err.exitCode ?? 1);
954
+ await exitAfterFlush(err.exitCode ?? 1);
886
955
  }
887
956
  }
888
957
 
@@ -909,7 +978,7 @@ if (command === 'approve') {
909
978
  } else {
910
979
  console.error(`Error: ${msg}`);
911
980
  }
912
- process.exit(1);
981
+ await exitAfterFlush(1);
913
982
  }
914
983
 
915
984
  // --- Checkpoint-level postPublish hook approval (v0.6.3 R1, review N-6) ---
@@ -935,7 +1004,7 @@ if (command === 'approve') {
935
1004
  console.log(` ${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`);
936
1005
  }
937
1006
  }
938
- process.exit(0);
1007
+ await exitAfterFlush(0);
939
1008
  } catch (err) {
940
1009
  if (hasJson) {
941
1010
  console.log(JSON.stringify({
@@ -946,7 +1015,7 @@ if (command === 'approve') {
946
1015
  } else {
947
1016
  console.error(`Error: ${err.message}`);
948
1017
  }
949
- process.exit(err.exitCode ?? 1);
1018
+ await exitAfterFlush(err.exitCode ?? 1);
950
1019
  }
951
1020
  }
952
1021
 
@@ -979,7 +1048,7 @@ if (command === 'approve') {
979
1048
  console.log(`Expires at: ${record.expiresAt}`);
980
1049
  }
981
1050
 
982
- process.exit(0);
1051
+ await exitAfterFlush(0);
983
1052
  } catch (err) {
984
1053
  if (hasJson) {
985
1054
  const errOutput = {
@@ -991,7 +1060,7 @@ if (command === 'approve') {
991
1060
  } else {
992
1061
  console.error(`Error: ${err.message}`);
993
1062
  }
994
- process.exit(err.exitCode ?? 1);
1063
+ await exitAfterFlush(err.exitCode ?? 1);
995
1064
  }
996
1065
  }
997
1066
 
@@ -1015,7 +1084,7 @@ if (command === 'reconcile') {
1015
1084
  } else {
1016
1085
  console.error(`Error: ${msg}`);
1017
1086
  }
1018
- process.exit(1);
1087
+ await exitAfterFlush(1);
1019
1088
  }
1020
1089
 
1021
1090
  try {
@@ -1050,7 +1119,7 @@ if (command === 'reconcile') {
1050
1119
  }
1051
1120
  }
1052
1121
 
1053
- process.exit(result.status === 'PUBLISHED' ? 0 : 1);
1122
+ await exitAfterFlush(result.status === 'PUBLISHED' ? 0 : 1);
1054
1123
  } catch (err) {
1055
1124
  if (hasJson) {
1056
1125
  const errOutput = {
@@ -1062,7 +1131,7 @@ if (command === 'reconcile') {
1062
1131
  } else {
1063
1132
  console.error(`Error: ${err.message}`);
1064
1133
  }
1065
- process.exit(err.exitCode ?? 1);
1134
+ await exitAfterFlush(err.exitCode ?? 1);
1066
1135
  }
1067
1136
  }
1068
1137
 
@@ -1085,7 +1154,7 @@ if (command === 'verify') {
1085
1154
  } else {
1086
1155
  console.error(`Error: ${msg}`);
1087
1156
  }
1088
- process.exit(1);
1157
+ await exitAfterFlush(1);
1089
1158
  }
1090
1159
 
1091
1160
  try {
@@ -1127,7 +1196,7 @@ if (command === 'verify') {
1127
1196
  }
1128
1197
  }
1129
1198
 
1130
- process.exit(result.status === 'VERIFIED' ? 0 : 1);
1199
+ await exitAfterFlush(result.status === 'VERIFIED' ? 0 : 1);
1131
1200
  } catch (err) {
1132
1201
  if (hasJson) {
1133
1202
  const errOutput = {
@@ -1139,7 +1208,7 @@ if (command === 'verify') {
1139
1208
  } else {
1140
1209
  console.error(`Error: ${err.message}`);
1141
1210
  }
1142
- process.exit(err.exitCode ?? 1);
1211
+ await exitAfterFlush(err.exitCode ?? 1);
1143
1212
  }
1144
1213
  }
1145
1214
 
@@ -1161,7 +1230,7 @@ if (command === 'publish') {
1161
1230
  } else {
1162
1231
  console.error(`Error: ${msg}`);
1163
1232
  }
1164
- process.exit(1);
1233
+ await exitAfterFlush(1);
1165
1234
  }
1166
1235
 
1167
1236
  try {
@@ -1196,7 +1265,7 @@ if (command === 'publish') {
1196
1265
  }
1197
1266
  }
1198
1267
 
1199
- process.exit(result.status === 'PUBLISHED' ? 0 : 1);
1268
+ await exitAfterFlush(result.status === 'PUBLISHED' ? 0 : 1);
1200
1269
  } catch (err) {
1201
1270
  if (hasJson) {
1202
1271
  const errOutput = {
@@ -1208,7 +1277,7 @@ if (command === 'publish') {
1208
1277
  } else {
1209
1278
  console.error(`Error: ${err.message}`);
1210
1279
  }
1211
- process.exit(err.exitCode ?? 1);
1280
+ await exitAfterFlush(err.exitCode ?? 1);
1212
1281
  }
1213
1282
  }
1214
1283
 
@@ -1242,7 +1311,7 @@ if (command === 'artifacts') {
1242
1311
  'BASE_UNAVAILABLE', 'POLICY_INVALID', 'PATH_UNSAFE',
1243
1312
  'CONFLICT', 'DIRTY_SCOPE_CONFLICT',
1244
1313
  ]);
1245
- process.exit(blockingStatuses.has(result.status) ? 1 : 0);
1314
+ await exitAfterFlush(blockingStatuses.has(result.status) ? 1 : 0);
1246
1315
  } catch (err) {
1247
1316
  if (hasJson) {
1248
1317
  const errOutput = {
@@ -1259,7 +1328,7 @@ if (command === 'artifacts') {
1259
1328
  } else {
1260
1329
  console.error(`Error: ${err.message}`);
1261
1330
  }
1262
- process.exit(err.exitCode ?? 1);
1331
+ await exitAfterFlush(err.exitCode ?? 1);
1263
1332
  }
1264
1333
  }
1265
1334
 
@@ -1369,7 +1438,7 @@ if (command === 'docs') {
1369
1438
  }
1370
1439
  }
1371
1440
 
1372
- process.exit(0);
1441
+ await exitAfterFlush(0);
1373
1442
  } catch (err) {
1374
1443
  // docs parameter errors must surface the stable JSON error shape even in
1375
1444
  // text mode (CLI parameter validation precedes any service I/O).
@@ -1379,7 +1448,7 @@ if (command === 'docs') {
1379
1448
  details: err.details ?? {},
1380
1449
  exitCode: err.exitCode ?? 1,
1381
1450
  }));
1382
- process.exit(err.exitCode ?? 1);
1451
+ await exitAfterFlush(err.exitCode ?? 1);
1383
1452
  }
1384
1453
  }
1385
1454
 
@@ -1426,7 +1495,7 @@ if (command === 'distribute') {
1426
1495
  console.log(` write-downstream: ${preset.writeDownstream}; target optional: ${preset.targetOptional}; status: ${status}`);
1427
1496
  }
1428
1497
  }
1429
- process.exit(0);
1498
+ await exitAfterFlush(0);
1430
1499
  }
1431
1500
 
1432
1501
  // Handle --help explicitly
@@ -1468,7 +1537,7 @@ Description:
1468
1537
  } else {
1469
1538
  console.log(helpText);
1470
1539
  }
1471
- process.exit(0);
1540
+ await exitAfterFlush(0);
1472
1541
  }
1473
1542
 
1474
1543
  if (!planPath || !runPath || !approvalPath) {
@@ -1478,7 +1547,7 @@ Description:
1478
1547
  } else {
1479
1548
  console.error(`Error: ${msg}`);
1480
1549
  }
1481
- process.exit(1);
1550
+ await exitAfterFlush(1);
1482
1551
  }
1483
1552
 
1484
1553
  try {
@@ -1522,7 +1591,7 @@ Description:
1522
1591
  }
1523
1592
  }
1524
1593
 
1525
- process.exit(result.status === 'DISTRIBUTED' ? 0 : 1);
1594
+ await exitAfterFlush(result.status === 'DISTRIBUTED' ? 0 : 1);
1526
1595
  } catch (err) {
1527
1596
  if (hasJson) {
1528
1597
  const errOutput = {
@@ -1535,7 +1604,7 @@ Description:
1535
1604
  } else {
1536
1605
  console.error(`Error: ${err.message}`);
1537
1606
  }
1538
- process.exit(err.exitCode ?? 1);
1607
+ await exitAfterFlush(err.exitCode ?? 1);
1539
1608
  }
1540
1609
  }
1541
1610
 
@@ -1558,7 +1627,7 @@ if (command === 'route') {
1558
1627
  }
1559
1628
  }
1560
1629
 
1561
- process.exit(result.exitCode ?? 0);
1630
+ await exitAfterFlush(result.exitCode ?? 0);
1562
1631
  } catch (err) {
1563
1632
  if (hasJson) {
1564
1633
  console.log(JSON.stringify({
@@ -1570,7 +1639,7 @@ if (command === 'route') {
1570
1639
  } else {
1571
1640
  console.error(`Error: ${err.message}`);
1572
1641
  }
1573
- process.exit(err.exitCode ?? 1);
1642
+ await exitAfterFlush(err.exitCode ?? 1);
1574
1643
  }
1575
1644
  }
1576
1645
 
@@ -1592,7 +1661,7 @@ if (command === 'lineage') {
1592
1661
  }
1593
1662
  }
1594
1663
 
1595
- process.exit(result.status === 'ERROR' ? 1 : 0);
1664
+ await exitAfterFlush(result.status === 'ERROR' ? 1 : 0);
1596
1665
  } catch (err) {
1597
1666
  if (hasJson) {
1598
1667
  console.log(JSON.stringify({
@@ -1604,10 +1673,10 @@ if (command === 'lineage') {
1604
1673
  } else {
1605
1674
  console.error(`Error: ${err.message}`);
1606
1675
  }
1607
- process.exit(err.exitCode ?? 1);
1676
+ await exitAfterFlush(err.exitCode ?? 1);
1608
1677
  }
1609
1678
  }
1610
1679
 
1611
1680
  // Placeholder: remaining commands will be wired in later commands
1612
1681
  console.error(`Command '${command}' is not yet implemented.`);
1613
- process.exit(1);
1682
+ await exitAfterFlush(1);