@vitest-agent/sdk 1.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 (121) hide show
  1. package/DataReader-2kJ9I9Hh.d.ts +1809 -0
  2. package/LICENSE +21 -0
  3. package/README.md +52 -0
  4. package/dispatch.d.ts +48 -0
  5. package/dispatch.js +5 -0
  6. package/errors/AgentErrors.js +65 -0
  7. package/errors/DataStoreError.js +44 -0
  8. package/errors/DiscoveryError.js +21 -0
  9. package/errors/PathResolutionError.js +27 -0
  10. package/errors/ProjectIdentityError.js +29 -0
  11. package/errors/RunContextError.js +35 -0
  12. package/errors/TddErrors.js +66 -0
  13. package/exit-code-for-tag.js +18 -0
  14. package/formatters/ci-annotations.js +61 -0
  15. package/formatters/gfm.js +20 -0
  16. package/formatters/json.js +18 -0
  17. package/formatters/markdown.js +39 -0
  18. package/formatters/silent.js +9 -0
  19. package/formatters/terminal.js +56 -0
  20. package/index.d.ts +3334 -0
  21. package/index.js +120 -0
  22. package/internal-inject-env.js +42 -0
  23. package/layers/ConfigLive.js +47 -0
  24. package/layers/DataReaderLive.js +1666 -0
  25. package/layers/DataStoreLive.js +1185 -0
  26. package/layers/DetailResolverLive.js +9 -0
  27. package/layers/DiscoveryRegistryLive.js +70 -0
  28. package/layers/EnvironmentDetectorLive.js +21 -0
  29. package/layers/EnvironmentDetectorTest.js +13 -0
  30. package/layers/ExecutorResolverLive.js +9 -0
  31. package/layers/FormatSelectorLive.js +20 -0
  32. package/layers/HistoryTrackerLive.js +47 -0
  33. package/layers/HistoryTrackerTest.js +25 -0
  34. package/layers/LoggerLive.js +64 -0
  35. package/layers/OutputPipelineLive.js +13 -0
  36. package/layers/OutputRendererLive.js +28 -0
  37. package/layers/PathResolutionLive.js +36 -0
  38. package/layers/PerClientSessionMapLive.js +145 -0
  39. package/layers/ProjectDiscoveryLive.js +70 -0
  40. package/layers/ProjectDiscoveryTest.js +12 -0
  41. package/layers/ProjectIdentityLive.js +92 -0
  42. package/layers/RunContextLive.js +81 -0
  43. package/lib/format-triage.js +103 -0
  44. package/lib/format-wrapup.js +67 -0
  45. package/migrations/0001_initial.js +849 -0
  46. package/migrations/registry_0001_initial.js +39 -0
  47. package/migrations/session_map_0001_initial.js +61 -0
  48. package/package.json +64 -0
  49. package/schemas/Agent.js +42 -0
  50. package/schemas/AgentReport.js +71 -0
  51. package/schemas/Baselines.js +16 -0
  52. package/schemas/CacheManifest.js +27 -0
  53. package/schemas/ChannelEvent.js +138 -0
  54. package/schemas/Common.js +105 -0
  55. package/schemas/Config.js +28 -0
  56. package/schemas/Coverage.js +51 -0
  57. package/schemas/CoverageLevel.js +117 -0
  58. package/schemas/CoverageTargets.js +39 -0
  59. package/schemas/History.js +31 -0
  60. package/schemas/Identity.js +64 -0
  61. package/schemas/Options.js +57 -0
  62. package/schemas/RenderState.js +161 -0
  63. package/schemas/RunEvent.js +163 -0
  64. package/schemas/Tdd.js +44 -0
  65. package/schemas/Thresholds.js +30 -0
  66. package/schemas/Transport.js +16 -0
  67. package/schemas/Trends.js +23 -0
  68. package/schemas/turns/FileEditPayload.js +15 -0
  69. package/schemas/turns/HookFirePayload.js +13 -0
  70. package/schemas/turns/HypothesisPayload.js +13 -0
  71. package/schemas/turns/NotePayload.js +13 -0
  72. package/schemas/turns/ToolCallPayload.js +13 -0
  73. package/schemas/turns/ToolResultPayload.js +15 -0
  74. package/schemas/turns/UserPromptPayload.js +12 -0
  75. package/schemas/turns/index.js +15 -0
  76. package/services/Config.js +15 -0
  77. package/services/DataReader.js +8 -0
  78. package/services/DataStore.js +8 -0
  79. package/services/DetailResolver.js +8 -0
  80. package/services/DiscoveryRegistry.js +8 -0
  81. package/services/EnvironmentDetector.js +8 -0
  82. package/services/ExecutorResolver.js +8 -0
  83. package/services/FormatSelector.js +8 -0
  84. package/services/HistoryTracker.js +8 -0
  85. package/services/OutputRenderer.js +8 -0
  86. package/services/PerClientSessionMap.js +22 -0
  87. package/services/ProjectDiscovery.js +8 -0
  88. package/services/ProjectIdentity.js +63 -0
  89. package/services/RunContext.js +53 -0
  90. package/services/idempotency.js +57 -0
  91. package/sidecar-dispatch.js +84 -0
  92. package/sql/assemblers.js +59 -0
  93. package/testing/layers.js +21 -0
  94. package/testing.d.ts +25 -0
  95. package/testing.js +243 -0
  96. package/tsdoc-metadata.json +11 -0
  97. package/utils/ansi.js +49 -0
  98. package/utils/build-report.js +130 -0
  99. package/utils/canonicalize-git-url.js +66 -0
  100. package/utils/classify-test.js +22 -0
  101. package/utils/compress-lines.js +45 -0
  102. package/utils/compute-trend.js +88 -0
  103. package/utils/detect-pm.js +58 -0
  104. package/utils/detect-timeout.js +17 -0
  105. package/utils/ensure-migrated.js +52 -0
  106. package/utils/failure-signature.js +33 -0
  107. package/utils/format-console.js +219 -0
  108. package/utils/format-fatal-error.js +35 -0
  109. package/utils/format-gfm.js +161 -0
  110. package/utils/format-terminal.js +480 -0
  111. package/utils/function-boundary.js +75 -0
  112. package/utils/hyperlink.js +11 -0
  113. package/utils/match-vitest-command.js +88 -0
  114. package/utils/normalize-workspace-key.js +34 -0
  115. package/utils/probe-host-metadata.js +99 -0
  116. package/utils/resolve-data-path.js +66 -0
  117. package/utils/resolve-project-key-from-cwd.js +52 -0
  118. package/utils/resolve-workspace-key.js +33 -0
  119. package/utils/safe-filename.js +34 -0
  120. package/utils/validate-coverage-targets-shape.js +84 -0
  121. package/utils/validate-phase-transition.js +113 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C. Spencer Beggs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @vitest-agent/sdk
