@vitest-agent/sdk 1.0.1 → 1.2.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/index.d.ts +2051 -141
- package/index.js +6 -5
- package/package.json +1 -1
- package/schemas/AgentReport.js +3 -1
- package/schemas/ConsoleLeaks.js +28 -0
- package/schemas/RunEvent.js +1 -1
- package/schemas/turns/index.js +1 -1
- package/testing.d.ts +1523 -10
- package/testing.js +11 -1
- package/utils/console-leaks.js +77 -0
- package/utils/validate-phase-transition.js +23 -3
- package/DataReader-2kJ9I9Hh.d.ts +0 -1809
package/testing.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import { AgentNotFoundError, RegistrationConflictError } from "./errors/AgentErrors.js";
|
|
1
2
|
import { DataStoreError } from "./errors/DataStoreError.js";
|
|
2
3
|
import { BehaviorNotFoundError, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError } from "./errors/TddErrors.js";
|
|
3
4
|
import { DataReader } from "./services/DataReader.js";
|
|
5
|
+
import { AgentId, ChatId } from "./schemas/Identity.js";
|
|
6
|
+
import { Agent, IdempotencyHit } from "./schemas/Agent.js";
|
|
4
7
|
import { DataStore } from "./services/DataStore.js";
|
|
8
|
+
import { CoverageReport, FileCoverageReport } from "./schemas/Coverage.js";
|
|
9
|
+
import { AgentReport } from "./schemas/AgentReport.js";
|
|
10
|
+
import { CoverageBaselines } from "./schemas/Baselines.js";
|
|
11
|
+
import { CacheManifest } from "./schemas/CacheManifest.js";
|
|
12
|
+
import { HistoryRecord } from "./schemas/History.js";
|
|
13
|
+
import { BehaviorDetail, BehaviorRow, BehaviorStatus, GoalDetail, GoalRow, GoalStatus } from "./schemas/Tdd.js";
|
|
14
|
+
import { TrendEntry, TrendRecord } from "./schemas/Trends.js";
|
|
5
15
|
import { DataStoreTestLayer, makeTestLayer } from "./testing/layers.js";
|
|
6
16
|
import { Effect, Layer } from "effect";
|
|
7
17
|
|
|
@@ -240,4 +250,4 @@ function withTddTask(filename) {
|
|
|
240
250
|
}
|
|
241
251
|
|
|
242
252
|
//#endregion
|
|
243
|
-
export { BehaviorNotFoundError, DataReader, DataStore, DataStoreError, DataStoreTestLayer, GoalNotFoundError, IllegalStatusTransitionError, TddTaskAlreadyEndedError, TddTaskNotFoundError, empty, flaky, makeTestLayer, singlePassingRun, withFailures, withTddTask };
|
|
253
|
+
export { Agent, AgentId, AgentNotFoundError, AgentReport, BehaviorDetail, BehaviorNotFoundError, BehaviorRow, BehaviorStatus, CacheManifest, ChatId, CoverageBaselines, CoverageReport, DataReader, DataStore, DataStoreError, DataStoreTestLayer, FileCoverageReport, GoalDetail, GoalNotFoundError, GoalRow, GoalStatus, HistoryRecord, IdempotencyHit, IllegalStatusTransitionError, RegistrationConflictError, TddTaskAlreadyEndedError, TddTaskNotFoundError, TrendEntry, TrendRecord, empty, flaky, makeTestLayer, singlePassingRun, withFailures, withTddTask };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//#region src/utils/console-leaks.ts
|
|
2
|
+
const SAMPLE_MAX_CHARS = 160;
|
|
3
|
+
const MAX_FILES = 25;
|
|
4
|
+
const MAX_TESTS_PER_FILE = 10;
|
|
5
|
+
function truncateSample(content) {
|
|
6
|
+
const trimmed = (content.split("\n", 1)[0] ?? "").trim();
|
|
7
|
+
return trimmed.length > SAMPLE_MAX_CHARS ? `${trimmed.slice(0, SAMPLE_MAX_CHARS)}…` : trimmed;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Aggregate raw console-leak entries into a ConsoleLeaks signal:
|
|
11
|
+
* bucket by file, split stdout/stderr, collect attributable test names,
|
|
12
|
+
* capture one truncated sample per file, sort by total writes descending,
|
|
13
|
+
* and cap the file list. Returns `undefined` when there are no entries so a
|
|
14
|
+
* clean run attaches nothing.
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
function buildConsoleLeaks(entries) {
|
|
18
|
+
if (entries.length === 0) return void 0;
|
|
19
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const e of entries) {
|
|
21
|
+
let acc = byFile.get(e.file);
|
|
22
|
+
if (acc === void 0) {
|
|
23
|
+
acc = {
|
|
24
|
+
stdout: 0,
|
|
25
|
+
stderr: 0,
|
|
26
|
+
tests: /* @__PURE__ */ new Set()
|
|
27
|
+
};
|
|
28
|
+
byFile.set(e.file, acc);
|
|
29
|
+
}
|
|
30
|
+
if (e.type === "stdout") acc.stdout++;
|
|
31
|
+
else acc.stderr++;
|
|
32
|
+
if (e.test !== void 0 && e.test !== "") acc.tests.add(e.test);
|
|
33
|
+
if (acc.sample === void 0) {
|
|
34
|
+
const candidate = truncateSample(e.content);
|
|
35
|
+
if (candidate !== "") acc.sample = candidate;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const files = [];
|
|
39
|
+
for (const [file, acc] of byFile) files.push({
|
|
40
|
+
file,
|
|
41
|
+
stdout: acc.stdout,
|
|
42
|
+
stderr: acc.stderr,
|
|
43
|
+
...acc.tests.size > 0 ? { tests: [...acc.tests].slice(0, MAX_TESTS_PER_FILE) } : {},
|
|
44
|
+
...acc.sample !== void 0 ? { sample: acc.sample } : {}
|
|
45
|
+
});
|
|
46
|
+
files.sort((a, b) => b.stdout + b.stderr - (a.stdout + a.stderr));
|
|
47
|
+
const truncated = files.length > MAX_FILES;
|
|
48
|
+
return {
|
|
49
|
+
total: entries.length,
|
|
50
|
+
byFile: truncated ? files.slice(0, MAX_FILES) : files,
|
|
51
|
+
...truncated ? { truncated: true } : {}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Walk a Vitest `File[]` task tree (from `vitest.state.getFiles()`) into flat
|
|
56
|
+
* {@link ConsoleLeakEntry} values. Each task `log` becomes one entry attributed
|
|
57
|
+
* to its enclosing file and, when the log sits on a test task, that test's name.
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
function collectConsoleLeakEntries(files) {
|
|
61
|
+
const entries = [];
|
|
62
|
+
const visit = (task, file, test) => {
|
|
63
|
+
const currentTest = task.type === "test" ? task.fullTestName ?? task.name ?? test : test;
|
|
64
|
+
for (const log of task.logs ?? []) entries.push({
|
|
65
|
+
file,
|
|
66
|
+
...currentTest !== void 0 ? { test: currentTest } : {},
|
|
67
|
+
type: log.type,
|
|
68
|
+
content: log.content
|
|
69
|
+
});
|
|
70
|
+
for (const child of task.tasks ?? []) visit(child, file, currentTest);
|
|
71
|
+
};
|
|
72
|
+
for (const file of files) visit(file, file.name ?? "(unknown)", void 0);
|
|
73
|
+
return entries;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { buildConsoleLeaks, collectConsoleLeakEntries };
|
|
@@ -14,6 +14,10 @@ const requiredArtifactForTransition = (from, to) => {
|
|
|
14
14
|
kind: "test_failed_run",
|
|
15
15
|
humanHint: "Run the failing test via run_tests, then record the test_failed_run artifact before requesting red→green."
|
|
16
16
|
};
|
|
17
|
+
if (from === "red.triangulate" && to === "green") return {
|
|
18
|
+
kind: "test_failed_run",
|
|
19
|
+
humanHint: "A triangulation batch must still have produced at least one real failing run; record the batch's test_failed_run before requesting red.triangulate→green."
|
|
20
|
+
};
|
|
17
21
|
if (from === "green" && to === "refactor") return {
|
|
18
22
|
kind: "test_passed_run",
|
|
19
23
|
humanHint: "Run the test via run_tests and confirm it passes; record test_passed_run before requesting green→refactor."
|
|
@@ -24,6 +28,21 @@ const requiredArtifactForTransition = (from, to) => {
|
|
|
24
28
|
};
|
|
25
29
|
return null;
|
|
26
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Whether D2 binding rule 2 (behavior-match) applies to a given transition.
|
|
33
|
+
*
|
|
34
|
+
* Only the transitions whose cited evidence must belong to the behavior being
|
|
35
|
+
* transitioned enforce behavior-match: `red→green` (the failing test for this
|
|
36
|
+
* behavior) and `green→refactor` (its passing test). It deliberately excludes
|
|
37
|
+
* `red.triangulate→green` (the cited failing run belongs to an earlier batch
|
|
38
|
+
* behavior) and `refactor→red` (the required `test_passed_run` is the
|
|
39
|
+
* just-finished behavior's, never the new target). Exported so the MCP
|
|
40
|
+
* `tdd_phase_transition_request` tool scopes artifact auto-resolution by
|
|
41
|
+
* behavior on exactly the same transitions the validator will accept —
|
|
42
|
+
* keeping the two in lockstep (issue #115).
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
const transitionEnforcesBehaviorMatch = (from, to) => from === "red" && to === "green" || from === "green" && to === "refactor";
|
|
27
46
|
/** @public */
|
|
28
47
|
const validatePhaseTransition = (ctx) => {
|
|
29
48
|
if (ctx.requested_phase === "green" && ctx.current_phase !== "red" && ctx.current_phase !== "red.triangulate" && ctx.current_phase !== "green.fake-it") return {
|
|
@@ -36,6 +55,7 @@ const validatePhaseTransition = (ctx) => {
|
|
|
36
55
|
humanHint: `Cannot transition from '${ctx.current_phase}' directly to 'green'. The red phase must be entered explicitly first (${ctx.current_phase}→red), then a failing test written and run, then red→green requested with a test_failed_run artifact.`
|
|
37
56
|
}
|
|
38
57
|
};
|
|
58
|
+
const isTriangulateGreen = ctx.current_phase === "red.triangulate" && ctx.requested_phase === "green";
|
|
39
59
|
const expected = requiredArtifactForTransition(ctx.current_phase, ctx.requested_phase);
|
|
40
60
|
if (expected === null) return {
|
|
41
61
|
accepted: true,
|
|
@@ -62,7 +82,7 @@ const validatePhaseTransition = (ctx) => {
|
|
|
62
82
|
}
|
|
63
83
|
};
|
|
64
84
|
if (expected.kind === "test_failed_run") {
|
|
65
|
-
if (ctx.cited_artifact.test_case_created_turn_at !== null && ctx.cited_artifact.test_case_created_turn_at < ctx.phase_started_at) return {
|
|
85
|
+
if (!isTriangulateGreen && ctx.cited_artifact.test_case_created_turn_at !== null && ctx.cited_artifact.test_case_created_turn_at < ctx.phase_started_at) return {
|
|
66
86
|
accepted: false,
|
|
67
87
|
phase: ctx.current_phase,
|
|
68
88
|
denialReason: "evidence_not_in_phase_window",
|
|
@@ -83,7 +103,7 @@ const validatePhaseTransition = (ctx) => {
|
|
|
83
103
|
}
|
|
84
104
|
};
|
|
85
105
|
}
|
|
86
|
-
if (ctx.requested_behavior_id !== null && ctx.cited_artifact.behavior_id !== ctx.requested_behavior_id) return {
|
|
106
|
+
if (transitionEnforcesBehaviorMatch(ctx.current_phase, ctx.requested_phase) && ctx.requested_behavior_id !== null && ctx.cited_artifact.behavior_id !== ctx.requested_behavior_id) return {
|
|
87
107
|
accepted: false,
|
|
88
108
|
phase: ctx.current_phase,
|
|
89
109
|
denialReason: "evidence_not_for_behavior",
|
|
@@ -110,4 +130,4 @@ const validatePhaseTransition = (ctx) => {
|
|
|
110
130
|
};
|
|
111
131
|
|
|
112
132
|
//#endregion
|
|
113
|
-
export { requiredArtifactForTransition, validatePhaseTransition };
|
|
133
|
+
export { requiredArtifactForTransition, transitionEnforcesBehaviorMatch, validatePhaseTransition };
|