kiri-mcp-server 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 (212) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +399 -0
  3. package/config/default.example.yml +12 -0
  4. package/config/denylist.yml +15 -0
  5. package/config/scoring-profiles.yml +37 -0
  6. package/config/security.yml +10 -0
  7. package/dist/client/cli.js +68 -0
  8. package/dist/client/cli.js.map +1 -0
  9. package/dist/client/index.js +5 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/config/default.example.yml +12 -0
  12. package/dist/config/denylist.yml +15 -0
  13. package/dist/config/scoring-profiles.yml +37 -0
  14. package/dist/config/security.yml +10 -0
  15. package/dist/eval/metrics.js +47 -0
  16. package/dist/eval/metrics.js.map +1 -0
  17. package/dist/indexer/cli.js +362 -0
  18. package/dist/indexer/cli.js.map +1 -0
  19. package/dist/indexer/codeintel.js +182 -0
  20. package/dist/indexer/codeintel.js.map +1 -0
  21. package/dist/indexer/git.js +30 -0
  22. package/dist/indexer/git.js.map +1 -0
  23. package/dist/indexer/language.js +34 -0
  24. package/dist/indexer/language.js.map +1 -0
  25. package/dist/indexer/pipeline/filters/denylist.js +71 -0
  26. package/dist/indexer/pipeline/filters/denylist.js.map +1 -0
  27. package/dist/indexer/schema.js +101 -0
  28. package/dist/indexer/schema.js.map +1 -0
  29. package/dist/package.json +93 -0
  30. package/dist/server/bootstrap.js +19 -0
  31. package/dist/server/bootstrap.js.map +1 -0
  32. package/dist/server/context.js +2 -0
  33. package/dist/server/context.js.map +1 -0
  34. package/dist/server/fallbacks/degradeController.js +69 -0
  35. package/dist/server/fallbacks/degradeController.js.map +1 -0
  36. package/dist/server/handlers.js +800 -0
  37. package/dist/server/handlers.js.map +1 -0
  38. package/dist/server/main.js +151 -0
  39. package/dist/server/main.js.map +1 -0
  40. package/dist/server/observability/metrics.js +56 -0
  41. package/dist/server/observability/metrics.js.map +1 -0
  42. package/dist/server/observability/tracing.js +58 -0
  43. package/dist/server/observability/tracing.js.map +1 -0
  44. package/dist/server/rpc.js +477 -0
  45. package/dist/server/rpc.js.map +1 -0
  46. package/dist/server/runtime.js +47 -0
  47. package/dist/server/runtime.js.map +1 -0
  48. package/dist/server/scoring.js +111 -0
  49. package/dist/server/scoring.js.map +1 -0
  50. package/dist/server/stdio.js +76 -0
  51. package/dist/server/stdio.js.map +1 -0
  52. package/dist/shared/duckdb.js +121 -0
  53. package/dist/shared/duckdb.js.map +1 -0
  54. package/dist/shared/embedding.js +85 -0
  55. package/dist/shared/embedding.js.map +1 -0
  56. package/dist/shared/index.js +9 -0
  57. package/dist/shared/index.js.map +1 -0
  58. package/dist/shared/security/config.js +64 -0
  59. package/dist/shared/security/config.js.map +1 -0
  60. package/dist/shared/security/masker.js +56 -0
  61. package/dist/shared/security/masker.js.map +1 -0
  62. package/dist/shared/tokenizer.js +5 -0
  63. package/dist/shared/tokenizer.js.map +1 -0
  64. package/dist/shared/utils/simpleYaml.js +90 -0
  65. package/dist/shared/utils/simpleYaml.js.map +1 -0
  66. package/dist/sql/schema.sql +6 -0
  67. package/dist/src/client/cli.d.ts +3 -0
  68. package/dist/src/client/cli.d.ts.map +1 -0
  69. package/dist/src/client/cli.js +68 -0
  70. package/dist/src/client/cli.js.map +1 -0
  71. package/dist/src/client/index.d.ts +5 -0
  72. package/dist/src/client/index.d.ts.map +1 -0
  73. package/dist/src/client/index.js +5 -0
  74. package/dist/src/client/index.js.map +1 -0
  75. package/dist/src/client/proxy.d.ts +9 -0
  76. package/dist/src/client/proxy.d.ts.map +1 -0
  77. package/dist/src/client/proxy.js +198 -0
  78. package/dist/src/client/proxy.js.map +1 -0
  79. package/dist/src/client/start-daemon.d.ts +30 -0
  80. package/dist/src/client/start-daemon.d.ts.map +1 -0
  81. package/dist/src/client/start-daemon.js +175 -0
  82. package/dist/src/client/start-daemon.js.map +1 -0
  83. package/dist/src/daemon/daemon.d.ts +9 -0
  84. package/dist/src/daemon/daemon.d.ts.map +1 -0
  85. package/dist/src/daemon/daemon.js +149 -0
  86. package/dist/src/daemon/daemon.js.map +1 -0
  87. package/dist/src/daemon/lifecycle.d.ts +101 -0
  88. package/dist/src/daemon/lifecycle.d.ts.map +1 -0
  89. package/dist/src/daemon/lifecycle.js +266 -0
  90. package/dist/src/daemon/lifecycle.js.map +1 -0
  91. package/dist/src/daemon/socket.d.ts +26 -0
  92. package/dist/src/daemon/socket.d.ts.map +1 -0
  93. package/dist/src/daemon/socket.js +132 -0
  94. package/dist/src/daemon/socket.js.map +1 -0
  95. package/dist/src/eval/metrics.d.ts +23 -0
  96. package/dist/src/eval/metrics.d.ts.map +1 -0
  97. package/dist/src/eval/metrics.js +47 -0
  98. package/dist/src/eval/metrics.js.map +1 -0
  99. package/dist/src/index.d.ts +11 -0
  100. package/dist/src/index.d.ts.map +1 -0
  101. package/dist/src/index.js +11 -0
  102. package/dist/src/index.js.map +1 -0
  103. package/dist/src/indexer/cli.d.ts +9 -0
  104. package/dist/src/indexer/cli.d.ts.map +1 -0
  105. package/dist/src/indexer/cli.js +402 -0
  106. package/dist/src/indexer/cli.js.map +1 -0
  107. package/dist/src/indexer/codeintel.d.ts +28 -0
  108. package/dist/src/indexer/codeintel.d.ts.map +1 -0
  109. package/dist/src/indexer/codeintel.js +451 -0
  110. package/dist/src/indexer/codeintel.js.map +1 -0
  111. package/dist/src/indexer/git.d.ts +4 -0
  112. package/dist/src/indexer/git.d.ts.map +1 -0
  113. package/dist/src/indexer/git.js +30 -0
  114. package/dist/src/indexer/git.js.map +1 -0
  115. package/dist/src/indexer/language.d.ts +2 -0
  116. package/dist/src/indexer/language.d.ts.map +1 -0
  117. package/dist/src/indexer/language.js +34 -0
  118. package/dist/src/indexer/language.js.map +1 -0
  119. package/dist/src/indexer/pipeline/filters/denylist.d.ts +10 -0
  120. package/dist/src/indexer/pipeline/filters/denylist.d.ts.map +1 -0
  121. package/dist/src/indexer/pipeline/filters/denylist.js +71 -0
  122. package/dist/src/indexer/pipeline/filters/denylist.js.map +1 -0
  123. package/dist/src/indexer/schema.d.ts +9 -0
  124. package/dist/src/indexer/schema.d.ts.map +1 -0
  125. package/dist/src/indexer/schema.js +125 -0
  126. package/dist/src/indexer/schema.js.map +1 -0
  127. package/dist/src/indexer/watch.d.ts +97 -0
  128. package/dist/src/indexer/watch.d.ts.map +1 -0
  129. package/dist/src/indexer/watch.js +264 -0
  130. package/dist/src/indexer/watch.js.map +1 -0
  131. package/dist/src/server/bootstrap.d.ts +11 -0
  132. package/dist/src/server/bootstrap.d.ts.map +1 -0
  133. package/dist/src/server/bootstrap.js +19 -0
  134. package/dist/src/server/bootstrap.js.map +1 -0
  135. package/dist/src/server/context.d.ts +9 -0
  136. package/dist/src/server/context.d.ts.map +1 -0
  137. package/dist/src/server/context.js +2 -0
  138. package/dist/src/server/context.js.map +1 -0
  139. package/dist/src/server/fallbacks/degradeController.d.ts +24 -0
  140. package/dist/src/server/fallbacks/degradeController.d.ts.map +1 -0
  141. package/dist/src/server/fallbacks/degradeController.js +135 -0
  142. package/dist/src/server/fallbacks/degradeController.js.map +1 -0
  143. package/dist/src/server/handlers.d.ts +105 -0
  144. package/dist/src/server/handlers.d.ts.map +1 -0
  145. package/dist/src/server/handlers.js +954 -0
  146. package/dist/src/server/handlers.js.map +1 -0
  147. package/dist/src/server/indexBootstrap.d.ts +13 -0
  148. package/dist/src/server/indexBootstrap.d.ts.map +1 -0
  149. package/dist/src/server/indexBootstrap.js +109 -0
  150. package/dist/src/server/indexBootstrap.js.map +1 -0
  151. package/dist/src/server/main.d.ts +10 -0
  152. package/dist/src/server/main.d.ts.map +1 -0
  153. package/dist/src/server/main.js +217 -0
  154. package/dist/src/server/main.js.map +1 -0
  155. package/dist/src/server/observability/metrics.d.ts +35 -0
  156. package/dist/src/server/observability/metrics.d.ts.map +1 -0
  157. package/dist/src/server/observability/metrics.js +70 -0
  158. package/dist/src/server/observability/metrics.js.map +1 -0
  159. package/dist/src/server/observability/tracing.d.ts +3 -0
  160. package/dist/src/server/observability/tracing.d.ts.map +1 -0
  161. package/dist/src/server/observability/tracing.js +58 -0
  162. package/dist/src/server/observability/tracing.js.map +1 -0
  163. package/dist/src/server/rpc.d.ts +39 -0
  164. package/dist/src/server/rpc.d.ts.map +1 -0
  165. package/dist/src/server/rpc.js +551 -0
  166. package/dist/src/server/rpc.js.map +1 -0
  167. package/dist/src/server/runtime.d.ts +21 -0
  168. package/dist/src/server/runtime.d.ts.map +1 -0
  169. package/dist/src/server/runtime.js +59 -0
  170. package/dist/src/server/runtime.js.map +1 -0
  171. package/dist/src/server/scoring.d.ts +20 -0
  172. package/dist/src/server/scoring.d.ts.map +1 -0
  173. package/dist/src/server/scoring.js +112 -0
  174. package/dist/src/server/scoring.js.map +1 -0
  175. package/dist/src/server/stdio.d.ts +4 -0
  176. package/dist/src/server/stdio.d.ts.map +1 -0
  177. package/dist/src/server/stdio.js +88 -0
  178. package/dist/src/server/stdio.js.map +1 -0
  179. package/dist/src/shared/duckdb.d.ts +16 -0
  180. package/dist/src/shared/duckdb.d.ts.map +1 -0
  181. package/dist/src/shared/duckdb.js +121 -0
  182. package/dist/src/shared/duckdb.js.map +1 -0
  183. package/dist/src/shared/embedding.d.ts +19 -0
  184. package/dist/src/shared/embedding.d.ts.map +1 -0
  185. package/dist/src/shared/embedding.js +85 -0
  186. package/dist/src/shared/embedding.js.map +1 -0
  187. package/dist/src/shared/index.d.ts +3 -0
  188. package/dist/src/shared/index.d.ts.map +1 -0
  189. package/dist/src/shared/index.js +9 -0
  190. package/dist/src/shared/index.js.map +1 -0
  191. package/dist/src/shared/security/config.d.ts +23 -0
  192. package/dist/src/shared/security/config.d.ts.map +1 -0
  193. package/dist/src/shared/security/config.js +66 -0
  194. package/dist/src/shared/security/config.js.map +1 -0
  195. package/dist/src/shared/security/masker.d.ts +10 -0
  196. package/dist/src/shared/security/masker.d.ts.map +1 -0
  197. package/dist/src/shared/security/masker.js +56 -0
  198. package/dist/src/shared/security/masker.js.map +1 -0
  199. package/dist/src/shared/tokenizer.d.ts +2 -0
  200. package/dist/src/shared/tokenizer.d.ts.map +1 -0
  201. package/dist/src/shared/tokenizer.js +5 -0
  202. package/dist/src/shared/tokenizer.js.map +1 -0
  203. package/dist/src/shared/utils/lockfile.d.ts +46 -0
  204. package/dist/src/shared/utils/lockfile.d.ts.map +1 -0
  205. package/dist/src/shared/utils/lockfile.js +136 -0
  206. package/dist/src/shared/utils/lockfile.js.map +1 -0
  207. package/dist/src/shared/utils/simpleYaml.d.ts +6 -0
  208. package/dist/src/shared/utils/simpleYaml.d.ts.map +1 -0
  209. package/dist/src/shared/utils/simpleYaml.js +90 -0
  210. package/dist/src/shared/utils/simpleYaml.js.map +1 -0
  211. package/package.json +91 -0
  212. package/sql/schema.sql +6 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Unix Socket Transport Layer for KIRI Daemon
