Skip to content

Commit 9525b06

Browse files
committed
fix: refresh shared indexer after knowledge base updates
1 parent a77e26c commit 9525b06

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

src/tools/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { tool, type ToolDefinition } from "@opencode-ai/plugin";
22

3+
import { parseConfig, type ParsedCodebaseIndexConfig } from "../config/schema.js";
34
import { Indexer } from "../indexer/index.js";
4-
import { ParsedCodebaseIndexConfig } from "../config/schema.js";
55
import { formatCostEstimate } from "../utils/cost.js";
66
import type { LogLevel } from "../config/schema.js";
77
import type { LogEntry } from "../utils/logger.js";
@@ -30,6 +30,14 @@ export function initializeTools(projectRoot: string, config: ParsedCodebaseIndex
3030
sharedIndexer = new Indexer(projectRoot, config);
3131
}
3232

33+
function refreshIndexerFromConfig(): void {
34+
if (!sharedProjectRoot) {
35+
throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
36+
}
37+
38+
sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadConfig()));
39+
}
40+
3341
function getIndexer(): Indexer {
3442
if (!sharedIndexer) {
3543
throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
@@ -372,6 +380,7 @@ export const add_knowledge_base: ToolDefinition = tool({
372380
knowledgeBases.push(resolvedPath);
373381
config.knowledgeBases = knowledgeBases;
374382
saveConfig(config);
383+
refreshIndexerFromConfig();
375384

376385
let result = `${resolvedPath}\n`;
377386
result += `Total knowledge bases: ${knowledgeBases.length}\n`;
@@ -458,6 +467,7 @@ export const remove_knowledge_base: ToolDefinition = tool({
458467
const removed = knowledgeBases.splice(index, 1)[0];
459468
config.knowledgeBases = knowledgeBases;
460469
saveConfig(config);
470+
refreshIndexerFromConfig();
461471

462472
let result = `Removed: ${removed}\n\n`;
463473
result += `Remaining knowledge bases: ${knowledgeBases.length}\n`;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import * as fs from "fs";
2+
import * as os from "os";
3+
import * as path from "path";
4+
5+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6+
7+
const { indexerInstances, MockIndexer } = vi.hoisted(() => {
8+
const indexerInstances: Array<{ projectRoot: string; config: Record<string, unknown> }> = [];
9+
10+
class MockIndexer {
11+
public readonly projectRoot: string;
12+
public readonly config: Record<string, unknown>;
13+
14+
public constructor(projectRoot: string, config: Record<string, unknown>) {
15+
this.projectRoot = projectRoot;
16+
this.config = config;
17+
indexerInstances.push({ projectRoot, config });
18+
}
19+
20+
public estimateCost = vi.fn().mockResolvedValue({
21+
filesCount: 0,
22+
totalSizeBytes: 0,
23+
estimatedChunks: 0,
24+
estimatedTokens: 0,
25+
estimatedCost: 0,
26+
isFree: true,
27+
provider: "ollama",
28+
model: "nomic-embed-text",
29+
});
30+
31+
public clearIndex = vi.fn().mockResolvedValue(undefined);
32+
public index = vi.fn().mockResolvedValue({
33+
totalFiles: 0,
34+
totalChunks: 0,
35+
indexedChunks: 0,
36+
failedChunks: 0,
37+
tokensUsed: 0,
38+
durationMs: 0,
39+
existingChunks: 0,
40+
removedChunks: 0,
41+
skippedFiles: [],
42+
parseFailures: [],
43+
});
44+
45+
public getStatus = vi.fn().mockResolvedValue({
46+
indexed: true,
47+
vectorCount: 0,
48+
provider: "ollama",
49+
model: "nomic-embed-text",
50+
indexPath: "/tmp/index",
51+
currentBranch: "main",
52+
baseBranch: "main",
53+
});
54+
55+
public healthCheck = vi.fn().mockResolvedValue({
56+
removed: 0,
57+
gcOrphanEmbeddings: 0,
58+
gcOrphanChunks: 0,
59+
gcOrphanSymbols: 0,
60+
gcOrphanCallEdges: 0,
61+
filePaths: [],
62+
});
63+
64+
public getLogger = vi.fn().mockReturnValue({
65+
isEnabled: vi.fn().mockReturnValue(false),
66+
isMetricsEnabled: vi.fn().mockReturnValue(false),
67+
getLogs: vi.fn().mockReturnValue([]),
68+
getLogsByCategory: vi.fn().mockReturnValue([]),
69+
getLogsByLevel: vi.fn().mockReturnValue([]),
70+
formatMetrics: vi.fn().mockReturnValue(""),
71+
});
72+
}
73+
74+
return { indexerInstances, MockIndexer };
75+
});
76+
77+
vi.mock("../src/indexer/index.js", () => ({
78+
Indexer: MockIndexer,
79+
}));
80+
81+
import { parseConfig } from "../src/config/schema.js";
82+
import { add_knowledge_base, initializeTools, remove_knowledge_base } from "../src/tools/index.js";
83+
84+
describe("knowledge base tool config refresh", () => {
85+
let tempDir: string;
86+
let kbDir: string;
87+
88+
beforeEach(() => {
89+
indexerInstances.length = 0;
90+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "kb-tools-test-"));
91+
kbDir = fs.mkdtempSync(path.join(os.tmpdir(), "kb-source-"));
92+
initializeTools(tempDir, parseConfig({ indexing: { watchFiles: false } }));
93+
});
94+
95+
afterEach(() => {
96+
fs.rmSync(tempDir, { recursive: true, force: true });
97+
fs.rmSync(kbDir, { recursive: true, force: true });
98+
});
99+
100+
it("rebuilds the shared indexer after adding a knowledge base", async () => {
101+
await add_knowledge_base.execute({ path: kbDir });
102+
103+
expect(indexerInstances).toHaveLength(2);
104+
expect(indexerInstances[1]?.projectRoot).toBe(tempDir);
105+
expect(indexerInstances[1]?.config.knowledgeBases).toEqual([path.normalize(kbDir)]);
106+
});
107+
108+
it("rebuilds the shared indexer after removing a knowledge base", async () => {
109+
await add_knowledge_base.execute({ path: kbDir });
110+
await remove_knowledge_base.execute({ path: kbDir });
111+
112+
expect(indexerInstances).toHaveLength(3);
113+
expect(indexerInstances[2]?.projectRoot).toBe(tempDir);
114+
expect(indexerInstances[2]?.config.knowledgeBases).toEqual([]);
115+
});
116+
});

0 commit comments

Comments
 (0)