agent-inspect 2.1.0 → 2.2.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 +16 -0
- package/README.md +13 -3
- package/docs/API.md +29 -4
- package/docs/CLI.md +34 -7
- package/package.json +11 -1
- package/packages/cli/dist/index.cjs +461 -93
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +460 -92
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/core/dist/reporters.cjs +185 -0
- package/packages/core/dist/reporters.cjs.map +1 -0
- package/packages/core/dist/reporters.d.cts +91 -0
- package/packages/core/dist/reporters.d.ts +91 -0
- package/packages/core/dist/reporters.mjs +175 -0
- package/packages/core/dist/reporters.mjs.map +1 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var path = require('path');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
8
|
+
|
|
9
|
+
// packages/core/src/reporters/index.ts
|
|
10
|
+
var TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION = "0.1";
|
|
11
|
+
var FORMAT_EXTENSIONS = {
|
|
12
|
+
json: "json",
|
|
13
|
+
jsonl: "jsonl",
|
|
14
|
+
md: "md",
|
|
15
|
+
html: "html"
|
|
16
|
+
};
|
|
17
|
+
function sortDiagnostics(a, b) {
|
|
18
|
+
return compareStrings(a.target ?? "", b.target ?? "") || compareStrings(a.code, b.code) || compareStrings(a.severity, b.severity) || compareStrings(a.message, b.message);
|
|
19
|
+
}
|
|
20
|
+
function sortArtifacts(a, b) {
|
|
21
|
+
return compareStrings(a.path, b.path) || compareStrings(a.kind, b.kind) || compareStrings(a.format, b.format) || compareStrings(a.redactionProfile, b.redactionProfile) || compareStrings(a.title ?? "", b.title ?? "");
|
|
22
|
+
}
|
|
23
|
+
function sortResults(a, b) {
|
|
24
|
+
return compareStrings(a.file ?? "", b.file ?? "") || compareStrings(a.name, b.name) || compareStrings(a.testId, b.testId);
|
|
25
|
+
}
|
|
26
|
+
function compareStrings(a, b) {
|
|
27
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
28
|
+
}
|
|
29
|
+
function cloneDiagnostic(diagnostic) {
|
|
30
|
+
return {
|
|
31
|
+
code: diagnostic.code,
|
|
32
|
+
severity: diagnostic.severity,
|
|
33
|
+
message: diagnostic.message,
|
|
34
|
+
...diagnostic.target === void 0 ? {} : { target: diagnostic.target }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function cloneArtifact(artifact) {
|
|
38
|
+
return {
|
|
39
|
+
kind: artifact.kind,
|
|
40
|
+
path: artifact.path,
|
|
41
|
+
format: artifact.format,
|
|
42
|
+
redactionProfile: artifact.redactionProfile,
|
|
43
|
+
...artifact.title === void 0 ? {} : { title: artifact.title },
|
|
44
|
+
...artifact.sizeBytes === void 0 ? {} : { sizeBytes: artifact.sizeBytes },
|
|
45
|
+
diagnostics: (artifact.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function cloneResult(result) {
|
|
49
|
+
return {
|
|
50
|
+
testId: result.testId,
|
|
51
|
+
name: result.name,
|
|
52
|
+
...result.file === void 0 ? {} : { file: result.file },
|
|
53
|
+
status: result.status,
|
|
54
|
+
...result.durationMs === void 0 ? {} : { durationMs: result.durationMs },
|
|
55
|
+
...result.tracePath === void 0 ? {} : { tracePath: result.tracePath },
|
|
56
|
+
artifacts: (result.artifacts ?? []).map(cloneArtifact).sort(sortArtifacts),
|
|
57
|
+
diagnostics: (result.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function artifactIdentity(artifact) {
|
|
61
|
+
return `${artifact.path}\0${artifact.kind}\0${artifact.format}\0${artifact.redactionProfile}`;
|
|
62
|
+
}
|
|
63
|
+
function dedupeArtifacts(artifacts) {
|
|
64
|
+
const seen = /* @__PURE__ */ new Set();
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const artifact of artifacts.sort(sortArtifacts)) {
|
|
67
|
+
const key = artifactIdentity(artifact);
|
|
68
|
+
if (seen.has(key)) continue;
|
|
69
|
+
seen.add(key);
|
|
70
|
+
out.push(artifact);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
function createTraceArtifactManifest(options) {
|
|
75
|
+
const results = options.results.map(cloneResult).sort(sortResults);
|
|
76
|
+
const resultArtifacts = results.flatMap((result) => result.artifacts ?? []);
|
|
77
|
+
const artifacts = dedupeArtifacts([
|
|
78
|
+
...resultArtifacts,
|
|
79
|
+
...(options.artifacts ?? []).map(cloneArtifact)
|
|
80
|
+
]);
|
|
81
|
+
return {
|
|
82
|
+
schemaVersion: TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION,
|
|
83
|
+
generatedAt: options.generatedAt,
|
|
84
|
+
framework: options.framework,
|
|
85
|
+
results,
|
|
86
|
+
artifacts,
|
|
87
|
+
diagnostics: (options.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function createReporterArtifactPath(options) {
|
|
91
|
+
const fileSlug = slugForPathLabel(options.file ?? "manual");
|
|
92
|
+
const testSlug = slugForPathLabel(`${options.name}-${options.testId}`);
|
|
93
|
+
const relativePath = path__default.default.posix.join(
|
|
94
|
+
"tests",
|
|
95
|
+
fileSlug,
|
|
96
|
+
testSlug,
|
|
97
|
+
`${options.kind}.${FORMAT_EXTENSIONS[options.format]}`
|
|
98
|
+
);
|
|
99
|
+
return validateReporterArtifactPath({
|
|
100
|
+
outputDir: options.outputDir,
|
|
101
|
+
relativePath
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function validateReporterArtifactPath(options) {
|
|
105
|
+
const outputDir = path__default.default.resolve(options.outputDir);
|
|
106
|
+
const diagnostics = [];
|
|
107
|
+
const rawPath = options.relativePath;
|
|
108
|
+
if (rawPath.length === 0) {
|
|
109
|
+
diagnostics.push({
|
|
110
|
+
code: "artifact_path_empty",
|
|
111
|
+
severity: "error",
|
|
112
|
+
message: "Reporter artifact path must not be empty."
|
|
113
|
+
});
|
|
114
|
+
return { ok: false, outputDir, diagnostics };
|
|
115
|
+
}
|
|
116
|
+
if (rawPath.includes("\0")) {
|
|
117
|
+
diagnostics.push({
|
|
118
|
+
code: "invalid_artifact_path",
|
|
119
|
+
severity: "error",
|
|
120
|
+
message: "Reporter artifact path must not contain null bytes.",
|
|
121
|
+
target: rawPath
|
|
122
|
+
});
|
|
123
|
+
return { ok: false, outputDir, diagnostics };
|
|
124
|
+
}
|
|
125
|
+
if (path__default.default.isAbsolute(rawPath) || path__default.default.win32.isAbsolute(rawPath)) {
|
|
126
|
+
diagnostics.push({
|
|
127
|
+
code: "artifact_path_absolute",
|
|
128
|
+
severity: "error",
|
|
129
|
+
message: "Reporter artifact path must be relative.",
|
|
130
|
+
target: rawPath
|
|
131
|
+
});
|
|
132
|
+
return { ok: false, outputDir, diagnostics };
|
|
133
|
+
}
|
|
134
|
+
const normalized = path__default.default.posix.normalize(rawPath.replace(/\\/g, "/"));
|
|
135
|
+
const segments = normalized.split("/");
|
|
136
|
+
if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
|
|
137
|
+
diagnostics.push({
|
|
138
|
+
code: "artifact_path_escape",
|
|
139
|
+
severity: "error",
|
|
140
|
+
message: "Reporter artifact path must stay under the output directory.",
|
|
141
|
+
target: rawPath
|
|
142
|
+
});
|
|
143
|
+
return { ok: false, outputDir, diagnostics };
|
|
144
|
+
}
|
|
145
|
+
const absolutePath = path__default.default.resolve(outputDir, normalized);
|
|
146
|
+
const relFromOutput = path__default.default.relative(outputDir, absolutePath);
|
|
147
|
+
if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path__default.default.isAbsolute(relFromOutput)) {
|
|
148
|
+
diagnostics.push({
|
|
149
|
+
code: "artifact_path_escape",
|
|
150
|
+
severity: "error",
|
|
151
|
+
message: "Reporter artifact path resolved outside the output directory.",
|
|
152
|
+
target: rawPath
|
|
153
|
+
});
|
|
154
|
+
return { ok: false, outputDir, diagnostics };
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
ok: true,
|
|
158
|
+
outputDir,
|
|
159
|
+
relativePath: normalized,
|
|
160
|
+
absolutePath,
|
|
161
|
+
diagnostics
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function createReporterFailureDiagnostic(error, target) {
|
|
165
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown reporter failure.";
|
|
166
|
+
return {
|
|
167
|
+
code: "reporter_failure",
|
|
168
|
+
severity: "error",
|
|
169
|
+
message,
|
|
170
|
+
...target === void 0 ? {} : { target }
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function slugForPathLabel(value) {
|
|
174
|
+
const base = path__default.default.basename(value).replace(/\.(test|spec)\.[^.]+$/, "").replace(/\.[^.]+$/, "");
|
|
175
|
+
const slug = base.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "").slice(0, 80);
|
|
176
|
+
return slug.length > 0 ? slug : "artifact";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
exports.TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION = TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION;
|
|
180
|
+
exports.createReporterArtifactPath = createReporterArtifactPath;
|
|
181
|
+
exports.createReporterFailureDiagnostic = createReporterFailureDiagnostic;
|
|
182
|
+
exports.createTraceArtifactManifest = createTraceArtifactManifest;
|
|
183
|
+
exports.validateReporterArtifactPath = validateReporterArtifactPath;
|
|
184
|
+
//# sourceMappingURL=reporters.cjs.map
|
|
185
|
+
//# sourceMappingURL=reporters.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reporters/index.ts"],"names":["path"],"mappings":"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","file":"reporters.cjs","sourcesContent":["import path from \"node:path\";\n\nexport const TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION = \"0.1\" as const;\n\n/** Experimental reporter framework identifier for local CI artifacts. */\nexport type TraceReporterFramework = \"vitest\" | \"jest\" | \"manual\";\n\n/** Experimental reporter test status normalized across supported test runners. */\nexport type TraceTestStatus = \"passed\" | \"failed\" | \"skipped\" | \"todo\";\n\n/** Experimental reporter artifact kind used in artifact manifests. */\nexport type TraceArtifactKind = \"trace\" | \"report\" | \"eval\" | \"redaction\" | \"summary\";\n\n/** Experimental reporter artifact file format used in artifact manifests. */\nexport type TraceArtifactFormat = \"json\" | \"jsonl\" | \"md\" | \"html\";\n\n/** Experimental redaction profile marker recorded with reporter artifacts. */\nexport type TraceArtifactRedactionProfile = \"local\" | \"share\" | \"strict\";\n\n/** Experimental reporter diagnostic severity. */\nexport type TraceReporterDiagnosticSeverity = \"info\" | \"warning\" | \"error\";\n\n/** Experimental reporter diagnostic code for safe, non-throwing reporter helpers. */\nexport type TraceReporterDiagnosticCode =\n | \"invalid_artifact_path\"\n | \"artifact_path_escape\"\n | \"artifact_path_empty\"\n | \"artifact_path_absolute\"\n | \"reporter_failure\";\n\n/** Experimental diagnostic emitted by reporter helpers instead of throwing. */\nexport interface TraceReporterDiagnostic {\n code: TraceReporterDiagnosticCode;\n severity: TraceReporterDiagnosticSeverity;\n message: string;\n target?: string;\n}\n\n/** Experimental manifest artifact entry shared by framework reporters. */\nexport interface TraceArtifact {\n kind: TraceArtifactKind;\n path: string;\n format: TraceArtifactFormat;\n redactionProfile: TraceArtifactRedactionProfile;\n title?: string;\n sizeBytes?: number;\n diagnostics?: TraceReporterDiagnostic[];\n}\n\n/** Experimental normalized test result entry shared by framework reporters. */\nexport interface TraceTestResult {\n testId: string;\n name: string;\n file?: string;\n status: TraceTestStatus;\n durationMs?: number;\n tracePath?: string;\n artifacts?: TraceArtifact[];\n diagnostics?: TraceReporterDiagnostic[];\n}\n\n/** Experimental reporter artifact manifest shared by framework reporters. */\nexport interface TraceArtifactManifest {\n schemaVersion: typeof TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION;\n generatedAt: string;\n framework: TraceReporterFramework;\n results: TraceTestResult[];\n artifacts: TraceArtifact[];\n diagnostics: TraceReporterDiagnostic[];\n}\n\n/** Experimental options for deterministic reporter artifact manifest creation. */\nexport interface CreateTraceArtifactManifestOptions {\n framework: TraceReporterFramework;\n generatedAt: string;\n results: TraceTestResult[];\n artifacts?: TraceArtifact[];\n diagnostics?: TraceReporterDiagnostic[];\n}\n\n/** Experimental options for deriving a safe reporter artifact path. */\nexport interface CreateReporterArtifactPathOptions {\n outputDir: string;\n testId: string;\n name: string;\n kind: TraceArtifactKind;\n format: TraceArtifactFormat;\n file?: string;\n}\n\n/** Experimental result from reporter artifact path validation/resolution. */\nexport interface ReporterArtifactPathResult {\n ok: boolean;\n outputDir: string;\n relativePath?: string;\n absolutePath?: string;\n diagnostics: TraceReporterDiagnostic[];\n}\n\nexport interface ValidateReporterArtifactPathOptions {\n outputDir: string;\n relativePath: string;\n}\n\nconst FORMAT_EXTENSIONS: Record<TraceArtifactFormat, string> = {\n json: \"json\",\n jsonl: \"jsonl\",\n md: \"md\",\n html: \"html\",\n};\n\nfunction sortDiagnostics(a: TraceReporterDiagnostic, b: TraceReporterDiagnostic): number {\n return compareStrings(a.target ?? \"\", b.target ?? \"\") ||\n compareStrings(a.code, b.code) ||\n compareStrings(a.severity, b.severity) ||\n compareStrings(a.message, b.message);\n}\n\nfunction sortArtifacts(a: TraceArtifact, b: TraceArtifact): number {\n return compareStrings(a.path, b.path) ||\n compareStrings(a.kind, b.kind) ||\n compareStrings(a.format, b.format) ||\n compareStrings(a.redactionProfile, b.redactionProfile) ||\n compareStrings(a.title ?? \"\", b.title ?? \"\");\n}\n\nfunction sortResults(a: TraceTestResult, b: TraceTestResult): number {\n return compareStrings(a.file ?? \"\", b.file ?? \"\") ||\n compareStrings(a.name, b.name) ||\n compareStrings(a.testId, b.testId);\n}\n\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction cloneDiagnostic(diagnostic: TraceReporterDiagnostic): TraceReporterDiagnostic {\n return {\n code: diagnostic.code,\n severity: diagnostic.severity,\n message: diagnostic.message,\n ...(diagnostic.target === undefined ? {} : { target: diagnostic.target }),\n };\n}\n\nfunction cloneArtifact(artifact: TraceArtifact): TraceArtifact {\n return {\n kind: artifact.kind,\n path: artifact.path,\n format: artifact.format,\n redactionProfile: artifact.redactionProfile,\n ...(artifact.title === undefined ? {} : { title: artifact.title }),\n ...(artifact.sizeBytes === undefined ? {} : { sizeBytes: artifact.sizeBytes }),\n diagnostics: (artifact.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics),\n };\n}\n\nfunction cloneResult(result: TraceTestResult): TraceTestResult {\n return {\n testId: result.testId,\n name: result.name,\n ...(result.file === undefined ? {} : { file: result.file }),\n status: result.status,\n ...(result.durationMs === undefined ? {} : { durationMs: result.durationMs }),\n ...(result.tracePath === undefined ? {} : { tracePath: result.tracePath }),\n artifacts: (result.artifacts ?? []).map(cloneArtifact).sort(sortArtifacts),\n diagnostics: (result.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics),\n };\n}\n\nfunction artifactIdentity(artifact: TraceArtifact): string {\n return `${artifact.path}\\0${artifact.kind}\\0${artifact.format}\\0${artifact.redactionProfile}`;\n}\n\nfunction dedupeArtifacts(artifacts: TraceArtifact[]): TraceArtifact[] {\n const seen = new Set<string>();\n const out: TraceArtifact[] = [];\n for (const artifact of artifacts.sort(sortArtifacts)) {\n const key = artifactIdentity(artifact);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(artifact);\n }\n return out;\n}\n\n/** Experimental helper that creates a deterministic reporter artifact manifest. */\nexport function createTraceArtifactManifest(\n options: CreateTraceArtifactManifestOptions,\n): TraceArtifactManifest {\n const results = options.results.map(cloneResult).sort(sortResults);\n const resultArtifacts = results.flatMap((result) => result.artifacts ?? []);\n const artifacts = dedupeArtifacts([\n ...resultArtifacts,\n ...(options.artifacts ?? []).map(cloneArtifact),\n ]);\n\n return {\n schemaVersion: TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION,\n generatedAt: options.generatedAt,\n framework: options.framework,\n results,\n artifacts,\n diagnostics: (options.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics),\n };\n}\n\n/** Experimental helper that creates a safe, deterministic artifact path for a test. */\nexport function createReporterArtifactPath(\n options: CreateReporterArtifactPathOptions,\n): ReporterArtifactPathResult {\n const fileSlug = slugForPathLabel(options.file ?? \"manual\");\n const testSlug = slugForPathLabel(`${options.name}-${options.testId}`);\n const relativePath = path.posix.join(\n \"tests\",\n fileSlug,\n testSlug,\n `${options.kind}.${FORMAT_EXTENSIONS[options.format]}`,\n );\n\n return validateReporterArtifactPath({\n outputDir: options.outputDir,\n relativePath,\n });\n}\n\n/** Experimental helper that validates an artifact path stays inside outputDir. */\nexport function validateReporterArtifactPath(\n options: ValidateReporterArtifactPathOptions,\n): ReporterArtifactPathResult {\n const outputDir = path.resolve(options.outputDir);\n const diagnostics: TraceReporterDiagnostic[] = [];\n const rawPath = options.relativePath;\n\n if (rawPath.length === 0) {\n diagnostics.push({\n code: \"artifact_path_empty\",\n severity: \"error\",\n message: \"Reporter artifact path must not be empty.\",\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n if (rawPath.includes(\"\\0\")) {\n diagnostics.push({\n code: \"invalid_artifact_path\",\n severity: \"error\",\n message: \"Reporter artifact path must not contain null bytes.\",\n target: rawPath,\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n if (path.isAbsolute(rawPath) || path.win32.isAbsolute(rawPath)) {\n diagnostics.push({\n code: \"artifact_path_absolute\",\n severity: \"error\",\n message: \"Reporter artifact path must be relative.\",\n target: rawPath,\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n const normalized = path.posix.normalize(rawPath.replace(/\\\\/g, \"/\"));\n const segments = normalized.split(\"/\");\n if (\n normalized === \".\" ||\n normalized.startsWith(\"../\") ||\n segments.some((segment) => segment === \"..\")\n ) {\n diagnostics.push({\n code: \"artifact_path_escape\",\n severity: \"error\",\n message: \"Reporter artifact path must stay under the output directory.\",\n target: rawPath,\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n const absolutePath = path.resolve(outputDir, normalized);\n const relFromOutput = path.relative(outputDir, absolutePath);\n if (\n relFromOutput.length === 0 ||\n relFromOutput.startsWith(\"..\") ||\n path.isAbsolute(relFromOutput)\n ) {\n diagnostics.push({\n code: \"artifact_path_escape\",\n severity: \"error\",\n message: \"Reporter artifact path resolved outside the output directory.\",\n target: rawPath,\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n return {\n ok: true,\n outputDir,\n relativePath: normalized,\n absolutePath,\n diagnostics,\n };\n}\n\n/** Experimental helper that converts an unknown reporter failure into a diagnostic. */\nexport function createReporterFailureDiagnostic(\n error: unknown,\n target?: string,\n): TraceReporterDiagnostic {\n const message = error instanceof Error\n ? error.message\n : typeof error === \"string\"\n ? error\n : \"Unknown reporter failure.\";\n\n return {\n code: \"reporter_failure\",\n severity: \"error\",\n message,\n ...(target === undefined ? {} : { target }),\n };\n}\n\nfunction slugForPathLabel(value: string): string {\n const base = path.basename(value)\n .replace(/\\.(test|spec)\\.[^.]+$/, \"\")\n .replace(/\\.[^.]+$/, \"\");\n const slug = base\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^[._-]+|[._-]+$/g, \"\")\n .slice(0, 80);\n return slug.length > 0 ? slug : \"artifact\";\n}\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
declare const TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION: "0.1";
|
|
2
|
+
/** Experimental reporter framework identifier for local CI artifacts. */
|
|
3
|
+
type TraceReporterFramework = "vitest" | "jest" | "manual";
|
|
4
|
+
/** Experimental reporter test status normalized across supported test runners. */
|
|
5
|
+
type TraceTestStatus = "passed" | "failed" | "skipped" | "todo";
|
|
6
|
+
/** Experimental reporter artifact kind used in artifact manifests. */
|
|
7
|
+
type TraceArtifactKind = "trace" | "report" | "eval" | "redaction" | "summary";
|
|
8
|
+
/** Experimental reporter artifact file format used in artifact manifests. */
|
|
9
|
+
type TraceArtifactFormat = "json" | "jsonl" | "md" | "html";
|
|
10
|
+
/** Experimental redaction profile marker recorded with reporter artifacts. */
|
|
11
|
+
type TraceArtifactRedactionProfile = "local" | "share" | "strict";
|
|
12
|
+
/** Experimental reporter diagnostic severity. */
|
|
13
|
+
type TraceReporterDiagnosticSeverity = "info" | "warning" | "error";
|
|
14
|
+
/** Experimental reporter diagnostic code for safe, non-throwing reporter helpers. */
|
|
15
|
+
type TraceReporterDiagnosticCode = "invalid_artifact_path" | "artifact_path_escape" | "artifact_path_empty" | "artifact_path_absolute" | "reporter_failure";
|
|
16
|
+
/** Experimental diagnostic emitted by reporter helpers instead of throwing. */
|
|
17
|
+
interface TraceReporterDiagnostic {
|
|
18
|
+
code: TraceReporterDiagnosticCode;
|
|
19
|
+
severity: TraceReporterDiagnosticSeverity;
|
|
20
|
+
message: string;
|
|
21
|
+
target?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Experimental manifest artifact entry shared by framework reporters. */
|
|
24
|
+
interface TraceArtifact {
|
|
25
|
+
kind: TraceArtifactKind;
|
|
26
|
+
path: string;
|
|
27
|
+
format: TraceArtifactFormat;
|
|
28
|
+
redactionProfile: TraceArtifactRedactionProfile;
|
|
29
|
+
title?: string;
|
|
30
|
+
sizeBytes?: number;
|
|
31
|
+
diagnostics?: TraceReporterDiagnostic[];
|
|
32
|
+
}
|
|
33
|
+
/** Experimental normalized test result entry shared by framework reporters. */
|
|
34
|
+
interface TraceTestResult {
|
|
35
|
+
testId: string;
|
|
36
|
+
name: string;
|
|
37
|
+
file?: string;
|
|
38
|
+
status: TraceTestStatus;
|
|
39
|
+
durationMs?: number;
|
|
40
|
+
tracePath?: string;
|
|
41
|
+
artifacts?: TraceArtifact[];
|
|
42
|
+
diagnostics?: TraceReporterDiagnostic[];
|
|
43
|
+
}
|
|
44
|
+
/** Experimental reporter artifact manifest shared by framework reporters. */
|
|
45
|
+
interface TraceArtifactManifest {
|
|
46
|
+
schemaVersion: typeof TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION;
|
|
47
|
+
generatedAt: string;
|
|
48
|
+
framework: TraceReporterFramework;
|
|
49
|
+
results: TraceTestResult[];
|
|
50
|
+
artifacts: TraceArtifact[];
|
|
51
|
+
diagnostics: TraceReporterDiagnostic[];
|
|
52
|
+
}
|
|
53
|
+
/** Experimental options for deterministic reporter artifact manifest creation. */
|
|
54
|
+
interface CreateTraceArtifactManifestOptions {
|
|
55
|
+
framework: TraceReporterFramework;
|
|
56
|
+
generatedAt: string;
|
|
57
|
+
results: TraceTestResult[];
|
|
58
|
+
artifacts?: TraceArtifact[];
|
|
59
|
+
diagnostics?: TraceReporterDiagnostic[];
|
|
60
|
+
}
|
|
61
|
+
/** Experimental options for deriving a safe reporter artifact path. */
|
|
62
|
+
interface CreateReporterArtifactPathOptions {
|
|
63
|
+
outputDir: string;
|
|
64
|
+
testId: string;
|
|
65
|
+
name: string;
|
|
66
|
+
kind: TraceArtifactKind;
|
|
67
|
+
format: TraceArtifactFormat;
|
|
68
|
+
file?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Experimental result from reporter artifact path validation/resolution. */
|
|
71
|
+
interface ReporterArtifactPathResult {
|
|
72
|
+
ok: boolean;
|
|
73
|
+
outputDir: string;
|
|
74
|
+
relativePath?: string;
|
|
75
|
+
absolutePath?: string;
|
|
76
|
+
diagnostics: TraceReporterDiagnostic[];
|
|
77
|
+
}
|
|
78
|
+
interface ValidateReporterArtifactPathOptions {
|
|
79
|
+
outputDir: string;
|
|
80
|
+
relativePath: string;
|
|
81
|
+
}
|
|
82
|
+
/** Experimental helper that creates a deterministic reporter artifact manifest. */
|
|
83
|
+
declare function createTraceArtifactManifest(options: CreateTraceArtifactManifestOptions): TraceArtifactManifest;
|
|
84
|
+
/** Experimental helper that creates a safe, deterministic artifact path for a test. */
|
|
85
|
+
declare function createReporterArtifactPath(options: CreateReporterArtifactPathOptions): ReporterArtifactPathResult;
|
|
86
|
+
/** Experimental helper that validates an artifact path stays inside outputDir. */
|
|
87
|
+
declare function validateReporterArtifactPath(options: ValidateReporterArtifactPathOptions): ReporterArtifactPathResult;
|
|
88
|
+
/** Experimental helper that converts an unknown reporter failure into a diagnostic. */
|
|
89
|
+
declare function createReporterFailureDiagnostic(error: unknown, target?: string): TraceReporterDiagnostic;
|
|
90
|
+
|
|
91
|
+
export { type CreateReporterArtifactPathOptions, type CreateTraceArtifactManifestOptions, type ReporterArtifactPathResult, TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION, type TraceArtifact, type TraceArtifactFormat, type TraceArtifactKind, type TraceArtifactManifest, type TraceArtifactRedactionProfile, type TraceReporterDiagnostic, type TraceReporterDiagnosticCode, type TraceReporterDiagnosticSeverity, type TraceReporterFramework, type TraceTestResult, type TraceTestStatus, type ValidateReporterArtifactPathOptions, createReporterArtifactPath, createReporterFailureDiagnostic, createTraceArtifactManifest, validateReporterArtifactPath };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
declare const TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION: "0.1";
|
|
2
|
+
/** Experimental reporter framework identifier for local CI artifacts. */
|
|
3
|
+
type TraceReporterFramework = "vitest" | "jest" | "manual";
|
|
4
|
+
/** Experimental reporter test status normalized across supported test runners. */
|
|
5
|
+
type TraceTestStatus = "passed" | "failed" | "skipped" | "todo";
|
|
6
|
+
/** Experimental reporter artifact kind used in artifact manifests. */
|
|
7
|
+
type TraceArtifactKind = "trace" | "report" | "eval" | "redaction" | "summary";
|
|
8
|
+
/** Experimental reporter artifact file format used in artifact manifests. */
|
|
9
|
+
type TraceArtifactFormat = "json" | "jsonl" | "md" | "html";
|
|
10
|
+
/** Experimental redaction profile marker recorded with reporter artifacts. */
|
|
11
|
+
type TraceArtifactRedactionProfile = "local" | "share" | "strict";
|
|
12
|
+
/** Experimental reporter diagnostic severity. */
|
|
13
|
+
type TraceReporterDiagnosticSeverity = "info" | "warning" | "error";
|
|
14
|
+
/** Experimental reporter diagnostic code for safe, non-throwing reporter helpers. */
|
|
15
|
+
type TraceReporterDiagnosticCode = "invalid_artifact_path" | "artifact_path_escape" | "artifact_path_empty" | "artifact_path_absolute" | "reporter_failure";
|
|
16
|
+
/** Experimental diagnostic emitted by reporter helpers instead of throwing. */
|
|
17
|
+
interface TraceReporterDiagnostic {
|
|
18
|
+
code: TraceReporterDiagnosticCode;
|
|
19
|
+
severity: TraceReporterDiagnosticSeverity;
|
|
20
|
+
message: string;
|
|
21
|
+
target?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Experimental manifest artifact entry shared by framework reporters. */
|
|
24
|
+
interface TraceArtifact {
|
|
25
|
+
kind: TraceArtifactKind;
|
|
26
|
+
path: string;
|
|
27
|
+
format: TraceArtifactFormat;
|
|
28
|
+
redactionProfile: TraceArtifactRedactionProfile;
|
|
29
|
+
title?: string;
|
|
30
|
+
sizeBytes?: number;
|
|
31
|
+
diagnostics?: TraceReporterDiagnostic[];
|
|
32
|
+
}
|
|
33
|
+
/** Experimental normalized test result entry shared by framework reporters. */
|
|
34
|
+
interface TraceTestResult {
|
|
35
|
+
testId: string;
|
|
36
|
+
name: string;
|
|
37
|
+
file?: string;
|
|
38
|
+
status: TraceTestStatus;
|
|
39
|
+
durationMs?: number;
|
|
40
|
+
tracePath?: string;
|
|
41
|
+
artifacts?: TraceArtifact[];
|
|
42
|
+
diagnostics?: TraceReporterDiagnostic[];
|
|
43
|
+
}
|
|
44
|
+
/** Experimental reporter artifact manifest shared by framework reporters. */
|
|
45
|
+
interface TraceArtifactManifest {
|
|
46
|
+
schemaVersion: typeof TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION;
|
|
47
|
+
generatedAt: string;
|
|
48
|
+
framework: TraceReporterFramework;
|
|
49
|
+
results: TraceTestResult[];
|
|
50
|
+
artifacts: TraceArtifact[];
|
|
51
|
+
diagnostics: TraceReporterDiagnostic[];
|
|
52
|
+
}
|
|
53
|
+
/** Experimental options for deterministic reporter artifact manifest creation. */
|
|
54
|
+
interface CreateTraceArtifactManifestOptions {
|
|
55
|
+
framework: TraceReporterFramework;
|
|
56
|
+
generatedAt: string;
|
|
57
|
+
results: TraceTestResult[];
|
|
58
|
+
artifacts?: TraceArtifact[];
|
|
59
|
+
diagnostics?: TraceReporterDiagnostic[];
|
|
60
|
+
}
|
|
61
|
+
/** Experimental options for deriving a safe reporter artifact path. */
|
|
62
|
+
interface CreateReporterArtifactPathOptions {
|
|
63
|
+
outputDir: string;
|
|
64
|
+
testId: string;
|
|
65
|
+
name: string;
|
|
66
|
+
kind: TraceArtifactKind;
|
|
67
|
+
format: TraceArtifactFormat;
|
|
68
|
+
file?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Experimental result from reporter artifact path validation/resolution. */
|
|
71
|
+
interface ReporterArtifactPathResult {
|
|
72
|
+
ok: boolean;
|
|
73
|
+
outputDir: string;
|
|
74
|
+
relativePath?: string;
|
|
75
|
+
absolutePath?: string;
|
|
76
|
+
diagnostics: TraceReporterDiagnostic[];
|
|
77
|
+
}
|
|
78
|
+
interface ValidateReporterArtifactPathOptions {
|
|
79
|
+
outputDir: string;
|
|
80
|
+
relativePath: string;
|
|
81
|
+
}
|
|
82
|
+
/** Experimental helper that creates a deterministic reporter artifact manifest. */
|
|
83
|
+
declare function createTraceArtifactManifest(options: CreateTraceArtifactManifestOptions): TraceArtifactManifest;
|
|
84
|
+
/** Experimental helper that creates a safe, deterministic artifact path for a test. */
|
|
85
|
+
declare function createReporterArtifactPath(options: CreateReporterArtifactPathOptions): ReporterArtifactPathResult;
|
|
86
|
+
/** Experimental helper that validates an artifact path stays inside outputDir. */
|
|
87
|
+
declare function validateReporterArtifactPath(options: ValidateReporterArtifactPathOptions): ReporterArtifactPathResult;
|
|
88
|
+
/** Experimental helper that converts an unknown reporter failure into a diagnostic. */
|
|
89
|
+
declare function createReporterFailureDiagnostic(error: unknown, target?: string): TraceReporterDiagnostic;
|
|
90
|
+
|
|
91
|
+
export { type CreateReporterArtifactPathOptions, type CreateTraceArtifactManifestOptions, type ReporterArtifactPathResult, TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION, type TraceArtifact, type TraceArtifactFormat, type TraceArtifactKind, type TraceArtifactManifest, type TraceArtifactRedactionProfile, type TraceReporterDiagnostic, type TraceReporterDiagnosticCode, type TraceReporterDiagnosticSeverity, type TraceReporterFramework, type TraceTestResult, type TraceTestStatus, type ValidateReporterArtifactPathOptions, createReporterArtifactPath, createReporterFailureDiagnostic, createTraceArtifactManifest, validateReporterArtifactPath };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
// packages/core/src/reporters/index.ts
|
|
4
|
+
var TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION = "0.1";
|
|
5
|
+
var FORMAT_EXTENSIONS = {
|
|
6
|
+
json: "json",
|
|
7
|
+
jsonl: "jsonl",
|
|
8
|
+
md: "md",
|
|
9
|
+
html: "html"
|
|
10
|
+
};
|
|
11
|
+
function sortDiagnostics(a, b) {
|
|
12
|
+
return compareStrings(a.target ?? "", b.target ?? "") || compareStrings(a.code, b.code) || compareStrings(a.severity, b.severity) || compareStrings(a.message, b.message);
|
|
13
|
+
}
|
|
14
|
+
function sortArtifacts(a, b) {
|
|
15
|
+
return compareStrings(a.path, b.path) || compareStrings(a.kind, b.kind) || compareStrings(a.format, b.format) || compareStrings(a.redactionProfile, b.redactionProfile) || compareStrings(a.title ?? "", b.title ?? "");
|
|
16
|
+
}
|
|
17
|
+
function sortResults(a, b) {
|
|
18
|
+
return compareStrings(a.file ?? "", b.file ?? "") || compareStrings(a.name, b.name) || compareStrings(a.testId, b.testId);
|
|
19
|
+
}
|
|
20
|
+
function compareStrings(a, b) {
|
|
21
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
22
|
+
}
|
|
23
|
+
function cloneDiagnostic(diagnostic) {
|
|
24
|
+
return {
|
|
25
|
+
code: diagnostic.code,
|
|
26
|
+
severity: diagnostic.severity,
|
|
27
|
+
message: diagnostic.message,
|
|
28
|
+
...diagnostic.target === void 0 ? {} : { target: diagnostic.target }
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function cloneArtifact(artifact) {
|
|
32
|
+
return {
|
|
33
|
+
kind: artifact.kind,
|
|
34
|
+
path: artifact.path,
|
|
35
|
+
format: artifact.format,
|
|
36
|
+
redactionProfile: artifact.redactionProfile,
|
|
37
|
+
...artifact.title === void 0 ? {} : { title: artifact.title },
|
|
38
|
+
...artifact.sizeBytes === void 0 ? {} : { sizeBytes: artifact.sizeBytes },
|
|
39
|
+
diagnostics: (artifact.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function cloneResult(result) {
|
|
43
|
+
return {
|
|
44
|
+
testId: result.testId,
|
|
45
|
+
name: result.name,
|
|
46
|
+
...result.file === void 0 ? {} : { file: result.file },
|
|
47
|
+
status: result.status,
|
|
48
|
+
...result.durationMs === void 0 ? {} : { durationMs: result.durationMs },
|
|
49
|
+
...result.tracePath === void 0 ? {} : { tracePath: result.tracePath },
|
|
50
|
+
artifacts: (result.artifacts ?? []).map(cloneArtifact).sort(sortArtifacts),
|
|
51
|
+
diagnostics: (result.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function artifactIdentity(artifact) {
|
|
55
|
+
return `${artifact.path}\0${artifact.kind}\0${artifact.format}\0${artifact.redactionProfile}`;
|
|
56
|
+
}
|
|
57
|
+
function dedupeArtifacts(artifacts) {
|
|
58
|
+
const seen = /* @__PURE__ */ new Set();
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const artifact of artifacts.sort(sortArtifacts)) {
|
|
61
|
+
const key = artifactIdentity(artifact);
|
|
62
|
+
if (seen.has(key)) continue;
|
|
63
|
+
seen.add(key);
|
|
64
|
+
out.push(artifact);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function createTraceArtifactManifest(options) {
|
|
69
|
+
const results = options.results.map(cloneResult).sort(sortResults);
|
|
70
|
+
const resultArtifacts = results.flatMap((result) => result.artifacts ?? []);
|
|
71
|
+
const artifacts = dedupeArtifacts([
|
|
72
|
+
...resultArtifacts,
|
|
73
|
+
...(options.artifacts ?? []).map(cloneArtifact)
|
|
74
|
+
]);
|
|
75
|
+
return {
|
|
76
|
+
schemaVersion: TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION,
|
|
77
|
+
generatedAt: options.generatedAt,
|
|
78
|
+
framework: options.framework,
|
|
79
|
+
results,
|
|
80
|
+
artifacts,
|
|
81
|
+
diagnostics: (options.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function createReporterArtifactPath(options) {
|
|
85
|
+
const fileSlug = slugForPathLabel(options.file ?? "manual");
|
|
86
|
+
const testSlug = slugForPathLabel(`${options.name}-${options.testId}`);
|
|
87
|
+
const relativePath = path.posix.join(
|
|
88
|
+
"tests",
|
|
89
|
+
fileSlug,
|
|
90
|
+
testSlug,
|
|
91
|
+
`${options.kind}.${FORMAT_EXTENSIONS[options.format]}`
|
|
92
|
+
);
|
|
93
|
+
return validateReporterArtifactPath({
|
|
94
|
+
outputDir: options.outputDir,
|
|
95
|
+
relativePath
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function validateReporterArtifactPath(options) {
|
|
99
|
+
const outputDir = path.resolve(options.outputDir);
|
|
100
|
+
const diagnostics = [];
|
|
101
|
+
const rawPath = options.relativePath;
|
|
102
|
+
if (rawPath.length === 0) {
|
|
103
|
+
diagnostics.push({
|
|
104
|
+
code: "artifact_path_empty",
|
|
105
|
+
severity: "error",
|
|
106
|
+
message: "Reporter artifact path must not be empty."
|
|
107
|
+
});
|
|
108
|
+
return { ok: false, outputDir, diagnostics };
|
|
109
|
+
}
|
|
110
|
+
if (rawPath.includes("\0")) {
|
|
111
|
+
diagnostics.push({
|
|
112
|
+
code: "invalid_artifact_path",
|
|
113
|
+
severity: "error",
|
|
114
|
+
message: "Reporter artifact path must not contain null bytes.",
|
|
115
|
+
target: rawPath
|
|
116
|
+
});
|
|
117
|
+
return { ok: false, outputDir, diagnostics };
|
|
118
|
+
}
|
|
119
|
+
if (path.isAbsolute(rawPath) || path.win32.isAbsolute(rawPath)) {
|
|
120
|
+
diagnostics.push({
|
|
121
|
+
code: "artifact_path_absolute",
|
|
122
|
+
severity: "error",
|
|
123
|
+
message: "Reporter artifact path must be relative.",
|
|
124
|
+
target: rawPath
|
|
125
|
+
});
|
|
126
|
+
return { ok: false, outputDir, diagnostics };
|
|
127
|
+
}
|
|
128
|
+
const normalized = path.posix.normalize(rawPath.replace(/\\/g, "/"));
|
|
129
|
+
const segments = normalized.split("/");
|
|
130
|
+
if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
|
|
131
|
+
diagnostics.push({
|
|
132
|
+
code: "artifact_path_escape",
|
|
133
|
+
severity: "error",
|
|
134
|
+
message: "Reporter artifact path must stay under the output directory.",
|
|
135
|
+
target: rawPath
|
|
136
|
+
});
|
|
137
|
+
return { ok: false, outputDir, diagnostics };
|
|
138
|
+
}
|
|
139
|
+
const absolutePath = path.resolve(outputDir, normalized);
|
|
140
|
+
const relFromOutput = path.relative(outputDir, absolutePath);
|
|
141
|
+
if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path.isAbsolute(relFromOutput)) {
|
|
142
|
+
diagnostics.push({
|
|
143
|
+
code: "artifact_path_escape",
|
|
144
|
+
severity: "error",
|
|
145
|
+
message: "Reporter artifact path resolved outside the output directory.",
|
|
146
|
+
target: rawPath
|
|
147
|
+
});
|
|
148
|
+
return { ok: false, outputDir, diagnostics };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
outputDir,
|
|
153
|
+
relativePath: normalized,
|
|
154
|
+
absolutePath,
|
|
155
|
+
diagnostics
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function createReporterFailureDiagnostic(error, target) {
|
|
159
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown reporter failure.";
|
|
160
|
+
return {
|
|
161
|
+
code: "reporter_failure",
|
|
162
|
+
severity: "error",
|
|
163
|
+
message,
|
|
164
|
+
...target === void 0 ? {} : { target }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function slugForPathLabel(value) {
|
|
168
|
+
const base = path.basename(value).replace(/\.(test|spec)\.[^.]+$/, "").replace(/\.[^.]+$/, "");
|
|
169
|
+
const slug = base.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "").slice(0, 80);
|
|
170
|
+
return slug.length > 0 ? slug : "artifact";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION, createReporterArtifactPath, createReporterFailureDiagnostic, createTraceArtifactManifest, validateReporterArtifactPath };
|
|
174
|
+
//# sourceMappingURL=reporters.mjs.map
|
|
175
|
+
//# sourceMappingURL=reporters.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reporters/index.ts"],"names":[],"mappings":"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","file":"reporters.mjs","sourcesContent":["import path from \"node:path\";\n\nexport const TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION = \"0.1\" as const;\n\n/** Experimental reporter framework identifier for local CI artifacts. */\nexport type TraceReporterFramework = \"vitest\" | \"jest\" | \"manual\";\n\n/** Experimental reporter test status normalized across supported test runners. */\nexport type TraceTestStatus = \"passed\" | \"failed\" | \"skipped\" | \"todo\";\n\n/** Experimental reporter artifact kind used in artifact manifests. */\nexport type TraceArtifactKind = \"trace\" | \"report\" | \"eval\" | \"redaction\" | \"summary\";\n\n/** Experimental reporter artifact file format used in artifact manifests. */\nexport type TraceArtifactFormat = \"json\" | \"jsonl\" | \"md\" | \"html\";\n\n/** Experimental redaction profile marker recorded with reporter artifacts. */\nexport type TraceArtifactRedactionProfile = \"local\" | \"share\" | \"strict\";\n\n/** Experimental reporter diagnostic severity. */\nexport type TraceReporterDiagnosticSeverity = \"info\" | \"warning\" | \"error\";\n\n/** Experimental reporter diagnostic code for safe, non-throwing reporter helpers. */\nexport type TraceReporterDiagnosticCode =\n | \"invalid_artifact_path\"\n | \"artifact_path_escape\"\n | \"artifact_path_empty\"\n | \"artifact_path_absolute\"\n | \"reporter_failure\";\n\n/** Experimental diagnostic emitted by reporter helpers instead of throwing. */\nexport interface TraceReporterDiagnostic {\n code: TraceReporterDiagnosticCode;\n severity: TraceReporterDiagnosticSeverity;\n message: string;\n target?: string;\n}\n\n/** Experimental manifest artifact entry shared by framework reporters. */\nexport interface TraceArtifact {\n kind: TraceArtifactKind;\n path: string;\n format: TraceArtifactFormat;\n redactionProfile: TraceArtifactRedactionProfile;\n title?: string;\n sizeBytes?: number;\n diagnostics?: TraceReporterDiagnostic[];\n}\n\n/** Experimental normalized test result entry shared by framework reporters. */\nexport interface TraceTestResult {\n testId: string;\n name: string;\n file?: string;\n status: TraceTestStatus;\n durationMs?: number;\n tracePath?: string;\n artifacts?: TraceArtifact[];\n diagnostics?: TraceReporterDiagnostic[];\n}\n\n/** Experimental reporter artifact manifest shared by framework reporters. */\nexport interface TraceArtifactManifest {\n schemaVersion: typeof TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION;\n generatedAt: string;\n framework: TraceReporterFramework;\n results: TraceTestResult[];\n artifacts: TraceArtifact[];\n diagnostics: TraceReporterDiagnostic[];\n}\n\n/** Experimental options for deterministic reporter artifact manifest creation. */\nexport interface CreateTraceArtifactManifestOptions {\n framework: TraceReporterFramework;\n generatedAt: string;\n results: TraceTestResult[];\n artifacts?: TraceArtifact[];\n diagnostics?: TraceReporterDiagnostic[];\n}\n\n/** Experimental options for deriving a safe reporter artifact path. */\nexport interface CreateReporterArtifactPathOptions {\n outputDir: string;\n testId: string;\n name: string;\n kind: TraceArtifactKind;\n format: TraceArtifactFormat;\n file?: string;\n}\n\n/** Experimental result from reporter artifact path validation/resolution. */\nexport interface ReporterArtifactPathResult {\n ok: boolean;\n outputDir: string;\n relativePath?: string;\n absolutePath?: string;\n diagnostics: TraceReporterDiagnostic[];\n}\n\nexport interface ValidateReporterArtifactPathOptions {\n outputDir: string;\n relativePath: string;\n}\n\nconst FORMAT_EXTENSIONS: Record<TraceArtifactFormat, string> = {\n json: \"json\",\n jsonl: \"jsonl\",\n md: \"md\",\n html: \"html\",\n};\n\nfunction sortDiagnostics(a: TraceReporterDiagnostic, b: TraceReporterDiagnostic): number {\n return compareStrings(a.target ?? \"\", b.target ?? \"\") ||\n compareStrings(a.code, b.code) ||\n compareStrings(a.severity, b.severity) ||\n compareStrings(a.message, b.message);\n}\n\nfunction sortArtifacts(a: TraceArtifact, b: TraceArtifact): number {\n return compareStrings(a.path, b.path) ||\n compareStrings(a.kind, b.kind) ||\n compareStrings(a.format, b.format) ||\n compareStrings(a.redactionProfile, b.redactionProfile) ||\n compareStrings(a.title ?? \"\", b.title ?? \"\");\n}\n\nfunction sortResults(a: TraceTestResult, b: TraceTestResult): number {\n return compareStrings(a.file ?? \"\", b.file ?? \"\") ||\n compareStrings(a.name, b.name) ||\n compareStrings(a.testId, b.testId);\n}\n\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction cloneDiagnostic(diagnostic: TraceReporterDiagnostic): TraceReporterDiagnostic {\n return {\n code: diagnostic.code,\n severity: diagnostic.severity,\n message: diagnostic.message,\n ...(diagnostic.target === undefined ? {} : { target: diagnostic.target }),\n };\n}\n\nfunction cloneArtifact(artifact: TraceArtifact): TraceArtifact {\n return {\n kind: artifact.kind,\n path: artifact.path,\n format: artifact.format,\n redactionProfile: artifact.redactionProfile,\n ...(artifact.title === undefined ? {} : { title: artifact.title }),\n ...(artifact.sizeBytes === undefined ? {} : { sizeBytes: artifact.sizeBytes }),\n diagnostics: (artifact.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics),\n };\n}\n\nfunction cloneResult(result: TraceTestResult): TraceTestResult {\n return {\n testId: result.testId,\n name: result.name,\n ...(result.file === undefined ? {} : { file: result.file }),\n status: result.status,\n ...(result.durationMs === undefined ? {} : { durationMs: result.durationMs }),\n ...(result.tracePath === undefined ? {} : { tracePath: result.tracePath }),\n artifacts: (result.artifacts ?? []).map(cloneArtifact).sort(sortArtifacts),\n diagnostics: (result.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics),\n };\n}\n\nfunction artifactIdentity(artifact: TraceArtifact): string {\n return `${artifact.path}\\0${artifact.kind}\\0${artifact.format}\\0${artifact.redactionProfile}`;\n}\n\nfunction dedupeArtifacts(artifacts: TraceArtifact[]): TraceArtifact[] {\n const seen = new Set<string>();\n const out: TraceArtifact[] = [];\n for (const artifact of artifacts.sort(sortArtifacts)) {\n const key = artifactIdentity(artifact);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(artifact);\n }\n return out;\n}\n\n/** Experimental helper that creates a deterministic reporter artifact manifest. */\nexport function createTraceArtifactManifest(\n options: CreateTraceArtifactManifestOptions,\n): TraceArtifactManifest {\n const results = options.results.map(cloneResult).sort(sortResults);\n const resultArtifacts = results.flatMap((result) => result.artifacts ?? []);\n const artifacts = dedupeArtifacts([\n ...resultArtifacts,\n ...(options.artifacts ?? []).map(cloneArtifact),\n ]);\n\n return {\n schemaVersion: TRACE_ARTIFACT_MANIFEST_SCHEMA_VERSION,\n generatedAt: options.generatedAt,\n framework: options.framework,\n results,\n artifacts,\n diagnostics: (options.diagnostics ?? []).map(cloneDiagnostic).sort(sortDiagnostics),\n };\n}\n\n/** Experimental helper that creates a safe, deterministic artifact path for a test. */\nexport function createReporterArtifactPath(\n options: CreateReporterArtifactPathOptions,\n): ReporterArtifactPathResult {\n const fileSlug = slugForPathLabel(options.file ?? \"manual\");\n const testSlug = slugForPathLabel(`${options.name}-${options.testId}`);\n const relativePath = path.posix.join(\n \"tests\",\n fileSlug,\n testSlug,\n `${options.kind}.${FORMAT_EXTENSIONS[options.format]}`,\n );\n\n return validateReporterArtifactPath({\n outputDir: options.outputDir,\n relativePath,\n });\n}\n\n/** Experimental helper that validates an artifact path stays inside outputDir. */\nexport function validateReporterArtifactPath(\n options: ValidateReporterArtifactPathOptions,\n): ReporterArtifactPathResult {\n const outputDir = path.resolve(options.outputDir);\n const diagnostics: TraceReporterDiagnostic[] = [];\n const rawPath = options.relativePath;\n\n if (rawPath.length === 0) {\n diagnostics.push({\n code: \"artifact_path_empty\",\n severity: \"error\",\n message: \"Reporter artifact path must not be empty.\",\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n if (rawPath.includes(\"\\0\")) {\n diagnostics.push({\n code: \"invalid_artifact_path\",\n severity: \"error\",\n message: \"Reporter artifact path must not contain null bytes.\",\n target: rawPath,\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n if (path.isAbsolute(rawPath) || path.win32.isAbsolute(rawPath)) {\n diagnostics.push({\n code: \"artifact_path_absolute\",\n severity: \"error\",\n message: \"Reporter artifact path must be relative.\",\n target: rawPath,\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n const normalized = path.posix.normalize(rawPath.replace(/\\\\/g, \"/\"));\n const segments = normalized.split(\"/\");\n if (\n normalized === \".\" ||\n normalized.startsWith(\"../\") ||\n segments.some((segment) => segment === \"..\")\n ) {\n diagnostics.push({\n code: \"artifact_path_escape\",\n severity: \"error\",\n message: \"Reporter artifact path must stay under the output directory.\",\n target: rawPath,\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n const absolutePath = path.resolve(outputDir, normalized);\n const relFromOutput = path.relative(outputDir, absolutePath);\n if (\n relFromOutput.length === 0 ||\n relFromOutput.startsWith(\"..\") ||\n path.isAbsolute(relFromOutput)\n ) {\n diagnostics.push({\n code: \"artifact_path_escape\",\n severity: \"error\",\n message: \"Reporter artifact path resolved outside the output directory.\",\n target: rawPath,\n });\n return { ok: false, outputDir, diagnostics };\n }\n\n return {\n ok: true,\n outputDir,\n relativePath: normalized,\n absolutePath,\n diagnostics,\n };\n}\n\n/** Experimental helper that converts an unknown reporter failure into a diagnostic. */\nexport function createReporterFailureDiagnostic(\n error: unknown,\n target?: string,\n): TraceReporterDiagnostic {\n const message = error instanceof Error\n ? error.message\n : typeof error === \"string\"\n ? error\n : \"Unknown reporter failure.\";\n\n return {\n code: \"reporter_failure\",\n severity: \"error\",\n message,\n ...(target === undefined ? {} : { target }),\n };\n}\n\nfunction slugForPathLabel(value: string): string {\n const base = path.basename(value)\n .replace(/\\.(test|spec)\\.[^.]+$/, \"\")\n .replace(/\\.[^.]+$/, \"\");\n const slug = base\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^[._-]+|[._-]+$/g, \"\")\n .slice(0, 80);\n return slug.length > 0 ? slug : \"artifact\";\n}\n"]}
|