token-pilot 0.8.3 → 0.10.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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "token-pilot",
3
3
  "displayName": "Token Pilot",
4
- "description": "Reduces token consumption by 80-95% via AST-aware lazy file reading. 14 MCP tools for structural code reading, symbol navigation, and cross-file search.",
5
- "version": "0.1.1",
4
+ "description": "Reduces token consumption by 80-95% via AST-aware lazy file reading. 16 MCP tools for structural code reading, symbol navigation, and cross-file search.",
5
+ "version": "0.10.0",
6
6
  "author": "Digital-Threads",
7
7
  "repository": "https://github.com/Digital-Threads/token-pilot",
8
8
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.7.3",
3
+ "version": "0.10.0",
4
4
  "description": "Reduces token consumption by 80-95% via AST-aware lazy file reading. Returns structural overviews instead of full files.",
5
5
  "author": "token-pilot",
6
6
  "license": "MIT",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@ 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.10.0] - 2026-03-14
9
+
10
+ ### Added
11
+ - **`smart_diff` tool** — structural git diff with AST symbol mapping. Shows which functions/classes were modified/added/removed instead of raw patch output. Supports scopes: `unstaged`, `staged`, `commit` (ref required), `branch` (ref required). Small diffs (<=30 lines) include actual hunks, large diffs show summary. Returns `rawTokens` for precise savings analytics.
12
+ - **`explore_area` tool** — one-call directory exploration combining outline + imports + tests + git changes. Replaces 3-5 separate tool calls when starting work on an area. Sections: `outline` (recursive depth 2), `imports` (external deps + who imports this area), `tests` (matching test/spec files), `changes` (recent git log). All sections run in parallel via `Promise.allSettled`.
13
+ - **26 new tests** — smart_diff parser (10), symbol mapping (5), validation (11). Total: 170 tests (was 144).
14
+
15
+ ### Changed
16
+ - **16 tools** (was 14) — added `smart_diff`, `explore_area`
17
+ - **MCP instructions** — updated workflow: `project_overview → explore_area → smart_read → read_symbol → read_for_edit → edit → smart_diff`
18
+ - **`outlineDir` and `CODE_EXTENSIONS` exported** from outline.ts for reuse by explore_area
19
+
20
+ ## [0.9.0] - 2026-03-08
21
+
22
+ ### Added
23
+ - **`module_info` tool** — analyze module dependencies, dependents, public API, and unused deps. Uses ast-index v3.27.0 module commands (`modules`, `module-deps`, `module-dependents`, `module-api`, `unused-deps`). Includes degradation check when ast-index is unavailable.
24
+ - **`project_overview` dual-detection** — shows BOTH ast-index type detection AND config-file detection (package.json, composer.json, Cargo.toml, pyproject.toml, go.mod) with CONFIDENCE scoring (high/medium/low/unknown). Detects frameworks, quality tools (PHPStan, ESLint, Vitest, Jest, Biome, etc.), CI pipelines (GitHub Actions, GitLab CI, Jenkins), and Docker.
25
+ - **`project_overview` `include` parameter** — filter sections: `["stack"]` for quick type check, `["quality","ci"]` for tooling overview. Default: all sections.
26
+ - **`find_usages` post-filters** — `scope` (path prefix), `kind` (definitions/imports/usages), `lang` (14 languages by extension), `limit` (per category, 1-500). All filters optional, backward compatible.
27
+ - **`outline` recursive mode** — `recursive=true` with `max_depth` (default 2, max 5) recurses into subdirectories. At max depth shows file counts only.
28
+ - **`src/core/project-detector.ts`** — extracted config-based detection logic into reusable module. Framework detection maps for PHP (7), JS (10), Python (5). Quality tools scanner (13 tools). CI pipeline detector (6 platforms).
29
+ - **ast-index client: 5 module methods** — `modules()`, `moduleDeps()`, `moduleDependents()`, `unusedDeps()`, `moduleApi()` with JSON-first + text fallback parsing.
30
+ - **ast-index types: 4 module interfaces** — `AstIndexModuleEntry`, `AstIndexModuleDep`, `AstIndexUnusedDep`, `AstIndexModuleApi`.
31
+
32
+ ### Fixed
33
+ - **`module_info` token savings** — `tokensWouldBe` was equal to `tokensReturned` (0% savings). Now estimates manual analysis cost correctly.
34
+ - **`outline` recursive overflow** — added `MAX_OUTLINE_LINES=500` guard to prevent runaway output on large projects with `recursive=true`.
35
+ - **`project_overview` "frontend" label** — removed hardcoded "frontend" suffix for secondary stacks (Node.js is not always frontend).
36
+ - **Ruff detection** — no longer double-reads `pyproject.toml`. Checks `ruff.toml`/`.ruff.toml` first, falls back to `pyproject.toml [tool.ruff]` only if needed.
37
+ - **44 new tests** — validators (23) + project-detector (21). Total: 144 tests (was 100).
38
+
39
+ ### Changed
40
+ - **14 tools** (was 13) — added `module_info`
41
+ - **Tool descriptions** — updated with `(v1.1: ...)` version hints for enhanced tools
42
+ - **MCP instructions** — added module_info to "COMBINE BOTH" workflow section
43
+ - **Version sync** — package.json, plugin.json, marketplace.json all at 0.9.0
44
+
8
45
  ## [0.8.3] - 2026-03-08
