codesearch-mcp 0.0.17-alpha → 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 +30 -26
- 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
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MonorepoAwareDiscoveryService = void 0;
|
|
4
|
+
exports.toLeafProjects = toLeafProjects;
|
|
5
|
+
const ProjectDiscoveryService_1 = require("./ProjectDiscoveryService");
|
|
6
|
+
/**
|
|
7
|
+
* Expand discovery units into the leaf projects to index: a nested monorepo
|
|
8
|
+
* unit (with `children`) becomes its sub-projects; an ordinary project stays
|
|
9
|
+
* itself. Shared by IndexCommand (indexing loop) and SearcherService (name→path
|
|
10
|
+
* resolution) so both agree on the leaf set.
|
|
11
|
+
*/
|
|
12
|
+
function toLeafProjects(projects) {
|
|
13
|
+
return projects.flatMap(p => (p.children && p.children.length > 0 ? p.children : [p]));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Project discovery that is monorepo-aware.
|
|
17
|
+
*
|
|
18
|
+
* When a monorepo is detected (via the resolver), it uses the resolver
|
|
19
|
+
* to discover projects. Otherwise, it falls back to the original
|
|
20
|
+
* flat-workspace discovery (directories with package.json). Cross-project
|
|
21
|
+
* import resolution relies on the tsconfig `paths` attached via
|
|
22
|
+
* `rootTsConfigPath` — no separate scope map is built.
|
|
23
|
+
*
|
|
24
|
+
* Single Responsibility: only discovers projects and workspace metadata.
|
|
25
|
+
* Does not index or search.
|
|
26
|
+
*
|
|
27
|
+
* Implements {@link IProjectDiscovery} so it can be wired as the default
|
|
28
|
+
* discovery in the container. `discover()` returns one entry per top-level
|
|
29
|
+
* project: when the root itself is a monorepo, each entry carries the
|
|
30
|
+
* workspace-wide `rootTsConfigPath`; under a flat root, an ordinary project
|
|
31
|
+
* gets `rootTsConfigPath: null`, while a top-level directory that is itself a
|
|
32
|
+
* nested monorepo is returned as a single unit with expanded `children` (see
|
|
33
|
+
* `toLeafProjects`). `analyzeWorkspace()` exposes the full workspace metadata
|
|
34
|
+
* (monorepo flag, root tsconfig, project list).
|
|
35
|
+
*/
|
|
36
|
+
class MonorepoAwareDiscoveryService {
|
|
37
|
+
resolvers;
|
|
38
|
+
logger;
|
|
39
|
+
flatDiscovery;
|
|
40
|
+
constructor(resolvers, logger,
|
|
41
|
+
/** Discovery used when no monorepo is detected. Defaults to flat package.json scan. */
|
|
42
|
+
flatDiscovery = new ProjectDiscoveryService_1.ProjectDiscoveryService()) {
|
|
43
|
+
this.resolvers = resolvers;
|
|
44
|
+
this.logger = logger;
|
|
45
|
+
this.flatDiscovery = flatDiscovery;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* IProjectDiscovery contract: enumerate projects in the workspace.
|
|
49
|
+
* Attaches the workspace-wide root tsconfig to every project so the
|
|
50
|
+
* indexer can resolve cross-project path aliases.
|
|
51
|
+
*/
|
|
52
|
+
discover(rootDir) {
|
|
53
|
+
const workspace = this.analyzeWorkspace(rootDir);
|
|
54
|
+
// Case A: workspaceRoot itself is a monorepo — attach the workspace-wide
|
|
55
|
+
// tsconfig to every project (original behavior).
|
|
56
|
+
if (workspace.isMonorepo) {
|
|
57
|
+
return workspace.projects.map(p => ({ ...p, rootTsConfigPath: workspace.rootTsConfigPath }));
|
|
58
|
+
}
|
|
59
|
+
// Case B: flat workspaceRoot — a top-level project directory may itself be a
|
|
60
|
+
// nested monorepo (e.g. an Nx repo). Expand such a directory into its
|
|
61
|
+
// sub-projects as `children`, keeping the repo a single selectable unit.
|
|
62
|
+
return workspace.projects.map(p => this.expandIfNestedMonorepo(p));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* If `project`'s directory is itself a monorepo (resolver detects it),
|
|
66
|
+
* return it with expanded `children` named `<repo>/<sub-project>`, each
|
|
67
|
+
* carrying the repo's workspace-wide tsconfig. Otherwise return it unchanged.
|
|
68
|
+
*/
|
|
69
|
+
expandIfNestedMonorepo(project) {
|
|
70
|
+
// Ordinary flat project — preserve the original contract (null root tsconfig).
|
|
71
|
+
const flat = { ...project, rootTsConfigPath: null };
|
|
72
|
+
const resolver = this.resolvers.find(r => r.isMonorepo(project.path));
|
|
73
|
+
if (!resolver)
|
|
74
|
+
return flat;
|
|
75
|
+
const rootTsConfigPath = resolver.getRootTsConfigPath(project.path);
|
|
76
|
+
const children = resolver.discoverProjects(project.path).map(c => ({
|
|
77
|
+
name: `${project.name}/${c.name}`,
|
|
78
|
+
path: c.path,
|
|
79
|
+
packageName: c.packageName,
|
|
80
|
+
type: c.type,
|
|
81
|
+
rootTsConfigPath,
|
|
82
|
+
}));
|
|
83
|
+
if (children.length === 0)
|
|
84
|
+
return flat;
|
|
85
|
+
this.logger.info('MonorepoAwareDiscovery: nested monorepo expanded', {
|
|
86
|
+
repo: project.name,
|
|
87
|
+
projects: children.length,
|
|
88
|
+
rootTsConfigPath,
|
|
89
|
+
});
|
|
90
|
+
return { ...project, children };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Analyze the workspace root and return full workspace information:
|
|
94
|
+
* monorepo detection flag, root tsconfig path, and project list.
|
|
95
|
+
*/
|
|
96
|
+
analyzeWorkspace(rootDir) {
|
|
97
|
+
for (const resolver of this.resolvers) {
|
|
98
|
+
if (resolver.isMonorepo(rootDir)) {
|
|
99
|
+
this.logger.info('MonorepoAwareDiscovery: monorepo detected', { rootDir });
|
|
100
|
+
const monoProjects = resolver.discoverProjects(rootDir);
|
|
101
|
+
const rootTsConfigPath = resolver.getRootTsConfigPath(rootDir);
|
|
102
|
+
const projects = monoProjects.map(p => ({
|
|
103
|
+
name: p.name,
|
|
104
|
+
path: p.path,
|
|
105
|
+
packageName: p.packageName,
|
|
106
|
+
type: p.type,
|
|
107
|
+
}));
|
|
108
|
+
this.logger.debug('MonorepoAwareDiscovery: workspace analyzed', {
|
|
109
|
+
projects: projects.length,
|
|
110
|
+
rootTsConfigPath,
|
|
111
|
+
});
|
|
112
|
+
return { isMonorepo: true, rootTsConfigPath, projects };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Fallback: flat workspace (original behavior) — delegated to the default discovery.
|
|
116
|
+
this.logger.debug('MonorepoAwareDiscovery: no monorepo detected, using flat discovery', { rootDir });
|
|
117
|
+
const projects = this.flatDiscovery.discover(rootDir);
|
|
118
|
+
return {
|
|
119
|
+
isMonorepo: false,
|
|
120
|
+
rootTsConfigPath: null,
|
|
121
|
+
projects,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.MonorepoAwareDiscoveryService = MonorepoAwareDiscoveryService;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { IMonorepoResolver, MonorepoProject } from '../interfaces/IMonorepoResolver';
|
|
2
|
+
import type { ILogger } from '../interfaces';
|
|
3
|
+
/**
|
|
4
|
+
* Nx monorepo resolver.
|
|
5
|
+
*
|
|
6
|
+
* Detection: presence of `nx.json` at the workspace root.
|
|
7
|
+
*
|
|
8
|
+
* Project discovery strategies (results deduplicated by absolute path):
|
|
9
|
+
* 1. Read `workspace.json` or `angular.json` → projects map
|
|
10
|
+
* 2. Scan standard Nx directories (`apps/`, `libs/`, `packages/`) for
|
|
11
|
+
* `project.json`, then `package.json` (only when strategy 1 found nothing)
|
|
12
|
+
* 3. `tsconfig.base.json` `paths` — root-level libs referenced only by path
|
|
13
|
+
* alias (e.g. `contracts/`, `interfaces/`) that the directory scan misses
|
|
14
|
+
* 4. Root-level fallback: treat the workspace root itself as a project
|
|
15
|
+
* (standalone Nx setup) when nothing else matched
|
|
16
|
+
*
|
|
17
|
+
* tsconfig resolution:
|
|
18
|
+
* - Returns `tsconfig.base.json` at workspace root (Nx convention)
|
|
19
|
+
* - Falls back to `tsconfig.json` at workspace root
|
|
20
|
+
*
|
|
21
|
+
* Cross-project import resolution is handled by the AST parser via the
|
|
22
|
+
* workspace-wide tsconfig `paths` (tsconfig.base.json), so no scope map is built here.
|
|
23
|
+
*/
|
|
24
|
+
export declare class NxMonorepoResolver implements IMonorepoResolver {
|
|
25
|
+
private logger;
|
|
26
|
+
constructor(logger: ILogger);
|
|
27
|
+
isMonorepo(rootDir: string): boolean;
|
|
28
|
+
discoverProjects(rootDir: string): MonorepoProject[];
|
|
29
|
+
/**
|
|
30
|
+
* Discover projects from `tsconfig.base.json` `compilerOptions.paths`. Each
|
|
31
|
+
* alias (e.g. `@scope/interfaces` → `interfaces/src/index.ts`) resolves to a
|
|
32
|
+
* lib root (the path before `/src/`), which for real Nx libs contains a
|
|
33
|
+
* project.json. Aliases pointing at non-projects (no project.json/package.json,
|
|
34
|
+
* e.g. `@env` → `environments/environment.ts`) are skipped.
|
|
35
|
+
*/
|
|
36
|
+
private discoverFromTsconfigPaths;
|
|
37
|
+
/** Parse JSON that may contain comments / trailing commas (tsconfig style). */
|
|
38
|
+
private parseJsonc;
|
|
39
|
+
getRootTsConfigPath(rootDir: string): string | null;
|
|
40
|
+
private readWorkspaceJson;
|
|
41
|
+
private scanDirectory;
|
|
42
|
+
private tryReadProject;
|
|
43
|
+
private readPackageName;
|
|
44
|
+
private inferProjectType;
|
|
45
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.NxMonorepoResolver = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const file_utils_1 = require("../file-utils");
|
|
40
|
+
/**
|
|
41
|
+
* Nx monorepo resolver.
|
|
42
|
+
*
|
|
43
|
+
* Detection: presence of `nx.json` at the workspace root.
|
|
44
|
+
*
|
|
45
|
+
* Project discovery strategies (results deduplicated by absolute path):
|
|
46
|
+
* 1. Read `workspace.json` or `angular.json` → projects map
|
|
47
|
+
* 2. Scan standard Nx directories (`apps/`, `libs/`, `packages/`) for
|
|
48
|
+
* `project.json`, then `package.json` (only when strategy 1 found nothing)
|
|
49
|
+
* 3. `tsconfig.base.json` `paths` — root-level libs referenced only by path
|
|
50
|
+
* alias (e.g. `contracts/`, `interfaces/`) that the directory scan misses
|
|
51
|
+
* 4. Root-level fallback: treat the workspace root itself as a project
|
|
52
|
+
* (standalone Nx setup) when nothing else matched
|
|
53
|
+
*
|
|
54
|
+
* tsconfig resolution:
|
|
55
|
+
* - Returns `tsconfig.base.json` at workspace root (Nx convention)
|
|
56
|
+
* - Falls back to `tsconfig.json` at workspace root
|
|
57
|
+
*
|
|
58
|
+
* Cross-project import resolution is handled by the AST parser via the
|
|
59
|
+
* workspace-wide tsconfig `paths` (tsconfig.base.json), so no scope map is built here.
|
|
60
|
+
*/
|
|
61
|
+
class NxMonorepoResolver {
|
|
62
|
+
logger;
|
|
63
|
+
constructor(logger) {
|
|
64
|
+
this.logger = logger;
|
|
65
|
+
}
|
|
66
|
+
isMonorepo(rootDir) {
|
|
67
|
+
return fs.existsSync(path.join(rootDir, 'nx.json'));
|
|
68
|
+
}
|
|
69
|
+
discoverProjects(rootDir) {
|
|
70
|
+
// Deduplicate across strategies by absolute project path.
|
|
71
|
+
const byPath = new Map();
|
|
72
|
+
const add = (p) => { if (p && !byPath.has(p.path))
|
|
73
|
+
byPath.set(p.path, p); };
|
|
74
|
+
// Strategy 1: workspace.json / angular.json (authoritative when present)
|
|
75
|
+
const workspaceProjects = this.readWorkspaceJson(rootDir);
|
|
76
|
+
for (const p of workspaceProjects)
|
|
77
|
+
add(p);
|
|
78
|
+
// Strategy 2 & 3: scan standard directories (only when no workspace.json map)
|
|
79
|
+
if (byPath.size === 0) {
|
|
80
|
+
for (const dir of ['apps', 'libs', 'packages']) {
|
|
81
|
+
const fullDir = path.join(rootDir, dir);
|
|
82
|
+
if (!fs.existsSync(fullDir))
|
|
83
|
+
continue;
|
|
84
|
+
const type = dir === 'apps' ? 'app' : 'lib';
|
|
85
|
+
for (const p of this.scanDirectory(fullDir, type, rootDir))
|
|
86
|
+
add(p);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Strategy 4: tsconfig.base.json `paths` — captures libs that live at the
|
|
90
|
+
// repo root (e.g. `contracts/`, `interfaces/`) referenced by path aliases,
|
|
91
|
+
// which the apps/libs/packages scan misses. Only real Nx projects (with a
|
|
92
|
+
// project.json/package.json) are added; dedup handles overlap with the scan.
|
|
93
|
+
for (const p of this.discoverFromTsconfigPaths(rootDir))
|
|
94
|
+
add(p);
|
|
95
|
+
// Root-level fallback (standalone Nx setup)
|
|
96
|
+
if (byPath.size === 0) {
|
|
97
|
+
const rootProject = this.tryReadProject(rootDir, rootDir);
|
|
98
|
+
if (rootProject)
|
|
99
|
+
add(rootProject);
|
|
100
|
+
}
|
|
101
|
+
const projects = [...byPath.values()];
|
|
102
|
+
this.logger.debug('NxMonorepoResolver: discovered projects', { count: projects.length });
|
|
103
|
+
return projects;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Discover projects from `tsconfig.base.json` `compilerOptions.paths`. Each
|
|
107
|
+
* alias (e.g. `@scope/interfaces` → `interfaces/src/index.ts`) resolves to a
|
|
108
|
+
* lib root (the path before `/src/`), which for real Nx libs contains a
|
|
109
|
+
* project.json. Aliases pointing at non-projects (no project.json/package.json,
|
|
110
|
+
* e.g. `@env` → `environments/environment.ts`) are skipped.
|
|
111
|
+
*/
|
|
112
|
+
discoverFromTsconfigPaths(rootDir) {
|
|
113
|
+
const tsconfigPath = this.getRootTsConfigPath(rootDir);
|
|
114
|
+
if (!tsconfigPath)
|
|
115
|
+
return [];
|
|
116
|
+
let json;
|
|
117
|
+
try {
|
|
118
|
+
json = this.parseJsonc(fs.readFileSync(tsconfigPath, 'utf-8'));
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
this.logger.warn('NxMonorepoResolver: failed to parse tsconfig paths', { file: tsconfigPath, reason: err.message });
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const co = json?.compilerOptions ?? {};
|
|
125
|
+
const baseUrl = typeof co.baseUrl === 'string' ? co.baseUrl : '.';
|
|
126
|
+
const paths = co.paths;
|
|
127
|
+
if (!paths || typeof paths !== 'object')
|
|
128
|
+
return [];
|
|
129
|
+
const projects = [];
|
|
130
|
+
for (const targets of Object.values(paths)) {
|
|
131
|
+
const target = Array.isArray(targets) ? targets[0] : targets;
|
|
132
|
+
if (typeof target !== 'string')
|
|
133
|
+
continue;
|
|
134
|
+
const srcIdx = target.indexOf('/src/');
|
|
135
|
+
const relRoot = srcIdx >= 0 ? target.slice(0, srcIdx) : path.dirname(target);
|
|
136
|
+
const libRoot = path.resolve(rootDir, baseUrl, relRoot);
|
|
137
|
+
const project = this.tryReadProject(libRoot, rootDir); // null unless a real project marker exists
|
|
138
|
+
if (project)
|
|
139
|
+
projects.push(project);
|
|
140
|
+
}
|
|
141
|
+
return projects;
|
|
142
|
+
}
|
|
143
|
+
/** Parse JSON that may contain comments / trailing commas (tsconfig style). */
|
|
144
|
+
parseJsonc(text) {
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(text);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
const stripped = text
|
|
150
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
151
|
+
.replace(/(^|[^:])\/\/.*$/gm, '$1')
|
|
152
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
153
|
+
return JSON.parse(stripped);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
getRootTsConfigPath(rootDir) {
|
|
157
|
+
// Nx convention: tsconfig.base.json at workspace root
|
|
158
|
+
const basePath = path.join(rootDir, 'tsconfig.base.json');
|
|
159
|
+
if (fs.existsSync(basePath))
|
|
160
|
+
return basePath;
|
|
161
|
+
// Fallback: tsconfig.json at workspace root
|
|
162
|
+
const rootPath = path.join(rootDir, 'tsconfig.json');
|
|
163
|
+
if (fs.existsSync(rootPath))
|
|
164
|
+
return rootPath;
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
168
|
+
readWorkspaceJson(rootDir) {
|
|
169
|
+
const projects = [];
|
|
170
|
+
for (const filename of ['workspace.json', 'angular.json']) {
|
|
171
|
+
const filePath = path.join(rootDir, filename);
|
|
172
|
+
if (!fs.existsSync(filePath))
|
|
173
|
+
continue;
|
|
174
|
+
try {
|
|
175
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
176
|
+
const projectsMap = content.projects ?? {};
|
|
177
|
+
for (const [name, value] of Object.entries(projectsMap)) {
|
|
178
|
+
// value can be a string (path) or an object with a `root` property.
|
|
179
|
+
// Guard per-entry: a malformed entry (object without `root`) must not
|
|
180
|
+
// abort discovery of the remaining projects in this file.
|
|
181
|
+
const rawRoot = typeof value === 'string'
|
|
182
|
+
? value
|
|
183
|
+
: value?.root;
|
|
184
|
+
if (typeof rawRoot !== 'string' || rawRoot.length === 0) {
|
|
185
|
+
this.logger.warn('NxMonorepoResolver: skipping workspace entry without valid root', { file: filePath, name });
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const projectPath = path.resolve(rootDir, rawRoot);
|
|
189
|
+
if (!fs.existsSync(projectPath))
|
|
190
|
+
continue;
|
|
191
|
+
const type = this.inferProjectType(projectPath, name);
|
|
192
|
+
const packageName = this.readPackageName(projectPath);
|
|
193
|
+
projects.push({ name, path: projectPath, packageName, type });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
this.logger.warn('NxMonorepoResolver: failed to parse workspace file', {
|
|
198
|
+
file: filePath,
|
|
199
|
+
reason: err.message,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return projects;
|
|
204
|
+
}
|
|
205
|
+
scanDirectory(dir, defaultType, rootDir) {
|
|
206
|
+
const projects = [];
|
|
207
|
+
let entries;
|
|
208
|
+
try {
|
|
209
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return projects;
|
|
213
|
+
}
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
if (!entry.isDirectory())
|
|
216
|
+
continue;
|
|
217
|
+
if (file_utils_1.IGNORE_DIRS.has(entry.name))
|
|
218
|
+
continue;
|
|
219
|
+
const projectPath = path.join(dir, entry.name);
|
|
220
|
+
const project = this.tryReadProject(projectPath, rootDir, defaultType);
|
|
221
|
+
if (project) {
|
|
222
|
+
projects.push(project);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
// Check for nested grouping directories (e.g. libs/shared/utils)
|
|
226
|
+
const nested = this.scanDirectory(projectPath, defaultType, rootDir);
|
|
227
|
+
projects.push(...nested);
|
|
228
|
+
}
|
|
229
|
+
return projects;
|
|
230
|
+
}
|
|
231
|
+
tryReadProject(projectPath, rootDir, defaultType = 'lib') {
|
|
232
|
+
// Check for project.json (Nx project marker)
|
|
233
|
+
const projectJsonPath = path.join(projectPath, 'project.json');
|
|
234
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
235
|
+
const hasProjectJson = fs.existsSync(projectJsonPath);
|
|
236
|
+
const hasPackageJson = fs.existsSync(packageJsonPath);
|
|
237
|
+
if (!hasProjectJson && !hasPackageJson)
|
|
238
|
+
return null;
|
|
239
|
+
let name = path.relative(rootDir, projectPath).replace(/\\/g, '/');
|
|
240
|
+
let type = defaultType;
|
|
241
|
+
let packageName = null;
|
|
242
|
+
// Read project.json for name and type
|
|
243
|
+
if (hasProjectJson) {
|
|
244
|
+
try {
|
|
245
|
+
const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
|
|
246
|
+
if (projectJson.name)
|
|
247
|
+
name = projectJson.name;
|
|
248
|
+
if (projectJson.projectType === 'application')
|
|
249
|
+
type = 'app';
|
|
250
|
+
else if (projectJson.projectType === 'library')
|
|
251
|
+
type = 'lib';
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
this.logger.warn('NxMonorepoResolver: failed to parse project.json', {
|
|
255
|
+
file: projectJsonPath,
|
|
256
|
+
reason: err.message,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Read package.json for npm scope
|
|
261
|
+
if (hasPackageJson) {
|
|
262
|
+
packageName = this.readPackageName(projectPath);
|
|
263
|
+
// If no project.json name, use package name without scope
|
|
264
|
+
if (!hasProjectJson && packageName) {
|
|
265
|
+
const shortName = packageName.replace(/^@[^/]+\//, '');
|
|
266
|
+
name = shortName;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { name, path: projectPath, packageName, type };
|
|
270
|
+
}
|
|
271
|
+
readPackageName(projectPath) {
|
|
272
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
273
|
+
if (!fs.existsSync(packageJsonPath))
|
|
274
|
+
return null;
|
|
275
|
+
try {
|
|
276
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
277
|
+
return typeof pkg.name === 'string' ? pkg.name : null;
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
inferProjectType(projectPath, name) {
|
|
284
|
+
// Check project.json
|
|
285
|
+
const projectJsonPath = path.join(projectPath, 'project.json');
|
|
286
|
+
if (fs.existsSync(projectJsonPath)) {
|
|
287
|
+
try {
|
|
288
|
+
const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
|
|
289
|
+
if (projectJson.projectType === 'application')
|
|
290
|
+
return 'app';
|
|
291
|
+
if (projectJson.projectType === 'library')
|
|
292
|
+
return 'lib';
|
|
293
|
+
}
|
|
294
|
+
catch { /* fall through */ }
|
|
295
|
+
}
|
|
296
|
+
// Infer from path
|
|
297
|
+
const rel = projectPath.replace(/\\/g, '/');
|
|
298
|
+
if (rel.includes('/apps/') || rel.includes('/app/'))
|
|
299
|
+
return 'app';
|
|
300
|
+
if (rel.includes('/libs/') || rel.includes('/lib/') || rel.includes('/packages/'))
|
|
301
|
+
return 'lib';
|
|
302
|
+
// Infer from name
|
|
303
|
+
if (name.includes('lib') || name.includes('shared') || name.includes('util'))
|
|
304
|
+
return 'lib';
|
|
305
|
+
return 'app';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
exports.NxMonorepoResolver = NxMonorepoResolver;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Config } from '../config';
|
|
2
|
-
import type { IEmbedder } from '../interfaces
|
|
2
|
+
import type { IEmbedder } from '../interfaces';
|
|
3
|
+
import type { ILogger } from '../interfaces';
|
|
3
4
|
export declare class OllamaEmbedder implements IEmbedder {
|
|
4
5
|
private config;
|
|
6
|
+
private logger;
|
|
5
7
|
private client;
|
|
6
8
|
rerank?: (query: string, documents: string[], topN: number) => Promise<number[]>;
|
|
7
|
-
constructor(config: Config);
|
|
9
|
+
constructor(config: Config, logger?: ILogger);
|
|
8
10
|
embed(text: string): Promise<number[]>;
|
|
11
|
+
listModels(): Promise<string[]>;
|
|
9
12
|
translate(text: string, model: string): Promise<string>;
|
|
10
13
|
private _rerank;
|
|
11
14
|
}
|
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OllamaEmbedder = void 0;
|
|
4
4
|
const ollama_1 = require("ollama");
|
|
5
|
+
const LoggerService_1 = require("./LoggerService");
|
|
5
6
|
class OllamaEmbedder {
|
|
6
7
|
config;
|
|
8
|
+
logger;
|
|
7
9
|
client;
|
|
8
10
|
rerank;
|
|
9
|
-
constructor(config) {
|
|
11
|
+
constructor(config, logger = LoggerService_1.LoggerService.silent()) {
|
|
10
12
|
this.config = config;
|
|
13
|
+
this.logger = logger;
|
|
11
14
|
this.client = new ollama_1.Ollama({ host: config.ollama.host });
|
|
12
15
|
if (config.search.rerankerEnabled) {
|
|
13
16
|
if (config.ollama.rerankerModel) {
|
|
14
17
|
this.rerank = this._rerank.bind(this);
|
|
15
18
|
}
|
|
16
19
|
else {
|
|
17
|
-
|
|
20
|
+
this.logger.warn('OllamaEmbedder: rerankerEnabled=true but rerankerModel is not set — reranking disabled');
|
|
18
21
|
}
|
|
19
22
|
}
|
|
20
23
|
}
|
|
@@ -25,6 +28,10 @@ class OllamaEmbedder {
|
|
|
25
28
|
});
|
|
26
29
|
return embedding;
|
|
27
30
|
}
|
|
31
|
+
async listModels() {
|
|
32
|
+
const res = await this.client.list();
|
|
33
|
+
return res.models.map(m => m.name).filter(Boolean).sort();
|
|
34
|
+
}
|
|
28
35
|
async translate(text, model) {
|
|
29
36
|
const response = await this.client.chat({
|
|
30
37
|
model,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IProjectDiscovery, DiscoveredProject } from '../interfaces/IProjectDiscovery';
|
|
2
|
+
/**
|
|
3
|
+
* Default project discovery: finds immediate subdirectories
|
|
4
|
+
* of the workspace root that contain a package.json.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ProjectDiscoveryService implements IProjectDiscovery {
|
|
7
|
+
discover(rootDir: string): DiscoveredProject[];
|
|
8
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ProjectDiscoveryService = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const file_utils_1 = require("../file-utils");
|
|
40
|
+
/**
|
|
41
|
+
* Default project discovery: finds immediate subdirectories
|
|
42
|
+
* of the workspace root that contain a package.json.
|
|
43
|
+
*/
|
|
44
|
+
class ProjectDiscoveryService {
|
|
45
|
+
discover(rootDir) {
|
|
46
|
+
return fs.readdirSync(rootDir, { withFileTypes: true })
|
|
47
|
+
.filter(d => d.isDirectory() && !file_utils_1.IGNORE_DIRS.has(d.name))
|
|
48
|
+
.map(d => ({ name: d.name, path: path.join(rootDir, d.name) }))
|
|
49
|
+
.filter(p => fs.existsSync(path.join(p.path, 'package.json')));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.ProjectDiscoveryService = ProjectDiscoveryService;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Config } from '../config';
|
|
2
|
-
import type { IEmbedder } from '../interfaces
|
|
3
|
-
import type {
|
|
2
|
+
import type { IEmbedder } from '../interfaces';
|
|
3
|
+
import type { IDatabase } from '../interfaces';
|
|
4
4
|
import { type ProjectMeta } from '../file-utils';
|
|
5
|
-
import {
|
|
5
|
+
import type { ILogger } from '../interfaces';
|
|
6
|
+
import type { IProjectDiscovery } from '../interfaces';
|
|
6
7
|
export interface ChunkRow {
|
|
7
8
|
serviceName: string;
|
|
8
9
|
filePath: string;
|
|
@@ -44,6 +45,10 @@ export interface DependencyRow {
|
|
|
44
45
|
export interface ProjectRow extends ProjectMeta {
|
|
45
46
|
serviceName: string;
|
|
46
47
|
chunks: number;
|
|
48
|
+
packageName?: string | null;
|
|
49
|
+
type?: 'app' | 'lib';
|
|
50
|
+
/** Real filesystem root of the project (resolved via discovery). */
|
|
51
|
+
path?: string;
|
|
47
52
|
}
|
|
48
53
|
export interface GrepMatch {
|
|
49
54
|
filePath: string;
|
|
@@ -67,9 +72,32 @@ export interface SymbolHit {
|
|
|
67
72
|
export declare class SearcherService {
|
|
68
73
|
private db;
|
|
69
74
|
private embedder;
|
|
75
|
+
private discovery;
|
|
70
76
|
private config;
|
|
71
77
|
private logger;
|
|
72
|
-
constructor(db:
|
|
78
|
+
constructor(db: IDatabase, embedder: IEmbedder, discovery: IProjectDiscovery, config: Config, logger?: ILogger);
|
|
79
|
+
/** Lazily-built, cached map of project name → real filesystem root. */
|
|
80
|
+
private rootMap?;
|
|
81
|
+
/** Cached, lazily-built map of leaf project name → real filesystem root (via discovery). */
|
|
82
|
+
private projectRootMap;
|
|
83
|
+
/**
|
|
84
|
+
* Resolve a project name to its real filesystem root. Uses discovery (leaf
|
|
85
|
+
* projects) so nested-monorepo names like `repo/app` map to their true path;
|
|
86
|
+
* falls back to `join(workspaceRoot, name)` when the name is unknown (e.g. a
|
|
87
|
+
* project since removed from disk, or a flat project not surfaced by discovery).
|
|
88
|
+
*/
|
|
89
|
+
private resolveProjectRoot;
|
|
90
|
+
/**
|
|
91
|
+
* Project-level dependency edges aggregated from `code_dependency`: an edge
|
|
92
|
+
* `A → B` means some file in project A imports a file owned by project B.
|
|
93
|
+
* Used by the graph to draw links between a monorepo's sub-projects. Only
|
|
94
|
+
* edges whose target file belongs to an indexed project are returned (targets
|
|
95
|
+
* outside any indexed project — e.g. unindexed libs — are skipped).
|
|
96
|
+
*/
|
|
97
|
+
getProjectEdges(): Promise<{
|
|
98
|
+
from: string;
|
|
99
|
+
to: string;
|
|
100
|
+
}[]>;
|
|
73
101
|
prepareQuery(raw: string): Promise<{
|
|
74
102
|
query: string;
|
|
75
103
|
translated: boolean;
|
|
@@ -81,10 +109,10 @@ export declare class SearcherService {
|
|
|
81
109
|
getDependencies(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
|
|
82
110
|
getDependents(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
|
|
83
111
|
getCallers(symbol: string, project: string, calleeFile?: string | null): Promise<CallerRow[]>;
|
|
84
|
-
grepSymbol(symbol: string, project: string, excludeFile: string): {
|
|
112
|
+
grepSymbol(symbol: string, project: string, excludeFile: string): Promise<{
|
|
85
113
|
matches: GrepMatch[];
|
|
86
114
|
truncated: boolean;
|
|
87
|
-
}
|
|
115
|
+
}>;
|
|
88
116
|
findSymbol(name: string, project: string | null, kind: SymbolKind): Promise<{
|
|
89
117
|
hits: SymbolHit[];
|
|
90
118
|
truncated: boolean;
|
|
@@ -101,4 +129,9 @@ export declare class SearcherService {
|
|
|
101
129
|
private walkDir;
|
|
102
130
|
private buildFindSymbolPattern;
|
|
103
131
|
private inferKind;
|
|
132
|
+
/**
|
|
133
|
+
* Async wrapper around grep to avoid blocking the event loop.
|
|
134
|
+
* Returns stdout as a string, or null if grep returned no results or errored.
|
|
135
|
+
*/
|
|
136
|
+
private spawnGrep;
|
|
104
137
|
}
|