phasegate 0.254.0 → 0.283.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 (82) hide show
  1. package/CHANGELOG.md +60 -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/guide/installation.md +1 -1
  8. package/docs/templates/ci/aidlc-gate.yml +60 -2
  9. package/package.json +2 -2
  10. package/scripts/harness/agent-integration/application/dto/open-world-obligations-context-dto.ts +30 -0
  11. package/scripts/harness/agent-integration/application/ports/world-obligations-query-port.ts +30 -0
  12. package/scripts/harness/agent-integration/application/usecases/get-open-world-obligations-context-usecase.ts +66 -0
  13. package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +30 -27
  14. package/scripts/harness/agent-integration/infrastructure/adapters/world-model-open-obligations-query-adapter.ts +58 -0
  15. package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +29 -16
  16. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +37 -1
  17. package/scripts/harness/agent-integration/presentation/stop-hook.ts +35 -26
  18. package/scripts/harness/agent-integration/presentation/world-obligations-session-context.ts +60 -0
  19. package/scripts/harness/attestation/application/dto/attestation-document.ts +17 -4
  20. package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +55 -4
  21. package/scripts/harness/attestation/application/ports/world-snapshot-root-provider.ts +10 -0
  22. package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +33 -7
  23. package/scripts/harness/attestation/composition-root.ts +5 -0
  24. package/scripts/harness/attestation/domain/entities/attestation-record.ts +37 -2
  25. package/scripts/harness/attestation/index.ts +7 -1
  26. package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +5 -2
  27. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +40 -9
  28. package/scripts/harness/config-foundation/application/mappers/world-model-config-mapper.ts +15 -0
  29. package/scripts/harness/config-foundation/domain/harness-config.ts +70 -87
  30. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +80 -88
  31. package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +34 -18
  32. package/scripts/harness/config-foundation/domain/value-objects/world-config.ts +198 -0
  33. package/scripts/harness/config-foundation/index.ts +14 -15
  34. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +24 -0
  35. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +24 -0
  36. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +24 -0
  37. package/scripts/harness/config-foundation/infrastructure/repositories/file-system-config-repository.ts +27 -18
  38. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +84 -8
  39. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +84 -8
  40. package/scripts/harness/harness-api/domain/ports/config-query-port.ts +11 -1
  41. package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +125 -86
  42. package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +31 -21
  43. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +18 -4
  44. package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +23 -8
  45. package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +4 -4
  46. package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +79 -34
  47. package/scripts/harness/installation/application/usecases/run-install.ts +199 -51
  48. package/scripts/harness/installation/application/usecases/run-reconcile.ts +277 -69
  49. package/scripts/harness/installation/domain/deployment-manifest.ts +43 -0
  50. package/scripts/harness/integrations/pre-commit.ts +169 -27
  51. package/scripts/harness/main.ts +200 -136
  52. package/scripts/harness/traceability-model/application/dto/changed-design-fragment-dto.ts +31 -0
  53. package/scripts/harness/traceability-model/application/facades/design-change-read-facade.ts +14 -0
  54. package/scripts/harness/traceability-model/application/ports/staged-design-change-source-port.ts +9 -0
  55. package/scripts/harness/traceability-model/composition-root.ts +5 -0
  56. package/scripts/harness/traceability-model/index.ts +9 -0
  57. package/scripts/harness/traceability-model/infrastructure/adapters/git-staged-design-change-adapter.ts +155 -0
  58. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +31 -0
  59. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +57 -6
  60. package/scripts/harness/validator-system/composition-root.ts +25 -7
  61. package/scripts/harness/validator-system/domain/ports/ac-coverage-policy-port.ts +10 -1
  62. package/scripts/harness/validator-system/domain/ports/world-constraint-admission-policy-port.ts +28 -0
  63. package/scripts/harness/validator-system/domain/ports/world-constraint-rederivation-policy-port.ts +11 -0
  64. package/scripts/harness/validator-system/domain/services/design-change-declaration-policy.ts +75 -0
  65. package/scripts/harness/validator-system/domain/services/world-constraint-admission-service.ts +78 -0
  66. package/scripts/harness/validator-system/domain/services/world-constraint-rederivation-service.ts +76 -0
  67. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +4 -0
  68. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +126 -18
  69. package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +49 -20
  70. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-admission-adapter.ts +56 -0
  71. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-rederivation-adapter.ts +56 -0
  72. package/scripts/harness/world-model/application/dto/pinned-design-endpoint-dto.ts +19 -0
  73. package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +92 -27
  74. package/scripts/harness/world-model/application/dto/world-snapshot-root-dto.ts +8 -0
  75. package/scripts/harness/world-model/application/facades/pinned-design-endpoint-facade.ts +63 -0
  76. package/scripts/harness/world-model/application/facades/world-snapshot-root-facade.ts +19 -0
  77. package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +3 -1
  78. package/scripts/harness/world-model/composition-root.ts +62 -33
  79. package/scripts/harness/world-model/index.ts +11 -0
  80. package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +48 -13
  81. package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +6 -4
  82. 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";
@@ -690,8 +694,8 @@ Options:
690
694
  --agent <claude|codex|both> Agent context and hook targets (default: both)
691
695
  --skills <core|all> Rendered agent context skill mode (default: all)
