@vitest-agent/sdk 3.1.2 → 5.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/README.md +23 -19
- package/dispatch.d.ts +25 -6
- package/formatters/markdown.js +1 -0
- package/formatters/terminal.js +4 -3
- package/index.d.ts +454 -2343
- package/index.js +10 -68
- package/internal-inject-env.js +5 -7
- package/package.json +3 -19
- package/schemas/{run-report-file-1.0.0.json → 5.0/run.json} +1 -1
- package/schemas/RunReportFile.js +6 -2
- package/sidecar-dispatch.js +7 -5
- package/utils/format-console.js +9 -10
- package/utils/format-terminal.js +7 -7
- package/utils/posix-path.js +52 -0
- package/utils/test-location.js +11 -5
- package/version.js +17 -0
- package/layers/ConfigLive.js +0 -46
- package/layers/DataReaderLive.js +0 -1923
- package/layers/DataStoreLive.js +0 -1287
- package/layers/DetailResolverLive.js +0 -9
- package/layers/DiscoveryRegistryLive.js +0 -70
- package/layers/EnvironmentDetectorLive.js +0 -21
- package/layers/EnvironmentDetectorTest.js +0 -13
- package/layers/ExecutorResolverLive.js +0 -9
- package/layers/FormatSelectorLive.js +0 -20
- package/layers/HistoryTrackerLive.js +0 -49
- package/layers/HistoryTrackerTest.js +0 -25
- package/layers/LoggerLive.js +0 -64
- package/layers/OutputPipelineLive.js +0 -13
- package/layers/OutputRendererLive.js +0 -28
- package/layers/PathResolutionLive.js +0 -40
- package/layers/PerClientSessionMapLive.js +0 -145
- package/layers/ProjectDiscoveryLive.js +0 -68
- package/layers/ProjectDiscoveryTest.js +0 -12
- package/layers/ProjectIdentityLive.js +0 -96
- package/layers/RunContextLive.js +0 -81
- package/lib/format-triage.js +0 -103
- package/lib/format-wrapup.js +0 -67
- package/migrations/0001_initial.js +0 -824
- package/migrations/0002_test_artifacts.js +0 -30
- package/migrations/index.js +0 -26
- package/migrations/registry_0001_initial.js +0 -39
- package/migrations/session_map_0001_initial.js +0 -38
- package/services/Config.js +0 -15
- package/services/DataReader.js +0 -8
- package/services/DataStore.js +0 -15
- package/services/DetailResolver.js +0 -8
- package/services/DiscoveryRegistry.js +0 -8
- package/services/EnvironmentDetector.js +0 -8
- package/services/ExecutorResolver.js +0 -8
- package/services/FormatSelector.js +0 -8
- package/services/HistoryTracker.js +0 -20
- package/services/OutputRenderer.js +0 -8
- package/services/PerClientSessionMap.js +0 -22
- package/services/ProjectDiscovery.js +0 -8
- package/services/ProjectIdentity.js +0 -63
- package/services/RunContext.js +0 -53
- package/services/idempotency.js +0 -58
- package/sql/assemblers.js +0 -69
- package/testing/layers.js +0 -21
- package/testing.d.ts +0 -1862
- package/testing.js +0 -254
- package/utils/ensure-migrated.js +0 -52
- package/utils/failure-signature.js +0 -34
- package/utils/resolve-data-path.js +0 -65
- package/utils/resolve-project-key-from-cwd.js +0 -53
- package/utils/resolve-workspace-key.js +0 -41
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
3
|
-
|
|
4
|
-
//#region src/migrations/0002_test_artifacts.ts
|
|
5
|
-
/** @internal */
|
|
6
|
-
const migration = Effect.gen(function* () {
|
|
7
|
-
const sql = yield* SqlClient;
|
|
8
|
-
yield* sql`DROP INDEX IF EXISTS idx_test_annotations_case`;
|
|
9
|
-
yield* sql`DROP INDEX IF EXISTS idx_test_annotations_type`;
|
|
10
|
-
yield* sql`DROP TABLE IF EXISTS test_annotations`;
|
|
11
|
-
yield* sql`
|
|
12
|
-
CREATE TABLE test_annotations (
|
|
13
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14
|
-
test_case_id INTEGER NOT NULL REFERENCES test_cases(id) ON DELETE CASCADE,
|
|
15
|
-
type TEXT NOT NULL,
|
|
16
|
-
message TEXT NOT NULL,
|
|
17
|
-
location_file_id INTEGER REFERENCES files(id),
|
|
18
|
-
location_line INTEGER,
|
|
19
|
-
location_column INTEGER
|
|
20
|
-
)
|
|
21
|
-
`;
|
|
22
|
-
yield* sql`CREATE INDEX idx_test_annotations_case ON test_annotations(test_case_id)`;
|
|
23
|
-
yield* sql`CREATE INDEX idx_test_annotations_type ON test_annotations(type)`;
|
|
24
|
-
yield* sql`ALTER TABLE test_artifacts ADD COLUMN data TEXT`;
|
|
25
|
-
yield* sql`ALTER TABLE attachments ADD COLUMN byte_size INTEGER`;
|
|
26
|
-
yield* sql`ALTER TABLE attachments ADD COLUMN body_encoding TEXT`;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
//#endregion
|
|
30
|
-
export { migration as default };
|
package/migrations/index.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import migration from "./0001_initial.js";
|
|
2
|
-
import migration$1 from "./0002_test_artifacts.js";
|
|
3
|
-
|
|
4
|
-
//#region src/migrations/index.ts
|
|
5
|
-
/**
|
|
6
|
-
* Every migration that must run against a per-project `data.db`, keyed by
|
|
7
|
-
* migration id in application order.
|
|
8
|
-
*
|
|
9
|
-
* This is the single source of truth for the project database's migration
|
|
10
|
-
* set: `ensureMigrated`, the sdk testing layer, `ReporterLive`, `McpLive`,
|
|
11
|
-
* `CliLive` and `SidecarLive` all pass it straight to
|
|
12
|
-
* `SqliteMigrator.fromRecord`, so adding a migration file and registering it
|
|
13
|
-
* here is enough to reach every process that opens a project database.
|
|
14
|
-
*
|
|
15
|
-
* The session-map and discovery-registry databases are separate schemas with
|
|
16
|
-
* their own migrations and are deliberately not listed here.
|
|
17
|
-
*
|
|
18
|
-
* @public
|
|
19
|
-
*/
|
|
20
|
-
const PROJECT_MIGRATIONS = {
|
|
21
|
-
"0001_initial": migration,
|
|
22
|
-
"0002_test_artifacts": migration$1
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
//#endregion
|
|
26
|
-
export { PROJECT_MIGRATIONS };
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
3
|
-
|
|
4
|
-
//#region src/migrations/registry_0001_initial.ts
|
|
5
|
-
/**
|
|
6
|
-
* Migration for the global discovery registry SQLite at
|
|
7
|
-
* `$XDG_DATA_HOME/vitest-agent/registry.db`.
|
|
8
|
-
*
|
|
9
|
-
* Single table `known_projects` indexed by `project_key` (the
|
|
10
|
-
* filesystem-safe form from {@link ProjectIdentity}). Tooling like
|
|
11
|
-
* `mcp-app` queries this table to enumerate every vitest-agent
|
|
12
|
-
* project the user has ever run on this machine.
|
|
13
|
-
*
|
|
14
|
-
* The registry never blocks anything — its writes are best-effort.
|
|
15
|
-
* Concurrent writers from multiple Claude Code windows converge via
|
|
16
|
-
* the `ON CONFLICT(project_key) DO UPDATE` upsert.
|
|
17
|
-
* @public
|
|
18
|
-
*/
|
|
19
|
-
const migration = Effect.gen(function* () {
|
|
20
|
-
const sql = yield* SqlClient;
|
|
21
|
-
yield* sql`PRAGMA journal_mode=WAL`;
|
|
22
|
-
yield* sql`PRAGMA foreign_keys=ON`;
|
|
23
|
-
yield* sql`PRAGMA busy_timeout=5000`;
|
|
24
|
-
yield* sql`
|
|
25
|
-
CREATE TABLE known_projects (
|
|
26
|
-
project_key TEXT PRIMARY KEY,
|
|
27
|
-
canonical_form TEXT NOT NULL,
|
|
28
|
-
data_db_path TEXT NOT NULL,
|
|
29
|
-
git_remote_origin TEXT,
|
|
30
|
-
workspace_root TEXT NOT NULL,
|
|
31
|
-
first_seen_at INTEGER NOT NULL,
|
|
32
|
-
last_seen_at INTEGER NOT NULL
|
|
33
|
-
) STRICT
|
|
34
|
-
`;
|
|
35
|
-
yield* sql`CREATE INDEX idx_known_projects_workspace_root ON known_projects(workspace_root)`;
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
//#endregion
|
|
39
|
-
export { migration as default };
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
3
|
-
|
|
4
|
-
//#region src/migrations/session_map_0001_initial.ts
|
|
5
|
-
/** @internal */
|
|
6
|
-
const migration = Effect.gen(function* () {
|
|
7
|
-
const sql = yield* SqlClient;
|
|
8
|
-
yield* sql`PRAGMA journal_mode=WAL`;
|
|
9
|
-
yield* sql`PRAGMA foreign_keys=ON`;
|
|
10
|
-
yield* sql`PRAGMA busy_timeout=5000`;
|
|
11
|
-
yield* sql`
|
|
12
|
-
CREATE TABLE conversation_map (
|
|
13
|
-
transcript_uuid TEXT PRIMARY KEY,
|
|
14
|
-
transcript_path TEXT NOT NULL,
|
|
15
|
-
conversation_id TEXT NOT NULL UNIQUE,
|
|
16
|
-
first_seen_at INTEGER NOT NULL,
|
|
17
|
-
last_seen_at INTEGER NOT NULL
|
|
18
|
-
) STRICT
|
|
19
|
-
`;
|
|
20
|
-
yield* sql`
|
|
21
|
-
CREATE TABLE session_map (
|
|
22
|
-
host_session_id TEXT PRIMARY KEY,
|
|
23
|
-
conversation_id TEXT NOT NULL REFERENCES conversation_map(conversation_id) ON DELETE RESTRICT,
|
|
24
|
-
project_key TEXT NOT NULL,
|
|
25
|
-
project_dir TEXT NOT NULL,
|
|
26
|
-
main_agent_id TEXT NOT NULL,
|
|
27
|
-
started_at INTEGER NOT NULL,
|
|
28
|
-
ended_at INTEGER
|
|
29
|
-
) STRICT
|
|
30
|
-
`;
|
|
31
|
-
yield* sql`CREATE INDEX idx_session_map_project_key ON session_map(project_key)`;
|
|
32
|
-
yield* sql`CREATE INDEX idx_session_map_project_dir ON session_map(project_dir)`;
|
|
33
|
-
yield* sql`CREATE INDEX idx_session_map_active ON session_map(project_dir) WHERE ended_at IS NULL`;
|
|
34
|
-
yield* sql`CREATE INDEX idx_session_map_conversation_id ON session_map(conversation_id)`;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
//#endregion
|
|
38
|
-
export { migration as default };
|
package/services/Config.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ConfigFile } from "@effected/config-file";
|
|
2
|
-
|
|
3
|
-
//#region src/services/Config.ts
|
|
4
|
-
/**
|
|
5
|
-
* Typed service class (Context tag) for the vitest-agent config file service.
|
|
6
|
-
*
|
|
7
|
-
* Both runtime packages (reporter, MCP) yield this tag to access the loaded
|
|
8
|
-
* `VitestAgentConfig`. The live layer is built per `projectDir` via
|
|
9
|
-
* `ConfigLive(projectDir)`.
|
|
10
|
-
* @public
|
|
11
|
-
*/
|
|
12
|
-
var VitestAgentConfigFile = class extends ConfigFile.Service()("vitest-agent/Config") {};
|
|
13
|
-
|
|
14
|
-
//#endregion
|
|
15
|
-
export { VitestAgentConfigFile };
|
package/services/DataReader.js
DELETED
package/services/DataStore.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Context } from "effect";
|
|
2
|
-
|
|
3
|
-
//#region src/services/DataStore.ts
|
|
4
|
-
/**
|
|
5
|
-
* Ceiling on an inline attachment body. Vitest has already copied file
|
|
6
|
-
* attachments into `.vitest/attachments/`, so a large body is referenced
|
|
7
|
-
* by `path` rather than duplicated into `data.db`.
|
|
8
|
-
* @public
|
|
9
|
-
*/
|
|
10
|
-
const INLINE_ATTACHMENT_BODY_CAP_BYTES = 65536;
|
|
11
|
-
/** @public */
|
|
12
|
-
var DataStore = class extends Context.Service()("vitest-agent/DataStore") {};
|
|
13
|
-
|
|
14
|
-
//#endregion
|
|
15
|
-
export { DataStore, INLINE_ATTACHMENT_BODY_CAP_BYTES };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Context } from "effect";
|
|
2
|
-
|
|
3
|
-
//#region src/services/HistoryTracker.ts
|
|
4
|
-
/**
|
|
5
|
-
* Builds the composite (modulePath, fullName) key used to key the internal
|
|
6
|
-
* testMap and the returned classifications Map, so identically-named tests
|
|
7
|
-
* in different files classify independently instead of colliding.
|
|
8
|
-
*
|
|
9
|
-
* Uses `JSON.stringify` for an injective encoding: a plain delimiter such as
|
|
10
|
-
* a space is not collision-proof (e.g. `("a b", "c")` and `("a", "b c")` would
|
|
11
|
-
* both yield `"a b c"`), which would reintroduce the very collision class this
|
|
12
|
-
* key exists to prevent.
|
|
13
|
-
* @public
|
|
14
|
-
*/
|
|
15
|
-
const historyKey = (modulePath, fullName) => JSON.stringify([modulePath, fullName]);
|
|
16
|
-
/** @public */
|
|
17
|
-
var HistoryTracker = class extends Context.Service()("vitest-agent/HistoryTracker") {};
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
|
-
export { HistoryTracker, historyKey };
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Context } from "effect";
|
|
2
|
-
|
|
3
|
-
//#region src/services/PerClientSessionMap.ts
|
|
4
|
-
/**
|
|
5
|
-
* Read-only side of the session map.
|
|
6
|
-
*
|
|
7
|
-
* Provided by `PerClientSessionMapReaderLive` (opens SQLite with
|
|
8
|
-
* `?mode=ro`). MCP server depends on this tag — calls to write
|
|
9
|
-
* methods would be a type error.
|
|
10
|
-
* @public
|
|
11
|
-
*/
|
|
12
|
-
var PerClientSessionMapReader = class extends Context.Service()("vitest-agent/PerClientSessionMapReader") {};
|
|
13
|
-
/**
|
|
14
|
-
* Read-write side of the session map. Sidecar provides this; the
|
|
15
|
-
* Writer layer also satisfies the Reader tag so consumers of either
|
|
16
|
-
* one resolve.
|
|
17
|
-
* @public
|
|
18
|
-
*/
|
|
19
|
-
var PerClientSessionMapWriter = class extends Context.Service()("vitest-agent/PerClientSessionMapWriter") {};
|
|
20
|
-
|
|
21
|
-
//#endregion
|
|
22
|
-
export { PerClientSessionMapReader, PerClientSessionMapWriter };
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { canonicalizeGitUrl, gitUrlToProjectKey } from "../utils/canonicalize-git-url.js";
|
|
2
|
-
import { normalizeWorkspaceKey } from "../utils/normalize-workspace-key.js";
|
|
3
|
-
import { Context } from "effect";
|
|
4
|
-
|
|
5
|
-
//#region src/services/ProjectIdentity.ts
|
|
6
|
-
const isUseful = (value) => value !== void 0 && value.trim().length > 0;
|
|
7
|
-
/**
|
|
8
|
-
* Pure priority resolver. Returns `null` when no candidate produces a
|
|
9
|
-
* usable value — the caller wraps `null` into a tagged error.
|
|
10
|
-
*
|
|
11
|
-
* For non-git sources the `canonicalForm` equals the original
|
|
12
|
-
* (un-normalized) input so the discovery registry can display
|
|
13
|
-
* `@spencerbeggs/vitest-agent` rather than the underscored form. For
|
|
14
|
-
* git sources, `canonicalForm` is the slash-preserving `host/path`
|
|
15
|
-
* shape from `canonicalizeGitUrl`.
|
|
16
|
-
* @public
|
|
17
|
-
*/
|
|
18
|
-
const resolveProjectIdentityFromCandidates = (candidates) => {
|
|
19
|
-
if (isUseful(candidates.explicit)) return {
|
|
20
|
-
projectKey: normalizeWorkspaceKey(candidates.explicit),
|
|
21
|
-
canonicalForm: candidates.explicit,
|
|
22
|
-
source: "explicit"
|
|
23
|
-
};
|
|
24
|
-
if (isUseful(candidates.toml)) return {
|
|
25
|
-
projectKey: normalizeWorkspaceKey(candidates.toml),
|
|
26
|
-
canonicalForm: candidates.toml,
|
|
27
|
-
source: "toml"
|
|
28
|
-
};
|
|
29
|
-
if (isUseful(candidates.gitRemote)) {
|
|
30
|
-
const canonical = canonicalizeGitUrl(candidates.gitRemote);
|
|
31
|
-
const key = gitUrlToProjectKey(candidates.gitRemote);
|
|
32
|
-
if (canonical !== null && key !== null) return {
|
|
33
|
-
projectKey: key,
|
|
34
|
-
canonicalForm: canonical,
|
|
35
|
-
source: "git-remote"
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
if (isUseful(candidates.packageJsonRepoUrl)) {
|
|
39
|
-
const canonical = canonicalizeGitUrl(candidates.packageJsonRepoUrl);
|
|
40
|
-
const key = gitUrlToProjectKey(candidates.packageJsonRepoUrl);
|
|
41
|
-
if (canonical !== null && key !== null) return {
|
|
42
|
-
projectKey: key,
|
|
43
|
-
canonicalForm: canonical,
|
|
44
|
-
source: "package-repository"
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
if (isUseful(candidates.packageJsonName)) return {
|
|
48
|
-
projectKey: normalizeWorkspaceKey(candidates.packageJsonName),
|
|
49
|
-
canonicalForm: candidates.packageJsonName,
|
|
50
|
-
source: "package-name"
|
|
51
|
-
};
|
|
52
|
-
return null;
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Service tag. The Live layer (in `layers/ProjectIdentityLive.ts`)
|
|
56
|
-
* wires up the I/O sources; tests provide a stub that returns a
|
|
57
|
-
* pre-built `ResolvedIdentity`.
|
|
58
|
-
* @public
|
|
59
|
-
*/
|
|
60
|
-
var ProjectIdentity = class extends Context.Service()("vitest-agent/ProjectIdentity") {};
|
|
61
|
-
|
|
62
|
-
//#endregion
|
|
63
|
-
export { ProjectIdentity, resolveProjectIdentityFromCandidates };
|
package/services/RunContext.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Context, Schema } from "effect";
|
|
2
|
-
|
|
3
|
-
//#region src/services/RunContext.ts
|
|
4
|
-
/**
|
|
5
|
-
* Per-run snapshot of the workspace's git state and host context.
|
|
6
|
-
* Every field is nullable: a non-git workspace has all-NULL git
|
|
7
|
-
* fields, a CI run with no terminal probe match has all-NULL host
|
|
8
|
-
* fields, and a worker that can't read upstream info has a NULL
|
|
9
|
-
* `gitUpstream` while everything else is set.
|
|
10
|
-
*
|
|
11
|
-
* Declared as a `Schema.Class` so callers can `new RunContext({...})`
|
|
12
|
-
* and `Schema.encode(...)` for the SQLite row mapping.
|
|
13
|
-
* @public
|
|
14
|
-
*/
|
|
15
|
-
var RunContext = class extends Schema.Class("vitest-agent/RunContext")({
|
|
16
|
-
gitBranch: Schema.NullOr(Schema.String),
|
|
17
|
-
gitCommitSha: Schema.NullOr(Schema.String),
|
|
18
|
-
gitDirty: Schema.NullOr(Schema.Boolean),
|
|
19
|
-
gitUpstream: Schema.NullOr(Schema.String),
|
|
20
|
-
gitWorktreeDir: Schema.NullOr(Schema.String),
|
|
21
|
-
hostSource: Schema.NullOr(Schema.String),
|
|
22
|
-
hostValue: Schema.NullOr(Schema.String),
|
|
23
|
-
hostMetadata: Schema.NullOr(Schema.Unknown)
|
|
24
|
-
}) {};
|
|
25
|
-
/**
|
|
26
|
-
* Subset of {@link RunContext} captured at agent registration. Only
|
|
27
|
-
* the inheritable git context — branch, sha, worktree path. Mid-run
|
|
28
|
-
* dirty-tree state and upstream tracking are per-run concerns.
|
|
29
|
-
* @public
|
|
30
|
-
*/
|
|
31
|
-
var AgentContext = class extends Schema.Class("vitest-agent/AgentContext")({
|
|
32
|
-
startGitBranch: Schema.NullOr(Schema.String),
|
|
33
|
-
startGitCommitSha: Schema.NullOr(Schema.String),
|
|
34
|
-
startWorktreeDir: Schema.NullOr(Schema.String)
|
|
35
|
-
}) {};
|
|
36
|
-
/**
|
|
37
|
-
* Service tag. The Live layer captures real I/O; the Test layer
|
|
38
|
-
* returns whatever the test fixture passes.
|
|
39
|
-
* @public
|
|
40
|
-
*/
|
|
41
|
-
var RunContext$ = class extends Context.Service()("vitest-agent/RunContextService") {};
|
|
42
|
-
/**
|
|
43
|
-
* Re-export the service tag under a less-collision-prone name. The
|
|
44
|
-
* `RunContext` Schema.Class above shares its symbol-name with the
|
|
45
|
-
* service tag concept; the `$` suffix on the Context.Tag class name
|
|
46
|
-
* keeps both addressable from the same module.
|
|
47
|
-
*
|
|
48
|
-
* @public
|
|
49
|
-
*/
|
|
50
|
-
const RunContextService = RunContext$;
|
|
51
|
-
|
|
52
|
-
//#endregion
|
|
53
|
-
export { AgentContext, RunContext, RunContext$, RunContextService };
|
package/services/idempotency.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
//#region src/services/idempotency.ts
|
|
4
|
-
/**
|
|
5
|
-
* Sentinel substituted for `parentAgentId` when the value is `null`.
|
|
6
|
-
* Avoids ambiguity around hashing the literal string "null" or empty
|
|
7
|
-
* string, both of which could collide with caller-supplied values.
|
|
8
|
-
* @public
|
|
9
|
-
*/
|
|
10
|
-
const IDEMPOTENCY_ROOT_SENTINEL = "__ROOT__";
|
|
11
|
-
/**
|
|
12
|
-
* Derive the canonical idempotency key for an `agents` row.
|
|
13
|
-
*
|
|
14
|
-
* Algorithm:
|
|
15
|
-
* sha256(agentType + "|" + (parentAgentId ?? "__ROOT__") + "|" + clientNonce)
|
|
16
|
-
* then base32 (RFC 4648 alphabet, lowercased) truncated to 26 chars.
|
|
17
|
-
*
|
|
18
|
-
* 26 base32 chars = 130 bits, far beyond the birthday-bound for the
|
|
19
|
-
* scoping (UNIQUE per session). The base32 alphabet is a-z2-7 — all
|
|
20
|
-
* lowercase to keep the column lexicographically friendly and
|
|
21
|
-
* case-insensitive on filesystems that we never hit but might in
|
|
22
|
-
* future debugging.
|
|
23
|
-
* @public
|
|
24
|
-
*/
|
|
25
|
-
const deriveIdempotencyKey = (input) => {
|
|
26
|
-
const parent = input.parentAgentId ?? "__ROOT__";
|
|
27
|
-
const message = `${input.agentType}|${parent}|${input.clientNonce}`;
|
|
28
|
-
const digest = createHash("sha256").update(message).digest();
|
|
29
|
-
return base32Encode(digest).slice(0, 26);
|
|
30
|
-
};
|
|
31
|
-
const BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
32
|
-
/**
|
|
33
|
-
* RFC 4648 base32 encoder, lowercase, no padding. Produces ceil(8n/5)
|
|
34
|
-
* characters from an `n`-byte input. SHA-256 output (32 bytes) yields
|
|
35
|
-
* 52 base32 chars; the caller truncates to 26.
|
|
36
|
-
*/
|
|
37
|
-
const base32Encode = (bytes) => {
|
|
38
|
-
let output = "";
|
|
39
|
-
let buffer = 0;
|
|
40
|
-
let bitsInBuffer = 0;
|
|
41
|
-
for (const byte of bytes) {
|
|
42
|
-
buffer = buffer << 8 | byte;
|
|
43
|
-
bitsInBuffer += 8;
|
|
44
|
-
while (bitsInBuffer >= 5) {
|
|
45
|
-
bitsInBuffer -= 5;
|
|
46
|
-
const index = buffer >> bitsInBuffer & 31;
|
|
47
|
-
output += BASE32_ALPHABET[index];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (bitsInBuffer > 0) {
|
|
51
|
-
const index = buffer << 5 - bitsInBuffer & 31;
|
|
52
|
-
output += BASE32_ALPHABET[index];
|
|
53
|
-
}
|
|
54
|
-
return output;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
//#endregion
|
|
58
|
-
export { IDEMPOTENCY_ROOT_SENTINEL, deriveIdempotencyKey };
|
package/sql/assemblers.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { historyKey } from "../services/HistoryTracker.js";
|
|
2
|
-
|
|
3
|
-
//#region src/sql/assemblers.ts
|
|
4
|
-
/**
|
|
5
|
-
* Builds a `CacheManifest` from an array of test_runs aggregation rows.
|
|
6
|
-
*
|
|
7
|
-
* Each row represents the latest run for a project/sub_project pair.
|
|
8
|
-
* The `dbPath` is stored as `cacheDir` so CLI tools can locate the source.
|
|
9
|
-
* @public
|
|
10
|
-
*/
|
|
11
|
-
function assembleManifest(rows, dbPath) {
|
|
12
|
-
return {
|
|
13
|
-
updatedAt: rows.reduce((latest, row) => {
|
|
14
|
-
return row.timestamp > latest ? row.timestamp : latest;
|
|
15
|
-
}, (/* @__PURE__ */ new Date(0)).toISOString()),
|
|
16
|
-
cacheDir: dbPath,
|
|
17
|
-
projects: rows.map((row) => {
|
|
18
|
-
const projectName = row.sub_project ? `${row.project}:${row.sub_project}` : row.project;
|
|
19
|
-
const safeProjectName = projectName.replace(/[^a-z0-9@._-]/gi, "_");
|
|
20
|
-
const reportFile = `sql:reports/${safeProjectName}.json`;
|
|
21
|
-
const historyFile = `sql:history/${safeProjectName}.history.json`;
|
|
22
|
-
const lastResult = (() => {
|
|
23
|
-
if (row.reason === "passed" || row.reason === "failed" || row.reason === "interrupted") return row.reason;
|
|
24
|
-
return null;
|
|
25
|
-
})();
|
|
26
|
-
return {
|
|
27
|
-
project: projectName,
|
|
28
|
-
reportFile,
|
|
29
|
-
historyFile,
|
|
30
|
-
lastRun: row.timestamp,
|
|
31
|
-
lastResult
|
|
32
|
-
};
|
|
33
|
-
})
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Groups test_history rows by the composite (module_path, full_name) key
|
|
38
|
-
* into a keyed record.
|
|
39
|
-
*
|
|
40
|
-
* Returns `Record<compositeKey, { modulePath, fullName, runs: TestRun[] }>`
|
|
41
|
-
* where `runs` are the accumulated test runs for that test, in the order
|
|
42
|
-
* they appear in `rows`. Two rows sharing `full_name` but differing
|
|
43
|
-
* `module_path` are tracked as distinct entries rather than merged.
|
|
44
|
-
*
|
|
45
|
-
* Only rows with state `"passed"` or `"failed"` are included in runs
|
|
46
|
-
* (matching the `TestRun` schema constraint).
|
|
47
|
-
* @public
|
|
48
|
-
*/
|
|
49
|
-
function assembleHistoryRecord(rows) {
|
|
50
|
-
const record = {};
|
|
51
|
-
for (const row of rows) {
|
|
52
|
-
const state = row.state === "passed" || row.state === "failed" ? row.state : null;
|
|
53
|
-
if (state === null) continue;
|
|
54
|
-
const key = historyKey(row.module_path, row.full_name);
|
|
55
|
-
if (!record[key]) record[key] = {
|
|
56
|
-
modulePath: row.module_path,
|
|
57
|
-
fullName: row.full_name,
|
|
58
|
-
runs: []
|
|
59
|
-
};
|
|
60
|
-
record[key].runs.push({
|
|
61
|
-
timestamp: row.timestamp,
|
|
62
|
-
state
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
return record;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
//#endregion
|
|
69
|
-
export { assembleHistoryRecord, assembleManifest };
|
package/testing/layers.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { DataReaderLive } from "../layers/DataReaderLive.js";
|
|
2
|
-
import { DataStoreLive } from "../layers/DataStoreLive.js";
|
|
3
|
-
import { PROJECT_MIGRATIONS } from "../migrations/index.js";
|
|
4
|
-
import { Layer } from "effect";
|
|
5
|
-
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
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 = NodeServices.layer;
|
|
14
|
-
const MigratorLayer = SqliteMigrator.layer({ loader: SqliteMigrator.fromRecord(PROJECT_MIGRATIONS) }).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 };
|