@vitest-agent/cli 2.2.12 → 2.2.14
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/commands/agent.js +47 -2
- package/commands/record.js +13 -6
- package/index.js +1 -1
- package/lib/internal-register-agent.js +6 -1
- package/lib/record-tdd-artifact.js +76 -12
- package/package.json +4 -4
package/commands/agent.js
CHANGED
|
@@ -6,9 +6,9 @@ import { recordCommand } from "./record.js";
|
|
|
6
6
|
import { triageCommand } from "./triage.js";
|
|
7
7
|
import { wrapupCommand } from "./wrapup.js";
|
|
8
8
|
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
9
|
-
import { classifyTestPath, findOwningWorkspace, resolveProjectKeyFromCwd } from "@vitest-agent/sdk";
|
|
9
|
+
import { classifyTestPath, detectNonDefaultDiscoverStrategy, findOwningWorkspace, resolveProjectKeyFromCwd } from "@vitest-agent/sdk";
|
|
10
10
|
import { Cause, Effect, Option } from "effect";
|
|
11
|
-
import { existsSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
13
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
14
14
|
import { findWorkspaceRootSync, getWorkspacePackagesSync } from "@effected/workspaces";
|
|
@@ -182,11 +182,56 @@ function crossesNestedPackageBoundary(ownerPath, filePath) {
|
|
|
182
182
|
}
|
|
183
183
|
return false;
|
|
184
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Candidate Vitest/Vite config filenames at a workspace root, in the order
|
|
187
|
+
* Vitest itself would prefer them. `vite.config.*` is a fallback for a
|
|
188
|
+
* workspace that configures the plugin through Vite directly rather than a
|
|
189
|
+
* dedicated `vitest.config.*`.
|
|
190
|
+
* @internal
|
|
191
|
+
*/
|
|
192
|
+
const VITEST_CONFIG_CANDIDATES = [
|
|
193
|
+
"vitest.config.ts",
|
|
194
|
+
"vitest.config.mts",
|
|
195
|
+
"vitest.config.js",
|
|
196
|
+
"vitest.config.mjs",
|
|
197
|
+
"vitest.workspace.ts",
|
|
198
|
+
"vitest.workspace.mts",
|
|
199
|
+
"vitest.workspace.js",
|
|
200
|
+
"vitest.workspace.mjs",
|
|
201
|
+
"vite.config.ts",
|
|
202
|
+
"vite.config.mts",
|
|
203
|
+
"vite.config.js",
|
|
204
|
+
"vite.config.mjs"
|
|
205
|
+
];
|
|
206
|
+
/**
|
|
207
|
+
* Reads the first Vitest/Vite config file found at `root`, returning its
|
|
208
|
+
* source text, or `null` when none of {@link VITEST_CONFIG_CANDIDATES} exists
|
|
209
|
+
* or the file that does exist cannot be read.
|
|
210
|
+
*
|
|
211
|
+
* A `null` result is deliberately indistinguishable between "no config file"
|
|
212
|
+
* and "unreadable config file" — both mean the caller cannot rule out a
|
|
213
|
+
* non-default `DiscoverStrategy` and must fail open (issue #230).
|
|
214
|
+
* @internal
|
|
215
|
+
*/
|
|
216
|
+
function readWorkspaceVitestConfigSource(root) {
|
|
217
|
+
for (const candidate of VITEST_CONFIG_CANDIDATES) {
|
|
218
|
+
const candidatePath = join(root, candidate);
|
|
219
|
+
if (!existsSync(candidatePath)) continue;
|
|
220
|
+
try {
|
|
221
|
+
return readFileSync(candidatePath, "utf8");
|
|
222
|
+
} catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
185
228
|
const checkTestPathSubcommand = Command.make("check-test-path", { path: testPathArg }, (opts) => Effect.sync(() => {
|
|
186
229
|
const from = process.env.VITEST_AGENT_PROJECT_DIR ?? process.cwd();
|
|
187
230
|
const absolute = isAbsolute(opts.path) ? opts.path : resolve(from, opts.path);
|
|
188
231
|
const root = findWorkspaceRootSync(from, nodeSyncOps);
|
|
189
232
|
if (root === null) process.exit(1);
|
|
233
|
+
const configSource = readWorkspaceVitestConfigSource(root);
|
|
234
|
+
if (configSource === null || detectNonDefaultDiscoverStrategy(configSource)) process.exit(1);
|
|
190
235
|
const packages = getWorkspacePackagesSync(root, nodeSyncOps);
|
|
191
236
|
const owner = findOwningWorkspace(packages, absolute);
|
|
192
237
|
if (owner === null) process.exit(1);
|
package/commands/record.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { recordSessionEnd, recordSessionStart } from "../lib/record-session.js";
|
|
2
|
-
import {
|
|
2
|
+
import { dispatchRecordTddArtifactEffect } from "../lib/record-tdd-artifact.js";
|
|
3
3
|
import { recordTurnEffect } from "../lib/record-turn.js";
|
|
4
4
|
import { recordRunWorkspaceChangesEffect } from "../lib/record-workspace-changes.js";
|
|
5
5
|
import { DataReader, DataStore } from "@vitest-agent/sdk";
|
|
@@ -85,8 +85,12 @@ const testRunIdOpt = Flag.optional(Flag.integer("test-run-id"));
|
|
|
85
85
|
const testFirstFailureRunIdOpt = Flag.optional(Flag.integer("test-first-failure-run-id"));
|
|
86
86
|
const diffExcerptOpt = Flag.optional(Flag.string("diff-excerpt"));
|
|
87
87
|
const recordedAtOpt = Flag.string("recorded-at").pipe(Flag.withDefault((/* @__PURE__ */ new Date()).toISOString()));
|
|
88
|
+
const chatIdOptional = Flag.optional(Flag.string("chat-id")).pipe(Flag.withDescription("Host chat id; omit when --tdd-task-id is supplied instead"));
|
|
89
|
+
const tddTaskIdOpt = Flag.optional(Flag.integer("tdd-task-id")).pipe(Flag.withDescription("Explicit TDD task id escape hatch (issue #144): bypasses chat-id -> session -> task resolution entirely"));
|
|
90
|
+
const suiteOpt = Flag.choice("suite", ["vitest", "bats"]).pipe(Flag.withDefault("vitest"), Flag.withDescription("Explicit test-runner marker (issue #363): 'vitest' (default) or 'bats'. Distinguishes a bats run-level artifact (no test_case_id) from a vitest one for the D2 phase-transition validator."));
|
|
88
91
|
const tddArtifactSubcommand = Command.make("tdd-artifact", {
|
|
89
|
-
chatId,
|
|
92
|
+
chatId: chatIdOptional,
|
|
93
|
+
tddTaskId: tddTaskIdOpt,
|
|
90
94
|
project: projectOptional,
|
|
91
95
|
cwd: cwdOptional,
|
|
92
96
|
artifactKind: artifactKindOpt,
|
|
@@ -95,12 +99,14 @@ const tddArtifactSubcommand = Command.make("tdd-artifact", {
|
|
|
95
99
|
testRunId: testRunIdOpt,
|
|
96
100
|
testFirstFailureRunId: testFirstFailureRunIdOpt,
|
|
97
101
|
diffExcerpt: diffExcerptOpt,
|
|
98
|
-
recordedAt: recordedAtOpt
|
|
102
|
+
recordedAt: recordedAtOpt,
|
|
103
|
+
suite: suiteOpt
|
|
99
104
|
}, (opts) => Effect.gen(function* () {
|
|
100
105
|
let fileId;
|
|
101
106
|
if (opts.filePath._tag === "Some") fileId = yield* (yield* DataStore).ensureFile(opts.filePath.value);
|
|
102
|
-
return yield*
|
|
103
|
-
chatId: opts.chatId,
|
|
107
|
+
return yield* dispatchRecordTddArtifactEffect({
|
|
108
|
+
...opts.chatId._tag === "Some" && { chatId: opts.chatId.value },
|
|
109
|
+
...opts.tddTaskId._tag === "Some" && { tddTaskId: opts.tddTaskId.value },
|
|
104
110
|
...opts.project._tag === "Some" && { project: opts.project.value },
|
|
105
111
|
...opts.cwd._tag === "Some" && { cwd: opts.cwd.value },
|
|
106
112
|
artifactKind: opts.artifactKind,
|
|
@@ -109,7 +115,8 @@ const tddArtifactSubcommand = Command.make("tdd-artifact", {
|
|
|
109
115
|
...opts.testRunId._tag === "Some" && { testRunId: opts.testRunId.value },
|
|
110
116
|
...opts.testFirstFailureRunId._tag === "Some" && { testFirstFailureRunId: opts.testFirstFailureRunId.value },
|
|
111
117
|
...opts.diffExcerpt._tag === "Some" && { diffExcerpt: opts.diffExcerpt.value },
|
|
112
|
-
recordedAt: opts.recordedAt
|
|
118
|
+
recordedAt: opts.recordedAt,
|
|
119
|
+
suite: opts.suite
|
|
113
120
|
});
|
|
114
121
|
}).pipe(Effect.flatMap((result) => Effect.sync(() => process.stdout.write(`${JSON.stringify(result)}\n`))), Effect.catch((err) => Effect.sync(() => {
|
|
115
122
|
process.stderr.write(`record tdd-artifact: ${err instanceof Error ? err.message : String(err)}\n`);
|
package/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { DATA_DB_FILENAME, REGISTRY_DB_FILENAME, SESSIONS_DB_FILENAME, resolvePr
|
|
|
11
11
|
*
|
|
12
12
|
* @public
|
|
13
13
|
*/
|
|
14
|
-
const CURRENT_CLI_VERSION = "2.2.
|
|
14
|
+
const CURRENT_CLI_VERSION = "2.2.14";
|
|
15
15
|
|
|
16
16
|
//#endregion
|
|
17
17
|
export { CURRENT_CLI_VERSION, CliLive, DATA_DB_FILENAME, REGISTRY_DB_FILENAME, SESSIONS_DB_FILENAME, SidecarLive, registerAgentEffect, resolveProjectDataDir, resolveRegistryDir, resolveSessionMapPath };
|
|
@@ -39,10 +39,15 @@ const registerAgentEffect = (input) => Effect.gen(function* () {
|
|
|
39
39
|
agentKind: input.parentAgentId === void 0 ? "main" : "subagent",
|
|
40
40
|
agentType: input.agentType,
|
|
41
41
|
triageWasNonEmpty: false,
|
|
42
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43
|
+
conversationId
|
|
43
44
|
}),
|
|
44
45
|
onSome: (s) => Effect.succeed(s.id)
|
|
45
46
|
});
|
|
47
|
+
yield* store.setSessionConversationIdIfNull({
|
|
48
|
+
sessionId: sessionRowId,
|
|
49
|
+
conversationId
|
|
50
|
+
});
|
|
46
51
|
const agentContext = yield* ctx.captureAgentContext(input.cwd);
|
|
47
52
|
const clientNonce = input.clientNonce ?? deriveDefaultClientNonce(input);
|
|
48
53
|
const idempotencyKey = deriveIdempotencyKey({
|
|
@@ -3,20 +3,18 @@ import { DataReader, DataStore } from "@vitest-agent/sdk";
|
|
|
3
3
|
import { Effect, Option } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/lib/record-tdd-artifact.ts
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Resolve (auto-opening a `spike` phase if needed) the current open
|
|
8
|
+
* phase for `tddTaskId`, then write the artifact under it. Shared by
|
|
9
|
+
* both `recordTddArtifactEffect` (chat-id → session → task resolution)
|
|
10
|
+
* and `recordTddArtifactByTaskIdEffect` (explicit task-id escape hatch).
|
|
11
|
+
*/
|
|
12
|
+
const writeArtifactUnderOpenPhase = (tddTaskId, input) => Effect.gen(function* () {
|
|
7
13
|
const reader = yield* DataReader;
|
|
8
14
|
const store = yield* DataStore;
|
|
9
|
-
const
|
|
10
|
-
chatId: input.chatId,
|
|
11
|
-
recordedAt: input.recordedAt,
|
|
12
|
-
...input.project !== void 0 && { project: input.project },
|
|
13
|
-
...input.cwd !== void 0 && { cwd: input.cwd }
|
|
14
|
-
});
|
|
15
|
-
const openTdd = (yield* reader.listTddTasksForSession(session.id, { walkParents: true })).find((t) => t.endedAt === null);
|
|
16
|
-
if (openTdd === void 0) return yield* Effect.fail(/* @__PURE__ */ new Error(`No open TDD task under chat_id ${input.chatId}. Call tdd_task start first.`));
|
|
17
|
-
const phaseOpt = yield* reader.getCurrentTddPhase(openTdd.id);
|
|
15
|
+
const phaseOpt = yield* reader.getCurrentTddPhase(tddTaskId);
|
|
18
16
|
const phaseId = Option.isSome(phaseOpt) ? phaseOpt.value.id : (yield* store.writeTddPhase({
|
|
19
|
-
tddTaskId
|
|
17
|
+
tddTaskId,
|
|
20
18
|
phase: "spike",
|
|
21
19
|
startedAt: input.recordedAt,
|
|
22
20
|
transitionReason: "auto-opened by record tdd-artifact (no prior phase)"
|
|
@@ -30,11 +28,77 @@ const recordTddArtifactEffect = (input) => Effect.gen(function* () {
|
|
|
30
28
|
...input.testRunId !== void 0 && { testRunId: input.testRunId },
|
|
31
29
|
...input.testFirstFailureRunId !== void 0 && { testFirstFailureRunId: input.testFirstFailureRunId },
|
|
32
30
|
...input.diffExcerpt !== void 0 && { diffExcerpt: input.diffExcerpt },
|
|
31
|
+
...input.suite !== void 0 && { suite: input.suite },
|
|
33
32
|
recordedAt: input.recordedAt
|
|
34
33
|
}),
|
|
35
34
|
phaseId
|
|
36
35
|
};
|
|
37
36
|
});
|
|
37
|
+
const recordTddArtifactEffect = (input) => Effect.gen(function* () {
|
|
38
|
+
const reader = yield* DataReader;
|
|
39
|
+
const session = yield* resolveSessionForRecording({
|
|
40
|
+
chatId: input.chatId,
|
|
41
|
+
recordedAt: input.recordedAt,
|
|
42
|
+
...input.project !== void 0 && { project: input.project },
|
|
43
|
+
...input.cwd !== void 0 && { cwd: input.cwd }
|
|
44
|
+
});
|
|
45
|
+
const openTdd = (yield* reader.listTddTasksForSession(session.id, {
|
|
46
|
+
walkParents: true,
|
|
47
|
+
walkConversation: true
|
|
48
|
+
})).find((t) => t.endedAt === null);
|
|
49
|
+
if (openTdd === void 0) return yield* Effect.fail(/* @__PURE__ */ new Error(`No open TDD task under chat_id ${input.chatId}. Call tdd_task start first.`));
|
|
50
|
+
return yield* writeArtifactUnderOpenPhase(openTdd.id, input);
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Explicit task-id escape hatch (issue #144). Bypasses `chat_id` →
|
|
54
|
+
* session → task resolution entirely — the caller already knows which
|
|
55
|
+
* TDD task the artifact belongs to (e.g. a hook seeded with
|
|
56
|
+
* `VITEST_AGENT_TDD_TASK_ID` in a detached-session environment where
|
|
57
|
+
* neither the parent walk nor the conversation-id fallback resolves
|
|
58
|
+
* the right task). Fails loudly when the task does not exist or has
|
|
59
|
+
* already ended — writing to a closed task's phase would silently
|
|
60
|
+
* corrupt the evidence trail.
|
|
61
|
+
*/
|
|
62
|
+
const recordTddArtifactByTaskIdEffect = (input) => Effect.gen(function* () {
|
|
63
|
+
const taskOpt = yield* (yield* DataReader).getTddTaskById(input.tddTaskId);
|
|
64
|
+
if (Option.isNone(taskOpt)) return yield* Effect.fail(/* @__PURE__ */ new Error(`No TDD task found for id ${input.tddTaskId}.`));
|
|
65
|
+
if (taskOpt.value.endedAt !== null) return yield* Effect.fail(/* @__PURE__ */ new Error(`TDD task ${input.tddTaskId} has already ended (endedAt=${taskOpt.value.endedAt}).`));
|
|
66
|
+
return yield* writeArtifactUnderOpenPhase(input.tddTaskId, input);
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* CLI-command-facing dispatcher (issue #144). Keeps `commands/record.ts`
|
|
70
|
+
* a thin flag-parsing wrapper per this package's convention. `tddTaskId`
|
|
71
|
+
* takes priority when both are supplied — it is the explicit escape
|
|
72
|
+
* hatch and should never silently fall back to (weaker) session
|
|
73
|
+
* resolution.
|
|
74
|
+
*/
|
|
75
|
+
const dispatchRecordTddArtifactEffect = (input) => {
|
|
76
|
+
if (input.tddTaskId !== void 0) return recordTddArtifactByTaskIdEffect({
|
|
77
|
+
tddTaskId: input.tddTaskId,
|
|
78
|
+
artifactKind: input.artifactKind,
|
|
79
|
+
...input.fileId !== void 0 && { fileId: input.fileId },
|
|
80
|
+
...input.testCaseId !== void 0 && { testCaseId: input.testCaseId },
|
|
81
|
+
...input.testRunId !== void 0 && { testRunId: input.testRunId },
|
|
82
|
+
...input.testFirstFailureRunId !== void 0 && { testFirstFailureRunId: input.testFirstFailureRunId },
|
|
83
|
+
...input.diffExcerpt !== void 0 && { diffExcerpt: input.diffExcerpt },
|
|
84
|
+
...input.suite !== void 0 && { suite: input.suite },
|
|
85
|
+
recordedAt: input.recordedAt
|
|
86
|
+
});
|
|
87
|
+
if (input.chatId !== void 0) return recordTddArtifactEffect({
|
|
88
|
+
chatId: input.chatId,
|
|
89
|
+
artifactKind: input.artifactKind,
|
|
90
|
+
...input.fileId !== void 0 && { fileId: input.fileId },
|
|
91
|
+
...input.testCaseId !== void 0 && { testCaseId: input.testCaseId },
|
|
92
|
+
...input.testRunId !== void 0 && { testRunId: input.testRunId },
|
|
93
|
+
...input.testFirstFailureRunId !== void 0 && { testFirstFailureRunId: input.testFirstFailureRunId },
|
|
94
|
+
...input.diffExcerpt !== void 0 && { diffExcerpt: input.diffExcerpt },
|
|
95
|
+
...input.suite !== void 0 && { suite: input.suite },
|
|
96
|
+
recordedAt: input.recordedAt,
|
|
97
|
+
...input.cwd !== void 0 && { cwd: input.cwd },
|
|
98
|
+
...input.project !== void 0 && { project: input.project }
|
|
99
|
+
});
|
|
100
|
+
return Effect.fail(/* @__PURE__ */ new Error("record tdd-artifact requires either --chat-id or --tdd-task-id."));
|
|
101
|
+
};
|
|
38
102
|
|
|
39
103
|
//#endregion
|
|
40
|
-
export { recordTddArtifactEffect };
|
|
104
|
+
export { dispatchRecordTddArtifactEffect, recordTddArtifactByTaskIdEffect, recordTddArtifactEffect };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/cli",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "On-demand CLI for vitest-agent. Reads cached test data and reports status, overview, coverage, history, trends, and cache health.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@effect/platform-node": "4.0.0-rc.109",
|
|
42
42
|
"@effect/sql-sqlite-node": "4.0.0-rc.109",
|
|
43
|
-
"@effected/workspaces": "^0.
|
|
44
|
-
"@vitest-agent/sdk": "2.
|
|
45
|
-
"@vitest-agent/sidecar": "2.1.
|
|
43
|
+
"@effected/workspaces": "^0.19.0",
|
|
44
|
+
"@vitest-agent/sdk": "2.5.0",
|
|
45
|
+
"@vitest-agent/sidecar": "2.1.14",
|
|
46
46
|
"effect": "4.0.0-rc.109"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|