Cover high-priority untested modules: - util: base32, result, refs-field, storage-root, log-tag - util-agent: storage (normalizeWorkflowConfig, resolveStorageRoot), run (parseArgv) - agent-builtin: tools (read-file, write-file, run-command), session, detail 627 → 719 tests (+92), all passing. Refs #35
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"@types/node": "^25.7.0",
|
||||
"@types/xxhashjs": "^0.2.4",
|
||||
"@united-workforce/agent-hermes": "workspace:*",
|
||||
"@vitest/coverage-v8": "^4.1.8",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.1",
|
||||
"yaml": "^2.9.0"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { createMemoryStore } from "@ocas/core";
|
||||
import { storeBuiltinDetail } from "../src/detail.js";
|
||||
import { appendSessionTurn, initSessionDir } from "../src/session.js";
|
||||
import type { BuiltinTurnPayload } from "../src/types.js";
|
||||
|
||||
describe("storeBuiltinDetail", () => {
|
||||
let storageRoot: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storageRoot = await mkdtemp(join(tmpdir(), "builtin-detail-storage-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(storageRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const makeTurn = (role: "assistant" | "tool", content: string): BuiltinTurnPayload => ({
|
||||
role,
|
||||
content,
|
||||
toolCalls: null,
|
||||
reasoning: null,
|
||||
});
|
||||
|
||||
test("stores detail with turns, returns hash and turnCount", async () => {
|
||||
const store = createMemoryStore();
|
||||
await initSessionDir(storageRoot);
|
||||
const sid = "detail-test";
|
||||
await appendSessionTurn(storageRoot, sid, makeTurn("tool", "question"));
|
||||
await appendSessionTurn(storageRoot, sid, makeTurn("assistant", "answer"));
|
||||
|
||||
const result = await storeBuiltinDetail(store, storageRoot, sid, "test-model", 1000, 2000);
|
||||
expect(result.turnCount).toBe(2);
|
||||
expect(typeof result.detailHash).toBe("string");
|
||||
expect(result.detailHash.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("empty session returns turnCount 0", async () => {
|
||||
const store = createMemoryStore();
|
||||
const sid = "empty-session";
|
||||
|
||||
const result = await storeBuiltinDetail(store, storageRoot, sid, "test-model", 1000, 2000);
|
||||
expect(result.turnCount).toBe(0);
|
||||
expect(typeof result.detailHash).toBe("string");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
||||
import { readFileTool } from "../src/tools/read-file.js";
|
||||
import { writeFile, mkdir, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
const testDir = join(tmpdir(), `read-file-test-${Date.now()}`);
|
||||
const ctx = { cwd: testDir, storageRoot: testDir };
|
||||
|
||||
beforeAll(async () => {
|
||||
await mkdir(testDir, { recursive: true });
|
||||
await writeFile(join(testDir, "hello.txt"), "hello world", "utf8");
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await rm(testDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("readFileTool", () => {
|
||||
it("reads a file successfully", async () => {
|
||||
const result = await readFileTool.execute({ path: "hello.txt" }, ctx);
|
||||
expect(result).toBe("hello world");
|
||||
});
|
||||
|
||||
it("returns error for non-existent file", async () => {
|
||||
const result = await readFileTool.execute({ path: "nope.txt" }, ctx);
|
||||
expect(result).toMatch(/^Error:/);
|
||||
});
|
||||
|
||||
it("returns error for directory", async () => {
|
||||
const result = await readFileTool.execute({ path: "." }, ctx);
|
||||
expect(result).toBe("Error: not a file");
|
||||
});
|
||||
|
||||
it("returns error when path is not a string", async () => {
|
||||
const result = await readFileTool.execute({ path: 123 }, ctx);
|
||||
expect(result).toBe("Error: path must be a string");
|
||||
});
|
||||
|
||||
it("returns error when args is null", async () => {
|
||||
const result = await readFileTool.execute(null, ctx);
|
||||
expect(result).toBe("Error: path must be a string");
|
||||
});
|
||||
|
||||
it("returns error for file exceeding 512KB limit", async () => {
|
||||
const bigFile = join(testDir, "big.txt");
|
||||
await writeFile(bigFile, Buffer.alloc(512 * 1024 + 1, 65));
|
||||
const result = await readFileTool.execute({ path: "big.txt" }, ctx);
|
||||
expect(result).toMatch(/Error:.*limit/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { runCommandTool } from "../src/tools/run-command.js";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
const ctx = { cwd: tmpdir(), storageRoot: tmpdir() };
|
||||
|
||||
describe("runCommandTool", () => {
|
||||
it("runs echo command and checks stdout", async () => {
|
||||
const result = await runCommandTool.execute({ command: "echo hello" }, ctx);
|
||||
expect(result).toContain("hello");
|
||||
expect(result).toContain("stdout");
|
||||
});
|
||||
|
||||
it("returns exit code", async () => {
|
||||
const result = await runCommandTool.execute({ command: "exit 0" }, ctx);
|
||||
expect(result).toContain("exit_code: 0");
|
||||
});
|
||||
|
||||
it("returns non-zero exit code", async () => {
|
||||
const result = await runCommandTool.execute({ command: "exit 42" }, ctx);
|
||||
expect(result).toContain("exit_code: 42");
|
||||
});
|
||||
|
||||
it("returns error when command is not a string", async () => {
|
||||
const result = await runCommandTool.execute({ command: 123 }, ctx);
|
||||
expect(result).toBe("Error: command must be a string");
|
||||
});
|
||||
|
||||
it("returns error when args is null", async () => {
|
||||
const result = await runCommandTool.execute(null, ctx);
|
||||
expect(result).toBe("Error: command must be a string");
|
||||
});
|
||||
|
||||
it("custom cwd works", async () => {
|
||||
const result = await runCommandTool.execute({ command: "pwd", cwd: "/tmp" }, ctx);
|
||||
expect(result).toContain("/tmp");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import type { BuiltinTurnPayload } from "../src/types.js";
|
||||
import {
|
||||
appendSessionTurn,
|
||||
initSessionDir,
|
||||
readSessionTurns,
|
||||
removeSession,
|
||||
} from "../src/session.js";
|
||||
|
||||
describe("session", () => {
|
||||
let storageRoot: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storageRoot = await mkdtemp(join(tmpdir(), "builtin-session-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(storageRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const makeTurn = (role: "assistant" | "tool", content: string): BuiltinTurnPayload => ({
|
||||
role,
|
||||
content,
|
||||
toolCalls: null,
|
||||
reasoning: null,
|
||||
});
|
||||
|
||||
test("initSessionDir creates directory", async () => {
|
||||
await initSessionDir(storageRoot);
|
||||
expect(existsSync(join(storageRoot, "sessions"))).toBe(true);
|
||||
});
|
||||
|
||||
test("append + read roundtrip", async () => {
|
||||
await initSessionDir(storageRoot);
|
||||
const sid = "test-session-1";
|
||||
const t1 = makeTurn("tool", "hello");
|
||||
const t2 = makeTurn("assistant", "hi there");
|
||||
await appendSessionTurn(storageRoot, sid, t1);
|
||||
await appendSessionTurn(storageRoot, sid, t2);
|
||||
const turns = await readSessionTurns(storageRoot, sid);
|
||||
expect(turns).toEqual([t1, t2]);
|
||||
});
|
||||
|
||||
test("read from non-existent returns []", async () => {
|
||||
const turns = await readSessionTurns(storageRoot, "no-such-session");
|
||||
expect(turns).toEqual([]);
|
||||
});
|
||||
|
||||
test("removeSession deletes file", async () => {
|
||||
await initSessionDir(storageRoot);
|
||||
const sid = "to-remove";
|
||||
await appendSessionTurn(storageRoot, sid, makeTurn("tool", "bye"));
|
||||
await removeSession(storageRoot, sid);
|
||||
const turns = await readSessionTurns(storageRoot, sid);
|
||||
expect(turns).toEqual([]);
|
||||
});
|
||||
|
||||
test("removeSession on non-existent does not throw", async () => {
|
||||
await expect(removeSession(storageRoot, "ghost")).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect, afterAll } from "vitest";
|
||||
import { writeFileTool } from "../src/tools/write-file.js";
|
||||
import { readFile, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
const testDir = join(tmpdir(), `write-file-test-${Date.now()}`);
|
||||
const ctx = { cwd: testDir, storageRoot: testDir };
|
||||
|
||||
afterAll(async () => {
|
||||
await rm(testDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("writeFileTool", () => {
|
||||
it("writes file successfully", async () => {
|
||||
const result = await writeFileTool.execute({ path: "out.txt", content: "hi" }, ctx);
|
||||
expect(result).toMatch(/Wrote 2 bytes/);
|
||||
const content = await readFile(join(testDir, "out.txt"), "utf8");
|
||||
expect(content).toBe("hi");
|
||||
});
|
||||
|
||||
it("creates parent directories", async () => {
|
||||
const result = await writeFileTool.execute({ path: "a/b/c.txt", content: "nested" }, ctx);
|
||||
expect(result).toMatch(/Wrote/);
|
||||
const content = await readFile(join(testDir, "a/b/c.txt"), "utf8");
|
||||
expect(content).toBe("nested");
|
||||
});
|
||||
|
||||
it("returns error when path is not a string", async () => {
|
||||
const result = await writeFileTool.execute({ path: 123, content: "x" }, ctx);
|
||||
expect(result).toBe("Error: path and content must be strings");
|
||||
});
|
||||
|
||||
it("returns error when content is not a string", async () => {
|
||||
const result = await writeFileTool.execute({ path: "x.txt", content: 42 }, ctx);
|
||||
expect(result).toBe("Error: path and content must be strings");
|
||||
});
|
||||
|
||||
it("returns error when args is null", async () => {
|
||||
const result = await writeFileTool.execute(null, ctx);
|
||||
expect(result).toBe("Error: path and content must be strings");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { parseArgv } from "../src/run.js";
|
||||
|
||||
describe("parseArgv", () => {
|
||||
let exitSpy: ReturnType<typeof vi.spyOn>;
|
||||
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {
|
||||
throw new Error("process.exit");
|
||||
}) as never);
|
||||
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation((() => true) as never);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns threadId, role, prompt for valid argv", () => {
|
||||
const result = parseArgv(["node", "script", "--thread", "abc123", "--role", "developer", "--prompt", "do stuff"]);
|
||||
expect(result).toEqual({ threadId: "abc123", role: "developer", prompt: "do stuff" });
|
||||
});
|
||||
|
||||
it("exits when --thread is missing", () => {
|
||||
expect(() => parseArgv(["node", "script", "--role", "dev", "--prompt", "x"])).toThrow("process.exit");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("exits when --role is missing", () => {
|
||||
expect(() => parseArgv(["node", "script", "--thread", "t1", "--prompt", "x"])).toThrow("process.exit");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("exits when --prompt is missing", () => {
|
||||
expect(() => parseArgv(["node", "script", "--thread", "t1", "--role", "dev"])).toThrow("process.exit");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,165 @@
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import {
|
||||
resolveStorageRoot,
|
||||
getDefaultStorageRoot,
|
||||
getCasDir,
|
||||
getConfigPath,
|
||||
getEnvPath,
|
||||
getGlobalCasDir,
|
||||
normalizeWorkflowConfig,
|
||||
} from "../src/storage.js";
|
||||
|
||||
const VALID_CONFIG = {
|
||||
defaultAgent: "builtin",
|
||||
defaultModel: "main",
|
||||
providers: { openai: { baseUrl: "https://api.openai.com/v1", apiKey: "sk-xxx" } },
|
||||
models: { main: { provider: "openai", name: "gpt-4" } },
|
||||
agents: { builtin: { command: "uwf-builtin", args: ["--verbose"] } },
|
||||
};
|
||||
|
||||
describe("getDefaultStorageRoot", () => {
|
||||
it("returns homedir/.uwf", () => {
|
||||
expect(getDefaultStorageRoot()).toBe(join(homedir(), ".uwf"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveStorageRoot", () => {
|
||||
const saved: Record<string, string | undefined> = {};
|
||||
|
||||
beforeEach(() => {
|
||||
saved.UWF_STORAGE_ROOT = process.env.UWF_STORAGE_ROOT;
|
||||
saved.WORKFLOW_STORAGE_ROOT = process.env.WORKFLOW_STORAGE_ROOT;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const k of ["UWF_STORAGE_ROOT", "WORKFLOW_STORAGE_ROOT"] as const) {
|
||||
if (saved[k] === undefined) delete process.env[k];
|
||||
else process.env[k] = saved[k];
|
||||
}
|
||||
});
|
||||
|
||||
it("uses UWF_STORAGE_ROOT first", () => {
|
||||
process.env.UWF_STORAGE_ROOT = "/tmp/uwf1";
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf2";
|
||||
expect(resolveStorageRoot()).toBe("/tmp/uwf1");
|
||||
});
|
||||
|
||||
it("falls back to WORKFLOW_STORAGE_ROOT", () => {
|
||||
delete process.env.UWF_STORAGE_ROOT;
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf2";
|
||||
expect(resolveStorageRoot()).toBe("/tmp/uwf2");
|
||||
});
|
||||
|
||||
it("falls back to default when both unset", () => {
|
||||
delete process.env.UWF_STORAGE_ROOT;
|
||||
delete process.env.WORKFLOW_STORAGE_ROOT;
|
||||
expect(resolveStorageRoot()).toBe(getDefaultStorageRoot());
|
||||
});
|
||||
|
||||
it("ignores empty UWF_STORAGE_ROOT", () => {
|
||||
process.env.UWF_STORAGE_ROOT = "";
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf2";
|
||||
expect(resolveStorageRoot()).toBe("/tmp/uwf2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("path helpers", () => {
|
||||
it("getCasDir", () => expect(getCasDir("/root")).toBe("/root/cas"));
|
||||
it("getConfigPath", () => expect(getConfigPath("/root")).toBe("/root/config.yaml"));
|
||||
it("getEnvPath", () => expect(getEnvPath("/root")).toBe("/root/.env"));
|
||||
});
|
||||
|
||||
describe("getGlobalCasDir", () => {
|
||||
const saved = { OCAS_DIR: process.env.OCAS_DIR };
|
||||
|
||||
afterEach(() => {
|
||||
if (saved.OCAS_DIR === undefined) delete process.env.OCAS_DIR;
|
||||
else process.env.OCAS_DIR = saved.OCAS_DIR;
|
||||
});
|
||||
|
||||
it("uses OCAS_DIR when set", () => {
|
||||
process.env.OCAS_DIR = "/tmp/ocas";
|
||||
expect(getGlobalCasDir()).toBe("/tmp/ocas");
|
||||
});
|
||||
|
||||
it("defaults to ~/.ocas", () => {
|
||||
delete process.env.OCAS_DIR;
|
||||
expect(getGlobalCasDir()).toBe(join(homedir(), ".ocas"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeWorkflowConfig", () => {
|
||||
it("normalizes a valid config", () => {
|
||||
const result = normalizeWorkflowConfig(VALID_CONFIG);
|
||||
expect(result.defaultAgent).toBe("builtin");
|
||||
expect(result.defaultModel).toBe("main");
|
||||
expect(result.providers.openai.baseUrl).toBe("https://api.openai.com/v1");
|
||||
expect(result.models.main.name).toBe("gpt-4");
|
||||
expect(result.agents.builtin.command).toBe("uwf-builtin");
|
||||
expect(result.agents.builtin.args).toEqual(["--verbose"]);
|
||||
expect(result.modelOverrides).toBeNull();
|
||||
expect(result.agentOverrides).toBeNull();
|
||||
});
|
||||
|
||||
it("throws on non-record root", () => {
|
||||
expect(() => normalizeWorkflowConfig("bad")).toThrow("root must be a mapping");
|
||||
expect(() => normalizeWorkflowConfig(null)).toThrow("root must be a mapping");
|
||||
expect(() => normalizeWorkflowConfig([])).toThrow("root must be a mapping");
|
||||
});
|
||||
|
||||
it("throws when defaultAgent missing", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, defaultAgent: undefined }))
|
||||
.toThrow("defaultAgent and defaultModel");
|
||||
});
|
||||
|
||||
it("throws when defaultModel missing", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, defaultModel: 42 }))
|
||||
.toThrow("defaultAgent and defaultModel");
|
||||
});
|
||||
|
||||
it("throws on invalid providers entry", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, providers: { bad: "string" } }))
|
||||
.toThrow("config.providers.bad must be a mapping");
|
||||
});
|
||||
|
||||
it("throws on invalid models entry", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, models: { m: { provider: 123, name: "x" } } }))
|
||||
.toThrow("config.models.m requires provider and name");
|
||||
});
|
||||
|
||||
it("throws on invalid agents entry", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, agents: "bad" }))
|
||||
.toThrow("config.agents must be a mapping");
|
||||
});
|
||||
|
||||
it("returns null for undefined modelOverrides", () => {
|
||||
const result = normalizeWorkflowConfig(VALID_CONFIG);
|
||||
expect(result.modelOverrides).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for null agentOverrides", () => {
|
||||
const result = normalizeWorkflowConfig({ ...VALID_CONFIG, agentOverrides: null });
|
||||
expect(result.agentOverrides).toBeNull();
|
||||
});
|
||||
|
||||
it("normalizes agentOverrides with nested roles", () => {
|
||||
const config = {
|
||||
...VALID_CONFIG,
|
||||
agentOverrides: {
|
||||
"solve-issue": { coder: "hermes", reviewer: "claude" },
|
||||
},
|
||||
};
|
||||
const result = normalizeWorkflowConfig(config);
|
||||
expect(result.agentOverrides).toEqual({
|
||||
"solve-issue": { coder: "hermes", reviewer: "claude" },
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes modelOverrides", () => {
|
||||
const config = { ...VALID_CONFIG, modelOverrides: { coding: "fast" } };
|
||||
const result = normalizeWorkflowConfig(config);
|
||||
expect(result.modelOverrides).toEqual({ coding: "fast" });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
CROCKFORD_BASE32_ALPHABET,
|
||||
encodeCrockfordBase32Bits,
|
||||
decodeCrockfordBase32Bits,
|
||||
encodeUint64AsCrockford,
|
||||
decodeCrockfordToUint64,
|
||||
} from "../src/base32.js";
|
||||
|
||||
describe("CROCKFORD_BASE32_ALPHABET", () => {
|
||||
it("has exactly 32 characters", () => {
|
||||
expect(CROCKFORD_BASE32_ALPHABET).toHaveLength(32);
|
||||
});
|
||||
|
||||
it("excludes I, L, O, U", () => {
|
||||
expect(CROCKFORD_BASE32_ALPHABET).not.toContain("I");
|
||||
expect(CROCKFORD_BASE32_ALPHABET).not.toContain("L");
|
||||
expect(CROCKFORD_BASE32_ALPHABET).not.toContain("O");
|
||||
expect(CROCKFORD_BASE32_ALPHABET).not.toContain("U");
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeCrockfordBase32Bits / decodeCrockfordBase32Bits", () => {
|
||||
it("roundtrips zero with bitLength=5", () => {
|
||||
const encoded = encodeCrockfordBase32Bits(0n, 5);
|
||||
expect(encoded).toBe("0");
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 5);
|
||||
expect(decoded).toEqual({ ok: true, value: 0n });
|
||||
});
|
||||
|
||||
it("roundtrips value 31 with bitLength=5", () => {
|
||||
const encoded = encodeCrockfordBase32Bits(31n, 5);
|
||||
expect(encoded).toBe("Z");
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 5);
|
||||
expect(decoded).toEqual({ ok: true, value: 31n });
|
||||
});
|
||||
|
||||
it("roundtrips with bitLength=10", () => {
|
||||
const encoded = encodeCrockfordBase32Bits(1023n, 10);
|
||||
expect(encoded).toBe("ZZ");
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 10);
|
||||
expect(decoded).toEqual({ ok: true, value: 1023n });
|
||||
});
|
||||
|
||||
it("roundtrips with non-multiple-of-5 bitLength", () => {
|
||||
const value = 255n; // 8 bits
|
||||
const encoded = encodeCrockfordBase32Bits(value, 8);
|
||||
expect(encoded).toHaveLength(2); // 8 bits -> 10 bits padded -> 2 chars
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 8);
|
||||
expect(decoded).toEqual({ ok: true, value });
|
||||
});
|
||||
|
||||
it("roundtrips large value", () => {
|
||||
const value = (1n << 64n) - 1n;
|
||||
const encoded = encodeCrockfordBase32Bits(value, 64);
|
||||
const decoded = decodeCrockfordBase32Bits(encoded, 64);
|
||||
expect(decoded).toEqual({ ok: true, value });
|
||||
});
|
||||
|
||||
it("throws on bitLength <= 0", () => {
|
||||
expect(() => encodeCrockfordBase32Bits(0n, 0)).toThrow("bitLength must be positive");
|
||||
expect(() => encodeCrockfordBase32Bits(0n, -1)).toThrow("bitLength must be positive");
|
||||
});
|
||||
|
||||
it("returns error on decode with bitLength <= 0", () => {
|
||||
const result = decodeCrockfordBase32Bits("0", 0);
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("returns error on invalid character", () => {
|
||||
const result = decodeCrockfordBase32Bits("U", 5);
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("returns error on wrong encoded length", () => {
|
||||
const result = decodeCrockfordBase32Bits("00", 5);
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("handles lowercase input on decode", () => {
|
||||
const encoded = encodeCrockfordBase32Bits(10n, 5);
|
||||
const decoded = decodeCrockfordBase32Bits(encoded.toLowerCase(), 5);
|
||||
expect(decoded).toEqual({ ok: true, value: 10n });
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeUint64AsCrockford / decodeCrockfordToUint64", () => {
|
||||
it("encodes to 13 characters", () => {
|
||||
expect(encodeUint64AsCrockford(0n)).toHaveLength(13);
|
||||
expect(encodeUint64AsCrockford(1n)).toHaveLength(13);
|
||||
});
|
||||
|
||||
it("roundtrips 0n", () => {
|
||||
const encoded = encodeUint64AsCrockford(0n);
|
||||
expect(encoded).toBe("0000000000000");
|
||||
const decoded = decodeCrockfordToUint64(encoded);
|
||||
expect(decoded).toEqual({ ok: true, value: 0n });
|
||||
});
|
||||
|
||||
it("roundtrips max uint64", () => {
|
||||
const max = (1n << 64n) - 1n;
|
||||
const encoded = encodeUint64AsCrockford(max);
|
||||
const decoded = decodeCrockfordToUint64(encoded);
|
||||
expect(decoded).toEqual({ ok: true, value: max });
|
||||
});
|
||||
|
||||
it("roundtrips arbitrary value", () => {
|
||||
const value = 0xDEAD_BEEF_CAFE_BABEn;
|
||||
const encoded = encodeUint64AsCrockford(value);
|
||||
const decoded = decodeCrockfordToUint64(encoded);
|
||||
expect(decoded).toEqual({ ok: true, value });
|
||||
});
|
||||
|
||||
it("masks values beyond 64 bits", () => {
|
||||
const over = (1n << 64n) + 42n;
|
||||
const encoded = encodeUint64AsCrockford(over);
|
||||
const decoded = decodeCrockfordToUint64(encoded);
|
||||
expect(decoded).toEqual({ ok: true, value: 42n });
|
||||
});
|
||||
|
||||
it("returns error for invalid input", () => {
|
||||
const result = decodeCrockfordToUint64("!!!");
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("returns error for wrong length", () => {
|
||||
const result = decodeCrockfordToUint64("000");
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { assertValidLogTag } from '../src/process-logger/log-tag.js';
|
||||
|
||||
describe('assertValidLogTag', () => {
|
||||
it('accepts valid 8-char Crockford Base32 tags', () => {
|
||||
expect(() => assertValidLogTag('0123ABCD')).not.toThrow();
|
||||
expect(() => assertValidLogTag('VWXYZ789')).not.toThrow();
|
||||
expect(() => assertValidLogTag('00000000')).not.toThrow();
|
||||
expect(() => assertValidLogTag('ZZZZZZZZ')).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts lowercase (converted via toUpperCase)', () => {
|
||||
expect(() => assertValidLogTag('abcdefgh')).not.toThrow();
|
||||
expect(() => assertValidLogTag('0a1b2c3d')).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws on too short', () => {
|
||||
expect(() => assertValidLogTag('1234567')).toThrow();
|
||||
expect(() => assertValidLogTag('')).toThrow();
|
||||
});
|
||||
|
||||
it('throws on too long', () => {
|
||||
expect(() => assertValidLogTag('123456789')).toThrow();
|
||||
});
|
||||
|
||||
it('throws on invalid chars I, L, O, U', () => {
|
||||
expect(() => assertValidLogTag('IIIIIIII')).toThrow();
|
||||
expect(() => assertValidLogTag('LLLLLLLL')).toThrow();
|
||||
expect(() => assertValidLogTag('OOOOOOOO')).toThrow();
|
||||
expect(() => assertValidLogTag('UUUUUUUU')).toThrow();
|
||||
});
|
||||
|
||||
it('throws on special characters', () => {
|
||||
expect(() => assertValidLogTag('1234567!')).toThrow();
|
||||
expect(() => assertValidLogTag('ABCD-EFG')).toThrow();
|
||||
expect(() => assertValidLogTag('ABCD EFG')).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mergeRefsWithContentHash, normalizeRefsField } from '../src/refs-field.js';
|
||||
|
||||
describe('mergeRefsWithContentHash', () => {
|
||||
it('appends a new content hash', () => {
|
||||
expect(mergeRefsWithContentHash(['a', 'b'], 'c')).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('skips duplicate content hash', () => {
|
||||
expect(mergeRefsWithContentHash(['a', 'b'], 'b')).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('preserves order', () => {
|
||||
expect(mergeRefsWithContentHash(['x', 'y'], 'z')).toEqual(['x', 'y', 'z']);
|
||||
});
|
||||
|
||||
it('handles empty refs', () => {
|
||||
expect(mergeRefsWithContentHash([], 'a')).toEqual(['a']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeRefsField', () => {
|
||||
it('returns empty array for non-array', () => {
|
||||
expect(normalizeRefsField(null)).toEqual([]);
|
||||
expect(normalizeRefsField(undefined)).toEqual([]);
|
||||
expect(normalizeRefsField(42)).toEqual([]);
|
||||
});
|
||||
|
||||
it('passes through string array', () => {
|
||||
expect(normalizeRefsField(['a', 'b'])).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('filters non-strings from mixed array', () => {
|
||||
expect(normalizeRefsField(['a', 1, 'b', null])).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('handles empty array', () => {
|
||||
expect(normalizeRefsField([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ok, err } from '../src/result.js';
|
||||
|
||||
describe('result', () => {
|
||||
describe('ok', () => {
|
||||
it('wraps a value', () => {
|
||||
const r = ok(42);
|
||||
expect(r).toEqual({ ok: true, value: 42 });
|
||||
});
|
||||
|
||||
it('wraps a string value', () => {
|
||||
const r = ok('hello');
|
||||
expect(r.ok).toBe(true);
|
||||
if (r.ok) expect(r.value).toBe('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('err', () => {
|
||||
it('wraps an error', () => {
|
||||
const r = err('fail');
|
||||
expect(r).toEqual({ ok: false, error: 'fail' });
|
||||
});
|
||||
|
||||
it('wraps an Error object', () => {
|
||||
const e = new Error('boom');
|
||||
const r = err(e);
|
||||
expect(r.ok).toBe(false);
|
||||
if (!r.ok) expect(r.error).toBe(e);
|
||||
});
|
||||
});
|
||||
|
||||
describe('type narrowing', () => {
|
||||
it('narrows ok result', () => {
|
||||
const r = ok(10) as ReturnType<typeof ok<number>> | ReturnType<typeof err<string>>;
|
||||
if (r.ok) {
|
||||
expect(r.value).toBe(10);
|
||||
} else {
|
||||
expect.unreachable();
|
||||
}
|
||||
});
|
||||
|
||||
it('narrows err result', () => {
|
||||
const r = err('bad') as ReturnType<typeof ok<number>> | ReturnType<typeof err<string>>;
|
||||
if (!r.ok) {
|
||||
expect(r.error).toBe('bad');
|
||||
} else {
|
||||
expect.unreachable();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { homedir } from 'node:os';
|
||||
import { getDefaultStorageRoot, getDefaultWorkflowStorageRoot, getGlobalCasDir } from '../src/storage-root.js';
|
||||
|
||||
describe('getDefaultStorageRoot', () => {
|
||||
it('returns homedir + /.uwf', () => {
|
||||
expect(getDefaultStorageRoot()).toBe(homedir() + '/.uwf');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultWorkflowStorageRoot', () => {
|
||||
it('returns same as getDefaultStorageRoot (deprecated alias)', () => {
|
||||
expect(getDefaultWorkflowStorageRoot()).toBe(getDefaultStorageRoot());
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGlobalCasDir', () => {
|
||||
it('appends /cas to given storage root', () => {
|
||||
expect(getGlobalCasDir('/tmp/test')).toBe('/tmp/test/cas');
|
||||
});
|
||||
|
||||
it('falls back to default when undefined', () => {
|
||||
expect(getGlobalCasDir(undefined)).toBe(homedir() + '/.uwf/cas');
|
||||
});
|
||||
});
|
||||
Generated
+139
@@ -29,6 +29,9 @@ importers:
|
||||
'@united-workforce/agent-hermes':
|
||||
specifier: workspace:*
|
||||
version: link:packages/agent-hermes
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^4.1.8
|
||||
version: 4.1.8(vitest@3.2.6(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(msw@2.14.6(@types/node@25.9.1)(typescript@5.9.3))(yaml@2.9.0))
|
||||
typescript:
|
||||
specifier: ^5.8.3
|
||||
version: 5.9.3
|
||||
@@ -411,6 +414,10 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2':
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@biomejs/biome@2.4.16':
|
||||
resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
@@ -1293,6 +1300,15 @@ packages:
|
||||
babel-plugin-react-compiler:
|
||||
optional: true
|
||||
|
||||
'@vitest/coverage-v8@4.1.8':
|
||||
resolution: {integrity: sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 4.1.8
|
||||
vitest: 4.1.8
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@3.2.6':
|
||||
resolution: {integrity: sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==}
|
||||
|
||||
@@ -1310,6 +1326,9 @@ packages:
|
||||
'@vitest/pretty-format@3.2.6':
|
||||
resolution: {integrity: sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==}
|
||||
|
||||
'@vitest/pretty-format@4.1.8':
|
||||
resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==}
|
||||
|
||||
'@vitest/runner@3.2.6':
|
||||
resolution: {integrity: sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==}
|
||||
|
||||
@@ -1322,6 +1341,9 @@ packages:
|
||||
'@vitest/utils@3.2.6':
|
||||
resolution: {integrity: sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==}
|
||||
|
||||
'@vitest/utils@4.1.8':
|
||||
resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==}
|
||||
|
||||
'@xyflow/react@12.11.0':
|
||||
resolution: {integrity: sha512-na4IO33FSs2OS72hASgZDmTYwFAkef7Z74uBUVrong3ARmQQHfnRUVaCFn1kTt5LbS6pK03TbYjCPGLjLFfziA==}
|
||||
peerDependencies:
|
||||
@@ -1391,6 +1413,9 @@ packages:
|
||||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ast-v8-to-istanbul@1.0.3:
|
||||
resolution: {integrity: sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==}
|
||||
|
||||
balanced-match@4.0.4:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
@@ -1952,6 +1977,10 @@ packages:
|
||||
resolution: {integrity: sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg==}
|
||||
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1967,6 +1996,9 @@ packages:
|
||||
resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
http-errors@2.0.1:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -2106,6 +2138,18 @@ packages:
|
||||
resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
istanbul-lib-coverage@3.2.2:
|
||||
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-reports@3.2.0:
|
||||
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
jiti@2.7.0:
|
||||
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
|
||||
hasBin: true
|
||||
@@ -2113,6 +2157,9 @@ packages:
|
||||
jose@6.2.3:
|
||||
resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==}
|
||||
|
||||
js-tokens@10.0.0:
|
||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -2267,6 +2314,13 @@ packages:
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
magicast@0.5.3:
|
||||
resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==}
|
||||
|
||||
make-dir@4.0.0:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2383,6 +2437,9 @@ packages:
|
||||
resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
obug@2.1.1:
|
||||
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -2731,6 +2788,9 @@ packages:
|
||||
std-env@3.10.0:
|
||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||
|
||||
std-env@4.1.0:
|
||||
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
|
||||
|
||||
stdin-discarder@0.2.2:
|
||||
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2777,6 +2837,10 @@ packages:
|
||||
resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
tagged-tag@1.0.0:
|
||||
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
|
||||
engines: {node: '>=20'}
|
||||
@@ -2816,6 +2880,10 @@ packages:
|
||||
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyrainbow@3.1.0:
|
||||
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyspy@4.0.4:
|
||||
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -3334,6 +3402,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.16
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@biomejs/biome@2.4.16':
|
||||
optionalDependencies:
|
||||
'@biomejs/cli-darwin-arm64': 2.4.16
|
||||
@@ -4093,6 +4163,20 @@ snapshots:
|
||||
'@rolldown/pluginutils': 1.0.1
|
||||
vite: 8.0.16(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)
|
||||
|
||||
'@vitest/coverage-v8@4.1.8(vitest@3.2.6(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(msw@2.14.6(@types/node@25.9.1)(typescript@5.9.3))(yaml@2.9.0))':
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
'@vitest/utils': 4.1.8
|
||||
ast-v8-to-istanbul: 1.0.3
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
istanbul-reports: 3.2.0
|
||||
magicast: 0.5.3
|
||||
obug: 2.1.1
|
||||
std-env: 4.1.0
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 3.2.6(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(msw@2.14.6(@types/node@25.9.1)(typescript@5.9.3))(yaml@2.9.0)
|
||||
|
||||
'@vitest/expect@3.2.6':
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
@@ -4114,6 +4198,10 @@ snapshots:
|
||||
dependencies:
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/pretty-format@4.1.8':
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/runner@3.2.6':
|
||||
dependencies:
|
||||
'@vitest/utils': 3.2.6
|
||||
@@ -4136,6 +4224,12 @@ snapshots:
|
||||
loupe: 3.2.1
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/utils@4.1.8':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.8
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@xyflow/react@12.11.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(immer@11.1.8)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@xyflow/system': 0.0.77
|
||||
@@ -4203,6 +4297,12 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
ast-v8-to-istanbul@1.0.3:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
estree-walker: 3.0.3
|
||||
js-tokens: 10.0.0
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
baseline-browser-mapping@2.10.33: {}
|
||||
@@ -4760,6 +4860,8 @@ snapshots:
|
||||
|
||||
graphql@16.14.1: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
hasown@2.0.4:
|
||||
@@ -4773,6 +4875,8 @@ snapshots:
|
||||
|
||||
hono@4.12.23: {}
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
http-errors@2.0.1:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
@@ -4869,10 +4973,25 @@ snapshots:
|
||||
|
||||
isexe@3.1.5: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
dependencies:
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
make-dir: 4.0.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
istanbul-reports@3.2.0:
|
||||
dependencies:
|
||||
html-escaper: 2.0.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
|
||||
jiti@2.7.0: {}
|
||||
|
||||
jose@6.2.3: {}
|
||||
|
||||
js-tokens@10.0.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
@@ -4990,6 +5109,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
magicast@0.5.3:
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.7
|
||||
'@babel/types': 7.29.7
|
||||
source-map-js: 1.2.1
|
||||
|
||||
make-dir@4.0.0:
|
||||
dependencies:
|
||||
semver: 7.8.1
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
media-typer@1.1.0: {}
|
||||
@@ -5085,6 +5214,8 @@ snapshots:
|
||||
|
||||
object-treeify@1.1.33: {}
|
||||
|
||||
obug@2.1.1: {}
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
@@ -5497,6 +5628,8 @@ snapshots:
|
||||
|
||||
std-env@3.10.0: {}
|
||||
|
||||
std-env@4.1.0: {}
|
||||
|
||||
stdin-discarder@0.2.2: {}
|
||||
|
||||
strict-event-emitter@0.5.1: {}
|
||||
@@ -5541,6 +5674,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
tagged-tag@1.0.0: {}
|
||||
|
||||
tailwind-merge@3.6.0: {}
|
||||
@@ -5566,6 +5703,8 @@ snapshots:
|
||||
|
||||
tinyrainbow@2.0.0: {}
|
||||
|
||||
tinyrainbow@3.1.0: {}
|
||||
|
||||
tinyspy@4.0.4: {}
|
||||
|
||||
tldts-core@7.4.2: {}
|
||||
|
||||
Reference in New Issue
Block a user