692
696
  --workflow <standard|strict> Rendered agent context workflow mode (default: standard)
693
- --with-husky Include Husky hook targets
694
- --with-ci Include GitHub Actions target
697
+ --with-husky Include Husky hook targets (opt-in; omitted by default)
698
+ --with-ci Include GitHub Actions target (opt-in; omitted by default)
695
699
  --personal Use local-only install: no package.json, agent docs, Husky, CI, .gitignore, GitHub CLI, secrets, or CI setting writes.
696
700
  With --agent claude, initializes .phasegate-local config/settings/skills and ignored .claude shims.
697
701
  --json Output machine-readable JSON
@@ -1851,7 +1855,18 @@ function emitV2SchemaWarningOnce(sourcePath: string): void {
1851
1855
  );
1852
1856
  }
1853
1857
 
1854
- async function loadResolvedConfig(): Promise<HarnessConfigV2 | undefined> {
1858
+ /**
1859
+ * 不正 config でも fail-open(警告 + 既定設定で続行)にするコマンド。
1860
+ * GitHub #40: config 検証の fail-closed が dispatch より上流にあると、pre-tool-use hook
1861
+ * 経由で Write/Edit/Bash が全遮断され、config 自身を修復する経路が消える(自己修復
1862
+ * デッドロック)。hook(エージェントのツール遮断点)と doctor(自己診断)は復旧経路
1863
+ * として常に起動可能でなければならない。validate / ci-check 等の検査系コマンドは
1864
+ * fail-closed を維持する(gated スコープへの書き込みは hook 内の phase-gate 判定が
1865
+ * 引き続き fail-closed でブロックする)。
1866
+ */
1867
+ const CONFIG_FAIL_OPEN_COMMANDS: ReadonlySet<string> = new Set(["hook", "doctor"]);
1868
+
1869
+ async function loadResolvedConfig(command?: string): Promise<HarnessConfigV2 | undefined> {
1855
1870
  try {
1856
1871
  const configModule = createConfigFoundationModule();
1857
1872
  const result = await configModule.usecases.loadResolvedConfigUseCase.execute();
@@ -1862,6 +1877,15 @@ async function loadResolvedConfig(): Promise<HarnessConfigV2 | undefined> {
1862
1877
  } catch (error) {
1863
1878
  if (error instanceof ConfigValidationError) {
1864
1879
  process.stderr.write(`Invalid phasegate.config.json: ${error.message}\n`);
1880
+ if (command !== undefined && CONFIG_FAIL_OPEN_COMMANDS.has(command)) {
1881
+ process.stderr.write(
1882
+ "Warning: continuing with default settings so diagnosis and self-repair stay possible. Fix the reported path in phasegate.config.json to restore full gating.\n",
1883
+ );
1884
+ return undefined;
1885
+ }
1886
+ process.stderr.write(
1887
+ "Recovery: fix the reported path/type in phasegate.config.json (or restore it from version control). `phasegate doctor` and agent hooks remain available while the config is invalid.\n",
1888
+ );
1865
1889
  process.exit(2);
1866
1890
  }
1867
1891
  if (error instanceof ConfigNotFoundError) {
@@ -1881,24 +1905,41 @@ async function loadWorldResolvedConfig() {
1881
1905
  try {
1882
1906
  const configModule = createConfigFoundationModule();
1883
1907
  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
- };
1908
+ return toWorldModelConfig(result.config);
1889
1909
  } catch (error) {
1890
1910
  if (error instanceof ConfigNotFoundError) return undefined;
1891
1911
  throw error;
1892
1912
  }
1893
1913
  }
1894
1914
 
1915
+ /**
1916
+ * WI-308: pipe接続されたstdout / stderrのpending writeを完了してから終了状態へ移る。
1917
+ * `process.exit()`はNodeのstream queueを待たないため、64 KiB超のJSONを切断し得る。
1918
+ */
1919
+ async function finishCliExit(exitCode: number): Promise<void> {
1920
+ const drain = (stream: NodeJS.WriteStream): Promise<void> =>
1921
+ new Promise((resolvePromise, reject) => {
1922
+ stream.write("", (error) => {
1923
+ if (error) {
1924
+ reject(error);
1925
+ return;
1926
+ }
1927
+ resolvePromise();
1928
+ });
1929
+ });
1930
+
1931
+ await Promise.all([drain(process.stdout), drain(process.stderr)]);
1932
+ process.exitCode = exitCode;
1933
+ }
1934
+
1895
1935
  async function main(): Promise<void> {
1896
1936
  const args = process.argv.slice(2);
1897
1937
  const command = args[0];
1898
1938
 
1899
1939
  if (!command || command === "--help" || command === "help") {
1900
1940
  printUsage();
1901
- process.exit(0);
1941
+ await finishCliExit(0);
1942
+ return;
1902
1943
  }
1903
1944
 
1904
1945
  const rootDir = getProjectRoot();
@@ -1907,19 +1948,21 @@ async function main(): Promise<void> {
1907
1948
  if (command === "--version" || command === "version") {
1908
1949
  const version = await getHarnessVersion(harnessRoot);
1909
1950
  console.log(`phasegate v${version}`);
1910
- process.exit(0);
1951
+ await finishCliExit(0);
1952
+ return;
1911
1953
  }
1912
1954
 
1913
1955
  // Pre-dispatch: 全 subcommand で --help / -h を最優先で解釈し usage 出力 (副作用走行を防ぐ — WI-091 finding #3)
1914
1956
  if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
1915
1957
  printSubcommandHelp(command);
1916
- process.exit(0);
1958
+ await finishCliExit(0);
1959
+ return;
1917
1960
  }
1918
1961
 
1919
1962
  const json = hasFlag(args, "--json");
1920
1963
 
1921
1964
  // Cross-unit wiring: 設定を先に解決し、各Unit に注入する
1922
- const resolvedConfig = command.startsWith("world:") ? undefined : await loadResolvedConfig();
1965
+ const resolvedConfig = command.startsWith("world:") ? undefined : await loadResolvedConfig(command);
1923
1966
 
1924
1967
  try {
1925
1968
  switch (command) {
@@ -2188,8 +2231,8 @@ async function main(): Promise<void> {
2188
2231
  console.log(" • Q&A about phasegate concepts: invoke /phasegate-toolkit-guide");
2189
2232
  console.log(" • Diagnose & tune phasegate.config.json: invoke /phasegate-config-doctor");
2190
2233
  }
2191
- process.exit(hasStructuredInstallError(installResult) ? 1 : 0);
2192
- break;
2234
+ await finishCliExit(hasStructuredInstallError(installResult) ? 1 : 0);
2235
+ return;
2193
2236
  }
2194
2237
 
2195
2238
  case "update-skills": {
@@ -2214,8 +2257,8 @@ async function main(): Promise<void> {
2214
2257
  });
2215
2258
  if (!json) console.log("phasegate update-skills is deprecated; running phasegate reconcile.");
2216
2259
  console.log(result.stdout);
2217
- process.exit(result.exitCode);
2218
- break;
2260
+ await finishCliExit(result.exitCode);
2261
+ return;
2219
2262
  }
2220
2263
 
2221
2264
  case "doctor": {
@@ -2241,14 +2284,14 @@ async function main(): Promise<void> {
2241
2284
  agent,
2242
2285
  });
2243
2286
  console.log(result.stdout);
2244
- process.exit(result.exitCode);
2245
- break;
2287
+ await finishCliExit(result.exitCode);
2288
+ return;
2246
2289
  }
2247
2290
 
2248
2291
  case "emit-agent-rules": {
2249
2292
  console.log(emitAgentRulesBlock());
2250
- process.exit(0);
2251
- break;
2293
+ await finishCliExit(0);
2294
+ return;
2252
2295
  }
2253
2296
 
2254
2297
  case "scaffold-wi": {
@@ -2274,8 +2317,8 @@ async function main(): Promise<void> {
2274
2317
  inceptionRoot: parseFlag(args, "--root") ?? configuredPersonalRoot,
2275
2318
  });
2276
2319
  console.log(`Created ${descriptionPath}`);
2277
- process.exit(0);
2278
- break;
2320
+ await finishCliExit(0);
2321
+ return;
2279
2322
  }
2280
2323
 
2281
2324
  case "install": {
@@ -2323,8 +2366,8 @@ async function main(): Promise<void> {
2323
2366
  force: hasFlag(args, "--force"),
2324
2367
  includeClaude,
2325
2368
  includeCodex,
2326
- includeHusky: !personal,
2327
- includeCi: !personal,
2369
+ includeHusky: !personal && hasFlag(args, "--with-husky"),
2370
+ includeCi: !personal && hasFlag(args, "--with-ci"),
2328
2371
  skillSet: skillSetRaw,
2329
2372
  workflow: parseWorkflowMode(workflowRaw),
2330
2373
  agent,
@@ -2332,8 +2375,8 @@ async function main(): Promise<void> {
2332
2375
  json,
2333
2376
  });
2334
2377
  console.log(result.stdout);
2335
- process.exit(result.exitCode);
2336
- break;
2378
+ await finishCliExit(result.exitCode);
2379
+ return;
2337
2380
  }
2338
2381
 
2339
2382
  case "setup:agent": {
@@ -2415,8 +2458,8 @@ async function main(): Promise<void> {
2415
2458
  console.log("Validation:");
2416
2459
  for (const command of plan.validation) console.log(`- ${command}`);
2417
2460
  }
2418
- process.exit(hasStructuredInstallError(installResult) ? 1 : 0);
2419
- break;
2461
+ await finishCliExit(hasStructuredInstallError(installResult) ? 1 : 0);
2462
+ return;
2420
2463
  }
2421
2464
 
2422
2465
  case "config:plan": {
@@ -2446,7 +2489,8 @@ async function main(): Promise<void> {
2446
2489
  for (const operation of applyResult.appliedOperations)
2447
2490
  console.log(`- ${operation.op} ${operation.pointer}`);
2448
2491
  }
2449
- process.exit(0);
2492
+ await finishCliExit(0);
2493
+ return;
2450
2494
  } catch (error) {
2451
2495
  const message = error instanceof Error ? error.message : String(error);
2452
2496
  if (json) {
@@ -2460,7 +2504,8 @@ async function main(): Promise<void> {
2460
2504
  } else {
2461
2505
  console.error(`Error: ${message}`);
2462
2506
  }
2463
- process.exit(1);
2507
+ await finishCliExit(1);
2508
+ return;
2464
2509
  }
2465
2510
  }
2466
2511
  if (json) {
@@ -2474,8 +2519,8 @@ async function main(): Promise<void> {
2474
2519
  console.log("Validation:");
2475
2520
  for (const validation of plan.validations) console.log(`- ${validation}`);
2476
2521
  }
2477
- process.exit(0);
2478
- break;
2522
+ await finishCliExit(0);
2523
+ return;
2479
2524
  }
2480
2525
 
2481
2526
  case "session": {
@@ -2496,7 +2541,8 @@ async function main(): Promise<void> {
2496
2541
  `Full Mode session started: ${session.workItemId} unit=${session.unit} expiresAt=${session.expiresAt}`,
2497
2542
  );
2498
2543
  }
2499
- process.exit(0);
2544
+ await finishCliExit(0);
2545
+ return;
2500
2546
  } catch (error) {
2501
2547
  const message = error instanceof Error ? error.message : String(error);
2502
2548
  if (json) {
@@ -2504,7 +2550,8 @@ async function main(): Promise<void> {
2504
2550
  } else {
2505
2551
  console.error(`Error: ${message}`);
2506
2552
  }
2507
- process.exit(2);
2553
+ await finishCliExit(2);
2554
+ return;
2508
2555
  }
2509
2556
  }
2510
2557
  if (subcommand === "end") {
@@ -2517,7 +2564,8 @@ async function main(): Promise<void> {
2517
2564
  } else {
2518
2565
  console.log("No Full Mode session was active.");
2519
2566
  }
2520
- process.exit(0);
2567
+ await finishCliExit(0);
2568
+ return;
2521
2569
  } catch (error) {
2522
2570
  const message = error instanceof Error ? error.message : String(error);
2523
2571
  if (json) {
@@ -2525,7 +2573,8 @@ async function main(): Promise<void> {
2525
2573
  } else {
2526
2574
  console.error(`Error: ${message}`);
2527
2575
  }
2528
- process.exit(2);
2576
+ await finishCliExit(2);
2577
+ return;
2529
2578
  }
2530
2579
  }
2531
2580
  console.error("Error: session subcommand must be begin or end.");
@@ -2552,8 +2601,8 @@ async function main(): Promise<void> {
2552
2601
  json,
2553
2602
  });
2554
2603
  console.log(result.stdout);
2555
- process.exit(result.exitCode);
2556
- break;
2604
+ await finishCliExit(result.exitCode);
2605
+ return;
2557
2606
  }
