phasegate 0.254.0 → 0.264.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.ja.md +6 -3
  3. package/README.md +6 -3
  4. package/docs/contracts/attestation-v2.schema.json +110 -0
  5. package/docs/guide/cli-reference.md +12 -1
  6. package/docs/guide/configuration.md +56 -0
  7. package/docs/templates/ci/aidlc-gate.yml +42 -2
  8. package/package.json +1 -1
  9. package/scripts/harness/agent-integration/application/dto/open-world-obligations-context-dto.ts +30 -0
  10. package/scripts/harness/agent-integration/application/ports/world-obligations-query-port.ts +30 -0
  11. package/scripts/harness/agent-integration/application/usecases/get-open-world-obligations-context-usecase.ts +66 -0
  12. package/scripts/harness/agent-integration/infrastructure/adapters/world-model-open-obligations-query-adapter.ts +58 -0
  13. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +37 -1
  14. package/scripts/harness/agent-integration/presentation/world-obligations-session-context.ts +60 -0
  15. package/scripts/harness/attestation/application/dto/attestation-document.ts +17 -4
  16. package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +55 -4
  17. package/scripts/harness/attestation/application/ports/world-snapshot-root-provider.ts +10 -0
  18. package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +33 -7
  19. package/scripts/harness/attestation/composition-root.ts +5 -0
  20. package/scripts/harness/attestation/domain/entities/attestation-record.ts +37 -2
  21. package/scripts/harness/attestation/index.ts +7 -1
  22. package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +5 -2
  23. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +35 -8
  24. package/scripts/harness/config-foundation/application/mappers/world-model-config-mapper.ts +15 -0
  25. package/scripts/harness/config-foundation/domain/harness-config.ts +67 -87
  26. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +77 -88
  27. package/scripts/harness/config-foundation/domain/value-objects/world-config.ts +198 -0
  28. package/scripts/harness/config-foundation/index.ts +14 -15
  29. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +24 -0
  30. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +24 -0
  31. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +24 -0
  32. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +83 -0
  33. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +83 -0
  34. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +18 -4
  35. package/scripts/harness/integrations/pre-commit.ts +169 -27
  36. package/scripts/harness/main.ts +174 -130
  37. package/scripts/harness/traceability-model/application/dto/changed-design-fragment-dto.ts +31 -0
  38. package/scripts/harness/traceability-model/application/facades/design-change-read-facade.ts +14 -0
  39. package/scripts/harness/traceability-model/application/ports/staged-design-change-source-port.ts +9 -0
  40. package/scripts/harness/traceability-model/composition-root.ts +5 -0
  41. package/scripts/harness/traceability-model/index.ts +9 -0
  42. package/scripts/harness/traceability-model/infrastructure/adapters/git-staged-design-change-adapter.ts +155 -0
  43. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +31 -0
  44. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +34 -0
  45. package/scripts/harness/validator-system/composition-root.ts +21 -6
  46. package/scripts/harness/validator-system/domain/ports/world-constraint-admission-policy-port.ts +28 -0
  47. package/scripts/harness/validator-system/domain/ports/world-constraint-rederivation-policy-port.ts +11 -0
  48. package/scripts/harness/validator-system/domain/services/design-change-declaration-policy.ts +75 -0
  49. package/scripts/harness/validator-system/domain/services/world-constraint-admission-service.ts +78 -0
  50. package/scripts/harness/validator-system/domain/services/world-constraint-rederivation-service.ts +76 -0
  51. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +4 -0
  52. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +38 -16
  53. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-admission-adapter.ts +56 -0
  54. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-rederivation-adapter.ts +56 -0
  55. package/scripts/harness/world-model/application/dto/pinned-design-endpoint-dto.ts +19 -0
  56. package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +92 -27
  57. package/scripts/harness/world-model/application/dto/world-snapshot-root-dto.ts +8 -0
  58. package/scripts/harness/world-model/application/facades/pinned-design-endpoint-facade.ts +63 -0
  59. package/scripts/harness/world-model/application/facades/world-snapshot-root-facade.ts +19 -0
  60. package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +3 -1
  61. package/scripts/harness/world-model/composition-root.ts +62 -33
  62. package/scripts/harness/world-model/index.ts +11 -0
  63. package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +48 -13
  64. package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +6 -4
  65. package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +6 -2
@@ -22,6 +22,9 @@
22
22
  * @work-item-id WI-217
23
23
  * @work-item-id WI-219
24
24
  * @work-item-id WI-291
25
+ * @work-item-id WI-300
26
+ * @work-item-id WI-306
27
+ * @work-item-id WI-308
25
28
  *
26
29
  * Phasegate CLI エントリポイント。
27
30
  * 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
@@ -45,6 +48,7 @@ import { createBiomeAstEngineModule } from "./biome-ast-engine/composition-root.
45
48
  import { buildCiGovernance } from "./ci-governance/composition-root.js";
