phasegate 0.160.21 → 0.162.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 (96) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +6 -1
  3. package/docs/contracts/requirement-test-matrix.schema.json +1 -1
  4. package/docs/guide/cli-reference.md +1 -1
  5. package/docs/guide/configuration.md +31 -1
  6. package/docs/guide/skills-overview.md +2 -0
  7. package/docs/principles/model-routing.md +1 -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 +6 -3
  11. package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +91 -23
  12. package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +4 -0
  13. package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +54 -5
  14. package/scripts/harness/agent-integration/infrastructure/adapters/ci-governance-baseline-grandfather-adapter.ts +25 -2
  15. package/scripts/harness/agent-integration/infrastructure/adapters/phase-gate-query-adapter.ts +17 -2
  16. package/scripts/harness/biome-ast-engine/domain/value-objects/import-graph.ts +28 -1
  17. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +6 -1
  18. package/scripts/harness/config-foundation/domain/harness-config.ts +10 -0
  19. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +2 -0
  20. package/scripts/harness/config-foundation/domain/value-objects/l3-config.ts +4 -0
  21. package/scripts/harness/config-foundation/domain/value-objects/layers-config.ts +1 -1
  22. package/scripts/harness/config-foundation/domain/value-objects/project-config.ts +20 -5
  23. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +2 -1
  24. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +2 -1
  25. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +2 -1
  26. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +39 -9
  27. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +39 -9
  28. package/scripts/harness/harness-api/infrastructure/adapters/phase-dependency-model-query-adapter.ts +18 -4
  29. package/scripts/harness/harness-api/infrastructure/adapters/validator-system-execution-adapter.ts +27 -4
  30. package/scripts/harness/installation/application/ports/model-delegation-port.ts +10 -0
  31. package/scripts/harness/installation/application/usecases/run-install.ts +67 -5
  32. package/scripts/harness/installation/application/usecases/run-reconcile.ts +53 -5
  33. package/scripts/harness/installation/composition-root.ts +4 -2
  34. package/scripts/harness/installation/infrastructure/adapters/skill-deployer-model-delegation-adapter.ts +16 -0
  35. package/scripts/harness/installation/presentation/cli/install-handler.ts +2 -1
  36. package/scripts/harness/main.ts +99 -13
  37. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +9 -7
  38. package/scripts/harness/nyquist-validation/domain/services/matrix-validation-service.ts +27 -2
  39. package/scripts/harness/nyquist-validation/domain/services/requirement-intent-coverage-service.ts +4 -4
  40. package/scripts/harness/nyquist-validation/domain/value-objects/intent-coverage.ts +33 -0
  41. package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +3 -1
  42. package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +8 -3
  43. package/scripts/harness/quick-mode/composition-root.ts +3 -0
  44. package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +6 -1
  45. package/scripts/harness/quick-mode/infrastructure/adapters/validator-system-quick-mode-execution-adapter.ts +18 -0
  46. package/scripts/harness/quick-mode/presentation/handlers/ci-check-quick-mode-handler.ts +51 -5
  47. package/scripts/harness/setup/skill-deployer.ts +47 -0
  48. package/scripts/harness/skill-quality/domain/services/skill-structure-validator.ts +21 -0
  49. package/scripts/harness/skill-quality/domain/value-objects/skill-structure.ts +2 -0
  50. package/scripts/harness/skill-quality/infrastructure/adapters/l1-biome-validator-adapter.ts +12 -2
  51. package/scripts/harness/skill-quality/infrastructure/adapters/l2-validator-system-adapter.ts +12 -2
  52. package/scripts/harness/traceability-model/application/usecases/apply-work-item-status-usecase.ts +6 -1
  53. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +28 -9
  54. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +76 -25
  55. package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +20 -8
  56. package/scripts/harness/validator-system/composition-root.ts +8 -1
  57. package/scripts/harness/validator-system/domain/ports/validator-config-port.ts +2 -0
  58. package/scripts/harness/validator-system/domain/services/validator-language-capability-service.ts +64 -0
  59. package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +16 -6
  60. package/scripts/harness/validator-system/infrastructure/adapters/file-system-work-item-reflection-adapter.ts +10 -1
  61. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +7 -1
  62. package/scripts/harness/validator-system/infrastructure/adapters/import-graph-source-analysis-adapter.ts +35 -3
  63. package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-coverage-policy-adapter.ts +86 -14
  64. package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +29 -3
  65. package/skills/cascade-updater/SKILL.md +1 -0
  66. package/skills/codebase-mapper/SKILL.md +1 -0
  67. package/skills/codex-delegator/SKILL.md +1 -0
  68. package/skills/consistency-checker/SKILL.md +1 -0
  69. package/skills/doc-freshness-checker/SKILL.md +1 -0
  70. package/skills/domain-designer/SKILL.md +1 -0
  71. package/skills/engineering-perspective/SKILL.md +1 -0
  72. package/skills/environment-designer/SKILL.md +1 -0
  73. package/skills/implementation-planner/SKILL.md +1 -0
  74. package/skills/implementation-readiness-checker/SKILL.md +1 -0
  75. package/skills/it-test-designer/SKILL.md +1 -0
  76. package/skills/it-test-logic-designer/SKILL.md +1 -0
  77. package/skills/logical-designer/SKILL.md +1 -0
  78. package/skills/mock-designer/SKILL.md +1 -0
  79. package/skills/phasegate-config-doctor/SKILL.md +1 -0
  80. package/skills/phasegate-toolkit-guide/SKILL.md +1 -0
  81. package/skills/pointer-validator/SKILL.md +1 -0
  82. package/skills/product-architect/SKILL.md +1 -0
  83. package/skills/quick-implementor/SKILL.md +1 -0
  84. package/skills/scenario-test-designer/SKILL.md +1 -0
  85. package/skills/scenario-test-logic-designer/SKILL.md +1 -0
  86. package/skills/skill-creator/SKILL.md +1 -0
  87. package/skills/story-implementor/SKILL.md +1 -0
  88. package/skills/story-mapper/SKILL.md +1 -0
  89. package/skills/story-writer/SKILL.md +1 -0
  90. package/skills/test-coverage-checker/SKILL.md +1 -0
  91. package/skills/uiux-designer/SKILL.md +1 -0
  92. package/skills/unit-designer/SKILL.md +1 -0
  93. package/skills/unit-test-designer/SKILL.md +1 -0
  94. package/skills/unit-test-logic-designer/SKILL.md +1 -0
  95. package/templates/.claude/scripts/deny-check.sh +73 -12
  96. package/templates/.claude/settings.json +4 -0
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer application
3
3
  * @unit validator-system
