token-pilot 0.7.5 → 0.8.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/CHANGELOG.md +28 -0
- package/README.md +34 -11
- package/dist/ast-index/client.d.ts +21 -1
- package/dist/ast-index/client.js +167 -0
- package/dist/ast-index/types.d.ts +29 -0
- package/dist/core/validation.d.ts +8 -0
- package/dist/core/validation.js +27 -0
- package/dist/git/file-watcher.d.ts +10 -1
- package/dist/git/file-watcher.js +23 -1
- package/dist/handlers/code-audit.d.ts +9 -0
- package/dist/handlers/code-audit.js +200 -0
- package/dist/handlers/smart-read.js +10 -0
- package/dist/index.js +61 -3
- package/dist/server.js +42 -5
- package/package.json +8 -3
- package/dist/ast-index/binary-manager.d.ts.map +0 -1
- package/dist/ast-index/binary-manager.js.map +0 -1
- package/dist/ast-index/client.d.ts.map +0 -1
- package/dist/ast-index/client.js.map +0 -1
- package/dist/ast-index/tar-extract.d.ts.map +0 -1
- package/dist/ast-index/tar-extract.js.map +0 -1
- package/dist/ast-index/types.d.ts.map +0 -1
- package/dist/ast-index/types.js.map +0 -1
- package/dist/config/defaults.d.ts.map +0 -1
- package/dist/config/defaults.js.map +0 -1
- package/dist/config/loader.d.ts.map +0 -1
- package/dist/config/loader.js.map +0 -1
- package/dist/core/context-registry.d.ts.map +0 -1
- package/dist/core/context-registry.js.map +0 -1
- package/dist/core/file-cache.d.ts.map +0 -1
- package/dist/core/file-cache.js.map +0 -1
- package/dist/core/format-duration.d.ts.map +0 -1
- package/dist/core/format-duration.js.map +0 -1
- package/dist/core/session-analytics.d.ts.map +0 -1
- package/dist/core/session-analytics.js.map +0 -1
- package/dist/core/symbol-resolver.d.ts.map +0 -1
- package/dist/core/symbol-resolver.js.map +0 -1
- package/dist/core/token-estimator.d.ts.map +0 -1
- package/dist/core/token-estimator.js.map +0 -1
- package/dist/core/validation.d.ts.map +0 -1
- package/dist/core/validation.js.map +0 -1
- package/dist/formatters/structure.d.ts.map +0 -1
- package/dist/formatters/structure.js.map +0 -1
- package/dist/git/file-watcher.d.ts.map +0 -1
- package/dist/git/file-watcher.js.map +0 -1
- package/dist/git/watcher.d.ts.map +0 -1
- package/dist/git/watcher.js.map +0 -1
- package/dist/handlers/class-hierarchy.d.ts.map +0 -1
- package/dist/handlers/class-hierarchy.js.map +0 -1
- package/dist/handlers/export-ast-index.d.ts.map +0 -1
- package/dist/handlers/export-ast-index.js.map +0 -1
- package/dist/handlers/find-implementations.d.ts.map +0 -1
- package/dist/handlers/find-implementations.js.map +0 -1
- package/dist/handlers/find-unused.d.ts.map +0 -1
- package/dist/handlers/find-unused.js.map +0 -1
- package/dist/handlers/find-usages.d.ts.map +0 -1
- package/dist/handlers/find-usages.js.map +0 -1
- package/dist/handlers/non-code.d.ts.map +0 -1
- package/dist/handlers/non-code.js.map +0 -1
- package/dist/handlers/outline.d.ts.map +0 -1
- package/dist/handlers/outline.js.map +0 -1
- package/dist/handlers/project-overview.d.ts.map +0 -1
- package/dist/handlers/project-overview.js.map +0 -1
- package/dist/handlers/read-diff.d.ts.map +0 -1
- package/dist/handlers/read-diff.js.map +0 -1
- package/dist/handlers/read-for-edit.d.ts.map +0 -1
- package/dist/handlers/read-for-edit.js.map +0 -1
- package/dist/handlers/read-range.d.ts.map +0 -1
- package/dist/handlers/read-range.js.map +0 -1
- package/dist/handlers/read-symbol.d.ts.map +0 -1
- package/dist/handlers/read-symbol.js.map +0 -1
- package/dist/handlers/related-files.d.ts.map +0 -1
- package/dist/handlers/related-files.js.map +0 -1
- package/dist/handlers/search-code.d.ts.map +0 -1
- package/dist/handlers/search-code.js.map +0 -1
- package/dist/handlers/smart-read-many.d.ts.map +0 -1
- package/dist/handlers/smart-read-many.js.map +0 -1
- package/dist/handlers/smart-read.d.ts.map +0 -1
- package/dist/handlers/smart-read.js.map +0 -1
- package/dist/hooks/installer.d.ts.map +0 -1
- package/dist/hooks/installer.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integration/context-mode-detector.d.ts.map +0 -1
- package/dist/integration/context-mode-detector.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes to Token Pilot will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.8.0] - 2026-03-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`code_audit` tool** — find code quality issues in one call: TODO/FIXME comments (`check="todo"`), deprecated symbols (`check="deprecated"`), structural code patterns via ast-grep (`check="pattern"`), decorator search (`check="annotations"`), or combined audit (`check="all"`).
|
|
12
|
+
- **Incremental index update on file changes** — file watcher now triggers `ast-index update` (debounced 2s) after edits. Keeps index fresh for find_usages, find_unused, code_audit.
|
|
13
|
+
- **ast-index client methods** — `agrep()`, `todo()`, `deprecated()`, `annotations()`, `incrementalUpdate()`.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **smart_read on directories** — now returns helpful message instead of EISDIR crash.
|
|
17
|
+
- **MCP instructions** — added "COMBINE BOTH" section for audit tasks (Token Pilot + Grep).
|
|
18
|
+
|
|
19
|
+
## [0.7.6] - 2026-03-07
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **`npx token-pilot init`** — one command creates `.mcp.json` with both token-pilot and context-mode configured. Idempotent — safely updates existing configs without overwriting.
|
|
23
|
+
- **MCP Server Instructions** — protocol-level `instructions` field tells AI agents WHEN to use Token Pilot tools instead of built-in defaults. Works universally on all MCP clients.
|
|
24
|
+
- **Improved tool descriptions** — each tool explicitly states what built-in tool it replaces (e.g. "Use INSTEAD OF Read/cat").
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **3 high severity vulnerabilities** — updated hono and express-rate-limit.
|
|
28
|
+
- **npm package size** — excluded source maps from package. 505 kB → 286 kB (−43%).
|
|
29
|
+
- **Accurate thresholds** — README and instructions now correctly state smallFileThreshold=200 (was 80).
|
|
30
|
+
- **read_diff documentation** — clarified that smart_read must be called BEFORE editing to create baseline snapshot.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- **README** — honest metrics (60-80%), Quick Start with `init` command, MCP instructions section, Codex/Antigravity support.
|
|
34
|
+
- **npm keywords** — added `codex`, `cline`, `model-context-protocol`, `token-savings`.
|
|
35
|
+
|
|
8
36
|
## [0.7.4] - 2026-03-07
|
|
9
37
|
|
|
10
38
|
### Added
|
package/README.md
CHANGED
|
@@ -13,13 +13,36 @@ Token Pilot: smart_read("user-service.ts") → 15-line outline → ~200 tok
|
|
|
13
13
|
After edit: read_diff("user-service.ts") → ~20 tokens
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
**~80% reduction** in this example. Files under
|
|
16
|
+
**~80% reduction** in this example. Files under 200 lines are returned in full automatically (no overhead for small files). Real savings start at ~200+ lines.
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
20
|
-
###
|
|
20
|
+
### Quick Start (recommended)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
One command creates `.mcp.json` with token-pilot + context-mode:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx -y token-pilot init
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Safe to run in any project — if `.mcp.json` already exists, only adds missing servers without overwriting existing config.
|
|
29
|
+
|
|
30
|
+
This generates:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"token-pilot": { "command": "npx", "args": ["-y", "token-pilot"] },
|
|
36
|
+
"context-mode": { "command": "npx", "args": ["-y", "claude-context-mode"] }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**That's it.** Restart your AI assistant. Both packages are downloaded automatically, ast-index binary is fetched on first run. No Rust, no Cargo, no manual setup.
|
|
42
|
+
|
|
43
|
+
### Manual Setup
|
|
44
|
+
|
|
45
|
+
Add to your `.mcp.json` (project-level or `~/.mcp.json` for global):
|
|
23
46
|
|
|
24
47
|
```json
|
|
25
48
|
{
|
|
@@ -32,9 +55,7 @@ Zero install. Add to your `.mcp.json` (project-level or `~/.mcp.json` for global
|
|
|
32
55
|
}
|
|
33
56
|
```
|
|
34
57
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Works with: **Cursor**, **Cline**, **Continue**, **Codex**, **Antigravity**, and any MCP-compatible client.
|
|
58
|
+
Works with: **Claude Code**, **Cursor**, **Codex**, **Antigravity**, **Cline**, and any MCP-compatible client.
|
|
38
59
|
|
|
39
60
|
#### Cursor
|
|
40
61
|
|
|
@@ -124,7 +145,7 @@ For more control, you can add rules to your project:
|
|
|
124
145
|
- **Cursor** → `.cursorrules` in project root
|
|
125
146
|
- **Codex** → `AGENTS.md` in project root
|
|
126
147
|
|
|
127
|
-
## MCP Tools (
|
|
148
|
+
## MCP Tools (13)
|
|
128
149
|
|
|
129
150
|
### Core Reading
|
|
130
151
|
|
|
@@ -134,7 +155,7 @@ For more control, you can add rules to your project:
|
|
|
134
155
|
| `read_symbol` | `Read` + scroll | Load source of a specific symbol. Supports `Class.method`. `show` param: full/head/tail/outline. |
|
|
135
156
|
| `read_for_edit` | `Read` before `Edit` | Minimal RAW code around a symbol — copy directly as `old_string` for Edit tool. |
|
|
136
157
|
| `read_range` | `Read` offset | Read a specific line range from a file. |
|
|
137
|
-
| `read_diff` | re-`Read` | Show only
|
|
158
|
+
| `read_diff` | re-`Read` | Show only changed hunks since last smart_read. Requires smart_read before editing (for baseline). Works with any edit tool. |
|
|
138
159
|
| `smart_read_many` | multiple `Read` | Batch smart_read for up to 20 files in one call. |
|
|
139
160
|
|
|
140
161
|
### Search & Navigation
|
|
@@ -146,6 +167,7 @@ For more control, you can add rules to your project:
|
|
|
146
167
|
| `related_files` | manual explore | Import graph: what a file imports, what imports it, test files. |
|
|
147
168
|
| `outline` | multiple `smart_read` | Compact overview of all code files in a directory. One call instead of 5-6. |
|
|
148
169
|
| `find_unused` | manual | Detect dead code — unused functions, classes, variables. |
|
|
170
|
+
| `code_audit` | multiple `Grep` | Code quality issues in one call: TODO/FIXME comments, deprecated symbols, structural code patterns (via ast-grep), decorator search. |
|
|
149
171
|
|
|
150
172
|
### Analytics
|
|
151
173
|
|
|
@@ -158,6 +180,7 @@ For more control, you can add rules to your project:
|
|
|
158
180
|
```bash
|
|
159
181
|
token-pilot # Start MCP server (uses cwd as project root)
|
|
160
182
|
token-pilot /path/to/project # Start with specific project root
|
|
183
|
+
token-pilot init [dir] # Create/update .mcp.json (token-pilot + context-mode)
|
|
161
184
|
token-pilot install-ast-index # Download ast-index binary (auto on first run)
|
|
162
185
|
token-pilot install-hook [root] # Install PreToolUse hook
|
|
163
186
|
token-pilot uninstall-hook # Remove hook
|
|
@@ -174,7 +197,7 @@ Create `.token-pilot.json` in your project root to customize behavior:
|
|
|
174
197
|
```json
|
|
175
198
|
{
|
|
176
199
|
"smartRead": {
|
|
177
|
-
"smallFileThreshold":
|
|
200
|
+
"smallFileThreshold": 200,
|
|
178
201
|
"advisoryReminders": true
|
|
179
202
|
},
|
|
180
203
|
"cache": {
|
|
@@ -210,7 +233,7 @@ All fields are optional — sensible defaults are used for anything not specifie
|
|
|
210
233
|
|
|
211
234
|
| Option | Default | Description |
|
|
212
235
|
|--------|---------|-------------|
|
|
213
|
-
| `smartRead.smallFileThreshold` | `
|
|
236
|
+
| `smartRead.smallFileThreshold` | `200` | Files with fewer lines are returned in full (no AST overhead). |
|
|
214
237
|
| `cache.maxSizeMB` | `100` | Max memory for file cache. LRU eviction when exceeded. |
|
|
215
238
|
| `cache.watchFiles` | `true` | Auto-invalidate cache on file changes (chokidar). |
|
|
216
239
|
| `git.watchHead` | `true` | Watch `.git/HEAD` for branch switches, invalidate changed files. |
|
|
@@ -232,7 +255,6 @@ When both are configured, Token Pilot automatically:
|
|
|
232
255
|
- Detects context-mode via `.mcp.json`
|
|
233
256
|
- Suggests context-mode for large non-code files
|
|
234
257
|
- Shows combined architecture info in `session_analytics`
|
|
235
|
-
- Provides `export_ast_index` to feed AST data into context-mode's BM25 index
|
|
236
258
|
|
|
237
259
|
**Combined savings: 60-80%** in a typical coding session.
|
|
238
260
|
|
|
@@ -320,6 +342,7 @@ src/
|
|
|
320
342
|
related-files.ts — related_files handler (import graph)
|
|
321
343
|
outline.ts — outline handler (directory overview)
|
|
322
344
|
find-unused.ts — find_unused handler
|
|
345
|
+
code-audit.ts — code_audit handler (TODOs, deprecated, patterns)
|
|
323
346
|
project-overview.ts — project_overview (via ast-index map + conventions)
|
|
324
347
|
non-code.ts — JSON/YAML/MD/TOML structural summaries
|
|
325
348
|
export-ast-index.ts — AST export for context-mode BM25
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FileStructure } from '../types.js';
|
|
2
|
-
import type { AstIndexSymbolDetail, AstIndexSearchResult, AstIndexUsageResult, AstIndexImplementation, AstIndexHierarchyNode, AstIndexRefsResponse, AstIndexMapResponse, AstIndexConventionsResponse, AstIndexCallerEntry, AstIndexCallTreeNode, AstIndexChangedEntry, AstIndexUnusedSymbol, AstIndexImportEntry } from './types.js';
|
|
2
|
+
import type { AstIndexSymbolDetail, AstIndexSearchResult, AstIndexUsageResult, AstIndexImplementation, AstIndexHierarchyNode, AstIndexRefsResponse, AstIndexMapResponse, AstIndexConventionsResponse, AstIndexCallerEntry, AstIndexCallTreeNode, AstIndexChangedEntry, AstIndexUnusedSymbol, AstIndexImportEntry, AstIndexAgrepMatch, AstIndexTodoEntry, AstIndexDeprecatedEntry, AstIndexAnnotationEntry } from './types.js';
|
|
3
3
|
export declare class AstIndexClient {
|
|
4
4
|
private static readonly MAX_INDEX_FILES;
|
|
5
5
|
private binaryPath;
|
|
@@ -11,6 +11,7 @@ export declare class AstIndexClient {
|
|
|
11
11
|
private timeout;
|
|
12
12
|
private configBinaryPath;
|
|
13
13
|
private autoInstall;
|
|
14
|
+
private astGrepAvailable;
|
|
14
15
|
constructor(projectRoot: string, timeout?: number, options?: {
|
|
15
16
|
binaryPath?: string | null;
|
|
16
17
|
autoInstall?: boolean;
|
|
@@ -92,6 +93,25 @@ export declare class AstIndexClient {
|
|
|
92
93
|
*/
|
|
93
94
|
fileImports(filePath: string): Promise<AstIndexImportEntry[]>;
|
|
94
95
|
private parseImportsText;
|
|
96
|
+
/** Check if ast-grep (sg) is available for structural pattern search */
|
|
97
|
+
private checkAstGrep;
|
|
98
|
+
/** Structural pattern search via ast-grep. Requires ast-grep (sg) installed. */
|
|
99
|
+
agrep(pattern: string, options?: {
|
|
100
|
+
lang?: string;
|
|
101
|
+
limit?: number;
|
|
102
|
+
}): Promise<AstIndexAgrepMatch[]>;
|
|
103
|
+
private parseAgrepText;
|
|
104
|
+
/** Find TODO/FIXME/HACK comments in the project */
|
|
105
|
+
todo(): Promise<AstIndexTodoEntry[]>;
|
|
106
|
+
private parseTodoText;
|
|
107
|
+
/** Find @Deprecated symbols in the project */
|
|
108
|
+
deprecated(): Promise<AstIndexDeprecatedEntry[]>;
|
|
109
|
+
private parseDeprecatedText;
|
|
110
|
+
/** Find symbols with a specific annotation/decorator */
|
|
111
|
+
annotations(name: string): Promise<AstIndexAnnotationEntry[]>;
|
|
112
|
+
private parseAnnotationsText;
|
|
113
|
+
/** Trigger incremental index update (called by file watcher after edits) */
|
|
114
|
+
incrementalUpdate(): Promise<void>;
|
|
95
115
|
isAvailable(): boolean;
|
|
96
116
|
/** Returns true if the index was built but found >50k files (node_modules leak) */
|
|
97
117
|
isOversized(): boolean;
|
package/dist/ast-index/client.js
CHANGED
|
@@ -16,6 +16,7 @@ export class AstIndexClient {
|
|
|
16
16
|
timeout;
|
|
17
17
|
configBinaryPath;
|
|
18
18
|
autoInstall;
|
|
19
|
+
astGrepAvailable = null;
|
|
19
20
|
constructor(projectRoot, timeout = 5000, options) {
|
|
20
21
|
this.projectRoot = projectRoot;
|
|
21
22
|
this.timeout = timeout;
|
|
@@ -636,6 +637,172 @@ export class AstIndexClient {
|
|
|
636
637
|
}
|
|
637
638
|
return entries;
|
|
638
639
|
}
|
|
640
|
+
// --- Code audit commands ---
|
|
641
|
+
/** Check if ast-grep (sg) is available for structural pattern search */
|
|
642
|
+
async checkAstGrep() {
|
|
643
|
+
if (this.astGrepAvailable !== null)
|
|
644
|
+
return this.astGrepAvailable;
|
|
645
|
+
try {
|
|
646
|
+
await execFileAsync('sg', ['--version'], { timeout: 3000 });
|
|
647
|
+
this.astGrepAvailable = true;
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
this.astGrepAvailable = false;
|
|
651
|
+
}
|
|
652
|
+
return this.astGrepAvailable;
|
|
653
|
+
}
|
|
654
|
+
/** Structural pattern search via ast-grep. Requires ast-grep (sg) installed. */
|
|
655
|
+
async agrep(pattern, options) {
|
|
656
|
+
if (this.indexDisabled || this.indexOversized)
|
|
657
|
+
return [];
|
|
658
|
+
await this.ensureIndex();
|
|
659
|
+
const available = await this.checkAstGrep();
|
|
660
|
+
if (!available) {
|
|
661
|
+
throw new Error('ast-grep (sg) not installed — required for structural pattern search.\n' +
|
|
662
|
+
'Install: brew install ast-grep OR npm i -g @ast-grep/cli\n' +
|
|
663
|
+
'Alternative: use Grep/ripgrep for text-based pattern search.');
|
|
664
|
+
}
|
|
665
|
+
const limit = options?.limit ?? 50;
|
|
666
|
+
const args = ['agrep', pattern];
|
|
667
|
+
if (options?.lang)
|
|
668
|
+
args.push('--lang', options.lang);
|
|
669
|
+
args.push('--limit', String(limit));
|
|
670
|
+
try {
|
|
671
|
+
const result = await this.exec(args, 15000);
|
|
672
|
+
return this.parseAgrepText(result).slice(0, limit);
|
|
673
|
+
}
|
|
674
|
+
catch (err) {
|
|
675
|
+
console.error(`[token-pilot] ast-index agrep failed: ${err instanceof Error ? err.message : err}`);
|
|
676
|
+
return [];
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
parseAgrepText(text) {
|
|
680
|
+
const results = [];
|
|
681
|
+
for (const line of text.split('\n')) {
|
|
682
|
+
if (!line.trim())
|
|
683
|
+
continue;
|
|
684
|
+
// Format: file:line:matched_text OR file:line: matched_text
|
|
685
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
686
|
+
if (match) {
|
|
687
|
+
results.push({
|
|
688
|
+
file: match[1],
|
|
689
|
+
line: parseInt(match[2], 10),
|
|
690
|
+
text: match[3].trim(),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return results;
|
|
695
|
+
}
|
|
696
|
+
/** Find TODO/FIXME/HACK comments in the project */
|
|
697
|
+
async todo() {
|
|
698
|
+
if (this.indexDisabled || this.indexOversized)
|
|
699
|
+
return [];
|
|
700
|
+
await this.ensureIndex();
|
|
701
|
+
try {
|
|
702
|
+
const result = await this.exec(['todo'], 15000);
|
|
703
|
+
return this.parseTodoText(result);
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
706
|
+
console.error(`[token-pilot] ast-index todo failed: ${err instanceof Error ? err.message : err}`);
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
parseTodoText(text) {
|
|
711
|
+
const results = [];
|
|
712
|
+
for (const line of text.split('\n')) {
|
|
713
|
+
if (!line.trim())
|
|
714
|
+
continue;
|
|
715
|
+
// Try format: file:line: KIND: message OR file:line: KIND message
|
|
716
|
+
const match = line.match(/^(.+?):(\d+):\s*(TODO|FIXME|HACK|XXX|NOTE|WARN(?:ING)?)[:\s]+(.*)$/i);
|
|
717
|
+
if (match) {
|
|
718
|
+
results.push({
|
|
719
|
+
file: match[1],
|
|
720
|
+
line: parseInt(match[2], 10),
|
|
721
|
+
kind: match[3].toUpperCase(),
|
|
722
|
+
text: match[4].trim(),
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return results;
|
|
727
|
+
}
|
|
728
|
+
/** Find @Deprecated symbols in the project */
|
|
729
|
+
async deprecated() {
|
|
730
|
+
if (this.indexDisabled || this.indexOversized)
|
|
731
|
+
return [];
|
|
732
|
+
await this.ensureIndex();
|
|
733
|
+
try {
|
|
734
|
+
const result = await this.exec(['deprecated'], 15000);
|
|
735
|
+
return this.parseDeprecatedText(result);
|
|
736
|
+
}
|
|
737
|
+
catch (err) {
|
|
738
|
+
console.error(`[token-pilot] ast-index deprecated failed: ${err instanceof Error ? err.message : err}`);
|
|
739
|
+
return [];
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
parseDeprecatedText(text) {
|
|
743
|
+
const results = [];
|
|
744
|
+
for (const line of text.split('\n')) {
|
|
745
|
+
if (!line.trim())
|
|
746
|
+
continue;
|
|
747
|
+
// Try format: kind name (file:line) - message OR kind name (file:line)
|
|
748
|
+
const match = line.match(/^(\w+)\s+(\S+)\s+\((.+?):(\d+)\)(?:\s*-\s*(.+))?$/);
|
|
749
|
+
if (match) {
|
|
750
|
+
results.push({
|
|
751
|
+
kind: match[1],
|
|
752
|
+
name: match[2],
|
|
753
|
+
file: match[3],
|
|
754
|
+
line: parseInt(match[4], 10),
|
|
755
|
+
message: match[5]?.trim(),
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return results;
|
|
760
|
+
}
|
|
761
|
+
/** Find symbols with a specific annotation/decorator */
|
|
762
|
+
async annotations(name) {
|
|
763
|
+
if (this.indexDisabled || this.indexOversized)
|
|
764
|
+
return [];
|
|
765
|
+
await this.ensureIndex();
|
|
766
|
+
try {
|
|
767
|
+
const result = await this.exec(['annotations', name], 15000);
|
|
768
|
+
return this.parseAnnotationsText(result, name);
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
console.error(`[token-pilot] ast-index annotations failed: ${err instanceof Error ? err.message : err}`);
|
|
772
|
+
return [];
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
parseAnnotationsText(text, annotationName) {
|
|
776
|
+
const results = [];
|
|
777
|
+
for (const line of text.split('\n')) {
|
|
778
|
+
if (!line.trim())
|
|
779
|
+
continue;
|
|
780
|
+
// Try format: kind name (file:line) OR @Annotation kind name (file:line)
|
|
781
|
+
const match = line.match(/^(?:@\S+\s+)?(\w+)\s+(\S+)\s+\((.+?):(\d+)\)$/);
|
|
782
|
+
if (match) {
|
|
783
|
+
results.push({
|
|
784
|
+
kind: match[1],
|
|
785
|
+
name: match[2],
|
|
786
|
+
file: match[3],
|
|
787
|
+
line: parseInt(match[4], 10),
|
|
788
|
+
annotation: annotationName,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return results;
|
|
793
|
+
}
|
|
794
|
+
/** Trigger incremental index update (called by file watcher after edits) */
|
|
795
|
+
async incrementalUpdate() {
|
|
796
|
+
if (!this.indexed || this.indexDisabled || this.indexOversized)
|
|
797
|
+
return;
|
|
798
|
+
try {
|
|
799
|
+
await this.exec(['update'], 15000);
|
|
800
|
+
}
|
|
801
|
+
catch (err) {
|
|
802
|
+
console.error(`[token-pilot] ast-index incremental update failed: ${err instanceof Error ? err.message : err}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
// --- Utility methods ---
|
|
639
806
|
isAvailable() {
|
|
640
807
|
return this.binaryPath !== null;
|
|
641
808
|
}
|
|
@@ -153,4 +153,33 @@ export interface AstIndexImportEntry {
|
|
|
153
153
|
isDefault?: boolean;
|
|
154
154
|
isNamespace?: boolean;
|
|
155
155
|
}
|
|
156
|
+
/** ast-index agrep — structural pattern search via ast-grep */
|
|
157
|
+
export interface AstIndexAgrepMatch {
|
|
158
|
+
file: string;
|
|
159
|
+
line: number;
|
|
160
|
+
text: string;
|
|
161
|
+
}
|
|
162
|
+
/** ast-index todo — TODO/FIXME/HACK comments */
|
|
163
|
+
export interface AstIndexTodoEntry {
|
|
164
|
+
file: string;
|
|
165
|
+
line: number;
|
|
166
|
+
kind: string;
|
|
167
|
+
text: string;
|
|
168
|
+
}
|
|
169
|
+
/** ast-index deprecated — @Deprecated symbols */
|
|
170
|
+
export interface AstIndexDeprecatedEntry {
|
|
171
|
+
name: string;
|
|
172
|
+
kind: string;
|
|
173
|
+
file: string;
|
|
174
|
+
line: number;
|
|
175
|
+
message?: string;
|
|
176
|
+
}
|
|
177
|
+
/** ast-index annotations — symbols with specific decorator/annotation */
|
|
178
|
+
export interface AstIndexAnnotationEntry {
|
|
179
|
+
name: string;
|
|
180
|
+
kind: string;
|
|
181
|
+
file: string;
|
|
182
|
+
line: number;
|
|
183
|
+
annotation: string;
|
|
184
|
+
}
|
|
156
185
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -76,6 +76,14 @@ export declare function validateFindUnusedArgs(args: unknown): {
|
|
|
76
76
|
export_only?: boolean;
|
|
77
77
|
limit?: number;
|
|
78
78
|
};
|
|
79
|
+
export interface CodeAuditArgs {
|
|
80
|
+
check: 'pattern' | 'todo' | 'deprecated' | 'annotations' | 'all';
|
|
81
|
+
pattern?: string;
|
|
82
|
+
name?: string;
|
|
83
|
+
lang?: string;
|
|
84
|
+
limit?: number;
|
|
85
|
+
}
|
|
86
|
+
export declare function validateCodeAuditArgs(args: unknown): CodeAuditArgs;
|
|
79
87
|
/** Detect roots that would cause ast-index to scan the entire filesystem */
|
|
80
88
|
export declare function isDangerousRoot(root: string): boolean;
|
|
81
89
|
//# sourceMappingURL=validation.d.ts.map
|
package/dist/core/validation.js
CHANGED
|
@@ -207,6 +207,33 @@ export function validateFindUnusedArgs(args) {
|
|
|
207
207
|
limit: optionalNumber(a.limit, 'limit'),
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
|
+
export function validateCodeAuditArgs(args) {
|
|
211
|
+
if (!args || typeof args !== 'object') {
|
|
212
|
+
throw new Error('Arguments must be an object with a "check" parameter.');
|
|
213
|
+
}
|
|
214
|
+
const a = args;
|
|
215
|
+
const validChecks = ['pattern', 'todo', 'deprecated', 'annotations', 'all'];
|
|
216
|
+
if (typeof a.check !== 'string' || !validChecks.includes(a.check)) {
|
|
217
|
+
throw new Error(`Required parameter "check" must be one of: ${validChecks.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
if (a.check === 'pattern') {
|
|
220
|
+
if (typeof a.pattern !== 'string' || a.pattern.length === 0) {
|
|
221
|
+
throw new Error('Parameter "pattern" is required when check="pattern". Example: "except:" or "print($$$ARGS)"');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (a.check === 'annotations') {
|
|
225
|
+
if (typeof a.name !== 'string' || a.name.length === 0) {
|
|
226
|
+
throw new Error('Parameter "name" is required when check="annotations". Example: "Deprecated" or "Controller"');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
check: a.check,
|
|
231
|
+
pattern: optionalString(a.pattern, 'pattern'),
|
|
232
|
+
name: optionalString(a.name, 'name'),
|
|
233
|
+
lang: optionalString(a.lang, 'lang'),
|
|
234
|
+
limit: optionalNumber(a.limit, 'limit'),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
210
237
|
/** Detect roots that would cause ast-index to scan the entire filesystem */
|
|
211
238
|
export function isDangerousRoot(root) {
|
|
212
239
|
const normalized = root.replace(/\/+$/, '') || '/';
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import type { FileCache } from '../core/file-cache.js';
|
|
2
2
|
import type { ContextRegistry } from '../core/context-registry.js';
|
|
3
|
+
import type { AstIndexClient } from '../ast-index/client.js';
|
|
3
4
|
/**
|
|
4
5
|
* Watches individual files for changes and auto-invalidates cache.
|
|
5
6
|
* Only watches files that have been explicitly added (via watchFile()),
|
|
6
7
|
* NOT the entire project root — avoids scanning thousands of files
|
|
7
8
|
* and permission errors on Docker volumes, restricted dirs, etc.
|
|
9
|
+
*
|
|
10
|
+
* Also triggers debounced ast-index incremental update on file changes
|
|
11
|
+
* to keep the index fresh for find_usages, find_unused, code_audit.
|
|
8
12
|
*/
|
|
9
13
|
export declare class FileWatcher {
|
|
14
|
+
private static readonly UPDATE_DEBOUNCE_MS;
|
|
10
15
|
private fileCache;
|
|
11
16
|
private contextRegistry;
|
|
17
|
+
private astIndex;
|
|
12
18
|
private watcher;
|
|
13
19
|
private watchedFiles;
|
|
14
|
-
|
|
20
|
+
private updateTimer;
|
|
21
|
+
constructor(_projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry, _ignore: string[], astIndex?: AstIndexClient);
|
|
15
22
|
start(): void;
|
|
23
|
+
/** Debounced ast-index incremental update after file changes */
|
|
24
|
+
private scheduleIndexUpdate;
|
|
16
25
|
/** Add a specific file to watch. Called after smart_read/read_symbol loads a file. */
|
|
17
26
|
watchFile(filePath: string): void;
|
|
18
27
|
stop(): void;
|
package/dist/git/file-watcher.js
CHANGED
|
@@ -5,15 +5,22 @@ import { resolve } from 'node:path';
|
|
|
5
5
|
* Only watches files that have been explicitly added (via watchFile()),
|
|
6
6
|
* NOT the entire project root — avoids scanning thousands of files
|
|
7
7
|
* and permission errors on Docker volumes, restricted dirs, etc.
|
|
8
|
+
*
|
|
9
|
+
* Also triggers debounced ast-index incremental update on file changes
|
|
10
|
+
* to keep the index fresh for find_usages, find_unused, code_audit.
|
|
8
11
|
*/
|
|
9
12
|
export class FileWatcher {
|
|
13
|
+
static UPDATE_DEBOUNCE_MS = 2000;
|
|
10
14
|
fileCache;
|
|
11
15
|
contextRegistry;
|
|
16
|
+
astIndex;
|
|
12
17
|
watcher = null;
|
|
13
18
|
watchedFiles = new Set();
|
|
14
|
-
|
|
19
|
+
updateTimer = null;
|
|
20
|
+
constructor(_projectRoot, fileCache, contextRegistry, _ignore, astIndex) {
|
|
15
21
|
this.fileCache = fileCache;
|
|
16
22
|
this.contextRegistry = contextRegistry;
|
|
23
|
+
this.astIndex = astIndex ?? null;
|
|
17
24
|
}
|
|
18
25
|
start() {
|
|
19
26
|
// Start with an empty watcher — files are added on demand via watchFile()
|
|
@@ -30,14 +37,26 @@ export class FileWatcher {
|
|
|
30
37
|
if (this.fileCache.get(absPath)) {
|
|
31
38
|
this.fileCache.invalidate(absPath);
|
|
32
39
|
}
|
|
40
|
+
this.scheduleIndexUpdate();
|
|
33
41
|
});
|
|
34
42
|
this.watcher.on('unlink', (filePath) => {
|
|
35
43
|
const absPath = resolve(filePath);
|
|
36
44
|
this.fileCache.invalidate(absPath);
|
|
37
45
|
this.contextRegistry.forget(absPath);
|
|
38
46
|
this.watchedFiles.delete(absPath);
|
|
47
|
+
this.scheduleIndexUpdate();
|
|
39
48
|
});
|
|
40
49
|
}
|
|
50
|
+
/** Debounced ast-index incremental update after file changes */
|
|
51
|
+
scheduleIndexUpdate() {
|
|
52
|
+
if (!this.astIndex)
|
|
53
|
+
return;
|
|
54
|
+
if (this.updateTimer)
|
|
55
|
+
clearTimeout(this.updateTimer);
|
|
56
|
+
this.updateTimer = setTimeout(() => {
|
|
57
|
+
this.astIndex?.incrementalUpdate().catch(() => { });
|
|
58
|
+
}, FileWatcher.UPDATE_DEBOUNCE_MS);
|
|
59
|
+
}
|
|
41
60
|
/** Add a specific file to watch. Called after smart_read/read_symbol loads a file. */
|
|
42
61
|
watchFile(filePath) {
|
|
43
62
|
const absPath = resolve(filePath);
|
|
@@ -49,6 +68,9 @@ export class FileWatcher {
|
|
|
49
68
|
this.watchedFiles.add(absPath);
|
|
50
69
|
}
|
|
51
70
|
stop() {
|
|
71
|
+
if (this.updateTimer)
|
|
72
|
+
clearTimeout(this.updateTimer);
|
|
73
|
+
this.updateTimer = null;
|
|
52
74
|
this.watcher?.close();
|
|
53
75
|
this.watcher = null;
|
|
54
76
|
this.watchedFiles.clear();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AstIndexClient } from '../ast-index/client.js';
|
|
2
|
+
import type { CodeAuditArgs } from '../core/validation.js';
|
|
3
|
+
export declare function handleCodeAudit(args: CodeAuditArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
|
|
4
|
+
content: Array<{
|
|
5
|
+
type: 'text';
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
}>;
|
|
9
|
+
//# sourceMappingURL=code-audit.d.ts.map
|