46
49
  import { toPhaseConfigSection } from "./config-foundation/application/mappers/phase-config-section-mapper.js";
47
50
  import { toValidatorSystemConfig } from "./config-foundation/application/mappers/validator-system-config-mapper.js";
51
+ import { toWorldModelConfig } from "./config-foundation/application/mappers/world-model-config-mapper.js";
48
52
  import { createConfigFoundationModule } from "./config-foundation/composition-root.js";
49
53
  import { ConfigValidationError } from "./config-foundation/domain/errors/config-validation-error.js";
50
54
  import type { HarnessConfigV2 } from "./config-foundation/domain/harness-config.js";
@@ -1881,24 +1885,41 @@ async function loadWorldResolvedConfig() {
1881
1885
  try {
1882
1886
  const configModule = createConfigFoundationModule();
1883
1887
  const result = await configModule.usecases.loadResolvedConfigUseCase.execute();
1884
- return {
1885
- designDocsRoot: result.config.paths.designDocs,
1886
- inceptionRoot: result.config.paths.inceptionDocs,
1887
- requirementMatrixPath: result.config.layers.L3.requirementMatrixPath,
1888
- };
1888
+ return toWorldModelConfig(result.config);
1889
1889
  } catch (error) {
1890
1890
  if (error instanceof ConfigNotFoundError) return undefined;
1891
1891
  throw error;
1892
1892
  }
1893
1893
  }
1894
1894
 
1895
+ /**
1896
+ * WI-308: pipe接続されたstdout / stderrのpending writeを完了してから終了状態へ移る。
1897
+ * `process.exit()`はNodeのstream queueを待たないため、64 KiB超のJSONを切断し得る。
1898
+ */
1899
+ async function finishCliExit(exitCode: number): Promise<void> {
1900
+ const drain = (stream: NodeJS.WriteStream): Promise<void> =>
1901
+ new Promise((resolvePromise, reject) => {
1902
+ stream.write("", (error) => {
1903
+ if (error) {
1904
+ reject(error);
1905
+ return;
1906
+ }
1907
+ resolvePromise();
1908
+ });
1909
+ });
1910
+
1911
+ await Promise.all([drain(process.stdout), drain(process.stderr)]);
1912
+ process.exitCode = exitCode;
1913
+ }
1914
+
1895
1915
  async function main(): Promise<void> {
1896
1916
  const args = process.argv.slice(2);
1897
1917
  const command = args[0];
1898
1918
 
1899
1919
  if (!command || command === "--help" || command === "help") {
1900
1920
  printUsage();
1901
- process.exit(0);
1921
+ await finishCliExit(0);
1922
+ return;
1902
1923
  }
1903
1924
 
1904
1925
  const rootDir = getProjectRoot();
@@ -1907,13 +1928,15 @@ async function main(): Promise<void> {
1907
1928
  if (command === "--version" || command === "version") {
1908
1929
  const version = await getHarnessVersion(harnessRoot);
1909
1930
  console.log(`phasegate v${version}`);
1910
- process.exit(0);
1931
+ await finishCliExit(0);
1932
+ return;
1911
1933
  }
1912
1934
 
1913
1935
  // Pre-dispatch: 全 subcommand で --help / -h を最優先で解釈し usage 出力 (副作用走行を防ぐ — WI-091 finding #3)
1914
1936
  if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
1915
1937
  printSubcommandHelp(command);
1916
- process.exit(0);
1938
+ await finishCliExit(0);
1939
+ return;
1917
1940
  }
1918
1941
 
1919
1942
  const json = hasFlag(args, "--json");
@@ -2188,8 +2211,8 @@ async function main(): Promise<void> {
2188
2211
  console.log(" • Q&A about phasegate concepts: invoke /phasegate-toolkit-guide");
2189
2212
  console.log(" • Diagnose & tune phasegate.config.json: invoke /phasegate-config-doctor");
2190
2213
  }
2191
- process.exit(hasStructuredInstallError(installResult) ? 1 : 0);
2192
- break;
2214
+ await finishCliExit(hasStructuredInstallError(installResult) ? 1 : 0);
2215
+ return;
2193
2216
  }
2194
2217
 
2195
2218
  case "update-skills": {
@@ -2214,8 +2237,8 @@ async function main(): Promise<void> {
2214
2237
  });
2215
2238
  if (!json) console.log("phasegate update-skills is deprecated; running phasegate reconcile.");
2216
2239
  console.log(result.stdout);
2217
- process.exit(result.exitCode);
2218
- break;
2240
+ await finishCliExit(result.exitCode);
2241
+ return;
2219
2242
  }
2220
2243
 
2221
2244
  case "doctor": {
@@ -2241,14 +2264,14 @@ async function main(): Promise<void> {
2241
2264
  agent,
2242
2265
  });
2243
2266
  console.log(result.stdout);
