@vitest-agent/sdk 3.1.2 → 4.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.
Files changed (65) hide show
  1. package/README.md +23 -19
  2. package/dispatch.d.ts +25 -6
  3. package/formatters/markdown.js +1 -0
  4. package/formatters/terminal.js +4 -3
  5. package/index.d.ts +448 -2341
  6. package/index.js +10 -68
  7. package/internal-inject-env.js +5 -7
  8. package/package.json +3 -19
  9. package/sidecar-dispatch.js +7 -5
  10. package/utils/format-console.js +9 -10
  11. package/utils/format-terminal.js +7 -7
  12. package/utils/posix-path.js +52 -0
  13. package/utils/test-location.js +11 -5
  14. package/version.js +17 -0
  15. package/layers/ConfigLive.js +0 -46
  16. package/layers/DataReaderLive.js +0 -1923
  17. package/layers/DataStoreLive.js +0 -1287
  18. package/layers/DetailResolverLive.js +0 -9
  19. package/layers/DiscoveryRegistryLive.js +0 -70
  20. package/layers/EnvironmentDetectorLive.js +0 -21
  21. package/layers/EnvironmentDetectorTest.js +0 -13
  22. package/layers/ExecutorResolverLive.js +0 -9
  23. package/layers/FormatSelectorLive.js +0 -20
  24. package/layers/HistoryTrackerLive.js +0 -49
  25. package/layers/HistoryTrackerTest.js +0 -25
  26. package/layers/LoggerLive.js +0 -64
  27. package/layers/OutputPipelineLive.js +0 -13
  28. package/layers/OutputRendererLive.js +0 -28
  29. package/layers/PathResolutionLive.js +0 -40
  30. package/layers/PerClientSessionMapLive.js +0 -145
  31. package/layers/ProjectDiscoveryLive.js +0 -68
  32. package/layers/ProjectDiscoveryTest.js +0 -12
  33. package/layers/ProjectIdentityLive.js +0 -96
  34. package/layers/RunContextLive.js +0 -81
  35. package/lib/format-triage.js +0 -103
  36. package/lib/format-wrapup.js +0 -67
  37. package/migrations/0001_initial.js +0 -824
  38. package/migrations/0002_test_artifacts.js +0 -30
  39. package/migrations/index.js +0 -26
  40. package/migrations/registry_0001_initial.js +0 -39
  41. package/migrations/session_map_0001_initial.js +0 -38
  42. package/services/Config.js +0 -15
  43. package/services/DataReader.js +0 -8
  44. package/services/DataStore.js +0 -15
  45. package/services/DetailResolver.js +0 -8
  46. package/services/DiscoveryRegistry.js +0 -8
  47. package/services/EnvironmentDetector.js +0 -8
  48. package/services/ExecutorResolver.js +0 -8
  49. package/services/FormatSelector.js +0 -8
  50. package/services/HistoryTracker.js +0 -20
  51. package/services/OutputRenderer.js +0 -8
  52. package/services/PerClientSessionMap.js +0 -22
  53. package/services/ProjectDiscovery.js +0 -8
  54. package/services/ProjectIdentity.js +0 -63
  55. package/services/RunContext.js +0 -53
  56. package/services/idempotency.js +0 -58
  57. package/sql/assemblers.js +0 -69
  58. package/testing/layers.js +0 -21
  59. package/testing.d.ts +0 -1862
  60. package/testing.js +0 -254
  61. package/utils/ensure-migrated.js +0 -52
  62. package/utils/failure-signature.js +0 -34
  63. package/utils/resolve-data-path.js +0 -65
  64. package/utils/resolve-project-key-from-cwd.js +0 -53
  65. package/utils/resolve-workspace-key.js +0 -41
