Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | 59x 59x 7x 7x 10x 10x 155x 155x 4x 6x 151x 155x 151x 155x 20x 20x 1x 1x 19x 19x 19x 20x 19x 19x 6x 19x 3x 3x 3x 19x 1x 1x 1x 19x 6x | import { resolve } from "node:path";
import {
loadAllureResultsDirectory,
type EvidenceAdapterResult,
type UnmappedResult,
} from "./adapters/allure.js";
import { checkVerification } from "./check.js";
import type { EvidenceIssue, VerificationProjectCheckResult } from "./check.js";
import type { MouraManifest } from "./manifest.js";
import type { RequirementSourceLocation } from "./markdown.js";
import { safeDisplayString } from "./id.js";
import { loadProjectDirectory } from "./project.js";
export interface CheckCommandOutput {
readonly exitCode: 0 | 1;
readonly stdout: readonly string[];
readonly stderr: readonly string[];
}
export interface CheckCommandDependencies {
readonly loadEvidence?: (directory: string) => Promise<EvidenceAdapterResult>;
}
export interface CheckCommandOptions extends CheckCommandDependencies {
readonly strictTraceability?: boolean;
}
export interface TraceabilityDiagnostic extends UnmappedResult {
readonly code: "UNMAPPED";
readonly severity: "warning" | "error";
readonly message: string;
}
export function formatEvidenceAdapterIssue(
issue: EvidenceAdapterResult["issues"][number],
): string {
const source = issue.source === undefined ? "" : `${issue.source}: `;
return `${issue.code}: ${source}${issue.message}`;
}
export function formatEvidenceIssue(issue: EvidenceIssue): string {
const target = issue.canonicalId ?? "(no Case ID)";
return `${issue.code}: ${target} [${issue.layer}]: ${issue.message}`;
}
export function formatTraceabilityDiagnostic(
diagnostic: TraceabilityDiagnostic,
): string {
const location = diagnostic.source
? `${safeDisplayString(diagnostic.name)} (${safeDisplayString(diagnostic.source)})`
: safeDisplayString(diagnostic.name);
return `${diagnostic.severity.toUpperCase()} ${diagnostic.code} ${location}: ${diagnostic.message}`;
}
export type ProjectCheckEvaluation =
| {
readonly kind: "invalid-project";
readonly errors: readonly string[];
}
| {
readonly kind: "checked";
readonly manifest: MouraManifest;
readonly requirementLocations: ReadonlyMap<
string,
RequirementSourceLocation
>;
readonly requirementSources: ReadonlyMap<string, string>;
readonly specificationSources: ReadonlyMap<string, string>;
readonly evidence: EvidenceAdapterResult["evidence"];
readonly check: VerificationProjectCheckResult;
readonly adapterIssues: EvidenceAdapterResult["issues"];
readonly traceabilityDiagnostics: readonly TraceabilityDiagnostic[];
};
/** Shared filesystem evaluation used by both human CLI and artifact renderers. */
export async function evaluateProjectDirectory(
directory: string,
options: CheckCommandOptions = {},
): Promise<ProjectCheckEvaluation> {
const project = await loadProjectDirectory(directory);
if (!project.manifest)
return {
kind: "invalid-project",
errors: project.errors.map((problem) => problem.message),
};
const loadEvidence = options.loadEvidence ?? loadAllureResultsDirectory;
const adapted = await loadEvidence(resolve(directory, "allure-results"));
const traceabilityDiagnostics = (adapted.unmapped ?? []).map((result) => ({
...result,
code: "UNMAPPED" as const,
severity: options.strictTraceability
? ("error" as const)
: ("warning" as const),
message: "No Moura Case is associated with this test result.",
}));
return {
kind: "checked",
manifest: project.manifest,
requirementLocations: project.requirementLocations ?? new Map(),
requirementSources: project.requirementSources ?? new Map(),
specificationSources: project.specificationSources ?? new Map(),
evidence: adapted.evidence,
check: checkVerification(project.manifest, adapted.evidence),
adapterIssues: adapted.issues,
traceabilityDiagnostics,
};
}
/** Filesystem command boundary; the check core remains adapter-neutral and pure. */
export async function checkProjectDirectory(
directory: string,
options: CheckCommandOptions = {},
): Promise<CheckCommandOutput> {
const evaluation = await evaluateProjectDirectory(directory, options);
if (evaluation.kind === "invalid-project") {
return {
exitCode: 1,
stdout: [],
stderr: [
"✗ Traceability validation failed",
...evaluation.errors.map((message) => `- ${message}`),
],
};
}
const adapted = { issues: evaluation.adapterIssues };
const checked = evaluation.check;
const stdout = checked.entries.map(
(entry) =>
`${entry.status} ${entry.caseId} [${entry.layer}] (${entry.severity})`,
);
const stderr: string[] = [];
for (const diagnostic of evaluation.traceabilityDiagnostics)
stderr.push(formatTraceabilityDiagnostic(diagnostic));
if (adapted.issues.length > 0) {
stderr.push("✗ Evidence adapter issues");
for (const issue of adapted.issues) {
stderr.push(`- ${formatEvidenceAdapterIssue(issue)}`);
}
}
if (checked.evidenceIssues.length > 0) {
stderr.push("✗ Semantic evidence issues");
for (const issue of checked.evidenceIssues) {
stderr.push(`- ${formatEvidenceIssue(issue)}`);
}
}
return {
exitCode:
adapted.issues.length === 0 &&
checked.passed &&
evaluation.traceabilityDiagnostics.every(
(diagnostic) => diagnostic.severity !== "error",
)
? 0
: 1,
stdout,
stderr,
};
}
|