@vitest-agent/engine 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Context, Effect, FileSystem, Layer, LogLevel, Option, Path, Schema } from "effect";
2
2
  import { ConfigFileShape } from "@effected/config-file";
3
- import { Agent, AgentNotFoundError, AgentReport, ArtifactKind, ArtifactKind as ArtifactKind$1, ArtifactSuite, ArtifactSuite as ArtifactSuite$1, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorStatus, CacheManifest, CitedArtifact, CoverageBaselines, CoverageReport, DataStoreError, DetailLevel, DiscoveryError, Environment, Executor, FileCoverageReport, FormatterContext, GoalDetail, GoalNotFoundError, GoalRow, GoalStatus, HistoryRecord, IdempotencyHit, IllegalStatusTransitionError, OutputFormat, Phase, ProjectIdentityNotResolvableError, RegistrationConflictError, RenderedOutput, ResolvedThresholds, TddTaskAlreadyEndedError, TddTaskNotFoundError, TestClassification, TestRun, TrendEntry, TrendRecord, TurnPayload, VitestAgentConfig } from "@vitest-agent/sdk";
3
+ import { Agent, AgentNotFoundError, AgentReport, ArtifactKind, ArtifactKind as ArtifactKind$1, ArtifactSuite, ArtifactSuite as ArtifactSuite$1, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorStatus, CacheManifest, CitedArtifact, CoverageBaselines, CoverageReport, DataStoreError, DetailLevel, DiscoveryError, Environment, Executor, FileCoverageReport, GoalDetail, GoalNotFoundError, GoalRow, GoalStatus, HistoryRecord, IdempotencyHit, IllegalStatusTransitionError, OutputFormat, Phase, ProjectIdentityNotResolvableError, RegistrationConflictError, ResolvedThresholds, TddTaskAlreadyEndedError, TddTaskNotFoundError, TestClassification, TestRun, TrendEntry, TrendRecord, TurnPayload, VitestAgentConfig } from "@vitest-agent/sdk";
4
4
  import { SqlClient } from "effect/sql/SqlClient";
5
+ import { RuntimeEnv } from "@effected/env";
5
6
  import { WorkspaceDiscovery, WorkspaceDiscoveryFailure, WorkspaceRootNotFoundError } from "@effected/workspaces";
6
7
  import { AppDirs, AppDirsError, XdgEnvError } from "@effected/xdg";
7
8
  import { ChildProcessSpawner } from "effect/process";
@@ -1206,14 +1207,24 @@ export declare class EnvironmentDetector extends EnvironmentDetector_base {}
1206
1207
  * Pure classification behind {@link EnvironmentDetectorLive}: an agent shell
1207
1208
  * wins, then GitHub Actions, then generic CI, else a terminal.
1208
1209
  *
