token-pilot 0.8.3 → 0.9.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.
@@ -2,7 +2,7 @@
2
2
  "name": "token-pilot",
3
3
  "displayName": "Token Pilot",
4
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",
5
+ "version": "0.9.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.9.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,31 @@ 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.9.0] - 2026-03-08
9
+
10
+ ### Added
11
+ - **`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.
12
+ - **`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.
13
+ - **`project_overview` `include` parameter** — filter sections: `["stack"]` for quick type check, `["quality","ci"]` for tooling overview. Default: all sections.
14
+ - **`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.
15
+ - **`outline` recursive mode** — `recursive=true` with `max_depth` (default 2, max 5) recurses into subdirectories. At max depth shows file counts only.
16
+ - **`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).
17
+ - **ast-index client: 5 module methods** — `modules()`, `moduleDeps()`, `moduleDependents()`, `unusedDeps()`, `moduleApi()` with JSON-first + text fallback parsing.
18
+ - **ast-index types: 4 module interfaces** — `AstIndexModuleEntry`, `AstIndexModuleDep`, `AstIndexUnusedDep`, `AstIndexModuleApi`.
19
+
20
+ ### Fixed
21
+ - **`module_info` token savings** — `tokensWouldBe` was equal to `tokensReturned` (0% savings). Now estimates manual analysis cost correctly.
22
+ - **`outline` recursive overflow** — added `MAX_OUTLINE_LINES=500` guard to prevent runaway output on large projects with `recursive=true`.
23
+ - **`project_overview` "frontend" label** — removed hardcoded "frontend" suffix for secondary stacks (Node.js is not always frontend).
24
+ - **Ruff detection** — no longer double-reads `pyproject.toml`. Checks `ruff.toml`/`.ruff.toml` first, falls back to `pyproject.toml [tool.ruff]` only if needed.
25
+ - **44 new tests** — validators (23) + project-detector (21). Total: 144 tests (was 100).
26
+
27
+ ### Changed
28
+ - **14 tools** (was 13) — added `module_info`
29
+ - **Tool descriptions** — updated with `(v1.1: ...)` version hints for enhanced tools
30
+ - **MCP instructions** — added module_info to "COMBINE BOTH" workflow section
31
+ - **Version sync** — package.json, plugin.json, marketplace.json all at 0.9.0
32
+
8
33
  ## [0.8.3] - 2026-03-08
9
34
 
10
35
  ### 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,13 @@ 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. |
175
176
 
176
177
  ### Analytics
177
178
 
@@ -315,13 +316,13 @@ npm run dev # TypeScript watch mode
315
316
  ```
316
317
  src/
317
318
  index.ts — CLI entry point (6 commands)
318
- server.ts — MCP server setup, tool definitions, instructions
319
+ server.ts — MCP server setup, 14 tool definitions, instructions
319
320
  types.ts — Core domain types
320
321
  ast-index/
321
- client.ts — ast-index CLI wrapper
322
+ client.ts — ast-index CLI wrapper (22+ methods)
322
323
  binary-manager.ts — Auto-download & manage ast-index binary
323
324
  tar-extract.ts — Minimal tar extractor (zero deps)
324
- types.ts — ast-index response types
325
+ types.ts — ast-index response types (20+ interfaces)
325
326
  core/
326
327
  file-cache.ts — LRU file cache with staleness detection
327
328
  context-registry.ts — Advisory context tracking + compact reminders
@@ -330,6 +331,7 @@ src/
330
331
  session-analytics.ts — Token savings tracking
331
332
  validation.ts — Input validators for all tools
332
333
  format-duration.ts — Shared duration formatter
334
+ project-detector.ts — Config-based project detection (frameworks, CI, quality tools)
333
335
  config/
334
336
  loader.ts — Config loading + deep merge
335
337
  defaults.ts — Default config values
@@ -341,13 +343,14 @@ src/
341
343
  read-range.ts — read_range handler
342
344
  read-diff.ts — read_diff handler (O(n) diff)
343
345
  smart-read-many.ts — Batch smart_read
344
- find-usages.ts — find_usages handler (via ast-index refs)
346
+ find-usages.ts — find_usages handler (scope/kind/lang/limit filters)
345
347
  read-for-edit.ts — read_for_edit handler (minimal edit context)
346
348
  related-files.ts — related_files handler (import graph)
347
- outline.ts — outline handler (directory overview)
349
+ outline.ts — outline handler (recursive directory overview)
348
350
  find-unused.ts — find_unused handler
349
351
  code-audit.ts — code_audit handler (TODOs, deprecated, patterns)
350
- project-overview.ts — project_overview (via ast-index map + conventions)
352
+ project-overview.ts — project_overview (dual-detection + confidence)
353
+ module-info.ts — module_info handler (deps, dependents, API, unused)
351
354
  non-code.ts — JSON/YAML/MD/TOML structural summaries
352
355
  export-ast-index.ts — AST export for context-mode BM25
353
356
  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