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
@@ -0,0 +1,198 @@
1
+ // @unit config-foundation
2
+ // @layer domain
3
+ // @work-item-id WI-300
4
+
5
+ import { ConfigValidationError } from "../errors/config-validation-error.js";
6
+
7
+ export interface WorldConfigDocument {
8
+ readonly enabled: boolean;
9
+ readonly corpus: {
10
+ readonly productRoots: readonly string[];
11
+ readonly inceptionRoots: readonly string[];
12
+ readonly adrRoots: readonly string[];
13
+ readonly sourceRoots: readonly string[];
14
+ readonly include: readonly string[];
15
+ readonly exclude: readonly string[];
16
+ };
17
+ readonly inputs: {
18
+ readonly matrixPath: string;
19
+ readonly attestationPath: string;
20
+ readonly integrityManifestPath: string;
21
+ };
22
+ readonly declarations: {
23
+ readonly constraintsPath: string;
24
+ readonly baselinePath: string;
25
+ readonly waiversPath: string;
26
+ readonly debtsPath: string;
27
+ };
28
+ readonly output: {
29
+ readonly obligationReportPath: string;
30
+ };
31
+ readonly sessionStart: {
32
+ readonly enabled: boolean;
33
+ readonly maxItems: number;
34
+ readonly maxChars: number;
35
+ };
36
+ }
37
+
38
+ export type WorldConfigSourceDocument = {
39
+ readonly [K in keyof WorldConfigDocument]?: WorldConfigDocument[K] extends readonly string[]
40
+ ? readonly string[]
41
+ : WorldConfigDocument[K] extends object
42
+ ? Partial<WorldConfigDocument[K]>
43
+ : WorldConfigDocument[K];
44
+ };
45
+
46
+ export const WORLD_CONFIG_DEFAULTS: WorldConfigDocument = Object.freeze({
47
+ enabled: false,
48
+ corpus: Object.freeze({
49
+ productRoots: Object.freeze(["docs/product"]),
50
+ inceptionRoots: Object.freeze(["docs/inception"]),
51
+ adrRoots: Object.freeze(["docs/ADR"]),
52
+ sourceRoots: Object.freeze(["scripts/harness"]),
53
+ include: Object.freeze(["**/*"]),
54
+ exclude: Object.freeze([]),
55
+ }),
56
+ inputs: Object.freeze({
57
+ matrixPath: ".harness/requirement-test-matrix.json",
58
+ attestationPath: ".harness/attestation.json",
59
+ integrityManifestPath: "phasegate.integrity.json",
60
+ }),
61
+ declarations: Object.freeze({
62
+ constraintsPath: "phasegate.world-constraints.json",
63
+ baselinePath: "phasegate.world-baseline.json",
64
+ waiversPath: "phasegate.world-waivers.json",
65
+ debtsPath: "phasegate.world-debts.json",
66
+ }),
67
+ output: Object.freeze({
68
+ obligationReportPath: ".harness/world-obligations.json",
69
+ }),
70
+ sessionStart: Object.freeze({ enabled: true, maxItems: 5, maxChars: 2000 }),
71
+ });
72
+
73
+ const cloneDocument = (document: WorldConfigDocument): WorldConfigDocument =>
74
+ Object.freeze({
75
+ enabled: document.enabled,
76
+ corpus: Object.freeze({
77
+ productRoots: Object.freeze([...document.corpus.productRoots]),
78
+ inceptionRoots: Object.freeze([...document.corpus.inceptionRoots]),
79
+ adrRoots: Object.freeze([...document.corpus.adrRoots]),
80
+ sourceRoots: Object.freeze([...document.corpus.sourceRoots]),
81
+ include: Object.freeze([...document.corpus.include]),
82
+ exclude: Object.freeze([...document.corpus.exclude]),
83
+ }),
84
+ inputs: Object.freeze({ ...document.inputs }),
85
+ declarations: Object.freeze({ ...document.declarations }),
86
+ output: Object.freeze({ ...document.output }),
87
+ sessionStart: Object.freeze({ ...document.sessionStart }),
88
+ });
89
+
90
+ const assertProjectRelativePosixPath = (value: string): void => {
91
+ const segments = value.split("/");
92
+ if (
93
+ value.length === 0 ||
94
+ value.startsWith("/") ||
95
+ value.includes("\\") ||
96
+ segments.some((segment) => segment === "" || segment === "." || segment === "..")
97
+ ) {
98
+ throw new ConfigValidationError(`world path must be a project-relative POSIX path: ${value}`);
99
+ }
100
+ };
101
+
102
+ const assertUniqueStrings = (name: string, values: readonly string[], allowEmpty: boolean): void => {
103
+ if (!allowEmpty && values.length === 0) {
104
+ throw new ConfigValidationError(`${name} must not be empty`);
105
+ }
106
+ if (values.some((value) => value.length === 0) || new Set(values).size !== values.length) {
107
+ throw new ConfigValidationError(`${name} must contain unique non-empty values`);
108
+ }
109
+ };
110
+
111
+ interface CorpusRootEntry {
112
+ readonly role: string;
113
+ readonly path: string;
114
+ }
115
+
116
+ const rootsOverlap = (left: string, right: string): boolean =>
117
+ left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
118
+
119
+ const assertCorpusRoots = (document: WorldConfigDocument): void => {
120
+ const entries: readonly CorpusRootEntry[] = [
121
+ ...document.corpus.productRoots.map((path) => ({ role: "product", path })),
122
+ ...document.corpus.inceptionRoots.map((path) => ({ role: "inception", path })),
123
+ ...document.corpus.adrRoots.map((path) => ({ role: "adr", path })),
124
+ ...document.corpus.sourceRoots.map((path) => ({ role: "source", path })),
125
+ ];
126
+
127
+ for (let leftIndex = 0; leftIndex < entries.length; leftIndex += 1) {
128
+ for (let rightIndex = leftIndex + 1; rightIndex < entries.length; rightIndex += 1) {
129
+ const left = entries[leftIndex];
130
+ const right = entries[rightIndex];
131
+ if (left.path.toLowerCase() === right.path.toLowerCase() && left.path !== right.path) {
132
+ throw new ConfigValidationError(`world path case-fold collision: ${left.path} / ${right.path}`);
133
+ }
134
+ if (left.role !== right.role && rootsOverlap(left.path, right.path)) {
135
+ throw new ConfigValidationError(`world corpus root role overlap: ${left.path} / ${right.path}`);
136
+ }
137
+ }
138
+ }
139
+ };
140
+
141
+ const allPaths = (document: WorldConfigDocument): readonly string[] => [
142
+ ...document.corpus.productRoots,
143
+ ...document.corpus.inceptionRoots,
144
+ ...document.corpus.adrRoots,
145
+ ...document.corpus.sourceRoots,
146
+ document.inputs.matrixPath,
147
+ document.inputs.attestationPath,
148
+ document.inputs.integrityManifestPath,
149
+ document.declarations.constraintsPath,
150
+ document.declarations.baselinePath,
151
+ document.declarations.waiversPath,
152
+ document.declarations.debtsPath,
153
+ document.output.obligationReportPath,
154
+ ];
155
+
156
+ const assertNoCaseFoldPathCollisions = (paths: readonly string[]): void => {
157
+ const byFoldedPath = new Map<string, string>();
158
+ for (const path of paths) {
159
+ const folded = path.toLowerCase();
160
+ const existing = byFoldedPath.get(folded);
161
+ if (existing !== undefined && existing !== path) {
162
+ throw new ConfigValidationError(`world path case-fold collision: ${existing} / ${path}`);
163
+ }
164
+ byFoldedPath.set(folded, path);
165
+ }
166
+ };
167
+
168
+ export class WorldConfig {
169
+ private constructor(private readonly document: WorldConfigDocument) {}
170
+
171
+ static create(document: WorldConfigDocument): WorldConfig {
172
+ assertUniqueStrings("world.corpus.productRoots", document.corpus.productRoots, false);
173
+ assertUniqueStrings("world.corpus.inceptionRoots", document.corpus.inceptionRoots, false);
174
+ assertUniqueStrings("world.corpus.adrRoots", document.corpus.adrRoots, false);
175
+ assertUniqueStrings("world.corpus.sourceRoots", document.corpus.sourceRoots, false);
176
+ assertUniqueStrings("world.corpus.include", document.corpus.include, false);
177
+ assertUniqueStrings("world.corpus.exclude", document.corpus.exclude, true);
178
+ const paths = allPaths(document);
179
+ for (const path of paths) assertProjectRelativePosixPath(path);
180
+ assertNoCaseFoldPathCollisions(paths);
181
+ assertCorpusRoots(document);
182
+ if (
183
+ !Number.isInteger(document.sessionStart.maxItems) ||
184
+ document.sessionStart.maxItems < 1 ||
185
+ document.sessionStart.maxItems > 20 ||
186
+ !Number.isInteger(document.sessionStart.maxChars) ||
187
+ document.sessionStart.maxChars < 1 ||
188
+ document.sessionStart.maxChars > 8000
189
+ ) {
190
+ throw new ConfigValidationError("world.sessionStart must use maxItems 1..20 and maxChars 1..8000");
191
+ }
192
+ return new WorldConfig(cloneDocument(document));
193
+ }
194
+
195
+ toDocument(): WorldConfigDocument {
196
+ return cloneDocument(this.document);
197
+ }
198
+ }
@@ -1,18 +1,17 @@
1
1
  // @unit config-foundation
