obsidian-anchor 0.2.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -0
  3. package/dist/chunk/chunker.js +169 -0
  4. package/dist/chunk/chunker.js.map +1 -0
  5. package/dist/chunk/markdown.js +49 -0
  6. package/dist/chunk/markdown.js.map +1 -0
  7. package/dist/chunk/types.js +5 -0
  8. package/dist/chunk/types.js.map +1 -0
  9. package/dist/config.js +17 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/container.js +84 -0
  12. package/dist/container.js.map +1 -0
  13. package/dist/edit/safeEdit.js +130 -0
  14. package/dist/edit/safeEdit.js.map +1 -0
  15. package/dist/edit/snapshots.js +31 -0
  16. package/dist/edit/snapshots.js.map +1 -0
  17. package/dist/embeddings/local.js +61 -0
  18. package/dist/embeddings/local.js.map +1 -0
  19. package/dist/embeddings/openai.js +63 -0
  20. package/dist/embeddings/openai.js.map +1 -0
  21. package/dist/embeddings/provider.js +5 -0
  22. package/dist/embeddings/provider.js.map +1 -0
  23. package/dist/index/indexer.js +108 -0
  24. package/dist/index/indexer.js.map +1 -0
  25. package/dist/index/walk.js +28 -0
  26. package/dist/index/walk.js.map +1 -0
  27. package/dist/index/watcher.js +115 -0
  28. package/dist/index/watcher.js.map +1 -0
  29. package/dist/index.js +192 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/server.js +61 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/store/chunkStore.js +82 -0
  34. package/dist/store/chunkStore.js.map +1 -0
  35. package/dist/store/db.js +59 -0
  36. package/dist/store/db.js.map +1 -0
  37. package/dist/store/schema.js +67 -0
  38. package/dist/store/schema.js.map +1 -0
  39. package/dist/store/search.js +33 -0
  40. package/dist/store/search.js.map +1 -0
  41. package/dist/store/types.js +3 -0
  42. package/dist/store/types.js.map +1 -0
  43. package/dist/store/vectorStore.js +77 -0
  44. package/dist/store/vectorStore.js.map +1 -0
  45. package/dist/tools/cite.js +51 -0
  46. package/dist/tools/cite.js.map +1 -0
  47. package/dist/tools/restoreNote.js +49 -0
  48. package/dist/tools/restoreNote.js.map +1 -0
  49. package/dist/tools/safeEdit.js +65 -0
  50. package/dist/tools/safeEdit.js.map +1 -0
  51. package/dist/tools/searchNotes.js +47 -0
  52. package/dist/tools/searchNotes.js.map +1 -0
  53. package/dist/tools/verifyGrounding.js +66 -0
  54. package/dist/tools/verifyGrounding.js.map +1 -0
  55. package/dist/util/hash.js +6 -0
  56. package/dist/util/hash.js.map +1 -0
  57. package/dist/util/logger.js +41 -0
  58. package/dist/util/logger.js.map +1 -0
  59. package/dist/util/timeout.js +19 -0
  60. package/dist/util/timeout.js.map +1 -0
  61. package/dist/verify/anthropic.js +126 -0
  62. package/dist/verify/anthropic.js.map +1 -0
  63. package/dist/verify/decompose.js +100 -0
  64. package/dist/verify/decompose.js.map +1 -0
  65. package/dist/verify/localVerifier.js +89 -0
  66. package/dist/verify/localVerifier.js.map +1 -0
  67. package/dist/verify/pipeline.js +165 -0
  68. package/dist/verify/pipeline.js.map +1 -0
  69. package/dist/verify/score.js +64 -0
  70. package/dist/verify/score.js.map +1 -0
  71. package/dist/verify/types.js +3 -0
  72. package/dist/verify/types.js.map +1 -0
  73. package/dist/verify/verifier.js +2 -0
  74. package/dist/verify/verifier.js.map +1 -0
  75. package/package.json +71 -0
