phasegate 0.160.20 → 0.161.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 +22 -0
- package/README.md +6 -1
- package/docs/guide/cli-reference.md +1 -1
- package/docs/guide/configuration.md +31 -1
- package/docs/guide/skills-overview.md +2 -0
- package/docs/principles/model-routing.md +1 -0
- package/docs/templates/personal/hooks/pre-commit +16 -0
- package/docs/templates/personal/phasegate-local-config.json +4 -1
- package/docs/templates/project/phasegate.config.json +4 -1
- package/package.json +1 -1
- package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +54 -5
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +24 -3
- package/scripts/harness/config-foundation/domain/harness-config.ts +9 -0
- package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +2 -0
- package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +20 -5
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +25 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +26 -3
- package/scripts/harness/installation/application/usecases/run-install.ts +16 -3
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +48 -5
- package/scripts/harness/main.ts +71 -19
- package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +6 -1
- package/scripts/harness/setup/skill-deployer.ts +47 -0
- package/scripts/harness/skill-quality/domain/services/skill-structure-validator.ts +21 -0
- package/scripts/harness/skill-quality/domain/value-objects/skill-structure.ts +2 -0
- package/scripts/harness/validator-system/application/dto/validation-result-contract.ts +1 -0
- package/scripts/harness/validator-system/application/mappers/validation-result-contract-mapper.ts +1 -0
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +18 -5
- package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +48 -6
- package/scripts/harness/validator-system/composition-root.ts +15 -1
- package/scripts/harness/validator-system/domain/ports/validator-config-port.ts +2 -0
- package/scripts/harness/validator-system/domain/services/l4/consistency-check-service.ts +78 -1
- package/scripts/harness/validator-system/domain/services/validator-language-capability-service.ts +64 -0
- package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +7 -0
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-work-item-reflection-adapter.ts +102 -0
- package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +7 -1
- package/scripts/harness/validator-system/presentation/formatters/agent-validation-result-formatter.ts +3 -0
- package/scripts/harness/validator-system/presentation/formatters/human-validation-result-formatter.ts +3 -0
- package/skills/cascade-updater/SKILL.md +1 -0
- package/skills/codebase-mapper/SKILL.md +1 -0
- package/skills/codex-delegator/SKILL.md +1 -0
- package/skills/consistency-checker/SKILL.md +1 -0
- package/skills/doc-freshness-checker/SKILL.md +1 -0
- package/skills/domain-designer/SKILL.md +1 -0
- package/skills/engineering-perspective/SKILL.md +1 -0
- package/skills/environment-designer/SKILL.md +1 -0
- package/skills/implementation-planner/SKILL.md +1 -0
- package/skills/implementation-readiness-checker/SKILL.md +1 -0
- package/skills/it-test-designer/SKILL.md +1 -0
- package/skills/it-test-logic-designer/SKILL.md +1 -0
- package/skills/logical-designer/SKILL.md +1 -0
- package/skills/mock-designer/SKILL.md +1 -0
- package/skills/phasegate-config-doctor/SKILL.md +1 -0
- package/skills/phasegate-toolkit-guide/SKILL.md +1 -0
- package/skills/pointer-validator/SKILL.md +1 -0
- package/skills/product-architect/SKILL.md +1 -0
- package/skills/quick-implementor/SKILL.md +1 -0
- package/skills/scenario-test-designer/SKILL.md +1 -0
- package/skills/scenario-test-logic-designer/SKILL.md +1 -0
- package/skills/skill-creator/SKILL.md +1 -0
- package/skills/story-implementor/SKILL.md +1 -0
- package/skills/story-mapper/SKILL.md +1 -0
- package/skills/story-writer/SKILL.md +1 -0
- package/skills/test-coverage-checker/SKILL.md +1 -0
- package/skills/uiux-designer/SKILL.md +1 -0
- package/skills/unit-designer/SKILL.md +1 -0
- package/skills/unit-test-designer/SKILL.md +1 -0
- package/skills/unit-test-logic-designer/SKILL.md +1 -0
- package/templates/.claude/settings.json +4 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* @layer application
|
|
3
3
|
* @unit validator-system
|
|
4
4
|
* @work-item-id WI-107 / WI-156
|
|
5
|
+
* @work-item-id WI-217
|
|
6
|
+
* @work-item-id WI-212
|
|
5
7
|
*
|
|
6
8
|
* RunL4ValidatorsUseCase — H08-03: L4バリデータ実行
|
|
7
9
|
*/
|
|
@@ -10,6 +12,7 @@ import { ValidationResult } from '../../domain/value-objects/validation-result.j
|
|
|
10
12
|
import { LayerConfig } from '../../domain/value-objects/layer-config.js';
|
|
11
13
|
import { ValidatorRegistry } from '../../domain/services/validator-registry.js';
|
|
12
14
|
import { ValidatorExecutionService, ValidatorExecutionError } from '../../domain/services/validator-execution-service.js';
|
|
15
|
+
import { ValidatorLanguageCapabilityService } from '../../domain/services/validator-language-capability-service.js';
|
|
13
16
|
import { ValidationResultContractMapper } from '../mappers/validation-result-contract-mapper.js';
|
|
14
17
|
import type { ValidationResultContract } from '../dto/validation-result-contract.js';
|
|
15
18
|
import type { RunL4ValidatorsInput } from '../dto/run-l4-validators-input.js';
|
|
@@ -70,6 +73,10 @@ export interface RunL4ValidatorsUseCaseDeps {
|
|
|
70
73
|
deadCodeDetectionService?: DeadCodeDetectionService;
|
|
71
74
|
architectureSemanticAnalysisService?: ArchitectureSemanticAnalysisService;
|
|
72
75
|
skillCatalogDriftPort?: SkillCatalogDriftPort;
|
|
76
|
+
pathRoots?: {
|
|
77
|
+
readonly inceptionRoot: string;
|
|
78
|
+
readonly designRoot: string;
|
|
79
|
+
};
|
|
73
80
|
checkDocFreshnessUseCase?: CheckDocFreshnessUseCasePort;
|
|
74
81
|
validateDocPointersUseCase?: ValidateDocPointersUseCasePort;
|
|
75
82
|
}
|
|
@@ -85,8 +92,10 @@ export class RunL4ValidatorsUseCase {
|
|
|
85
92
|
private readonly architectureSemanticAnalysisService?: ArchitectureSemanticAnalysisService;
|
|
86
93
|
private readonly skillCatalogDriftPort?: SkillCatalogDriftPort;
|
|
87
94
|
private readonly skillCatalogDriftService = new SkillCatalogDriftService();
|
|
95
|
+
private readonly pathRoots: { readonly inceptionRoot: string; readonly designRoot: string };
|
|
88
96
|
private readonly checkDocFreshnessUseCase?: CheckDocFreshnessUseCasePort;
|
|
89
97
|
private readonly validateDocPointersUseCase?: ValidateDocPointersUseCasePort;
|
|
98
|
+
private readonly languageCapabilityService = new ValidatorLanguageCapabilityService();
|
|
90
99
|
|
|
91
100
|
constructor(deps: RunL4ValidatorsUseCaseDeps) {
|
|
92
101
|
this.registry = deps.validatorRegistry;
|
|
@@ -98,6 +107,10 @@ export class RunL4ValidatorsUseCase {
|
|
|
98
107
|
this.deadCodeDetectionService = deps.deadCodeDetectionService;
|
|
99
108
|
this.architectureSemanticAnalysisService = deps.architectureSemanticAnalysisService;
|
|
100
109
|
this.skillCatalogDriftPort = deps.skillCatalogDriftPort;
|
|
110
|
+
this.pathRoots = deps.pathRoots ?? {
|
|
111
|
+
inceptionRoot: 'docs/inception',
|
|
112
|
+
designRoot: 'docs/product/construction',
|
|
113
|
+
};
|
|
101
114
|
this.checkDocFreshnessUseCase = deps.checkDocFreshnessUseCase;
|
|
102
115
|
this.validateDocPointersUseCase = deps.validateDocPointersUseCase;
|
|
103
116
|
}
|
|
@@ -131,11 +144,17 @@ export class RunL4ValidatorsUseCase {
|
|
|
131
144
|
thresholds: { ...layerConfig.thresholds },
|
|
132
145
|
strictOnly: layerConfig.strictOnly,
|
|
133
146
|
preset: layerConfig.preset,
|
|
134
|
-
|
|
147
|
+
})
|
|
135
148
|
: layerConfig;
|
|
136
149
|
|
|
137
|
-
const
|
|
138
|
-
const
|
|
150
|
+
const projectLanguages = await this.getProjectLanguages();
|
|
151
|
+
const { executableDefinitions, unsupportedResults } =
|
|
152
|
+
this.languageCapabilityService.splitDefinitions(definitions, projectLanguages);
|
|
153
|
+
|
|
154
|
+
const results = this.executionService.execute(executableDefinitions, [effectiveLayerConfig]);
|
|
155
|
+
const overrideMap = new Map<string, ValidationResult>(
|
|
156
|
+
[...unsupportedResults, ...results].map((result) => [result.validatorId.value, result]),
|
|
157
|
+
);
|
|
139
158
|
|
|
140
159
|
if (this.driftDetectionService) {
|
|
141
160
|
const l4001Result = overrideMap.get('L4-001');
|
|
@@ -158,13 +177,25 @@ export class RunL4ValidatorsUseCase {
|
|
|
158
177
|
const l4002Result = overrideMap.get('L4-002');
|
|
159
178
|
if (l4002Result && !l4002Result.skipped) {
|
|
160
179
|
const report = await this.consistencyCheckService.check(input.targetUnits ? [...input.targetUnits] : undefined);
|
|
180
|
+
const reflectionResult = this.usesConfiguredDocumentRoots()
|
|
181
|
+
? await this.consistencyCheckService.checkWorkItemReflection(this.pathRoots)
|
|
182
|
+
: undefined;
|
|
161
183
|
const architectureSemanticErrors = this.architectureSemanticAnalysisService
|
|
162
184
|
? await this.architectureSemanticAnalysisService.analyze()
|
|
163
185
|
: [];
|
|
164
|
-
if (report.hasMismatches() || architectureSemanticErrors.length > 0) {
|
|
186
|
+
if (report.hasMismatches() || (reflectionResult?.report.hasMismatches() ?? false) || architectureSemanticErrors.length > 0) {
|
|
165
187
|
overrideMap.set(
|
|
166
188
|
'L4-002',
|
|
167
|
-
ValidationResult.fail(
|
|
189
|
+
ValidationResult.fail(
|
|
190
|
+
ValidatorId.create('L4-002'),
|
|
191
|
+
[...report.toHarnessErrors(), ...(reflectionResult?.report.toHarnessErrors() ?? []), ...architectureSemanticErrors],
|
|
192
|
+
0,
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
} else if (reflectionResult?.skipReason && !report.hasMismatches() && architectureSemanticErrors.length === 0) {
|
|
196
|
+
overrideMap.set(
|
|
197
|
+
'L4-002',
|
|
198
|
+
ValidationResult.skipWithReason(ValidatorId.create('L4-002'), reflectionResult.skipReason),
|
|
168
199
|
);
|
|
169
200
|
}
|
|
170
201
|
}
|
|
@@ -187,7 +218,10 @@ export class RunL4ValidatorsUseCase {
|
|
|
187
218
|
if (this.checkDocFreshnessUseCase) {
|
|
188
219
|
const l4004Result = overrideMap.get('L4-004');
|
|
189
220
|
if (l4004Result && !l4004Result.skipped) {
|
|
190
|
-
const freshnessOutput = await this.checkDocFreshnessUseCase.execute({
|
|
221
|
+
const freshnessOutput = await this.checkDocFreshnessUseCase.execute({
|
|
222
|
+
format: 'json',
|
|
223
|
+
targetPattern: `${this.pathRoots.designRoot.replace(/\/+$/g, '')}/**/*.md`,
|
|
224
|
+
});
|
|
191
225
|
const errors = this.toDocFreshnessHarnessErrors(freshnessOutput);
|
|
192
226
|
overrideMap.set(
|
|
193
227
|
'L4-004',
|
|
@@ -251,6 +285,14 @@ export class RunL4ValidatorsUseCase {
|
|
|
251
285
|
return [...executionErrors, ...freshnessFindings];
|
|
252
286
|
}
|
|
253
287
|
|
|
288
|
+
private async getProjectLanguages(): Promise<readonly string[]> {
|
|
289
|
+
return this.configPort.getProjectLanguages ? await this.configPort.getProjectLanguages() : ['typescript'];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private usesConfiguredDocumentRoots(): boolean {
|
|
293
|
+
return this.pathRoots.inceptionRoot !== 'docs/inception' || this.pathRoots.designRoot !== 'docs/product/construction';
|
|
294
|
+
}
|
|
295
|
+
|
|
254
296
|
private toPointerValidationHarnessErrors(output: ValidateDocPointersOutputContract): readonly ValidationResult['errors'][number][] {
|
|
255
297
|
const executionErrors = output.errors.map((error) => this.toHarnessErrorLike(
|
|
256
298
|
'L4-005',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* DI 組み立て — validator-system の全依存関係を構築する
|
|
6
6
|
* @work-item-id WI-110 / WI-111 / WI-132 / WI-133 / WI-136 / WI-137 / WI-138 / WI-156
|
|
7
|
+
* @work-item-id WI-217
|
|
7
8
|
*/
|
|
8
9
|
import { ValidatorId } from './domain/value-objects/validator-id.js';
|
|
9
10
|
import { ValidatorDefinition } from './domain/value-objects/validator-definition.js';
|
|
@@ -37,6 +38,7 @@ import { AdrFoundationReferenceAdapter } from './infrastructure/adapters/adr-fou
|
|
|
37
38
|
import { ImportGraphSourceAnalysisAdapter } from './infrastructure/adapters/import-graph-source-analysis-adapter.js';
|
|
38
39
|
import { FileSystemArchitectureSemanticSourceAdapter } from './infrastructure/adapters/file-system-architecture-semantic-source-adapter.js';
|
|
39
40
|
import { FileSystemSkillCatalogDriftAdapter } from './infrastructure/adapters/file-system-skill-catalog-drift-adapter.js';
|
|
41
|
+
import { FileSystemWorkItemReflectionAdapter } from './infrastructure/adapters/file-system-work-item-reflection-adapter.js';
|
|
40
42
|
import { DriftDetectionService } from './domain/services/l4/drift-detection-service.js';
|
|
41
43
|
import { ConsistencyCheckService } from './domain/services/l4/consistency-check-service.js';
|
|
42
44
|
import { DeadCodeDetectionService } from './domain/services/l4/dead-code-detection-service.js';
|
|
@@ -55,6 +57,10 @@ const DEFAULT_CONFIG = {
|
|
|
55
57
|
L3: { enabled: true, validators: ['L3-001', 'L3-002', 'L3-003', 'L3-004'], coverageThreshold: 90, bundleSizeLimit: 512000 },
|
|
56
58
|
L4: { enabled: true, validators: ['L4-001', 'L4-002', 'L4-003', 'L4-004', 'L4-005', 'L4-006'] },
|
|
57
59
|
},
|
|
60
|
+
paths: {
|
|
61
|
+
designDocs: 'docs/product/construction',
|
|
62
|
+
inceptionDocs: 'docs/inception',
|
|
63
|
+
},
|
|
58
64
|
validate: { failOnWarning: false },
|
|
59
65
|
architecture: {
|
|
60
66
|
capabilityPolicies: {
|
|
@@ -149,8 +155,10 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
|
|
|
149
155
|
const workItemStatusPolicyPort = new TraceabilityWorkItemStatusPolicyAdapter(process.cwd());
|
|
150
156
|
const contractTraceabilityPolicyPort = new FileSystemContractTraceabilityPolicyAdapter();
|
|
151
157
|
|
|
152
|
-
const docsRoot = join(process.cwd(), 'docs/product/construction');
|
|
153
158
|
const cwd = process.cwd();
|
|
159
|
+
const designDocsRoot = configData.paths?.designDocs ?? 'docs/product/construction';
|
|
160
|
+
const inceptionDocsRoot = configData.paths?.inceptionDocs ?? 'docs/inception';
|
|
161
|
+
const docsRoot = join(cwd, designDocsRoot);
|
|
154
162
|
const e2eTestFileRegistryPort = new E2eTestFileRegistryAdapter({ e2eTestRoot: join(cwd, 'scripts/harness/__tests__/e2e') });
|
|
155
163
|
const cliCommandRegistryPort = new CliCommandRegistryAdapter({
|
|
156
164
|
commands: [
|
|
@@ -191,6 +199,7 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
|
|
|
191
199
|
const sourceAnalysisPort = new ImportGraphSourceAnalysisAdapter();
|
|
192
200
|
const architectureSemanticSourcePort = new FileSystemArchitectureSemanticSourceAdapter();
|
|
193
201
|
const skillCatalogDriftPort = new FileSystemSkillCatalogDriftAdapter(cwd);
|
|
202
|
+
const workItemReflectionPort = new FileSystemWorkItemReflectionAdapter(cwd);
|
|
194
203
|
|
|
195
204
|
const driftDetectionService = new DriftDetectionService({
|
|
196
205
|
designDocumentPort: markdownDesignDocumentPort,
|
|
@@ -199,6 +208,7 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
|
|
|
199
208
|
const consistencyCheckService = new ConsistencyCheckService({
|
|
200
209
|
designDocumentPort: markdownDesignDocumentPort,
|
|
201
210
|
adrReferencePort,
|
|
211
|
+
workItemReflectionPort,
|
|
202
212
|
});
|
|
203
213
|
const deadCodeDetectionService = new DeadCodeDetectionService({
|
|
204
214
|
sourceAnalysisPort,
|
|
@@ -219,6 +229,10 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
|
|
|
219
229
|
deadCodeDetectionService,
|
|
220
230
|
architectureSemanticAnalysisService,
|
|
221
231
|
skillCatalogDriftPort,
|
|
232
|
+
pathRoots: {
|
|
233
|
+
inceptionRoot: inceptionDocsRoot,
|
|
234
|
+
designRoot: designDocsRoot,
|
|
235
|
+
},
|
|
222
236
|
checkDocFreshnessUseCase: phase2Extensions.checkDocFreshnessUseCase,
|
|
223
237
|
validateDocPointersUseCase: phase2Extensions.validateDocPointersUseCase,
|
|
224
238
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit validator-system
|
|
4
|
+
* @work-item-id WI-212
|
|
4
5
|
*
|
|
5
6
|
* ValidatorConfigPort — HarnessConfigV2からLayerConfig取得
|
|
6
7
|
*/
|
|
@@ -8,4 +9,5 @@ import type { LayerConfig } from '../value-objects/layer-config.js';
|
|
|
8
9
|
|
|
9
10
|
export interface ValidatorConfigPort {
|
|
10
11
|
getLayerConfig(layer: 'L2' | 'L3' | 'L4'): Promise<LayerConfig>;
|
|
12
|
+
getProjectLanguages?(): Promise<readonly string[]>;
|
|
11
13
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 設計文書間のレイヤー整合性検証(L4-002)
|
|
8
8
|
*/
|
|
9
9
|
import { ConsistencyReport } from '../../value-objects/consistency-report.js';
|
|
10
|
+
import type { MismatchPair } from '../../value-objects/consistency-report.js';
|
|
10
11
|
|
|
11
12
|
export interface ConsistencyDesignDocumentPort {
|
|
12
13
|
getLayerAnnotations(targetDocs?: readonly string[]): Promise<Record<string, string>>;
|
|
@@ -16,24 +17,47 @@ export interface ConsistencyAdrReferencePort {
|
|
|
16
17
|
exists(adrRef: string): Promise<boolean>;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
export interface WorkItemReflectionSnapshot {
|
|
21
|
+
readonly workItems: readonly {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly type?: string;
|
|
25
|
+
}[];
|
|
26
|
+
readonly productRefs: readonly {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly path: string;
|
|
29
|
+
}[];
|
|
30
|
+
readonly skipReason?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface WorkItemReflectionPort {
|
|
34
|
+
collect(input: {
|
|
35
|
+
readonly inceptionRoot: string;
|
|
36
|
+
readonly designRoot: string;
|
|
37
|
+
}): Promise<WorkItemReflectionSnapshot>;
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
export interface ConsistencyCheckServiceDeps {
|
|
20
41
|
designDocumentPort: ConsistencyDesignDocumentPort;
|
|
21
42
|
adrReferencePort: ConsistencyAdrReferencePort;
|
|
43
|
+
workItemReflectionPort?: WorkItemReflectionPort;
|
|
22
44
|
}
|
|
23
45
|
|
|
24
46
|
export class ConsistencyCheckService {
|
|
25
47
|
private readonly designDocumentPort: ConsistencyDesignDocumentPort;
|
|
26
48
|
private readonly adrReferencePort: ConsistencyAdrReferencePort;
|
|
49
|
+
private readonly workItemReflectionPort?: WorkItemReflectionPort;
|
|
27
50
|
|
|
28
51
|
constructor(deps: ConsistencyCheckServiceDeps) {
|
|
29
52
|
this.designDocumentPort = deps.designDocumentPort;
|
|
30
53
|
this.adrReferencePort = deps.adrReferencePort;
|
|
54
|
+
this.workItemReflectionPort = deps.workItemReflectionPort;
|
|
31
55
|
}
|
|
32
56
|
|
|
33
57
|
async check(targetDocs?: readonly string[]): Promise<ConsistencyReport> {
|
|
34
58
|
const layerAnnotations = await this.designDocumentPort.getLayerAnnotations(targetDocs);
|
|
35
59
|
|
|
36
|
-
const mismatchPairs:
|
|
60
|
+
const mismatchPairs: MismatchPair[] = [];
|
|
37
61
|
const checkTargets = Object.keys(layerAnnotations);
|
|
38
62
|
|
|
39
63
|
for (const [location, annotation] of Object.entries(layerAnnotations)) {
|
|
@@ -75,4 +99,57 @@ export class ConsistencyCheckService {
|
|
|
75
99
|
checkedAt: new Date().toISOString(),
|
|
76
100
|
});
|
|
77
101
|
}
|
|
102
|
+
|
|
103
|
+
async checkWorkItemReflection(input: {
|
|
104
|
+
readonly inceptionRoot: string;
|
|
105
|
+
readonly designRoot: string;
|
|
106
|
+
}): Promise<{ readonly report: ConsistencyReport; readonly skipReason?: string }> {
|
|
107
|
+
if (!this.workItemReflectionPort) {
|
|
108
|
+
return {
|
|
109
|
+
report: ConsistencyReport.create({ mismatchPairs: [], checkTargets: [], checkedAt: new Date().toISOString() }),
|
|
110
|
+
skipReason: 'work item reflection scanner is not configured',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const snapshot = await this.workItemReflectionPort.collect(input);
|
|
115
|
+
const checkTargets = [
|
|
116
|
+
...snapshot.workItems.map((item) => `${item.path}#work-item:${item.id}`),
|
|
117
|
+
...snapshot.productRefs.map((ref) => `${ref.path}#work-item:${ref.id}`),
|
|
118
|
+
];
|
|
119
|
+
const productRefIds = new Set(snapshot.productRefs.map((ref) => ref.id));
|
|
120
|
+
const workItemIds = new Set(snapshot.workItems.map((item) => item.id));
|
|
121
|
+
const reflectionRequiredItems = snapshot.workItems.filter((item) => item.type !== 'chore');
|
|
122
|
+
const mismatchPairs: MismatchPair[] = [];
|
|
123
|
+
|
|
124
|
+
for (const item of reflectionRequiredItems) {
|
|
125
|
+
if (!productRefIds.has(item.id)) {
|
|
126
|
+
mismatchPairs.push({
|
|
127
|
+
expected: `product docs contain @work-item-id ${item.id}`,
|
|
128
|
+
actual: 'missing product reflection',
|
|
129
|
+
location: item.path,
|
|
130
|
+
nextAction: `Add @work-item-id ${item.id} to the matching product construction document under ${input.designRoot}.`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const ref of snapshot.productRefs) {
|
|
136
|
+
if (!workItemIds.has(ref.id)) {
|
|
137
|
+
mismatchPairs.push({
|
|
138
|
+
expected: `inception description exists for ${ref.id}`,
|
|
139
|
+
actual: 'orphan product reflection',
|
|
140
|
+
location: ref.path,
|
|
141
|
+
nextAction: `Create or restore an inception description for ${ref.id} under ${input.inceptionRoot}, or remove the stale product annotation.`,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
report: ConsistencyReport.create({
|
|
148
|
+
mismatchPairs,
|
|
149
|
+
checkTargets,
|
|
150
|
+
checkedAt: new Date().toISOString(),
|
|
151
|
+
}),
|
|
152
|
+
skipReason: snapshot.skipReason,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
78
155
|
}
|
package/scripts/harness/validator-system/domain/services/validator-language-capability-service.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit validator-system
|
|
4
|
+
* @work-item-id WI-212
|
|
5
|
+
*/
|
|
6
|
+
import type { ValidatorId } from '../value-objects/validator-id.js';
|
|
7
|
+
import { ValidationResult } from '../value-objects/validation-result.js';
|
|
8
|
+
import type { ValidatorDefinition } from '../value-objects/validator-definition.js';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_SUPPORTED_LANGUAGES = ['typescript'] as const;
|
|
11
|
+
|
|
12
|
+
const VALIDATOR_LANGUAGE_CAPABILITIES: Readonly<Record<string, readonly string[]>> = {
|
|
13
|
+
'L3-002': DEFAULT_SUPPORTED_LANGUAGES,
|
|
14
|
+
'L3-003': DEFAULT_SUPPORTED_LANGUAGES,
|
|
15
|
+
'L4-003': DEFAULT_SUPPORTED_LANGUAGES,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class ValidatorLanguageCapabilityService {
|
|
19
|
+
getUnsupportedValidatorIds(
|
|
20
|
+
validatorIds: readonly ValidatorId[],
|
|
21
|
+
projectLanguages: readonly string[],
|
|
22
|
+
): ReadonlySet<string> {
|
|
23
|
+
const normalizedProjectLanguages = new Set(projectLanguages.map((language) => language.toLowerCase()));
|
|
24
|
+
const unsupportedIds = validatorIds
|
|
25
|
+
.filter((validatorId) => {
|
|
26
|
+
const supportedLanguages = VALIDATOR_LANGUAGE_CAPABILITIES[validatorId.value];
|
|
27
|
+
if (!supportedLanguages) return false;
|
|
28
|
+
return !supportedLanguages.some((language) => normalizedProjectLanguages.has(language));
|
|
29
|
+
})
|
|
30
|
+
.map((validatorId) => validatorId.value);
|
|
31
|
+
|
|
32
|
+
return new Set(unsupportedIds);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
splitDefinitions(
|
|
36
|
+
definitions: readonly ValidatorDefinition[],
|
|
37
|
+
projectLanguages: readonly string[],
|
|
38
|
+
): {
|
|
39
|
+
readonly executableDefinitions: readonly ValidatorDefinition[];
|
|
40
|
+
readonly unsupportedResults: readonly ValidationResult[];
|
|
41
|
+
readonly unsupportedValidatorIds: ReadonlySet<string>;
|
|
42
|
+
} {
|
|
43
|
+
const unsupportedValidatorIds = this.getUnsupportedValidatorIds(
|
|
44
|
+
definitions.map((definition) => definition.validatorId),
|
|
45
|
+
projectLanguages,
|
|
46
|
+
);
|
|
47
|
+
const executableDefinitions = definitions.filter(
|
|
48
|
+
(definition) => !unsupportedValidatorIds.has(definition.validatorId.value),
|
|
49
|
+
);
|
|
50
|
+
const unsupportedResults = definitions
|
|
51
|
+
.filter((definition) => unsupportedValidatorIds.has(definition.validatorId.value))
|
|
52
|
+
.map((definition) => ValidationResult.skipWithReason(
|
|
53
|
+
definition.validatorId,
|
|
54
|
+
this.createSkipReason(definition.validatorId, projectLanguages),
|
|
55
|
+
));
|
|
56
|
+
|
|
57
|
+
return { executableDefinitions, unsupportedResults, unsupportedValidatorIds };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private createSkipReason(validatorId: ValidatorId, projectLanguages: readonly string[]): string {
|
|
61
|
+
const supportedLanguages = VALIDATOR_LANGUAGE_CAPABILITIES[validatorId.value] ?? DEFAULT_SUPPORTED_LANGUAGES;
|
|
62
|
+
return `unsupported-language: ${validatorId.value} currently supports ${supportedLanguages.join(', ')}; project languages are ${projectLanguages.join(', ')}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -22,6 +22,7 @@ export interface ValidationResultRawProps {
|
|
|
22
22
|
readonly errors: readonly HarnessErrorLike[];
|
|
23
23
|
readonly durationMs: number;
|
|
24
24
|
readonly skipped: boolean;
|
|
25
|
+
readonly skipReason?: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export class ValidationResult {
|
|
@@ -30,6 +31,7 @@ export class ValidationResult {
|
|
|
30
31
|
readonly errors: readonly HarnessErrorLike[];
|
|
31
32
|
readonly durationMs: number;
|
|
32
33
|
readonly skipped: boolean;
|
|
34
|
+
readonly skipReason?: string;
|
|
33
35
|
|
|
34
36
|
private constructor(props: ValidationResultRawProps) {
|
|
35
37
|
this.validatorId = props.validatorId;
|
|
@@ -37,6 +39,7 @@ export class ValidationResult {
|
|
|
37
39
|
this.errors = Object.freeze([...props.errors]);
|
|
38
40
|
this.durationMs = props.durationMs;
|
|
39
41
|
this.skipped = props.skipped;
|
|
42
|
+
this.skipReason = props.skipReason;
|
|
40
43
|
Object.freeze(this);
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -71,6 +74,10 @@ export class ValidationResult {
|
|
|
71
74
|
return new ValidationResult({ validatorId, passed: true, errors: [], durationMs: 0, skipped: true });
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
static skipWithReason(validatorId: ValidatorId, skipReason: string): ValidationResult {
|
|
78
|
+
return new ValidationResult({ validatorId, passed: true, errors: [], durationMs: 0, skipped: true, skipReason });
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
hasErrors(): boolean {
|
|
75
82
|
return this.errors.length > 0;
|
|
76
83
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer infrastructure
|
|
3
|
+
* @unit validator-system
|
|
4
|
+
* @work-item-id WI-217
|
|
5
|
+
*/
|
|
6
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
7
|
+
import { join, relative } from 'node:path';
|
|
8
|
+
import type {
|
|
9
|
+
WorkItemReflectionPort,
|
|
10
|
+
WorkItemReflectionSnapshot,
|
|
11
|
+
} from '../../domain/services/l4/consistency-check-service.js';
|
|
12
|
+
|
|
13
|
+
const DESCRIPTION_FILE = 'description.md';
|
|
14
|
+
const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
15
|
+
const WORK_ITEM_ANNOTATION_PATTERN = /@work-item-id\s+([^<\r\n]+)/g;
|
|
16
|
+
|
|
17
|
+
function normalizePath(value: string): string {
|
|
18
|
+
return value.replace(/\\/g, '/');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function listFiles(root: string): Promise<string[]> {
|
|
22
|
+
try {
|
|
23
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
24
|
+
const files: string[] = [];
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const path = join(root, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
files.push(...(await listFiles(path)));
|
|
29
|
+
} else if (entry.isFile()) {
|
|
30
|
+
files.push(path);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return files;
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractFrontmatterValue(markdown: string, key: string): string | undefined {
|
|
40
|
+
const frontmatter = FRONTMATTER_PATTERN.exec(markdown)?.[1];
|
|
41
|
+
if (!frontmatter) return undefined;
|
|
42
|
+
const pattern = new RegExp(`^${key}:\\s*(.+?)\\s*$`, 'm');
|
|
43
|
+
return pattern.exec(frontmatter)?.[1]?.replace(/^["']|["']$/g, '').trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractWorkItemRefs(markdown: string): string[] {
|
|
47
|
+
const refs = new Set<string>();
|
|
48
|
+
for (const match of markdown.matchAll(WORK_ITEM_ANNOTATION_PATTERN)) {
|
|
49
|
+
const raw = match[1].replace(/-->.*/, '');
|
|
50
|
+
for (const token of raw.split(/[,\s]+/).map((part) => part.trim()).filter(Boolean)) {
|
|
51
|
+
if (/^[A-Za-z][A-Za-z0-9_-]*-\d+(?:-\d+)*$/.test(token)) {
|
|
52
|
+
refs.add(token);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return [...refs];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class FileSystemWorkItemReflectionAdapter implements WorkItemReflectionPort {
|
|
60
|
+
constructor(private readonly projectRoot: string) {}
|
|
61
|
+
|
|
62
|
+
async collect(input: {
|
|
63
|
+
readonly inceptionRoot: string;
|
|
64
|
+
readonly designRoot: string;
|
|
65
|
+
}): Promise<WorkItemReflectionSnapshot> {
|
|
66
|
+
const inceptionRoot = join(this.projectRoot, input.inceptionRoot);
|
|
67
|
+
const designRoot = join(this.projectRoot, input.designRoot);
|
|
68
|
+
const descriptionFiles = (await listFiles(inceptionRoot))
|
|
69
|
+
.filter((path) => path.endsWith(`/${DESCRIPTION_FILE}`));
|
|
70
|
+
|
|
71
|
+
if (descriptionFiles.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
workItems: [],
|
|
74
|
+
productRefs: [],
|
|
75
|
+
skipReason: `no work item descriptions found under ${input.inceptionRoot}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const workItems: Array<WorkItemReflectionSnapshot['workItems'][number]> = [];
|
|
80
|
+
for (const path of descriptionFiles) {
|
|
81
|
+
const markdown = await readFile(path, 'utf8');
|
|
82
|
+
const id = extractFrontmatterValue(markdown, 'id');
|
|
83
|
+
if (!id) continue;
|
|
84
|
+
workItems.push({
|
|
85
|
+
id,
|
|
86
|
+
path: normalizePath(relative(this.projectRoot, path)),
|
|
87
|
+
type: extractFrontmatterValue(markdown, 'type'),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const productRefs: Array<WorkItemReflectionSnapshot['productRefs'][number]> = [];
|
|
92
|
+
const productFiles = (await listFiles(designRoot)).filter((path) => path.endsWith('.md'));
|
|
93
|
+
for (const path of productFiles) {
|
|
94
|
+
const markdown = await readFile(path, 'utf8');
|
|
95
|
+
for (const id of extractWorkItemRefs(markdown)) {
|
|
96
|
+
productRefs.push({ id, path: normalizePath(relative(this.projectRoot, path)) });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { workItems, productRefs };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @layer infrastructure
|
|
3
3
|
* @unit validator-system
|
|
4
4
|
* @work-item-id WI-156
|
|
5
|
+
* @work-item-id WI-212
|
|
5
6
|
*
|
|
6
7
|
* HarnessConfigValidatorConfigAdapter — ValidatorConfigPort実装
|
|
7
8
|
* HarnessConfigV2からLayerConfig VOを構築する
|
|
@@ -18,7 +19,7 @@ export interface HarnessConfigLayers {
|
|
|
18
19
|
|
|
19
20
|
export interface HarnessConfigV2Like {
|
|
20
21
|
preset?: 'minimal' | 'standard' | 'strict';
|
|
21
|
-
project?: { preset?: 'minimal' | 'standard' | 'strict' };
|
|
22
|
+
project?: { preset?: 'minimal' | 'standard' | 'strict'; languages?: readonly string[] };
|
|
22
23
|
layers?: HarnessConfigLayers;
|
|
23
24
|
harnesses?: {
|
|
24
25
|
bundleSizeLimit?: number;
|
|
@@ -71,6 +72,11 @@ export class HarnessConfigValidatorConfigAdapter implements ValidatorConfigPort
|
|
|
71
72
|
});
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
async getProjectLanguages(): Promise<readonly string[]> {
|
|
76
|
+
const languages = this.config.project?.languages;
|
|
77
|
+
return languages && languages.length > 0 ? [...languages] : ['typescript'];
|
|
78
|
+
}
|
|
79
|
+
|
|
74
80
|
private normalizeValidatorId(idOrName: string): string {
|
|
75
81
|
try {
|
|
76
82
|
return ValidatorId.create(idOrName).value;
|
|
@@ -18,6 +18,9 @@ export class AgentValidationResultFormatter {
|
|
|
18
18
|
lines.push(`VALIDATOR: ${result.validatorId}`);
|
|
19
19
|
lines.push(`STATUS: ${result.skipped ? 'SKIPPED' : result.passed ? 'PASSED' : 'FAILED'}`);
|
|
20
20
|
lines.push(`DURATION: ${result.durationMs}ms`);
|
|
21
|
+
if (result.skipped && result.skipReason) {
|
|
22
|
+
lines.push(`SKIP_REASON: ${result.skipReason}`);
|
|
23
|
+
}
|
|
21
24
|
if (result.errors.length > 0) {
|
|
22
25
|
lines.push('ERRORS:');
|
|
23
26
|
for (const error of result.errors) {
|
|
@@ -25,6 +25,9 @@ export class HumanValidationResultFormatter {
|
|
|
25
25
|
? 'FAIL'
|
|
26
26
|
: 'WARN';
|
|
27
27
|
lines.push(`[${status}] ${result.validatorId} (${result.durationMs}ms)`);
|
|
28
|
+
if (result.skipped && result.skipReason) {
|
|
29
|
+
lines.push(` → ${result.skipReason}`);
|
|
30
|
+
}
|
|
28
31
|
for (const error of result.errors) {
|
|
29
32
|
lines.push(` ⚠ ${error.message}`);
|
|
30
33
|
lines.push(` → ${error.suggestion}`);
|
|
@@ -3,6 +3,7 @@ name: codebase-mapper
|
|
|
3
3
|
description: コードベースの構造マップを生成するスキル。全ソースファイルの `@unit`/`@layer` アノテーションを解析し、Unit・レイヤー分布・Unit間依存関係・循環依存を可視化したマップ文書を出力する。L4バリデータ(drift-detection/dead-code)の入力精度向上にも使用。使用タイミング: 「コードベースの構造を把握したい」「Unitマップを作って」「どのファイルがどのUnitに属するか調べて」「依存関係を可視化して」など。
|
|
4
4
|
model: sonnet
|
|
5
5
|
review: opus
|
|
6
|
+
languages: [typescript]
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
# Codebase Mapper
|
|
@@ -3,6 +3,7 @@ name: doc-freshness-checker
|
|
|
3
3
|
description: 設計文書の鮮度チェック(L4バリデータ拡張)。`phasegate check-freshness` CLIを使い、設計文書の最終更新日が閾値を超えていないか、コード変更と設計文書の乖離がないかを検出する。使用タイミング: 「設計文書が古くなっていないか確認して」「ドキュメントの鮮度チェックを実行して」「L4 freshness チェック」「設計とコードの乖離を調べて」など。
|
|
4
4
|
model: sonnet
|
|
5
5
|
review: opus
|
|
6
|
+
languages: [typescript]
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
# Doc Freshness Checker
|
|
@@ -3,6 +3,7 @@ name: implementation-planner
|
|
|
3
3
|
description: "Unit仕様とドメインモデル設計を元に実装計画を立てる。WI IDや機能名から関連Unitを特定し、API設計・レイヤー別実装方針を整理してmdファイルで出力する。使用タイミング: 実装計画を立てて、WI-XXXの実装方針を決めて、この機能の設計を整理して、など実装前の計画策定時。"
|
|
4
4
|
model: sonnet
|
|
5
5
|
review: opus
|
|
6
|
+
languages: [typescript]
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
# Implementation Planner
|