2244
- process.exit(result.exitCode);
2245
- break;
2267
+ await finishCliExit(result.exitCode);
2268
+ return;
2246
2269
  }
2247
2270
 
2248
2271
  case "emit-agent-rules": {
2249
2272
  console.log(emitAgentRulesBlock());
2250
- process.exit(0);
2251
- break;
2273
+ await finishCliExit(0);
2274
+ return;
2252
2275
  }
2253
2276
 
2254
2277
  case "scaffold-wi": {
@@ -2274,8 +2297,8 @@ async function main(): Promise<void> {
2274
2297
  inceptionRoot: parseFlag(args, "--root") ?? configuredPersonalRoot,
2275
2298
  });
2276
2299
  console.log(`Created ${descriptionPath}`);
2277
- process.exit(0);
2278
- break;
2300
+ await finishCliExit(0);
2301
+ return;
2279
2302
  }
2280
2303
 
2281
2304
  case "install": {
@@ -2332,8 +2355,8 @@ async function main(): Promise<void> {
2332
2355
  json,
2333
2356
  });
2334
2357
  console.log(result.stdout);
2335
- process.exit(result.exitCode);
2336
- break;
2358
+ await finishCliExit(result.exitCode);
2359
+ return;
2337
2360
  }
2338
2361
 
2339
2362
  case "setup:agent": {
@@ -2415,8 +2438,8 @@ async function main(): Promise<void> {
2415
2438
  console.log("Validation:");
2416
2439
  for (const command of plan.validation) console.log(`- ${command}`);
2417
2440
  }
2418
- process.exit(hasStructuredInstallError(installResult) ? 1 : 0);
2419
- break;
2441
+ await finishCliExit(hasStructuredInstallError(installResult) ? 1 : 0);
2442
+ return;
2420
2443
  }
2421
2444
 
2422
2445
  case "config:plan": {
@@ -2446,7 +2469,8 @@ async function main(): Promise<void> {
2446
2469
  for (const operation of applyResult.appliedOperations)
2447
2470
  console.log(`- ${operation.op} ${operation.pointer}`);
2448
2471
  }
2449
- process.exit(0);
2472
+ await finishCliExit(0);
2473
+ return;
2450
2474
  } catch (error) {
2451
2475
  const message = error instanceof Error ? error.message : String(error);
2452
2476
  if (json) {
@@ -2460,7 +2484,8 @@ async function main(): Promise<void> {
2460
2484
  } else {
2461
2485
  console.error(`Error: ${message}`);
2462
2486
  }
2463
- process.exit(1);
2487
+ await finishCliExit(1);
2488
+ return;
2464
2489
  }
2465
2490
  }
2466
2491
  if (json) {
@@ -2474,8 +2499,8 @@ async function main(): Promise<void> {
2474
2499
  console.log("Validation:");
2475
2500
  for (const validation of plan.validations) console.log(`- ${validation}`);
2476
2501
  }
2477
- process.exit(0);
2478
- break;
2502
+ await finishCliExit(0);
2503
+ return;
2479
2504
  }
2480
2505
 
2481
2506
  case "session": {
@@ -2496,7 +2521,8 @@ async function main(): Promise<void> {
2496
2521
  `Full Mode session started: ${session.workItemId} unit=${session.unit} expiresAt=${session.expiresAt}`,
2497
2522
  );
2498
2523
  }
2499
- process.exit(0);
2524
+ await finishCliExit(0);
2525
+ return;
2500
2526
  } catch (error) {
2501
2527
  const message = error instanceof Error ? error.message : String(error);
2502
2528
  if (json) {
@@ -2504,7 +2530,8 @@ async function main(): Promise<void> {
2504
2530
  } else {
2505
2531
  console.error(`Error: ${message}`);
2506
2532
  }
2507
- process.exit(2);
2533
+ await finishCliExit(2);
2534
+ return;
2508
2535
  }
2509
2536
  }
2510
2537
  if (subcommand === "end") {
@@ -2517,7 +2544,8 @@ async function main(): Promise<void> {
2517
2544
  } else {
2518
2545
  console.log("No Full Mode session was active.");
2519
2546
  }
2520
- process.exit(0);
2547
+ await finishCliExit(0);
2548
+ return;
2521
2549
  } catch (error) {
2522
2550
  const message = error instanceof Error ? error.message : String(error);
2523
2551
  if (json) {
@@ -2525,7 +2553,8 @@ async function main(): Promise<void> {
2525
2553
  } else {
2526
2554
  console.error(`Error: ${message}`);
2527
2555
  }
2528
- process.exit(2);
2556
+ await finishCliExit(2);
2557
+ return;
2529
2558
  }
2530
2559
  }
2531
2560
  console.error("Error: session subcommand must be begin or end.");
@@ -2552,8 +2581,8 @@ async function main(): Promise<void> {
2552
2581
  json,
2553
2582
  });
