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.
Files changed (68) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +6 -1
  3. package/docs/guide/cli-reference.md +1 -1
  4. package/docs/guide/configuration.md +31 -1
  5. package/docs/guide/skills-overview.md +2 -0
  6. package/docs/principles/model-routing.md +1 -0
  7. package/docs/templates/personal/hooks/pre-commit +16 -0
  8. package/docs/templates/personal/phasegate-local-config.json +4 -1
  9. package/docs/templates/project/phasegate.config.json +4 -1
  10. package/package.json +1 -1
  11. package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +54 -5
  12. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +24 -3
  13. package/scripts/harness/config-foundation/domain/harness-config.ts +9 -0
  14. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +2 -0
  15. package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +20 -5
  16. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +25 -0
  17. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +26 -3
  18. package/scripts/harness/installation/application/usecases/run-install.ts +16 -3
  19. package/scripts/harness/installation/application/usecases/run-reconcile.ts +48 -5
  20. package/scripts/harness/main.ts +71 -19
  21. package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +6 -1
  22. package/scripts/harness/setup/skill-deployer.ts +47 -0
  23. package/scripts/harness/skill-quality/domain/services/skill-structure-validator.ts +21 -0
  24. package/scripts/harness/skill-quality/domain/value-objects/skill-structure.ts +2 -0
  25. package/scripts/harness/validator-system/application/dto/validation-result-contract.ts +1 -0
  26. package/scripts/harness/validator-system/application/mappers/validation-result-contract-mapper.ts +1 -0
  27. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +18 -5
  28. package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +48 -6
  29. package/scripts/harness/validator-system/composition-root.ts +15 -1
  30. package/scripts/harness/validator-system/domain/ports/validator-config-port.ts +2 -0
  31. package/scripts/harness/validator-system/domain/services/l4/consistency-check-service.ts +78 -1
  32. package/scripts/harness/validator-system/domain/services/validator-language-capability-service.ts +64 -0
  33. package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +7 -0
  34. package/scripts/harness/validator-system/infrastructure/adapters/file-system-work-item-reflection-adapter.ts +102 -0
  35. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +7 -1
  36. package/scripts/harness/validator-system/presentation/formatters/agent-validation-result-formatter.ts +3 -0
  37. package/scripts/harness/validator-system/presentation/formatters/human-validation-result-formatter.ts +3 -0
  38. package/skills/cascade-updater/SKILL.md +1 -0
  39. package/skills/codebase-mapper/SKILL.md +1 -0
  40. package/skills/codex-delegator/SKILL.md +1 -0
  41. package/skills/consistency-checker/SKILL.md +1 -0
  42. package/skills/doc-freshness-checker/SKILL.md +1 -0
  43. package/skills/domain-designer/SKILL.md +1 -0
  44. package/skills/engineering-perspective/SKILL.md +1 -0
  45. package/skills/environment-designer/SKILL.md +1 -0
  46. package/skills/implementation-planner/SKILL.md +1 -0
  47. package/skills/implementation-readiness-checker/SKILL.md +1 -0
  48. package/skills/it-test-designer/SKILL.md +1 -0
  49. package/skills/it-test-logic-designer/SKILL.md +1 -0
  50. package/skills/logical-designer/SKILL.md +1 -0
  51. package/skills/mock-designer/SKILL.md +1 -0
  52. package/skills/phasegate-config-doctor/SKILL.md +1 -0
  53. package/skills/phasegate-toolkit-guide/SKILL.md +1 -0
  54. package/skills/pointer-validator/SKILL.md +1 -0
  55. package/skills/product-architect/SKILL.md +1 -0
  56. package/skills/quick-implementor/SKILL.md +1 -0
  57. package/skills/scenario-test-designer/SKILL.md +1 -0
  58. package/skills/scenario-test-logic-designer/SKILL.md +1 -0
  59. package/skills/skill-creator/SKILL.md +1 -0
  60. package/skills/story-implementor/SKILL.md +1 -0
  61. package/skills/story-mapper/SKILL.md +1 -0
  62. package/skills/story-writer/SKILL.md +1 -0
  63. package/skills/test-coverage-checker/SKILL.md +1 -0
  64. package/skills/uiux-designer/SKILL.md +1 -0
  65. package/skills/unit-designer/SKILL.md +1 -0
  66. package/skills/unit-test-designer/SKILL.md +1 -0
  67. package/skills/unit-test-logic-designer/SKILL.md +1 -0
  68. 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 results = this.executionService.execute(definitions, [effectiveLayerConfig]);
138
- const overrideMap = new Map<string, ValidationResult>(results.map((result) => [result.validatorId.value, result]));
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(ValidatorId.create('L4-002'), [...report.toHarnessErrors(), ...architectureSemanticErrors], 0),
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({ format: 'json' });
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: { expected: string; actual: string; location: string }[] = [];
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
  }
@@ -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: cascade-updater
3
3
  description: 下位フェーズの発見・変更を上位設計文書にフィードバックし影響範囲を整合的に更新
4
4
  model: sonnet
5
5
  review: opus
6
+ languages: [typescript]
6
7
  ---
7
8
 
8
9
  # Cascade Updater
@@ -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
@@ -6,6 +6,7 @@ description: |
6
6
  使用タイミング: 「codexに委任して」「codexで並列に作成」「codexに実装させて」「agentに依頼して」など、
7
7
  codex CLIへのタスク委任・並列実行が必要な場面。
8
8
  model: codex
9
+ languages: [typescript]
9
10
  ---
10
11
 
11
12
  # Codex Delegator
@@ -3,6 +3,7 @@ name: consistency-checker
3
3
  description: AIDLC設計文書群のレイヤー間整合性チェック・矛盾/漏れ検出
4
4
  model: sonnet
5
5
  review: opus
6
+ languages: [typescript]
6
7
  ---
7
8
 
8
9
  # Consistency Checker
@@ -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
@@ -2,6 +2,7 @@
2
2
  name: domain-designer
3
3
  description: DDD戦術パターンでドメインモデル(集約・エンティティ・VO・イベント)を設計(AIDLC Step 2.1)
4
4
  model: opus
5
+ languages: [typescript]
5
6
  ---
6
7
 
7
8
  # Domain Designer
@@ -9,6 +9,7 @@ description: |
9
9
  (4) TDD/リファクタリング/DDDのベストプラクティスを適用したい
10
10
  (5) チーム議論で共通認識を構築したい
11
11
  トリガー例: 「レビューして」「これどう思う?」「設計見て」「SOLID的にどう?」「リファクタ必要?」「ドメイン的に正しい?」
12
+ languages: [typescript]
12
13
  ---
13
14
 
14
15
  # Engineering Perspective
@@ -3,6 +3,7 @@ name: environment-designer
3
3
  description: ローカル開発環境のプラットフォーム構成設計。コードと環境のブリッジ(AIDLC Step 2.2並行)
4
4
  model: sonnet
5
5
  review: opus
6
+ languages: [typescript]
6
7
  ---
7
8
 
8
9
  # Environment Designer
@@ -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
@@ -3,6 +3,7 @@ name: implementation-readiness-checker
3
3
  description: 実装開始前の準備状況を自動検証 - テスト設計・カバレッジ・ロジック設計の存在チェックとギャップ分析
4
4
  model: sonnet
5
5
  review: opus
6
+ languages: [typescript]
6
7
  ---
7
8
 
8
9
  # Implementation Readiness Checker