3
+ *
4
+ * Provides IPC communication between daemon and clients via Unix domain sockets.
5
+ * Handles multiple concurrent client connections with newline-delimited JSON-RPC protocol.
6
+ */
7
+ import * as fs from "fs/promises";
8
+ import * as net from "net";
9
+ import * as readline from "readline";
10
+ /**
11
+ * ソケットサーバーを作成し、複数クライアント接続を処理する
12
+ *
13
+ * @param options - サーバー設定
14
+ * @returns クリーンアップ用のcloseハンドラ
15
+ */
16
+ export async function createSocketServer(options) {
17
+ const { socketPath, onRequest, onError } = options;
18
+ // 既存のソケットファイルが残っている場合は削除
19
+ try {
20
+ await fs.unlink(socketPath);
21
+ }
22
+ catch (err) {
23
+ // ファイルが存在しない場合は無視
24
+ if (err.code !== "ENOENT") {
25
+ throw err;
26
+ }
27
+ }
28
+ const server = net.createServer((socket) => {
29
+ handleClientConnection(socket, onRequest, onError);
30
+ });
31
+ // Unixソケットをリッスン
32
+ await new Promise((resolve, reject) => {
33
+ server.listen(socketPath, () => {
34
+ resolve();
35
+ });
36
+ server.on("error", reject);
37
+ });
38
+ // ソケットファイルのパーミッションを0600に設定(所有者のみアクセス可能)
39
+ await fs.chmod(socketPath, 0o600);
40
+ console.error(`[Daemon] Listening on socket: ${socketPath}`);
41
+ // クリーンアップハンドラを返す
42
+ return async () => {
43
+ return new Promise((resolve) => {
44
+ server.close(async () => {
45
+ try {
46
+ await fs.unlink(socketPath);
47
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
48
+ }
49
+ catch (_err) {
50
+ // 削除エラーは無視(既に削除されている可能性)
51
+ }
52
+ resolve();
53
+ });
54
+ });
55
+ };
56
+ }
57
+ /**
58
+ * クライアント接続を処理する
59
+ *
60
+ * 各接続に対して:
61
+ * 1. 改行区切りのJSON-RPCメッセージを読み取る
62
+ * 2. onRequestハンドラを呼び出す
63
+ * 3. レスポンスを改行区切りで返す
64
+ */
65
+ function handleClientConnection(socket, onRequest, onError) {
66
+ const rl = readline.createInterface({
67
+ input: socket,
68
+ crlfDelay: Infinity,
69
+ });
70
+ rl.on("line", async (line) => {
71
+ let request = null;
72
+ try {
73
+ request = JSON.parse(line);
74
+ }
75
+ catch (err) {
76
+ const error = err;
77
+ if (onError) {
78
+ onError(error);
79
+ }
80
+ const errorResponse = {
81
+ jsonrpc: "2.0",
82
+ id: null,
83
+ error: {
84
+ code: -32700,
85
+ message: "Parse error: Invalid JSON received.",
86
+ },
87
+ };
88
+ socket.write(JSON.stringify(errorResponse) + "\n");
89
+ return;
90
+ }
91
+ try {
92
+ const result = await onRequest(request);
93
+ if (result) {
94
+ const hasResponseId = typeof request.id === "string" || typeof request.id === "number";
95
+ if (!hasResponseId) {
96
+ return;
97
+ }
98
+ // Extract response from RpcHandleResult (statusCode is only for HTTP)
99
+ socket.write(JSON.stringify(result.response) + "\n");
100
+ }
101
+ }
102
+ catch (err) {
103
+ const error = err;
104
+ if (onError) {
105
+ onError(error);
106
+ }
107
+ const hasResponseId = request && (typeof request.id === "string" || typeof request.id === "number");
108
+ if (!hasResponseId) {
109
+ return;
110
+ }
111
+ // エラーレスポンスを送信
112
+ const errorResponse = {
113
+ jsonrpc: "2.0",
114
+ id: request.id,
115
+ error: {
116
+ code: -32603,
117
+ message: `Internal error: ${error.message}`,
118
+ },
119
+ };
120
+ socket.write(JSON.stringify(errorResponse) + "\n");
121
+ }
122
+ });
123
+ socket.on("error", (err) => {
124
+ if (onError) {
125
+ onError(err);
126
+ }
127
+ });
128
+ socket.on("end", () => {
129
+ rl.close();
130
+ });
131
+ }
132
+ //# sourceMappingURL=socket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket.js","sourceRoot":"","sources":["../../../src/daemon/socket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface RetrievalEvent {
2
+ id: string;
3
+ timestampMs: number;
4
+ }
5
+ export declare function precisionAtK(retrievedIds: string[], relevantIds: Iterable<string>, k: number): number;
6
+ export interface LatencyEvent {
7
+ timestampMs: number;
8
+ relevant: boolean;
9
+ }
10
+ export declare function timeToFirstUseful(events: LatencyEvent[], options?: {
11
+ startTimestampMs?: number;
12
+ }): number;
13
+ export interface EvaluateRetrievalOptions {
14
+ items: RetrievalEvent[];
15
+ relevant: Iterable<string>;
16
+ k: number;
17
+ }
18
+ export interface RetrievalMetrics {
19
+ precisionAtK: number;
20
+ timeToFirstUseful: number;
21
+ }
22
+ export declare function evaluateRetrieval(options: EvaluateRetrievalOptions): RetrievalMetrics;
23
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../src/eval/metrics.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,EAAE,EACtB,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC7B,CAAC,EAAE,MAAM,GACR,MAAM,CAiBR;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1C,MAAM,CAgBR;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,gBAAgB,CAWrF"}
@@ -0,0 +1,47 @@
1
+ export function precisionAtK(retrievedIds, relevantIds, k) {
2
+ if (k <= 0 || retrievedIds.length === 0) {
3
+ return 0;
4
+ }
5
+ const relevantSet = new Set(relevantIds);
6
+ if (relevantSet.size === 0) {
7
+ return 0;
8
+ }
9
+ const limit = Math.min(k, retrievedIds.length);
10
+ let hits = 0;
11
+ for (let index = 0; index < limit; index += 1) {
12
+ const id = retrievedIds[index];
13
+ if (id !== undefined && relevantSet.has(id)) {
14
+ hits += 1;
15
+ }
16
+ }
17
+ return hits / limit;
18
+ }
19
+ export function timeToFirstUseful(events, options = {}) {
20
+ if (events.length === 0) {
21
+ return Number.POSITIVE_INFINITY;
22
+ }
23
+ const sorted = [...events].sort((a, b) => a.timestampMs - b.timestampMs);
24
+ const baseline = typeof options.startTimestampMs === "number"
25
+ ? options.startTimestampMs
26
+ : (sorted[0]?.timestampMs ?? 0);
27
+ for (const event of sorted) {
28
+ if (event.relevant) {
29
+ const deltaMs = event.timestampMs - baseline;
30
+ return Math.max(0, deltaMs) / 1000;
31
+ }
32
+ }
33
+ return Number.POSITIVE_INFINITY;
34
+ }
35
+ export function evaluateRetrieval(options) {
36
+ const { items, relevant, k } = options;
37
+ const ids = items.map((item) => item.id);
38
+ const precision = precisionAtK(ids, relevant, k);
39
+ const relevantSet = new Set(relevant);
40
+ const latencyEvents = items.map((item) => ({
41
+ timestampMs: item.timestampMs,
42
+ relevant: relevantSet.has(item.id),
43
+ }));
44
+ const ttff = timeToFirstUseful(latencyEvents);
45
+ return { precisionAtK: precision, timeToFirstUseful: ttff };
46
+ }
47
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/eval/metrics.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,YAAY,CAC1B,YAAsB,EACtB,WAA6B,EAC7B,CAAS;IAET,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC9C,MAAM,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,EAAE,KAAK,SAAS,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC5C,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,GAAG,KAAK,CAAC;AACtB,CAAC;AAOD,MAAM,UAAU,iBAAiB,CAC/B,MAAsB,EACtB,UAAyC,EAAE;IAE3C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,iBAAiB,CAAC;IAClC,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;IACzE,MAAM,QAAQ,GACZ,OAAO,OAAO,CAAC,gBAAgB,KAAK,QAAQ;QAC1C,CAAC,CAAC,OAAO,CAAC,gBAAgB;QAC1B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,IAAI,CAAC,CAAC,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,GAAG,QAAQ,CAAC;YAC7C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;QACrC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,iBAAiB,CAAC;AAClC,CAAC;AAaD,MAAM,UAAU,iBAAiB,CAAC,OAAiC;IACjE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC;IACvC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,aAAa,GAAmB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzD,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;KACnC,CAAC,CAAC,CAAC;IACJ,MAAM,IAAI,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;IAC9C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,11 @@
1
+ export { DuckDBClient, type DuckDBClientOptions } from "./shared/duckdb.js";
2
+ export { startDaemon, isDaemonRunning, type StartDaemonOptions } from "./client/start-daemon.js";
3
+ export { buildContextBundleRequest } from "./client/index.js";
4
+ export { bootstrapServer, type BootstrapOptions } from "./server/bootstrap.js";
5
+ export { createServerRuntime, type CommonServerOptions, type ServerRuntime, } from "./server/runtime.js";
6
+ export { startServer, type ServerOptions } from "./server/main.js";
7
+ export { ensureDatabaseIndexed } from "./server/indexBootstrap.js";
8
+ export { IndexWatcher, type IndexWatcherOptions } from "./indexer/watch.js";
9
+ export { runIndexer } from "./indexer/cli.js";
10
+ export { DaemonLifecycle } from "./daemon/lifecycle.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACjG,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EACL,mBAAmB,EACnB,KAAK,mBAAmB,EACxB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,11 @@
1
+ export { DuckDBClient } from "./shared/duckdb.js";
2
+ export { startDaemon, isDaemonRunning } from "./client/start-daemon.js";
3
+ export { buildContextBundleRequest } from "./client/index.js";
4
+ export { bootstrapServer } from "./server/bootstrap.js";
5
+ export { createServerRuntime, } from "./server/runtime.js";
6
+ export { startServer } from "./server/main.js";
7
+ export { ensureDatabaseIndexed } from "./server/indexBootstrap.js";
8
+ export { IndexWatcher } from "./indexer/watch.js";
9
+ export { runIndexer } from "./indexer/cli.js";
10
+ export { DaemonLifecycle } from "./daemon/lifecycle.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA4B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,eAAe,EAA2B,MAAM,0BAA0B,CAAC;AACjG,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAyB,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EACL,mBAAmB,GAGpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,YAAY,EAA4B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,9 @@
1
+ interface IndexerOptions {
2
+ repoRoot: string;
3
+ databasePath: string;
4
+ full: boolean;
5
+ since?: string;
6
+ }
7
+ export declare function runIndexer(options: IndexerOptions): Promise<void>;
8
+ export {};
9
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/indexer/cli.ts"],"names":[],"mappings":"AAcA,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA6dD,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDvE"}
@@ -0,0 +1,402 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { join, resolve, extname } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { DuckDBClient } from "../shared/duckdb.js";
6
+ import { generateEmbedding } from "../shared/embedding.js";
7
+ import { analyzeSource, buildFallbackSnippet } from "./codeintel.js";
8
+ import { getDefaultBranch, getHeadCommit, gitLsFiles } from "./git.js";
9
+ import { detectLanguage } from "./language.js";
10
+ import { ensureBaseSchema } from "./schema.js";
11
+ import { IndexWatcher } from "./watch.js";
12
+ const MAX_SAMPLE_BYTES = 32_768;
13
+ const MAX_FILE_BYTES = 32 * 1024 * 1024; // 32MB limit to prevent memory exhaustion
14
+ const SCAN_BATCH_SIZE = 100; // Process files in batches to limit memory usage
15
+ function countLines(content) {
16
+ if (content.length === 0) {
17
+ return 0;
18
+ }
19
+ return content.split(/\r?\n/).length;
20
+ }
21
+ function isBinaryBuffer(buffer) {
22
+ const sample = buffer.subarray(0, Math.min(buffer.length, MAX_SAMPLE_BYTES));
23
+ if (sample.includes(0)) {
24
+ return true;
25
+ }
26
+ const decoded = sample.toString("utf8");
27
+ return decoded.includes("\uFFFD");
28
+ }
29
+ /**
30
+ * Ensures a repository record exists in the database, creating it if necessary.
31
+ * Uses ON CONFLICT with auto-increment to prevent race conditions in concurrent scenarios.
32
+ *
33
+ * @param db - Database client instance
34
+ * @param repoRoot - Absolute path to the repository root
35
+ * @param defaultBranch - Default branch name (e.g., "main", "master"), or null if unknown
36
+ * @returns The repository ID (auto-generated on first insert, reused thereafter)
37
+ */
38
+ async function ensureRepo(db, repoRoot, defaultBranch) {
39
+ // Atomically insert or update using ON CONFLICT to leverage auto-increment
40
+ // This eliminates the TOCTOU race condition present in manual ID generation
41
+ await db.run(`INSERT INTO repo (root, default_branch, indexed_at)
42
+ VALUES (?, ?, CURRENT_TIMESTAMP)
43
+ ON CONFLICT(root) DO UPDATE SET
44
+ default_branch = COALESCE(excluded.default_branch, repo.default_branch)`, [repoRoot, defaultBranch]);
45
+ // Fetch the ID of the existing or newly created repo
46
+ const rows = await db.all("SELECT id FROM repo WHERE root = ?", [repoRoot]);
47
+ if (rows.length === 0) {
48
+ throw new Error("Failed to create or find repository record. Check database constraints and schema.");
49
+ }
50
+ const row = rows[0];
51
+ if (!row) {
52
+ throw new Error("Failed to retrieve repository record. Database returned empty result.");
53
+ }
54
+ return row.id;
55
+ }
56
+ async function persistBlobs(db, blobs) {
57
+ if (blobs.size === 0)
58
+ return;
59
+ // Use bulk insert for better performance
60
+ const blobArray = Array.from(blobs.values());
61
+ const placeholders = blobArray.map(() => "(?, ?, ?, ?)").join(", ");
62
+ const sql = `INSERT OR REPLACE INTO blob (hash, size_bytes, line_count, content) VALUES ${placeholders}`;
63
+ const params = [];
64
+ for (const blob of blobArray) {
65
+ params.push(blob.hash, blob.sizeBytes, blob.lineCount, blob.content);
66
+ }
67
+ await db.run(sql, params);
68
+ }
69
+ async function persistTrees(db, repoId, commitHash, records) {
70
+ if (records.length === 0)
71
+ return;
72
+ // Use bulk insert for better performance
73
+ const placeholders = records.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
74
+ const sql = `INSERT OR REPLACE INTO tree (repo_id, commit_hash, path, blob_hash, ext, lang, is_binary, mtime) VALUES ${placeholders}`;
75
+ const params = [];
76
+ for (const record of records) {
77
+ params.push(repoId, commitHash, record.path, record.blobHash, record.ext, record.lang, record.isBinary, record.mtimeIso);
78
+ }
79
+ await db.run(sql, params);
80
+ }
81
+ async function persistFiles(db, repoId, records) {
82
+ if (records.length === 0)
83
+ return;
84
+ // Use bulk insert for better performance
85
+ const placeholders = records.map(() => "(?, ?, ?, ?, ?, ?, ?)").join(", ");
86
+ const sql = `INSERT OR REPLACE INTO file (repo_id, path, blob_hash, ext, lang, is_binary, mtime) VALUES ${placeholders}`;
87
+ const params = [];
88
+ for (const record of records) {
89
+ params.push(repoId, record.path, record.blobHash, record.ext, record.lang, record.isBinary, record.mtimeIso);
90
+ }
91
+ await db.run(sql, params);
92
+ }
93
+ async function persistSymbols(db, repoId, records) {
94
+ if (records.length === 0)
95
+ return;
96
+ // バッチサイズを1000に制限してスタックオーバーフローを防ぐ
97
+ const BATCH_SIZE = 1000;
98
+ for (let i = 0; i < records.length; i += BATCH_SIZE) {
99
+ const batch = records.slice(i, i + BATCH_SIZE);
100
+ const placeholders = batch.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
101
+ const sql = `
102
+ INSERT OR REPLACE INTO symbol (
103
+ repo_id, path, symbol_id, name, kind, range_start_line, range_end_line, signature, doc
104
+ ) VALUES ${placeholders}
105
+ `;
106
+ const params = [];
107
+ for (const record of batch) {
108
+ params.push(repoId, record.path, record.symbolId, record.name, record.kind, record.rangeStartLine, record.rangeEndLine, record.signature, record.doc);
109
+ }
110
+ await db.run(sql, params);
111
+ }
112
+ }
113
+ async function persistSnippets(db, repoId, records) {
114
+ if (records.length === 0)
115
+ return;
116
+ // バッチサイズを1000に制限してスタックオーバーフローを防ぐ
117
+ const BATCH_SIZE = 1000;
118
+ for (let i = 0; i < records.length; i += BATCH_SIZE) {
119
+ const batch = records.slice(i, i + BATCH_SIZE);
120
+ const placeholders = batch.map(() => "(?, ?, ?, ?, ?, ?)").join(", ");
121
+ const sql = `
122
+ INSERT OR REPLACE INTO snippet (
123
+ repo_id, path, snippet_id, start_line, end_line, symbol_id
124
+ ) VALUES ${placeholders}
125
+ `;
126
+ const params = [];
127
+ for (const record of batch) {
128
+ params.push(repoId, record.path, record.snippetId, record.startLine, record.endLine, record.symbolId);
129
+ }
130
+ await db.run(sql, params);
131
+ }
132
+ }
133
+ async function persistDependencies(db, repoId, records) {
134
+ if (records.length === 0)
135
+ return;
136
+ // バッチサイズを1000に制限してスタックオーバーフローを防ぐ
137
+ const BATCH_SIZE = 1000;
138
+ for (let i = 0; i < records.length; i += BATCH_SIZE) {
139
+ const batch = records.slice(i, i + BATCH_SIZE);
140
+ const placeholders = batch.map(() => "(?, ?, ?, ?, ?)").join(", ");
141
+ const sql = `
142
+ INSERT OR REPLACE INTO dependency (
143
+ repo_id, src_path, dst_kind, dst, rel
144
+ ) VALUES ${placeholders}
145
+ `;
146
+ const params = [];
147
+ for (const record of batch) {
148
+ params.push(repoId, record.srcPath, record.dstKind, record.dst, record.rel);
149
+ }
150
+ await db.run(sql, params);
151
+ }
152
+ }
153
+ async function persistEmbeddings(db, repoId, records) {
154
+ if (records.length === 0)
155
+ return;
156
+ const placeholders = records.map(() => "(?, ?, ?, ?, CURRENT_TIMESTAMP)").join(", ");
157
+ const sql = `
158
+ INSERT OR REPLACE INTO file_embedding (
159
+ repo_id, path, dims, vector_json, updated_at
160
+ ) VALUES ${placeholders}
161
+ `;
162
+ const params = [];
163
+ for (const record of records) {
164
+ params.push(repoId, record.path, record.dims, JSON.stringify(record.vector));
165
+ }
166
+ await db.run(sql, params);
167
+ }
168
+ function buildCodeIntel(files, blobs) {
169
+ const fileSet = new Set(files.map((file) => file.path));
170
+ const symbols = [];
171
+ const snippets = [];
172
+ const dependencies = new Map();
173
+ for (const file of files) {
174
+ if (file.isBinary) {
175
+ continue;
176
+ }
177
+ const blob = blobs.get(file.blobHash);
178
+ if (!blob || blob.content === null) {
179
+ continue;
180
+ }
181
+ const analysis = analyzeSource(file.path, file.lang, blob.content, fileSet);
182
+ for (const symbol of analysis.symbols) {
183
+ symbols.push({
184
+ path: file.path,
185
+ symbolId: symbol.symbolId,
186
+ name: symbol.name,
187
+ kind: symbol.kind,
188
+ rangeStartLine: symbol.rangeStartLine,
189
+ rangeEndLine: symbol.rangeEndLine,
190
+ signature: symbol.signature,
191
+ doc: symbol.doc,
192
+ });
193
+ }
194
+ if (analysis.snippets.length > 0) {
195
+ analysis.snippets.forEach((snippet, index) => {
196
+ snippets.push({
197
+ path: file.path,
198
+ snippetId: index + 1,
199
+ startLine: snippet.startLine,
200
+ endLine: snippet.endLine,
201
+ symbolId: snippet.symbolId,
202
+ });
203
+ });
204
+ }
205
+ else if (blob.lineCount !== null) {
206
+ const fallback = buildFallbackSnippet(blob.lineCount);
207
+ snippets.push({
208
+ path: file.path,
209
+ snippetId: 1,
210
+ startLine: fallback.startLine,
211
+ endLine: fallback.endLine,
212
+ symbolId: fallback.symbolId,
213
+ });
214
+ }
215
+ for (const dependency of analysis.dependencies) {
216
+ const key = `${file.path}::${dependency.dstKind}::${dependency.dst}::${dependency.rel}`;
217
+ if (!dependencies.has(key)) {
218
+ dependencies.set(key, {
219
+ srcPath: file.path,
220
+ dstKind: dependency.dstKind,
221
+ dst: dependency.dst,
222
+ rel: dependency.rel,
223
+ });
224
+ }
225
+ }
226
+ }
227
+ return { symbols, snippets, dependencies: Array.from(dependencies.values()) };
228
+ }
229
+ /**
230
+ * scanFilesのバッチ処理版
231
+ * メモリ枯渇を防ぐため、ファイルをバッチで処理する
232
+ */
233
+ async function scanFilesInBatches(repoRoot, paths) {
234
+ const allBlobs = new Map();
235
+ const allFiles = [];
236
+ const allEmbeddings = [];
237
+ for (let i = 0; i < paths.length; i += SCAN_BATCH_SIZE) {
238
+ const batch = paths.slice(i, i + SCAN_BATCH_SIZE);
239
+ const { blobs, files, embeddings } = await scanFiles(repoRoot, batch);
240
+ // マージ: blobはhashでユニークなので重複排除
241
+ for (const [hash, blob] of blobs) {
242
+ if (!allBlobs.has(hash)) {
243
+ allBlobs.set(hash, blob);
244
+ }
245
+ }
246
+ allFiles.push(...files);
247
+ allEmbeddings.push(...embeddings);
248
+ // バッチデータを明示的にクリアしてGCを促す
249
+ blobs.clear();
250
+ }
251
+ return { blobs: allBlobs, files: allFiles, embeddings: allEmbeddings };
252
+ }
253
+ async function scanFiles(repoRoot, paths) {
254
+ const blobs = new Map();
255
+ const files = [];
256
+ const embeddings = [];
257
+ for (const relativePath of paths) {
258
+ const absolutePath = join(repoRoot, relativePath);
259
+ try {
260
+ const fileStat = await stat(absolutePath);
261
+ if (!fileStat.isFile()) {
262
+ continue;
263
+ }
264
+ // Check file size before reading to prevent memory exhaustion
265
+ if (fileStat.size > MAX_FILE_BYTES) {
266
+ console.warn(`File ${relativePath} exceeds size limit (${fileStat.size} bytes). Increase MAX_FILE_BYTES constant to include it.`);
267
+ continue;
268
+ }
269
+ const buffer = await readFile(absolutePath);
270
+ const isBinary = isBinaryBuffer(buffer);
271
+ const hash = createHash("sha1").update(buffer).digest("hex");
272
+ const ext = extname(relativePath) || null;
273
+ const lang = ext ? detectLanguage(ext) : null;
274
+ const mtimeIso = fileStat.mtime.toISOString();
275
+ let content = null;
276
+ let lineCount = null;
277
+ if (!isBinary) {
278
+ content = buffer.toString("utf8");
279
+ lineCount = countLines(content);
280
+ }
281
+ if (!blobs.has(hash)) {
282
+ blobs.set(hash, {
283
+ hash,
284
+ sizeBytes: buffer.length,
285
+ lineCount,
286
+ content,
287
+ });
288
+ }
289
+ files.push({
290
+ path: relativePath,
291
+ blobHash: hash,
292
+ ext,
293
+ lang,
294
+ isBinary,
295
+ mtimeIso,
296
+ });
297
+ if (!isBinary && content) {
298
+ const embedding = generateEmbedding(content);
299
+ if (embedding) {
300
+ embeddings.push({ path: relativePath, dims: embedding.dims, vector: embedding.values });
301
+ }
302
+ }
303
+ }
304
+ catch (error) {
305
+ console.warn(`Cannot read ${relativePath} due to filesystem error. Fix file permissions or remove the file.`);
306
+ console.warn(error);
307
+ }
308
+ }
309
+ return { blobs, files, embeddings };
310
+ }
311
+ export async function runIndexer(options) {
312
+ if (!options.full && options.since) {
313
+ console.warn("Incremental indexing is not yet supported. Falling back to full reindex.");
314
+ }
315
+ const repoRoot = resolve(options.repoRoot);
316
+ const databasePath = resolve(options.databasePath);
317
+ const [paths, headCommit, defaultBranch] = await Promise.all([
318
+ gitLsFiles(repoRoot),
319
+ getHeadCommit(repoRoot),
320
+ getDefaultBranch(repoRoot),
321
+ ]);
322
+ const { blobs, files, embeddings } = await scanFilesInBatches(repoRoot, paths);
323
+ const codeIntel = buildCodeIntel(files, blobs);
324
+ const db = await DuckDBClient.connect({ databasePath, ensureDirectory: true });
325
+ try {
326
+ await ensureBaseSchema(db);
327
+ const repoId = await ensureRepo(db, repoRoot, defaultBranch);
328
+ await db.transaction(async () => {
329
+ await db.run("DELETE FROM tree WHERE repo_id = ?", [repoId]);
330
+ await db.run("DELETE FROM file WHERE repo_id = ?", [repoId]);
331
+ await db.run("DELETE FROM symbol WHERE repo_id = ?", [repoId]);
332
+ await db.run("DELETE FROM snippet WHERE repo_id = ?", [repoId]);
333
+ await db.run("DELETE FROM dependency WHERE repo_id = ?", [repoId]);
334
+ await db.run("DELETE FROM file_embedding WHERE repo_id = ?", [repoId]);
335
+ await persistBlobs(db, blobs);
336
+ await persistTrees(db, repoId, headCommit, files);
337
+ await persistFiles(db, repoId, files);
338
+ await persistSymbols(db, repoId, codeIntel.symbols);
339
+ await persistSnippets(db, repoId, codeIntel.snippets);
340
+ await persistDependencies(db, repoId, codeIntel.dependencies);
341
+ await persistEmbeddings(db, repoId, embeddings);
342
+ // Update timestamp inside transaction to ensure atomicity
343
+ if (defaultBranch) {
344
+ await db.run("UPDATE repo SET indexed_at = CURRENT_TIMESTAMP, default_branch = ? WHERE id = ?", [defaultBranch, repoId]);
345
+ }
346
+ else {
347
+ await db.run("UPDATE repo SET indexed_at = CURRENT_TIMESTAMP WHERE id = ?", [repoId]);
348
+ }
349
+ });
350
+ }
351
+ finally {
352
+ await db.close();
353
+ }
354
+ console.info(`Indexed ${files.length} files for repo ${repoRoot} at ${databasePath} (commit ${headCommit.slice(0, 12)})`);
355
+ }
356
+ function parseArg(flag) {
357
+ const index = process.argv.indexOf(flag);
358
+ if (index >= 0) {
359
+ return process.argv[index + 1];
360
+ }
361
+ return undefined;
362
+ }
363
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
364
+ const repoRoot = resolve(parseArg("--repo") ?? ".");
365
+ const databasePath = resolve(parseArg("--db") ?? "var/index.duckdb");
366
+ const full = process.argv.includes("--full");
367
+ const since = parseArg("--since");
368
+ const watch = process.argv.includes("--watch");
369
+ const debounceMs = parseInt(parseArg("--debounce") ?? "500", 10);
370
+ const options = { repoRoot, databasePath, full: full || !since };
371
+ if (since) {
372
+ options.since = since;
373
+ }
374
+ // Run initial indexing
375
+ runIndexer(options)
376
+ .then(async () => {
377
+ if (watch) {
378
+ // Start watch mode after initial indexing completes
379
+ const abortController = new AbortController();
380
+ const watcher = new IndexWatcher({
381
+ repoRoot,
382
+ databasePath,
383
+ debounceMs,
384
+ signal: abortController.signal,
385
+ });
386
+ // Handle graceful shutdown on SIGINT/SIGTERM
387
+ const shutdownHandler = () => {
388
+ process.stderr.write("\n🛑 Received shutdown signal. Stopping watch mode...\n");
389
+ abortController.abort();
390
+ };
391
+ process.on("SIGINT", shutdownHandler);
392
+ process.on("SIGTERM", shutdownHandler);
393
+ await watcher.start();
394
+ }
395
+ })
396
+ .catch((error) => {
397
+ console.error("Failed to index repository. Retry after resolving the logged error.");
398
+ console.error(error);
399
+ process.exitCode = 1;
400
+ });
401
+ }
402
+ //# sourceMappingURL=cli.js.map