2554
2583
  console.log(result.stdout);
2555
- process.exit(result.exitCode);
2556
- break;
2584
+ await finishCliExit(result.exitCode);
2585
+ return;
2557
2586
  }
2558
2587
 
2559
2588
  case "reconcile": {
@@ -2577,8 +2606,8 @@ async function main(): Promise<void> {
2577
2606
  json,
2578
2607
  });
2579
2608
  console.log(result.stdout);
2580
- process.exit(result.exitCode);
2581
- break;
2609
+ await finishCliExit(result.exitCode);
2610
+ return;
2582
2611
  }
2583
2612
 
2584
2613
  // ── config-foundation ──
@@ -2593,8 +2622,8 @@ async function main(): Promise<void> {
2593
2622
  configPath,
2594
2623
  });
2595
2624
  console.log(result.output);
2596
- process.exit(result.exitCode);
2597
- break;
2625
+ await finishCliExit(result.exitCode);
2626
+ return;
2598
2627
  }
2599
2628
 
2600
2629
  case "disable-feature": {
@@ -2608,8 +2637,8 @@ async function main(): Promise<void> {
2608
2637
  configPath,
2609
2638
  });
2610
2639
  console.log(result.output);
2611
- process.exit(result.exitCode);
2612
- break;
2640
+ await finishCliExit(result.exitCode);
2641
+ return;
2613
2642
  }
2614
2643
 
2615
2644
  case "list-features": {
@@ -2618,8 +2647,8 @@ async function main(): Promise<void> {
2618
2647
  list: true,
2619
2648
  });
2620
2649
  console.log(result.output);
2621
- process.exit(result.exitCode);
2622
- break;
2650
+ await finishCliExit(result.exitCode);
2651
+ return;
2623
2652
  }
2624
2653
 
2625
2654
  case "migrate": {
@@ -2631,8 +2660,8 @@ async function main(): Promise<void> {
2631
2660
  json,
2632
2661
  });
2633
2662
  console.log(result.text);
2634
- process.exit(result.exitCode);
2635
- break;
2663
+ await finishCliExit(result.exitCode);
2664
+ return;
2636
2665
  }
2637
2666
  const mod = createConfigFoundationModule();
2638
2667
  const targetVersion = parseFlag(args, "--schema") ?? "v3";
@@ -2642,8 +2671,8 @@ async function main(): Promise<void> {
2642
2671
  configPath,
2643
2672
  });
2644
2673
  console.log(result.output);
2645
- process.exit(result.exitCode);
2646
- break;
2674
+ await finishCliExit(result.exitCode);
2675
+ return;
2647
2676
  }
2648
2677
 
2649
2678
  // ── harness-error ──
@@ -2657,8 +2686,8 @@ async function main(): Promise<void> {
2657
2686
  failOnError,
2658
2687
  });
2659
2688
  console.log(result.output);
2660
- process.exit(result.exitCode);
2661
- break;
2689
+ await finishCliExit(result.exitCode);
2690
+ return;
2662
2691
  }
2663
2692
 
2664
2693
  case "validate-fix": {
@@ -2672,8 +2701,8 @@ async function main(): Promise<void> {
2672
2701
  format,
2673
2702
  });
2674
2703
  console.log(result.output);
2675
- process.exit(result.exitCode);
2676
- break;
2704
+ await finishCliExit(result.exitCode);
2705
+ return;
2677
2706
  }
2678
2707
 
2679
2708
  case "list-errors": {
@@ -2685,8 +2714,8 @@ async function main(): Promise<void> {
2685
2714
  layer,
2686
2715
  });
2687
2716
  console.log(result.output);
2688
- process.exit(result.exitCode);
2689
- break;
2717
+ await finishCliExit(result.exitCode);
2718
+ return;
2690
2719
  }
2691
2720
 
2692
2721
  // ── traceability-model ──
@@ -2698,8 +2727,8 @@ async function main(): Promise<void> {
2698
2727
  json,
2699
2728
  });
2700
2729
  console.log(result.text);
2701
- process.exit(result.exitCode);
2702
- break;
2730
+ await finishCliExit(result.exitCode);
2731
+ return;
2703
2732
  }
2704
2733
 
2705
2734
  case "work-items:status": {
@@ -2714,8 +2743,8 @@ async function main(): Promise<void> {
2714
2743
  json,
2715
2744
  });
2716
2745
  console.log(result.text);
2717
- process.exit(result.exitCode);
2718
- break;
2746
+ await finishCliExit(result.exitCode);
2747
+ return;
2719
2748
  }
2720
2749
 
2721
2750
  // ── world-model ──
@@ -2791,8 +2820,8 @@ async function main(): Promise<void> {
2791
2820
  json,
2792
2821
  });
2793
2822
  console.log(result.text);
