@pella-labs/pinakes 0.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.
Files changed (174) hide show
  1. package/README.md +208 -0
  2. package/dist/cli/audit.d.ts +30 -0
  3. package/dist/cli/audit.d.ts.map +1 -0
  4. package/dist/cli/audit.js +49 -0
  5. package/dist/cli/audit.js.map +1 -0
  6. package/dist/cli/export.d.ts +32 -0
  7. package/dist/cli/export.d.ts.map +1 -0
  8. package/dist/cli/export.js +73 -0
  9. package/dist/cli/export.js.map +1 -0
  10. package/dist/cli/import.d.ts +24 -0
  11. package/dist/cli/import.d.ts.map +1 -0
  12. package/dist/cli/import.js +96 -0
  13. package/dist/cli/import.js.map +1 -0
  14. package/dist/cli/index.d.ts +3 -0
  15. package/dist/cli/index.d.ts.map +1 -0
  16. package/dist/cli/index.js +172 -0
  17. package/dist/cli/index.js.map +1 -0
  18. package/dist/cli/purge.d.ts +23 -0
  19. package/dist/cli/purge.d.ts.map +1 -0
  20. package/dist/cli/purge.js +57 -0
  21. package/dist/cli/purge.js.map +1 -0
  22. package/dist/cli/rebuild.d.ts +54 -0
  23. package/dist/cli/rebuild.d.ts.map +1 -0
  24. package/dist/cli/rebuild.js +113 -0
  25. package/dist/cli/rebuild.js.map +1 -0
  26. package/dist/cli/serve.d.ts +49 -0
  27. package/dist/cli/serve.d.ts.map +1 -0
  28. package/dist/cli/serve.js +296 -0
  29. package/dist/cli/serve.js.map +1 -0
  30. package/dist/cli/status.d.ts +39 -0
  31. package/dist/cli/status.d.ts.map +1 -0
  32. package/dist/cli/status.js +108 -0
  33. package/dist/cli/status.js.map +1 -0
  34. package/dist/db/client.d.ts +109 -0
  35. package/dist/db/client.d.ts.map +1 -0
  36. package/dist/db/client.js +175 -0
  37. package/dist/db/client.js.map +1 -0
  38. package/dist/db/repository.d.ts +82 -0
  39. package/dist/db/repository.d.ts.map +1 -0
  40. package/dist/db/repository.js +173 -0
  41. package/dist/db/repository.js.map +1 -0
  42. package/dist/db/schema.d.ts +990 -0
  43. package/dist/db/schema.d.ts.map +1 -0
  44. package/dist/db/schema.js +259 -0
  45. package/dist/db/schema.js.map +1 -0
  46. package/dist/db/types.d.ts +28 -0
  47. package/dist/db/types.d.ts.map +1 -0
  48. package/dist/db/types.js +11 -0
  49. package/dist/db/types.js.map +1 -0
  50. package/dist/gaps/detector.d.ts +67 -0
  51. package/dist/gaps/detector.d.ts.map +1 -0
  52. package/dist/gaps/detector.js +160 -0
  53. package/dist/gaps/detector.js.map +1 -0
  54. package/dist/gate/budget.d.ts +90 -0
  55. package/dist/gate/budget.d.ts.map +1 -0
  56. package/dist/gate/budget.js +145 -0
  57. package/dist/gate/budget.js.map +1 -0
  58. package/dist/ingest/chokidar.d.ts +33 -0
  59. package/dist/ingest/chokidar.d.ts.map +1 -0
  60. package/dist/ingest/chokidar.js +152 -0
  61. package/dist/ingest/chokidar.js.map +1 -0
  62. package/dist/ingest/ingester.d.ts +117 -0
  63. package/dist/ingest/ingester.d.ts.map +1 -0
  64. package/dist/ingest/ingester.js +312 -0
  65. package/dist/ingest/ingester.js.map +1 -0
  66. package/dist/ingest/manifest.d.ts +87 -0
  67. package/dist/ingest/manifest.d.ts.map +1 -0
  68. package/dist/ingest/manifest.js +223 -0
  69. package/dist/ingest/manifest.js.map +1 -0
  70. package/dist/ingest/memory-store.d.ts +55 -0
  71. package/dist/ingest/memory-store.d.ts.map +1 -0
  72. package/dist/ingest/memory-store.js +94 -0
  73. package/dist/ingest/memory-store.js.map +1 -0
  74. package/dist/ingest/parse/chunk.d.ts +15 -0
  75. package/dist/ingest/parse/chunk.d.ts.map +1 -0
  76. package/dist/ingest/parse/chunk.js +88 -0
  77. package/dist/ingest/parse/chunk.js.map +1 -0
  78. package/dist/ingest/parse/markdown.d.ts +64 -0
  79. package/dist/ingest/parse/markdown.d.ts.map +1 -0
  80. package/dist/ingest/parse/markdown.js +152 -0
  81. package/dist/ingest/parse/markdown.js.map +1 -0
  82. package/dist/ingest/queue.d.ts +21 -0
  83. package/dist/ingest/queue.d.ts.map +1 -0
  84. package/dist/ingest/queue.js +24 -0
  85. package/dist/ingest/queue.js.map +1 -0
  86. package/dist/ingest/source.d.ts +42 -0
  87. package/dist/ingest/source.d.ts.map +1 -0
  88. package/dist/ingest/source.js +19 -0
  89. package/dist/ingest/source.js.map +1 -0
  90. package/dist/mcp/envelope.d.ts +73 -0
  91. package/dist/mcp/envelope.d.ts.map +1 -0
  92. package/dist/mcp/envelope.js +46 -0
  93. package/dist/mcp/envelope.js.map +1 -0
  94. package/dist/mcp/tools/execute.d.ts +55 -0
  95. package/dist/mcp/tools/execute.d.ts.map +1 -0
  96. package/dist/mcp/tools/execute.js +232 -0
  97. package/dist/mcp/tools/execute.js.map +1 -0
  98. package/dist/mcp/tools/search.d.ts +53 -0
  99. package/dist/mcp/tools/search.d.ts.map +1 -0
  100. package/dist/mcp/tools/search.js +114 -0
  101. package/dist/mcp/tools/search.js.map +1 -0
  102. package/dist/observability/audit.d.ts +25 -0
  103. package/dist/observability/audit.d.ts.map +1 -0
  104. package/dist/observability/audit.js +38 -0
  105. package/dist/observability/audit.js.map +1 -0
  106. package/dist/observability/logger.d.ts +4 -0
  107. package/dist/observability/logger.d.ts.map +1 -0
  108. package/dist/observability/logger.js +56 -0
  109. package/dist/observability/logger.js.map +1 -0
  110. package/dist/observability/metrics.d.ts +38 -0
  111. package/dist/observability/metrics.d.ts.map +1 -0
  112. package/dist/observability/metrics.js +64 -0
  113. package/dist/observability/metrics.js.map +1 -0
  114. package/dist/retrieval/embedder.d.ts +130 -0
  115. package/dist/retrieval/embedder.d.ts.map +1 -0
  116. package/dist/retrieval/embedder.js +278 -0
  117. package/dist/retrieval/embedder.js.map +1 -0
  118. package/dist/retrieval/fts.d.ts +42 -0
  119. package/dist/retrieval/fts.d.ts.map +1 -0
  120. package/dist/retrieval/fts.js +46 -0
  121. package/dist/retrieval/fts.js.map +1 -0
  122. package/dist/retrieval/hybrid.d.ts +43 -0
  123. package/dist/retrieval/hybrid.d.ts.map +1 -0
  124. package/dist/retrieval/hybrid.js +120 -0
  125. package/dist/retrieval/hybrid.js.map +1 -0
  126. package/dist/retrieval/vec.d.ts +39 -0
  127. package/dist/retrieval/vec.d.ts.map +1 -0
  128. package/dist/retrieval/vec.js +50 -0
  129. package/dist/retrieval/vec.js.map +1 -0
  130. package/dist/sandbox/bindings/budget.d.ts +10 -0
  131. package/dist/sandbox/bindings/budget.d.ts.map +1 -0
  132. package/dist/sandbox/bindings/budget.js +44 -0
  133. package/dist/sandbox/bindings/budget.js.map +1 -0
  134. package/dist/sandbox/bindings/install.d.ts +23 -0
  135. package/dist/sandbox/bindings/install.d.ts.map +1 -0
  136. package/dist/sandbox/bindings/install.js +15 -0
  137. package/dist/sandbox/bindings/install.js.map +1 -0
  138. package/dist/sandbox/bindings/kg.d.ts +29 -0
  139. package/dist/sandbox/bindings/kg.d.ts.map +1 -0
  140. package/dist/sandbox/bindings/kg.js +323 -0
  141. package/dist/sandbox/bindings/kg.js.map +1 -0
  142. package/dist/sandbox/bindings/logger.d.ts +11 -0
  143. package/dist/sandbox/bindings/logger.d.ts.map +1 -0
  144. package/dist/sandbox/bindings/logger.js +33 -0
  145. package/dist/sandbox/bindings/logger.js.map +1 -0
  146. package/dist/sandbox/bindings/write.d.ts +34 -0
  147. package/dist/sandbox/bindings/write.d.ts.map +1 -0
  148. package/dist/sandbox/bindings/write.js +195 -0
  149. package/dist/sandbox/bindings/write.js.map +1 -0
  150. package/dist/sandbox/executor.d.ts +68 -0
  151. package/dist/sandbox/executor.d.ts.map +1 -0
  152. package/dist/sandbox/executor.js +280 -0
  153. package/dist/sandbox/executor.js.map +1 -0
  154. package/dist/sandbox/helpers.d.ts +26 -0
  155. package/dist/sandbox/helpers.d.ts.map +1 -0
  156. package/dist/sandbox/helpers.js +131 -0
  157. package/dist/sandbox/helpers.js.map +1 -0
  158. package/dist/sandbox/pool.d.ts +63 -0
  159. package/dist/sandbox/pool.d.ts.map +1 -0
  160. package/dist/sandbox/pool.js +98 -0
  161. package/dist/sandbox/pool.js.map +1 -0
  162. package/dist/sandbox/vendored-codemode.d.ts +99 -0
  163. package/dist/sandbox/vendored-codemode.d.ts.map +1 -0
  164. package/dist/sandbox/vendored-codemode.js +471 -0
  165. package/dist/sandbox/vendored-codemode.js.map +1 -0
  166. package/dist/server.d.ts +3 -0
  167. package/dist/server.d.ts.map +1 -0
  168. package/dist/server.js +74 -0
  169. package/dist/server.js.map +1 -0
  170. package/dist/spike.d.ts +15 -0
  171. package/dist/spike.d.ts.map +1 -0
  172. package/dist/spike.js +90 -0
  173. package/dist/spike.js.map +1 -0
  174. package/package.json +60 -0
