@vitest-agent/sdk 1.3.4 → 2.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 (58) hide show
  1. package/errors/DataStoreError.js +22 -13
  2. package/index.d.ts +1032 -1101
  3. package/index.js +3 -3
  4. package/layers/ConfigLive.js +8 -9
  5. package/layers/DataReaderLive.js +2 -2
  6. package/layers/DataStoreLive.js +2 -2
  7. package/layers/DiscoveryRegistryLive.js +1 -1
  8. package/layers/LoggerLive.js +24 -24
  9. package/layers/PathResolutionLive.js +18 -17
  10. package/layers/PerClientSessionMapLive.js +1 -1
  11. package/layers/ProjectDiscoveryLive.js +1 -2
  12. package/layers/ProjectIdentityLive.js +16 -12
  13. package/layers/RunContextLive.js +20 -20
  14. package/migrations/0001_initial.js +1 -1
  15. package/migrations/registry_0001_initial.js +1 -1
  16. package/migrations/session_map_0001_initial.js +1 -1
  17. package/package.json +18 -18
  18. package/schemas/AgentReport.js +6 -9
  19. package/schemas/Baselines.js +3 -3
  20. package/schemas/CacheManifest.js +2 -2
  21. package/schemas/ChannelEvent.js +30 -3
  22. package/schemas/Common.js +76 -14
  23. package/schemas/ConsoleLeaks.js +2 -2
  24. package/schemas/Coverage.js +8 -8
  25. package/schemas/CoverageTargets.js +16 -10
  26. package/schemas/History.js +4 -4
  27. package/schemas/Identity.js +19 -7
  28. package/schemas/Options.js +3 -3
  29. package/schemas/RenderState.js +26 -19
  30. package/schemas/RunEvent.js +154 -115
  31. package/schemas/Tdd.js +16 -6
  32. package/schemas/Thresholds.js +6 -6
  33. package/schemas/Transport.js +1 -1
  34. package/schemas/Trends.js +7 -3
  35. package/schemas/turns/FileEditPayload.js +5 -1
  36. package/schemas/turns/HookFirePayload.js +15 -1
  37. package/schemas/turns/index.js +9 -1
  38. package/services/Config.js +3 -3
  39. package/services/DataReader.js +1 -1
  40. package/services/DataStore.js +1 -1
  41. package/services/DetailResolver.js +1 -1
  42. package/services/DiscoveryRegistry.js +1 -1
  43. package/services/EnvironmentDetector.js +1 -1
  44. package/services/ExecutorResolver.js +1 -1
  45. package/services/FormatSelector.js +1 -1
  46. package/services/HistoryTracker.js +1 -1
  47. package/services/OutputRenderer.js +1 -1
  48. package/services/PerClientSessionMap.js +2 -2
  49. package/services/ProjectDiscovery.js +1 -1
  50. package/services/ProjectIdentity.js +1 -1
  51. package/services/RunContext.js +1 -1
  52. package/testing/layers.js +2 -2
  53. package/testing.d.ts +312 -332
  54. package/tsdoc-metadata.json +1 -1
  55. package/utils/build-report.js +16 -1
  56. package/utils/ensure-migrated.js +3 -3
  57. package/utils/resolve-data-path.js +3 -3
  58. package/utils/resolve-workspace-key.js +11 -6
package/index.js CHANGED
@@ -89,7 +89,7 @@ import { UserPromptPayload } from "./schemas/turns/UserPromptPayload.js";
89
89
  import { TurnPayload } from "./schemas/turns/index.js";
90
90
  import { IDEMPOTENCY_ROOT_SENTINEL, deriveIdempotencyKey } from "./services/idempotency.js";
91
91
  import { assembleHistoryRecord, assembleManifest } from "./sql/assemblers.js";
92
- import { buildAgentReport } from "./utils/build-report.js";
92
+ import { buildAgentReport, countSuiteFailures } from "./utils/build-report.js";
93
93
  import { computeTrend, getRecentDirection, hashTargets } from "./utils/compute-trend.js";
94
94
  import { buildConsoleLeaks, collectConsoleLeakEntries } from "./utils/console-leaks.js";
95
95
  import { detectPackageManager, getRunCommand } from "./utils/detect-pm.js";
