ragcode-context-engine 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/LICENSE +21 -0
  2. package/README.md +366 -0
  3. package/README.zh-CN.md +363 -0
  4. package/dist/src/cli/configure/app.d.ts +6 -0
  5. package/dist/src/cli/configure/app.js +81 -0
  6. package/dist/src/cli/configure/run.d.ts +5 -0
  7. package/dist/src/cli/configure/run.js +85 -0
  8. package/dist/src/cli/configure/state.d.ts +42 -0
  9. package/dist/src/cli/configure/state.js +174 -0
  10. package/dist/src/cli/configure.d.ts +31 -0
  11. package/dist/src/cli/configure.js +101 -0
  12. package/dist/src/cli/index.d.ts +2 -0
  13. package/dist/src/cli/index.js +503 -0
  14. package/dist/src/cli/tui/index-progress.d.ts +12 -0
  15. package/dist/src/cli/tui/index-progress.js +49 -0
  16. package/dist/src/cli/tui/watch-status.d.ts +10 -0
  17. package/dist/src/cli/tui/watch-status.js +27 -0
  18. package/dist/src/cli/update.d.ts +18 -0
  19. package/dist/src/cli/update.js +111 -0
  20. package/dist/src/config/dotenv.d.ts +1 -0
  21. package/dist/src/config/dotenv.js +14 -0
  22. package/dist/src/config/graph-runtime.d.ts +13 -0
  23. package/dist/src/config/graph-runtime.js +29 -0
  24. package/dist/src/config/runtime-config.d.ts +87 -0
  25. package/dist/src/config/runtime-config.js +215 -0
  26. package/dist/src/config/semantic-runtime.d.ts +24 -0
  27. package/dist/src/config/semantic-runtime.js +89 -0
  28. package/dist/src/context/context-builder.d.ts +20 -0
  29. package/dist/src/context/context-builder.js +277 -0
  30. package/dist/src/context/expansion-policy.d.ts +6 -0
  31. package/dist/src/context/expansion-policy.js +49 -0
  32. package/dist/src/context/skeletonizer.d.ts +2 -0
  33. package/dist/src/context/skeletonizer.js +79 -0
  34. package/dist/src/context/snippet-renderer.d.ts +2 -0
  35. package/dist/src/context/snippet-renderer.js +67 -0
  36. package/dist/src/core/contracts.d.ts +74 -0
  37. package/dist/src/core/contracts.js +1 -0
  38. package/dist/src/core/engine.d.ts +64 -0
  39. package/dist/src/core/engine.js +442 -0
  40. package/dist/src/core/types.d.ts +490 -0
  41. package/dist/src/core/types.js +1 -0
  42. package/dist/src/diagnostics/doctor.d.ts +66 -0
  43. package/dist/src/diagnostics/doctor.js +193 -0
  44. package/dist/src/diagnostics/embedding-test.d.ts +24 -0
  45. package/dist/src/diagnostics/embedding-test.js +83 -0
  46. package/dist/src/graph/diff-files.d.ts +1 -0
  47. package/dist/src/graph/diff-files.js +14 -0
  48. package/dist/src/graph/impact-report.d.ts +10 -0
  49. package/dist/src/graph/impact-report.js +173 -0
  50. package/dist/src/graph/in-memory-graph-store.d.ts +36 -0
  51. package/dist/src/graph/in-memory-graph-store.js +395 -0
  52. package/dist/src/graph/owner-ranking.d.ts +2 -0
  53. package/dist/src/graph/owner-ranking.js +41 -0
  54. package/dist/src/graph/sqlite-graph-store.d.ts +51 -0
  55. package/dist/src/graph/sqlite-graph-store.js +724 -0
  56. package/dist/src/graph/sqlite-statements.d.ts +36 -0
  57. package/dist/src/graph/sqlite-statements.js +105 -0
  58. package/dist/src/graph/target-matcher.d.ts +13 -0
  59. package/dist/src/graph/target-matcher.js +64 -0
  60. package/dist/src/index.d.ts +32 -0
  61. package/dist/src/index.js +32 -0
  62. package/dist/src/indexing/analyzers/fallback-analyzer.d.ts +6 -0
  63. package/dist/src/indexing/analyzers/fallback-analyzer.js +45 -0
  64. package/dist/src/indexing/analyzers/go-treesitter-analyzer.d.ts +2 -0
  65. package/dist/src/indexing/analyzers/go-treesitter-analyzer.js +87 -0
  66. package/dist/src/indexing/analyzers/java-treesitter-analyzer.d.ts +2 -0
  67. package/dist/src/indexing/analyzers/java-treesitter-analyzer.js +88 -0
  68. package/dist/src/indexing/analyzers/python-treesitter-analyzer.d.ts +2 -0
  69. package/dist/src/indexing/analyzers/python-treesitter-analyzer.js +96 -0
  70. package/dist/src/indexing/analyzers/registry.d.ts +5 -0
  71. package/dist/src/indexing/analyzers/registry.js +23 -0
  72. package/dist/src/indexing/analyzers/rust-treesitter-analyzer.d.ts +2 -0
  73. package/dist/src/indexing/analyzers/rust-treesitter-analyzer.js +96 -0
  74. package/dist/src/indexing/analyzers/tree-sitter-base.d.ts +30 -0
  75. package/dist/src/indexing/analyzers/tree-sitter-base.js +163 -0
  76. package/dist/src/indexing/analyzers/types.d.ts +17 -0
  77. package/dist/src/indexing/analyzers/types.js +1 -0
  78. package/dist/src/indexing/analyzers/typescript-analyzer.d.ts +5 -0
  79. package/dist/src/indexing/analyzers/typescript-analyzer.js +199 -0
  80. package/dist/src/indexing/ast-analyzer.d.ts +11 -0
  81. package/dist/src/indexing/ast-analyzer.js +11 -0
  82. package/dist/src/indexing/chunker.d.ts +11 -0
  83. package/dist/src/indexing/chunker.js +157 -0
  84. package/dist/src/indexing/ignore-policy.d.ts +6 -0
  85. package/dist/src/indexing/ignore-policy.js +40 -0
  86. package/dist/src/indexing/indexer.d.ts +13 -0
  87. package/dist/src/indexing/indexer.js +189 -0
  88. package/dist/src/indexing/language.d.ts +3 -0
  89. package/dist/src/indexing/language.js +24 -0
  90. package/dist/src/indexing/scanner.d.ts +13 -0
  91. package/dist/src/indexing/scanner.js +87 -0
  92. package/dist/src/lsp/definition-resolver.d.ts +6 -0
  93. package/dist/src/lsp/definition-resolver.js +60 -0
  94. package/dist/src/lsp/typescript-language-service.d.ts +21 -0
  95. package/dist/src/lsp/typescript-language-service.js +82 -0
  96. package/dist/src/mcp/server.d.ts +11 -0
  97. package/dist/src/mcp/server.js +64 -0
  98. package/dist/src/mcp/tools.d.ts +266 -0
  99. package/dist/src/mcp/tools.js +309 -0
  100. package/dist/src/project/project-identity.d.ts +2 -0
  101. package/dist/src/project/project-identity.js +24 -0
  102. package/dist/src/project/project-registry.d.ts +12 -0
  103. package/dist/src/project/project-registry.js +49 -0
  104. package/dist/src/project/workspace-resolver.d.ts +20 -0
  105. package/dist/src/project/workspace-resolver.js +62 -0
  106. package/dist/src/retrieval/graph-reranker.d.ts +11 -0
  107. package/dist/src/retrieval/graph-reranker.js +0 -0
  108. package/dist/src/retrieval/hybrid-retriever.d.ts +31 -0
  109. package/dist/src/retrieval/hybrid-retriever.js +111 -0
  110. package/dist/src/retrieval/path-classification.d.ts +6 -0
  111. package/dist/src/retrieval/path-classification.js +22 -0
  112. package/dist/src/retrieval/query-matching.d.ts +22 -0
  113. package/dist/src/retrieval/query-matching.js +166 -0
  114. package/dist/src/retrieval/query-planner.d.ts +5 -0
  115. package/dist/src/retrieval/query-planner.js +77 -0
  116. package/dist/src/retrieval/ranking-signals.d.ts +19 -0
  117. package/dist/src/retrieval/ranking-signals.js +97 -0
  118. package/dist/src/retrieval/topology-distance.d.ts +21 -0
  119. package/dist/src/retrieval/topology-distance.js +116 -0
  120. package/dist/src/reuse/reuse-detector.d.ts +12 -0
  121. package/dist/src/reuse/reuse-detector.js +564 -0
  122. package/dist/src/semantic/deterministic-embedding.d.ts +7 -0
  123. package/dist/src/semantic/deterministic-embedding.js +31 -0
  124. package/dist/src/semantic/in-memory-semantic-store.d.ts +11 -0
  125. package/dist/src/semantic/in-memory-semantic-store.js +65 -0
  126. package/dist/src/semantic/lance-semantic-store.d.ts +131 -0
  127. package/dist/src/semantic/lance-semantic-store.js +623 -0
  128. package/dist/src/semantic/openai-compatible-embedding.d.ts +19 -0
  129. package/dist/src/semantic/openai-compatible-embedding.js +75 -0
  130. package/dist/src/service/service-identity.d.ts +13 -0
  131. package/dist/src/service/service-identity.js +48 -0
  132. package/dist/src/service/service-manager.d.ts +29 -0
  133. package/dist/src/service/service-manager.js +231 -0
  134. package/dist/src/service/service-templates.d.ts +22 -0
  135. package/dist/src/service/service-templates.js +101 -0
  136. package/dist/src/subgraph/impact-explainer.d.ts +2 -0
  137. package/dist/src/subgraph/impact-explainer.js +54 -0
  138. package/dist/src/subgraph/node-expander.d.ts +13 -0
  139. package/dist/src/subgraph/node-expander.js +139 -0
  140. package/dist/src/subgraph/output-preset.d.ts +3 -0
  141. package/dist/src/subgraph/output-preset.js +102 -0
  142. package/dist/src/subgraph/subgraph-builder.d.ts +17 -0
  143. package/dist/src/subgraph/subgraph-builder.js +688 -0
  144. package/dist/src/topology/export-index.d.ts +7 -0
  145. package/dist/src/topology/export-index.js +14 -0
  146. package/dist/src/topology/framework-topology.d.ts +3 -0
  147. package/dist/src/topology/framework-topology.js +460 -0
  148. package/dist/src/topology/import-resolver.d.ts +2 -0
  149. package/dist/src/topology/import-resolver.js +29 -0
  150. package/dist/src/topology/orm-topology.d.ts +3 -0
  151. package/dist/src/topology/orm-topology.js +200 -0
  152. package/dist/src/topology/runtime-topology.d.ts +3 -0
  153. package/dist/src/topology/runtime-topology.js +204 -0
  154. package/dist/src/topology/symbol-resolver.d.ts +6 -0
  155. package/dist/src/topology/symbol-resolver.js +74 -0
  156. package/dist/src/topology/test-topology.d.ts +2 -0
  157. package/dist/src/topology/test-topology.js +82 -0
  158. package/dist/src/utils/hash.d.ts +2 -0
  159. package/dist/src/utils/hash.js +7 -0
  160. package/dist/src/utils/path.d.ts +2 -0
  161. package/dist/src/utils/path.js +7 -0
  162. package/dist/src/watch/event-journal.d.ts +17 -0
  163. package/dist/src/watch/event-journal.js +81 -0
  164. package/dist/src/watch/file-event-coalescer.d.ts +9 -0
  165. package/dist/src/watch/file-event-coalescer.js +39 -0
  166. package/dist/src/watch/index-scheduler.d.ts +52 -0
  167. package/dist/src/watch/index-scheduler.js +190 -0
  168. package/dist/src/watch/watch-daemon.d.ts +73 -0
  169. package/dist/src/watch/watch-daemon.js +368 -0
  170. package/dist/src/watch/watcher-liveness.d.ts +47 -0
  171. package/dist/src/watch/watcher-liveness.js +168 -0
  172. package/dist/src/web/server.d.ts +1 -0
  173. package/dist/src/web/server.js +375 -0
  174. package/package.json +94 -0