2558
2607
 
2559
2608
  case "reconcile": {
@@ -2577,8 +2626,8 @@ async function main(): Promise<void> {
2577
2626
  json,
2578
2627
  });
2579
2628
  console.log(result.stdout);
2580
- process.exit(result.exitCode);
2581
- break;
2629
+ await finishCliExit(result.exitCode);
2630
+ return;
2582
2631
  }
2583
2632
 
2584
2633
  // ── config-foundation ──
@@ -2593,8 +2642,8 @@ async function main(): Promise<void> {
2593
2642
  configPath,
2594
2643
  });
2595
2644
  console.log(result.output);
2596
- process.exit(result.exitCode);
2597
- break;
2645
+ await finishCliExit(result.exitCode);
2646
+ return;
2598
2647
  }
2599
2648
 
2600
2649
  case "disable-feature": {
@@ -2608,8 +2657,8 @@ async function main(): Promise<void> {
2608
2657
  configPath,
2609
2658
  });
2610
2659
  console.log(result.output);
2611
- process.exit(result.exitCode);
2612
- break;
2660
+ await finishCliExit(result.exitCode);
2661
+ return;
2613
2662
  }
2614
2663
 
2615
2664
  case "list-features": {
@@ -2618,8 +2667,8 @@ async function main(): Promise<void> {
2618
2667
  list: true,
2619
2668
  });
