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 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
  /**
@@ -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
- const filesMatch = stats.match(/Files:\s*(\d+)/);
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
- const statsText = await this.exec(['stats']);
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
- this.astGrepAvailable = false;
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
- return this.astGrepAvailable;
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
- return handleAnnotations(args.name, limit, projectRoot, astIndex);
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.0",
4
- "description": "MCP server that reduces token consumption in AI coding assistants via AST-aware lazy file reading",
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
- "mcp",
32
- "token",
33
- "ast",
34
- "claude",
35
- "cursor",
36
- "codex",
37
- "cline",
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
- "token-savings"
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
  }