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.
- package/CHANGELOG.md +60 -0
- package/README.ja.md +6 -3
- package/README.md +6 -3
- package/docs/contracts/attestation-v2.schema.json +110 -0
- package/docs/guide/cli-reference.md +12 -1
- package/docs/guide/configuration.md +56 -0
- package/docs/guide/installation.md +1 -1
- package/docs/templates/ci/aidlc-gate.yml +60 -2
- package/package.json +2 -2
- package/scripts/harness/agent-integration/application/dto/open-world-obligations-context-dto.ts +30 -0
- package/scripts/harness/agent-integration/application/ports/world-obligations-query-port.ts +30 -0
- package/scripts/harness/agent-integration/application/usecases/get-open-world-obligations-context-usecase.ts +66 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +30 -27
- package/scripts/harness/agent-integration/infrastructure/adapters/world-model-open-obligations-query-adapter.ts +58 -0
- package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +29 -16
- package/scripts/harness/agent-integration/presentation/session-start-hook.ts +37 -1
- package/scripts/harness/agent-integration/presentation/stop-hook.ts +35 -26
- package/scripts/harness/agent-integration/presentation/world-obligations-session-context.ts +60 -0
- package/scripts/harness/attestation/application/dto/attestation-document.ts +17 -4
- package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +55 -4
- package/scripts/harness/attestation/application/ports/world-snapshot-root-provider.ts +10 -0
- package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +33 -7
- package/scripts/harness/attestation/composition-root.ts +5 -0
- package/scripts/harness/attestation/domain/entities/attestation-record.ts +37 -2
- package/scripts/harness/attestation/index.ts +7 -1
- package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +5 -2
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +40 -9
- package/scripts/harness/config-foundation/application/mappers/world-model-config-mapper.ts +15 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +70 -87
- package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +80 -88
- package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +34 -18
- package/scripts/harness/config-foundation/domain/value-objects/world-config.ts +198 -0
- package/scripts/harness/config-foundation/index.ts +14 -15
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +24 -0
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +24 -0
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +24 -0
- package/scripts/harness/config-foundation/infrastructure/repositories/file-system-config-repository.ts +27 -18
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +84 -8
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +84 -8
- package/scripts/harness/harness-api/domain/ports/config-query-port.ts +11 -1
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +125 -86
- package/scripts/harness/harness-api/domain/services/status-derivation-service.ts +31 -21
- package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +18 -4
- package/scripts/harness/harness-api/domain/value-objects/harness-status-summary.ts +23 -8
- package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +4 -4
- package/scripts/harness/harness-api/infrastructure/adapters/harness-config-query-adapter.ts +79 -34
- package/scripts/harness/installation/application/usecases/run-install.ts +199 -51
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +277 -69
- package/scripts/harness/installation/domain/deployment-manifest.ts +43 -0
- package/scripts/harness/integrations/pre-commit.ts +169 -27
- package/scripts/harness/main.ts +200 -136
- package/scripts/harness/traceability-model/application/dto/changed-design-fragment-dto.ts +31 -0
- package/scripts/harness/traceability-model/application/facades/design-change-read-facade.ts +14 -0
- package/scripts/harness/traceability-model/application/ports/staged-design-change-source-port.ts +9 -0
- package/scripts/harness/traceability-model/composition-root.ts +5 -0
- package/scripts/harness/traceability-model/index.ts +9 -0
- package/scripts/harness/traceability-model/infrastructure/adapters/git-staged-design-change-adapter.ts +155 -0
- package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +31 -0
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +57 -6
- package/scripts/harness/validator-system/composition-root.ts +25 -7
- package/scripts/harness/validator-system/domain/ports/ac-coverage-policy-port.ts +10 -1
- package/scripts/harness/validator-system/domain/ports/world-constraint-admission-policy-port.ts +28 -0
- package/scripts/harness/validator-system/domain/ports/world-constraint-rederivation-policy-port.ts +11 -0
- package/scripts/harness/validator-system/domain/services/design-change-declaration-policy.ts +75 -0
- package/scripts/harness/validator-system/domain/services/world-constraint-admission-service.ts +78 -0
- package/scripts/harness/validator-system/domain/services/world-constraint-rederivation-service.ts +76 -0
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +4 -0
- package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +126 -18
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +49 -20
- package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-admission-adapter.ts +56 -0
- package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-rederivation-adapter.ts +56 -0
- package/scripts/harness/world-model/application/dto/pinned-design-endpoint-dto.ts +19 -0
- package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +92 -27
- package/scripts/harness/world-model/application/dto/world-snapshot-root-dto.ts +8 -0
- package/scripts/harness/world-model/application/facades/pinned-design-endpoint-facade.ts +63 -0
- package/scripts/harness/world-model/application/facades/world-snapshot-root-facade.ts +19 -0
- package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +3 -1
- package/scripts/harness/world-model/composition-root.ts +62 -33
- package/scripts/harness/world-model/index.ts +11 -0
- package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +48 -13
- package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +6 -4
- package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +6 -2
|
@@ -4,36 +4,37 @@
|
|
|
4
4
|
* @work-item-id WI-012
|
|
5
5
|
* @work-item-id WI-212
|
|
6
6
|
* @work-item-id WI-219
|
|
7
|
+
* @work-item-id WI-300
|
|
8
|
+
* @work-item-id WI-327
|
|
7
9
|
*/
|
|
8
|
-
import { ConfigFoundationDomainError } from
|
|
9
|
-
import { ConfigValidationError } from
|
|
10
|
-
import { UnsupportedFeatureError } from
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
import type {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export type
|
|
33
|
-
export type
|
|
34
|
-
export type PlanningModeValue = 'interactive' | 'embedded-qa' | 'manual';
|
|
35
|
-
export type ModelDelegationPolicy = 'delegate-sonnet' | 'none';
|
|
10
|
+
import { ConfigFoundationDomainError } from "./errors/config-foundation-domain-error.js";
|
|
11
|
+
import { ConfigValidationError } from "./errors/config-validation-error.js";
|
|
12
|
+
import { UnsupportedFeatureError } from "./errors/unsupported-feature-error.js";
|
|
13
|
+
import type { ArchitectureConfigDocument, ArchitectureConfigSource } from "./value-objects/architecture-config.js";
|
|
14
|
+
import type { FeatureName, FeatureNameValue } from "./value-objects/feature-name.js";
|
|
15
|
+
import { HarnessesConfig } from "./value-objects/harnesses-config.js";
|
|
16
|
+
import type { L1Config } from "./value-objects/l1-config.js";
|
|
17
|
+
import type { L2Config } from "./value-objects/l2-config.js";
|
|
18
|
+
import type { L3Config } from "./value-objects/l3-config.js";
|
|
19
|
+
import type { L4Config } from "./value-objects/l4-config.js";
|
|
20
|
+
import { LayersConfig } from "./value-objects/layers-config.js";
|
|
21
|
+
import { PathsConfig } from "./value-objects/paths-config.js";
|
|
22
|
+
import type { PhaseDependenciesPresetId } from "./value-objects/phase-dependencies-config.js";
|
|
23
|
+
import { PhaseDependenciesConfig } from "./value-objects/phase-dependencies-config.js";
|
|
24
|
+
import { PlanningModeConfig } from "./value-objects/planning-mode-config.js";
|
|
25
|
+
import { ProjectConfig } from "./value-objects/project-config.js";
|
|
26
|
+
import { QuickModeConfig } from "./value-objects/quick-mode-config.js";
|
|
27
|
+
import { ReportingConfig } from "./value-objects/reporting-config.js";
|
|
28
|
+
import { ValidateConfig } from "./value-objects/validate-config.js";
|
|
29
|
+
import type { WorldConfigDocument, WorldConfigSourceDocument } from "./value-objects/world-config.js";
|
|
30
|
+
import { WORLD_CONFIG_DEFAULTS, WorldConfig } from "./value-objects/world-config.js";
|
|
31
|
+
|
|
32
|
+
export type LayerId = "L1" | "L2" | "L3" | "L4";
|
|
33
|
+
export type PresetId = "minimal" | "standard" | "strict";
|
|
34
|
+
export type PlanningModeValue = "interactive" | "embedded-qa" | "manual";
|
|
35
|
+
export type ModelDelegationPolicy = "delegate-sonnet" | "none";
|
|
36
36
|
export type { PhaseDependenciesPresetId };
|
|
37
|
+
|
|
37
38
|
type DeepPartial<T> = {
|
|
38
39
|
[K in keyof T]?: T[K] extends Array<infer TItem>
|
|
39
40
|
? Array<DeepPartial<TItem>>
|
|
@@ -48,19 +49,22 @@ export interface HarnessConfigSourceDocument {
|
|
|
48
49
|
preset: PresetId;
|
|
49
50
|
languages?: string[];
|
|
50
51
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
// WI-327: project 以外の top-level セクションは手書き config で省略可。
|
|
53
|
+
// 省略時は PresetResolutionService が防御プリセット(project.preset)のデフォルト値で補完する。
|
|
54
|
+
layers?: DeepPartial<HarnessConfigResolvedDocument["layers"]>;
|
|
55
|
+
quickMode?: Partial<HarnessConfigResolvedDocument["quickMode"]>;
|
|
56
|
+
phaseDependencies?: HarnessConfigResolvedDocument["phaseDependencies"];
|
|
57
|
+
planningMode?: HarnessConfigResolvedDocument["planningMode"];
|
|
58
|
+
harnesses?: Partial<HarnessConfigResolvedDocument["harnesses"]>;
|
|
59
|
+
paths?: HarnessConfigResolvedDocument["paths"];
|
|
60
|
+
reporting?: HarnessConfigResolvedDocument["reporting"];
|
|
61
|
+
ci?: HarnessConfigResolvedDocument["ci"];
|
|
62
|
+
validate?: HarnessConfigResolvedDocument["validate"];
|
|
63
|
+
preCommit?: Partial<HarnessConfigResolvedDocument["preCommit"]>;
|
|
64
|
+
modelRouting?: HarnessConfigResolvedDocument["modelRouting"];
|
|
65
|
+
phase2Extensions?: HarnessConfigResolvedDocument["phase2Extensions"];
|
|
63
66
|
architecture?: ArchitectureConfigSource;
|
|
67
|
+
world?: WorldConfigSourceDocument;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
export interface HarnessConfigResolvedDocument {
|
|
@@ -142,11 +146,12 @@ export interface HarnessConfigResolvedDocument {
|
|
|
142
146
|
documentPattern: string;
|
|
143
147
|
daysThreshold: number;
|
|
144
148
|
commitCountThreshold: number;
|
|
145
|
-
evaluationMode:
|
|
149
|
+
evaluationMode: "or" | "and";
|
|
146
150
|
enabled?: boolean;
|
|
147
151
|
}>;
|
|
148
152
|
};
|
|
149
153
|
architecture?: ArchitectureConfigDocument;
|
|
154
|
+
world?: WorldConfigDocument;
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
export type HarnessConfigV2 = HarnessConfigResolvedDocument;
|
|
@@ -159,7 +164,7 @@ export interface HarnessConfigReconstitutionProps {
|
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
export interface FeatureToggled {
|
|
162
|
-
type:
|
|
167
|
+
type: "FeatureToggled";
|
|
163
168
|
occurredAt: Date;
|
|
164
169
|
projectName: string;
|
|
165
170
|
featureName: FeatureNameValue | string;
|
|
@@ -168,7 +173,7 @@ export interface FeatureToggled {
|
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
export interface PresetApplied {
|
|
171
|
-
type:
|
|
176
|
+
type: "PresetApplied";
|
|
172
177
|
occurredAt: Date;
|
|
173
178
|
projectName: string;
|
|
174
179
|
preset: PresetId;
|
|
@@ -178,23 +183,23 @@ export interface PresetApplied {
|
|
|
178
183
|
export type DomainEvent = FeatureToggled | PresetApplied;
|
|
179
184
|
|
|
180
185
|
const SUPPORTED_FEATURE_NAMES = [
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
"agentLessonCollection",
|
|
187
|
+
"cascadeUpdate",
|
|
188
|
+
"bundleSizeLimit",
|
|
189
|
+
"deadCodeGC",
|
|
185
190
|
] as const satisfies readonly FeatureNameValue[];
|
|
186
191
|
|
|
187
192
|
export class UnknownLayerError extends ConfigFoundationDomainError {
|
|
188
193
|
constructor(layerId: string) {
|
|
189
|
-
super(`未知のレイヤーです: ${layerId} [L1-006]`,
|
|
190
|
-
this.name =
|
|
194
|
+
super(`未知のレイヤーです: ${layerId} [L1-006]`, "L1-006");
|
|
195
|
+
this.name = "UnknownLayerError";
|
|
191
196
|
}
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
export class FeatureActivationRuleError extends ConfigFoundationDomainError {
|
|
195
200
|
constructor(message: string) {
|
|
196
|
-
super(`${message} [L1-005]`,
|
|
197
|
-
this.name =
|
|
201
|
+
super(`${message} [L1-005]`, "L1-005");
|
|
202
|
+
this.name = "FeatureActivationRuleError";
|
|
198
203
|
}
|
|
199
204
|
}
|
|
200
205
|
|
|
@@ -207,11 +212,8 @@ function deepClone<T>(value: T): T {
|
|
|
207
212
|
return value.map((item) => deepClone(item)) as T;
|
|
208
213
|
}
|
|
209
214
|
|
|
210
|
-
if (value !== null && typeof value ===
|
|
211
|
-
const clonedEntries = Object.entries(value).map(([key, entryValue]) => [
|
|
212
|
-
key,
|
|
213
|
-
deepClone(entryValue),
|
|
214
|
-
]);
|
|
215
|
+
if (value !== null && typeof value === "object") {
|
|
216
|
+
const clonedEntries = Object.entries(value).map(([key, entryValue]) => [key, deepClone(entryValue)]);
|
|
215
217
|
|
|
216
218
|
return Object.fromEntries(clonedEntries) as T;
|
|
217
219
|
}
|
|
@@ -226,7 +228,7 @@ function createFeatureToggledEvent(
|
|
|
226
228
|
currentState: boolean,
|
|
227
229
|
): FeatureToggled {
|
|
228
230
|
return Object.freeze({
|
|
229
|
-
type:
|
|
231
|
+
type: "FeatureToggled",
|
|
230
232
|
occurredAt: new Date(),
|
|
231
233
|
projectName,
|
|
232
234
|
featureName,
|
|
@@ -240,14 +242,14 @@ function assertSupportedFeature(name: FeatureName): FeatureNameValue {
|
|
|
240
242
|
|
|
241
243
|
if (!SUPPORTED_FEATURE_NAMES.includes(raw as FeatureNameValue)) {
|
|
242
244
|
throw new UnsupportedFeatureError(
|
|
243
|
-
`機能名 "${raw}" は利用できません。利用可能: ${SUPPORTED_FEATURE_NAMES.join(
|
|
245
|
+
`機能名 "${raw}" は利用できません。利用可能: ${SUPPORTED_FEATURE_NAMES.join(", ")}`,
|
|
244
246
|
);
|
|
245
247
|
}
|
|
246
248
|
|
|
247
249
|
return raw as FeatureNameValue;
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
function toHarnessesDocument(harnesses: HarnessesConfig): HarnessConfigResolvedDocument[
|
|
252
|
+
function toHarnessesDocument(harnesses: HarnessesConfig): HarnessConfigResolvedDocument["harnesses"] {
|
|
251
253
|
return {
|
|
252
254
|
agentLessonCollection: harnesses.agentLessonCollection,
|
|
253
255
|
cascadeUpdate: harnesses.cascadeUpdate,
|
|
@@ -303,22 +305,17 @@ export class HarnessConfig {
|
|
|
303
305
|
const project = ProjectConfig.create(props.resolvedDocument.project);
|
|
304
306
|
const layers = LayersConfig.create(props.resolvedDocument.layers);
|
|
305
307
|
const quickMode = QuickModeConfig.create(props.resolvedDocument.quickMode);
|
|
306
|
-
const phaseDependencies = PhaseDependenciesConfig.create(
|
|
307
|
-
|
|
308
|
-
);
|
|
309
|
-
const planningMode = PlanningModeConfig.create(
|
|
310
|
-
props.resolvedDocument.planningMode,
|
|
311
|
-
);
|
|
308
|
+
const phaseDependencies = PhaseDependenciesConfig.create(props.resolvedDocument.phaseDependencies);
|
|
309
|
+
const planningMode = PlanningModeConfig.create(props.resolvedDocument.planningMode);
|
|
312
310
|
const harnesses = HarnessesConfig.create(props.resolvedDocument.harnesses);
|
|
313
311
|
const paths = PathsConfig.create(props.resolvedDocument.paths);
|
|
314
312
|
const reporting = ReportingConfig.create(props.resolvedDocument.reporting);
|
|
315
313
|
const validate = ValidateConfig.create(props.resolvedDocument.validate);
|
|
314
|
+
const world = WorldConfig.create(props.resolvedDocument.world ?? WORLD_CONFIG_DEFAULTS).toDocument();
|
|
316
315
|
|
|
317
|
-
if (
|
|
318
|
-
props.sourceDocument.project.preset !== props.resolvedDocument.project.preset
|
|
319
|
-
) {
|
|
316
|
+
if (props.sourceDocument.project.preset !== props.resolvedDocument.project.preset) {
|
|
320
317
|
throw new ConfigValidationError(
|
|
321
|
-
|
|
318
|
+
"sourceDocument.project.preset と resolvedDocument.project.preset は一致しなければなりません",
|
|
322
319
|
);
|
|
323
320
|
}
|
|
324
321
|
|
|
@@ -333,7 +330,7 @@ export class HarnessConfig {
|
|
|
333
330
|
reporting,
|
|
334
331
|
validate,
|
|
335
332
|
sourceDocument: props.sourceDocument,
|
|
336
|
-
resolvedDocument: props.resolvedDocument,
|
|
333
|
+
resolvedDocument: { ...props.resolvedDocument, world },
|
|
337
334
|
pendingEvents: props.pendingEvents ?? [],
|
|
338
335
|
});
|
|
339
336
|
}
|
|
@@ -355,14 +352,7 @@ export class HarnessConfig {
|
|
|
355
352
|
...this.resolvedDocument,
|
|
356
353
|
harnesses: toHarnessesDocument(nextHarnesses),
|
|
357
354
|
};
|
|
358
|
-
this.pendingEvents.push(
|
|
359
|
-
createFeatureToggledEvent(
|
|
360
|
-
this.project.name,
|
|
361
|
-
featureName,
|
|
362
|
-
previousState,
|
|
363
|
-
true,
|
|
364
|
-
),
|
|
365
|
-
);
|
|
355
|
+
this.pendingEvents.push(createFeatureToggledEvent(this.project.name, featureName, previousState, true));
|
|
366
356
|
}
|
|
367
357
|
|
|
368
358
|
disableFeature(name: FeatureName): void {
|
|
@@ -382,18 +372,11 @@ export class HarnessConfig {
|
|
|
382
372
|
...this.resolvedDocument,
|
|
383
373
|
harnesses: toHarnessesDocument(nextHarnesses),
|
|
384
374
|
};
|
|
385
|
-
this.pendingEvents.push(
|
|
386
|
-
createFeatureToggledEvent(
|
|
387
|
-
this.project.name,
|
|
388
|
-
featureName,
|
|
389
|
-
previousState,
|
|
390
|
-
false,
|
|
391
|
-
),
|
|
392
|
-
);
|
|
375
|
+
this.pendingEvents.push(createFeatureToggledEvent(this.project.name, featureName, previousState, false));
|
|
393
376
|
}
|
|
394
377
|
|
|
395
378
|
getLayerConfig(layerId: LayerId): L1Config | L2Config | L3Config | L4Config {
|
|
396
|
-
if (![
|
|
379
|
+
if (!["L1", "L2", "L3", "L4"].includes(layerId)) {
|
|
397
380
|
throw new UnknownLayerError(layerId);
|
|
398
381
|
}
|
|
399
382
|
|
|
@@ -2,43 +2,44 @@
|
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit config-foundation
|
|
4
4
|
* @work-item-id WI-212
|
|
5
|
+
* @work-item-id WI-300
|
|
6
|
+
* @work-item-id WI-320
|
|
5
7
|
*/
|
|
6
|
-
import { ConfigFoundationDomainError } from
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from
|
|
11
|
-
import { HarnessesConfig } from '../value-objects/harnesses-config.js';
|
|
12
|
-
import type { FeatureToggle } from '../value-objects/feature-toggle.js';
|
|
8
|
+
import { ConfigFoundationDomainError } from "../errors/config-foundation-domain-error.js";
|
|
9
|
+
import type { HarnessConfigResolvedDocument, HarnessConfigSourceDocument } from "../harness-config.js";
|
|
10
|
+
import type { FeatureToggle } from "../value-objects/feature-toggle.js";
|
|
11
|
+
import { HarnessesConfig } from "../value-objects/harnesses-config.js";
|
|
12
|
+
import { WORLD_CONFIG_DEFAULTS, WorldConfig, type WorldConfigDocument } from "../value-objects/world-config.js";
|
|
13
13
|
|
|
14
14
|
export interface PresetDefinition {
|
|
15
|
-
layers: HarnessConfigResolvedDocument[
|
|
16
|
-
quickMode: HarnessConfigResolvedDocument[
|
|
17
|
-
phaseDependencies: HarnessConfigResolvedDocument[
|
|
18
|
-
planningMode: HarnessConfigResolvedDocument[
|
|
19
|
-
harnesses: HarnessConfigResolvedDocument[
|
|
20
|
-
paths: HarnessConfigResolvedDocument[
|
|
21
|
-
reporting: HarnessConfigResolvedDocument[
|
|
22
|
-
validate: HarnessConfigResolvedDocument[
|
|
23
|
-
preCommit?: HarnessConfigResolvedDocument[
|
|
15
|
+
layers: HarnessConfigResolvedDocument["layers"];
|
|
16
|
+
quickMode: HarnessConfigResolvedDocument["quickMode"];
|
|
17
|
+
phaseDependencies: HarnessConfigResolvedDocument["phaseDependencies"];
|
|
18
|
+
planningMode: HarnessConfigResolvedDocument["planningMode"];
|
|
19
|
+
harnesses: HarnessConfigResolvedDocument["harnesses"];
|
|
20
|
+
paths: HarnessConfigResolvedDocument["paths"];
|
|
21
|
+
reporting: HarnessConfigResolvedDocument["reporting"];
|
|
22
|
+
validate: HarnessConfigResolvedDocument["validate"];
|
|
23
|
+
preCommit?: HarnessConfigResolvedDocument["preCommit"];
|
|
24
|
+
world?: WorldConfigDocument;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export class InvalidPresetDefinitionError extends ConfigFoundationDomainError {
|
|
27
28
|
constructor(message: string) {
|
|
28
|
-
super(`${message} [L1-007]`,
|
|
29
|
-
this.name =
|
|
29
|
+
super(`${message} [L1-007]`, "L1-007");
|
|
30
|
+
this.name = "InvalidPresetDefinitionError";
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export class ConfigMergeError extends ConfigFoundationDomainError {
|
|
34
35
|
constructor(message: string) {
|
|
35
|
-
super(`${message} [L1-008]`,
|
|
36
|
-
this.name =
|
|
36
|
+
super(`${message} [L1-008]`, "L1-008");
|
|
37
|
+
this.name = "ConfigMergeError";
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
41
|
-
return value !== null && typeof value ===
|
|
42
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
function deepClone<T>(value: T): T {
|
|
@@ -47,9 +48,7 @@ function deepClone<T>(value: T): T {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
if (isPlainObject(value)) {
|
|
50
|
-
return Object.fromEntries(
|
|
51
|
-
Object.entries(value).map(([key, entryValue]) => [key, deepClone(entryValue)]),
|
|
52
|
-
) as T;
|
|
51
|
+
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, deepClone(entryValue)])) as T;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
return value;
|
|
@@ -74,10 +73,7 @@ function deepMerge<T>(baseValue: T, overrideValue: unknown, path: string): T {
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
const merged: Record<string, unknown> = {};
|
|
77
|
-
const keys = new Set([
|
|
78
|
-
...Object.keys(baseValue),
|
|
79
|
-
...Object.keys(overrideValue),
|
|
80
|
-
]);
|
|
76
|
+
const keys = new Set([...Object.keys(baseValue), ...Object.keys(overrideValue)]);
|
|
81
77
|
|
|
82
78
|
for (const key of keys) {
|
|
83
79
|
const nestedBaseValue = (baseValue as Record<string, unknown>)[key];
|
|
@@ -88,11 +84,7 @@ function deepMerge<T>(baseValue: T, overrideValue: unknown, path: string): T {
|
|
|
88
84
|
continue;
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
merged[key] = deepMerge(
|
|
92
|
-
nestedBaseValue,
|
|
93
|
-
nestedOverrideValue,
|
|
94
|
-
`${path}.${key}`,
|
|
95
|
-
);
|
|
87
|
+
merged[key] = deepMerge(nestedBaseValue, nestedOverrideValue, `${path}.${key}`);
|
|
96
88
|
}
|
|
97
89
|
|
|
98
90
|
return merged as T;
|
|
@@ -105,35 +97,26 @@ function deepMerge<T>(baseValue: T, overrideValue: unknown, path: string): T {
|
|
|
105
97
|
return deepClone(overrideValue as T);
|
|
106
98
|
}
|
|
107
99
|
|
|
108
|
-
function assertValidPresetDefinition(
|
|
109
|
-
presetDefinition: PresetDefinition,
|
|
110
|
-
): void {
|
|
100
|
+
function assertValidPresetDefinition(presetDefinition: PresetDefinition): void {
|
|
111
101
|
const requiredKeys: Array<keyof PresetDefinition> = [
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
102
|
+
"layers",
|
|
103
|
+
"quickMode",
|
|
104
|
+
"phaseDependencies",
|
|
105
|
+
"planningMode",
|
|
106
|
+
"harnesses",
|
|
107
|
+
"paths",
|
|
108
|
+
"reporting",
|
|
109
|
+
"validate",
|
|
120
110
|
];
|
|
121
111
|
|
|
122
112
|
for (const key of requiredKeys) {
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
(presetDefinition as unknown as Record<string, unknown>)[key] === undefined
|
|
126
|
-
) {
|
|
127
|
-
throw new InvalidPresetDefinitionError(
|
|
128
|
-
`presetDefinition.${key} は必須です`,
|
|
129
|
-
);
|
|
113
|
+
if (!(key in presetDefinition) || (presetDefinition as unknown as Record<string, unknown>)[key] === undefined) {
|
|
114
|
+
throw new InvalidPresetDefinitionError(`presetDefinition.${key} は必須です`);
|
|
130
115
|
}
|
|
131
116
|
}
|
|
132
117
|
}
|
|
133
118
|
|
|
134
|
-
function toHarnessesDocument(
|
|
135
|
-
harnesses: HarnessesConfig,
|
|
136
|
-
): HarnessConfigResolvedDocument['harnesses'] {
|
|
119
|
+
function toHarnessesDocument(harnesses: HarnessesConfig): HarnessConfigResolvedDocument["harnesses"] {
|
|
137
120
|
return {
|
|
138
121
|
agentLessonCollection: harnesses.agentLessonCollection,
|
|
139
122
|
cascadeUpdate: harnesses.cascadeUpdate,
|
|
@@ -149,49 +132,60 @@ export class PresetResolutionService {
|
|
|
149
132
|
): HarnessConfigResolvedDocument {
|
|
150
133
|
assertValidPresetDefinition(presetDefinition);
|
|
151
134
|
|
|
135
|
+
const layers = deepMerge(presetDefinition.layers, sourceDocument.layers, "layers");
|
|
136
|
+
const paths = deepMerge(presetDefinition.paths, sourceDocument.paths, "paths");
|
|
137
|
+
const world = deepMerge(presetDefinition.world ?? WORLD_CONFIG_DEFAULTS, sourceDocument.world, "world");
|
|
138
|
+
const explicitCorpus = sourceDocument.world?.corpus;
|
|
139
|
+
const explicitInputs = sourceDocument.world?.inputs;
|
|
140
|
+
const derivedProductRoot = paths.designDocs.endsWith("/construction")
|
|
141
|
+
? paths.designDocs.slice(0, -"/construction".length)
|
|
142
|
+
: paths.designDocs;
|
|
143
|
+
const resolvedWorld: WorldConfigDocument = {
|
|
144
|
+
...world,
|
|
145
|
+
corpus: {
|
|
146
|
+
...world.corpus,
|
|
147
|
+
productRoots:
|
|
148
|
+
explicitCorpus?.productRoots === undefined && !world.corpus.productRoots.includes(derivedProductRoot)
|
|
149
|
+
? [...world.corpus.productRoots, derivedProductRoot]
|
|
150
|
+
: world.corpus.productRoots,
|
|
151
|
+
inceptionRoots:
|
|
152
|
+
explicitCorpus?.inceptionRoots === undefined ? [paths.inceptionDocs] : world.corpus.inceptionRoots,
|
|
153
|
+
},
|
|
154
|
+
inputs: {
|
|
155
|
+
...world.inputs,
|
|
156
|
+
matrixPath:
|
|
157
|
+
explicitInputs?.matrixPath === undefined
|
|
158
|
+
? (layers.L3.requirementMatrixPath ?? world.inputs.matrixPath)
|
|
159
|
+
: world.inputs.matrixPath,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
152
163
|
return {
|
|
153
164
|
project: {
|
|
154
165
|
name: sourceDocument.project.name,
|
|
155
166
|
preset: sourceDocument.project.preset,
|
|
156
|
-
|
|
167
|
+
// WI-320 (github#39): 未宣言時に ["typescript"] を注入しない。「未宣言」を resolved config まで
|
|
168
|
+
// 生存させ、validator-system adapter のファイルシステム言語検出(WI-319)を実 CLI 経路で有効にする。
|
|
169
|
+
...(sourceDocument.project.languages === undefined ? {} : { languages: sourceDocument.project.languages }),
|
|
157
170
|
},
|
|
158
|
-
layers
|
|
159
|
-
quickMode: deepMerge(
|
|
160
|
-
presetDefinition.quickMode,
|
|
161
|
-
sourceDocument.quickMode,
|
|
162
|
-
'quickMode',
|
|
163
|
-
),
|
|
171
|
+
layers,
|
|
172
|
+
quickMode: deepMerge(presetDefinition.quickMode, sourceDocument.quickMode, "quickMode"),
|
|
164
173
|
phaseDependencies: deepMerge(
|
|
165
174
|
presetDefinition.phaseDependencies,
|
|
166
175
|
sourceDocument.phaseDependencies,
|
|
167
|
-
|
|
168
|
-
),
|
|
169
|
-
planningMode: deepMerge(
|
|
170
|
-
presetDefinition.planningMode,
|
|
171
|
-
sourceDocument.planningMode,
|
|
172
|
-
'planningMode',
|
|
173
|
-
),
|
|
174
|
-
harnesses: deepMerge(
|
|
175
|
-
presetDefinition.harnesses,
|
|
176
|
-
sourceDocument.harnesses,
|
|
177
|
-
'harnesses',
|
|
178
|
-
),
|
|
179
|
-
paths: deepMerge(presetDefinition.paths, sourceDocument.paths, 'paths'),
|
|
180
|
-
reporting: deepMerge(
|
|
181
|
-
presetDefinition.reporting,
|
|
182
|
-
sourceDocument.reporting,
|
|
183
|
-
'reporting',
|
|
184
|
-
),
|
|
185
|
-
validate: deepMerge(
|
|
186
|
-
presetDefinition.validate,
|
|
187
|
-
sourceDocument.validate,
|
|
188
|
-
'validate',
|
|
176
|
+
"phaseDependencies",
|
|
189
177
|
),
|
|
178
|
+
planningMode: deepMerge(presetDefinition.planningMode, sourceDocument.planningMode, "planningMode"),
|
|
179
|
+
harnesses: deepMerge(presetDefinition.harnesses, sourceDocument.harnesses, "harnesses"),
|
|
180
|
+
paths,
|
|
181
|
+
reporting: deepMerge(presetDefinition.reporting, sourceDocument.reporting, "reporting"),
|
|
182
|
+
validate: deepMerge(presetDefinition.validate, sourceDocument.validate, "validate"),
|
|
190
183
|
preCommit: deepMerge(
|
|
191
|
-
presetDefinition.preCommit ?? { implementationExtensions: [
|
|
184
|
+
presetDefinition.preCommit ?? { implementationExtensions: [".ts"] },
|
|
192
185
|
sourceDocument.preCommit,
|
|
193
|
-
|
|
186
|
+
"preCommit",
|
|
194
187
|
),
|
|
188
|
+
world: WorldConfig.create(resolvedWorld).toDocument(),
|
|
195
189
|
};
|
|
196
190
|
}
|
|
197
191
|
|
|
@@ -200,9 +194,7 @@ export class PresetResolutionService {
|
|
|
200
194
|
override: FeatureToggle,
|
|
201
195
|
): HarnessConfigResolvedDocument {
|
|
202
196
|
const harnesses = HarnessesConfig.create(document.harnesses);
|
|
203
|
-
const nextHarnesses = override.enabled
|
|
204
|
-
? harnesses.enable(override.name)
|
|
205
|
-
: harnesses.disable(override.name);
|
|
197
|
+
const nextHarnesses = override.enabled ? harnesses.enable(override.name) : harnesses.disable(override.name);
|
|
206
198
|
|
|
207
199
|
return {
|
|
208
200
|
...deepClone(document),
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit config-foundation
|
|
4
4
|
* @work-item-id WI-212
|
|
5
|
+
* @work-item-id WI-320
|
|
5
6
|
*
|
|
6
7
|
* ProjectConfig値オブジェクト - プロジェクト名とPresetを保持する
|
|
7
8
|
*/
|
|
8
|
-
import { ConfigValidationError } from
|
|
9
|
-
import { Preset } from
|
|
9
|
+
import { ConfigValidationError } from "../errors/config-validation-error.js";
|
|
10
|
+
import { Preset } from "./preset.js";
|
|
10
11
|
|
|
11
12
|
interface ProjectConfigProps {
|
|
12
13
|
readonly name: string;
|
|
@@ -17,29 +18,37 @@ interface ProjectConfigProps {
|
|
|
17
18
|
export class ProjectConfig {
|
|
18
19
|
readonly name: string;
|
|
19
20
|
readonly preset: Preset;
|
|
20
|
-
|
|
21
|
+
/**
|
|
22
|
+
* WI-320 (github#39): 宣言があった場合のみ保持する(undefined = 未宣言)。
|
|
23
|
+
* default `['typescript']` の注入をやめ、「未宣言」シグナルを下流
|
|
24
|
+
* (validator-system のファイルシステム言語検出、WI-319)まで生存させる。
|
|
25
|
+
*/
|
|
26
|
+
readonly languages?: readonly string[];
|
|
21
27
|
|
|
22
28
|
constructor(props: ProjectConfigProps) {
|
|
23
|
-
if (!props.name || props.name.trim() ===
|
|
24
|
-
throw new ConfigValidationError(
|
|
25
|
-
}
|
|
26
|
-
const sourceLanguages = props.languages ?? ['typescript'];
|
|
27
|
-
if (sourceLanguages.length === 0) {
|
|
28
|
-
throw new ConfigValidationError('ProjectConfig: languages must not be empty');
|
|
29
|
+
if (!props.name || props.name.trim() === "") {
|
|
30
|
+
throw new ConfigValidationError("ProjectConfig: name must not be empty");
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
if (props.languages !== undefined) {
|
|
33
|
+
if (props.languages.length === 0) {
|
|
34
|
+
throw new ConfigValidationError("ProjectConfig: languages must not be empty");
|
|
35
|
+
}
|
|
36
|
+
const normalizedLanguages = props.languages
|
|
37
|
+
.map((language) => language.trim())
|
|
38
|
+
.filter((language) => language.length > 0);
|
|
39
|
+
if (normalizedLanguages.length !== props.languages.length) {
|
|
40
|
+
throw new ConfigValidationError("ProjectConfig: languages must contain non-empty strings");
|
|
41
|
+
}
|
|
42
|
+
this.languages = Object.freeze([...normalizedLanguages]);
|
|
33
43
|
}
|
|
34
44
|
this.name = props.name;
|
|
35
45
|
this.preset = props.preset;
|
|
36
|
-
this.languages = Object.freeze([...normalizedLanguages]);
|
|
37
46
|
Object.freeze(this);
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
static create(raw: { name: string; preset: string; languages?: readonly string[] }): ProjectConfig {
|
|
41
50
|
const preset = Preset.create(raw.preset);
|
|
42
|
-
return new ProjectConfig({ name: raw.name, preset, languages: raw.languages
|
|
51
|
+
return new ProjectConfig({ name: raw.name, preset, languages: raw.languages });
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
rename(name: string): ProjectConfig {
|
|
@@ -51,9 +60,16 @@ export class ProjectConfig {
|
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
equals(other: ProjectConfig): boolean {
|
|
54
|
-
return this.name === other.name
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
return this.name === other.name && this.preset.equals(other.preset) && this.languagesEqual(other.languages);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private languagesEqual(otherLanguages: readonly string[] | undefined): boolean {
|
|
67
|
+
if (this.languages === undefined || otherLanguages === undefined) {
|
|
68
|
+
return this.languages === otherLanguages;
|
|
69
|
+
}
|
|
70
|
+
return (
|
|
71
|
+
this.languages.length === otherLanguages.length &&
|
|
72
|
+
this.languages.every((language, index) => language === otherLanguages[index])
|
|
73
|
+
);
|
|
58
74
|
}
|
|
59
75
|
}
|