@@ -0,0 +1,312 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { logger } from '../observability/logger.js';
6
+ import { countTokens } from '../gate/budget.js';
7
+ import { emptyManifest, fileSha, manifestPathFor, readManifest, removeManifestEntry, updateManifestEntry, writeManifest, } from './manifest.js';
8
+ import { parseMarkdown, detectConfidence } from './parse/markdown.js';
9
+ import { detectGaps } from '../gaps/detector.js';
10
+ import { chunkSection } from './parse/chunk.js';
11
+ // ----------------------------------------------------------------------------
12
+ // Single-flight gate (module-level)
13
+ // ----------------------------------------------------------------------------
14
+ /**
15
+ * Module-level single-flight map. Keyed by `${scope}:${absPath}` so the same
16
+ * file can be ingested concurrently for different scopes (e.g. if the wiki
17
+ * dir lives at the same path for both scopes — unusual but possible).
18
+ *
19
+ * The promise stored here is the FULL `ingestFile` operation; concurrent
20
+ * callers await the same promise and observe the same result. After the
21
+ * promise settles, the entry is removed so subsequent calls run fresh.
22
+ *
23
+ * Test reset hook: `__resetSingleFlightForTests()` clears the map between
24
+ * tests so a leftover entry from a prior test doesn't affect the next one.
25
+ */
26
+ const inFlight = new Map();
27
+ /** Test-only: clear the single-flight map. Do not call from production code. */
28
+ export function __resetSingleFlightForTests() {
29
+ inFlight.clear();
30
+ }
31
+ // ----------------------------------------------------------------------------
32
+ // IngesterService
33
+ // ----------------------------------------------------------------------------
34
+ export class IngesterService {
35
+ bundle;
36
+ embedder;
37
+ scope;
38
+ manifest;
39
+ manifestPath;
40
+ constructor(bundle, embedder, scope, wikiRoot, options = {}) {
41
+ this.bundle = bundle;
42
+ this.embedder = embedder;
43
+ this.scope = scope;
44
+ this.manifestPath = options.manifestPath ?? manifestPathFor(wikiRoot);
45
+ this.manifest = readManifest(this.manifestPath);
46
+ }
47
+ /**
48
+ * Ingest a single markdown file. Single-flight: concurrent calls for the
49
+ * same file return the same in-progress promise.
50
+ *
51
+ * Returns the per-chunk metrics for this run (added/skipped/embedder calls)
52
+ * so callers (rebuild CLI, chokidar handler) can log a summary.
53
+ */
54
+ async ingestFile(absPath) {
55
+ const key = `${this.scope}:${resolve(absPath)}`;
56
+ const existing = inFlight.get(key);
57
+ if (existing)
58
+ return existing;
59
+ const promise = this.ingestFileImpl(resolve(absPath))
60
+ .catch((err) => {
61
+ // Make sure single-flight cleanup runs even on error.
62
+ logger.error({ err, file: absPath, scope: this.scope }, 'ingest failed');
63
+ this.appendLog('ingest:error', absPath, {
64
+ error: err instanceof Error ? err.message : String(err),
65
+ });
66
+ throw err;
67
+ })
68
+ .finally(() => {
69
+ inFlight.delete(key);
70
+ });
71
+ inFlight.set(key, promise);
72
+ return promise;
73
+ }
74
+ /**
75
+ * Remove a file from the index (for `file:removed` chokidar events).
76
+ * Deletes the file's nodes (cascades to chunks/edges/vec rows via FK)
77
+ * and updates the manifest. Single-statement, no transaction needed.
78
+ */
79
+ removeFile(absPath) {
80
+ const abs = resolve(absPath);
81
+ const sourceUri = pathToFileURL(abs).href;
82
+ this.bundle.writer
83
+ .prepare('DELETE FROM kg_nodes WHERE scope = ? AND source_uri = ?')
84
+ .run(this.scope, sourceUri);
85
+ this.manifest = removeManifestEntry(this.manifest, abs);
86
+ writeManifest(this.manifestPath, this.manifest);
87
+ this.appendLog('ingest:removed', abs, {});
88
+ }
89
+ /**
90
+ * The current in-memory manifest. Exposed for tests + the startup
91
+ * consistency check in `cli/serve.ts`.
92
+ */
93
+ getManifest() {
94
+ return this.manifest;
95
+ }
96
+ /**
97
+ * Reload the manifest from disk. Used at startup before walking the
98
+ * consistency check, and by tests that mutate the manifest externally.
99
+ */
100
+ reloadManifest() {
101
+ this.manifest = readManifest(this.manifestPath);
102
+ }
103
+ // --------------------------------------------------------------------------
104
+ // Internal: the actual ingest pipeline
105
+ // --------------------------------------------------------------------------
106
+ async ingestFileImpl(absPath) {
107
+ // Step 1: read file + compute file-level sha
108
+ const text = readFileSync(absPath, 'utf8');
109
+ const sourceSha = sha1(text);
110
+ const sourceUri = pathToFileURL(absPath).href;
111
+ // Step 2: manifest fast path — same source_sha → no work
112
+ const existingEntry = this.manifest.files[absPath];
113
+ if (existingEntry && existingEntry.source_sha === sourceSha) {
114
+ return {
115
+ chunks_added: 0,
116
+ chunks_skipped: 0,
117
+ embedder_calls: 0,
118
+ nodes_written: 0,
119
+ noop: true,
120
+ };
121
+ }
122
+ // Step 3: parse + chunk + detect confidence
123
+ const sections = parseMarkdown(text);
124
+ const confidence = detectConfidence(text);
125
+ const planned = sections.map((section) => {
126
+ const nodeId = nodeIdFor(this.scope, sourceUri, section.section_path);
127
+ const chunks = chunkSection(section.content);
128
+ return {
129
+ nodeId,
130
+ section,
131
+ chunks: chunks.map((chunkText, idx) => ({
132
+ id: chunkIdFor(nodeId, idx),
133
+ text: chunkText.text,
134
+ chunkSha: sha1(chunkText.text),
135
+ tokenCount: chunkText.token_count,
136
+ chunkIndex: idx,
137
+ })),
138
+ };
139
+ });
140
+ // Step 4: load existing chunk_shas → embedding map for skip-unchanged
141
+ const existingEmbeddings = this.loadExistingEmbeddings(sourceUri);
142
+ // Step 5: embed only new chunks
143
+ let embedderCalls = 0;
144
+ let chunksSkipped = 0;
145
+ const embeddings = new Map();
146
+ for (const node of planned) {
147
+ for (const chunk of node.chunks) {
148
+ const cached = existingEmbeddings.get(chunk.chunkSha);
149
+ if (cached) {
150
+ embeddings.set(chunk.id, cached);
151
+ chunksSkipped++;
152
+ }
153
+ else {
154
+ try {
155
+ const vec = await this.embedder.embed(chunk.text);
156
+ embeddings.set(chunk.id, vec);
157
+ embedderCalls++;
158
+ }
159
+ catch (err) {
160
+ // Per CLAUDE.md §AI Rules #4: embedder failure is non-fatal.
161
+ // Insert the chunk without a vec row; FTS5 still works.
162
+ logger.warn({ err, chunkId: chunk.id, file: absPath }, 'embedder failed for chunk; inserting without vec row');
163
+ }
164
+ }
165
+ }
166
+ }
167
+ // Step 6: transaction — delete-then-insert nodes/chunks/vec for this file
168
+ const now = Date.now();
169
+ const totalChunks = planned.reduce((acc, n) => acc + n.chunks.length, 0);
170
+ this.runInTransaction((w) => {
171
+ // Delete old vec rows BEFORE deleting kg_chunks rows. We need to know
172
+ // the rowids first because the FK cascade will drop the kg_chunks rows
173
+ // (and sqlite-vec doesn't have FK awareness).
174
+ const oldRowids = w
175
+ .prepare(`SELECT c.rowid AS rowid
176
+ FROM kg_chunks c JOIN kg_nodes n ON c.node_id = n.id
177
+ WHERE n.scope = ? AND n.source_uri = ?`)
178
+ .all(this.scope, sourceUri);
179
+ if (oldRowids.length > 0) {
180
+ const placeholders = oldRowids.map(() => '?').join(',');
181
+ w.prepare(`DELETE FROM kg_chunks_vec WHERE rowid IN (${placeholders})`).run(...oldRowids.map((r) => r.rowid));
182
+ }
183
+ // Delete old nodes for this file (cascades to chunks → FTS5 trigger fires).
184
+ w.prepare('DELETE FROM kg_nodes WHERE scope = ? AND source_uri = ?').run(this.scope, sourceUri);
185
+ // Insert new nodes
186
+ const insertNode = w.prepare(`INSERT INTO kg_nodes (id, scope, source_uri, section_path, kind, title, content, source_sha, token_count, created_at, updated_at, last_accessed_at, confidence)
187
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
188
+ const insertChunk = w.prepare(`INSERT INTO kg_chunks (id, node_id, chunk_index, text, chunk_sha, token_count, created_at)
189
+ VALUES (?, ?, ?, ?, ?, ?, ?)`);
190
+ const insertVec = w.prepare('INSERT INTO kg_chunks_vec(rowid, embedding) VALUES (?, ?)');
191
+ for (const node of planned) {
192
+ const sectionTokenCount = countTokens(node.section.content);
193
+ insertNode.run(node.nodeId, this.scope, sourceUri, node.section.section_path, node.section.kind, node.section.title || null, node.section.content, sourceSha, sectionTokenCount, now, now, now, confidence);
194
+ for (const chunk of node.chunks) {
195
+ // Capture lastInsertRowid directly from the chunk insert. This avoids
196
+ // a follow-up SELECT.
197
+ const info = insertChunk.run(chunk.id, node.nodeId, chunk.chunkIndex, chunk.text, chunk.chunkSha, chunk.tokenCount, now);
198
+ // sqlite-vec's vec0 virtual table is strict: it rejects JS Number
199
+ // bindings for the rowid column with "Only integers are allowed for
200
+ // primary key values" — even though the value IS an integer at the
201
+ // SQL level. We MUST bind as BigInt. (Verified via repro 2026-04-09.)
202
+ // lastInsertRowid is `number | bigint`; coerce to BigInt either way.
203
+ const rowidBig = typeof info.lastInsertRowid === 'bigint'
204
+ ? info.lastInsertRowid
205
+ : BigInt(info.lastInsertRowid);
206
+ const vec = embeddings.get(chunk.id);
207
+ if (vec) {
208
+ insertVec.run(rowidBig, Buffer.from(vec.buffer));
209
+ }
210
+ }
211
+ }
212
+ });
213
+ // Step 7: append kg_log row
214
+ this.appendLog('ingest:done', absPath, {
215
+ nodes: planned.length,
216
+ chunks_added: totalChunks - chunksSkipped,
217
+ chunks_skipped: chunksSkipped,
218
+ embedder_calls: embedderCalls,
219
+ });
220
+ // Step 7.5: gap detection (Phase 6) — runs after commit, scans for concept
221
+ // mentions and resolves gaps when matching nodes appear
222
+ try {
223
+ const nodeTitles = planned
224
+ .map((n) => n.section.title)
225
+ .filter((t) => !!t);
226
+ detectGaps(this.bundle.writer, this.scope, text, nodeTitles);
227
+ }
228
+ catch (err) {
229
+ // Non-fatal: gap detection failure should never block ingest
230
+ logger.warn({ err, file: absPath }, 'gap detection failed');
231
+ }
232
+ // Step 8: update manifest (AFTER commit)
233
+ const allChunkShas = planned.flatMap((n) => n.chunks.map((c) => c.chunkSha));
234
+ const newEntry = { source_sha: sourceSha, chunk_shas: allChunkShas };
235
+ this.manifest = updateManifestEntry(this.manifest, absPath, newEntry);
236
+ writeManifest(this.manifestPath, this.manifest);
237
+ return {
238
+ chunks_added: totalChunks - chunksSkipped,
239
+ chunks_skipped: chunksSkipped,
240
+ embedder_calls: embedderCalls,
241
+ nodes_written: planned.length,
242
+ noop: false,
243
+ };
244
+ }
245
+ /**
246
+ * Load `chunk_sha → embedding` for every existing chunk of a file. Used by
247
+ * the per-chunk skip optimization: if a new chunk's sha matches one in
248
+ * this map, we reuse the embedding instead of calling the embedder.
249
+ *
250
+ * Returns an empty map if the file isn't in the DB yet (first ingest).
251
+ */
252
+ loadExistingEmbeddings(sourceUri) {
253
+ const out = new Map();
254
+ const rows = this.bundle.writer
255
+ .prepare(`SELECT c.chunk_sha AS chunk_sha, vec.embedding AS embedding
256
+ FROM kg_chunks c
257
+ JOIN kg_nodes n ON c.node_id = n.id
258
+ LEFT JOIN kg_chunks_vec vec ON vec.rowid = c.rowid
259
+ WHERE n.scope = ? AND n.source_uri = ?`)
260
+ .all(this.scope, sourceUri);
261
+ for (const row of rows) {
262
+ if (!row.embedding)
263
+ continue;
264
+ // sqlite-vec stores Float32 as a raw bytes blob. Reinterpret the buffer
265
+ // (no copy needed since we won't mutate it).
266
+ const buf = row.embedding;
267
+ const view = new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
268
+ // Make a stable copy in case better-sqlite3 reuses the buffer.
269
+ out.set(row.chunk_sha, new Float32Array(view));
270
+ }
271
+ return out;
272
+ }
273
+ runInTransaction(fn) {
274
+ const txn = this.bundle.writer.transaction(fn);
275
+ txn(this.bundle.writer);
276
+ }
277
+ appendLog(kind, absPath, payload) {
278
+ try {
279
+ this.bundle.writer
280
+ .prepare(`INSERT INTO kg_log (ts, scope, kind, source_uri, payload)
281
+ VALUES (?, ?, ?, ?, ?)`)
282
+ .run(Date.now(), this.scope, kind, pathToFileURL(absPath).href, JSON.stringify(payload));
283
+ }
284
+ catch (err) {
285
+ logger.warn({ err, kind, absPath }, 'failed to append kg_log row');
286
+ }
287
+ }
288
+ }
289
+ /** sha1(scope + ':' + source_uri + ':' + section_path) — locked id derivation */
290
+ function nodeIdFor(scope, sourceUri, sectionPath) {
291
+ return sha1(`${scope}:${sourceUri}:${sectionPath}`);
292
+ }
293
+ /** sha1(node_id + ':' + chunk_index) — locked id derivation */
294
+ function chunkIdFor(nodeId, chunkIndex) {
295
+ return sha1(`${nodeId}:${chunkIndex}`);
296
+ }
297
+ function sha1(input) {
298
+ return createHash('sha1').update(input).digest('hex');
299
+ }
300
+ // ----------------------------------------------------------------------------
301
+ // Cold-start helper for the rebuild CLI
302
+ // ----------------------------------------------------------------------------
303
+ /**
304
+ * Reset the in-memory manifest to empty. Used by `kg rebuild` when the
305
+ * caller passes `--clean` to force a full re-ingest from scratch.
306
+ */
307
+ export function freshManifest() {
308
+ return emptyManifest();
309
+ }
310
+ export { manifestPathFor };
311
+ export { fileSha };
312
+ //# sourceMappingURL=ingester.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingester.js","sourceRoot":"","sources":["../../src/ingest/ingester.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import type { Database as BetterSqliteDatabase } from 'better-sqlite3';
2
+ export interface ManifestEntry {
3
+ /** sha1 of the entire source file */
4
+ source_sha: string;
5
+ /** Ordered list of chunk shas (one per chunk produced by the chunker) */
6
+ chunk_shas: string[];
7
+ }
8
+ export interface Manifest {
9
+ version: number;
10
+ /** Maps absolute file paths → entry */
11
+ files: Record<string, ManifestEntry>;
12
+ }
13
+ /**
14
+ * Build an empty manifest. Useful for cold starts and tests.
15
+ */
16
+ export declare function emptyManifest(): Manifest;
17
+ /**
18
+ * Read a manifest from disk. Returns an empty manifest if the file doesn't
19
+ * exist or is unreadable. Logs a warning on parse errors but doesn't throw —
20
+ * a corrupted manifest is treated as "no manifest", and the consistency
21
+ * check will conservatively re-ingest everything (which is correct).
22
+ */
23
+ export declare function readManifest(path: string): Manifest;
24
+ /**
25
+ * Write a manifest atomically. Creates the parent directory if missing.
26
+ * The write is tmpfile + rename, so a SIGKILL mid-write either leaves the
27
+ * old manifest intact or replaces it with the new one — never half-written.
28
+ */
29
+ export declare function writeManifest(path: string, manifest: Manifest): void;
30
+ /**
31
+ * Update one entry in a manifest in-memory. Mutates the input and returns it
32
+ * (the caller is expected to follow up with `writeManifest`). The entry's
33
+ * key is the absolute path to the file as it was ingested.
34
+ */
35
+ export declare function updateManifestEntry(manifest: Manifest, filePath: string, entry: ManifestEntry): Manifest;
36
+ /**
37
+ * Drop a file from the manifest (e.g. after a `file:removed` event).
38
+ */
39
+ export declare function removeManifestEntry(manifest: Manifest, filePath: string): Manifest;
40
+ /**
41
+ * Compute the sha1 of a file's contents on disk. Used by `checkConsistency`
42
+ * and the ingester. Synchronous because we're already in the single-flight
43
+ * path and parallelizing this against itself buys nothing.
44
+ */
45
+ export declare function fileSha(filePath: string): string;
46
+ /**
47
+ * Walk a wiki directory recursively and return absolute paths of all *.md
48
+ * files in deterministic (sorted) order. Used by both the consistency check
49
+ * and the rebuild CLI.
50
+ */
51
+ export declare function listMarkdownFiles(rootDir: string): string[];
52
+ /**
53
+ * Compare an in-memory manifest against the current on-disk state of a wiki
54
+ * directory. Returns a list of file paths that need re-ingestion:
55
+ * - Files present on disk that are NOT in the manifest (new or unindexed)
56
+ * - Files present in BOTH but with mismatched `source_sha` (edited externally,
57
+ * or last ingest crashed mid-flight before the manifest could be updated)
58
+ * - Files where the manifest says "indexed" but the DB has zero rows (DB/manifest
59
+ * divergence — e.g. DB was recreated, migration dropped tables, or crash
60
+ * between COMMIT and manifest write left the manifest ahead of the DB)
61
+ *
62
+ * When DB/manifest divergence is detected for a file, its manifest entry is
63
+ * cleared so the ingester's manifest fast-path won't noop the re-ingest.
64
+ *
65
+ * Files that exist in the manifest but no longer on disk are NOT returned —
66
+ * they're deletions, handled separately by the chokidar `file:removed`
67
+ * pathway in Pass 3. The startup consistency check is for additions and
68
+ * mutations, not removals.
69
+ *
70
+ * **Performance**: O(N) over markdown files, one stat + sha per file, plus
71
+ * one lightweight COUNT query per file that passes the manifest check when
72
+ * a DB writer is provided. For a typical wiki of <100 files this is <100ms.
73
+ */
74
+ export declare function checkConsistency(manifest: Manifest, rootDir: string, writer?: BetterSqliteDatabase): string[];
75
+ /**
76
+ * Compute the canonical manifest path for a given wiki directory and scope.
77
+ *
78
+ * - `'project'` → `<wikiPath>/../kg-manifest.json`
79
+ * (lives next to the wiki dir, inside the same `.pharos/` folder)
80
+ * - `'personal'` → `<wikiPath>/../kg-manifest.json` as well
81
+ * (lives next to `~/.pharos/profile/wiki/`, in `~/.pharos/profile/`)
82
+ *
83
+ * Both scopes use the same relative shape — the difference is the wikiPath
84
+ * the caller passes in.
85
+ */
86
+ export declare function manifestPathFor(wikiPath: string): string;
87
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/ingest/manifest.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,IAAI,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAwCvE,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACtC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,QAAQ,CAExC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAqBnD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAOpE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,GACnB,QAAQ,CAIV;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAIlF;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CA4B3D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,oBAAoB,GAC5B,MAAM,EAAE,CAsCV;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAExD"}
@@ -0,0 +1,223 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync, readdirSync, statSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { logger } from '../observability/logger.js';
6
+ /**
7
+ * Consistency manifest for KG-MCP Phase 2.
8
+ *
9
+ * Lives at:
10
+ * - `<wikiPath>/../kg-manifest.json` for project scope
11
+ * (e.g. `.pharos/kg-manifest.json` next to `.pharos/wiki/`)
12
+ * - `<profilePath>/kg-manifest.json` for personal scope
13
+ * (e.g. `~/.pharos/profile/kg-manifest.json`)
14
+ *
15
+ * **What it stores**: per ingested file, the file-level `source_sha` and
16
+ * the list of per-chunk `chunk_shas` that ingest produced. This is the
17
+ * mid-ingest crash recovery surface — if the process dies during a vec
18
+ * insert, the manifest may disagree with the DB on startup, and the
19
+ * consistency check (`checkConsistency`) detects which files need re-ingest.
20
+ *
21
+ * **Why we need this**: pre-v1 sqlite-vec has untested crash semantics
22
+ * (presearch.md F9). The DB may end up in a state where vec rows exist for
23
+ * a chunk_sha that doesn't match the on-disk file. The manifest is the
24
+ * tiebreaker — it records what we INTENDED to be on disk. On startup we
25
+ * compare it against the actual on-disk markdown file_sha; mismatches mean
26
+ * the file was edited externally (or the previous run crashed before
27
+ * completing). Either way, re-ingest is the right move.
28
+ *
29
+ * **Atomic writes**: writeManifest() writes to a tmpfile and renames over
30
+ * the target. SQLite uses the same pattern; rename is atomic on Linux/macOS
31
+ * for files on the same filesystem. This avoids the failure mode where a
32
+ * SIGKILL mid-write leaves a half-written manifest that fails to parse on
33
+ * the next startup.
34
+ *
35
+ * **Format**: JSON, hand-readable, future-proofed with a `version` field
36
+ * so we can detect old manifests if the structure ever changes (e.g. adding
37
+ * per-chunk embedding metadata).
38
+ */
39
+ const MANIFEST_VERSION = 1;
40
+ /**
41
+ * Build an empty manifest. Useful for cold starts and tests.
42
+ */
43
+ export function emptyManifest() {
44
+ return { version: MANIFEST_VERSION, files: {} };
45
+ }
46
+ /**
47
+ * Read a manifest from disk. Returns an empty manifest if the file doesn't
48
+ * exist or is unreadable. Logs a warning on parse errors but doesn't throw —
49
+ * a corrupted manifest is treated as "no manifest", and the consistency
50
+ * check will conservatively re-ingest everything (which is correct).
51
+ */
52
+ export function readManifest(path) {
53
+ if (!existsSync(path))
54
+ return emptyManifest();
55
+ try {
56
+ const raw = readFileSync(path, 'utf8');
57
+ const parsed = JSON.parse(raw);
58
+ if (typeof parsed !== 'object' || parsed === null)
59
+ return emptyManifest();
60
+ if (parsed.version !== MANIFEST_VERSION) {
61
+ logger.warn({ path, foundVersion: parsed.version, expectedVersion: MANIFEST_VERSION }, 'manifest version mismatch — discarding');
62
+ return emptyManifest();
63
+ }
64
+ if (typeof parsed.files !== 'object' || parsed.files === null) {
65
+ return emptyManifest();
66
+ }
67
+ return parsed;
68
+ }
69
+ catch (err) {
70
+ logger.warn({ err, path }, 'failed to parse manifest — starting fresh');
71
+ return emptyManifest();
72
+ }
73
+ }
74
+ /**
75
+ * Write a manifest atomically. Creates the parent directory if missing.
76
+ * The write is tmpfile + rename, so a SIGKILL mid-write either leaves the
77
+ * old manifest intact or replaces it with the new one — never half-written.
78
+ */
79
+ export function writeManifest(path, manifest) {
80
+ const abs = resolve(path);
81
+ mkdirSync(dirname(abs), { recursive: true });
82
+ const tmp = `${abs}.tmp.${process.pid}.${Date.now()}`;
83
+ const json = JSON.stringify(manifest, null, 2);
84
+ writeFileSync(tmp, json, 'utf8');
85
+ renameSync(tmp, abs);
86
+ }
87
+ /**
88
+ * Update one entry in a manifest in-memory. Mutates the input and returns it
89
+ * (the caller is expected to follow up with `writeManifest`). The entry's
90
+ * key is the absolute path to the file as it was ingested.
91
+ */
92
+ export function updateManifestEntry(manifest, filePath, entry) {
93
+ const abs = resolve(filePath);
94
+ manifest.files[abs] = entry;
95
+ return manifest;
96
+ }
97
+ /**
98
+ * Drop a file from the manifest (e.g. after a `file:removed` event).
99
+ */
100
+ export function removeManifestEntry(manifest, filePath) {
101
+ const abs = resolve(filePath);
102
+ delete manifest.files[abs];
103
+ return manifest;
104
+ }
105
+ /**
106
+ * Compute the sha1 of a file's contents on disk. Used by `checkConsistency`
107
+ * and the ingester. Synchronous because we're already in the single-flight
108
+ * path and parallelizing this against itself buys nothing.
109
+ */
110
+ export function fileSha(filePath) {
111
+ const buf = readFileSync(filePath);
112
+ return createHash('sha1').update(buf).digest('hex');
113
+ }
114
+ /**
115
+ * Walk a wiki directory recursively and return absolute paths of all *.md
116
+ * files in deterministic (sorted) order. Used by both the consistency check
117
+ * and the rebuild CLI.
118
+ */
119
+ export function listMarkdownFiles(rootDir) {
120
+ const out = [];
121
+ const stack = [resolve(rootDir)];
122
+ while (stack.length > 0) {
123
+ const dir = stack.pop();
124
+ let entries;
125
+ try {
126
+ entries = readdirSync(dir);
127
+ }
128
+ catch {
129
+ continue;
130
+ }
131
+ for (const name of entries) {
132
+ const full = join(dir, name);
133
+ let st;
134
+ try {
135
+ st = statSync(full);
136
+ }
137
+ catch {
138
+ continue;
139
+ }
140
+ if (st.isDirectory()) {
141
+ stack.push(full);
142
+ }
143
+ else if (st.isFile() && name.toLowerCase().endsWith('.md')) {
144
+ out.push(full);
145
+ }
146
+ }
147
+ }
148
+ out.sort();
149
+ return out;
150
+ }
151
+ /**
152
+ * Compare an in-memory manifest against the current on-disk state of a wiki
153
+ * directory. Returns a list of file paths that need re-ingestion:
154
+ * - Files present on disk that are NOT in the manifest (new or unindexed)
155
+ * - Files present in BOTH but with mismatched `source_sha` (edited externally,
156
+ * or last ingest crashed mid-flight before the manifest could be updated)
157
+ * - Files where the manifest says "indexed" but the DB has zero rows (DB/manifest
158
+ * divergence — e.g. DB was recreated, migration dropped tables, or crash
159
+ * between COMMIT and manifest write left the manifest ahead of the DB)
160
+ *
161
+ * When DB/manifest divergence is detected for a file, its manifest entry is
162
+ * cleared so the ingester's manifest fast-path won't noop the re-ingest.
163
+ *
164
+ * Files that exist in the manifest but no longer on disk are NOT returned —
165
+ * they're deletions, handled separately by the chokidar `file:removed`
166
+ * pathway in Pass 3. The startup consistency check is for additions and
167
+ * mutations, not removals.
168
+ *
169
+ * **Performance**: O(N) over markdown files, one stat + sha per file, plus
170
+ * one lightweight COUNT query per file that passes the manifest check when
171
+ * a DB writer is provided. For a typical wiki of <100 files this is <100ms.
172
+ */
173
+ export function checkConsistency(manifest, rootDir, writer) {
174
+ const stale = [];
175
+ const files = listMarkdownFiles(rootDir);
176
+ // Pre-build a set of indexed source_uris for O(1) lookup when DB is available.
177
+ // A single query is cheaper than N per-file COUNT queries.
178
+ let indexedUris = null;
179
+ if (writer) {
180
+ try {
181
+ const rows = writer
182
+ .prepare('SELECT DISTINCT source_uri FROM kg_nodes')
183
+ .all();
184
+ indexedUris = new Set(rows.map((r) => r.source_uri));
185
+ }
186
+ catch (err) {
187
+ logger.warn({ err }, 'checkConsistency: failed to query kg_nodes — falling back to manifest-only');
188
+ }
189
+ }
190
+ for (const f of files) {
191
+ const entry = manifest.files[f];
192
+ const onDiskSha = fileSha(f);
193
+ if (!entry || entry.source_sha !== onDiskSha) {
194
+ stale.push(f);
195
+ }
196
+ else if (indexedUris) {
197
+ // Manifest says this file is current — verify the DB agrees.
198
+ const sourceUri = pathToFileURL(resolve(f)).href;
199
+ if (!indexedUris.has(sourceUri)) {
200
+ logger.info({ file: f }, 'checkConsistency: manifest says indexed but DB has no rows — marking stale');
201
+ // Clear the manifest entry so the ingester's fast-path doesn't noop.
202
+ delete manifest.files[f];
203
+ stale.push(f);
204
+ }
205
+ }
206
+ }
207
+ return stale;
208
+ }
209
+ /**
210
+ * Compute the canonical manifest path for a given wiki directory and scope.
211
+ *
212
+ * - `'project'` → `<wikiPath>/../kg-manifest.json`
213
+ * (lives next to the wiki dir, inside the same `.pharos/` folder)
214
+ * - `'personal'` → `<wikiPath>/../kg-manifest.json` as well
215
+ * (lives next to `~/.pharos/profile/wiki/`, in `~/.pharos/profile/`)
216
+ *
217
+ * Both scopes use the same relative shape — the difference is the wikiPath
218
+ * the caller passes in.
219
+ */
220
+ export function manifestPathFor(wikiPath) {
221
+ return resolve(dirname(resolve(wikiPath)), 'kg-manifest.json');
222
+ }
223
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/ingest/manifest.ts"],"names":[],"mappings":"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"}