Skip to content
1,785 changes: 1,629 additions & 156 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
"smoke:openhands": "npm run build && node scripts/smoke-openhands-install.mjs",
"smoke:copilot": "npm run build && node scripts/smoke-copilot-install.mjs",
"smoke:opencode": "npm run build && node scripts/smoke-opencode-install.mjs",
"test": "npm run build && node scripts/check-transcript-bundle.js && node scripts/check-repo-context.js && node scripts/check-install-options.js && node scripts/check-graph-view.js && node scripts/check-proposal-validate.js && node scripts/check-bm25-tokenizer.js && node scripts/check-anchor-drift.js",
"test:transcript-bundle": "npm run build && node scripts/check-transcript-bundle.js",
"test:repo-context": "npm run build && node scripts/check-repo-context.js",
"test": "npm run build && vitest run",
"test:fast": "vitest run",
"test:watch": "vitest",
"test:coverage": "npm run build && vitest run --coverage",
"test:transcript-bundle": "npm run build && vitest run scripts/check-transcript-bundle.js",
"test:repo-context": "npm run build && vitest run scripts/check-repo-context.js",
"eval:bootstrap-current": "npm run build && node dist/evals/cases/bootstrap-current-repo-at-8038fe8/run.js",
"eval:coding-proposal-apply-atomicity": "npm run build && node dist/evals/cases/coding-proposal-apply-atomicity-at-d6ebf80/run.js",
"eval:curated-doc-bootstrap-vercel-chat": "npm run build && node dist/evals/cases/curated-doc-bootstrap-vercel-chat-at-f3de128/run.js",
Expand Down Expand Up @@ -65,6 +68,8 @@
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.15.3",
"typescript": "^5.8.3"
"@vitest/coverage-v8": "^4.1.10",
"typescript": "^5.8.3",
"vitest": "^4.1.10"
}
}
115 changes: 87 additions & 28 deletions scripts/check-anchor-drift.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,101 @@
import assert from "node:assert/strict";
import { describe, test, expect, beforeAll } from "vitest";
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const root = new URL("..", import.meta.url);
const { CodeAnchorResolver } = await import(new URL("dist/libs/knowledge-graph/code-anchors/resolver.js", root));
const { fingerprintClaimAnchors } = await import(new URL("dist/libs/knowledge-graph/code-anchors/fingerprint.js", root));
const { auditClaimCodeAnchors } = await import(new URL("dist/libs/knowledge-graph/code-anchors/audit.js", root));
let CodeAnchorResolver;
let fingerprintClaimAnchors;
let auditClaimCodeAnchors;

const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-"));
const file = join(repo, "mod.py");
const anchor = { file: "mod.py", symbol: "foo" };
const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] };
beforeAll(async () => {
const resolver = await import(new URL("dist/libs/knowledge-graph/code-anchors/resolver.js", root));
const fingerprint = await import(new URL("dist/libs/knowledge-graph/code-anchors/fingerprint.js", root));
const audit = await import(new URL("dist/libs/knowledge-graph/code-anchors/audit.js", root));
CodeAnchorResolver = resolver.CodeAnchorResolver;
fingerprintClaimAnchors = fingerprint.fingerprintClaimAnchors;
auditClaimCodeAnchors = audit.auditClaimCodeAnchors;
});

// Baseline fingerprint captured when the fact was "written".
writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n");
const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]);
describe("anchor drift", () => {
test("unchanged code does not drift", async () => {
const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-"));
const file = join(repo, "mod.py");
const anchor = { file: "mod.py", symbol: "foo" };
const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] };

async function driftedIds(variant) {
writeFileSync(file, variant);
const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline);
return result.drifted.map((issue) => issue.claim_id);
}
writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n");
const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]);

// Unchanged code does not drift.
assert.deepEqual(await driftedIds("def foo():\n # returns the threshold\n return 3\n"), []);
async function driftedIds(variant) {
writeFileSync(file, variant);
const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline);
return result.drifted.map((issue) => issue.claim_id);
}

// A real value change (3 -> 8) drifts.
assert.deepEqual(await driftedIds("def foo():\n # returns the threshold\n return 8\n"), ["claim.foo"]);
expect(await driftedIds("def foo():\n # returns the threshold\n return 3\n")).toEqual([]);
});

