phasegate 0.165.0 → 0.171.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 (64) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/docs/ADR/021-severity-contract.md +46 -0
  3. package/docs/contracts/requirement-test-matrix.schema.json +5 -0
  4. package/package.json +1 -1
  5. package/scripts/harness/attestation/application/dto/attestation-document.ts +2 -0
  6. package/scripts/harness/attestation/application/dto/verify-attestation-output.ts +2 -0
  7. package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +13 -0
  8. package/scripts/harness/attestation/application/ports/ac-bound-allowlist-port.ts +12 -0
  9. package/scripts/harness/attestation/application/ports/matrix-source-port.ts +16 -0
  10. package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +39 -0
  11. package/scripts/harness/attestation/application/usecases/verify-attestation-usecase.ts +60 -1
  12. package/scripts/harness/attestation/composition-root.ts +14 -0
  13. package/scripts/harness/attestation/domain/entities/attestation-record.ts +13 -0
  14. package/scripts/harness/attestation/domain/services/ac-bound-scope-service.ts +80 -0
  15. package/scripts/harness/attestation/infrastructure/adapters/config-ac-bound-allowlist-adapter.ts +23 -0
  16. package/scripts/harness/attestation/infrastructure/adapters/file-system-matrix-source-adapter.ts +25 -0
  17. package/scripts/harness/attestation/presentation/handlers/verify-attestation-handler.ts +1 -0
  18. package/scripts/harness/ci-governance/infrastructure/adapters/validator-id-registry-adapter.ts +8 -0
  19. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +3 -0
  20. package/scripts/harness/config-foundation/domain/harness-config.ts +1 -0
  21. package/scripts/harness/config-foundation/domain/value-objects/l3-config.ts +4 -0
  22. package/scripts/harness/config-foundation/domain/value-objects/layers-config.ts +1 -1
  23. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +7 -0
  24. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +7 -0
  25. package/scripts/harness/harness-error/domain/errors/severity-downgrade-violation-error.ts +1 -1
  26. package/scripts/harness/main.ts +2 -2
  27. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +42 -0
  28. package/scripts/harness/nyquist-validation/application/mappers/coverage-result-mapper.ts +13 -15
  29. package/scripts/harness/nyquist-validation/application/mappers/impact-analysis-result-mapper.ts +12 -12
  30. package/scripts/harness/nyquist-validation/application/usecases/analyze-impact-usecase.ts +11 -11
  31. package/scripts/harness/nyquist-validation/application/usecases/calculate-coverage-usecase.ts +11 -11
  32. package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +9 -9
  33. package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +62 -7
  34. package/scripts/harness/nyquist-validation/domain/aggregates/requirement-test-matrix.ts +1 -1
  35. package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +1 -1
  36. package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +1 -1
  37. package/scripts/harness/nyquist-validation/domain/services/coverage-calculation-service.ts +1 -1
  38. package/scripts/harness/nyquist-validation/domain/services/impact-analysis-service.ts +2 -2
  39. package/scripts/harness/nyquist-validation/domain/services/matrix-validation-service.ts +5 -5
  40. package/scripts/harness/nyquist-validation/domain/value-objects/impact-analysis-result.ts +1 -1
  41. package/scripts/harness/nyquist-validation/infrastructure/adapters/ajv-json-schema-validator-adapter.ts +3 -3
  42. package/scripts/harness/nyquist-validation/infrastructure/adapters/config-foundation-coverage-threshold-adapter.ts +3 -3
  43. package/scripts/harness/nyquist-validation/infrastructure/adapters/type-script-test-reference-source-adapter.ts +110 -8
  44. package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +5 -0
  45. package/scripts/harness/validator-system/application/dto/run-l3-validators-input.ts +2 -0
  46. package/scripts/harness/validator-system/application/use-cases/run-l1-validators-usecase.ts +1 -1
  47. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +3 -3
  48. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +33 -3
  49. package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +26 -3
  50. package/scripts/harness/validator-system/application/use-cases/run-quick-mode-usecase.ts +3 -3
  51. package/scripts/harness/validator-system/composition-root.ts +22 -0
  52. package/scripts/harness/validator-system/domain/ports/ac-bound-coverage-policy-port.ts +22 -0
  53. package/scripts/harness/validator-system/domain/ports/ac-level-traceability-port.ts +14 -0
  54. package/scripts/harness/validator-system/domain/services/l4/ac-level-traceability-service.ts +102 -0
  55. package/scripts/harness/validator-system/domain/services/validator-execution-service.ts +1 -1
  56. package/scripts/harness/validator-system/domain/services/validator-registry.ts +2 -2
  57. package/scripts/harness/validator-system/domain/value-objects/layer-config.ts +1 -1
  58. package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +1 -1
  59. package/scripts/harness/validator-system/domain/value-objects/validator-definition.ts +2 -2
  60. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +5 -0
  61. package/scripts/harness/validator-system/infrastructure/adapters/json-coverage-report-adapter.ts +1 -1
  62. package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-bound-coverage-policy-adapter.ts +118 -0
  63. package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-level-traceability-adapter.ts +104 -0
  64. package/scripts/harness/validator-system/presentation/handlers/report-validation-results-handler.ts +4 -4
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @layer domain
3
+ * @unit validator-system
4
+ * @work-item-id WI-222
5
+ *
6
+ * HF2-05 / L4-007: AC 単位トレーサビリティ advisory サービス。
7
+ *
8
+ * このサービスは fileFallbackOnly な AC(story-level では linked だが、個別 AC を検証する
9
+ * @ac 注釈付きテストが 1 件も無い AC)と orphanAcTags(解決に失敗した @ac)を
10
+ * warning severity の finding へ変換する。**error は一切出さない**(ADR-019 §5: advisory tier は
11
+ * non-blocking でなければならない)。fileFallbackOnly が 0 かつ orphanAcTags が空なら PASS。
12
+ *
13
+ * L3-004 の二値 pass/fail 判定には一切関与しない(verdict 不変)。
14
+ */
15
+
16
+ export interface AcLevelCoverageSummary {
17
+ readonly total: number;
18
+ readonly acBound: number;
19
+ readonly fileFallbackOnly: number;
20
+ }
21
+
22
+ export interface FileFallbackOnlyAc {
23
+ readonly storyId: string;
24
+ readonly acId: string;
25
+ }
26
+
27
+ export interface OrphanAcTag {
28
+ readonly storyId: string;
29
+ readonly filePath: string;
30
+ readonly testName?: string;
31
+ readonly rawTag: string;
32
+ readonly reason: 'ac-not-in-story' | 'relative-multi-story';
33
+ }
34
+
35
+ export interface AcLevelTraceabilitySnapshot {
36
+ readonly acLevelCoverage: AcLevelCoverageSummary;
37
+ readonly fileFallbackOnlyAcs: readonly FileFallbackOnlyAc[];
38
+ readonly orphanAcTags: readonly OrphanAcTag[];
39
+ }
40
+
41
+ export interface AcLevelTraceabilityFinding {
42
+ readonly kind: 'file-fallback-only-ac' | 'orphan-ac-tag';
43
+ readonly message: string;
44
+ readonly suggestion: string;
45
+ }
46
+
47
+ export class AcLevelTraceabilityReport {
48
+ readonly findings: readonly AcLevelTraceabilityFinding[];
49
+
50
+ constructor(findings: readonly AcLevelTraceabilityFinding[]) {
51
+ this.findings = Object.freeze([...findings]);
52
+ Object.freeze(this);
53
+ }
54
+
55
+ hasFindings(): boolean {
56
+ return this.findings.length > 0;
57
+ }
58
+
59
+ toHarnessErrors(): readonly {
60
+ readonly code: { readonly value: string; toString(): string };
61
+ readonly severity: { readonly value: string; toString(): string };
62
+ readonly message: string;
63
+ readonly suggestion: string;
64
+ readonly kind: string;
65
+ }[] {
66
+ return this.findings.map((finding) => ({
67
+ code: { value: 'L4-007', toString: () => 'L4-007' },
68
+ severity: { value: 'warning', toString: () => 'warning' },
69
+ message: finding.message,
70
+ suggestion: finding.suggestion,
71
+ kind: finding.kind,
72
+ }));
73
+ }
74
+ }
75
+
76
+ export class AcLevelTraceabilityService {
77
+ check(snapshot: AcLevelTraceabilitySnapshot): AcLevelTraceabilityReport {
78
+ const findings: AcLevelTraceabilityFinding[] = [];
79
+
80
+ for (const ac of snapshot.fileFallbackOnlyAcs) {
81
+ findings.push({
82
+ kind: 'file-fallback-only-ac',
83
+ message: `${ac.storyId} ${ac.acId} is linked only via file-fallback (no @ac-bound test asserts it individually).`,
84
+ suggestion: `Add an "// @ac ${ac.storyId}-${ac.acId.replace(/^AC-/, '')}" annotation to the test case that specifically asserts ${ac.acId}, or accept this as a known file-level gap.`,
85
+ });
86
+ }
87
+
88
+ for (const orphan of snapshot.orphanAcTags) {
89
+ const location = orphan.testName ? `${orphan.filePath} ("${orphan.testName}")` : orphan.filePath;
90
+ const reasonText = orphan.reason === 'relative-multi-story'
91
+ ? 'relative @ac cannot be resolved because the file declares multiple @story tags'
92
+ : 'the referenced AC does not belong to the file\'s story';
93
+ findings.push({
94
+ kind: 'orphan-ac-tag',
95
+ message: `${location}: @ac "${orphan.rawTag}" is unresolved — ${reasonText}.`,
96
+ suggestion: 'Use an absolute @ac (HXX-YY-N) that matches the file\'s @story, or split multi-story test files so relative AC-N resolves unambiguously.',
97
+ });
98
+ }
99
+
100
+ return new AcLevelTraceabilityReport(findings);
101
+ }
102
+ }
@@ -5,7 +5,7 @@
5
5
  * ValidatorExecutionService ドメインサービス
