phasegate 0.152.0 → 0.152.2

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 CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.152.2] - 2026-05-12
11
+
12
+ ### Fixed
13
+
14
+ - **WI status evidence closure / WI-119 / WI-120 / WI-121 / WI-127 / WI-128** — adds missing implementation/test traceability annotations and applies derived WI statuses so the remaining G1/G5 backlog items no longer report stale or reflected-only status.
15
+
16
+ ## [0.152.1] - 2026-05-12
17
+
18
+ ### Fixed
19
+
20
+ - **G5 post-publish dogfood / WI-134 / WI-135** — wires architecture semantic policy into `L4-002` runtime validation so side-effect capability denials and decision-placement advisories are reported with file zone, evidence, confidence, and suggested owner zone.
21
+
10
22
  ## [0.152.0] - 2026-05-12
11
23
 
12
24
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.152.0",
3
+ "version": "0.152.2",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "Phasegate — AI-agnostic quality defense toolkit. Enforces structural integrity between design intent and code.",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  // @unit ci-governance
2
2
  // @layer infrastructure
3
- // @work-item-id WI-124
3
+ // @work-item-id WI-124 / WI-128
4
4
 
5
5
  import type { ValidatorIdRegistryPort } from '../../domain/ports/validator-id-registry-port.js';
6
6
  import { buildDefaultRegistry } from '../../../validator-system/composition-root.js';
@@ -8,15 +8,57 @@ import type { HarnessConfigV2 } from '../../domain/harness-config.js';
8
8
  export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefined): object | undefined {
9
9
  if (!resolvedConfig) return undefined;
10
10
 
11
+ const l3Validators = normalizeValidators(resolvedConfig.layers.L3.validators, {
12
+ security: 'L3-001',
13
+ performance: 'L3-002',
14
+ coverage: 'L3-003',
15
+ nyquist: 'L3-004',
16
+ }, /^L3-\d{3}$/);
17
+ const l4Validators = normalizeValidators(resolvedConfig.layers.L4.validators, {
18
+ 'drift-detect': 'L4-001',
19
+ 'drift-detector': 'L4-001',
20
+ 'consistency-check': 'L4-002',
21
+ 'consistency-checker': 'L4-002',
22
+ 'dead-code': 'L4-003',
23
+ 'dead-code-detector': 'L4-003',
24
+ 'doc-freshness': 'L4-004',
25
+ 'doc-freshness-checker': 'L4-004',
26
+ 'pointer-validation': 'L4-005',
27
+ 'pointer-validator': 'L4-005',
28
+ }, /^L4-\d{3}$/);
29
+
11
30
  return {
12
31
  project: { preset: resolvedConfig.project.preset },
13
32
  layers: {
14
33
  L2: { enabled: resolvedConfig.layers.L2.enabled, validators: ['L2-001', 'L2-002', 'L2-003', 'L2-013', 'L2-014', 'L2-015'] },
15
- L3: { enabled: resolvedConfig.layers.L3.enabled },
16
- L4: { enabled: resolvedConfig.layers.L4.enabled, validators: resolvedConfig.layers.L4.validators },
34
+ L3: {
35
+ enabled: resolvedConfig.layers.L3.enabled,
36
+ ...(l3Validators.length > 0 ? { validators: l3Validators } : {}),
37
+ coverageThreshold: resolvedConfig.layers.L3.coverageThreshold,
38
+ },
39
+ L4: {
40
+ enabled: resolvedConfig.layers.L4.enabled,
41
+ ...(l4Validators.length > 0 ? { validators: l4Validators } : {}),
42
+ },
43
+ },
44
+ harnesses: {
45
+ bundleSizeLimit: resolvedConfig.harnesses.bundleSizeLimit,
46
+ deadCodeGC: resolvedConfig.harnesses.deadCodeGC,
17
47
  },
48
+ architecture: resolvedConfig.architecture,
18
49
  validate: {
19
50
  failOnWarning: resolvedConfig.validate.failOnWarning,
20
51
  },
21
52
  };
22
53
  }
