@vitest-agent/engine 0.2.11 → 0.3.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 +36 -1
- package/index.js +2 -2
- package/package.json +1 -1
- package/programs/session-env.js +15 -6
- package/utils/resolve-data-path.js +2 -2
- package/utils/resolve-project-key-from-cwd.js +62 -12
- package/version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -2427,6 +2427,21 @@ export interface SessionContext {
|
|
|
2427
2427
|
* @public
|
|
2428
2428
|
*/
|
|
2429
2429
|
export declare const parseSessionEnvExports: (content: string) => Record<string, string>;
|
|
2430
|
+
/**
|
|
2431
|
+
* The synchronous filesystem reads {@link recoverSessionContextFromSessionEnv}
|
|
2432
|
+
* performs. Each member throws on a missing or unreadable path, exactly as
|
|
2433
|
+
* the `node:fs` call it stands in for; recovery treats a throw as "skip".
|
|
2434
|
+
*
|
|
2435
|
+
* @public
|
|
2436
|
+
*/
|
|
2437
|
+
export interface SessionEnvFileSystem {
|
|
2438
|
+
/** Entry names directly under `path` (`readdirSync`). */
|
|
2439
|
+
readonly readDirectory: (path: string) => ReadonlyArray<string>;
|
|
2440
|
+
/** The UTF-8 text of the file at `path` (`readFileSync(path, "utf8")`). */
|
|
2441
|
+
readonly readFile: (path: string) => string;
|
|
2442
|
+
/** Modification time of the file at `path`, in epoch milliseconds (`statSync(path).mtimeMs`). */
|
|
2443
|
+
readonly mtimeMs: (path: string) => number;
|
|
2444
|
+
}
|
|
2430
2445
|
/**
|
|
2431
2446
|
* Recover a {@link SessionContext} from the newest session-env hook file
|
|
2432
2447
|
* whose `VITEST_AGENT_PROJECT_DIR` matches `projectDir`.
|
|
@@ -2440,12 +2455,15 @@ export declare const parseSessionEnvExports: (content: string) => Record<string,
|
|
|
2440
2455
|
*
|
|
2441
2456
|
* @param opts - `projectDir` to match against; `homeDir` is the caller's
|
|
2442
2457
|
* home directory (ambient input — the MCP bin passes `os.homedir()`),
|
|
2443
|
-
* under which `.claude/session-env` is read
|
|
2458
|
+
* under which `.claude/session-env` is read; `fileSystem` replaces the
|
|
2459
|
+
* `node:fs` reads (tests pass an in-memory volume) and defaults to the
|
|
2460
|
+
* real disk
|
|
2444
2461
|
* @public
|
|
2445
2462
|
*/
|
|
2446
2463
|
export declare const recoverSessionContextFromSessionEnv: (opts: {
|
|
2447
2464
|
readonly projectDir: string;
|
|
2448
2465
|
readonly homeDir: string;
|
|
2466
|
+
readonly fileSystem?: SessionEnvFileSystem;
|
|
2449
2467
|
}) => SessionContext | null;
|
|
2450
2468
|
//#endregion
|
|
2451
2469
|
//#region src/project-dir.d.ts
|
|
@@ -2650,9 +2668,26 @@ export declare const resolveDataPath: (projectDir: string, options?: ResolveData
|
|
|
2650
2668
|
* Always returns a non-empty string — falls back to the cwd basename
|
|
2651
2669
|
* (or `"anonymous-project"`) so callers don't have to handle the
|
|
2652
2670
|
* empty case.
|
|
2671
|
+
*
|
|
2672
|
+
* Synchronous and reads the real disk through `node:fs`; Effect callers
|
|
2673
|
+
* that already hold a `FileSystem` use
|
|
2674
|
+
* {@link resolveProjectKeyFromCwdEffect}, which applies the same rules.
|
|
2653
2675
|
* @public
|
|
2654
2676
|
*/
|
|
2655
2677
|
export declare const resolveProjectKeyFromCwd: (cwd: string) => string;
|
|
2678
|
+
/**
|
|
2679
|
+
* {@link resolveProjectKeyFromCwd} read through the ambient
|
|
2680
|
+
* `FileSystem` service instead of `node:fs` — same upward walk to the
|
|
2681
|
+
* nearest `package.json`, same precedence, same basename fallback.
|
|
2682
|
+
*
|
|
2683
|
+
* Never fails: a filesystem error while probing or reading is treated like
|
|
2684
|
+
* a missing or malformed `package.json`, exactly as the synchronous form
|
|
2685
|
+
* does.
|
|
2686
|
+
*
|
|
2687
|
+
* @param cwd - Absolute directory to start the upward walk from.
|
|
2688
|
+
* @public
|
|
2689
|
+
*/
|
|
2690
|
+
export declare const resolveProjectKeyFromCwdEffect: (cwd: string) => Effect.Effect<string, never, FileSystem.FileSystem>;
|
|
2656
2691
|
//#endregion
|
|
2657
2692
|
//#region src/utils/resolve-workspace-key.d.ts
|
|
2658
2693
|
/**
|
package/index.js
CHANGED
|
@@ -55,9 +55,9 @@ import { resolveProjectDir } from "./project-dir.js";
|
|
|
55
55
|
import { assembleHistoryRecord, assembleManifest } from "./sql/assemblers.js";
|
|
56
56
|
import { _resetMigrationCacheForTesting, ensureMigrated } from "./utils/ensure-migrated.js";
|
|
57
57
|
import { computeFailureSignature, normalizeAssertionShape } from "./utils/failure-signature.js";
|
|
58
|
-
import { resolveProjectKeyFromCwd } from "./utils/resolve-project-key-from-cwd.js";
|
|
58
|
+
import { resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect } from "./utils/resolve-project-key-from-cwd.js";
|
|
59
59
|
import { DATABASE_FILENAME, resolveDataPath } from "./utils/resolve-data-path.js";
|
|
60
60
|
import { resolveWorkspaceKey } from "./utils/resolve-workspace-key.js";
|
|
61
61
|
import { CURRENT_ENGINE_VERSION } from "./version.js";
|
|
62
62
|
|
|
63
|
-
export { APP_NAMESPACE, AgentContext, CURRENT_ENGINE_VERSION, ConfigLive, DATABASE_FILENAME, DATA_DB_FILENAME, DATA_FALLBACK_DIR, DataReader, DataReaderLive, DataStore, DataStoreLive, DetailResolver, DetailResolverLive, DiscoveryRegistry, DiscoveryRegistryLive, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, ExecutorResolver, ExecutorResolverLive, FormatSelector, FormatSelectorLive, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, IDEMPOTENCY_ROOT_SENTINEL, INLINE_ATTACHMENT_BODY_CAP_BYTES, LoggerLive, NodePlatformLayer, OutputPipelineLive, OutputRenderer, OutputRendererLive, PROJECT_MIGRATIONS, PathResolutionLive, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PlatformLive, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityTest, REGISTRY_DB_FILENAME, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, SESSIONS_DB_FILENAME, SidecarPlatformLive, VitestAgentConfigFile, _resetMigrationCacheForTesting, assembleHistoryRecord, assembleManifest, classifyEnvironment, computeFailureSignature, deriveIdempotencyKey, dispatchRecordTddArtifactEffect, endAgentEffect, ensureMigrated, formatTriageEffect, formatWrapupEffect, historyKey, makeSqliteStack, migration as migration0001, migration$1 as migration0002, normalizeAssertionShape, parseAndValidateTurnPayload, parseSessionEnvExports, recordRunWorkspaceChangesEffect, recordSessionEnd, recordSessionStart, recordTddArtifactByTaskIdEffect, recordTddArtifactEffect, recordTurnEffect, recoverSessionContextFromSessionEnv, registerAgentEffect, migration$2 as registryMigration0001, resolveDataPath, resolveHookPaths, resolveLogFile, resolveLogLevel, resolveProjectDir, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveSessionForRecording, resolveSessionMapPath, resolveWorkspaceKey, migration$3 as sessionMapMigration0001 };
|
|
63
|
+
export { APP_NAMESPACE, AgentContext, CURRENT_ENGINE_VERSION, ConfigLive, DATABASE_FILENAME, DATA_DB_FILENAME, DATA_FALLBACK_DIR, DataReader, DataReaderLive, DataStore, DataStoreLive, DetailResolver, DetailResolverLive, DiscoveryRegistry, DiscoveryRegistryLive, EnvironmentDetector, EnvironmentDetectorLive, EnvironmentDetectorTest, ExecutorResolver, ExecutorResolverLive, FormatSelector, FormatSelectorLive, HistoryTracker, HistoryTrackerLive, HistoryTrackerTest, IDEMPOTENCY_ROOT_SENTINEL, INLINE_ATTACHMENT_BODY_CAP_BYTES, LoggerLive, NodePlatformLayer, OutputPipelineLive, OutputRenderer, OutputRendererLive, PROJECT_MIGRATIONS, PathResolutionLive, PerClientSessionMapReader, PerClientSessionMapReaderLive, PerClientSessionMapWriter, PerClientSessionMapWriterLive, PlatformLive, ProjectDiscovery, ProjectDiscoveryLive, ProjectDiscoveryTest, ProjectIdentity, ProjectIdentityLive, ProjectIdentityTest, REGISTRY_DB_FILENAME, RunContext, RunContext$, RunContextLive, RunContextService, RunContextTest, SESSIONS_DB_FILENAME, SidecarPlatformLive, VitestAgentConfigFile, _resetMigrationCacheForTesting, assembleHistoryRecord, assembleManifest, classifyEnvironment, computeFailureSignature, deriveIdempotencyKey, dispatchRecordTddArtifactEffect, endAgentEffect, ensureMigrated, formatTriageEffect, formatWrapupEffect, historyKey, makeSqliteStack, migration as migration0001, migration$1 as migration0002, normalizeAssertionShape, parseAndValidateTurnPayload, parseSessionEnvExports, recordRunWorkspaceChangesEffect, recordSessionEnd, recordSessionStart, recordTddArtifactByTaskIdEffect, recordTddArtifactEffect, recordTurnEffect, recoverSessionContextFromSessionEnv, registerAgentEffect, migration$2 as registryMigration0001, resolveDataPath, resolveHookPaths, resolveLogFile, resolveLogLevel, resolveProjectDir, resolveProjectIdentityFromCandidates, resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect, resolveSessionForRecording, resolveSessionMapPath, resolveWorkspaceKey, migration$3 as sessionMapMigration0001 };
|
package/package.json
CHANGED
package/programs/session-env.js
CHANGED
|
@@ -32,6 +32,12 @@ const parseSessionEnvExports = (content) => {
|
|
|
32
32
|
}
|
|
33
33
|
return out;
|
|
34
34
|
};
|
|
35
|
+
/** The default {@link SessionEnvFileSystem}: the real disk through `node:fs`. */
|
|
36
|
+
const nodeSessionEnvFileSystem = {
|
|
37
|
+
readDirectory: (path) => readdirSync(path),
|
|
38
|
+
readFile: (path) => readFileSync(path, "utf8"),
|
|
39
|
+
mtimeMs: (path) => statSync(path).mtimeMs
|
|
40
|
+
};
|
|
35
41
|
/**
|
|
36
42
|
* Recover a {@link SessionContext} from the newest session-env hook file
|
|
37
43
|
* whose `VITEST_AGENT_PROJECT_DIR` matches `projectDir`.
|
|
@@ -45,15 +51,18 @@ const parseSessionEnvExports = (content) => {
|
|
|
45
51
|
*
|
|
46
52
|
* @param opts - `projectDir` to match against; `homeDir` is the caller's
|
|
47
53
|
* home directory (ambient input — the MCP bin passes `os.homedir()`),
|
|
48
|
-
* under which `.claude/session-env` is read
|
|
54
|
+
* under which `.claude/session-env` is read; `fileSystem` replaces the
|
|
55
|
+
* `node:fs` reads (tests pass an in-memory volume) and defaults to the
|
|
56
|
+
* real disk
|
|
49
57
|
* @public
|
|
50
58
|
*/
|
|
51
59
|
const recoverSessionContextFromSessionEnv = (opts) => {
|
|
60
|
+
const fs = opts.fileSystem ?? nodeSessionEnvFileSystem;
|
|
52
61
|
const root = join(opts.homeDir, ".claude", "session-env");
|
|
53
62
|
const wantDir = resolve(opts.projectDir);
|
|
54
63
|
let entries;
|
|
55
64
|
try {
|
|
56
|
-
entries =
|
|
65
|
+
entries = fs.readDirectory(root);
|
|
57
66
|
} catch {
|
|
58
67
|
return null;
|
|
59
68
|
}
|
|
@@ -61,15 +70,15 @@ const recoverSessionContextFromSessionEnv = (opts) => {
|
|
|
61
70
|
for (const entry of entries) {
|
|
62
71
|
const file = join(root, entry, "vitest-agent-hook.sh");
|
|
63
72
|
try {
|
|
64
|
-
const
|
|
65
|
-
const env = parseSessionEnvExports(
|
|
73
|
+
const mtimeMs = fs.mtimeMs(file);
|
|
74
|
+
const env = parseSessionEnvExports(fs.readFile(file));
|
|
66
75
|
const chatId = env.VITEST_AGENT_CHAT_ID;
|
|
67
76
|
const conversationId = env.VITEST_AGENT_CONVERSATION_ID;
|
|
68
77
|
const mainAgentId = env.VITEST_AGENT_MAIN_AGENT_ID ?? env.VITEST_AGENT_AGENT_ID;
|
|
69
78
|
const fileProjectDir = env.VITEST_AGENT_PROJECT_DIR;
|
|
70
79
|
if (chatId === void 0 || chatId.length === 0 || conversationId === void 0 || conversationId.length === 0 || mainAgentId === void 0 || mainAgentId.length === 0 || fileProjectDir === void 0 || resolve(fileProjectDir) !== wantDir) continue;
|
|
71
|
-
if (best === null ||
|
|
72
|
-
mtimeMs
|
|
80
|
+
if (best === null || mtimeMs > best.mtimeMs) best = {
|
|
81
|
+
mtimeMs,
|
|
73
82
|
ctx: {
|
|
74
83
|
chatId,
|
|
75
84
|
conversationId,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VitestAgentConfigFile } from "../services/Config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveProjectKeyFromCwdEffect } from "./resolve-project-key-from-cwd.js";
|
|
3
3
|
import { VitestAgentConfig, normalizeWorkspaceKey } from "@vitest-agent/sdk";
|
|
4
4
|
import { Effect, FileSystem } from "effect";
|
|
5
5
|
import { AppDirs } from "@effected/xdg";
|
|
@@ -54,7 +54,7 @@ const resolveDataPath = (projectDir, options = {}) => Effect.gen(function* () {
|
|
|
54
54
|
return join(loaded.cacheDir, DATABASE_FILENAME);
|
|
55
55
|
}
|
|
56
56
|
const dataRoot = yield* (yield* AppDirs).ensureData;
|
|
57
|
-
const key = loaded.projectKey ? normalizeWorkspaceKey(loaded.projectKey) :
|
|
57
|
+
const key = loaded.projectKey ? normalizeWorkspaceKey(loaded.projectKey) : yield* resolveProjectKeyFromCwdEffect(projectDir);
|
|
58
58
|
const dir = join(dataRoot, key);
|
|
59
59
|
yield* fs.makeDirectory(dir, { recursive: true });
|
|
60
60
|
return join(dir, DATABASE_FILENAME);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { gitUrlToProjectKey, normalizeWorkspaceKey } from "@vitest-agent/sdk";
|
|
2
|
+
import { Effect, FileSystem } from "effect";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
4
5
|
|
|
@@ -24,6 +25,27 @@ const readRepositoryUrl = (parsed) => {
|
|
|
24
25
|
return null;
|
|
25
26
|
};
|
|
26
27
|
/**
|
|
28
|
+
* The key a `package.json` body names: the canonical `host__path` form of
|
|
29
|
+
* `repository.url` when present and canonicalizable, else the normalized
|
|
30
|
+
* `name`, else `null` (no usable field, or malformed JSON).
|
|
31
|
+
*/
|
|
32
|
+
const keyFromPackageJson = (content) => {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(content);
|
|
35
|
+
const repoUrl = readRepositoryUrl(parsed);
|
|
36
|
+
if (repoUrl !== null) {
|
|
37
|
+
const key = gitUrlToProjectKey(repoUrl);
|
|
38
|
+
if (key !== null) return key;
|
|
39
|
+
}
|
|
40
|
+
if (typeof parsed.name === "string" && parsed.name.length > 0) return normalizeWorkspaceKey(parsed.name);
|
|
41
|
+
} catch {}
|
|
42
|
+
return null;
|
|
43
|
+
};
|
|
44
|
+
const basenameKey = (cwd) => {
|
|
45
|
+
const basename = cwd.split("/").filter((segment) => segment.length > 0).pop() ?? "anonymous-project";
|
|
46
|
+
return normalizeWorkspaceKey(basename);
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
27
49
|
* Compute the project key for a workspace by reading `package.json`
|
|
28
50
|
* fields directly. Returns the canonical `host__path` form when a git
|
|
29
51
|
* remote URL is present, otherwise the normalized package name.
|
|
@@ -31,22 +53,50 @@ const readRepositoryUrl = (parsed) => {
|
|
|
31
53
|
* Always returns a non-empty string — falls back to the cwd basename
|
|
32
54
|
* (or `"anonymous-project"`) so callers don't have to handle the
|
|
33
55
|
* empty case.
|
|
56
|
+
*
|
|
57
|
+
* Synchronous and reads the real disk through `node:fs`; Effect callers
|
|
58
|
+
* that already hold a `FileSystem` use
|
|
59
|
+
* {@link resolveProjectKeyFromCwdEffect}, which applies the same rules.
|
|
34
60
|
* @public
|
|
35
61
|
*/
|
|
36
62
|
const resolveProjectKeyFromCwd = (cwd) => {
|
|
37
63
|
const pkgPath = findNearestPackageJson(cwd);
|
|
38
|
-
if (pkgPath !== null)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const basename = cwd.split("/").filter((segment) => segment.length > 0).pop() ?? "anonymous-project";
|
|
48
|
-
return normalizeWorkspaceKey(basename);
|
|
64
|
+
if (pkgPath !== null) {
|
|
65
|
+
let content = null;
|
|
66
|
+
try {
|
|
67
|
+
content = readFileSync(pkgPath, "utf-8");
|
|
68
|
+
} catch {}
|
|
69
|
+
const key = content === null ? null : keyFromPackageJson(content);
|
|
70
|
+
if (key !== null) return key;
|
|
71
|
+
}
|
|
72
|
+
return basenameKey(cwd);
|
|
49
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* {@link resolveProjectKeyFromCwd} read through the ambient
|
|
76
|
+
* `FileSystem` service instead of `node:fs` — same upward walk to the
|
|
77
|
+
* nearest `package.json`, same precedence, same basename fallback.
|
|
78
|
+
*
|
|
79
|
+
* Never fails: a filesystem error while probing or reading is treated like
|
|
80
|
+
* a missing or malformed `package.json`, exactly as the synchronous form
|
|
81
|
+
* does.
|
|
82
|
+
*
|
|
83
|
+
* @param cwd - Absolute directory to start the upward walk from.
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
const resolveProjectKeyFromCwdEffect = (cwd) => Effect.gen(function* () {
|
|
87
|
+
const fs = yield* FileSystem.FileSystem;
|
|
88
|
+
let dir = cwd;
|
|
89
|
+
while (true) {
|
|
90
|
+
const candidate = join(dir, PACKAGE_JSON);
|
|
91
|
+
if (yield* fs.exists(candidate).pipe(Effect.orElseSucceed(() => false))) {
|
|
92
|
+
const content = yield* fs.readFileString(candidate).pipe(Effect.orElseSucceed(() => null));
|
|
93
|
+
return (content === null ? null : keyFromPackageJson(content)) ?? basenameKey(cwd);
|
|
94
|
+
}
|
|
95
|
+
const parent = dirname(dir);
|
|
96
|
+
if (parent === dir) return basenameKey(cwd);
|
|
97
|
+
dir = parent;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
50
100
|
|
|
51
101
|
//#endregion
|
|
52
|
-
export { resolveProjectKeyFromCwd };
|
|
102
|
+
export { resolveProjectKeyFromCwd, resolveProjectKeyFromCwdEffect };
|
package/version.js
CHANGED