2
2
  // @layer application
3
+ // @work-item-id WI-300
3
4
 
4
- export { createConfigFoundationModule } from './composition-root.js';
5
-
6
- export { LoadResolvedConfigUseCase } from './application/usecases/load-resolved-config-use-case.js';
7
- export { toValidatorSystemConfig } from './application/mappers/validator-system-config-mapper.js';
8
-
9
- export type { ResolvedConfigOutput } from './application/dto/resolved-config-output.js';
10
- export type { FeatureToggleResult } from './application/dto/feature-toggle-result.js';
11
- export type { AvailableFeatureItem } from './application/dto/available-feature-item.js';
12
-
13
- export { EnableFeatureCommandHandler } from './presentation/cli/enable-feature-command-handler.js';
14
- export { DisableFeatureCommandHandler } from './presentation/cli/disable-feature-command-handler.js';
15
-
16
- export type { ConfigRepositoryPort } from './domain/ports/config-repository-port.js';
17
- export type { ConfigSchemaValidatorPort } from './domain/ports/config-schema-validator-port.js';
18
- export type { FeatureRegistryPort } from './domain/ports/feature-registry-port.js';
5
+ export type { AvailableFeatureItem } from "./application/dto/available-feature-item.js";
6
+ export type { FeatureToggleResult } from "./application/dto/feature-toggle-result.js";
7
+ export type { ResolvedConfigOutput } from "./application/dto/resolved-config-output.js";
8
+ export { toValidatorSystemConfig } from "./application/mappers/validator-system-config-mapper.js";
9
+ export { toWorldModelConfig } from "./application/mappers/world-model-config-mapper.js";
10
+ export { LoadResolvedConfigUseCase } from "./application/usecases/load-resolved-config-use-case.js";
11
+ export { createConfigFoundationModule } from "./composition-root.js";
12
+ export type { ConfigRepositoryPort } from "./domain/ports/config-repository-port.js";
13
+ export type { ConfigSchemaValidatorPort } from "./domain/ports/config-schema-validator-port.js";
14
+ export type { FeatureRegistryPort } from "./domain/ports/feature-registry-port.js";
15
+ export type { WorldConfigDocument } from "./domain/value-objects/world-config.js";
16
+ export { DisableFeatureCommandHandler } from "./presentation/cli/disable-feature-command-handler.js";
17
+ export { EnableFeatureCommandHandler } from "./presentation/cli/enable-feature-command-handler.js";
@@ -55,5 +55,29 @@
55
55
  },