2794
- process.exit(result.exitCode);
2795
- break;
2823
+ await finishCliExit(result.exitCode);
2824
+ return;
2796
2825
  }
2797
2826
 
2798
2827
  // ── adr-foundation ──
@@ -2807,8 +2836,8 @@ async function main(): Promise<void> {
2807
2836
  json,
2808
2837
  });
2809
2838
  console.log(result.text);
2810
- process.exit(result.exitCode);
2811
- break;
2839
+ await finishCliExit(result.exitCode);
2840
+ return;
2812
2841
  }
2813
2842
 
2814
2843
  case "validate-adr": {
@@ -2821,8 +2850,8 @@ async function main(): Promise<void> {
2821
2850
  json,
2822
2851
  });
2823
2852
  console.log(result.text);
2824
- process.exit(result.exitCode);
2825
- break;
2853
+ await finishCliExit(result.exitCode);
2854
+ return;
2826
2855
  }
2827
2856
 
2828
2857
  // ── biome-ast-engine ──
@@ -2832,8 +2861,8 @@ async function main(): Promise<void> {
2832
2861
  const mod = createBiomeAstEngineModule(rootDir, { l1Config, architecture });
2833
2862
  const result = await mod.harnessLintCommandHandler.execute(args.slice(1));
2834
2863
  console.log(result.text);
2835
- process.exit(result.exitCode);
2836
- break;
2864
+ await finishCliExit(result.exitCode);
2865
+ return;
2837
2866
  }
2838
2867
 
2839
2868
  // ── validator-system ──
@@ -2860,8 +2889,8 @@ async function main(): Promise<void> {
2860
2889
  if ((layer === "L2" || layer === "all") && format !== "ci") {
2861
2890
  await printStoryReflectionValidationSummary(rootDir, unit);
2862
2891
  }
2863
- process.exit(result.exitCode);
2864
- break;
2892
+ await finishCliExit(result.exitCode);
2893
+ return;
2865
2894
  }
2866
2895
 
2867
2896
  // ── quick-mode / ci-check ──
@@ -2916,8 +2945,8 @@ async function main(): Promise<void> {
2916
2945
  format,
2917
2946
  failOnFullRequired,
2918
2947
  });
2919
- process.exit(result.exitCode);
2920
- break;
2948
+ await finishCliExit(result.exitCode);
2949
+ return;
2921
2950
  }
2922
2951
 
2923
2952
  // ── harness-api ──
@@ -3077,7 +3106,16 @@ async function main(): Promise<void> {
3077
3106
  }
3078
3107
  const { createAttestationModule } = await import("./attestation/index.js");
3079
3108
  const pkgVersion = await getHarnessVersion(harnessRoot);
3080
- const mod = createAttestationModule(rootDir, { pkgVersion });
3109
+ const worldModule = createWorldModelModule({
3110
+ rootDir,
3111
+ resolvedConfig: await loadWorldResolvedConfig(),
3112
+ });
3113
+ const mod = createAttestationModule(rootDir, {
3114
+ pkgVersion,
3115
+ worldSnapshotRootProvider: {
3116
+ getWorldSnapshotRoot: async () => (await worldModule.worldSnapshotRootFacade.read()).worldSnapshotRoot,
3117
+ },
3118
+ });
3081
3119
  const result = await mod.attestHandler.handle({
3082
3120
  out: parseFlag(args, "--out") ?? ".harness/attestation.json",
3083
3121
  requirePass: hasFlag(args, "--require-pass"),
@@ -3085,8 +3123,8 @@ async function main(): Promise<void> {
3085
3123
  mode: parseFlag(args, "--mode"),
3086
3124
  });
3087
3125
  console.log(result.output);
3088
- process.exit(result.exitCode);
3089
- break;
3126
+ await finishCliExit(result.exitCode);
3127
+ return;
3090
3128
  }
3091
3129
 
3092
3130
  case "phasegate:verify-attestation": {
@@ -3103,8 +3141,8 @@ async function main(): Promise<void> {
3103
3141
  emitJson: json,
3104
3142
  });
3105
3143
  console.log(result.output);
3106
- process.exit(result.exitCode);
3107
- break;
3144
+ await finishCliExit(result.exitCode);
3145
+ return;
3108
3146
  }
3109
3147
 
3110
3148
  // ── ci-governance ──
@@ -3127,7 +3165,8 @@ Options:
3127
3165
  Examples:
3128
3166
  phasegate ci:generate-template --type aidlc-gate