@@ -115,7 +115,7 @@ import { requiredArtifactForTransition, transitionEnforcesBehaviorMatch, validat
115
115
  * substituted yet. Exported for version introspection by downstream tooling.
116
116
  * @public
117
117
  */
118
- const CURRENT_SDK_VERSION = "1.3.4";
118
+ const CURRENT_SDK_VERSION = "2.0.0";
119
119
 
120
120
  //#endregion
121
- export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentContext, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConfigLive, ConsoleLeakFile, ConsoleLeaks, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, CoverageGap, CoverageLevel, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTotals, DATABASE_FILENAME, DataReader, DataReaderLive, DataStore, DataStoreError, DataStoreLive, DetailLevel, DetailResolver, DetailResolverLive, DiscoveryError, DiscoveryRegistry, DiscoveryRegistryLive, Environment, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, Executor, ExecutorResolver, ExecutorResolverLive, FailureRecord, FileCoverageReport, FileEditPayload, FormatSelector, FormatSelectorLive, GfmFormatter, GitCommandError, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HumanConsoleMode, HypothesisPayload, IDEMPOTENCY_ROOT_SENTINEL, IdempotencyHit, IllegalStatusTransitionError, JsonFormatter, LoggerLive, MarkdownFormatter, MetricThresholds, ModuleRecord, ModuleReport, NotePayload, OutputFormat, OutputPipelineLive, OutputRenderer, OutputRendererLive, PackageManager, PathResolutionError, PathResolutionLive, PatternThresholds, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PhaseTransitionEvent, ProbeMissError, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityNotResolvableError, ProjectIdentityTest, ProjectKey, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportSummary, ResolvedThresholds, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, RunEvent, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, SilentFormatter, SuggestedActionRecord, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TerminalFormatter, TestClassification, TestHistory, TestRecord, TestReport, TestRun, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TurnPayload, UserPromptPayload, VitestAgentConfig, VitestAgentConfigFile, _resetMigrationCacheForTesting, ansi, assembleHistoryRecord, assembleManifest, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, ciAnnotationsFormatter, classifyTest, collectConsoleLeakEntries, compressLines, computeFailureSignature, computeTrend, deriveIdempotencyKey, detectPackageManager, detectVitestScripts, determineTier, ensureMigrated, extractSqlReason, findFunctionBoundary, formatConsoleMarkdown, formatFatalError, formatGfm, formatTriageEffect, formatWrapupEffect, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, historyKey, initialRenderState, isTimeoutError, isVitestInvocation, migration as migration0001, normalizeAssertionShape, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, migration$1 as registryMigration0001, relativePath, requiredArtifactForTransition, resolveCoverageInput, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveWorkspaceKey, rewriteBashCommand, safeFilename, migration$2 as sessionMapMigration0001, stripAnsi, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
121
+ export { ActionSeverity, ActorType, Agent, AgentConsoleMode, AgentContext, AgentId, AgentNotFoundError, AgentPluginOptions, AgentReport, AgentReporterOptions, BehaviorAbandonedEvent, BehaviorAddedEvent, BehaviorCompletedEvent, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorScopedEventTypes, BehaviorStartedEvent, BehaviorStatus, BehaviorsReadyEvent, BlockedEvent, CURRENT_SDK_VERSION, CacheManifest, CacheManifestEntry, ChannelEvent, ChatId, CiConsoleMode, ConfigLive, ConsoleLeakFile, ConsoleLeaks, ConsoleLogLevel, ConsoleMode, ConsoleOutputMode, ConsoleOutputs, ConversationId, ConversationNotFoundError, CoverageBaselines, FileCoverageReport as CoverageFile, CoverageGap, CoverageLevel, CoverageMetric, CoverageRenderState, CoverageReport, CoverageTargets, CoverageTargetsMetrics, CoverageTotals, DATABASE_FILENAME, DataReader, DataReaderLive, DataStore, DataStoreError, DataStoreLive, DetailLevel, DetailResolver, DetailResolverLive, DiscoveryError, DiscoveryRegistry, DiscoveryRegistryLive, Environment, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, Executor, ExecutorResolver, ExecutorResolverLive, FailureRecord, FileCoverageReport, FileEditPayload, FormatSelector, FormatSelectorLive, GfmFormatter, GitCommandError, GoalAbandonedEvent, GoalAddedEvent, GoalCompletedEvent, GoalDetail, GoalNotFoundError, GoalRow, GoalStartedEvent, GoalStatus, GoalsReadyEvent, HistoryRecord, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, HookFirePayload, HookStatus, HookType, HostKind, HostMetadataParseError, HumanConsoleMode, HypothesisPayload, IDEMPOTENCY_ROOT_SENTINEL, IdempotencyHit, IllegalStatusTransitionError, JsonFormatter, LoggerLive, MarkdownFormatter, MetricThresholds, ModuleRecord, ModuleReport, NotePayload, OutputFormat, OutputPipelineLive, OutputRenderer, OutputRendererLive, PackageManager, PathResolutionError, PathResolutionLive, PatternThresholds, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PhaseTransitionEvent, ProbeMissError, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityNotResolvableError, ProjectIdentityTest, ProjectKey, RegistrationConflictError, RenderState, RenderTotals, ReportError, ReportSummary, ResolvedThresholds, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, RunEvent, SessionCompleteEvent, SessionNotFoundError, SessionOutcome, SidecarTimeoutError, SilentFormatter, SuggestedActionRecord, TagCountEntry, TddTaskAlreadyEndedError, TddTaskId, TddTaskNotFoundError, TerminalFormatter, TestClassification, TestHistory, TestRecord, TestReport, TestRun, TestRunReason, TestState, ToolCallPayload, ToolResultPayload, Transport, TrendEntry, TrendRecord, TurnPayload, UserPromptPayload, VitestAgentConfig, VitestAgentConfigFile, _resetMigrationCacheForTesting, ansi, assembleHistoryRecord, assembleManifest, buildAgentReport, buildConsoleLeaks, buildEnvPrefix, canonicalizeGitUrl, ciAnnotationsFormatter, classifyTest, collectConsoleLeakEntries, compressLines, computeFailureSignature, computeTrend, countSuiteFailures, deriveIdempotencyKey, detectPackageManager, detectVitestScripts, determineTier, ensureMigrated, extractSqlReason, findFunctionBoundary, formatConsoleMarkdown, formatFatalError, formatGfm, formatTriageEffect, formatWrapupEffect, getRecentDirection, getRunCommand, getWorstMetric, gitUrlToProjectKey, hashTargets, historyKey, initialRenderState, isTimeoutError, isVitestInvocation, migration as migration0001, normalizeAssertionShape, normalizeWorkspaceKey, osc8, probeHostMetadataFromEnv, migration$1 as registryMigration0001, relativePath, requiredArtifactForTransition, resolveCoverageInput, resolveDataPath, resolveLogFile, resolveLogLevel, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveWorkspaceKey, rewriteBashCommand, safeFilename, migration$2 as sessionMapMigration0001, stripAnsi, transitionEnforcesBehaviorMatch, validateCoverageConfig, validateCoverageTargetsShape, validatePhaseTransition };
@@ -1,6 +1,6 @@
1
1
  import { VitestAgentConfig } from "../schemas/Config.js";
2
2
  import { VitestAgentConfigFile } from "../services/Config.js";
3
- import { ConfigFile, FirstMatch, GitRoot, TomlCodec, UpwardWalk, WorkspaceRoot } from "config-file-effect";
3
+ import { ConfigFile, ConfigResolver, MergeStrategy, TomlCodec } from "@effected/config-file";
4
4
 
5
5
  //#region src/layers/ConfigLive.ts
6
6
  const CONFIG_FILENAME = "vitest-agent.config.toml";
@@ -13,8 +13,8 @@ const CONFIG_FILENAME = "vitest-agent.config.toml";
13
13
  * 2. The git repository root (when `projectDir` is inside a git repo).
14
14
  * 3. Walking upward from `projectDir`.
15
15
  *
16
- * The first found file wins (`FirstMatch` strategy). If no file is present,
17
- * downstream callers use `loadOrDefault(new VitestAgentConfig({}))`
16
+ * The first found file wins (`MergeStrategy.firstMatch()`). If no file is
17
+ * present, downstream callers use `loadOrDefault(new VitestAgentConfig({}))`
18
18
  * to get an empty config.
19
19
  *
20
20
  * @param projectDir - Absolute path inside the user's workspace. Resolvers
@@ -22,21 +22,20 @@ const CONFIG_FILENAME = "vitest-agent.config.toml";
22
22
  * sees the right config even when invoked from elsewhere.
23
23
  * @public
24
24
  */
25
- const ConfigLive = (projectDir) => ConfigFile.Live({
26
- tag: VitestAgentConfigFile,
25
+ const ConfigLive = (projectDir) => ConfigFile.layer(VitestAgentConfigFile, {
27
26
  schema: VitestAgentConfig,
28
27
  codec: TomlCodec,
29
- strategy: FirstMatch,
28
+ strategy: MergeStrategy.firstMatch(),
30
29
  resolvers: [
31
- WorkspaceRoot({
30
+ ConfigResolver.workspaceRoot({
32
31
  filename: CONFIG_FILENAME,
33
32
  cwd: projectDir
34
33
  }),
35
- GitRoot({
34
+ ConfigResolver.gitRoot({
36
35
  filename: CONFIG_FILENAME,
37
36
  cwd: projectDir
38
37
  }),
39
- UpwardWalk({
38
+ ConfigResolver.upwardWalk({
40
39
  filename: CONFIG_FILENAME,
41
40
  cwd: projectDir
42
41
  })
@@ -2,7 +2,7 @@ import { DataStoreError, extractSqlReason } from "../errors/DataStoreError.js";
2
2
  import { DataReader } from "../services/DataReader.js";
3
3
  import { historyKey } from "../services/HistoryTracker.js";
4
4
  import { Effect, Layer, Option } from "effect";
5
- import { SqlClient } from "@effect/sql/SqlClient";
5
+ import { SqlClient } from "effect/unstable/sql/SqlClient";
6
6
 
7
7
  //#region src/layers/DataReaderLive.ts
8
8
  /** @public */
@@ -598,7 +598,7 @@ const DataReaderLive = Layer.effect(DataReader, Effect.gen(function* () {
598
598
  ) t2 ON t1.project = t2.project
599
599
  AND t1.timestamp = t2.max_ts`;
600
600
  if (rows.length === 0) return Option.none();
601
- const dbPath = (yield* sql`PRAGMA database_list`.pipe(Effect.catchAll(() => Effect.succeed([])))).find((d) => d.name === "main")?.file ?? "";
601
+ const dbPath = (yield* sql`PRAGMA database_list`.pipe(Effect.catch(() => Effect.succeed([])))).find((d) => d.name === "main")?.file ?? "";
602
602
  const projects = rows.map((r) => ({
603
603
  project: r.project,
604
604
  reportFile: dbPath,
@@ -4,7 +4,7 @@ import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError,
4
4
  import { Agent, IdempotencyHit } from "../schemas/Agent.js";
5
5
  import { DataStore } from "../services/DataStore.js";
6
6
  import { Effect, Layer, Option } from "effect";
7
- import { SqlClient } from "@effect/sql/SqlClient";
7
+ import { SqlClient } from "effect/unstable/sql/SqlClient";
8
8
  import { randomUUID } from "node:crypto";
9
9
 
10
10
  //#region src/layers/DataStoreLive.ts
@@ -36,7 +36,7 @@ const boolToInt = (v) => v === void 0 ? null : v ? 1 : 0;
36
36
  /** @public */
37
37
  const DataStoreLive = Layer.effect(DataStore, Effect.gen(function* () {
38
38
  const sql = yield* SqlClient;
39
- yield* sql`PRAGMA foreign_keys=ON`.pipe(Effect.catchAll(() => Effect.void));
39
+ yield* sql`PRAGMA foreign_keys=ON`.pipe(Effect.catch(() => Effect.void));
40
40
  const ensureFile = (filePath) => Effect.gen(function* () {
41
41
  yield* Effect.logDebug("ensureFile").pipe(Effect.annotateLogs({ filePath }));
42
42
  yield* sql`INSERT OR IGNORE INTO files (path) VALUES (${filePath})`;
@@ -1,7 +1,7 @@
1
1
  import { DataStoreError, extractSqlReason } from "../errors/DataStoreError.js";
2
2
  import { DiscoveryRegistry } from "../services/DiscoveryRegistry.js";
3
3
  import { Effect, Layer } from "effect";
4
- import { SqlClient } from "@effect/sql/SqlClient";
4
+ import { SqlClient } from "effect/unstable/sql/SqlClient";
5
5
 
6
6
  //#region src/layers/DiscoveryRegistryLive.ts
7
7
  const TABLE = "known_projects";
@@ -1,33 +1,33 @@
1
- import { Layer, LogLevel, Logger } from "effect";
1
+ import { Layer, LogLevel, Logger, References } from "effect";
2
2
  import { appendFileSync } from "node:fs";
3
3
 
4
4
  //#region src/layers/LoggerLive.ts
5
5
  /**
6
6
  * Create a structured JSON (NDJSON) logger layer for stderr.
7
7
  *
8
- * When level is undefined or LogLevel.None, replaces the default logger with Logger.none (silent).
9
- * When logFile is set, composes with a file logger via Logger.zip.
10
- * Uses Logger.structuredLogger serialized to JSON for machine-readable output.
8
+ * When level is undefined or `"None"`, installs an empty logger set (silent).
9
+ * When logFile is set, composes the stderr logger with a file logger.
10
+ * Uses `Logger.formatJson` / `Logger.formatStructured` for machine-readable
11
+ * output.
11
12
  * @public
12
13
  */
13
14
  const LoggerLive = (level, logFile) => {
14
- if (!level || level._tag === "None") return Logger.replace(Logger.defaultLogger, Logger.none);
15
- const stderrLogger = Logger.structuredLogger.pipe(Logger.map((entry) => JSON.stringify(entry)), Logger.withConsoleError);
16
- const baseLogger = logFile ? (() => {
17
- const fileLogger = Logger.make(({ message, date, logLevel, annotations }) => {
18
- const entry = JSON.stringify({
19
- timestamp: date.toISOString(),
20
- level: logLevel._tag,
21
- message,
22
- ...Object.fromEntries(annotations)
23
- });
24
- try {
25
- appendFileSync(logFile, `${entry}\n`);
26
- } catch {}
15
+ if (!level || level === "None") return Logger.layer([]);
16
+ const stderrLogger = Logger.formatJson.pipe(Logger.withConsoleError);
17
+ const fileLogger = logFile ? Logger.formatStructured.pipe(Logger.map((entry) => {
18
+ const line = JSON.stringify({
19
+ timestamp: entry.timestamp,
20
+ level: entry.level,
21
+ message: entry.message,
22
+ ...entry.annotations
27
23
  });
28
- return Logger.zip(stderrLogger, fileLogger).pipe(Logger.map(() => void 0));
29
- })() : stderrLogger;
30
- return Layer.merge(Logger.replace(Logger.defaultLogger, baseLogger), Logger.minimumLogLevel(level));
24
+ try {
25
+ appendFileSync(logFile, `${line}\n`);
26
+ } catch {}
27
+ return line;
28
+ })) : void 0;
29
+ const loggers = fileLogger ? [stderrLogger, fileLogger] : [stderrLogger];
30
+ return Layer.merge(Logger.layer(loggers), Layer.succeed(References.MinimumLogLevel, level));
31
31
  };
32
32
  /**
33
33
  * Resolve log level from option or environment variable.
@@ -35,22 +35,22 @@ const LoggerLive = (level, logFile) => {
35
35
  * @public
36
36
  */
37
37
  const LEVEL_ALIASES = {
38
- warn: "Warning",
38
+ warn: "Warn",
39
+ warning: "Warn",
39
40
  error: "Error",
40
41
  info: "Info",
41
42
  debug: "Debug",
42
43
  trace: "Trace",
43
44
  fatal: "Fatal",
44
45
  all: "All",
45
- none: "None",
46
- warning: "Warning"
46
+ none: "None"
47
47
  };
48
48
  /** @public */
49
49
  function resolveLogLevel(option) {
50
50
  const raw = option ?? process.env.VITEST_REPORTER_LOG_LEVEL;
51
51
  if (!raw) return void 0;
52
52
  const normalized = LEVEL_ALIASES[raw.toLowerCase()] ?? `${raw.charAt(0).toUpperCase()}${raw.slice(1).toLowerCase()}`;
53
- return LogLevel.fromLiteral(normalized);
53
+ return LogLevel.values.includes(normalized) ? normalized : void 0;
54
54
  }
55
55
  /**
56
56
  * Resolve log file from option or environment variable.
@@ -1,36 +1,37 @@
1
1
  import { ConfigLive } from "./ConfigLive.js";
2
2
  import { Layer } from "effect";
3
- import { WorkspaceDiscoveryLive, WorkspaceRootLive } from "workspaces-effect";
4
- import { AppDirsConfig, XdgLive } from "xdg-effect";
3
+ import { WorkspaceDiscovery, WorkspaceRoot } from "@effected/workspaces";
4
+ import { AppDirs, Xdg } from "@effected/xdg";
5
5
 
6
6
  //#region src/layers/PathResolutionLive.ts
7
- const APP_NAMESPACE = "vitest-agent";
8
7
  /**
9
- * Minimal slice of `workspaces-effect` for `resolveWorkspaceKey`.
10
- *
11
- * `WorkspacesLive` from `workspaces-effect` composes seven services and
12
- * runs eager I/O at layer construction in `LockfileReaderLive` (reads and
13
- * parses the entire lockfile). We only call `WorkspaceDiscovery.listPackages`
14
- * to read the root `package.json`'s `name` — `WorkspaceRoot` is the only
15
- * upstream dependency `WorkspaceDiscoveryLive` actually needs. Skipping
16
- * the rest avoids the lockfile-read cost on every reporter init, which
17
- * otherwise fires once per Vitest project (5+ reads on monorepos).
8
+ * `AppDirs` over the ambient `Xdg` environment, bound to a `const` so the
9
+ * layer memoizes by reference (calling `AppDirs.layer(...)` inline at two
10
+ * provide sites would resolve two independent services). Requires
11
+ * `FileSystem` + `Path` at the edge.
18
12
  */
19
- const WorkspaceMinimalLive = WorkspaceDiscoveryLive.pipe(Layer.provide(WorkspaceRootLive));
13
+ const AppDirsLive = AppDirs.layer({ namespace: "vitest-agent" }).pipe(Layer.provide(Xdg.layer));
20
14
  /**
21
15
  * Composite layer providing every service `resolveDataPath` requires:
22
16
  * `AppDirs` (XDG path resolution), `VitestAgentConfigFile` (TOML
23
- * config loader), `WorkspaceDiscovery` and `WorkspaceRoot` (workspace name
17
+ * config loader), and `WorkspaceDiscovery` / `WorkspaceRoot` (workspace name
24
18
  * lookup).
25
19
  *
26
20
  * Callers still need to provide `FileSystem` and `Path` from
27
- * `@effect/platform-node`'s `NodeContext.layer` (or the equivalent on Bun).
21
+ * `@effect/platform-node`'s `NodeServices.layer` (or the equivalent on Bun).
22
+ *
23
+ * `WorkspaceDiscovery` is anchored at `projectDir`: on v4,
24
+ * `@effected/workspaces` resolves the workspace root from the layer's `cwd`
25
+ * rather than a per-call path, so the `cwd` is pinned here.
28
26
  *
29
27
  * @param projectDir - Absolute path inside the user's workspace, used to
30
- * anchor the config file resolvers.
28
+ * anchor the config file resolvers and workspace discovery.
31
29
  * @public
32
30
  */
33
- const PathResolutionLive = (projectDir) => Layer.mergeAll(XdgLive(new AppDirsConfig({ namespace: APP_NAMESPACE })), ConfigLive(projectDir), WorkspaceMinimalLive);
31
+ const PathResolutionLive = (projectDir) => {
32
+ const WorkspaceMinimalLive = WorkspaceDiscovery.layer({ cwd: projectDir }).pipe(Layer.provide(WorkspaceRoot.layer));
33
+ return Layer.mergeAll(AppDirsLive, ConfigLive(projectDir), WorkspaceMinimalLive);
34
+ };
34
35
 
35
36
  //#endregion
36
37
  export { PathResolutionLive };
@@ -2,7 +2,7 @@ import { DataStoreError, extractSqlReason } from "../errors/DataStoreError.js";
2
2
  import { PerClientSessionMapReader, PerClientSessionMapWriter } from "../services/PerClientSessionMap.js";
3
3
  import { Effect, Layer, Option } from "effect";
4
4
  import { basename } from "node:path";
5
- import { SqlClient } from "@effect/sql/SqlClient";
5
+ import { SqlClient } from "effect/unstable/sql/SqlClient";
6
6
  import { randomUUID } from "node:crypto";
7
7
 
8
8
  //#region src/layers/PerClientSessionMapLive.ts
@@ -1,7 +1,6 @@
1
1
  import { DiscoveryError } from "../errors/DiscoveryError.js";
2
2
  import { ProjectDiscovery } from "../services/ProjectDiscovery.js";
3
- import { Effect, Layer } from "effect";
4
- import { FileSystem } from "@effect/platform";
3
+ import { Effect, FileSystem, Layer } from "effect";
5
4
 
6
5
  //#region src/layers/ProjectDiscoveryLive.ts
7
6
  const TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx)$/;
@@ -2,17 +2,21 @@ import { ProjectIdentityNotResolvableError } from "../errors/ProjectIdentityErro
2
2
  import { VitestAgentConfig } from "../schemas/Config.js";
3
3
  import { VitestAgentConfigFile } from "../services/Config.js";
4
4
  import { ProjectIdentity, resolveProjectIdentityFromCandidates } from "../services/ProjectIdentity.js";
5
- import { Effect, Layer, Option } from "effect";
6
- import { WorkspaceDiscovery } from "workspaces-effect";
7
- import { Command, CommandExecutor, FileSystem } from "@effect/platform";
5
+ import { Effect, FileSystem, Layer, Option } from "effect";
6
+ import { WorkspaceDiscovery } from "@effected/workspaces";
7
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
8
8
 
9
9
  //#region src/layers/ProjectIdentityLive.ts
10
- const readGitRemote = (cwd) => Command.make("git", "config", "--get", "remote.origin.url").pipe(Command.workingDirectory(cwd), Command.string, Effect.map((output) => {
11
- const trimmed = output.trim();
10
+ const readGitRemote = (cwd) => Effect.gen(function* () {
11
+ const trimmed = (yield* (yield* ChildProcessSpawner.ChildProcessSpawner).string(ChildProcess.make("git", [
12
+ "config",
13
+ "--get",
14
+ "remote.origin.url"
15
+ ]).pipe(ChildProcess.setCwd(cwd)))).trim();
12
16
  return trimmed.length > 0 ? Option.some(trimmed) : Option.none();
13
- }), Effect.catchAll(() => Effect.succeed(Option.none())));
17
+ }).pipe(Effect.catch(() => Effect.succeed(Option.none())));
14
18
  const readPackageRepoUrl = (packageJsonPath) => Effect.gen(function* () {
15
- const raw = yield* (yield* FileSystem.FileSystem).readFileString(packageJsonPath).pipe(Effect.catchAll(() => Effect.succeed("")));
19
+ const raw = yield* (yield* FileSystem.FileSystem).readFileString(packageJsonPath).pipe(Effect.catch(() => Effect.succeed("")));
16
20
  if (raw.length === 0) return Option.none();
17
21
  try {
18
22
  const repo = JSON.parse(raw).repository;
@@ -25,9 +29,9 @@ const readPackageRepoUrl = (packageJsonPath) => Effect.gen(function* () {
25
29
  return Option.none();
26
30
  });
27
31
  const collectCandidates = (workspaceRoot, options) => Effect.gen(function* () {
28
- const tomlConfig = yield* (yield* VitestAgentConfigFile).loadOrDefault(new VitestAgentConfig({})).pipe(Effect.catchAll(() => Effect.succeed(new VitestAgentConfig({}))));
32
+ const tomlConfig = yield* (yield* VitestAgentConfigFile).loadOrDefault(new VitestAgentConfig({})).pipe(Effect.catch(() => Effect.succeed(new VitestAgentConfig({}))));
29
33
  const gitRemote = yield* readGitRemote(workspaceRoot);
30
- const root = (yield* (yield* WorkspaceDiscovery).listPackages(workspaceRoot).pipe(Effect.catchAll(() => Effect.succeed([])))).find((pkg) => pkg.isRootWorkspace);
34
+ const root = (yield* (yield* WorkspaceDiscovery).listPackages().pipe(Effect.catch(() => Effect.succeed([])))).find((pkg) => pkg.isRootWorkspace);
31
35
  const packageJsonRepoUrl = root ? yield* readPackageRepoUrl(root.packageJsonPath) : Option.none();
32
36
  const candidates = {};
33
37
  if (options.projectId !== void 0) candidates.explicit = options.projectId;
@@ -65,15 +69,15 @@ const candidatesToTriedList = (candidates) => [
65
69
  * Live layer that wires the resolver into real I/O. Requires
66
70
  * `FileSystem`, `CommandExecutor`, `WorkspaceDiscovery`, and
67
71
  * `VitestAgentConfigFile`. The platform context comes from
68
- * `NodeContext.layer` at the entry point.
72
+ * `NodeServices.layer` at the entry point.
69
73
  * @public
70
74
  */
71
75
  const ProjectIdentityLive = Layer.effect(ProjectIdentity, Effect.gen(function* () {
72
76
  const fs = yield* FileSystem.FileSystem;
73
77
  const discovery = yield* WorkspaceDiscovery;
74
78
  const configFile = yield* VitestAgentConfigFile;
75
- const executor = yield* CommandExecutor.CommandExecutor;
76
- return { resolve: (workspaceRoot, options = {}) => collectCandidates(workspaceRoot, options).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(WorkspaceDiscovery, discovery), Effect.provideService(VitestAgentConfigFile, configFile), Effect.provideService(CommandExecutor.CommandExecutor, executor), Effect.flatMap((candidates) => {
79
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
80
+ return { resolve: (workspaceRoot, options = {}) => collectCandidates(workspaceRoot, options).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(WorkspaceDiscovery, discovery), Effect.provideService(VitestAgentConfigFile, configFile), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.flatMap((candidates) => {
77
81
  const result = resolveProjectIdentityFromCandidates(candidates);
78
82
  if (result === null) return Effect.fail(new ProjectIdentityNotResolvableError({ tried: candidatesToTriedList(candidates) }));
79
83
  return Effect.succeed(result);
@@ -1,27 +1,27 @@
1
1
  import { AgentContext, RunContext, RunContextService } from "../services/RunContext.js";
2
2
  import { probeHostMetadataFromEnv } from "../utils/probe-host-metadata.js";
3
3
  import { Effect, Layer, Option } from "effect";
4
- import { Command, CommandExecutor } from "@effect/platform";
4
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
5
5
 
6
6
  //#region src/layers/RunContextLive.ts
7
- const runGit = (cwd, args) => Command.make("git", ...args).pipe(Command.workingDirectory(cwd), Command.string, Effect.map((output) => {
7
+ const runGit = (spawner, cwd, args) => spawner.string(ChildProcess.make("git", args).pipe(ChildProcess.setCwd(cwd))).pipe(Effect.map((output) => {
8
8
  const trimmed = output.trim();
9
9
  return trimmed.length > 0 ? Option.some(trimmed) : Option.none();
10
- }), Effect.catchAll(() => Effect.succeed(Option.none())));
11
- const captureRunContextEffect = (cwd, executor, env) => Effect.gen(function* () {
12
- const branch = yield* runGit(cwd, [
10
+ }), Effect.catch(() => Effect.succeed(Option.none())));
11
+ const captureRunContextEffect = (cwd, spawner, env) => Effect.gen(function* () {
12
+ const branch = yield* runGit(spawner, cwd, [
13
13
  "rev-parse",
14
14
  "--abbrev-ref",
15
15
  "HEAD"
16
16
  ]);
17
- const sha = yield* runGit(cwd, ["rev-parse", "HEAD"]);
18
- const dirtyOut = yield* runGit(cwd, ["status", "--porcelain"]);
19
- const upstream = yield* runGit(cwd, [
17
+ const sha = yield* runGit(spawner, cwd, ["rev-parse", "HEAD"]);
18
+ const dirtyOut = yield* runGit(spawner, cwd, ["status", "--porcelain"]);
19
+ const upstream = yield* runGit(spawner, cwd, [
20
20
  "rev-parse",
21
21
  "--abbrev-ref",
22
22
  "@{upstream}"
23
23
  ]);
24
- const worktree = yield* runGit(cwd, ["rev-parse", "--show-toplevel"]);
24
+ const worktree = yield* runGit(spawner, cwd, ["rev-parse", "--show-toplevel"]);
25
25
  const dirtyValue = Option.match(dirtyOut, {
26
26
  onNone: () => null,
27
27
  onSome: (out) => out.length > 0
@@ -37,34 +37,34 @@ const captureRunContextEffect = (cwd, executor, env) => Effect.gen(function* ()
37
37
  hostValue: host.value,
38
38
  hostMetadata: host.metadata
39
39
  });
40
- }).pipe(Effect.provideService(CommandExecutor.CommandExecutor, executor));
41
- const captureAgentContextEffect = (cwd, executor) => Effect.gen(function* () {
42
- const branch = yield* runGit(cwd, [
40
+ });
41
+ const captureAgentContextEffect = (cwd, spawner) => Effect.gen(function* () {
42
+ const branch = yield* runGit(spawner, cwd, [
43
43
  "rev-parse",
44
44
  "--abbrev-ref",
45
45
  "HEAD"
46
46
  ]);
47
- const sha = yield* runGit(cwd, ["rev-parse", "HEAD"]);
48
- const worktree = yield* runGit(cwd, ["rev-parse", "--show-toplevel"]);
47
+ const sha = yield* runGit(spawner, cwd, ["rev-parse", "HEAD"]);
48
+ const worktree = yield* runGit(spawner, cwd, ["rev-parse", "--show-toplevel"]);
49
49
  return new AgentContext({
50
50
  startGitBranch: Option.getOrNull(branch),
51
51
  startGitCommitSha: Option.getOrNull(sha),
52
52
  startWorktreeDir: Option.getOrNull(worktree)
53
53
  });
54
- }).pipe(Effect.provideService(CommandExecutor.CommandExecutor, executor));
54
+ });
55
55
  /**
56
- * Live layer. Requires `CommandExecutor` (provided by
57
- * `NodeContext.layer` at the entry point). Reads `process.env`
56
+ * Live layer. Requires `ChildProcessSpawner` (provided by
57
+ * `NodeServices.layer` at the entry point). Reads `process.env`
58
58
  * directly for host-metadata probes — the env walk is pure but the
59
59
  * env source is process-global, captured once when the layer
60
60
  * constructs.
61
61
  * @public
62
62
  */
63
63
  const RunContextLive = Layer.effect(RunContextService, Effect.gen(function* () {
64
- const executor = yield* CommandExecutor.CommandExecutor;
64
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
65
65
  return {
66
- captureRunContext: (cwd) => captureRunContextEffect(cwd, executor, process.env),
67
- captureAgentContext: (cwd) => captureAgentContextEffect(cwd, executor)
66
+ captureRunContext: (cwd) => captureRunContextEffect(cwd, spawner, process.env),
67
+ captureAgentContext: (cwd) => captureAgentContextEffect(cwd, spawner)
68
68
  };
69
69
  }));
70
70
  /**
@@ -1,5 +1,5 @@
1
1
  import { Effect } from "effect";
2
- import { SqlClient } from "@effect/sql/SqlClient";
2
+ import { SqlClient } from "effect/unstable/sql/SqlClient";
3
3
 
4
4
  //#region src/migrations/0001_initial.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { Effect } from "effect";
2
- import { SqlClient } from "@effect/sql/SqlClient";
2
+ import { SqlClient } from "effect/unstable/sql/SqlClient";
3
3
 
4
4
  //#region src/migrations/registry_0001_initial.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { Effect } from "effect";
2
- import { SqlClient } from "@effect/sql/SqlClient";
2
+ import { SqlClient } from "effect/unstable/sql/SqlClient";
3
3
 
4
4
  //#region src/migrations/session_map_0001_initial.ts
5
5
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/sdk",
3
- "version": "1.3.4",
3
+ "version": "2.0.0",
4
4
  "private": false,
5
5
  "description": "Shared Effect Schema, SQLite migrations, data layer, services, formatters, and utilities for vitest-agent packages.",
6
6
  "keywords": [
@@ -29,36 +29,36 @@
29
29
  "exports": {
30
30
  ".": {
31
31
  "types": "./index.d.ts",
32
- "import": "./index.js"
32
+ "import": "./index.js",
33
+ "default": "./index.js"
33
34
  },
34
35
  "./dispatch": {
35
36
  "types": "./dispatch.d.ts",
36
- "import": "./dispatch.js"
37
+ "import": "./dispatch.js",
38
+ "default": "./dispatch.js"
37
39
  },
38
40
  "./testing": {
39
41
  "types": "./testing.d.ts",
40
- "import": "./testing.js"
42
+ "import": "./testing.js",
43
+ "default": "./testing.js"
41
44
  },
42
45
  "./package.json": "./package.json"
43
46
  },
44
47
  "dependencies": {
45
- "@effect/cluster": "^0.59.0",
46
- "@effect/experimental": "^0.60.0",
47
- "@effect/platform": "^0.96.2",
48
- "@effect/platform-node": "^0.107.0",
49
- "@effect/rpc": "^0.75.1",
50
- "@effect/sql": "^0.51.1",
51
- "@effect/sql-sqlite-node": "^0.52.0",
52
- "@effect/workflow": "^0.18.2",
53
- "@types/acorn": "^6.0.4",
48
+ "@effect/platform-node": "4.0.0-beta.98",
49
+ "@effect/sql-sqlite-node": "4.0.0-beta.98",
50
+ "@effected/config-file": "^0.1.2",
51
+ "@effected/jsonc": "^0.2.0",
52
+ "@effected/toml": "^0.1.0",
53
+ "@effected/walker": "^0.2.1",
54
+ "@effected/workspaces": "^0.3.1",
55
+ "@effected/xdg": "^0.1.2",
56
+ "@effected/yaml": "^0.3.0",
54
57
  "acorn": "^8.17.0",
55
58
  "acorn-typescript": "^1.4.13",
56
59
  "ajv": "^8.20.0",
57
- "config-file-effect": "^0.3.0",
58
- "effect": "^3.21.4",
59
- "std-env": "^4.2.0",
60
- "workspaces-effect": "^2.1.0",
61
- "xdg-effect": "^2.1.1"
60
+ "effect": "4.0.0-beta.98",
61
+ "std-env": "^4.2.0"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=24.11.0"
@@ -14,7 +14,7 @@ const ReportSummary = Schema.Struct({
14
14
  failed: Schema.Number,
15
15
  skipped: Schema.Number,
16
16
  duration: Schema.Number
17
- }).annotations({ identifier: "ReportSummary" });
17
+ }).annotate({ identifier: "ReportSummary" });
18
18
  /**
19
19
  * An individual test case result.
20
20
  * @public
@@ -28,7 +28,7 @@ const TestReport = Schema.Struct({
28
28
  slow: Schema.optional(Schema.Boolean),
29
29
  errors: Schema.optional(Schema.Array(ReportError)),
30
30
  classification: Schema.optional(TestClassification)
31
- }).annotations({ identifier: "TestReport" });
31
+ }).annotate({ identifier: "TestReport" });
32
32
  /**
33
33
  * A test module (file) and its contained test cases.
34
34
  * @public
@@ -39,7 +39,7 @@ const ModuleReport = Schema.Struct({
39
39
  duration: Schema.optional(Schema.Number),
40
40
  errors: Schema.optional(Schema.Array(ReportError)),
41
41
  tests: Schema.Array(TestReport)
42
- }).annotations({ identifier: "ModuleReport" });
42
+ }).annotate({ identifier: "ModuleReport" });
43
43
  /**
44
44
  * Per-tag pass/fail/skip aggregates for a single project run.
45
45
  * @public
@@ -48,7 +48,7 @@ const TagCountEntry = Schema.Struct({
48
48
  passed: Schema.optional(Schema.Number),
49
49
  failed: Schema.optional(Schema.Number),
50
50
  skipped: Schema.optional(Schema.Number)
51
- }).annotations({ identifier: "TagCountEntry" });
51
+ }).annotate({ identifier: "TagCountEntry" });
52
52
  /**
53
53
  * Complete per-project test report written to disk as JSON.
54
54
  * @public
@@ -62,12 +62,9 @@ const AgentReport = Schema.Struct({
62
62
  unhandledErrors: Schema.Array(ReportError),
63
63
  failedFiles: Schema.Array(Schema.String),
64
64
  coverage: Schema.optional(CoverageReport),
65
- tagCounts: Schema.optional(Schema.Record({
66
- key: Schema.String,
67
- value: TagCountEntry
68
- })),
65
+ tagCounts: Schema.optional(Schema.Record(Schema.String, TagCountEntry)),
69
66
  consoleLeaks: Schema.optional(ConsoleLeaks)
70
- }).annotations({ identifier: "AgentReport" });
67
+ }).annotate({ identifier: "AgentReport" });
71
68
 
72
69
  //#endregion
73
70
  export { AgentReport, ModuleReport, ReportSummary, TagCountEntry, TestReport };
@@ -1,5 +1,5 @@
1
1
  import { MetricThresholds, PatternThresholds } from "./Thresholds.js";
2
- import { Schema } from "effect";
2
+ import { Effect, Schema } from "effect";
3
3
 
4
4
  //#region src/schemas/Baselines.ts
5
5
  /**
@@ -9,8 +9,8 @@ import { Schema } from "effect";
9
9
  const CoverageBaselines = Schema.Struct({
10
10
  updatedAt: Schema.String,
11
11
  global: MetricThresholds,
12
- patterns: Schema.optionalWith(Schema.Array(PatternThresholds), { default: () => [] })
13
- }).annotations({ identifier: "CoverageBaselines" });
12
+ patterns: Schema.Array(PatternThresholds).pipe(Schema.withDecodingDefaultKey(Effect.succeed([])))
13
+ }).annotate({ identifier: "CoverageBaselines" });
14
14
 
15
15
  //#endregion
16
16
  export { CoverageBaselines };
@@ -12,7 +12,7 @@ const CacheManifestEntry = Schema.Struct({
12
12
  historyFile: Schema.optional(Schema.String),
13
13
  lastRun: Schema.NullOr(Schema.String),
14
14
  lastResult: Schema.NullOr(TestRunReason)
15
- }).annotations({ identifier: "CacheManifestEntry" });
15
+ }).annotate({ identifier: "CacheManifestEntry" });
16
16
  /**
17
17
  * Root manifest file that indexes all project reports.
18
18
  * @public
@@ -21,7 +21,7 @@ const CacheManifest = Schema.Struct({
21
21
  updatedAt: Schema.String,
22
22
  cacheDir: Schema.String,
23
23
  projects: Schema.Array(CacheManifestEntry)
24
- }).annotations({ identifier: "CacheManifest" });
24
+ }).annotate({ identifier: "CacheManifest" });
25
25
 
26
26
  //#endregion
27
27
  export { CacheManifest, CacheManifestEntry };