sverklo 0.2.12 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -13
- package/dist/bin/sverklo.js +69 -1
- package/dist/bin/sverklo.js.map +1 -1
- package/dist/src/audit-prompt.d.ts +2 -0
- package/dist/src/audit-prompt.js +91 -0
- package/dist/src/audit-prompt.js.map +1 -0
- package/dist/src/doctor.js +55 -0
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/indexer/embedding-providers.d.ts +34 -0
- package/dist/src/indexer/embedding-providers.js +215 -0
- package/dist/src/indexer/embedding-providers.js.map +1 -0
- package/dist/src/indexer/embedding-providers.test.d.ts +1 -0
- package/dist/src/indexer/embedding-providers.test.js +83 -0
- package/dist/src/indexer/embedding-providers.test.js.map +1 -0
- package/dist/src/indexer/indexer-freshness.test.d.ts +1 -0
- package/dist/src/indexer/indexer-freshness.test.js +109 -0
- package/dist/src/indexer/indexer-freshness.test.js.map +1 -0
- package/dist/src/indexer/indexer-provider-integration.test.d.ts +1 -0
- package/dist/src/indexer/indexer-provider-integration.test.js +142 -0
- package/dist/src/indexer/indexer-provider-integration.test.js.map +1 -0
- package/dist/src/indexer/indexer.d.ts +44 -0
- package/dist/src/indexer/indexer.js +153 -10
- package/dist/src/indexer/indexer.js.map +1 -1
- package/dist/src/indexer/migration.test.d.ts +1 -0
- package/dist/src/indexer/migration.test.js +144 -0
- package/dist/src/indexer/migration.test.js.map +1 -0
- package/dist/src/indexer/symbol-extractor.js +17 -5
- package/dist/src/indexer/symbol-extractor.js.map +1 -1
- package/dist/src/indexer/symbol-extractor.test.d.ts +1 -0
- package/dist/src/indexer/symbol-extractor.test.js +88 -0
- package/dist/src/indexer/symbol-extractor.test.js.map +1 -0
- package/dist/src/indexer/watcher.js +4 -0
- package/dist/src/indexer/watcher.js.map +1 -1
- package/dist/src/memory/journal.d.ts +62 -0
- package/dist/src/memory/journal.js +136 -0
- package/dist/src/memory/journal.js.map +1 -0
- package/dist/src/memory/journal.test.d.ts +1 -0
- package/dist/src/memory/journal.test.js +98 -0
- package/dist/src/memory/journal.test.js.map +1 -0
- package/dist/src/modes.d.ts +16 -0
- package/dist/src/modes.js +104 -0
- package/dist/src/modes.js.map +1 -0
- package/dist/src/modes.test.d.ts +1 -0
- package/dist/src/modes.test.js +48 -0
- package/dist/src/modes.test.js.map +1 -0
- package/dist/src/search/hybrid-search.d.ts +22 -0
- package/dist/src/search/hybrid-search.js +169 -7
- package/dist/src/search/hybrid-search.js.map +1 -1
- package/dist/src/search/hybrid-search.test.d.ts +1 -0
- package/dist/src/search/hybrid-search.test.js +112 -0
- package/dist/src/search/hybrid-search.test.js.map +1 -0
- package/dist/src/search/token-budget.d.ts +1 -0
- package/dist/src/search/token-budget.js +34 -6
- package/dist/src/search/token-budget.js.map +1 -1
- package/dist/src/server/mcp-server.js +32 -25
- package/dist/src/server/mcp-server.js.map +1 -1
- package/dist/src/server/tool-overrides.d.ts +16 -0
- package/dist/src/server/tool-overrides.js +102 -0
- package/dist/src/server/tool-overrides.js.map +1 -0
- package/dist/src/server/tool-overrides.test.d.ts +1 -0
- package/dist/src/server/tool-overrides.test.js +124 -0
- package/dist/src/server/tool-overrides.test.js.map +1 -0
- package/dist/src/server/tools/context-budget.test.d.ts +1 -0
- package/dist/src/server/tools/context-budget.test.js +150 -0
- package/dist/src/server/tools/context-budget.test.js.map +1 -0
- package/dist/src/server/tools/context.d.ts +11 -1
- package/dist/src/server/tools/context.js +202 -8
- package/dist/src/server/tools/context.js.map +1 -1
- package/dist/src/server/tools/diff-heuristics.d.ts +28 -0
- package/dist/src/server/tools/diff-heuristics.js +213 -0
- package/dist/src/server/tools/diff-heuristics.js.map +1 -0
- package/dist/src/server/tools/diff-heuristics.test.d.ts +1 -0
- package/dist/src/server/tools/diff-heuristics.test.js +151 -0
- package/dist/src/server/tools/diff-heuristics.test.js.map +1 -0
- package/dist/src/server/tools/find-references.d.ts +4 -0
- package/dist/src/server/tools/find-references.js +46 -6
- package/dist/src/server/tools/find-references.js.map +1 -1
- package/dist/src/server/tools/find-references.test.d.ts +1 -0
- package/dist/src/server/tools/find-references.test.js +98 -0
- package/dist/src/server/tools/find-references.test.js.map +1 -0
- package/dist/src/server/tools/forget.js +3 -0
- package/dist/src/server/tools/forget.js.map +1 -1
- package/dist/src/server/tools/index-status.js +16 -0
- package/dist/src/server/tools/index-status.js.map +1 -1
- package/dist/src/server/tools/lookup.js +21 -13
- package/dist/src/server/tools/lookup.js.map +1 -1
- package/dist/src/server/tools/lookup.test.d.ts +1 -0
- package/dist/src/server/tools/lookup.test.js +110 -0
- package/dist/src/server/tools/lookup.test.js.map +1 -0
- package/dist/src/server/tools/recall.d.ts +5 -1
- package/dist/src/server/tools/recall.js +68 -6
- package/dist/src/server/tools/recall.js.map +1 -1
- package/dist/src/server/tools/recall.test.d.ts +1 -0
- package/dist/src/server/tools/recall.test.js +116 -0
- package/dist/src/server/tools/recall.test.js.map +1 -0
- package/dist/src/server/tools/remember.js +16 -2
- package/dist/src/server/tools/remember.js.map +1 -1
- package/dist/src/server/tools/review-diff.js +31 -0
- package/dist/src/server/tools/review-diff.js.map +1 -1
- package/dist/src/server/tools/search.js +29 -4
- package/dist/src/server/tools/search.js.map +1 -1
- package/dist/src/server/tools/search.test.d.ts +1 -0
- package/dist/src/server/tools/search.test.js +118 -0
- package/dist/src/server/tools/search.test.js.map +1 -0
- package/dist/src/server/tools/tier.js +25 -1
- package/dist/src/server/tools/tier.js.map +1 -1
- package/dist/src/storage/chunk-store.d.ts +12 -0
- package/dist/src/storage/chunk-store.js +16 -0
- package/dist/src/storage/chunk-store.js.map +1 -1
- package/dist/src/storage/chunk-store.test.d.ts +1 -0
- package/dist/src/storage/chunk-store.test.js +69 -0
- package/dist/src/storage/chunk-store.test.js.map +1 -0
- package/dist/src/storage/database.d.ts +13 -0
- package/dist/src/storage/database.js +47 -0
- package/dist/src/storage/database.js.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// Pluggable embedding providers (issue #9).
|
|
2
|
+
//
|
|
3
|
+
// Sverklo historically hardcoded the bundled all-MiniLM-L6-v2 ONNX
|
|
4
|
+
// model. That's a great Pareto choice for the zero-config path but
|
|
5
|
+
// leaves two legitimate user groups out:
|
|
6
|
+
//
|
|
7
|
+
// 1. Enterprise users with existing embedding infrastructure who
|
|
8
|
+
// want sverklo's index to share a similarity space with their
|
|
9
|
+
// RAG pipeline (Voyage AI, OpenAI, Cohere).
|
|
10
|
+
// 2. Privacy-sensitive users who can't ship ONNX binaries and need
|
|
11
|
+
// to point at a local Ollama / llamafile endpoint.
|
|
12
|
+
//
|
|
13
|
+
// This module defines the provider interface and a registry. The
|
|
14
|
+
// bundled ONNX model implements the interface as the "default"
|
|
15
|
+
// provider and is still selected when the user sets nothing. All
|
|
16
|
+
// other providers are additive — shipping a new one is a matter of
|
|
17
|
+
// adding a class and registering it.
|
|
18
|
+
//
|
|
19
|
+
// Critical constraint: changing providers changes the embedding
|
|
20
|
+
// dimension and the similarity space. We don't support mixing vectors
|
|
21
|
+
// from different providers in the same index. The caller (Indexer)
|
|
22
|
+
// checks the stored provider/dimensions against the current config
|
|
23
|
+
// on startup and triggers a full rebuild if they don't match.
|
|
24
|
+
// ────────────────────────────────────────────────────────────────────
|
|
25
|
+
// Provider: default (bundled ONNX all-MiniLM-L6-v2)
|
|
26
|
+
// ────────────────────────────────────────────────────────────────────
|
|
27
|
+
import { embed as legacyEmbed, initEmbedder } from "./embedder.js";
|
|
28
|
+
class BundledOnnxProvider {
|
|
29
|
+
name = "default";
|
|
30
|
+
dimensions = 384;
|
|
31
|
+
async init() {
|
|
32
|
+
await initEmbedder();
|
|
33
|
+
}
|
|
34
|
+
async embed(texts) {
|
|
35
|
+
return legacyEmbed(texts);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class OpenAIProvider {
|
|
39
|
+
name;
|
|
40
|
+
dimensions;
|
|
41
|
+
apiKey;
|
|
42
|
+
model;
|
|
43
|
+
endpoint;
|
|
44
|
+
constructor(opts) {
|
|
45
|
+
this.apiKey = opts.apiKey;
|
|
46
|
+
this.model = opts.model || "text-embedding-3-small";
|
|
47
|
+
// Default to 1536 for 3-small. Users targeting a different model
|
|
48
|
+
// MUST set SVERKLO_OPENAI_DIMENSIONS to match — we don't auto-probe
|
|
49
|
+
// because that would fire a billed request just to learn the size.
|
|
50
|
+
this.dimensions = opts.dimensions || 1536;
|
|
51
|
+
this.endpoint = opts.endpoint || "https://api.openai.com/v1/embeddings";
|
|
52
|
+
this.name = `openai:${this.model}`;
|
|
53
|
+
}
|
|
54
|
+
async init() {
|
|
55
|
+
// Smoke-test the endpoint with an empty ping. On failure, throw —
|
|
56
|
+
// the indexer wraps this in a try/catch and falls back to the
|
|
57
|
+
// bundled provider with a warning.
|
|
58
|
+
if (!this.apiKey) {
|
|
59
|
+
throw new Error("OpenAI embedding provider selected but SVERKLO_OPENAI_API_KEY is unset.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async embed(texts) {
|
|
63
|
+
if (texts.length === 0)
|
|
64
|
+
return [];
|
|
65
|
+
// OpenAI's embeddings endpoint accepts a batch in a single call.
|
|
66
|
+
// We keep batches at <= 100 inputs to stay well under the 300k
|
|
67
|
+
// token request limit. The indexer already chunks at ~400 tokens
|
|
68
|
+
// per chunk so 100 × 400 = 40k tokens is safely under.
|
|
69
|
+
const out = [];
|
|
70
|
+
const BATCH = 100;
|
|
71
|
+
for (let i = 0; i < texts.length; i += BATCH) {
|
|
72
|
+
const batch = texts.slice(i, i + BATCH);
|
|
73
|
+
const res = await fetch(this.endpoint, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: {
|
|
76
|
+
"content-type": "application/json",
|
|
77
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
model: this.model,
|
|
81
|
+
input: batch,
|
|
82
|
+
dimensions: this.dimensions,
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
const body = await res.text().catch(() => "<no body>");
|
|
87
|
+
throw new Error(`OpenAI embeddings failed: ${res.status} ${res.statusText} — ${body.slice(0, 200)}`);
|
|
88
|
+
}
|
|
89
|
+
const json = (await res.json());
|
|
90
|
+
// Preserve input order — OpenAI is supposed to echo back sorted
|
|
91
|
+
// by index but we're defensive.
|
|
92
|
+
const sorted = [...json.data].sort((a, b) => a.index - b.index);
|
|
93
|
+
for (const row of sorted) {
|
|
94
|
+
out.push(new Float32Array(row.embedding));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
class OllamaProvider {
|
|
101
|
+
name;
|
|
102
|
+
dimensions;
|
|
103
|
+
endpoint;
|
|
104
|
+
model;
|
|
105
|
+
constructor(opts) {
|
|
106
|
+
this.endpoint = opts.endpoint || "http://localhost:11434/api/embeddings";
|
|
107
|
+
this.model = opts.model || "nomic-embed-text";
|
|
108
|
+
// Nomic embed text is 768 dims. Users swapping models must set
|
|
109
|
+
// SVERKLO_OLLAMA_DIMENSIONS to match.
|
|
110
|
+
this.dimensions = opts.dimensions || 768;
|
|
111
|
+
this.name = `ollama:${this.model}`;
|
|
112
|
+
}
|
|
113
|
+
async init() {
|
|
114
|
+
// Probe with the read-only /api/tags listing endpoint instead of
|
|
115
|
+
// firing a real embedding request. Derives the tags endpoint from
|
|
116
|
+
// the embeddings endpoint so custom SVERKLO_OLLAMA_URL overrides
|
|
117
|
+
// still work. Failure falls back to the bundled provider via the
|
|
118
|
+
// factory wrapper.
|
|
119
|
+
const tagsEndpoint = this.endpoint.replace(/\/api\/embeddings\/?$/, "/api/tags");
|
|
120
|
+
try {
|
|
121
|
+
const res = await fetch(tagsEndpoint, { method: "GET" });
|
|
122
|
+
if (!res.ok) {
|
|
123
|
+
throw new Error(`Ollama probe failed: ${res.status} ${res.statusText}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
throw new Error(`Ollama embedding provider could not reach ${tagsEndpoint}. ` +
|
|
128
|
+
`Is Ollama running? Original error: ${err.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async embed(texts) {
|
|
132
|
+
// Ollama's embeddings endpoint takes one prompt per request, so
|
|
133
|
+
// we parallelize with a bounded concurrency of 4.
|
|
134
|
+
const out = new Array(texts.length);
|
|
135
|
+
const CONCURRENCY = 4;
|
|
136
|
+
let next = 0;
|
|
137
|
+
async function worker() {
|
|
138
|
+
while (true) {
|
|
139
|
+
const i = next++;
|
|
140
|
+
if (i >= texts.length)
|
|
141
|
+
return;
|
|
142
|
+
const res = await fetch(this.endpoint, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: { "content-type": "application/json" },
|
|
145
|
+
body: JSON.stringify({ model: this.model, prompt: texts[i] }),
|
|
146
|
+
});
|
|
147
|
+
if (!res.ok) {
|
|
148
|
+
throw new Error(`Ollama embeddings failed on index ${i}: ${res.status}`);
|
|
149
|
+
}
|
|
150
|
+
const json = (await res.json());
|
|
151
|
+
out[i] = new Float32Array(json.embedding);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const workers = Array.from({ length: Math.min(CONCURRENCY, texts.length) }, () => worker.call(this));
|
|
155
|
+
await Promise.all(workers);
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// ────────────────────────────────────────────────────────────────────
|
|
160
|
+
// Factory
|
|
161
|
+
// ────────────────────────────────────────────────────────────────────
|
|
162
|
+
//
|
|
163
|
+
// Reads config (env vars for now; .sverklo/config.json later) and
|
|
164
|
+
// returns the selected provider. If init() throws, falls back to the
|
|
165
|
+
// bundled default with a warning.
|
|
166
|
+
import { log } from "../utils/logger.js";
|
|
167
|
+
export async function createEmbeddingProvider(env = process.env) {
|
|
168
|
+
const providerName = (env.SVERKLO_EMBEDDING_PROVIDER || "default").toLowerCase();
|
|
169
|
+
let provider;
|
|
170
|
+
try {
|
|
171
|
+
switch (providerName) {
|
|
172
|
+
case "default":
|
|
173
|
+
case "bundled":
|
|
174
|
+
case "onnx":
|
|
175
|
+
provider = new BundledOnnxProvider();
|
|
176
|
+
break;
|
|
177
|
+
case "openai":
|
|
178
|
+
provider = new OpenAIProvider({
|
|
179
|
+
apiKey: env.SVERKLO_OPENAI_API_KEY || "",
|
|
180
|
+
model: env.SVERKLO_OPENAI_MODEL,
|
|
181
|
+
dimensions: env.SVERKLO_OPENAI_DIMENSIONS
|
|
182
|
+
? parseInt(env.SVERKLO_OPENAI_DIMENSIONS, 10)
|
|
183
|
+
: undefined,
|
|
184
|
+
});
|
|
185
|
+
break;
|
|
186
|
+
case "ollama":
|
|
187
|
+
provider = new OllamaProvider({
|
|
188
|
+
endpoint: env.SVERKLO_OLLAMA_URL,
|
|
189
|
+
model: env.SVERKLO_OLLAMA_MODEL,
|
|
190
|
+
dimensions: env.SVERKLO_OLLAMA_DIMENSIONS
|
|
191
|
+
? parseInt(env.SVERKLO_OLLAMA_DIMENSIONS, 10)
|
|
192
|
+
: undefined,
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
default:
|
|
196
|
+
log(`[embedding] Unknown provider '${providerName}'. Falling back to default (bundled ONNX).`);
|
|
197
|
+
provider = new BundledOnnxProvider();
|
|
198
|
+
}
|
|
199
|
+
await provider.init();
|
|
200
|
+
if (providerName !== "default") {
|
|
201
|
+
log(`[embedding] Using ${provider.name} (${provider.dimensions} dims) — override with SVERKLO_EMBEDDING_PROVIDER.`);
|
|
202
|
+
}
|
|
203
|
+
return provider;
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
log(`[embedding] Provider '${providerName}' init failed: ${err.message}. Falling back to default.`);
|
|
207
|
+
const fallback = new BundledOnnxProvider();
|
|
208
|
+
await fallback.init();
|
|
209
|
+
return fallback;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export function fingerprintOf(p) {
|
|
213
|
+
return { provider: p.name, dimensions: p.dimensions };
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=embedding-providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-providers.js","sourceRoot":"","sources":["../../../src/indexer/embedding-providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { createEmbeddingProvider, fingerprintOf } from "./embedding-providers.js";
|
|
3
|
+
// We mock the underlying embedder module so tests don't try to load
|
|
4
|
+
// the real ONNX runtime or download the model.
|
|
5
|
+
vi.mock("./embedder.js", () => ({
|
|
6
|
+
initEmbedder: vi.fn(async () => { }),
|
|
7
|
+
embed: vi.fn(async (texts) => texts.map(() => new Float32Array(384))),
|
|
8
|
+
}));
|
|
9
|
+
describe("createEmbeddingProvider", () => {
|
|
10
|
+
const originalFetch = global.fetch;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
global.fetch = originalFetch;
|
|
16
|
+
});
|
|
17
|
+
it("defaults to the bundled ONNX provider when no env var is set", async () => {
|
|
18
|
+
const p = await createEmbeddingProvider({});
|
|
19
|
+
expect(p.name).toBe("default");
|
|
20
|
+
expect(p.dimensions).toBe(384);
|
|
21
|
+
});
|
|
22
|
+
it("accepts provider aliases (bundled, onnx)", async () => {
|
|
23
|
+
const p1 = await createEmbeddingProvider({ SVERKLO_EMBEDDING_PROVIDER: "bundled" });
|
|
24
|
+
const p2 = await createEmbeddingProvider({ SVERKLO_EMBEDDING_PROVIDER: "onnx" });
|
|
25
|
+
expect(p1.name).toBe("default");
|
|
26
|
+
expect(p2.name).toBe("default");
|
|
27
|
+
});
|
|
28
|
+
it("creates an OpenAI provider when requested and API key is set", async () => {
|
|
29
|
+
const p = await createEmbeddingProvider({
|
|
30
|
+
SVERKLO_EMBEDDING_PROVIDER: "openai",
|
|
31
|
+
SVERKLO_OPENAI_API_KEY: "sk-test",
|
|
32
|
+
});
|
|
33
|
+
expect(p.name).toContain("openai");
|
|
34
|
+
expect(p.dimensions).toBe(1536);
|
|
35
|
+
});
|
|
36
|
+
it("falls back to default when OpenAI is requested without an API key", async () => {
|
|
37
|
+
const p = await createEmbeddingProvider({
|
|
38
|
+
SVERKLO_EMBEDDING_PROVIDER: "openai",
|
|
39
|
+
});
|
|
40
|
+
// Init throws on missing key → factory falls back to default.
|
|
41
|
+
expect(p.name).toBe("default");
|
|
42
|
+
});
|
|
43
|
+
it("respects SVERKLO_OPENAI_DIMENSIONS override", async () => {
|
|
44
|
+
const p = await createEmbeddingProvider({
|
|
45
|
+
SVERKLO_EMBEDDING_PROVIDER: "openai",
|
|
46
|
+
SVERKLO_OPENAI_API_KEY: "sk-test",
|
|
47
|
+
SVERKLO_OPENAI_DIMENSIONS: "512",
|
|
48
|
+
});
|
|
49
|
+
expect(p.dimensions).toBe(512);
|
|
50
|
+
});
|
|
51
|
+
it("creates an Ollama provider when the endpoint probe succeeds", async () => {
|
|
52
|
+
global.fetch = vi.fn(async () => new Response(JSON.stringify({ embedding: new Array(768).fill(0) }), { status: 200 }));
|
|
53
|
+
const p = await createEmbeddingProvider({
|
|
54
|
+
SVERKLO_EMBEDDING_PROVIDER: "ollama",
|
|
55
|
+
});
|
|
56
|
+
expect(p.name).toContain("ollama");
|
|
57
|
+
expect(p.dimensions).toBe(768);
|
|
58
|
+
});
|
|
59
|
+
it("falls back to default when Ollama is unreachable", async () => {
|
|
60
|
+
global.fetch = vi.fn(async () => {
|
|
61
|
+
throw new Error("ECONNREFUSED");
|
|
62
|
+
});
|
|
63
|
+
const p = await createEmbeddingProvider({
|
|
64
|
+
SVERKLO_EMBEDDING_PROVIDER: "ollama",
|
|
65
|
+
});
|
|
66
|
+
expect(p.name).toBe("default");
|
|
67
|
+
});
|
|
68
|
+
it("falls back to default for unknown provider names", async () => {
|
|
69
|
+
const p = await createEmbeddingProvider({
|
|
70
|
+
SVERKLO_EMBEDDING_PROVIDER: "magic-ai",
|
|
71
|
+
});
|
|
72
|
+
expect(p.name).toBe("default");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("fingerprintOf", () => {
|
|
76
|
+
it("captures provider name and dimensions", async () => {
|
|
77
|
+
const p = await createEmbeddingProvider({});
|
|
78
|
+
const fp = fingerprintOf(p);
|
|
79
|
+
expect(fp.provider).toBe("default");
|
|
80
|
+
expect(fp.dimensions).toBe(384);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=embedding-providers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-providers.test.js","sourceRoot":"","sources":["../../../src/indexer/embedding-providers.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Indexer } from "./indexer.js";
|
|
6
|
+
import { getProjectConfig } from "../utils/config.js";
|
|
7
|
+
// Tests for the freshness cache contract (issue #6). The cache exists
|
|
8
|
+
// to keep sverklo_status fast — the disk walk costs ~95ms on a small
|
|
9
|
+
// repo and agents can call status repeatedly in one session. The
|
|
10
|
+
// contract:
|
|
11
|
+
//
|
|
12
|
+
// 1. Result is cached for FRESHNESS_CACHE_MS (2s) after the first
|
|
13
|
+
// computation.
|
|
14
|
+
// 2. The cache is invalidated by explicit reindex / clearIndex.
|
|
15
|
+
// 3. The file watcher also invalidates on change events (tested via
|
|
16
|
+
// direct invalidateFreshnessCache() call since we don't want to
|
|
17
|
+
// stand up chokidar in a unit test).
|
|
18
|
+
describe("Indexer freshness cache", () => {
|
|
19
|
+
let tmpRoot;
|
|
20
|
+
let indexer;
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
tmpRoot = mkdtempSync(join(tmpdir(), "sverklo-freshness-"));
|
|
23
|
+
// Minimal real repo: one TypeScript file. Indexing this should
|
|
24
|
+
// take well under a second.
|
|
25
|
+
mkdirSync(join(tmpRoot, "src"), { recursive: true });
|
|
26
|
+
writeFileSync(join(tmpRoot, "src", "foo.ts"), "export function foo() { return 42; }\n", "utf-8");
|
|
27
|
+
const cfg = getProjectConfig(tmpRoot);
|
|
28
|
+
indexer = new Indexer(cfg);
|
|
29
|
+
await indexer.index();
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
try {
|
|
33
|
+
indexer.close();
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
try {
|
|
37
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
});
|
|
41
|
+
it("returns a freshness result with ageSeconds and dirty/missing lists", () => {
|
|
42
|
+
const result = indexer.getFreshness();
|
|
43
|
+
expect(result).toBeDefined();
|
|
44
|
+
expect(Array.isArray(result.dirtyFiles)).toBe(true);
|
|
45
|
+
expect(Array.isArray(result.missingFiles)).toBe(true);
|
|
46
|
+
// Either a number or null — never undefined
|
|
47
|
+
expect(result.ageSeconds === null || typeof result.ageSeconds === "number").toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it("serves from cache on rapid successive calls", () => {
|
|
50
|
+
// First call: compute. Second call within TTL: serve from cache.
|
|
51
|
+
// We can't directly observe "did it re-walk?" without instrumentation,
|
|
52
|
+
// but we can observe that the second call returns the *same*
|
|
53
|
+
// dirtyFiles array reference if we patch it — or more simply,
|
|
54
|
+
// measure that 100 calls in a row take negligible time (the cache
|
|
55
|
+
// hit path is O(1)). A perf threshold works as a regression guard.
|
|
56
|
+
const t0 = Date.now();
|
|
57
|
+
for (let i = 0; i < 100; i++) {
|
|
58
|
+
indexer.getFreshness();
|
|
59
|
+
}
|
|
60
|
+
const elapsed = Date.now() - t0;
|
|
61
|
+
// 100 cached reads should complete in <50ms on any machine that
|
|
62
|
+
// isn't actively on fire. Real disk walks would take 100× longer.
|
|
63
|
+
expect(elapsed).toBeLessThan(500);
|
|
64
|
+
});
|
|
65
|
+
it("reflects filesystem changes after invalidateFreshnessCache()", () => {
|
|
66
|
+
const first = indexer.getFreshness();
|
|
67
|
+
expect(first.dirtyFiles.length).toBe(0);
|
|
68
|
+
// Add a new file on disk (bypassing the watcher so we control timing)
|
|
69
|
+
writeFileSync(join(tmpRoot, "src", "bar.ts"), "export const bar = 1;\n", "utf-8");
|
|
70
|
+
// Without invalidation, the cached result persists for up to 2s.
|
|
71
|
+
// With invalidation, the next call sees the new file.
|
|
72
|
+
indexer.invalidateFreshnessCache();
|
|
73
|
+
const second = indexer.getFreshness();
|
|
74
|
+
expect(second.dirtyFiles.some((p) => p.includes("bar.ts"))).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
it("clearIndex() invalidates the cache", () => {
|
|
77
|
+
// Establish a cached result first
|
|
78
|
+
const beforeClear = indexer.getFreshness();
|
|
79
|
+
expect(beforeClear).toBeDefined();
|
|
80
|
+
// clearIndex nukes the database and reinitializes. The cache must
|
|
81
|
+
// be cleared or sverklo_status would report stale dirty/missing
|
|
82
|
+
// lists against the (now empty) index.
|
|
83
|
+
indexer.clearIndex();
|
|
84
|
+
// After clear, the indexed file count is 0 — so the freshness
|
|
85
|
+
// result should show all on-disk files as dirty (new to the index).
|
|
86
|
+
const afterClear = indexer.getFreshness();
|
|
87
|
+
expect(afterClear.dirtyFiles.length).toBeGreaterThan(0);
|
|
88
|
+
expect(afterClear.missingFiles.length).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
it("updates ageSeconds even when serving from cache", () => {
|
|
91
|
+
// The cache stores the expensive disk-walk result but still
|
|
92
|
+
// recomputes ageSeconds on every call (wall clock moves on).
|
|
93
|
+
const first = indexer.getFreshness();
|
|
94
|
+
const firstAge = first.ageSeconds;
|
|
95
|
+
expect(typeof firstAge).toBe("number");
|
|
96
|
+
// Wait a moment and call again. Age should advance even though
|
|
97
|
+
// the dirty/missing lists are cached.
|
|
98
|
+
vi.useFakeTimers();
|
|
99
|
+
vi.advanceTimersByTime(1500);
|
|
100
|
+
const second = indexer.getFreshness();
|
|
101
|
+
vi.useRealTimers();
|
|
102
|
+
// Age on the second call should be higher than the first (by ~1s).
|
|
103
|
+
// Allow some slack for the test harness timing.
|
|
104
|
+
if (typeof firstAge === "number" && typeof second.ageSeconds === "number") {
|
|
105
|
+
expect(second.ageSeconds).toBeGreaterThanOrEqual(firstAge);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
//# sourceMappingURL=indexer-freshness.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer-freshness.test.js","sourceRoot":"","sources":["../../../src/indexer/indexer-freshness.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Indexer } from "./indexer.js";
|
|
6
|
+
import { getProjectConfig } from "../utils/config.js";
|
|
7
|
+
// Integration test that would have caught the issue #9 wiring gap:
|
|
8
|
+
// the provider factory existed and its unit tests passed, but the
|
|
9
|
+
// Indexer never actually called the factory — it imported legacyEmbed
|
|
10
|
+
// directly from ./embedder.js and used it everywhere. Users setting
|
|
11
|
+
// SVERKLO_EMBEDDING_PROVIDER=openai silently got the bundled ONNX
|
|
12
|
+
// model and no visible error.
|
|
13
|
+
//
|
|
14
|
+
// The fix was to lazily initialize the provider on the first index()
|
|
15
|
+
// call and expose it via `indexer.embed()` / `indexer.embeddingProviderName`.
|
|
16
|
+
// These tests lock that wiring in so a refactor can't silently break
|
|
17
|
+
// it again.
|
|
18
|
+
//
|
|
19
|
+
// We don't hit real OpenAI / Ollama endpoints here. The point isn't
|
|
20
|
+
// to prove the providers work (their own unit tests do that). The
|
|
21
|
+
// point is to prove the indexer *uses* whichever provider the env
|
|
22
|
+
// vars selected, instead of always defaulting.
|
|
23
|
+
describe("Indexer + embedding provider integration", () => {
|
|
24
|
+
let tmpRoot;
|
|
25
|
+
const originalEnv = { ...process.env };
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
tmpRoot = mkdtempSync(join(tmpdir(), "sverklo-provider-int-"));
|
|
28
|
+
mkdirSync(join(tmpRoot, "src"), { recursive: true });
|
|
29
|
+
writeFileSync(join(tmpRoot, "src", "a.ts"), "export function hello() { return 'world'; }\n", "utf-8");
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
try {
|
|
33
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
// Restore env so tests don't leak into each other.
|
|
37
|
+
for (const key of Object.keys(process.env)) {
|
|
38
|
+
if (key.startsWith("SVERKLO_") && !(key in originalEnv)) {
|
|
39
|
+
delete process.env[key];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const [k, v] of Object.entries(originalEnv)) {
|
|
43
|
+
if (k.startsWith("SVERKLO_"))
|
|
44
|
+
process.env[k] = v;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it("defaults to the bundled provider when no env var is set", async () => {
|
|
48
|
+
delete process.env.SVERKLO_EMBEDDING_PROVIDER;
|
|
49
|
+
const indexer = new Indexer(getProjectConfig(tmpRoot));
|
|
50
|
+
try {
|
|
51
|
+
await indexer.index();
|
|
52
|
+
expect(indexer.embeddingProviderName).toBe("default");
|
|
53
|
+
expect(indexer.embeddingDimensions).toBe(384);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
indexer.close();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
it("selects the OpenAI provider when SVERKLO_EMBEDDING_PROVIDER=openai and key is set", async () => {
|
|
60
|
+
// We can't actually call OpenAI in a test — patch fetch to a
|
|
61
|
+
// deterministic stub so the provider init succeeds. The indexer
|
|
62
|
+
// then commits to the openai provider for the rest of its life.
|
|
63
|
+
process.env.SVERKLO_EMBEDDING_PROVIDER = "openai";
|
|
64
|
+
process.env.SVERKLO_OPENAI_API_KEY = "sk-test";
|
|
65
|
+
// Mock fetch so the embedder calls look successful and return
|
|
66
|
+
// the right number of dimensions per request.
|
|
67
|
+
const originalFetch = global.fetch;
|
|
68
|
+
global.fetch = vi.fn(async (_url, init) => {
|
|
69
|
+
const body = JSON.parse(init.body);
|
|
70
|
+
const input = Array.isArray(body.input) ? body.input : [body.input];
|
|
71
|
+
return new Response(JSON.stringify({
|
|
72
|
+
data: input.map((_, i) => ({
|
|
73
|
+
index: i,
|
|
74
|
+
embedding: new Array(1536).fill(0),
|
|
75
|
+
})),
|
|
76
|
+
}), { status: 200 });
|
|
77
|
+
});
|
|
78
|
+
try {
|
|
79
|
+
const indexer = new Indexer(getProjectConfig(tmpRoot));
|
|
80
|
+
try {
|
|
81
|
+
await indexer.index();
|
|
82
|
+
expect(indexer.embeddingProviderName).toContain("openai");
|
|
83
|
+
expect(indexer.embeddingDimensions).toBe(1536);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
indexer.close();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
global.fetch = originalFetch;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
it("falls back to default when OpenAI is requested but the API key is missing", async () => {
|
|
94
|
+
process.env.SVERKLO_EMBEDDING_PROVIDER = "openai";
|
|
95
|
+
delete process.env.SVERKLO_OPENAI_API_KEY;
|
|
96
|
+
const indexer = new Indexer(getProjectConfig(tmpRoot));
|
|
97
|
+
try {
|
|
98
|
+
await indexer.index();
|
|
99
|
+
// Factory init() throws on missing key → factory falls back to
|
|
100
|
+
// default. The whole point of the wiring is that this fallback
|
|
101
|
+
// is visible, not silent.
|
|
102
|
+
expect(indexer.embeddingProviderName).toBe("default");
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
indexer.close();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
it("initializes the provider lazily on first embed() if index() hasn't run yet", async () => {
|
|
109
|
+
delete process.env.SVERKLO_EMBEDDING_PROVIDER;
|
|
110
|
+
const indexer = new Indexer(getProjectConfig(tmpRoot));
|
|
111
|
+
try {
|
|
112
|
+
// Call embed() without first calling index(). The embed method
|
|
113
|
+
// should fall back to the legacy path and still return vectors
|
|
114
|
+
// — this covers agents that hit search/recall before the first
|
|
115
|
+
// index cycle completes.
|
|
116
|
+
const vecs = await indexer.embed(["hello world"]);
|
|
117
|
+
expect(Array.isArray(vecs)).toBe(true);
|
|
118
|
+
expect(vecs.length).toBe(1);
|
|
119
|
+
expect(vecs[0]).toBeInstanceOf(Float32Array);
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
indexer.close();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
it("reuses the same provider across multiple index() calls", async () => {
|
|
126
|
+
delete process.env.SVERKLO_EMBEDDING_PROVIDER;
|
|
127
|
+
const indexer = new Indexer(getProjectConfig(tmpRoot));
|
|
128
|
+
try {
|
|
129
|
+
await indexer.index();
|
|
130
|
+
const firstName = indexer.embeddingProviderName;
|
|
131
|
+
// Write a new file and reindex
|
|
132
|
+
writeFileSync(join(tmpRoot, "src", "b.ts"), "export function goodbye() { return 'world'; }\n", "utf-8");
|
|
133
|
+
await indexer.index();
|
|
134
|
+
// Provider identity stays stable — reindex should not reselect.
|
|
135
|
+
expect(indexer.embeddingProviderName).toBe(firstName);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
indexer.close();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=indexer-provider-integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexer-provider-integration.test.js","sourceRoot":"","sources":["../../../src/indexer/indexer-provider-integration.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,mEAAmE;AACnE,kEAAkE;AAClE,sEAAsE;AACtE,oEAAoE;AACpE,kEAAkE;AAClE,8BAA8B;AAC9B,EAAE;AACF,qEAAqE;AACrE,8EAA8E;AAC9E,qEAAqE;AACrE,YAAY;AACZ,EAAE;AACF,oEAAoE;AACpE,kEAAkE;AAClE,kEAAkE;AAClE,+CAA+C;AAE/C,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACxD,IAAI,OAAe,CAAC;IACpB,MAAM,WAAW,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAEvC,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;QAC/D,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,aAAa,CACX,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,EAC5B,+CAA+C,EAC/C,OAAO,CACR,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC;YACH,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,mDAAmD;QACnD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3C,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC;gBACxD,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,OAAO,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;QAE9C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtD,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChD,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;QACjG,6DAA6D;QAC7D,gEAAgE;QAChE,gEAAgE;QAChE,OAAO,CAAC,GAAG,CAAC,0BAA0B,GAAG,QAAQ,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,sBAAsB,GAAG,SAAS,CAAC;QAE/C,8DAA8D;QAC9D,8CAA8C;QAC9C,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC;QACnC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,IAAa,EAAE,IAAc,EAAE,EAAE;YAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAE,IAAyB,CAAC,IAAI,CAAC,CAAC;YACzD,MAAM,KAAK,GAAa,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC9E,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;gBACb,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;oBACzB,KAAK,EAAE,CAAC;oBACR,SAAS,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;iBACnC,CAAC,CAAC;aACJ,CAAC,EACF,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAC;QACJ,CAAC,CAA4B,CAAC;QAE9B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;YACvD,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;gBACtB,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;gBAC1D,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjD,CAAC;oBAAS,CAAC;gBACT,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,KAAK,GAAG,aAAa,CAAC;QAC/B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACzF,OAAO,CAAC,GAAG,CAAC,0BAA0B,GAAG,QAAQ,CAAC;QAClD,OAAO,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;QAE1C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,+DAA+D;YAC/D,+DAA+D;YAC/D,0BAA0B;YAC1B,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxD,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;QAC1F,OAAO,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;QAE9C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC;YACH,+DAA+D;YAC/D,+DAA+D;YAC/D,+DAA+D;YAC/D,yBAAyB;YACzB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;YAClD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;QAC/C,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,OAAO,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;QAE9C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,MAAM,SAAS,GAAG,OAAO,CAAC,qBAAqB,CAAC;YAEhD,+BAA+B;YAC/B,aAAa,CACX,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,EAC5B,iDAAiD,EACjD,OAAO,CACR,CAAC;YACF,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YAEtB,gEAAgE;YAChE,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxD,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|