9
46
 
10
47
  ### Fixed
package/README.md CHANGED
@@ -149,7 +149,7 @@ For more control, you can add rules to your project:
149
149
  - **Cursor** → `.cursorrules` in project root
150
150
  - **Codex** → `AGENTS.md` in project root
151
151
 
152
- ## MCP Tools (13)
152
+ ## MCP Tools (14)
153
153
 
154
154
  ### Core Reading
155
155
 
@@ -166,12 +166,15 @@ For more control, you can add rules to your project:
166
166
 
167
167
  | Tool | Instead of | Description |
168
168
  |------|-----------|-------------|
169
- | `find_usages` | `Grep` (refs) | All usages of a symbol: definitions, imports, references. |
170
- | `project_overview` | `ls` + explore | Project type, architecture, frameworks, directory map. |
169
+ | `find_usages` | `Grep` (refs) | All usages of a symbol: definitions, imports, references. Filters: `scope` (path prefix), `kind` (definitions/imports/usages), `lang`, `limit`. |
170
+ | `project_overview` | `ls` + explore | Dual-detection (ast-index + config files) with confidence scoring. Project type, frameworks, quality tools, CI, architecture, directory map. Filter sections with `include`. |
171
171
  | `related_files` | manual explore | Import graph: what a file imports, what imports it, test files. |
172
- | `outline` | multiple `smart_read` | Compact overview of all code files in a directory. One call instead of 5-6. |
172
+ | `outline` | multiple `smart_read` | Compact overview of all code files in a directory. One call instead of 5-6. Supports `recursive` mode with `max_depth` for deep exploration. |
173
173
  | `find_unused` | manual | Detect dead code — unused functions, classes, variables. |
174
174
  | `code_audit` | multiple `Grep` | Code quality issues in one call: TODO/FIXME comments, deprecated symbols, structural code patterns (via ast-grep), decorator search. |
175
+ | `module_info` | manual analysis | Module dependency analysis: dependencies, dependents, public API, unused deps. Use for architecture understanding and dependency cleanup. |
176
+ | `smart_diff` | raw `git diff` | Structural diff with AST symbol mapping — shows which functions/classes changed instead of raw patch. Scopes: unstaged, staged, commit, branch. |
177
+ | `explore_area` | outline + related_files + git log | One-call directory exploration: structure, imports, tests, recent changes. Replaces 3-5 separate calls. |
175
178
 
