@takara-ai/miru-code 1.0.5 → 1.1.0

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.
@@ -1,4 +1,4 @@
1
- import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
1
+ import { chmod, mkdir } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { hasTakaraApiKeyInEnv, normalizeTakaraApiKeyEnv } from "./env.ts";
4
4
 
@@ -37,7 +37,7 @@ export async function readStoredCredentials(): Promise<StoredCredentials | null>
37
37
  return null;
38
38
  }
39
39
  try {
40
- const parsed = JSON.parse(await readFile(path, "utf-8")) as StoredCredentials;
40
+ const parsed = JSON.parse(await Bun.file(path).text()) as StoredCredentials;
41
41
  if (parsed.version !== CREDENTIALS_VERSION || !parsed.takara_api_key) {
42
42
  return null;
43
43
  }
@@ -69,10 +69,7 @@ export async function saveStoredCredentials(apiKey: string): Promise<string> {
69
69
  version: CREDENTIALS_VERSION,
70
70
  takara_api_key: apiKey,
71
71
  };
72
- await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`, {
73
- encoding: "utf-8",
74
- mode: 0o600,
75
- });
72
+ await Bun.write(path, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
76
73
  try {
77
74
  await chmod(path, 0o600);
78
75
  } catch {
@@ -88,7 +85,7 @@ export async function clearStoredCredentials(): Promise<{ cleared: boolean; path
88
85
  }
89
86
 
90
87
  const stored = await readStoredCredentials();
91
- await rm(path, { force: true });
88
+ await Bun.file(path).delete();
92
89
 
93
90
  if (stored && process.env.TAKARA_API_KEY === stored.takara_api_key) {
94
91
  delete process.env.TAKARA_API_KEY;
@@ -1,8 +1,8 @@
1
1
  import { mapPool, resolveWorkerConcurrency } from "../concurrency.ts";
2
2
  import { envFirstString, envOptionalInt, resolveEmbeddingApiKey } from "../env.ts";
3
3
 
4
- const DEFAULT_MODEL = "ds1-miru-int8";
5
- const DEFAULT_BASE_URL = "https://infer.takara.ai/v1";
4
+ export const DEFAULT_EMBEDDING_MODEL = "ds1-miru-int8";
5
+ export const DEFAULT_EMBEDDING_BASE_URL = "https://infer.takara.ai/v1";
6
6
  const DEFAULT_BATCH_SIZE = 32;
7
7
  const DEFAULT_MAX_EMBED_CHARS = 1300;
8
8
  const WINDOW_OVERLAP_CHARS = 120;
@@ -32,7 +32,10 @@ export interface EmbeddingBackend {
32
32
  }
33
33
 
34
34
  export function resolveEmbeddingModel(): string {
35
- return envFirstString(["MIRU_OPENAI_EMBEDDING_MODEL", "OPENAI_EMBEDDING_MODEL"], DEFAULT_MODEL);
35
+ return envFirstString(
36
+ ["MIRU_OPENAI_EMBEDDING_MODEL", "OPENAI_EMBEDDING_MODEL"],
37
+ DEFAULT_EMBEDDING_MODEL,
38
+ );
36
39
  }
37
40
 
38
41
  export function resolveMaxEmbedChars(): number {
@@ -118,10 +121,10 @@ export function resolveEmbeddingBatchSize(): number {
118
121
  }
119
122
 
120
123
  export function resolveEmbeddingBaseUrl(): string {
121
- return envFirstString(["MIRU_OPENAI_BASE_URL", "OPENAI_BASE_URL"], DEFAULT_BASE_URL).replace(
122
- /\/$/,
123
- "",
124
- );
124
+ return envFirstString(
125
+ ["MIRU_OPENAI_BASE_URL", "OPENAI_BASE_URL"],
126
+ DEFAULT_EMBEDDING_BASE_URL,
127
+ ).replace(/\/$/, "");
125
128
  }
126
129
 
127
130
  interface Int8Embedding {
package/src/env-files.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
 
4
3
  function applyEnvLine(line: string): void {
@@ -24,21 +23,25 @@ function applyEnvLine(line: string): void {
24
23
  process.env[key] = value;
25
24
  }
26
25
 
27
- function loadEnvFile(path: string): void {
28
- if (!existsSync(path)) {
26
+ async function loadEnvFile(path: string): Promise<void> {
27
+ const file = Bun.file(path);
28
+ if (!(await file.exists())) {
29
29
  return;
30
30
  }
31
- for (const line of readFileSync(path, "utf-8").split("\n")) {
31
+ for (const line of (await file.text()).split("\n")) {
32
32
  applyEnvLine(line);
33
33
  }
34
34
  }
35
35
 
36
36
  /** Load .env.local / .env without overriding vars already set (e.g. MCP config env). */
37
- export function loadEnvFiles(options?: { packageRoot?: string; cwd?: string }): void {
37
+ export async function loadEnvFiles(options?: {
38
+ packageRoot?: string;
39
+ cwd?: string;
40
+ }): Promise<void> {
38
41
  const cwd = options?.cwd ?? process.cwd();
39
42
  const packageRoot = options?.packageRoot ?? join(import.meta.dir, "..");
40
43
  for (const dir of [packageRoot, cwd]) {
41
- loadEnvFile(join(dir, ".env.local"));
42
- loadEnvFile(join(dir, ".env"));
44
+ await loadEnvFile(join(dir, ".env.local"));
45
+ await loadEnvFile(join(dir, ".env"));
43
46
  }
44
47
  }
package/src/help.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { AgentId } from "./agents.ts";
2
2
  import {
3
- brandTitle,
3
+ commandHeader,
4
4
  commandRow,
5
5
  divider,
6
6
  fail,
@@ -9,6 +9,9 @@ import {
9
9
  section,
10
10
  writeStdout,
11
11
  } from "./cli-ui.ts";
12
+ import { DEFAULT_RESERVE_CORES } from "./concurrency.ts";
13
+ import { DEFAULT_EMBEDDING_BASE_URL, DEFAULT_EMBEDDING_MODEL } from "./embeddings/openai.ts";
14
+ import { TAKARA_API_KEY_ENV } from "./env.ts";
12
15
 
13
16
  export const AGENT_IDS: readonly AgentId[] = [
14
17
  "claude",
@@ -22,7 +25,7 @@ export const AGENT_IDS: readonly AgentId[] = [
22
25
  const AGENT_LIST = AGENT_IDS.join(", ");
23
26
 
24
27
  export function printMainHelp(): void {
25
- header("hybrid code search for agents");
28
+ header();
26
29
 
27
30
  section("Usage");
28
31
  writeStdout(" miru Start MCP server (stdio)");
@@ -51,14 +54,14 @@ export function printMainHelp(): void {
51
54
 
52
55
  export function printEnvHelp(): void {
53
56
  section("Environment");
54
- writeStdout(" TAKARA_API_KEY");
57
+ writeStdout(` ${TAKARA_API_KEY_ENV}`);
55
58
  writeStdout(" Takara bearer token for embeddings");
56
59
  writeStdout(" MIRU_OPENAI_BASE_URL");
57
- writeStdout(" Default: https://infer.takara.ai/v1");
60
+ writeStdout(` Default: ${DEFAULT_EMBEDDING_BASE_URL}`);
58
61
  writeStdout(" MIRU_OPENAI_EMBEDDING_MODEL");
59
- writeStdout(" Default: ds1-miru-int8");
62
+ writeStdout(` Default: ${DEFAULT_EMBEDDING_MODEL}`);
60
63
  writeStdout(" MIRU_CONCURRENCY");
61
- writeStdout(" Parallel workers (default: CPUs − 2)");
64
+ writeStdout(` Parallel workers (default: CPUs − ${DEFAULT_RESERVE_CORES})`);
62
65
  writeStdout("");
63
66
  }
64
67
 
@@ -67,17 +70,10 @@ export function printFullHelp(): void {
67
70
  printEnvHelp();
68
71
  }
69
72
 
70
- function printCommandHeader(name: string, summary: string): void {
71
- writeStdout("");
72
- writeStdout(`${brandTitle()} ${name}`);
73
- writeStdout(summary);
74
- divider();
75
- }
76
-
77
73
  export function printCommandHelp(command: string): void {
78
74
  switch (command) {
79
75
  case "search":
80
- printCommandHeader("search", "Hybrid semantic + keyword search.");
76
+ commandHeader("search", "Hybrid semantic + keyword search.");
81
77
  section("Usage");
82
78
  writeStdout(" miru search <query> [path] [options]");
83
79
  section("Options");
@@ -89,7 +85,7 @@ export function printCommandHelp(command: string): void {
89
85
  writeStdout("");
90
86
  return;
91
87
  case "expand":
92
- printCommandHeader("expand", "More context in the same file as a search hit.");
88
+ commandHeader("expand", "More context in the same file as a search hit.");
93
89
  section("Usage");
94
90
  writeStdout(" miru expand <file> <line> [path] [--before N] [--after N]");
95
91
  section("Example");
@@ -97,7 +93,7 @@ export function printCommandHelp(command: string): void {
97
93
  writeStdout("");
98
94
  return;
99
95
  case "find-related":
100
- printCommandHeader("find-related", "Semantic neighbors of a file location.");
96
+ commandHeader("find-related", "Semantic neighbors of a file location.");
101
97
  section("Usage");
102
98
  writeStdout(" miru find-related <file> <line> [path] [options]");
103
99
  section("Example");
@@ -105,7 +101,7 @@ export function printCommandHelp(command: string): void {
105
101
  writeStdout("");
106
102
  return;
107
103
  case "setup":
108
- printCommandHeader("setup", "Store and validate your Takara API key.");
104
+ commandHeader("setup", "Store and validate your Takara API key.");
109
105
  section("Usage");
110
106
  writeStdout(" miru setup [--key TOKEN] [--force] [--clear]");
111
107
  section("Options");
@@ -115,7 +111,7 @@ export function printCommandHelp(command: string): void {
115
111
  writeStdout("");
116
112
  return;
117
113
  case "install":
118
- printCommandHeader("install", "Interactive global agent setup.");
114
+ commandHeader("install", "Interactive global agent setup.");
119
115
  writeStdout("Configures MCP server, instructions, and sub-agent files under");
120
116
  writeStdout("your user config (~/.claude, ~/.cursor, etc.).");
121
117
  writeStdout("");
@@ -123,12 +119,12 @@ export function printCommandHelp(command: string): void {
123
119
  writeStdout("");
124
120
  return;
125
121
  case "uninstall":
126
- printCommandHeader("uninstall", "Remove miru configuration from agents.");
122
+ commandHeader("uninstall", "Remove miru configuration from agents.");
127
123
  writeStdout("Removes MCP entries, marked instruction blocks, and global sub-agents.");
128
124
  writeStdout("");
129
125
  return;
130
126
  case "init":
131
- printCommandHeader("init", "Project-local sub-agent file.");
127
+ commandHeader("init", "Project-local sub-agent file.");
132
128
  writeStdout("Prefer miru install for global setup. Use init to commit into a repo.");
133
129
  section("Usage");
134
130
  writeStdout(" miru init --agent AGENT [--force]");
@@ -139,7 +135,7 @@ export function printCommandHelp(command: string): void {
139
135
  writeStdout("");
140
136
  return;
141
137
  case "clear":
142
- printCommandHeader("clear", "Drop the on-disk index cache.");
138
+ commandHeader("clear", "Drop the on-disk index cache.");
143
139
  section("Usage");
144
140
  writeStdout(" miru clear [path]");
145
141
  section("Example");
@@ -147,7 +143,7 @@ export function printCommandHelp(command: string): void {
147
143
  writeStdout("");
148
144
  return;
149
145
  case "mcp":
150
- printCommandHeader("mcp", "Stdio MCP server (default with no subcommand).");
146
+ commandHeader("mcp", "Stdio MCP server (default with no subcommand).");
151
147
  section("Usage");
152
148
  writeStdout(" miru [--ref BRANCH] [--content TYPE ...]");
153
149
  writeStdout("");
@@ -0,0 +1,57 @@
1
+ export type ChunkIndexMappings = {
2
+ fileMapping: ReadonlyMap<string, readonly number[]>;
3
+ languageMapping: ReadonlyMap<string, readonly number[]>;
4
+ };
5
+
6
+ /**
7
+ * Union of chunk indices matching language and/or path filters.
8
+ *
9
+ * Filters combine with OR semantics: a chunk is included if it matches any
10
+ * requested language or path. Returns `undefined` when no filters are given
11
+ * (search all chunks) or when no chunks match.
12
+ *
13
+ * Single-filter queries return the prebuilt map array directly (read-only for
14
+ * callers). Multi-filter queries allocate a deduped index list.
15
+ */
16
+ export function buildChunkSelector(
17
+ mappings: ChunkIndexMappings,
18
+ filterLanguages?: readonly string[],
19
+ filterPaths?: readonly string[],
20
+ ): readonly number[] | undefined {
21
+ const langCount = filterLanguages?.length ?? 0;
22
+ const pathCount = filterPaths?.length ?? 0;
23
+
24
+ if (langCount === 0 && pathCount === 0) {
25
+ return undefined;
26
+ }
27
+
28
+ if (langCount === 1 && pathCount === 0) {
29
+ const lang = filterLanguages?.[0];
30
+ if (!lang) {
31
+ return undefined;
32
+ }
33
+ const indices = mappings.languageMapping.get(lang);
34
+ return indices && indices.length > 0 ? indices : undefined;
35
+ }
36
+
37
+ if (pathCount === 1 && langCount === 0) {
38
+ const fp = filterPaths?.[0];
39
+ if (!fp) {
40
+ return undefined;
41
+ }
42
+ const indices = mappings.fileMapping.get(fp);
43
+ return indices && indices.length > 0 ? indices : undefined;
44
+ }
45
+
46
+ const selector: number[] = [];
47
+ for (const lang of filterLanguages ?? []) {
48
+ selector.push(...(mappings.languageMapping.get(lang) ?? []));
49
+ }
50
+ for (const fp of filterPaths ?? []) {
51
+ selector.push(...(mappings.fileMapping.get(fp) ?? []));
52
+ }
53
+ if (selector.length === 0) {
54
+ return undefined;
55
+ }
56
+ return [...new Set(selector)];
57
+ }
@@ -1,70 +1,114 @@
1
- /** Cosine-distance vector index with optional chunk selector. */
2
- import { mkdir } from "node:fs/promises";
1
+ /** Uncompressed float32 semantic index. Debug/precision baseline only; production uses int8. */
3
2
  import type { SemanticIndex } from "./semantic-index.ts";
4
- import { selectTopKByDistance } from "./topk.ts";
3
+ import { TopKDistanceCollector } from "./topk.ts";
4
+
5
+ function cosineDistanceFlat(
6
+ data: Float32Array,
7
+ dim: number,
8
+ docIndex: number,
9
+ query: Float32Array,
10
+ ): number {
11
+ const offset = docIndex * dim;
12
+ let dot = 0;
13
+ for (let i = 0; i < dim; i++) {
14
+ dot += (query[i] ?? 0) * (data[offset + i] ?? 0);
15
+ }
16
+ return 1 - dot;
17
+ }
5
18
 
6
19
  export class VectorIndex implements SemanticIndex {
7
- private vectors: Float32Array[];
20
+ private readonly data: Float32Array;
21
+ private readonly count: number;
22
+ private readonly dim: number;
23
+ private vectorsCache: Float32Array[] | null = null;
8
24
 
9
25
  constructor(vectors: Float32Array[]) {
10
- this.vectors = vectors;
26
+ if (vectors.length === 0) {
27
+ this.count = 0;
28
+ this.dim = 0;
29
+ this.data = new Float32Array(0);
30
+ return;
31
+ }
32
+ this.count = vectors.length;
33
+ this.dim = vectors[0]?.length ?? 0;
34
+ this.data = new Float32Array(this.count * this.dim);
35
+ for (let i = 0; i < this.count; i++) {
36
+ const vec = vectors[i];
37
+ if (vec) {
38
+ this.data.set(vec, i * this.dim);
39
+ }
40
+ }
41
+ }
42
+
43
+ static fromFlatBuffer(data: Float32Array, count: number, dim: number): VectorIndex {
44
+ const index = Object.create(VectorIndex.prototype) as VectorIndex;
45
+ Object.assign(index, { data, count, dim });
46
+ return index;
11
47
  }
12
48
 
13
49
  get size(): number {
14
- return this.vectors.length;
50
+ return this.count;
15
51
  }
16
52
 
17
53
  get dimensions(): number {
18
- return this.vectors[0]?.length ?? 0;
54
+ return this.dim;
19
55
  }
20
56
 
21
57
  getVectors(): readonly Float32Array[] {
22
- return this.vectors;
58
+ if (this.vectorsCache) {
59
+ return this.vectorsCache;
60
+ }
61
+ const vectors: Float32Array[] = [];
62
+ for (let i = 0; i < this.count; i++) {
63
+ vectors.push(this.data.subarray(i * this.dim, (i + 1) * this.dim));
64
+ }
65
+ this.vectorsCache = vectors;
66
+ return vectors;
23
67
  }
24
68
 
25
69
  vectorAt(docIndex: number): Float32Array {
26
- const vec = this.vectors[docIndex];
27
- if (!vec) {
70
+ if (docIndex < 0 || docIndex >= this.count) {
28
71
  throw new Error(`Missing vector at index ${docIndex}`);
29
72
  }
30
- return vec;
73
+ return this.data.subarray(docIndex * this.dim, (docIndex + 1) * this.dim);
31
74
  }
32
75
 
33
76
  memoryBytes(): number {
34
- return this.vectors.length * this.dimensions * 4;
77
+ return this.count * this.dim * 4;
35
78
  }
36
79
 
37
80
  query(
38
81
  queryVector: Float32Array,
39
82
  k: number,
40
- selector?: number[],
83
+ selector?: readonly number[],
41
84
  ): { indices: number[]; distances: number[] } {
42
85
  if (k < 1) {
43
86
  throw new Error(`k should be >= 1, is now ${k}`);
44
87
  }
45
-
46
- const numVectors = this.vectors.length;
47
- if (numVectors === 0) {
88
+ if (this.count === 0) {
48
89
  return { indices: [], distances: [] };
49
90
  }
50
91
 
51
- const indices = selector ?? Array.from({ length: numVectors }, (_, i) => i);
52
- const effectiveK = Math.min(k, indices.length);
92
+ const effectiveK = Math.min(k, selector?.length ?? this.count);
53
93
  if (effectiveK === 0) {
54
94
  return { indices: [], distances: [] };
55
95
  }
56
96
 
57
- const top = selectTopKByDistance(
58
- indices.flatMap((idx) => {
59
- const vec = this.vectors[idx];
60
- if (!vec) {
61
- return [];
97
+ const collector = new TopKDistanceCollector(effectiveK);
98
+ if (selector) {
99
+ for (const idx of selector) {
100
+ if (idx < 0 || idx >= this.count) {
101
+ continue;
62
102
  }
63
- return [{ index: idx, distance: cosineDistance(queryVector, vec) }];
64
- }),
65
- effectiveK,
66
- );
103
+ collector.offer(idx, cosineDistanceFlat(this.data, this.dim, idx, queryVector));
104
+ }
105
+ } else {
106
+ for (let i = 0; i < this.count; i++) {
107
+ collector.offer(i, cosineDistanceFlat(this.data, this.dim, i, queryVector));
108
+ }
109
+ }
67
110
 
111
+ const top = collector.finish();
68
112
  return {
69
113
  indices: top.map((t) => t.index),
70
114
  distances: top.map((t) => t.distance),
@@ -72,21 +116,10 @@ export class VectorIndex implements SemanticIndex {
72
116
  }
73
117
 
74
118
  async save(dir: string): Promise<void> {
75
- await mkdir(dir, { recursive: true });
76
-
77
- const dim = this.dimensions;
78
- const count = this.size;
79
- const buffer = new Float32Array(count * dim);
80
- for (let i = 0; i < count; i++) {
81
- const vec = this.vectors[i];
82
- if (vec) {
83
- buffer.set(vec, i * dim);
84
- }
85
- }
86
- await Bun.write(`${dir}/vectors.bin`, buffer);
119
+ await Bun.write(`${dir}/vectors.bin`, this.data);
87
120
  await Bun.write(
88
121
  `${dir}/meta.json`,
89
- JSON.stringify({ count, dimensions: dim, storage: "float32" }),
122
+ JSON.stringify({ count: this.count, dimensions: this.dim, storage: "float32" }),
90
123
  );
91
124
  }
92
125
 
@@ -95,19 +128,7 @@ export class VectorIndex implements SemanticIndex {
95
128
  count: number;
96
129
  dimensions: number;
97
130
  };
98
- const raw = new Float32Array(await Bun.file(`${dir}/vectors.bin`).arrayBuffer());
99
- const vectors: Float32Array[] = [];
100
- for (let i = 0; i < meta.count; i++) {
101
- vectors.push(raw.subarray(i * meta.dimensions, (i + 1) * meta.dimensions));
102
- }
103
- return new VectorIndex(vectors);
131
+ const data = new Float32Array(await Bun.file(`${dir}/vectors.bin`).arrayBuffer());
132
+ return VectorIndex.fromFlatBuffer(data, meta.count, meta.dimensions);
104
133
  }
105
134
  }
106
-
107
- function cosineDistance(a: Float32Array, b: Float32Array): number {
108
- let dot = 0;
109
- for (let i = 0; i < a.length; i++) {
110
- dot += (a[i] ?? 0) * (b[i] ?? 0);
111
- }
112
- return 1 - dot;
113
- }
@@ -1,7 +1,7 @@
1
1
  import { join, relative } from "node:path";
2
2
  import type { Chunk } from "../types.ts";
3
3
 
4
- function indexedPath(absPath: string, repoRoot: string, displayRoot?: string): string {
4
+ function indexedPath(absPath: string, _repoRoot: string, displayRoot?: string): string {
5
5
  if (displayRoot) {
6
6
  return relative(displayRoot, absPath).replace(/\\/g, "/");
7
7
  }
@@ -0,0 +1,55 @@
1
+ type QuantizedVector = {
2
+ codes: Int8Array;
3
+ scale: number;
4
+ };
5
+
6
+ /** Reference scalar int8 dot product (A/B regression baseline). */
7
+ export function quantizedDotFlatScalar(
8
+ query: QuantizedVector,
9
+ codes: Int8Array,
10
+ offset: number,
11
+ dim: number,
12
+ docScale: number,
13
+ ): number {
14
+ let sum = 0;
15
+ const q = query.codes;
16
+ for (let i = 0; i < dim; i++) {
17
+ sum += q[i] * codes[offset + i];
18
+ }
19
+ return sum * query.scale * docScale;
20
+ }
21
+
22
+ /** Production int8 dot product with 8-wide unrolling for JSC auto-vectorization. */
23
+ export function quantizedDotFlat(
24
+ query: QuantizedVector,
25
+ codes: Int8Array,
26
+ offset: number,
27
+ dim: number,
28
+ docScale: number,
29
+ ): number {
30
+ const q = query.codes;
31
+ let s0 = 0;
32
+ let s1 = 0;
33
+ let s2 = 0;
34
+ let s3 = 0;
35
+ let s4 = 0;
36
+ let s5 = 0;
37
+ let s6 = 0;
38
+ let s7 = 0;
39
+ let i = 0;
40
+ for (; i + 7 < dim; i += 8) {
41
+ s0 += q[i] * codes[offset + i];
42
+ s1 += q[i + 1] * codes[offset + i + 1];
43
+ s2 += q[i + 2] * codes[offset + i + 2];
44
+ s3 += q[i + 3] * codes[offset + i + 3];
45
+ s4 += q[i + 4] * codes[offset + i + 4];
46
+ s5 += q[i + 5] * codes[offset + i + 5];
47
+ s6 += q[i + 6] * codes[offset + i + 6];
48
+ s7 += q[i + 7] * codes[offset + i + 7];
49
+ }
50
+ let sum = s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7;
51
+ for (; i < dim; i++) {
52
+ sum += q[i] * codes[offset + i];
53
+ }
54
+ return sum * query.scale * docScale;
55
+ }
@@ -1,4 +1,3 @@
1
- import { mkdir } from "node:fs/promises";
2
1
  import { join } from "node:path";
3
2
  import { BM25Index } from "./bm25.ts";
4
3
  import { VectorIndex } from "./dense.ts";
@@ -78,7 +77,6 @@ export async function saveIndexBundle(options: {
78
77
  metadata: Record<string, unknown>;
79
78
  }): Promise<void> {
80
79
  const { paths, bm25, semantic, chunks, metadata } = options;
81
- await mkdir(paths.root, { recursive: true });
82
80
  await saveBm25(bm25, paths.bm25Index);
83
81
  await saveSemantic(semantic, paths.semanticIndex);
84
82
  await Bun.write(