1209
- * @param env - the environment map to consult
1210
- * @param agentShell - whether the process runs under an AI agent (`std-env`'s `isAgent`)
1210
+ * @remarks
1211
+ * The agent/CI/human split is `@effected/env`'s `Audience.detect`, so the
1212
+ * engine and every `@effected/cli` front end agree on who is running; this
1213
+ * only refines CI into GitHub Actions versus any other CI.
1214
+ *
1215
+ * @param runtime - the `@effected/env` runtime snapshot to classify
1211
1216
  * @public
1212
1217
  */
1213
- export declare const classifyEnvironment: (env: Record<string, string | undefined>, agentShell: boolean) => Environment;
1218
+ export declare const classifyEnvironment: (runtime: RuntimeEnv) => Environment;
1214
1219
  /**
1215
- * Live environment detector. CI detection reads `GITHUB_ACTIONS` / `CI`
1216
- * from the injected `env` map; agent detection comes from `std-env`.
1220
+ * Live environment detector over `@effected/env`'s `CurrentRuntimeEnv`, read
1221
+ * from the injected `env` map only: agent, GitHub Actions and generic CI
1222
+ * detection all come from `env`, never from the ambient process.
1223
+ *
1224
+ * @remarks
1225
+ * The snapshot is taken once, when the layer is built, through
1226
+ * `CurrentRuntimeEnv.layerFrom(env)`: a fresh layer per call and per use, so two
1227
+ * detectors built over different maps in one layer graph each see their own.
1217
1228
  *
1218
1229
  * @param env - the environment map to consult (the front end passes `process.env`)
1219
1230
  * @public
@@ -1313,15 +1324,35 @@ export declare const HistoryTrackerTest: {
1313
1324
  //#endregion
1314
1325
  //#region src/layers/LoggerLive.d.ts
1315
1326
  /**
1316
- * Create a structured JSON (NDJSON) logger layer for stderr.
1327
+ * Create a structured JSON (NDJSON) logger layer for stderr, over
1328
+ * `@effected/cli`'s `CliLog` in its diagnostics-only mode
1329
+ * (`format: "json"`, `plainLogger: false`): the already-resolved level is
1330
+ * passed as `level`, and no plain `CliLogger` is installed beside the sink, so
1331
+ * each record prints once and nothing reads the audience or the terminal.
1317
1332
  *
1318
1333
  * When level is undefined or `"None"`, installs an empty logger set (silent).
1319
- * When logFile is set, composes the stderr logger with a file logger.
1320
- * Uses `Logger.formatJson` / `Logger.formatStructured` for machine-readable
1321
- * output.
1334
+ * When logFile is set, `CliLog`'s asynchronous file sink also appends each
1335
+ * NDJSON line (the same shape as the stderr line) to that file; the first
1336
+ * write error prints one stderr line and disables the file sink.
1337
+ *
1338
+ * When `env` is given, it is built into `CurrentRuntimeEnv` for the layer's
1339
+ * own build context, which `CliLog` captures: under GitHub Actions every
1340
+ * stderr record is neutralized (no workflow command at line start, `##[`
1341
+ * escaped), including records logged from a fiber that carries no
1342
+ * `CurrentRuntimeEnv` of its own. Without `env`, records are sanitised but
1343
+ * not neutralized unless the logging fiber provides one.
1344
+ *
1345
+ * @remarks
1346
+ * The file sink needs `FileSystem` and `Path`. `CliLog` leaves them in `R`
1347
+ * (for `file: undefined` too), but they are provided here (Node) so the
1348
+ * public signature stays `Layer.Layer<never>` for every caller.
1349
+ *
1350
+ * @param level - the diagnostics level; see {@link resolveLogLevel}
1351
+ * @param logFile - a file to append NDJSON lines to; see {@link resolveLogFile}
1352
+ * @param env - the environment map to detect the runtime from (the front end passes `process.env`)
1322
1353
  * @public
1323
1354
  */
1324
- export declare const LoggerLive: (level?: LogLevel.LogLevel, logFile?: string) => Layer.Layer<never>;
1355
+ export declare const LoggerLive: (level?: LogLevel.LogLevel, logFile?: string, env?: Readonly<Record<string, string | undefined>>) => Layer.Layer<never>;
1325
1356
  /**
1326
1357
  * Resolve log level from option or environment variable.
1327
1358
  * Priority: explicit option \> `VITEST_REPORTER_LOG_LEVEL` in `env` \> undefined.
@@ -1344,24 +1375,13 @@ export declare function resolveLogFile(env: Record<string, string | undefined>,
1344
1375
  //#region src/layers/OutputPipelineLive.d.ts
1345
1376
  /**
1346
1377
  * The output pipeline: environment detection, executor resolution, format
1347
- * selection, detail resolution and rendering.
1378
+ * selection and detail resolution.
1348
1379
  *
1349
1380
  * @param env - the environment map `EnvironmentDetectorLive` consults
1350
1381
  * (the front end passes `process.env`)
1351
1382
  * @public
1352
1383
  */
1353
- export declare const OutputPipelineLive: (env: Record<string, string | undefined>) => Layer.Layer<DetailResolver | EnvironmentDetector | ExecutorResolver | FormatSelector | OutputRenderer, never, never>;
1354
- //#endregion
1355
- //#region src/services/OutputRenderer.d.ts
1356
- declare const OutputRenderer_base: Context.ServiceClass<OutputRenderer, "vitest-agent/OutputRenderer", {
1357
- readonly render: (reports: ReadonlyArray<AgentReport>, format: OutputFormat, context: FormatterContext) => Effect.Effect<ReadonlyArray<RenderedOutput>>;
1358
- }>;
1359
- /** @public */
1360
- export declare class OutputRenderer extends OutputRenderer_base {}
1361
- //#endregion
1362
- //#region src/layers/OutputRendererLive.d.ts
1363
- /** @public */
1364
- export declare const OutputRendererLive: Layer.Layer<OutputRenderer, never, never>;
1384
+ export declare const OutputPipelineLive: (env: Record<string, string | undefined>) => Layer.Layer<DetailResolver | EnvironmentDetector | ExecutorResolver | FormatSelector, never, never>;
1365
1385
  //#endregion
1366
1386
  //#region src/layers/PathResolutionLive.d.ts
1367
1387
  /**
@@ -1835,18 +1855,27 @@ export interface PlatformOptions {
1835
1855
  readonly logLevel?: LogLevel.LogLevel | undefined;
1836
1856
  /** Optional path for structured NDJSON log output. */
1837
1857
  readonly logFile?: string | undefined;
1858
+ /**
1859
+ * Whether `PlatformLive` installs its own logger set (`LoggerLive`, from
1860
+ * `logLevel` / `logFile`). Defaults to `true`. Pass `false` when the caller
1861
+ * already owns the logger set (for example `@effected/cli`'s `CliLog`
1862
+ * installed by `CliRuntime.main`'s `env.log`): `LoggerLive` replaces the
1863
+ * installed loggers, so leaving it in would silence the caller's set.
1864
+ * `logLevel` and `logFile` are ignored when this is `false`.
1865
+ */
1866
+ readonly logger?: boolean | undefined;
1838
1867
  }
