@vitest-agent/cli 2.2.13 → 2.2.15
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 +48 -3
- package/commands/record.js +13 -6
- package/index.d.ts +11 -11
- package/index.js +1 -1
- package/lib/internal-register-agent.js +6 -1
- package/lib/record-tdd-artifact.js +76 -12
- package/package.json +7 -7
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";
|
|
@@ -23,7 +23,7 @@ import { resolveSidecarBinaryPath } from "@vitest-agent/sidecar";
|
|
|
23
23
|
* Commands intended for agents and hook scripts — humans typically do
|
|
24
24
|
* not invoke these directly. The group composes the hook-driven
|
|
25
25
|
* utilities (triage, wrapup, record) with the sidecar invocations
|
|
26
|
-
* called by
|
|
26
|
+
* called by plugins/claude-code/hooks/*.sh scripts (register-agent, end-agent,
|
|
27
27
|
* inject-env).
|
|
28
28
|
*
|
|
29
29
|
* The sidecar subcommands return plain text on stdout that the bash
|
|
@@ -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.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { DataReader, DataStore, PerClientSessionMapWriter, ProjectIdentityNotRes
|
|
|
16
16
|
* @param logFile - optional path for structured log output
|
|
17
17
|
* @public
|
|
18
18
|
*/
|
|
19
|
-
declare const CliLive: (dbPath: string, logLevel?: LogLevel.LogLevel, logFile?: string) => Layer.Layer<import("@vitest-agent/sdk").DataReader | import("@vitest-agent/sdk").DataStore | import("@vitest-agent/sdk").DetailResolver | import("@vitest-agent/sdk").EnvironmentDetector | import("@vitest-agent/sdk").ExecutorResolver | import("@vitest-agent/sdk").FormatSelector | import("@vitest-agent/sdk").HistoryTracker | import("@vitest-agent/sdk").OutputRenderer | import("@vitest-agent/sdk").ProjectDiscovery | import("effect/unstable/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | NodeServices.NodeServices, SqliteMigrator.MigrationError | import("effect/unstable/sql/SqlError").SqlError, never>;
|
|
19
|
+
export declare const CliLive: (dbPath: string, logLevel?: LogLevel.LogLevel, logFile?: string) => Layer.Layer<import("@vitest-agent/sdk").DataReader | import("@vitest-agent/sdk").DataStore | import("@vitest-agent/sdk").DetailResolver | import("@vitest-agent/sdk").EnvironmentDetector | import("@vitest-agent/sdk").ExecutorResolver | import("@vitest-agent/sdk").FormatSelector | import("@vitest-agent/sdk").HistoryTracker | import("@vitest-agent/sdk").OutputRenderer | import("@vitest-agent/sdk").ProjectDiscovery | import("effect/unstable/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | NodeServices.NodeServices, SqliteMigrator.MigrationError | import("effect/unstable/sql/SqlError").SqlError, never>;
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/layers/SidecarLive.d.ts
|
|
22
22
|
/**
|
|
@@ -42,7 +42,7 @@ interface SidecarPaths {
|
|
|
42
42
|
* @param paths - the three SQLite database paths to open
|
|
43
43
|
* @public
|
|
44
44
|
*/
|
|
45
|
-
declare const SidecarLive: (paths: SidecarPaths) => Layer.Layer<import("@vitest-agent/sdk").DataReader | import("@vitest-agent/sdk").DataStore | import("@vitest-agent/sdk").DiscoveryRegistry | import("@vitest-agent/sdk").PerClientSessionMapReader | import("@vitest-agent/sdk").PerClientSessionMapWriter | import("@vitest-agent/sdk").RunContext$ | NodeServices.NodeServices, SqliteMigrator.MigrationError | import("effect/unstable/sql/SqlError").SqlError, never>;
|
|
45
|
+
export declare const SidecarLive: (paths: SidecarPaths) => Layer.Layer<import("@vitest-agent/sdk").DataReader | import("@vitest-agent/sdk").DataStore | import("@vitest-agent/sdk").DiscoveryRegistry | import("@vitest-agent/sdk").PerClientSessionMapReader | import("@vitest-agent/sdk").PerClientSessionMapWriter | import("@vitest-agent/sdk").RunContext$ | NodeServices.NodeServices, SqliteMigrator.MigrationError | import("effect/unstable/sql/SqlError").SqlError, never>;
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/lib/internal-register-agent.d.ts
|
|
48
48
|
/**
|
|
@@ -95,7 +95,7 @@ interface RegisterAgentOutput {
|
|
|
95
95
|
* @returns an Effect resolving to `RegisterAgentOutput`
|
|
96
96
|
* @public
|
|
97
97
|
*/
|
|
98
|
-
declare const registerAgentEffect: (input: RegisterAgentInput) => Effect.Effect<{
|
|
98
|
+
export declare const registerAgentEffect: (input: RegisterAgentInput) => Effect.Effect<{
|
|
99
99
|
agentId: string;
|
|
100
100
|
conversationId: string;
|
|
101
101
|
mainAgentId: string;
|
|
@@ -109,19 +109,19 @@ declare const registerAgentEffect: (input: RegisterAgentInput) => Effect.Effect<
|
|
|
109
109
|
*
|
|
110
110
|
* @public
|
|
111
111
|
*/
|
|
112
|
-
declare const DATA_DB_FILENAME = "data.db";
|
|
112
|
+
export declare const DATA_DB_FILENAME = "data.db";
|
|
113
113
|
/**
|
|
114
114
|
* Filename of the per-client session-map SQLite database.
|
|
115
115
|
*
|
|
116
116
|
* @public
|
|
117
117
|
*/
|
|
118
|
-
declare const SESSIONS_DB_FILENAME = "sessions.db";
|
|
118
|
+
export declare const SESSIONS_DB_FILENAME = "sessions.db";
|
|
119
119
|
/**
|
|
120
120
|
* Filename of the global discovery-registry SQLite database.
|
|
121
121
|
*
|
|
122
122
|
* @public
|
|
123
123
|
*/
|
|
124
|
-
declare const REGISTRY_DB_FILENAME = "registry.db";
|
|
124
|
+
export declare const REGISTRY_DB_FILENAME = "registry.db";
|
|
125
125
|
/**
|
|
126
126
|
* Resolve (and create) the per-project data directory for the supplied
|
|
127
127
|
* normalized `projectKey`. Returns the directory; callers join
|
|
@@ -131,7 +131,7 @@ declare const REGISTRY_DB_FILENAME = "registry.db";
|
|
|
131
131
|
* @returns absolute path to the resolved (and created) data directory
|
|
132
132
|
* @public
|
|
133
133
|
*/
|
|
134
|
-
declare const resolveProjectDataDir: (projectKey: string) => string;
|
|
134
|
+
export declare const resolveProjectDataDir: (projectKey: string) => string;
|
|
135
135
|
/**
|
|
136
136
|
* Resolve (and create) the directory holding the global `registry.db`.
|
|
137
137
|
* Callers join {@link REGISTRY_DB_FILENAME} onto it.
|
|
@@ -139,7 +139,7 @@ declare const resolveProjectDataDir: (projectKey: string) => string;
|
|
|
139
139
|
* @returns absolute path to the resolved (and created) registry directory
|
|
140
140
|
* @public
|
|
141
141
|
*/
|
|
142
|
-
declare const resolveRegistryDir: () => string;
|
|
142
|
+
export declare const resolveRegistryDir: () => string;
|
|
143
143
|
/**
|
|
144
144
|
* Resolve the per-client `sessions.db` path.
|
|
145
145
|
*
|
|
@@ -151,7 +151,7 @@ declare const resolveRegistryDir: () => string;
|
|
|
151
151
|
* @returns an Effect resolving to the absolute `sessions.db` path
|
|
152
152
|
* @public
|
|
153
153
|
*/
|
|
154
|
-
declare const resolveSessionMapPath: () => Effect.Effect<string, ProjectIdentityNotResolvableError>;
|
|
154
|
+
export declare const resolveSessionMapPath: () => Effect.Effect<string, ProjectIdentityNotResolvableError>;
|
|
155
155
|
//#endregion
|
|
156
156
|
//#region src/index.d.ts
|
|
157
157
|
/**
|
|
@@ -161,7 +161,7 @@ declare const resolveSessionMapPath: () => Effect.Effect<string, ProjectIdentity
|
|
|
161
161
|
*
|
|
162
162
|
* @public
|
|
163
163
|
*/
|
|
164
|
-
declare const CURRENT_CLI_VERSION: string;
|
|
164
|
+
export declare const CURRENT_CLI_VERSION: string;
|
|
165
165
|
//#endregion
|
|
166
|
-
export {
|
|
166
|
+
export type { RegisterAgentInput, RegisterAgentOutput, SidecarPaths };
|
|
167
167
|
//# sourceMappingURL=index.d.ts.map
|
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.15";
|
|
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.15",
|
|
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": [
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
"vitest-agent": "bin/vitest-agent.js"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@effect/platform-node": "4.0.0-rc.
|
|
42
|
-
"@effect/sql-sqlite-node": "4.0.0-rc.
|
|
43
|
-
"@effected/workspaces": "^0.
|
|
44
|
-
"@vitest-agent/sdk": "2.
|
|
45
|
-
"@vitest-agent/sidecar": "2.1.
|
|
46
|
-
"effect": "4.0.0-rc.
|
|
41
|
+
"@effect/platform-node": "4.0.0-rc.112",
|
|
42
|
+
"@effect/sql-sqlite-node": "4.0.0-rc.112",
|
|
43
|
+
"@effected/workspaces": "^0.20.0",
|
|
44
|
+
"@vitest-agent/sdk": "2.5.1",
|
|
45
|
+
"@vitest-agent/sidecar": "2.1.15",
|
|
46
|
+
"effect": "4.0.0-rc.112"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=24.11.0"
|