@vitest-agent/sdk 7.1.0 → 9.0.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.
@@ -1,222 +0,0 @@
1
- import { ansi } from "./ansi.js";
2
- import { formatScopedCoverageNote } from "./format-scoped-coverage-note.js";
3
-
4
- // src/utils/format-console.ts
5
- /**
6
- * Return the metric with the lowest percentage from a `FileCoverageReport`.
7
- *
8
- * @remarks
9
- * Used to surface the single worst coverage metric per file in console output,
10
- * keeping the display compact. Labels are abbreviated: `"Stmts"`, `"Branch"`,
11
- * `"Funcs"`, `"Lines"`.
12
- *
13
- * @param f - File coverage report to analyze
14
- * @returns Object with `label` and `pct` of the worst metric
15
- *
16
- * @internal
17
- */
18
- function getWorstMetric(f) {
19
- return [
20
- {
21
- label: "Stmts",
22
- pct: f.summary.statements
23
- },
24
- {
25
- label: "Branch",
26
- pct: f.summary.branches
27
- },
28
- {
29
- label: "Funcs",
30
- pct: f.summary.functions
31
- },
32
- {
33
- label: "Lines",
34
- pct: f.summary.lines
35
- }
36
- ].reduce((worst, m) => m.pct < worst.pct ? m : worst);
37
- }
38
- /**
39
- * Strip the working directory prefix from a file path to produce a
40
- * project-relative path for display.
41
- *
42
- * @param filePath - Absolute or relative file path
43
- * @param cwd - Working directory to strip
44
- * @returns Project-relative path string
45
- *
46
- * @internal
47
- */
48
- function relativePath(filePath, cwd) {
49
- if (filePath.startsWith(cwd)) {
50
- const rel = filePath.slice(cwd.length);
51
- return rel.startsWith("/") ? rel.slice(1) : rel;
52
- }
53
- return filePath;
54
- }
55
- /**
56
- * Determine the output tier based on test results and coverage state.
57
- *
58
- * - `"red"` -- failures, unhandled errors, or files below threshold
59
- * - `"yellow"` -- all tests pass but coverage targets exist (room to improve)
60
- * - `"green"` -- all tests pass, all coverage targets met
61
- *
62
- * @internal
63
- */
64
- function determineTier(report) {
65
- if (report.summary.failed > 0 || report.unhandledErrors.length > 0) return "red";
66
- if (report.coverage) {
67
- if (report.coverage.lowCoverage.length > 0) return "red";
68
- if (report.coverage.targets && report.coverage.belowTarget && report.coverage.belowTarget.length > 0) return "yellow";
69
- }
70
- return "green";
71
- }
72
- /**
73
- * Format an {@link (AgentReport:variable)} as compact, actionable console markdown.
74
- *
75
- * @remarks
76
- * The output is structured for LLM agent consumption with four sections:
77
- *
78
- * 1. **Compact header** with pass/fail counts and duration
79
- * 2. **Failed test details** with error messages and diffs (the core value)
80
- * 3. **Coverage gaps** showing only files below threshold, worst metric first
81
- * 4. **Next steps** with re-run commands and cache file pointer
82
- *
83
- * When all tests pass, the output collapses to a single success line
84
- * with an optional cache file reference.
85
- *
86
- * Returns an empty string when `consoleOutput` is `"silent"`.
87
- *
88
- * @param report - The report to format
89
- * @param options - Formatting options controlling verbosity and color
90
- * @returns Formatted markdown string, or empty string for silent mode
91
- *
92
- * @internal
93
- */
94
- function formatConsoleMarkdown(report, options) {
95
- const { consoleOutput, coverageConsoleLimit, noColor, cwd } = options;
96
- if (consoleOutput === "silent") return "";
97
- const ao = { noColor };
98
- const lines = [];
99
- const hasFailures = report.summary.failed > 0;
100
- const duration = Math.round(report.summary.duration);
101
- const projectLabel = report.project ? ` (${report.project})` : "";
102
- if (hasFailures) lines.push(`## ${ansi("✗", "red", ao)} Vitest${projectLabel} \u2014 ${report.summary.failed} failed, ${report.summary.passed} passed (${duration}ms)`);
103
- else lines.push(`## ${ansi("✓", "green", ao)} Vitest${projectLabel} \u2014 ${report.summary.passed} passed (${duration}ms)`);
104
- lines.push("");
105
- if (options.trendSummary && report.coverage) {
106
- const { direction, runCount, firstMetric } = options.trendSummary;
107
- const tier = determineTier(report);
108
- if (tier === "green") {
109
- const trendNote = `Coverage: all targets met | trending ${direction} over ${runCount} runs`;
110
- lines[0] = `${lines[0]} — ${trendNote}`;
111
- } else if (tier === "yellow") {
112
- const belowTargetCount = report.coverage.belowTarget?.length ?? 0;
113
- let summary = `Coverage: ${belowTargetCount} file${belowTargetCount !== 1 ? "s" : ""} below target`;
114
- summary += ` | trending ${direction} over ${runCount} runs`;
115
- if (firstMetric) {
116
- summary += ` (${firstMetric.name}: ${firstMetric.from}% -> ${firstMetric.to}%`;
117
- if (firstMetric.target) summary += `, target: ${firstMetric.target}%`;
118
- summary += ")";
119
- }
120
- lines.push(summary);
121
- lines.push("");
122
- } else {
123
- let summary = "Coverage:";
124
- if (report.coverage.lowCoverage.length > 0) summary += ` ${report.coverage.lowCoverage.length} file${report.coverage.lowCoverage.length !== 1 ? "s" : ""} below threshold`;
125
- summary += ` | trending ${direction} over ${runCount} runs`;
126
- if (firstMetric) summary += ` (${firstMetric.name}: ${firstMetric.from}% -> ${firstMetric.to}%)`;
127
- lines.push(summary);
128
- lines.push("");
129
- }
130
- }
131
- if (hasFailures) for (const mod of report.failed) {
132
- const file = relativePath(mod.file, cwd);
133
- lines.push(`### ${ansi("✗", "red", ao)} \`${file}\``);
134
- lines.push("");
135
- if (mod.errors && mod.errors.length > 0) for (const err of mod.errors) {
136
- lines.push(ansi(err.message, "red", ao));
137
- if (err.diff) {
138
- lines.push("");
139
- lines.push("```diff");
140
- lines.push(err.diff);
141
- lines.push("```");
142
- }
143
- lines.push("");
144
- }
145
- const failedTests = mod.tests.filter((t) => t.state === "failed");
146
- for (const test of failedTests) {
147
- const label = test.classification ? ` [${test.classification}]` : "";
148
- lines.push(`- ${ansi("✗", "red", ao)} **${test.fullName}**${label}`);
149
- if (test.errors && test.errors.length > 0) for (const err of test.errors) {
150
- lines.push(` ${ansi(err.message, "dim", ao)}`);
151
- if (err.diff) {
152
- lines.push("");
153
- lines.push(" ```diff");
154
- for (const diffLine of err.diff.split("\n")) lines.push(` ${diffLine}`);
155
- lines.push(" ```");
156
- }
157
- }
158
- lines.push("");
159
- }
160
- }
161
- if (report.coverage) {
162
- const cov = report.coverage;
163
- const globalThresholds = cov.thresholds.global;
164
- if (cov.scoped) {
165
- lines.push(formatScopedCoverageNote(cov.scopedFiles?.length ?? 0, cov.totalFiles));
166
- lines.push("");
167
- }
168
- const filesToShow = cov.lowCoverage.slice(0, coverageConsoleLimit);
169
- if (filesToShow.length > 0) {
170
- lines.push(`### Coverage gaps`);
171
- lines.push("");
172
- for (const f of filesToShow) {
173
- const file = relativePath(f.file, cwd);
174
- const worst = getWorstMetric(f);
175
- const thresholdValue = globalThresholds[worst.label === "Stmts" ? "statements" : worst.label === "Branch" ? "branches" : worst.label === "Funcs" ? "functions" : "lines"];
176
- const thresholdSuffix = thresholdValue != null ? ` (threshold: ${thresholdValue}%)` : "";
177
- lines.push(`- \`${file}\` \u2014 ${worst.label}: ${worst.pct}%${thresholdSuffix} \u2014 uncovered: ${f.uncoveredLines}`);
178
- }
179
- lines.push("");
180
- }
181
- if (cov.targets && cov.belowTarget && cov.belowTarget.length > 0) {
182
- lines.push(`### Coverage improvements needed`);
183
- lines.push("");
184
- for (const f of cov.belowTarget.slice(0, coverageConsoleLimit)) {
185
- const file = relativePath(f.file, cwd);
186
- const worst = getWorstMetric(f);
187
- const metricKey = worst.label === "Stmts" ? "statements" : worst.label === "Branch" ? "branches" : worst.label === "Funcs" ? "functions" : "lines";
188
- const targetValue = cov.targets.global[metricKey];
189
- const targetSuffix = targetValue != null ? ` (target: ${targetValue}%)` : "";
190
- lines.push(`- \`${file}\` \u2014 ${worst.label}: ${worst.pct}%${targetSuffix} \u2014 uncovered: ${f.uncoveredLines}`);
191
- }
192
- lines.push("");
193
- }
194
- }
195
- if (hasFailures || report.coverage && report.coverage.lowCoverage.length > 0) {
196
- lines.push("### Next steps");
197
- lines.push("");
198
- if (hasFailures) {
199
- const allFailedTests = report.failed.flatMap((m) => m.tests.filter((t) => t.state === "failed"));
200
- const newFailures = allFailedTests.filter((t) => t.classification === "new-failure");
201
- const persistent = allFailedTests.filter((t) => t.classification === "persistent");
202
- const flaky = allFailedTests.filter((t) => t.classification === "flaky");
203
- if (newFailures.length > 0) {
204
- const files = [...new Set(report.failed.filter((m) => m.tests.some((t) => t.classification === "new-failure")).map((m) => relativePath(m.file, cwd)))];
205
- lines.push(`- Fix ${newFailures.length} new failure${newFailures.length > 1 ? "s" : ""} in ${files.map((f) => `\`${f}\``).join(", ")} (likely caused by recent changes)`);
206
- }
207
- if (persistent.length > 0) lines.push(`- ${persistent.length} persistent failure${persistent.length > 1 ? "s" : ""} (pre-existing, may not be yours)`);
208
- if (flaky.length > 0) lines.push(`- ${flaky.length} flaky test${flaky.length > 1 ? "s" : ""} (may pass on retry)`);
209
- for (const file of report.failedFiles) lines.push(`- Re-run: \`vitest run ${relativePath(file, cwd)}\``);
210
- if (options.mcp) {
211
- lines.push("- Use `test_history` for failure trends");
212
- lines.push("- Use `test_errors` to search errors by type");
213
- lines.push("- Use `test_coverage` for coverage gap analysis");
214
- lines.push("- Use `note({ action: \"create\" })` to record debugging findings");
215
- }
216
- }
217
- lines.push("");
218
- }
219
- return lines.join("\n");
220
- }
221
-
222
- export { determineTier, formatConsoleMarkdown, getWorstMetric, relativePath };
@@ -1,46 +0,0 @@
1
- import { Cause } from "effect";
2
-
3
- // src/utils/format-fatal-error.ts
4
- const ISSUE_URL = "https://github.com/spencerbeggs/vitest-agent/issues";
5
- const FIBER_FAILURE_CAUSE_KEY = Symbol.for("effect/Runtime/FiberFailure/Cause");
6
- /**
7
- * Format an unknown error into a human-readable string with issue URL.
8
- *
9
- * Extraction order:
10
- * 1. Effect Cause — use `Cause.pretty()` for full formatting
11
- * 2. Effect FiberFailure — extract Cause and use `Cause.pretty()`
12
- * 3. Plain Error — extract `.message` and `.stack`
13
- * 4. Unknown — `String(err)`
14
- *
15
- * @public
16
- */
17
- function formatFatalError(err) {
18
- let detail;
19
- if (Cause.isCause(err)) detail = Cause.pretty(err);
20
- else if (err != null && typeof err === "object" && FIBER_FAILURE_CAUSE_KEY in err) {
21
- const cause = err[FIBER_FAILURE_CAUSE_KEY];
22
- if (Cause.isCause(cause)) detail = Cause.pretty(cause);
23
- else detail = String(err);
24
- } else if (err instanceof Error) try {
25
- detail = err.stack ?? err.message;
26
- } catch {
27
- let ctorName = "Error";
28
- try {
29
- ctorName = err.constructor?.name ?? "Error";
30
- } catch {}
31
- detail = `<unserializable ${ctorName}>`;
32
- }
33
- else if (err !== null && typeof err === "object") try {
34
- detail = JSON.stringify(err, null, 2);
35
- } catch {
36
- try {
37
- detail = String(err);
38
- } catch {
39
- detail = "<unserializable error>";
40
- }
41
- }
42
- else detail = String(err);
43
- return `${detail}\nPlease report at ${ISSUE_URL}`;
44
- }
45
-
46
- export { formatFatalError };
@@ -1,160 +0,0 @@
1
- // src/utils/format-gfm.ts
2
- /**
3
- * Format a number as a percentage string.
4
- *
5
- * @internal
6
- */
7
- function pct(n) {
8
- return `${n}%`;
9
- }
10
- /**
11
- * Find the worst (lowest) coverage metric for a file.
12
- *
13
- * @internal
14
- */
15
- function worstCoverage(f) {
16
- return [
17
- {
18
- label: "Stmts",
19
- pct: f.summary.statements
20
- },
21
- {
22
- label: "Branch",
23
- pct: f.summary.branches
24
- },
25
- {
26
- label: "Funcs",
27
- pct: f.summary.functions
28
- },
29
- {
30
- label: "Lines",
31
- pct: f.summary.lines
32
- }
33
- ].reduce((worst, m) => m.pct < worst.pct ? m : worst);
34
- }
35
- /**
36
- * Format the body of a single project's GFM output.
37
- *
38
- * @internal
39
- */
40
- function formatProjectBody(report) {
41
- const lines = [];
42
- if (report.failed.length > 0) {
43
- lines.push("## Failed Tests");
44
- lines.push("");
45
- for (const mod of report.failed) {
46
- lines.push(`### \`${mod.file}\``);
47
- lines.push("");
48
- if (mod.errors && mod.errors.length > 0) for (const err of mod.errors) {
49
- lines.push(`> ${err.message}`);
50
- if (err.diff) {
51
- lines.push("");
52
- lines.push("```diff");
53
- lines.push(err.diff);
54
- lines.push("```");
55
- }
56
- lines.push("");
57
- }
58
- const failedTests = mod.tests.filter((t) => t.state === "failed");
59
- for (const test of failedTests) {
60
- lines.push(`**${test.fullName}**`);
61
- lines.push("");
62
- if (test.errors && test.errors.length > 0) for (const err of test.errors) {
63
- lines.push(`> ${err.message}`);
64
- if (err.diff) {
65
- lines.push("");
66
- lines.push("```diff");
67
- lines.push(err.diff);
68
- lines.push("```");
69
- }
70
- }
71
- lines.push("");
72
- }
73
- }
74
- }
75
- if (report.coverage) {
76
- const cov = report.coverage;
77
- lines.push("## Coverage");
78
- lines.push("");
79
- lines.push("| Metric | Coverage |");
80
- lines.push("| --- | --- |");
81
- lines.push(`| Statements | ${pct(cov.totals.statements)} |`);
82
- lines.push(`| Branches | ${pct(cov.totals.branches)} |`);
83
- lines.push(`| Functions | ${pct(cov.totals.functions)} |`);
84
- lines.push(`| Lines | ${pct(cov.totals.lines)} |`);
85
- lines.push("");
86
- if (cov.lowCoverage.length > 0) {
87
- const thresholdParts = [];
88
- const g = cov.thresholds.global;
89
- if (g.lines !== void 0) thresholdParts.push(`lines: ${g.lines}%`);
90
- if (g.functions !== void 0) thresholdParts.push(`functions: ${g.functions}%`);
91
- if (g.branches !== void 0) thresholdParts.push(`branches: ${g.branches}%`);
92
- if (g.statements !== void 0) thresholdParts.push(`statements: ${g.statements}%`);
93
- const thresholdDisplay = thresholdParts.length > 0 ? thresholdParts.join(", ") : "configured thresholds";
94
- lines.push("> [!WARNING]");
95
- lines.push(`> ${cov.lowCoverage.length} file(s) below coverage threshold (${thresholdDisplay}):`);
96
- lines.push(">");
97
- for (const f of cov.lowCoverage) {
98
- const worst = worstCoverage(f);
99
- lines.push(`> - \`${f.file}\` — ${worst.label} ${pct(worst.pct)}`);
100
- }
101
- lines.push("");
102
- }
103
- }
104
- return lines;
105
- }
106
- /**
107
- * Format one or more {@link (AgentReport:variable) | AgentReports} as a GitHub-Flavored
108
- * Markdown string for writing to `GITHUB_STEP_SUMMARY`.
109
- *
110
- * @remarks
111
- * GFM-specific features used in the output:
112
- *
113
- * - **Summary table** with pass/fail counts across all projects
114
- * - **Collapsible `<details>` blocks** for per-project results in monorepo setups
115
- * - **Tables** for coverage metrics
116
- * - **Diff-fenced code blocks** for expected/received comparison output
117
- * - **GitHub Alert callouts** (`[!WARNING]`) for coverage threshold violations
118
- *
119
- * For a single report, the project body is rendered directly without
120
- * a `<details>` wrapper. For multiple reports (monorepo), each project
121
- * gets its own collapsible section with a status icon in the summary line.
122
- *
123
- * @param reports - One or more agent reports to format
124
- * @returns GFM-formatted string ready for appending to `GITHUB_STEP_SUMMARY`
125
- *
126
- * @public
127
- */
128
- function formatGfm(reports) {
129
- const lines = [];
130
- const headerIcon = reports.some((r) => r.summary.failed > 0) ? "❌" : "✅";
131
- lines.push(`# ${headerIcon} Vitest Results`);
132
- lines.push("");
133
- const totalPassed = reports.reduce((acc, r) => acc + r.summary.passed, 0);
134
- const totalFailed = reports.reduce((acc, r) => acc + r.summary.failed, 0);
135
- const totalTests = reports.reduce((acc, r) => acc + r.summary.total, 0);
136
- lines.push("| | Count |");
137
- lines.push("| --- | --- |");
138
- lines.push(`| Passed | **${totalPassed}** |`);
139
- lines.push(`| Failed | **${totalFailed}** |`);
140
- lines.push(`| Total | **${totalTests}** |`);
141
- lines.push("");
142
- if (reports.length > 1) for (const report of reports) {
143
- const projectLabel = report.project ?? "default";
144
- const statusIcon = report.summary.failed > 0 ? "❌" : "✅";
145
- lines.push(`<details>`);
146
- lines.push(`<summary>${statusIcon} ${projectLabel}</summary>`);
147
- lines.push("");
148
- const bodyLines = formatProjectBody(report);
149
- lines.push(...bodyLines);
150
- lines.push("</details>");
151
- lines.push("");
152
- }
153
- else if (reports.length === 1) {
154
- const bodyLines = formatProjectBody(reports[0]);
155
- lines.push(...bodyLines);
156
- }
157
- return lines.join("\n");
158
- }
159
-
160
- export { formatGfm };
@@ -1,10 +0,0 @@
1
- // src/utils/hyperlink.ts
2
- const ESC = "\x1B";
3
- const BEL = "\x07";
4
- /** @public */
5
- const osc8 = (url, label, options) => {
6
- if (!options.enabled) return label;
7
- return `${ESC}]8;;${url}${BEL}${label}${ESC}]8;;${BEL}`;
8
- };
9
-
10
- export { osc8 };
@@ -1,33 +0,0 @@
1
- // src/utils/normalize-workspace-key.ts
2
- /**
3
- * Normalize a workspace name into a filesystem-safe directory segment.
4
- *
5
- * The workspace key drives the per-project subdirectory under the XDG data
6
- * directory (e.g. `$XDG_DATA_HOME/vitest-agent/<key>/data.db`). The
7
- * key must be deterministic for a given input and safe to use as a single
8
- * path segment on macOS, Linux, and Windows.
9
- *
10
- * Rules (applied in order):
11
- *
12
- * 1. Replace `/` with `__` so scoped names like `@org/pkg` collapse to
13
- * `@org__pkg` rather than introducing a subdirectory boundary. Scoped
14
- * npm names contain at most one `/` (between scope and name), and
15
- * leading-`@` requires the scope/name form, so `@org__pkg` is not a
16
- * valid unscoped npm name -- the `__` token is unambiguous in
17
- * practice.
18
- * 2. Replace any character outside `[A-Za-z0-9._@-]` with `_`. This catches
19
- * Windows-reserved characters (`\`, `:`, `*`, `?`, `"`, `<`, `>`, `|`),
20
- * control characters, whitespace, and anything else that varies in
21
- * interpretation across filesystems.
22
- * 3. Collapse runs of underscores produced by the substitution above into a
23
- * single `_` so the output stays compact.
24
- *
25
- * @param name - The raw `name` field from a workspace's `package.json`.
26
- * @returns A filesystem-safe single path segment.
27
- * @public
28
- */
29
- function normalizeWorkspaceKey(name) {
30
- return name.replaceAll("/", "__").replace(/[^A-Za-z0-9._@-]/g, "_").replace(/_{3,}/g, "__");
31
- }
32
-
33
- export { normalizeWorkspaceKey };
@@ -1,98 +0,0 @@
1
- // src/utils/probe-host-metadata.ts
2
- const isUseful = (value) => value !== void 0 && value.length > 0;
3
- /**
4
- * Walk the priority chain of env-var probes. Returns the first match
5
- * along with any decorating metadata (`term_program`, `ci_provider`,
6
- * etc.) for that source. When no probe matches, `source`/`value`/
7
- * `metadata` are all `null`.
8
- *
9
- * The probe order matches the plan's table exactly:
10
- *
11
- * 1. `TMUX_PANE` (tmux reattach-stable)
12
- * 2. `WT_SESSION` (Windows Terminal pane)
13
- * 3. `WEZTERM_PANE` (WezTerm pane)
14
- * 4. `KITTY_WINDOW_ID` (kitty window)
15
- * 5. `TERM_SESSION_ID` (iTerm2, Terminal.app)
16
- * 6. `VSCODE_INJECTION` + `process.ppid` (handled by the Live layer
17
- * since it needs `process.ppid`, not env)
18
- * 7. `GITHUB_RUN_ID` + `GITHUB_RUN_ATTEMPT`
19
- * 8. `BUILDKITE_JOB_ID` / `CIRCLE_BUILD_NUM` / `GITLAB_CI_JOB_ID`
20
- * 9. `process.ppid` walk (handled by the Live layer)
21
- * @public
22
- */
23
- const probeHostMetadataFromEnv = (env) => {
24
- const decoration = (extra) => {
25
- const baseDecoration = {};
26
- if (isUseful(env.TERM_PROGRAM)) baseDecoration.term_program = env.TERM_PROGRAM;
27
- if (isUseful(env.TERM_PROGRAM_VERSION)) baseDecoration.term_program_version = env.TERM_PROGRAM_VERSION;
28
- return {
29
- ...baseDecoration,
30
- ...extra
31
- };
32
- };
33
- if (isUseful(env.TMUX_PANE)) return {
34
- source: "TMUX_PANE",
35
- value: env.TMUX_PANE,
36
- metadata: decoration(isUseful(env.TMUX) ? { tmux_socket: env.TMUX } : {})
37
- };
38
- if (isUseful(env.WT_SESSION)) return {
39
- source: "WT_SESSION",
40
- value: env.WT_SESSION,
41
- metadata: decoration({})
42
- };
43
- if (isUseful(env.WEZTERM_PANE)) return {
44
- source: "WEZTERM_PANE",
45
- value: env.WEZTERM_PANE,
46
- metadata: decoration({})
47
- };
48
- if (isUseful(env.KITTY_WINDOW_ID)) return {
49
- source: "KITTY_WINDOW_ID",
50
- value: env.KITTY_WINDOW_ID,
51
- metadata: decoration({})
52
- };
53
- if (isUseful(env.TERM_SESSION_ID)) return {
54
- source: "TERM_SESSION_ID",
55
- value: env.TERM_SESSION_ID,
56
- metadata: decoration({})
57
- };
58
- if (isUseful(env.GITHUB_RUN_ID)) return {
59
- source: "GITHUB_RUN_ID",
60
- value: env.GITHUB_RUN_ID,
61
- metadata: decoration({
62
- ci: true,
63
- ci_provider: "github",
64
- ...isUseful(env.GITHUB_RUN_ATTEMPT) ? { github_run_attempt: env.GITHUB_RUN_ATTEMPT } : {}
65
- })
66
- };
67
- if (isUseful(env.BUILDKITE_JOB_ID)) return {
68
- source: "BUILDKITE_JOB_ID",
69
- value: env.BUILDKITE_JOB_ID,
70
- metadata: decoration({
71
- ci: true,
72
- ci_provider: "buildkite"
73
- })
74
- };
75
- if (isUseful(env.CIRCLE_BUILD_NUM)) return {
76
- source: "CIRCLE_BUILD_NUM",
77
- value: env.CIRCLE_BUILD_NUM,
78
- metadata: decoration({
79
- ci: true,
80
- ci_provider: "circle"
81
- })
82
- };
83
- if (isUseful(env.GITLAB_CI_JOB_ID)) return {
84
- source: "GITLAB_CI_JOB_ID",
85
- value: env.GITLAB_CI_JOB_ID,
86
- metadata: decoration({
87
- ci: true,
88
- ci_provider: "gitlab"
89
- })
90
- };
91
- return {
92
- source: null,
93
- value: null,
94
- metadata: null
95
- };
96
- };
97
-
98
- export { probeHostMetadataFromEnv };
@@ -1,33 +0,0 @@
1
- // src/utils/safe-filename.ts
2
- /**
3
- * Convert a project name to a filesystem-safe filename.
4
- *
5
- * Replaces `/` and `:` characters with `__` (double underscore).
6
- * Returns `"default"` for empty strings, which is used as the
7
- * fallback project name for single-repo configurations.
8
- *
9
- * @param name - Project name to sanitize
10
- * @returns Filesystem-safe filename string
11
- *
12
- * @example
13
- * ```typescript
14
- * import { safeFilename } from "@vitest-agent/sdk/utils";
15
- *
16
- * safeFilename("\@savvy-web/my-lib:unit");
17
- * // Returns: "\@savvy-web__my-lib__unit"
18
- *
19
- * safeFilename("core");
20
- * // Returns: "core"
21
- *
22
- * safeFilename("");
23
- * // Returns: "default"
24
- * ```
25
- *
26
- * @public
27
- */
28
- function safeFilename(name) {
29
- if (!name) return "default";
30
- return name.replace(/[/:]/g, "__");
31
- }
32
-
33
- export { safeFilename };