1839
1868
  /**
1840
1869
  * The services {@link PlatformLive} provides.
1841
1870
  * @public
1842
1871
  */
1843
- export type PlatformServices = DataReader | DataStore | ProjectDiscovery | HistoryTracker | EnvironmentDetector | ExecutorResolver | FormatSelector | DetailResolver | OutputRenderer | NodeServices.NodeServices | SqliteClient | SqlClient;
1872
+ export type PlatformServices = DataReader | DataStore | ProjectDiscovery | HistoryTracker | EnvironmentDetector | ExecutorResolver | FormatSelector | DetailResolver | NodeServices.NodeServices | SqliteClient | SqlClient;
1844
1873
  /**
1845
1874
  * The one platform assembly shared by the CLI, the MCP server and the Vitest
1846
1875
  * plugin: SQLite + migrator + Node platform services + Logger, with
1847
1876
  * `DataReader`, `DataStore`, `ProjectDiscovery`, `HistoryTracker` and the
1848
1877
  * output pipeline (`EnvironmentDetector`, `ExecutorResolver`,
1849
- * `FormatSelector`, `DetailResolver`, `OutputRenderer`) built over them.
1878
+ * `FormatSelector`, `DetailResolver`) built over them.
1850
1879
  *
1851
1880
  * Every env read goes through `options.env`; the engine never touches
1852
1881
  * `process` itself.
@@ -2583,9 +2612,15 @@ export declare function assembleHistoryRecord(rows: HistoryRow[]): AssembledHist
2583
2612
  * most once per dbPath in the current process; concurrent calls share the
2584
2613
  * same in-flight promise. Subsequent calls (after the first resolves) are
2585
2614
  * no-ops.
2615
+ *
2616
+ * @param dbPath - the SQLite file to migrate
2617
+ * @param logLevel - the diagnostics level for the migrator's records; see `LoggerLive`
2618
+ * @param logFile - a file to append NDJSON diagnostics to; see `LoggerLive`
2619
+ * @param env - the environment map `LoggerLive` detects the runtime from, so
2620
+ * records are neutralized under GitHub Actions (the front end passes `process.env`)
2586
2621
  * @public
2587
2622
  */
