@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,117 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/CoverageLevel.ts
4
+ /**
5
+ * A coverage level configuration with per-metric thresholds and an optional
6
+ * perFile flag. Extend this class to define custom named presets.
7
+ * @public
8
+ */
9
+ var CoverageLevel = class CoverageLevel extends Schema.Class("CoverageLevel")({
10
+ lines: Schema.Number,
11
+ functions: Schema.Number,
12
+ branches: Schema.Number,
13
+ statements: Schema.Number,
14
+ perFile: Schema.optional(Schema.Boolean)
15
+ }) {
16
+ /** No enforcement — all metrics at 0%. */
17
+ static none = new CoverageLevel({
18
+ lines: 0,
19
+ functions: 0,
20
+ branches: 0,
21
+ statements: 0
22
+ });
23
+ /** Minimal coverage bar — all metrics at 50%. */
24
+ static basic = new CoverageLevel({
25
+ lines: 50,
26
+ functions: 50,
27
+ branches: 50,
28
+ statements: 50
29
+ });
30
+ /** Recommended default — lines/functions/statements at 70%, branches at 65%. */
31
+ static standard = new CoverageLevel({
32
+ lines: 70,
33
+ functions: 70,
34
+ branches: 65,
35
+ statements: 70
36
+ });
37
+ /** High-quality bar — lines/functions/statements at 80%, branches at 75%. */
38
+ static strict = new CoverageLevel({
39
+ lines: 80,
40
+ functions: 80,
41
+ branches: 75,
42
+ statements: 80
43
+ });
44
+ /** Near-complete coverage — lines/functions/statements at 90%, branches at 85%. */
45
+ static full = new CoverageLevel({
46
+ lines: 90,
47
+ functions: 90,
48
+ branches: 85,
49
+ statements: 90
50
+ });
51
+ /**
52
+ * Returns a new CoverageLevel instance with perFile set to true.
53
+ * Does not mutate the original.
54
+ */
55
+ withPerFile() {
56
+ return new CoverageLevel({
57
+ lines: this.lines,
58
+ functions: this.functions,
59
+ branches: this.branches,
60
+ statements: this.statements,
61
+ perFile: true
62
+ });
63
+ }
64
+ /**
65
+ * Returns a new CoverageLevel instance with the given fields overridden.
66
+ * Does not mutate the original. Existing perFile is preserved unless
67
+ * explicitly overridden.
68
+ */
69
+ extend(overrides) {
70
+ return new CoverageLevel({
71
+ lines: this.lines,
72
+ functions: this.functions,
73
+ branches: this.branches,
74
+ statements: this.statements,
75
+ perFile: this.perFile,
76
+ ...overrides
77
+ });
78
+ }
79
+ };
80
+ const PRESET_MAP = Object.freeze({
81
+ none: CoverageLevel.none,
82
+ basic: CoverageLevel.basic,
83
+ standard: CoverageLevel.standard,
84
+ strict: CoverageLevel.strict,
85
+ full: CoverageLevel.full
86
+ });
87
+ /**
88
+ * Resolves a CoverageInput (preset name or CoverageLevel instance) to a
89
+ * CoverageLevel. When input is undefined, returns the named fallback preset.
90
+ * Throws if a string is provided that does not match a known preset name.
91
+ * @public
92
+ */
93
+ function resolveCoverageInput(input, fallback) {
94
+ if (input === void 0) return PRESET_MAP[fallback];
95
+ if (input instanceof CoverageLevel) return input;
96
+ if (input in PRESET_MAP) return PRESET_MAP[input];
97
+ throw new Error(`[vitest-agent] Unknown coverage level name: "${input}". Valid names are: ${Object.keys(PRESET_MAP).join(", ")}.`);
98
+ }
99
+ /**
100
+ * Validates that coverageTargets are not below coverageThresholds for any
101
+ * metric, and that both agree on perFile. Throws a descriptive Error on
102
+ * any violation.
103
+ * @public
104
+ */
105
+ function validateCoverageConfig(thresholds, targets) {
106
+ for (const metric of [
107
+ "lines",
108
+ "functions",
109
+ "branches",
110
+ "statements"
111
+ ]) if (targets[metric] < thresholds[metric]) throw new Error(`[vitest-agent] coverageTargets.${metric} (${targets[metric]}%) is lower than coverageThresholds.${metric} (${thresholds[metric]}%). Targets must be ≥ thresholds.`);
112
+ if (thresholds.perFile === true && targets.perFile !== true) throw new Error("[vitest-agent] coverageThresholds sets perFile: true but coverageTargets does not. Both must agree on perFile.");
113
+ if (targets.perFile === true && thresholds.perFile !== true) throw new Error("[vitest-agent] coverageTargets sets perFile: true but coverageThresholds does not. Both must agree on perFile.");
114
+ }
115
+
116
+ //#endregion
117
+ export { CoverageLevel, resolveCoverageInput, validateCoverageConfig };
@@ -0,0 +1,39 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/CoverageTargets.ts
4
+ /**
5
+ * Per-metric leaf shape for coverageTargets entries. Allows optional
6
+ * numeric targets per metric and the `100: true` shortcut.
7
+ * @public
8
+ */
9
+ const CoverageTargetsMetrics = Schema.Struct({
10
+ lines: Schema.optional(Schema.Positive),
11
+ functions: Schema.optional(Schema.Positive),
12
+ branches: Schema.optional(Schema.Positive),
13
+ statements: Schema.optional(Schema.Positive),
14
+ 100: Schema.optional(Schema.Literal(true))
15
+ }).annotations({ identifier: "CoverageTargetsMetrics" });
16
+ /**
17
+ * Typed schema for the coverageTargets option.
18
+ *
19
+ * Mirrors Vitest's coverage.thresholds shape — per-metric positive numbers,
20
+ * the `100: true` shortcut (only valid at key `"100"`), and glob-pattern
21
+ * entries with metric objects. Positive numbers only; negatives and zeros
22
+ * are rejected. `perFile` is not allowed here — inherit it from
23
+ * `coverage.thresholds.perFile`.
24
+ *
25
+ * A decode-time refinement rejects `true` at any key other than `"100"`
26
+ * so `{ statements: true }` fails parse rather than silently flowing
27
+ * through to a runtime parser that only honors the canonical shorthand
28
+ * at key `"100"`.
29
+ * @public
30
+ */
31
+ const CoverageTargets = Schema.Record({
32
+ key: Schema.String,
33
+ value: Schema.Union(Schema.Positive, Schema.Literal(true), CoverageTargetsMetrics)
34
+ }).pipe(Schema.filter((targets) => {
35
+ for (const [key, value] of Object.entries(targets)) if (value === true && key !== "100") return `coverageTargets.${key} cannot be true. The "true" shorthand is only valid at key "100" (meaning 100% across all metrics).`;
36
+ }), Schema.annotations({ identifier: "CoverageTargets" }));
37
+
38
+ //#endregion
39
+ export { CoverageTargets, CoverageTargetsMetrics };
@@ -0,0 +1,31 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/History.ts
4
+ /**
5
+ * A single test run outcome (pass or fail only).
6
+ * @public
7
+ */
8
+ const TestRun = Schema.Struct({
9
+ timestamp: Schema.String,
10
+ state: Schema.Literal("passed", "failed")
11
+ }).annotations({ identifier: "TestRun" });
12
+ /**
13
+ * History for a single test across multiple runs.
14
+ * @public
15
+ */
16
+ const TestHistory = Schema.Struct({
17
+ fullName: Schema.String,
18
+ runs: Schema.Array(TestRun)
19
+ }).annotations({ identifier: "TestHistory" });
20
+ /**
21
+ * Per-project history record containing all tracked tests.
22
+ * @public
23
+ */
24
+ const HistoryRecord = Schema.Struct({
25
+ project: Schema.String,
26
+ updatedAt: Schema.String,
27
+ tests: Schema.Array(TestHistory)
28
+ }).annotations({ identifier: "HistoryRecord" });
29
+
30
+ //#endregion
31
+ export { HistoryRecord, TestHistory, TestRun };
@@ -0,0 +1,64 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/Identity.ts
4
+ /**
5
+ * Canonical UUID for an LLM agent invocation. Generated server-side at
6
+ * `register_agent` time. Stable across transport reconnects within an
7
+ * agent's lifetime.
8
+ * @public
9
+ */
10
+ const AgentId = Schema.UUID.pipe(Schema.brand("AgentId"));
11
+ /**
12
+ * The unit "all work on this feature" rolls up to. Survives
13
+ * `claude --resume` and parallel windows; lives in the host's
14
+ * transcript filename for Claude Code.
15
+ * @public
16
+ */
17
+ const ConversationId = Schema.UUID.pipe(Schema.brand("ConversationId"));
18
+ /**
19
+ * The agent host process's chat id (Claude Code's per-process chat
20
+ * UUID, etc.). Shorter-lived than a conversation; one conversation can
21
+ * have multiple chats across `--resume` invocations. Distinct from
22
+ * `sessions.id` (the SQLite agent-run PK).
23
+ * @public
24
+ */
25
+ const ChatId = Schema.UUID.pipe(Schema.brand("ChatId"));
26
+ /**
27
+ * One TDD orchestration task. Replaces the legacy `tdd_session_id`
28
+ * vocabulary so "session" is unambiguous in the codebase.
29
+ * @public
30
+ */
31
+ const TddTaskId = Schema.UUID.pipe(Schema.brand("TddTaskId"));
32
+ /**
33
+ * Filesystem-safe key for the per-project data store directory under
34
+ * `$XDG_DATA_HOME/vitest-agent/<projectKey>/`. Resolved by the
35
+ * `ProjectIdentity` service from the explicit option / TOML config /
36
+ * git remote / package.json fallback chain. Always non-empty (a missing
37
+ * value is `ProjectIdentityNotResolvableError`, not an empty string).
38
+ * @public
39
+ */
40
+ const ProjectKey = Schema.String.pipe(Schema.minLength(1), Schema.brand("ProjectKey"));
41
+ /**
42
+ * Who initiated the action (test run, hypothesis, note, TDD phase, …).
43
+ * Recorded on every action row alongside `agent_id`.
44
+ *
45
+ * - `agent` — an LLM agent (`agent_id` is set, `conversation_id` is set)
46
+ * - `user` — a human at a terminal (`agent_id` and `conversation_id` are NULL)
47
+ * - `system` — a CI run, scheduler, or other non-human/non-agent actor
48
+ * @public
49
+ */
50
+ const ActorType = Schema.Literal("agent", "user", "system").annotations({ identifier: "ActorType" });
51
+ /**
52
+ * Recommended canonical host identifiers. Open enum at the SDK level so
53
+ * a new client doesn't require an SDK release; the MCP server defaults
54
+ * `agentType` validation against the `${hostKind}-` prefix using this
55
+ * value.
56
+ *
57
+ * `unknown` is the explicit fallback when `clientInfo.name` doesn't
58
+ * match a known pattern — preferred over guessing.
59
+ * @public
60
+ */
61
+ const HostKind = Schema.Literal("claude-code", "claude-desktop", "cursor", "goose", "chatgpt", "mcp-inspector", "unknown").annotations({ identifier: "HostKind" });
62
+
63
+ //#endregion
64
+ export { ActorType, AgentId, ChatId, ConversationId, HostKind, ProjectKey, TddTaskId };
@@ -0,0 +1,57 @@
1
+ import { AgentConsoleMode, CiConsoleMode, HumanConsoleMode } from "./Common.js";
2
+ import { CoverageTargets } from "./CoverageTargets.js";
3
+ import { Transport } from "./Transport.js";
4
+ import { Schema } from "effect";
5
+
6
+ //#region src/schemas/Options.ts
7
+ /**
8
+ * Per-executor console-output matrix. Most users never set this — the
9
+ * built-in defaults cover the common cases. Override per slot when you
10
+ * want to force a specific layout for debugging or to suppress one
11
+ * channel without affecting the others.
12
+ *
13
+ * Defaults:
14
+ * - `human` → `"passthrough"` today; users opt into `"stream"` explicitly
15
+ * for the progressively-drawn, animated agent-shaped live renderer.
16
+ * - `agent` → `"agent"` (markdown-flavored final frame).
17
+ * - `ci` → `"passthrough"` (Vitest's reporters produce log-friendly
18
+ * output). `"ci-annotations"` is opt-in until the dedicated GHA
19
+ * annotations writer ships.
20
+ * @public
21
+ */
22
+ const ConsoleOutputs = Schema.Struct({
23
+ human: Schema.optional(HumanConsoleMode),
24
+ agent: Schema.optional(AgentConsoleMode),
25
+ ci: Schema.optional(CiConsoleMode)
26
+ }).annotations({ identifier: "ConsoleOutputs" });
27
+ /**
28
+ * The 2.0 user-facing options for `AgentPlugin`.
29
+ *
30
+ * Five fields total. Two are function-typed (`reporter`, `onRunEvent`)
31
+ * and live on the `AgentPluginConstructorOptions` companion interface in
32
+ * `@vitest-agent/plugin` — Effect Schema cannot encode functions cleanly,
33
+ * so the schema-decodable struct carries the three data-shaped fields.
34
+ * @public
35
+ */
36
+ const AgentPluginOptions = Schema.Struct({
37
+ console: Schema.optional(ConsoleOutputs),
38
+ coverageTargets: Schema.optional(CoverageTargets),
39
+ transport: Schema.optional(Transport)
40
+ }).annotations({ identifier: "AgentPluginOptions" });
41
+ /**
42
+ * Reporter-implementation options.
43
+ *
44
+ * Public for users building custom reporters via `@vitest-agent/reporter`;
45
+ * most users never see this type — they wire `AgentPlugin({ reporter })`
46
+ * and the factory receives a fully-resolved `ReporterKit` instead.
47
+ *
48
+ * Carries internal flags the plugin sets when constructing the reporter
49
+ * (currently only `projectFilter` for per-project scoping).
50
+ * @public
51
+ */
52
+ const AgentReporterOptions = Schema.Struct({
53
+ /** Set by the plugin per-project. Filters reports to one project. */
54
+ projectFilter: Schema.optional(Schema.String) }).annotations({ identifier: "AgentReporterOptions" });
55
+
56
+ //#endregion
57
+ export { AgentPluginOptions, AgentReporterOptions, ConsoleOutputs };
@@ -0,0 +1,161 @@
1
+ import { ReportError, TestClassification, TestState } from "./Common.js";
2
+ import { MetricThresholds } from "./Thresholds.js";
3
+ import { CoverageTotals } from "./Coverage.js";
4
+ import { ActionSeverity, CoverageGap, CoverageMetric } from "./RunEvent.js";
5
+ import { Schema } from "effect";
6
+
7
+ //#region src/schemas/RenderState.ts
8
+ /**
9
+ * Per-test record accumulated as `TestStarted` / `TestFinished` events arrive.
10
+ * @public
11
+ */
12
+ const TestRecord = Schema.Struct({
13
+ testName: Schema.String,
14
+ suitePath: Schema.Array(Schema.String),
15
+ status: Schema.Union(TestState, Schema.Literal("running", "timed-out")),
16
+ durationMs: Schema.NullOr(Schema.Number),
17
+ error: Schema.optional(ReportError)
18
+ }).annotations({ identifier: "TestRecord" });
19
+ /**
20
+ * Per-module aggregation. The reducer keeps modules keyed on
21
+ * `modulePath` so out-of-order events can update the right slot.
22
+ * @public
23
+ */
24
+ const ModuleRecord = Schema.Struct({
25
+ modulePath: Schema.String,
26
+ status: Schema.Literal("queued", "running", "finished"),
27
+ passCount: Schema.Number,
28
+ failCount: Schema.Number,
29
+ skipCount: Schema.Number,
30
+ timeoutCount: Schema.Number,
31
+ durationMs: Schema.Number,
32
+ tests: Schema.Array(TestRecord),
33
+ /**
34
+ * Owning Vitest project name. Optional — events from project-less
35
+ * configs or older replays leave it `undefined`, which the renderer
36
+ * treats as a single anonymous project.
37
+ */
38
+ projectName: Schema.optional(Schema.String),
39
+ /**
40
+ * ISO wall-clock stamp captured at `onTestModuleStart`. The live
41
+ * renderer derives a ticking elapsed column from it while the module
42
+ * runs; once finished it shows `durationMs` instead. Optional — a
43
+ * module seeded by `ModuleQueued` (or replayed) has no start stamp.
44
+ */
45
+ startedAt: Schema.optional(Schema.String),
46
+ tagCounts: Schema.optional(Schema.Record({
47
+ key: Schema.String,
48
+ value: Schema.Number
49
+ }))
50
+ }).annotations({ identifier: "ModuleRecord" });
51
+ /**
52
+ * One row in the failure list. A failure can exist before its
53
+ * classification arrives (`FailureClassified` is emitted from a
54
+ * separate pipeline), so `classification` is nullable.
55
+ * @public
56
+ */
57
+ const FailureRecord = Schema.Struct({
58
+ modulePath: Schema.String,
59
+ testName: Schema.String,
60
+ suitePath: Schema.Array(Schema.String),
61
+ error: Schema.optional(ReportError),
62
+ timedOut: Schema.optional(Schema.Boolean),
63
+ classification: Schema.NullOr(TestClassification)
64
+ }).annotations({ identifier: "FailureRecord" });
65
+ /**
66
+ * Coverage block, populated only after `CoverageReady`. Threshold
67
+ * violations are accumulated separately as they arrive.
68
+ * @public
69
+ */
70
+ const CoverageRenderState = Schema.Struct({
71
+ metrics: CoverageTotals,
72
+ thresholds: MetricThresholds,
73
+ gaps: Schema.Array(CoverageGap),
74
+ violations: Schema.Array(Schema.Struct({
75
+ metric: CoverageMetric,
76
+ expected: Schema.Number,
77
+ actual: Schema.Number
78
+ }))
79
+ }).annotations({ identifier: "CoverageRenderState" });
80
+ /**
81
+ * A `SuggestedAction` event, denormalized into the queue the renderer reads.
82
+ * @public
83
+ */
84
+ const SuggestedActionRecord = Schema.Struct({
85
+ severity: ActionSeverity,
86
+ title: Schema.String,
87
+ detail: Schema.String,
88
+ targetTool: Schema.optional(Schema.String)
89
+ }).annotations({ identifier: "SuggestedActionRecord" });
90
+ /**
91
+ * Per-run totals, recomputed on every `ModuleFinished` and reconciled
92
+ * against `RunFinished` (which carries the runner's own count).
93
+ * @public
94
+ */
95
+ const RenderTotals = Schema.Struct({
96
+ passCount: Schema.Number,
97
+ failCount: Schema.Number,
98
+ skipCount: Schema.Number,
99
+ timeoutCount: Schema.Number,
100
+ durationMs: Schema.Number
101
+ }).annotations({ identifier: "RenderTotals" });
102
+ /**
103
+ * The renderer state. Both the human (Ink) and agent (string)
104
+ * renderers read this shape; the reducer is the only producer.
105
+ *
106
+ * `phase` is the layout discriminator — agent mode emits one final
107
+ * frame when `phase === "finished"`; human mode redraws at every
108
+ * state change. `"timed-out"` is a terminal phase like `"finished"`
109
+ * — the run is over, but it ended because `onProcessTimeout` fired
110
+ * rather than the suite completing.
111
+ * @public
112
+ */
113
+ const RenderState = Schema.Struct({
114
+ phase: Schema.Literal("idle", "running", "finished", "timed-out"),
115
+ runId: Schema.NullOr(Schema.String),
116
+ configHash: Schema.NullOr(Schema.String),
117
+ startedAt: Schema.NullOr(Schema.String),
118
+ finishedAt: Schema.NullOr(Schema.String),
119
+ modules: Schema.Record({
120
+ key: Schema.String,
121
+ value: ModuleRecord
122
+ }),
123
+ moduleOrder: Schema.Array(Schema.String),
124
+ totals: RenderTotals,
125
+ coverage: Schema.NullOr(CoverageRenderState),
126
+ trend: Schema.NullOr(Schema.Struct({
127
+ direction: Schema.Literal("improving", "regressing", "stable"),
128
+ runCount: Schema.Number
129
+ })),
130
+ failures: Schema.Array(FailureRecord),
131
+ suggestedActions: Schema.Array(SuggestedActionRecord)
132
+ }).annotations({ identifier: "RenderState" });
133
+ /**
134
+ * Initial state — what the reducer returns when no events have been
135
+ * applied yet. Exported so tests and the renderers' bootstrapping
136
+ * code share one definition.
137
+ * @public
138
+ */
139
+ const initialRenderState = {
140
+ phase: "idle",
141
+ runId: null,
142
+ configHash: null,
143
+ startedAt: null,
144
+ finishedAt: null,
145
+ modules: {},
146
+ moduleOrder: [],
147
+ totals: {
148
+ passCount: 0,
149
+ failCount: 0,
150
+ skipCount: 0,
151
+ timeoutCount: 0,
152
+ durationMs: 0
153
+ },
154
+ coverage: null,
155
+ trend: null,
156
+ failures: [],
157
+ suggestedActions: []
158
+ };
159
+
160
+ //#endregion
161
+ export { CoverageRenderState, FailureRecord, ModuleRecord, RenderState, RenderTotals, SuggestedActionRecord, TestRecord, initialRenderState };
@@ -0,0 +1,163 @@
1
+ import { ReportError, TestClassification, TestState } from "./Common.js";
2
+ import { MetricThresholds } from "./Thresholds.js";
3
+ import { CoverageTotals, FileCoverageReport } from "./Coverage.js";
4
+ import { Schema } from "effect";
5
+
6
+ //#region src/schemas/RunEvent.ts
7
+ /**
8
+ * Coverage metric whose threshold can be violated independently.
9
+ * @public
10
+ */
11
+ const CoverageMetric = Schema.Literal("lines", "branches", "functions", "statements").annotations({ identifier: "CoverageMetric" });
12
+ /**
13
+ * Severity tier for a suggested action emitted during a run.
14
+ * @public
15
+ */
16
+ const ActionSeverity = Schema.Literal("info", "warn", "blocker").annotations({ identifier: "ActionSeverity" });
17
+ /**
18
+ * Lifecycle scope of a Vitest setup/teardown hook.
19
+ * @public
20
+ */
21
+ const HookType = Schema.Literal("beforeAll", "afterAll", "beforeEach", "afterEach").annotations({ identifier: "HookType" });
22
+ /**
23
+ * Terminal status of a Vitest hook run.
24
+ * @public
25
+ */
26
+ const HookStatus = Schema.Literal("passed", "failed").annotations({ identifier: "HookStatus" });
27
+ /**
28
+ * Output stream a captured user `console.log` was written to.
29
+ * @public
30
+ */
31
+ const ConsoleLogLevel = Schema.Literal("stdout", "stderr").annotations({ identifier: "ConsoleLogLevel" });
32
+ /**
33
+ * A coverage gap surfaced to the renderer — a per-file shortfall that
34
+ * a renderer may want to call out individually.
35
+ * @public
36
+ */
37
+ const CoverageGap = Schema.Struct({
38
+ file: Schema.String,
39
+ missing: CoverageTotals,
40
+ uncoveredLines: Schema.optional(Schema.String)
41
+ }).annotations({ identifier: "CoverageGap" });
42
+ /**
43
+ * The reducer projects this union into `RenderState`. Renderers
44
+ * never read the raw event stream — they read the projected state.
45
+ *
46
+ * @remarks
47
+ * Adding a variant here is one change in one place; the reducer's
48
+ * exhaustive `Match.exhaustive` will surface any renderer that hasn't
49
+ * been updated to consume the new state shape.
50
+ * @public
51
+ */
52
+ const RunEvent = Schema.Union(Schema.TaggedStruct("RunStarted", {
53
+ runId: Schema.String,
54
+ startedAt: Schema.String,
55
+ configHash: Schema.String
56
+ }), Schema.TaggedStruct("ModuleQueued", {
57
+ modulePath: Schema.String,
58
+ projectName: Schema.optional(Schema.String)
59
+ }), Schema.TaggedStruct("ModuleStarted", {
60
+ modulePath: Schema.String,
61
+ startedAt: Schema.String,
62
+ projectName: Schema.optional(Schema.String)
63
+ }), Schema.TaggedStruct("TestStarted", {
64
+ modulePath: Schema.String,
65
+ testName: Schema.String,
66
+ suitePath: Schema.Array(Schema.String)
67
+ }), Schema.TaggedStruct("TestFinished", {
68
+ modulePath: Schema.String,
69
+ testName: Schema.String,
70
+ suitePath: Schema.Array(Schema.String),
71
+ status: TestState,
72
+ durationMs: Schema.Number,
73
+ error: Schema.optional(ReportError),
74
+ timedOut: Schema.optional(Schema.Boolean)
75
+ }), Schema.TaggedStruct("ModuleFinished", {
76
+ modulePath: Schema.String,
77
+ passCount: Schema.Number,
78
+ failCount: Schema.Number,
79
+ skipCount: Schema.Number,
80
+ durationMs: Schema.Number,
81
+ projectName: Schema.optional(Schema.String),
82
+ timeoutCount: Schema.optional(Schema.Number),
83
+ tagCounts: Schema.optional(Schema.Record({
84
+ key: Schema.String,
85
+ value: Schema.Number
86
+ }))
87
+ }), Schema.TaggedStruct("ModuleCollected", {
88
+ modulePath: Schema.String,
89
+ testCount: Schema.Number,
90
+ suiteCount: Schema.Number
91
+ }), Schema.TaggedStruct("SuiteStarted", {
92
+ modulePath: Schema.String,
93
+ suitePath: Schema.Array(Schema.String),
94
+ suiteName: Schema.String
95
+ }), Schema.TaggedStruct("SuiteFinished", {
96
+ modulePath: Schema.String,
97
+ suitePath: Schema.Array(Schema.String),
98
+ suiteName: Schema.String,
99
+ passCount: Schema.Number,
100
+ failCount: Schema.Number,
101
+ skipCount: Schema.Number
102
+ }), Schema.TaggedStruct("HookStarted", {
103
+ modulePath: Schema.String,
104
+ hookType: HookType,
105
+ scopeName: Schema.String
106
+ }), Schema.TaggedStruct("HookFinished", {
107
+ modulePath: Schema.String,
108
+ hookType: HookType,
109
+ scopeName: Schema.String,
110
+ durationMs: Schema.Number,
111
+ status: HookStatus,
112
+ error: Schema.optional(ReportError)
113
+ }), Schema.TaggedStruct("ConsoleLog", {
114
+ modulePath: Schema.optional(Schema.String),
115
+ testName: Schema.optional(Schema.String),
116
+ level: ConsoleLogLevel,
117
+ content: Schema.String,
118
+ time: Schema.Number
119
+ }), Schema.TaggedStruct("RunTimedOut", { message: Schema.String }), Schema.TaggedStruct("TestAnnotated", {
120
+ modulePath: Schema.String,
121
+ testName: Schema.String,
122
+ suitePath: Schema.Array(Schema.String),
123
+ annotation: Schema.String
124
+ }), Schema.TaggedStruct("TestArtifactRecorded", {
125
+ modulePath: Schema.String,
126
+ testName: Schema.String,
127
+ suitePath: Schema.Array(Schema.String),
128
+ artifact: Schema.String
129
+ }), Schema.TaggedStruct("WatcherReady", {}), Schema.TaggedStruct("WatcherRerun", {
130
+ triggerFiles: Schema.Array(Schema.String),
131
+ reason: Schema.optional(Schema.String)
132
+ }), Schema.TaggedStruct("TrendComputed", {
133
+ direction: Schema.Literal("improving", "regressing", "stable"),
134
+ runCount: Schema.Number
135
+ }), Schema.TaggedStruct("CoverageReady", {
136
+ metrics: CoverageTotals,
137
+ thresholds: MetricThresholds,
138
+ gaps: Schema.Array(CoverageGap)
139
+ }), Schema.TaggedStruct("ThresholdViolation", {
140
+ metric: CoverageMetric,
141
+ expected: Schema.Number,
142
+ actual: Schema.Number
143
+ }), Schema.TaggedStruct("FailureClassified", {
144
+ modulePath: Schema.String,
145
+ testName: Schema.String,
146
+ classification: TestClassification
147
+ }), Schema.TaggedStruct("SuggestedAction", {
148
+ severity: ActionSeverity,
149
+ title: Schema.String,
150
+ detail: Schema.String,
151
+ targetTool: Schema.optional(Schema.String)
152
+ }), Schema.TaggedStruct("RunFinished", {
153
+ runId: Schema.String,
154
+ finishedAt: Schema.String,
155
+ passCount: Schema.Number,
156
+ failCount: Schema.Number,
157
+ skipCount: Schema.Number,
158
+ durationMs: Schema.Number,
159
+ timeoutCount: Schema.optional(Schema.Number)
160
+ })).annotations({ identifier: "RunEvent" });
161
+
162
+ //#endregion
163
+ export { ActionSeverity, ConsoleLogLevel, CoverageGap, CoverageMetric, HookStatus, HookType, RunEvent };
package/schemas/Tdd.js ADDED
@@ -0,0 +1,44 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/schemas/Tdd.ts
4
+ /** @public */
5
+ const GoalStatus = Schema.Literal("pending", "in_progress", "done", "abandoned").annotations({ identifier: "GoalStatus" });
6
+ /** @public */
7
+ const BehaviorStatus = Schema.Literal("pending", "in_progress", "done", "abandoned").annotations({ identifier: "BehaviorStatus" });
8
+ /** @public */
9
+ const GoalRow = Schema.Struct({
10
+ id: Schema.Number,
11
+ sessionId: Schema.Number,
12
+ ordinal: Schema.Number,
13
+ goal: Schema.String,
14
+ status: GoalStatus,
15
+ createdAt: Schema.String
16
+ }).annotations({ identifier: "GoalRow" });
17
+ /** @public */
18
+ const BehaviorRow = Schema.Struct({
19
+ id: Schema.Number,
20
+ goalId: Schema.Number,
21
+ ordinal: Schema.Number,
22
+ behavior: Schema.String,
23
+ suggestedTestName: Schema.NullOr(Schema.String),
24
+ status: BehaviorStatus,
25
+ createdAt: Schema.String
26
+ }).annotations({ identifier: "BehaviorRow" });
27
+ /** @public */
28
+ const GoalDetail = Schema.Struct({
29
+ ...GoalRow.fields,
30
+ behaviors: Schema.Array(BehaviorRow)
31
+ }).annotations({ identifier: "GoalDetail" });
32
+ /** @public */
33
+ const BehaviorDetail = Schema.Struct({
34
+ ...BehaviorRow.fields,
35
+ parentGoal: Schema.Struct({
36
+ id: Schema.Number,
37
+ goal: Schema.String,
38
+ status: GoalStatus
39
+ }),
40
+ dependencies: Schema.Array(BehaviorRow)
41
+ }).annotations({ identifier: "BehaviorDetail" });
42
+
43
+ //#endregion
44
+ export { BehaviorDetail, BehaviorRow, BehaviorStatus, GoalDetail, GoalRow, GoalStatus };