@@ -0,0 +1,503 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { Command } from "commander";
4
+ import { loadDotEnv } from "../config/dotenv.js";
5
+ import { createRuntimeComponentsForRepo } from "../config/runtime-config.js";
6
+ import { RagCodeEngine } from "../core/engine.js";
7
+ import { runConfigureCommand } from "./configure.js";
8
+ import { runDoctor } from "../diagnostics/doctor.js";
9
+ import { startStdioMcpServer } from "../mcp/server.js";
10
+ import { buildExplainImpactReport } from "../subgraph/impact-explainer.js";
11
+ import { expandNode, parseNodeRef } from "../subgraph/node-expander.js";
12
+ import { FileWatchDaemon } from "../watch/watch-daemon.js";
13
+ import { WatcherLockError, readWatcherLiveness } from "../watch/watcher-liveness.js";
14
+ import { installWatcherService, uninstallWatcherService, watcherServiceStatus } from "../service/service-manager.js";
15
+ import { runInitConfig } from "../../scripts/init-config.js";
16
+ import { setupMCP } from "../../scripts/setup-mcp.js";
17
+ import { runUpdate } from "./update.js";
18
+ loadDotEnv();
19
+ const program = new Command();
20
+ program.name("ragcode").description("Local code intelligence context engine").version("0.1.0");
21
+ program
22
+ .command("index")
23
+ .argument("<repoRoot>")
24
+ .description("Index a repository")
25
+ .action(async (repoRoot) => {
26
+ if (process.stdout.isTTY) {
27
+ const { runIndexProgressTui } = await import("./tui/index-progress.js");
28
+ await runIndexProgressTui({
29
+ repoRoot,
30
+ run: async (onProgress) => withEngine(repoRoot, (engine) => engine.indexRepo(repoRoot, { onProgress }))
31
+ });
32
+ return;
33
+ }
34
+ await withEngine(repoRoot, async (engine) => {
35
+ const result = await engine.indexRepo(repoRoot);
36
+ console.log(JSON.stringify({ repoRoot: result.repoRoot, files: result.files.length, chunks: result.chunks.length }, null, 2));
37
+ });
38
+ });
39
+ program
40
+ .command("search")
41
+ .argument("<repoRoot>")
42
+ .argument("<query>")
43
+ .option("-l, --limit <number>", "maximum hits", parseNumber)
44
+ .description("Search an already-indexed repository without re-indexing")
45
+ .action(async (repoRoot, query, options) => {
46
+ await withEngine(repoRoot, async (engine) => {
47
+ const hits = await engine.searchCode({ repoRoot, query, limit: options.limit });
48
+ console.log(JSON.stringify(hits.map((hit) => ({ filePath: hit.chunk.filePath, startLine: hit.chunk.startLine, endLine: hit.chunk.endLine, score: hit.score, source: hit.source, reason: hit.reason })), null, 2));
49
+ });
50
+ });
51
+ program
52
+ .command("status")
53
+ .argument("<repoRoot>")
54
+ .description("Report persisted index and dirty watcher state without indexing")
55
+ .action(async (repoRoot) => {
56
+ await withEngine(repoRoot, async (engine) => {
57
+ console.log(JSON.stringify(await engine.indexStatus(repoRoot), null, 2));
58
+ });
59
+ });
60
+ program
61
+ .command("context")
62
+ .argument("<repoRoot>")
63
+ .argument("<query>")
64
+ .option("-m, --mode <mode>", "context mode: auto/debug/feature/refactor/review/explain")
65
+ .option("-b, --budget <chars>", "character budget", parseNumber)
66
+ .description("Build a context pack")
67
+ .action(async (repoRoot, query, options) => {
68
+ await withEngine(repoRoot, async (engine) => {
69
+ const context = await engine.getContext({ repoRoot, query, budgetChars: options.budget, mode: options.mode });
70
+ console.log(JSON.stringify(context, null, 2));
71
+ });
72
+ });
73
+ program
74
+ .command("owner")
75
+ .argument("<repoRoot>")
76
+ .argument("<query>")
77
+ .description("Find likely owner files and symbols")
78
+ .action(async (repoRoot, query) => {
79
+ await withEngine(repoRoot, async (engine) => {
80
+ console.log(JSON.stringify(await engine.findOwner(repoRoot, query), null, 2));
81
+ });
82
+ });
83
+ program
84
+ .command("reuse")
85
+ .argument("<repoRoot>")
86
+ .argument("<query>")
87
+ .option("-l, --limit <number>", "maximum candidates", parseNumber)
88
+ .description("Find reusable existing code before implementing new behavior")
89
+ .action(async (repoRoot, query, options) => {
90
+ await withEngine(repoRoot, async (engine) => {
91
+ console.log(JSON.stringify(await engine.findReuseCandidates({ repoRoot, query, limit: options.limit }), null, 2));
92
+ });
93
+ });
94
+ program
95
+ .command("expand-node")
96
+ .argument("<repoRoot>")
97
+ .argument("<nodeRef>")
98
+ .option("-e, --expansion <level>", "file_card/skeleton/focused_body/full_body")
99
+ .option("-b, --budget <chars>", "character budget", parseNumber)
100
+ .description("Expand one compact subgraph node under budget")
101
+ .action(async (repoRoot, nodeRef, options) => {
102
+ await withEngine(repoRoot, async (engine) => {
103
+ const parsed = parseNodeRef(nodeRef);
104
+ const indexedFile = await engine.explainFile(repoRoot, parsed.filePath);
105
+ console.log(JSON.stringify(expandNode({
106
+ nodeRef,
107
+ chunks: indexedFile.chunks,
108
+ symbols: indexedFile.symbols,
109
+ expansionLevel: options.expansion,
110
+ budgetChars: options.budget
111
+ }), null, 2));
112
+ });
113
+ });
114
+ program
115
+ .command("impact")
116
+ .argument("<repoRoot>")
117
+ .argument("<target>")
118
+ .description("Estimate structural impact for a file or symbol")
119
+ .action(async (repoRoot, target) => {
120
+ await withEngine(repoRoot, async (engine) => {
121
+ console.log(JSON.stringify(await engine.impactAnalysis(repoRoot, target), null, 2));
122
+ });
123
+ });
124
+ program
125
+ .command("explain-impact")
126
+ .argument("<repoRoot>")
127
+ .argument("<target>")
128
+ .option("-b, --budget <chars>", "character budget", parseNumber)
129
+ .option("--max-hops <number>", "maximum graph hops", parseNumber)
130
+ .description("Explain blast radius as a verified minimal code subgraph")
131
+ .action(async (repoRoot, target, options) => {
132
+ await withEngine(repoRoot, async (engine) => {
133
+ const subgraph = await engine.verifiedSubgraph({
134
+ repoRoot,
135
+ query: target,
136
+ seed: target,
137
+ mode: "impact",
138
+ budgetChars: options.budget,
139
+ maxHops: options.maxHops
140
+ });
141
+ console.log(JSON.stringify(buildExplainImpactReport(target, subgraph), null, 2));
142
+ });
143
+ });
144
+ program
145
+ .command("tests")
146
+ .argument("<repoRoot>")
147
+ .argument("<target>")
148
+ .description("Find likely related tests")
149
+ .action(async (repoRoot, target) => {
150
+ await withEngine(repoRoot, async (engine) => {
151
+ console.log(JSON.stringify(await engine.relatedTests(repoRoot, target), null, 2));
152
+ });
153
+ });
154
+ program
155
+ .command("trace-request-flow")
156
+ .argument("<repoRoot>")
157
+ .argument("<entry>")
158
+ .option("-q, --query <query>", "optional flow query text")
159
+ .option("-b, --budget <chars>", "character budget", parseNumber)
160
+ .option("--max-hops <number>", "maximum graph hops", parseNumber)
161
+ .description("Trace request/data flow as a verified ordered code subgraph")
162
+ .action(async (repoRoot, entry, options) => {
163
+ await withEngine(repoRoot, async (engine) => {
164
+ console.log(JSON.stringify(await engine.verifiedSubgraph({
165
+ repoRoot,
166
+ query: options.query ?? entry,
167
+ seed: entry,
168
+ mode: "flow",
169
+ budgetChars: options.budget,
170
+ maxHops: options.maxHops
171
+ }), null, 2));
172
+ });
173
+ });
174
+ program
175
+ .command("record-events")
176
+ .argument("<repoRoot>")
177
+ .argument("<filePaths...>")
178
+ .option("--burst-threshold <number>", "dirty file count that activates burst mode", parseNumber)
179
+ .option("--max-dirty-files <number>", "maximum dirty file paths to retain from one batch", parseNumber)
180
+ .description("Record watcher file events as dirty state without indexing immediately")
181
+ .action(async (repoRoot, filePaths, options) => {
182
+ await withEngine(repoRoot, async (engine) => {
183
+ console.log(JSON.stringify(await engine.recordFileEvents(repoRoot, filePaths, options), null, 2));
184
+ });
185
+ });
186
+ program
187
+ .command("watch")
188
+ .argument("<repoRoot>")
189
+ .option("--batch-delay <ms>", "delay before background indexing after dirty events", parseNumber)
190
+ .option("--quiet <ms>", "minimum quiet period before indexing", parseNumber)
191
+ .option("--flush-events <ms>", "delay before flushing observed file events to dirty state", parseNumber)
192
+ .option("--await-write <ms>", "await-write-finish stability threshold", parseNumber)
193
+ .option("--burst-threshold <number>", "dirty file count that activates burst mode", parseNumber)
194
+ .option("--max-dirty-files <number>", "maximum dirty file paths to retain from one batch", parseNumber)
195
+ .option("--max-batch-files <number>", "maximum dirty file paths to mark indexing in one scheduler batch", parseNumber)
196
+ .option("--poll", "use polling instead of native fs.watch")
197
+ .option("--no-auto-index", "record dirty events but do not run background refresh")
198
+ .option("--no-index-on-start", "fail if the repo is not already indexed instead of indexing before watching")
199
+ .description("Run a long-lived filesystem watcher daemon with event journal replay and background batch indexing")
200
+ .action(async (repoRoot, options) => {
201
+ const engine = buildCliEngine(repoRoot);
202
+ if (process.stdout.isTTY) {
203
+ const { createWatchStatusTui } = await import("./tui/watch-status.js");
204
+ const absoluteRoot = path.resolve(repoRoot);
205
+ let tui;
206
+ let shuttingDown = false;
207
+ const daemon = new FileWatchDaemon(engine, repoRoot, {
208
+ batchDelayMs: options.batchDelay,
209
+ minQuietMs: options.quiet,
210
+ flushEventsMs: options.flushEvents,
211
+ awaitWriteFinishMs: options.awaitWrite,
212
+ burstThreshold: options.burstThreshold,
213
+ maxDirtyFiles: options.maxDirtyFiles,
214
+ maxBatchFiles: options.maxBatchFiles,
215
+ usePolling: options.poll,
216
+ autoIndex: options.autoIndex,
217
+ indexOnStart: options.indexOnStart,
218
+ onStatus: (status) => tui?.update(status)
219
+ });
220
+ tui = createWatchStatusTui({
221
+ repoRoot: absoluteRoot,
222
+ running: false,
223
+ ready: false,
224
+ bufferedEvents: 0,
225
+ scheduler: {
226
+ repoRoot: absoluteRoot,
227
+ running: false,
228
+ scheduled: false,
229
+ indexing: false,
230
+ pendingFiles: 0,
231
+ indexingFiles: 0
232
+ }
233
+ });
234
+ const shutdown = async () => {
235
+ if (shuttingDown)
236
+ return;
237
+ shuttingDown = true;
238
+ await daemon.stop();
239
+ engine.close();
240
+ tui?.unmount();
241
+ };
242
+ const exitFromSignal = () => {
243
+ void shutdown().finally(() => process.exit(0));
244
+ };
245
+ process.once("SIGINT", exitFromSignal);
246
+ process.once("SIGTERM", exitFromSignal);
247
+ try {
248
+ await daemon.start();
249
+ tui.update(await daemon.status());
250
+ await tui.waitUntilExit();
251
+ }
252
+ catch (error) {
253
+ if (error instanceof WatcherLockError) {
254
+ // Another live watcher already owns this repo. shutdown() (guarded, runs in finally)
255
+ // tears down our half-started daemon and closes the engine; just surface the reason.
256
+ tui?.unmount();
257
+ console.error(`⚠️ ${error.message}`);
258
+ return;
259
+ }
260
+ throw error;
261
+ }
262
+ finally {
263
+ process.removeListener("SIGINT", exitFromSignal);
264
+ process.removeListener("SIGTERM", exitFromSignal);
265
+ await shutdown();
266
+ }
267
+ return;
268
+ }
269
+ const daemon = new FileWatchDaemon(engine, repoRoot, {
270
+ batchDelayMs: options.batchDelay,
271
+ minQuietMs: options.quiet,
272
+ flushEventsMs: options.flushEvents,
273
+ awaitWriteFinishMs: options.awaitWrite,
274
+ burstThreshold: options.burstThreshold,
275
+ maxDirtyFiles: options.maxDirtyFiles,
276
+ maxBatchFiles: options.maxBatchFiles,
277
+ usePolling: options.poll,
278
+ autoIndex: options.autoIndex,
279
+ indexOnStart: options.indexOnStart,
280
+ onStatus: (status) => {
281
+ console.error(JSON.stringify({ watcher: status }));
282
+ }
283
+ });
284
+ const shutdown = async () => {
285
+ await daemon.stop();
286
+ engine.close();
287
+ };
288
+ process.once("SIGINT", () => {
289
+ void shutdown().finally(() => process.exit(0));
290
+ });
291
+ process.once("SIGTERM", () => {
292
+ void shutdown().finally(() => process.exit(0));
293
+ });
294
+ try {
295
+ await daemon.start();
296
+ }
297
+ catch (error) {
298
+ if (error instanceof WatcherLockError) {
299
+ engine.close();
300
+ console.error(error.message);
301
+ process.exitCode = 1;
302
+ return;
303
+ }
304
+ throw error;
305
+ }
306
+ console.log(JSON.stringify(await daemon.status(), null, 2));
307
+ });
308
+ const service = program
309
+ .command("service")
310
+ .description("Manage the background watcher as an OS service (one per repo) so freshness survives reboots");
311
+ service
312
+ .command("install")
313
+ .argument("<repoRoot>")
314
+ .option("--poll", "register the watcher with polling instead of native fs.watch")
315
+ .description("Register and start a background watcher service for this repo (systemd user / launchd / Task Scheduler)")
316
+ .action(async (repoRoot, options) => {
317
+ // Index once up front so the service can start with --no-index-on-start and not block on a full
318
+ // reindex at boot. Then register the OS unit. The per-repo lock (P0) makes redundant launches safe.
319
+ await withEngine(repoRoot, (engine) => engine.indexRepo(repoRoot));
320
+ try {
321
+ const result = await installWatcherService(repoRoot, { extraArgs: options.poll ? ["--poll"] : undefined });
322
+ console.log(JSON.stringify(result, null, 2));
323
+ if (!result.ok)
324
+ process.exitCode = 1;
325
+ }
326
+ catch (error) {
327
+ console.error(error instanceof Error ? error.message : String(error));
328
+ process.exitCode = 1;
329
+ }
330
+ });
331
+ service
332
+ .command("uninstall")
333
+ .argument("<repoRoot>")
334
+ .description("Stop and remove the background watcher service for this repo")
335
+ .action(async (repoRoot) => {
336
+ try {
337
+ const result = await uninstallWatcherService(repoRoot);
338
+ console.log(JSON.stringify(result, null, 2));
339
+ if (!result.ok)
340
+ process.exitCode = 1;
341
+ }
342
+ catch (error) {
343
+ console.error(error instanceof Error ? error.message : String(error));
344
+ process.exitCode = 1;
345
+ }
346
+ });
347
+ service
348
+ .command("status")
349
+ .argument("<repoRoot>")
350
+ .description("Report whether a watcher service is registered and whether the watcher process is alive")
351
+ .action(async (repoRoot) => {
352
+ const [registration, liveness] = await Promise.all([
353
+ watcherServiceStatus(repoRoot),
354
+ readWatcherLiveness(path.resolve(repoRoot))
355
+ ]);
356
+ console.log(JSON.stringify({ registration, liveness }, null, 2));
357
+ });
358
+ program
359
+ .command("doctor")
360
+ .argument("[repoRoot]")
361
+ .option("-q, --query <query>", "query to use when repoRoot smoke indexing is enabled")
362
+ .description("Check runtime dependencies, env config, MCP registration, and optional repo indexing/search smoke")
363
+ .action(async (repoRoot, options) => {
364
+ const report = await runDoctor({ repoRoot, searchQuery: options.query });
365
+ console.log(JSON.stringify(report, null, 2));
366
+ if (!report.ok)
367
+ process.exitCode = 1;
368
+ });
369
+ program
370
+ .command("mcp")
371
+ .description("Start the RagCode MCP server over stdio")
372
+ .action(async () => {
373
+ await startStdioMcpServer();
374
+ });
375
+ program
376
+ .command("init")
377
+ .argument("[directory]")
378
+ .option("-y, --yes", "write offline-first defaults without interactive prompts")
379
+ .option("--defaults", "write offline-first defaults without interactive prompts")
380
+ .description("Initialize RagCode configuration (interactive first-run setup)")
381
+ .action(async (directory, options) => {
382
+ const targetDir = directory || process.cwd();
383
+ const defaults = Boolean(options.yes || options.defaults);
384
+ if (!defaults && process.stdin.isTTY) {
385
+ // Interactive first-run goes through the Ink wizard (same app as `ragcode configure`,
386
+ // first_run mode defaults index/setup-mcp to yes). Loaded lazily to keep --defaults light.
387
+ const { runInkConfigure } = await import("./configure/run.js");
388
+ await runInkConfigure({ repoRoot: targetDir, mode: "first_run" });
389
+ return;
390
+ }
391
+ await runInitConfig({ targetDir, defaults: true });
392
+ });
393
+ program
394
+ .command("setup-mcp")
395
+ .option("--config <path>", "Custom MCP config path")
396
+ .option("--print", "Print config without writing")
397
+ .option("--include-secrets", "Include real secrets instead of redacted placeholders")
398
+ .option("--client <client>", "Client format: claude (Desktop), claude-code (project .mcp.json), codex (~/.codex/config.toml), or generic")
399
+ .option("--force", "Overwrite an existing ragcode entry without prompting")
400
+ .description("Register RagCode as an MCP server for Claude Desktop, Claude Code, or Codex")
401
+ .action(async (options) => {
402
+ setupMCP({
403
+ configPath: options.config,
404
+ print: options.print,
405
+ includeSecrets: options.includeSecrets,
406
+ client: options.client,
407
+ force: options.force,
408
+ cwd: process.cwd(),
409
+ env: process.env
410
+ });
411
+ });
412
+ program
413
+ .command("configure")
414
+ .argument("[repoRoot]")
415
+ .option("--show", "Print the effective runtime config (secrets redacted) and exit")
416
+ .option("--graph-store <store>", "Graph store: memory or sqlite")
417
+ .option("--sqlite-path <path>", "SQLite graph database path")
418
+ .option("--semantic-store <store>", "Semantic store: memory or lancedb")
419
+ .option("--lancedb-uri <path>", "LanceDB storage path")
420
+ .option("--embedding-provider <provider>", "Embedding provider: deterministic or openai-compatible")
421
+ .option("--base-url <url>", "Embedding API base URL")
422
+ .option("--model <model>", "Embedding model name")
423
+ .option("--api-key <key>", "Embedding API key (persisted to .ragcode/config.json)")
424
+ .option("--dimensions <number>", "Embedding dimensions", parseNumber)
425
+ .option("--request-dimensions <bool>", "Send dimensions parameter to the provider (true/false)")
426
+ .description("Edit storage and embedding runtime config from the terminal, with embedding test")
427
+ .action(async (repoRoot, options) => {
428
+ await runConfigureCommand(repoRoot, {
429
+ show: options.show,
430
+ test: options.test,
431
+ graphStore: options.graphStore,
432
+ sqlitePath: options.sqlitePath,
433
+ semanticStore: options.semanticStore,
434
+ lancedbUri: options.lancedbUri,
435
+ embeddingProvider: options.embeddingProvider,
436
+ embeddingBaseUrl: options.baseUrl,
437
+ embeddingModel: options.model,
438
+ embeddingApiKey: options.apiKey,
439
+ embeddingDimensions: options.dimensions,
440
+ embeddingRequestDimensions: options.requestDimensions === undefined ? undefined : options.requestDimensions === "true"
441
+ });
442
+ });
443
+ program
444
+ .command("update")
445
+ .option("--check", "only report whether a newer version is available; don't install")
446
+ .option("--pm <manager>", "package manager to use: npm, pnpm, or yarn (default: auto-detect)")
447
+ .option("--version <version>", "install a specific version or dist-tag instead of latest")
448
+ .description("Update the globally-installed RagCode CLI to the latest version")
449
+ .action(async (options) => {
450
+ const result = await runUpdate({ checkOnly: options.check, packageManager: options.pm, version: options.version });
451
+ console.log(result.message);
452
+ if (!result.ok)
453
+ process.exitCode = 1;
454
+ });
455
+ program
456
+ .command("dashboard")
457
+ .description("Start the Web observability dashboard API (graph/search/context/watch observation)")
458
+ .action(async () => {
459
+ await import("../web/server.js");
460
+ });
461
+ program.parseAsync().catch((error) => {
462
+ const message = formatCliError(error);
463
+ console.error(message);
464
+ process.exitCode = 1;
465
+ });
466
+ async function withEngine(repoRoot, fn) {
467
+ const engine = buildCliEngine(repoRoot);
468
+ try {
469
+ return await fn(engine);
470
+ }
471
+ finally {
472
+ engine.close();
473
+ }
474
+ }
475
+ // CLI commands resolve runtime config through the shared loader so that explicit args, env,
476
+ // .ragcode/config.json, and offline-first defaults (sqlite + lancedb + deterministic) apply
477
+ // uniformly across CLI, MCP, Web, and doctor. The engine constructor itself stays env-driven
478
+ // for embedded/library/test usage.
479
+ function buildCliEngine(repoRoot) {
480
+ const components = createRuntimeComponentsForRepo({
481
+ cwd: process.cwd(),
482
+ overrides: repoRoot ? { repoRoot } : undefined
483
+ });
484
+ return new RagCodeEngine({
485
+ cwd: repoRoot,
486
+ graphStore: components.graphStore,
487
+ semanticStore: components.semanticStore,
488
+ embeddingProvider: components.embeddingProvider
489
+ });
490
+ }
491
+ function parseNumber(value) {
492
+ const parsed = Number(value);
493
+ if (!Number.isFinite(parsed))
494
+ throw new Error(`Invalid number: ${value}`);
495
+ return parsed;
496
+ }
497
+ function formatCliError(error) {
498
+ const message = error instanceof Error ? error.message : String(error);
499
+ if (/Workspace is not indexed|Missing workspace|Repository is not indexed/i.test(message)) {
500
+ return `${message}\nRun "ragcode index <repoRoot>" first, then retry the read command. CLI reads default to SQLite at <repoRoot>/.ragcode/graph.sqlite unless RAGCODE_SQLITE_PATH is set.`;
501
+ }
502
+ return message;
503
+ }
@@ -0,0 +1,12 @@
1
+ import type { IndexProgressEvent, RepoIndex } from "../../core/types.js";
2
+ export interface IndexProgressAppProps {
3
+ repoRoot: string;
4
+ events: IndexProgressEvent[];
5
+ result?: RepoIndex;
6
+ error?: string;
7
+ }
8
+ export declare function IndexProgressApp({ repoRoot, events, result, error }: IndexProgressAppProps): React.ReactElement;
9
+ export declare function runIndexProgressTui(options: {
10
+ repoRoot: string;
11
+ run: (onProgress: (event: IndexProgressEvent) => void) => Promise<RepoIndex>;
12
+ }): Promise<RepoIndex>;
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { render, Box, Text } from "ink";
3
+ export function IndexProgressApp({ repoRoot, events, result, error }) {
4
+ const latest = events.at(-1);
5
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "RagCode Index" }), _jsxs(Text, { dimColor: true, children: ["repo: ", repoRoot] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [events.slice(-6).map((event, index) => (_jsxs(Text, { color: index === events.slice(-6).length - 1 && !result && !error ? "green" : undefined, children: [event.phase === "complete" ? "✓" : "•", " ", event.message, formatEventStats(event)] }, `${event.phase}-${index}`))), !latest ? _jsx(Text, { children: "\u2022 Preparing index" }) : null] }), result ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: "green", children: ["Indexed ", result.files.length, " files, ", result.chunks.length, " chunks."] }), _jsxs(Text, { dimColor: true, children: ["changed ", result.changedFiles.length, ", deleted ", result.deletedFiles.length, ", refreshed ", result.refreshedFiles?.length ?? 0] })] })) : null, error ? _jsxs(Text, { color: "red", children: ["Index failed: ", error] }) : null] }));
6
+ }
7
+ export async function runIndexProgressTui(options) {
8
+ const events = [];
9
+ let result;
10
+ let error;
11
+ const app = render(_jsx(IndexProgressApp, { repoRoot: options.repoRoot, events: events }));
12
+ const update = () => {
13
+ app.rerender(_jsx(IndexProgressApp, { repoRoot: options.repoRoot, events: [...events], result: result, error: error }));
14
+ };
15
+ try {
16
+ result = await options.run((event) => {
17
+ events.push(event);
18
+ update();
19
+ });
20
+ update();
21
+ return result;
22
+ }
23
+ catch (caught) {
24
+ error = caught instanceof Error ? caught.message : String(caught);
25
+ update();
26
+ throw caught;
27
+ }
28
+ finally {
29
+ app.unmount();
30
+ }
31
+ }
32
+ function formatEventStats(event) {
33
+ const parts = [];
34
+ if (event.scannedFiles !== undefined)
35
+ parts.push(`scanned ${event.scannedFiles}`);
36
+ if (event.changedFiles !== undefined)
37
+ parts.push(`changed ${event.changedFiles}`);
38
+ if (event.deletedFiles !== undefined)
39
+ parts.push(`deleted ${event.deletedFiles}`);
40
+ if (event.refreshedFiles !== undefined)
41
+ parts.push(`refreshed ${event.refreshedFiles}`);
42
+ if (event.chunks !== undefined)
43
+ parts.push(`chunks ${event.chunks}`);
44
+ if (event.symbols !== undefined)
45
+ parts.push(`symbols ${event.symbols}`);
46
+ if (event.edges !== undefined)
47
+ parts.push(`edges ${event.edges}`);
48
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
49
+ }
@@ -0,0 +1,10 @@
1
+ import type { WatchDaemonStatus } from "../../watch/watch-daemon.js";
2
+ export interface WatchStatusAppProps {
3
+ status: WatchDaemonStatus;
4
+ }
5
+ export declare function WatchStatusApp({ status }: WatchStatusAppProps): React.ReactElement;
6
+ export declare function createWatchStatusTui(initialStatus: WatchDaemonStatus): {
7
+ update(status: WatchDaemonStatus): void;
8
+ waitUntilExit(): Promise<void>;
9
+ unmount(): void;
10
+ };
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { render, Box, Text, useApp, useInput } from "ink";
3
+ export function WatchStatusApp({ status }) {
4
+ const { exit } = useApp();
5
+ useInput((input, key) => {
6
+ if (key.escape || (key.ctrl && input === "c"))
7
+ exit();
8
+ });
9
+ const scheduler = status.scheduler;
10
+ const healthColor = scheduler.lastError ? "red" : status.ready ? "green" : "yellow";
11
+ const health = scheduler.lastError ? "error" : status.ready ? "ready" : "starting";
12
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "RagCode Watch" }), _jsxs(Text, { dimColor: true, children: ["repo: ", status.repoRoot] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: ["Status: ", _jsx(Text, { color: healthColor, children: health }), " ", status.running ? "running" : "stopped"] }), _jsxs(Text, { children: ["Events: buffered ", status.bufferedEvents, ", pending ", scheduler.pendingFiles, ", indexing ", scheduler.indexingFiles] }), _jsxs(Text, { children: ["Scheduler: ", scheduler.running ? "running" : "stopped", ", ", scheduler.scheduled ? "scheduled" : "idle", ", ", scheduler.indexing ? "indexing" : "not indexing"] }), _jsxs(Text, { children: ["Last indexed: ", scheduler.lastIndexedAtMs ? new Date(scheduler.lastIndexedAtMs).toLocaleString() : "never in this session"] }), scheduler.lastError ? _jsxs(Text, { color: "red", children: ["Last error: ", scheduler.lastError] }) : null] }), _jsx(Text, { dimColor: true, children: "Ctrl+C or Esc to stop" })] }));
13
+ }
14
+ export function createWatchStatusTui(initialStatus) {
15
+ const app = render(_jsx(WatchStatusApp, { status: initialStatus }));
16
+ return {
17
+ update(status) {
18
+ app.rerender(_jsx(WatchStatusApp, { status: status }));
19
+ },
20
+ async waitUntilExit() {
21
+ await app.waitUntilExit();
22
+ },
23
+ unmount() {
24
+ app.unmount();
25
+ }
26
+ };
27
+ }
@@ -0,0 +1,18 @@
1
+ export declare const PACKAGE_NAME = "ragcode-context-engine";
2
+ export interface UpdateOptions {
3
+ /** Only report current vs latest; don't install. */
4
+ checkOnly?: boolean;
5
+ /** Override the package manager (npm/pnpm/yarn). Defaults to auto-detect. */
6
+ packageManager?: string;
7
+ /** Override the dist-tag/version to install. Defaults to "latest". */
8
+ version?: string;
9
+ }
10
+ export interface UpdateResult {
11
+ ok: boolean;
12
+ currentVersion: string;
13
+ latestVersion?: string;
14
+ upToDate: boolean;
15
+ installed: boolean;
16
+ message: string;
17
+ }
18
+ export declare function runUpdate(options?: UpdateOptions): Promise<UpdateResult>;