176
179
  ### Analytics
177
180
 
@@ -315,13 +318,13 @@ npm run dev # TypeScript watch mode
315
318
  ```
316
319
  src/
317
320
  index.ts — CLI entry point (6 commands)
318
- server.ts — MCP server setup, tool definitions, instructions
321
+ server.ts — MCP server setup, 14 tool definitions, instructions
319
322
  types.ts — Core domain types
320
323
  ast-index/
321
- client.ts — ast-index CLI wrapper
324
+ client.ts — ast-index CLI wrapper (22+ methods)
322
325
  binary-manager.ts — Auto-download & manage ast-index binary
323
326
  tar-extract.ts — Minimal tar extractor (zero deps)
324
- types.ts — ast-index response types
327
+ types.ts — ast-index response types (20+ interfaces)
325
328
  core/
326
329
  file-cache.ts — LRU file cache with staleness detection
327
330
  context-registry.ts — Advisory context tracking + compact reminders
@@ -330,6 +333,7 @@ src/
330
333
  session-analytics.ts — Token savings tracking
331
334
  validation.ts — Input validators for all tools
332
335
  format-duration.ts — Shared duration formatter
336
+ project-detector.ts — Config-based project detection (frameworks, CI, quality tools)
333
337
  config/
334
338
  loader.ts — Config loading + deep merge
335
339
  defaults.ts — Default config values
@@ -341,13 +345,16 @@ src/
341
345
  read-range.ts — read_range handler
342
346
  read-diff.ts — read_diff handler (O(n) diff)
343
347
  smart-read-many.ts — Batch smart_read
344
- find-usages.ts — find_usages handler (via ast-index refs)
348
+ find-usages.ts — find_usages handler (scope/kind/lang/limit filters)
345
349
  read-for-edit.ts — read_for_edit handler (minimal edit context)
346
350
  related-files.ts — related_files handler (import graph)
347
- outline.ts — outline handler (directory overview)
351
+ outline.ts — outline handler (recursive directory overview)
348
352
  find-unused.ts — find_unused handler
349
353
  code-audit.ts — code_audit handler (TODOs, deprecated, patterns)
350
- project-overview.ts — project_overview (via ast-index map + conventions)
354
+ project-overview.ts — project_overview (dual-detection + confidence)
355
+ module-info.ts — module_info handler (deps, dependents, API, unused)
356
+ smart-diff.ts — smart_diff handler (structural git diff + symbol mapping)
357
+ explore-area.ts — explore_area handler (outline + imports + tests + changes)
351
358
  non-code.ts — JSON/YAML/MD/TOML structural summaries
352
359
  export-ast-index.ts — AST export for context-mode BM25
353
360
  git/
@@ -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, AstIndexAgrepMatch, AstIndexTodoEntry, AstIndexDeprecatedEntry, AstIndexAnnotationEntry } from './types.js';
2
+ import type { AstIndexSymbolDetail, AstIndexSearchResult, AstIndexUsageResult, AstIndexImplementation, AstIndexHierarchyNode, AstIndexRefsResponse, AstIndexMapResponse, AstIndexConventionsResponse, AstIndexCallerEntry, AstIndexCallTreeNode, AstIndexChangedEntry, AstIndexUnusedSymbol, AstIndexImportEntry, AstIndexAgrepMatch, AstIndexTodoEntry, AstIndexDeprecatedEntry, AstIndexAnnotationEntry, AstIndexModuleEntry, AstIndexModuleDep, AstIndexUnusedDep, AstIndexModuleApi } from './types.js';
3
3
  export declare class AstIndexClient {
4
4
  private static readonly MAX_INDEX_FILES;
5
5
  private binaryPath;
@@ -113,6 +113,20 @@ export declare class AstIndexClient {
113
113
  private parseAnnotationsText;
114
114
  /** Trigger incremental index update (called by file watcher after edits) */
115
115
  incrementalUpdate(): Promise<void>;
116
+ /** List project modules matching optional pattern */
117
+ modules(pattern?: string): Promise<AstIndexModuleEntry[]>;
118
+ /** Get dependencies of a module */
119
+ moduleDeps(module: string): Promise<AstIndexModuleDep[]>;
120
+ /** Get modules that depend on this module */
121
+ moduleDependents(module: string): Promise<AstIndexModuleDep[]>;
122
+ /** Find unused dependencies of a module */
123
+ unusedDeps(module: string): Promise<AstIndexUnusedDep[]>;
124
+ /** Get public API of a module */
125
+ moduleApi(module: string): Promise<AstIndexModuleApi[]>;
126
+ private parseModuleListText;
127
+ private parseModuleDepText;
128
+ private parseUnusedDepsText;
129
+ private parseModuleApiText;
116
130
  isAvailable(): boolean;
117
131
  /** Returns true if the index was built but found >50k files (node_modules leak) */
118
132
  isOversized(): boolean;
@@ -817,6 +817,185 @@ export class AstIndexClient {
817
817
  console.error(`[token-pilot] ast-index incremental update failed: ${err instanceof Error ? err.message : err}`);
818
818
  }
819
819
  }
820
+ // --- Module analysis methods (ast-index v3.27.0) ---
821
+ /** List project modules matching optional pattern */
822
+ async modules(pattern) {
823
+ if (this.indexDisabled || this.indexOversized)
824
+ return [];
825
+ await this.ensureIndex();
826
+ try {
827
+ const cmdArgs = pattern ? ['module', pattern] : ['module'];
828
+ const result = await this.exec(cmdArgs, 15000);
829
+ return this.parseModuleListText(result);
830
+ }
831
+ catch (err) {
832
+ console.error(`[token-pilot] ast-index module failed: ${err instanceof Error ? err.message : err}`);
833
+ return [];
834
+ }
835
+ }
836
+ /** Get dependencies of a module */
837
+ async moduleDeps(module) {
838
+ if (this.indexDisabled || this.indexOversized)
839
+ return [];
840
+ await this.ensureIndex();
841
+ try {
842
+ const result = await this.exec(['deps', module], 15000);
843
+ return this.parseModuleDepText(result);
844
+ }
845
+ catch (err) {
846
+ console.error(`[token-pilot] ast-index deps failed: ${err instanceof Error ? err.message : err}`);
847
+ return [];
848
+ }
849
+ }
850
+ /** Get modules that depend on this module */
851
+ async moduleDependents(module) {
852
+ if (this.indexDisabled || this.indexOversized)
853
+ return [];
854
+ await this.ensureIndex();
855
+ try {
856
+ const result = await this.exec(['dependents', module], 15000);
857
+ return this.parseModuleDepText(result);
858
+ }
859
+ catch (err) {
860
+ console.error(`[token-pilot] ast-index dependents failed: ${err instanceof Error ? err.message : err}`);
861
+ return [];
862
+ }
863
+ }
864
+ /** Find unused dependencies of a module */
865
+ async unusedDeps(module) {
866
+ if (this.indexDisabled || this.indexOversized)
867
+ return [];
868
+ await this.ensureIndex();
869
+ try {
870
+ const result = await this.exec(['unused-deps', module], 15000);
871
+ return this.parseUnusedDepsText(result);
872
+ }
873
+ catch (err) {
874
+ console.error(`[token-pilot] ast-index unused-deps failed: ${err instanceof Error ? err.message : err}`);
875
+ return [];
876
+ }
877
+ }
878
+ /** Get public API of a module */
879
+ async moduleApi(module) {
880
+ if (this.indexDisabled || this.indexOversized)
881
+ return [];
882
+ await this.ensureIndex();
883
+ try {
884
+ const result = await this.exec(['api', module], 15000);
885
+ return this.parseModuleApiText(result);
886
+ }
887
+ catch (err) {
888
+ console.error(`[token-pilot] ast-index api failed: ${err instanceof Error ? err.message : err}`);
889
+ return [];
890
+ }
891
+ }
892
+ // Parsers for module commands (text format — JSON may not be supported for all)
893
+ parseModuleListText(text) {
894
+ const results = [];
895
+ for (const line of text.split('\n')) {
896
+ if (!line.trim())
897
+ continue;
898
+ // Try JSON first
899
+ try {
900
+ const parsed = JSON.parse(line);
901
+ if (Array.isArray(parsed))
902
+ return parsed;
903
+ }
904
+ catch { /* not JSON, parse as text */ }
905
+ // Format: name (path) — N files OR name (path) OR path
906
+ const match = line.match(/^(\S+)\s+\((.+?)\)(?:\s*—\s*(\d+)\s+files?)?$/);
907
+ if (match) {
908
+ results.push({
909
+ name: match[1],
910
+ path: match[2],
911
+ file_count: match[3] ? parseInt(match[3], 10) : undefined,
912
+ });
913
+ }
914
+ else {
915
+ // Fallback: treat entire line as a path-based module
916
+ const trimmed = line.trim();
917
+ if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('─')) {
918
+ const name = trimmed.split('/').pop() ?? trimmed;
919
+ results.push({ name, path: trimmed });
920
+ }
921
+ }
922
+ }
923
+ return results;
924
+ }
925
+ parseModuleDepText(text) {
926
+ const results = [];
927
+ for (const line of text.split('\n')) {
928
+ if (!line.trim())
929
+ continue;
930
+ // Try JSON first
931
+ try {
932
+ const parsed = JSON.parse(line);
933
+ if (Array.isArray(parsed))
934
+ return parsed;
935
+ }
936
+ catch { /* not JSON, parse as text */ }
937
+ // Format: → name (path) OR ← name (path) OR name (path) OR name
938
+ const match = line.match(/^[→←\-\s]*(\S+)(?:\s+\((.+?)\))?(?:\s+\[(direct|transitive)\])?$/);
939
+ if (match) {
940
+ results.push({
941
+ name: match[1],
942
+ path: match[2] ?? match[1],
943
+ type: match[3],
944
+ });
945
+ }
946
+ }
947
+ return results;
948
+ }
949
+ parseUnusedDepsText(text) {
950
+ const results = [];
951
+ for (const line of text.split('\n')) {
952
+ if (!line.trim())
953
+ continue;
954
+ // Try JSON first
955
+ try {
956
+ const parsed = JSON.parse(line);
957
+ if (Array.isArray(parsed))
958
+ return parsed;
959
+ }
960
+ catch { /* not JSON, parse as text */ }
961
+ // Format: ⚠ name (path) — reason OR name (path) OR name — reason
962
+ const match = line.match(/^[⚠!\s]*(\S+)(?:\s+\((.+?)\))?(?:\s*[—\-]+\s*(.+))?$/);
963
+ if (match) {
964
+ results.push({
965
+ name: match[1],
966
+ path: match[2] ?? match[1],
967
+ reason: match[3]?.trim(),
968
+ });
969
+ }
970
+ }
971
+ return results;
972
+ }
973
+ parseModuleApiText(text) {
974
+ const results = [];
975
+ for (const line of text.split('\n')) {
976
+ if (!line.trim())
977
+ continue;
978
+ // Try JSON first
979
+ try {
980
+ const parsed = JSON.parse(line);
981
+ if (Array.isArray(parsed))
982
+ return parsed;
983
+ }
984
+ catch { /* not JSON, parse as text */ }
985
+ // Format: kind name (file:line) OR kind name signature (file:line)
986
+ const match = line.match(/^(\w+)\s+(\S+)(?:\s+(.*?))?\s+\((.+?):(\d+)\)$/);
987
+ if (match) {
988
+ results.push({
989
+ kind: match[1],
990
+ name: match[2],
991
+ signature: match[3]?.trim() || undefined,
992
+ file: match[4],
993
+ line: parseInt(match[5], 10),
994
+ });
995
+ }
996
+ }
997
+ return results;
998
+ }
820
999
  // --- Utility methods ---
821
1000
  isAvailable() {
822
1001
  return this.binaryPath !== null;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Types for ast-index CLI JSON output.
3
- * These map to the ACTUAL JSON responses from ast-index v3.24.0 commands.
3
+ * These map to the ACTUAL JSON responses from ast-index v3.27.0 commands.
4
4
  */
5
5
  /** ast-index outline — parsed from text output (JSON not supported) */
6
6
  export interface AstIndexOutlineEntry {
@@ -182,4 +182,30 @@ export interface AstIndexAnnotationEntry {
182
182
  line: number;
183
183
  annotation: string;
184
184
  }
185
+ /** ast-index module — list project modules */
186
+ export interface AstIndexModuleEntry {
187
+ name: string;
188
+ path: string;
189
+ file_count?: number;
190
+ }
191
+ /** ast-index deps / dependents — module dependency */
192
+ export interface AstIndexModuleDep {
193
+ name: string;
194
+ path: string;
195
+ type?: string;
196
+ }
197
+ /** ast-index unused-deps — unused module dependency */
198
+ export interface AstIndexUnusedDep {
199
+ name: string;
200
+ path: string;
201
+ reason?: string;
202
+ }
203
+ /** ast-index api — public API of a module */
204
+ export interface AstIndexModuleApi {
205
+ name: string;
206
+ kind: string;
207
+ signature?: string;
208
+ file: string;
209
+ line: number;
210
+ }
185
211
  //# sourceMappingURL=types.d.ts.map
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Types for ast-index CLI JSON output.
3
- * These map to the ACTUAL JSON responses from ast-index v3.24.0 commands.
3
+ * These map to the ACTUAL JSON responses from ast-index v3.27.0 commands.
4
4
  */
5
5
  export {};
6
6
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,42 @@
1
+ export interface DetectedStack {
2
+ type: string;
3
+ name: string;
4
+ version: string;
5
+ langVersion?: string;
6
+ framework?: string;
7
+ description?: string;
8
+ }
9
+ export interface ProjectDetection {
10
+ /** Project name (from primary config or dirname) */
11
+ projectName: string;
12
+ /** Project version (from primary config) */
13
+ projectVersion: string;
14
+ /** Project description */
15
+ projectDescription?: string;
16
+ /** What ast-index map() says about project type */
17
+ astIndexType?: string;
18
+ /** All detected stacks from config files */
19
+ configStacks: DetectedStack[];
20
+ /** Primary stack (most source files) */
21
+ primaryStack?: DetectedStack;
22
+ /** Confidence of detection */
23
+ confidence: 'high' | 'medium' | 'low' | 'unknown';
24
+ /** Quality tools found */
25
+ qualityTools: string[];
26
+ /** CI pipelines found */
27
+ ciPipelines: string[];
28
+ /** Docker presence */
29
+ hasDocker: boolean;
30
+ }
31
+ /**
32
+ * Detect project stacks by reading all config files in parallel.
33
+ * Returns dual-detection data: ast-index type + config-based stacks.
34
+ */
35
+ export declare function detectProject(projectRoot: string, astIndexType?: string): Promise<ProjectDetection>;
36
+ /** Detect quality/linting tools present in project root */
37
+ export declare function detectQualityTools(projectRoot: string): Promise<string[]>;
38
+ /** Detect CI/CD pipelines present in project */
39
+ export declare function detectCI(projectRoot: string): Promise<string[]>;
40
+ /** Detect Docker presence */
41
+ export declare function detectDocker(projectRoot: string): Promise<boolean>;
42
+ //# sourceMappingURL=project-detector.d.ts.map