karajan-code 3.12.0 → 3.12.2
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/package.json +2 -5
- package/packages/hu-board/package.json +1 -1
- package/packages/hu-board/src/plan-mutations.js +4 -4
- package/packages/hu-board/src/routes/api.js +1 -1
- package/packages/hu-board/src/routes/rag.js +1 -1
- package/packages/hu-board/src/routes/wiki.js +1 -1
- package/packages/hu-board/src/run-tracker.js +1 -1
- package/packages/hu-board/src/server.js +2 -2
- package/packages/hu-board/src/sync.js +1 -1
- package/scripts/verify-pack.mjs +38 -6
- package/src/brain/standby-scheduler.js +2 -2
- package/src/brain/standby-store.js +2 -2
- package/src/cli/register-meta.js +5 -20
- package/src/git/hu-snapshot.js +2 -2
- package/src/plan/plan-hu-ops.js +2 -2
- package/src/plan/plan-id.js +2 -2
- package/src/plan/plan-validation.js +2 -2
- package/src/rag/vec-store.js +2 -2
- package/src/utils/atomic-write.js +3 -3
- package/src/utils/paths.js +2 -2
- package/src/utils/port-check.js +3 -3
- package/src/utils/process.js +2 -2
- package/src/utils/run-registry.js +2 -2
- package/src/utils/shared-paths.js +3 -3
- package/src/utils/update-check.js +47 -0
- package/node_modules/@karajan/core/README.md +0 -42
- package/node_modules/@karajan/core/package.json +0 -49
- package/node_modules/@karajan/core/src/atomic-write.js +0 -43
- package/node_modules/@karajan/core/src/cost/index.js +0 -102
- package/node_modules/@karajan/core/src/hu-snapshot.js +0 -105
- package/node_modules/@karajan/core/src/index.js +0 -18
- package/node_modules/@karajan/core/src/paths.js +0 -123
- package/node_modules/@karajan/core/src/plan-hu-ops.js +0 -338
- package/node_modules/@karajan/core/src/plan-id.js +0 -45
- package/node_modules/@karajan/core/src/plan-validation.js +0 -82
- package/node_modules/@karajan/core/src/port-check.js +0 -45
- package/node_modules/@karajan/core/src/pricing/index.js +0 -64
- package/node_modules/@karajan/core/src/pricing/model-pricing.json +0 -28
- package/node_modules/@karajan/core/src/process.js +0 -188
- package/node_modules/@karajan/core/src/run-registry.js +0 -115
- package/node_modules/@karajan/core/src/shared-paths.js +0 -41
- package/node_modules/@karajan/core/src/standby-scheduler.js +0 -92
- package/node_modules/@karajan/core/src/standby-store.js +0 -168
- package/node_modules/@karajan/core/src/vec-store.js +0 -253
- package/node_modules/@karajan/core/tests/atomic-write.test.js +0 -30
- package/node_modules/@karajan/core/tests/cost/index.test.js +0 -89
- package/node_modules/@karajan/core/tests/hu-snapshot.test.js +0 -39
- package/node_modules/@karajan/core/tests/paths.test.js +0 -51
- package/node_modules/@karajan/core/tests/plan-hu-ops.test.js +0 -68
- package/node_modules/@karajan/core/tests/plan-id.test.js +0 -21
- package/node_modules/@karajan/core/tests/plan-validation.test.js +0 -39
- package/node_modules/@karajan/core/tests/port-check.test.js +0 -47
- package/node_modules/@karajan/core/tests/pricing/index.test.js +0 -95
- package/node_modules/@karajan/core/tests/process.test.js +0 -22
- package/node_modules/@karajan/core/tests/run-registry.test.js +0 -71
- package/node_modules/@karajan/core/tests/shared-paths.test.js +0 -30
- package/node_modules/@karajan/core/tests/standby.test.js +0 -66
- package/node_modules/@karajan/core/tests/vec-store.test.js +0 -98
- package/node_modules/@karajan/core/vitest.config.js +0 -12
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
resolveHome,
|
|
6
|
-
getKarajanHome,
|
|
7
|
-
getSessionRoot,
|
|
8
|
-
getRunsDir,
|
|
9
|
-
getPromptsDir,
|
|
10
|
-
__resetKjHomeWarningForTests,
|
|
11
|
-
} from "../src/paths.js";
|
|
12
|
-
|
|
13
|
-
describe("@karajan/core/paths", () => {
|
|
14
|
-
let originalKarajanHome;
|
|
15
|
-
let originalKjHome;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
originalKarajanHome = process.env.KARAJAN_HOME;
|
|
19
|
-
originalKjHome = process.env.KJ_HOME;
|
|
20
|
-
delete process.env.KARAJAN_HOME;
|
|
21
|
-
delete process.env.KJ_HOME;
|
|
22
|
-
__resetKjHomeWarningForTests();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
if (originalKarajanHome !== undefined) process.env.KARAJAN_HOME = originalKarajanHome;
|
|
27
|
-
if (originalKjHome !== undefined) process.env.KJ_HOME = originalKjHome;
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("KARAJAN_HOME takes precedence", () => {
|
|
31
|
-
process.env.KARAJAN_HOME = "/tmp/explicit-home";
|
|
32
|
-
expect(resolveHome()).toBe(path.resolve("/tmp/explicit-home"));
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("getKarajanHome defaults to .karajan segment", () => {
|
|
36
|
-
process.env.KARAJAN_HOME = "/tmp/root";
|
|
37
|
-
expect(getKarajanHome()).toBe(path.resolve("/tmp/root"));
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("derived dirs hang off getKarajanHome", () => {
|
|
41
|
-
process.env.KARAJAN_HOME = "/tmp/root";
|
|
42
|
-
const home = path.resolve("/tmp/root");
|
|
43
|
-
expect(getSessionRoot()).toBe(path.join(home, "sessions"));
|
|
44
|
-
expect(getRunsDir()).toBe(path.join(home, "runs"));
|
|
45
|
-
expect(getPromptsDir()).toBe(path.join(home, "prompts"));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("VITEST env auto-isolates to tmpdir", () => {
|
|
49
|
-
expect(resolveHome().startsWith(os.tmpdir())).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
addHu,
|
|
4
|
-
removeHu,
|
|
5
|
-
updateHu,
|
|
6
|
-
updateHuStatus,
|
|
7
|
-
certifyAllHus,
|
|
8
|
-
assertPlanRunnable,
|
|
9
|
-
computePlanOutcome,
|
|
10
|
-
} from "../src/plan-hu-ops.js";
|
|
11
|
-
|
|
12
|
-
const newPlan = () => ({ planId: "plan-T", hus: [], updatedAt: null });
|
|
13
|
-
|
|
14
|
-
describe("@karajan/core/plan-hu-ops", () => {
|
|
15
|
-
it("addHu assigns sequential ID + defaults", () => {
|
|
16
|
-
const plan = newPlan();
|
|
17
|
-
const hu = addHu(plan, { title: "T1" });
|
|
18
|
-
expect(hu.id).toBe("hu_plan-T_001");
|
|
19
|
-
expect(hu.status).toBe("pending");
|
|
20
|
-
expect(plan.hus).toHaveLength(1);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("removeHu cleans blocked_by + reuse refs", () => {
|
|
24
|
-
const plan = newPlan();
|
|
25
|
-
addHu(plan, { title: "T1" });
|
|
26
|
-
addHu(plan, { title: "T2", blocked_by: ["hu_plan-T_001"], reuse: ["hu_plan-T_001"] });
|
|
27
|
-
expect(removeHu(plan, "hu_plan-T_001")).toBe(true);
|
|
28
|
-
expect(plan.hus[0].blocked_by).toEqual([]);
|
|
29
|
-
expect(plan.hus[0].reuse).toEqual([]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("updateHu only writes allowed fields", () => {
|
|
33
|
-
const plan = newPlan();
|
|
34
|
-
addHu(plan, { title: "T1" });
|
|
35
|
-
updateHu(plan, "hu_plan-T_001", { title: "T1!", id: "evil" });
|
|
36
|
-
expect(plan.hus[0].title).toBe("T1!");
|
|
37
|
-
expect(plan.hus[0].id).toBe("hu_plan-T_001");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("updateHuStatus + certifyAllHus + assertPlanRunnable", () => {
|
|
41
|
-
const plan = newPlan();
|
|
42
|
-
addHu(plan, { title: "T1" });
|
|
43
|
-
expect(updateHuStatus(plan, "hu_plan-T_001", "in_progress")).toBe(true);
|
|
44
|
-
addHu(plan, { title: "T2" });
|
|
45
|
-
expect(certifyAllHus(plan)).toBe(1);
|
|
46
|
-
expect(plan.status).toBe("ready");
|
|
47
|
-
expect(() => assertPlanRunnable(plan, "plan-T")).not.toThrow();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("assertPlanRunnable throws when no HU certified", () => {
|
|
51
|
-
const plan = newPlan();
|
|
52
|
-
addHu(plan, { title: "T1" });
|
|
53
|
-
expect(() => assertPlanRunnable(plan, "plan-T")).toThrow(/0 HU/);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("computePlanOutcome rolls up counts + dedupes", () => {
|
|
57
|
-
const plan = newPlan();
|
|
58
|
-
addHu(plan, { title: "T1" });
|
|
59
|
-
addHu(plan, { title: "T2" });
|
|
60
|
-
plan.hus[0].outcome = { status: "done", pr_url: "u", blockers: [] };
|
|
61
|
-
plan.hus[1].outcome = { status: "failed", pr_url: "u", blockers: ["x"] };
|
|
62
|
-
const o = computePlanOutcome(plan, { duration_ms: 1 });
|
|
63
|
-
expect(o.status).toBe("partial");
|
|
64
|
-
expect(o.counts).toEqual({ done: 1, failed: 1, blocked: 0, pending: 0 });
|
|
65
|
-
expect(o.prs).toEqual(["u"]);
|
|
66
|
-
expect(o.blockers).toEqual(["x"]);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { generatePlanId, generateHuId, normaliseAlias } from "../src/plan-id.js";
|
|
3
|
-
|
|
4
|
-
describe("@karajan/core/plan-id", () => {
|
|
5
|
-
it("generatePlanId returns plan-<ts>-<rand>", () => {
|
|
6
|
-
const id = generatePlanId();
|
|
7
|
-
expect(id).toMatch(/^plan-\d{14}-[a-z0-9]{4}$/);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("generateHuId formats sequence zero-padded", () => {
|
|
11
|
-
expect(generateHuId("plan-X", 1)).toBe("hu_plan-X_001");
|
|
12
|
-
expect(generateHuId("plan-X", 42)).toBe("hu_plan-X_042");
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("normaliseAlias slugifies + caps to 60 chars", () => {
|
|
16
|
-
expect(normaliseAlias("Greta App")).toBe("greta-app");
|
|
17
|
-
expect(normaliseAlias("Café — Ñoño")).toBe("cafe-nono");
|
|
18
|
-
expect(normaliseAlias("")).toBeNull();
|
|
19
|
-
expect(normaliseAlias(null)).toBeNull();
|
|
20
|
-
});
|
|
21
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { validateBlockedByChange } from "../src/plan-validation.js";
|
|
3
|
-
|
|
4
|
-
const plan = () => ({
|
|
5
|
-
hus: [
|
|
6
|
-
{ id: "a", blocked_by: [] },
|
|
7
|
-
{ id: "b", blocked_by: ["a"] },
|
|
8
|
-
{ id: "c", blocked_by: ["b"] },
|
|
9
|
-
],
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
describe("@karajan/core/plan-validation", () => {
|
|
13
|
-
it("ok when the new blocked_by is a valid subset", () => {
|
|
14
|
-
expect(validateBlockedByChange(plan(), "c", ["a"]).ok).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("rejects self-dependency", () => {
|
|
18
|
-
const r = validateBlockedByChange(plan(), "a", ["a"]);
|
|
19
|
-
expect(r.ok).toBe(false);
|
|
20
|
-
expect(r.error).toMatch(/auto-dependencia/);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("rejects unknown HU reference", () => {
|
|
24
|
-
const r = validateBlockedByChange(plan(), "a", ["zzz"]);
|
|
25
|
-
expect(r.ok).toBe(false);
|
|
26
|
-
expect(r.error).toMatch(/inexistente/);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("rejects cycle creation", () => {
|
|
30
|
-
const r = validateBlockedByChange(plan(), "a", ["c"]);
|
|
31
|
-
expect(r.ok).toBe(false);
|
|
32
|
-
expect(r.cycle).toBeDefined();
|
|
33
|
-
expect(r.error).toMatch(/Ciclo/);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("rejects non-array blocked_by", () => {
|
|
37
|
-
expect(validateBlockedByChange(plan(), "a", "nope").ok).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Smoke coverage for the @karajan/core/port-check extraction
|
|
2
|
-
// (KJC-TSK-0511 PR2). Real socket binding — tiny but fast.
|
|
3
|
-
import { createServer } from "node:net";
|
|
4
|
-
import { describe, it, expect } from "vitest";
|
|
5
|
-
|
|
6
|
-
import { isPortAvailable, findAvailablePort } from "../src/port-check.js";
|
|
7
|
-
|
|
8
|
-
function occupy() {
|
|
9
|
-
return new Promise((resolve) => {
|
|
10
|
-
const srv = createServer();
|
|
11
|
-
srv.listen(0, () => resolve({ srv, port: srv.address().port }));
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe("@karajan/core/port-check", () => {
|
|
16
|
-
it("isPortAvailable returns false for a port we already bound", async () => {
|
|
17
|
-
const { srv, port } = await occupy();
|
|
18
|
-
try {
|
|
19
|
-
expect(await isPortAvailable(port)).toBe(false);
|
|
20
|
-
} finally {
|
|
21
|
-
srv.close();
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("findAvailablePort skips busy ports and returns the next free one", async () => {
|
|
26
|
-
const { srv, port } = await occupy();
|
|
27
|
-
try {
|
|
28
|
-
const got = await findAvailablePort(port, 5);
|
|
29
|
-
expect(got).toBeGreaterThan(port);
|
|
30
|
-
} finally {
|
|
31
|
-
srv.close();
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("findAvailablePort throws when the range is exhausted", async () => {
|
|
36
|
-
const occupied = [];
|
|
37
|
-
for (let i = 0; i < 3; i += 1) occupied.push(await occupy());
|
|
38
|
-
const ports = occupied.map((o) => o.port).sort((a, b) => a - b);
|
|
39
|
-
try {
|
|
40
|
-
const gap = ports[2] - ports[0];
|
|
41
|
-
if (gap > 2) return;
|
|
42
|
-
await expect(findAvailablePort(ports[0], gap + 1)).rejects.toThrow(/No available port/);
|
|
43
|
-
} finally {
|
|
44
|
-
for (const { srv } of occupied) srv.close();
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
getModelPricing,
|
|
8
|
-
listKnownModels,
|
|
9
|
-
_resetCache,
|
|
10
|
-
} from "../../src/pricing/index.js";
|
|
11
|
-
|
|
12
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const REGISTRY_PATH = join(__dirname, "../../src/pricing/model-pricing.json");
|
|
14
|
-
|
|
15
|
-
describe("pricing — model-pricing.json structure", () => {
|
|
16
|
-
it("is valid JSON with a _meta block", () => {
|
|
17
|
-
const raw = readFileSync(REGISTRY_PATH, "utf8");
|
|
18
|
-
const data = JSON.parse(raw);
|
|
19
|
-
expect(data._meta).toBeDefined();
|
|
20
|
-
expect(data._meta.currency).toBe("USD");
|
|
21
|
-
expect(data._meta.updatedAt).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
22
|
-
expect(data._meta.unit).toMatch(/1M tokens/);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("every entry (except _meta) has inputPerMTok, outputPerMTok and source", () => {
|
|
26
|
-
const raw = readFileSync(REGISTRY_PATH, "utf8");
|
|
27
|
-
const data = JSON.parse(raw);
|
|
28
|
-
for (const [key, entry] of Object.entries(data)) {
|
|
29
|
-
if (key.startsWith("_")) continue;
|
|
30
|
-
expect(typeof entry.inputPerMTok).toBe("number");
|
|
31
|
-
expect(typeof entry.outputPerMTok).toBe("number");
|
|
32
|
-
expect(entry.inputPerMTok).toBeGreaterThanOrEqual(0);
|
|
33
|
-
expect(entry.outputPerMTok).toBeGreaterThanOrEqual(0);
|
|
34
|
-
expect(typeof entry.source).toBe("string");
|
|
35
|
-
expect(entry.source.length).toBeGreaterThan(0);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe("pricing — getModelPricing()", () => {
|
|
41
|
-
beforeEach(() => _resetCache());
|
|
42
|
-
|
|
43
|
-
it("returns full pricing for a known Anthropic model", () => {
|
|
44
|
-
const p = getModelPricing("claude-opus-4-7");
|
|
45
|
-
expect(p).not.toBeNull();
|
|
46
|
-
expect(p.inputPerMTok).toBe(15.0);
|
|
47
|
-
expect(p.outputPerMTok).toBe(75.0);
|
|
48
|
-
expect(p.currency).toBe("USD");
|
|
49
|
-
expect(p.source).toBe("anthropic");
|
|
50
|
-
expect(p.updatedAt).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("returns full pricing for an OpenAI model", () => {
|
|
54
|
-
const p = getModelPricing("gpt-4o");
|
|
55
|
-
expect(p).not.toBeNull();
|
|
56
|
-
expect(p.source).toBe("openai");
|
|
57
|
-
expect(p.inputPerMTok).toBeGreaterThan(0);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("returns full pricing for a Google model", () => {
|
|
61
|
-
const p = getModelPricing("gemini-1.5-flash");
|
|
62
|
-
expect(p).not.toBeNull();
|
|
63
|
-
expect(p.source).toBe("google");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("returns null for an unknown model", () => {
|
|
67
|
-
expect(getModelPricing("nonexistent-model-xyz")).toBeNull();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("returns null for the _meta key (does not leak metadata)", () => {
|
|
71
|
-
expect(getModelPricing("_meta")).toBeNull();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("returns null for empty / non-string inputs", () => {
|
|
75
|
-
expect(getModelPricing("")).toBeNull();
|
|
76
|
-
expect(getModelPricing(null)).toBeNull();
|
|
77
|
-
expect(getModelPricing(undefined)).toBeNull();
|
|
78
|
-
expect(getModelPricing(42)).toBeNull();
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe("pricing — listKnownModels()", () => {
|
|
83
|
-
it("returns a non-empty list and never includes _meta", () => {
|
|
84
|
-
const models = listKnownModels();
|
|
85
|
-
expect(Array.isArray(models)).toBe(true);
|
|
86
|
-
expect(models.length).toBeGreaterThan(5);
|
|
87
|
-
expect(models).not.toContain("_meta");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("every listed model resolves via getModelPricing", () => {
|
|
91
|
-
for (const m of listKnownModels()) {
|
|
92
|
-
expect(getModelPricing(m)).not.toBeNull();
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { runCommand } from "../src/process.js";
|
|
3
|
-
|
|
4
|
-
describe("@karajan/core/process", () => {
|
|
5
|
-
it("captures stdout from a real subprocess", async () => {
|
|
6
|
-
const r = await runCommand("node", ["-e", "process.stdout.write('hi')"]);
|
|
7
|
-
expect(r.exitCode).toBe(0);
|
|
8
|
-
expect(r.stdout).toBe("hi");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("returns exitCode 1 + spawnError when binary missing (ENOENT)", async () => {
|
|
12
|
-
const r = await runCommand("__definitely_not_a_real_bin__", []);
|
|
13
|
-
expect(r.exitCode).toBe(1);
|
|
14
|
-
expect(r.stderr).toBeTruthy();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("times out and reports timedOut", async () => {
|
|
18
|
-
const r = await runCommand("node", ["-e", "setInterval(() => {}, 1000)"], { timeout: 200 });
|
|
19
|
-
expect(r.timedOut).toBe(true);
|
|
20
|
-
expect(r.exitCode).toBe(143);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
registerRun,
|
|
7
|
-
unregisterRun,
|
|
8
|
-
listActiveRuns,
|
|
9
|
-
runsDir,
|
|
10
|
-
} from "../src/run-registry.js";
|
|
11
|
-
|
|
12
|
-
describe("@karajan/core/run-registry", () => {
|
|
13
|
-
let tmpHome;
|
|
14
|
-
let originalKarajanHome;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "kj-runreg-"));
|
|
18
|
-
originalKarajanHome = process.env.KARAJAN_HOME;
|
|
19
|
-
process.env.KARAJAN_HOME = tmpHome;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
if (originalKarajanHome === undefined) delete process.env.KARAJAN_HOME;
|
|
24
|
-
else process.env.KARAJAN_HOME = originalKarajanHome;
|
|
25
|
-
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("runsDir hangs off KARAJAN_HOME/runs", () => {
|
|
29
|
-
expect(runsDir()).toBe(path.join(path.resolve(tmpHome), "runs"));
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("registerRun → listActiveRuns round-trips for the live process", () => {
|
|
33
|
-
const runId = registerRun({
|
|
34
|
-
pid: process.pid,
|
|
35
|
-
planId: "PLN-1",
|
|
36
|
-
huIds: ["HU-1"],
|
|
37
|
-
projectDir: "/tmp/x",
|
|
38
|
-
source: "test",
|
|
39
|
-
});
|
|
40
|
-
expect(runId).toMatch(/^run-\d+-\d+-[a-z0-9]+$/);
|
|
41
|
-
const list = listActiveRuns({ planId: "PLN-1" });
|
|
42
|
-
expect(list.length).toBe(1);
|
|
43
|
-
expect(list[0].pid).toBe(process.pid);
|
|
44
|
-
unregisterRun(runId);
|
|
45
|
-
expect(listActiveRuns({ planId: "PLN-1" })).toEqual([]);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("listActiveRuns reaps entries with dead PIDs from disk", () => {
|
|
49
|
-
fs.mkdirSync(runsDir(), { recursive: true });
|
|
50
|
-
const deadEntry = {
|
|
51
|
-
runId: "run-dead",
|
|
52
|
-
pid: 999_999_999,
|
|
53
|
-
planId: "PLN-x",
|
|
54
|
-
huIds: null,
|
|
55
|
-
projectDir: null,
|
|
56
|
-
source: "test",
|
|
57
|
-
startedAt: new Date().toISOString(),
|
|
58
|
-
};
|
|
59
|
-
fs.writeFileSync(
|
|
60
|
-
path.join(runsDir(), "run-dead.json"),
|
|
61
|
-
JSON.stringify(deadEntry),
|
|
62
|
-
);
|
|
63
|
-
expect(listActiveRuns()).toEqual([]);
|
|
64
|
-
expect(fs.existsSync(path.join(runsDir(), "run-dead.json"))).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("registerRun returns null when pid is missing", () => {
|
|
68
|
-
expect(registerRun({})).toBeNull();
|
|
69
|
-
expect(registerRun({ pid: 0 })).toBeNull();
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// Smoke coverage for the @karajan/core/shared-paths extraction
|
|
2
|
-
// (KJC-TSK-0511 PR2). Full unit coverage stays next to the CLI;
|
|
3
|
-
// this file proves the new package entry point resolves and returns
|
|
4
|
-
// stable paths so a future workspace-wiring regression fails loudly.
|
|
5
|
-
import { sep } from "node:path";
|
|
6
|
-
import { describe, it, expect } from "vitest";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
SHARED_DIR_NAME,
|
|
10
|
-
getSharedRoot,
|
|
11
|
-
getSharedPlansDir,
|
|
12
|
-
getSharedHuStoriesDir,
|
|
13
|
-
isSharedPath,
|
|
14
|
-
} from "../src/shared-paths.js";
|
|
15
|
-
|
|
16
|
-
describe("@karajan/core/shared-paths", () => {
|
|
17
|
-
it("constants and joins land inside the project dir", () => {
|
|
18
|
-
expect(SHARED_DIR_NAME).toBe(".karajan-shared");
|
|
19
|
-
expect(getSharedRoot("/tmp/proj")).toBe(`/tmp/proj${sep}.karajan-shared`);
|
|
20
|
-
expect(getSharedPlansDir("/tmp/proj").endsWith(`${sep}plans`)).toBe(true);
|
|
21
|
-
expect(getSharedHuStoriesDir("/tmp/proj").endsWith(`${sep}hu-stories`)).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("isSharedPath recognises only paths within .karajan-shared", () => {
|
|
25
|
-
expect(isSharedPath("/repo/.karajan-shared/plans/x.json")).toBe(true);
|
|
26
|
-
expect(isSharedPath("/repo/local/plans/x.json")).toBe(false);
|
|
27
|
-
expect(isSharedPath("")).toBe(false);
|
|
28
|
-
expect(isSharedPath(null)).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { mkdtempSync, rmSync, existsSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
persistStandby, loadStandby, listPendingStandby, markStandbyDone,
|
|
7
|
-
acquireStandbyLock, standbyDir,
|
|
8
|
-
} from "../src/standby-store.js";
|
|
9
|
-
import {
|
|
10
|
-
scheduleResume, cancelScheduled, _resetScheduledForTests,
|
|
11
|
-
} from "../src/standby-scheduler.js";
|
|
12
|
-
|
|
13
|
-
let tmp;
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
tmp = mkdtempSync(join(tmpdir(), "kj-core-standby-"));
|
|
16
|
-
process.env.KJ_HOME = tmp;
|
|
17
|
-
_resetScheduledForTests();
|
|
18
|
-
});
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
delete process.env.KJ_HOME;
|
|
21
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe("@karajan/core/standby-store", () => {
|
|
25
|
-
it("persists + loads + lists pending sessions", () => {
|
|
26
|
-
persistStandby({ sessionId: "s_1", cooldownUntil: "2099-01-01T00:00:00Z", reason: "QUOTA" });
|
|
27
|
-
const loaded = loadStandby("s_1");
|
|
28
|
-
expect(loaded.sessionId).toBe("s_1");
|
|
29
|
-
expect(listPendingStandby()).toHaveLength(1);
|
|
30
|
-
expect(standbyDir().startsWith(tmp)).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("markStandbyDone moves the file under done/", () => {
|
|
34
|
-
persistStandby({ sessionId: "s_2", cooldownUntil: "2099-01-01T00:00:00Z" });
|
|
35
|
-
expect(markStandbyDone("s_2")).toBe(true);
|
|
36
|
-
expect(loadStandby("s_2")).toBe(null);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("acquireStandbyLock is exclusive", () => {
|
|
40
|
-
const a = acquireStandbyLock("s_3");
|
|
41
|
-
expect(a.acquired).toBe(true);
|
|
42
|
-
const b = acquireStandbyLock("s_3");
|
|
43
|
-
expect(b.acquired).toBe(false);
|
|
44
|
-
a.release();
|
|
45
|
-
const c = acquireStandbyLock("s_3");
|
|
46
|
-
expect(c.acquired).toBe(true);
|
|
47
|
-
c.release();
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe("@karajan/core/standby-scheduler", () => {
|
|
52
|
-
it("scheduleResume with expired cooldown fires immediate", async () => {
|
|
53
|
-
const cb = vi.fn();
|
|
54
|
-
const r = scheduleResume({ sessionId: "s_4", cooldownUntil: "1990-01-01T00:00:00Z", onResume: cb });
|
|
55
|
-
expect(r.scheduled).toBe(true);
|
|
56
|
-
expect(r.immediate).toBe(true);
|
|
57
|
-
await new Promise((res) => setImmediate(res));
|
|
58
|
-
expect(cb).toHaveBeenCalledWith("s_4");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("cancelScheduled removes a queued timeout", () => {
|
|
62
|
-
scheduleResume({ sessionId: "s_5", cooldownUntil: "2099-01-01T00:00:00Z", onResume: () => {} });
|
|
63
|
-
expect(cancelScheduled("s_5")).toBe(true);
|
|
64
|
-
expect(cancelScheduled("s_5")).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
openVecStore,
|
|
7
|
-
insertChunk,
|
|
8
|
-
searchSimilar,
|
|
9
|
-
countChunks,
|
|
10
|
-
projectSlug,
|
|
11
|
-
deleteChunksBySource,
|
|
12
|
-
findChunkByHash,
|
|
13
|
-
getLastIndexedCommit,
|
|
14
|
-
setLastIndexedCommit,
|
|
15
|
-
dbPath,
|
|
16
|
-
} from "../src/vec-store.js";
|
|
17
|
-
|
|
18
|
-
describe("@karajan/core/vec-store", () => {
|
|
19
|
-
let tmpDir;
|
|
20
|
-
let originalRagDb;
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kj-vecstore-"));
|
|
24
|
-
originalRagDb = process.env.KJ_RAG_DB;
|
|
25
|
-
process.env.KJ_RAG_DB = path.join(tmpDir, "rag.db");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
if (originalRagDb === undefined) delete process.env.KJ_RAG_DB;
|
|
30
|
-
else process.env.KJ_RAG_DB = originalRagDb;
|
|
31
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("dbPath honours KJ_RAG_DB env", () => {
|
|
35
|
-
expect(dbPath()).toBe(path.join(tmpDir, "rag.db"));
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("openVecStore initialises chunks + vec_chunks + fts tables", () => {
|
|
39
|
-
const db = openVecStore({ dim: 4 });
|
|
40
|
-
expect(countChunks(db)).toBe(0);
|
|
41
|
-
db.close();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("insertChunk + searchSimilar round-trip", () => {
|
|
45
|
-
const db = openVecStore({ dim: 4 });
|
|
46
|
-
const id = insertChunk(db, {
|
|
47
|
-
source: "/a.md",
|
|
48
|
-
kind: "code",
|
|
49
|
-
text: "hola mundo",
|
|
50
|
-
embedding: [1, 0, 0, 0],
|
|
51
|
-
});
|
|
52
|
-
expect(id).toBeGreaterThan(0);
|
|
53
|
-
const hits = searchSimilar(db, [1, 0, 0, 0], 1);
|
|
54
|
-
expect(hits.length).toBe(1);
|
|
55
|
-
expect(hits[0].text).toBe("hola mundo");
|
|
56
|
-
db.close();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("deleteChunksBySource removes chunks + vec rows", () => {
|
|
60
|
-
const db = openVecStore({ dim: 4 });
|
|
61
|
-
insertChunk(db, { source: "/x.md", kind: "code", text: "a", embedding: [1, 0, 0, 0] });
|
|
62
|
-
insertChunk(db, { source: "/x.md", kind: "code", text: "b", embedding: [0, 1, 0, 0] });
|
|
63
|
-
expect(deleteChunksBySource(db, "/x.md")).toBe(2);
|
|
64
|
-
expect(countChunks(db)).toBe(0);
|
|
65
|
-
db.close();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("projectSlug normalises basenames", () => {
|
|
69
|
-
expect(projectSlug("/home/me/My Project")).toBe("my-project");
|
|
70
|
-
expect(projectSlug("/home/me/karajan-code/")).toBe("karajan-code");
|
|
71
|
-
expect(projectSlug(null)).toBeNull();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("findChunkByHash scopes to project", () => {
|
|
75
|
-
const db = openVecStore({ dim: 4 });
|
|
76
|
-
insertChunk(db, {
|
|
77
|
-
source: "/y.md",
|
|
78
|
-
kind: "code",
|
|
79
|
-
text: "hi",
|
|
80
|
-
embedding: [1, 0, 0, 0],
|
|
81
|
-
project: "p1",
|
|
82
|
-
contentHash: "h1",
|
|
83
|
-
});
|
|
84
|
-
expect(findChunkByHash(db, "h1", "p1").source).toBe("/y.md");
|
|
85
|
-
expect(findChunkByHash(db, "h1", "p2")).toBeNull();
|
|
86
|
-
db.close();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("get/setLastIndexedCommit persists per-project watermark", () => {
|
|
90
|
-
const db = openVecStore({ dim: 4 });
|
|
91
|
-
expect(getLastIndexedCommit(db, "p1")).toBeNull();
|
|
92
|
-
setLastIndexedCommit(db, "p1", "abc123");
|
|
93
|
-
expect(getLastIndexedCommit(db, "p1")).toBe("abc123");
|
|
94
|
-
setLastIndexedCommit(db, "p1", "def456");
|
|
95
|
-
expect(getLastIndexedCommit(db, "p1")).toBe("def456");
|
|
96
|
-
db.close();
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Local Vitest config for @karajan/core so the workspace package does
|
|
2
|
-
// not inherit the root config (which excludes `packages/**` and points
|
|
3
|
-
// at a global setup file outside the workspace).
|
|
4
|
-
|
|
5
|
-
import { defineConfig } from "vitest/config";
|
|
6
|
-
|
|
7
|
-
export default defineConfig({
|
|
8
|
-
test: {
|
|
9
|
-
include: ["tests/**/*.test.js"],
|
|
10
|
-
environment: "node",
|
|
11
|
-
},
|
|
12
|
-
});
|