56
56
  "preCommit": {
57
57
  "implementationExtensions": [".ts"]
58
+ },
59
+ "world": {
60
+ "enabled": false,
61
+ "corpus": {
62
+ "productRoots": ["docs/product"],
63
+ "inceptionRoots": ["docs/inception"],
64
+ "adrRoots": ["docs/ADR"],
65
+ "sourceRoots": ["scripts/harness"],
66
+ "include": ["**/*"],
67
+ "exclude": []
68
+ },
69
+ "inputs": {
70
+ "matrixPath": ".harness/requirement-test-matrix.json",
71
+ "attestationPath": ".harness/attestation.json",
72
+ "integrityManifestPath": "phasegate.integrity.json"
73
+ },
74
+ "declarations": {
75
+ "constraintsPath": "phasegate.world-constraints.json",
76
+ "baselinePath": "phasegate.world-baseline.json",
77
+ "waiversPath": "phasegate.world-waivers.json",
78
+ "debtsPath": "phasegate.world-debts.json"
79
+ },
80
+ "output": { "obligationReportPath": ".harness/world-obligations.json" },
81
+ "sessionStart": { "enabled": true, "maxItems": 5, "maxChars": 2000 }
58
82
  }
59
83
  }
@@ -55,5 +55,29 @@
55
55
  },