2588
- export declare function ensureMigrated(dbPath: string, logLevel?: LogLevel.LogLevel, logFile?: string): Promise<void>;
2623
+ export declare function ensureMigrated(dbPath: string, logLevel?: LogLevel.LogLevel, logFile?: string, env?: Readonly<Record<string, string | undefined>>): Promise<void>;
2589
2624
  /**
2590
2625
  * Reset the migration cache. Test-only.
2591
2626
  *
package/index.js CHANGED
@@ -19,8 +19,6 @@ import { FormatSelectorLive } from "./layers/FormatSelectorLive.js";
19
19
  import { HistoryTrackerLive } from "./layers/HistoryTrackerLive.js";
20
20
  import { HistoryTrackerTest } from "./layers/HistoryTrackerTest.js";
21
21
  import { LoggerLive, resolveLogFile, resolveLogLevel } from "./layers/LoggerLive.js";
22
- import { OutputRenderer } from "./services/OutputRenderer.js";
23
- import { OutputRendererLive } from "./layers/OutputRendererLive.js";
24
22
  import { OutputPipelineLive } from "./layers/OutputPipelineLive.js";
25
23
  import { APP_NAMESPACE, DATA_FALLBACK_DIR, PathResolutionLive } from "./layers/PathResolutionLive.js";
26
24
  import { PerClientSessionMapReader, PerClientSessionMapWriter } from "./services/PerClientSessionMap.js";
@@ -60,4 +58,4 @@ import { DATABASE_FILENAME, resolveDataPath } from "./utils/resolve-data-path.js
60
58
  import { resolveWorkspaceKey } from "./utils/resolve-workspace-key.js";
61
59
  import { CURRENT_ENGINE_VERSION } from "./version.js";
62
60
 
63
- export { APP_NAMESPACE, AgentContext, CURRENT_ENGINE_VERSION, ConfigLive, DATABASE_FILENAME, DATA_DB_FILENAME, DATA_FALLBACK_DIR, DataReader, DataReaderLive, DataStore, DataStoreLive, DetailResolver, DetailResolverLive, DiscoveryRegistry, DiscoveryRegistryLive, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, ExecutorResolver, ExecutorResolverLive, FormatSelector, FormatSelectorLive, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, IDEMPOTENCY_ROOT_SENTINEL, INLINE_ATTACHMENT_BODY_CAP_BYTES, LoggerLive, NodePlatformLayer, OutputPipelineLive, OutputRenderer, OutputRendererLive, PROJECT_MIGRATIONS, PathResolutionLive, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PlatformLive, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityTest, REGISTRY_DB_FILENAME, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, SESSIONS_DB_FILENAME, SidecarPlatformLive, VitestAgentConfigFile, _resetMigrationCacheForTesting, assembleHistoryRecord, assembleManifest, classifyEnvironment, computeFailureSignature, deriveIdempotencyKey, dispatchRecordTddArtifactEffect, endAgentEffect, ensureMigrated, formatTriageEffect, formatWrapupEffect, historyKey, makeSqliteStack, migration as migration0001, migration$1 as migration0002, normalizeAssertionShape, parseAndValidateTurnPayload, parseSessionEnvExports, recordRunWorkspaceChangesEffect, recordSessionEnd, recordSessionStart, recordTddArtifactByTaskIdEffect, recordTddArtifactEffect, recordTurnEffect, recoverSessionContextFromSessionEnv, registerAgentEffect, migration$2 as registryMigration0001, resolveDataPath, resolveHookPaths, resolveLogFile, resolveLogLevel, resolveProjectDir, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect, resolveSessionForRecording, resolveSessionMapPath, resolveWorkspaceKey, migration$3 as sessionMapMigration0001 };
61
+ export { APP_NAMESPACE, AgentContext, CURRENT_ENGINE_VERSION, ConfigLive, DATABASE_FILENAME, DATA_DB_FILENAME, DATA_FALLBACK_DIR, DataReader, DataReaderLive, DataStore, DataStoreLive, DetailResolver, DetailResolverLive, DiscoveryRegistry, DiscoveryRegistryLive, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, ExecutorResolver, ExecutorResolverLive, FormatSelector, FormatSelectorLive, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, IDEMPOTENCY_ROOT_SENTINEL, INLINE_ATTACHMENT_BODY_CAP_BYTES, LoggerLive, NodePlatformLayer, OutputPipelineLive, PROJECT_MIGRATIONS, PathResolutionLive, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PlatformLive, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityTest, REGISTRY_DB_FILENAME, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, SESSIONS_DB_FILENAME, SidecarPlatformLive, VitestAgentConfigFile, _resetMigrationCacheForTesting, assembleHistoryRecord, assembleManifest, classifyEnvironment, computeFailureSignature, deriveIdempotencyKey, dispatchRecordTddArtifactEffect, endAgentEffect, ensureMigrated, formatTriageEffect, formatWrapupEffect, historyKey, makeSqliteStack, migration as migration0001, migration$1 as migration0002, normalizeAssertionShape, parseAndValidateTurnPayload, parseSessionEnvExports, recordRunWorkspaceChangesEffect, recordSessionEnd, recordSessionStart, recordTddArtifactByTaskIdEffect, recordTddArtifactEffect, recordTurnEffect, recoverSessionContextFromSessionEnv, registerAgentEffect, migration$2 as registryMigration0001, resolveDataPath, resolveHookPaths, resolveLogFile, resolveLogLevel, resolveProjectDir, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect, resolveSessionForRecording, resolveSessionMapPath, resolveWorkspaceKey, migration$3 as sessionMapMigration0001 };
@@ -1,36 +1,56 @@
1
1
  import { EnvironmentDetector } from "../services/EnvironmentDetector.js";
2
- import { Effect, Layer } from "effect";
3
- import { agent, isAgent } from "std-env";
2
+ import { Effect, Layer, Option } from "effect";
3
+ import { Audience, CurrentRuntimeEnv } from "@effected/env";
4
4
 
5
5
  //#region src/layers/EnvironmentDetectorLive.ts
6
- const isGitHub = (env) => env.GITHUB_ACTIONS === "true" || env.GITHUB_ACTIONS === "1";
7
- const isCI = (env) => isGitHub(env) || env.CI === "true";
6
+ /** The engine environment for each CI `@effected/env` names; exhaustive over `CiName`. */
7
+ const CI_ENVIRONMENT = {
8
+ "github-actions": "ci-github",
9
+ generic: "ci-generic"
10
+ };
8
11
  /**
9
12
  * Pure classification behind {@link EnvironmentDetectorLive}: an agent shell
10
13
  * wins, then GitHub Actions, then generic CI, else a terminal.
11
14
  *
12
- * @param env - the environment map to consult
13
- * @param agentShell - whether the process runs under an AI agent (`std-env`'s `isAgent`)
15
+ * @remarks
16
+ * The agent/CI/human split is `@effected/env`'s `Audience.detect`, so the
17
+ * engine and every `@effected/cli` front end agree on who is running; this
18
+ * only refines CI into GitHub Actions versus any other CI.
19
+ *
20
+ * @param runtime - the `@effected/env` runtime snapshot to classify
14
21
  * @public
15
22
  */
