@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.
- package/DataReader-2kJ9I9Hh.d.ts +1809 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dispatch.d.ts +48 -0
- package/dispatch.js +5 -0
- package/errors/AgentErrors.js +65 -0
- package/errors/DataStoreError.js +44 -0
- package/errors/DiscoveryError.js +21 -0
- package/errors/PathResolutionError.js +27 -0
- package/errors/ProjectIdentityError.js +29 -0
- package/errors/RunContextError.js +35 -0
- package/errors/TddErrors.js +66 -0
- package/exit-code-for-tag.js +18 -0
- package/formatters/ci-annotations.js +61 -0
- package/formatters/gfm.js +20 -0
- package/formatters/json.js +18 -0
- package/formatters/markdown.js +39 -0
- package/formatters/silent.js +9 -0
- package/formatters/terminal.js +56 -0
- package/index.d.ts +3334 -0
- package/index.js +120 -0
- package/internal-inject-env.js +42 -0
- package/layers/ConfigLive.js +47 -0
- package/layers/DataReaderLive.js +1666 -0
- package/layers/DataStoreLive.js +1185 -0
- package/layers/DetailResolverLive.js +9 -0
- package/layers/DiscoveryRegistryLive.js +70 -0
- package/layers/EnvironmentDetectorLive.js +21 -0
- package/layers/EnvironmentDetectorTest.js +13 -0
- package/layers/ExecutorResolverLive.js +9 -0
- package/layers/FormatSelectorLive.js +20 -0
- package/layers/HistoryTrackerLive.js +47 -0
- package/layers/HistoryTrackerTest.js +25 -0
- package/layers/LoggerLive.js +64 -0
- package/layers/OutputPipelineLive.js +13 -0
- package/layers/OutputRendererLive.js +28 -0
- package/layers/PathResolutionLive.js +36 -0
- package/layers/PerClientSessionMapLive.js +145 -0
- package/layers/ProjectDiscoveryLive.js +70 -0
- package/layers/ProjectDiscoveryTest.js +12 -0
- package/layers/ProjectIdentityLive.js +92 -0
- package/layers/RunContextLive.js +81 -0
- package/lib/format-triage.js +103 -0
- package/lib/format-wrapup.js +67 -0
- package/migrations/0001_initial.js +849 -0
- package/migrations/registry_0001_initial.js +39 -0
- package/migrations/session_map_0001_initial.js +61 -0
- package/package.json +64 -0
- package/schemas/Agent.js +42 -0
- package/schemas/AgentReport.js +71 -0
- package/schemas/Baselines.js +16 -0
- package/schemas/CacheManifest.js +27 -0
- package/schemas/ChannelEvent.js +138 -0
- package/schemas/Common.js +105 -0
- package/schemas/Config.js +28 -0
- package/schemas/Coverage.js +51 -0
- package/schemas/CoverageLevel.js +117 -0
- package/schemas/CoverageTargets.js +39 -0
- package/schemas/History.js +31 -0
- package/schemas/Identity.js +64 -0
- package/schemas/Options.js +57 -0
- package/schemas/RenderState.js +161 -0
- package/schemas/RunEvent.js +163 -0
- package/schemas/Tdd.js +44 -0
- package/schemas/Thresholds.js +30 -0
- package/schemas/Transport.js +16 -0
- package/schemas/Trends.js +23 -0
- package/schemas/turns/FileEditPayload.js +15 -0
- package/schemas/turns/HookFirePayload.js +13 -0
- package/schemas/turns/HypothesisPayload.js +13 -0
- package/schemas/turns/NotePayload.js +13 -0
- package/schemas/turns/ToolCallPayload.js +13 -0
- package/schemas/turns/ToolResultPayload.js +15 -0
- package/schemas/turns/UserPromptPayload.js +12 -0
- package/schemas/turns/index.js +15 -0
- package/services/Config.js +15 -0
- package/services/DataReader.js +8 -0
- package/services/DataStore.js +8 -0
- package/services/DetailResolver.js +8 -0
- package/services/DiscoveryRegistry.js +8 -0
- package/services/EnvironmentDetector.js +8 -0
- package/services/ExecutorResolver.js +8 -0
- package/services/FormatSelector.js +8 -0
- package/services/HistoryTracker.js +8 -0
- package/services/OutputRenderer.js +8 -0
- package/services/PerClientSessionMap.js +22 -0
- package/services/ProjectDiscovery.js +8 -0
- package/services/ProjectIdentity.js +63 -0
- package/services/RunContext.js +53 -0
- package/services/idempotency.js +57 -0
- package/sidecar-dispatch.js +84 -0
- package/sql/assemblers.js +59 -0
- package/testing/layers.js +21 -0
- package/testing.d.ts +25 -0
- package/testing.js +243 -0
- package/tsdoc-metadata.json +11 -0
- package/utils/ansi.js +49 -0
- package/utils/build-report.js +130 -0
- package/utils/canonicalize-git-url.js +66 -0
- package/utils/classify-test.js +22 -0
- package/utils/compress-lines.js +45 -0
- package/utils/compute-trend.js +88 -0
- package/utils/detect-pm.js +58 -0
- package/utils/detect-timeout.js +17 -0
- package/utils/ensure-migrated.js +52 -0
- package/utils/failure-signature.js +33 -0
- package/utils/format-console.js +219 -0
- package/utils/format-fatal-error.js +35 -0
- package/utils/format-gfm.js +161 -0
- package/utils/format-terminal.js +480 -0
- package/utils/function-boundary.js +75 -0
- package/utils/hyperlink.js +11 -0
- package/utils/match-vitest-command.js +88 -0
- package/utils/normalize-workspace-key.js +34 -0
- package/utils/probe-host-metadata.js +99 -0
- package/utils/resolve-data-path.js +66 -0
- package/utils/resolve-project-key-from-cwd.js +52 -0
- package/utils/resolve-workspace-key.js +33 -0
- package/utils/safe-filename.js +34 -0
- package/utils/validate-coverage-targets-shape.js +84 -0
- 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 };
|