codesearch-mcp 0.0.17-alpha.1 → 0.0.18-alpha
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.
- package/CHANGELOG.md +25 -0
- package/README.md +20 -0
- package/dist/app.js +34 -10
- package/dist/src/commands/ConfigCommand.d.ts +1 -0
- package/dist/src/commands/ConfigCommand.js +84 -4
- package/dist/src/commands/IndexCommand.js +22 -7
- package/dist/src/commands/McpCommand.js +4 -3
- package/dist/src/commands/WebCommand.d.ts +8 -0
- package/dist/src/commands/WebCommand.js +56 -0
- package/dist/src/config-store.d.ts +43 -0
- package/dist/src/config-store.js +92 -27
- package/dist/src/config.js +16 -17
- package/dist/src/constants.d.ts +2 -1
- package/dist/src/constants.js +6 -4
- package/dist/src/container.d.ts +10 -6
- package/dist/src/container.js +6 -3
- package/dist/src/graph.d.ts +60 -0
- package/dist/src/graph.js +239 -120
- package/dist/src/help.js +15 -1
- package/dist/src/interfaces/IAstParser.d.ts +6 -3
- package/dist/src/interfaces/IDatabase.d.ts +13 -0
- package/dist/src/interfaces/IDatabase.js +2 -0
- package/dist/src/interfaces/IEmbedder.d.ts +1 -0
- package/dist/src/interfaces/ILogger.d.ts +10 -0
- package/dist/src/interfaces/ILogger.js +2 -0
- package/dist/src/interfaces/IMcpToolHandler.d.ts +22 -0
- package/dist/src/interfaces/IMcpToolHandler.js +2 -0
- package/dist/src/interfaces/IMonorepoResolver.d.ts +33 -0
- package/dist/src/interfaces/IMonorepoResolver.js +2 -0
- package/dist/src/interfaces/IProjectDiscovery.d.ts +28 -0
- package/dist/src/interfaces/IProjectDiscovery.js +2 -0
- package/dist/src/interfaces/ISearcher.d.ts +36 -0
- package/dist/src/interfaces/ISearcher.js +2 -0
- package/dist/src/interfaces/index.d.ts +7 -0
- package/dist/src/interfaces/index.js +2 -0
- package/dist/src/project-selector.d.ts +1 -1
- package/dist/src/services/AstParserService.d.ts +4 -6
- package/dist/src/services/AstParserService.js +29 -13
- package/dist/src/services/DatabaseService.d.ts +2 -1
- package/dist/src/services/IndexerService.d.ts +6 -8
- package/dist/src/services/IndexerService.js +5 -8
- package/dist/src/services/LoggerService.d.ts +2 -1
- package/dist/src/services/McpServer.d.ts +16 -7
- package/dist/src/services/McpServer.js +54 -367
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +62 -0
- package/dist/src/services/MonorepoAwareDiscoveryService.js +125 -0
- package/dist/src/services/NxMonorepoResolver.d.ts +45 -0
- package/dist/src/services/NxMonorepoResolver.js +308 -0
- package/dist/src/services/OllamaEmbedder.d.ts +5 -2
- package/dist/src/services/OllamaEmbedder.js +9 -2
- package/dist/src/services/ProjectDiscoveryService.d.ts +8 -0
- package/dist/src/services/ProjectDiscoveryService.js +52 -0
- package/dist/src/services/SearcherService.d.ts +39 -6
- package/dist/src/services/SearcherService.js +151 -40
- package/dist/src/services/WebServerService.d.ts +72 -0
- package/dist/src/services/WebServerService.js +247 -0
- package/dist/src/setup.js +7 -7
- package/dist/src/tools/FindSymbolTool.d.ts +8 -0
- package/dist/src/tools/FindSymbolTool.js +45 -0
- package/dist/src/tools/GetCallersTool.d.ts +8 -0
- package/dist/src/tools/GetCallersTool.js +42 -0
- package/dist/src/tools/GetDependenciesTool.d.ts +8 -0
- package/dist/src/tools/GetDependenciesTool.js +38 -0
- package/dist/src/tools/GetDependentsTool.d.ts +8 -0
- package/dist/src/tools/GetDependentsTool.js +57 -0
- package/dist/src/tools/ListFilesTool.d.ts +8 -0
- package/dist/src/tools/ListFilesTool.js +39 -0
- package/dist/src/tools/ListProjectsTool.d.ts +8 -0
- package/dist/src/tools/ListProjectsTool.js +44 -0
- package/dist/src/tools/ReadFileRangeTool.d.ts +8 -0
- package/dist/src/tools/ReadFileRangeTool.js +44 -0
- package/dist/src/tools/ReadFileTool.d.ts +8 -0
- package/dist/src/tools/ReadFileTool.js +42 -0
- package/dist/src/tools/SearchCodebaseTool.d.ts +13 -0
- package/dist/src/tools/SearchCodebaseTool.js +81 -0
- package/dist/src/tools/format-helpers.d.ts +8 -0
- package/dist/src/tools/format-helpers.js +57 -0
- package/dist/src/tools/index.d.ts +9 -0
- package/dist/src/tools/index.js +21 -0
- package/dist/src/web/dashboard.d.ts +9 -0
- package/dist/src/web/dashboard.js +337 -0
- package/docs/web-dashboard.md +187 -0
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
5
5
|
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.0.18-alpha] - 2026-07-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Web dashboard (`rag-web`)** — a local browser UI at `http://127.0.0.1:4319` (`--port` to change), bound to `127.0.0.1` only, no auth (single-user local tool). Three tabs: a **schema-driven config editor** (form generated from the config JSON Schema; shows the applied default for any unset key, a strict dropdown for the embedding model, and comboboxes suggesting the user's installed Ollama models for the translate/reranker models), a **project overview**, and the **interactive dependency graph** (the same page as `project-graph.html`, embedded via iframe — filters, legend, and project toggles included). The DB password never reaches the browser (masked as `********`, preserved on save). Requires a valid `config.json`; a failed DB/Ollama connection is non-fatal (config editing still works). Full reference: `docs/web-dashboard.md`.
|
|
13
|
+
- **Config JSON Schema + `rag-config edit`** — `config.json` now gets a generated `config.schema.json` sibling, referenced via `"$schema"`, so schema-aware editors (VS Code, JetBrains) provide key autocomplete, type hints, and inline validation. `rag-config edit` opens the config in `$VISUAL` / `$EDITOR` (falls back to `nano` / `notepad`) and re-validates on save.
|
|
14
|
+
- **Nx monorepo support** — when `workspaceRoot` is an Nx workspace (detected by `nx.json`), projects under `apps/`, `libs/`, and `packages/` are discovered as separate indexed projects instead of a single flat scan. Cross-project imports (`@scope/lib`) resolve through the workspace-wide `tsconfig.base.json` path aliases, so `get_dependencies` / `get_dependents` see edges across projects. Non-Nx (flat) workspaces are unaffected.
|
|
15
|
+
- **`IMonorepoResolver` interface + `NxMonorepoResolver`** — resolver abstraction for monorepo tooling. `NxMonorepoResolver` detects `nx.json`, discovers projects via `workspace.json` / `angular.json` or an `apps/`/`libs/`/`packages/` scan for `project.json` / `package.json`, and resolves the root tsconfig (`tsconfig.base.json`, falling back to `tsconfig.json`).
|
|
16
|
+
- **`MonorepoAwareDiscoveryService`** — implements `IProjectDiscovery` and is wired as the default discovery in the container. Uses the resolver when a monorepo is detected; otherwise delegates to `ProjectDiscoveryService` (flat `package.json` scan). Exposes `analyzeWorkspace()` for full workspace metadata.
|
|
17
|
+
- **`DiscoveredProject` metadata** — discovery now carries optional `packageName` (npm scope + name), `type` (`app` | `lib`), and `rootTsConfigPath` per project.
|
|
18
|
+
- **Nested Nx monorepo under a flat workspace** — when `workspaceRoot` is a flat directory but one of its top-level subdirectories is itself an Nx repo, that repo is discovered as a single selectable unit whose sub-projects (`<repo>/<project>`, e.g. `micros-learning/account`) are expanded and indexed individually. `DiscoveredProject` gains an optional `children` field; the exported `toLeafProjects()` helper expands units into their leaf projects and is shared by `IndexCommand` (indexing loop) and `SearcherService` (name → path resolution). Each sub-project carries the repo's `tsconfig.base.json` as `rootTsConfigPath` for cross-project alias resolution.
|
|
19
|
+
- **Root-level Nx libs via `tsconfig.base.json` paths** — `NxMonorepoResolver` now also discovers projects referenced only through `compilerOptions.paths` aliases (e.g. `contracts/`, `interfaces/` living at the repo root), which the `apps/`/`libs/`/`packages/` scan misses. Only targets that resolve to a real project (with `project.json` / `package.json`) are added; the `tsconfig` reader tolerates JSONC (comments, trailing commas).
|
|
20
|
+
- **Tabbed project graph** — `project-graph.html` and the dashboard's `/graph` now have inner tabs: a **Workspace** tab (flat projects + each nested Nx repo collapsed to one node, double-click to open) and **one tab per Nx repo** showing its sub-projects with real import edges between them. Intra-repo edges come from `SearcherService.getProjectEdges()`, which aggregates `code_dependency` (file → file) into project → project links (most-specific owner wins). `getProjectEdges` is exposed on `ISearcher`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **Single source of truth for config defaults** — built-in defaults now live once in `CONFIG_DEFAULTS` (`config-store.ts`); `config.ts` references them in its schema `.default(...)` calls, so the runtime default and the value the dashboard shows for an unset key cannot diverge. No behavior change.
|
|
25
|
+
- **`analyzeProject` / `scanAndIndex` accept `rootTsConfigPath`** — for monorepo projects the AST parser loads the workspace-wide tsconfig (`tsconfig.base.json`) so ts-morph resolves cross-project path aliases; flat-workspace projects continue to use their local `tsconfig.json`.
|
|
26
|
+
- **`workspace.json` parsing hardened** — a project entry missing a valid `root` is skipped with a warning instead of aborting discovery of the remaining projects in that file.
|
|
27
|
+
- **Nx project discovery deduplicated across strategies** — `workspace.json` / `angular.json`, the directory scan, `tsconfig` paths, and the root-level fallback are now merged and deduped by absolute project path, so a project surfaced by more than one strategy is reported once.
|
|
28
|
+
- **`SearcherService` resolves project names to real roots** — takes the discovery service and builds a lazily-cached `name → filesystem root` map, so nested-monorepo names (`repo/app`) map to their true path (`repo/apps/app`) instead of a naïve `workspaceRoot/name` join. The cache rebuilds on a miss, so projects added or renamed while a long-running `rag-mcp` / `rag-web` server is up are picked up without a restart. `listProjects`, `grepSymbol`, `find_symbol` grep fallback, and `list_files` use it.
|
|
29
|
+
- **Project graph build is non-fatal in `rag-index`** — a failure while computing import edges or writing `project-graph.html` after a successful index is now caught and logged instead of failing the whole run.
|
|
30
|
+
|
|
6
31
|
## [0.0.17-alpha] - 2026-05-13
|
|
7
32
|
|
|
8
33
|
### Added
|
package/README.md
CHANGED
|
@@ -54,6 +54,14 @@ rag-index --reindex # force re-embed all files (use after model change)
|
|
|
54
54
|
rag-index --select --reindex
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
## Monorepo support
|
|
58
|
+
|
|
59
|
+
If `workspaceRoot` is an [Nx](https://nx.dev) workspace (detected by `nx.json`), `rag-index` discovers each project under `apps/`, `libs/`, and `packages/` as a **separate indexed project** rather than scanning the workspace as one blob. Cross-project imports like `@scope/shared` resolve through the workspace `tsconfig.base.json` path aliases, so `get_dependencies` / `get_dependents` report edges **across** projects.
|
|
60
|
+
|
|
61
|
+
**Nested Nx repos.** `workspaceRoot` may also be a plain directory that *contains* an Nx repo in one of its immediate subdirectories (alongside ordinary projects). That nested repo is detected and expanded into its own projects, named `<repo>/<project>` (e.g. `micros-learning/account`), each resolved with the repo's own `tsconfig.base.json`. In the project selector the whole Nx repo appears as **one** selectable entry (so `.ragprojects` stores just `micros-learning`); selecting it indexes all its sub-projects. Ordinary sibling directories are unaffected.
|
|
62
|
+
|
|
63
|
+
No configuration needed — detection is automatic. Plain (non-Nx) workspaces keep the original behavior: each immediate subdirectory with a `package.json` is one project.
|
|
64
|
+
|
|
57
65
|
## Register with Claude Code
|
|
58
66
|
|
|
59
67
|
```bash
|
|
@@ -62,6 +70,18 @@ claude mcp add codesearch-mcp -s user -- rag-mcp
|
|
|
62
70
|
|
|
63
71
|
Then ask Claude anything about your code — it will use the tools below automatically.
|
|
64
72
|
|
|
73
|
+
## Web dashboard
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
rag-web # http://127.0.0.1:4319 (--port to change)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A local web UI (bound to `127.0.0.1` only) with a schema-driven config editor,
|
|
80
|
+
an overview of indexed projects, and the interactive dependency graph. The
|
|
81
|
+
config form shows the applied default for any unset key and offers dropdowns for
|
|
82
|
+
model fields (the embedding model is restricted to dimension-compatible models).
|
|
83
|
+
See [docs/web-dashboard.md](docs/web-dashboard.md) for the full reference.
|
|
84
|
+
|
|
65
85
|
## MCP Tools
|
|
66
86
|
|
|
67
87
|
| Tool | Description |
|
package/dist/app.js
CHANGED
|
@@ -42,6 +42,7 @@ const IndexCommand_1 = require("./src/commands/IndexCommand");
|
|
|
42
42
|
const McpCommand_1 = require("./src/commands/McpCommand");
|
|
43
43
|
const BackfillCommand_1 = require("./src/commands/BackfillCommand");
|
|
44
44
|
const ConfigCommand_1 = require("./src/commands/ConfigCommand");
|
|
45
|
+
const WebCommand_1 = require("./src/commands/WebCommand");
|
|
45
46
|
const setup_1 = require("./src/setup");
|
|
46
47
|
async function main() {
|
|
47
48
|
const argv1 = path.basename(process.argv[1] ?? '');
|
|
@@ -49,10 +50,11 @@ async function main() {
|
|
|
49
50
|
const isMcp = argv1 === 'rag-mcp' || process.argv[2] === 'mcp' || process.argv[2] === 'rag-mcp';
|
|
50
51
|
const isSetup = argv1 === 'rag-setup' || process.argv[2] === 'setup' || process.argv[2] === 'rag-setup';
|
|
51
52
|
const isConfig = argv1 === 'rag-config' || process.argv[2] === 'config' || process.argv[2] === 'rag-config';
|
|
53
|
+
const isWeb = argv1 === 'rag-web' || process.argv[2] === 'web' || process.argv[2] === 'rag-web';
|
|
52
54
|
const isBackfill = process.argv[2] === 'backfill' ||
|
|
53
55
|
(process.argv[2] === 'rag-index' && process.argv[3] === 'backfill');
|
|
54
|
-
const mode = isBackfill ? 'backfill' : isIndex ? 'index' : isMcp ? 'mcp' : isSetup ? 'setup' : isConfig ? 'config' : (process.argv[2] ?? '');
|
|
55
|
-
const isBinary = ['rag-index', 'rag-mcp', 'rag-setup', 'rag-config'].includes(argv1);
|
|
56
|
+
const mode = isBackfill ? 'backfill' : isIndex ? 'index' : isMcp ? 'mcp' : isSetup ? 'setup' : isConfig ? 'config' : isWeb ? 'web' : (process.argv[2] ?? '');
|
|
57
|
+
const isBinary = ['rag-index', 'rag-mcp', 'rag-setup', 'rag-config', 'rag-web'].includes(argv1);
|
|
56
58
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
57
59
|
(0, help_1.printHelp)(isBinary ? argv1 : mode);
|
|
58
60
|
process.exit(0);
|
|
@@ -75,6 +77,24 @@ async function main() {
|
|
|
75
77
|
process.exit(1);
|
|
76
78
|
}
|
|
77
79
|
}
|
|
80
|
+
else if (mode === 'web') {
|
|
81
|
+
for (let i = 0; i < effectiveArgs.length; i++) {
|
|
82
|
+
if (effectiveArgs[i] === '--port') {
|
|
83
|
+
const raw = effectiveArgs[i + 1];
|
|
84
|
+
const n = Number(raw);
|
|
85
|
+
if (!raw || !Number.isInteger(n) || n < 1 || n > 65535) {
|
|
86
|
+
console.error(`Invalid --port value: ${raw ?? '(missing)'}\n`);
|
|
87
|
+
(0, help_1.printHelp)(isBinary ? argv1 : 'rag-web');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
i++;
|
|
91
|
+
continue; // skip its (validated) value
|
|
92
|
+
}
|
|
93
|
+
console.error(`Unexpected argument: ${effectiveArgs[i]}\n`);
|
|
94
|
+
(0, help_1.printHelp)(isBinary ? argv1 : 'rag-web');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
78
98
|
else if (mode === 'backfill') {
|
|
79
99
|
const knownBackfillFlags = new Set(['--calls']);
|
|
80
100
|
const backfillIdx = process.argv.indexOf('backfill');
|
|
@@ -95,7 +115,7 @@ async function main() {
|
|
|
95
115
|
process.exit(0);
|
|
96
116
|
}
|
|
97
117
|
// All other modes require a valid config.json
|
|
98
|
-
const loggerName = mode === 'index' || mode === 'backfill' ? 'indexer' : mode === 'mcp' ? 'mcp' : undefined;
|
|
118
|
+
const loggerName = mode === 'index' || mode === 'backfill' ? 'indexer' : mode === 'mcp' ? 'mcp' : mode === 'web' ? 'web' : undefined;
|
|
99
119
|
let container;
|
|
100
120
|
try {
|
|
101
121
|
container = (0, container_1.createContainer)(loggerName);
|
|
@@ -114,20 +134,24 @@ async function main() {
|
|
|
114
134
|
console.error(`workspaceRoot is not configured. ${hint}`);
|
|
115
135
|
process.exit(1);
|
|
116
136
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
137
|
+
// Construct only the selected command — building every command eagerly would
|
|
138
|
+
// run each constructor's arg parsing (e.g. WebCommand's --port) for modes that
|
|
139
|
+
// never use it.
|
|
140
|
+
const factories = {
|
|
141
|
+
index: () => new IndexCommand_1.IndexCommand(container, argv1),
|
|
142
|
+
mcp: () => new McpCommand_1.McpCommand(container),
|
|
143
|
+
backfill: () => new BackfillCommand_1.BackfillCommand(container),
|
|
144
|
+
web: () => new WebCommand_1.WebCommand(container, effectiveArgs),
|
|
121
145
|
};
|
|
122
|
-
const
|
|
123
|
-
if (!
|
|
146
|
+
const factory = factories[mode];
|
|
147
|
+
if (!factory) {
|
|
124
148
|
const hasUnknownArg = mode !== '';
|
|
125
149
|
if (hasUnknownArg)
|
|
126
150
|
console.error(`Unknown command: ${mode}\n`);
|
|
127
151
|
(0, help_1.printHelp)('default');
|
|
128
152
|
process.exit(hasUnknownArg ? 1 : 0);
|
|
129
153
|
}
|
|
130
|
-
await
|
|
154
|
+
await factory().execute();
|
|
131
155
|
await container.db.destroy();
|
|
132
156
|
process.exit(0);
|
|
133
157
|
}
|
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.ALL_KEYS = exports.ConfigCommand = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
4
39
|
const config_store_1 = require("../config-store");
|
|
5
40
|
Object.defineProperty(exports, "ALL_KEYS", { enumerable: true, get: function () { return config_store_1.ALL_KEYS; } });
|
|
6
41
|
const help_1 = require("../help");
|
|
@@ -16,6 +51,7 @@ class ConfigCommand {
|
|
|
16
51
|
case 'get': return this.get(key);
|
|
17
52
|
case 'set': return this.set(key, value);
|
|
18
53
|
case 'reset': return this.reset(key);
|
|
54
|
+
case 'edit': return this.edit();
|
|
19
55
|
default:
|
|
20
56
|
if (sub)
|
|
21
57
|
console.error(`Unknown subcommand: ${sub}\n`);
|
|
@@ -29,7 +65,7 @@ class ConfigCommand {
|
|
|
29
65
|
cfg = (0, config_store_1.readConfigJson)();
|
|
30
66
|
}
|
|
31
67
|
catch (e) {
|
|
32
|
-
console.error(e.message);
|
|
68
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
33
69
|
process.exit(1);
|
|
34
70
|
}
|
|
35
71
|
if (!cfg) {
|
|
@@ -52,7 +88,7 @@ class ConfigCommand {
|
|
|
52
88
|
cfg = (0, config_store_1.readConfigJson)();
|
|
53
89
|
}
|
|
54
90
|
catch (e) {
|
|
55
|
-
console.error(e.message);
|
|
91
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
56
92
|
process.exit(1);
|
|
57
93
|
}
|
|
58
94
|
if (!cfg) {
|
|
@@ -74,7 +110,7 @@ class ConfigCommand {
|
|
|
74
110
|
console.log(`${key} = ${value}`);
|
|
75
111
|
}
|
|
76
112
|
catch (e) {
|
|
77
|
-
console.error(e.message);
|
|
113
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
78
114
|
process.exit(1);
|
|
79
115
|
}
|
|
80
116
|
}
|
|
@@ -90,7 +126,51 @@ class ConfigCommand {
|
|
|
90
126
|
console.log(`${key} reset to default`);
|
|
91
127
|
}
|
|
92
128
|
catch (e) {
|
|
93
|
-
console.error(e.message);
|
|
129
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
edit() {
|
|
134
|
+
// Ensure config + schema exist so the editor has something to open and
|
|
135
|
+
// resolve the schema against.
|
|
136
|
+
try {
|
|
137
|
+
if (!fs.existsSync(config_store_1.CONFIG_FILE)) {
|
|
138
|
+
(0, config_store_1.writeConfigJson)({});
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
(0, config_store_1.writeSchemaFile)();
|
|
142
|
+
// Migrate existing configs: re-add the "$schema" reference if absent
|
|
143
|
+
// (rewrite through writeConfigJson) so editors bind to the schema.
|
|
144
|
+
// Skip silently if the file is currently invalid — the user will fix
|
|
145
|
+
// it in the editor and validation runs on save.
|
|
146
|
+
try {
|
|
147
|
+
const cfg = (0, config_store_1.readConfigJson)();
|
|
148
|
+
if (cfg)
|
|
149
|
+
(0, config_store_1.writeConfigJson)(cfg);
|
|
150
|
+
}
|
|
151
|
+
catch { /* invalid file — leave as-is for the user to edit */ }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
const editor = process.env.VISUAL ||
|
|
159
|
+
process.env.EDITOR ||
|
|
160
|
+
(process.platform === 'win32' ? 'notepad' : 'nano');
|
|
161
|
+
const res = (0, child_process_1.spawnSync)(editor, [config_store_1.CONFIG_FILE], { stdio: 'inherit' });
|
|
162
|
+
if (res.error) {
|
|
163
|
+
console.error(`Cannot launch editor "${editor}": ${res.error.message}`);
|
|
164
|
+
console.error('Set $EDITOR or edit the file directly: ' + config_store_1.CONFIG_FILE);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
// Validate what the user saved; readConfigJson throws with a helpful message.
|
|
168
|
+
try {
|
|
169
|
+
(0, config_store_1.readConfigJson)();
|
|
170
|
+
console.log('Config OK');
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
94
174
|
process.exit(1);
|
|
95
175
|
}
|
|
96
176
|
}
|
|
@@ -4,6 +4,7 @@ exports.IndexCommand = void 0;
|
|
|
4
4
|
const interrupt_1 = require("../interrupt");
|
|
5
5
|
const project_selector_1 = require("../project-selector");
|
|
6
6
|
const graph_1 = require("../graph");
|
|
7
|
+
const MonorepoAwareDiscoveryService_1 = require("../services/MonorepoAwareDiscoveryService");
|
|
7
8
|
const file_utils_1 = require("../file-utils");
|
|
8
9
|
class IndexCommand {
|
|
9
10
|
container;
|
|
@@ -13,7 +14,7 @@ class IndexCommand {
|
|
|
13
14
|
this.argv1 = argv1;
|
|
14
15
|
}
|
|
15
16
|
async execute() {
|
|
16
|
-
const { db, indexer, config } = this.container;
|
|
17
|
+
const { db, indexer, searcher, config, discovery } = this.container;
|
|
17
18
|
const args = process.argv.slice(this.argv1 === 'rag-index' ? 2 : 3);
|
|
18
19
|
const force = args.includes('--reindex');
|
|
19
20
|
const forceSelect = args.includes('--select');
|
|
@@ -21,10 +22,13 @@ class IndexCommand {
|
|
|
21
22
|
const controller = new AbortController();
|
|
22
23
|
const teardown = (0, interrupt_1.setupInterrupt)(controller);
|
|
23
24
|
try {
|
|
24
|
-
const discovered =
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
25
|
+
const discovered = discovery.discover(config.workspaceRoot);
|
|
26
|
+
const selected = await new project_selector_1.ProjectSelector(config.workspaceRoot).select(discovered, forceSelect);
|
|
27
|
+
if (selected.length === 0)
|
|
27
28
|
return;
|
|
29
|
+
// Expand any selected nested-monorepo unit (e.g. an Nx repo) into its
|
|
30
|
+
// sub-projects; ordinary projects pass through unchanged.
|
|
31
|
+
const projects = (0, MonorepoAwareDiscoveryService_1.toLeafProjects)(selected);
|
|
28
32
|
const filesPerProject = projects.map(p => (0, file_utils_1.collectFiles)(p.path, (0, file_utils_1.loadIgnore)(p.path), p.path));
|
|
29
33
|
const totalFiles = filesPerProject.reduce((sum, f) => sum + f.length, 0);
|
|
30
34
|
console.log(`\n${projects.length} projects — ${totalFiles} files total`);
|
|
@@ -34,7 +38,7 @@ class IndexCommand {
|
|
|
34
38
|
for (let i = 0; i < projects.length; i++) {
|
|
35
39
|
if (controller.signal.aborted)
|
|
36
40
|
break;
|
|
37
|
-
const stats = await indexer.scanAndIndex(projects[i].path, projects[i].name, force, controller.signal, filesPerProject[i]);
|
|
41
|
+
const stats = await indexer.scanAndIndex(projects[i].path, projects[i].name, force, controller.signal, filesPerProject[i], { rootTsConfigPath: projects[i].rootTsConfigPath ?? undefined });
|
|
38
42
|
allStats.push(stats);
|
|
39
43
|
const remaining = projects.length - allStats.length;
|
|
40
44
|
if (remaining > 0 && !controller.signal.aborted) {
|
|
@@ -45,8 +49,19 @@ class IndexCommand {
|
|
|
45
49
|
if (controller.signal.aborted)
|
|
46
50
|
console.log('\nIndexing interrupted.');
|
|
47
51
|
this.printSummary(allStats);
|
|
48
|
-
if (!controller.signal.aborted)
|
|
49
|
-
|
|
52
|
+
if (!controller.signal.aborted) {
|
|
53
|
+
// The graph is a post-index nicety — a failure here (DB dropped after a
|
|
54
|
+
// long run, unreadable dir) must not fail the whole run: indexing already
|
|
55
|
+
// succeeded and the summary is printed.
|
|
56
|
+
try {
|
|
57
|
+
// Real import edges (code_dependency, just written) → intra-repo graph links.
|
|
58
|
+
const projectEdges = await searcher.getProjectEdges();
|
|
59
|
+
(0, graph_1.generateProjectGraph)(projects, projectEdges, config.workspaceRoot);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
console.error(` ⚠ Could not build project graph: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
50
65
|
}
|
|
51
66
|
finally {
|
|
52
67
|
teardown();
|
|
@@ -13,14 +13,15 @@ class McpCommand {
|
|
|
13
13
|
await db.initDb();
|
|
14
14
|
}
|
|
15
15
|
catch (err) {
|
|
16
|
-
startupError = err;
|
|
17
|
-
logger.error('McpCommand: database connection failed', { reason:
|
|
16
|
+
startupError = err instanceof Error ? err : new Error(String(err));
|
|
17
|
+
logger.error('McpCommand: database connection failed', { reason: startupError.message });
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
20
|
await mcp.start(startupError);
|
|
21
21
|
}
|
|
22
22
|
catch (err) {
|
|
23
|
-
|
|
23
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
24
|
+
logger.error('McpCommand: MCP server failed to start', { reason });
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
27
|
// server.connect() resolves immediately after setup; keep the process alive
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Container } from '../container';
|
|
2
|
+
import type { ICommand } from './ICommand';
|
|
3
|
+
export declare class WebCommand implements ICommand {
|
|
4
|
+
private container;
|
|
5
|
+
private readonly port;
|
|
6
|
+
constructor(container: Container, args?: string[]);
|
|
7
|
+
execute(): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebCommand = void 0;
|
|
4
|
+
const WebServerService_1 = require("../services/WebServerService");
|
|
5
|
+
const DEFAULT_PORT = 4319;
|
|
6
|
+
class WebCommand {
|
|
7
|
+
container;
|
|
8
|
+
port;
|
|
9
|
+
constructor(container, args = []) {
|
|
10
|
+
this.container = container;
|
|
11
|
+
this.port = parsePort(args) ?? DEFAULT_PORT;
|
|
12
|
+
}
|
|
13
|
+
async execute() {
|
|
14
|
+
const { db, searcher, config, logger, embedder } = this.container;
|
|
15
|
+
try {
|
|
16
|
+
await db.initDb();
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
20
|
+
logger.error('WebCommand: database connection failed', { reason });
|
|
21
|
+
console.error(`Database connection failed: ${reason}`);
|
|
22
|
+
// Continue: config editing still works; project/graph tabs will error in-page.
|
|
23
|
+
}
|
|
24
|
+
const server = new WebServerService_1.WebServer(searcher, config, logger, embedder);
|
|
25
|
+
const host = '127.0.0.1';
|
|
26
|
+
try {
|
|
27
|
+
await server.start(this.port, host);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
31
|
+
logger.error('WebCommand: server failed to start', { reason });
|
|
32
|
+
console.error(`Failed to start web server on ${host}:${this.port}: ${reason}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.log(`RAG dashboard: http://${host}:${this.port} (Ctrl+C to stop)`);
|
|
36
|
+
// Keep execute() pending until a shutdown signal, otherwise app.ts tears
|
|
37
|
+
// down the DB and exits immediately (mirrors McpCommand's keep-alive).
|
|
38
|
+
await new Promise(resolve => {
|
|
39
|
+
const shutdown = () => { void server.stop().then(resolve); };
|
|
40
|
+
process.once('SIGINT', shutdown);
|
|
41
|
+
process.once('SIGTERM', shutdown);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.WebCommand = WebCommand;
|
|
46
|
+
function parsePort(args) {
|
|
47
|
+
const i = args.indexOf('--port');
|
|
48
|
+
if (i === -1)
|
|
49
|
+
return undefined;
|
|
50
|
+
const raw = args[i + 1];
|
|
51
|
+
const n = Number(raw);
|
|
52
|
+
if (!raw || !Number.isInteger(n) || n < 1 || n > 65535) {
|
|
53
|
+
throw new Error(`Invalid --port value: ${raw ?? '(missing)'}`);
|
|
54
|
+
}
|
|
55
|
+
return n;
|
|
56
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const CONFIG_DIR: string;
|
|
3
3
|
export declare const CONFIG_FILE: string;
|
|
4
|
+
export declare const SCHEMA_FILE: string;
|
|
4
5
|
declare const jsonSchema: z.ZodObject<{
|
|
5
6
|
db: z.ZodOptional<z.ZodObject<{
|
|
6
7
|
host: z.ZodOptional<z.ZodString>;
|
|
@@ -45,8 +46,50 @@ declare const jsonSchema: z.ZodObject<{
|
|
|
45
46
|
}, z.core.$strip>;
|
|
46
47
|
export type JsonConfig = z.infer<typeof jsonSchema>;
|
|
47
48
|
export declare const ALL_KEYS: string[];
|
|
49
|
+
/**
|
|
50
|
+
* Single source of truth for built-in defaults, keyed by raw config.json dotted
|
|
51
|
+
* key. Consumed both by the web dashboard (`GET /api/defaults`) and by
|
|
52
|
+
* `resolvedSchema` in `src/config.ts`, whose `.default(...)` calls reference
|
|
53
|
+
* these values — so the runtime default and the dashboard-displayed default
|
|
54
|
+
* cannot diverge. `as const` keeps the literal types so those `.default()`
|
|
55
|
+
* lookups stay type-safe. Keys omitted here have no static default (required
|
|
56
|
+
* fields like db.host, or values derived at runtime like db.name → rag_<user>
|
|
57
|
+
* and reranker.url → ${ollama.host}/api/rerank).
|
|
58
|
+
*/
|
|
59
|
+
export declare const CONFIG_DEFAULTS: {
|
|
60
|
+
readonly 'db.port': 5433;
|
|
61
|
+
readonly 'ollama.host': "http://localhost:11434";
|
|
62
|
+
readonly 'ollama.model': string;
|
|
63
|
+
readonly 'indexing.chunkSize': 1500;
|
|
64
|
+
readonly 'indexing.chunkOverlap': 200;
|
|
65
|
+
readonly 'indexing.embedBatchSize': 5;
|
|
66
|
+
readonly 'indexing.astBatchSize': 3000;
|
|
67
|
+
readonly 'search.similarityThreshold': 0.6;
|
|
68
|
+
readonly 'search.defaultLimit': 5;
|
|
69
|
+
readonly 'search.maxLimit': 20;
|
|
70
|
+
readonly 'search.autoTranslate': false;
|
|
71
|
+
readonly 'search.translateModel': "llama3.2";
|
|
72
|
+
readonly 'reranker.enabled': false;
|
|
73
|
+
readonly 'reranker.model': "";
|
|
74
|
+
readonly 'reranker.overfetch': 3;
|
|
75
|
+
readonly 'logging.level': "error";
|
|
76
|
+
};
|
|
48
77
|
export declare function readConfigJson(): JsonConfig | null;
|
|
78
|
+
/**
|
|
79
|
+
* Validate a parsed config object against the Zod schema. Unknown keys (e.g.
|
|
80
|
+
* the "$schema" editor reference) are stripped. Throws with a caller-friendly
|
|
81
|
+
* message on validation failure. Pure — no file I/O.
|
|
82
|
+
*/
|
|
83
|
+
export declare function parseConfig(raw: unknown, source?: string): JsonConfig;
|
|
84
|
+
/** Build the JSON Schema object generated from the Zod config schema. Pure. */
|
|
85
|
+
export declare function buildJsonSchema(): Record<string, unknown>;
|
|
49
86
|
export declare function writeConfigJson(data: JsonConfig): void;
|
|
87
|
+
/**
|
|
88
|
+
* Generate a JSON Schema from the Zod config schema and write it next to
|
|
89
|
+
* config.json. Editors resolve it via the "$schema" reference and provide
|
|
90
|
+
* key autocomplete, type hints, and inline validation while editing.
|
|
91
|
+
*/
|
|
92
|
+
export declare function writeSchemaFile(): void;
|
|
50
93
|
export declare function getNestedValue(cfg: JsonConfig, key: string): unknown;
|
|
51
94
|
export declare function setNestedValue(cfg: JsonConfig, key: string, raw: string): JsonConfig;
|
|
52
95
|
export declare function deleteNestedValue(cfg: JsonConfig, key: string): JsonConfig;
|