16
- const classifyEnvironment = (env, agentShell) => {
17
- if (agentShell) return "agent-shell";
18
- if (isGitHub(env)) return "ci-github";
19
- if (isCI(env)) return "ci-generic";
20
- return "terminal";
23
+ const classifyEnvironment = (runtime) => {
24
+ switch (Audience.detect(runtime)) {
25
+ case "agent": return "agent-shell";
26
+ case "ci": return Option.match(runtime.ci, {
27
+ onNone: () => "ci-generic",
28
+ onSome: (ci) => CI_ENVIRONMENT[ci]
29
+ });
30
+ case "human": return "terminal";
31
+ }
21
32
  };
22
33
  /**
23
- * Live environment detector. CI detection reads `GITHUB_ACTIONS` / `CI`
24
- * from the injected `env` map; agent detection comes from `std-env`.
34
+ * Live environment detector over `@effected/env`'s `CurrentRuntimeEnv`, read
35
+ * from the injected `env` map only: agent, GitHub Actions and generic CI
36
+ * detection all come from `env`, never from the ambient process.
37
+ *
38
+ * @remarks
39
+ * The snapshot is taken once, when the layer is built, through
40
+ * `CurrentRuntimeEnv.layerFrom(env)`: a fresh layer per call and per use, so two
41
+ * detectors built over different maps in one layer graph each see their own.
25
42
  *
26
43
  * @param env - the environment map to consult (the front end passes `process.env`)
27
44
  * @public
28
45
  */
29
- const EnvironmentDetectorLive = (env) => Layer.succeed(EnvironmentDetector, {
30
- detect: () => Effect.sync(() => classifyEnvironment(env, isAgent)),
31
- isAgent: Effect.sync(() => isAgent),
32
- agentName: Effect.sync(() => agent ?? void 0)
33
- });
46
+ const EnvironmentDetectorLive = (env) => Layer.effect(EnvironmentDetector, Effect.map(CurrentRuntimeEnv, (runtime) => {
47
+ const environment = classifyEnvironment(runtime);
48
+ return {
49
+ detect: () => Effect.succeed(environment),
50
+ isAgent: Effect.succeed(Option.isSome(runtime.agent)),
51
+ agentName: Effect.succeed(Option.getOrUndefined(runtime.agent))
52
+ };
53
+ })).pipe(Layer.provide(CurrentRuntimeEnv.layerFrom(env)));
34
54
 
35
55
  //#endregion
36
56
  export { EnvironmentDetectorLive, classifyEnvironment };
@@ -2,8 +2,20 @@ import { ExecutorResolver } from "../services/ExecutorResolver.js";
2
2
  import { Effect, Layer } from "effect";
3
3
 
4
4
  //#region src/layers/ExecutorResolverLive.ts
5
+ /**
6
+ * The `@effected/env` audience each environment implies. An `Executor` is the
7
+ * same three-way split as an `AudienceKind` (`human` / `agent` / `ci`); typing
8
+ * the table as `AudienceKind` and returning it as `Executor` keeps the two
9
+ * unions from drifting apart at compile time.
10
+ */
11
+ const AUDIENCE_OF = {
12
+ "agent-shell": "agent",
13
+ terminal: "human",
14
+ "ci-github": "ci",
15
+ "ci-generic": "ci"
16
+ };
5
17
  /** @public */
6
- const ExecutorResolverLive = Layer.succeed(ExecutorResolver, { resolve: (env) => Effect.succeed(env === "agent-shell" ? "agent" : env === "terminal" ? "human" : "ci") });
18
+ const ExecutorResolverLive = Layer.succeed(ExecutorResolver, { resolve: (env) => Effect.succeed(AUDIENCE_OF[env]) });
7
19
 
8
20
  //#endregion
9
21
  export { ExecutorResolverLive };
@@ -1,33 +1,48 @@
1
- import { Layer, LogLevel, Logger, References } from "effect";
2
- import { appendFileSync } from "node:fs";
1
+ import { Layer, LogLevel, Logger } from "effect";
2
+ import { CurrentRuntimeEnv } from "@effected/env";
3
+ import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
4
+ import * as NodePath from "@effect/platform-node/NodePath";
5
+ import { CliLog } from "@effected/cli";
3
6
 
4
7
  //#region src/layers/LoggerLive.ts
5
8
  /**
6
- * Create a structured JSON (NDJSON) logger layer for stderr.
9
+ * Create a structured JSON (NDJSON) logger layer for stderr, over
10
+ * `@effected/cli`'s `CliLog` in its diagnostics-only mode
11
+ * (`format: "json"`, `plainLogger: false`): the already-resolved level is
12
+ * passed as `level`, and no plain `CliLogger` is installed beside the sink, so
13
+ * each record prints once and nothing reads the audience or the terminal.
7
14
  *
8
15
  * 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.
16
+ * When logFile is set, `CliLog`'s asynchronous file sink also appends each
17
+ * NDJSON line (the same shape as the stderr line) to that file; the first
18
+ * write error prints one stderr line and disables the file sink.
19
+ *
20
+ * When `env` is given, it is built into `CurrentRuntimeEnv` for the layer's
21
+ * own build context, which `CliLog` captures: under GitHub Actions every
22
+ * stderr record is neutralized (no workflow command at line start, `##[`
23
+ * escaped), including records logged from a fiber that carries no
24
+ * `CurrentRuntimeEnv` of its own. Without `env`, records are sanitised but
25
+ * not neutralized unless the logging fiber provides one.
26
+ *
27
+ * @remarks
28
+ * The file sink needs `FileSystem` and `Path`. `CliLog` leaves them in `R`
29
+ * (for `file: undefined` too), but they are provided here (Node) so the
30
+ * public signature stays `Layer.Layer<never>` for every caller.
31
+ *
32
+ * @param level - the diagnostics level; see {@link resolveLogLevel}
33
+ * @param logFile - a file to append NDJSON lines to; see {@link resolveLogFile}
34
+ * @param env - the environment map to detect the runtime from (the front end passes `process.env`)
12
35
  * @public
13
36
  */
14
- const LoggerLive = (level, logFile) => {
37
+ const LoggerLive = (level, logFile, env) => {
15
38
  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
23
- });
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));
39
+ const sink = CliLog.layer({
40
+ format: "json",
41
+ plainLogger: false,
42
+ level,
43
+ file: logFile === void 0 ? void 0 : { path: logFile }
44
+ }).pipe(Layer.provide(Layer.merge(NodeFileSystem.layer, NodePath.layer)));
45
+ return env === void 0 ? sink : sink.pipe(Layer.provide(CurrentRuntimeEnv.layerFrom(env)));
31
46
  };
