phasegate 0.229.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 (154) hide show
  1. package/CHANGELOG.md +73 -1
  2. package/README.ja.md +31 -1
  3. package/README.md +30 -0
  4. package/docs/ADR/031-world-model-ownership-and-corpus-lifecycle.md +155 -0
  5. package/docs/ADR/032-world-node-identity.md +198 -0
  6. package/docs/ADR/033-world-snapshot-canonicalization.md +246 -0
  7. package/docs/ADR/034-world-constraint-semantics.md +218 -0
  8. package/docs/ADR/035-world-adoption-baseline-and-waiver.md +341 -0
  9. package/docs/ADR/036-world-model-and-doc-freshness.md +169 -0
  10. package/docs/ADR/037-world-cli-and-output-contract.md +398 -0
  11. package/docs/contracts/attestation-v2.schema.json +110 -0
  12. package/docs/contracts/requirement-test-matrix.schema.json +15 -0
  13. package/docs/contracts/world-baseline.schema.json +35 -0
  14. package/docs/contracts/world-constraints.schema.json +56 -0
  15. package/docs/contracts/world-debts.schema.json +32 -0
  16. package/docs/contracts/world-obligation-report.schema.json +259 -0
  17. package/docs/contracts/world-waivers.schema.json +32 -0
  18. package/docs/guide/cli-reference.md +45 -0
  19. package/docs/guide/configuration.md +56 -0
  20. package/docs/templates/ci/aidlc-gate.yml +42 -2
  21. package/package.json +1 -1
  22. package/scripts/harness/agent-integration/application/dto/open-world-obligations-context-dto.ts +30 -0
  23. package/scripts/harness/agent-integration/application/ports/world-obligations-query-port.ts +30 -0
  24. package/scripts/harness/agent-integration/application/usecases/get-open-world-obligations-context-usecase.ts +66 -0
  25. package/scripts/harness/agent-integration/infrastructure/adapters/world-model-open-obligations-query-adapter.ts +58 -0
  26. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +37 -1
  27. package/scripts/harness/agent-integration/presentation/world-obligations-session-context.ts +60 -0
  28. package/scripts/harness/attestation/application/dto/attestation-document.ts +17 -4
  29. package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +55 -4
  30. package/scripts/harness/attestation/application/ports/sha256-capability.ts +25 -0
  31. package/scripts/harness/attestation/application/ports/world-snapshot-root-provider.ts +10 -0
  32. package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +33 -7
  33. package/scripts/harness/attestation/composition-root.ts +19 -4
  34. package/scripts/harness/attestation/domain/entities/attestation-record.ts +37 -2
  35. package/scripts/harness/attestation/index.ts +13 -2
  36. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-content-hasher-adapter.ts +6 -6
  37. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-sha256-capability.ts +19 -0
  38. package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +5 -2
  39. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +35 -8
  40. package/scripts/harness/config-foundation/application/mappers/world-model-config-mapper.ts +15 -0
  41. package/scripts/harness/config-foundation/domain/harness-config.ts +67 -87
  42. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +77 -88
  43. package/scripts/harness/config-foundation/domain/value-objects/world-config.ts +198 -0
  44. package/scripts/harness/config-foundation/index.ts +14 -15
  45. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +24 -0
  46. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +24 -0
  47. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +24 -0
  48. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +83 -0
  49. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +83 -0
  50. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +18 -4
  51. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +4 -1
  52. package/scripts/harness/integrations/pre-commit.ts +169 -27
  53. package/scripts/harness/main.ts +276 -126
  54. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +11 -0
  55. package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +6 -0
  56. package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +8 -4
  57. package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +29 -2
  58. package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +28 -0
  59. package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +71 -4
  60. package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +4 -0
  61. package/scripts/harness/traceability-model/application/dto/changed-design-fragment-dto.ts +31 -0
  62. package/scripts/harness/traceability-model/application/dto/traceability-world-read-dto.ts +67 -0
  63. package/scripts/harness/traceability-model/application/facades/design-change-read-facade.ts +14 -0
  64. package/scripts/harness/traceability-model/application/facades/traceability-world-read-facade.ts +372 -0
  65. package/scripts/harness/traceability-model/application/ports/staged-design-change-source-port.ts +9 -0
  66. package/scripts/harness/traceability-model/application/ports/traceability-world-read-source-port.ts +61 -0
  67. package/scripts/harness/traceability-model/composition-root.ts +26 -5
  68. package/scripts/harness/traceability-model/index.ts +26 -6
  69. package/scripts/harness/traceability-model/infrastructure/adapters/file-system-traceability-world-read-adapter.ts +223 -0
  70. package/scripts/harness/traceability-model/infrastructure/adapters/git-staged-design-change-adapter.ts +155 -0
  71. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +86 -8
  72. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +31 -0
  73. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +34 -0
  74. package/scripts/harness/validator-system/composition-root.ts +21 -6
  75. package/scripts/harness/validator-system/domain/ports/world-constraint-admission-policy-port.ts +28 -0
  76. package/scripts/harness/validator-system/domain/ports/world-constraint-rederivation-policy-port.ts +11 -0
  77. package/scripts/harness/validator-system/domain/services/design-change-declaration-policy.ts +75 -0
  78. package/scripts/harness/validator-system/domain/services/world-constraint-admission-service.ts +78 -0
  79. package/scripts/harness/validator-system/domain/services/world-constraint-rederivation-service.ts +76 -0
  80. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +4 -0
  81. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +38 -16
  82. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-admission-adapter.ts +56 -0
  83. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-rederivation-adapter.ts +56 -0
  84. package/scripts/harness/world-model/application/dto/pinned-design-endpoint-dto.ts +19 -0
  85. package/scripts/harness/world-model/application/dto/world-inspection-dto.ts +43 -0
  86. package/scripts/harness/world-model/application/dto/world-obligation-report-dto.ts +54 -0
  87. package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +127 -0
  88. package/scripts/harness/world-model/application/dto/world-snapshot-root-dto.ts +8 -0
  89. package/scripts/harness/world-model/application/facades/pinned-design-endpoint-facade.ts +63 -0
  90. package/scripts/harness/world-model/application/facades/world-snapshot-root-facade.ts +19 -0
  91. package/scripts/harness/world-model/application/ports/obligation-report-writer-port.ts +7 -0
  92. package/scripts/harness/world-model/application/ports/world-control-declaration-repository-port.ts +57 -0
  93. package/scripts/harness/world-model/application/ports/world-fact-source-port.ts +17 -0
  94. package/scripts/harness/world-model/application/usecases/build-snapshot-use-case.ts +111 -0
  95. package/scripts/harness/world-model/application/usecases/derive-obligations-use-case.ts +155 -0
  96. package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +146 -0
  97. package/scripts/harness/world-model/application/usecases/inspect-world-use-case.ts +74 -0
  98. package/scripts/harness/world-model/application/usecases/pin-constraint-endpoint-use-case.ts +139 -0
  99. package/scripts/harness/world-model/composition-root.ts +247 -0
  100. package/scripts/harness/world-model/domain/entities/constraint-record.ts +148 -0
  101. package/scripts/harness/world-model/domain/entities/control-declarations.ts +274 -0
  102. package/scripts/harness/world-model/domain/entities/edge.ts +41 -0
  103. package/scripts/harness/world-model/domain/entities/extraction-diagnostic.ts +60 -0
  104. package/scripts/harness/world-model/domain/entities/snapshot.ts +75 -0
  105. package/scripts/harness/world-model/domain/entities/world-node.ts +210 -0
  106. package/scripts/harness/world-model/domain/ports/world-hashing-port.ts +8 -0
  107. package/scripts/harness/world-model/domain/services/canonical-json-serializer.ts +113 -0
  108. package/scripts/harness/world-model/domain/services/constraint-evaluator.ts +486 -0
  109. package/scripts/harness/world-model/domain/services/obligation-derivation-service.ts +199 -0
  110. package/scripts/harness/world-model/domain/services/policy-inputs-digest-deriver.ts +66 -0
  111. package/scripts/harness/world-model/domain/services/snapshot-root-deriver.ts +218 -0
  112. package/scripts/harness/world-model/domain/services/text-content-normalizer.ts +30 -0
  113. package/scripts/harness/world-model/domain/services/violation-fingerprint-deriver.ts +183 -0
  114. package/scripts/harness/world-model/domain/value-objects/artifact-kind.ts +58 -0
  115. package/scripts/harness/world-model/domain/value-objects/change-provenance.ts +137 -0
  116. package/scripts/harness/world-model/domain/value-objects/corpus-role.ts +57 -0
  117. package/scripts/harness/world-model/domain/value-objects/declared-key.ts +35 -0
  118. package/scripts/harness/world-model/domain/value-objects/evaluation-id.ts +41 -0
  119. package/scripts/harness/world-model/domain/value-objects/explicit-constraint-relation.ts +53 -0
  120. package/scripts/harness/world-model/domain/value-objects/explicit-node-alias.ts +30 -0
  121. package/scripts/harness/world-model/domain/value-objects/node-pin.ts +34 -0
  122. package/scripts/harness/world-model/domain/value-objects/path-key.ts +103 -0
  123. package/scripts/harness/world-model/domain/value-objects/sha256-digest.ts +39 -0
  124. package/scripts/harness/world-model/domain/value-objects/violation-fingerprint.ts +36 -0
  125. package/scripts/harness/world-model/domain/value-objects/wcr-rule-id.ts +59 -0
  126. package/scripts/harness/world-model/domain/value-objects/world-node-id.ts +222 -0
  127. package/scripts/harness/world-model/index.ts +33 -0
  128. package/scripts/harness/world-model/infrastructure/adapters/adr-fact-extractor.ts +27 -0
  129. package/scripts/harness/world-model/infrastructure/adapters/assembled-world-fact-source.ts +17 -0
  130. package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +323 -0
  131. package/scripts/harness/world-model/infrastructure/adapters/attestation-sha256-world-hashing-adapter.ts +15 -0
  132. package/scripts/harness/world-model/infrastructure/adapters/composite-design-fact-source.ts +23 -0
  133. package/scripts/harness/world-model/infrastructure/adapters/design-corpus-fact-extractor.ts +285 -0
  134. package/scripts/harness/world-model/infrastructure/adapters/design-fact-extraction.ts +63 -0
  135. package/scripts/harness/world-model/infrastructure/adapters/file-system-obligation-report-writer-adapter.ts +37 -0
  136. package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +332 -0
  137. package/scripts/harness/world-model/infrastructure/adapters/integrity-manifest-fact-extractor.ts +101 -0
  138. package/scripts/harness/world-model/infrastructure/adapters/json-fact-extractor-support.ts +151 -0
  139. package/scripts/harness/world-model/infrastructure/adapters/markdown-design-fact-extractor.ts +493 -0
  140. package/scripts/harness/world-model/infrastructure/adapters/matrix-fact-extractor.ts +283 -0
  141. package/scripts/harness/world-model/infrastructure/adapters/product-fact-extractor.ts +29 -0
  142. package/scripts/harness/world-model/infrastructure/adapters/proposal-fact-extractor.ts +29 -0
  143. package/scripts/harness/world-model/infrastructure/adapters/runtime-fact-extraction.ts +19 -0
  144. package/scripts/harness/world-model/infrastructure/adapters/source-metadata-fact-extractor.ts +22 -0
  145. package/scripts/harness/world-model/infrastructure/adapters/test-reference-source-fact-extractor.ts +22 -0
  146. package/scripts/harness/world-model/infrastructure/adapters/traceability-design-fact-adapter.ts +112 -0
  147. package/scripts/harness/world-model/infrastructure/adapters/traceability-world-read-facade-merger.ts +38 -0
  148. package/scripts/harness/world-model/infrastructure/adapters/type-script-source-fact-extractor.ts +236 -0
  149. package/scripts/harness/world-model/infrastructure/adapters/unit-fact-extractor.ts +71 -0
  150. package/scripts/harness/world-model/infrastructure/adapters/world-control-declaration-mapper.ts +383 -0
  151. package/scripts/harness/world-model/presentation/cli/world-command-support.ts +60 -0
  152. package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +109 -0
  153. package/scripts/harness/world-model/presentation/cli/world-inspect-command-handler.ts +194 -0
  154. package/scripts/harness/world-model/presentation/cli/world-pin-command-handler.ts +100 -0