54
+
55
+ function normalizeValidators(
56
+ validators: readonly string[],
57
+ aliases: Readonly<Record<string, string>>,
58
+ idPattern: RegExp,
59
+ ): readonly string[] {
60
+ const normalized = validators
61
+ .map((validator) => aliases[validator] ?? validator)
62
+ .filter((validator) => idPattern.test(validator));
63
+ return [...new Set(normalized)];
64
+ }
@@ -1,5 +1,6 @@
1
1
  // @unit harness-api
2
2
  // @layer infrastructure
3
+ // @work-item-id WI-127
3
4
  // Note: import.meta.url を使わず、呼び出し元 (main.ts) がパスを解決して渡す設計。
4
5
 
5
6
  import { promises as fs } from "node:fs";
@@ -17,6 +17,7 @@ import type { ValidatorConfigPort } from '../../domain/ports/validator-config-po
17
17
  import type { DriftDetectionService } from '../../domain/services/l4/drift-detection-service.js';
18
18
  import type { ConsistencyCheckService } from '../../domain/services/l4/consistency-check-service.js';
19
19
  import type { DeadCodeDetectionService } from '../../domain/services/l4/dead-code-detection-service.js';
20
+ import type { ArchitectureSemanticAnalysisService } from '../../domain/services/l4/architecture-semantic-analysis-service.js';
20
21
 