package/testing.js DELETED
@@ -1,254 +0,0 @@
1
- import { AgentNotFoundError, RegistrationConflictError } from "./errors/AgentErrors.js";
2
- import { DataStoreError } from "./errors/DataStoreError.js";
3
- import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "./errors/TddErrors.js";
4
- import { DataReader } from "./services/DataReader.js";
5
- import { AgentId, ChatId } from "./schemas/Identity.js";
6
- import { Agent, IdempotencyHit } from "./schemas/Agent.js";
7
- import { DataStore } from "./services/DataStore.js";
8
- import { PROJECT_MIGRATIONS } from "./migrations/index.js";
9
- import { CoverageReport, FileCoverageReport } from "./schemas/Coverage.js";
10
- import { AgentReport } from "./schemas/AgentReport.js";
11
- import { CoverageBaselines } from "./schemas/Baselines.js";
12
- import { CacheManifest } from "./schemas/CacheManifest.js";
13
- import { HistoryRecord } from "./schemas/History.js";
14
- import { BehaviorDetail, BehaviorRow, BehaviorStatus, GoalDetail, GoalRow, GoalStatus } from "./schemas/Tdd.js";
15
- import { TrendEntry, TrendRecord } from "./schemas/Trends.js";
16
- import { DataStoreTestLayer, makeTestLayer } from "./testing/layers.js";
17
- import { Effect, Layer } from "effect";
18
-
19
- //#region src/testing/index.ts
20
- /** @public */
21
- function empty(filename) {
22
- return makeTestLayer(filename);
23
- }
24
- /** @public */
25
- function singlePassingRun(filename) {
26
- const base = makeTestLayer(filename);
27
- const seed = Effect.gen(function* () {
28
- const store = yield* DataStore;
29
- yield* store.writeSettings("hash-preset-spr", {
30
- vitestVersion: "5.0.0",
31
- pool: "forks"
32
- }, {});
33
- const runId = yield* store.writeRun({
34
- invocationId: "inv-preset-spr-1",
35
- project: "default",
36
- settingsHash: "hash-preset-spr",
37
- timestamp: "2026-01-01T00:00:00.000Z",
38
- commitSha: null,
39
- branch: null,
40
- reason: "passed",
41
- duration: 1200,
42
- total: 3,
43
- passed: 3,
44
- failed: 0,
45
- skipped: 0,
46
- scoped: false
47
- });
48
- const fileId = yield* store.ensureFile("src/math.test.ts");
49
- const [moduleId] = yield* store.writeModules(runId, [{
50
- fileId,
51
- relativeModuleId: "src/math.test.ts",
52
- state: "passed",
53
- duration: 1200
54
- }]);
55
- yield* store.writeTestCases(moduleId, [
56
- {
57
- name: "adds two numbers",
58
- fullName: "math > adds two numbers",
59
- state: "passed",
60
- duration: 400
61
- },
62
- {
63
- name: "subtracts two numbers",
64
- fullName: "math > subtracts two numbers",
65
- state: "passed",
66
- duration: 400
67
- },
68
- {
69
- name: "multiplies two numbers",
70
- fullName: "math > multiplies two numbers",
71
- state: "passed",
72
- duration: 400
73
- }
74
- ]);
75
- });
76
- return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
77
- }
78
- /** @public */
79
- function withFailures(filename) {
80
- const base = makeTestLayer(filename);
81
- const seed = Effect.gen(function* () {
82
- const store = yield* DataStore;
83
- yield* store.writeSettings("hash-preset-wf", {
84
- vitestVersion: "5.0.0",
85
- pool: "forks"
86
- }, {});
87
- const runId = yield* store.writeRun({
88
- invocationId: "inv-preset-wf-1",
89
- project: "default",
90
- settingsHash: "hash-preset-wf",
91
- timestamp: "2026-01-01T00:00:00.000Z",
92
- commitSha: null,
93
- branch: null,
94
- reason: "failed",
95
- duration: 1500,
96
- total: 4,
97
- passed: 2,
98
- failed: 2,
99
- skipped: 0,
100
- scoped: false
101
- });
102
- const fileId = yield* store.ensureFile("src/parser.test.ts");
103
- const [moduleId] = yield* store.writeModules(runId, [{
104
- fileId,
105
- relativeModuleId: "src/parser.test.ts",
106
- state: "failed",
107
- duration: 1500
108
- }]);
109
- yield* store.writeTestCases(moduleId, [
110
- {
111
- name: "parses valid input",
112
- fullName: "parser > parses valid input",
113
- state: "passed",
114
- duration: 300
115
- },
116
- {
117
- name: "parses nested structure",
118
- fullName: "parser > parses nested structure",
119
- state: "passed",
120
- duration: 300
121
- },
122
- {
123
- name: "rejects empty input",
124
- fullName: "parser > rejects empty input",
125
- state: "failed",
126
- duration: 450
127
- },
128
- {
129
- name: "rejects malformed input",
130
- fullName: "parser > rejects malformed input",
131
- state: "failed",
132
- duration: 450
133
- }
134
- ]);
135
- });
136
- return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
137
- }
138
- /** @public */
139
- function flaky(filename) {
140
- const base = makeTestLayer(filename);
141
- const seed = Effect.gen(function* () {
142
- const store = yield* DataStore;
143
- yield* store.writeSettings("hash-preset-flaky", {
144
- vitestVersion: "5.0.0",
145
- pool: "forks"
146
- }, {});
147
- const runId1 = yield* store.writeRun({
148
- invocationId: "inv-preset-flaky-1",
149
- project: "default",
150
- settingsHash: "hash-preset-flaky",
151
- timestamp: "2026-01-01T00:00:00.000Z",
152
- commitSha: null,
153
- branch: null,
154
- reason: "failed",
155
- duration: 800,
156
- total: 2,
157
- passed: 1,
158
- failed: 1,
159
- skipped: 0,
160
- scoped: false
161
- });
162
- const fileId = yield* store.ensureFile("src/async.test.ts");
163
- const [moduleId1] = yield* store.writeModules(runId1, [{
164
- fileId,
165
- relativeModuleId: "src/async.test.ts",
166
- state: "failed",
167
- duration: 800
168
- }]);
169
- yield* store.writeTestCases(moduleId1, [{
170
- name: "resolves within timeout",
171
- fullName: "async > resolves within timeout",
172
- state: "failed",
173
- duration: 500
174
- }, {
175
- name: "rejects on error",
176
- fullName: "async > rejects on error",
177
- state: "passed",
178
- duration: 300
179
- }]);
180
- yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId1, "2025-12-31T23:00:00.000Z", "passed", 500, false, 0, null);
181
- yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId1, "2026-01-01T00:00:00.000Z", "failed", 500, false, 0, null);
182
- yield* store.writeHistory("default", "async > rejects on error", "src/async.test.ts", runId1, "2026-01-01T00:00:00.000Z", "passed", 300, false, 0, null);
183
- const runId2 = yield* store.writeRun({
184
- invocationId: "inv-preset-flaky-2",
185
- project: "default",
186
- settingsHash: "hash-preset-flaky",
187
- timestamp: "2026-01-01T01:00:00.000Z",
188
- commitSha: null,
189
- branch: null,
190
- reason: "passed",
191
- duration: 700,
192
- total: 2,
193
- passed: 2,
194
- failed: 0,
195
- skipped: 0,
196
- scoped: false
197
- });
198
- const [moduleId2] = yield* store.writeModules(runId2, [{
199
- fileId,
200
- relativeModuleId: "src/async.test.ts",
201
- state: "passed",
202
- duration: 700
203
- }]);
204
- yield* store.writeTestCases(moduleId2, [{
205
- name: "resolves within timeout",
206
- fullName: "async > resolves within timeout",
207
- state: "passed",
208
- duration: 400
209
- }, {
210
- name: "rejects on error",
211
- fullName: "async > rejects on error",
212
- state: "passed",
213
- duration: 300
214
- }]);
215
- yield* store.writeHistory("default", "async > resolves within timeout", "src/async.test.ts", runId2, "2026-01-01T01:00:00.000Z", "passed", 400, false, 0, null);
216
- yield* store.writeHistory("default", "async > rejects on error", "src/async.test.ts", runId2, "2026-01-01T01:00:00.000Z", "passed", 300, false, 0, null);
217
- });
218
- return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
219
- }
220
- /** @public */
221
- function withTddTask(filename) {
222
- const base = makeTestLayer(filename);
223
- const seed = Effect.gen(function* () {
224
- const store = yield* DataStore;
225
- const sessionId = yield* store.writeSession({
226
- chatId: "chat-preset-tdd",
227
- project: "default",
228
- cwd: "/workspace",
229
- agentKind: "main",
230
- startedAt: "2026-01-01T00:00:00.000Z"
231
- });
232
- const tddTaskId = yield* store.writeTddTask({
233
- sessionId,
234
- goal: "Implement the parser feature",
235
- startedAt: "2026-01-01T00:00:00.000Z"
236
- });
237
- const goal = yield* store.createGoal({
238
- tddTaskId,
239
- goal: "Handle edge cases"
240
- });
241
- yield* store.createBehavior({
242
- goalId: goal.id,
243
- behavior: "rejects empty input"
244
- });
245
- yield* store.createBehavior({
246
- goalId: goal.id,
247
- behavior: "rejects null input"
248
- });
249
- });
250
- return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
251
- }
252
-
253
- //#endregion
254
- export { Agent, AgentId, AgentNotFoundError, AgentReport, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorStatus, CacheManifest, ChatId, CoverageBaselines, CoverageReport, DataReader, DataStore, DataStoreError, DataStoreTestLayer, FileCoverageReport, GoalDetail, GoalNotFoundError, GoalRow, GoalStatus, HistoryRecord, IdempotencyHit, IllegalStatusTransitionError, PROJECT_MIGRATIONS, RegistrationConflictError, TddTaskAlreadyEndedError, TddTaskNotFoundError, TrendEntry, TrendRecord, empty, flaky, makeTestLayer, singlePassingRun, withFailures, withTddTask };
@@ -1,52 +0,0 @@
1
- import { LoggerLive } from "../layers/LoggerLive.js";
2
- import { PROJECT_MIGRATIONS } from "../migrations/index.js";
3
- import { Effect, Layer } from "effect";
4
- import { SqlClient } from "effect/unstable/sql/SqlClient";
5
- import * as NodeServices from "@effect/platform-node/NodeServices";
6
- import { layer } from "@effect/sql-sqlite-node/SqliteClient";
7
- import * as SqliteMigrator from "@effect/sql-sqlite-node/SqliteMigrator";
8
-
9
- //#region src/utils/ensure-migrated.ts
10
- const GLOBAL_KEY = Symbol.for("vitest-agent/migration-promises");
11
- const getCache = () => {
12
- const g = globalThis;
13
- let cache = g[GLOBAL_KEY];
14
- if (!cache) {
15
- cache = /* @__PURE__ */ new Map();
16
- g[GLOBAL_KEY] = cache;
17
- }
18
- return cache;
19
- };
20
- /**
21
- * Ensure the SQLite database at `dbPath` is migrated. Runs migrations at
22
- * most once per dbPath in the current process; concurrent calls share the
23
- * same in-flight promise. Subsequent calls (after the first resolves) are
24
- * no-ops.
25
- * @public
26
- */
27
- function ensureMigrated(dbPath, logLevel, logFile) {
28
- const cache = getCache();
29
- const cached = cache.get(dbPath);
30
- if (cached) return cached;
31
- const SqliteLayer = layer({ filename: dbPath });
32
- const PlatformLayer = NodeServices.layer;
33
- const MigratorLayer = SqliteMigrator.layer({ loader: SqliteMigrator.fromRecord(PROJECT_MIGRATIONS) }).pipe(Layer.provide(Layer.merge(SqliteLayer, PlatformLayer)));
34
- const program = Effect.gen(function* () {
35
- yield* SqlClient;
36
- }).pipe(Effect.provide(MigratorLayer), Effect.provide(Layer.merge(SqliteLayer, PlatformLayer)), Effect.provide(LoggerLive(logLevel, logFile)));
37
- const promise = Effect.runPromise(program);
38
- cache.set(dbPath, promise);
39
- promise.catch(() => {});
40
- return promise;
41
- }
42
- /**
43
- * Reset the migration cache. Test-only.
44
- *
45
- * @internal
46
- */
47
- function _resetMigrationCacheForTesting() {
48
- getCache().clear();
49
- }
50
-
51
- //#endregion
52
- export { _resetMigrationCacheForTesting, ensureMigrated };
@@ -1,34 +0,0 @@
1
- import { createHash } from "node:crypto";
2
-
3
- //#region src/utils/failure-signature.ts
4
- const ASSERTION_REGEX = /(toBe|toEqual|toContain|toMatch|toThrow|toBeNull|toBeUndefined|toBeTruthy|toBeFalsy|toBeGreaterThan|toBeLessThan|toBeInstanceOf|toBeCloseTo|toHaveBeenCalled|toHaveBeenCalledWith|toHaveProperty|toHaveLength)\s*\(([^)]*)\)/;
5
- const typeTagOf = (literal) => {
6
- const trimmed = literal.trim();
7
- if (trimmed === "" || trimmed === ")") return "";
8
- if (/^\d+(\.\d+)?$/.test(trimmed)) return "<number>";
9
- if (/^["'`]/.test(trimmed)) return "<string>";
10
- if (/^(true|false)$/.test(trimmed)) return "<boolean>";
11
- if (/^null$/.test(trimmed)) return "<null>";
12
- if (/^undefined$/.test(trimmed)) return "<undefined>";
13
- if (/^[{[]/.test(trimmed)) return "<object>";
14
- return "<expr>";
15
- };
16
- /** @public */
17
- const normalizeAssertionShape = (message) => {
18
- if (typeof message !== "string") return "";
19
- const match = message.match(ASSERTION_REGEX);
20
- if (match === null) return null;
21
- const matcher = match[1];
22
- const arg = typeTagOf(match[2]);
23
- return arg === "" ? matcher : `${matcher}(${arg})`;
24
- };
25
- /** @public */
26
- const computeFailureSignature = (input) => {
27
- const shape = normalizeAssertionShape(input.assertion_message) ?? "<unknown>";
28
- const lineCoord = input.top_frame_function_boundary_line !== null ? `fb:${input.top_frame_function_boundary_line}` : input.top_frame_raw_line !== void 0 ? `raw:${Math.floor(input.top_frame_raw_line / 10) * 10}` : "raw:?";
29
- const key = `${input.error_name}|${shape}|${input.top_frame_function_name}|${lineCoord}`;
30
- return createHash("sha256").update(key).digest("hex").substring(0, 16);
31
- };
32
-
33
- //#endregion
34
- export { computeFailureSignature, normalizeAssertionShape };
@@ -1,65 +0,0 @@
1
- import { VitestAgentConfig } from "../schemas/Config.js";
2
- import { VitestAgentConfigFile } from "../services/Config.js";
3
- import { normalizeWorkspaceKey } from "./normalize-workspace-key.js";
4
- import { resolveProjectKeyFromCwd } from "./resolve-project-key-from-cwd.js";
5
- import { Effect, FileSystem } from "effect";
6
- import { join } from "node:path";
7
- import { AppDirs } from "@effected/xdg";
8
-
9
- //#region src/utils/resolve-data-path.ts
10
- /**
11
- * Filename of the SQLite database that stores all reporter data.
12
- * @public
13
- */
14
- const DATABASE_FILENAME = "data.db";
15
- /**
16
- * Resolve the absolute path to the SQLite database for `projectDir`.
17
- *
18
- * The directory containing the database is ensured to exist before the
19
- * function returns, so callers can open the database immediately without
20
- * needing to create parent directories. The SQLite driver creates the file
21
- * but not its parent.
22
- *
23
- * Precedence (highest first):
24
- *
25
- * 1. `options.cacheDir` — programmatic override.
26
- * 2. `cacheDir` from `vitest-agent.config.toml`.
27
- * 3. `<XDG data>/<normalized projectKey from config>/data.db`.
28
- * 4. `<XDG data>/<projectKey resolved from cwd's package.json>/data.db`.
29
- * Source 4 prefers `repository.url` (canonicalized to `host__path`),
30
- * falling back to the normalized `name`. This matches the sidecar
31
- * CLI's `_internal register-agent` resolver so the reporter and
32
- * sidecar always write to the same `data.db`.
33
- *
34
- * The XDG data directory is namespaced via `AppDirs` (typically
35
- * `$XDG_DATA_HOME/vitest-agent`, defaulting to
36
- * `~/.local/share/vitest-agent`).
37
- *
38
- * The path is a function of identity, not filesystem layout. When no
39
- * `package.json` is reachable from `projectDir`, the cwd basename is used
40
- * as the final fallback so the function never throws on identity lookup.
41
- *
42
- * @param projectDir - Absolute path inside the user's workspace.
43
- * @param options - Optional programmatic overrides.
44
- * @public
45
- */
46
- const resolveDataPath = (projectDir, options = {}) => Effect.gen(function* () {
47
- const fs = yield* FileSystem.FileSystem;
48
- if (options.cacheDir) {
49
- yield* fs.makeDirectory(options.cacheDir, { recursive: true });
50
- return join(options.cacheDir, DATABASE_FILENAME);
51
- }
52
- const loaded = yield* (yield* VitestAgentConfigFile).loadOrDefault(new VitestAgentConfig({}));
53
- if (loaded.cacheDir) {
54
- yield* fs.makeDirectory(loaded.cacheDir, { recursive: true });
55
- return join(loaded.cacheDir, DATABASE_FILENAME);
56
- }
57
- const dataRoot = yield* (yield* AppDirs).ensureData;
58
- const key = loaded.projectKey ? normalizeWorkspaceKey(loaded.projectKey) : resolveProjectKeyFromCwd(projectDir);
59
- const dir = join(dataRoot, key);
60
- yield* fs.makeDirectory(dir, { recursive: true });
61
- return join(dir, DATABASE_FILENAME);
62
- });
63
-
64
- //#endregion
65
- export { DATABASE_FILENAME, resolveDataPath };
@@ -1,53 +0,0 @@
1
- import { gitUrlToProjectKey } from "./canonicalize-git-url.js";
2
- import { normalizeWorkspaceKey } from "./normalize-workspace-key.js";
3
- import { dirname, join } from "node:path";
4
- import { existsSync, readFileSync } from "node:fs";
5
-
6
- //#region src/utils/resolve-project-key-from-cwd.ts
7
- const PACKAGE_JSON = "package.json";
8
- const findNearestPackageJson = (startDir) => {
9
- let dir = startDir;
10
- while (true) {
11
- const candidate = join(dir, PACKAGE_JSON);
12
- if (existsSync(candidate)) return candidate;
13
- const parent = dirname(dir);
14
- if (parent === dir) return null;
15
- dir = parent;
16
- }
17
- };
18
- const readRepositoryUrl = (parsed) => {
19
- const repo = parsed.repository;
20
- if (typeof repo === "string" && repo.trim().length > 0) return repo.trim();
21
- if (repo !== null && typeof repo === "object" && "url" in repo && typeof repo.url === "string") {
22
- const url = repo.url.trim();
23
- return url.length > 0 ? url : null;
24
- }
25
- return null;
26
- };
27
- /**
28
- * Compute the project key for a workspace by reading `package.json`
29
- * fields directly. Returns the canonical `host__path` form when a git
30
- * remote URL is present, otherwise the normalized package name.
31
- *
32
- * Always returns a non-empty string — falls back to the cwd basename
33
- * (or `"anonymous-project"`) so callers don't have to handle the
34
- * empty case.
35
- * @public
36
- */
37
- const resolveProjectKeyFromCwd = (cwd) => {
38
- const pkgPath = findNearestPackageJson(cwd);
39
- if (pkgPath !== null) try {
40
- const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
41
- const repoUrl = readRepositoryUrl(parsed);
42
- if (repoUrl !== null) {
43
- const key = gitUrlToProjectKey(repoUrl);
44
- if (key !== null) return key;
45
- }
46
- if (typeof parsed.name === "string" && parsed.name.length > 0) return normalizeWorkspaceKey(parsed.name);
47
- } catch {}
48
- const basename = cwd.split("/").filter((segment) => segment.length > 0).pop() ?? "anonymous-project";
49
- return normalizeWorkspaceKey(basename);
50
- };
51
-
52
- //#endregion
53
- export { resolveProjectKeyFromCwd };
@@ -1,41 +0,0 @@
1
- import { normalizeWorkspaceKey } from "./normalize-workspace-key.js";
2
- import { Effect } from "effect";
3
- import { WorkspaceDiscovery, WorkspaceRootNotFoundError } from "@effected/workspaces";
4
-
5
- //#region src/utils/resolve-workspace-key.ts
6
- /**
7
- * Resolve the normalized workspace key for the workspace containing
8
- * `projectDir`.
9
- *
10
- * The key is the root `package.json`'s `name`, normalized via
11
- * `normalizeWorkspaceKey`. This becomes the directory segment under
12
- * `$XDG_DATA_HOME/vitest-agent/` where the SQLite database lives.
13
- *
14
- * Fails with `WorkspaceRootNotFoundError` when no root workspace is
15
- * discoverable. `WorkspacePackage.name` is enforced non-empty by
16
- * `@effected/workspaces`'s schema, so a successful root lookup always yields a
17
- * usable name.
18
- *
19
- * Discovery is anchored at `projectDir` per call via `listPackagesIn`, not at
20
- * the `WorkspaceDiscovery` layer's `cwd`: patterns, member manifests and names
21
- * are read from beneath `projectDir`'s own root, so one long-lived layer
22
- * answers correctly for a git worktree, a nested repository, or an unrelated
23
- * project. Fails with the library's `WorkspaceRootNotFoundError` when
24
- * `projectDir` sits in no workspace at all; results are memoized per resolved
25
- * root, so many directories in one workspace share a single discovery.
26
- *
27
- * @param projectDir - Absolute path inside the workspace. Typically the
28
- * reporter's resolved `projectDir` (CLAUDE_PROJECT_DIR or process.cwd()).
29
- * @public
30
- */
31
- const resolveWorkspaceKey = (projectDir) => Effect.gen(function* () {
32
- const root = (yield* (yield* WorkspaceDiscovery).listPackagesIn(projectDir)).find((pkg) => pkg.isRootWorkspace);
33
- if (!root) return yield* Effect.fail(new WorkspaceRootNotFoundError({
34
- searchPath: projectDir,
35
- markers: []
36
- }));
37
- return normalizeWorkspaceKey(root.name);
38
- });
39
-
40
- //#endregion
41
- export { resolveWorkspaceKey };