@zuvia-software-solutions/code-mapper 1.4.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.
- package/README.md +215 -0
- package/dist/cli/ai-context.d.ts +19 -0
- package/dist/cli/ai-context.js +168 -0
- package/dist/cli/analyze.d.ts +7 -0
- package/dist/cli/analyze.js +325 -0
- package/dist/cli/augment.d.ts +7 -0
- package/dist/cli/augment.js +27 -0
- package/dist/cli/clean.d.ts +5 -0
- package/dist/cli/clean.js +56 -0
- package/dist/cli/eval-server.d.ts +25 -0
- package/dist/cli/eval-server.js +365 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +102 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +19 -0
- package/dist/cli/list.d.ts +2 -0
- package/dist/cli/list.js +27 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +35 -0
- package/dist/cli/refresh.d.ts +12 -0
- package/dist/cli/refresh.js +165 -0
- package/dist/cli/serve.d.ts +5 -0
- package/dist/cli/serve.js +8 -0
- package/dist/cli/setup.d.ts +6 -0
- package/dist/cli/setup.js +218 -0
- package/dist/cli/status.d.ts +2 -0
- package/dist/cli/status.js +33 -0
- package/dist/cli/tool.d.ts +28 -0
- package/dist/cli/tool.js +87 -0
- package/dist/config/ignore-service.d.ts +32 -0
- package/dist/config/ignore-service.js +282 -0
- package/dist/config/supported-languages.d.ts +23 -0
- package/dist/config/supported-languages.js +52 -0
- package/dist/core/augmentation/engine.d.ts +22 -0
- package/dist/core/augmentation/engine.js +232 -0
- package/dist/core/embeddings/embedder.d.ts +35 -0
- package/dist/core/embeddings/embedder.js +171 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +41 -0
- package/dist/core/embeddings/embedding-pipeline.js +402 -0
- package/dist/core/embeddings/index.d.ts +5 -0
- package/dist/core/embeddings/index.js +6 -0
- package/dist/core/embeddings/text-generator.d.ts +20 -0
- package/dist/core/embeddings/text-generator.js +159 -0
- package/dist/core/embeddings/types.d.ts +60 -0
- package/dist/core/embeddings/types.js +23 -0
- package/dist/core/graph/graph.d.ts +4 -0
- package/dist/core/graph/graph.js +65 -0
- package/dist/core/graph/types.d.ts +69 -0
- package/dist/core/graph/types.js +3 -0
- package/dist/core/incremental/child-process.d.ts +8 -0
- package/dist/core/incremental/child-process.js +649 -0
- package/dist/core/incremental/refresh-coordinator.d.ts +32 -0
- package/dist/core/incremental/refresh-coordinator.js +147 -0
- package/dist/core/incremental/types.d.ts +78 -0
- package/dist/core/incremental/types.js +153 -0
- package/dist/core/incremental/watcher.d.ts +63 -0
- package/dist/core/incremental/watcher.js +338 -0
- package/dist/core/ingestion/ast-cache.d.ts +12 -0
- package/dist/core/ingestion/ast-cache.js +34 -0
- package/dist/core/ingestion/call-processor.d.ts +34 -0
- package/dist/core/ingestion/call-processor.js +937 -0
- package/dist/core/ingestion/call-routing.d.ts +40 -0
- package/dist/core/ingestion/call-routing.js +97 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +30 -0
- package/dist/core/ingestion/cluster-enricher.js +151 -0
- package/dist/core/ingestion/community-processor.d.ts +26 -0
- package/dist/core/ingestion/community-processor.js +272 -0
- package/dist/core/ingestion/constants.d.ts +5 -0
- package/dist/core/ingestion/constants.js +8 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +23 -0
- package/dist/core/ingestion/entry-point-scoring.js +317 -0
- package/dist/core/ingestion/export-detection.d.ts +11 -0
- package/dist/core/ingestion/export-detection.js +203 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +18 -0
- package/dist/core/ingestion/filesystem-walker.js +64 -0
- package/dist/core/ingestion/framework-detection.d.ts +42 -0
- package/dist/core/ingestion/framework-detection.js +405 -0
- package/dist/core/ingestion/heritage-processor.d.ts +15 -0
- package/dist/core/ingestion/heritage-processor.js +237 -0
- package/dist/core/ingestion/import-processor.d.ts +31 -0
- package/dist/core/ingestion/import-processor.js +416 -0
- package/dist/core/ingestion/language-config.d.ts +32 -0
- package/dist/core/ingestion/language-config.js +161 -0
- package/dist/core/ingestion/mro-processor.d.ts +32 -0
- package/dist/core/ingestion/mro-processor.js +343 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +51 -0
- package/dist/core/ingestion/named-binding-extraction.js +343 -0
- package/dist/core/ingestion/parsing-processor.d.ts +20 -0
- package/dist/core/ingestion/parsing-processor.js +282 -0
- package/dist/core/ingestion/pipeline.d.ts +3 -0
- package/dist/core/ingestion/pipeline.js +416 -0
- package/dist/core/ingestion/process-processor.d.ts +42 -0
- package/dist/core/ingestion/process-processor.js +357 -0
- package/dist/core/ingestion/resolution-context.d.ts +40 -0
- package/dist/core/ingestion/resolution-context.js +171 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +10 -0
- package/dist/core/ingestion/resolvers/csharp.js +101 -0
- package/dist/core/ingestion/resolvers/go.d.ts +8 -0
- package/dist/core/ingestion/resolvers/go.js +33 -0
- package/dist/core/ingestion/resolvers/index.d.ts +14 -0
- package/dist/core/ingestion/resolvers/index.js +10 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +9 -0
- package/dist/core/ingestion/resolvers/jvm.js +74 -0
- package/dist/core/ingestion/resolvers/php.d.ts +7 -0
- package/dist/core/ingestion/resolvers/php.js +30 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +9 -0
- package/dist/core/ingestion/resolvers/ruby.js +13 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +5 -0
- package/dist/core/ingestion/resolvers/rust.js +62 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +16 -0
- package/dist/core/ingestion/resolvers/standard.js +144 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +18 -0
- package/dist/core/ingestion/resolvers/utils.js +113 -0
- package/dist/core/ingestion/structure-processor.d.ts +4 -0
- package/dist/core/ingestion/structure-processor.js +39 -0
- package/dist/core/ingestion/symbol-table.d.ts +34 -0
- package/dist/core/ingestion/symbol-table.js +48 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +20 -0
- package/dist/core/ingestion/tree-sitter-queries.js +691 -0
- package/dist/core/ingestion/type-env.d.ts +52 -0
- package/dist/core/ingestion/type-env.js +349 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +214 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/csharp.js +224 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/go.js +261 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +20 -0
- package/dist/core/ingestion/type-extractors/index.js +30 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/jvm.js +386 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/php.js +280 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/python.js +175 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +12 -0
- package/dist/core/ingestion/type-extractors/ruby.js +218 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/rust.js +290 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +81 -0
- package/dist/core/ingestion/type-extractors/shared.js +322 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/swift.js +140 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +111 -0
- package/dist/core/ingestion/type-extractors/types.js +4 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/typescript.js +227 -0
- package/dist/core/ingestion/utils.d.ts +73 -0
- package/dist/core/ingestion/utils.js +992 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +99 -0
- package/dist/core/ingestion/workers/parse-worker.js +1055 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +123 -0
- package/dist/core/lbug/csv-generator.d.ts +28 -0
- package/dist/core/lbug/csv-generator.js +355 -0
- package/dist/core/lbug/lbug-adapter.d.ts +96 -0
- package/dist/core/lbug/lbug-adapter.js +753 -0
- package/dist/core/lbug/schema.d.ts +46 -0
- package/dist/core/lbug/schema.js +402 -0
- package/dist/core/search/bm25-index.d.ts +20 -0
- package/dist/core/search/bm25-index.js +123 -0
- package/dist/core/search/hybrid-search.d.ts +32 -0
- package/dist/core/search/hybrid-search.js +131 -0
- package/dist/core/search/query-cache.d.ts +18 -0
- package/dist/core/search/query-cache.js +47 -0
- package/dist/core/search/query-expansion.d.ts +19 -0
- package/dist/core/search/query-expansion.js +75 -0
- package/dist/core/search/reranker.d.ts +29 -0
- package/dist/core/search/reranker.js +122 -0
- package/dist/core/search/types.d.ts +154 -0
- package/dist/core/search/types.js +51 -0
- package/dist/core/semantic/tsgo-service.d.ts +67 -0
- package/dist/core/semantic/tsgo-service.js +355 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +12 -0
- package/dist/core/tree-sitter/parser-loader.js +71 -0
- package/dist/lib/memory-guard.d.ts +35 -0
- package/dist/lib/memory-guard.js +70 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.js +6 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +32 -0
- package/dist/mcp/compatible-stdio-transport.js +209 -0
- package/dist/mcp/core/embedder.d.ts +24 -0
- package/dist/mcp/core/embedder.js +168 -0
- package/dist/mcp/core/lbug-adapter.d.ts +29 -0
- package/dist/mcp/core/lbug-adapter.js +330 -0
- package/dist/mcp/local/local-backend.d.ts +188 -0
- package/dist/mcp/local/local-backend.js +2759 -0
- package/dist/mcp/resources.d.ts +22 -0
- package/dist/mcp/resources.js +379 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +217 -0
- package/dist/mcp/staleness.d.ts +10 -0
- package/dist/mcp/staleness.js +25 -0
- package/dist/mcp/tools.d.ts +21 -0
- package/dist/mcp/tools.js +202 -0
- package/dist/server/api.d.ts +5 -0
- package/dist/server/api.js +340 -0
- package/dist/server/mcp-http.d.ts +7 -0
- package/dist/server/mcp-http.js +95 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +87 -0
- package/dist/storage/repo-manager.js +249 -0
- package/dist/types/pipeline.d.ts +35 -0
- package/dist/types/pipeline.js +20 -0
- package/hooks/claude/code-mapper-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/models/mlx-embedder.py +185 -0
- package/package.json +100 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** @file resources.ts
|
|
2
|
+
* @description MCP resource definitions and handlers for AI agents
|
|
3
|
+
* All resources use repo-scoped URIs: code-mapper://repo/{name}/context */
|
|
4
|
+
import type { LocalBackend } from './local/local-backend.js';
|
|
5
|
+
export interface ResourceDefinition {
|
|
6
|
+
uri: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ResourceTemplate {
|
|
12
|
+
uriTemplate: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
}
|
|
17
|
+
/** Static resources including per-repo resources and the global repos list */
|
|
18
|
+
export declare function getResourceDefinitions(): ResourceDefinition[];
|
|
19
|
+
/** Dynamic resource templates */
|
|
20
|
+
export declare function getResourceTemplates(): ResourceTemplate[];
|
|
21
|
+
/** Read a resource and return its content */
|
|
22
|
+
export declare function readResource(uri: string, backend: LocalBackend): Promise<string>;
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// code-mapper/src/mcp/resources.ts
|
|
2
|
+
/** @file resources.ts
|
|
3
|
+
* @description MCP resource definitions and handlers for AI agents
|
|
4
|
+
* All resources use repo-scoped URIs: code-mapper://repo/{name}/context */
|
|
5
|
+
import { checkStaleness } from './staleness.js';
|
|
6
|
+
/** Static resources including per-repo resources and the global repos list */
|
|
7
|
+
export function getResourceDefinitions() {
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
uri: 'code-mapper://repos',
|
|
11
|
+
name: 'All Indexed Repositories',
|
|
12
|
+
description: 'List of all indexed repos with stats. Read this first to discover available repos.',
|
|
13
|
+
mimeType: 'text/yaml',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
uri: 'code-mapper://setup',
|
|
17
|
+
name: 'Code Mapper Setup Content',
|
|
18
|
+
description: 'Returns AGENTS.md content for all indexed repos. Useful for setup/onboarding.',
|
|
19
|
+
mimeType: 'text/markdown',
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
/** Dynamic resource templates */
|
|
24
|
+
export function getResourceTemplates() {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
uriTemplate: 'code-mapper://repo/{name}/context',
|
|
28
|
+
name: 'Repo Overview',
|
|
29
|
+
description: 'Codebase stats, staleness check, and available tools',
|
|
30
|
+
mimeType: 'text/yaml',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
uriTemplate: 'code-mapper://repo/{name}/clusters',
|
|
34
|
+
name: 'Repo Modules',
|
|
35
|
+
description: 'All functional areas (Leiden clusters)',
|
|
36
|
+
mimeType: 'text/yaml',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
uriTemplate: 'code-mapper://repo/{name}/processes',
|
|
40
|
+
name: 'Repo Processes',
|
|
41
|
+
description: 'All execution flows',
|
|
42
|
+
mimeType: 'text/yaml',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
uriTemplate: 'code-mapper://repo/{name}/schema',
|
|
46
|
+
name: 'Graph Schema',
|
|
47
|
+
description: 'Node/edge schema for Cypher queries',
|
|
48
|
+
mimeType: 'text/yaml',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
uriTemplate: 'code-mapper://repo/{name}/cluster/{clusterName}',
|
|
52
|
+
name: 'Module Detail',
|
|
53
|
+
description: 'Deep dive into a specific functional area',
|
|
54
|
+
mimeType: 'text/yaml',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
uriTemplate: 'code-mapper://repo/{name}/process/{processName}',
|
|
58
|
+
name: 'Process Trace',
|
|
59
|
+
description: 'Step-by-step execution trace',
|
|
60
|
+
mimeType: 'text/yaml',
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
/** Parse a resource URI to extract the repo name and resource type */
|
|
65
|
+
function parseUri(uri) {
|
|
66
|
+
if (uri === 'code-mapper://repos')
|
|
67
|
+
return { resourceType: 'repos' };
|
|
68
|
+
if (uri === 'code-mapper://setup')
|
|
69
|
+
return { resourceType: 'setup' };
|
|
70
|
+
// Repo-scoped: code-mapper://repo/{name}/context
|
|
71
|
+
const repoMatch = uri.match(/^code-mapper:\/\/repo\/([^/]+)\/(.+)$/);
|
|
72
|
+
if (repoMatch) {
|
|
73
|
+
const repoName = decodeURIComponent(repoMatch[1]);
|
|
74
|
+
const rest = repoMatch[2];
|
|
75
|
+
if (rest.startsWith('cluster/')) {
|
|
76
|
+
return { repoName, resourceType: 'cluster', param: decodeURIComponent(rest.replace('cluster/', '')) };
|
|
77
|
+
}
|
|
78
|
+
if (rest.startsWith('process/')) {
|
|
79
|
+
return { repoName, resourceType: 'process', param: decodeURIComponent(rest.replace('process/', '')) };
|
|
80
|
+
}
|
|
81
|
+
return { repoName, resourceType: rest };
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
84
|
+
}
|
|
85
|
+
/** Read a resource and return its content */
|
|
86
|
+
export async function readResource(uri, backend) {
|
|
87
|
+
const parsed = parseUri(uri);
|
|
88
|
+
// Global repos list (no repo context needed)
|
|
89
|
+
if (parsed.resourceType === 'repos') {
|
|
90
|
+
return getReposResource(backend);
|
|
91
|
+
}
|
|
92
|
+
// Setup resource (AGENTS.md content for all repos)
|
|
93
|
+
if (parsed.resourceType === 'setup') {
|
|
94
|
+
return getSetupResource(backend);
|
|
95
|
+
}
|
|
96
|
+
const repoName = parsed.repoName;
|
|
97
|
+
switch (parsed.resourceType) {
|
|
98
|
+
case 'context':
|
|
99
|
+
return getContextResource(backend, repoName);
|
|
100
|
+
case 'clusters':
|
|
101
|
+
return getClustersResource(backend, repoName);
|
|
102
|
+
case 'processes':
|
|
103
|
+
return getProcessesResource(backend, repoName);
|
|
104
|
+
case 'schema':
|
|
105
|
+
return getSchemaResource();
|
|
106
|
+
case 'cluster':
|
|
107
|
+
return getClusterDetailResource(parsed.param, backend, repoName);
|
|
108
|
+
case 'process':
|
|
109
|
+
return getProcessDetailResource(parsed.param, backend, repoName);
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Resource Implementations
|
|
115
|
+
/** List all indexed repositories */
|
|
116
|
+
async function getReposResource(backend) {
|
|
117
|
+
const repos = await backend.listRepos();
|
|
118
|
+
if (repos.length === 0) {
|
|
119
|
+
return 'repos: []\n# No repositories indexed. Run: code-mapper analyze';
|
|
120
|
+
}
|
|
121
|
+
const lines = ['repos:'];
|
|
122
|
+
for (const repo of repos) {
|
|
123
|
+
lines.push(` - name: "${repo.name}"`);
|
|
124
|
+
lines.push(` path: "${repo.path}"`);
|
|
125
|
+
lines.push(` indexed: "${repo.indexedAt}"`);
|
|
126
|
+
lines.push(` commit: "${repo.lastCommit?.slice(0, 7) || 'unknown'}"`);
|
|
127
|
+
if (repo.stats) {
|
|
128
|
+
lines.push(` files: ${repo.stats.files || 0}`);
|
|
129
|
+
lines.push(` symbols: ${repo.stats.nodes || 0}`);
|
|
130
|
+
lines.push(` processes: ${repo.stats.processes || 0}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (repos.length > 1) {
|
|
134
|
+
lines.push('');
|
|
135
|
+
lines.push('# Multiple repos indexed. Use repo parameter in tool calls:');
|
|
136
|
+
lines.push(`# code-mapper_search({query: "auth", repo: "${repos[0].name}"})`);
|
|
137
|
+
}
|
|
138
|
+
return lines.join('\n');
|
|
139
|
+
}
|
|
140
|
+
/** Codebase overview for a specific repo */
|
|
141
|
+
async function getContextResource(backend, repoName) {
|
|
142
|
+
// Resolve repo and check staleness
|
|
143
|
+
const repo = await backend.resolveRepo(repoName);
|
|
144
|
+
const repoId = repo.name.toLowerCase();
|
|
145
|
+
const context = backend.getContext(repoId) || backend.getContext();
|
|
146
|
+
if (!context) {
|
|
147
|
+
return 'error: No codebase loaded. Run: code-mapper analyze';
|
|
148
|
+
}
|
|
149
|
+
const repoPath = repo.repoPath;
|
|
150
|
+
const lastCommit = repo.lastCommit || 'HEAD';
|
|
151
|
+
const staleness = repoPath ? checkStaleness(repoPath, lastCommit) : { isStale: false, commitsBehind: 0 };
|
|
152
|
+
const lines = [
|
|
153
|
+
`project: ${context.projectName}`,
|
|
154
|
+
];
|
|
155
|
+
if (staleness.isStale && staleness.hint) {
|
|
156
|
+
lines.push('');
|
|
157
|
+
lines.push(`staleness: "${staleness.hint}"`);
|
|
158
|
+
}
|
|
159
|
+
lines.push('');
|
|
160
|
+
lines.push('stats:');
|
|
161
|
+
lines.push(` files: ${context.stats.fileCount}`);
|
|
162
|
+
lines.push(` symbols: ${context.stats.functionCount}`);
|
|
163
|
+
lines.push(` processes: ${context.stats.processCount}`);
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push('tools_available:');
|
|
166
|
+
lines.push(' - query: Process-grouped code intelligence (execution flows related to a concept)');
|
|
167
|
+
lines.push(' - context: 360-degree symbol view (categorized refs, process participation)');
|
|
168
|
+
lines.push(' - impact: Blast radius analysis (what breaks if you change a symbol)');
|
|
169
|
+
lines.push(' - detect_changes: Git-diff impact analysis (what do your changes affect)');
|
|
170
|
+
lines.push(' - rename: Multi-file coordinated rename with confidence tags');
|
|
171
|
+
lines.push(' - cypher: Raw graph queries');
|
|
172
|
+
lines.push(' - list_repos: Discover all indexed repositories');
|
|
173
|
+
lines.push('');
|
|
174
|
+
lines.push('re_index: Run `npx code-mapper analyze` in terminal if data is stale');
|
|
175
|
+
lines.push('');
|
|
176
|
+
lines.push('resources_available:');
|
|
177
|
+
lines.push(' - code-mapper://repos: All indexed repositories');
|
|
178
|
+
lines.push(` - code-mapper://repo/${context.projectName}/clusters: All functional areas`);
|
|
179
|
+
lines.push(` - code-mapper://repo/${context.projectName}/processes: All execution flows`);
|
|
180
|
+
lines.push(` - code-mapper://repo/${context.projectName}/cluster/{name}: Module details`);
|
|
181
|
+
lines.push(` - code-mapper://repo/${context.projectName}/process/{name}: Process trace`);
|
|
182
|
+
return lines.join('\n');
|
|
183
|
+
}
|
|
184
|
+
/** Functional areas via backend.queryClusters() */
|
|
185
|
+
async function getClustersResource(backend, repoName) {
|
|
186
|
+
try {
|
|
187
|
+
const result = await backend.queryClusters(repoName, 100);
|
|
188
|
+
if (!result.clusters || result.clusters.length === 0) {
|
|
189
|
+
return 'modules: []\n# No functional areas detected. Run: code-mapper analyze';
|
|
190
|
+
}
|
|
191
|
+
const displayLimit = 20;
|
|
192
|
+
const lines = ['modules:'];
|
|
193
|
+
const toShow = result.clusters.slice(0, displayLimit);
|
|
194
|
+
for (const cluster of toShow) {
|
|
195
|
+
const label = cluster.heuristicLabel || cluster.label || cluster.id;
|
|
196
|
+
lines.push(` - name: "${label}"`);
|
|
197
|
+
lines.push(` symbols: ${cluster.symbolCount || 0}`);
|
|
198
|
+
if (cluster.cohesion) {
|
|
199
|
+
lines.push(` cohesion: ${(cluster.cohesion * 100).toFixed(0)}%`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (result.clusters.length > displayLimit) {
|
|
203
|
+
lines.push(`\n# Showing top ${displayLimit} of ${result.clusters.length} modules. Use code-mapper_query for deeper search.`);
|
|
204
|
+
}
|
|
205
|
+
return lines.join('\n');
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
return `error: ${err.message}`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Execution flows via backend.queryProcesses() */
|
|
212
|
+
async function getProcessesResource(backend, repoName) {
|
|
213
|
+
try {
|
|
214
|
+
const result = await backend.queryProcesses(repoName, 50);
|
|
215
|
+
if (!result.processes || result.processes.length === 0) {
|
|
216
|
+
return 'processes: []\n# No processes detected. Run: code-mapper analyze';
|
|
217
|
+
}
|
|
218
|
+
const displayLimit = 20;
|
|
219
|
+
const lines = ['processes:'];
|
|
220
|
+
const toShow = result.processes.slice(0, displayLimit);
|
|
221
|
+
for (const proc of toShow) {
|
|
222
|
+
const label = proc.heuristicLabel || proc.label || proc.id;
|
|
223
|
+
lines.push(` - name: "${label}"`);
|
|
224
|
+
lines.push(` type: ${proc.processType || 'unknown'}`);
|
|
225
|
+
lines.push(` steps: ${proc.stepCount || 0}`);
|
|
226
|
+
}
|
|
227
|
+
if (result.processes.length > displayLimit) {
|
|
228
|
+
lines.push(`\n# Showing top ${displayLimit} of ${result.processes.length} processes. Use code-mapper_query for deeper search.`);
|
|
229
|
+
}
|
|
230
|
+
return lines.join('\n');
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
return `error: ${err.message}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** Graph schema reference for Cypher queries */
|
|
237
|
+
function getSchemaResource() {
|
|
238
|
+
return `# Code Mapper Graph Schema
|
|
239
|
+
|
|
240
|
+
nodes:
|
|
241
|
+
- File: Source code files
|
|
242
|
+
- Folder: Directory containers
|
|
243
|
+
- Function: Functions and arrow functions
|
|
244
|
+
- Class: Class definitions
|
|
245
|
+
- Interface: Interface/type definitions
|
|
246
|
+
- Method: Class methods
|
|
247
|
+
- CodeElement: Catch-all for other code elements
|
|
248
|
+
- Community: Auto-detected functional area (Leiden algorithm)
|
|
249
|
+
- Process: Execution flow trace
|
|
250
|
+
|
|
251
|
+
additional_node_types: "Multi-language: Struct, Enum, Macro, Typedef, Union, Namespace, Trait, Impl, TypeAlias, Const, Static, Property, Record, Delegate, Annotation, Constructor, Template, Module (use backticks in queries: \`Struct\`, \`Enum\`, etc.)"
|
|
252
|
+
|
|
253
|
+
relationships:
|
|
254
|
+
- CONTAINS: File/Folder contains child
|
|
255
|
+
- DEFINES: File defines a symbol
|
|
256
|
+
- CALLS: Function/method invocation
|
|
257
|
+
- IMPORTS: Module imports
|
|
258
|
+
- EXTENDS: Class inheritance
|
|
259
|
+
- IMPLEMENTS: Interface implementation
|
|
260
|
+
- MEMBER_OF: Symbol belongs to community
|
|
261
|
+
- STEP_IN_PROCESS: Symbol is step N in process
|
|
262
|
+
|
|
263
|
+
relationship_table: "All relationships use a single CodeRelation table with a 'type' property. Properties: type (STRING), confidence (DOUBLE), reason (STRING), step (INT32)"
|
|
264
|
+
|
|
265
|
+
example_queries:
|
|
266
|
+
find_callers: |
|
|
267
|
+
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
|
|
268
|
+
RETURN caller.name, caller.filePath
|
|
269
|
+
|
|
270
|
+
find_community_members: |
|
|
271
|
+
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
272
|
+
WHERE c.heuristicLabel = "Auth"
|
|
273
|
+
RETURN s.name, labels(s) AS type
|
|
274
|
+
|
|
275
|
+
trace_process: |
|
|
276
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
277
|
+
WHERE p.heuristicLabel = "LoginFlow"
|
|
278
|
+
RETURN s.name, r.step
|
|
279
|
+
ORDER BY r.step
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
/** Deep dive into a specific functional area */
|
|
283
|
+
async function getClusterDetailResource(name, backend, repoName) {
|
|
284
|
+
try {
|
|
285
|
+
const result = await backend.queryClusterDetail(name, repoName);
|
|
286
|
+
if (result.error) {
|
|
287
|
+
return `error: ${result.error}`;
|
|
288
|
+
}
|
|
289
|
+
const cluster = result.cluster;
|
|
290
|
+
const members = result.members || [];
|
|
291
|
+
const lines = [
|
|
292
|
+
`module: "${cluster.heuristicLabel || cluster.label || cluster.id}"`,
|
|
293
|
+
`symbols: ${cluster.symbolCount || members.length}`,
|
|
294
|
+
];
|
|
295
|
+
if (cluster.cohesion) {
|
|
296
|
+
lines.push(`cohesion: ${(cluster.cohesion * 100).toFixed(0)}%`);
|
|
297
|
+
}
|
|
298
|
+
if (members.length > 0) {
|
|
299
|
+
lines.push('');
|
|
300
|
+
lines.push('members:');
|
|
301
|
+
for (const member of members.slice(0, 20)) {
|
|
302
|
+
lines.push(` - name: ${member.name}`);
|
|
303
|
+
lines.push(` type: ${member.type}`);
|
|
304
|
+
lines.push(` file: ${member.filePath}`);
|
|
305
|
+
}
|
|
306
|
+
if (members.length > 20) {
|
|
307
|
+
lines.push(` # ... and ${members.length - 20} more`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return lines.join('\n');
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
return `error: ${err.message}`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/** Step-by-step execution trace for a specific process */
|
|
317
|
+
async function getProcessDetailResource(name, backend, repoName) {
|
|
318
|
+
try {
|
|
319
|
+
const result = await backend.queryProcessDetail(name, repoName);
|
|
320
|
+
if (result.error) {
|
|
321
|
+
return `error: ${result.error}`;
|
|
322
|
+
}
|
|
323
|
+
const proc = result.process;
|
|
324
|
+
const steps = result.steps || [];
|
|
325
|
+
const lines = [
|
|
326
|
+
`name: "${proc.heuristicLabel || proc.label || proc.id}"`,
|
|
327
|
+
`type: ${proc.processType || 'unknown'}`,
|
|
328
|
+
`step_count: ${proc.stepCount || steps.length}`,
|
|
329
|
+
];
|
|
330
|
+
if (steps.length > 0) {
|
|
331
|
+
lines.push('');
|
|
332
|
+
lines.push('trace:');
|
|
333
|
+
for (const step of steps) {
|
|
334
|
+
lines.push(` ${step.step}: ${step.name} (${step.filePath})`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return lines.join('\n');
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
return `error: ${err.message}`;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/** Generate AGENTS.md content for all indexed repos (used for onboarding) */
|
|
344
|
+
async function getSetupResource(backend) {
|
|
345
|
+
const repos = await backend.listRepos();
|
|
346
|
+
if (repos.length === 0) {
|
|
347
|
+
return '# Code Mapper\n\nNo repositories indexed. Run: `npx code-mapper analyze` in a repository.';
|
|
348
|
+
}
|
|
349
|
+
const sections = [];
|
|
350
|
+
for (const repo of repos) {
|
|
351
|
+
const stats = repo.stats || {};
|
|
352
|
+
const lines = [
|
|
353
|
+
`# Code Mapper MCP — ${repo.name}`,
|
|
354
|
+
'',
|
|
355
|
+
`This project is indexed by Code Mapper as **${repo.name}** (${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows).`,
|
|
356
|
+
'',
|
|
357
|
+
'## Tools',
|
|
358
|
+
'',
|
|
359
|
+
'| Tool | What it gives you |',
|
|
360
|
+
'|------|-------------------|',
|
|
361
|
+
'| `query` | Process-grouped code intelligence — execution flows related to a concept |',
|
|
362
|
+
'| `context` | 360-degree symbol view — categorized refs, processes it participates in |',
|
|
363
|
+
'| `impact` | Symbol blast radius — what breaks at depth 1/2/3 with confidence |',
|
|
364
|
+
'| `detect_changes` | Git-diff impact — what do your current changes affect |',
|
|
365
|
+
'| `rename` | Multi-file coordinated rename with confidence-tagged edits |',
|
|
366
|
+
'| `cypher` | Raw graph queries |',
|
|
367
|
+
'| `list_repos` | Discover indexed repos |',
|
|
368
|
+
'',
|
|
369
|
+
'## Resources',
|
|
370
|
+
'',
|
|
371
|
+
`- \`code-mapper://repo/${repo.name}/context\` — Stats, staleness check`,
|
|
372
|
+
`- \`code-mapper://repo/${repo.name}/clusters\` — All functional areas`,
|
|
373
|
+
`- \`code-mapper://repo/${repo.name}/processes\` — All execution flows`,
|
|
374
|
+
`- \`code-mapper://repo/${repo.name}/schema\` — Graph schema for Cypher`,
|
|
375
|
+
];
|
|
376
|
+
sections.push(lines.join('\n'));
|
|
377
|
+
}
|
|
378
|
+
return sections.join('\n\n---\n\n');
|
|
379
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @file server.ts
|
|
2
|
+
* @description Model Context Protocol server for multi-repo code intelligence
|
|
3
|
+
* Runs on stdio for external AI tools (Cursor, Claude) via MCP protocol
|
|
4
|
+
* Tools: list_repos, query, cypher, context, impact, detect_changes, rename */
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
import type { LocalBackend } from './local/local-backend.js';
|
|
7
|
+
/** Create a configured MCP Server with all handlers registered (transport-agnostic) */
|
|
8
|
+
export declare function createMCPServer(backend: LocalBackend): Server;
|
|
9
|
+
/** Start the MCP server on stdio transport (for CLI use) */
|
|
10
|
+
export declare function startMCPServer(backend: LocalBackend): Promise<void>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// code-mapper/src/mcp/server.ts
|
|
2
|
+
/** @file server.ts
|
|
3
|
+
* @description Model Context Protocol server for multi-repo code intelligence
|
|
4
|
+
* Runs on stdio for external AI tools (Cursor, Claude) via MCP protocol
|
|
5
|
+
* Tools: list_repos, query, cypher, context, impact, detect_changes, rename */
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
|
+
import { CompatibleStdioServerTransport } from './compatible-stdio-transport.js';
|
|
9
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
import { CODE_MAPPER_TOOLS } from './tools.js';
|
|
11
|
+
import { getResourceDefinitions, getResourceTemplates, readResource } from './resources.js';
|
|
12
|
+
// Next-step hints removed — LLMs already know how to chain tools from
|
|
13
|
+
// the MCP tool descriptions. Hints wasted ~40 tokens per response.
|
|
14
|
+
/** Create a configured MCP Server with all handlers registered (transport-agnostic) */
|
|
15
|
+
export function createMCPServer(backend) {
|
|
16
|
+
// Preload embedding model in background so first query doesn't pay cold-start cost
|
|
17
|
+
import('./core/embedder.js').then(m => m.initEmbedder()).catch(() => { });
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const pkgVersion = require('../../package.json').version;
|
|
20
|
+
const server = new Server({
|
|
21
|
+
name: 'code-mapper',
|
|
22
|
+
version: pkgVersion,
|
|
23
|
+
}, {
|
|
24
|
+
capabilities: {
|
|
25
|
+
tools: {},
|
|
26
|
+
resources: {},
|
|
27
|
+
prompts: {},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
// List resources
|
|
31
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
32
|
+
const resources = getResourceDefinitions();
|
|
33
|
+
return {
|
|
34
|
+
resources: resources.map(r => ({
|
|
35
|
+
uri: r.uri,
|
|
36
|
+
name: r.name,
|
|
37
|
+
description: r.description,
|
|
38
|
+
mimeType: r.mimeType,
|
|
39
|
+
})),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
// List resource templates (dynamic resources)
|
|
43
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
44
|
+
const templates = getResourceTemplates();
|
|
45
|
+
return {
|
|
46
|
+
resourceTemplates: templates.map(t => ({
|
|
47
|
+
uriTemplate: t.uriTemplate,
|
|
48
|
+
name: t.name,
|
|
49
|
+
description: t.description,
|
|
50
|
+
mimeType: t.mimeType,
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
// Read resource
|
|
55
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
56
|
+
const { uri } = request.params;
|
|
57
|
+
try {
|
|
58
|
+
const content = await readResource(uri, backend);
|
|
59
|
+
return {
|
|
60
|
+
contents: [
|
|
61
|
+
{
|
|
62
|
+
uri,
|
|
63
|
+
mimeType: 'text/yaml',
|
|
64
|
+
text: content,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return {
|
|
71
|
+
contents: [
|
|
72
|
+
{
|
|
73
|
+
uri,
|
|
74
|
+
mimeType: 'text/plain',
|
|
75
|
+
text: `Error: ${err.message}`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// List tools
|
|
82
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
83
|
+
tools: CODE_MAPPER_TOOLS.map((tool) => ({
|
|
84
|
+
name: tool.name,
|
|
85
|
+
description: tool.description,
|
|
86
|
+
inputSchema: tool.inputSchema,
|
|
87
|
+
})),
|
|
88
|
+
}));
|
|
89
|
+
// Tool calls with next-step hints
|
|
90
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
91
|
+
const { name, arguments: args } = request.params;
|
|
92
|
+
try {
|
|
93
|
+
const result = await backend.callTool(name, args);
|
|
94
|
+
const resultText = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: resultText,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: 'text',
|
|
110
|
+
text: `Error: ${message}`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
isError: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
// List prompts
|
|
118
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
119
|
+
prompts: [
|
|
120
|
+
{
|
|
121
|
+
name: 'detect_impact',
|
|
122
|
+
description: 'Analyze the impact of your current changes before committing. Guides through scope selection, change detection, process analysis, and risk assessment.',
|
|
123
|
+
arguments: [
|
|
124
|
+
{ name: 'scope', description: 'What to analyze: unstaged, staged, all, or compare', required: false },
|
|
125
|
+
{ name: 'base_ref', description: 'Branch/commit for compare scope', required: false },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'generate_map',
|
|
130
|
+
description: 'Generate architecture documentation from the knowledge graph. Creates a codebase overview with execution flows and mermaid diagrams.',
|
|
131
|
+
arguments: [
|
|
132
|
+
{ name: 'repo', description: 'Repository name (omit if only one indexed)', required: false },
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
}));
|
|
137
|
+
// Get prompt
|
|
138
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
139
|
+
const { name, arguments: args } = request.params;
|
|
140
|
+
if (name === 'detect_impact') {
|
|
141
|
+
const scope = args?.scope || 'all';
|
|
142
|
+
const baseRef = args?.base_ref || '';
|
|
143
|
+
return {
|
|
144
|
+
messages: [
|
|
145
|
+
{
|
|
146
|
+
role: 'user',
|
|
147
|
+
content: {
|
|
148
|
+
type: 'text',
|
|
149
|
+
text: `Analyze the impact of my current code changes before committing.
|
|
150
|
+
|
|
151
|
+
Follow these steps:
|
|
152
|
+
1. Run \`detect_changes(${JSON.stringify({ scope, ...(baseRef ? { base_ref: baseRef } : {}) })})\` to find what changed and affected processes
|
|
153
|
+
2. For each changed symbol in critical processes, run \`context({name: "<symbol>"})\` to see its full reference graph
|
|
154
|
+
3. For any high-risk items (many callers or cross-process), run \`impact({target: "<symbol>", direction: "upstream"})\` for blast radius
|
|
155
|
+
4. Summarize: changes, affected processes, risk level, and recommended actions
|
|
156
|
+
|
|
157
|
+
Present the analysis as a clear risk report.`,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (name === 'generate_map') {
|
|
164
|
+
const repo = args?.repo || '';
|
|
165
|
+
return {
|
|
166
|
+
messages: [
|
|
167
|
+
{
|
|
168
|
+
role: 'user',
|
|
169
|
+
content: {
|
|
170
|
+
type: 'text',
|
|
171
|
+
text: `Generate architecture documentation for this codebase using the knowledge graph.
|
|
172
|
+
|
|
173
|
+
Follow these steps:
|
|
174
|
+
1. READ \`code-mapper://repo/${repo || '{name}'}/context\` for codebase stats
|
|
175
|
+
2. READ \`code-mapper://repo/${repo || '{name}'}/clusters\` to see all functional areas
|
|
176
|
+
3. READ \`code-mapper://repo/${repo || '{name}'}/processes\` to see all execution flows
|
|
177
|
+
4. For the top 5 most important processes, READ \`code-mapper://repo/${repo || '{name}'}/process/{name}\` for step-by-step traces
|
|
178
|
+
5. Generate a mermaid architecture diagram showing the major areas and their connections
|
|
179
|
+
6. Write an ARCHITECTURE.md file with: overview, functional areas, key execution flows, and the mermaid diagram`,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
186
|
+
});
|
|
187
|
+
return server;
|
|
188
|
+
}
|
|
189
|
+
/** Start the MCP server on stdio transport (for CLI use) */
|
|
190
|
+
export async function startMCPServer(backend) {
|
|
191
|
+
const server = createMCPServer(backend);
|
|
192
|
+
// Connect stdio transport
|
|
193
|
+
const transport = new CompatibleStdioServerTransport();
|
|
194
|
+
await server.connect(transport);
|
|
195
|
+
// Graceful shutdown
|
|
196
|
+
let shuttingDown = false;
|
|
197
|
+
const shutdown = async () => {
|
|
198
|
+
if (shuttingDown)
|
|
199
|
+
return;
|
|
200
|
+
shuttingDown = true;
|
|
201
|
+
try {
|
|
202
|
+
await backend.disconnect();
|
|
203
|
+
}
|
|
204
|
+
catch { }
|
|
205
|
+
try {
|
|
206
|
+
await server.close();
|
|
207
|
+
}
|
|
208
|
+
catch { }
|
|
209
|
+
process.exit(0);
|
|
210
|
+
};
|
|
211
|
+
process.on('SIGINT', shutdown);
|
|
212
|
+
process.on('SIGTERM', shutdown);
|
|
213
|
+
// stdin close means the parent process is gone
|
|
214
|
+
process.stdin.on('end', shutdown);
|
|
215
|
+
process.stdin.on('error', () => shutdown());
|
|
216
|
+
process.stdout.on('error', () => shutdown());
|
|
217
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @file staleness.ts
|
|
2
|
+
* @description Checks if the Code Mapper index is behind the current git HEAD
|
|
3
|
+
* Returns a hint for the LLM to call analyze if stale */
|
|
4
|
+
export interface StalenessInfo {
|
|
5
|
+
isStale: boolean;
|
|
6
|
+
commitsBehind: number;
|
|
7
|
+
hint?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Check how many commits the index is behind HEAD */
|
|
10
|
+
export declare function checkStaleness(repoPath: string, lastCommit: string): StalenessInfo;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// code-mapper/src/mcp/staleness.ts
|
|
2
|
+
/** @file staleness.ts
|
|
3
|
+
* @description Checks if the Code Mapper index is behind the current git HEAD
|
|
4
|
+
* Returns a hint for the LLM to call analyze if stale */
|
|
5
|
+
import { execFileSync } from 'child_process';
|
|
6
|
+
/** Check how many commits the index is behind HEAD */
|
|
7
|
+
export function checkStaleness(repoPath, lastCommit) {
|
|
8
|
+
try {
|
|
9
|
+
// Get count of commits between lastCommit and HEAD
|
|
10
|
+
const result = execFileSync('git', ['rev-list', '--count', `${lastCommit}..HEAD`], { cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
11
|
+
const commitsBehind = parseInt(result, 10) || 0;
|
|
12
|
+
if (commitsBehind > 0) {
|
|
13
|
+
return {
|
|
14
|
+
isStale: true,
|
|
15
|
+
commitsBehind,
|
|
16
|
+
hint: `⚠️ Index is ${commitsBehind} commit${commitsBehind > 1 ? 's' : ''} behind HEAD. Run analyze tool to update.`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return { isStale: false, commitsBehind: 0 };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// If git command fails, assume not stale (fail open)
|
|
23
|
+
return { isStale: false, commitsBehind: 0 };
|
|
24
|
+
}
|
|
25
|
+
}
|