@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
@@ -0,0 +1,30 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/Thresholds.ts
4
+ /**
5
+ * Per-metric threshold values. All optional -- only set metrics are enforced.
6
+ * @public
7
+ */
8
+ const MetricThresholds = Schema.Struct({
9
+ lines: Schema.optional(Schema.Number),
10
+ functions: Schema.optional(Schema.Number),
11
+ branches: Schema.optional(Schema.Number),
12
+ statements: Schema.optional(Schema.Number)
13
+ }).annotations({ identifier: "MetricThresholds" });
14
+ /**
15
+ * A glob pattern paired with its metric thresholds.
16
+ * @public
17
+ */
18
+ const PatternThresholds = Schema.Tuple(Schema.String, MetricThresholds).annotations({ identifier: "PatternThresholds" });
19
+ /**
20
+ * Fully resolved thresholds ready for evaluation.
21
+ * @public
22
+ */
23
+ const ResolvedThresholds = Schema.Struct({
24
+ global: MetricThresholds,
25
+ perFile: Schema.optionalWith(Schema.Boolean, { default: () => false }),
26
+ patterns: Schema.optionalWith(Schema.Array(PatternThresholds), { default: () => [] })
27
+ }).annotations({ identifier: "ResolvedThresholds" });
28
+
29
+ //#endregion
30
+ export { MetricThresholds, PatternThresholds, ResolvedThresholds };
@@ -0,0 +1,16 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/Transport.ts
4
+ /**
5
+ * Transport binding for the persistence layer.
6
+ *
7
+ * 2.x ships only `{ kind: "local" }`. Modeled as a single-member
8
+ * discriminated union so the 3.0 cloud-backend swap (D1, Turso, etc.)
9
+ * lands as a pure addition of new union members — no schema-shape
10
+ * diff and no breaking API change at the call site.
11
+ * @public
12
+ */
13
+ const Transport = Schema.Union(Schema.Struct({ kind: Schema.Literal("local") })).annotations({ identifier: "Transport" });
14
+
15
+ //#endregion
16
+ export { Transport };
@@ -0,0 +1,23 @@
1
+ import { CoverageTotals } from "./Coverage.js";
2
+ import { Schema } from "effect";
3
+
4
+ //#region src/schemas/Trends.ts
5
+ /**
6
+ * A single coverage trend data point recorded on a full test run.
7
+ * @public
8
+ */
9
+ const TrendEntry = Schema.Struct({
10
+ timestamp: Schema.String,
11
+ coverage: CoverageTotals,
12
+ delta: CoverageTotals,
13
+ direction: Schema.Literal("improving", "regressing", "stable"),
14
+ targetsHash: Schema.optional(Schema.String)
15
+ }).annotations({ identifier: "TrendEntry" });
16
+ /**
17
+ * Per-project trend record with sliding window of entries.
18
+ * @public
19
+ */
20
+ const TrendRecord = Schema.Struct({ entries: Schema.Array(TrendEntry) }).annotations({ identifier: "TrendRecord" });
21
+
22
+ //#endregion
23
+ export { TrendEntry, TrendRecord };
@@ -0,0 +1,15 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/turns/FileEditPayload.ts
4
+ /** @public */
5
+ const FileEditPayload = Schema.Struct({
6
+ type: Schema.Literal("file_edit"),
7
+ file_path: Schema.String,
8
+ edit_kind: Schema.Literal("write", "edit", "multi_edit"),
9
+ lines_added: Schema.optional(Schema.Number),
10
+ lines_removed: Schema.optional(Schema.Number),
11
+ diff: Schema.optional(Schema.String)
12
+ });
13
+
14
+ //#endregion
15
+ export { FileEditPayload };
@@ -0,0 +1,13 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/turns/HookFirePayload.ts
4
+ /** @public */
5
+ const HookFirePayload = Schema.Struct({
6
+ type: Schema.Literal("hook_fire"),
7
+ hook_kind: Schema.Literal("SessionStart", "SessionEnd", "Stop", "StopFailure", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "PreToolUse", "PostToolUse", "PostToolUseFailure", "UserPromptSubmit", "FileChanged"),
8
+ chat_id: Schema.optional(Schema.String),
9
+ previous_record_failures: Schema.optional(Schema.Array(Schema.String))
10
+ });
11
+
12
+ //#endregion
13
+ export { HookFirePayload };
@@ -0,0 +1,13 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/turns/HypothesisPayload.ts
4
+ /** @public */
5
+ const HypothesisPayload = Schema.Struct({
6
+ type: Schema.Literal("hypothesis"),
7
+ content: Schema.String,
8
+ cited_test_error_id: Schema.optional(Schema.Number),
9
+ cited_stack_frame_id: Schema.optional(Schema.Number)
10
+ });
11
+
12
+ //#endregion
13
+ export { HypothesisPayload };
@@ -0,0 +1,13 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/turns/NotePayload.ts
4
+ /** @public */
5
+ const NotePayload = Schema.Struct({
6
+ type: Schema.Literal("note"),
7
+ scope: Schema.String,
8
+ title: Schema.optional(Schema.String),
9
+ content: Schema.String
10
+ });
11
+
12
+ //#endregion
13
+ export { NotePayload };
@@ -0,0 +1,13 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/turns/ToolCallPayload.ts
4
+ /** @public */
5
+ const ToolCallPayload = Schema.Struct({
6
+ type: Schema.Literal("tool_call"),
7
+ tool_name: Schema.String,
8
+ tool_input: Schema.Unknown,
9
+ tool_use_id: Schema.optional(Schema.String)
10
+ });
11
+
12
+ //#endregion
13
+ export { ToolCallPayload };
@@ -0,0 +1,15 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/turns/ToolResultPayload.ts
4
+ /** @public */
5
+ const ToolResultPayload = Schema.Struct({
6
+ type: Schema.Literal("tool_result"),
7
+ tool_name: Schema.String,
8
+ tool_use_id: Schema.optional(Schema.String),
9
+ result_summary: Schema.optional(Schema.String),
10
+ success: Schema.Boolean,
11
+ duration_ms: Schema.optional(Schema.Number)
12
+ });
13
+
14
+ //#endregion
15
+ export { ToolResultPayload };
@@ -0,0 +1,12 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/turns/UserPromptPayload.ts
4
+ /** @public */
5
+ const UserPromptPayload = Schema.Struct({
6
+ type: Schema.Literal("user_prompt"),
7
+ prompt: Schema.String,
8
+ cc_message_id: Schema.optional(Schema.String)
9
+ });
10
+
11
+ //#endregion
12
+ export { UserPromptPayload };
@@ -0,0 +1,15 @@
1
+ import { FileEditPayload } from "./FileEditPayload.js";
2
+ import { HookFirePayload } from "./HookFirePayload.js";
3
+ import { HypothesisPayload } from "./HypothesisPayload.js";
4
+ import { NotePayload } from "./NotePayload.js";
5
+ import { ToolCallPayload } from "./ToolCallPayload.js";
6
+ import { ToolResultPayload } from "./ToolResultPayload.js";
7
+ import { UserPromptPayload } from "./UserPromptPayload.js";
8
+ import { Schema } from "effect";
9
+
10
+ //#region src/schemas/turns/index.ts
11
+ /** @public */
12
+ const TurnPayload = Schema.Union(UserPromptPayload, ToolCallPayload, ToolResultPayload, FileEditPayload, HookFirePayload, NotePayload, HypothesisPayload);
13
+
14
+ //#endregion
15
+ export { TurnPayload };
@@ -0,0 +1,15 @@
1
+ import { ConfigFile } from "config-file-effect";
2
+
3
+ //#region src/services/Config.ts
4
+ /**
5
+ * Typed `Context.Tag` for the vitest-agent config file service.
6
+ *
7
+ * Both runtime packages (reporter, MCP) yield this tag to access the loaded
8
+ * `VitestAgentConfig`. The live layer is built per `projectDir` via
9
+ * `ConfigLive(projectDir)`.
10
+ * @public
11
+ */
12
+ const VitestAgentConfigFile = ConfigFile.Tag("vitest-agent/Config");
13
+
14
+ //#endregion
15
+ export { VitestAgentConfigFile };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/DataReader.ts
4
+ /** @public */
5
+ var DataReader = class extends Context.Tag("vitest-agent/DataReader")() {};
6
+
7
+ //#endregion
8
+ export { DataReader };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/DataStore.ts
4
+ /** @public */
5
+ var DataStore = class extends Context.Tag("vitest-agent/DataStore")() {};
6
+
7
+ //#endregion
8
+ export { DataStore };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/DetailResolver.ts
4
+ /** @public */
5
+ var DetailResolver = class extends Context.Tag("vitest-agent/DetailResolver")() {};
6
+
7
+ //#endregion
8
+ export { DetailResolver };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/DiscoveryRegistry.ts
4
+ /** @public */
5
+ var DiscoveryRegistry = class extends Context.Tag("vitest-agent/DiscoveryRegistry")() {};
6
+
7
+ //#endregion
8
+ export { DiscoveryRegistry };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/EnvironmentDetector.ts
4
+ /** @public */
5
+ var EnvironmentDetector = class extends Context.Tag("vitest-agent/EnvironmentDetector")() {};
6
+
7
+ //#endregion
8
+ export { EnvironmentDetector };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/ExecutorResolver.ts
4
+ /** @public */
5
+ var ExecutorResolver = class extends Context.Tag("vitest-agent/ExecutorResolver")() {};
6
+
7
+ //#endregion
8
+ export { ExecutorResolver };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/FormatSelector.ts
4
+ /** @public */
5
+ var FormatSelector = class extends Context.Tag("vitest-agent/FormatSelector")() {};
6
+
7
+ //#endregion
8
+ export { FormatSelector };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/HistoryTracker.ts
4
+ /** @public */
5
+ var HistoryTracker = class extends Context.Tag("vitest-agent/HistoryTracker")() {};
6
+
7
+ //#endregion
8
+ export { HistoryTracker };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/OutputRenderer.ts
4
+ /** @public */
5
+ var OutputRenderer = class extends Context.Tag("vitest-agent/OutputRenderer")() {};
6
+
7
+ //#endregion
8
+ export { OutputRenderer };
@@ -0,0 +1,22 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/PerClientSessionMap.ts
4
+ /**
5
+ * Read-only side of the session map.
6
+ *
7
+ * Provided by `PerClientSessionMapReaderLive` (opens SQLite with
8
+ * `?mode=ro`). MCP server depends on this tag — calls to write
9
+ * methods would be a type error.
10
+ * @public
11
+ */
12
+ var PerClientSessionMapReader = class extends Context.Tag("vitest-agent/PerClientSessionMapReader")() {};
13
+ /**
14
+ * Read-write side of the session map. Sidecar provides this; the
15
+ * Writer layer also satisfies the Reader tag so consumers of either
16
+ * one resolve.
17
+ * @public
18
+ */
19
+ var PerClientSessionMapWriter = class extends Context.Tag("vitest-agent/PerClientSessionMapWriter")() {};
20
+
21
+ //#endregion
22
+ export { PerClientSessionMapReader, PerClientSessionMapWriter };
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+
3
+ //#region src/services/ProjectDiscovery.ts
4
+ /** @public */
5
+ var ProjectDiscovery = class extends Context.Tag("vitest-agent/ProjectDiscovery")() {};
6
+
7
+ //#endregion
8
+ export { ProjectDiscovery };
@@ -0,0 +1,63 @@
1
+ import { canonicalizeGitUrl, gitUrlToProjectKey } from "../utils/canonicalize-git-url.js";
2
+ import { normalizeWorkspaceKey } from "../utils/normalize-workspace-key.js";
3
+ import { Context } from "effect";
4
+
5
+ //#region src/services/ProjectIdentity.ts
6
+ const isUseful = (value) => value !== void 0 && value.trim().length > 0;
7
+ /**
8
+ * Pure priority resolver. Returns `null` when no candidate produces a
9
+ * usable value — the caller wraps `null` into a tagged error.
10
+ *
11
+ * For non-git sources the `canonicalForm` equals the original
12
+ * (un-normalized) input so the discovery registry can display
13
+ * `@spencerbeggs/vitest-agent` rather than the underscored form. For
14
+ * git sources, `canonicalForm` is the slash-preserving `host/path`
15
+ * shape from `canonicalizeGitUrl`.
16
+ * @public
17
+ */
18
+ const resolveProjectIdentityFromCandidates = (candidates) => {
19
+ if (isUseful(candidates.explicit)) return {
20
+ projectKey: normalizeWorkspaceKey(candidates.explicit),
21
+ canonicalForm: candidates.explicit,
22
+ source: "explicit"
23
+ };
24
+ if (isUseful(candidates.toml)) return {
25
+ projectKey: normalizeWorkspaceKey(candidates.toml),
26
+ canonicalForm: candidates.toml,
27
+ source: "toml"
28
+ };
29
+ if (isUseful(candidates.gitRemote)) {
30
+ const canonical = canonicalizeGitUrl(candidates.gitRemote);
31
+ const key = gitUrlToProjectKey(candidates.gitRemote);
32
+ if (canonical !== null && key !== null) return {
33
+ projectKey: key,
34
+ canonicalForm: canonical,
35
+ source: "git-remote"
36
+ };
37
+ }
38
+ if (isUseful(candidates.packageJsonRepoUrl)) {
39
+ const canonical = canonicalizeGitUrl(candidates.packageJsonRepoUrl);
40
+ const key = gitUrlToProjectKey(candidates.packageJsonRepoUrl);
41
+ if (canonical !== null && key !== null) return {
42
+ projectKey: key,
43
+ canonicalForm: canonical,
44
+ source: "package-repository"
45
+ };
46
+ }
47
+ if (isUseful(candidates.packageJsonName)) return {
48
+ projectKey: normalizeWorkspaceKey(candidates.packageJsonName),
49
+ canonicalForm: candidates.packageJsonName,
50
+ source: "package-name"
51
+ };
52
+ return null;
53
+ };
54
+ /**
55
+ * Service tag. The Live layer (in `layers/ProjectIdentityLive.ts`)
56
+ * wires up the I/O sources; tests provide a stub that returns a
57
+ * pre-built `ResolvedIdentity`.
58
+ * @public
59
+ */
60
+ var ProjectIdentity = class extends Context.Tag("vitest-agent/ProjectIdentity")() {};
61
+
62
+ //#endregion
63
+ export { ProjectIdentity, resolveProjectIdentityFromCandidates };
@@ -0,0 +1,53 @@
1
+ import { Context, Schema } from "effect";
2
+
3
+ //#region src/services/RunContext.ts
4
+ /**
5
+ * Per-run snapshot of the workspace's git state and host context.
6
+ * Every field is nullable: a non-git workspace has all-NULL git
7
+ * fields, a CI run with no terminal probe match has all-NULL host
8
+ * fields, and a worker that can't read upstream info has a NULL
9
+ * `gitUpstream` while everything else is set.
10
+ *
11
+ * Declared as a `Schema.Class` so callers can `new RunContext({...})`
12
+ * and `Schema.encode(...)` for the SQLite row mapping.
13
+ * @public
14
+ */
15
+ var RunContext = class extends Schema.Class("vitest-agent/RunContext")({
16
+ gitBranch: Schema.NullOr(Schema.String),
17
+ gitCommitSha: Schema.NullOr(Schema.String),
18
+ gitDirty: Schema.NullOr(Schema.Boolean),
19
+ gitUpstream: Schema.NullOr(Schema.String),
20
+ gitWorktreeDir: Schema.NullOr(Schema.String),
21
+ hostSource: Schema.NullOr(Schema.String),
22
+ hostValue: Schema.NullOr(Schema.String),
23
+ hostMetadata: Schema.NullOr(Schema.Unknown)
24
+ }) {};
25
+ /**
26
+ * Subset of {@link RunContext} captured at agent registration. Only
27
+ * the inheritable git context — branch, sha, worktree path. Mid-run
28
+ * dirty-tree state and upstream tracking are per-run concerns.
29
+ * @public
30
+ */
31
+ var AgentContext = class extends Schema.Class("vitest-agent/AgentContext")({
32
+ startGitBranch: Schema.NullOr(Schema.String),
33
+ startGitCommitSha: Schema.NullOr(Schema.String),
34
+ startWorktreeDir: Schema.NullOr(Schema.String)
35
+ }) {};
36
+ /**
37
+ * Service tag. The Live layer captures real I/O; the Test layer
38
+ * returns whatever the test fixture passes.
39
+ * @public
40
+ */
41
+ var RunContext$ = class extends Context.Tag("vitest-agent/RunContextService")() {};
42
+ /**
43
+ * Re-export the service tag under a less-collision-prone name. The
44
+ * `RunContext` Schema.Class above shares its symbol-name with the
45
+ * service tag concept; the `$` suffix on the Context.Tag class name
46
+ * keeps both addressable from the same module.
47
+ *
48
+ * @public
49
+ */
50
+ const RunContextService = RunContext$;
51
+
52
+ //#endregion
53
+ export { AgentContext, RunContext, RunContext$, RunContextService };
@@ -0,0 +1,57 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ //#region src/services/idempotency.ts
4
+ /**
5
+ * Sentinel substituted for `parentAgentId` when the value is `null`.
6
+ * Avoids ambiguity around hashing the literal string "null" or empty
7
+ * string, both of which could collide with caller-supplied values.
8
+ * @public
9
+ */
10
+ const IDEMPOTENCY_ROOT_SENTINEL = "__ROOT__";
11
+ /**
12
+ * Derive the canonical idempotency key for an `agents` row.
13
+ *
14
+ * Algorithm:
15
+ * sha256(agentType + "|" + (parentAgentId ?? "__ROOT__") + "|" + clientNonce)
16
+ * then base32 (RFC 4648 alphabet, lowercased) truncated to 26 chars.
17
+ *
18
+ * 26 base32 chars = 130 bits, far beyond the birthday-bound for the
19
+ * scoping (UNIQUE per session). The base32 alphabet is a-z2-7 — all
20
+ * lowercase to keep the column lexicographically friendly and
21
+ * case-insensitive on filesystems that we never hit but might in
22
+ * future debugging.
23
+ * @public
24
+ */
25
+ const deriveIdempotencyKey = (input) => {
26
+ const parent = input.parentAgentId ?? "__ROOT__";
27
+ const message = `${input.agentType}|${parent}|${input.clientNonce}`;
28
+ return base32Encode(createHash("sha256").update(message).digest()).slice(0, 26);
29
+ };
30
+ const BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
31
+ /**
32
+ * RFC 4648 base32 encoder, lowercase, no padding. Produces ceil(8n/5)
33
+ * characters from an `n`-byte input. SHA-256 output (32 bytes) yields
34
+ * 52 base32 chars; the caller truncates to 26.
35
+ */
36
+ const base32Encode = (bytes) => {
37
+ let output = "";
38
+ let buffer = 0;
39
+ let bitsInBuffer = 0;
40
+ for (const byte of bytes) {
41
+ buffer = buffer << 8 | byte;
42
+ bitsInBuffer += 8;
43
+ while (bitsInBuffer >= 5) {
44
+ bitsInBuffer -= 5;
45
+ const index = buffer >> bitsInBuffer & 31;
46
+ output += BASE32_ALPHABET[index];
47
+ }
48
+ }
49
+ if (bitsInBuffer > 0) {
50
+ const index = buffer << 5 - bitsInBuffer & 31;
51
+ output += BASE32_ALPHABET[index];
52
+ }
53
+ return output;
54
+ };
55
+
56
+ //#endregion
57
+ export { IDEMPOTENCY_ROOT_SENTINEL, deriveIdempotencyKey };
@@ -0,0 +1,84 @@
1
+ import { exitCodeForTag } from "./exit-code-for-tag.js";
2
+ import { injectEnv } from "./internal-inject-env.js";
3
+
4
+ //#region src/sidecar-dispatch.ts
5
+ /**
6
+ * Parse `--flag value` pairs from an argv slice into a plain record.
7
+ * Bare tokens and unknown shapes are ignored — the subcommand handlers
8
+ * read only the flags they expect.
9
+ */
10
+ const parseFlags = (argv) => {
11
+ const flags = {};
12
+ for (let i = 0; i < argv.length; i += 1) {
13
+ const token = argv[i];
14
+ if (token?.startsWith("--")) {
15
+ const key = token.slice(2);
16
+ const next = argv[i + 1];
17
+ if (next !== void 0 && !next.startsWith("--")) {
18
+ flags[key] = next;
19
+ i += 1;
20
+ } else flags[key] = "";
21
+ }
22
+ }
23
+ return flags;
24
+ };
25
+ const tagFromError = (err) => {
26
+ if (err !== null && typeof err === "object" && "_tag" in err && typeof err._tag === "string") return err._tag;
27
+ return "Defect";
28
+ };
29
+ const messageFromError = (err) => {
30
+ if (err instanceof Error) return err.message;
31
+ if (err !== null && typeof err === "object") {
32
+ const e = err;
33
+ if (typeof e.reason === "string") return e.reason;
34
+ if (typeof e.message === "string") return e.message;
35
+ }
36
+ return String(err ?? "unknown error");
37
+ };
38
+ /**
39
+ * Dispatch one argv invocation. `argv` is the post-`node post-bin`
40
+ * slice — i.e. `process.argv.slice(2)`. Never throws: every failure is
41
+ * folded into the returned {@link DispatchResult}.
42
+ * @public
43
+ */
44
+ const dispatch = async (argv) => {
45
+ const subcommand = argv[0];
46
+ const rest = argv.slice(1);
47
+ if (subcommand === "inject-env") {
48
+ const flags = parseFlags(rest);
49
+ const command = flags.command;
50
+ const cwd = flags.cwd ?? process.cwd();
51
+ if (command === void 0) return {
52
+ stdout: "",
53
+ stderr: `${exitCodeForTag("Defect")} Defect: inject-env requires --command\n`,
54
+ code: exitCodeForTag("Defect")
55
+ };
56
+ try {
57
+ return {
58
+ stdout: `${injectEnv({
59
+ command,
60
+ cwd,
61
+ env: process.env
62
+ })}\n`,
63
+ stderr: "",
64
+ code: 0
65
+ };
66
+ } catch (err) {
67
+ const tag = tagFromError(err);
68
+ return {
69
+ stdout: "",
70
+ stderr: `${exitCodeForTag(tag)} ${tag}: ${messageFromError(err)}\n`,
71
+ code: exitCodeForTag(tag)
72
+ };
73
+ }
74
+ }
75
+ const label = subcommand ?? "(none)";
76
+ return {
77
+ stdout: "",
78
+ stderr: `${exitCodeForTag("Defect")} Defect: unknown subcommand: ${label}\n`,
79
+ code: exitCodeForTag("Defect")
80
+ };
81
+ };
82
+
83
+ //#endregion
84
+ export { dispatch };
@@ -0,0 +1,59 @@
1
+ //#region src/sql/assemblers.ts
2
+ /**
3
+ * Builds a `CacheManifest` from an array of test_runs aggregation rows.
4
+ *
5
+ * Each row represents the latest run for a project/sub_project pair.
6
+ * The `dbPath` is stored as `cacheDir` so CLI tools can locate the source.
7
+ * @public
8
+ */
9
+ function assembleManifest(rows, dbPath) {
10
+ return {
11
+ updatedAt: rows.reduce((latest, row) => {
12
+ return row.timestamp > latest ? row.timestamp : latest;
13
+ }, (/* @__PURE__ */ new Date(0)).toISOString()),
14
+ cacheDir: dbPath,
15
+ projects: rows.map((row) => {
16
+ const projectName = row.sub_project ? `${row.project}:${row.sub_project}` : row.project;
17
+ const safeProjectName = projectName.replace(/[^a-z0-9@._-]/gi, "_");
18
+ const reportFile = `sql:reports/${safeProjectName}.json`;
19
+ const historyFile = `sql:history/${safeProjectName}.history.json`;
20
+ const lastResult = (() => {
21
+ if (row.reason === "passed" || row.reason === "failed" || row.reason === "interrupted") return row.reason;
22
+ return null;
23
+ })();
24
+ return {
25
+ project: projectName,
26
+ reportFile,
27
+ historyFile,
28
+ lastRun: row.timestamp,
29
+ lastResult
30
+ };
31
+ })
32
+ };
33
+ }
34
+ /**
35
+ * Groups test_history rows by `full_name` into a keyed record.
36
+ *
37
+ * Returns `Record<fullName, { runs: TestRun[] }>` where `runs` are the
38
+ * accumulated test runs for that test, in the order they appear in `rows`.
39
+ *
40
+ * Only rows with state `"passed"` or `"failed"` are included in runs
41
+ * (matching the `TestRun` schema constraint).
42
+ * @public
43
+ */
44
+ function assembleHistoryRecord(rows) {
45
+ const record = {};
46
+ for (const row of rows) {
47
+ const state = row.state === "passed" || row.state === "failed" ? row.state : null;
48
+ if (state === null) continue;
49
+ if (!record[row.full_name]) record[row.full_name] = { runs: [] };
50
+ record[row.full_name].runs.push({
51
+ timestamp: row.timestamp,
52
+ state
53
+ });
54
+ }
55
+ return record;
56
+ }
57
+
58
+ //#endregion
59
+ export { assembleHistoryRecord, assembleManifest };