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.
- package/CHANGELOG.md +63 -0
- package/docs/ADR/021-severity-contract.md +46 -0
- package/docs/contracts/requirement-test-matrix.schema.json +5 -0
- package/package.json +1 -1
- package/scripts/harness/attestation/application/dto/attestation-document.ts +2 -0
- package/scripts/harness/attestation/application/dto/verify-attestation-output.ts +2 -0
- package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +13 -0
- package/scripts/harness/attestation/application/ports/ac-bound-allowlist-port.ts +12 -0
- package/scripts/harness/attestation/application/ports/matrix-source-port.ts +16 -0
- package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +39 -0
- package/scripts/harness/attestation/application/usecases/verify-attestation-usecase.ts +60 -1
- package/scripts/harness/attestation/composition-root.ts +14 -0
- package/scripts/harness/attestation/domain/entities/attestation-record.ts +13 -0
- package/scripts/harness/attestation/domain/services/ac-bound-scope-service.ts +80 -0
- package/scripts/harness/attestation/infrastructure/adapters/config-ac-bound-allowlist-adapter.ts +23 -0
- package/scripts/harness/attestation/infrastructure/adapters/file-system-matrix-source-adapter.ts +25 -0
- package/scripts/harness/attestation/presentation/handlers/verify-attestation-handler.ts +1 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/validator-id-registry-adapter.ts +8 -0
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +3 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +1 -0
- package/scripts/harness/config-foundation/domain/value-objects/l3-config.ts +4 -0
- package/scripts/harness/config-foundation/domain/value-objects/layers-config.ts +1 -1
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +7 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +7 -0
- package/scripts/harness/harness-error/domain/errors/severity-downgrade-violation-error.ts +1 -1
- package/scripts/harness/main.ts +2 -2
- package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +42 -0
- package/scripts/harness/nyquist-validation/application/mappers/coverage-result-mapper.ts +13 -15
- package/scripts/harness/nyquist-validation/application/mappers/impact-analysis-result-mapper.ts +12 -12
- package/scripts/harness/nyquist-validation/application/usecases/analyze-impact-usecase.ts +11 -11
- package/scripts/harness/nyquist-validation/application/usecases/calculate-coverage-usecase.ts +11 -11
- package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +9 -9
- package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +62 -7
- package/scripts/harness/nyquist-validation/domain/aggregates/requirement-test-matrix.ts +1 -1
- package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +1 -1
- package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +1 -1
- package/scripts/harness/nyquist-validation/domain/services/coverage-calculation-service.ts +1 -1
- package/scripts/harness/nyquist-validation/domain/services/impact-analysis-service.ts +2 -2
- package/scripts/harness/nyquist-validation/domain/services/matrix-validation-service.ts +5 -5
- package/scripts/harness/nyquist-validation/domain/value-objects/impact-analysis-result.ts +1 -1
- package/scripts/harness/nyquist-validation/infrastructure/adapters/ajv-json-schema-validator-adapter.ts +3 -3
- package/scripts/harness/nyquist-validation/infrastructure/adapters/config-foundation-coverage-threshold-adapter.ts +3 -3
- package/scripts/harness/nyquist-validation/infrastructure/adapters/type-script-test-reference-source-adapter.ts +110 -8
- package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +5 -0
- package/scripts/harness/validator-system/application/dto/run-l3-validators-input.ts +2 -0
- package/scripts/harness/validator-system/application/use-cases/run-l1-validators-usecase.ts +1 -1
- package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +3 -3
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +33 -3
- package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +26 -3
- package/scripts/harness/validator-system/application/use-cases/run-quick-mode-usecase.ts +3 -3
- package/scripts/harness/validator-system/composition-root.ts +22 -0
- package/scripts/harness/validator-system/domain/ports/ac-bound-coverage-policy-port.ts +22 -0
- package/scripts/harness/validator-system/domain/ports/ac-level-traceability-port.ts +14 -0
- package/scripts/harness/validator-system/domain/services/l4/ac-level-traceability-service.ts +102 -0
- package/scripts/harness/validator-system/domain/services/validator-execution-service.ts +1 -1
- package/scripts/harness/validator-system/domain/services/validator-registry.ts +2 -2
- package/scripts/harness/validator-system/domain/value-objects/layer-config.ts +1 -1
- package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +1 -1
- package/scripts/harness/validator-system/domain/value-objects/validator-definition.ts +2 -2
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +5 -0
- package/scripts/harness/validator-system/infrastructure/adapters/json-coverage-report-adapter.ts +1 -1
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-bound-coverage-policy-adapter.ts +118 -0
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-level-traceability-adapter.ts +104 -0
- package/scripts/harness/validator-system/presentation/handlers/report-validation-results-handler.ts +4 -4
package/scripts/harness/attestation/infrastructure/adapters/file-system-matrix-source-adapter.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// @unit attestation
|
|
2
|
+
// @layer infrastructure
|
|
3
|
+
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute, resolve } from "node:path";
|
|
6
|
+
import type { MatrixSourcePort } from "../../application/ports/matrix-source-port.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* MatrixSourcePort の node:fs 実装(H16-03 / WI-227)。
|
|
10
|
+
* requirement-test-matrix.json を読み込み parse して返す。
|
|
11
|
+
* 不在・parse 不能は throw する(呼び出し側 usecase が fail-closed / [] に変換する)。
|
|
12
|
+
*/
|
|
13
|
+
export class FileSystemMatrixSourceAdapter implements MatrixSourcePort {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly baseDir: string,
|
|
16
|
+
private readonly defaultMatrixPath = ".harness/requirement-test-matrix.json",
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
async load(matrixFilePath?: string): Promise<unknown> {
|
|
20
|
+
const rel = matrixFilePath && matrixFilePath.length > 0 ? matrixFilePath : this.defaultMatrixPath;
|
|
21
|
+
const absPath = isAbsolute(rel) ? rel : resolve(this.baseDir, rel);
|
|
22
|
+
const raw = await readFile(absPath, "utf8");
|
|
23
|
+
return JSON.parse(raw);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -55,6 +55,7 @@ function renderHuman(output: VerifyAttestationOutput, exitCode: 0 | 1 | 2): stri
|
|
|
55
55
|
lines.push(`attestationDigest: ${mark(c.attestationDigest)}`);
|
|
56
56
|
lines.push(`inputHashes : ${mark(c.inputHashes)}`);
|
|
57
57
|
lines.push(`granularity : ${mark(c.granularity)}`);
|
|
58
|
+
lines.push(`acBoundScope : ${mark(c.acBoundScope)}`);
|
|
58
59
|
if (output.mismatches.length > 0) {
|
|
59
60
|
lines.push("");
|
|
60
61
|
lines.push("Mismatches:");
|
package/scripts/harness/ci-governance/infrastructure/adapters/validator-id-registry-adapter.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @unit ci-governance
|
|
2
2
|
// @layer infrastructure
|
|
3
3
|
// @work-item-id WI-124 / WI-128
|
|
4
|
+
// @work-item-id WI-222
|
|
4
5
|
|
|
5
6
|
import type { ValidatorIdRegistryPort } from '../../domain/ports/validator-id-registry-port.js';
|
|
6
7
|
import { buildDefaultRegistry } from '../../../validator-system/composition-root.js';
|
|
@@ -8,6 +9,11 @@ import type { ValidatorDefinition } from '../../../validator-system/domain/value
|
|
|
8
9
|
|
|
9
10
|
type PresetId = 'minimal' | 'standard' | 'strict';
|
|
10
11
|
|
|
12
|
+
// WI-222 / HF2-05: default-OFF な advisory-only バリデータ。scheduled audit metadata や
|
|
13
|
+
// preset 導出リストには含めない(default-OFF の不変条件を保つ)。runtime では
|
|
14
|
+
// DEFAULT_CONFIG.layers.L4.validators に含まれないことで skip される。
|
|
15
|
+
const ADVISORY_DEFAULT_OFF_IDS = new Set<string>(['L4-007']);
|
|
16
|
+
|
|
11
17
|
export class ValidatorIdRegistryAdapter implements ValidatorIdRegistryPort {
|
|
12
18
|
constructor(private readonly registryUrl?: string) {}
|
|
13
19
|
|
|
@@ -30,6 +36,8 @@ function normalizePreset(presetId: string): PresetId {
|
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
function isIncludedForPreset(definition: ValidatorDefinition, preset: PresetId, templateType: string): boolean {
|
|
39
|
+
// WI-222 / HF2-05: default-OFF advisory バリデータは全 preset で除外する。
|
|
40
|
+
if (ADVISORY_DEFAULT_OFF_IDS.has(definition.validatorId.value)) return false;
|
|
33
41
|
if (preset === 'minimal') return false;
|
|
34
42
|
if (definition.layer === 'L4') {
|
|
35
43
|
if (templateType === 'consistency-check') return true;
|
package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts
CHANGED
|
@@ -15,6 +15,7 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
|
|
|
15
15
|
performance: 'L3-002',
|
|
16
16
|
coverage: 'L3-003',
|
|
17
17
|
nyquist: 'L3-004',
|
|
18
|
+
'ac-bound-coverage': 'L3-005',
|
|
18
19
|
}, /^L3-\d{3}$/);
|
|
19
20
|
const l4Validators = normalizeValidators(resolvedConfig.layers.L4.validators, {
|
|
20
21
|
'drift-detect': 'L4-001',
|
|
@@ -49,6 +50,8 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
|
|
|
49
50
|
validators: l3Validators.length > 0 ? l3Validators : ['L3-001', 'L3-002', 'L3-003', 'L3-004'],
|
|
50
51
|
coverageThreshold: resolvedConfig.layers.L3.coverageThreshold,
|
|
51
52
|
requirementMatrixPath: resolvedConfig.layers.L3.requirementMatrixPath,
|
|
53
|
+
// WI-227 / H16-03: L3-005 のスコープ(additive-safe。未設定は [])
|
|
54
|
+
acBoundStories: resolvedConfig.layers.L3.acBoundStories ?? [],
|
|
52
55
|
},
|
|
53
56
|
L4: {
|
|
54
57
|
enabled: resolvedConfig.layers.L4.enabled,
|
|
@@ -11,6 +11,7 @@ interface L3ConfigProps {
|
|
|
11
11
|
readonly validators: string[];
|
|
12
12
|
readonly coverageThreshold: number;
|
|
13
13
|
readonly requirementMatrixPath?: string;
|
|
14
|
+
readonly acBoundStories?: readonly string[];
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export class L3Config {
|
|
@@ -18,6 +19,7 @@ export class L3Config {
|
|
|
18
19
|
readonly validators: readonly string[];
|
|
19
20
|
readonly coverageThreshold: number;
|
|
20
21
|
readonly requirementMatrixPath?: string;
|
|
22
|
+
readonly acBoundStories?: readonly string[];
|
|
21
23
|
|
|
22
24
|
constructor(props: L3ConfigProps) {
|
|
23
25
|
if (props.coverageThreshold < 0) {
|
|
@@ -40,6 +42,7 @@ export class L3Config {
|
|
|
40
42
|
this.validators = Object.freeze([...props.validators]);
|
|
41
43
|
this.coverageThreshold = props.coverageThreshold;
|
|
42
44
|
this.requirementMatrixPath = props.requirementMatrixPath;
|
|
45
|
+
this.acBoundStories = props.acBoundStories ? Object.freeze([...props.acBoundStories]) : undefined;
|
|
43
46
|
Object.freeze(this);
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -48,6 +51,7 @@ export class L3Config {
|
|
|
48
51
|
validators: string[];
|
|
49
52
|
coverageThreshold: number;
|
|
50
53
|
requirementMatrixPath?: string;
|
|
54
|
+
acBoundStories?: readonly string[];
|
|
51
55
|
}): L3Config {
|
|
52
56
|
return new L3Config(raw);
|
|
53
57
|
}
|
|
@@ -13,7 +13,7 @@ import { L4Config } from './l4-config.js';
|
|
|
13
13
|
interface ResolvedLayersDocument {
|
|
14
14
|
L1: { enabled: boolean; rules: Record<string, string> };
|
|
15
15
|
L2: { enabled: boolean; validators: string[] };
|
|
16
|
-
L3: { enabled: boolean; validators: string[]; coverageThreshold: number; requirementMatrixPath?: string };
|
|
16
|
+
L3: { enabled: boolean; validators: string[]; coverageThreshold: number; requirementMatrixPath?: string; acBoundStories?: readonly string[] };
|
|
17
17
|
L4: { enabled: boolean; validators: string[]; schedule: string };
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -6,7 +6,7 @@ import { HarnessErrorDomainError } from './harness-error-domain-error.js';
|
|
|
6
6
|
export class SeverityDowngradeViolationError extends HarnessErrorDomainError {
|
|
7
7
|
constructor(requested: string, defaultSeverity: string) {
|
|
8
8
|
super(
|
|
9
|
-
`severity の格下げは禁止されています: default="${defaultSeverity}" に対して "${requested}"
|
|
9
|
+
`severity の格下げは禁止されています: default="${defaultSeverity}" に対して "${requested}" が要求されました。根拠: ADR-021`
|
|
10
10
|
);
|
|
11
11
|
this.name = 'SeverityDowngradeViolationError';
|
|
12
12
|
}
|
package/scripts/harness/main.ts
CHANGED
|
@@ -2150,8 +2150,8 @@ async function main(): Promise<void> {
|
|
|
2150
2150
|
force: hasFlag(args, "--force"),
|
|
2151
2151
|
includeClaude,
|
|
2152
2152
|
includeCodex,
|
|
2153
|
-
includeHusky: personal
|
|
2154
|
-
includeCi: personal
|
|
2153
|
+
includeHusky: !personal,
|
|
2154
|
+
includeCi: !personal,
|
|
2155
2155
|
skillSet: skillSetRaw,
|
|
2156
2156
|
workflow: parseWorkflowMode(workflowRaw),
|
|
2157
2157
|
agent,
|
|
@@ -15,17 +15,43 @@ export interface RequirementSourceDto {
|
|
|
15
15
|
readonly acIds: readonly string[];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* HF2-05: @ac が story 外の AC を指した/複数 @story のため相対 AC が解決できなかった等の
|
|
20
|
+
* 「解決に失敗した @ac タグ」を advisory として記録する(error ではない)。
|
|
21
|
+
*/
|
|
22
|
+
export interface OrphanAcTagDto {
|
|
23
|
+
readonly storyId: string;
|
|
24
|
+
readonly filePath: string;
|
|
25
|
+
readonly testName?: string;
|
|
26
|
+
/** 解決できなかった生の @ac 表記(絶対 HXX-YY-N / 相対 AC-N のいずれか) */
|
|
27
|
+
readonly rawTag: string;
|
|
28
|
+
readonly reason: 'ac-not-in-story' | 'relative-multi-story';
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
export interface TestReferenceSourceDto {
|
|
19
32
|
readonly storyId: string;
|
|
20
33
|
readonly filePath: string;
|
|
21
34
|
readonly testType: 'unit' | 'it' | 'scenario';
|
|
22
35
|
readonly testName?: string;
|
|
36
|
+
/**
|
|
37
|
+
* HF2-05: このテストケースが個別に検証する AC の id 群(`AC-N` 形式)。
|
|
38
|
+
* @ac 注釈が無いテストは undefined(従来どおり file-fallback で全 AC にファンアウトする)。
|
|
39
|
+
*/
|
|
40
|
+
readonly acIds?: readonly string[];
|
|
41
|
+
/** HF2-05: 解決に失敗した @ac タグ(advisory)。 */
|
|
42
|
+
readonly orphanAcTags?: readonly OrphanAcTagDto[];
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
export interface MatrixTestReferenceDto {
|
|
26
46
|
readonly filePath: string;
|
|
27
47
|
readonly testType: 'unit' | 'it' | 'scenario';
|
|
28
48
|
readonly testName?: string;
|
|
49
|
+
/**
|
|
50
|
+
* HF2-05: この参照が AC 単位で紐づいたか(`"ac"`)、
|
|
51
|
+
* @ac 注釈が無く file-fallback で全 AC にファンアウトしたか(`"file"`)。
|
|
52
|
+
* 1.0 マトリクス(binding 無し)は `"file"` と等価に扱う(dedup 正規化)。
|
|
53
|
+
*/
|
|
54
|
+
readonly binding?: 'ac' | 'file';
|
|
29
55
|
}
|
|
30
56
|
|
|
31
57
|
export interface MatrixAcMappingDto {
|
|
@@ -59,12 +85,28 @@ export type IntentCoverageStatus = DomainIntentCoverageStatus;
|
|
|
59
85
|
|
|
60
86
|
export type IntentCoverageItemDto = DomainIntentCoverageItem;
|
|
61
87
|
|
|
88
|
+
/**
|
|
89
|
+
* HF2-05: AC 単位トレーサビリティの advisory 集計。
|
|
90
|
+
* - total: マトリクス上の全 AC 数
|
|
91
|
+
* - acBound: 少なくとも 1 件の binding="ac" 参照を持つ AC 数
|
|
92
|
+
* - fileFallbackOnly: binding="ac" 参照を持たない(file-fallback のみで linked な)AC 数
|
|
93
|
+
*/
|
|
94
|
+
export interface AcLevelCoverageDto {
|
|
95
|
+
readonly total: number;
|
|
96
|
+
readonly acBound: number;
|
|
97
|
+
readonly fileFallbackOnly: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
62
100
|
export interface MatrixGenerationReportDto {
|
|
63
101
|
readonly missingTests: readonly MissingTestDto[];
|
|
64
102
|
readonly orphanTests: readonly OrphanTestDto[];
|
|
65
103
|
readonly unknownStories: readonly string[];
|
|
66
104
|
readonly preservedReferences: number;
|
|
67
105
|
readonly intentCoverage: readonly IntentCoverageItemDto[];
|
|
106
|
+
/** HF2-05: AC 単位カバレッジ集計(advisory)。 */
|
|
107
|
+
readonly acLevelCoverage: AcLevelCoverageDto;
|
|
108
|
+
/** HF2-05: 解決に失敗した @ac タグ(advisory)。 */
|
|
109
|
+
readonly orphanAcTags: readonly OrphanAcTagDto[];
|
|
68
110
|
}
|
|
69
111
|
|
|
70
112
|
export interface GenerateMatrixOutput {
|
|
@@ -7,19 +7,17 @@
|
|
|
7
7
|
import type { CoverageResult } from '../../domain/value-objects/coverage-result.js';
|
|
8
8
|
import type { CalculateCoverageOutput } from '../dto/calculate-coverage-output.js';
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
meetsThreshold:
|
|
22
|
-
|
|
23
|
-
};
|
|
24
|
-
}
|
|
10
|
+
export function toCalculateCoverageOutput(
|
|
11
|
+
result: CoverageResult,
|
|
12
|
+
threshold: { active: number } | null
|
|
13
|
+
): CalculateCoverageOutput {
|
|
14
|
+
return {
|
|
15
|
+
coveredAcCount: result.coveredAcCount,
|
|
16
|
+
totalAcCount: result.totalAcCount,
|
|
17
|
+
ratePercent: result.toPercentage(),
|
|
18
|
+
uncoveredAcIds: result.uncoveredAcIds,
|
|
19
|
+
threshold: threshold?.active ?? null,
|
|
20
|
+
meetsThreshold:
|
|
21
|
+
threshold !== null ? result.meetsThreshold(threshold.active) : null,
|
|
22
|
+
};
|
|
25
23
|
}
|
package/scripts/harness/nyquist-validation/application/mappers/impact-analysis-result-mapper.ts
CHANGED
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
import type { ImpactAnalysisResult } from '../../domain/value-objects/impact-analysis-result.js';
|
|
8
8
|
import type { AnalyzeImpactOutput } from '../dto/analyze-impact-output.js';
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
10
|
+
export function toAnalyzeImpactOutput(
|
|
11
|
+
result: ImpactAnalysisResult
|
|
12
|
+
): AnalyzeImpactOutput {
|
|
13
|
+
return {
|
|
14
|
+
storyId: result.storyId,
|
|
15
|
+
directTests: result.directTests.map((ref) => ({
|
|
16
|
+
filePath: ref.filePath,
|
|
17
|
+
testType: ref.testType,
|
|
18
|
+
})),
|
|
19
|
+
directMappingOnly: true,
|
|
20
|
+
found: !result.isEmpty(),
|
|
21
|
+
};
|
|
22
22
|
}
|
|
@@ -12,7 +12,7 @@ import { RequirementTestMatrix } from '../../domain/aggregates/requirement-test-
|
|
|
12
12
|
import { StoryMapping } from '../../domain/entities/story-mapping.js';
|
|
13
13
|
import { AcMapping } from '../../domain/value-objects/ac-mapping.js';
|
|
14
14
|
import { TestReference } from '../../domain/value-objects/test-reference.js';
|
|
15
|
-
import {
|
|
15
|
+
import { toAnalyzeImpactOutput } from '../mappers/impact-analysis-result-mapper.js';
|
|
16
16
|
import type { AnalyzeImpactInput } from '../dto/analyze-impact-input.js';
|
|
17
17
|
import type { AnalyzeImpactOutput } from '../dto/analyze-impact-output.js';
|
|
18
18
|
|
|
@@ -25,29 +25,29 @@ export interface AnalyzeImpactUseCaseDeps {
|
|
|
25
25
|
|
|
26
26
|
function buildMatrix(data: unknown): RequirementTestMatrix {
|
|
27
27
|
const obj = data as Record<string, unknown>;
|
|
28
|
-
const rawStories = Array.isArray(obj
|
|
28
|
+
const rawStories = Array.isArray(obj.stories) ? obj.stories : (Array.isArray(obj.storyMappings) ? obj.storyMappings : []);
|
|
29
29
|
|
|
30
30
|
const storyMappings = (rawStories as unknown[]).map((s) => {
|
|
31
31
|
const story = s as Record<string, unknown>;
|
|
32
|
-
const rawAcMappings = Array.isArray(story
|
|
33
|
-
? story
|
|
34
|
-
: (Array.isArray(story
|
|
32
|
+
const rawAcMappings = Array.isArray(story.storyMappings)
|
|
33
|
+
? story.storyMappings
|
|
34
|
+
: (Array.isArray(story.acMappings) ? story.acMappings : []);
|
|
35
35
|
|
|
36
36
|
const acMappings = (rawAcMappings as unknown[]).map((a) => {
|
|
37
37
|
const acm = a as Record<string, unknown>;
|
|
38
|
-
const rawRefs = Array.isArray(acm
|
|
38
|
+
const rawRefs = Array.isArray(acm.testReferences) ? acm.testReferences : [];
|
|
39
39
|
const testReferences = (rawRefs as unknown[]).map((r) => {
|
|
40
40
|
const ref = r as Record<string, unknown>;
|
|
41
41
|
return TestReference.create({
|
|
42
|
-
filePath: String(ref
|
|
43
|
-
testType: String(ref
|
|
42
|
+
filePath: String(ref.filePath ?? ''),
|
|
43
|
+
testType: String(ref.testType ?? ''),
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
|
-
return AcMapping.create({ acId: String(acm
|
|
46
|
+
return AcMapping.create({ acId: String(acm.acId ?? ''), testReferences });
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
return StoryMapping.create({
|
|
50
|
-
storyId: String(story
|
|
50
|
+
storyId: String(story.storyId ?? ''),
|
|
51
51
|
acMappings,
|
|
52
52
|
});
|
|
53
53
|
});
|
|
@@ -77,6 +77,6 @@ export class AnalyzeImpactUseCase {
|
|
|
77
77
|
const matrix = buildMatrix(rawData);
|
|
78
78
|
const result = this.impactAnalysisService.analyze(matrix, input.storyId);
|
|
79
79
|
|
|
80
|
-
return
|
|
80
|
+
return toAnalyzeImpactOutput(result);
|
|
81
81
|
}
|
|
82
82
|
}
|
package/scripts/harness/nyquist-validation/application/usecases/calculate-coverage-usecase.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { RequirementTestMatrix } from '../../domain/aggregates/requirement-test-
|
|
|
13
13
|
import { StoryMapping } from '../../domain/entities/story-mapping.js';
|
|
14
14
|
import { AcMapping } from '../../domain/value-objects/ac-mapping.js';
|
|
15
15
|
import { TestReference } from '../../domain/value-objects/test-reference.js';
|
|
16
|
-
import {
|
|
16
|
+
import { toCalculateCoverageOutput } from '../mappers/coverage-result-mapper.js';
|
|
17
17
|
import type { CalculateCoverageInput } from '../dto/calculate-coverage-input.js';
|
|
18
18
|
import type { CalculateCoverageOutput } from '../dto/calculate-coverage-output.js';
|
|
19
19
|
|
|
@@ -27,29 +27,29 @@ export interface CalculateCoverageUseCaseDeps {
|
|
|
27
27
|
|
|
28
28
|
function buildMatrix(data: unknown): RequirementTestMatrix {
|
|
29
29
|
const obj = data as Record<string, unknown>;
|
|
30
|
-
const rawStories = Array.isArray(obj
|
|
30
|
+
const rawStories = Array.isArray(obj.stories) ? obj.stories : (Array.isArray(obj.storyMappings) ? obj.storyMappings : []);
|
|
31
31
|
|
|
32
32
|
const storyMappings = (rawStories as unknown[]).map((s) => {
|
|
33
33
|
const story = s as Record<string, unknown>;
|
|
34
|
-
const rawAcMappings = Array.isArray(story
|
|
35
|
-
? story
|
|
36
|
-
: (Array.isArray(story
|
|
34
|
+
const rawAcMappings = Array.isArray(story.storyMappings)
|
|
35
|
+
? story.storyMappings
|
|
36
|
+
: (Array.isArray(story.acMappings) ? story.acMappings : []);
|
|
37
37
|
|
|
38
38
|
const acMappings = (rawAcMappings as unknown[]).map((a) => {
|
|
39
39
|
const acm = a as Record<string, unknown>;
|
|
40
|
-
const rawRefs = Array.isArray(acm
|
|
40
|
+
const rawRefs = Array.isArray(acm.testReferences) ? acm.testReferences : [];
|
|
41
41
|
const testReferences = (rawRefs as unknown[]).map((r) => {
|
|
42
42
|
const ref = r as Record<string, unknown>;
|
|
43
43
|
return TestReference.create({
|
|
44
|
-
filePath: String(ref
|
|
45
|
-
testType: String(ref
|
|
44
|
+
filePath: String(ref.filePath ?? ''),
|
|
45
|
+
testType: String(ref.testType ?? ''),
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
|
-
return AcMapping.create({ acId: String(acm
|
|
48
|
+
return AcMapping.create({ acId: String(acm.acId ?? ''), testReferences });
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
return StoryMapping.create({
|
|
52
|
-
storyId: String(story
|
|
52
|
+
storyId: String(story.storyId ?? ''),
|
|
53
53
|
acMappings,
|
|
54
54
|
});
|
|
55
55
|
});
|
|
@@ -86,6 +86,6 @@ export class CalculateCoverageUseCase {
|
|
|
86
86
|
threshold = await this.coverageThresholdPort.getThreshold();
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
return
|
|
89
|
+
return toCalculateCoverageOutput(coverageResult, threshold);
|
|
90
90
|
}
|
|
91
91
|
}
|
package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts
CHANGED
|
@@ -24,29 +24,29 @@ export interface CheckAcCoverageGateUseCaseDeps {
|
|
|
24
24
|
|
|
25
25
|
function buildMatrix(data: unknown): RequirementTestMatrix {
|
|
26
26
|
const obj = data as Record<string, unknown>;
|
|
27
|
-
const rawStories = Array.isArray(obj
|
|
27
|
+
const rawStories = Array.isArray(obj.stories) ? obj.stories : (Array.isArray(obj.storyMappings) ? obj.storyMappings : []);
|
|
28
28
|
|
|
29
29
|
const storyMappings = (rawStories as unknown[]).map((s) => {
|
|
30
30
|
const story = s as Record<string, unknown>;
|
|
31
|
-
const rawAcMappings = Array.isArray(story
|
|
32
|
-
? story
|
|
33
|
-
: (Array.isArray(story
|
|
31
|
+
const rawAcMappings = Array.isArray(story.storyMappings)
|
|
32
|
+
? story.storyMappings
|
|
33
|
+
: (Array.isArray(story.acMappings) ? story.acMappings : []);
|
|
34
34
|
|
|
35
35
|
const acMappings = (rawAcMappings as unknown[]).map((a) => {
|
|
36
36
|
const acm = a as Record<string, unknown>;
|
|
37
|
-
const rawRefs = Array.isArray(acm
|
|
37
|
+
const rawRefs = Array.isArray(acm.testReferences) ? acm.testReferences : [];
|
|
38
38
|
const testReferences = (rawRefs as unknown[]).map((r) => {
|
|
39
39
|
const ref = r as Record<string, unknown>;
|
|
40
40
|
return TestReference.create({
|
|
41
|
-
filePath: String(ref
|
|
42
|
-
testType: String(ref
|
|
41
|
+
filePath: String(ref.filePath ?? ''),
|
|
42
|
+
testType: String(ref.testType ?? ''),
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
|
-
return AcMapping.create({ acId: String(acm
|
|
45
|
+
return AcMapping.create({ acId: String(acm.acId ?? ''), testReferences });
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
return StoryMapping.create({
|
|
49
|
-
storyId: String(story
|
|
49
|
+
storyId: String(story.storyId ?? ''),
|
|
50
50
|
acMappings,
|
|
51
51
|
});
|
|
52
52
|
});
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
import type { RequirementIntentCoverageService } from '../../domain/services/requirement-intent-coverage-service.js';
|
|
7
7
|
import type {
|
|
8
|
+
AcLevelCoverageDto,
|
|
8
9
|
GenerateMatrixOutput,
|
|
9
10
|
MatrixAcMappingDto,
|
|
10
11
|
MatrixStoryDto,
|
|
11
12
|
MatrixTestReferenceDto,
|
|
13
|
+
OrphanAcTagDto,
|
|
12
14
|
RequirementSourceDto,
|
|
13
15
|
RequirementTestMatrixDto,
|
|
14
16
|
TestReferenceSourceDto,
|
|
@@ -42,8 +44,12 @@ export interface GenerateRequirementTestMatrixUseCaseDeps {
|
|
|
42
44
|
readonly now?: () => Date;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
// HF2-05: dedup キーは binding を含める。1.0 マトリクス(binding undefined)は
|
|
48
|
+
// "file" に正規化するため、binding を持たない既存参照と file-fallback 生成参照が
|
|
49
|
+
// 同一キーに畳まれ、意図しない重複(=L3-004 の testReferences 件数変化)を防ぐ。
|
|
45
50
|
function referenceKey(reference: MatrixTestReferenceDto): string {
|
|
46
|
-
|
|
51
|
+
const binding = reference.binding ?? 'file';
|
|
52
|
+
return `${reference.filePath}\0${reference.testType}\0${reference.testName ?? ''}\0${binding}`;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
function mergeReferences(
|
|
@@ -112,30 +118,77 @@ export class GenerateRequirementTestMatrixUseCase {
|
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
let preservedReferences = 0;
|
|
121
|
+
// HF2-05: 各 AC の ac-bound 判定を集計するためのアキュムレータ。
|
|
122
|
+
let acLevelTotal = 0;
|
|
123
|
+
let acLevelBound = 0;
|
|
124
|
+
let acLevelFileFallbackOnly = 0;
|
|
125
|
+
const orphanAcTags: OrphanAcTagDto[] = [];
|
|
115
126
|
const stories: MatrixStoryDto[] = requirements.map((requirement) => {
|
|
116
127
|
const storyReferences = referencesByStory.get(requirement.storyId) ?? [];
|
|
117
128
|
const storyMappings: MatrixAcMappingDto[] = requirement.acIds.map((acId) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
// HF2-05:
|
|
130
|
+
// - acIds を持つ参照は、その acId を含むときだけ binding="ac" で紐づく(ファンアウトしない)。
|
|
131
|
+
// - acIds を持たない参照は従来どおり全 AC へ binding="file" でファンアウトする(L3-004 不変)。
|
|
132
|
+
const generatedReferences: MatrixTestReferenceDto[] = [];
|
|
133
|
+
let hasAcBound = false;
|
|
134
|
+
for (const reference of storyReferences) {
|
|
135
|
+
const acIds = reference.acIds;
|
|
136
|
+
if (acIds && acIds.length > 0) {
|
|
137
|
+
if (acIds.includes(acId)) {
|
|
138
|
+
generatedReferences.push({
|
|
139
|
+
filePath: reference.filePath,
|
|
140
|
+
testType: reference.testType,
|
|
141
|
+
testName: reference.testName,
|
|
142
|
+
binding: 'ac',
|
|
143
|
+
});
|
|
144
|
+
hasAcBound = true;
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
generatedReferences.push({
|
|
149
|
+
filePath: reference.filePath,
|
|
150
|
+
testType: reference.testType,
|
|
151
|
+
testName: reference.testName,
|
|
152
|
+
binding: 'file',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
123
155
|
const existingReferences = normalizeExistingReferences(existingMatrix, requirement.storyId, acId);
|
|
124
156
|
const merged = mergeReferences(generatedReferences, existingReferences);
|
|
125
157
|
preservedReferences += merged.preserved;
|
|
158
|
+
// AC 単位カバレッジ集計(advisory): linked(参照 1 件以上)な AC のみ分母に数える。
|
|
159
|
+
if (merged.references.length > 0) {
|
|
160
|
+
acLevelTotal += 1;
|
|
161
|
+
if (hasAcBound) {
|
|
162
|
+
acLevelBound += 1;
|
|
163
|
+
} else {
|
|
164
|
+
acLevelFileFallbackOnly += 1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
126
167
|
return {
|
|
127
168
|
acId,
|
|
128
169
|
testReferences: merged.references,
|
|
129
170
|
};
|
|
130
171
|
});
|
|
172
|
+
// 解決に失敗した @ac タグを advisory として集約する。
|
|
173
|
+
for (const reference of storyReferences) {
|
|
174
|
+
if (reference.orphanAcTags) orphanAcTags.push(...reference.orphanAcTags);
|
|
175
|
+
}
|
|
131
176
|
return {
|
|
132
177
|
storyId: requirement.storyId,
|
|
133
178
|
storyMappings: Object.freeze(storyMappings),
|
|
134
179
|
};
|
|
135
180
|
});
|
|
181
|
+
const acLevelCoverage: AcLevelCoverageDto = {
|
|
182
|
+
total: acLevelTotal,
|
|
183
|
+
acBound: acLevelBound,
|
|
184
|
+
fileFallbackOnly: acLevelFileFallbackOnly,
|
|
185
|
+
};
|
|
136
186
|
|
|
137
187
|
const matrix: RequirementTestMatrixDto = {
|
|
138
|
-
|
|
188
|
+
// HF2-05: binding フィールドを付与するため schema 1.1 として出力する。
|
|
189
|
+
// 1.1 は 1.0 に対し optional な binding のみ追加であり、L3-004 の判定(各 AC に
|
|
190
|
+
// testReference が 1 件以上あるか)には一切影響しない。
|
|
191
|
+
version: '1.1',
|
|
139
192
|
generatedAt: this.now().toISOString(),
|
|
140
193
|
stories: Object.freeze(stories),
|
|
141
194
|
};
|
|
@@ -150,6 +203,8 @@ export class GenerateRequirementTestMatrixUseCase {
|
|
|
150
203
|
unknownStories: [...new Set(orphanTests.map((test) => test.storyId))],
|
|
151
204
|
preservedReferences,
|
|
152
205
|
intentCoverage,
|
|
206
|
+
acLevelCoverage,
|
|
207
|
+
orphanAcTags: Object.freeze(orphanAcTags),
|
|
153
208
|
};
|
|
154
209
|
|
|
155
210
|
if (input.write) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* requirement-test-matrix の集約ルート
|
|
6
6
|
*/
|
|
7
7
|
import { DuplicateStoryMappingError } from '../errors/duplicate-story-mapping-error.js';
|
|
8
|
-
import { StoryMapping } from '../entities/story-mapping.js';
|
|
8
|
+
import type { StoryMapping } from '../entities/story-mapping.js';
|
|
9
9
|
|
|
10
10
|
export interface RequirementTestMatrixCreateProps {
|
|
11
11
|
readonly storyMappings: readonly StoryMapping[];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* ストーリー単位のAC→テスト参照マッピングエンティティ
|
|
6
6
|
*/
|
|
7
|
-
import { AcMapping,
|
|
7
|
+
import type { AcMapping, RawAcMapping } from '../value-objects/ac-mapping.js';
|
|
8
8
|
|
|
9
9
|
export interface StoryMappingCreateProps {
|
|
10
10
|
readonly storyId: string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* AC網羅ゲート判定ポリシー
|
|
6
6
|
* validator-system の L3-004 が実行主体として呼び出す公開ポリシー
|
|
7
7
|
*/
|
|
8
|
-
import { RequirementTestMatrix } from '../aggregates/requirement-test-matrix.js';
|
|
8
|
+
import type { RequirementTestMatrix } from '../aggregates/requirement-test-matrix.js';
|
|
9
9
|
|
|
10
10
|
export interface NyquistHarnessError {
|
|
11
11
|
readonly code: string;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* AC網羅率算出サービス(純粋な算出ロジック)
|
|
6
6
|
*/
|
|
7
|
-
import { RequirementTestMatrix } from '../aggregates/requirement-test-matrix.js';
|
|
7
|
+
import type { RequirementTestMatrix } from '../aggregates/requirement-test-matrix.js';
|
|
8
8
|
import { CoverageResult } from '../value-objects/coverage-result.js';
|
|
9
9
|
|
|
10
10
|
export class CoverageCalculationService {
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* テストケース逆引きサービス(v1: 直接マッピングのみ)
|
|
6
6
|
*/
|
|
7
|
-
import { RequirementTestMatrix } from '../aggregates/requirement-test-matrix.js';
|
|
7
|
+
import type { RequirementTestMatrix } from '../aggregates/requirement-test-matrix.js';
|
|
8
8
|
import { ImpactAnalysisResult } from '../value-objects/impact-analysis-result.js';
|
|
9
|
-
import { TestReference } from '../value-objects/test-reference.js';
|
|
9
|
+
import type { TestReference } from '../value-objects/test-reference.js';
|
|
10
10
|
|
|
11
11
|
export class ImpactAnalysisService {
|
|
12
12
|
analyze(matrix: RequirementTestMatrix, storyId: string): ImpactAnalysisResult {
|