@vitest-agent/sdk 8.0.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,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,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 };