skannr 0.2.0 → 0.2.2
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/dist/cli.js +86 -0
- package/dist/cli.js.map +1 -1
- package/dist/guard/call-context.d.ts +14 -0
- package/dist/guard/call-context.d.ts.map +1 -0
- package/dist/guard/call-context.js +120 -0
- package/dist/guard/call-context.js.map +1 -0
- package/dist/guard/fix-applier.d.ts +38 -0
- package/dist/guard/fix-applier.d.ts.map +1 -0
- package/dist/guard/fix-applier.js +181 -0
- package/dist/guard/fix-applier.js.map +1 -0
- package/dist/guard/hook-installer.d.ts +19 -0
- package/dist/guard/hook-installer.d.ts.map +1 -0
- package/dist/guard/hook-installer.js +119 -0
- package/dist/guard/hook-installer.js.map +1 -0
- package/dist/guard/index.d.ts +34 -0
- package/dist/guard/index.d.ts.map +1 -0
- package/dist/guard/index.js +152 -0
- package/dist/guard/index.js.map +1 -0
- package/dist/guard/provider.d.ts +12 -0
- package/dist/guard/provider.d.ts.map +1 -0
- package/dist/guard/provider.js +211 -0
- package/dist/guard/provider.js.map +1 -0
- package/dist/guard/review-runner.d.ts +21 -0
- package/dist/guard/review-runner.d.ts.map +1 -0
- package/dist/guard/review-runner.js +85 -0
- package/dist/guard/review-runner.js.map +1 -0
- package/dist/guard/rules-loader.d.ts +10 -0
- package/dist/guard/rules-loader.d.ts.map +1 -0
- package/dist/guard/rules-loader.js +156 -0
- package/dist/guard/rules-loader.js.map +1 -0
- package/dist/guard/schema.d.ts +182 -0
- package/dist/guard/schema.d.ts.map +1 -0
- package/dist/guard/schema.js +53 -0
- package/dist/guard/schema.js.map +1 -0
- package/dist/guard/symbol-diff.d.ts +18 -0
- package/dist/guard/symbol-diff.d.ts.map +1 -0
- package/dist/guard/symbol-diff.js +243 -0
- package/dist/guard/symbol-diff.js.map +1 -0
- package/dist/guard/types.d.ts +62 -0
- package/dist/guard/types.d.ts.map +1 -0
- package/dist/guard/types.js +6 -0
- package/dist/guard/types.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +44 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/scanner.d.ts +0 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +0 -7
- package/dist/scanner.js.map +1 -1
- package/package.json +3 -2
- package/src/cli.ts +101 -0
- package/src/guard/call-context.ts +101 -0
- package/src/guard/fix-applier.ts +172 -0
- package/src/guard/hook-installer.ts +96 -0
- package/src/guard/index.ts +134 -0
- package/src/guard/provider.ts +209 -0
- package/src/guard/review-runner.ts +110 -0
- package/src/guard/rules-loader.ts +131 -0
- package/src/guard/schema.ts +59 -0
- package/src/guard/symbol-diff.ts +227 -0
- package/src/guard/types.ts +68 -0
- package/src/index.ts +2 -2
- package/src/mcp-server.ts +49 -0
- package/src/scanner.ts +0 -7
- package/gemini-extension/GEMINI.md +0 -26
- package/gemini-extension/gemini-extension.json +0 -12
- package/gemini-extension/package.json +0 -14
- package/gemini-extension/src/server.ts +0 -69
- package/gemini-extension/tsconfig.json +0 -14
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build symbol-level diff units from staged git changes.
|
|
3
|
+
* Uses Skannr's existing parser to identify which symbols changed,
|
|
4
|
+
* instead of working with raw file diffs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
import { getAdapter } from '../languages/registry';
|
|
11
|
+
import { readFileContent } from '../scanner';
|
|
12
|
+
import type { Symbol } from '../languages/LanguageAdapter';
|
|
13
|
+
import type { SymbolDiffUnit } from './types';
|
|
14
|
+
|
|
15
|
+
/** Get the list of staged files (paths relative to root). */
|
|
16
|
+
export function getStagedFiles(root: string): string[] {
|
|
17
|
+
try {
|
|
18
|
+
const output = execSync('git diff --cached --name-only --diff-filter=ACMR', {
|
|
19
|
+
cwd: root,
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
});
|
|
22
|
+
return output.trim().split('\n').filter(Boolean);
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Get the diff text for a specific staged file. */
|
|
29
|
+
function getStagedDiff(root: string, file: string): string {
|
|
30
|
+
try {
|
|
31
|
+
return execSync(`git diff --cached -- "${file}"`, {
|
|
32
|
+
cwd: root,
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
35
|
+
});
|
|
36
|
+
} catch {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Get the old (HEAD) version of a file, or null if newly added. */
|
|
42
|
+
function getHeadContent(root: string, file: string): string | null {
|
|
43
|
+
try {
|
|
44
|
+
return execSync(`git show HEAD:"${file}"`, {
|
|
45
|
+
cwd: root,
|
|
46
|
+
encoding: 'utf-8',
|
|
47
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
48
|
+
});
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Extract changed line numbers from a unified diff. */
|
|
55
|
+
function extractChangedLines(diff: string): Set<number> {
|
|
56
|
+
const lines = new Set<number>();
|
|
57
|
+
let currentLine = 0;
|
|
58
|
+
|
|
59
|
+
for (const line of diff.split('\n')) {
|
|
60
|
+
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
|
|
61
|
+
if (hunkMatch) {
|
|
62
|
+
currentLine = parseInt(hunkMatch[1], 10);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
66
|
+
lines.add(currentLine);
|
|
67
|
+
currentLine++;
|
|
68
|
+
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
69
|
+
// Deleted lines don't advance the new-file line counter
|
|
70
|
+
} else {
|
|
71
|
+
currentLine++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return lines;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Determine which symbols in a file were affected by the staged changes.
|
|
80
|
+
* Matches symbols whose line range overlaps any changed line.
|
|
81
|
+
*/
|
|
82
|
+
function findAffectedSymbols(
|
|
83
|
+
symbols: Symbol[],
|
|
84
|
+
changedLines: Set<number>,
|
|
85
|
+
allSymbols: Symbol[],
|
|
86
|
+
): Symbol[] {
|
|
87
|
+
if (changedLines.size === 0) return [];
|
|
88
|
+
|
|
89
|
+
// Build approximate end lines: each symbol extends until the next symbol starts
|
|
90
|
+
const sorted = [...allSymbols].sort((a, b) => a.line - b.line);
|
|
91
|
+
const affected: Symbol[] = [];
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
94
|
+
const sym = sorted[i];
|
|
95
|
+
const startLine = sym.line;
|
|
96
|
+
const endLine = i + 1 < sorted.length ? sorted[i + 1].line - 1 : startLine + 50;
|
|
97
|
+
|
|
98
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
99
|
+
if (changedLines.has(line)) {
|
|
100
|
+
affected.push(sym);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return affected;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build symbol-level diff units for all staged files in the project.
|
|
111
|
+
* This is the primary input to the review runner.
|
|
112
|
+
*/
|
|
113
|
+
export function buildSymbolDiffs(root: string): SymbolDiffUnit[] {
|
|
114
|
+
const stagedFiles = getStagedFiles(root);
|
|
115
|
+
const units: SymbolDiffUnit[] = [];
|
|
116
|
+
|
|
117
|
+
for (const relFile of stagedFiles) {
|
|
118
|
+
const absPath = path.join(root, relFile);
|
|
119
|
+
if (!fs.existsSync(absPath)) continue;
|
|
120
|
+
|
|
121
|
+
const adapter = getAdapter(absPath);
|
|
122
|
+
const newContent = readFileContent(absPath);
|
|
123
|
+
if (!newContent) continue;
|
|
124
|
+
|
|
125
|
+
const newSymbols = adapter.extractSymbols(newContent);
|
|
126
|
+
const oldContent = getHeadContent(root, relFile);
|
|
127
|
+
const oldSymbols = oldContent ? adapter.extractSymbols(oldContent) : [];
|
|
128
|
+
|
|
129
|
+
const diff = getStagedDiff(root, relFile);
|
|
130
|
+
const changedLines = extractChangedLines(diff);
|
|
131
|
+
|
|
132
|
+
// Find symbols that overlap changed lines in the new version
|
|
133
|
+
const affected = findAffectedSymbols(newSymbols, changedLines, newSymbols);
|
|
134
|
+
|
|
135
|
+
// Determine change type for each affected symbol
|
|
136
|
+
const oldSymbolNames = new Set(oldSymbols.map((s) => s.name));
|
|
137
|
+
|
|
138
|
+
for (const sym of affected) {
|
|
139
|
+
if (sym.kind === 'export') continue; // skip synthetic export markers
|
|
140
|
+
|
|
141
|
+
const changeType = oldSymbolNames.has(sym.name) ? 'modified' : 'added';
|
|
142
|
+
const oldSym = oldSymbols.find((s) => s.name === sym.name);
|
|
143
|
+
|
|
144
|
+
units.push({
|
|
145
|
+
file: relFile,
|
|
146
|
+
symbol: sym.name,
|
|
147
|
+
changeType,
|
|
148
|
+
oldSignature: oldSym ? `${oldSym.kind} ${oldSym.name}` : undefined,
|
|
149
|
+
newSignature: `${sym.kind} ${sym.name}`,
|
|
150
|
+
callers: [], // populated by call-context module
|
|
151
|
+
callees: [], // populated by call-context module
|
|
152
|
+
diffText: diff,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle deleted symbols (in old but not in new)
|
|
157
|
+
if (oldContent) {
|
|
158
|
+
const newSymbolNames = new Set(newSymbols.map((s) => s.name));
|
|
159
|
+
for (const oldSym of oldSymbols) {
|
|
160
|
+
if (oldSym.kind === 'export') continue;
|
|
161
|
+
if (!newSymbolNames.has(oldSym.name)) {
|
|
162
|
+
units.push({
|
|
163
|
+
file: relFile,
|
|
164
|
+
symbol: oldSym.name,
|
|
165
|
+
changeType: 'deleted',
|
|
166
|
+
oldSignature: `${oldSym.kind} ${oldSym.name}`,
|
|
167
|
+
callers: [],
|
|
168
|
+
callees: [],
|
|
169
|
+
diffText: diff,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return units;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Build symbol diffs from a PR diff string (for --pr-mode).
|
|
181
|
+
*/
|
|
182
|
+
export function buildSymbolDiffsFromDiff(root: string, diffContent: string): SymbolDiffUnit[] {
|
|
183
|
+
const parseDiff = require('parse-diff');
|
|
184
|
+
const parsedFiles = parseDiff(diffContent);
|
|
185
|
+
const units: SymbolDiffUnit[] = [];
|
|
186
|
+
|
|
187
|
+
for (const file of parsedFiles) {
|
|
188
|
+
const raw = file.to ?? file.from;
|
|
189
|
+
if (!raw || raw === '/dev/null') continue;
|
|
190
|
+
const relFile = raw.replace(/^[ab]\//, '');
|
|
191
|
+
const absPath = path.join(root, relFile);
|
|
192
|
+
if (!fs.existsSync(absPath)) continue;
|
|
193
|
+
|
|
194
|
+
const adapter = getAdapter(absPath);
|
|
195
|
+
const content = readFileContent(absPath);
|
|
196
|
+
if (!content) continue;
|
|
197
|
+
|
|
198
|
+
const symbols = adapter.extractSymbols(content);
|
|
199
|
+
const changedLines = new Set<number>();
|
|
200
|
+
|
|
201
|
+
for (const chunk of file.chunks) {
|
|
202
|
+
for (const change of chunk.changes) {
|
|
203
|
+
if (change.type === 'add' || change.type === 'del') {
|
|
204
|
+
const ln = (change as any).ln;
|
|
205
|
+
if (typeof ln === 'number') changedLines.add(ln);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const affected = findAffectedSymbols(symbols, changedLines, symbols);
|
|
211
|
+
|
|
212
|
+
for (const sym of affected) {
|
|
213
|
+
if (sym.kind === 'export') continue;
|
|
214
|
+
units.push({
|
|
215
|
+
file: relFile,
|
|
216
|
+
symbol: sym.name,
|
|
217
|
+
changeType: 'modified',
|
|
218
|
+
newSignature: `${sym.kind} ${sym.name}`,
|
|
219
|
+
callers: [],
|
|
220
|
+
callees: [],
|
|
221
|
+
diffText: file.chunks.map((c: any) => c.content).join('\n'),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return units;
|
|
227
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for the guard module.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Severity levels for rules and violations. */
|
|
6
|
+
export type Severity = 'low' | 'medium' | 'high' | 'critical';
|
|
7
|
+
|
|
8
|
+
/** A team-defined review rule. */
|
|
9
|
+
export interface GuardRule {
|
|
10
|
+
id: string;
|
|
11
|
+
description: string;
|
|
12
|
+
severity: Severity;
|
|
13
|
+
fixable: boolean;
|
|
14
|
+
category: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A single symbol-level diff unit sent to the LLM for review. */
|
|
18
|
+
export interface SymbolDiffUnit {
|
|
19
|
+
file: string;
|
|
20
|
+
symbol: string;
|
|
21
|
+
changeType: 'added' | 'modified' | 'deleted';
|
|
22
|
+
oldSignature?: string;
|
|
23
|
+
newSignature?: string;
|
|
24
|
+
callers: string[];
|
|
25
|
+
callees: string[];
|
|
26
|
+
diffText: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A violation found by the review. */
|
|
30
|
+
export interface Violation {
|
|
31
|
+
rule_id: string;
|
|
32
|
+
file: string;
|
|
33
|
+
symbol: string;
|
|
34
|
+
line_start: number;
|
|
35
|
+
line_end: number;
|
|
36
|
+
severity: Severity;
|
|
37
|
+
confidence: number;
|
|
38
|
+
fixable: boolean;
|
|
39
|
+
message: string;
|
|
40
|
+
suggested_fix?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The structured response contract from the LLM. */
|
|
44
|
+
export interface ReviewResponse {
|
|
45
|
+
violations: Violation[];
|
|
46
|
+
status: 'pass' | 'fail';
|
|
47
|
+
summary: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Result of a full guard run. */
|
|
51
|
+
export interface GuardResult {
|
|
52
|
+
response: ReviewResponse;
|
|
53
|
+
rulesUsed: GuardRule[];
|
|
54
|
+
symbolsReviewed: number;
|
|
55
|
+
durationMs: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Guard configuration (provider, model, etc.). */
|
|
59
|
+
export interface GuardConfig {
|
|
60
|
+
/** LLM provider: 'gemini' | 'openai' */
|
|
61
|
+
provider: 'gemini' | 'openai';
|
|
62
|
+
/** Model name (e.g. 'gemini-2.0-flash-exp', 'gpt-4o'). */
|
|
63
|
+
model: string;
|
|
64
|
+
/** API key (resolved from env if not set). */
|
|
65
|
+
apiKey?: string;
|
|
66
|
+
/** OpenAI-compatible base URL (for openai provider). */
|
|
67
|
+
baseUrl?: string;
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import * as path from 'path';
|
|
6
|
-
import { discoverModules, readFileContent, scanFiles,
|
|
6
|
+
import { discoverModules, readFileContent, scanFiles, scanTypeScriptFiles } from './scanner';
|
|
7
7
|
import { rankFiles } from './ranker';
|
|
8
8
|
import { rankFilesEnhanced } from './ranker-enhanced';
|
|
9
9
|
import { buildSkeletonForFile } from './skeletonizer';
|
|
@@ -194,7 +194,7 @@ function resolveExtensions(
|
|
|
194
194
|
export { rankFiles } from './ranker';
|
|
195
195
|
export { rankFilesEnhanced } from './ranker-enhanced';
|
|
196
196
|
export { buildSkeletonForFile } from './skeletonizer';
|
|
197
|
-
export { scanTypeScriptFiles,
|
|
197
|
+
export { scanTypeScriptFiles, scanFiles, discoverModules } from './scanner';
|
|
198
198
|
export { countTokens } from './tokenizer';
|
|
199
199
|
export {
|
|
200
200
|
buildSkeletonWithMapping,
|
package/src/mcp-server.ts
CHANGED
|
@@ -84,6 +84,33 @@ export async function startMcpServer(): Promise<void> {
|
|
|
84
84
|
required: ['root'],
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
|
+
{
|
|
88
|
+
name: 'guard_review',
|
|
89
|
+
description:
|
|
90
|
+
'Review staged or diff changes against team-defined rules. Returns structured violations with severity, confidence, and fixability.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
root: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Absolute path to the repo root',
|
|
97
|
+
},
|
|
98
|
+
diff: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Unified diff content (if omitted, reviews staged files)',
|
|
101
|
+
},
|
|
102
|
+
diff_only: {
|
|
103
|
+
type: 'boolean',
|
|
104
|
+
description: 'Skip cross-file context for faster review (default: false)',
|
|
105
|
+
},
|
|
106
|
+
fix: {
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
description: 'Apply auto-fixes for fixable violations (default: false)',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ['root'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
87
114
|
],
|
|
88
115
|
}));
|
|
89
116
|
|
|
@@ -143,6 +170,28 @@ export async function startMcpServer(): Promise<void> {
|
|
|
143
170
|
};
|
|
144
171
|
}
|
|
145
172
|
|
|
173
|
+
if (toolName === 'guard_review') {
|
|
174
|
+
const { runGuard, formatGuardJson } = await import('./guard/index');
|
|
175
|
+
const root = String(args.root ?? '');
|
|
176
|
+
const diffContent =
|
|
177
|
+
typeof args.diff === 'string' && args.diff.length > 0
|
|
178
|
+
? args.diff
|
|
179
|
+
: undefined;
|
|
180
|
+
const diffOnly = args.diff_only === true;
|
|
181
|
+
const fix = args.fix === true;
|
|
182
|
+
|
|
183
|
+
const { result } = await runGuard({
|
|
184
|
+
root,
|
|
185
|
+
diffOnly,
|
|
186
|
+
fix,
|
|
187
|
+
...(diffContent ? { prMode: false } : {}),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: 'text', text: formatGuardJson(result) }],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
146
195
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
147
196
|
});
|
|
148
197
|
|
package/src/scanner.ts
CHANGED
|
@@ -145,13 +145,6 @@ export function scanTypeScriptFiles(rootDir: string): string[] {
|
|
|
145
145
|
return scanFiles(rootDir, { extensions: ['.ts', '.tsx'] });
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
export function scanRocketChatFiles(rootDir: string, moduleKeys?: string[]): string[] {
|
|
149
|
-
return scanFiles(rootDir, {
|
|
150
|
-
extensions: ['.ts', '.tsx'],
|
|
151
|
-
moduleKeys,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
148
|
export function readFileContent(filePath: string): string | null {
|
|
156
149
|
try {
|
|
157
150
|
return fs.readFileSync(filePath, 'utf-8');
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Universal Code Analyzer
|
|
2
|
-
|
|
3
|
-
You have a tool called `scan_codebase` that scans any repository and returns compressed **skeletons** — function signatures and types without the full implementation bodies.
|
|
4
|
-
|
|
5
|
-
## When to use it
|
|
6
|
-
|
|
7
|
-
Use `scan_codebase` before reading source files. It tells you which files are relevant and shows their structure without burning tokens on full file contents.
|
|
8
|
-
|
|
9
|
-
## Parameters
|
|
10
|
-
|
|
11
|
-
- `question` (**required**): what you want to understand, e.g. `"how are messages sent?"`
|
|
12
|
-
- `root` (**required**): absolute path to the repository, e.g. `/home/user/my-project`
|
|
13
|
-
- `limit`: how many files to return (default: 10)
|
|
14
|
-
- `modules`: narrow the search to specific modules or directories
|
|
15
|
-
|
|
16
|
-
## Example
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
scan_codebase({
|
|
20
|
-
question: "How does the authentication flow work?",
|
|
21
|
-
root: "/home/user/my-project",
|
|
22
|
-
modules: ["auth"]
|
|
23
|
-
})
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
The response includes each file's path, relevance score, skeleton code, and token counts so you can decide what to read in full.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "code-analyzer",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"description": "Analyze any codebase and return token-efficient structural skeletons",
|
|
5
|
-
"mcpServers": {
|
|
6
|
-
"code-analyzer": {
|
|
7
|
-
"command": "node",
|
|
8
|
-
"args": ["${extensionPath}/dist/server.js"]
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
"contextFileName": "GEMINI.md"
|
|
12
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
import { analyzeProject } from '../../dist/index';
|
|
5
|
-
|
|
6
|
-
const server = new Server(
|
|
7
|
-
{ name: 'code-analyzer', version: '0.1.0' },
|
|
8
|
-
{ capabilities: { tools: {} } }
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
12
|
-
tools: [
|
|
13
|
-
{
|
|
14
|
-
name: 'scan_codebase',
|
|
15
|
-
description: 'Analyzes any codebase to answer questions about its structure and architecture. Scans files, generates structural skeletons, and ranks results using hybrid retrieval. Supports TypeScript, JavaScript, and Python. Pass the root path of any repository.',
|
|
16
|
-
inputSchema: {
|
|
17
|
-
type: 'object',
|
|
18
|
-
properties: {
|
|
19
|
-
question: {
|
|
20
|
-
type: 'string',
|
|
21
|
-
description: 'What you want to understand about the codebase',
|
|
22
|
-
},
|
|
23
|
-
root: {
|
|
24
|
-
type: 'string',
|
|
25
|
-
description: 'Absolute path to the repository root',
|
|
26
|
-
},
|
|
27
|
-
limit: {
|
|
28
|
-
type: 'number',
|
|
29
|
-
description: 'Max number of files to return (default: 10)',
|
|
30
|
-
},
|
|
31
|
-
modules: {
|
|
32
|
-
type: 'array',
|
|
33
|
-
items: { type: 'string' },
|
|
34
|
-
description: 'Optional module keys (or directory names) to narrow the search scope',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
required: ['question', 'root'],
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
44
|
-
if (request.params.name !== 'scan_codebase') {
|
|
45
|
-
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const { question, root, limit = 10, modules } = request.params.arguments as {
|
|
49
|
-
question: string;
|
|
50
|
-
root: string;
|
|
51
|
-
limit?: number;
|
|
52
|
-
modules?: string[];
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const result = await analyzeProject({
|
|
56
|
-
question,
|
|
57
|
-
root,
|
|
58
|
-
limit,
|
|
59
|
-
moduleKeys: modules,
|
|
60
|
-
enhancedRanking: true,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const transport = new StdioServerTransport();
|
|
69
|
-
server.connect(transport).catch(console.error);
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"outDir": "./dist",
|
|
6
|
-
"rootDir": "./src",
|
|
7
|
-
"strict": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"moduleResolution": "node",
|
|
11
|
-
"types": ["node"]
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*"]
|
|
14
|
-
}
|