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
@@ -0,0 +1,60 @@
1
+ // @unit world-model
2
+ // @layer presentation
3
+ // @work-item-id WI-296
4
+
5
+ export type WorldOutputFormat = "human" | "json";
6
+ export interface WorldCommandResult {
7
+ readonly exitCode: 0 | 1 | 2;
8
+ readonly stdout: string;
9
+ readonly stderr: string;
10
+ }
11
+
12
+ export const parseFormat = (
13
+ args: readonly string[],
14
+ ):
15
+ | { readonly ok: true; readonly format: WorldOutputFormat; readonly rest: readonly string[] }
16
+ | {
17
+ readonly ok: false;
18
+ readonly format: WorldOutputFormat;
19
+ readonly message: string;
20
+ } => {
21
+ let format: WorldOutputFormat | undefined;
22
+ let json = false;
23
+ const rest: string[] = [];
24
+ for (let index = 0; index < args.length; index += 1) {
25
+ const argument = args[index];
26
+ if (argument === "--json") {
27
+ json = true;
28
+ } else if (argument === "--format") {
29
+ const value = args[index + 1];
30
+ if (value !== "human" && value !== "json") {
31
+ return { ok: false, format: json ? "json" : "human", message: "--format requires human or json" };
32
+ }
33
+ if (format !== undefined && format !== value) {
34
+ return { ok: false, format: json ? "json" : "human", message: "conflicting output format flags" };
35
+ }
36
+ format = value;
37
+ index += 1;
38
+ } else {
39
+ rest.push(argument);
40
+ }
41
+ }
42
+ if (json && format === "human") {
43
+ return { ok: false, format: "human", message: "conflicting output format flags" };
44
+ }
45
+ return { ok: true, format: json || format === "json" ? "json" : "human", rest };
46
+ };
47
+
48
+ export const envelope = (
49
+ command: "world:pin" | "world:derive",
50
+ exitCode: 0 | 1 | 2,
51
+ data: unknown,
52
+ diagnostics: readonly unknown[],
53
+ ) => ({
54
+ schemaVersion: "phasegate-world-cli/v1",
55
+ command,
56
+ ok: exitCode === 0,
57
+ exitCode,
58
+ data,
59
+ diagnostics,
60
+ });
@@ -0,0 +1,109 @@
1
+ // @unit world-model
2
+ // @layer presentation
3
+ // @work-item-id WI-296
4
+ // @work-item-id WI-300
5
+
6
+ import type { DeriveWorldObligationsUseCase } from "../../application/usecases/derive-world-obligations-use-case.js";
7
+ import { PathKey } from "../../domain/value-objects/path-key.js";
8
+ import { envelope, parseFormat, type WorldCommandResult, type WorldOutputFormat } from "./world-command-support.js";
9
+
10
+ export class WorldDeriveCommandHandler {
11
+ constructor(
12
+ private readonly useCase: Pick<DeriveWorldObligationsUseCase, "execute">,
13
+ private readonly defaultReportPath = ".harness/world-obligations.json",
14
+ ) {}
15
+
16
+ static fromFailure(error: unknown): WorldDeriveCommandHandler {
17
+ return new WorldDeriveCommandHandler({
18
+ execute: async () => {
19
+ throw error;
20
+ },
21
+ });
22
+ }
23
+
24
+ async execute(args: readonly string[]): Promise<WorldCommandResult> {
25
+ const formatted = parseFormat(args);
26
+ if (!formatted.ok) return this.failure(formatted.format, formatted.message);
27
+ let writeReport = false;
28
+ let reportPath: string | undefined;
29
+ for (let index = 0; index < formatted.rest.length; index += 1) {
30
+ const argument = formatted.rest[index];
31
+ if (argument === "--write") writeReport = true;
32
+ else if (argument === "--out") reportPath = formatted.rest[++index];
33
+ else return this.failure(formatted.format, `unknown argument: ${argument}`);
34
+ }
35
+ if (reportPath !== undefined && !writeReport) return this.failure(formatted.format, "--out requires --write");
36
+ if (reportPath !== undefined) {
37
+ try {
38
+ reportPath = PathKey.create(reportPath).toString();
39
+ } catch {
40
+ return this.failure(formatted.format, "--out must be a project-relative path");
41
+ }
42
+ }
43
+ try {
44
+ const result = await this.useCase.execute({ writeReport, reportPath });
45
+ if (result.status === "execution-failure") {
46
+ return this.executionFailure(formatted.format, result.diagnostics);
47
+ }
48
+ const report = result.result.report;
49
+ if (result.result.persistence.state === "failed") {
50
+ return this.executionFailure(formatted.format, [
51
+ { code: "report-write-failure", message: result.result.persistence.message },
52
+ ]);
53
+ }
54
+ const hasFinding =
55
+ report.structuralObligations.some(
56
+ (item) => item.classification === "new-structural" || item.classification === "invalid-declaration",
57
+ ) ||
58
+ report.repaidBaselineEntries.length > 0 ||
59
+ report.policyDiagnostics.length > 0;
60
+ const exitCode = hasFinding ? 1 : 0;
61
+ const data = {
62
+ report,
63
+ persistence: result.result.persistence,
64
+ writtenPath: writeReport ? (reportPath ?? this.defaultReportPath) : null,
65
+ };
66
+ if (formatted.format === "json") {
67
+ return {
68
+ exitCode,
69
+ stdout: `${JSON.stringify(envelope("world:derive", exitCode, data, report.policyDiagnostics))}\n`,
70
+ stderr: "",
71
+ };
72
+ }
73
+ return {
74
+ exitCode,
75
+ stdout: [
76
+ "World Obligations",
77
+ ` evaluationId: ${report.evaluationId}`,
78
+ ` structural: ${report.summary.structuralObligations}`,
79
+ ` repaid: ${report.summary.repaidBaselineEntries}`,
80
+ ` semantic debts: ${report.summary.declaredSemanticDebts}`,
81
+ ` policy diagnostics: ${report.summary.policyDiagnostics}`,
82
+ ` persistence: ${result.result.persistence.state}`,
83
+ ...(writeReport ? [` writtenPath: ${data.writtenPath}`] : []),
84
+ "",
85
+ ].join("\n"),
86
+ stderr: "",
87
+ };
88
+ } catch (error) {
89
+ return this.failure(formatted.format, error instanceof Error ? error.message : String(error), true);
90
+ }
91
+ }
92
+
93
+ private executionFailure(format: WorldOutputFormat, diagnostics: readonly unknown[]): WorldCommandResult {
94
+ return format === "json"
95
+ ? { exitCode: 2, stdout: `${JSON.stringify(envelope("world:derive", 2, null, diagnostics))}\n`, stderr: "" }
96
+ : { exitCode: 2, stdout: "", stderr: `world:derive failed: ${JSON.stringify(diagnostics)}\n` };
97
+ }
98
+
99
+ private failure(format: WorldOutputFormat, message: string, execution = false): WorldCommandResult {
100
+ const code = execution ? "world-derive-execution-failure" : "invalid-invocation";
101
+ return format === "json"
102
+ ? {
103
+ exitCode: 2,
104
+ stdout: `${JSON.stringify(envelope("world:derive", 2, null, [{ code, message }]))}\n`,
105
+ stderr: "",
106
+ }
107
+ : { exitCode: 2, stdout: "", stderr: `world:derive ${execution ? "failed" : "usage error"}: ${message}\n` };
108
+ }
109
+ }
@@ -0,0 +1,194 @@
1
+ // @unit world-model
2
+ // @layer presentation
3
+ // @work-item-id WI-291
4
+
5
+ import type {
6
+ WorldExtractionDiagnosticDto,
7
+ WorldInspectionDto,
8
+ WorldJsonObject,
9
+ } from "../../application/dto/world-inspection-dto.js";
10
+ import type { InspectWorldContract } from "../../application/usecases/inspect-world-use-case.js";
11
+
12
+ export interface WorldInspectCommandResult {
13
+ readonly exitCode: 0 | 1 | 2;
14
+ readonly stdout: string;
15
+ readonly stderr: string;
16
+ }
17
+
18
+ export interface WorldInspectCommandHandlerDeps {
19
+ readonly inspectWorld: InspectWorldContract;
20
+ }
21
+
22
+ type OutputFormat = "human" | "json";
23
+
24
+ interface ParsedFlags {
25
+ readonly ok: true;
26
+ readonly format: OutputFormat;
27
+ }
28
+
29
+ interface FlagFailure {
30
+ readonly ok: false;
31
+ readonly message: string;
32
+ readonly format: OutputFormat;
33
+ }
34
+
35
+ const executionDiagnostic = (message: string): WorldExtractionDiagnosticDto => ({
36
+ code: "world-inspect-execution-failure",
37
+ line: null,
38
+ nodeId: null,
39
+ pathKey: null,
40
+ payload: { message },
41
+ });
42
+
43
+ export class WorldInspectCommandHandler {
44
+ constructor(private readonly deps: WorldInspectCommandHandlerDeps) {}
45
+
46
+ static fromFailure(error: unknown): WorldInspectCommandHandler {
47
+ return new WorldInspectCommandHandler({
48
+ inspectWorld: {
49
+ execute: async () => {
50
+ throw error;
51
+ },
52
+ },
53
+ });
54
+ }
55
+
56
+ async execute(args: readonly string[]): Promise<WorldInspectCommandResult> {
57
+ const parsed = this.parseFlags(args);
58
+ if (!parsed.ok) return this.renderFailure(parsed.format, parsed.message, "invalid-invocation");
59
+ try {
60
+ const inspection = await this.deps.inspectWorld.execute();
61
+ const exitCode = inspection.summary.hardDiagnosticCount > 0 ? 1 : 0;
62
+ if (parsed.format === "json") {
63
+ return {
64
+ exitCode,
65
+ stdout: `${JSON.stringify(this.envelope(inspection, exitCode))}\n`,
66
+ stderr: "",
67
+ };
68
+ }
69
+ return {
70
+ exitCode,
71
+ stdout: this.renderHuman(inspection),
72
+ stderr: "",
73
+ };
74
+ } catch (error) {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ return this.renderFailure(parsed.format, message, "world-inspect-execution-failure");
77
+ }
78
+ }
79
+
80
+ private parseFlags(args: readonly string[]): ParsedFlags | FlagFailure {
81
+ let explicitFormat: OutputFormat | undefined;
82
+ let jsonAlias = false;
83
+ for (let index = 0; index < args.length; index += 1) {
84
+ const argument = args[index];
85
+ if (argument === "--json") {
86
+ jsonAlias = true;
87
+ continue;
88
+ }
89
+ if (argument === "--format") {
90
+ const value = args[index + 1];
91
+ if (value !== "human" && value !== "json") {
92
+ return {
93
+ ok: false,
94
+ message: "--format requires human or json",
95
+ format: jsonAlias ? "json" : "human",
96
+ };
97
+ }
98
+ if (explicitFormat !== undefined && explicitFormat !== value) {
99
+ return { ok: false, message: "conflicting output format flags", format: jsonAlias ? "json" : "human" };
100
+ }
101
+ explicitFormat = value;
102
+ index += 1;
103
+ continue;
104
+ }
105
+ return {
106
+ ok: false,
107
+ message: `unknown argument: ${argument}`,
108
+ format: jsonAlias || explicitFormat === "json" ? "json" : "human",
109
+ };
110
+ }
111
+ if (jsonAlias && explicitFormat === "human") {
112
+ return { ok: false, message: "conflicting output format flags", format: "human" };
113
+ }
114
+ return { ok: true, format: jsonAlias || explicitFormat === "json" ? "json" : "human" };
115
+ }
116
+
117
+ private envelope(
118
+ data: WorldInspectionDto | null,
119
+ exitCode: 0 | 1 | 2,
120
+ diagnostics?: readonly WorldExtractionDiagnosticDto[],
121
+ ): WorldJsonObject {
122
+ return {
123
+ schemaVersion: "phasegate-world-cli/v1",
124
+ command: "world:inspect",
125
+ ok: exitCode === 0,
126
+ exitCode,
127
+ data: data as unknown as WorldJsonObject | null,
128
+ diagnostics: diagnostics ?? data?.diagnostics ?? [],
129
+ };
130
+ }
131
+
132
+ private renderFailure(
133
+ format: OutputFormat,
134
+ message: string,
135
+ code: "invalid-invocation" | "world-inspect-execution-failure",
136
+ ): WorldInspectCommandResult {
137
+ if (format === "json") {
138
+ const diagnostic: WorldExtractionDiagnosticDto = {
139
+ ...executionDiagnostic(message),
140
+ code,
141
+ };
142
+ return {
143
+ exitCode: 2,
144
+ stdout: `${JSON.stringify(this.envelope(null, 2, [diagnostic]))}\n`,
145
+ stderr: "",
146
+ };
147
+ }
148
+ return {
149
+ exitCode: 2,
150
+ stdout: "",
151
+ stderr:
152
+ code === "invalid-invocation"
153
+ ? `world:inspect usage error: ${message}\n`
154
+ : `world:inspect failed: ${message}\n`,
155
+ };
156
+ }
157
+
158
+ private renderHuman(inspection: WorldInspectionDto): string {
159
+ const lines = [
160
+ "World Snapshot",
161
+ ` snapshotId: ${inspection.snapshotId}`,
162
+ ` corpusRoot: ${inspection.corpusRoot}`,
163
+ ` nodes: ${inspection.summary.nodeCount}`,
164
+ ` edges: ${inspection.summary.edgeCount}`,
165
+ ` diagnostics: ${inspection.summary.diagnosticCount} (hard: ${inspection.summary.hardDiagnosticCount})`,
166
+ "",
167
+ "Inventory",
168
+ ` nodeTypes: ${this.renderCounts(inspection.inventory.nodeTypes)}`,
169
+ ` corpusRoles: ${this.renderCounts(inspection.inventory.corpusRoles)}`,
170
+ ` artifactKinds: ${this.renderCounts(inspection.inventory.artifactKinds)}`,
171
+ "",
172
+ "Diagnostics",
173
+ ...(inspection.diagnostics.length === 0
174
+ ? [" none"]
175
+ : inspection.diagnostics.map(
176
+ (diagnostic) =>
177
+ ` ${diagnostic.code} ${diagnostic.nodeId ?? diagnostic.pathKey ?? "<global>"}${
178
+ diagnostic.line === null ? "" : `:${diagnostic.line}`
179
+ }`,
180
+ )),
181
+ "",
182
+ "Next action",
183
+ inspection.summary.hardDiagnosticCount > 0
184
+ ? " Resolve hard extraction diagnostics and rerun phasegate world:inspect."
185
+ : " Snapshot is inspectable; no hard extraction diagnostics were found.",
186
+ "",
187
+ ];
188
+ return lines.join("\n");
189
+ }
190
+
191
+ private renderCounts(values: readonly { value: string; count: number }[]): string {
192
+ return values.length === 0 ? "none" : values.map((entry) => `${entry.value}=${entry.count}`).join(", ");
193
+ }
194
+ }
@@ -0,0 +1,100 @@
1
+ // @unit world-model
2
+ // @layer presentation
3
+ // @work-item-id WI-296
4
+
5
+ import type {
6
+ PinConstraintEndpointResult,
7
+ PinConstraintEndpointUseCase,
8
+ PinEndpointRole,
9
+ } from "../../application/usecases/pin-constraint-endpoint-use-case.js";
10
+ import { envelope, parseFormat, type WorldCommandResult, type WorldOutputFormat } from "./world-command-support.js";
11
+
12
+ export class WorldPinCommandHandler {
13
+ constructor(private readonly useCase: Pick<PinConstraintEndpointUseCase, "execute">) {}
14
+
15
+ static fromFailure(error: unknown): WorldPinCommandHandler {
16
+ return new WorldPinCommandHandler({
17
+ execute: async () => {
18
+ throw error;
19
+ },
20
+ });
21
+ }
22
+
23
+ async execute(args: readonly string[]): Promise<WorldCommandResult> {
24
+ const formatted = parseFormat(args);
25
+ if (!formatted.ok) return this.failure(formatted.format, formatted.message);
26
+ let constraintId: string | undefined;
27
+ let endpoint: PinEndpointRole | undefined;
28
+ let apply = false;
29
+ for (let index = 0; index < formatted.rest.length; index += 1) {
30
+ const argument = formatted.rest[index];
31
+ if (argument === "--apply") apply = true;
32
+ else if (argument === "--constraint") constraintId = formatted.rest[++index];
33
+ else if (argument === "--endpoint") {
34
+ const value = formatted.rest[++index];
35
+ if (value !== "claimant" && value !== "premise")
36
+ return this.failure(formatted.format, "--endpoint requires claimant or premise");
37
+ endpoint = value;
38
+ } else return this.failure(formatted.format, `unknown argument: ${argument}`);
39
+ }
40
+ if (!constraintId || !/^pgw:v1:constraint:.+/.test(constraintId)) {
41
+ return this.failure(formatted.format, "--constraint requires a pgw:v1:constraint ID");
42
+ }
43
+ if (!endpoint) return this.failure(formatted.format, "--endpoint is required");
44
+ try {
45
+ const result = await this.useCase.execute({ constraintId, endpoint, apply });
46
+ return this.render(formatted.format, result);
47
+ } catch (error) {
48
+ return this.failure(formatted.format, error instanceof Error ? error.message : String(error), true);
49
+ }
50
+ }
51
+
52
+ private render(format: WorldOutputFormat, result: PinConstraintEndpointResult): WorldCommandResult {
53
+ const exitCode = result.status === "execution-failure" ? 2 : result.status === "domain-failure" ? 1 : 0;
54
+ const diagnostics =
55
+ result.status === "execution-failure"
56
+ ? result.diagnostics
57
+ : result.status === "domain-failure"
58
+ ? [{ code: result.code, message: result.message }]
59
+ : [];
60
+ if (format === "json") {
61
+ return {
62
+ exitCode,
63
+ stdout: `${JSON.stringify(envelope("world:pin", exitCode, result, diagnostics))}\n`,
64
+ stderr: "",
65
+ };
66
+ }
67
+ if (result.status === "execution-failure") {
68
+ return {
69
+ exitCode: 2,
70
+ stdout: "",
71
+ stderr: `world:pin failed: ${result.diagnostics[0]?.message ?? "execution failure"}\n`,
72
+ };
73
+ }
74
+ if (result.status === "domain-failure") {
75
+ return { exitCode: 1, stdout: `World Pin\n status: ${result.code}\n message: ${result.message}\n`, stderr: "" };
76
+ }
77
+ return {
78
+ exitCode: 0,
79
+ stdout: [
80
+ "World Pin",
81
+ ` status: ${result.status}`,
82
+ ` constraintId: ${result.candidate.constraintId}`,
83
+ ` endpoint: ${result.candidate.endpoint}`,
84
+ ` nodeId: ${result.candidate.nodeId}`,
85
+ ` before: ${result.candidate.beforeDigest}`,
86
+ ` after: ${result.candidate.afterDigest}`,
87
+ ` changed: ${result.candidate.changed}`,
88
+ "",
89
+ ].join("\n"),
90
+ stderr: "",
91
+ };
92
+ }
93
+
94
+ private failure(format: WorldOutputFormat, message: string, execution = false): WorldCommandResult {
95
+ const code = execution ? "world-pin-execution-failure" : "invalid-invocation";
96
+ return format === "json"
97
+ ? { exitCode: 2, stdout: `${JSON.stringify(envelope("world:pin", 2, null, [{ code, message }]))}\n`, stderr: "" }
98
+ : { exitCode: 2, stdout: "", stderr: `world:pin ${execution ? "failed" : "usage error"}: ${message}\n` };
99
+ }
100
+ }