token-pilot 0.8.0 → 0.8.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/CHANGELOG.md +15 -0
- package/README.md +4 -0
- package/dist/ast-index/client.d.ts +2 -1
- package/dist/ast-index/client.js +34 -12
- package/dist/handlers/code-audit.js +2 -1
- package/dist/server.js +2 -0
- package/package.json +21 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.2] - 2026-03-08
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **code_audit pattern search** — inject `node_modules/.bin` into PATH so `ast-index agrep` can find `sg` (ast-grep) when it's installed as optional dependency but not in system PATH.
|
|
12
|
+
- **code_audit annotations** — strip `@` prefix from annotation names (`@Injectable` → `Injectable`). ast-index expects names without `@`.
|
|
13
|
+
|
|
14
|
+
## [0.8.1] - 2026-03-08
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **ast-grep auto-install** — `@ast-grep/cli` added as optional dependency. `code_audit(check="pattern")` now works out-of-the-box without manual `brew install ast-grep`.
|
|
18
|
+
- **MCP instructions: security audit guidance** — instructions now recommend Grep for security patterns (password, token, secret, credential) and `find_unused` for dead code detection.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **ast-index stats → JSON parsing** — `--format json` for reliable file count extraction instead of regex on text output.
|
|
22
|
+
|
|
8
23
|
## [0.8.0] - 2026-03-07
|
|
9
24
|
|
|
10
25
|
### Added
|
package/README.md
CHANGED
|
@@ -107,6 +107,10 @@ brew tap defendend/ast-index && brew install ast-index
|
|
|
107
107
|
npx token-pilot install-ast-index
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
### ast-grep (bundled)
|
|
111
|
+
|
|
112
|
+
[ast-grep](https://ast-grep.github.io/) (`sg`) is included as optional dependency for structural code pattern search via `code_audit(check="pattern")`. Installs automatically with `npm i -g token-pilot`.
|
|
113
|
+
|
|
110
114
|
### PreToolUse Hook (Claude Code only)
|
|
111
115
|
|
|
112
116
|
Optional hook that intercepts `Read` calls for large code files (>500 lines) and suggests `smart_read`. Claude Code only.
|
|
@@ -12,6 +12,7 @@ export declare class AstIndexClient {
|
|
|
12
12
|
private configBinaryPath;
|
|
13
13
|
private autoInstall;
|
|
14
14
|
private astGrepAvailable;
|
|
15
|
+
private astGrepBinDir;
|
|
15
16
|
constructor(projectRoot: string, timeout?: number, options?: {
|
|
16
17
|
binaryPath?: string | null;
|
|
17
18
|
autoInstall?: boolean;
|
|
@@ -21,7 +22,7 @@ export declare class AstIndexClient {
|
|
|
21
22
|
private buildIndex;
|
|
22
23
|
/** Mark index as oversized — disables index-dependent tools, outline still works */
|
|
23
24
|
private handleOversizedIndex;
|
|
24
|
-
/** Extract file count from stats output */
|
|
25
|
+
/** Extract file count from stats output (JSON or text) */
|
|
25
26
|
private parseFileCount;
|
|
26
27
|
outline(filePath: string): Promise<FileStructure | null>;
|
|
27
28
|
/**
|
package/dist/ast-index/client.js
CHANGED
|
@@ -17,6 +17,7 @@ export class AstIndexClient {
|
|
|
17
17
|
configBinaryPath;
|
|
18
18
|
autoInstall;
|
|
19
19
|
astGrepAvailable = null;
|
|
20
|
+
astGrepBinDir = null;
|
|
20
21
|
constructor(projectRoot, timeout = 5000, options) {
|
|
21
22
|
this.projectRoot = projectRoot;
|
|
22
23
|
this.timeout = timeout;
|
|
@@ -75,9 +76,8 @@ export class AstIndexClient {
|
|
|
75
76
|
// Check if index already exists and has files
|
|
76
77
|
let existingFileCount = 0;
|
|
77
78
|
try {
|
|
78
|
-
const stats = await this.exec(['stats']);
|
|
79
|
-
|
|
80
|
-
existingFileCount = filesMatch ? parseInt(filesMatch[1], 10) : 0;
|
|
79
|
+
const stats = await this.exec(['--format', 'json', 'stats']);
|
|
80
|
+
existingFileCount = this.parseFileCount(stats);
|
|
81
81
|
}
|
|
82
82
|
catch { /* no index yet */ }
|
|
83
83
|
// Guard: existing index is oversized (node_modules leak from previous build)
|
|
@@ -97,9 +97,7 @@ export class AstIndexClient {
|
|
|
97
97
|
await this.exec(['update'], 30000);
|
|
98
98
|
// Re-check count after update
|
|
99
99
|
try {
|
|
100
|
-
|
|
101
|
-
const filesMatch = statsText.match(/Files:\s*(\d+)/);
|
|
102
|
-
existingFileCount = filesMatch ? parseInt(filesMatch[1], 10) : existingFileCount;
|
|
100
|
+
existingFileCount = this.parseFileCount(await this.exec(['--format', 'json', 'stats']));
|
|
103
101
|
}
|
|
104
102
|
catch { /* keep previous count */ }
|
|
105
103
|
// Guard: update may have grown index beyond limit
|
|
@@ -118,7 +116,7 @@ export class AstIndexClient {
|
|
|
118
116
|
console.error('[token-pilot] ast-index: building index (this may take a moment)...');
|
|
119
117
|
try {
|
|
120
118
|
await this.exec(['rebuild'], 120000);
|
|
121
|
-
const fileCount = this.parseFileCount(await this.exec(['stats']).catch(() => ''));
|
|
119
|
+
const fileCount = this.parseFileCount(await this.exec(['--format', 'json', 'stats']).catch(() => ''));
|
|
122
120
|
// Guard: rebuild produced oversized index
|
|
123
121
|
if (fileCount > AstIndexClient.MAX_INDEX_FILES) {
|
|
124
122
|
return this.handleOversizedIndex(fileCount);
|
|
@@ -130,7 +128,7 @@ export class AstIndexClient {
|
|
|
130
128
|
// If rebuild failed due to lock, check if index is usable anyway
|
|
131
129
|
const errMsg = buildErr instanceof Error ? buildErr.message : String(buildErr);
|
|
132
130
|
if (errMsg.includes('lock') || errMsg.includes('already running')) {
|
|
133
|
-
const count = this.parseFileCount(await this.exec(['stats']).catch(() => ''));
|
|
131
|
+
const count = this.parseFileCount(await this.exec(['--format', 'json', 'stats']).catch(() => ''));
|
|
134
132
|
if (count > 0 && count <= AstIndexClient.MAX_INDEX_FILES) {
|
|
135
133
|
this.indexed = true;
|
|
136
134
|
console.error(`[token-pilot] ast-index: using existing index (${count} files, rebuild skipped due to lock)`);
|
|
@@ -158,8 +156,16 @@ export class AstIndexClient {
|
|
|
158
156
|
` → Tools disabled: find_unused, find_usages, related_files, project_overview\n` +
|
|
159
157
|
` → Tools still working: outline, smart_read, smart_read_many, read_symbol`);
|
|
160
158
|
}
|
|
161
|
-
/** Extract file count from stats output */
|
|
159
|
+
/** Extract file count from stats output (JSON or text) */
|
|
162
160
|
parseFileCount(statsText) {
|
|
161
|
+
// Try JSON first (--format json)
|
|
162
|
+
try {
|
|
163
|
+
const json = JSON.parse(statsText);
|
|
164
|
+
if (json?.stats?.file_count !== undefined)
|
|
165
|
+
return json.stats.file_count;
|
|
166
|
+
}
|
|
167
|
+
catch { /* not JSON, fall through */ }
|
|
168
|
+
// Fallback: text format
|
|
163
169
|
const match = statsText.match(/Files:\s*(\d+)/);
|
|
164
170
|
return match ? parseInt(match[1], 10) : 0;
|
|
165
171
|
}
|
|
@@ -642,14 +648,24 @@ export class AstIndexClient {
|
|
|
642
648
|
async checkAstGrep() {
|
|
643
649
|
if (this.astGrepAvailable !== null)
|
|
644
650
|
return this.astGrepAvailable;
|
|
651
|
+
// Try system PATH first
|
|
645
652
|
try {
|
|
646
653
|
await execFileAsync('sg', ['--version'], { timeout: 3000 });
|
|
647
654
|
this.astGrepAvailable = true;
|
|
655
|
+
return true;
|
|
648
656
|
}
|
|
649
|
-
catch {
|
|
650
|
-
|
|
657
|
+
catch { /* not in PATH */ }
|
|
658
|
+
// Try node_modules/.bin/sg (from optionalDependencies or source installs)
|
|
659
|
+
try {
|
|
660
|
+
const localBinDir = new URL('../../node_modules/.bin', import.meta.url).pathname;
|
|
661
|
+
await execFileAsync(localBinDir + '/sg', ['--version'], { timeout: 3000 });
|
|
662
|
+
this.astGrepBinDir = localBinDir;
|
|
663
|
+
this.astGrepAvailable = true;
|
|
664
|
+
return true;
|
|
651
665
|
}
|
|
652
|
-
|
|
666
|
+
catch { /* not found locally either */ }
|
|
667
|
+
this.astGrepAvailable = false;
|
|
668
|
+
return false;
|
|
653
669
|
}
|
|
654
670
|
/** Structural pattern search via ast-grep. Requires ast-grep (sg) installed. */
|
|
655
671
|
async agrep(pattern, options) {
|
|
@@ -831,10 +847,16 @@ export class AstIndexClient {
|
|
|
831
847
|
if (!this.binaryPath) {
|
|
832
848
|
throw new Error('ast-index not initialized. Call init() first.');
|
|
833
849
|
}
|
|
850
|
+
// If ast-grep was found in node_modules/.bin, inject it into PATH
|
|
851
|
+
// so ast-index can find sg when running agrep
|
|
852
|
+
const env = this.astGrepBinDir
|
|
853
|
+
? { ...process.env, PATH: `${this.astGrepBinDir}:${process.env.PATH ?? ''}` }
|
|
854
|
+
: undefined;
|
|
834
855
|
const { stdout, stderr } = await execFileAsync(this.binaryPath, args, {
|
|
835
856
|
timeout: timeoutMs ?? this.timeout,
|
|
836
857
|
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
837
858
|
cwd: this.projectRoot,
|
|
859
|
+
...(env && { env }),
|
|
838
860
|
});
|
|
839
861
|
if (stderr) {
|
|
840
862
|
console.error(`[token-pilot] ast-index stderr (${args[0]}): ${stderr.trim()}`);
|
|
@@ -17,7 +17,8 @@ export async function handleCodeAudit(args, projectRoot, astIndex) {
|
|
|
17
17
|
case 'deprecated':
|
|
18
18
|
return handleDeprecated(limit, projectRoot, astIndex);
|
|
19
19
|
case 'annotations':
|
|
20
|
-
|
|
20
|
+
// Strip @ prefix — ast-index expects "Injectable" not "@Injectable"
|
|
21
|
+
return handleAnnotations(args.name.replace(/^@/, ''), limit, projectRoot, astIndex);
|
|
21
22
|
case 'all':
|
|
22
23
|
return handleAll(limit, projectRoot, astIndex);
|
|
23
24
|
default:
|
package/dist/server.js
CHANGED
|
@@ -193,8 +193,10 @@ export async function createServer(projectRoot, options) {
|
|
|
193
193
|
'',
|
|
194
194
|
'COMBINE BOTH for audits and code review:',
|
|
195
195
|
'• Structure/navigation → Token Pilot (project_overview, outline, smart_read)',
|
|
196
|
+
'• Dead code detection → find_unused (finds unreferenced symbols)',
|
|
196
197
|
'• Code issues → code_audit (TODOs, deprecated, structural patterns like bare except:)',
|
|
197
198
|
'• Text pattern search/counting → Grep (regex, count mode)',
|
|
199
|
+
'• Security audit → Grep for: password, token, secret, credential, hardcoded, api_key, TODO.*security',
|
|
198
200
|
'• Deep dive into specific code → read_symbol (after finding issues)',
|
|
199
201
|
'',
|
|
200
202
|
'WORKFLOW: project_overview → smart_read → read_symbol → read_for_edit → edit → read_diff',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.2",
|
|
4
|
+
"description": "Save 60-80% tokens when AI reads code — MCP server for token-efficient code navigation, AST-aware structural reading instead of dumping full files into context window",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -28,18 +28,24 @@
|
|
|
28
28
|
"prepublishOnly": "npm run build && chmod +x dist/index.js"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
|
-
"
|
|
32
|
-
"token",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"ai",
|
|
39
|
-
"coding-assistant",
|
|
31
|
+
"token-savings",
|
|
32
|
+
"token-reduction",
|
|
33
|
+
"context-window",
|
|
34
|
+
"save-tokens",
|
|
35
|
+
"reduce-tokens",
|
|
36
|
+
"token-efficient",
|
|
37
|
+
"token-economy",
|
|
40
38
|
"context-optimization",
|
|
39
|
+
"fewer-tokens",
|
|
40
|
+
"mcp",
|
|
41
|
+
"mcp-server",
|
|
41
42
|
"model-context-protocol",
|
|
42
|
-
"
|
|
43
|
+
"ast",
|
|
44
|
+
"code-reading",
|
|
45
|
+
"code-navigation",
|
|
46
|
+
"smart-read",
|
|
47
|
+
"ai-coding",
|
|
48
|
+
"llm-tools"
|
|
43
49
|
],
|
|
44
50
|
"repository": {
|
|
45
51
|
"type": "git",
|
|
@@ -69,5 +75,8 @@
|
|
|
69
75
|
"ast-index": {
|
|
70
76
|
"optional": true
|
|
71
77
|
}
|
|
78
|
+
},
|
|
79
|
+
"optionalDependencies": {
|
|
80
|
+
"@ast-grep/cli": "^0.41.0"
|
|
72
81
|
}
|
|
73
82
|
}
|