56
56
  "preCommit": {
57
57
  "implementationExtensions": [".ts"]
58
+ },
59
+ "world": {
60
+ "enabled": false,
61
+ "corpus": {
62
+ "productRoots": ["docs/product"],
63
+ "inceptionRoots": ["docs/inception"],
64
+ "adrRoots": ["docs/ADR"],
65
+ "sourceRoots": ["scripts/harness"],
66
+ "include": ["**/*"],
67
+ "exclude": []
68
+ },
69
+ "inputs": {
70
+ "matrixPath": ".harness/requirement-test-matrix.json",
71
+ "attestationPath": ".harness/attestation.json",
72
+ "integrityManifestPath": "phasegate.integrity.json"
73
+ },
74
+ "declarations": {
75
+ "constraintsPath": "phasegate.world-constraints.json",
76
+ "baselinePath": "phasegate.world-baseline.json",
77
+ "waiversPath": "phasegate.world-waivers.json",
78
+ "debtsPath": "phasegate.world-debts.json"
79
+ },
80
+ "output": { "obligationReportPath": ".harness/world-obligations.json" },
81
+ "sessionStart": { "enabled": true, "maxItems": 5, "maxChars": 2000 }
58
82
  }
59
83
  }
@@ -55,5 +55,29 @@
55
55
  },
56
56
  "preCommit": {
57
57
  "implementationExtensions": [".ts"]
58
+ },
59
+ "world": {
60
+ "enabled": false,
61
+ "corpus": {
62
+ "productRoots": ["docs/product"],
63
+ "inceptionRoots": ["docs/inception"],
64
+ "adrRoots": ["docs/ADR"],
65
+ "sourceRoots": ["scripts/harness"],
66
+ "include": ["**/*"],
67
+ "exclude": []
68
+ },
69
+ "inputs": {
70
+ "matrixPath": ".harness/requirement-test-matrix.json",
71
+ "attestationPath": ".harness/attestation.json",
72
+ "integrityManifestPath": "phasegate.integrity.json"
73
+ },
74
+ "declarations": {
75
+ "constraintsPath": "phasegate.world-constraints.json",
76
+ "baselinePath": "phasegate.world-baseline.json",
77
+ "waiversPath": "phasegate.world-waivers.json",
78
+ "debtsPath": "phasegate.world-debts.json"
79
+ },
80
+ "output": { "obligationReportPath": ".harness/world-obligations.json" },
81
+ "sessionStart": { "enabled": true, "maxItems": 5, "maxChars": 2000 }
58
82
  }
59
83
  }
@@ -2,19 +2,19 @@
2
2
  * @layer infrastructure
3
3
  * @unit config-foundation
4
4
  */
5
- import * as fs from 'node:fs/promises';
6
- import path from 'node:path';
7
- import type { ConfigRepositoryPort } from '../../domain/ports/config-repository-port.js';
5
+ import * as fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import type { ConfigRepositoryPort } from "../../domain/ports/config-repository-port.js";
8
8
 