4
+ * @work-item-id WI-212
4
5
  *
5
6
  * RunL3ValidatorsUseCase — H08-02: L3バリデータ実行
6
7
  */
@@ -8,6 +9,7 @@ import { ValidatorId } from '../../domain/value-objects/validator-id.js';
8
9
  import { ValidationResult } from '../../domain/value-objects/validation-result.js';
9
10
  import { ValidatorRegistry } from '../../domain/services/validator-registry.js';
10
11
  import { ValidatorExecutionService, ValidatorExecutionError } from '../../domain/services/validator-execution-service.js';
12
+ import { ValidatorLanguageCapabilityService } from '../../domain/services/validator-language-capability-service.js';
11
13
  import { ValidationResultContractMapper } from '../mappers/validation-result-contract-mapper.js';
12
14
  import type { ValidationResultContract } from '../dto/validation-result-contract.js';
13
15
  import type { RunL3ValidatorsInput } from '../dto/run-l3-validators-input.js';
@@ -43,6 +45,7 @@ export class RunL3ValidatorsUseCase {
43
45
  private readonly coverageReportPort?: RunL3ValidatorsUseCaseDeps['coverageReportPort'];
44
46
  private readonly securityScannerPort?: SecurityPatternScannerPort;
45
47
  private readonly performanceScannerPort?: PerformanceScannerPort;
48
+ private readonly languageCapabilityService = new ValidatorLanguageCapabilityService();
46
49
 
47
50
  constructor(deps: RunL3ValidatorsUseCaseDeps) {
48
51
  this.registry = deps.validatorRegistry;
@@ -77,36 +80,80 @@ export class RunL3ValidatorsUseCase {
77
80
  return [];
78
81
  }
79
82
 
80
- // coverageReportPort が存在する場合、カバレッジを取得して判定
81
- if (this.coverageReportPort) {
82
- const coverageData = await this.coverageReportPort.getCoverage();
83
+ const projectLanguages = await this.getProjectLanguages();
84
+ const { executableDefinitions, unsupportedResults, unsupportedValidatorIds } =
85
+ this.languageCapabilityService.splitDefinitions(definitions, projectLanguages);
86
+
87
+ const results = this.executionService.execute(executableDefinitions, [layerConfig]);
88
+ const overrideMap = new Map<string, ValidationResult>(
89
+ [...unsupportedResults, ...results].map((result) => [result.validatorId.value, result]),
90
+ );
91
+
92
+ // L3-003: カバレッジ判定(カバレッジゲートはオプトイン)
93
+ // - coverageThreshold 未設定 → SKIP(透過的に判定をスキップ。getCoverage() は呼ばない)
94
+ // - coverageThreshold 設定あり → getCoverage() を try/catch で包み FAIL-CLOSED で判定する
95
+ // - 閾値未満 → FAIL / 閾値以上 → PASS
96
+ // - レポート不在などで getCoverage() が失敗 → FAIL(合格扱いにしない)
97
+ // このブロックは例外を送出せず、L3-003 の per-validator 結果のみを差し替える。
98
+ // これにより兄弟バリデータ(L3-001/002/004 および L2/L4 バッチ)は常に通常実行される。
99
+ const l3003InScope =
100
+ !unsupportedValidatorIds.has('L3-003') &&
101
+ definitions.some((d) => d.validatorId.value === 'L3-003');
102
+ if (this.coverageReportPort && l3003InScope) {
103
+ const l3003Id = ValidatorId.create('L3-003');
83
104
  const threshold = layerConfig.getThreshold('coverageThreshold');
84
105
 
85
- // カバレッジ不足の場合は fail 結果を生成
86
- if (threshold !== null && coverageData.overallCoverage < threshold) {
87
- const deficit = threshold - coverageData.overallCoverage;
88
-
89
- // 他のバリデータを実行
90
- const otherDefs = definitions.filter((d) => d.validatorId.value !== 'L3-003');
91
- const otherResults = this.executionService.execute(otherDefs, [layerConfig]);
92
- const otherContracts = this.mapper.toContracts(otherResults);
93
-
94
- return [
95
- ...otherContracts.filter((r) => r.validatorId !== 'L3-003'),
96
- {
97
- validatorId: 'L3-003',
98
- passed: false,
99
- errors: [{ code: 'L3-003', severity: 'error', message: `カバレッジ不足: 現在値 ${coverageData.overallCoverage}%、不足 ${deficit}%` , suggestion: `テストカバレッジを ${threshold}% 以上に引き上げてください` }],
100
- durationMs: 0,
101
- skipped: false,
102
- },
103
- ];
106
+ if (threshold === null) {
107
+ overrideMap.set(
108
+ 'L3-003',
109
+ ValidationResult.skipWithReason(
110
+ l3003Id,
111
+ 'coverageThreshold が未設定のためカバレッジ判定をスキップ(カバレッジゲートはオプトイン)',
112
+ ),
113
+ );
114
+ } else {
115
+ try {
116
+ const coverageData = await this.coverageReportPort.getCoverage();
117
+ if (coverageData.overallCoverage < threshold) {
118
+ overrideMap.set(
119
+ 'L3-003',
120
+ ValidationResult.fail(
121
+ l3003Id,
122
+ [
123
+ {
124
+ code: 'L3-003',
125
+ severity: 'error',
126
+ message: `カバレッジ不足: 現在値 ${coverageData.overallCoverage}%、不足 ${threshold - coverageData.overallCoverage}%`,
127
+ suggestion: `テストカバレッジを ${threshold}% 以上に引き上げてください`,
128
+ },
129
+ ],
130
+ 0,
131
+ ),
132
+ );
133
+ } else {
134
+ overrideMap.set('L3-003', ValidationResult.pass(l3003Id, 0));
135
+ }
136
+ } catch {
137
+ // レポート不在などで取得失敗 → FAIL-CLOSED(例外は握りつぶし per-validator FAIL に変換)
138
+ overrideMap.set(
139
+ 'L3-003',
140
+ ValidationResult.fail(
141
+ l3003Id,
142
+ [
143
+ {
144
+ code: 'L3-003',
145
+ severity: 'error',
146
+ message: `coverageThreshold=${threshold}% が設定されていますがカバレッジレポートが見つかりません(テストをカバレッジ付きで実行してください)`,
147
+ suggestion: 'vitest --coverage 等でカバレッジレポートを生成してから再実行してください',
148
+ },
149
+ ],
150
+ 0,
151
+ ),
152
+ );
153
+ }
104
154
  }
105
155
  }
106
156
 
107
- const results = this.executionService.execute(definitions, [layerConfig]);
108
- const overrideMap = new Map<string, ValidationResult>(results.map((result) => [result.validatorId.value, result]));
109
-
110
157
  if (this.securityScannerPort) {
111
158
  const l3001Result = overrideMap.get('L3-001');
112
159
  if (l3001Result && !l3001Result.skipped) {
@@ -153,4 +200,8 @@ export class RunL3ValidatorsUseCase {
153
200
  );
154
201
  return this.mapper.toContracts(finalResults);
155
202
  }
203
+
204
+ private async getProjectLanguages(): Promise<readonly string[]> {
205
+ return this.configPort.getProjectLanguages ? await this.configPort.getProjectLanguages() : ['typescript'];
206
+ }
156
207
  }
@@ -3,6 +3,7 @@
3
3
  * @unit validator-system
4
4
  * @work-item-id WI-107 / WI-156
5
5
  * @work-item-id WI-217
6
+ * @work-item-id WI-212
6
7
  *
7
8
  * RunL4ValidatorsUseCase — H08-03: L4バリデータ実行
8
9
  */
@@ -11,6 +12,7 @@ import { ValidationResult } from '../../domain/value-objects/validation-result.j
11
12
  import { LayerConfig } from '../../domain/value-objects/layer-config.js';
12
13
  import { ValidatorRegistry } from '../../domain/services/validator-registry.js';
13
14
  import { ValidatorExecutionService, ValidatorExecutionError } from '../../domain/services/validator-execution-service.js';
15
+ import { ValidatorLanguageCapabilityService } from '../../domain/services/validator-language-capability-service.js';
14
16
  import { ValidationResultContractMapper } from '../mappers/validation-result-contract-mapper.js';
15
17
  import type { ValidationResultContract } from '../dto/validation-result-contract.js';
16
18
  import type { RunL4ValidatorsInput } from '../dto/run-l4-validators-input.js';
@@ -93,6 +95,7 @@ export class RunL4ValidatorsUseCase {
93
95
  private readonly pathRoots: { readonly inceptionRoot: string; readonly designRoot: string };
94
96
  private readonly checkDocFreshnessUseCase?: CheckDocFreshnessUseCasePort;
95
97
  private readonly validateDocPointersUseCase?: ValidateDocPointersUseCasePort;
98
+ private readonly languageCapabilityService = new ValidatorLanguageCapabilityService();
96
99
 
97
100
  constructor(deps: RunL4ValidatorsUseCaseDeps) {
98
101
  this.registry = deps.validatorRegistry;
@@ -141,11 +144,17 @@ export class RunL4ValidatorsUseCase {
141
144
  thresholds: { ...layerConfig.thresholds },
142
145
  strictOnly: layerConfig.strictOnly,
143
146
  preset: layerConfig.preset,
144
- })
147
+ })
145
148
  : layerConfig;
146
149
 
147
- const results = this.executionService.execute(definitions, [effectiveLayerConfig]);
148
- 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
+ );
149
158
 
150
159
  if (this.driftDetectionService) {
151
160
  const l4001Result = overrideMap.get('L4-001');
@@ -168,9 +177,12 @@ export class RunL4ValidatorsUseCase {
168
177
  const l4002Result = overrideMap.get('L4-002');
169
178
  if (l4002Result && !l4002Result.skipped) {
170
179
  const report = await this.consistencyCheckService.check(input.targetUnits ? [...input.targetUnits] : undefined);
171
- const reflectionResult = this.usesConfiguredDocumentRoots()
172
- ? await this.consistencyCheckService.checkWorkItemReflection(this.pathRoots)
173
- : undefined;
180
+ // WI-217: 標準レイアウト (docs/inception, docs/product/construction) でも
181
+ // reflection を必ず実行する。以前は usesConfiguredDocumentRoots() が false を
182
+ // 返すため標準構成では reflection が恒久的にスキップされ、L4-002 が常に PASS
183
+ // していた。description が存在しない場合はアダプタ側が skipReason を返すため、
184
+ // 常時実行しても未整備プロジェクトを誤って失敗させることはない。
185
+ const reflectionResult = await this.consistencyCheckService.checkWorkItemReflection(this.pathRoots);
174
186
  const architectureSemanticErrors = this.architectureSemanticAnalysisService
175
187
  ? await this.architectureSemanticAnalysisService.analyze()
176
188
  : [];
@@ -276,8 +288,8 @@ export class RunL4ValidatorsUseCase {
276
288
  return [...executionErrors, ...freshnessFindings];
277
289
  }
278
290
 
279
- private usesConfiguredDocumentRoots(): boolean {
280
- return this.pathRoots.inceptionRoot !== 'docs/inception' || this.pathRoots.designRoot !== 'docs/product/construction';
291
+ private async getProjectLanguages(): Promise<readonly string[]> {
292
+ return this.configPort.getProjectLanguages ? await this.configPort.getProjectLanguages() : ['typescript'];
281
293
  }
282
294
 
283
295
  private toPointerValidationHarnessErrors(output: ValidateDocPointersOutputContract): readonly ValidationResult['errors'][number][] {
@@ -29,6 +29,7 @@ import { FileSystemContractTraceabilityPolicyAdapter } from './infrastructure/ad
29
29
  import { PhaseDependencyPhaseGatePolicyAdapter } from './infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.js';
30
30
  import { TraceabilityMetadataPolicyAdapter } from './infrastructure/adapters/traceability-metadata-policy-adapter.js';
31
31
  import { NyquistAcCoveragePolicyAdapter } from './infrastructure/adapters/nyquist-ac-coverage-policy-adapter.js';
32
+ import { JsonCoverageReportAdapter } from './infrastructure/adapters/json-coverage-report-adapter.js';
32
33
  import { BiomeAstTestQualityAnalyzerAdapter } from './infrastructure/adapters/biome-ast-test-quality-analyzer-adapter.js';
33
34
  import { FileSystemSecurityPatternScannerAdapter } from './infrastructure/adapters/file-system-security-pattern-scanner-adapter.js';
34
35
  import { AstPerformanceScannerAdapter } from './infrastructure/adapters/ast-performance-scanner-adapter.js';
@@ -54,7 +55,7 @@ const DEFAULT_CONFIG = {
54
55
  preset: 'standard' as const,
55
56
  layers: {
56
57
  L2: { enabled: true, validators: ['L2-001', 'L2-002', 'L2-003', 'L2-013', 'L2-014', 'L2-015'] },
57
- L3: { enabled: true, validators: ['L3-001', 'L3-002', 'L3-003', 'L3-004'], coverageThreshold: 90, bundleSizeLimit: 512000 },
58
+ L3: { enabled: true, validators: ['L3-001', 'L3-002', 'L3-003', 'L3-004'], coverageThreshold: 90, bundleSizeLimit: 512000, requirementMatrixPath: '.harness/requirement-test-matrix.json' },
58
59
  L4: { enabled: true, validators: ['L4-001', 'L4-002', 'L4-003', 'L4-004', 'L4-005', 'L4-006'] },
59
60
  },
60
61
  paths: {
@@ -149,6 +150,11 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
149
150
  const phaseGatePolicyPort = new PhaseDependencyPhaseGatePolicyAdapter();
150
151
  const metadataPolicyPort = new TraceabilityMetadataPolicyAdapter();
151
152
  const acCoveragePolicyPort = new NyquistAcCoveragePolicyAdapter();
153
+ // L3-003: テストカバレッジレポート(vitest --coverage の json-summary 出力)を読み取る。
154
+ // 未配線だと L3-003 は装飾的な pass になり 90% 閾値が形骸化するため必ず配線する。
155
+ const coverageReportPort = new JsonCoverageReportAdapter(
156
+ join(process.cwd(), 'coverage', 'coverage-summary.json'),
157
+ );
152
158
  const testQualityAnalyzerPort = new BiomeAstTestQualityAnalyzerAdapter();
153
159
  const securityScannerPort = new FileSystemSecurityPatternScannerAdapter();
154
160
  const performanceScannerPort = new AstPerformanceScannerAdapter();
@@ -189,6 +195,7 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
189
195
  validatorConfigPort: configPort,
190
196
  contractMapper,
191
197
  acCoveragePolicyPort,
198
+ coverageReportPort,
192
199
  securityScannerPort,
193
200
  performanceScannerPort,
194
201
  });
@@ -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
  }
@@ -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
+ }
@@ -40,12 +40,14 @@ export class FileSystemSecurityPatternScannerAdapter implements SecurityPatternS
40
40
  for (const filePath of targetPaths) {
41
41
  try {
42
42
  const content = await readFile(filePath, 'utf-8');
43
- if (content.includes(ALLOWLIST_MARKER)) {
44
- continue;
45
- }
46
43
  const lines = content.split('\n');
47
44
  lines.forEach((line, idx) => {
48
- if (isAllowlisted(line, content)) {
45
+ // WI-120: allowlist は行/領域スコープ。以前はファイル内のどこかに
46
+ // マーカーが 1 つでもあればファイル全体をスキップしていたため、同一
47
+ // ファイル内の本物の秘密情報も検出漏れしていた。マーカーは当該行、
48
+ // または直前行(次行抑止コメント)にある場合のみ有効とする。
49
+ const previousLine = idx > 0 ? lines[idx - 1] : undefined;
50
+ if (isAllowlisted(line, previousLine)) {
49
51
  return;
50
52
  }
51
53
  for (const { pattern, description, ruleId } of SECURITY_PATTERNS) {
@@ -71,9 +73,17 @@ export class FileSystemSecurityPatternScannerAdapter implements SecurityPatternS
71
73
  }
72
74
  }
73
75
 
74
- function isAllowlisted(line: string, content: string): boolean {
76
+ /**
77
+ * 当該行が allowlist されているか判定する(行/領域スコープ)。
78
+ *
79
+ * - 当該行にマーカーがある → 許可
80
+ * - 直前行にマーカーがある(次行抑止コメント) → 許可
81
+ *
82
+ * ファイル全体を対象にしないことで、同一ファイル内の本物の秘密情報を検出できる。
83
+ */
84
+ function isAllowlisted(line: string, previousLine: string | undefined): boolean {
75
85
  if (line.includes(ALLOWLIST_MARKER)) return true;
76
- return /@example|dummy|placeholder/i.test(line) && content.includes(ALLOWLIST_MARKER);
86
+ return previousLine?.includes(ALLOWLIST_MARKER) ?? false;
77
87
  }
78
88
 
79
89
  function redactSecret(secretValue: string): string {
@@ -18,6 +18,13 @@ function normalizePath(value: string): string {
18
18
  return value.replace(/\\/g, '/');
19
19
  }
20
20
 
21
+ // Separator-agnostic check for whether a path points at a work-item
22
+ // description file. Exported so the Windows (`\`) normalization is unit
23
+ // testable without depending on the host filesystem separator.
24
+ export function isDescriptionFilePath(value: string): boolean {
25
+ return normalizePath(value).endsWith(`/${DESCRIPTION_FILE}`);
26
+ }
27
+
21
28
  async function listFiles(root: string): Promise<string[]> {
22
29
  try {
23
30
  const entries = await readdir(root, { withFileTypes: true });
@@ -66,7 +73,9 @@ export class FileSystemWorkItemReflectionAdapter implements WorkItemReflectionPo
66
73
  const inceptionRoot = join(this.projectRoot, input.inceptionRoot);
67
74
  const designRoot = join(this.projectRoot, input.designRoot);
68
75
  const descriptionFiles = (await listFiles(inceptionRoot))
69
- .filter((path) => path.endsWith(`/${DESCRIPTION_FILE}`));
76
+ // Normalize separators first so Windows paths (which use `\`) still match
77
+ // the `/description.md` suffix.
78
+ .filter(isDescriptionFilePath);
70
79
 
71
80
  if (descriptionFiles.length === 0) {
72
81
  return {
@@ -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;
@@ -137,9 +137,7 @@ function extractExports(content: string): string[] {
137
137
  function resolveImportTarget(fromFile: string, specifier: string, fileSet: ReadonlySet<string>): string | null {
138
138
  if (!specifier.startsWith('.')) return null;
139
139
  const base = resolve(dirname(fromFile), specifier);
140
- const candidates = extname(base)
141
- ? [base]
142
- : [`${base}.ts`, `${base}.tsx`, join(base, 'index.ts'), join(base, 'index.tsx')];
140
+ const candidates = buildResolutionCandidates(base);
143
141
  for (const candidate of candidates) {
144
142
  const normalized = normalize(candidate);
145
143
  if (fileSet.has(normalized)) return normalized;
@@ -147,6 +145,40 @@ function resolveImportTarget(fromFile: string, specifier: string, fileSet: Reado
147
145
  return null;
148
146
  }
149
147
 
148
+ /**
149
+ * import 指定子から実ファイル候補を生成する。
150
+ *
151
+ * このプロジェクトは ESM 規約に従い `./foo.js` と書いて実体 `foo.ts` を参照する
152
+ * (TypeScript の `.js` 拡張子付き import)。素朴に extname を見ると `.js` 実体を
153
+ * 探しに行き解決に失敗するため、`.js`/`.jsx`/`.mjs`/`.cjs` 拡張子は対応する
154
+ * TypeScript 拡張子(およびディレクトリの index)へマッピングした候補も加える。
155
+ */
156
+ function buildResolutionCandidates(base: string): string[] {
157
+ const ext = extname(base);
158
+ const JS_TO_TS: Record<string, readonly string[]> = {
159
+ '.js': ['.ts', '.tsx'],
160
+ '.jsx': ['.tsx', '.ts'],
161
+ '.mjs': ['.mts', '.ts'],
162
+ '.cjs': ['.cts', '.ts'],
163
+ };
164
+
165
+ if (ext) {
166
+ const mapped = JS_TO_TS[ext];
167
+ if (mapped) {
168
+ const withoutExt = base.slice(0, base.length - ext.length);
169
+ // 例: `./foo.js` → `foo.ts` / `foo.tsx`、`./dir.js`(ディレクトリ)→ `dir/index.ts`
170
+ return [
171
+ ...mapped.map((tsExt) => `${withoutExt}${tsExt}`),
172
+ base,
173
+ ...mapped.map((tsExt) => join(withoutExt, `index${tsExt}`)),
174
+ ];
175
+ }
176
+ return [base];
177
+ }
178
+
179
+ return [`${base}.ts`, `${base}.tsx`, join(base, 'index.ts'), join(base, 'index.tsx')];
180
+ }
181
+
150
182
  function classifyDeadCodeExclusion(filePath: string): string | undefined {
151
183
  const normalized = filePath.replaceAll('\\', '/');
152
184
  if (/(^|\/)__tests__\//.test(normalized) || /\.test\.ts$/.test(normalized) || /\.it\.test\.ts$/.test(normalized)) {
@@ -3,39 +3,111 @@
3
3
  * @unit validator-system
4
4
  *
5
5
  * NyquistAcCoveragePolicyAdapter — AcCoveragePolicyPort実装
6
+ *
7
+ * FAIL-CLOSED: nyquist-validation モジュールの実行時に例外が発生した場合は
8
+ * passed=false(不合格)として扱い、エラーを結果に含めて上位へ通知する。
9
+ * かつては catch 節で passed=true を返す fail-open だったが、AC 網羅ゲート
10
+ * (L3-004)を無効化する重大な品質防御の抜け穴だったため fail-closed へ是正した。
11
+ *
12
+ * REAL REGISTRY: storyId 整合性検査に必要な有効 storyId 一覧を
13
+ * traceability-model の StoryCatalog から取得して nyquist モジュールへ配線する。
14
+ * かつては `getStoryIds: async () => []`(空スタブ)だったため、正当かつ完全網羅の
15
+ * マトリクスでも全 storyId が「未登録」と判定され L3-004 が構造的に PASS 不能だった。
16
+ *
17
+ * MATRIX PATH: requirement-test-matrix.json のパスは config
18
+ * (layers.L3.requirementMatrixPath)から供給される。未指定時は既定値
19
+ * `.harness/requirement-test-matrix.json` を用いる。マトリクスが不在の場合は
20
+ * fail-closed で「設定されているが不在」という実行可能なメッセージを返す。
6
21
  */
22
+ import { access } from 'node:fs/promises';
23
+ import { isAbsolute, join } from 'node:path';
7
24
  import type { AcCoveragePolicyPort } from '../../domain/ports/ac-coverage-policy-port.js';
8
25
  import type { HarnessErrorLike } from '../../domain/value-objects/validation-result.js';
9
26
 
27
+ const DEFAULT_MATRIX_PATH = '.harness/requirement-test-matrix.json';
28
+
29
+ function toL3004Error(message: string, suggestion: string): HarnessErrorLike {
30
+ return {
31
+ code: { value: 'L3-004', toString: () => 'L3-004' },
32
+ severity: { value: 'error', toString: () => 'error' },
33
+ message,
34
+ suggestion,
35
+ };
36
+ }
37
+
10
38
  export class NyquistAcCoveragePolicyAdapter implements AcCoveragePolicyPort {
11
39
  async checkCoverage(context: { matrixFilePath?: string }): Promise<{
12
40
  passed: boolean;
13
41
  errors: readonly HarnessErrorLike[];
14
42
  }> {
43
+ const rootDir = process.cwd();
44
+ const relativeOrAbsolute =
45
+ context.matrixFilePath && context.matrixFilePath.length > 0
46
+ ? context.matrixFilePath
47
+ : DEFAULT_MATRIX_PATH;
48
+ const matrixFilePath = isAbsolute(relativeOrAbsolute)
49
+ ? relativeOrAbsolute
50
+ : join(rootDir, relativeOrAbsolute);
51
+
52
+ // マトリクス不在は threshold-configured-but-absent と同様に fail-closed で
53
+ // 明確なメッセージを返す(ENOENT の生メッセージで落とさない)。
54
+ try {
55
+ await access(matrixFilePath);
56
+ } catch {
57
+ return {
58
+ passed: false,
59
+ errors: [
60
+ toL3004Error(
61
+ `AC網羅マトリクスが見つかりません: ${relativeOrAbsolute}(L3-004 は fail-closed)`,
62
+ `${relativeOrAbsolute} を生成してください(phasegate:generate-matrix)。パスは config の layers.L3.requirementMatrixPath で変更できます`,
63
+ ),
64
+ ],
65
+ };
66
+ }
67
+
15
68
  try {
69
+ // 有効 storyId 一覧を traceability-model の StoryCatalog から取得する(REAL registry)
70
+ const storyIds = await this.loadValidStoryIds(rootDir);
71
+
16
72
  const { createNyquistValidationModule } = await import('../../../nyquist-validation/composition-root.js');
17
73
  const mod = createNyquistValidationModule({
18
- rootDir: process.cwd(),
19
- getStoryIds: async () => [],
20
- } as {
21
- rootDir: string;
22
- getStoryIds: () => Promise<readonly string[]>;
74
+ getStoryIds: async () => storyIds,
23
75
  });
24
76
  const output = await mod.checkAcCoverageGateUseCase.execute({
25
- matrixFilePath: context.matrixFilePath ?? '',
77
+ matrixFilePath,
26
78
  });
27
79
 
28
80
  return {
29
81
  passed: output.passed,
30
- errors: output.errors.map((err) => ({
31
- code: { value: 'L3-004', toString: () => 'L3-004' },
32
- severity: { value: 'error', toString: () => 'error' },
33
- message: err.message,
34
- suggestion: '',
35
- })),
82
+ errors: output.errors.map((err) =>
83
+ toL3004Error(err.message, ''),
84
+ ),
85
+ };
86
+ } catch (error) {
87
+ const message = error instanceof Error ? error.message : String(error);
88
+ // FAIL-CLOSED: 例外を握り潰して合格扱いにせず、不合格として通知する
89
+ return {
90
+ passed: false,
91
+ errors: [
92
+ toL3004Error(
93
+ `AC網羅ゲートの検査に失敗しました(fail-closed): ${message}`,
94
+ 'requirement-test-matrix.json の存在・形式・スキーマ整合性を確認してください',
95
+ ),
96
+ ],
36
97
  };
37
- } catch {
38
- return { passed: true, errors: [] };
39
98
  }
40
99
  }
100
+
101
+ private async loadValidStoryIds(rootDir: string): Promise<readonly string[]> {
102
+ const { createConfigFoundationModule } = await import('../../../config-foundation/composition-root.js');
103
+ const configModule = createConfigFoundationModule();
104
+ const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
105
+
106
+ const { createTraceabilityModelModule } = await import('../../../traceability-model/composition-root.js');
107
+ const traceModule = createTraceabilityModelModule(rootDir, {
108
+ pathRoots: { designDocsRoot: resolvedConfig.config.paths.designDocs },
109
+ });
110
+ const storyIds = await traceModule.storyCatalog.getAllStoryIds();
111
+ return storyIds.map((s) => s.value);
112
+ }
41
113
  }
@@ -51,9 +51,35 @@ export class PhaseDependencyPhaseGatePolicyAdapter implements PhaseGatePolicyPor
51
51
  };
52
52
  }
53
53
 
54
- return { satisfied: true, violations: [] };
55
- } catch {
56
- return { satisfied: true, violations: [] };
54
+ // Fail-closed: an unexpected exit code means prerequisites could not be confirmed.
55
+ console.error(`[validator-system] phase gate check returned unexpected exit code ${result.exitCode}`);
56
+ return {
57
+ satisfied: false,
58
+ violations: [
59
+ {
60
+ code: { value: 'L2-002', toString: () => 'L2-002' },
61
+ severity: { value: 'error', toString: () => 'error' },
62
+ message: `phase gate check returned unexpected exit code ${result.exitCode}; prerequisites cannot be confirmed`,
63
+ suggestion: 'phase gate prerequisites could not be evaluated; treating as NOT satisfied (fail-closed)',
64
+ },
65
+ ],
66
+ };
67
+ } catch (err) {
68
+ // Fail-closed: if the phase-dependency-model could not be loaded/executed we cannot
69
+ // confirm prerequisites are met, so treat them as NOT satisfied rather than opening the gate.
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ console.error(`[validator-system] phase gate prerequisite check failed to run: ${message}`);
72
+ return {
73
+ satisfied: false,
74
+ violations: [
75
+ {
76
+ code: { value: 'L2-002', toString: () => 'L2-002' },
77
+ severity: { value: 'error', toString: () => 'error' },
78
+ message: `phase gate prerequisite check could not be evaluated: ${message}`,
79
+ suggestion: 'phase gate prerequisites could not be evaluated; treating as NOT satisfied (fail-closed)',
80
+ },
81
+ ],
82
+ };
57
83
  }
58
84
  }
59
85
  }
@@ -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