2
+
3
+ [![npm](https://img.shields.io/npm/v/@vitest-agent/sdk?label=npm&color=cb3837)](https://www.npmjs.com/package/@vitest-agent/sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-4caf50.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript 6.0](https://img.shields.io/badge/TypeScript-6.0-3178c6.svg)](https://www.typescriptlang.org/)
6
+
7
+ > **Part of the [vitest-agent](https://vitest-agent.dev) ecosystem.** Most users want **[@vitest-agent/plugin](https://www.npmjs.com/package/@vitest-agent/plugin)**, which pulls this package in automatically. Install `@vitest-agent/sdk` directly only if you build tooling on the shared schemas or data layer.
8
+
9
+ The no-internal-deps base for the vitest-agent ecosystem. Carries the Effect Schemas, SQLite migrations and data layer, services and live layers, formatters, XDG path resolution, the public reporter and dispatcher contract types, and testing utilities.
10
+
11
+ ## Features
12
+
13
+ - **Effect Schemas** — all domain types (`RunEvent`, `RenderState`, `CoverageTargets`, `TurnPayload`, identity types) defined with Effect Schema; runtime validation and TypeScript types from one source
14
+ - **SQLite data layer** — `DataStore` and `DataReader` Effect services with live and `:memory:` test layers; `ensureMigrated` for safe multi-project setups
15
+ - **XDG path resolution** — deterministic `dbPath` derivation from workspace identity with a five-source fallback chain
16
+ - **Reporter and dispatcher contracts** — `VitestAgentReporterFactory`, `ReporterKit`, `ResolvedReporterConfig`, `DispatchInputs` and the types consumed by every other package
17
+ - **Sidecar dispatch core** — `dispatch`, `injectEnv` and `exitCodeForTag` on the `@vitest-agent/sdk/dispatch` sub-path for a minimal SEA bundle
18
+ - **Test utilities** — `makeTestLayer`, `DataStoreTestLayer` and five preset factory functions on the `@vitest-agent/sdk/testing` sub-path
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install @vitest-agent/sdk
24
+ # or
25
+ pnpm add @vitest-agent/sdk
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ ```ts
31
+ import { Effect } from "effect";
32
+ import { DataReader } from "@vitest-agent/sdk";
33
+ import { singlePassingRun } from "@vitest-agent/sdk/testing";
34
+
35
+ const layer = singlePassingRun(":memory:");
36
+
37
+ await Effect.runPromise(
38
+ Effect.provide(
39
+ Effect.flatMap(DataReader, (r) => r.getLatestRun("default", null)),
40
+ layer,
41
+ ),
42
+ );
43
+ // returns the seeded run row
44
+ ```
45
+
46
+ ## Documentation
47
+
48
+ Package reference at [vitest-agent.dev/sdk](https://vitest-agent.dev/sdk).
49
+
50
+ ## License
51
+
52
+ [MIT](LICENSE)
package/dispatch.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ //#region src/exit-code-for-tag.d.ts
2
+ /**
3
+ * Map a tagged-error `_tag` to the agent-agnostic sidecar exit-code
4
+ * taxonomy. Unknown tags collapse to `5` (other unexpected defect).
5
+ * @public
6
+ */
7
+ declare const exitCodeForTag: (tag: string) => number;
8
+ //#endregion
9
+ //#region src/internal-inject-env.d.ts
10
+ /** Input to {@link injectEnv}. @public */
11
+ interface InjectEnvInput {
12
+ readonly command: string;
13
+ readonly cwd: string;
14
+ readonly env: Record<string, string | undefined>;
15
+ }
16
+ /**
17
+ * Compute the (possibly rewritten) Bash command. Returns the original
18
+ * unchanged when:
19
+ *
20
+ * - The command does not match any Vitest pattern
21
+ * - `VITEST_AGENT_CONVERSATION_ID` or `VITEST_AGENT_AGENT_ID` is
22
+ * missing from env (no agent context to attribute to)
23
+ *
24
+ * Always synchronous — the package.json read is the only I/O and is
25
+ * fast enough not to need Effect wrapping.
26
+ * @public
27
+ */
28
+ declare const injectEnv: (input: InjectEnvInput) => string;
29
+ //#endregion
30
+ //#region src/sidecar-dispatch.d.ts
31
+ /** Result of a {@link dispatch} call: captured stdout/stderr + exit code.
32
+ * @public
33
+ */
34
+ interface DispatchResult {
35
+ readonly stdout: string;
36
+ readonly stderr: string;
37
+ readonly code: number;
38
+ }
39
+ /**
40
+ * Dispatch one argv invocation. `argv` is the post-`node post-bin`
41
+ * slice — i.e. `process.argv.slice(2)`. Never throws: every failure is
42
+ * folded into the returned {@link DispatchResult}.
43
+ * @public
44
+ */
45
+ declare const dispatch: (argv: readonly string[]) => Promise<DispatchResult>;
46
+ //#endregion
47
+ export { type DispatchResult, type InjectEnvInput, dispatch, exitCodeForTag, injectEnv };
48
+ //# sourceMappingURL=dispatch.d.ts.map
package/dispatch.js ADDED
@@ -0,0 +1,5 @@
1
+ import { exitCodeForTag } from "./exit-code-for-tag.js";
2
+ import { injectEnv } from "./internal-inject-env.js";
3
+ import { dispatch } from "./sidecar-dispatch.js";
4
+
5
+ export { dispatch, exitCodeForTag, injectEnv };
@@ -0,0 +1,65 @@
1
+ import { Data } from "effect";
2
+
3
+ //#region src/errors/AgentErrors.ts
4
+ /**
5
+ * `agent_id` does not exist for the given chat. Surface to the
6
+ * caller; the typical resolution is to call `register_agent` for
7
+ * this agent first.
8
+ * @public
9
+ */
10
+ var AgentNotFoundError = class extends Data.TaggedError("AgentNotFoundError") {};
11
+ /**
12
+ * Session row for the given `chat_id` does not exist. The host
13
+ * probably hasn't fired the SessionStart equivalent for this chat yet.
14
+ * @public
15
+ */
16
+ var SessionNotFoundError = class extends Data.TaggedError("SessionNotFoundError") {};
17
+ /**
18
+ * `conversation_id` does not exist. Used by rollup queries that
19
+ * require a conversation row.
20
+ * @public
21
+ */
22
+ var ConversationNotFoundError = class extends Data.TaggedError("ConversationNotFoundError") {};
23
+ /**
24
+ * True conflict — e.g., the `parentAgentId` supplied to
25
+ * `register_agent` refers to a different session than the one the
26
+ * caller named. Distinct from `IdempotencyHit`, which is a success.
27
+ * @public
28
+ */
29
+ var RegistrationConflictError = class extends Data.TaggedError("RegistrationConflictError") {
30
+ constructor(args) {
31
+ super(args);
32
+ Object.defineProperty(this, "message", {
33
+ value: `[register_agent] ${args.reason}`,
34
+ enumerable: true,
35
+ writable: false,
36
+ configurable: true
37
+ });
38
+ }
39
+ };
40
+ /**
41
+ * `host_metadata` JSON column failed to parse on read. Usually mapped
42
+ * to `DataStoreError` at the row-read boundary; surfaces alone in
43
+ * paths that explicitly carry through `Schema.parseJson` errors.
44
+ * @public
45
+ */
46
+ var HostMetadataParseError = class extends Data.TaggedError("HostMetadataParseError") {};
47
+ /**
48
+ * Sidecar exceeded its timeout. Surfaces from the sidecar CLI's
49
+ * `Effect.timeout`; the top-level wrapper maps it to exit code 2.
50
+ * @public
51
+ */
52
+ var SidecarTimeoutError = class extends Data.TaggedError("SidecarTimeoutError") {
53
+ constructor(args) {
54
+ super(args);
55
+ Object.defineProperty(this, "message", {
56
+ value: `[sidecar ${args.operation}] timed out after ${args.timeoutMs}ms`,
57
+ enumerable: true,
58
+ writable: false,
59
+ configurable: true
60
+ });
61
+ }
62
+ };
63
+
64
+ //#endregion
65
+ export { AgentNotFoundError, ConversationNotFoundError, HostMetadataParseError, RegistrationConflictError, SessionNotFoundError, SidecarTimeoutError };
@@ -0,0 +1,44 @@
1
+ import { Data } from "effect";
2
+
3
+ //#region src/errors/DataStoreError.ts
4
+ /**
5
+ * Error raised when a SQLite operation fails at read, write, or migration time.
6
+ * @public
7
+ */
8
+ var DataStoreError = class extends Data.TaggedError("DataStoreError") {
9
+ constructor(args) {
10
+ super(args);
11
+ Object.defineProperty(this, "message", {
12
+ value: `[${args.operation} ${args.table}] ${args.reason}`,
13
+ enumerable: true,
14
+ writable: false,
15
+ configurable: true
16
+ });
17
+ }
18
+ };
19
+ /**
20
+ * Extract a human-readable reason string from an Effect SqlError or unknown error.
21
+ *
22
+ * SqlError wraps the underlying better-sqlite3 error in `cause`. The actual SQLite
23
+ * message (e.g. "SQLITE_BUSY: database is locked", "UNIQUE constraint failed: ...")
24
+ * lives on `cause.message`, while the SqlError's own `message` is generic
25
+ * ("Failed to execute statement"). Prefer the cause's message when available.
26
+ * @public
27
+ */
28
+ const extractSqlReason = (e) => {
29
+ const err = e;
30
+ if (err && typeof err === "object") {
31
+ if (err.cause) {
32
+ if (typeof err.cause === "string") return err.cause;
33
+ if (typeof err.cause === "object" && typeof err.cause.message === "string") return err.cause.message;
34
+ }
35
+ if (typeof err.message === "string" && err.message.length > 0) return err.message;
36
+ try {
37
+ return JSON.stringify(e);
38
+ } catch {}
39
+ }
40
+ return String(e);
41
+ };
42
+
43
+ //#endregion
44
+ export { DataStoreError, extractSqlReason };
@@ -0,0 +1,21 @@
1
+ import { Data } from "effect";
2
+
3
+ //#region src/errors/DiscoveryError.ts
4
+ /**
5
+ * Error raised when project discovery (glob, file read, or stat) fails.
6
+ * @public
7
+ */
8
+ var DiscoveryError = class extends Data.TaggedError("DiscoveryError") {
9
+ constructor(args) {
10
+ super(args);
11
+ Object.defineProperty(this, "message", {
12
+ value: `[${args.operation} ${args.path}] ${args.reason}`,
13
+ enumerable: true,
14
+ writable: false,
15
+ configurable: true
16
+ });
17
+ }
18
+ };
19
+
20
+ //#endregion
21
+ export { DiscoveryError };
@@ -0,0 +1,27 @@
1
+ import { Data } from "effect";
2
+
3
+ //#region src/errors/PathResolutionError.ts
4
+ /**
5
+ * Error raised when the data directory path cannot be resolved.
6
+ *
7
+ * The most common case is missing workspace identity: no `projectKey`
8
+ * override in `vitest-agent.config.toml` and no `name` field in
9
+ * the root workspace's `package.json`. The path is a function of the
10
+ * workspace's identity, so without one we have no canonical location to
11
+ * write to.
12
+ * @public
13
+ */
14
+ var PathResolutionError = class extends Data.TaggedError("PathResolutionError") {
15
+ constructor(args) {
16
+ super(args);
17
+ Object.defineProperty(this, "message", {
18
+ value: args.reason,
19
+ enumerable: true,
20
+ writable: false,
21
+ configurable: true
22
+ });
23
+ }
24
+ };
25
+
26
+ //#endregion
27
+ export { PathResolutionError };
@@ -0,0 +1,29 @@
1
+ import { Data } from "effect";
2
+
3
+ //#region src/errors/ProjectIdentityError.ts
4
+ /**
5
+ * Raised at plugin / sidecar / MCP boot when the project's identity
6
+ * cannot be resolved through the documented fallback chain. The error
7
+ * carries the full list of attempted sources so the user knows which
8
+ * lever to pull (set `projectId` explicitly, configure the TOML file,
9
+ * add a git remote, or set `name` in `package.json`).
10
+ *
11
+ * Replaces the legacy `WorkspaceRootNotFoundError` for callers that
12
+ * went through the new resolver.
13
+ * @public
14
+ */
15
+ var ProjectIdentityNotResolvableError = class extends Data.TaggedError("ProjectIdentityNotResolvableError") {
16
+ constructor(args) {
17
+ super(args);
18
+ const summary = args.tried.length === 0 ? "no sources attempted" : args.tried.map((c) => `${c.source}: ${c.reason}`).join("; ");
19
+ Object.defineProperty(this, "message", {
20
+ value: `Project identity could not be resolved (${summary}). Set projectId explicitly in vitest.config.ts, projectKey in vitest-agent.config.toml, configure a git remote, or add "name" to package.json.`,
21
+ enumerable: true,
22
+ writable: false,
23
+ configurable: true
24
+ });
25
+ }
26
+ };
27
+
28
+ //#endregion
29
+ export { ProjectIdentityNotResolvableError };
@@ -0,0 +1,35 @@
1
+ import { Data } from "effect";
2
+
3
+ //#region src/errors/RunContextError.ts
4
+ /**
5
+ * Surfaces unexpected git-command failures from `RunContext`. Expected
6
+ * non-zero exits (e.g., "not a git repository") are absorbed via
7
+ * `Effect.option` and surface as `null` columns — they are not errors.
8
+ * This error is reserved for "the executable is missing" /
9
+ * "permission denied" / other system-level failures the caller cannot
10
+ * meaningfully recover from.
11
+ * @public
12
+ */
13
+ var GitCommandError = class extends Data.TaggedError("GitCommandError") {
14
+ constructor(args) {
15
+ super(args);
16
+ Object.defineProperty(this, "message", {
17
+ value: `[git ${args.command}] ${args.reason}`,
18
+ enumerable: true,
19
+ writable: false,
20
+ configurable: true
21
+ });
22
+ }
23
+ };
24
+ /**
25
+ * Internal probe miss — consumed by `Effect.firstSuccessOf` /
26
+ * `Effect.orElse` inside the host-metadata probe chain. Never surfaces
27
+ * to the caller of `RunContext.captureRunContext`; the public API
28
+ * always returns a complete `RunContext` (with NULLs for missed
29
+ * probes).
30
+ * @internal
31
+ */
32
+ var ProbeMissError = class extends Data.TaggedError("ProbeMissError") {};
33
+
34
+ //#endregion
35
+ export { GitCommandError, ProbeMissError };
@@ -0,0 +1,66 @@
1
+ import { Data } from "effect";
2
+
3
+ //#region src/errors/TddErrors.ts
4
+ /** @public */
5
+ var GoalNotFoundError = class extends Data.TaggedError("GoalNotFoundError") {
6
+ constructor(args) {
7
+ super(args);
8
+ Object.defineProperty(this, "message", {
9
+ value: `[goal not_found id=${args.id}] ${args.reason}`,
10
+ enumerable: true,
11
+ writable: false,
12
+ configurable: true
13
+ });
14
+ }
15
+ };
16
+ /** @public */
17
+ var BehaviorNotFoundError = class extends Data.TaggedError("BehaviorNotFoundError") {
18
+ constructor(args) {
19
+ super(args);
20
+ Object.defineProperty(this, "message", {
21
+ value: `[behavior not_found id=${args.id}] ${args.reason}`,
22
+ enumerable: true,
23
+ writable: false,
24
+ configurable: true
25
+ });
26
+ }
27
+ };
28
+ /** @public */
29
+ var TddTaskNotFoundError = class extends Data.TaggedError("TddTaskNotFoundError") {
30
+ constructor(args) {
31
+ super(args);
32
+ Object.defineProperty(this, "message", {
33
+ value: `[tdd_task not_found id=${args.id}] ${args.reason}`,
34
+ enumerable: true,
35
+ writable: false,
36
+ configurable: true
37
+ });
38
+ }
39
+ };
40
+ /** @public */
41
+ var TddTaskAlreadyEndedError = class extends Data.TaggedError("TddTaskAlreadyEndedError") {
42
+ constructor(args) {
43
+ super(args);
44
+ Object.defineProperty(this, "message", {
45
+ value: `[tdd_task ended id=${args.id}] outcome=${args.outcome} endedAt=${args.endedAt}`,
46
+ enumerable: true,
47
+ writable: false,
48
+ configurable: true
49
+ });
50
+ }
51
+ };
52
+ /** @public */
53
+ var IllegalStatusTransitionError = class extends Data.TaggedError("IllegalStatusTransitionError") {
54
+ constructor(args) {
55
+ super(args);
56
+ Object.defineProperty(this, "message", {
57
+ value: `[${args.entity} illegal_transition id=${args.id}] ${args.from} → ${args.to}: ${args.reason}`,
58
+ enumerable: true,
59
+ writable: false,
60
+ configurable: true
61
+ });
62
+ }
63
+ };
64
+
65
+ //#endregion
66
+ export { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError };
@@ -0,0 +1,18 @@
1
+ //#region src/exit-code-for-tag.ts
2
+ /**
3
+ * Map a tagged-error `_tag` to the agent-agnostic sidecar exit-code
4
+ * taxonomy. Unknown tags collapse to `5` (other unexpected defect).
5
+ * @public
6
+ */
7
+ const exitCodeForTag = (tag) => {
8
+ switch (tag) {
9
+ case "RegistrationConflictError": return 1;
10
+ case "SidecarTimeoutError": return 2;
11
+ case "DataStoreError": return 3;
12
+ case "ProjectIdentityNotResolvableError": return 4;
13
+ default: return 5;
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ export { exitCodeForTag };
@@ -0,0 +1,61 @@
1
+ //#region src/formatters/ci-annotations.ts
2
+ const escapeData = (s) => s.replace(/%/g, "%25").replace(/\n/g, "%0A").replace(/\r/g, "%0D");
3
+ const escapeProperty = (s) => s.replace(/%/g, "%25").replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/:/g, "%3A").replace(/,/g, "%2C");
4
+ const STACK_FILE_LINE = /\(([^():\s]+):(\d+):\d+\)/;
5
+ function extractFileAndLine(error, fallbackFile) {
6
+ if (error.stack !== void 0) {
7
+ const m = STACK_FILE_LINE.exec(error.stack);
8
+ if (m) return {
9
+ file: m[1],
10
+ line: Number.parseInt(m[2], 10)
11
+ };
12
+ }
13
+ return {
14
+ file: fallbackFile,
15
+ line: 1
16
+ };
17
+ }
18
+ function annotateFailure(report) {
19
+ const lines = [];
20
+ for (const mod of report.failed) for (const test of mod.tests) {
21
+ if (test.state !== "failed") continue;
22
+ const errors = test.errors ?? [];
23
+ if (errors.length === 0) {
24
+ lines.push(`::error file=${escapeProperty(mod.file)},line=1,title=${escapeProperty(test.fullName)}::${escapeData(`${test.fullName} failed`)}`);
25
+ continue;
26
+ }
27
+ for (const err of errors) {
28
+ const { file, line } = extractFileAndLine(err, mod.file);
29
+ lines.push(`::error file=${escapeProperty(file)},line=${line},title=${escapeProperty(test.fullName)}::${escapeData(err.message)}`);
30
+ }
31
+ }
32
+ for (const err of report.unhandledErrors) lines.push(`::error title=Unhandled%20error::${escapeData(err.message)}`);
33
+ return lines;
34
+ }
35
+ /** @public */
36
+ const ciAnnotationsFormatter = {
37
+ format: "ci-annotations",
38
+ render: (reports, _context) => {
39
+ const allLines = [];
40
+ let totalPassed = 0;
41
+ let totalFailed = 0;
42
+ let totalSkipped = 0;
43
+ let totalDuration = 0;
44
+ for (const r of reports) {
45
+ allLines.push(...annotateFailure(r));
46
+ totalPassed += r.summary.passed;
47
+ totalFailed += r.summary.failed;
48
+ totalSkipped += r.summary.skipped;
49
+ totalDuration += r.summary.duration;
50
+ }
51
+ if (allLines.length === 0) allLines.push(`::notice::${escapeData(`Vitest: ${totalPassed} passed, ${totalSkipped} skipped (${totalDuration}ms)`)}`);
52
+ return [{
53
+ target: "stdout",
54
+ content: `${allLines.join("\n")}\n`,
55
+ contentType: "text/plain"
56
+ }];
57
+ }
58
+ };
59
+
60
+ //#endregion
61
+ export { ciAnnotationsFormatter };
@@ -0,0 +1,20 @@
1
+ import { formatGfm } from "../utils/format-gfm.js";
2
+
3
+ //#region src/formatters/gfm.ts
4
+ /** @public */
5
+ const GfmFormatter = {
6
+ format: "gfm",
7
+ render: (reports, _context) => {
8
+ const outputs = [];
9
+ const gfm = formatGfm([...reports]);
10
+ if (gfm) outputs.push({
11
+ target: "github-summary",
12
+ content: gfm,
13
+ contentType: "text/markdown"
14
+ });
15
+ return outputs;
16
+ }
17
+ };
18
+
19
+ //#endregion
20
+ export { GfmFormatter };
@@ -0,0 +1,18 @@
1
+ //#region src/formatters/json.ts
2
+ /** @public */
3
+ const JsonFormatter = {
4
+ format: "json",
5
+ render: (reports, _context) => {
6
+ const outputs = [];
7
+ const json = JSON.stringify(reports, null, 2);
8
+ outputs.push({
9
+ target: "stdout",
10
+ content: json,
11
+ contentType: "application/json"
12
+ });
13
+ return outputs;
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ export { JsonFormatter };
@@ -0,0 +1,39 @@
1
+ import { formatConsoleMarkdown } from "../utils/format-console.js";
2
+
3
+ //#region src/formatters/markdown.ts
4
+ /**
5
+ * Markdown formatter for surfaces that *render* markdown.
6
+ *
7
+ * Stdout output goes through {@link TerminalFormatter} (plain text +
8
+ * ANSI), not this formatter — markdown syntax (e.g. heading prefixes,
9
+ * backticks, list dashes) is visual noise in a terminal that doesn't
10
+ * render it. This formatter remains for callers that explicitly request
11
+ * `format: "markdown"`, e.g. when piping the rendered output into a
12
+ * Markdown-aware sink.
13
+ * @public
14
+ */
15
+ const MarkdownFormatter = {
16
+ format: "markdown",
17
+ render: (reports, context) => {
18
+ const outputs = [];
19
+ for (const report of reports) {
20
+ const md = formatConsoleMarkdown(report, {
21
+ consoleOutput: context.detail === "minimal" ? "failures" : "full",
22
+ coverageConsoleLimit: context.coverageConsoleLimit,
23
+ noColor: context.noColor,
24
+ ...context.trendSummary !== void 0 ? { trendSummary: context.trendSummary } : {},
25
+ ...context.runCommand !== void 0 ? { runCommand: context.runCommand } : {},
26
+ ...context.mcp !== void 0 ? { mcp: context.mcp } : {}
27
+ });
28
+ if (md) outputs.push({
29
+ target: "stdout",
30
+ content: md,
31
+ contentType: "text/markdown"
32
+ });
33
+ }
34
+ return outputs;
35
+ }
36
+ };
37
+
38
+ //#endregion
39
+ export { MarkdownFormatter };
@@ -0,0 +1,9 @@
1
+ //#region src/formatters/silent.ts
2
+ /** @public */
3
+ const SilentFormatter = {
4
+ format: "silent",
5
+ render: () => []
6
+ };
7
+
8
+ //#endregion
9
+ export { SilentFormatter };
@@ -0,0 +1,56 @@
1
+ import { formatTerminal } from "../utils/format-terminal.js";
2
+ import { osc8 } from "../utils/hyperlink.js";
3
+ import * as path from "node:path";
4
+
5
+ //#region src/formatters/terminal.ts
6
+ /**
7
+ * Wrap test-file paths in failing-test rows with `file://` OSC-8
8
+ * hyperlinks. Only fires when the target is stdout and noColor is
9
+ * unset; MCP responses never reach this formatter so they cannot
10
+ * accidentally pick up escape sequences.
11
+ *
12
+ * Failing-test rows are emitted by `renderFailedTest` in
13
+ * `format-terminal.ts` as
14
+ *
15
+ * ` ` + ANSI(red, ✗) + ` ` + path + ` > ` + fullName
16
+ *
17
+ * The ✗ is wrapped in ANSI color escapes whenever color is on (which
18
+ * is always when this function runs — `wrapHyperlinks` is gated on
19
+ * `!noColor`). The optional `(?:\x1b\[\d+m)?` groups around `✗` make
20
+ * the pattern tolerant of those escapes; without them the regex
21
+ * silently never matched and the hyperlink feature was a no-op.
22
+ *
23
+ * The captured path is project-relative (the failing-test row goes
24
+ * through `relativePath()` in `format-terminal.ts`). RFC 8089
25
+ * requires an absolute filesystem path inside a `file://` URL —
26
+ * iTerm2 / WezTerm / Kitty / VSCode all silently fail to open
27
+ * relative targets. Resolve the captured value back to absolute
28
+ * against the cwd before handing it to `osc8`. The display label
29
+ * stays relative so the rendered output is unchanged.
30
+ * @public
31
+ */
32
+ const FAILED_TEST_ROW = /^( {4}(?:\x1b\[\d+m)?✗(?:\x1b\[\d+m)? )([^ ]+)( > )/gm;
33
+ const wrapHyperlinks = (text, ctx) => text.replace(FAILED_TEST_ROW, (_match, prefix, captured, suffix) => {
34
+ return `${prefix}${osc8(`file://${path.resolve(process.cwd(), captured)}`, captured, { enabled: !ctx.noColor })}${suffix}`;
35
+ });
36
+ /** @public */
37
+ const TerminalFormatter = {
38
+ format: "terminal",
39
+ render: (reports, context) => {
40
+ const text = formatTerminal(reports, {
41
+ noColor: context.noColor,
42
+ coverageConsoleLimit: context.coverageConsoleLimit,
43
+ ...context.trendSummary !== void 0 ? { trendSummary: context.trendSummary } : {},
44
+ ...context.mcp !== void 0 ? { mcp: context.mcp } : {}
45
+ });
46
+ if (text === "") return [];
47
+ return [{
48
+ target: "stdout",
49
+ content: context.noColor ? text : wrapHyperlinks(text, context),
50
+ contentType: "text/plain"
51
+ }];
52
+ }
53
+ };
54
+
55
+ //#endregion
56
+ export { TerminalFormatter };