9
- const DEFAULT_CONFIG_FILE_NAME = 'phasegate.config.json';
10
- const PERSONAL_CONFIG_PATH = path.join('.phasegate-local', DEFAULT_CONFIG_FILE_NAME);
9
+ const DEFAULT_CONFIG_FILE_NAME = "phasegate.config.json";
10
+ const PERSONAL_CONFIG_PATH = path.join(".phasegate-local", DEFAULT_CONFIG_FILE_NAME);
11
11
 
12
12
  export class ConfigNotFoundError extends Error {
13
13
  readonly configPath: string;
14
14
 
15
15
  constructor(configPath: string) {
16
16
  super(`Config file not found: ${configPath}`);
17
- this.name = 'ConfigNotFoundError';
17
+ this.name = "ConfigNotFoundError";
18
18
  this.configPath = configPath;
19
19
  Object.setPrototypeOf(this, new.target.prototype);
20
20
  }
@@ -24,15 +24,28 @@ export class ConfigPersistenceError extends Error {
24
24
  readonly configPath: string;
25
25
  readonly cause?: unknown;
26
26
 
27
- constructor(configPath: string, cause?: unknown) {
28
- super(`Failed to persist config: ${configPath}`);
29
- this.name = 'ConfigPersistenceError';
27
+ constructor(configPath: string, cause?: unknown, message?: string) {
28
+ super(message ?? `Failed to persist config: ${configPath}`);
29
+ this.name = "ConfigPersistenceError";
30
30
  this.configPath = configPath;
31
31
  this.cause = cause;
32
32
  Object.setPrototypeOf(this, new.target.prototype);
33
33
  }
34
34
  }
35
35
 
36
+ /**
37
+ * WI-325: load 経路の JSON パース失敗専用エラー。
38
+ * ConfigPersistenceError のサブクラスなので、既存の
39
+ * `instanceof ConfigPersistenceError` fail-open 判定(main.ts / WI-314)は無変更で動く。
40
+ */
41
+ export class ConfigParseError extends ConfigPersistenceError {
42
+ constructor(configPath: string, cause?: unknown) {
43
+ super(configPath, cause, `Failed to parse config JSON: ${configPath}`);
44
+ this.name = "ConfigParseError";
45
+ Object.setPrototypeOf(this, new.target.prototype);
46
+ }
47
+ }
48
+
36
49
  async function exists(targetPath: string): Promise<boolean> {
37
50
  try {
38
51
  await fs.access(targetPath);
@@ -69,14 +82,10 @@ async function findNearestConfig(startDirectory: string): Promise<string | null>
69
82
 
70
83
  export class FileSystemConfigRepository implements ConfigRepositoryPort {
71
84
  async load(configPath?: string): Promise<{ path: string; document: unknown }> {
72
- const resolvedPath = configPath
73
- ? path.resolve(configPath)
74
- : await findNearestConfig(process.cwd());
85
+ const resolvedPath = configPath ? path.resolve(configPath) : await findNearestConfig(process.cwd());
75
86
 
76
87
  if (!resolvedPath) {
77
- throw new ConfigNotFoundError(
78
- path.resolve(process.cwd(), DEFAULT_CONFIG_FILE_NAME),
79
- );
88
+ throw new ConfigNotFoundError(path.resolve(process.cwd(), DEFAULT_CONFIG_FILE_NAME));
80
89
  }
81
90
 
82
91
  if (!(await exists(resolvedPath))) {
@@ -84,7 +93,7 @@ export class FileSystemConfigRepository implements ConfigRepositoryPort {
84
93
  }
85
94
 
86
95
  try {
87
- const raw = await fs.readFile(resolvedPath, 'utf8');
96
+ const raw = await fs.readFile(resolvedPath, "utf8");
88
97
 
89
98
  return {
90
99
  path: resolvedPath,
@@ -92,7 +101,7 @@ export class FileSystemConfigRepository implements ConfigRepositoryPort {
92
101
  };
93
102
  } catch (error) {
94
103
  if (error instanceof SyntaxError) {
95
- throw new ConfigPersistenceError(resolvedPath, error);
104
+ throw new ConfigParseError(resolvedPath, error);
96
105
  }
97
106
 
98
107
  throw error;
@@ -104,7 +113,7 @@ export class FileSystemConfigRepository implements ConfigRepositoryPort {
104
113
 
105
114
  try {
106
115
  const serialized = `${JSON.stringify(document, null, 2)}\n`;
107
- await fs.writeFile(resolvedPath, serialized, 'utf8');
116
+ await fs.writeFile(resolvedPath, serialized, "utf8");
108
117
  } catch (error) {
109
118
  throw new ConfigPersistenceError(resolvedPath, error);
110
119
  }
@@ -3,14 +3,7 @@
3
3
  "type": "object",
4
4
  "additionalProperties": false,
5
5
  "required": [
6
- "project",
7
- "layers",
8
- "quickMode",
9
- "phaseDependencies",
10
- "planningMode",
11
- "harnesses",
12
- "paths",
13
- "reporting"
6
+ "project"
14
7
  ],
15
8
  "properties": {
16
9
  "project": {
@@ -477,6 +470,89 @@
477
470
  }
478
471
  }
479
472
  },
473
+ "world": {
474
+ "type": "object",
475
+ "additionalProperties": false,
476
+ "properties": {
477
+ "enabled": { "type": "boolean" },
478
+ "corpus": {
479
+ "type": "object",
480
+ "additionalProperties": false,
481
+ "properties": {
482
+ "productRoots": {
483
+ "type": "array",
484
+ "minItems": 1,
485
+ "uniqueItems": true,
486
+ "items": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
487
+ },
488
+ "inceptionRoots": {
489
+ "type": "array",
490
+ "minItems": 1,
491
+ "uniqueItems": true,
492
+ "items": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
493
+ },
494
+ "adrRoots": {
495
+ "type": "array",
496
+ "minItems": 1,
497
+ "uniqueItems": true,
498
+ "items": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
499
+ },
500
+ "sourceRoots": {
501
+ "type": "array",
502
+ "minItems": 1,
503
+ "uniqueItems": true,
504
+ "items": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
505
+ },
506
+ "include": {
507
+ "type": "array",
508
+ "minItems": 1,
509
+ "uniqueItems": true,
510
+ "items": { "type": "string", "minLength": 1 }
511
+ },
512
+ "exclude": {
513
+ "type": "array",
514
+ "uniqueItems": true,
515
+ "items": { "type": "string", "minLength": 1 }
516
+ }
517
+ }
518
+ },
519
+ "inputs": {
520
+ "type": "object",
521
+ "additionalProperties": false,
522
+ "properties": {
523
+ "matrixPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
524
+ "attestationPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
525
+ "integrityManifestPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
526
+ }
527
+ },
528
+ "declarations": {
529
+ "type": "object",
530
+ "additionalProperties": false,
531
+ "properties": {
532
+ "constraintsPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
533
+ "baselinePath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
534
+ "waiversPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
535
+ "debtsPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
536
+ }
537
+ },
538
+ "output": {
539
+ "type": "object",
540
+ "additionalProperties": false,
541
+ "properties": {
542
+ "obligationReportPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
543
+ }
544
+ },
545
+ "sessionStart": {
546
+ "type": "object",
547
+ "additionalProperties": false,
548
+ "properties": {
549
+ "enabled": { "type": "boolean" },
550
+ "maxItems": { "type": "integer", "minimum": 1, "maximum": 20 },
551
+ "maxChars": { "type": "integer", "minimum": 1, "maximum": 8000 }
552
+ }
553
+ }
554
+ }
555
+ },
480
556
  "ci": {
481
557
  "type": "object",
482
558
  "additionalProperties": false,
@@ -3,14 +3,7 @@
3
3
  "type": "object",
4
4
  "additionalProperties": false,
5
5
  "required": [
6
- "project",
7
- "layers",
8
- "quickMode",
9
- "phaseDependencies",
10
- "planningMode",
11
- "harnesses",
12
- "paths",
13
- "reporting"
6
+ "project"
14
7
  ],
15
8
  "$defs": {
16
9
  "effectCapability": {
@@ -495,6 +488,89 @@
495
488
  }
496
489
  }
497
490
  },
491
+ "world": {
492
+ "type": "object",
493
+ "additionalProperties": false,
494
+ "properties": {
495
+ "enabled": { "type": "boolean" },
496
+ "corpus": {
497
+ "type": "object",
498
+ "additionalProperties": false,
499
+ "properties": {
500
+ "productRoots": {
501
+ "type": "array",
502
+ "minItems": 1,
503
+ "uniqueItems": true,
504
+ "items": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
505
+ },
506
+ "inceptionRoots": {
507
+ "type": "array",
508
+ "minItems": 1,
509
+ "uniqueItems": true,
510
+ "items": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
511
+ },
512
+ "adrRoots": {
513
+ "type": "array",
514
+ "minItems": 1,
515
+ "uniqueItems": true,
516
+ "items": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
517
+ },
518
+ "sourceRoots": {
519
+ "type": "array",
520
+ "minItems": 1,
521
+ "uniqueItems": true,
522
+ "items": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
523
+ },
524
+ "include": {
525
+ "type": "array",
526
+ "minItems": 1,
527
+ "uniqueItems": true,
528
+ "items": { "type": "string", "minLength": 1 }
529
+ },
530
+ "exclude": {
531
+ "type": "array",
532
+ "uniqueItems": true,
533
+ "items": { "type": "string", "minLength": 1 }
534
+ }
535
+ }
536
+ },
537
+ "inputs": {
538
+ "type": "object",
539
+ "additionalProperties": false,
540
+ "properties": {
541
+ "matrixPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
542
+ "attestationPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
543
+ "integrityManifestPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
544
+ }
545
+ },
546
+ "declarations": {
547
+ "type": "object",
548
+ "additionalProperties": false,
549
+ "properties": {
550
+ "constraintsPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
551
+ "baselinePath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
552
+ "waiversPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" },
553
+ "debtsPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
554
+ }
555
+ },
556
+ "output": {
557
+ "type": "object",
558
+ "additionalProperties": false,
559
+ "properties": {
560
+ "obligationReportPath": { "type": "string", "pattern": "^(?!/)(?!.*\\\\)(?!\\.\\.?(?:/|$))(?!.*\\/\\.\\.?(?:/|$)).+$" }
561
+ }
562
+ },
563
+ "sessionStart": {
564
+ "type": "object",
565
+ "additionalProperties": false,
566
+ "properties": {
567
+ "enabled": { "type": "boolean" },
568
+ "maxItems": { "type": "integer", "minimum": 1, "maximum": 20 },
569
+ "maxChars": { "type": "integer", "minimum": 1, "maximum": 8000 }
570
+ }
571
+ }
572
+ }
573
+ },
498
574
  "ci": {
499
575
  "type": "object",
500
576
  "additionalProperties": false,
@@ -1,10 +1,20 @@
1
1
  // @layer domain
2
2
  // config-query-port.ts
3
3
 
4
- import type { PresetInfo, ConfigSummary, PhaseGateSummary } from '../value-objects/harness-status-summary.js';
4
+ import type {
5
+ ConfigSummary,
6
+ LanguageInfo,
7
+ PhaseGateSummary,
8
+ PresetInfo,
9
+ } from "../value-objects/harness-status-summary.js";
5
10
 
6
11
  export interface ConfigQueryPort {
7
12
  getPresetInfo(): Promise<PresetInfo>;
8
13
  getConfigSummary(): Promise<ConfigSummary>;
9
14
  getPhaseGateSummary(): Promise<PhaseGateSummary>;
15
+ /**
16
+ * WI-328: 実効言語リストと出所(declared / detected / fallback)を返す。
17
+ * 後方互換のため optional — 未実装アダプタでは status に languages を出さない。
18
+ */
19
+ getLanguageInfo?(): Promise<LanguageInfo>;
10
20
  }