6
6
  * 指定されたValidatorDefinition[]を順次実行し、ValidationResult[]を集約
7
7
  */
8
- import { ValidatorDefinition } from '../value-objects/validator-definition.js';
8
+ import type { ValidatorDefinition } from '../value-objects/validator-definition.js';
9
9
  import { ValidationResult, type HarnessErrorLike } from '../value-objects/validation-result.js';
10
10
  import { LayerConfig } from '../value-objects/layer-config.js';
11
11
  import type { PhaseGatePolicyPort } from '../ports/phase-gate-policy-port.js';
@@ -5,8 +5,8 @@
5
5
  * ValidatorRegistry ドメインサービス
6
6
  * L2-L4バリデータ定義のカタログ管理と選択実行インターフェース
7
7
  */
8
- import { ValidatorId } from '../value-objects/validator-id.js';
9
- import { ValidatorDefinition } from '../value-objects/validator-definition.js';
8
+ import type { ValidatorId } from '../value-objects/validator-id.js';
9
+ import type { ValidatorDefinition } from '../value-objects/validator-definition.js';
10
10
 
11
11
  export class UnknownValidatorError extends Error {
12
12
  readonly validatorId: string;
@@ -5,7 +5,7 @@
5
5
  * LayerConfig 値オブジェクト
6
6
  * HarnessConfigV2から注入されるL2/L3/L4の実行設定VO
7
7
  */
8
- import { ValidatorId } from './validator-id.js';
8
+ import type { ValidatorId } from './validator-id.js';
9
9
 
10
10
  export interface LayerConfigProps {
11
11
  readonly layer: 'L2' | 'L3' | 'L4';
@@ -5,7 +5,7 @@
5
5
  * ValidationResult 値オブジェクト
6
6
  * バリデータ実行結果のスナップショット(不変)
7
7
  */
8
- import { ValidatorId } from './validator-id.js';
8
+ import type { ValidatorId } from './validator-id.js';
9
9
 
10
10
  /** HarnessError の最小互換型(harness-error Unit の HarnessError との疎結合) */
11
11
  export interface HarnessErrorLike {
@@ -5,8 +5,8 @@
5
5
  * ValidatorDefinition 値オブジェクト
6
6
  * 個々のバリデータの不変定義を保持する
7
7
  */
8
- import { ValidatorId } from './validator-id.js';
9
- import { ValidationRule } from './validation-rule.js';
8
+ import type { ValidatorId } from './validator-id.js';
9
+ import type { ValidationRule } from './validation-rule.js';
10
10
 
11
11
  export interface ValidatorDefinitionProps {
12
12
  readonly validatorId: ValidatorId;
@@ -9,6 +9,8 @@
9
9
  * WI-140 で L2-014 を追加
10
10
  * WI-132/WI-133/WI-136/WI-137/WI-138 で L2-015 を追加
11
11
  * WI-156 で L4-006 を追加
12
+ * WI-222 (HF2-05) で L4-007(ac-level-traceability, default-OFF advisory)を追加
13
+ * WI-227 (H16-03) で L3-005(ac-bound-coverage, default-OFF fail-closed)を追加
12
14
  */
13
15
 
14
16
  export class InvalidValidatorIdError extends Error {
@@ -36,12 +38,14 @@ const VALIDATOR_NAME_MAP: Record<string, string> = {
36
38
  'L3-002': 'performance',
37
39
  'L3-003': 'coverage',
38
40
  'L3-004': 'nyquist',
41
+ 'L3-005': 'ac-bound-coverage',
39
42
  'L4-001': 'drift-detect',
40
43
  'L4-002': 'consistency-check',
41
44
  'L4-003': 'dead-code',
42
45
  'L4-004': 'doc-freshness',
43
46
  'L4-005': 'pointer-validation',
44
47
  'L4-006': 'skill-catalog-drift',
48
+ 'L4-007': 'ac-level-traceability',
45
49
  };
46
50
 
47
51
  /** バリデータ名 -> バリデータID の逆引きマップ */
@@ -53,6 +57,7 @@ const NAME_TO_ID_MAP: Record<string, string> = {
53
57
  'doc-freshness-checker': 'L4-004',
54
58
  'pointer-validator': 'L4-005',
55
59
  'skill-catalog-drift': 'L4-006',
60
+ 'ac-level-traceability': 'L4-007',
56
61
  };
57
62
 
58
63
  /** 有効なValidatorID集合 */
@@ -33,7 +33,7 @@ export class JsonCoverageReportAdapter implements CoverageReportPort {
33
33
  }
34
34
 
35
35
  const data = JSON.parse(raw) as Record<string, { lines?: { pct?: number } }>;
36
- const total = data['total'];
36
+ const total = data.total;
37
37
  const overallCoverage = total?.lines?.pct ?? 0;
38
38
 
39
39
  const perFileCoverage: { filePath: string; coverage: number }[] = [];
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @layer infrastructure
3
+ * @unit validator-system
4
+ * @work-item-id WI-227
5
+ *
6
+ * NyquistAcBoundCoveragePolicyAdapter — AcBoundCoveragePolicyPort 実装(L3-005)
7
+ *
8
+ * FAIL-CLOSED: matrix の不在・parse 不能・例外はいずれも passed=false(不合格)として扱う。
9
+ * L4-007(advisory / fail-open)とは対照的に、L3-005 は CI を落とす blocking tier である。
10
+ *
11
+ * 判定: `acBoundStories`(スコープ)内の各 storyId について、matrix 上の全 linked AC を走査し、
12
+ * testReferences に `binding:"ac"` を 1 件も持たない AC(fileFallbackOnly)が 1 つでもあれば FAIL。
13
+ * スコープ外 story は完全に無視する。スコープが空なら検査対象が無いため PASS。
14
+ *
15
+ * matrix path は config(layers.L3.requirementMatrixPath)から供給される。CI では
16
+ * phasegate:generate-matrix で事前生成された最新マトリクスを READ する(L3-004 と同じ規約)。
17
+ */
18
+ import { readFile } from 'node:fs/promises';
19
+ import { isAbsolute, join } from 'node:path';
20
+ import type { AcBoundCoveragePolicyPort } from '../../domain/ports/ac-bound-coverage-policy-port.js';
21
+ import type { HarnessErrorLike } from '../../domain/value-objects/validation-result.js';
22
+
23
+ const DEFAULT_MATRIX_PATH = '.harness/requirement-test-matrix.json';
24
+
25
+ interface MatrixTestReference {
26
+ readonly binding?: string;
27
+ }
28
+
29
+ interface MatrixAcMapping {
30
+ readonly acId?: string;
31
+ readonly testReferences?: readonly MatrixTestReference[];
32
+ }
33
+
34
+ interface MatrixStory {
35
+ readonly storyId?: string;
36
+ readonly storyMappings?: readonly MatrixAcMapping[];
37
+ readonly acMappings?: readonly MatrixAcMapping[];
38
+ }
39
+
40
+ function toL3005Error(message: string, suggestion: string): HarnessErrorLike {
41
+ return {
42
+ code: { value: 'L3-005', toString: () => 'L3-005' },
43
+ severity: { value: 'error', toString: () => 'error' },
44
+ message,
45
+ suggestion,
46
+ };
47
+ }
48
+
49
+ export class NyquistAcBoundCoveragePolicyAdapter implements AcBoundCoveragePolicyPort {
50
+ async checkAcBoundCoverage(context: {
51
+ matrixFilePath?: string;
52
+ acBoundStories: readonly string[];
53
+ }): Promise<{ passed: boolean; errors: readonly HarnessErrorLike[] }> {
54
+ const scope = new Set(context.acBoundStories);
55
+ // スコープが空 = 検査すべき in-scope AC が存在しない → PASS
56
+ if (scope.size === 0) {
57
+ return { passed: true, errors: [] };
58
+ }
59
+
60
+ const rootDir = process.cwd();
61
+ const relativeOrAbsolute =
62
+ context.matrixFilePath && context.matrixFilePath.length > 0 ? context.matrixFilePath : DEFAULT_MATRIX_PATH;
63
+ const matrixFilePath = isAbsolute(relativeOrAbsolute) ? relativeOrAbsolute : join(rootDir, relativeOrAbsolute);
64
+
65
+ let parsed: unknown;
66
+ try {
67
+ const raw = await readFile(matrixFilePath, 'utf8');
68
+ parsed = JSON.parse(raw);
69
+ } catch (error) {
70
+ const message = error instanceof Error ? error.message : String(error);
71
+ // FAIL-CLOSED: 不在・parse 不能はいずれも不合格
72
+ return {
73
+ passed: false,
74
+ errors: [
75
+ toL3005Error(
76
+ `AC-bound マトリクスを読み込めません(L3-005 は fail-closed): ${relativeOrAbsolute}: ${message}`,
77
+ `${relativeOrAbsolute} を生成してください(phasegate:generate-matrix)。パスは config の layers.L3.requirementMatrixPath で変更できます`,
78
+ ),
79
+ ],
80
+ };
81
+ }
82
+
83
+ const stories = this.extractStories(parsed);
84
+ const errors: HarnessErrorLike[] = [];
85
+
86
+ for (const story of stories) {
87
+ const storyId = story.storyId ?? '';
88
+ if (!scope.has(storyId)) continue; // スコープ外は無視
89
+
90
+ const acMappings = story.storyMappings ?? story.acMappings ?? [];
91
+ for (const ac of acMappings) {
92
+ const refs = ac.testReferences ?? [];
93
+ if (refs.length === 0) continue; // 未リンク AC は L3-004 の責務。L3-005 は ac-binding 有無のみ判定
94
+ const hasAcBound = refs.some((ref) => ref.binding === 'ac');
95
+ if (!hasAcBound) {
96
+ errors.push(
97
+ toL3005Error(
98
+ `AC-bound coverage 不足: ${storyId}.${ac.acId ?? '?'} は binding:"ac" のテスト参照を持ちません(fileFallbackOnly)`,
99
+ `${storyId}.${ac.acId ?? '?'} を検証するテストに @ac タグ(絶対 ${storyId}-N / 相対 AC-N)を付与してください`,
100
+ ),
101
+ );
102
+ }
103
+ }
104
+ }
105
+
106
+ if (errors.length === 0) {
107
+ return { passed: true, errors: [] };
108
+ }
109
+ return { passed: false, errors };
110
+ }
111
+
112
+ private extractStories(parsed: unknown): readonly MatrixStory[] {
113
+ if (typeof parsed !== 'object' || parsed === null) return [];
114
+ const obj = parsed as { stories?: unknown; storyMappings?: unknown };
115
+ const raw = Array.isArray(obj.stories) ? obj.stories : Array.isArray(obj.storyMappings) ? obj.storyMappings : [];
116
+ return raw as readonly MatrixStory[];
117
+ }
118
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @layer infrastructure
3
+ * @unit validator-system
4
+ * @work-item-id WI-222
5
+ *
6
+ * HF2-05 / L4-007: nyquist-validation の generate-matrix usecase を実行し、
7
+ * AC 単位トレーサビリティの snapshot(acLevelCoverage / fileFallbackOnlyAcs / orphanAcTags)を
8
+ * 収集して AcLevelTraceabilityPort として提供する adapter。
9
+ *
10
+ * advisory 用途のため、収集に失敗しても throw せず「所見なし」snapshot を返す(fail-open)。
11
+ * これは L3-004(fail-closed な AC 網羅ゲート)とは独立した advisory tier であり、
12
+ * 収集失敗が CI を落とすことは意図しない(ADR-019 §5: advisory は non-blocking)。
13
+ */
14
+ import type { AcLevelTraceabilityPort } from '../../domain/ports/ac-level-traceability-port.js';
15
+ import type {
16
+ AcLevelTraceabilitySnapshot,
17
+ FileFallbackOnlyAc,
18
+ OrphanAcTag,
19
+ } from '../../domain/services/l4/ac-level-traceability-service.js';
20
+
21
+ const EMPTY_SNAPSHOT: AcLevelTraceabilitySnapshot = Object.freeze({
22
+ acLevelCoverage: { total: 0, acBound: 0, fileFallbackOnly: 0 },
23
+ fileFallbackOnlyAcs: Object.freeze([]),
24
+ orphanAcTags: Object.freeze([]),
25
+ });
26
+
27
+ export interface NyquistAcLevelTraceabilityAdapterOptions {
28
+ readonly requirementsPath?: string;
29
+ readonly testRoot?: string;
30
+ readonly matrixFilePath?: string;
31
+ }
32
+
33
+ export class NyquistAcLevelTraceabilityAdapter implements AcLevelTraceabilityPort {
34
+ private readonly requirementsPath: string;
35
+ private readonly testRoot: string;
36
+ private readonly matrixFilePath: string;
37
+
38
+ constructor(options: NyquistAcLevelTraceabilityAdapterOptions = {}) {
39
+ this.requirementsPath = options.requirementsPath ?? 'docs/product/user_stories.md';
40
+ this.testRoot = options.testRoot ?? 'scripts/harness/__tests__';
41
+ this.matrixFilePath = options.matrixFilePath ?? '.harness/requirement-test-matrix.json';
42
+ }
43
+
44
+ async collect(): Promise<AcLevelTraceabilitySnapshot> {
45
+ try {
46
+ const storyIds = await this.loadValidStoryIds();
47
+ const { createNyquistValidationModule } = await import('../../../nyquist-validation/composition-root.js');
48
+ const mod = createNyquistValidationModule({ getStoryIds: async () => storyIds });
49
+ const output = await mod.generateMatrixUseCase.execute({
50
+ requirementsPath: this.requirementsPath,
51
+ testRoot: this.testRoot,
52
+ matrixFilePath: this.matrixFilePath,
53
+ write: false,
54
+ });
55
+
56
+ const fileFallbackOnlyAcs: FileFallbackOnlyAc[] = [];
57
+ for (const story of output.matrix.stories) {
58
+ for (const mapping of story.storyMappings) {
59
+ if (mapping.testReferences.length === 0) continue;
60
+ const hasAcBound = mapping.testReferences.some((ref) => ref.binding === 'ac');
61
+ if (!hasAcBound) {
62
+ fileFallbackOnlyAcs.push({ storyId: story.storyId, acId: mapping.acId });
63
+ }
64
+ }
65
+ }
66
+
67
+ const orphanAcTags: OrphanAcTag[] = output.report.orphanAcTags.map((tag) => ({
68
+ storyId: tag.storyId,
69
+ filePath: tag.filePath,
70
+ testName: tag.testName,
71
+ rawTag: tag.rawTag,
72
+ reason: tag.reason,
73
+ }));
74
+
75
+ return {
76
+ acLevelCoverage: {
77
+ total: output.report.acLevelCoverage.total,
78
+ acBound: output.report.acLevelCoverage.acBound,
79
+ fileFallbackOnly: output.report.acLevelCoverage.fileFallbackOnly,
80
+ },
81
+ fileFallbackOnlyAcs: Object.freeze(fileFallbackOnlyAcs),
82
+ orphanAcTags: Object.freeze(orphanAcTags),
83
+ };
84
+ } catch {
85
+ return EMPTY_SNAPSHOT;
86
+ }
87
+ }
88
+
89
+ private async loadValidStoryIds(): Promise<readonly string[]> {
90
+ try {
91
+ const { createConfigFoundationModule } = await import('../../../config-foundation/composition-root.js');
92
+ const configModule = createConfigFoundationModule();
93
+ const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
94
+ const { createTraceabilityModelModule } = await import('../../../traceability-model/composition-root.js');
95
+ const traceModule = createTraceabilityModelModule(process.cwd(), {
96
+ pathRoots: { designDocsRoot: resolvedConfig.config.paths.designDocs },
97
+ });
98
+ const storyIds = await traceModule.storyCatalog.getAllStoryIds();
99
+ return storyIds.map((s) => s.value);
100
+ } catch {
101
+ return [];
102
+ }
103
+ }
104
+ }
@@ -21,10 +21,10 @@ export function isValidationResultContract(obj: unknown): obj is ValidationResul
21
21
  if (typeof obj !== 'object' || obj === null) return false;
22
22
  const o = obj as Record<string, unknown>;
23
23
  return (
24
- typeof o['validatorId'] === 'string' &&
25
- typeof o['passed'] === 'boolean' &&
26
- Array.isArray(o['errors']) &&
27
- typeof o['durationMs'] === 'number'
24
+ typeof o.validatorId === 'string' &&
25
+ typeof o.passed === 'boolean' &&
26
+ Array.isArray(o.errors) &&
27
+ typeof o.durationMs === 'number'
28
28
  );
29
29
  }
30
30