2620
2669
  console.log(result.output);
2621
- process.exit(result.exitCode);
2622
- break;
2670
+ await finishCliExit(result.exitCode);
2671
+ return;
2623
2672
  }
2624
2673
 
2625
2674
  case "migrate": {
@@ -2631,8 +2680,8 @@ async function main(): Promise<void> {
2631
2680
  json,
2632
2681
  });
2633
2682
  console.log(result.text);
2634
- process.exit(result.exitCode);
2635
- break;
2683
+ await finishCliExit(result.exitCode);
2684
+ return;
2636
2685
  }
2637
2686
  const mod = createConfigFoundationModule();
2638
2687
  const targetVersion = parseFlag(args, "--schema") ?? "v3";
@@ -2642,8 +2691,8 @@ async function main(): Promise<void> {
2642
2691
  configPath,
2643
2692
  });
2644
2693
  console.log(result.output);
2645
- process.exit(result.exitCode);
2646
- break;
2694
+ await finishCliExit(result.exitCode);
2695
+ return;
2647
2696
  }
2648
2697
 
2649
2698
  // ── harness-error ──
@@ -2657,8 +2706,8 @@ async function main(): Promise<void> {
2657
2706
  failOnError,
2658
2707
  });
2659
2708
  console.log(result.output);
2660
- process.exit(result.exitCode);
2661
- break;
2709
+ await finishCliExit(result.exitCode);
2710
+ return;
2662
2711
  }
2663
2712
 