// Comment-only edits do not drift.
assert.deepEqual(await driftedIds("def foo():\n # returns the configured threshold value\n return 3\n"), []);
test("a real value change drifts", async () => {
const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-"));
const file = join(repo, "mod.py");
const anchor = { file: "mod.py", symbol: "foo" };
const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] };

// Whitespace-only edits do not drift.
assert.deepEqual(await driftedIds("def foo():\n\n # returns the threshold\n return 3\n\n"), []);
writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n");
const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]);

// A claim with no stored baseline is treated as unknown, never drifted.
const noBaseline = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver());
assert.deepEqual(noBaseline.drifted, []);
async function driftedIds(variant) {
writeFileSync(file, variant);
const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline);
return result.drifted.map((issue) => issue.claim_id);
}

console.log("check-anchor-drift: ok");
expect(await driftedIds("def foo():\n # returns the threshold\n return 8\n")).toEqual(["claim.foo"]);
});

test("comment-only edits do not drift", async () => {
const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-"));
const file = join(repo, "mod.py");
const anchor = { file: "mod.py", symbol: "foo" };
const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] };

writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n");
const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]);

async function driftedIds(variant) {
writeFileSync(file, variant);
const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline);
return result.drifted.map((issue) => issue.claim_id);
}

expect(await driftedIds("def foo():\n # returns the configured threshold value\n return 3\n")).toEqual([]);
});

test("whitespace-only edits do not drift", async () => {
const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-"));
const file = join(repo, "mod.py");
const anchor = { file: "mod.py", symbol: "foo" };
const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] };

writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n");
const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]);

async function driftedIds(variant) {
writeFileSync(file, variant);
const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline);
return result.drifted.map((issue) => issue.claim_id);
}

expect(await driftedIds("def foo():\n\n # returns the threshold\n return 3\n\n")).toEqual([]);
});

test("a claim with no stored baseline is treated as unknown, never drifted", async () => {
const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-"));
const anchor = { file: "mod.py", symbol: "foo" };
const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] };

const noBaseline = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver());
expect(noBaseline.drifted).toEqual([]);
});
});
78 changes: 52 additions & 26 deletions scripts/check-bm25-tokenizer.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,54 @@
import assert from "node:assert/strict";
import { describe, test, expect, beforeAll } from "vitest";

const root = new URL("..", import.meta.url);
const { tokenize, scoreBm25 } = await import(new URL("dist/libs/knowledge-graph/graph-context/bm25.js", root));
const { graphContextConfig } = await import(new URL("dist/libs/knowledge-graph/graph-context/config.js", root));

assert.ok(tokenize("handleUserAuth").includes("user"), "camelCase should emit sub-token user");
assert.ok(tokenize("handleUserAuth").includes("auth"), "camelCase should emit sub-token auth");
assert.ok(tokenize("handleUserAuth").includes("handle"), "camelCase should emit sub-token handle");
assert.ok(tokenize("handleUserAuth").includes("handleuserauth"), "camelCase should keep full lowercased token");

assert.ok(tokenize("HandleUserAuth").includes("user"), "PascalCase should emit sub-token user");
assert.ok(tokenize("handle_user_auth").includes("user"), "snake_case should emit sub-token user");
assert.ok(tokenize("handle_user_auth").includes("handle_user_auth"), "snake_case should keep original token");

assert.ok(tokenize("graph-context").includes("graph"), "kebab-case should emit sub-token graph");
assert.ok(tokenize("graph-context").includes("context"), "kebab-case should emit sub-token context");

assert.ok(tokenize("user2FA").includes("user"), "letter-number boundary should emit user");
assert.ok(tokenize("user2FA").includes("fa"), "letter-number boundary should emit fa");

assert.ok(tokenize("tokens").includes("token"), "English stemming variants should still apply");

const documents = [{ key: "doc:auth", text: "The handleUserAuth function validates sessions." }];
const ranked = scoreBm25("user auth validation", documents, graphContextConfig);
assert.equal(ranked[0]?.id, "doc:auth", "BM25 should match camelCase doc tokens to spaced query terms");

console.log("BM25 tokenizer checks passed.");
let tokenize;
let scoreBm25;
let graphContextConfig;

beforeAll(async () => {
const bm25 = await import(new URL("dist/libs/knowledge-graph/graph-context/bm25.js", root));
const config = await import(new URL("dist/libs/knowledge-graph/graph-context/config.js", root));
tokenize = bm25.tokenize;
scoreBm25 = bm25.scoreBm25;
graphContextConfig = config.graphContextConfig;
});

