syntax-map-mcp 0.1.1 → 0.1.3
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 +38 -0
- package/README.md +194 -5
- package/dist/analysis/context.js +54 -2
- package/dist/analysis/index.js +234 -30
- package/dist/analysis/references.js +1 -1
- package/dist/analysis/summary.js +58 -10
- package/dist/tools.js +42 -5
- package/package.json +4 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.1.3 - 2026-05-04
|
|
6
|
+
|
|
7
|
+
- README에 `summarize_file`, 인덱스 검색 도구, `get_index_status`의 입력/응답 예시를 추가했습니다.
|
|
8
|
+
- Python 파일의 `summarize_file.exports`가 top-level `__all__`을 기준으로 반환되는 동작을 문서화했습니다.
|
|
9
|
+
- 배포 전 `typecheck`, 테스트, 빌드, 패키징 dry-run을 한 번에 실행하는 `release:check` 스크립트를 추가했습니다.
|
|
10
|
+
- Python 파일의 top-level `__all__` 문자열 이름을 `summarize_file.exports`로 반환하도록 했습니다.
|
|
11
|
+
- 인덱스 검색 도구에 `includePreview` 옵션을 추가해 `previewMarkdown`을 반환할 수 있게 했습니다.
|
|
12
|
+
- `build_context`가 `indexedSearch` 입력으로 인덱스 검색 결과 기반 markdown 컨텍스트를 만들 수 있게 했습니다.
|
|
13
|
+
- 패키지 버전을 `0.1.3`으로 올렸습니다.
|
|
14
|
+
|
|
15
|
+
## 0.1.2 - 2026-05-04
|
|
16
|
+
|
|
17
|
+
- `summarize_file`의 `exports` 추출을 AST 기반으로 변경해 문자열 내부 텍스트가 export로 잘못 반환되지 않도록 했습니다.
|
|
18
|
+
- `summarize_file` 응답에 `sources`를 추가해 `symbols`, `imports`, `exports` 추출 방식이 AST 기반임을 확인할 수 있게 했습니다.
|
|
19
|
+
- 인덱스 검색 도구에 `contextBefore`, `contextAfter` 옵션을 추가해 snippet 주변 라인을 함께 조회할 수 있게 했습니다.
|
|
20
|
+
- 패키지 버전을 `0.1.2`로 올렸습니다.
|
|
21
|
+
- `search_symbols` 결과에 현재 파일의 해당 줄 `snippet`을 추가했습니다.
|
|
22
|
+
- `find_indexed_references`를 추가해 SQLite 인덱스에서 식별자 참조를 검색할 수 있게 했습니다.
|
|
23
|
+
- `search_symbols`, `find_indexed_definition`, `find_indexed_references`가 `isStale`, `staleFiles`, `refreshed`를 반환하도록 했습니다.
|
|
24
|
+
- 인덱스 검색 도구에 `refreshIfStale` 옵션을 추가했습니다.
|
|
25
|
+
- `index_workspace`와 `get_index_status` 응답에 참조 인덱스 개수 `references`를 추가했습니다.
|
|
26
|
+
|
|
27
|
+
## 0.1.1 - 2026-05-04
|
|
28
|
+
|
|
29
|
+
- `find_indexed_definition`을 추가해 SQLite 인덱스에서 정확한 심볼 정의와 `snippet`을 조회할 수 있게 했습니다.
|
|
30
|
+
- README의 실행 예시와 MCP 설정 예시를 `npx -y syntax-map-mcp` 기반으로 변경했습니다.
|
|
31
|
+
- npm 페이지에서 GitHub 저장소가 노출되도록 `repository`, `homepage`, `bugs`, `license` 메타데이터를 추가했습니다.
|
|
32
|
+
- npm publish 경고를 피하도록 `bin` 경로를 `dist/cli.js` 형식으로 정규화했습니다.
|
|
33
|
+
|
|
34
|
+
## 0.1.0 - 2026-05-04
|
|
35
|
+
|
|
36
|
+
- Tree-sitter 기반 MCP 서버 초기 버전을 배포했습니다.
|
|
37
|
+
- JavaScript, TypeScript, TSX, Python 파일의 심볼, 정의, 참조, 요약, tree-sitter query, 컨텍스트 생성을 지원했습니다.
|
|
38
|
+
- SQLite 기반 workspace 심볼 인덱스를 추가했습니다.
|
package/README.md
CHANGED
|
@@ -33,20 +33,209 @@ npx -y syntax-map-mcp --workspace-root /path/to/workspace
|
|
|
33
33
|
- `list_symbols`: 파일 하나의 top-level 심볼 목록 반환
|
|
34
34
|
- `find_definition`: 여러 파일에서 이름과 선택적 kind로 정의 검색
|
|
35
35
|
- `find_references`: 여러 파일에서 식별자 참조 검색
|
|
36
|
-
- `summarize_file`: 파일 언어, 라인 수, imports, exports, symbols 요약
|
|
36
|
+
- `summarize_file`: 파일 언어, 라인 수, AST 기반 imports, exports, symbols 요약
|
|
37
37
|
- `run_query`: 파일 하나에 tree-sitter query 실행
|
|
38
38
|
- `build_context`: 여러 파일 요약을 markdown 컨텍스트로 구성
|
|
39
|
-
- `index_workspace`: 지원 소스 파일을 파싱해 SQLite
|
|
40
|
-
- `search_symbols`: SQLite 인덱스에서 심볼 이름 검색
|
|
39
|
+
- `index_workspace`: 지원 소스 파일을 파싱해 SQLite 심볼/참조 인덱스 생성 또는 갱신
|
|
40
|
+
- `search_symbols`: SQLite 인덱스에서 심볼 이름 검색 및 snippet 반환
|
|
41
41
|
- `find_indexed_definition`: SQLite 인덱스에서 정확한 심볼 정의 검색 및 snippet 반환
|
|
42
|
-
- `
|
|
42
|
+
- `find_indexed_references`: SQLite 인덱스에서 식별자 참조 검색 및 snippet 반환
|
|
43
|
+
- `get_index_status`: 인덱스 경로, 인덱싱된 파일 수, 심볼 수, 참조 수, stale 파일 수 반환
|
|
43
44
|
- `clear_index`: SQLite 인덱스 파일 삭제
|
|
44
45
|
|
|
45
46
|
## SQLite 인덱스
|
|
46
47
|
|
|
47
48
|
`index_workspace`는 `workspaceRoot` 아래의 `.syntax-map-mcp/index.sqlite`에 인덱스를 저장합니다. 인덱싱 대상은 `.js`, `.jsx`, `.ts`, `.tsx`, `.py` 파일이며, `.git`, `.syntax-map-mcp`, `dist`, `node_modules` 디렉터리는 제외합니다.
|
|
48
49
|
|
|
49
|
-
파일 변경 여부는 `mtimeMs`와 `size`로 판단합니다. 다시 `index_workspace`를 호출하면 변경된 파일만 재파싱하고, 삭제된 파일은 인덱스에서 제거합니다. `find_indexed_definition
|
|
50
|
+
파일 변경 여부는 `mtimeMs`와 `size`로 판단합니다. 다시 `index_workspace`를 호출하면 변경된 파일만 재파싱하고, 삭제된 파일은 인덱스에서 제거합니다. `search_symbols`, `find_indexed_definition`, `find_indexed_references`는 `isStale`, `staleFiles`, `refreshed`를 반환해 검색 결과가 최신 인덱스 기반인지 알려줍니다. 세 도구에 `refreshIfStale: true`를 전달하면 stale 파일이 있을 때 먼저 인덱스를 갱신한 뒤 검색합니다. 인덱스 검색 도구는 인덱스에 저장된 위치를 조회한 뒤 현재 파일에서 해당 줄 snippet을 읽어 반환합니다. `contextBefore`, `contextAfter`를 0-10 사이 정수로 전달하면 snippet 주변 라인도 함께 반환합니다. `includePreview: true`를 전달하면 `path:line` 헤더와 코드블록으로 구성된 `previewMarkdown`도 함께 반환합니다. 자동 watch 모드는 아직 포함하지 않았습니다.
|
|
51
|
+
|
|
52
|
+
`summarize_file`은 `sources` 필드로 `symbols`, `imports`, `exports`가 어떤 방식으로 추출되었는지 반환합니다.
|
|
53
|
+
|
|
54
|
+
Python은 JavaScript/TypeScript처럼 명시적인 `export` 문법이 없으므로, top-level `__all__`에 선언된 문자열 이름을 `exports`로 반환합니다. `__all__`이 없으면 `exports`는 빈 배열입니다.
|
|
55
|
+
|
|
56
|
+
## 도구 사용 예시
|
|
57
|
+
|
|
58
|
+
### summarize_file
|
|
59
|
+
|
|
60
|
+
입력:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"path": "src/index.ts"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
응답 일부:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"ok": true,
|
|
73
|
+
"path": "src/index.ts",
|
|
74
|
+
"language": "typescript",
|
|
75
|
+
"imports": ["import { createServer } from './server.js';"],
|
|
76
|
+
"exports": ["export async function main() {"],
|
|
77
|
+
"sources": {
|
|
78
|
+
"symbols": "ast",
|
|
79
|
+
"imports": "ast",
|
|
80
|
+
"exports": "ast"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### search_symbols
|
|
86
|
+
|
|
87
|
+
입력:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"query": "UserService",
|
|
92
|
+
"kinds": ["class"],
|
|
93
|
+
"refreshIfStale": true,
|
|
94
|
+
"contextBefore": 2,
|
|
95
|
+
"contextAfter": 2,
|
|
96
|
+
"includePreview": true
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
응답 일부:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"ok": true,
|
|
105
|
+
"isStale": false,
|
|
106
|
+
"refreshed": true,
|
|
107
|
+
"symbols": [
|
|
108
|
+
{
|
|
109
|
+
"path": "src/users.ts",
|
|
110
|
+
"name": "UserService",
|
|
111
|
+
"kind": "class",
|
|
112
|
+
"snippet": "export class UserService {",
|
|
113
|
+
"context": {
|
|
114
|
+
"before": ["export type UserId = User['id'];", ""],
|
|
115
|
+
"after": [" constructor(private readonly users: User[]) {}", ""]
|
|
116
|
+
},
|
|
117
|
+
"previewMarkdown": "src/users.ts:8\n\n```typescript\nexport type UserId = User['id'];\n\nexport class UserService {\n constructor(private readonly users: User[]) {}\n\n```"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### find_indexed_definition
|
|
124
|
+
|
|
125
|
+
입력:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"name": "UserService",
|
|
130
|
+
"refreshIfStale": true,
|
|
131
|
+
"contextBefore": 1,
|
|
132
|
+
"contextAfter": 1
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
응답 일부:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"ok": true,
|
|
141
|
+
"total": 1,
|
|
142
|
+
"definitions": [
|
|
143
|
+
{
|
|
144
|
+
"path": "src/users.ts",
|
|
145
|
+
"name": "UserService",
|
|
146
|
+
"kind": "class",
|
|
147
|
+
"snippet": "export class UserService {"
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### find_indexed_references
|
|
154
|
+
|
|
155
|
+
입력:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"name": "formatUser",
|
|
160
|
+
"limit": 20,
|
|
161
|
+
"refreshIfStale": true
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
응답 일부:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"ok": true,
|
|
170
|
+
"references": [
|
|
171
|
+
{
|
|
172
|
+
"path": "src/users.ts",
|
|
173
|
+
"name": "formatUser",
|
|
174
|
+
"nodeType": "identifier",
|
|
175
|
+
"snippet": "formatUser(defaultUser);"
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### build_context
|
|
182
|
+
|
|
183
|
+
파일 경로 기반 입력:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"paths": ["src/users.ts", "src/index.ts"],
|
|
188
|
+
"detail": "compact"
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
인덱스 검색 기반 입력:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"detail": "compact",
|
|
197
|
+
"indexedSearch": {
|
|
198
|
+
"query": "UserService",
|
|
199
|
+
"kinds": ["class"],
|
|
200
|
+
"refreshIfStale": true,
|
|
201
|
+
"contextBefore": 1,
|
|
202
|
+
"contextAfter": 1
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
응답 일부:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"ok": true,
|
|
212
|
+
"markdown": "# Code Context\n\n## Indexed Search Results\n\n### UserService\n\nsrc/users.ts:8\n\n```typescript\nexport class UserService {\n```"
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### get_index_status
|
|
217
|
+
|
|
218
|
+
입력:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
응답 일부:
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"ok": true,
|
|
229
|
+
"indexedFiles": 12,
|
|
230
|
+
"symbols": 84,
|
|
231
|
+
"references": 231,
|
|
232
|
+
"staleFiles": 0
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 변경 이력
|
|
237
|
+
|
|
238
|
+
버전별 변경 내용은 [CHANGELOG.md](./CHANGELOG.md)를 참고하세요.
|
|
50
239
|
|
|
51
240
|
## MCP 설정 예시
|
|
52
241
|
|
package/dist/analysis/context.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import { searchSymbols } from './index.js';
|
|
1
2
|
import { summarizeFile } from './summary.js';
|
|
2
3
|
export async function buildContext(workspace, input) {
|
|
4
|
+
if (input.indexedSearch) {
|
|
5
|
+
return buildIndexedSearchContext(workspace, {
|
|
6
|
+
detail: input.detail,
|
|
7
|
+
indexedSearch: input.indexedSearch
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
if (!input.paths) {
|
|
11
|
+
return failure('Either paths or indexedSearch must be provided');
|
|
12
|
+
}
|
|
3
13
|
const summaries = [];
|
|
4
14
|
for (const filePath of input.paths) {
|
|
5
15
|
const summary = await summarizeFile(workspace, filePath);
|
|
@@ -12,8 +22,50 @@ export async function buildContext(workspace, input) {
|
|
|
12
22
|
markdown: renderMarkdown(summaries, input.detail)
|
|
13
23
|
};
|
|
14
24
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
25
|
+
async function buildIndexedSearchContext(workspace, input) {
|
|
26
|
+
const search = await searchSymbols(workspace, {
|
|
27
|
+
...input.indexedSearch,
|
|
28
|
+
includePreview: true
|
|
29
|
+
});
|
|
30
|
+
if (!search.ok)
|
|
31
|
+
return search;
|
|
32
|
+
const paths = [...new Set(search.symbols.map(symbol => symbol.path))];
|
|
33
|
+
const summaries = [];
|
|
34
|
+
for (const filePath of paths) {
|
|
35
|
+
const summary = await summarizeFile(workspace, filePath);
|
|
36
|
+
if (!summary.ok)
|
|
37
|
+
return summary;
|
|
38
|
+
summaries.push(summary);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
markdown: renderMarkdown(summaries, input.detail, renderIndexedSearchResults(search.symbols))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function renderMarkdown(summaries, detail, intro) {
|
|
46
|
+
return ['# Code Context', intro, ...summaries.map(summary => renderFile(summary, detail))]
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join('\n\n');
|
|
49
|
+
}
|
|
50
|
+
function renderIndexedSearchResults(symbols) {
|
|
51
|
+
const lines = ['## Indexed Search Results'];
|
|
52
|
+
if (symbols.length === 0) {
|
|
53
|
+
lines.push('', '- None');
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}
|
|
56
|
+
for (const symbol of symbols) {
|
|
57
|
+
lines.push('', `### ${symbol.name}`, '', symbol.previewMarkdown ?? symbol.path);
|
|
58
|
+
}
|
|
59
|
+
return lines.join('\n');
|
|
60
|
+
}
|
|
61
|
+
function failure(message) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
error: {
|
|
65
|
+
code: 'INDEX_ERROR',
|
|
66
|
+
message
|
|
67
|
+
}
|
|
68
|
+
};
|
|
17
69
|
}
|
|
18
70
|
function renderFile(summary, detail) {
|
|
19
71
|
const lines = [
|
package/dist/analysis/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import path from 'node:path';
|
|
|
3
3
|
import initSqlJs from 'sql.js';
|
|
4
4
|
import { listSymbols } from './symbols.js';
|
|
5
5
|
import { parseSourceFile } from '../parser.js';
|
|
6
|
+
import { runTreeSitterQuery } from './query.js';
|
|
7
|
+
import { referenceQueryForLanguage } from './references.js';
|
|
6
8
|
const INDEX_DIRECTORY = '.syntax-map-mcp';
|
|
7
9
|
const INDEX_FILE = 'index.sqlite';
|
|
8
10
|
function indexPathForWorkspace(workspace) {
|
|
@@ -63,8 +65,22 @@ function initSchema(database) {
|
|
|
63
65
|
FOREIGN KEY(file_path) REFERENCES files(path) ON DELETE CASCADE
|
|
64
66
|
);
|
|
65
67
|
|
|
68
|
+
CREATE TABLE IF NOT EXISTS reference_captures (
|
|
69
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
70
|
+
file_path TEXT NOT NULL,
|
|
71
|
+
language TEXT NOT NULL,
|
|
72
|
+
name TEXT NOT NULL,
|
|
73
|
+
node_type TEXT NOT NULL,
|
|
74
|
+
start_row INTEGER NOT NULL,
|
|
75
|
+
start_column INTEGER NOT NULL,
|
|
76
|
+
end_row INTEGER NOT NULL,
|
|
77
|
+
end_column INTEGER NOT NULL,
|
|
78
|
+
FOREIGN KEY(file_path) REFERENCES files(path) ON DELETE CASCADE
|
|
79
|
+
);
|
|
80
|
+
|
|
66
81
|
CREATE INDEX IF NOT EXISTS symbols_name_idx ON symbols(name);
|
|
67
82
|
CREATE INDEX IF NOT EXISTS symbols_kind_idx ON symbols(kind);
|
|
83
|
+
CREATE INDEX IF NOT EXISTS reference_captures_name_idx ON reference_captures(name);
|
|
68
84
|
`);
|
|
69
85
|
}
|
|
70
86
|
function selectStoredFiles(database) {
|
|
@@ -90,6 +106,53 @@ function isCurrent(stored, current) {
|
|
|
90
106
|
stored.size === current.size &&
|
|
91
107
|
stored.mtimeMs === current.mtimeMs);
|
|
92
108
|
}
|
|
109
|
+
async function countStaleFiles(workspace, database) {
|
|
110
|
+
const storedFiles = selectStoredFiles(database);
|
|
111
|
+
const currentFiles = await workspace.listSourceFiles();
|
|
112
|
+
const currentPaths = new Set(currentFiles.map(file => file.relativePath));
|
|
113
|
+
let staleFiles = 0;
|
|
114
|
+
for (const file of currentFiles) {
|
|
115
|
+
if (!isCurrent(storedFiles.get(file.relativePath), file)) {
|
|
116
|
+
staleFiles += 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const storedPath of storedFiles.keys()) {
|
|
120
|
+
if (!currentPaths.has(storedPath)) {
|
|
121
|
+
staleFiles += 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return staleFiles;
|
|
125
|
+
}
|
|
126
|
+
async function openIndexForRead(workspace, input) {
|
|
127
|
+
const indexPath = indexPathForWorkspace(workspace);
|
|
128
|
+
let database = await openDatabase(indexPath);
|
|
129
|
+
initSchema(database);
|
|
130
|
+
const initialStaleFiles = await countStaleFiles(workspace, database);
|
|
131
|
+
if (!input.refreshIfStale || initialStaleFiles === 0) {
|
|
132
|
+
return {
|
|
133
|
+
database,
|
|
134
|
+
indexPath,
|
|
135
|
+
isStale: initialStaleFiles > 0,
|
|
136
|
+
staleFiles: initialStaleFiles,
|
|
137
|
+
refreshed: false
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
database.close();
|
|
141
|
+
const refreshedIndex = await indexWorkspace(workspace);
|
|
142
|
+
if (!refreshedIndex.ok) {
|
|
143
|
+
throw new Error(refreshedIndex.error.message);
|
|
144
|
+
}
|
|
145
|
+
database = await openDatabase(indexPath);
|
|
146
|
+
initSchema(database);
|
|
147
|
+
const staleFiles = await countStaleFiles(workspace, database);
|
|
148
|
+
return {
|
|
149
|
+
database,
|
|
150
|
+
indexPath,
|
|
151
|
+
isStale: staleFiles > 0,
|
|
152
|
+
staleFiles,
|
|
153
|
+
refreshed: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
93
156
|
function upsertFile(database, input) {
|
|
94
157
|
database.run(`
|
|
95
158
|
INSERT INTO files (path, language, size, mtime_ms, parse_status, error_message, indexed_at)
|
|
@@ -145,12 +208,39 @@ function insertSymbol(database, input) {
|
|
|
145
208
|
input.symbol.selectionRange?.end.column ?? null
|
|
146
209
|
]);
|
|
147
210
|
}
|
|
211
|
+
function insertReference(database, input) {
|
|
212
|
+
database.run(`
|
|
213
|
+
INSERT INTO reference_captures (
|
|
214
|
+
file_path,
|
|
215
|
+
language,
|
|
216
|
+
name,
|
|
217
|
+
node_type,
|
|
218
|
+
start_row,
|
|
219
|
+
start_column,
|
|
220
|
+
end_row,
|
|
221
|
+
end_column
|
|
222
|
+
)
|
|
223
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
224
|
+
`, [
|
|
225
|
+
input.filePath,
|
|
226
|
+
input.language,
|
|
227
|
+
input.name,
|
|
228
|
+
input.nodeType,
|
|
229
|
+
input.range.start.row,
|
|
230
|
+
input.range.start.column,
|
|
231
|
+
input.range.end.row,
|
|
232
|
+
input.range.end.column
|
|
233
|
+
]);
|
|
234
|
+
}
|
|
148
235
|
function deleteFile(database, filePath) {
|
|
149
236
|
database.run('DELETE FROM files WHERE path = ?', [filePath]);
|
|
150
237
|
}
|
|
151
238
|
function deleteSymbolsForFile(database, filePath) {
|
|
152
239
|
database.run('DELETE FROM symbols WHERE file_path = ?', [filePath]);
|
|
153
240
|
}
|
|
241
|
+
function deleteReferencesForFile(database, filePath) {
|
|
242
|
+
database.run('DELETE FROM reference_captures WHERE file_path = ?', [filePath]);
|
|
243
|
+
}
|
|
154
244
|
function scalarCount(database, sql) {
|
|
155
245
|
const result = database.exec(sql)[0];
|
|
156
246
|
if (!result)
|
|
@@ -163,8 +253,39 @@ function sqlLikePattern(query) {
|
|
|
163
253
|
function rowValue(row, key) {
|
|
164
254
|
return row[key];
|
|
165
255
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
256
|
+
function snippetDetails(path, language, text, row, options) {
|
|
257
|
+
if (text === undefined)
|
|
258
|
+
return { snippet: '' };
|
|
259
|
+
const lines = text.split(/\r?\n/);
|
|
260
|
+
const snippet = lines[row] ?? '';
|
|
261
|
+
const beforeCount = options.contextBefore ?? 0;
|
|
262
|
+
const afterCount = options.contextAfter ?? 0;
|
|
263
|
+
const context = beforeCount === 0 && afterCount === 0
|
|
264
|
+
? undefined
|
|
265
|
+
: {
|
|
266
|
+
before: lines.slice(Math.max(0, row - beforeCount), row),
|
|
267
|
+
after: lines.slice(row + 1, row + 1 + afterCount)
|
|
268
|
+
};
|
|
269
|
+
const details = {
|
|
270
|
+
snippet,
|
|
271
|
+
};
|
|
272
|
+
if (context)
|
|
273
|
+
details.context = context;
|
|
274
|
+
if (options.includePreview) {
|
|
275
|
+
details.previewMarkdown = previewMarkdown(path, language, row, snippet, context);
|
|
276
|
+
}
|
|
277
|
+
return details;
|
|
278
|
+
}
|
|
279
|
+
function previewMarkdown(filePath, language, row, snippet, context) {
|
|
280
|
+
return [
|
|
281
|
+
`${filePath}:${row + 1}`,
|
|
282
|
+
'',
|
|
283
|
+
`\`\`\`${language}`,
|
|
284
|
+
...(context?.before ?? []),
|
|
285
|
+
snippet,
|
|
286
|
+
...(context?.after ?? []),
|
|
287
|
+
'```'
|
|
288
|
+
].join('\n');
|
|
168
289
|
}
|
|
169
290
|
export async function indexWorkspace(workspace) {
|
|
170
291
|
const indexPath = indexPathForWorkspace(workspace);
|
|
@@ -197,6 +318,7 @@ export async function indexWorkspace(workspace) {
|
|
|
197
318
|
errorMessage: file.error.message
|
|
198
319
|
});
|
|
199
320
|
deleteSymbolsForFile(database, fileInfo.relativePath);
|
|
321
|
+
deleteReferencesForFile(database, fileInfo.relativePath);
|
|
200
322
|
indexedFiles += 1;
|
|
201
323
|
continue;
|
|
202
324
|
}
|
|
@@ -209,6 +331,7 @@ export async function indexWorkspace(workspace) {
|
|
|
209
331
|
errorMessage: parsed.error.message
|
|
210
332
|
});
|
|
211
333
|
deleteSymbolsForFile(database, fileInfo.relativePath);
|
|
334
|
+
deleteReferencesForFile(database, fileInfo.relativePath);
|
|
212
335
|
indexedFiles += 1;
|
|
213
336
|
continue;
|
|
214
337
|
}
|
|
@@ -219,6 +342,7 @@ export async function indexWorkspace(workspace) {
|
|
|
219
342
|
errorMessage: null
|
|
220
343
|
});
|
|
221
344
|
deleteSymbolsForFile(database, fileInfo.relativePath);
|
|
345
|
+
deleteReferencesForFile(database, fileInfo.relativePath);
|
|
222
346
|
for (const symbol of listSymbols(parsed)) {
|
|
223
347
|
insertSymbol(database, {
|
|
224
348
|
filePath: fileInfo.relativePath,
|
|
@@ -226,6 +350,19 @@ export async function indexWorkspace(workspace) {
|
|
|
226
350
|
symbol
|
|
227
351
|
});
|
|
228
352
|
}
|
|
353
|
+
const references = runTreeSitterQuery(parsed, referenceQueryForLanguage(parsed.language));
|
|
354
|
+
if (!references.ok) {
|
|
355
|
+
throw new Error(references.error.message);
|
|
356
|
+
}
|
|
357
|
+
for (const reference of references.captures) {
|
|
358
|
+
insertReference(database, {
|
|
359
|
+
filePath: fileInfo.relativePath,
|
|
360
|
+
language: parsed.language,
|
|
361
|
+
name: reference.text,
|
|
362
|
+
nodeType: reference.nodeType,
|
|
363
|
+
range: reference.range
|
|
364
|
+
});
|
|
365
|
+
}
|
|
229
366
|
indexedFiles += 1;
|
|
230
367
|
}
|
|
231
368
|
await saveDatabase(database, indexPath);
|
|
@@ -235,7 +372,8 @@ export async function indexWorkspace(workspace) {
|
|
|
235
372
|
indexedFiles,
|
|
236
373
|
skippedFiles,
|
|
237
374
|
removedFiles,
|
|
238
|
-
symbols: scalarCount(database, 'SELECT COUNT(*) FROM symbols')
|
|
375
|
+
symbols: scalarCount(database, 'SELECT COUNT(*) FROM symbols'),
|
|
376
|
+
references: scalarCount(database, 'SELECT COUNT(*) FROM reference_captures')
|
|
239
377
|
};
|
|
240
378
|
}
|
|
241
379
|
catch (error) {
|
|
@@ -246,10 +384,10 @@ export async function indexWorkspace(workspace) {
|
|
|
246
384
|
}
|
|
247
385
|
}
|
|
248
386
|
export async function findIndexedDefinitions(workspace, input) {
|
|
249
|
-
|
|
250
|
-
const database = await openDatabase(indexPath);
|
|
387
|
+
let readState;
|
|
251
388
|
try {
|
|
252
|
-
|
|
389
|
+
readState = await openIndexForRead(workspace, input);
|
|
390
|
+
const { database } = readState;
|
|
253
391
|
const where = ['name = ?'];
|
|
254
392
|
const params = [input.name];
|
|
255
393
|
if (input.kinds && input.kinds.length > 0) {
|
|
@@ -320,7 +458,7 @@ export async function findIndexedDefinitions(workspace, input) {
|
|
|
320
458
|
column: Number(selectionEndColumn)
|
|
321
459
|
}
|
|
322
460
|
},
|
|
323
|
-
|
|
461
|
+
...snippetDetails(filePath, rowValue(row, 'language'), file.ok ? file.text : undefined, startRow, input)
|
|
324
462
|
});
|
|
325
463
|
}
|
|
326
464
|
}
|
|
@@ -329,7 +467,10 @@ export async function findIndexedDefinitions(workspace, input) {
|
|
|
329
467
|
}
|
|
330
468
|
return {
|
|
331
469
|
ok: true,
|
|
332
|
-
indexPath,
|
|
470
|
+
indexPath: readState.indexPath,
|
|
471
|
+
isStale: readState.isStale,
|
|
472
|
+
staleFiles: readState.staleFiles,
|
|
473
|
+
refreshed: readState.refreshed,
|
|
333
474
|
total: definitions.length,
|
|
334
475
|
definitions
|
|
335
476
|
};
|
|
@@ -338,14 +479,81 @@ export async function findIndexedDefinitions(workspace, input) {
|
|
|
338
479
|
return failure(error instanceof Error ? error.message : String(error));
|
|
339
480
|
}
|
|
340
481
|
finally {
|
|
341
|
-
database.close();
|
|
482
|
+
readState?.database.close();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
export async function findIndexedReferences(workspace, input) {
|
|
486
|
+
let readState;
|
|
487
|
+
try {
|
|
488
|
+
readState = await openIndexForRead(workspace, input);
|
|
489
|
+
const { database } = readState;
|
|
490
|
+
const limit = input.limit ?? 50;
|
|
491
|
+
const statement = database.prepare(`
|
|
492
|
+
SELECT
|
|
493
|
+
file_path,
|
|
494
|
+
language,
|
|
495
|
+
name,
|
|
496
|
+
node_type,
|
|
497
|
+
start_row,
|
|
498
|
+
start_column,
|
|
499
|
+
end_row,
|
|
500
|
+
end_column
|
|
501
|
+
FROM reference_captures
|
|
502
|
+
WHERE name = ?
|
|
503
|
+
ORDER BY file_path ASC, start_row ASC
|
|
504
|
+
LIMIT ?
|
|
505
|
+
`, [input.name, limit]);
|
|
506
|
+
const references = [];
|
|
507
|
+
try {
|
|
508
|
+
while (statement.step()) {
|
|
509
|
+
const row = statement.getAsObject();
|
|
510
|
+
const filePath = String(rowValue(row, 'file_path'));
|
|
511
|
+
const startRow = Number(rowValue(row, 'start_row'));
|
|
512
|
+
const file = await workspace.readSourceFile(filePath);
|
|
513
|
+
references.push({
|
|
514
|
+
path: filePath,
|
|
515
|
+
language: rowValue(row, 'language'),
|
|
516
|
+
name: String(rowValue(row, 'name')),
|
|
517
|
+
nodeType: String(rowValue(row, 'node_type')),
|
|
518
|
+
range: {
|
|
519
|
+
start: {
|
|
520
|
+
row: startRow,
|
|
521
|
+
column: Number(rowValue(row, 'start_column'))
|
|
522
|
+
},
|
|
523
|
+
end: {
|
|
524
|
+
row: Number(rowValue(row, 'end_row')),
|
|
525
|
+
column: Number(rowValue(row, 'end_column'))
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
...snippetDetails(filePath, rowValue(row, 'language'), file.ok ? file.text : undefined, startRow, input)
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
finally {
|
|
533
|
+
statement.free();
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
ok: true,
|
|
537
|
+
indexPath: readState.indexPath,
|
|
538
|
+
isStale: readState.isStale,
|
|
539
|
+
staleFiles: readState.staleFiles,
|
|
540
|
+
refreshed: readState.refreshed,
|
|
541
|
+
total: references.length,
|
|
542
|
+
references
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
547
|
+
}
|
|
548
|
+
finally {
|
|
549
|
+
readState?.database.close();
|
|
342
550
|
}
|
|
343
551
|
}
|
|
344
552
|
export async function searchSymbols(workspace, input) {
|
|
345
|
-
|
|
346
|
-
const database = await openDatabase(indexPath);
|
|
553
|
+
let readState;
|
|
347
554
|
try {
|
|
348
|
-
|
|
555
|
+
readState = await openIndexForRead(workspace, input);
|
|
556
|
+
const { database } = readState;
|
|
349
557
|
const where = ['name LIKE ? ESCAPE "\\"'];
|
|
350
558
|
const params = [sqlLikePattern(input.query)];
|
|
351
559
|
if (input.kinds && input.kinds.length > 0) {
|
|
@@ -382,15 +590,18 @@ export async function searchSymbols(workspace, input) {
|
|
|
382
590
|
const selectionStartColumn = rowValue(row, 'selection_start_column');
|
|
383
591
|
const selectionEndRow = rowValue(row, 'selection_end_row');
|
|
384
592
|
const selectionEndColumn = rowValue(row, 'selection_end_column');
|
|
593
|
+
const filePath = String(rowValue(row, 'file_path'));
|
|
594
|
+
const startRow = Number(rowValue(row, 'start_row'));
|
|
595
|
+
const file = await workspace.readSourceFile(filePath);
|
|
385
596
|
symbols.push({
|
|
386
|
-
path:
|
|
597
|
+
path: filePath,
|
|
387
598
|
language: rowValue(row, 'language'),
|
|
388
599
|
name: String(rowValue(row, 'name')),
|
|
389
600
|
kind: rowValue(row, 'kind'),
|
|
390
601
|
parentName: rowValue(row, 'parent_name') === null ? undefined : String(rowValue(row, 'parent_name')),
|
|
391
602
|
range: {
|
|
392
603
|
start: {
|
|
393
|
-
row:
|
|
604
|
+
row: startRow,
|
|
394
605
|
column: Number(rowValue(row, 'start_column'))
|
|
395
606
|
},
|
|
396
607
|
end: {
|
|
@@ -412,7 +623,8 @@ export async function searchSymbols(workspace, input) {
|
|
|
412
623
|
row: Number(selectionEndRow),
|
|
413
624
|
column: Number(selectionEndColumn)
|
|
414
625
|
}
|
|
415
|
-
}
|
|
626
|
+
},
|
|
627
|
+
...snippetDetails(filePath, rowValue(row, 'language'), file.ok ? file.text : undefined, startRow, input)
|
|
416
628
|
});
|
|
417
629
|
}
|
|
418
630
|
}
|
|
@@ -421,7 +633,10 @@ export async function searchSymbols(workspace, input) {
|
|
|
421
633
|
}
|
|
422
634
|
return {
|
|
423
635
|
ok: true,
|
|
424
|
-
indexPath,
|
|
636
|
+
indexPath: readState.indexPath,
|
|
637
|
+
isStale: readState.isStale,
|
|
638
|
+
staleFiles: readState.staleFiles,
|
|
639
|
+
refreshed: readState.refreshed,
|
|
425
640
|
total: symbols.length,
|
|
426
641
|
symbols
|
|
427
642
|
};
|
|
@@ -430,7 +645,7 @@ export async function searchSymbols(workspace, input) {
|
|
|
430
645
|
return failure(error instanceof Error ? error.message : String(error));
|
|
431
646
|
}
|
|
432
647
|
finally {
|
|
433
|
-
database.close();
|
|
648
|
+
readState?.database.close();
|
|
434
649
|
}
|
|
435
650
|
}
|
|
436
651
|
export async function getIndexStatus(workspace) {
|
|
@@ -438,24 +653,13 @@ export async function getIndexStatus(workspace) {
|
|
|
438
653
|
const database = await openDatabase(indexPath);
|
|
439
654
|
try {
|
|
440
655
|
initSchema(database);
|
|
441
|
-
const
|
|
442
|
-
const currentFiles = await workspace.listSourceFiles();
|
|
443
|
-
let staleFiles = 0;
|
|
444
|
-
for (const file of currentFiles) {
|
|
445
|
-
if (!isCurrent(storedFiles.get(file.relativePath), file)) {
|
|
446
|
-
staleFiles += 1;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
for (const storedPath of storedFiles.keys()) {
|
|
450
|
-
if (!currentFiles.some(file => file.relativePath === storedPath)) {
|
|
451
|
-
staleFiles += 1;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
656
|
+
const staleFiles = await countStaleFiles(workspace, database);
|
|
454
657
|
return {
|
|
455
658
|
ok: true,
|
|
456
659
|
indexPath,
|
|
457
660
|
indexedFiles: scalarCount(database, 'SELECT COUNT(*) FROM files WHERE parse_status = "ok"'),
|
|
458
661
|
symbols: scalarCount(database, 'SELECT COUNT(*) FROM symbols'),
|
|
662
|
+
references: scalarCount(database, 'SELECT COUNT(*) FROM reference_captures'),
|
|
459
663
|
staleFiles
|
|
460
664
|
};
|
|
461
665
|
}
|
|
@@ -24,7 +24,7 @@ export async function findReferences(workspace, input) {
|
|
|
24
24
|
}
|
|
25
25
|
return { ok: true, references };
|
|
26
26
|
}
|
|
27
|
-
function referenceQueryForLanguage(language) {
|
|
27
|
+
export function referenceQueryForLanguage(language) {
|
|
28
28
|
switch (language) {
|
|
29
29
|
case 'typescript':
|
|
30
30
|
case 'tsx':
|
package/dist/analysis/summary.js
CHANGED
|
@@ -13,8 +13,13 @@ export async function summarizeFile(workspace, filePath) {
|
|
|
13
13
|
language: parsed.language,
|
|
14
14
|
lineCount: countLines(file.text),
|
|
15
15
|
symbols: listSymbols(parsed),
|
|
16
|
-
imports: findImports(
|
|
17
|
-
exports: findExports(
|
|
16
|
+
imports: findImports(parsed),
|
|
17
|
+
exports: findExports(parsed),
|
|
18
|
+
sources: {
|
|
19
|
+
symbols: 'ast',
|
|
20
|
+
imports: 'ast',
|
|
21
|
+
exports: 'ast'
|
|
22
|
+
}
|
|
18
23
|
};
|
|
19
24
|
}
|
|
20
25
|
function countLines(text) {
|
|
@@ -22,15 +27,58 @@ function countLines(text) {
|
|
|
22
27
|
return 0;
|
|
23
28
|
return text.replace(/\r\n|\r|\n$/, '').split(/\r\n|\r|\n/).length;
|
|
24
29
|
}
|
|
25
|
-
function findImports(
|
|
26
|
-
return
|
|
30
|
+
function findImports(parsed) {
|
|
31
|
+
return parsed.tree.rootNode.namedChildren
|
|
32
|
+
.filter(node => isImportNode(parsed.language, node.type))
|
|
33
|
+
.map(node => firstLine(node.text));
|
|
27
34
|
}
|
|
28
|
-
function
|
|
29
|
-
|
|
35
|
+
function isImportNode(language, nodeType) {
|
|
36
|
+
switch (language) {
|
|
37
|
+
case 'python':
|
|
38
|
+
return nodeType === 'import_statement' || nodeType === 'import_from_statement';
|
|
39
|
+
case 'javascript':
|
|
40
|
+
case 'typescript':
|
|
41
|
+
case 'tsx':
|
|
42
|
+
return nodeType === 'import_statement';
|
|
43
|
+
}
|
|
30
44
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
function findExports(parsed) {
|
|
46
|
+
if (parsed.language === 'python') {
|
|
47
|
+
return findPythonAllExports(parsed);
|
|
48
|
+
}
|
|
49
|
+
return parsed.tree.rootNode.namedChildren
|
|
50
|
+
.filter(node => isExportNode(parsed.language, node.type))
|
|
51
|
+
.map(node => firstLine(node.text));
|
|
52
|
+
}
|
|
53
|
+
function isExportNode(language, nodeType) {
|
|
54
|
+
switch (language) {
|
|
55
|
+
case 'javascript':
|
|
56
|
+
case 'typescript':
|
|
57
|
+
case 'tsx':
|
|
58
|
+
return nodeType === 'export_statement';
|
|
59
|
+
case 'python':
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function findPythonAllExports(parsed) {
|
|
64
|
+
return parsed.tree.rootNode.namedChildren.flatMap(node => pythonAllExportNames(node));
|
|
65
|
+
}
|
|
66
|
+
function pythonAllExportNames(node) {
|
|
67
|
+
if (node.type !== 'expression_statement')
|
|
68
|
+
return [];
|
|
69
|
+
const assignment = node.namedChildren[0];
|
|
70
|
+
if (!assignment || assignment.type !== 'assignment')
|
|
71
|
+
return [];
|
|
72
|
+
const [target, value] = assignment.namedChildren;
|
|
73
|
+
if (!target || !value || target.type !== 'identifier' || target.text !== '__all__')
|
|
74
|
+
return [];
|
|
75
|
+
if (value.type !== 'list' && value.type !== 'tuple')
|
|
76
|
+
return [];
|
|
77
|
+
return value.namedChildren
|
|
78
|
+
.filter(child => child.type === 'string')
|
|
79
|
+
.map(child => child.namedChildren.find(part => part.type === 'string_content')?.text ?? '')
|
|
35
80
|
.filter(Boolean);
|
|
36
81
|
}
|
|
82
|
+
function firstLine(text) {
|
|
83
|
+
return text.split(/\r\n|\r|\n/, 1)[0].trim();
|
|
84
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { buildContext as buildContextAnalysis } from './analysis/context.js';
|
|
3
3
|
import { findDefinitions } from './analysis/definitions.js';
|
|
4
|
-
import { clearIndex as clearWorkspaceIndex, findIndexedDefinitions, getIndexStatus as getWorkspaceIndexStatus, indexWorkspace as indexWorkspaceAnalysis, searchSymbols as searchIndexedSymbols } from './analysis/index.js';
|
|
4
|
+
import { clearIndex as clearWorkspaceIndex, findIndexedDefinitions, findIndexedReferences, getIndexStatus as getWorkspaceIndexStatus, indexWorkspace as indexWorkspaceAnalysis, searchSymbols as searchIndexedSymbols } from './analysis/index.js';
|
|
5
5
|
import { runTreeSitterQuery } from './analysis/query.js';
|
|
6
6
|
import { findReferences as findReferencesAnalysis } from './analysis/references.js';
|
|
7
7
|
import { summarizeFile as summarizeFileAnalysis } from './analysis/summary.js';
|
|
@@ -10,6 +10,7 @@ import { parseSourceFile } from './parser.js';
|
|
|
10
10
|
import { jsonResult, toolFailure } from './result.js';
|
|
11
11
|
const symbolKindSchema = z.enum(['function', 'method', 'class', 'variable', 'interface', 'type']);
|
|
12
12
|
const detailSchema = z.enum(['compact', 'full']);
|
|
13
|
+
const contextLineCountSchema = z.number().int().min(0).max(10);
|
|
13
14
|
export function createToolHandlers(workspace) {
|
|
14
15
|
return {
|
|
15
16
|
async listSymbols(input) {
|
|
@@ -80,6 +81,12 @@ export function createToolHandlers(workspace) {
|
|
|
80
81
|
return toolFailure(result.error.code, result.error.message);
|
|
81
82
|
return jsonResult(result);
|
|
82
83
|
},
|
|
84
|
+
async findIndexedReferences(input) {
|
|
85
|
+
const result = await findIndexedReferences(workspace, input);
|
|
86
|
+
if (!result.ok)
|
|
87
|
+
return toolFailure(result.error.code, result.error.message);
|
|
88
|
+
return jsonResult(result);
|
|
89
|
+
},
|
|
83
90
|
async getIndexStatus(_input) {
|
|
84
91
|
const result = await getWorkspaceIndexStatus(workspace);
|
|
85
92
|
if (!result.ok)
|
|
@@ -139,8 +146,18 @@ export function registerTools(server, workspace) {
|
|
|
139
146
|
title: 'Build context',
|
|
140
147
|
description: 'Build markdown context for supported source files.',
|
|
141
148
|
inputSchema: {
|
|
142
|
-
paths: z.array(z.string()),
|
|
143
|
-
detail: detailSchema
|
|
149
|
+
paths: z.array(z.string()).optional(),
|
|
150
|
+
detail: detailSchema,
|
|
151
|
+
indexedSearch: z
|
|
152
|
+
.object({
|
|
153
|
+
query: z.string(),
|
|
154
|
+
kinds: z.array(symbolKindSchema).optional(),
|
|
155
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
156
|
+
refreshIfStale: z.boolean().optional(),
|
|
157
|
+
contextBefore: contextLineCountSchema.optional(),
|
|
158
|
+
contextAfter: contextLineCountSchema.optional()
|
|
159
|
+
})
|
|
160
|
+
.optional()
|
|
144
161
|
}
|
|
145
162
|
}, handlers.buildContext);
|
|
146
163
|
server.registerTool('index_workspace', {
|
|
@@ -154,7 +171,11 @@ export function registerTools(server, workspace) {
|
|
|
154
171
|
inputSchema: {
|
|
155
172
|
query: z.string(),
|
|
156
173
|
kinds: z.array(symbolKindSchema).optional(),
|
|
157
|
-
limit: z.number().int().positive().max(500).optional()
|
|
174
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
175
|
+
refreshIfStale: z.boolean().optional(),
|
|
176
|
+
contextBefore: contextLineCountSchema.optional(),
|
|
177
|
+
contextAfter: contextLineCountSchema.optional(),
|
|
178
|
+
includePreview: z.boolean().optional()
|
|
158
179
|
}
|
|
159
180
|
}, handlers.searchSymbols);
|
|
160
181
|
server.registerTool('find_indexed_definition', {
|
|
@@ -163,9 +184,25 @@ export function registerTools(server, workspace) {
|
|
|
163
184
|
inputSchema: {
|
|
164
185
|
name: z.string(),
|
|
165
186
|
kinds: z.array(symbolKindSchema).optional(),
|
|
166
|
-
limit: z.number().int().positive().max(500).optional()
|
|
187
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
188
|
+
refreshIfStale: z.boolean().optional(),
|
|
189
|
+
contextBefore: contextLineCountSchema.optional(),
|
|
190
|
+
contextAfter: contextLineCountSchema.optional(),
|
|
191
|
+
includePreview: z.boolean().optional()
|
|
167
192
|
}
|
|
168
193
|
}, handlers.findIndexedDefinition);
|
|
194
|
+
server.registerTool('find_indexed_references', {
|
|
195
|
+
title: 'Find indexed references',
|
|
196
|
+
description: 'Find identifier references from the SQLite workspace index with snippets.',
|
|
197
|
+
inputSchema: {
|
|
198
|
+
name: z.string(),
|
|
199
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
200
|
+
refreshIfStale: z.boolean().optional(),
|
|
201
|
+
contextBefore: contextLineCountSchema.optional(),
|
|
202
|
+
contextAfter: contextLineCountSchema.optional(),
|
|
203
|
+
includePreview: z.boolean().optional()
|
|
204
|
+
}
|
|
205
|
+
}, handlers.findIndexedReferences);
|
|
169
206
|
server.registerTool('get_index_status', {
|
|
170
207
|
title: 'Get index status',
|
|
171
208
|
description: 'Return SQLite index path, indexed file count, symbol count, and stale file count.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "syntax-map-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Tree-sitter based code analysis MCP server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
20
|
"README.md",
|
|
21
|
+
"CHANGELOG.md",
|
|
21
22
|
"LICENSE"
|
|
22
23
|
],
|
|
23
24
|
"publishConfig": {
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
"build": "tsc -p tsconfig.json",
|
|
29
30
|
"test": "vitest run",
|
|
30
31
|
"test:watch": "vitest",
|
|
31
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"release:check": "npm run typecheck && npm test && npm run build && npm pack --dry-run"
|
|
32
34
|
},
|
|
33
35
|
"dependencies": {
|
|
34
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|