21
22
  interface ScheduledHarnessErrorContract {
22
23
  readonly severity: string;
@@ -65,6 +66,7 @@ export interface RunL4ValidatorsUseCaseDeps {
65
66
  driftDetectionService?: DriftDetectionService;
66
67
  consistencyCheckService?: ConsistencyCheckService;
67
68
  deadCodeDetectionService?: DeadCodeDetectionService;
69
+ architectureSemanticAnalysisService?: ArchitectureSemanticAnalysisService;
68
70
  checkDocFreshnessUseCase?: CheckDocFreshnessUseCasePort;
69
71
  validateDocPointersUseCase?: ValidateDocPointersUseCasePort;
70
72
  }
@@ -77,6 +79,7 @@ export class RunL4ValidatorsUseCase {
77
79
  private readonly driftDetectionService?: DriftDetectionService;
78
80
  private readonly consistencyCheckService?: ConsistencyCheckService;
79
81
  private readonly deadCodeDetectionService?: DeadCodeDetectionService;
82
+ private readonly architectureSemanticAnalysisService?: ArchitectureSemanticAnalysisService;
80
83
  private readonly checkDocFreshnessUseCase?: CheckDocFreshnessUseCasePort;
81
84
  private readonly validateDocPointersUseCase?: ValidateDocPointersUseCasePort;
82
85
 
@@ -88,6 +91,7 @@ export class RunL4ValidatorsUseCase {
88
91
  this.driftDetectionService = deps.driftDetectionService;
89
92
  this.consistencyCheckService = deps.consistencyCheckService;
90
93
  this.deadCodeDetectionService = deps.deadCodeDetectionService;
94
+ this.architectureSemanticAnalysisService = deps.architectureSemanticAnalysisService;
91
95
  this.checkDocFreshnessUseCase = deps.checkDocFreshnessUseCase;
92
96
  this.validateDocPointersUseCase = deps.validateDocPointersUseCase;
93
97
  }
@@ -148,10 +152,13 @@ export class RunL4ValidatorsUseCase {
148
152
  const l4002Result = overrideMap.get('L4-002');
149
153
  if (l4002Result && !l4002Result.skipped) {
150
154
  const report = await this.consistencyCheckService.check(input.targetUnits ? [...input.targetUnits] : undefined);
151
- if (report.hasMismatches()) {
155
+ const architectureSemanticErrors = this.architectureSemanticAnalysisService
156
+ ? await this.architectureSemanticAnalysisService.analyze()
157
+ : [];
158
+ if (report.hasMismatches() || architectureSemanticErrors.length > 0) {
152
159
  overrideMap.set(
153
160
  'L4-002',
154
- ValidationResult.fail(ValidatorId.create('L4-002'), [...report.toHarnessErrors()], 0),
161
+ ValidationResult.fail(ValidatorId.create('L4-002'), [...report.toHarnessErrors(), ...architectureSemanticErrors], 0),
155
162
  );
156
163
  }
157
164
  }
@@ -35,9 +35,11 @@ import { MarkdownDesignDocumentAdapter } from './infrastructure/adapters/markdow
35
35
  import { BiomeAstSourceCodeAnalyzerAdapter } from './infrastructure/adapters/biome-ast-source-code-analyzer-adapter.js';
36
36
  import { AdrFoundationReferenceAdapter } from './infrastructure/adapters/adr-foundation-reference-adapter.js';
37
37
  import { ImportGraphSourceAnalysisAdapter } from './infrastructure/adapters/import-graph-source-analysis-adapter.js';
38
+ import { FileSystemArchitectureSemanticSourceAdapter } from './infrastructure/adapters/file-system-architecture-semantic-source-adapter.js';
38
39
  import { DriftDetectionService } from './domain/services/l4/drift-detection-service.js';
39
40
  import { ConsistencyCheckService } from './domain/services/l4/consistency-check-service.js';
40
41
  import { DeadCodeDetectionService } from './domain/services/l4/dead-code-detection-service.js';
42
+ import { ArchitectureSemanticAnalysisService, type ArchitectureSemanticPolicy } from './domain/services/l4/architecture-semantic-analysis-service.js';
41
43
  import { buildPhase2Extensions } from '../phase2-extensions/composition-root.js';
42
44
  import { RunValidatorsHandler } from './presentation/handlers/run-validators-handler.js';
43
45
  import { RunQuickModeHandler } from './presentation/handlers/run-quick-mode-handler.js';
@@ -53,6 +55,20 @@ const DEFAULT_CONFIG = {
53
55
  L4: { enabled: true, validators: ['L4-001', 'L4-002', 'L4-003', 'L4-004', 'L4-005'] },
54
56
  },
55
57
  validate: { failOnWarning: false },
58
+ architecture: {
59
+ capabilityPolicies: {
60
+ domain: { allowed: [], denied: ['filesystem', 'network', 'database', 'process-env', 'subprocess', 'user-io'] },
61
+ application: { allowed: ['time', 'random'], denied: ['filesystem', 'network', 'database', 'subprocess'] },
62
+ infrastructure: { allowed: ['filesystem', 'network', 'database', 'process-env', 'time', 'random', 'subprocess', 'user-io'], denied: [] },
63
+ presentation: { allowed: ['user-io', 'time'], denied: ['database', 'subprocess'] },
64
+ },
65
+ decisionPolicies: {
66
+ domain: { expected: ['business-rule-branch', 'validation-rule', 'state-transition'], advisoryOnly: true },
67
+ application: { expected: ['policy-selection', 'error-construction'], advisoryOnly: true },
68
+ infrastructure: { expected: ['error-construction'], advisoryOnly: true },
69
+ presentation: { expected: ['validation-rule', 'error-construction'], advisoryOnly: true },
70
+ },
71
+ },
56
72
  };
57
73
 
58
74
  /** バリデータ定義カタログ */
@@ -171,6 +187,7 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
171
187
  const sourceCodeAnalyzerAdapter = new BiomeAstSourceCodeAnalyzerAdapter();
172
188
  const adrReferencePort = new AdrFoundationReferenceAdapter();
173
189
  const sourceAnalysisPort = new ImportGraphSourceAnalysisAdapter();
190
+ const architectureSemanticSourcePort = new FileSystemArchitectureSemanticSourceAdapter();
174
191
 
175
192
  const driftDetectionService = new DriftDetectionService({
176
193
  designDocumentPort: markdownDesignDocumentPort,
@@ -183,6 +200,10 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
183
200
  const deadCodeDetectionService = new DeadCodeDetectionService({
184
201
  sourceAnalysisPort,
185
202
  });
203
+ const architectureSemanticAnalysisService = new ArchitectureSemanticAnalysisService({
204
+ sourcePort: architectureSemanticSourcePort,
205
+ policy: toArchitectureSemanticPolicy(configData),
206
+ });
186
207
  const phase2Extensions = buildPhase2Extensions(process.cwd(), configData as never);
187
208
 
188
209
  const runL4ValidatorsUseCase = new RunL4ValidatorsUseCase({
@@ -193,6 +214,7 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
193
214
  driftDetectionService,
194
215
  consistencyCheckService,
195
216
  deadCodeDetectionService,
217
+ architectureSemanticAnalysisService,
196
218
  checkDocFreshnessUseCase: phase2Extensions.checkDocFreshnessUseCase,
197
219
  validateDocPointersUseCase: phase2Extensions.validateDocPointersUseCase,
198
220
  });
@@ -247,3 +269,11 @@ export function createValidatorSystemModule(config?: object): ValidatorSystemMod
247
269
  handlers,
248
270
  };
249
271
  }
272
+
273
+ function toArchitectureSemanticPolicy(configData: typeof DEFAULT_CONFIG): ArchitectureSemanticPolicy {
274
+ const architecture = configData.architecture;
275
+ return {
276
+ capabilityPolicies: architecture?.capabilityPolicies ?? DEFAULT_CONFIG.architecture.capabilityPolicies,
277
+ decisionPolicies: architecture?.decisionPolicies ?? DEFAULT_CONFIG.architecture.decisionPolicies,
278
+ } as ArchitectureSemanticPolicy;
279
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @layer domain
3
+ * @unit validator-system
4
+ * @work-item-id WI-134, WI-135
5
+ *
6
+ * ArchitectureSemanticAnalysisService
7
+ * Architecture preset の capability / decision policy と source semantic signal を照合する advisory service.
8
+ */
9
+ import type { HarnessErrorLike } from '../../value-objects/validation-result.js';
10
+
11
+ export type EffectCapability =
12
+ | 'filesystem'
13
+ | 'network'
14
+ | 'database'
15
+ | 'process-env'
16
+ | 'time'
17
+ | 'random'
18
+ | 'subprocess'
19
+ | 'user-io';
20
+
21
+ export type DecisionSignal =
22
+ | 'business-rule-branch'
23
+ | 'validation-rule'
24
+ | 'error-construction'
25
+ | 'state-transition'
26
+ | 'policy-selection';
27
+
28
+ export interface ArchitectureCapabilityPolicy {
29
+ readonly allowed: readonly EffectCapability[];
30
+ readonly denied: readonly EffectCapability[];
31
+ }
32
+
33
+ export interface ArchitectureDecisionPolicy {
34
+ readonly expected: readonly DecisionSignal[];
35
+ readonly advisoryOnly: boolean;
36
+ }
37
+
38
+ export interface ArchitectureSemanticPolicy {
39
+ readonly capabilityPolicies: Readonly<Record<string, ArchitectureCapabilityPolicy>>;
40
+ readonly decisionPolicies: Readonly<Record<string, ArchitectureDecisionPolicy>>;
41
+ }
42
+
43
+ export interface SourceSemanticSignal {
44
+ readonly kind: EffectCapability | DecisionSignal;
45
+ readonly evidence: string;
46
+ readonly confidence: number;
47
+ }
48
+
49
+ export interface SourceSemanticFile {
50
+ readonly filePath: string;
51
+ readonly zone: string;
52
+ readonly effects: readonly SourceSemanticSignal[];
53
+ readonly decisions: readonly SourceSemanticSignal[];
54
+ }
55
+
56
+ export interface ArchitectureSemanticSourcePort {
57
+ collectSourceSemantics(): Promise<readonly SourceSemanticFile[]>;
58
+ }
59
+
60
+ export interface ArchitectureSemanticAnalysisServiceDeps {
61
+ readonly sourcePort: ArchitectureSemanticSourcePort;
62
+ readonly policy: ArchitectureSemanticPolicy;
63
+ }
64
+
65
+ const DEFAULT_DECISION_OWNER: Readonly<Record<DecisionSignal, string>> = Object.freeze({
66
+ 'business-rule-branch': 'domain',
67
+ 'validation-rule': 'domain',
68
+ 'error-construction': 'application',
69
+ 'state-transition': 'domain',
70
+ 'policy-selection': 'application',
71
+ });
72
+
73
+ export class ArchitectureSemanticAnalysisService {
74
+ private readonly sourcePort: ArchitectureSemanticSourcePort;
75
+ private readonly policy: ArchitectureSemanticPolicy;
76
+
77
+ constructor(deps: ArchitectureSemanticAnalysisServiceDeps) {
78
+ this.sourcePort = deps.sourcePort;
79
+ this.policy = deps.policy;
80
+ }
81
+
82
+ async analyze(): Promise<readonly HarnessErrorLike[]> {
83
+ const files = await this.sourcePort.collectSourceSemantics();
84
+ return files.flatMap((file) => [
85
+ ...this.toCapabilityFindings(file),
86
+ ...this.toDecisionFindings(file),
87
+ ]);
88
+ }
89
+
90
+ private toCapabilityFindings(file: SourceSemanticFile): HarnessErrorLike[] {
91
+ const zonePolicy = this.policy.capabilityPolicies[file.zone];
92
+ if (!zonePolicy) return [];
93
+ const denied = new Set(zonePolicy.denied);
94
+
95
+ return file.effects
96
+ .filter((effect) => denied.has(effect.kind as EffectCapability))
97
+ .map((effect) => this.toHarnessError(
98
+ `Side effect capability denied: ${effect.kind} in ${file.zone} at ${file.filePath}`,
99
+ `confidence=${effect.confidence}; evidence=${effect.evidence}; suggested owner zone=infrastructure/adapters`,
100
+ ));
101
+ }
102
+
103
+ private toDecisionFindings(file: SourceSemanticFile): HarnessErrorLike[] {
104
+ const zonePolicy = this.policy.decisionPolicies[file.zone];
105
+ if (!zonePolicy) return [];
106
+ const expected = new Set(zonePolicy.expected);
107
+
108
+ return file.decisions
109
+ .filter((decision) => !expected.has(decision.kind as DecisionSignal))
110
+ .map((decision) => this.toHarnessError(
111
+ `Decision placement advisory: ${decision.kind} observed in ${file.zone} at ${file.filePath}`,
112
+ `confidence=${decision.confidence}; evidence=${decision.evidence}; suggested owner zone=${DEFAULT_DECISION_OWNER[decision.kind as DecisionSignal] ?? 'domain'}; rollout=advisory`,
113
+ ));
114
+ }
115
+
116
+ private toHarnessError(message: string, suggestion: string): HarnessErrorLike {
117
+ return {
118
+ code: { value: 'L4-002', toString: () => 'L4-002' },
119
+ severity: { value: 'warning', toString: () => 'warning' },
120
+ message,
121
+ suggestion,
122
+ };
123
+ }
124
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer infrastructure
3
3
  * @unit validator-system
4
+ * @work-item-id WI-121
4
5
  *
5
6
  * AstPerformanceScannerAdapter — PerformanceScannerPort実装
6
7
  * TypeScript Compiler API によるループ内 await 検出 + ファイルサイズチェック(L3-002)
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @layer infrastructure
3
+ * @unit validator-system
4
+ * @work-item-id WI-134, WI-135
5
+ *
6
+ * FileSystemArchitectureSemanticSourceAdapter
7
+ * TypeScript source から side-effect capability / decision placement signal を軽量抽出する。
8
+ */
9
+ import { readdir, readFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import type {
12
+ ArchitectureSemanticSourcePort,
13
+ DecisionSignal,
14
+ EffectCapability,
15
+ SourceSemanticFile,
16
+ SourceSemanticSignal,
17
+ } from '../../domain/services/l4/architecture-semantic-analysis-service.js';
18
+
19
+ export class FileSystemArchitectureSemanticSourceAdapter implements ArchitectureSemanticSourcePort {
20
+ private readonly sourceRoot: string;
21
+
22
+ constructor(sourceRoot: string = join(process.cwd(), 'scripts/harness')) {
23
+ this.sourceRoot = sourceRoot;
24
+ }
25
+
26
+ async collectSourceSemantics(): Promise<readonly SourceSemanticFile[]> {
27
+ const filePaths = await walkTsFiles(this.sourceRoot);
28
+ const files: SourceSemanticFile[] = [];
29
+
30
+ for (const filePath of filePaths) {
31
+ if (isExcluded(filePath)) continue;
32
+ try {
33
+ const content = await readFile(filePath, 'utf8');
34
+ files.push({
35
+ filePath,
36
+ zone: detectZone(filePath, content),
37
+ effects: detectEffects(content),
38
+ decisions: detectDecisions(content),
39
+ });
40
+ } catch {
41
+ continue;
42
+ }
43
+ }
44
+
45
+ return files;
46
+ }
47
+ }
48
+
49
+ async function walkTsFiles(root: string): Promise<string[]> {
50
+ try {
51
+ const entries = await readdir(root, { withFileTypes: true });
52
+ const files = await Promise.all(entries.map(async (entry) => {
53
+ const fullPath = join(root, entry.name);
54
+ if (entry.isDirectory()) return walkTsFiles(fullPath);
55
+ return fullPath.endsWith('.ts') ? [fullPath] : [];
56
+ }));
57
+ return files.flat();
58
+ } catch {
59
+ return [];
60
+ }
61
+ }
62
+
63
+ function isExcluded(filePath: string): boolean {
64
+ const normalized = filePath.replaceAll('\\', '/');
65
+ return /(^|\/)__tests__\//.test(normalized) || /\.test\.ts$/.test(normalized) || /(^|\/)fixtures?\//.test(normalized);
66
+ }
67
+
68
+ function detectZone(filePath: string, content: string): string {
69
+ const tagMatch = content.match(/@layer\s+([a-z][a-z0-9-]*)/);
70
+ if (tagMatch?.[1]) return tagMatch[1];
71
+
72
+ const normalized = filePath.replaceAll('\\', '/');
73
+ for (const zone of ['domain', 'application', 'infrastructure', 'presentation', 'controller', 'service', 'repository', 'core', 'ports', 'adapters']) {
74
+ if (normalized.includes(`/${zone}/`)) return zone;
75
+ }
76
+ return 'application';
77
+ }
78
+
79
+ function detectEffects(content: string): SourceSemanticSignal[] {
80
+ const checks: Array<{ kind: EffectCapability; pattern: RegExp; evidence: string }> = [
81
+ { kind: 'filesystem', pattern: /\bnode:fs\b|\bfrom\s+['"]fs['"]|\b(?:readFileSync|writeFileSync|readdirSync|statSync)\b/, evidence: 'filesystem API reference' },
82
+ { kind: 'network', pattern: /\bfetch\s*\(|\bnode:https?\b|\bfrom\s+['"]https?['"]/, evidence: 'network API reference' },
83
+ { kind: 'database', pattern: /\b(?:prisma|sequelize|knex|sql`|\.query\s*\()/i, evidence: 'database API reference' },
84
+ { kind: 'process-env', pattern: /\bprocess\.env\b/, evidence: 'process.env reference' },
85
+ { kind: 'time', pattern: /\bDate\.now\s*\(|\bnew\s+Date\s*\(/, evidence: 'time source reference' },
86
+ { kind: 'random', pattern: /\bMath\.random\s*\(|\brandomUUID\s*\(/, evidence: 'random source reference' },
87
+ { kind: 'subprocess', pattern: /\bnode:child_process\b|\b(?:execSync|execFileSync|spawnSync)\b/, evidence: 'subprocess API reference' },
88
+ { kind: 'user-io', pattern: /\bnode:readline\b|\bprompt\s*\(/, evidence: 'user I/O reference' },
89
+ ];
90
+
91
+ return checks
92
+ .filter((check) => check.pattern.test(content))
93
+ .map((check) => ({ kind: check.kind, evidence: check.evidence, confidence: 0.9 }));
94
+ }
95
+
96
+ function detectDecisions(content: string): SourceSemanticSignal[] {
97
+ const signals: SourceSemanticSignal[] = [];
98
+ const branchCount = (content.match(/\bif\s*\(|\bswitch\s*\(/g) ?? []).length;
99
+ if (branchCount >= 3) {
100
+ signals.push({ kind: 'business-rule-branch', evidence: `branch-count=${branchCount}`, confidence: 0.72 });
101
+ }
102
+ if (/\b(?:validate|isValid|required|invalid)\b/i.test(content) && /\bif\s*\(/.test(content)) {
103
+ signals.push({ kind: 'validation-rule', evidence: 'validation keyword with branch', confidence: 0.7 });
104
+ }
105
+ if (/\b(?:new\s+Error|HarnessError|throw\s+new)\b/.test(content)) {
106
+ signals.push({ kind: 'error-construction', evidence: 'error construction expression', confidence: 0.86 });
107
+ }
108
+ if (/\b(?:state|status)\s*=|transition[A-Z]\w*\s*\(/.test(content)) {
109
+ signals.push({ kind: 'state-transition', evidence: 'state/status transition expression', confidence: 0.78 });
110
+ }
111
+ if (/\bswitch\s*\(|\bselect[A-Z]\w*\s*\(/.test(content)) {
112
+ signals.push({ kind: 'policy-selection', evidence: 'policy selection branch', confidence: 0.68 });
113
+ }
114
+ return signals;
115
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer infrastructure
3
3
  * @unit validator-system
4
+ * @work-item-id WI-120
4
5
  *
5
6
  * FileSystemSecurityPatternScannerAdapter — SecurityPatternScannerPort実装
6
7
  */
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer infrastructure
3
3
  * @unit validator-system
4
+ * @work-item-id WI-119
4
5
  *
5
6
  * ImportGraphSourceAnalysisAdapter — SourceAnalysisPort実装
6
7
  */
@@ -19,9 +20,11 @@ const EXPORT_LIST_PATTERN = /export\s+(?:type\s+)?\{([^}]+)\}/g;
19
20
 
20
21
  export class ImportGraphSourceAnalysisAdapter implements SourceAnalysisPort {
21
22
  private readonly root: string;
23
+ private readonly includeExcludedFiles: boolean;
22
24
 
23
- constructor(root: string = HARNESS_ROOT) {
25
+ constructor(root: string = HARNESS_ROOT, options: { includeExcludedFiles?: boolean } = {}) {
24
26
  this.root = root;
27
+ this.includeExcludedFiles = options.includeExcludedFiles ?? false;
25
28
  }
26
29
 
27
30
  async getImportGraph(): Promise<ImportGraphData> {
@@ -34,7 +37,7 @@ export class ImportGraphSourceAnalysisAdapter implements SourceAnalysisPort {
34
37
  try {
35
38
  const content = await readFile(filePath, 'utf8');
36
39
  const exports = extractExports(content);
37
- const excludedReason = classifyDeadCodeExclusion(filePath);
40
+ const excludedReason = this.includeExcludedFiles ? undefined : classifyDeadCodeExclusion(filePath);
38
41
  nodes.push({
39
42
  filePath,
40
43
  exports,