3129
3167
  phasegate ci:generate-template --preset strict --type pre-commit --render`);
3130
- process.exit(0);
3168
+ await finishCliExit(0);
3169
+ return;
3131
3170
  }
3132
3171
  const flagError = validateKnownFlags(args.slice(1), ["--preset", "--type", "--render", "--json", "--help"]);
3133
3172
  if (flagError !== null) {
@@ -3141,8 +3180,8 @@ Examples:
3141
3180
  const format = json ? "json" : "human";
3142
3181
  const result = await mod.generateCiTemplateHandler.handle({ presetId, templateType, render, format });
3143
3182
  console.log(result.output);
3144
- process.exit(result.exitCode);
3145
- break;
3183
+ await finishCliExit(result.exitCode);
3184
+ return;
3146
3185
  }
3147
3186
 
3148
3187
  case "ci:migrate-agents-md": {
@@ -3152,8 +3191,8 @@ Examples:
3152
3191
  const format = json ? "json" : "human";
3153
3192
  const result = await mod.migrateAgentsMdHandler.handle({ dryRun, validateOnly, format });
3154
3193
  console.log(result.output);
3155
- process.exit(result.exitCode);
3156
- break;
3194
+ await finishCliExit(result.exitCode);
3195
+ return;
3157
3196
  }
3158
3197
 
3159
3198
  case "ci:auto-refresh-agent-context": {
@@ -3163,8 +3202,8 @@ Examples:
3163
3202
  const format = json ? "json" : "human";
3164
3203
  const result = await mod.refreshAgentContextHandler.handle({ dryRun, apply, format });
3165
3204
  console.log(result.output);
3166
- process.exit(result.exitCode);
3167
- break;
3205
+ await finishCliExit(result.exitCode);
3206
+ return;
3168
3207
  }
3169
3208
 
3170
3209
  case "refresh-claude-md": {
@@ -3174,8 +3213,8 @@ Examples:
3174
3213
  const format = json ? "json" : "human";
3175
3214
  const result = await mod.refreshClaudeMdHandler.handle({ dryRun, apply, format });
3176
3215
  console.log(result.output);
3177
- process.exit(result.exitCode);
3178
- break;
3216
+ await finishCliExit(result.exitCode);
3217
+ return;
3179
3218
  }
3180
3219
 
3181
3220
  case "p2:check-agent-context": {
@@ -3185,8 +3224,8 @@ Examples:
3185
3224
  const format = json ? "json" : "human";
3186
3225
  const result = await mod.checkAgentContextHandler.handle({ thresholdDays, format });
3187
3226
  console.log(result.output);
3188
- process.exit(result.exitCode);
3189
- break;
3227
+ await finishCliExit(result.exitCode);
3228
+ return;
3190
3229
  }
3191
3230
 
3192
3231
  case "ci:check-repetition": {
@@ -3196,8 +3235,8 @@ Examples:
3196
3235
  const format = json ? "json" : "human";
3197
3236
  const result = await mod.checkRepetitionHandler.handle({ errorCode, reset, format });
3198
3237
  console.log(result.output);
3199
- process.exit(result.exitCode);
3200
- break;
3238
+ await finishCliExit(result.exitCode);
3239
+ return;
3201
3240
  }
3202
3241
 
3203
3242
  case "baseline": {
@@ -3219,8 +3258,8 @@ Examples:
3219
3258
  format,
3220
3259
  });
3221
3260
  console.log(result.output);
3222
- process.exit(result.exitCode);
3223
- break;
3261
+ await finishCliExit(result.exitCode);
3262
+ return;
3224
3263
  }
3225
3264
 
3226
3265
  case "scaffold-design": {
@@ -3240,8 +3279,8 @@ Examples:
3240
3279
  format,
3241
3280
  });
3242
3281
  console.log(result.output);
3243
- process.exit(result.exitCode);
3244
- break;
3282
+ await finishCliExit(result.exitCode);
3283
+ return;
3245
3284
  }
3246
3285
 
3247
3286
  case "integrity:pin": {
@@ -3250,8 +3289,8 @@ Examples:
3250
3289
  const format = json ? "json" : "human";
3251
3290
  const result = await mod.integrityHandler.pin({ dryRun, format });
3252
3291
  console.log(result.output);
3253
- process.exit(result.exitCode);
3254
- break;
3292
+ await finishCliExit(result.exitCode);
3293
+ return;
3255
3294
  }
3256
3295
 
3257
3296
  case "integrity:verify": {
@@ -3259,8 +3298,8 @@ Examples:
3259
3298
  const format = json ? "json" : "human";
3260
3299
  const result = await mod.integrityHandler.verify({ format });
3261
3300
  console.log(result.output);
3262
- process.exit(result.exitCode);
3263
- break;
3301
+ await finishCliExit(result.exitCode);
3302
+ return;
3264
3303
  }
3265
3304
 
3266
3305
  // ── skill-quality ──
@@ -3275,8 +3314,8 @@ Examples:
3275
3314
  const passed = hasFlag(args, "--passed");
3276
3315
  const result = await mod.executeTddCycleHandler.handle({ unit, storyId, description, phase, passed });
3277
3316
  console.log(result.message);
3278
- process.exit(result.exitCode);
3279
- break;
3317
+ await finishCliExit(result.exitCode);
3318
+ return;
3280
3319
  }
3281
3320
 
3282
3321
  case "skill:check-coverage": {
@@ -3285,8 +3324,8 @@ Examples:
3285
3324
  const format = json ? "json" : "human";
3286
3325
  const result = await mod.checkCoverageHandler.handle({ storyId, format });
3287
3326
  console.log(result.message);
3288
- process.exit(result.exitCode);
3289
- break;
3327
+ await finishCliExit(result.exitCode);
3328
+ return;
3290
3329
  }
3291
3330
 
3292
3331
  case "skill:collect-lessons": {
@@ -3297,8 +3336,8 @@ Examples:
3297
3336
  const writeArtifact = hasFlag(args, "--write-artifact");
3298
3337
  const result = await mod.collectLessonsHandler.handle({ storyId, sources, writeArtifact });
3299
3338
  console.log(result.message);
3300
- process.exit(result.exitCode);
3301
- break;
3339
+ await finishCliExit(result.exitCode);
3340
+ return;
3302
3341
  }
3303
3342
 
3304
3343
  case "skill:apply-cascade-update": {
@@ -3307,8 +3346,8 @@ Examples:
3307
3346
  const dryRun = hasFlag(args, "--dry-run");
3308
3347
  const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun, format: json ? "json" : "human" });
3309
3348
  console.log(result.message);
3310
- process.exit(result.exitCode);
3311
- break;
3349
+ await finishCliExit(result.exitCode);
3350
+ return;
3312
3351
  }
3313
3352
 
3314
3353
  case "skill:validate-structure": {
@@ -3317,8 +3356,8 @@ Examples:
3317
3356
  const format = json ? "json" : "human";
3318
3357
  const result = await mod.validateSkillStructureHandler.handle({ skillFile, format });
3319
3358
  console.log(result.message);
3320
- process.exit(result.exitCode);
3321
- break;
3359
+ await finishCliExit(result.exitCode);
3360
+ return;
3322
3361
  }
3323
3362
 
3324
3363
  // ── regression-suite ──
@@ -3329,8 +3368,8 @@ Examples:
3329
3368
  ? JSON.stringify(result, null, 2)
3330
3369
  : `K-Requirements: ${result.passedCount}/${result.totalCount} passed`;
3331
3370
  console.log(output);
3332
- process.exit(result.failedCount > 0 ? 1 : 0);
3333
- break;
3371
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3372
+ return;
3334
3373
  }
3335
3374
 
3336
3375
  case "regression:run-gng-gate": {
@@ -3340,8 +3379,8 @@ Examples:
3340
3379
  ? JSON.stringify(result, null, 2)
3341
3380
  : `GnG Gate: ${result.passedCount}/${result.totalCount} passed`;
3342
3381
  console.log(output);
3343
- process.exit(result.failedCount > 0 ? 1 : 0);
3344
- break;
3382
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3383
+ return;
3345
3384
  }
3346
3385
 
3347
3386
  case "regression:run-agent-guard": {
@@ -3351,8 +3390,8 @@ Examples:
3351
3390
  ? JSON.stringify(result, null, 2)
3352
3391
  : `Agent Independence: ${result.passedCount}/${result.totalCount} passed`;
3353
3392
  console.log(output);
3354
- process.exit(result.failedCount > 0 ? 1 : 0);
3355
- break;
3393
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3394
+ return;
3356
3395
  }
3357
3396
 
3358
3397
  case "regression:run-k14-k15": {
@@ -3362,8 +3401,8 @@ Examples:
3362
3401
  ? JSON.stringify(result, null, 2)
3363
3402
  : `K14/K15: ${result.passedCount}/${result.totalCount} passed`;
3364
3403
  console.log(output);
3365
- process.exit(result.failedCount > 0 ? 1 : 0);
3366
- break;
3404
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3405
+ return;
3367
3406
  }
3368
3407
 
3369
3408
  case "regression:configure-ci-gate": {
@@ -3379,8 +3418,8 @@ Examples:
3379
3418
  ? JSON.stringify(result, null, 2)
3380
3419
  : `CI gate configured: suites=${result.requiredSuiteIds.join(",")}, threshold=${result.coverageThreshold}%`;
3381
3420
  console.log(output);
3382
- process.exit(0);
3383
- break;
3421
+ await finishCliExit(0);
3422
+ return;
3384
3423
  }
3385
3424
 
3386
3425
  case "regression:analyze-migration": {
@@ -3391,8 +3430,8 @@ Examples:
3391
3430
  ? JSON.stringify(result, null, 2)
3392
3431
  : `Migration analysis: total=${result.totalCount}, migrated=${result.migratedCount}, modified=${result.modifiedCount}, skipped=${result.skippedCount}`;
3393
3432
  console.log(output);
3394
- process.exit(0);
3395
- break;
3433
+ await finishCliExit(0);
3434
+ return;
3396
3435
  }
3397
3436
 
3398
3437
  case "regression:migrate-v0-tests": {
@@ -3403,8 +3442,8 @@ Examples:
3403
3442
  ? JSON.stringify(result, null, 2)
3404
3443
  : `Migration: total=${result.totalCount}, migrated=${result.migratedCount}, modified=${result.modifiedCount}, skipped=${result.skippedCount}`;
3405
3444
  console.log(output);
3406
- process.exit(0);
3407
- break;
3445
+ await finishCliExit(0);
3446
+ return;
3408
3447
  }
3409
3448
 
3410
3449
  // ── phase2-extensions ──
@@ -3413,8 +3452,8 @@ Examples:
3413
3452
  const p2args = args.slice(1);
3414
3453
  const result = await mod.checkFreshnessHandler.handle(p2args);
3415
3454
  console.log(result.stdout);
3416
- process.exit(result.exitCode);
3417
- break;
3455
+ await finishCliExit(result.exitCode);
3456
+ return;
3418
3457
  }
3419
3458
 
3420
3459
  case "p2:validate-pointers": {
@@ -3422,8 +3461,8 @@ Examples:
3422
3461
  const p2args = args.slice(1);
3423
3462
  const result = await mod.validatePointersHandler.handle(p2args);
3424
3463
  console.log(result.stdout);
3425
- process.exit(result.exitCode);
3426
- break;
3464
+ await finishCliExit(result.exitCode);
3465
+ return;
3427
3466
  }
3428
3467
 
3429
3468
  case "p2:generate-e2e-template": {
@@ -3431,8 +3470,8 @@ Examples:
3431
3470
  const p2args = args.slice(1);
3432
3471
  const result = await mod.generateE2ETemplateHandler.handle(p2args);
3433
3472
  console.log(result.stdout);
3434
- process.exit(result.exitCode);
3435
- break;
3473
+ await finishCliExit(result.exitCode);
3474
+ return;
3436
3475
  }
3437
3476
 
3438
3477
  case "p2:check-initial-creation": {
@@ -3440,8 +3479,8 @@ Examples:
3440
3479
  const p2args = args.slice(1);
3441
3480
  const result = await mod.checkInitialCreationExpirationHandler.handle(p2args);
3442
3481
  console.log(result.stdout);
3443
- process.exit(result.exitCode);
3444
- break;
3482
+ await finishCliExit(result.exitCode);
3483
+ return;
3445
3484
  }
3446
3485
 
3447
3486
  // ── agent integration / hooks ──
@@ -3500,7 +3539,8 @@ Examples:
3500
3539
  case "delegate-sonnet": {
3501
3540
  if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
3502
3541
  printSubcommandHelp("delegate-sonnet");
3503
- process.exit(0);
3542
+ await finishCliExit(0);
3543
+ return;
3504
3544
  }
3505
3545
  if (!hasFlag(args, "--dry-run") && (await isModelDelegationDisabled(rootDir))) {
3506
3546
  console.error(
@@ -3509,7 +3549,8 @@ Examples:
3509
3549
  message: "phasegate delegate-sonnet is disabled by modelRouting.delegation=none",
3510
3550
  }),
3511
3551
  );
3512
- process.exit(1);
3552
+ await finishCliExit(1);
3553
+ return;
3513
3554
  }
3514
3555
  const { spawn } = await import("node:child_process");
3515
3556
  const scriptPath = join(harnessRoot, "scripts/delegate-sonnet.sh");
@@ -3559,7 +3600,8 @@ Examples:
3559
3600
  }
3560
3601
  console.log("");
3561
3602
  }
3562
- process.exit(0);
3603
+ await finishCliExit(0);
3604
+ return;
3563
3605
  }
3564
3606
 
3565
3607
  if (subCommand === "info") {
@@ -3573,7 +3615,8 @@ Examples:
3573
3615
  try {
3574
3616
  const content = await fs.readFile(skillMdPath, "utf-8");
3575
3617
  console.log(content);
3576
- process.exit(0);
3618
+ await finishCliExit(0);
3619
+ return;
3577
3620
  } catch {
3578
3621
  console.error(`Skill not found: ${skillName}`);
3579
3622
  console.error(`Expected: ${skillMdPath}`);
@@ -3589,12 +3632,13 @@ Examples:
3589
3632
  default:
3590
3633
  console.error(`Unknown command: ${command}`);
3591
3634
  printUsage();
3592
- process.exit(2);
3635
+ await finishCliExit(2);
3636
+ return;
3593
3637
  }
3594
3638
  } catch (error: unknown) {
3595
3639
  const message = error instanceof Error ? error.message : "Unknown error occurred";
3596
3640
  console.error(`Fatal: ${message}`);
3597
- process.exit(2);
3641
+ await finishCliExit(2);
3598
3642
  }
3599
3643
  }
3600
3644