2664
2713
  case "validate-fix": {
@@ -2672,8 +2721,8 @@ async function main(): Promise<void> {
2672
2721
  format,
2673
2722
  });
2674
2723
  console.log(result.output);
2675
- process.exit(result.exitCode);
2676
- break;
2724
+ await finishCliExit(result.exitCode);
2725
+ return;
2677
2726
  }
2678
2727
 
2679
2728
  case "list-errors": {
@@ -2685,8 +2734,8 @@ async function main(): Promise<void> {
2685
2734
  layer,
2686
2735
  });
2687
2736
  console.log(result.output);
2688
- process.exit(result.exitCode);
2689
- break;
2737
+ await finishCliExit(result.exitCode);
2738
+ return;
2690
2739
  }
2691
2740
 
2692
2741
  // ── traceability-model ──
@@ -2698,8 +2747,8 @@ async function main(): Promise<void> {
2698
2747
  json,
2699
2748
  });
2700
2749
  console.log(result.text);
2701
- process.exit(result.exitCode);
2702
- break;
2750
+ await finishCliExit(result.exitCode);
2751
+ return;
2703
2752
  }
2704
2753
 
2705
2754
  case "work-items:status": {
@@ -2714,8 +2763,8 @@ async function main(): Promise<void> {
2714
2763
  json,
2715
2764
  });
2716
2765
  console.log(result.text);
2717
- process.exit(result.exitCode);
2718
- break;
2766
+ await finishCliExit(result.exitCode);
2767
+ return;
2719
2768
  }
2720
2769
 
2721
2770
  // ── world-model ──
@@ -2791,8 +2840,8 @@ async function main(): Promise<void> {
2791
2840
  json,
2792
2841
  });
2793
2842
  console.log(result.text);
2794
- process.exit(result.exitCode);
2795
- break;
2843
+ await finishCliExit(result.exitCode);
2844
+ return;
2796
2845
  }
2797
2846
 
2798
2847
  // ── adr-foundation ──
@@ -2807,8 +2856,8 @@ async function main(): Promise<void> {
2807
2856
  json,
2808
2857
  });
2809
2858
  console.log(result.text);
2810
- process.exit(result.exitCode);
2811
- break;
2859
+ await finishCliExit(result.exitCode);
2860
+ return;
2812
2861
  }
2813
2862
 
2814
2863
  case "validate-adr": {
@@ -2821,8 +2870,8 @@ async function main(): Promise<void> {
2821
2870
  json,
2822
2871
  });
2823
2872
  console.log(result.text);
2824
- process.exit(result.exitCode);
2825
- break;
2873
+ await finishCliExit(result.exitCode);
2874
+ return;
2826
2875
  }
2827
2876
 
2828
2877
  // ── biome-ast-engine ──
@@ -2832,8 +2881,8 @@ async function main(): Promise<void> {
2832
2881
  const mod = createBiomeAstEngineModule(rootDir, { l1Config, architecture });
2833
2882
  const result = await mod.harnessLintCommandHandler.execute(args.slice(1));
2834
2883
  console.log(result.text);
2835
- process.exit(result.exitCode);
2836
- break;
2884
+ await finishCliExit(result.exitCode);
2885
+ return;
2837
2886
  }
2838
2887
 
2839
2888
  // ── validator-system ──
@@ -2860,8 +2909,8 @@ async function main(): Promise<void> {
2860
2909
  if ((layer === "L2" || layer === "all") && format !== "ci") {
2861
2910
  await printStoryReflectionValidationSummary(rootDir, unit);
2862
2911
  }
2863
- process.exit(result.exitCode);
2864
- break;
2912
+ await finishCliExit(result.exitCode);
2913
+ return;
2865
2914
  }
2866
2915
 
2867
2916
  // ── quick-mode / ci-check ──
@@ -2916,8 +2965,8 @@ async function main(): Promise<void> {
2916
2965
  format,
2917
2966
  failOnFullRequired,
2918
2967
  });
2919
- process.exit(result.exitCode);
2920
- break;
2968
+ await finishCliExit(result.exitCode);
2969
+ return;
2921
2970
  }
2922
2971
 
2923
2972
  // ── harness-api ──
@@ -3077,7 +3126,16 @@ async function main(): Promise<void> {
3077
3126
  }
3078
3127
  const { createAttestationModule } = await import("./attestation/index.js");
3079
3128
  const pkgVersion = await getHarnessVersion(harnessRoot);
3080
- const mod = createAttestationModule(rootDir, { pkgVersion });
3129
+ const worldModule = createWorldModelModule({
3130
+ rootDir,
3131
+ resolvedConfig: await loadWorldResolvedConfig(),
3132
+ });
3133
+ const mod = createAttestationModule(rootDir, {
3134
+ pkgVersion,
3135
+ worldSnapshotRootProvider: {
3136
+ getWorldSnapshotRoot: async () => (await worldModule.worldSnapshotRootFacade.read()).worldSnapshotRoot,
3137
+ },
3138
+ });
3081
3139
  const result = await mod.attestHandler.handle({
3082
3140
  out: parseFlag(args, "--out") ?? ".harness/attestation.json",
3083
3141
  requirePass: hasFlag(args, "--require-pass"),
@@ -3085,8 +3143,8 @@ async function main(): Promise<void> {
3085
3143
  mode: parseFlag(args, "--mode"),
3086
3144
  });
3087
3145
  console.log(result.output);
3088
- process.exit(result.exitCode);
3089
- break;
3146
+ await finishCliExit(result.exitCode);
3147
+ return;
3090
3148
  }