@@ -21,6 +21,10 @@
21
21
  * @work-item-id WI-213
22
22
  * @work-item-id WI-217
23
23
  * @work-item-id WI-219
24
+ * @work-item-id WI-291
25
+ * @work-item-id WI-300
26
+ * @work-item-id WI-306
27
+ * @work-item-id WI-308
24
28
  *
25
29
  * Phasegate CLI エントリポイント。
26
30
  * 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
@@ -44,6 +48,7 @@ import { createBiomeAstEngineModule } from "./biome-ast-engine/composition-root.
44
48
  import { buildCiGovernance } from "./ci-governance/composition-root.js";
45
49
  import { toPhaseConfigSection } from "./config-foundation/application/mappers/phase-config-section-mapper.js";
46
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";
47
52
  import { createConfigFoundationModule } from "./config-foundation/composition-root.js";
48
53
  import { ConfigValidationError } from "./config-foundation/domain/errors/config-validation-error.js";
49
54
  import type { HarnessConfigV2 } from "./config-foundation/domain/harness-config.js";
@@ -92,6 +97,12 @@ import {
92
97
  import { createSkillQualityHandlers } from "./skill-quality/composition-root.js";
93
98
  import { createTraceabilityModelModule } from "./traceability-model/composition-root.js";
94
99
  import { createValidatorSystemModule } from "./validator-system/composition-root.js";
100
+ import {
101
+ createWorldModelModule,
102
+ WorldDeriveCommandHandler,
103
+ WorldInspectCommandHandler,
104
+ WorldPinCommandHandler,
105
+ } from "./world-model/index.js";
95
106
 
96
107
  /**
97
108
  * main.ts (scripts/harness/main.ts) から2階層上がパッケージルート。
@@ -232,6 +243,9 @@ Commands:
232
243
  phasegate:generate-matrix Generate requirement-test matrix (--requirements, --tests, --out, --json)
233
244
  phasegate:attest Produce a signed-attestation record of ci-check (--out <path>, --require-pass, --mode <unsigned-poc|signed>, --json)
234
245
  phasegate:verify-attestation Verify an attestation record's integrity (<file>, --json)
246
+ world:inspect Inspect the read-only World snapshot (--format human|json, --json)
247
+ world:pin Preview or apply an endpoint pin (--constraint, --endpoint, --apply)
248
+ world:derive Derive obligations (--write, --out, --format human|json, --json)
235
249
 
236
250
  Gate semantics:
237
251
  phasegate:complete-check Gate: lint + all validators; exits 1 on failure
@@ -780,6 +794,35 @@ Options:
780
794
  "phasegate:check-ready": `Usage: phasegate phasegate:check-ready
781
795
 
782
796
  Check whether the harness is ready (config valid, hooks deployed).`,
797
+ "world:inspect": `Usage: phasegate world:inspect [options]
798
+
799
+ Inspect the read-only World snapshot without writing declarations or generated reports.
800
+
801
+ Options:
802
+ --format <human|json> Output format (default: human)
803
+ --json Alias for --format json
804
+ --help, -h Show this help`,
805
+ "world:pin": `Usage: phasegate world:pin --constraint <id> --endpoint <claimant|premise> [options]
806
+
807
+ Preview a current endpoint digest. Use --apply to atomically update phasegate.world-constraints.json.
808
+
809
+ Options:
810
+ --constraint <id> pgw:v1:constraint ID
811
+ --endpoint <role> claimant or premise
812
+ --apply Atomically apply the candidate
813
+ --format <human|json> Output format (default: human)
814
+ --json Alias for --format json
815
+ --help, -h Show this help`,
816
+ "world:derive": `Usage: phasegate world:derive [options]
817
+
818
+ Derive the immutable World obligation report. Default mode is pure and read-only.
819
+
820
+ Options:
821
+ --write Persist the raw obligation report
822
+ --out <path> Override report path (requires --write)
823
+ --format <human|json> Output format (default: human)
824
+ --json Alias for --format json
825
+ --help, -h Show this help`,
783
826
  "phasegate:complete-check": `Usage: phasegate phasegate:complete-check
784
827
 
785
828
  Run completion check (used by Stop hook). Validates phase-gate, metadata, and test-quality.`,
@@ -1838,13 +1881,45 @@ async function loadResolvedConfig(): Promise<HarnessConfigV2 | undefined> {
1838
1881
  }
1839
1882
  }
1840
1883
 
1884
+ async function loadWorldResolvedConfig() {
1885
+ try {
1886
+ const configModule = createConfigFoundationModule();
1887
+ const result = await configModule.usecases.loadResolvedConfigUseCase.execute();
1888
+ return toWorldModelConfig(result.config);
1889
+ } catch (error) {
1890
+ if (error instanceof ConfigNotFoundError) return undefined;
1891
+ throw error;
1892
+ }
1893
+ }
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
+
1841
1915
  async function main(): Promise<void> {
1842
1916
  const args = process.argv.slice(2);
1843
1917
  const command = args[0];
1844
1918
 
1845
1919
  if (!command || command === "--help" || command === "help") {
1846
1920
  printUsage();
1847
- process.exit(0);
1921
+ await finishCliExit(0);
1922
+ return;
1848
1923
  }
1849
1924
 
1850
1925
  const rootDir = getProjectRoot();
@@ -1853,19 +1928,21 @@ async function main(): Promise<void> {
1853
1928
  if (command === "--version" || command === "version") {
1854
1929
  const version = await getHarnessVersion(harnessRoot);
1855
1930
  console.log(`phasegate v${version}`);
1856
- process.exit(0);
1931
+ await finishCliExit(0);
1932
+ return;
1857
1933
  }
1858
1934
 
1859
1935
  // Pre-dispatch: 全 subcommand で --help / -h を最優先で解釈し usage 出力 (副作用走行を防ぐ — WI-091 finding #3)
1860
1936
  if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
1861
1937
  printSubcommandHelp(command);
1862
- process.exit(0);
1938
+ await finishCliExit(0);
1939
+ return;
1863
1940
  }
1864
1941
 
1865
1942
  const json = hasFlag(args, "--json");
1866
1943
 
1867
1944
  // Cross-unit wiring: 設定を先に解決し、各Unit に注入する
1868
- const resolvedConfig = await loadResolvedConfig();
1945
+ const resolvedConfig = command.startsWith("world:") ? undefined : await loadResolvedConfig();
1869
1946
 
1870
1947
  try {
1871
1948
  switch (command) {
@@ -2134,8 +2211,8 @@ async function main(): Promise<void> {
2134
2211
  console.log(" • Q&A about phasegate concepts: invoke /phasegate-toolkit-guide");
2135
2212
  console.log(" • Diagnose & tune phasegate.config.json: invoke /phasegate-config-doctor");
2136
2213
  }
2137
- process.exit(hasStructuredInstallError(installResult) ? 1 : 0);
2138
- break;
2214
+ await finishCliExit(hasStructuredInstallError(installResult) ? 1 : 0);
2215
+ return;
2139
2216
  }
2140
2217
 
2141
2218
  case "update-skills": {
@@ -2160,8 +2237,8 @@ async function main(): Promise<void> {
2160
2237
  });
2161
2238
  if (!json) console.log("phasegate update-skills is deprecated; running phasegate reconcile.");
2162
2239
  console.log(result.stdout);
2163
- process.exit(result.exitCode);
2164
- break;
2240
+ await finishCliExit(result.exitCode);
2241
+ return;
2165
2242
  }
2166
2243
 
2167
2244
  case "doctor": {
@@ -2187,14 +2264,14 @@ async function main(): Promise<void> {
2187
2264
  agent,
2188
2265
  });
2189
2266
  console.log(result.stdout);
2190
- process.exit(result.exitCode);
2191
- break;
2267
+ await finishCliExit(result.exitCode);
2268
+ return;
2192
2269
  }
2193
2270
 
2194
2271
  case "emit-agent-rules": {
2195
2272
  console.log(emitAgentRulesBlock());
2196
- process.exit(0);
2197
- break;
2273
+ await finishCliExit(0);
2274
+ return;
2198
2275
  }
2199
2276
 
2200
2277
  case "scaffold-wi": {
@@ -2220,8 +2297,8 @@ async function main(): Promise<void> {
2220
2297
  inceptionRoot: parseFlag(args, "--root") ?? configuredPersonalRoot,
2221
2298
  });
2222
2299
  console.log(`Created ${descriptionPath}`);
2223
- process.exit(0);
2224
- break;
2300
+ await finishCliExit(0);
2301
+ return;
2225
2302
  }
2226
2303
 
2227
2304
  case "install": {
@@ -2278,8 +2355,8 @@ async function main(): Promise<void> {
2278
2355
  json,
2279
2356
  });
2280
2357
  console.log(result.stdout);
2281
- process.exit(result.exitCode);
2282
- break;
2358
+ await finishCliExit(result.exitCode);
2359
+ return;
2283
2360
  }
2284
2361
 
2285
2362
  case "setup:agent": {
@@ -2361,8 +2438,8 @@ async function main(): Promise<void> {
2361
2438
  console.log("Validation:");
2362
2439
  for (const command of plan.validation) console.log(`- ${command}`);
2363
2440
  }
2364
- process.exit(hasStructuredInstallError(installResult) ? 1 : 0);
2365
- break;
2441
+ await finishCliExit(hasStructuredInstallError(installResult) ? 1 : 0);
2442
+ return;
2366
2443
  }
2367
2444
 
2368
2445
  case "config:plan": {
@@ -2392,7 +2469,8 @@ async function main(): Promise<void> {
2392
2469
  for (const operation of applyResult.appliedOperations)
2393
2470
  console.log(`- ${operation.op} ${operation.pointer}`);
2394
2471
  }
2395
- process.exit(0);
2472
+ await finishCliExit(0);
2473
+ return;
2396
2474
  } catch (error) {
2397
2475
  const message = error instanceof Error ? error.message : String(error);
2398
2476
  if (json) {
@@ -2406,7 +2484,8 @@ async function main(): Promise<void> {
2406
2484
  } else {
2407
2485
  console.error(`Error: ${message}`);
2408
2486
  }
2409
- process.exit(1);
2487
+ await finishCliExit(1);
2488
+ return;
2410
2489
  }
2411
2490
  }
2412
2491
  if (json) {
@@ -2420,8 +2499,8 @@ async function main(): Promise<void> {
2420
2499
  console.log("Validation:");
2421
2500
  for (const validation of plan.validations) console.log(`- ${validation}`);
2422
2501
  }
2423
- process.exit(0);
2424
- break;
2502
+ await finishCliExit(0);
2503
+ return;
2425
2504
  }
2426
2505
 
2427
2506
  case "session": {
@@ -2442,7 +2521,8 @@ async function main(): Promise<void> {
2442
2521
  `Full Mode session started: ${session.workItemId} unit=${session.unit} expiresAt=${session.expiresAt}`,
2443
2522
  );
2444
2523
  }
2445
- process.exit(0);
2524
+ await finishCliExit(0);
2525
+ return;
2446
2526
  } catch (error) {
2447
2527
  const message = error instanceof Error ? error.message : String(error);
2448
2528
  if (json) {
@@ -2450,7 +2530,8 @@ async function main(): Promise<void> {
2450
2530
  } else {
2451
2531
  console.error(`Error: ${message}`);
2452
2532
  }
2453
- process.exit(2);
2533
+ await finishCliExit(2);
2534
+ return;
2454
2535
  }
2455
2536
  }
2456
2537
  if (subcommand === "end") {
@@ -2463,7 +2544,8 @@ async function main(): Promise<void> {
2463
2544
  } else {
2464
2545
  console.log("No Full Mode session was active.");
2465
2546
  }
2466
- process.exit(0);
2547
+ await finishCliExit(0);
2548
+ return;
2467
2549
  } catch (error) {
2468
2550
  const message = error instanceof Error ? error.message : String(error);
2469
2551
  if (json) {
@@ -2471,7 +2553,8 @@ async function main(): Promise<void> {
2471
2553
  } else {
2472
2554
  console.error(`Error: ${message}`);
2473
2555
  }
2474
- process.exit(2);
2556
+ await finishCliExit(2);
2557
+ return;
2475
2558
  }
2476
2559
  }
2477
2560
  console.error("Error: session subcommand must be begin or end.");
@@ -2498,8 +2581,8 @@ async function main(): Promise<void> {
2498
2581
  json,
2499
2582
  });
2500
2583
  console.log(result.stdout);
2501
- process.exit(result.exitCode);
2502
- break;
2584
+ await finishCliExit(result.exitCode);
2585
+ return;
2503
2586
  }
2504
2587
 
2505
2588
  case "reconcile": {
@@ -2523,8 +2606,8 @@ async function main(): Promise<void> {
2523
2606
  json,
2524
2607
  });
2525
2608
  console.log(result.stdout);
2526
- process.exit(result.exitCode);
2527
- break;
2609
+ await finishCliExit(result.exitCode);
2610
+ return;
2528
2611
  }
2529
2612
 
2530
2613
  // ── config-foundation ──
@@ -2539,8 +2622,8 @@ async function main(): Promise<void> {
2539
2622
  configPath,
2540
2623
  });
2541
2624
  console.log(result.output);
2542
- process.exit(result.exitCode);
2543
- break;
2625
+ await finishCliExit(result.exitCode);
2626
+ return;
2544
2627
  }
2545
2628
 
2546
2629
  case "disable-feature": {
@@ -2554,8 +2637,8 @@ async function main(): Promise<void> {
2554
2637
  configPath,
2555
2638
  });
2556
2639
  console.log(result.output);
2557
- process.exit(result.exitCode);
2558
- break;
2640
+ await finishCliExit(result.exitCode);
2641
+ return;
2559
2642
  }
2560
2643
 
2561
2644
  case "list-features": {
@@ -2564,8 +2647,8 @@ async function main(): Promise<void> {
2564
2647
  list: true,
2565
2648
  });
2566
2649
  console.log(result.output);
2567
- process.exit(result.exitCode);
2568
- break;
2650
+ await finishCliExit(result.exitCode);
2651
+ return;
2569
2652
  }
2570
2653
 
2571
2654
  case "migrate": {
@@ -2577,8 +2660,8 @@ async function main(): Promise<void> {
2577
2660
  json,
2578
2661
  });
2579
2662
  console.log(result.text);
2580
- process.exit(result.exitCode);
2581
- break;
2663
+ await finishCliExit(result.exitCode);
2664
+ return;
2582
2665
  }
2583
2666
  const mod = createConfigFoundationModule();
2584
2667
  const targetVersion = parseFlag(args, "--schema") ?? "v3";
@@ -2588,8 +2671,8 @@ async function main(): Promise<void> {
2588
2671
  configPath,
2589
2672
  });
2590
2673
  console.log(result.output);
2591
- process.exit(result.exitCode);
2592
- break;
2674
+ await finishCliExit(result.exitCode);
2675
+ return;
2593
2676
  }
2594
2677
 
2595
2678
  // ── harness-error ──
@@ -2603,8 +2686,8 @@ async function main(): Promise<void> {
2603
2686
  failOnError,
2604
2687
  });
2605
2688
  console.log(result.output);
2606
- process.exit(result.exitCode);
2607
- break;
2689
+ await finishCliExit(result.exitCode);
2690
+ return;
2608
2691
  }
2609
2692
 
2610
2693
  case "validate-fix": {
@@ -2618,8 +2701,8 @@ async function main(): Promise<void> {
2618
2701
  format,
2619
2702
  });
2620
2703
  console.log(result.output);
2621
- process.exit(result.exitCode);
2622
- break;
2704
+ await finishCliExit(result.exitCode);
2705
+ return;
2623
2706
  }
2624
2707
 
2625
2708
  case "list-errors": {
@@ -2631,8 +2714,8 @@ async function main(): Promise<void> {
2631
2714
  layer,
2632
2715
  });
2633
2716
  console.log(result.output);
2634
- process.exit(result.exitCode);
2635
- break;
2717
+ await finishCliExit(result.exitCode);
2718
+ return;
2636
2719
  }
2637
2720
 
2638
2721
  // ── traceability-model ──
@@ -2644,8 +2727,8 @@ async function main(): Promise<void> {
2644
2727
  json,
2645
2728
  });
2646
2729
  console.log(result.text);
2647
- process.exit(result.exitCode);
2648
- break;
2730
+ await finishCliExit(result.exitCode);
2731
+ return;
2649
2732
  }
2650
2733
 
2651
2734
  case "work-items:status": {
@@ -2660,8 +2743,60 @@ async function main(): Promise<void> {
2660
2743
  json,
2661
2744
  });
2662
2745
  console.log(result.text);
2663
- process.exit(result.exitCode);
2664
- break;
2746
+ await finishCliExit(result.exitCode);
2747
+ return;
2748
+ }
2749
+
2750
+ // ── world-model ──
2751
+ case "world:inspect": {
2752
+ let handler: WorldInspectCommandHandler;
2753
+ try {
2754
+ handler = createWorldModelModule({
2755
+ rootDir,
2756
+ resolvedConfig: await loadWorldResolvedConfig(),
2757
+ }).worldInspectCommandHandler;
2758
+ } catch (error) {
2759
+ handler = WorldInspectCommandHandler.fromFailure(error);
2760
+ }
2761
+ const result = await handler.execute(args.slice(1));
2762
+ if (result.stdout.length > 0) process.stdout.write(result.stdout);
2763
+ if (result.stderr.length > 0) process.stderr.write(result.stderr);
2764
+ process.exitCode = result.exitCode;
2765
+ return;
2766
+ }
2767
+
2768
+ case "world:pin": {
2769
+ let handler: WorldPinCommandHandler;
2770
+ try {
2771
+ handler = createWorldModelModule({
2772
+ rootDir,
2773
+ resolvedConfig: await loadWorldResolvedConfig(),
2774
+ }).worldPinCommandHandler;
2775
+ } catch (error) {
2776
+ handler = WorldPinCommandHandler.fromFailure(error);
2777
+ }
2778
+ const result = await handler.execute(args.slice(1));
2779
+ if (result.stdout.length > 0) process.stdout.write(result.stdout);
2780
+ if (result.stderr.length > 0) process.stderr.write(result.stderr);
2781
+ process.exitCode = result.exitCode;
2782
+ return;
2783
+ }
2784
+
2785
+ case "world:derive": {
2786
+ let handler: WorldDeriveCommandHandler;
2787
+ try {
2788
+ handler = createWorldModelModule({
2789
+ rootDir,
2790
+ resolvedConfig: await loadWorldResolvedConfig(),
2791
+ }).worldDeriveCommandHandler;
2792
+ } catch (error) {
2793
+ handler = WorldDeriveCommandHandler.fromFailure(error);
2794
+ }
2795
+ const result = await handler.execute(args.slice(1));
2796
+ if (result.stdout.length > 0) process.stdout.write(result.stdout);
2797
+ if (result.stderr.length > 0) process.stderr.write(result.stderr);
2798
+ process.exitCode = result.exitCode;
2799
+ return;
2665
2800
  }
2666
2801
 
2667
2802
  // ── phase-dependency-model ──
@@ -2685,8 +2820,8 @@ async function main(): Promise<void> {
2685
2820
  json,
2686
2821
  });
2687
2822
  console.log(result.text);
2688
- process.exit(result.exitCode);
2689
- break;
2823
+ await finishCliExit(result.exitCode);
2824
+ return;
2690
2825
  }
2691
2826
 
2692
2827
  // ── adr-foundation ──
@@ -2701,8 +2836,8 @@ async function main(): Promise<void> {
2701
2836
  json,
2702
2837
  });
2703
2838
  console.log(result.text);
2704
- process.exit(result.exitCode);
2705
- break;
2839
+ await finishCliExit(result.exitCode);
2840
+ return;
2706
2841
  }
2707
2842
 
2708
2843
  case "validate-adr": {
@@ -2715,8 +2850,8 @@ async function main(): Promise<void> {
2715
2850
  json,
2716
2851
  });
2717
2852
  console.log(result.text);
2718
- process.exit(result.exitCode);
2719
- break;
2853
+ await finishCliExit(result.exitCode);
2854
+ return;
2720
2855
  }
2721
2856
 
2722
2857
  // ── biome-ast-engine ──
@@ -2726,8 +2861,8 @@ async function main(): Promise<void> {
2726
2861
  const mod = createBiomeAstEngineModule(rootDir, { l1Config, architecture });
2727
2862
  const result = await mod.harnessLintCommandHandler.execute(args.slice(1));
2728
2863
  console.log(result.text);
2729
- process.exit(result.exitCode);
2730
- break;
2864
+ await finishCliExit(result.exitCode);
2865
+ return;
2731
2866
  }
2732
2867
 
2733
2868
  // ── validator-system ──
@@ -2754,8 +2889,8 @@ async function main(): Promise<void> {
2754
2889
  if ((layer === "L2" || layer === "all") && format !== "ci") {
2755
2890
  await printStoryReflectionValidationSummary(rootDir, unit);
2756
2891
  }
2757
- process.exit(result.exitCode);
2758
- break;
2892
+ await finishCliExit(result.exitCode);
2893
+ return;
2759
2894
  }
2760
2895
 
2761
2896
  // ── quick-mode / ci-check ──
@@ -2810,8 +2945,8 @@ async function main(): Promise<void> {
2810
2945
  format,
2811
2946
  failOnFullRequired,
2812
2947
  });
2813
- process.exit(result.exitCode);
2814
- break;
2948
+ await finishCliExit(result.exitCode);
2949
+ return;
2815
2950
  }
2816
2951
 
2817
2952
  // ── harness-api ──
@@ -2971,7 +3106,16 @@ async function main(): Promise<void> {
2971
3106
  }
2972
3107
  const { createAttestationModule } = await import("./attestation/index.js");
2973
3108
  const pkgVersion = await getHarnessVersion(harnessRoot);
2974
- 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
+ });
2975
3119
  const result = await mod.attestHandler.handle({
2976
3120
  out: parseFlag(args, "--out") ?? ".harness/attestation.json",
2977
3121
  requirePass: hasFlag(args, "--require-pass"),
@@ -2979,8 +3123,8 @@ async function main(): Promise<void> {
2979
3123
  mode: parseFlag(args, "--mode"),
2980
3124
  });
2981
3125
  console.log(result.output);
2982
- process.exit(result.exitCode);
2983
- break;
3126
+ await finishCliExit(result.exitCode);
3127
+ return;
2984
3128
  }
2985
3129
 
2986
3130
  case "phasegate:verify-attestation": {
@@ -2997,8 +3141,8 @@ async function main(): Promise<void> {
2997
3141
  emitJson: json,
2998
3142
  });
2999
3143
  console.log(result.output);
3000
- process.exit(result.exitCode);
3001
- break;
3144
+ await finishCliExit(result.exitCode);
3145
+ return;
3002
3146
  }
3003
3147
 
3004
3148
  // ── ci-governance ──
@@ -3021,7 +3165,8 @@ Options:
3021
3165
  Examples:
3022
3166
  phasegate ci:generate-template --type aidlc-gate
3023
3167
  phasegate ci:generate-template --preset strict --type pre-commit --render`);
3024
- process.exit(0);
3168
+ await finishCliExit(0);
3169
+ return;
3025
3170
  }
3026
3171
  const flagError = validateKnownFlags(args.slice(1), ["--preset", "--type", "--render", "--json", "--help"]);
3027
3172
  if (flagError !== null) {
@@ -3035,8 +3180,8 @@ Examples:
3035
3180
  const format = json ? "json" : "human";
3036
3181
  const result = await mod.generateCiTemplateHandler.handle({ presetId, templateType, render, format });
3037
3182
  console.log(result.output);
3038
- process.exit(result.exitCode);
3039
- break;
3183
+ await finishCliExit(result.exitCode);
3184
+ return;
3040
3185
  }
3041
3186
 
3042
3187
  case "ci:migrate-agents-md": {
@@ -3046,8 +3191,8 @@ Examples:
3046
3191
  const format = json ? "json" : "human";
3047
3192
  const result = await mod.migrateAgentsMdHandler.handle({ dryRun, validateOnly, format });
3048
3193
  console.log(result.output);
3049
- process.exit(result.exitCode);
3050
- break;
3194
+ await finishCliExit(result.exitCode);
3195
+ return;
3051
3196
  }
3052
3197
 
3053
3198
  case "ci:auto-refresh-agent-context": {
@@ -3057,8 +3202,8 @@ Examples:
3057
3202
  const format = json ? "json" : "human";
3058
3203
  const result = await mod.refreshAgentContextHandler.handle({ dryRun, apply, format });
3059
3204
  console.log(result.output);
3060
- process.exit(result.exitCode);
3061
- break;
3205
+ await finishCliExit(result.exitCode);
3206
+ return;
3062
3207
  }
3063
3208
 
3064
3209
  case "refresh-claude-md": {
@@ -3068,8 +3213,8 @@ Examples:
3068
3213
  const format = json ? "json" : "human";
3069
3214
  const result = await mod.refreshClaudeMdHandler.handle({ dryRun, apply, format });
3070
3215
  console.log(result.output);
3071
- process.exit(result.exitCode);
3072
- break;
3216
+ await finishCliExit(result.exitCode);
3217
+ return;
3073
3218
  }
3074
3219
 
3075
3220
  case "p2:check-agent-context": {
@@ -3079,8 +3224,8 @@ Examples:
3079
3224
  const format = json ? "json" : "human";
3080
3225
  const result = await mod.checkAgentContextHandler.handle({ thresholdDays, format });
3081
3226
  console.log(result.output);
3082
- process.exit(result.exitCode);
3083
- break;
3227
+ await finishCliExit(result.exitCode);
3228
+ return;
3084
3229
  }
3085
3230
 
3086
3231
  case "ci:check-repetition": {
@@ -3090,8 +3235,8 @@ Examples:
3090
3235
  const format = json ? "json" : "human";
3091
3236
  const result = await mod.checkRepetitionHandler.handle({ errorCode, reset, format });
3092
3237
  console.log(result.output);
3093
- process.exit(result.exitCode);
3094
- break;
3238
+ await finishCliExit(result.exitCode);
3239
+ return;
3095
3240
  }
3096
3241
 
3097
3242
  case "baseline": {
@@ -3113,8 +3258,8 @@ Examples:
3113
3258
  format,
3114
3259
  });
3115
3260
  console.log(result.output);
3116
- process.exit(result.exitCode);
3117
- break;
3261
+ await finishCliExit(result.exitCode);
3262
+ return;
3118
3263
  }
3119
3264
 
3120
3265
  case "scaffold-design": {
@@ -3134,8 +3279,8 @@ Examples:
3134
3279
  format,
3135
3280
  });
3136
3281
  console.log(result.output);
3137
- process.exit(result.exitCode);
3138
- break;
3282
+ await finishCliExit(result.exitCode);
3283
+ return;
3139
3284
  }
3140
3285
 
3141
3286
  case "integrity:pin": {
@@ -3144,8 +3289,8 @@ Examples:
3144
3289
  const format = json ? "json" : "human";
3145
3290
  const result = await mod.integrityHandler.pin({ dryRun, format });
3146
3291
  console.log(result.output);
3147
- process.exit(result.exitCode);
3148
- break;
3292
+ await finishCliExit(result.exitCode);
3293
+ return;
3149
3294
  }
3150
3295
 
3151
3296
  case "integrity:verify": {
@@ -3153,8 +3298,8 @@ Examples:
3153
3298
  const format = json ? "json" : "human";
3154
3299
  const result = await mod.integrityHandler.verify({ format });
3155
3300
  console.log(result.output);
3156
- process.exit(result.exitCode);
3157
- break;
3301
+ await finishCliExit(result.exitCode);
3302
+ return;
3158
3303
  }
3159
3304
 
3160
3305
  // ── skill-quality ──
@@ -3169,8 +3314,8 @@ Examples:
3169
3314
  const passed = hasFlag(args, "--passed");
3170
3315
  const result = await mod.executeTddCycleHandler.handle({ unit, storyId, description, phase, passed });
3171
3316
  console.log(result.message);
3172
- process.exit(result.exitCode);
3173
- break;
3317
+ await finishCliExit(result.exitCode);
3318
+ return;
3174
3319
  }
3175
3320
 
3176
3321
  case "skill:check-coverage": {
@@ -3179,8 +3324,8 @@ Examples:
3179
3324
  const format = json ? "json" : "human";
3180
3325
  const result = await mod.checkCoverageHandler.handle({ storyId, format });
3181
3326
  console.log(result.message);
3182
- process.exit(result.exitCode);
3183
- break;
3327
+ await finishCliExit(result.exitCode);
3328
+ return;
3184
3329
  }
3185
3330
 
3186
3331
  case "skill:collect-lessons": {
@@ -3191,8 +3336,8 @@ Examples:
3191
3336
  const writeArtifact = hasFlag(args, "--write-artifact");
3192
3337
  const result = await mod.collectLessonsHandler.handle({ storyId, sources, writeArtifact });
3193
3338
  console.log(result.message);
3194
- process.exit(result.exitCode);
3195
- break;
3339
+ await finishCliExit(result.exitCode);
3340
+ return;
3196
3341
  }
3197
3342
 
3198
3343
  case "skill:apply-cascade-update": {
@@ -3201,8 +3346,8 @@ Examples:
3201
3346
  const dryRun = hasFlag(args, "--dry-run");
3202
3347
  const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun, format: json ? "json" : "human" });
3203
3348
  console.log(result.message);
3204
- process.exit(result.exitCode);
3205
- break;
3349
+ await finishCliExit(result.exitCode);
3350
+ return;
3206
3351
  }
3207
3352
 
3208
3353
  case "skill:validate-structure": {
@@ -3211,8 +3356,8 @@ Examples:
3211
3356
  const format = json ? "json" : "human";
3212
3357
  const result = await mod.validateSkillStructureHandler.handle({ skillFile, format });
3213
3358
  console.log(result.message);
3214
- process.exit(result.exitCode);
3215
- break;
3359
+ await finishCliExit(result.exitCode);
3360
+ return;
3216
3361
  }
3217
3362
 
3218
3363
  // ── regression-suite ──
@@ -3223,8 +3368,8 @@ Examples:
3223
3368
  ? JSON.stringify(result, null, 2)
3224
3369
  : `K-Requirements: ${result.passedCount}/${result.totalCount} passed`;
3225
3370
  console.log(output);
3226
- process.exit(result.failedCount > 0 ? 1 : 0);
3227
- break;
3371
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3372
+ return;
3228
3373
  }
3229
3374
 
3230
3375
  case "regression:run-gng-gate": {
@@ -3234,8 +3379,8 @@ Examples:
3234
3379
  ? JSON.stringify(result, null, 2)
3235
3380
  : `GnG Gate: ${result.passedCount}/${result.totalCount} passed`;
3236
3381
  console.log(output);
3237
- process.exit(result.failedCount > 0 ? 1 : 0);
3238
- break;
3382
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3383
+ return;
3239
3384
  }
3240
3385
 
3241
3386
  case "regression:run-agent-guard": {
@@ -3245,8 +3390,8 @@ Examples:
3245
3390
  ? JSON.stringify(result, null, 2)
3246
3391
  : `Agent Independence: ${result.passedCount}/${result.totalCount} passed`;
3247
3392
  console.log(output);
3248
- process.exit(result.failedCount > 0 ? 1 : 0);
3249
- break;
3393
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3394
+ return;
3250
3395
  }
3251
3396
 
3252
3397
  case "regression:run-k14-k15": {
@@ -3256,8 +3401,8 @@ Examples:
3256
3401
  ? JSON.stringify(result, null, 2)
3257
3402
  : `K14/K15: ${result.passedCount}/${result.totalCount} passed`;
3258
3403
  console.log(output);
3259
- process.exit(result.failedCount > 0 ? 1 : 0);
3260
- break;
3404
+ await finishCliExit(result.failedCount > 0 ? 1 : 0);
3405
+ return;
3261
3406
  }
3262
3407
 
3263
3408
  case "regression:configure-ci-gate": {
@@ -3273,8 +3418,8 @@ Examples:
3273
3418
  ? JSON.stringify(result, null, 2)
3274
3419
  : `CI gate configured: suites=${result.requiredSuiteIds.join(",")}, threshold=${result.coverageThreshold}%`;
3275
3420
  console.log(output);
3276
- process.exit(0);
3277
- break;
3421
+ await finishCliExit(0);
3422
+ return;
3278
3423
  }
3279
3424
 
3280
3425
  case "regression:analyze-migration": {
@@ -3285,8 +3430,8 @@ Examples:
3285
3430
  ? JSON.stringify(result, null, 2)
3286
3431
  : `Migration analysis: total=${result.totalCount}, migrated=${result.migratedCount}, modified=${result.modifiedCount}, skipped=${result.skippedCount}`;
3287
3432
  console.log(output);
3288
- process.exit(0);
3289
- break;
3433
+ await finishCliExit(0);
3434
+ return;
3290
3435
  }
3291
3436
 
3292
3437
  case "regression:migrate-v0-tests": {
@@ -3297,8 +3442,8 @@ Examples:
3297
3442
  ? JSON.stringify(result, null, 2)
3298
3443
  : `Migration: total=${result.totalCount}, migrated=${result.migratedCount}, modified=${result.modifiedCount}, skipped=${result.skippedCount}`;
3299
3444
  console.log(output);
3300
- process.exit(0);
3301
- break;
3445
+ await finishCliExit(0);
3446
+ return;
3302
3447
  }
3303
3448
 
3304
3449
  // ── phase2-extensions ──
@@ -3307,8 +3452,8 @@ Examples:
3307
3452
  const p2args = args.slice(1);
3308
3453
  const result = await mod.checkFreshnessHandler.handle(p2args);
3309
3454
  console.log(result.stdout);
3310
- process.exit(result.exitCode);
3311
- break;
3455
+ await finishCliExit(result.exitCode);
3456
+ return;
3312
3457
  }
3313
3458
 
3314
3459
  case "p2:validate-pointers": {
@@ -3316,8 +3461,8 @@ Examples:
3316
3461
  const p2args = args.slice(1);
3317
3462
  const result = await mod.validatePointersHandler.handle(p2args);
3318
3463
  console.log(result.stdout);
3319
- process.exit(result.exitCode);
3320
- break;
3464
+ await finishCliExit(result.exitCode);
3465
+ return;
3321
3466
  }
3322
3467
 
3323
3468
  case "p2:generate-e2e-template": {
@@ -3325,8 +3470,8 @@ Examples:
3325
3470
  const p2args = args.slice(1);
3326
3471
  const result = await mod.generateE2ETemplateHandler.handle(p2args);
3327
3472
  console.log(result.stdout);
3328
- process.exit(result.exitCode);
3329
- break;
3473
+ await finishCliExit(result.exitCode);
3474
+ return;
3330
3475
  }
3331
3476
 
3332
3477
  case "p2:check-initial-creation": {
@@ -3334,8 +3479,8 @@ Examples:
3334
3479
  const p2args = args.slice(1);
3335
3480
  const result = await mod.checkInitialCreationExpirationHandler.handle(p2args);
3336
3481
  console.log(result.stdout);
3337
- process.exit(result.exitCode);
3338
- break;
3482
+ await finishCliExit(result.exitCode);
3483
+ return;
3339
3484
  }
3340
3485
 
3341
3486
  // ── agent integration / hooks ──
@@ -3394,7 +3539,8 @@ Examples:
3394
3539
  case "delegate-sonnet": {
3395
3540
  if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
3396
3541
  printSubcommandHelp("delegate-sonnet");
3397
- process.exit(0);
3542
+ await finishCliExit(0);
3543
+ return;
3398
3544
  }
3399
3545
  if (!hasFlag(args, "--dry-run") && (await isModelDelegationDisabled(rootDir))) {
3400
3546
  console.error(
@@ -3403,7 +3549,8 @@ Examples:
3403
3549
  message: "phasegate delegate-sonnet is disabled by modelRouting.delegation=none",
3404
3550
  }),
3405
3551
  );
3406
- process.exit(1);
3552
+ await finishCliExit(1);
3553
+ return;
3407
3554
  }
3408
3555
  const { spawn } = await import("node:child_process");
3409
3556
  const scriptPath = join(harnessRoot, "scripts/delegate-sonnet.sh");
@@ -3453,7 +3600,8 @@ Examples:
3453
3600
  }
3454
3601
  console.log("");
3455
3602
  }
3456
- process.exit(0);
3603
+ await finishCliExit(0);
3604
+ return;
3457
3605
  }
3458
3606
 
3459
3607
  if (subCommand === "info") {
@@ -3467,7 +3615,8 @@ Examples:
3467
3615
  try {
3468
3616
  const content = await fs.readFile(skillMdPath, "utf-8");
3469
3617
  console.log(content);
3470
- process.exit(0);
3618
+ await finishCliExit(0);
3619
+ return;
3471
3620
  } catch {
3472
3621
  console.error(`Skill not found: ${skillName}`);
3473
3622
  console.error(`Expected: ${skillMdPath}`);
@@ -3483,12 +3632,13 @@ Examples:
3483
3632
  default:
3484
3633
  console.error(`Unknown command: ${command}`);
3485
3634
  printUsage();
3486
- process.exit(2);
3635
+ await finishCliExit(2);
3636
+ return;
3487
3637
  }
3488
3638
  } catch (error: unknown) {
3489
3639
  const message = error instanceof Error ? error.message : "Unknown error occurred";
3490
3640
  console.error(`Fatal: ${message}`);
3491
- process.exit(2);
3641
+ await finishCliExit(2);
3492
3642
  }
3493
3643
  }
3494
3644