32
47
  const LEVEL_ALIASES = {
33
48
  warn: "Warn",
@@ -2,19 +2,18 @@ import { DetailResolverLive } from "./DetailResolverLive.js";
2
2
  import { EnvironmentDetectorLive } from "./EnvironmentDetectorLive.js";
3
3
  import { ExecutorResolverLive } from "./ExecutorResolverLive.js";
4
4
  import { FormatSelectorLive } from "./FormatSelectorLive.js";
5
- import { OutputRendererLive } from "./OutputRendererLive.js";
6
5
  import { Layer } from "effect";
7
6
 
8
7
  //#region src/layers/OutputPipelineLive.ts
9
8
  /**
10
9
  * The output pipeline: environment detection, executor resolution, format
11
- * selection, detail resolution and rendering.
10
+ * selection and detail resolution.
12
11
  *
13
12
  * @param env - the environment map `EnvironmentDetectorLive` consults
14
13
  * (the front end passes `process.env`)
15
14
  * @public
16
15
  */
17
- const OutputPipelineLive = (env) => Layer.mergeAll(EnvironmentDetectorLive(env), ExecutorResolverLive, FormatSelectorLive, DetailResolverLive, OutputRendererLive);
16
+ const OutputPipelineLive = (env) => Layer.mergeAll(EnvironmentDetectorLive(env), ExecutorResolverLive, FormatSelectorLive, DetailResolverLive);
18
17
 
19
18
  //#endregion
20
19
  export { OutputPipelineLive };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/engine",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "Shared services, layers, SQLite data layer, migrations and platform resolution for the vitest-agent CLI and MCP server.",
6
6
  "keywords": [
@@ -40,20 +40,21 @@
40
40
  "./package.json": "./package.json"
41
41
  },
42
42
  "dependencies": {
43
- "@effect/platform-node": "4.0.0-rc.118",
44
- "@effect/sql-sqlite-node": "4.0.0-rc.118",
45
- "@effected/config-file": "^0.13.1",
46
- "@effected/engine": "^0.2.0",
47
- "@effected/glob": "^0.9.0",
48
- "@effected/jsonc": "^0.14.0",
49
- "@effected/toml": "^0.10.0",
50
- "@effected/walker": "^0.14.0",
51
- "@effected/workspaces": "^0.30.3",
52
- "@effected/xdg": "^0.8.2",
53
- "@effected/yaml": "^0.18.0",
54
- "@vitest-agent/sdk": "5.1.3",
55
- "effect": "4.0.0-rc.118",
56
- "std-env": "^4.2.0"
43
+ "@effect/platform-node": "^4.0.0",
44
+ "@effect/sql-sqlite-node": "^4.0.0",
45
+ "@effected/cli": "^0.11.0",
46
+ "@effected/config-file": "^0.14.0",
47
+ "@effected/engine": "^0.3.0",
48
+ "@effected/env": "^0.1.0",
49
+ "@effected/glob": "^0.10.0",
50
+ "@effected/jsonc": "^0.15.0",
51
+ "@effected/toml": "^0.11.0",
52
+ "@effected/walker": "^0.15.0",
53
+ "@effected/workspaces": "^0.31.0",
54
+ "@effected/xdg": "^0.9.0",
55
+ "@effected/yaml": "^0.19.0",
56
+ "@vitest-agent/sdk": "6.0.0",
57
+ "effect": "^4.0.0"
57
58
  },
58
59
  "engines": {
59
60
  "node": ">=24.11.0"
package/platform.js CHANGED
@@ -41,7 +41,7 @@ const makeSqliteStack = (filename, migrations = PROJECT_MIGRATIONS) => {
41
41
  * plugin: SQLite + migrator + Node platform services + Logger, with
42
42
  * `DataReader`, `DataStore`, `ProjectDiscovery`, `HistoryTracker` and the
43
43
  * output pipeline (`EnvironmentDetector`, `ExecutorResolver`,
44
- * `FormatSelector`, `DetailResolver`, `OutputRenderer`) built over them.
44
+ * `FormatSelector`, `DetailResolver`) built over them.
45
45
  *
46
46
  * Every env read goes through `options.env`; the engine never touches
47
47
  * `process` itself.
@@ -51,7 +51,8 @@ const makeSqliteStack = (filename, migrations = PROJECT_MIGRATIONS) => {
51
51
  */
52
52
  const PlatformLive = (options) => {
53
53
  const { SqliteLayer, MigratorLayer } = makeSqliteStack(options.dbPath);
54
- return Layer.mergeAll(ProjectDiscoveryLive, HistoryTrackerLive, OutputPipelineLive(options.env)).pipe(Layer.provideMerge(DataReaderLive), Layer.provideMerge(DataStoreLive), Layer.provideMerge(MigratorLayer), Layer.provideMerge(SqliteLayer), Layer.provideMerge(NodePlatformLayer), Layer.provideMerge(LoggerLive(options.logLevel, options.logFile)));
54
+ const platform = Layer.mergeAll(ProjectDiscoveryLive, HistoryTrackerLive, OutputPipelineLive(options.env)).pipe(Layer.provideMerge(DataReaderLive), Layer.provideMerge(DataStoreLive), Layer.provideMerge(MigratorLayer), Layer.provideMerge(SqliteLayer), Layer.provideMerge(NodePlatformLayer));
55
+ return options.logger === false ? platform : platform.pipe(Layer.provideMerge(LoggerLive(options.logLevel, options.logFile, options.env)));
55
56
  };
56
57
 
57
58
  //#endregion
@@ -1,5 +1,5 @@
1
- import { readFileSync, readdirSync, statSync } from "node:fs";
2
1
  import { join, resolve } from "node:path";
2
+ import { readFileSync, readdirSync, statSync } from "node:fs";
3
3
 
4
4
  //#region src/programs/session-env.ts
5
5
  const EXPORT_LINE = /^export ([A-Z_][A-Z0-9_]*)=(.*)$/;
@@ -19,16 +19,22 @@ const getCache = () => {
19
19
  * most once per dbPath in the current process; concurrent calls share the
20
20
  * same in-flight promise. Subsequent calls (after the first resolves) are
21
21
  * no-ops.
22
+ *
23
+ * @param dbPath - the SQLite file to migrate
24
+ * @param logLevel - the diagnostics level for the migrator's records; see `LoggerLive`
25
+ * @param logFile - a file to append NDJSON diagnostics to; see `LoggerLive`
26
+ * @param env - the environment map `LoggerLive` detects the runtime from, so
27
+ * records are neutralized under GitHub Actions (the front end passes `process.env`)
22
28
  * @public
23
29
  */
24
- function ensureMigrated(dbPath, logLevel, logFile) {
30
+ function ensureMigrated(dbPath, logLevel, logFile, env) {
25
31
  const cache = getCache();
26
32
  const cached = cache.get(dbPath);
27
33
  if (cached) return cached;
28
34
  const { SqliteLayer, MigratorLayer } = makeSqliteStack(dbPath);
29
35
  const program = Effect.gen(function* () {
30
36
  yield* SqlClient;
31
- }).pipe(Effect.provide(MigratorLayer), Effect.provide(Layer.merge(SqliteLayer, NodePlatformLayer)), Effect.provide(LoggerLive(logLevel, logFile)));
37
+ }).pipe(Effect.provide(MigratorLayer), Effect.provide(Layer.merge(SqliteLayer, NodePlatformLayer)), Effect.provide(LoggerLive(logLevel, logFile, env)));
32
38
  const promise = Effect.runPromise(program);
33
39
  cache.set(dbPath, promise);
34
40
  promise.catch(() => {});
@@ -1,7 +1,7 @@
1
1
  import { gitUrlToProjectKey, normalizeWorkspaceKey } from "@vitest-agent/sdk";
2
2
  import { Effect, FileSystem } from "effect";
3
- import { existsSync, readFileSync } from "node:fs";
4
3
  import { dirname, join } from "node:path";
4
+ import { existsSync, readFileSync } from "node:fs";
5
5
 
6
6
  //#region src/utils/resolve-project-key-from-cwd.ts
7
7
  const PACKAGE_JSON = "package.json";
package/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region src/version.ts
2
2
  /** Build-time injected package version (see architecture.md D36). @public */
3
- const CURRENT_ENGINE_VERSION = "0.3.2";
3
+ const CURRENT_ENGINE_VERSION = "0.5.0";
4
4
 
5
5
  //#endregion
6
6
  export { CURRENT_ENGINE_VERSION };
@@ -1,23 +0,0 @@
1
- import { OutputRenderer } from "../services/OutputRenderer.js";
2
- import { GfmFormatter, JsonFormatter, MarkdownFormatter, SilentFormatter, TerminalFormatter, ciAnnotationsFormatter } from "@vitest-agent/sdk";
3
- import { Effect, Layer } from "effect";
4
-
5
- //#region src/layers/OutputRendererLive.ts
6
- const formatters = /* @__PURE__ */ new Map([
7
- ["terminal", TerminalFormatter],
8
- ["markdown", MarkdownFormatter],
9
- ["gfm", GfmFormatter],
10
- ["json", JsonFormatter],
11
- ["silent", SilentFormatter],
12
- ["vitest-bypass", SilentFormatter],
13
- ["ci-annotations", ciAnnotationsFormatter]
14
- ]);
15
- /** @public */
16
- const OutputRendererLive = Layer.succeed(OutputRenderer, { render: (reports, format, context) => Effect.sync(() => {
17
- const formatter = formatters.get(format);
18
- if (!formatter) return [];
19
- return formatter.render(reports, context);
20
- }) });
21
-
22
- //#endregion
23
- export { OutputRendererLive };
@@ -1,8 +0,0 @@
1
- import { Context } from "effect";
2
-
3
- //#region src/services/OutputRenderer.ts
4
- /** @public */
5
- var OutputRenderer = class extends Context.Service()("vitest-agent/OutputRenderer") {};
6
-
7
- //#endregion
8
- export { OutputRenderer };