kirograph 0.28.1 → 1.0.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 (110) hide show
  1. package/dist/architecture/index.js +4 -1
  2. package/dist/architecture/index.js.map +2 -2
  3. package/dist/architecture/manifest/index.js +13 -2
  4. package/dist/architecture/manifest/index.js.map +2 -2
  5. package/dist/bin/commands/install.js +2 -1
  6. package/dist/bin/commands/install.js.map +2 -2
  7. package/dist/bin/commands/status.js +2 -1
  8. package/dist/bin/commands/status.js.map +2 -2
  9. package/dist/bin/commands/uninit.js +2 -1
  10. package/dist/bin/commands/uninit.js.map +2 -2
  11. package/dist/bin/commands/vulns.js +10 -5
  12. package/dist/bin/commands/vulns.js.map +2 -2
  13. package/dist/bin/installer/common.js +7 -1
  14. package/dist/bin/installer/common.js.map +2 -2
  15. package/dist/bin/installer/detect.js +10 -0
  16. package/dist/bin/installer/detect.js.map +2 -2
  17. package/dist/bin/installer/hooks.js +13 -8
  18. package/dist/bin/installer/hooks.js.map +2 -2
  19. package/dist/bin/installer/targets/antigravity.js +1 -1
  20. package/dist/bin/installer/targets/antigravity.js.map +2 -2
  21. package/dist/bin/installer/targets/claude.js +1 -1
  22. package/dist/bin/installer/targets/claude.js.map +2 -2
  23. package/dist/bin/installer/targets/codex.js +1 -1
  24. package/dist/bin/installer/targets/codex.js.map +2 -2
  25. package/dist/bin/installer/targets/commandcode.js +84 -0
  26. package/dist/bin/installer/targets/commandcode.js.map +7 -0
  27. package/dist/bin/installer/targets/copilot.js +1 -1
  28. package/dist/bin/installer/targets/copilot.js.map +2 -2
  29. package/dist/bin/installer/targets/cursor.js +2 -2
  30. package/dist/bin/installer/targets/cursor.js.map +2 -2
  31. package/dist/bin/installer/targets/devin.js +1 -1
  32. package/dist/bin/installer/targets/devin.js.map +2 -2
  33. package/dist/bin/installer/targets/gemini-cli.js +1 -1
  34. package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
  35. package/dist/bin/installer/targets/index.js +10 -0
  36. package/dist/bin/installer/targets/index.js.map +2 -2
  37. package/dist/bin/installer/targets/opencode.js +1 -1
  38. package/dist/bin/installer/targets/opencode.js.map +2 -2
  39. package/dist/bin/installer/targets/windsurf.js +1 -1
  40. package/dist/bin/installer/targets/windsurf.js.map +2 -2
  41. package/dist/bin/kirograph.js +1 -1
  42. package/dist/bin/kirograph.js.map +1 -1
  43. package/dist/config.js +21 -1
  44. package/dist/config.js.map +2 -2
  45. package/dist/core/pipeline.js +18 -6
  46. package/dist/core/pipeline.js.map +2 -2
  47. package/dist/extraction/extractor.js +58 -1
  48. package/dist/extraction/extractor.js.map +2 -2
  49. package/dist/extraction/grammars.js +38 -3
  50. package/dist/extraction/grammars.js.map +2 -2
  51. package/dist/extraction/wasm/tree-sitter-bash.wasm +0 -0
  52. package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
  53. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  54. package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
  55. package/dist/extraction/wasm/tree-sitter-csharp.wasm +0 -0
  56. package/dist/extraction/wasm/tree-sitter-css.wasm +0 -0
  57. package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
  58. package/dist/extraction/wasm/tree-sitter-elisp.wasm +0 -0
  59. package/dist/extraction/wasm/tree-sitter-elixir.wasm +0 -0
  60. package/dist/extraction/wasm/tree-sitter-elm.wasm +0 -0
  61. package/dist/extraction/wasm/tree-sitter-embedded_template.wasm +0 -0
  62. package/dist/extraction/wasm/tree-sitter-embeddedtemplate.wasm +0 -0
  63. package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
  64. package/dist/extraction/wasm/tree-sitter-html.wasm +0 -0
  65. package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
  66. package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
  67. package/dist/extraction/wasm/tree-sitter-json.wasm +0 -0
  68. package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
  69. package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  70. package/dist/extraction/wasm/tree-sitter-objc.wasm +0 -0
  71. package/dist/extraction/wasm/tree-sitter-ocaml.wasm +0 -0
  72. package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
  73. package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
  74. package/dist/extraction/wasm/tree-sitter-ql.wasm +0 -0
  75. package/dist/extraction/wasm/tree-sitter-rescript.wasm +0 -0
  76. package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
  77. package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
  78. package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  79. package/dist/extraction/wasm/tree-sitter-solidity.wasm +0 -0
  80. package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
  81. package/dist/extraction/wasm/tree-sitter-systemrdl.wasm +0 -0
  82. package/dist/extraction/wasm/tree-sitter-tlaplus.wasm +0 -0
  83. package/dist/extraction/wasm/tree-sitter-toml.wasm +0 -0
  84. package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
  85. package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
  86. package/dist/extraction/wasm/tree-sitter-vue.wasm +0 -0
  87. package/dist/extraction/wasm/tree-sitter-yaml.wasm +0 -0
  88. package/dist/extraction/wasm/tree-sitter-zig.wasm +0 -0
  89. package/dist/frameworks/index.js +68 -7
  90. package/dist/frameworks/index.js.map +2 -2
  91. package/dist/security/manifest/adapter.js +17 -2
  92. package/dist/security/manifest/adapter.js.map +2 -2
  93. package/dist/security/manifest/parser.js +2 -2
  94. package/dist/security/manifest/parser.js.map +2 -2
  95. package/dist/security/pipeline.js +4 -1
  96. package/dist/security/pipeline.js.map +2 -2
  97. package/dist/security/reachability.js +61 -45
  98. package/dist/security/reachability.js.map +3 -3
  99. package/dist/security/types.js.map +1 -1
  100. package/dist/security/vuln/epss-client.js +1 -1
  101. package/dist/security/vuln/epss-client.js.map +1 -1
  102. package/dist/security/vuln/osv-adapter.js +99 -31
  103. package/dist/security/vuln/osv-adapter.js.map +2 -2
  104. package/dist/sync/index.js +9 -2
  105. package/dist/sync/index.js.map +2 -2
  106. package/dist/vectors/libsql-index.js +287 -0
  107. package/dist/vectors/libsql-index.js.map +7 -0
  108. package/dist/vectors/turso-index.js +307 -0
  109. package/dist/vectors/turso-index.js.map +7 -0
  110. package/package.json +3 -2
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var turso_index_exports = {};
30
+ __export(turso_index_exports, {
31
+ TursoIndex: () => TursoIndex
32
+ });
33
+ module.exports = __toCommonJS(turso_index_exports);
34
+ var path = __toESM(require("path"));
35
+ var import_errors = require("../errors");
36
+ const DEFAULT_DIM = 768;
37
+ const DB_FILE = "turso.db";
38
+ const VECTOR_WEIGHT = 0.7;
39
+ const FTS_WEIGHT = 0.3;
40
+ class TursoIndex {
41
+ constructor(kirographDir, dim = DEFAULT_DIM) {
42
+ this.kirographDir = kirographDir;
43
+ this.dim = dim;
44
+ this.db = null;
45
+ this._available = false;
46
+ this._failReason = null;
47
+ this.dbPath = path.join(kirographDir, DB_FILE);
48
+ }
49
+ isAvailable() {
50
+ return this._available;
51
+ }
52
+ getFailReason() {
53
+ return this._failReason;
54
+ }
55
+ /**
56
+ * Load @tursodatabase/database, open a local file-based database, and apply the schema.
57
+ * Silent no-op when the optional dep is missing.
58
+ */
59
+ async initialize() {
60
+ if (this._available) return;
61
+ let connect;
62
+ try {
63
+ const mod = require("@tursodatabase/database");
64
+ connect = mod.connect ?? mod.default?.connect ?? mod;
65
+ } catch {
66
+ (0, import_errors.logDebug)("TursoIndex: @tursodatabase/database not installed \u2014 Turso engine unavailable");
67
+ return;
68
+ }
69
+ try {
70
+ const fs = await import("fs");
71
+ let lastErr;
72
+ for (let attempt = 0; attempt < 3; attempt++) {
73
+ try {
74
+ this.db = await connect(this.dbPath);
75
+ lastErr = null;
76
+ break;
77
+ } catch (err) {
78
+ lastErr = err;
79
+ const msg = String(err);
80
+ if (msg.includes("locked") || msg.includes("Locking")) {
81
+ (0, import_errors.logDebug)(`TursoIndex: DB locked, retry ${attempt + 1}/3`);
82
+ if (attempt < 2) {
83
+ await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
84
+ }
85
+ } else {
86
+ throw err;
87
+ }
88
+ }
89
+ }
90
+ if (lastErr) {
91
+ const msg = String(lastErr);
92
+ if (msg.includes("locked") || msg.includes("Locking")) {
93
+ (0, import_errors.logDebug)("TursoIndex: removing stale locked DB and retrying");
94
+ try {
95
+ fs.unlinkSync(this.dbPath);
96
+ } catch {
97
+ }
98
+ try {
99
+ fs.unlinkSync(this.dbPath + "-wal");
100
+ } catch {
101
+ }
102
+ try {
103
+ fs.unlinkSync(this.dbPath + "-shm");
104
+ } catch {
105
+ }
106
+ this.db = await connect(this.dbPath);
107
+ } else {
108
+ throw lastErr;
109
+ }
110
+ }
111
+ await this.db.exec(`
112
+ CREATE TABLE IF NOT EXISTS kg_nodes (
113
+ node_id TEXT PRIMARY KEY,
114
+ name TEXT NOT NULL DEFAULT '',
115
+ kind TEXT NOT NULL DEFAULT '',
116
+ file_path TEXT NOT NULL DEFAULT '',
117
+ signature TEXT NOT NULL DEFAULT '',
118
+ embedding vector32(${this.dim})
119
+ );
120
+ `);
121
+ try {
122
+ await this.db.exec(`
123
+ CREATE INDEX IF NOT EXISTS kg_nodes_fts ON kg_nodes USING fts (name, signature);
124
+ `);
125
+ } catch {
126
+ (0, import_errors.logDebug)("TursoIndex: FTS index creation failed \u2014 hybrid search will fall back to pure vector");
127
+ }
128
+ this._available = true;
129
+ (0, import_errors.logDebug)("TursoIndex: ready", { path: this.dbPath, dim: this.dim });
130
+ } catch (err) {
131
+ this._failReason = String(err);
132
+ (0, import_errors.logError)("TursoIndex: initialization failed", { error: String(err) });
133
+ this._available = false;
134
+ this.db = null;
135
+ }
136
+ }
137
+ /**
138
+ * Insert or update a node's record using ON CONFLICT upsert.
139
+ */
140
+ async upsert(node, embedding) {
141
+ if (!this._available || !this.db) return;
142
+ try {
143
+ const vecLiteral = `vector32('[${Array.from(embedding).join(",")}]')`;
144
+ const stmt = this.db.prepare(`
145
+ INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)
146
+ VALUES (?, ?, ?, ?, ?, ${vecLiteral})
147
+ ON CONFLICT (node_id) DO UPDATE SET
148
+ name = excluded.name,
149
+ kind = excluded.kind,
150
+ file_path = excluded.file_path,
151
+ signature = excluded.signature,
152
+ embedding = excluded.embedding
153
+ `);
154
+ await stmt.run(
155
+ node.id,
156
+ node.name,
157
+ node.kind,
158
+ node.filePath,
159
+ node.signature ?? ""
160
+ );
161
+ } catch (err) {
162
+ (0, import_errors.logWarn)("TursoIndex: upsert failed", { nodeId: node.id, error: String(err) });
163
+ }
164
+ }
165
+ /**
166
+ * Bulk upsert for batch embedding. Uses a transaction for atomicity and speed.
167
+ */
168
+ async bulkUpsert(nodes, embeddings) {
169
+ if (!this._available || !this.db) return;
170
+ if (nodes.length === 0) return;
171
+ try {
172
+ await this.db.exec("BEGIN");
173
+ for (let i = 0; i < nodes.length; i++) {
174
+ const node = nodes[i];
175
+ const embedding = embeddings[i];
176
+ const vecLiteral = `vector32('[${Array.from(embedding).join(",")}]')`;
177
+ const stmt = this.db.prepare(`
178
+ INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)
179
+ VALUES (?, ?, ?, ?, ?, ${vecLiteral})
180
+ ON CONFLICT (node_id) DO UPDATE SET
181
+ name = excluded.name,
182
+ kind = excluded.kind,
183
+ file_path = excluded.file_path,
184
+ signature = excluded.signature,
185
+ embedding = excluded.embedding
186
+ `);
187
+ await stmt.run(
188
+ node.id,
189
+ node.name,
190
+ node.kind,
191
+ node.filePath,
192
+ node.signature ?? ""
193
+ );
194
+ }
195
+ await this.db.exec("COMMIT");
196
+ } catch (err) {
197
+ try {
198
+ await this.db.exec("ROLLBACK");
199
+ } catch {
200
+ }
201
+ (0, import_errors.logWarn)("TursoIndex: bulkUpsert failed", { count: nodes.length, error: String(err) });
202
+ }
203
+ }
204
+ /**
205
+ * Remove a node's record from the index.
206
+ */
207
+ async delete(nodeId) {
208
+ if (!this._available || !this.db) return;
209
+ try {
210
+ const stmt = this.db.prepare("DELETE FROM kg_nodes WHERE node_id = ?");
211
+ await stmt.run(nodeId);
212
+ } catch (err) {
213
+ (0, import_errors.logWarn)("TursoIndex: delete failed", { nodeId, error: String(err) });
214
+ }
215
+ }
216
+ /**
217
+ * Hybrid search: combines Turso native vector_distance_cos with Tantivy FTS.
218
+ * Returns node IDs ordered by combined score.
219
+ *
220
+ * Score = VECTOR_WEIGHT * (1 - cosine_distance) + FTS_WEIGHT * fts_score
221
+ *
222
+ * Falls back to pure vector search when the query text produces no FTS matches
223
+ * or when FTS is not available.
224
+ */
225
+ async search(queryText, queryVec, topN = 10) {
226
+ if (!this._available || !this.db) return [];
227
+ const vecLiteral = `vector32('[${Array.from(queryVec).join(",")}]')`;
228
+ try {
229
+ const stmt = this.db.prepare(`
230
+ SELECT
231
+ n.node_id,
232
+ (${VECTOR_WEIGHT} * (1.0 - vector_distance_cos(n.embedding, ${vecLiteral}))
233
+ + ${FTS_WEIGHT} * COALESCE(fts_score(n.name, n.signature, ?), 0.0)
234
+ ) AS score
235
+ FROM kg_nodes n
236
+ WHERE n.embedding IS NOT NULL
237
+ ORDER BY score DESC
238
+ LIMIT ?
239
+ `);
240
+ const rows = await stmt.all(queryText, topN);
241
+ if (rows && rows.length > 0) {
242
+ return rows.map((r) => r.node_id);
243
+ }
244
+ } catch {
245
+ (0, import_errors.logDebug)("TursoIndex: hybrid search unavailable, using pure vector");
246
+ }
247
+ return this.vectorSearch(queryVec, topN);
248
+ }
249
+ /**
250
+ * Pure vector similarity search using Turso's native vector_distance_cos.
251
+ */
252
+ async vectorSearch(queryVec, topN) {
253
+ const vecLiteral = `vector32('[${Array.from(queryVec).join(",")}]')`;
254
+ try {
255
+ const stmt = this.db.prepare(`
256
+ SELECT node_id, vector_distance_cos(embedding, ${vecLiteral}) AS distance
257
+ FROM kg_nodes
258
+ WHERE embedding IS NOT NULL
259
+ ORDER BY distance ASC
260
+ LIMIT ?
261
+ `);
262
+ const rows = await stmt.all(topN);
263
+ return (rows ?? []).map((r) => r.node_id);
264
+ } catch (err) {
265
+ (0, import_errors.logWarn)("TursoIndex: vector search failed", { error: String(err) });
266
+ return [];
267
+ }
268
+ }
269
+ /** Return all node IDs currently stored in the index. */
270
+ async getEmbeddedNodeIds() {
271
+ if (!this._available || !this.db) return [];
272
+ try {
273
+ const stmt = this.db.prepare("SELECT node_id FROM kg_nodes");
274
+ const rows = await stmt.all();
275
+ return (rows ?? []).map((r) => r.node_id);
276
+ } catch {
277
+ return [];
278
+ }
279
+ }
280
+ /** Number of documents currently in the index. */
281
+ async count() {
282
+ if (!this._available || !this.db) return 0;
283
+ try {
284
+ const stmt = this.db.prepare("SELECT COUNT(*) AS c FROM kg_nodes");
285
+ const row = await stmt.get();
286
+ return row ? row.c : 0;
287
+ } catch {
288
+ return 0;
289
+ }
290
+ }
291
+ /** Close the database connection and release resources. */
292
+ close() {
293
+ if (this.db) {
294
+ try {
295
+ if (typeof this.db.close === "function") this.db.close();
296
+ } catch {
297
+ }
298
+ this.db = null;
299
+ this._available = false;
300
+ }
301
+ }
302
+ }
303
+ // Annotate the CommonJS export names for ESM import in node:
304
+ 0 && (module.exports = {
305
+ TursoIndex
306
+ });
307
+ //# sourceMappingURL=turso-index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/vectors/turso-index.ts"],
4
+ "sourcesContent": ["/**\n * KiroGraph Turso Index\n *\n * Hybrid search (native vector similarity + Tantivy FTS) backed by Turso,\n * the ground-up rewrite of SQLite with MVCC, async I/O, and built-in vector search.\n * The database is persisted to .kirograph/turso.db (local-only, no cloud sync).\n *\n * Opt-in: set config.semanticEngine = 'turso'\n * Required optional dependency (not installed by default):\n * npm install @tursodatabase/database\n *\n * Key advantages:\n * - Native vector_distance_cos() \u2014 no extensions, built into the engine\n * - Tantivy-powered FTS with BM25 ranking (more powerful than FTS5)\n * - MVCC concurrent writes \u2014 no \"database is locked\" errors\n * - Async I/O \u2014 non-blocking operations\n * - SQLite-compatible \u2014 all standard SQL works unchanged\n * - Local-only: no cloud dependency, no network calls\n */\n\nimport * as path from 'path';\nimport { logDebug, logWarn, logError } from '../errors';\nimport type { Node } from '../types';\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst DEFAULT_DIM = 768;\nconst DB_FILE = 'turso.db';\n\n// Weight for vector score vs full-text score in hybrid ranking (must sum to 1.0)\nconst VECTOR_WEIGHT = 0.7;\nconst FTS_WEIGHT = 0.3;\n\n// \u2500\u2500 TursoIndex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class TursoIndex {\n private db: any = null;\n private _available = false;\n private dbPath: string;\n private _failReason: string | null = null;\n\n constructor(\n private readonly kirographDir: string,\n private readonly dim = DEFAULT_DIM,\n ) {\n this.dbPath = path.join(kirographDir, DB_FILE);\n }\n\n isAvailable(): boolean {\n return this._available;\n }\n\n getFailReason(): string | null {\n return this._failReason;\n }\n\n /**\n * Load @tursodatabase/database, open a local file-based database, and apply the schema.\n * Silent no-op when the optional dep is missing.\n */\n async initialize(): Promise<void> {\n if (this._available) return;\n\n let connect: any;\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mod = require('@tursodatabase/database');\n connect = mod.connect ?? mod.default?.connect ?? mod;\n } catch {\n logDebug('TursoIndex: @tursodatabase/database not installed \u2014 Turso engine unavailable');\n return;\n }\n\n try {\n // Turso uses exclusive file locking. If another process (e.g. MCP server) holds\n // the DB open, the connect call will fail. Retry after a brief delay.\n // If all retries fail due to locking, remove the stale DB and try once more\n // (safe because the DB is rebuilt from embeddings on next index).\n const fs = await import('fs');\n let lastErr: unknown;\n for (let attempt = 0; attempt < 3; attempt++) {\n try {\n this.db = await connect(this.dbPath);\n lastErr = null;\n break;\n } catch (err) {\n lastErr = err;\n const msg = String(err);\n if (msg.includes('locked') || msg.includes('Locking')) {\n logDebug(`TursoIndex: DB locked, retry ${attempt + 1}/3`);\n if (attempt < 2) {\n await new Promise(r => setTimeout(r, 500 * (attempt + 1)));\n }\n } else {\n throw err;\n }\n }\n }\n // If still locked after retries, remove the stale DB and try one final time\n if (lastErr) {\n const msg = String(lastErr);\n if (msg.includes('locked') || msg.includes('Locking')) {\n logDebug('TursoIndex: removing stale locked DB and retrying');\n try { fs.unlinkSync(this.dbPath); } catch { /* ignore */ }\n try { fs.unlinkSync(this.dbPath + '-wal'); } catch { /* ignore */ }\n try { fs.unlinkSync(this.dbPath + '-shm'); } catch { /* ignore */ }\n this.db = await connect(this.dbPath);\n } else {\n throw lastErr;\n }\n }\n\n // Create table with native vector column\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS kg_nodes (\n node_id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n kind TEXT NOT NULL DEFAULT '',\n file_path TEXT NOT NULL DEFAULT '',\n signature TEXT NOT NULL DEFAULT '',\n embedding vector32(${this.dim})\n );\n `);\n\n // Tantivy-powered FTS index for hybrid search\n // Note: Turso FTS uses CREATE INDEX ... USING fts syntax\n try {\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS kg_nodes_fts ON kg_nodes USING fts (name, signature);\n `);\n } catch {\n // FTS index creation may fail on older Turso versions \u2014 fall back to pure vector\n logDebug('TursoIndex: FTS index creation failed \u2014 hybrid search will fall back to pure vector');\n }\n\n this._available = true;\n logDebug('TursoIndex: ready', { path: this.dbPath, dim: this.dim });\n } catch (err) {\n this._failReason = String(err);\n logError('TursoIndex: initialization failed', { error: String(err) });\n this._available = false;\n this.db = null;\n }\n }\n\n /**\n * Insert or update a node's record using ON CONFLICT upsert.\n */\n async upsert(node: Node, embedding: Float32Array): Promise<void> {\n if (!this._available || !this.db) return;\n\n try {\n const vecLiteral = `vector32('[${Array.from(embedding).join(',')}]')`;\n const stmt = this.db.prepare(`\n INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)\n VALUES (?, ?, ?, ?, ?, ${vecLiteral})\n ON CONFLICT (node_id) DO UPDATE SET\n name = excluded.name,\n kind = excluded.kind,\n file_path = excluded.file_path,\n signature = excluded.signature,\n embedding = excluded.embedding\n `);\n await stmt.run(\n node.id,\n node.name,\n node.kind,\n node.filePath,\n node.signature ?? '',\n );\n } catch (err) {\n logWarn('TursoIndex: upsert failed', { nodeId: node.id, error: String(err) });\n }\n }\n\n /**\n * Bulk upsert for batch embedding. Uses a transaction for atomicity and speed.\n */\n async bulkUpsert(nodes: Node[], embeddings: Float32Array[]): Promise<void> {\n if (!this._available || !this.db) return;\n if (nodes.length === 0) return;\n\n try {\n await this.db.exec('BEGIN');\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]!;\n const embedding = embeddings[i]!;\n const vecLiteral = `vector32('[${Array.from(embedding).join(',')}]')`;\n const stmt = this.db.prepare(`\n INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)\n VALUES (?, ?, ?, ?, ?, ${vecLiteral})\n ON CONFLICT (node_id) DO UPDATE SET\n name = excluded.name,\n kind = excluded.kind,\n file_path = excluded.file_path,\n signature = excluded.signature,\n embedding = excluded.embedding\n `);\n await stmt.run(\n node.id,\n node.name,\n node.kind,\n node.filePath,\n node.signature ?? '',\n );\n }\n await this.db.exec('COMMIT');\n } catch (err) {\n try { await this.db.exec('ROLLBACK'); } catch { /* ignore */ }\n logWarn('TursoIndex: bulkUpsert failed', { count: nodes.length, error: String(err) });\n }\n }\n\n /**\n * Remove a node's record from the index.\n */\n async delete(nodeId: string): Promise<void> {\n if (!this._available || !this.db) return;\n\n try {\n const stmt = this.db.prepare('DELETE FROM kg_nodes WHERE node_id = ?');\n await stmt.run(nodeId);\n } catch (err) {\n logWarn('TursoIndex: delete failed', { nodeId, error: String(err) });\n }\n }\n\n /**\n * Hybrid search: combines Turso native vector_distance_cos with Tantivy FTS.\n * Returns node IDs ordered by combined score.\n *\n * Score = VECTOR_WEIGHT * (1 - cosine_distance) + FTS_WEIGHT * fts_score\n *\n * Falls back to pure vector search when the query text produces no FTS matches\n * or when FTS is not available.\n */\n async search(queryText: string, queryVec: Float32Array, topN = 10): Promise<string[]> {\n if (!this._available || !this.db) return [];\n\n const vecLiteral = `vector32('[${Array.from(queryVec).join(',')}]')`;\n\n // Try hybrid search first\n try {\n const stmt = this.db.prepare(`\n SELECT\n n.node_id,\n (${VECTOR_WEIGHT} * (1.0 - vector_distance_cos(n.embedding, ${vecLiteral}))\n + ${FTS_WEIGHT} * COALESCE(fts_score(n.name, n.signature, ?), 0.0)\n ) AS score\n FROM kg_nodes n\n WHERE n.embedding IS NOT NULL\n ORDER BY score DESC\n LIMIT ?\n `);\n const rows = await stmt.all(queryText, topN);\n\n if (rows && rows.length > 0) {\n return rows.map((r: any) => r.node_id as string);\n }\n } catch {\n // FTS functions may not be available \u2014 fall back to pure vector\n logDebug('TursoIndex: hybrid search unavailable, using pure vector');\n }\n\n // Fallback: pure vector search\n return this.vectorSearch(queryVec, topN);\n }\n\n /**\n * Pure vector similarity search using Turso's native vector_distance_cos.\n */\n private async vectorSearch(queryVec: Float32Array, topN: number): Promise<string[]> {\n const vecLiteral = `vector32('[${Array.from(queryVec).join(',')}]')`;\n try {\n const stmt = this.db.prepare(`\n SELECT node_id, vector_distance_cos(embedding, ${vecLiteral}) AS distance\n FROM kg_nodes\n WHERE embedding IS NOT NULL\n ORDER BY distance ASC\n LIMIT ?\n `);\n const rows = await stmt.all(topN);\n return (rows ?? []).map((r: any) => r.node_id as string);\n } catch (err) {\n logWarn('TursoIndex: vector search failed', { error: String(err) });\n return [];\n }\n }\n\n /** Return all node IDs currently stored in the index. */\n async getEmbeddedNodeIds(): Promise<string[]> {\n if (!this._available || !this.db) return [];\n try {\n const stmt = this.db.prepare('SELECT node_id FROM kg_nodes');\n const rows = await stmt.all();\n return (rows ?? []).map((r: any) => r.node_id as string);\n } catch {\n return [];\n }\n }\n\n /** Number of documents currently in the index. */\n async count(): Promise<number> {\n if (!this._available || !this.db) return 0;\n try {\n const stmt = this.db.prepare('SELECT COUNT(*) AS c FROM kg_nodes');\n const row = await stmt.get();\n return row ? (row as any).c : 0;\n } catch {\n return 0;\n }\n }\n\n /** Close the database connection and release resources. */\n close(): void {\n // @tursodatabase/database may not have a sync close \u2014 best-effort\n if (this.db) {\n try {\n if (typeof this.db.close === 'function') this.db.close();\n } catch { /* ignore */ }\n this.db = null;\n this._available = false;\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kirograph",
3
- "version": "0.28.1",
3
+ "version": "1.0.0",
4
4
  "description": "Semantic code knowledge graph for Kiro: fewer tool calls, instant symbol lookups, 100% local.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,6 +25,7 @@
25
25
  "test:git": "bash test/git/test.sh",
26
26
  "test:graph": "bash test/graph/test.sh",
27
27
  "test:health": "bash test/health/test.sh",
28
+ "test:hooks": "bash test/hooks/test.sh",
28
29
  "test:insights": "bash test/insights/test.sh",
29
30
  "test:mem": "bash test/mem/test.sh",
30
31
  "test:patterns": "bash test/patterns/test.sh",
@@ -34,7 +35,7 @@
34
35
  "test:watchmen": "bash test/watchmen/test.sh",
35
36
  "test:watchmen:skip-llm": "bash test/watchmen/test.sh --skip-llm",
36
37
  "test:wiki": "bash test/wiki/test.sh",
37
- "test:integration": "bash test/agent/test.sh --no-build && bash test/architecture/test.sh --no-build && bash test/branch/test.sh --no-build && bash test/data/test.sh --no-build && bash test/edit/test.sh --no-build && bash test/git/test.sh --no-build && bash test/graph/test.sh --no-build && bash test/health/test.sh --no-build && bash test/insights/test.sh --no-build && bash test/mem/test.sh --no-build && bash test/patterns/test.sh --no-build && bash test/security/test.sh --no-build && bash test/turboquant/test.sh --no-build && bash test/turbovec/test.sh --no-build && bash test/watchmen/test.sh --no-build --skip-llm && bash test/wiki/test.sh --no-build",
38
+ "test:integration": "bash test/agent/test.sh --no-build && bash test/architecture/test.sh --no-build && bash test/branch/test.sh --no-build && bash test/data/test.sh --no-build && bash test/edit/test.sh --no-build && bash test/git/test.sh --no-build && bash test/graph/test.sh --no-build && bash test/health/test.sh --no-build && bash test/hooks/test.sh --no-build && bash test/insights/test.sh --no-build && bash test/mem/test.sh --no-build && bash test/patterns/test.sh --no-build && bash test/security/test.sh --no-build && bash test/turboquant/test.sh --no-build && bash test/turbovec/test.sh --no-build && bash test/watchmen/test.sh --no-build --skip-llm && bash test/wiki/test.sh --no-build",
38
39
  "test:agent:mcp": "bash test/agent/mcp.sh",
39
40
  "test:architecture:mcp": "bash test/architecture/mcp.sh",
40
41
  "test:branch:mcp": "bash test/branch/mcp.sh",