token-pilot 0.14.1 → 0.15.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/dist/server.js CHANGED
@@ -28,7 +28,7 @@ import { handleNonCodeRead, isNonCodeStructured } from './handlers/non-code.js';
28
28
  import { handleFindUnused } from './handlers/find-unused.js';
29
29
  import { handleReadForEdit } from './handlers/read-for-edit.js';
30
30
  import { handleRelatedFiles } from './handlers/related-files.js';
31
- import { handleOutline, CODE_EXTENSIONS } from './handlers/outline.js';
31
+ import { handleOutline } from './handlers/outline.js';
32
32
  import { handleCodeAudit } from './handlers/code-audit.js';
33
33
  import { handleModuleInfo } from './handlers/module-info.js';
34
34
  import { handleSmartDiff } from './handlers/smart-diff.js';
@@ -38,7 +38,9 @@ import { handleTestSummary } from './handlers/test-summary.js';
38
38
  import { detectContextMode } from './integration/context-mode-detector.js';
39
39
  import { estimateTokens } from './core/token-estimator.js';
40
40
  import { checkPolicy, isFullReadTool } from './core/policy-engine.js';
41
- import { resolveSafePath, validateSmartReadArgs, validateReadSymbolArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateSmartLogArgs, validateTestSummaryArgs, } from './core/validation.js';
41
+ import { MCP_INSTRUCTIONS, TOOL_DEFINITIONS } from './server/tool-definitions.js';
42
+ import { createTokenEstimates } from './server/token-estimates.js';
43
+ import { validateSmartReadArgs, validateReadSymbolArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateSmartLogArgs, validateTestSummaryArgs, } from './core/validation.js';
42
44
  export async function createServer(projectRoot, options) {
43
45
  const config = await loadConfig(projectRoot);
44
46
  const astIndex = new AstIndexClient(projectRoot, config.astIndex.timeout, {
@@ -194,462 +196,13 @@ export async function createServer(projectRoot, options) {
194
196
  catch { /* fallback to hardcoded */ }
195
197
  const server = new Server({ name: 'token-pilot', version: pkgVersion }, {
196
198
  capabilities: { tools: {} },
197
- instructions: [
198
- 'Token Pilot provides token-efficient code reading. Use these rules:',
199
- '',
200
- 'WHEN TO USE TOKEN PILOT (saves 60-80% tokens):',
201
- '• Reading code files → smart_read (returns structure, not raw content)',
202
- '• Need one function/class → read_symbol (loads only that symbol)',
203
- '• Exploring a directory → outline (all symbols in one call)',
204
- '• Preparing an edit → read_for_edit (exact text for Edit old_string)',
205
- '• Verifying edits → read_diff (only changed hunks). IMPORTANT: call smart_read BEFORE editing to create baseline.',
206
- '• Finding symbol references → find_usages (semantic, grouped by type)',
207
- '• Understanding file relationships → related_files (imports, dependents, tests)',
208
- '• New codebase → project_overview first',
209
- '• Reading file again → smart_read (returns compact reminder, not full content)',
210
- '• Multiple files → smart_read_many (batch, max 20)',
211
- '• Code quality audit → code_audit (TODOs, deprecated, structural code patterns)',
212
- '• Reviewing git changes → smart_diff (structural diff with symbol mapping, not raw patch)',
213
- '• Starting work on an area → explore_area (outline + imports + tests + git log in one call)',
214
- '• Understanding commit history → smart_log (structured git log with categories, not raw output)',
215
- '• Running tests → test_summary (structured pass/fail summary, not 200 lines of raw output)',
216
- '',
217
- 'WHEN TO USE DEFAULT TOOLS (Token Pilot adds no value):',
218
- '• Small files (≤200 lines) → smart_read returns full content anyway, same as Read',
219
- '• Regex text search (e.g. TODO.*fix) → use Grep/ripgrep',
220
- '• Counting occurrences (e.g. how many `any` types?) → use Grep count mode',
221
- '• Finding code duplication → use Grep to search for repeated patterns',
222
- '• Non-code files (JSON, YAML, Markdown, configs) → smart_read handles these but default Read works too',
223
- '• You need exact raw content for copy-paste → use Read',
224
- '',
225
- 'COMBINE BOTH for audits and code review:',
226
- '• Structure/navigation → Token Pilot (project_overview, outline, smart_read)',
227
- '• Dead code detection → find_unused (finds unreferenced symbols)',
228
- '• Code issues → code_audit (TODOs, deprecated, structural patterns like bare except:)',
229
- '• Text pattern search/counting → Grep (regex, count mode)',
230
- '• Security audit → Grep for: password, token, secret, credential, hardcoded, api_key, TODO.*security',
231
- '• Deep dive into specific code → read_symbol (after finding issues)',
232
- '• Module architecture → module_info (deps, dependents, public API, unused deps)',
233
- '',
234
- 'WORKFLOW: project_overview → explore_area → smart_read → read_symbol → read_for_edit → edit → smart_diff',
235
- ].join('\n'),
199
+ instructions: MCP_INSTRUCTIONS,
236
200
  });
237
201
  server.setRequestHandler(ListToolsRequestSchema, () => ({
238
- tools: [
239
- // --- Core reading tools ---
240
- {
241
- name: 'smart_read',
242
- description: 'Use INSTEAD OF Read/cat for code files. Returns code structure (classes, functions, methods with signatures and line ranges) — 60-80% fewer tokens than raw content. Use read_symbol() to drill into specific code.',
243
- inputSchema: {
244
- type: 'object',
245
- properties: {
246
- path: { type: 'string', description: 'File path (absolute or relative to project root)' },
247
- show_imports: { type: 'boolean', description: 'Include import details (default: true)' },
248
- show_docs: { type: 'boolean', description: 'Include doc comments (default: true)' },
249
- depth: { type: 'number', description: 'Max depth for nested symbols (default: 2)' },
250
- },
251
- required: ['path'],
252
- },
253
- },
254
- {
255
- name: 'read_symbol',
256
- description: 'Read source code of ONE specific function/method/class — INSTEAD OF reading the whole file. Supports Class.method syntax.',
257
- inputSchema: {
258
- type: 'object',
259
- properties: {
260
- path: { type: 'string', description: 'File path' },
261
- symbol: { type: 'string', description: 'Symbol name, e.g. "UserService.updateUser"' },
262
- context_before: { type: 'number', description: 'Lines of context before (default: 2)' },
263
- context_after: { type: 'number', description: 'Lines of context after (default: 0)' },
264
- show: { type: 'string', enum: ['full', 'head', 'tail', 'outline'], description: 'Display mode: full (all lines), head (first 50), tail (last 30), outline (head + methods + tail). Default: auto (full ≤300 lines, outline >300)' },
265
- },
266
- required: ['path', 'symbol'],
267
- },
268
- },
269
- {
270
- name: 'read_range',
271
- description: 'Read a specific line range from a file.',
272
- inputSchema: {
273
- type: 'object',
274
- properties: {
275
- path: { type: 'string', description: 'File path' },
276
- start_line: { type: 'number', description: 'Start line (1-indexed)' },
277
- end_line: { type: 'number', description: 'End line (1-indexed, inclusive)' },
278
- },
279
- required: ['path', 'start_line', 'end_line'],
280
- },
281
- },
282
- {
283
- name: 'read_diff',
284
- description: 'Use INSTEAD OF re-reading whole file after edits. Shows only changed hunks. REQUIRES: call smart_read or read_for_edit BEFORE editing to create baseline snapshot.',
285
- inputSchema: {
286
- type: 'object',
287
- properties: {
288
- path: { type: 'string', description: 'File path' },
289
- context_lines: { type: 'number', description: 'Lines of context around changes (default: 3)' },
290
- },
291
- required: ['path'],
292
- },
293
- },
294
- {
295
- name: 'read_for_edit',
296
- description: 'Use INSTEAD OF Read when preparing an edit. Returns exact raw code around a symbol or line — copy directly as old_string for Edit tool. Optional: include_callers, include_tests, include_changes for enriched context.',
297
- inputSchema: {
298
- type: 'object',
299
- properties: {
300
- path: { type: 'string', description: 'File path' },
301
- symbol: { type: 'string', description: 'Symbol name to edit (e.g. "UserService.updateUser")' },
302
- line: { type: 'number', description: 'Line number to edit (alternative to symbol)' },
303
- context: { type: 'number', description: 'Lines of context around target (default: 5)' },
304
- include_callers: { type: 'boolean', description: 'Show top callers of this symbol (saves a separate find_usages call)' },
305
- include_tests: { type: 'boolean', description: 'Show related test file and test names' },
306
- include_changes: { type: 'boolean', description: 'Show recent git changes in the target region' },
307
- },
308
- required: ['path'],
309
- },
310
- },
311
- {
312
- name: 'smart_read_many',
313
- description: 'Batch smart_read for multiple files at once — INSTEAD OF calling Read on each file. Returns structure for each file. Max 20 files.',
314
- inputSchema: {
315
- type: 'object',
316
- properties: {
317
- paths: {
318
- type: 'array',
319
- items: { type: 'string' },
320
- description: 'Array of file paths',
321
- },
322
- },
323
- required: ['paths'],
324
- },
325
- },
326
- // --- Search & navigation ---
327
- {
328
- name: 'find_usages',
329
- description: 'Use INSTEAD OF Grep/ripgrep for finding symbol references. Semantic search across the project — groups results by: definitions, imports, usages. (v1.1: added scope, kind, limit, lang filters)',
330
- inputSchema: {
331
- type: 'object',
332
- properties: {
333
- symbol: { type: 'string', description: 'Symbol name to find usages of' },
334
- scope: { type: 'string', description: 'Filter results by path prefix (e.g., "src/Domain/")' },
335
- kind: { type: 'string', enum: ['definitions', 'imports', 'usages', 'all'], description: 'Show only specific section (default: "all")' },
336
- limit: { type: 'number', description: 'Max results per category (default: 50, max: 500)' },
337
- lang: { type: 'string', description: 'Filter by language/extension (e.g., "php", "typescript")' },
338
- },
339
- required: ['symbol'],
340
- },
341
- },
342
- {
343
- name: 'project_overview',
344
- description: 'START HERE for unfamiliar codebases. Shows project type (dual-detection: ast-index + config files), architecture, framework detection, quality tools, CI, directory map. (v1.1: added include filter)',
345
- inputSchema: {
346
- type: 'object',
347
- properties: {
348
- include: {
349
- type: 'array',
350
- items: { type: 'string', enum: ['stack', 'ci', 'quality', 'architecture'] },
351
- description: 'Sections to include (default: all). Use ["stack"] for quick type check, ["quality","ci"] for tooling overview.',
352
- },
353
- },
354
- },
355
- },
356
- {
357
- name: 'related_files',
358
- description: 'Show ranked import graph for a file: imports, importers, and tests scored by relevance (test adjacency, import closeness, recent changes, path proximity). Files ranked into HIGH VALUE / MEDIUM / LOW to prioritize reading.',
359
- inputSchema: {
360
- type: 'object',
361
- properties: {
362
- path: { type: 'string', description: 'File path to analyze' },
363
- },
364
- required: ['path'],
365
- },
366
- },
367
- {
368
- name: 'outline',
369
- description: 'Use INSTEAD OF listing dir + reading each file. One call returns all symbols (classes, functions, methods, routes) for every code file in a directory. (v1.1: added recursive, max_depth)',
370
- inputSchema: {
371
- type: 'object',
372
- properties: {
373
- path: { type: 'string', description: 'Directory path' },
374
- recursive: { type: 'boolean', description: 'Recursively outline subdirectories (default: false)' },
375
- max_depth: { type: 'number', description: 'Max recursion depth when recursive=true (default: 2, max: 5)' },
376
- },
377
- required: ['path'],
378
- },
379
- },
380
- // --- Analytics ---
381
- {
382
- name: 'session_analytics',
383
- description: 'Show token savings report for this session: total tokens saved, per-tool breakdown, top files by savings.',
384
- inputSchema: {
385
- type: 'object',
386
- properties: {},
387
- },
388
- },
389
- // --- Analysis ---
390
- {
391
- name: 'find_unused',
392
- description: 'Find dead code — functions, classes, and variables with no references across the project. Use for cleanup and refactoring.',
393
- inputSchema: {
394
- type: 'object',
395
- properties: {
396
- module: { type: 'string', description: 'Filter by module path (e.g., "src/services/")' },
397
- export_only: { type: 'boolean', description: 'Only check exported (capitalized) symbols' },
398
- limit: { type: 'number', description: 'Max results (default: 30)' },
399
- },
400
- },
401
- },
402
- {
403
- name: 'code_audit',
404
- description: 'Find code quality issues: TODO/FIXME comments, deprecated symbols, structural code patterns (bare except:, print() calls). Use for project-wide audits.',
405
- inputSchema: {
406
- type: 'object',
407
- properties: {
408
- check: {
409
- type: 'string',
410
- enum: ['pattern', 'todo', 'deprecated', 'annotations', 'all'],
411
- description: 'What to check: "pattern" (structural search via ast-grep, e.g. "except:", "print($$$ARGS)"), "todo" (TODO/FIXME comments), "deprecated" (deprecated symbols), "annotations" (find by decorator name), "all" (todo + deprecated summary)',
412
- },
413
- pattern: { type: 'string', description: 'Code pattern for check="pattern". ast-grep syntax: "except:" finds bare excepts, "print($$$ARGS)" finds print calls.' },
414
- name: { type: 'string', description: 'Decorator/annotation name for check="annotations". Example: "Deprecated", "Controller"' },
415
- lang: { type: 'string', description: 'Language filter for check="pattern" (e.g., "python", "typescript")' },
416
- limit: { type: 'number', description: 'Max results (default: 50)' },
417
- },
418
- required: ['check'],
419
- },
420
- },
421
- {
422
- name: 'module_info',
423
- description: 'Analyze module dependencies, dependents, public API, and unused deps. Use for architecture understanding and dependency cleanup.',
424
- inputSchema: {
425
- type: 'object',
426
- properties: {
427
- module: { type: 'string', description: 'Module name or path pattern (e.g., "auth", "src/Domain/")' },
428
- check: {
429
- type: 'string',
430
- enum: ['deps', 'dependents', 'api', 'unused-deps', 'all'],
431
- description: 'What to check: "deps" (dependencies), "dependents" (who depends on this), "api" (public symbols), "unused-deps" (dead dependencies), "all" (everything). Default: "all"',
432
- },
433
- },
434
- required: ['module'],
435
- },
436
- },
437
- // --- Diff & exploration ---
438
- {
439
- name: 'smart_diff',
440
- description: 'Use INSTEAD OF raw git diff. Shows changed files with AST symbol mapping — which functions/classes were modified/added/removed. Small diffs include hunks, large diffs show summary.',
441
- inputSchema: {
442
- type: 'object',
443
- properties: {
444
- scope: { type: 'string', enum: ['unstaged', 'staged', 'commit', 'branch'], description: 'Diff scope (default: "unstaged")' },
445
- path: { type: 'string', description: 'Filter to specific file or directory' },
446
- ref: { type: 'string', description: 'Git ref — required for scope="commit" (commit hash) or scope="branch" (branch name)' },
447
- },
448
- },
449
- },
450
- {
451
- name: 'explore_area',
452
- description: 'One-call exploration of a directory: outline (all symbols), imports (external deps + who imports this area), tests (matching test files), recent git changes. Use INSTEAD OF separate outline + related_files + git log calls.',
453
- inputSchema: {
454
- type: 'object',
455
- properties: {
456
- path: { type: 'string', description: 'Directory path (or file path — will use its parent directory)' },
457
- include: {
458
- type: 'array',
459
- items: { type: 'string', enum: ['outline', 'imports', 'tests', 'changes'] },
460
- description: 'Sections to include (default: all)',
461
- },
462
- },
463
- required: ['path'],
464
- },
465
- },
466
- {
467
- name: 'smart_log',
468
- description: 'Use INSTEAD OF raw git log. Structured commit history with category detection (feat/fix/refactor/docs), file stats, author breakdown. Filters by path and ref.',
469
- inputSchema: {
470
- type: 'object',
471
- properties: {
472
- path: { type: 'string', description: 'Filter to specific file or directory' },
473
- count: { type: 'number', description: 'Number of commits (default: 10, max: 50)' },
474
- ref: { type: 'string', description: 'Git ref — branch, tag, or commit (default: HEAD)' },
475
- },
476
- },
477
- },
478
- {
479
- name: 'test_summary',
480
- description: 'Run tests and return structured summary: total/passed/failed/skipped + failure details. 200 lines of raw output → 10-15 lines. Supports vitest, jest, pytest, phpunit, go test, cargo test.',
481
- inputSchema: {
482
- type: 'object',
483
- properties: {
484
- command: { type: 'string', description: 'Test command to run (e.g., "npm test", "pytest", "go test ./...")' },
485
- runner: { type: 'string', enum: ['vitest', 'jest', 'pytest', 'phpunit', 'go', 'cargo', 'rspec', 'mocha'], description: 'Force specific parser (auto-detected if omitted)' },
486
- timeout: { type: 'number', description: 'Timeout in ms (default: 60000, max: 300000)' },
487
- },
488
- required: ['command'],
489
- },
490
- },
491
- ],
202
+ tools: TOOL_DEFINITIONS,
492
203
  }));
493
- // Helper: get real full-file token count for honest analytics
494
- async function fullFileTokens(relativePath) {
495
- try {
496
- const absPath = resolveSafePath(projectRoot, relativePath);
497
- const cached = fileCache.get(absPath);
498
- if (cached)
499
- return estimateTokens(cached.content);
500
- const { readFile: readFileAsync } = await import('node:fs/promises');
501
- const content = await readFileAsync(absPath, 'utf-8');
502
- return estimateTokens(content);
503
- }
504
- catch {
505
- return 0;
506
- }
507
- }
508
- async function estimateProjectOverviewWorkflowTokens(includeSections) {
509
- const sectionFiles = {
510
- stack: ['package.json', 'composer.json', 'Cargo.toml', 'pyproject.toml', 'go.mod'],
511
- ci: ['.gitlab-ci.yml', 'Jenkinsfile', '.circleci/config.yml', 'bitbucket-pipelines.yml', '.travis.yml'],
512
- quality: [
513
- 'tsconfig.json',
514
- 'vitest.config.ts',
515
- 'vitest.config.js',
516
- 'vitest.config.mts',
517
- 'jest.config.js',
518
- 'jest.config.ts',
519
- 'jest.config.mjs',
520
- 'eslint.config.js',
521
- 'eslint.config.mjs',
522
- '.eslintrc',
523
- '.eslintrc.js',
524
- '.eslintrc.json',
525
- '.eslintrc.yml',
526
- 'biome.json',
527
- 'biome.jsonc',
528
- '.prettierrc',
529
- '.prettierrc.js',
530
- '.prettierrc.json',
531
- 'prettier.config.js',
532
- 'phpunit.xml',
533
- 'phpunit.xml.dist',
534
- 'phpstan.neon',
535
- 'phpstan.neon.dist',
536
- ],
537
- architecture: ['README.md'],
538
- };
539
- let total = 0;
540
- const seen = new Set();
541
- for (const section of includeSections) {
542
- for (const file of sectionFiles[section]) {
543
- if (seen.has(file))
544
- continue;
545
- seen.add(file);
546
- total += await fullFileTokens(file);
547
- }
548
- }
549
- if (includeSections.includes('ci')) {
550
- try {
551
- const { readdir: readDirAsync } = await import('node:fs/promises');
552
- const workflowDir = resolveSafePath(projectRoot, '.github/workflows');
553
- const workflowFiles = await readDirAsync(workflowDir, { withFileTypes: true });
554
- for (const file of workflowFiles) {
555
- if (!file.isFile())
556
- continue;
557
- if (!file.name.endsWith('.yml') && !file.name.endsWith('.yaml'))
558
- continue;
559
- total += await fullFileTokens(`.github/workflows/${file.name}`);
560
- }
561
- }
562
- catch {
563
- // ignore missing workflows dir
564
- }
565
- }
566
- if (includeSections.includes('architecture')) {
567
- total += 200;
568
- }
569
- return total;
570
- }
571
- async function estimateOutlineWorkflowTokens(relativePath, recursive, maxDepth) {
572
- const SAMPLE_LIMIT = 30;
573
- try {
574
- const { readdir: readDirAsync } = await import('node:fs/promises');
575
- const { resolve: resolvePath } = await import('node:path');
576
- const absDir = resolveSafePath(projectRoot, relativePath);
577
- const sampledFiles = [];
578
- let totalFiles = 0;
579
- async function walk(dirPath, depth) {
580
- const entries = await readDirAsync(dirPath, { withFileTypes: true });
581
- for (const entry of entries) {
582
- if (entry.isFile()) {
583
- const ext = entry.name.split('.').pop()?.toLowerCase() ?? '';
584
- if (!CODE_EXTENSIONS.has(ext))
585
- continue;
586
- totalFiles++;
587
- if (sampledFiles.length < SAMPLE_LIMIT) {
588
- sampledFiles.push(resolvePath(dirPath, entry.name));
589
- }
590
- continue;
591
- }
592
- if (entry.isDirectory() && recursive && depth < maxDepth) {
593
- await walk(resolvePath(dirPath, entry.name), depth + 1);
594
- }
595
- }
596
- }
597
- await walk(absDir, 0);
598
- if (totalFiles === 0)
599
- return 0;
600
- let sampledTokens = 0;
601
- for (const filePath of sampledFiles) {
602
- const relPath = filePath.startsWith(projectRoot)
603
- ? filePath.slice(projectRoot.length + 1)
604
- : filePath;
605
- sampledTokens += await fullFileTokens(relPath);
606
- }
607
- if (sampledFiles.length === 0 || sampledTokens === 0)
608
- return 0;
609
- if (sampledFiles.length === totalFiles)
610
- return sampledTokens;
611
- const averageTokens = sampledTokens / sampledFiles.length;
612
- return Math.round(averageTokens * totalFiles);
613
- }
614
- catch {
615
- return 0;
616
- }
617
- }
618
- async function estimateRelatedFilesWorkflowTokens(targetPath, meta) {
619
- const related = new Set([targetPath]);
620
- for (const path of meta?.imports ?? [])
621
- related.add(path);
622
- for (const path of meta?.importedBy ?? [])
623
- related.add(path);
624
- for (const path of meta?.tests ?? [])
625
- related.add(path);
626
- let total = 0;
627
- let counted = 0;
628
- for (const path of related) {
629
- total += await fullFileTokens(path);
630
- counted++;
631
- if (counted >= 12)
632
- break;
633
- }
634
- return total;
635
- }
636
- async function estimateFindUsagesWorkflowTokens(files) {
637
- let total = 0;
638
- let counted = 0;
639
- for (const file of files) {
640
- total += await fullFileTokens(file);
641
- counted++;
642
- if (counted >= 20)
643
- break;
644
- }
645
- return total;
646
- }
647
- /** Detect savings category from response text prefix. */
648
- function detectSavingsCategory(text) {
649
- if (text.startsWith('REMINDER:') || text.startsWith('DEDUP:'))
650
- return 'dedup';
651
- return 'compression';
652
- }
204
+ // Token estimation functions (extracted to server/token-estimates.ts)
205
+ const { fullFileTokens, estimateProjectOverviewWorkflowTokens, estimateOutlineWorkflowTokens, estimateRelatedFilesWorkflowTokens, estimateFindUsagesWorkflowTokens, estimateExploreAreaWorkflowTokens, detectSavingsCategory, } = createTokenEstimates(() => projectRoot, fileCache);
653
206
  /** Record analytics with intent classification and decision trace. Returns policy advisory if any. */
654
207
  function recordWithTrace(call) {
655
208
  const { absPath, args, recentlyEdited, ...rest } = call;
@@ -682,28 +235,6 @@ export async function createServer(projectRoot, options) {
682
235
  });
683
236
  return advisory ? `\n${advisory.message}` : null;
684
237
  }
685
- async function estimateExploreAreaWorkflowTokens(meta) {
686
- const localFiles = new Set();
687
- for (const file of meta.codeFiles ?? [])
688
- localFiles.add(file);
689
- for (const file of meta.testFiles ?? [])
690
- localFiles.add(file);
691
- for (const file of meta.internalDeps ?? [])
692
- localFiles.add(file);
693
- for (const file of meta.importedBy ?? [])
694
- localFiles.add(file);
695
- let total = 0;
696
- let counted = 0;
697
- for (const file of localFiles) {
698
- total += await fullFileTokens(file);
699
- counted++;
700
- if (counted >= 24)
701
- break;
702
- }
703
- total += (meta.externalDeps?.length ?? 0) * 30;
704
- total += (meta.changeCount ?? 0) * 40;
705
- return total;
706
- }
707
238
  // Handle tool calls with validated arguments
708
239
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
709
240
  const { name, arguments: args } = request.params;
package/dist/types.d.ts CHANGED
@@ -98,6 +98,7 @@ export interface TokenPilotConfig {
98
98
  enabled: boolean;
99
99
  interceptRead: boolean;
100
100
  autoInstall: boolean;
101
+ denyThreshold: number;
101
102
  };
102
103
  context: {
103
104
  estimateTokens: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.14.1",
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",
3
+ "version": "0.15.0",
4
+ "description": "Save up to 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": {
@@ -29,24 +29,26 @@
29
29
  "prepublishOnly": "npm run build && node --input-type=module -e \"import { chmod } from 'node:fs/promises'; await chmod('dist/index.js', 0o755);\""
30
30
  },
31
31
  "keywords": [
32
+ "mcp",
33
+ "mcp-server",
34
+ "model-context-protocol",
35
+ "claude",
36
+ "claude-code",
37
+ "cursor",
38
+ "codex",
39
+ "cline",
40
+ "ai-coding",
41
+ "llm-tools",
32
42
  "token-savings",
33
43
  "token-reduction",
34
44
  "context-window",
35
- "save-tokens",
36
- "reduce-tokens",
37
- "token-efficient",
38
- "token-economy",
39
45
  "context-optimization",
40
- "fewer-tokens",
41
- "mcp",
42
- "mcp-server",
43
- "model-context-protocol",
44
46
  "ast",
45
47
  "code-reading",
46
48
  "code-navigation",
47
49
  "smart-read",
48
- "ai-coding",
49
- "llm-tools"
50
+ "developer-tools",
51
+ "tree-sitter"
50
52
  ],
51
53
  "repository": {
52
54
  "type": "git",
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: guide
3
+ description: Show a quick-reference guide for all Token Pilot tools — when to use each one
4
+ command: guide
5
+ user_invocable: true
6
+ ---
7
+
8
+ Display the following Token Pilot tool reference to the user. Show it exactly as formatted below.
9
+
10
+ ---
11
+
12
+ ## Token Pilot — Tool Reference
13
+
14
+ ### Reading code
15
+ | Goal | Tool |
16
+ |------|------|
17
+ | Explore a file's structure | `smart_read(path)` |
18
+ | Read one function or class | `read_symbol(path, symbol="ClassName.method")` |
19
+ | Read a specific line range | `read_range(path, start, end)` |
20
+ | Get edit-ready exact text | `read_for_edit(path, symbol="name")` |
21
+ | Read many files at once | `smart_read_many([paths])` |
22
+
23
+ ### Exploring a codebase
24
+ | Goal | Tool |
25
+ |------|------|
26
+ | First look at a new project | `project_overview()` |
27
+ | All symbols in a file | `outline(path)` |
28
+ | Deep dive on one area | `explore_area(path)` |
29
+ | Related files (imports, tests) | `related_files(path)` |
30
+ | Module deps + public API | `module_info(module_path)` |
31
+
32
+ ### Finding things
33
+ | Goal | Tool |
34
+ |------|------|
35
+ | Where a symbol is used | `find_usages(symbol)` |
36
+ | Symbols with no references | `find_unused(path)` |
37
+
38
+ ### After editing
39
+ | Goal | Tool |
40
+ |------|------|
41
+ | Verify your edit is correct | `read_diff(path)` |
42
+
43
+ ### Git & tests
44
+ | Goal | Tool |
45
+ |------|------|
46
+ | Understand recent commits | `smart_log()` |
47
+ | Review a diff structurally | `smart_diff(base, head)` |
48
+ | Parse test output | `test_summary(command)` |
49
+
50
+ ### Code quality
51
+ | Goal | Tool |
52
+ |------|------|
53
+ | Find TODOs, deprecated, patterns | `code_audit(path)` |
54
+
55
+ ### Session
56
+ | Goal | Tool |
57
+ |------|------|
58
+ | Token savings this session | `session_analytics()` |
59
+
60
+ ---
61
+
62
+ **Workflow:** `project_overview` → `explore_area` → `smart_read` → `read_symbol` → `read_for_edit` → edit → `read_diff`
63
+
64
+ **Tip:** `smart_read` on a file you've already read returns a compact reminder (dedup) — no wasted tokens.