@@ -0,0 +1,130 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { resolve, sep } from "node:path";
3
+ import { sha256 } from "../util/hash.js";
4
+ import { writeSnapshot } from "./snapshots.js";
5
+ /**
6
+ * Safe note edits: a string replacement is first returned as a dry-run diff +
7
+ * token; calling again with that token applies it, saving a rollback snapshot
8
+ * and re-indexing the note. The token also guards against the file changing
9
+ * between preview and apply.
10
+ */
11
+ export class SafeEditService {
12
+ vaultPath;
13
+ indexer;
14
+ constructor(vaultPath, indexer) {
15
+ this.vaultPath = vaultPath;
16
+ this.indexer = indexer;
17
+ }
18
+ async edit(relPath, oldText, newText, confirm) {
19
+ const abs = this.resolveWithinVault(relPath);
20
+ let current;
21
+ try {
22
+ current = await readFile(abs, "utf8");
23
+ }
24
+ catch {
25
+ throw new Error(`Note not found or unreadable: ${relPath}`);
26
+ }
27
+ const next = applyReplacement(current, oldText, newText);
28
+ if (next === current) {
29
+ throw new Error("The replacement produces no change.");
30
+ }
31
+ const token = computeToken(relPath, current, next);
32
+ if (confirm === undefined) {
33
+ return { mode: "dry-run", path: relPath, diff: makeDiff(current, next), token };
34
+ }
35
+ if (confirm !== token) {
36
+ throw new Error("Confirmation token does not match; the note may have changed. Re-run the dry run and confirm with the new token.");
37
+ }
38
+ const snapshot = await writeSnapshot(this.vaultPath, relPath, current, Date.now());
39
+ await writeFile(abs, next, "utf8");
40
+ const reindexedChunks = await this.indexer.indexFile(relPath);
41
+ return { mode: "applied", path: relPath, snapshot, reindexedChunks };
42
+ }
43
+ /** Restores a note from a snapshot, saving a pre-restore snapshot first. */
44
+ async restore(relPath, snapshotRelPath) {
45
+ const abs = this.resolveWithinVault(relPath);
46
+ const snapshotAbs = this.resolveSnapshot(snapshotRelPath);
47
+ let snapshotContent;
48
+ try {
49
+ snapshotContent = await readFile(snapshotAbs, "utf8");
50
+ }
51
+ catch {
52
+ throw new Error(`Snapshot not found: ${snapshotRelPath}`);
53
+ }
54
+ // Save the current content first so the restore is itself undoable.
55
+ let preRestoreSnapshot = null;
56
+ try {
57
+ const current = await readFile(abs, "utf8");
58
+ preRestoreSnapshot = await writeSnapshot(this.vaultPath, relPath, current, Date.now());
59
+ }
60
+ catch {
61
+ // The note may not currently exist (restoring a deleted note); that's fine.
62
+ }
63
+ await writeFile(abs, snapshotContent, "utf8");
64
+ const reindexedChunks = await this.indexer.indexFile(relPath);
65
+ return { path: relPath, restoredFrom: snapshotRelPath, preRestoreSnapshot, reindexedChunks };
66
+ }
67
+ resolveWithinVault(relPath) {
68
+ const root = resolve(this.vaultPath);
69
+ const abs = resolve(root, relPath);
70
+ if (abs !== root && !abs.startsWith(root + sep)) {
71
+ throw new Error("Refusing to edit a path outside the vault.");
72
+ }
73
+ if (!abs.toLowerCase().endsWith(".md")) {
74
+ throw new Error("safe_edit only edits Markdown (.md) notes.");
75
+ }
76
+ return abs;
77
+ }
78
+ resolveSnapshot(snapshotRelPath) {
79
+ const root = resolve(this.vaultPath);
80
+ const snapshotsDir = resolve(root, ".anchor", "snapshots");
81
+ const abs = resolve(root, snapshotRelPath);
82
+ if (abs !== snapshotsDir && !abs.startsWith(snapshotsDir + sep)) {
83
+ throw new Error("Snapshot path must be inside .anchor/snapshots.");
84
+ }
85
+ return abs;
86
+ }
87
+ }
88
+ /** Replaces the single occurrence of `oldText`; rejects 0 or multiple matches. */
89
+ function applyReplacement(current, oldText, newText) {
90
+ const first = current.indexOf(oldText);
91
+ if (first === -1) {
92
+ throw new Error("The text to replace was not found in the note.");
93
+ }
94
+ if (current.indexOf(oldText, first + oldText.length) !== -1) {
95
+ throw new Error("The text to replace appears multiple times; include more surrounding context to make it unique.");
96
+ }
97
+ return current.slice(0, first) + newText + current.slice(first + oldText.length);
98
+ }
99
+ function computeToken(relPath, current, next) {
100
+ return sha256(`${relPath}\n${current}\n${next}`).slice(0, 16);
101
+ }
102
+ /** Produces a compact single-hunk unified diff for one contiguous change. */
103
+ function makeDiff(oldContent, newContent, context = 3) {
104
+ const a = oldContent.split("\n");
105
+ const b = newContent.split("\n");
106
+ let start = 0;
107
+ while (start < a.length && start < b.length && a[start] === b[start])
108
+ start += 1;
109
+ let endA = a.length - 1;
110
+ let endB = b.length - 1;
111
+ while (endA >= start && endB >= start && a[endA] === b[endB]) {
112
+ endA -= 1;
113
+ endB -= 1;
114
+ }
115
+ const ctxStart = Math.max(0, start - context);
116
+ const ctxEndA = Math.min(a.length - 1, endA + context);
117
+ const lines = [
118
+ `@@ -${String(start + 1)},${String(endA - start + 1)} +${String(start + 1)},${String(endB - start + 1)} @@`,
119
+ ];
120
+ for (let i = ctxStart; i < start; i += 1)
121
+ lines.push(` ${a[i] ?? ""}`);
122
+ for (let i = start; i <= endA; i += 1)
123
+ lines.push(`-${a[i] ?? ""}`);
124
+ for (let i = start; i <= endB; i += 1)
125
+ lines.push(`+${b[i] ?? ""}`);
126
+ for (let i = endA + 1; i <= ctxEndA; i += 1)
127
+ lines.push(` ${a[i] ?? ""}`);
128
+ return lines.join("\n");
129
+ }
130
+ //# sourceMappingURL=safeEdit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safeEdit.js","sourceRoot":"","sources":["../../src/edit/safeEdit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { mkdir, readdir, unlink, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ export const SNAPSHOT_DIR = join(".anchor", "snapshots");
4
+ const MAX_SNAPSHOTS_PER_NOTE = 20;
5
+ /**
6
+ * Saves a pre-edit copy of a note so an edit can be rolled back. Returns the
7
+ * vault-relative path of the snapshot, and prunes old snapshots for the note.
8
+ */
9
+ export async function writeSnapshot(vaultPath, relPath, content, timestamp) {
10
+ const dir = join(vaultPath, SNAPSHOT_DIR);
11
+ await mkdir(dir, { recursive: true });
12
+ const encoded = relPath.replace(/[\\/]/g, "__");
13
+ const safeName = `${String(timestamp)}-${encoded}.bak`;
14
+ await writeFile(join(dir, safeName), content, "utf8");
15
+ await prune(dir, encoded);
16
+ return join(SNAPSHOT_DIR, safeName);
17
+ }
18
+ /** Keeps only the most recent MAX_SNAPSHOTS_PER_NOTE snapshots for a note. */
19
+ async function prune(dir, encoded) {
20
+ const suffix = `-${encoded}.bak`;
21
+ const entries = (await readdir(dir)).filter((name) => name.endsWith(suffix));
22
+ if (entries.length <= MAX_SNAPSHOTS_PER_NOTE)
23
+ return;
24
+ // Filenames are `<timestamp>-<encoded>.bak`; the timestamp is the digits before
25
+ // the first dash. Sort newest-first and delete the overflow.
26
+ entries.sort((a, b) => Number(b.split("-")[0] ?? 0) - Number(a.split("-")[0] ?? 0));
27
+ for (const old of entries.slice(MAX_SNAPSHOTS_PER_NOTE)) {
28
+ await unlink(join(dir, old)).catch(() => undefined);
29
+ }
30
+ }
31
+ //# sourceMappingURL=snapshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../src/edit/snapshots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AACzD,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,OAAe,EACf,OAAe,EACf,SAAiB;IAEjB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC1C,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,OAAO,MAAM,CAAC;IACvD,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,8EAA8E;AAC9E,KAAK,UAAU,KAAK,CAAC,GAAW,EAAE,OAAe;IAC/C,MAAM,MAAM,GAAG,IAAI,OAAO,MAAM,CAAC;IACjC,MAAM,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7E,IAAI,OAAO,CAAC,MAAM,IAAI,sBAAsB;QAAE,OAAO;IACrD,gFAAgF;IAChF,6DAA6D;IAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpF,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;AACH,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { env, pipeline } from "@huggingface/transformers";
2
+ import { logger } from "../util/logger.js";
3
+ // Defaults are English. For non-English vaults, point these at a multilingual
4
+ // ONNX model, e.g. ANCHOR_EMBEDDING_MODEL=Xenova/paraphrase-multilingual-MiniLM-L12-v2
5
+ // (also 384-dim). Set ANCHOR_EMBEDDING_DIM if the model's dimensionality differs.
6
+ const MODEL_ID = process.env.ANCHOR_EMBEDDING_MODEL ?? "Xenova/all-MiniLM-L6-v2";
7
+ const DIM = Number(process.env.ANCHOR_EMBEDDING_DIM ?? 384);
8
+ const BATCH_SIZE = 32;
9
+ /**
10
+ * Local, in-process embeddings via transformers.js — no API key, notes never
11
+ * leave the machine. The model (~80MB) is downloaded once on first use and
12
+ * cached on disk.
13
+ */
14
+ export class LocalEmbeddingProvider {
15
+ // Model id is part of the identity so switching models triggers a re-index.
16
+ id = `local:${MODEL_ID}`;
17
+ dim = DIM;
18
+ extractor = null;
19
+ constructor(cacheDir) {
20
+ if (cacheDir) {
21
+ env.cacheDir = cacheDir;
22
+ }
23
+ }
24
+ load() {
25
+ if (this.extractor === null) {
26
+ logger.info("Loading local embedding model (first run downloads ~80MB)", { model: MODEL_ID });
27
+ this.extractor = (async () => {
28
+ try {
29
+ return (await pipeline("feature-extraction", MODEL_ID));
30
+ }
31
+ catch (error) {
32
+ this.extractor = null; // allow a retry on the next call
33
+ throw new Error(`Failed to load the local embedding model '${MODEL_ID}'. It downloads on first use — check network access and disk space.`, { cause: error });
34
+ }
35
+ })();
36
+ }
37
+ return this.extractor;
38
+ }
39
+ async embed(texts) {
40
+ if (texts.length === 0)
41
+ return [];
42
+ const extract = await this.load();
43
+ const vectors = [];
44
+ // Batch to bound memory while still amortizing model overhead across texts.
45
+ for (let i = 0; i < texts.length; i += BATCH_SIZE) {
46
+ const batch = texts.slice(i, i + BATCH_SIZE);
47
+ const tensor = await extract(batch, { pooling: "mean", normalize: true });
48
+ for (let row = 0; row < batch.length; row++) {
49
+ vectors.push(Float32Array.from(tensor.data.subarray(row * this.dim, (row + 1) * this.dim)));
50
+ }
51
+ }
52
+ return vectors;
53
+ }
54
+ async embedOne(text) {
55
+ const [vector] = await this.embed([text]);
56
+ if (vector === undefined)
57
+ throw new Error("Embedding produced no vector");
58
+ return vector;
59
+ }
60
+ }
61
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/embeddings/local.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ const MODEL = "text-embedding-3-small";
2
+ const DIM = 1536;
3
+ const ENDPOINT = "https://api.openai.com/v1/embeddings";
4
+ const BATCH_SIZE = 128;
5
+ /**
6
+ * Optional OpenAI embeddings (opt-in via `--embedding openai` / `ANCHOR_EMBEDDING=openai`).
7
+ * Uses fetch directly — no SDK dependency. Vectors are normalized so cosine/L2
8
+ * ranking matches the local provider.
9
+ */
10
+ export class OpenAIEmbeddingProvider {
11
+ id = "openai-3-small";
12
+ dim = DIM;
13
+ apiKey;
14
+ fetchImpl;
15
+ constructor(apiKey, fetchImpl) {
16
+ const key = apiKey ?? process.env.OPENAI_API_KEY;
17
+ if (!key) {
18
+ throw new Error("OPENAI_API_KEY is required for the OpenAI embedding provider.");
19
+ }
20
+ this.apiKey = key;
21
+ this.fetchImpl = fetchImpl ?? globalThis.fetch;
22
+ }
23
+ async embed(texts) {
24
+ if (texts.length === 0)
25
+ return [];
26
+ const vectors = [];
27
+ for (let i = 0; i < texts.length; i += BATCH_SIZE) {
28
+ vectors.push(...(await this.embedBatch(texts.slice(i, i + BATCH_SIZE))));
29
+ }
30
+ return vectors;
31
+ }
32
+ async embedOne(text) {
33
+ const [vector] = await this.embedBatch([text]);
34
+ if (vector === undefined)
35
+ throw new Error("OpenAI embedding produced no vector");
36
+ return vector;
37
+ }
38
+ async embedBatch(input) {
39
+ const response = await this.fetchImpl(ENDPOINT, {
40
+ method: "POST",
41
+ headers: { "content-type": "application/json", authorization: `Bearer ${this.apiKey}` },
42
+ body: JSON.stringify({ model: MODEL, input }),
43
+ });
44
+ if (!response.ok) {
45
+ const detail = await response.text().catch(() => "");
46
+ throw new Error(`OpenAI embeddings request failed (${String(response.status)}): ${detail.slice(0, 200)}`);
47
+ }
48
+ const parsed = (await response.json());
49
+ const data = [...(parsed.data ?? [])].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
50
+ return data.map((item) => normalize(Float32Array.from(item.embedding ?? [])));
51
+ }
52
+ }
53
+ /** Normalizes a vector to unit length so dot product == cosine similarity. */
54
+ function normalize(vector) {
55
+ let norm = 0;
56
+ for (const value of vector)
57
+ norm += value * value;
58
+ norm = Math.sqrt(norm) || 1;
59
+ for (let i = 0; i < vector.length; i += 1)
60
+ vector[i] = (vector[i] ?? 0) / norm;
61
+ return vector;
62
+ }
63
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/embeddings/openai.ts"],"names":[],"mappings":"AAEA,MAAM,KAAK,GAAG,wBAAwB,CAAC;AACvC,MAAM,GAAG,GAAG,IAAI,CAAC;AACjB,MAAM,QAAQ,GAAG,sCAAsC,CAAC;AACxD,MAAM,UAAU,GAAG,GAAG,CAAC;AAQvB;;;;GAIG;AACH,MAAM,OAAO,uBAAuB;IACzB,EAAE,GAAG,gBAAgB,CAAC;IACtB,GAAG,GAAG,GAAG,CAAC;IACF,MAAM,CAAS;IACf,SAAS,CAAY;IAEtC,YAAY,MAAe,EAAE,SAAqB;QAChD,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QACjD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,SAAS,GAAG,SAAS,IAAK,UAAU,CAAC,KAA8B,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAe;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAClC,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,UAAU,EAAE,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY;QACzB,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/C,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACjF,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,KAAe;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE;YAC9C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE,EAAE;YACvF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACrD,MAAM,IAAI,KAAK,CACb,qCAAqC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CACzF,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA0D,CAAC;QAChG,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAChF,CAAC;CACF;AAED,8EAA8E;AAC9E,SAAS,SAAS,CAAC,MAAoB;IACrC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,KAAK,IAAI,MAAM;QAAE,IAAI,IAAI,KAAK,GAAG,KAAK,CAAC;IAClD,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;QAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/E,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,5 @@
1
+ // Embedding backend abstraction. The default is a fully local, no-API-key
2
+ // provider; an OpenAI provider is a drop-in accuracy upgrade. The store fixes
3
+ // the vec0 column width to `dim`, so changing providers triggers a rebuild.
4
+ export {};
5
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/embeddings/provider.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8EAA8E;AAC9E,4EAA4E"}
@@ -0,0 +1,108 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { chunkNote } from "../chunk/chunker.js";
4
+ import { sha256 } from "../util/hash.js";
5
+ import { logger } from "../util/logger.js";
6
+ import { walkVault } from "./walk.js";
7
+ /**
8
+ * Builds and maintains the vault index: walk -> chunk -> embed -> store. Uses a
9
+ * per-note content hash to skip unchanged files, so repeated passes are cheap
10
+ * (full incremental file-watching lands in a later milestone).
11
+ */
12
+ export class Indexer {
13
+ vaultPath;
14
+ db;
15
+ chunkStore;
16
+ vectorStore;
17
+ embeddings;
18
+ constructor(vaultPath, db, chunkStore, vectorStore, embeddings) {
19
+ this.vaultPath = vaultPath;
20
+ this.db = db;
21
+ this.chunkStore = chunkStore;
22
+ this.vectorStore = vectorStore;
23
+ this.embeddings = embeddings;
24
+ }
25
+ async indexAll() {
26
+ const files = await walkVault(this.vaultPath);
27
+ const fileSet = new Set(files);
28
+ const stats = { notes: 0, chunks: 0, skipped: 0, errored: 0, pruned: 0 };
29
+ for (const rel of files) {
30
+ try {
31
+ const written = await this.indexFile(rel);
32
+ if (written === null) {
33
+ stats.skipped += 1;
34
+ }
35
+ else {
36
+ stats.notes += 1;
37
+ stats.chunks += written;
38
+ }
39
+ }
40
+ catch (error) {
41
+ // A single unreadable / malformed note must never abort the whole pass.
42
+ stats.errored += 1;
43
+ logger.warn("Skipped a note that failed to index", {
44
+ path: rel,
45
+ error: error instanceof Error ? error.message : String(error),
46
+ });
47
+ }
48
+ }
49
+ // Prune notes deleted on disk while the watcher wasn't running, so the index
50
+ // never cites a note that no longer exists.
51
+ for (const note of this.chunkStore.getAllNotes()) {
52
+ if (!fileSet.has(note.path)) {
53
+ this.removeNote(note.path);
54
+ stats.pruned += 1;
55
+ }
56
+ }
57
+ logger.info("Indexing complete", { ...stats, files: files.length });
58
+ return stats;
59
+ }
60
+ /** Indexes one note; returns chunk count, or null if skipped (unchanged/empty/binary). */
61
+ async indexFile(rel) {
62
+ const abs = join(this.vaultPath, rel);
63
+ const [buffer, fileStat] = await Promise.all([readFile(abs), stat(abs)]);
64
+ // Skip binary-looking files: a zero byte never appears in real markdown, but
65
+ // a mislabelled binary or non-UTF8 file would otherwise embed as garbage.
66
+ if (buffer.includes(0)) {
67
+ logger.debug("Skipping binary-looking file", { path: rel });
68
+ return null;
69
+ }
70
+ const content = buffer.toString("utf8");
71
+ const hash = sha256(content);
72
+ const existing = this.chunkStore.getNote(rel);
73
+ if (existing) {
74
+ if (existing.contentHash === hash)
75
+ return null; // unchanged
76
+ // Changed: drop the old chunks (cascades wikilinks) and their vector rows.
77
+ this.vectorStore.deleteByChunkIds(this.chunkStore.getChunkIdsForNote(existing.noteId));
78
+ this.chunkStore.deleteNoteRow(existing.noteId);
79
+ }
80
+ const chunks = chunkNote(rel, content);
81
+ if (chunks.length === 0)
82
+ return null;
83
+ // Embedding is async, so it must happen outside the synchronous transaction.
84
+ const vectors = await this.embeddings.embed(chunks.map((chunk) => chunk.text));
85
+ const persist = this.db.transaction(() => {
86
+ const noteId = this.chunkStore.insertNote({ path: rel, mtimeMs: Math.floor(fileStat.mtimeMs), contentHash: hash }, Date.now());
87
+ for (let i = 0; i < chunks.length; i++) {
88
+ const chunk = chunks[i];
89
+ const vector = vectors[i];
90
+ if (chunk === undefined || vector === undefined)
91
+ continue;
92
+ const chunkId = this.chunkStore.insertChunk(noteId, chunk);
93
+ this.vectorStore.insert(chunkId, vector);
94
+ }
95
+ });
96
+ persist();
97
+ return chunks.length;
98
+ }
99
+ /** Removes a note and its chunks/vectors from the index (for deletions). */
100
+ removeNote(rel) {
101
+ const existing = this.chunkStore.getNote(rel);
102
+ if (!existing)
103
+ return;
104
+ this.vectorStore.deleteByChunkIds(this.chunkStore.getChunkIdsForNote(existing.noteId));
105
+ this.chunkStore.deleteNoteRow(existing.noteId);
106
+ }
107
+ }
108
+ //# sourceMappingURL=indexer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../src/index/indexer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ // System/hidden directories that never contain user notes.
4
+ const SKIP_DIRS = new Set([".obsidian", ".trash", ".anchor", ".git", "node_modules"]);
5
+ /**
6
+ * Lists markdown files in a vault as vault-relative POSIX paths, skipping
7
+ * Obsidian/system directories and anything dot-prefixed.
8
+ */
9
+ export async function walkVault(vaultPath) {
10
+ const out = [];
11
+ async function walk(absDir, relDir) {
12
+ const entries = await readdir(absDir, { withFileTypes: true });
13
+ for (const entry of entries) {
14
+ const relPath = relDir === "" ? entry.name : `${relDir}/${entry.name}`;
15
+ if (entry.isDirectory()) {
16
+ if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name))
17
+ continue;
18
+ await walk(join(absDir, entry.name), relPath);
19
+ }
20
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
21
+ out.push(relPath);
22
+ }
23
+ }
24
+ }
25
+ await walk(vaultPath, "");
26
+ return out;
27
+ }
28
+ //# sourceMappingURL=walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.js","sourceRoot":"","sources":["../../src/index/walk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,2DAA2D;AAC3D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;AAEtF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,SAAiB;IAC/C,MAAM,GAAG,GAAa,EAAE,CAAC;IAEzB,KAAK,UAAU,IAAI,CAAC,MAAc,EAAE,MAAc;QAChD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACvE,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBACtE,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;YAChD,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC1B,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,115 @@
1
+ import { relative } from "node:path";
2
+ import { watch } from "chokidar";
3
+ import { logger } from "../util/logger.js";
4
+ const DEBOUNCE_MS = 250;
5
+ /**
6
+ * Watches a vault and keeps the index in sync as notes are added, changed, or
7
+ * removed. Rapid saves to a file are debounced before re-indexing.
8
+ */
9
+ export class VaultWatcher {
10
+ vaultPath;
11
+ indexer;
12
+ options;
13
+ watcher = null;
14
+ timers = new Map();
15
+ /** Resolves once the initial scan finishes and the watcher is live. */
16
+ ready = Promise.resolve();
17
+ constructor(vaultPath, indexer, options = {}) {
18
+ this.vaultPath = vaultPath;
19
+ this.indexer = indexer;
20
+ this.options = options;
21
+ }
22
+ start() {
23
+ if (this.watcher)
24
+ return;
25
+ this.watcher = watch(this.vaultPath, {
26
+ ignoreInitial: true,
27
+ persistent: true,
28
+ // Native fs events are efficient but unavailable on some filesystems
29
+ // (network drives, WSL, Docker volumes, some VMs); polling is the fallback.
30
+ usePolling: this.options.usePolling ?? false,
31
+ interval: 200,
32
+ awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
33
+ ignored: (path, stats) => {
34
+ const base = path.split(/[/\\]/).pop() ?? "";
35
+ if (base.startsWith("."))
36
+ return true; // .obsidian, .anchor, .git, dotfiles
37
+ if (stats?.isFile() === true && !base.toLowerCase().endsWith(".md"))
38
+ return true;
39
+ return false;
40
+ },
41
+ });
42
+ this.ready = new Promise((resolve) => {
43
+ this.watcher?.on("ready", () => {
44
+ resolve();
45
+ });
46
+ });
47
+ this.watcher.on("add", (path) => {
48
+ this.schedule(path);
49
+ });
50
+ this.watcher.on("change", (path) => {
51
+ this.schedule(path);
52
+ });
53
+ this.watcher.on("unlink", (path) => {
54
+ this.remove(path);
55
+ });
56
+ this.watcher.on("error", (error) => {
57
+ logger.error("Vault watcher error", {
58
+ error: error instanceof Error ? error.message : String(error),
59
+ });
60
+ });
61
+ }
62
+ async stop() {
63
+ for (const timer of this.timers.values())
64
+ clearTimeout(timer);
65
+ this.timers.clear();
66
+ await this.watcher?.close();
67
+ this.watcher = null;
68
+ }
69
+ toRel(absPath) {
70
+ return relative(this.vaultPath, absPath).split(/[/\\]/).join("/");
71
+ }
72
+ schedule(absPath) {
73
+ const rel = this.toRel(absPath);
74
+ if (!rel.toLowerCase().endsWith(".md"))
75
+ return;
76
+ const existing = this.timers.get(rel);
77
+ if (existing)
78
+ clearTimeout(existing);
79
+ this.timers.set(rel, setTimeout(() => {
80
+ this.timers.delete(rel);
81
+ void this.indexer
82
+ .indexFile(rel)
83
+ .then((written) => {
84
+ logger.debug("Re-indexed note", { path: rel, chunks: written });
85
+ })
86
+ .catch((error) => {
87
+ logger.warn("Failed to re-index a changed note", {
88
+ path: rel,
89
+ error: error instanceof Error ? error.message : String(error),
90
+ });
91
+ });
92
+ }, DEBOUNCE_MS));
93
+ }
94
+ remove(absPath) {
95
+ const rel = this.toRel(absPath);
96
+ if (!rel.toLowerCase().endsWith(".md"))
97
+ return;
98
+ const pending = this.timers.get(rel);
99
+ if (pending) {
100
+ clearTimeout(pending);
101
+ this.timers.delete(rel);
102
+ }
103
+ try {
104
+ this.indexer.removeNote(rel);
105
+ logger.debug("Removed note from index", { path: rel });
106
+ }
107
+ catch (error) {
108
+ logger.warn("Failed to remove a note from the index", {
109
+ path: rel,
110
+ error: error instanceof Error ? error.message : String(error),
111
+ });
112
+ }
113
+ }
114
+ }
115
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/index/watcher.ts"],"names":[],"mappings":"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"}