@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,21 @@
|
|
|
1
|
+
import { DataReaderLive } from "../layers/DataReaderLive.js";
|
|
2
|
+
import { DataStoreLive } from "../layers/DataStoreLive.js";
|
|
3
|
+
import migration from "../migrations/0001_initial.js";
|
|
4
|
+
import { Layer } from "effect";
|
|
5
|
+
import * as NodeContext from "@effect/platform-node/NodeContext";
|
|
6
|
+
import { layer } from "@effect/sql-sqlite-node/SqliteClient";
|
|
7
|
+
import * as SqliteMigrator from "@effect/sql-sqlite-node/SqliteMigrator";
|
|
8
|
+
|
|
9
|
+
//#region src/testing/layers.ts
|
|
10
|
+
/** @public */
|
|
11
|
+
function makeTestLayer(filename) {
|
|
12
|
+
const SqliteLayer = layer({ filename });
|
|
13
|
+
const PlatformLayer = NodeContext.layer;
|
|
14
|
+
const MigratorLayer = SqliteMigrator.layer({ loader: SqliteMigrator.fromRecord({ "0001_initial": migration }) }).pipe(Layer.provide(Layer.merge(SqliteLayer, PlatformLayer)));
|
|
15
|
+
return Layer.mergeAll(DataStoreLive.pipe(Layer.provide(SqliteLayer)), DataReaderLive.pipe(Layer.provide(SqliteLayer)), MigratorLayer, SqliteLayer, PlatformLayer);
|
|
16
|
+
}
|
|
17
|
+
/** @public */
|
|
18
|
+
const DataStoreTestLayer = makeTestLayer(":memory:");
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { DataStoreTestLayer, makeTestLayer };
|
package/testing.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Ft as IllegalStatusTransitionError, It as TddTaskAlreadyEndedError, Lt as TddTaskEndOutcome, Mt as BehaviorNotFoundError, Nt as GoalNotFoundError, Pt as IllegalStatusTransitionEntity, Rt as TddTaskNotFoundError, a as DataReader, j as DataStore, zt as DataStoreError } from "./DataReader-2kJ9I9Hh.js";
|
|
2
|
+
import { Layer } from "effect";
|
|
3
|
+
import * as NodeContext from "@effect/platform-node/NodeContext";
|
|
4
|
+
import * as SqliteMigrator from "@effect/sql-sqlite-node/SqliteMigrator";
|
|
5
|
+
|
|
6
|
+
//#region src/testing/layers.d.ts
|
|
7
|
+
/** @public */
|
|
8
|
+
declare function makeTestLayer(filename: string): Layer.Layer<DataStore | import("@effect/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | DataReader | NodeContext.NodeContext, import("effect/ConfigError").ConfigError | import("@effect/sql/SqlError").SqlError | SqliteMigrator.MigrationError, never>;
|
|
9
|
+
/** @public */
|
|
10
|
+
declare const DataStoreTestLayer: Layer.Layer<DataStore | import("@effect/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | DataReader | NodeContext.NodeContext, import("effect/ConfigError").ConfigError | import("@effect/sql/SqlError").SqlError | SqliteMigrator.MigrationError, never>;
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/testing/index.d.ts
|
|
13
|
+
/** @public */
|
|
14
|
+
declare function empty(filename: string): Layer.Layer<DataStore | import("@effect/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | DataReader | import("@effect/platform-node/NodeContext").NodeContext, import("effect/ConfigError").ConfigError | import("@effect/sql/SqlError").SqlError | import("@effect/sql/Migrator").MigrationError, never>;
|
|
15
|
+
/** @public */
|
|
16
|
+
declare function singlePassingRun(filename: string): Layer.Layer<DataStore | import("@effect/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | DataReader | import("@effect/platform-node/NodeContext").NodeContext, import("effect/ConfigError").ConfigError | import("@effect/sql/SqlError").SqlError | import("@effect/sql/Migrator").MigrationError | DataStoreError, never>;
|
|
17
|
+
/** @public */
|
|
18
|
+
declare function withFailures(filename: string): Layer.Layer<DataStore | import("@effect/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | DataReader | import("@effect/platform-node/NodeContext").NodeContext, import("effect/ConfigError").ConfigError | import("@effect/sql/SqlError").SqlError | import("@effect/sql/Migrator").MigrationError | DataStoreError, never>;
|
|
19
|
+
/** @public */
|
|
20
|
+
declare function flaky(filename: string): Layer.Layer<DataStore | import("@effect/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | DataReader | import("@effect/platform-node/NodeContext").NodeContext, import("effect/ConfigError").ConfigError | import("@effect/sql/SqlError").SqlError | import("@effect/sql/Migrator").MigrationError | DataStoreError, never>;
|
|
21
|
+
/** @public */
|
|
22
|
+
declare function withTddTask(filename: string): Layer.Layer<DataStore | import("@effect/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | DataReader | import("@effect/platform-node/NodeContext").NodeContext, import("effect/ConfigError").ConfigError | import("@effect/sql/SqlError").SqlError | import("@effect/sql/Migrator").MigrationError | DataStoreError | TddTaskNotFoundError | TddTaskAlreadyEndedError | GoalNotFoundError | IllegalStatusTransitionError | BehaviorNotFoundError, never>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { BehaviorNotFoundError, DataReader, DataStore, DataStoreError, DataStoreTestLayer, GoalNotFoundError, type IllegalStatusTransitionEntity, IllegalStatusTransitionError, TddTaskAlreadyEndedError, type TddTaskEndOutcome, TddTaskNotFoundError, empty, flaky, makeTestLayer, singlePassingRun, withFailures, withTddTask };
|
|
25
|
+
//# sourceMappingURL=testing.d.ts.map
|
package/testing.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { DataStoreError } from "./errors/DataStoreError.js";
|
|
2
|
+
import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "./errors/TddErrors.js";
|
|
3
|
+
import { DataReader } from "./services/DataReader.js";
|
|
4
|
+
import { DataStore } from "./services/DataStore.js";
|
|
5
|
+
import { DataStoreTestLayer, makeTestLayer } from "./testing/layers.js";
|
|
6
|
+
import { Effect, Layer } from "effect";
|
|
7
|
+
|
|
8
|
+
//#region src/testing/index.ts
|
|
9
|
+
/** @public */
|
|
10
|
+
function empty(filename) {
|
|
11
|
+
return makeTestLayer(filename);
|
|
12
|
+
}
|
|
13
|
+
/** @public */
|
|
14
|
+
function singlePassingRun(filename) {
|
|
15
|
+
const base = makeTestLayer(filename);
|
|
16
|
+
const seed = Effect.gen(function* () {
|
|
17
|
+
const store = yield* DataStore;
|
|
18
|
+
yield* store.writeSettings("hash-preset-spr", {
|
|
19
|
+
vitestVersion: "4.1.5",
|
|
20
|
+
pool: "forks"
|
|
21
|
+
}, {});
|
|
22
|
+
const runId = yield* store.writeRun({
|
|
23
|
+
invocationId: "inv-preset-spr-1",
|
|
24
|
+
project: "default",
|
|
25
|
+
settingsHash: "hash-preset-spr",
|
|
26
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
27
|
+
commitSha: null,
|
|
28
|
+
branch: null,
|
|
29
|
+
reason: "passed",
|
|
30
|
+
duration: 1200,
|
|
31
|
+
total: 3,
|
|
32
|
+
passed: 3,
|
|
33
|
+
failed: 0,
|
|
34
|
+
skipped: 0,
|
|
35
|
+
scoped: false
|
|
36
|
+
});
|
|
37
|
+
const fileId = yield* store.ensureFile("src/math.test.ts");
|
|
38
|
+
const [moduleId] = yield* store.writeModules(runId, [{
|
|
39
|
+
fileId,
|
|
40
|
+
relativeModuleId: "src/math.test.ts",
|
|
41
|
+
state: "passed",
|
|
42
|
+
duration: 1200
|
|
43
|
+
}]);
|
|
44
|
+
yield* store.writeTestCases(moduleId, [
|
|
45
|
+
{
|
|
46
|
+
name: "adds two numbers",
|
|
47
|
+
fullName: "math > adds two numbers",
|
|
48
|
+
state: "passed",
|
|
49
|
+
duration: 400
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "subtracts two numbers",
|
|
53
|
+
fullName: "math > subtracts two numbers",
|
|
54
|
+
state: "passed",
|
|
55
|
+
duration: 400
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "multiplies two numbers",
|
|
59
|
+
fullName: "math > multiplies two numbers",
|
|
60
|
+
state: "passed",
|
|
61
|
+
duration: 400
|
|
62
|
+
}
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
66
|
+
}
|
|
67
|
+
/** @public */
|
|
68
|
+
function withFailures(filename) {
|
|
69
|
+
const base = makeTestLayer(filename);
|
|
70
|
+
const seed = Effect.gen(function* () {
|
|
71
|
+
const store = yield* DataStore;
|
|
72
|
+
yield* store.writeSettings("hash-preset-wf", {
|
|
73
|
+
vitestVersion: "4.1.5",
|
|
74
|
+
pool: "forks"
|
|
75
|
+
}, {});
|
|
76
|
+
const runId = yield* store.writeRun({
|
|
77
|
+
invocationId: "inv-preset-wf-1",
|
|
78
|
+
project: "default",
|
|
79
|
+
settingsHash: "hash-preset-wf",
|
|
80
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
81
|
+
commitSha: null,
|
|
82
|
+
branch: null,
|
|
83
|
+
reason: "failed",
|
|
84
|
+
duration: 1500,
|
|
85
|
+
total: 4,
|
|
86
|
+
passed: 2,
|
|
87
|
+
failed: 2,
|
|
88
|
+
skipped: 0,
|
|
89
|
+
scoped: false
|
|
90
|
+
});
|
|
91
|
+
const fileId = yield* store.ensureFile("src/parser.test.ts");
|
|
92
|
+
const [moduleId] = yield* store.writeModules(runId, [{
|
|
93
|
+
fileId,
|
|
94
|
+
relativeModuleId: "src/parser.test.ts",
|
|
95
|
+
state: "failed",
|
|
96
|
+
duration: 1500
|
|
97
|
+
}]);
|
|
98
|
+
yield* store.writeTestCases(moduleId, [
|
|
99
|
+
{
|
|
100
|
+
name: "parses valid input",
|
|
101
|
+
fullName: "parser > parses valid input",
|
|
102
|
+
state: "passed",
|
|
103
|
+
duration: 300
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "parses nested structure",
|
|
107
|
+
fullName: "parser > parses nested structure",
|
|
108
|
+
state: "passed",
|
|
109
|
+
duration: 300
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "rejects empty input",
|
|
113
|
+
fullName: "parser > rejects empty input",
|
|
114
|
+
state: "failed",
|
|
115
|
+
duration: 450
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "rejects malformed input",
|
|
119
|
+
fullName: "parser > rejects malformed input",
|
|
120
|
+
state: "failed",
|
|
121
|
+
duration: 450
|
|
122
|
+
}
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
126
|
+
}
|
|
127
|
+
/** @public */
|
|
128
|
+
function flaky(filename) {
|
|
129
|
+
const base = makeTestLayer(filename);
|
|
130
|
+
const seed = Effect.gen(function* () {
|
|
131
|
+
const store = yield* DataStore;
|
|
132
|
+
yield* store.writeSettings("hash-preset-flaky", {
|
|
133
|
+
vitestVersion: "4.1.5",
|
|
134
|
+
pool: "forks"
|
|
135
|
+
}, {});
|
|
136
|
+
const runId1 = yield* store.writeRun({
|
|
137
|
+
invocationId: "inv-preset-flaky-1",
|
|
138
|
+
project: "default",
|
|
139
|
+
settingsHash: "hash-preset-flaky",
|
|
140
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
141
|
+
commitSha: null,
|
|
142
|
+
branch: null,
|
|
143
|
+
reason: "failed",
|
|
144
|
+
duration: 800,
|
|
145
|
+
total: 2,
|
|
146
|
+
passed: 1,
|
|
147
|
+
failed: 1,
|
|
148
|
+
skipped: 0,
|
|
149
|
+
scoped: false
|
|
150
|
+
});
|
|
151
|
+
const fileId = yield* store.ensureFile("src/async.test.ts");
|
|
152
|
+
const [moduleId1] = yield* store.writeModules(runId1, [{
|
|
153
|
+
fileId,
|
|
154
|
+
relativeModuleId: "src/async.test.ts",
|
|
155
|
+
state: "failed",
|
|
156
|
+
duration: 800
|
|
157
|
+
}]);
|
|
158
|
+
yield* store.writeTestCases(moduleId1, [{
|
|
159
|
+
name: "resolves within timeout",
|
|
160
|
+
fullName: "async > resolves within timeout",
|
|
161
|
+
state: "failed",
|
|
162
|
+
duration: 500
|
|
163
|
+
}, {
|
|
164
|
+
name: "rejects on error",
|
|
165
|
+
fullName: "async > rejects on error",
|
|
166
|
+
state: "passed",
|
|
167
|
+
duration: 300
|
|
168
|
+
}]);
|
|
169
|
+
yield* store.writeHistory("default", "async > resolves within timeout", runId1, "2025-12-31T23:00:00.000Z", "passed", 500, false, 0, null);
|
|
170
|
+
yield* store.writeHistory("default", "async > resolves within timeout", runId1, "2026-01-01T00:00:00.000Z", "failed", 500, false, 0, null);
|
|
171
|
+
yield* store.writeHistory("default", "async > rejects on error", runId1, "2026-01-01T00:00:00.000Z", "passed", 300, false, 0, null);
|
|
172
|
+
const runId2 = yield* store.writeRun({
|
|
173
|
+
invocationId: "inv-preset-flaky-2",
|
|
174
|
+
project: "default",
|
|
175
|
+
settingsHash: "hash-preset-flaky",
|
|
176
|
+
timestamp: "2026-01-01T01:00:00.000Z",
|
|
177
|
+
commitSha: null,
|
|
178
|
+
branch: null,
|
|
179
|
+
reason: "passed",
|
|
180
|
+
duration: 700,
|
|
181
|
+
total: 2,
|
|
182
|
+
passed: 2,
|
|
183
|
+
failed: 0,
|
|
184
|
+
skipped: 0,
|
|
185
|
+
scoped: false
|
|
186
|
+
});
|
|
187
|
+
const [moduleId2] = yield* store.writeModules(runId2, [{
|
|
188
|
+
fileId,
|
|
189
|
+
relativeModuleId: "src/async.test.ts",
|
|
190
|
+
state: "passed",
|
|
191
|
+
duration: 700
|
|
192
|
+
}]);
|
|
193
|
+
yield* store.writeTestCases(moduleId2, [{
|
|
194
|
+
name: "resolves within timeout",
|
|
195
|
+
fullName: "async > resolves within timeout",
|
|
196
|
+
state: "passed",
|
|
197
|
+
duration: 400
|
|
198
|
+
}, {
|
|
199
|
+
name: "rejects on error",
|
|
200
|
+
fullName: "async > rejects on error",
|
|
201
|
+
state: "passed",
|
|
202
|
+
duration: 300
|
|
203
|
+
}]);
|
|
204
|
+
yield* store.writeHistory("default", "async > resolves within timeout", runId2, "2026-01-01T01:00:00.000Z", "passed", 400, false, 0, null);
|
|
205
|
+
yield* store.writeHistory("default", "async > rejects on error", runId2, "2026-01-01T01:00:00.000Z", "passed", 300, false, 0, null);
|
|
206
|
+
});
|
|
207
|
+
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
208
|
+
}
|
|
209
|
+
/** @public */
|
|
210
|
+
function withTddTask(filename) {
|
|
211
|
+
const base = makeTestLayer(filename);
|
|
212
|
+
const seed = Effect.gen(function* () {
|
|
213
|
+
const store = yield* DataStore;
|
|
214
|
+
const sessionId = yield* store.writeSession({
|
|
215
|
+
chatId: "chat-preset-tdd",
|
|
216
|
+
project: "default",
|
|
217
|
+
cwd: "/workspace",
|
|
218
|
+
agentKind: "main",
|
|
219
|
+
startedAt: "2026-01-01T00:00:00.000Z"
|
|
220
|
+
});
|
|
221
|
+
const tddTaskId = yield* store.writeTddTask({
|
|
222
|
+
sessionId,
|
|
223
|
+
goal: "Implement the parser feature",
|
|
224
|
+
startedAt: "2026-01-01T00:00:00.000Z"
|
|
225
|
+
});
|
|
226
|
+
const goal = yield* store.createGoal({
|
|
227
|
+
tddTaskId,
|
|
228
|
+
goal: "Handle edge cases"
|
|
229
|
+
});
|
|
230
|
+
yield* store.createBehavior({
|
|
231
|
+
goalId: goal.id,
|
|
232
|
+
behavior: "rejects empty input"
|
|
233
|
+
});
|
|
234
|
+
yield* store.createBehavior({
|
|
235
|
+
goalId: goal.id,
|
|
236
|
+
behavior: "rejects null input"
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
return Layer.effectDiscard(seed).pipe(Layer.provideMerge(base));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
//#endregion
|
|
243
|
+
export { BehaviorNotFoundError, DataReader, DataStore, DataStoreError, DataStoreTestLayer, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError, empty, flaky, makeTestLayer, singlePassingRun, withFailures, withTddTask };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.58.9"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
package/utils/ansi.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/utils/ansi.ts
|
|
2
|
+
const ANSI_CODES = {
|
|
3
|
+
reset: [0, 0],
|
|
4
|
+
bold: [1, 22],
|
|
5
|
+
dim: [2, 22],
|
|
6
|
+
red: [31, 39],
|
|
7
|
+
green: [32, 39],
|
|
8
|
+
yellow: [33, 39],
|
|
9
|
+
cyan: [36, 39]
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Wrap text with an ANSI color/style escape code.
|
|
13
|
+
*
|
|
14
|
+
* No-ops when `options.noColor` is true, keeping output as valid plain
|
|
15
|
+
* markdown when captured by agents or piped to files.
|
|
16
|
+
*
|
|
17
|
+
* @privateRemarks
|
|
18
|
+
* Uses raw ANSI escape sequences (`\x1b[...m`) rather than a dependency
|
|
19
|
+
* like `chalk` or `picocolors` to keep the package zero-dependency. The
|
|
20
|
+
* `noColor` flag respects the `NO_COLOR` environment variable convention.
|
|
21
|
+
*
|
|
22
|
+
* @param text - Text to wrap
|
|
23
|
+
* @param color - ANSI code name: `reset`, `bold`, `dim`, `red`, `green`, `yellow`, `cyan`
|
|
24
|
+
* @param options - Control color output
|
|
25
|
+
* @returns Text with ANSI escape codes, or plain text if noColor
|
|
26
|
+
*
|
|
27
|
+
* @see {@link https://no-color.org/ | NO_COLOR convention}
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
function ansi(text, color, options) {
|
|
31
|
+
if (options?.noColor) return text;
|
|
32
|
+
const codes = ANSI_CODES[color];
|
|
33
|
+
if (!codes) return text;
|
|
34
|
+
return `\x1b[${codes[0]}m${text}\x1b[${codes[1]}m`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Strip all ANSI escape codes from a string.
|
|
38
|
+
*
|
|
39
|
+
* @param text - String potentially containing ANSI escape sequences
|
|
40
|
+
* @returns Plain text with all escape sequences removed
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function stripAnsi(text) {
|
|
45
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { ansi, stripAnsi };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//#region src/utils/build-report.ts
|
|
2
|
+
/**
|
|
3
|
+
* Format a single Vitest stack frame: strings pass through; ParsedStack
|
|
4
|
+
* objects render as `at <method> (<file>:<line>:<column>)`. An empty
|
|
5
|
+
* `method` falls back to `<anonymous>`.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
function formatStackFrame(frame) {
|
|
10
|
+
if (typeof frame === "string") return frame;
|
|
11
|
+
return `at ${frame.method.length > 0 ? frame.method : "<anonymous>"} (${frame.file}:${frame.line}:${frame.column})`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Map Vitest error objects to the {@link ReportError} shape.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
function mapErrors(errors) {
|
|
19
|
+
if (!errors || errors.length === 0) return void 0;
|
|
20
|
+
return errors.map((e) => {
|
|
21
|
+
return {
|
|
22
|
+
message: e.message,
|
|
23
|
+
..."diff" in e && e.diff != null ? { diff: e.diff } : {},
|
|
24
|
+
...e.stacks && e.stacks.length > 0 ? { stack: e.stacks.map(formatStackFrame).join("\n") } : {}
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a Vitest state string to one of the four canonical states.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
function normalizeState(state) {
|
|
34
|
+
if (state === "passed") return "passed";
|
|
35
|
+
if (state === "failed") return "failed";
|
|
36
|
+
if (state === "skipped") return "skipped";
|
|
37
|
+
return "pending";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Convert Vitest TestModule/TestCase objects into an `AgentReport`.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* This is a pure data transformation function with no I/O. It tallies
|
|
44
|
+
* pass/fail/skip counts, extracts error details with diffs, and builds
|
|
45
|
+
* the complete report structure. Only modules with at least one failing
|
|
46
|
+
* test are included in the `failed` array to keep reports compact.
|
|
47
|
+
*
|
|
48
|
+
* @param testModules - All test modules from the Vitest run
|
|
49
|
+
* @param unhandledErrors - Unhandled errors from the run
|
|
50
|
+
* @param reason - Overall run outcome (`"passed"`, `"failed"`, or `"interrupted"`)
|
|
51
|
+
* @param options - Reporter options; only `omitPassingTests` is used
|
|
52
|
+
* @param projectName - Optional project name for monorepo grouping
|
|
53
|
+
* @returns Structured report ready for JSON serialization
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
function buildAgentReport(testModules, unhandledErrors, reason, options, projectName) {
|
|
58
|
+
const omitPassing = options.omitPassingTests !== false;
|
|
59
|
+
let totalCount = 0;
|
|
60
|
+
let passedCount = 0;
|
|
61
|
+
let failedCount = 0;
|
|
62
|
+
let skippedCount = 0;
|
|
63
|
+
let totalDuration = 0;
|
|
64
|
+
const failedModules = [];
|
|
65
|
+
const failedFiles = [];
|
|
66
|
+
for (const testModule of testModules) {
|
|
67
|
+
const moduleDuration = testModule.diagnostic()?.duration ?? 0;
|
|
68
|
+
totalDuration += moduleDuration;
|
|
69
|
+
const moduleState = normalizeState(testModule.state());
|
|
70
|
+
const moduleErrors = mapErrors(testModule.errors());
|
|
71
|
+
const allTests = [...testModule.children.allTests()];
|
|
72
|
+
const testReports = [];
|
|
73
|
+
let moduleHasFailure = false;
|
|
74
|
+
for (const testCase of allTests) {
|
|
75
|
+
totalCount++;
|
|
76
|
+
const result = testCase.result();
|
|
77
|
+
const diag = testCase.diagnostic();
|
|
78
|
+
const state = normalizeState(result?.state ?? "pending");
|
|
79
|
+
if (state === "passed") passedCount++;
|
|
80
|
+
else if (state === "failed") {
|
|
81
|
+
failedCount++;
|
|
82
|
+
moduleHasFailure = true;
|
|
83
|
+
} else if (state === "skipped") skippedCount++;
|
|
84
|
+
else skippedCount++;
|
|
85
|
+
if (!omitPassing || state !== "passed") {
|
|
86
|
+
const errors = mapErrors(result?.errors);
|
|
87
|
+
const testReport = {
|
|
88
|
+
name: testCase.name,
|
|
89
|
+
fullName: testCase.fullName,
|
|
90
|
+
state,
|
|
91
|
+
...diag?.duration !== void 0 ? { duration: diag.duration } : {},
|
|
92
|
+
...diag?.flaky ? { flaky: true } : {},
|
|
93
|
+
...diag?.slow ? { slow: true } : {},
|
|
94
|
+
...errors ? { errors } : {}
|
|
95
|
+
};
|
|
96
|
+
testReports.push(testReport);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (moduleHasFailure || !moduleHasFailure && moduleState === "failed" && moduleErrors !== void 0) {
|
|
100
|
+
failedFiles.push(testModule.relativeModuleId);
|
|
101
|
+
const moduleReport = {
|
|
102
|
+
file: testModule.relativeModuleId,
|
|
103
|
+
state: moduleState,
|
|
104
|
+
duration: moduleDuration,
|
|
105
|
+
tests: testReports,
|
|
106
|
+
...moduleErrors ? { errors: moduleErrors } : {}
|
|
107
|
+
};
|
|
108
|
+
failedModules.push(moduleReport);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const mappedUnhandled = mapErrors(unhandledErrors) ?? [];
|
|
112
|
+
return {
|
|
113
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
114
|
+
...projectName != null ? { project: projectName } : {},
|
|
115
|
+
reason,
|
|
116
|
+
summary: {
|
|
117
|
+
total: totalCount,
|
|
118
|
+
passed: passedCount,
|
|
119
|
+
failed: failedCount,
|
|
120
|
+
skipped: skippedCount,
|
|
121
|
+
duration: totalDuration
|
|
122
|
+
},
|
|
123
|
+
failed: failedModules,
|
|
124
|
+
unhandledErrors: mappedUnhandled,
|
|
125
|
+
failedFiles
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
export { buildAgentReport };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//#region src/utils/canonicalize-git-url.ts
|
|
2
|
+
const SCHEME_PREFIXES = [
|
|
3
|
+
"git+ssh://",
|
|
4
|
+
"git+https://",
|
|
5
|
+
"ssh://",
|
|
6
|
+
"https://",
|
|
7
|
+
"http://"
|
|
8
|
+
];
|
|
9
|
+
const SSH_SHORTHAND_REGEX = /^(?:[\w.-]+@)?([\w.-]+):(.+)$/;
|
|
10
|
+
/**
|
|
11
|
+
* Canonicalize a git remote URL.
|
|
12
|
+
*
|
|
13
|
+
* Returns `null` when the input is empty, whitespace-only, or has
|
|
14
|
+
* neither a host nor a path after stripping known prefixes.
|
|
15
|
+
*
|
|
16
|
+
* Steps applied in order:
|
|
17
|
+
*
|
|
18
|
+
* 1. Trim whitespace.
|
|
19
|
+
* 2. If the value starts with `[git+]ssh|https|http://`, strip the
|
|
20
|
+
* scheme. After stripping, drop any embedded `user@` segment.
|
|
21
|
+
* 3. Otherwise, try the SSH shorthand `user@host:path` pattern; on a
|
|
22
|
+
* match, rebuild as `host/path` (drop the user).
|
|
23
|
+
* 4. Strip trailing `.git`.
|
|
24
|
+
* 5. Strip trailing `/`.
|
|
25
|
+
* 6. Lowercase the entire string.
|
|
26
|
+
* 7. Reject if the result is empty or has no `/` (no path) — a bare
|
|
27
|
+
* host like `github.com` is not a project identity.
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
const canonicalizeGitUrl = (raw) => {
|
|
31
|
+
const trimmed = raw.trim();
|
|
32
|
+
if (trimmed.length === 0) return null;
|
|
33
|
+
let value = trimmed;
|
|
34
|
+
const matchedPrefix = SCHEME_PREFIXES.find((prefix) => value.startsWith(prefix));
|
|
35
|
+
if (matchedPrefix !== void 0) {
|
|
36
|
+
value = value.slice(matchedPrefix.length);
|
|
37
|
+
const atIndex = value.indexOf("@");
|
|
38
|
+
const slashIndex = value.indexOf("/");
|
|
39
|
+
if (atIndex !== -1 && (slashIndex === -1 || atIndex < slashIndex)) value = value.slice(atIndex + 1);
|
|
40
|
+
} else {
|
|
41
|
+
const sshMatch = value.match(SSH_SHORTHAND_REGEX);
|
|
42
|
+
if (sshMatch !== null) value = `${sshMatch[1]}/${sshMatch[2]}`;
|
|
43
|
+
}
|
|
44
|
+
if (value.endsWith(".git")) value = value.slice(0, -4);
|
|
45
|
+
if (value.endsWith("/")) value = value.slice(0, -1);
|
|
46
|
+
value = value.toLowerCase();
|
|
47
|
+
if (value.length === 0 || !value.includes("/")) return null;
|
|
48
|
+
const slashIndex = value.indexOf("/");
|
|
49
|
+
if (slashIndex === 0 || slashIndex === value.length - 1) return null;
|
|
50
|
+
return value;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Convert a (possibly raw) git URL to the filesystem-safe `project_key`
|
|
54
|
+
* form. The canonical `host/path` is mapped 1:1 to `host__path` so it
|
|
55
|
+
* can serve as a single path segment under
|
|
56
|
+
* `$XDG_DATA_HOME/vitest-agent/`.
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
const gitUrlToProjectKey = (raw) => {
|
|
60
|
+
const canonical = canonicalizeGitUrl(raw);
|
|
61
|
+
if (canonical === null) return null;
|
|
62
|
+
return canonical.replaceAll("/", "__");
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { canonicalizeGitUrl, gitUrlToProjectKey };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/utils/classify-test.ts
|
|
2
|
+
/**
|
|
3
|
+
* Classify a test based on its current state and prior run history.
|
|
4
|
+
*
|
|
5
|
+
* @param current - The test's state in the current run
|
|
6
|
+
* @param priorRuns - Previous runs (most recent first), before the current run was prepended
|
|
7
|
+
* @returns The test's classification
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
function classifyTest(current, priorRuns) {
|
|
11
|
+
if (current === "failed") {
|
|
12
|
+
if (priorRuns.length === 0) return "new-failure";
|
|
13
|
+
if (priorRuns.every((r) => r.state === "passed")) return "new-failure";
|
|
14
|
+
if (priorRuns[0].state === "failed") return "persistent";
|
|
15
|
+
return "flaky";
|
|
16
|
+
}
|
|
17
|
+
if (priorRuns.length === 0) return "stable";
|
|
18
|
+
return priorRuns.some((r) => r.state === "failed") ? "recovered" : "stable";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { classifyTest };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/utils/compress-lines.ts
|
|
2
|
+
/**
|
|
3
|
+
* Compress an array of line numbers into a compact range string.
|
|
4
|
+
*
|
|
5
|
+
* Consecutive line numbers are collapsed into `start-end` ranges,
|
|
6
|
+
* separated by commas. Duplicate values are removed and the input
|
|
7
|
+
* is sorted ascending before processing.
|
|
8
|
+
*
|
|
9
|
+
* @param lines - Array of line numbers to compress
|
|
10
|
+
* @returns Compressed range string, or empty string for empty input
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { compressLines } from "@vitest-agent/sdk/utils";
|
|
15
|
+
*
|
|
16
|
+
* compressLines([1, 2, 3, 5, 10, 11, 12]);
|
|
17
|
+
* // Returns: "1-3,5,10-12"
|
|
18
|
+
*
|
|
19
|
+
* compressLines([42]);
|
|
20
|
+
* // Returns: "42"
|
|
21
|
+
*
|
|
22
|
+
* compressLines([]);
|
|
23
|
+
* // Returns: ""
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
function compressLines(lines) {
|
|
29
|
+
if (lines.length === 0) return "";
|
|
30
|
+
const sorted = [...new Set(lines)].sort((a, b) => a - b);
|
|
31
|
+
const ranges = [];
|
|
32
|
+
let start = sorted[0];
|
|
33
|
+
let end = sorted[0];
|
|
34
|
+
for (let i = 1; i < sorted.length; i++) if (sorted[i] === end + 1) end = sorted[i];
|
|
35
|
+
else {
|
|
36
|
+
ranges.push(start === end ? `${start}` : `${start}-${end}`);
|
|
37
|
+
start = sorted[i];
|
|
38
|
+
end = sorted[i];
|
|
39
|
+
}
|
|
40
|
+
ranges.push(start === end ? `${start}` : `${start}-${end}`);
|
|
41
|
+
return ranges.join(",");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { compressLines };
|