3091
3149
 
3092
3150
  case "phasegate:verify-attestation": {
@@ -3103,8 +3161,8 @@ async function main(): Promise<void> {
3103
3161
  emitJson: json,
3104
3162
  });
3105
3163
  console.log(result.output);
3106
- process.exit(result.exitCode);
3107
- break;
3164
+ await finishCliExit(result.exitCode);
3165
+ return;
3108
3166
  }
3109
3167
 
3110
3168
  // ── ci-governance ──
@@ -3127,7 +3185,8 @@ Options:
3127
3185
  Examples:
3128
3186
  phasegate ci:generate-template --type aidlc-gate
3129
3187
  phasegate ci:generate-template --preset strict --type pre-commit --render`);
3130
- process.exit(0);
3188
+ await finishCliExit(0);
3189
+ return;
3131
3190
  }
3132
3191
  const flagError = validateKnownFlags(args.slice(1), ["--preset", "--type", "--render", "--json", "--help"]);
3133
3192
  if (flagError !== null) {
@@ -3141,8 +3200,8 @@ Examples:
3141
3200
  const format = json ? "json" : "human";
3142
3201
  const result = await mod.generateCiTemplateHandler.handle({ presetId, templateType, render, format });
3143
3202
  console.log(result.output);
3144
- process.exit(result.exitCode);
3145
- break;
3203
+ await finishCliExit(result.exitCode);
3204
+ return;
3146
3205
  }
3147
3206
 
3148
3207
  case "ci:migrate-agents-md": {
@@ -3152,8 +3211,8 @@ Examples:
3152
3211
  const format = json ? "json" : "human";
3153
3212
  const result = await mod.migrateAgentsMdHandler.handle({ dryRun, validateOnly, format });
3154
3213
  console.log(result.output);
3155
- process.exit(result.exitCode);
3156
- break;
3214
+ await finishCliExit(result.exitCode);
3215
+ return;
3157
3216
  }
3158
3217
 
3159
3218
  case "ci:auto-refresh-agent-context": {
@@ -3163,8 +3222,8 @@ Examples:
3163
3222
  const format = json ? "json" : "human";
3164
3223
  const result = await mod.refreshAgentContextHandler.handle({ dryRun, apply, format });
3165
3224
  console.log(result.output);
3166
- process.exit(result.exitCode);
3167
- break;
3225
+ await finishCliExit(result.exitCode);
3226
+ return;
3168
3227
  }
3169
3228
 
3170
3229
  case "refresh-claude-md": {
@@ -3174,8 +3233,8 @@ Examples:
3174
3233
  const format = json ? "json" : "human";
3175
3234
  const result = await mod.refreshClaudeMdHandler.handle({ dryRun, apply, format });
3176
3235
  console.log(result.output);
3177
- process.exit(result.exitCode);
3178
- break;
3236
+ await finishCliExit(result.exitCode);
3237
+ return;
3179
3238
  }
3180
3239
 
3181
3240
  case "p2:check-agent-context": {
@@ -3185,8 +3244,8 @@ Examples:
3185
3244
  const format = json ? "json" : "human";
3186
3245
  const result = await mod.checkAgentContextHandler.handle({ thresholdDays, format });
3187
3246
  console.log(result.output);
3188
- process.exit(result.exitCode);
3189
- break;
3247
+ await finishCliExit(result.exitCode);
3248
+ return;
3190
3249
  }
3191
3250
 
3192
3251
  case "ci:check-repetition": {
@@ -3196,8 +3255,8 @@ Examples:
3196
3255
  const format = json ? "json" : "human";
3197
3256
  const result = await mod.checkRepetitionHandler.handle({ errorCode, reset, format });
3198
3257
  console.log(result.output);
3199
- process.exit(result.exitCode);
3200
- break;
3258
+ await finishCliExit(result.exitCode);
3259
+ return;
3201
3260
  }
3202
3261
 
3203
3262
  case "baseline": {
@@ -3219,8 +3278,8 @@ Examples:
3219
3278
  format,
3220
3279
  });
3221
3280
  console.log(result.output);
3222
- process.exit(result.exitCode);
3223
- break;
3281
+ await finishCliExit(result.exitCode);
3282
+ return;
3224
3283
  }
3225
3284
 
3226
3285
  case "scaffold-design": {
@@ -3240,8 +3299,8 @@ Examples:
3240
3299
  format,
3241
3300
  });
3242
3301
  console.log(result.output);
3243
- process.exit(result.exitCode);
3244
- break;
3302
+ await finishCliExit(result.exitCode);
3303
+ return;
3245
3304
  }
3246
3305
 
3247
3306
  case "integrity:pin": {
@@ -3250,8 +3309,8 @@ Examples:
3250
3309
  const format = json ? "json" : "human";
3251
3310
  const result = await mod.integrityHandler.pin({ dryRun, format });
3252
3311
  console.log(result.output);
3253
- process.exit(result.exitCode);
3254
- break;
3312
+ await finishCliExit(result.exitCode);
3313
+ return;
3255
3314
  }
3256
3315
 
3257
3316
  case "integrity:verify": {
@@ -3259,8 +3318,8 @@ Examples:
3259
3318
  const format = json ? "json" : "human";
3260
3319
  const result = await mod.integrityHandler.verify({ format });
3261
3320
  console.log(result.output);
3262
- process.exit(result.exitCode);
3263
- break;
3321
+ await finishCliExit(result.exitCode);
3322
+ return;
3264
3323
  }
3265
3324
 
3266
3325
  // ── skill-quality ──
@@ -3275,8 +3334,8 @@ Examples:
3275
3334
  const passed = hasFlag(args, "--passed");
3276
3335
  const result = await mod.executeTddCycleHandler.handle({ unit, storyId, description, phase, passed });
3277
3336
  console.log(result.message);
3278
- process.exit(result.exitCode);
3279
- break;
3337
+ await finishCliExit(result.exitCode);
3338
+ return;
3280
3339
  }
3281
3340
 
3282
3341
  case "skill:check-coverage": {
@@ -3285,8 +3344,8 @@ Examples:
3285
3344
  const format = json ? "json" : "human";
3286
3345
  const result = await mod.checkCoverageHandler.handle({ storyId, format });
3287
3346
  console.log(result.message);
3288
- process.exit(result.exitCode);
3289
- break;
3347
+ await finishCliExit(result.exitCode);
3348
+ return;
3290
3349
  }
3291
3350
 
3292
3351
  case "skill:collect-lessons": {
@@ -3297,8 +3356,8 @@ Examples:
3297
3356
  const writeArtifact = hasFlag(args, "--write-artifact");
3298
3357
  const result = await mod.collectLessonsHandler.handle({ storyId, sources, writeArtifact });
3299
3358
  console.log(result.message);
3300
- process.exit(result.exitCode);
3301
- break;
3359
+ await finishCliExit(result.exitCode);
3360
+ return;
3302
3361
  }
3303
3362
 
3304
3363
  case "skill:apply-cascade-update": {
@@ -3307,8 +3366,8 @@ Examples:
3307
3366
  const dryRun = hasFlag(args, "--dry-run");
3308
3367
  const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun, format: json ? "json" : "human" });
3309
3368
  console.log(result.message);
3310
- process.exit(result.exitCode);
3311
- break;
3369
+ await finishCliExit(result.exitCode);
3370
+ return;
3312
3371
  }
3313
3372
 
3314
3373
  case "skill:validate-structure": {
@@ -3317,8 +3376,8 @@ Examples:
3317
3376
  const format = json ? "json" : "human";
3318
3377
  const result = await mod.validateSkillStructureHandler.handle({ skillFile, format });
3319
3378
  console.log(result.message);
3320
- process.exit(result.exitCode);
3321
- break;
3379
+ await finishCliExit(result.exitCode);
3380
+ return;
3322
3381
  }
3323
3382
 
3324
3383
  // ── regression-suite ──
@@ -3329,8 +3388,8 @@ Examples:
3329
3388
  ? JSON.stringify(result, null, 2)
3330
3389
  : `K-Requirements: ${result.passedCount}/${result.totalCount} passed`;
3331
3390
  console.log(output);
3332
- process.exit(result.failedCount > 0 ? 1 : 0);
3333
- break;
3391
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3392
+ return;
3334
3393
  }
3335
3394
 
3336
3395
  case "regression:run-gng-gate": {
@@ -3340,8 +3399,8 @@ Examples:
3340
3399
  ? JSON.stringify(result, null, 2)
3341
3400
  : `GnG Gate: ${result.passedCount}/${result.totalCount} passed`;
3342
3401
  console.log(output);
3343
- process.exit(result.failedCount > 0 ? 1 : 0);
3344
- break;
3402
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3403
+ return;
3345
3404
  }
3346
3405
 
3347
3406
  case "regression:run-agent-guard": {
@@ -3351,8 +3410,8 @@ Examples:
3351
3410
  ? JSON.stringify(result, null, 2)
3352
3411
  : `Agent Independence: ${result.passedCount}/${result.totalCount} passed`;
3353
3412
  console.log(output);
3354
- process.exit(result.failedCount > 0 ? 1 : 0);
3355
- break;
3413
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3414
+ return;
3356
3415
  }
3357
3416
 
3358
3417
  case "regression:run-k14-k15": {
@@ -3362,8 +3421,8 @@ Examples:
3362
3421
  ? JSON.stringify(result, null, 2)
3363
3422
  : `K14/K15: ${result.passedCount}/${result.totalCount} passed`;
3364
3423
  console.log(output);
3365
- process.exit(result.failedCount > 0 ? 1 : 0);
3366
- break;
3424
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3425
+ return;
3367
3426
  }
3368
3427
 
3369
3428
  case "regression:configure-ci-gate": {
@@ -3379,8 +3438,8 @@ Examples:
3379
3438
  ? JSON.stringify(result, null, 2)
3380
3439
  : `CI gate configured: suites=${result.requiredSuiteIds.join(",")}, threshold=${result.coverageThreshold}%`;
3381
3440
  console.log(output);
3382
- process.exit(0);
3383
- break;
3441
+ await finishCliExit(0);
3442
+ return;
3384
3443
  }
3385
3444
 
3386
3445
  case "regression:analyze-migration": {
@@ -3391,8 +3450,8 @@ Examples:
3391
3450
  ? JSON.stringify(result, null, 2)
3392
3451
  : `Migration analysis: total=${result.totalCount}, migrated=${result.migratedCount}, modified=${result.modifiedCount}, skipped=${result.skippedCount}`;
3393
3452
  console.log(output);
3394
- process.exit(0);
3395
- break;
3453
+ await finishCliExit(0);
3454
+ return;
3396
3455
  }
3397
3456
 
3398
3457
  case "regression:migrate-v0-tests": {
@@ -3403,8 +3462,8 @@ Examples:
3403
3462
  ? JSON.stringify(result, null, 2)
3404
3463
  : `Migration: total=${result.totalCount}, migrated=${result.migratedCount}, modified=${result.modifiedCount}, skipped=${result.skippedCount}`;
3405
3464
  console.log(output);
3406
- process.exit(0);
3407
- break;
3465
+ await finishCliExit(0);
3466
+ return;
3408
3467
  }
3409
3468
 
3410
3469
  // ── phase2-extensions ──
@@ -3413,8 +3472,8 @@ Examples:
3413
3472
  const p2args = args.slice(1);
3414
3473
  const result = await mod.checkFreshnessHandler.handle(p2args);
3415
3474
  console.log(result.stdout);
3416
- process.exit(result.exitCode);
3417
- break;
3475
+ await finishCliExit(result.exitCode);
3476
+ return;
3418
3477
  }
3419
3478
 
3420
3479
  case "p2:validate-pointers": {
@@ -3422,8 +3481,8 @@ Examples:
3422
3481
  const p2args = args.slice(1);
3423
3482
  const result = await mod.validatePointersHandler.handle(p2args);
3424
3483
  console.log(result.stdout);
3425
- process.exit(result.exitCode);
3426
- break;
3484
+ await finishCliExit(result.exitCode);
3485
+ return;
3427
3486
  }
3428
3487
 
3429
3488
  case "p2:generate-e2e-template": {
@@ -3431,8 +3490,8 @@ Examples:
3431
3490
  const p2args = args.slice(1);
3432
3491
  const result = await mod.generateE2ETemplateHandler.handle(p2args);
3433
3492
  console.log(result.stdout);
3434
- process.exit(result.exitCode);
3435
- break;
3493
+ await finishCliExit(result.exitCode);
3494
+ return;
3436
3495
  }
3437
3496
 
3438
3497
  case "p2:check-initial-creation": {
@@ -3440,8 +3499,8 @@ Examples:
3440
3499
  const p2args = args.slice(1);
3441
3500
  const result = await mod.checkInitialCreationExpirationHandler.handle(p2args);
3442
3501
  console.log(result.stdout);
3443
- process.exit(result.exitCode);
3444
- break;
3502
+ await finishCliExit(result.exitCode);
3503
+ return;
3445
3504
  }
3446
3505
 
3447
3506
  // ── agent integration / hooks ──
@@ -3500,7 +3559,8 @@ Examples:
3500
3559
  case "delegate-sonnet": {
3501
3560
  if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
3502
3561
  printSubcommandHelp("delegate-sonnet");
3503
- process.exit(0);
3562
+ await finishCliExit(0);
3563
+ return;
3504
3564
  }
3505
3565
  if (!hasFlag(args, "--dry-run") && (await isModelDelegationDisabled(rootDir))) {
3506
3566
  console.error(
@@ -3509,7 +3569,8 @@ Examples:
3509
3569
  message: "phasegate delegate-sonnet is disabled by modelRouting.delegation=none",
3510
3570
  }),
3511
3571
  );
3512
- process.exit(1);
3572
+ await finishCliExit(1);
3573
+ return;
3513
3574
  }
3514
3575
  const { spawn } = await import("node:child_process");
3515
3576
  const scriptPath = join(harnessRoot, "scripts/delegate-sonnet.sh");
@@ -3559,7 +3620,8 @@ Examples:
3559
3620
  }
3560
3621
  console.log("");
3561
3622
  }
3562
- process.exit(0);
3623
+ await finishCliExit(0);
3624
+ return;
3563
3625
  }
3564
3626
 
3565
3627
  if (subCommand === "info") {
@@ -3573,7 +3635,8 @@ Examples:
3573
3635
  try {
3574
3636
  const content = await fs.readFile(skillMdPath, "utf-8");
3575
3637
  console.log(content);
3576
- process.exit(0);
3638
+ await finishCliExit(0);
3639
+ return;
3577
3640
  } catch {
3578
3641
  console.error(`Skill not found: ${skillName}`);
3579
3642
  console.error(`Expected: ${skillMdPath}`);
@@ -3589,12 +3652,13 @@ Examples:
3589
3652
  default:
3590
3653
  console.error(`Unknown command: ${command}`);
3591
3654
  printUsage();
3592
- process.exit(2);
3655
+ await finishCliExit(2);
3656
+ return;
3593
3657
  }
3594
3658
  } catch (error: unknown) {
3595
3659
  const message = error instanceof Error ? error.message : "Unknown error occurred";
3596
3660
  console.error(`Fatal: ${message}`);
3597
- process.exit(2);
3661
+ await finishCliExit(2);
3598
3662
  }
3599
3663
  }
3600
3664