describe("BM25 tokenizer", () => {
test("camelCase emits sub-tokens and keeps full lowercased token", () => {
const tokens = tokenize("handleUserAuth");
expect(tokens).toContain("user");
expect(tokens).toContain("auth");
expect(tokens).toContain("handle");
expect(tokens).toContain("handleuserauth");
});

test("PascalCase emits sub-tokens", () => {
expect(tokenize("HandleUserAuth")).toContain("user");
});

test("snake_case emits sub-tokens and keeps original token", () => {
const tokens = tokenize("handle_user_auth");
expect(tokens).toContain("user");
expect(tokens).toContain("handle_user_auth");
});

test("kebab-case emits sub-tokens", () => {
expect(tokenize("graph-context")).toContain("graph");
expect(tokenize("graph-context")).toContain("context");
});

test("letter-number boundary emits sub-tokens", () => {
expect(tokenize("user2FA")).toContain("user");
expect(tokenize("user2FA")).toContain("fa");
});

test("English stemming variants still apply", () => {
expect(tokenize("tokens")).toContain("token");
});

test("BM25 matches camelCase doc tokens to spaced query terms", () => {
const documents = [{ key: "doc:auth", text: "The handleUserAuth function validates sessions." }];
const ranked = scoreBm25("user auth validation", documents, graphContextConfig);
expect(ranked[0]?.id).toBe("doc:auth");
});
});
85 changes: 48 additions & 37 deletions scripts/check-graph-view.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
import assert from "node:assert/strict";
import { describe, test, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const root = new URL("..", import.meta.url);
const { openDatabase } = await import(new URL("dist/libs/storage/sqlite/db.js", root));
const { SqliteRepository } = await import(new URL("dist/libs/storage/sqlite/repository.js", root));
const { KnowledgeGraphService } = await import(new URL("dist/libs/knowledge-graph/service.js", root));
let openDatabase;
let SqliteRepository;
let KnowledgeGraphService;

const tmp = mkdtempSync(join(tmpdir(), "greplica-graph-view-test-"));
const db = openDatabase(join(tmp, "graph.db"));
beforeAll(async () => {
const db = await import(new URL("dist/libs/storage/sqlite/db.js", root));
const repo = await import(new URL("dist/libs/storage/sqlite/repository.js", root));
const service = await import(new URL("dist/libs/knowledge-graph/service.js", root));
openDatabase = db.openDatabase;
SqliteRepository = repo.SqliteRepository;
KnowledgeGraphService = service.KnowledgeGraphService;
});

try {
const repository = new SqliteRepository(db);
const service = new KnowledgeGraphService(repository);
const repo = {
repo_root: join(tmp, "repo"),
repo_name: "graph-view-null-anchor",
default_branch: "main",
};
describe("graph view", () => {
test("renders components without code anchors", () => {
const tmp = mkdtempSync(join(tmpdir(), "greplica-graph-view-test-"));
const db = openDatabase(join(tmp, "graph.db"));

const initialized = service.initRepo(repo);
const memoryCommit = repository.createMemoryCommit({
scope_id: initialized.working_scope_id,
title: "Seed null component anchor",
});
try {
const repository = new SqliteRepository(db);
const service = new KnowledgeGraphService(repository);
const repo = {
repo_root: join(tmp, "repo"),
repo_name: "graph-view-null-anchor",
default_branch: "main",
};

repository.createProposalRecords(initialized.working_scope_id, memoryCommit.id, {
title: "Seed null component anchor",
creates: {
components: [
{
id: "component.no_anchor",
name: "Component Without Anchor",
},
],
},
});
const initialized = service.initRepo(repo);
const memoryCommit = repository.createMemoryCommit({
scope_id: initialized.working_scope_id,
title: "Seed null component anchor",
});

const html = service.buildGraphView(repo);
assert.match(html, /Component Without Anchor/);
assert.match(html, /Greplica graph view/);
} finally {
db.close();
}
repository.createProposalRecords(initialized.working_scope_id, memoryCommit.id, {
title: "Seed null component anchor",
creates: {
components: [
{
id: "component.no_anchor",
name: "Component Without Anchor",
},
],
},
});

console.log("Graph view checks passed.");
const html = service.buildGraphView(repo);
expect(html).toMatch(/Component Without Anchor/);
expect(html).toMatch(/Greplica graph view/);
} finally {
db.close();
}
});
});
Loading