token-pilot 0.7.5 → 0.8.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +34 -11
  3. package/dist/ast-index/client.d.ts +21 -1
  4. package/dist/ast-index/client.js +167 -0
  5. package/dist/ast-index/types.d.ts +29 -0
  6. package/dist/core/validation.d.ts +8 -0
  7. package/dist/core/validation.js +27 -0
  8. package/dist/git/file-watcher.d.ts +10 -1
  9. package/dist/git/file-watcher.js +23 -1
  10. package/dist/handlers/code-audit.d.ts +9 -0
  11. package/dist/handlers/code-audit.js +200 -0
  12. package/dist/handlers/smart-read.js +10 -0
  13. package/dist/index.js +61 -3
  14. package/dist/server.js +42 -5
  15. package/package.json +8 -3
  16. package/dist/ast-index/binary-manager.d.ts.map +0 -1
  17. package/dist/ast-index/binary-manager.js.map +0 -1
  18. package/dist/ast-index/client.d.ts.map +0 -1
  19. package/dist/ast-index/client.js.map +0 -1
  20. package/dist/ast-index/tar-extract.d.ts.map +0 -1
  21. package/dist/ast-index/tar-extract.js.map +0 -1
  22. package/dist/ast-index/types.d.ts.map +0 -1
  23. package/dist/ast-index/types.js.map +0 -1
  24. package/dist/config/defaults.d.ts.map +0 -1
  25. package/dist/config/defaults.js.map +0 -1
  26. package/dist/config/loader.d.ts.map +0 -1
  27. package/dist/config/loader.js.map +0 -1
  28. package/dist/core/context-registry.d.ts.map +0 -1
  29. package/dist/core/context-registry.js.map +0 -1
  30. package/dist/core/file-cache.d.ts.map +0 -1
  31. package/dist/core/file-cache.js.map +0 -1
  32. package/dist/core/format-duration.d.ts.map +0 -1
  33. package/dist/core/format-duration.js.map +0 -1
  34. package/dist/core/session-analytics.d.ts.map +0 -1
  35. package/dist/core/session-analytics.js.map +0 -1
  36. package/dist/core/symbol-resolver.d.ts.map +0 -1
  37. package/dist/core/symbol-resolver.js.map +0 -1
  38. package/dist/core/token-estimator.d.ts.map +0 -1
  39. package/dist/core/token-estimator.js.map +0 -1
  40. package/dist/core/validation.d.ts.map +0 -1
  41. package/dist/core/validation.js.map +0 -1
  42. package/dist/formatters/structure.d.ts.map +0 -1
  43. package/dist/formatters/structure.js.map +0 -1
  44. package/dist/git/file-watcher.d.ts.map +0 -1
  45. package/dist/git/file-watcher.js.map +0 -1
  46. package/dist/git/watcher.d.ts.map +0 -1
  47. package/dist/git/watcher.js.map +0 -1
  48. package/dist/handlers/class-hierarchy.d.ts.map +0 -1
  49. package/dist/handlers/class-hierarchy.js.map +0 -1
  50. package/dist/handlers/export-ast-index.d.ts.map +0 -1
  51. package/dist/handlers/export-ast-index.js.map +0 -1
  52. package/dist/handlers/find-implementations.d.ts.map +0 -1
  53. package/dist/handlers/find-implementations.js.map +0 -1
  54. package/dist/handlers/find-unused.d.ts.map +0 -1
  55. package/dist/handlers/find-unused.js.map +0 -1
  56. package/dist/handlers/find-usages.d.ts.map +0 -1
  57. package/dist/handlers/find-usages.js.map +0 -1
  58. package/dist/handlers/non-code.d.ts.map +0 -1
  59. package/dist/handlers/non-code.js.map +0 -1
  60. package/dist/handlers/outline.d.ts.map +0 -1
  61. package/dist/handlers/outline.js.map +0 -1
  62. package/dist/handlers/project-overview.d.ts.map +0 -1
  63. package/dist/handlers/project-overview.js.map +0 -1
  64. package/dist/handlers/read-diff.d.ts.map +0 -1
  65. package/dist/handlers/read-diff.js.map +0 -1
  66. package/dist/handlers/read-for-edit.d.ts.map +0 -1
  67. package/dist/handlers/read-for-edit.js.map +0 -1
  68. package/dist/handlers/read-range.d.ts.map +0 -1
  69. package/dist/handlers/read-range.js.map +0 -1
  70. package/dist/handlers/read-symbol.d.ts.map +0 -1
  71. package/dist/handlers/read-symbol.js.map +0 -1
  72. package/dist/handlers/related-files.d.ts.map +0 -1
  73. package/dist/handlers/related-files.js.map +0 -1
  74. package/dist/handlers/search-code.d.ts.map +0 -1
  75. package/dist/handlers/search-code.js.map +0 -1
  76. package/dist/handlers/smart-read-many.d.ts.map +0 -1
  77. package/dist/handlers/smart-read-many.js.map +0 -1
  78. package/dist/handlers/smart-read.d.ts.map +0 -1
  79. package/dist/handlers/smart-read.js.map +0 -1
  80. package/dist/hooks/installer.d.ts.map +0 -1
  81. package/dist/hooks/installer.js.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js.map +0 -1
  84. package/dist/integration/context-mode-detector.d.ts.map +0 -1
  85. package/dist/integration/context-mode-detector.js.map +0 -1
  86. package/dist/server.d.ts.map +0 -1
  87. package/dist/server.js.map +0 -1
  88. package/dist/types.d.ts.map +0 -1
  89. package/dist/types.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ 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.0] - 2026-03-07
9
+
10
+ ### Added
11
+ - **`code_audit` tool** — find code quality issues in one call: TODO/FIXME comments (`check="todo"`), deprecated symbols (`check="deprecated"`), structural code patterns via ast-grep (`check="pattern"`), decorator search (`check="annotations"`), or combined audit (`check="all"`).
12
+ - **Incremental index update on file changes** — file watcher now triggers `ast-index update` (debounced 2s) after edits. Keeps index fresh for find_usages, find_unused, code_audit.
13
+ - **ast-index client methods** — `agrep()`, `todo()`, `deprecated()`, `annotations()`, `incrementalUpdate()`.
14
+
15
+ ### Fixed
16
+ - **smart_read on directories** — now returns helpful message instead of EISDIR crash.
17
+ - **MCP instructions** — added "COMBINE BOTH" section for audit tasks (Token Pilot + Grep).
18
+
19
+ ## [0.7.6] - 2026-03-07
20
+
21
+ ### Added
22
+ - **`npx token-pilot init`** — one command creates `.mcp.json` with both token-pilot and context-mode configured. Idempotent — safely updates existing configs without overwriting.
23
+ - **MCP Server Instructions** — protocol-level `instructions` field tells AI agents WHEN to use Token Pilot tools instead of built-in defaults. Works universally on all MCP clients.
24
+ - **Improved tool descriptions** — each tool explicitly states what built-in tool it replaces (e.g. "Use INSTEAD OF Read/cat").
25
+
26
+ ### Fixed
27
+ - **3 high severity vulnerabilities** — updated hono and express-rate-limit.
28
+ - **npm package size** — excluded source maps from package. 505 kB → 286 kB (−43%).
29
+ - **Accurate thresholds** — README and instructions now correctly state smallFileThreshold=200 (was 80).
30
+ - **read_diff documentation** — clarified that smart_read must be called BEFORE editing to create baseline snapshot.
31
+
32
+ ### Changed
33
+ - **README** — honest metrics (60-80%), Quick Start with `init` command, MCP instructions section, Codex/Antigravity support.
34
+ - **npm keywords** — added `codex`, `cline`, `model-context-protocol`, `token-savings`.
35
+
8
36
  ## [0.7.4] - 2026-03-07
9
37
 
10
38
  ### Added
package/README.md CHANGED
@@ -13,13 +13,36 @@ Token Pilot: smart_read("user-service.ts") → 15-line outline → ~200 tok
13
13
  After edit: read_diff("user-service.ts") → ~20 tokens
14
14
  ```
15
15
 
16
- **~80% reduction** in this example. Files under 80 lines are returned in full automatically (no overhead for small files).
16
+ **~80% reduction** in this example. Files under 200 lines are returned in full automatically (no overhead for small files). Real savings start at ~200+ lines.
17
17
 
18
18
  ## Installation
19
19
 
20
- ### npx Any MCP-compatible AI Assistant
20
+ ### Quick Start (recommended)
21
21
 
22
- Zero install. Add to your `.mcp.json` (project-level or `~/.mcp.json` for global):
22
+ One command creates `.mcp.json` with token-pilot + context-mode:
23
+
24
+ ```bash
25
+ npx -y token-pilot init
26
+ ```
27
+
28
+ Safe to run in any project — if `.mcp.json` already exists, only adds missing servers without overwriting existing config.
29
+
30
+ This generates:
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "token-pilot": { "command": "npx", "args": ["-y", "token-pilot"] },
36
+ "context-mode": { "command": "npx", "args": ["-y", "claude-context-mode"] }
37
+ }
38
+ }
39
+ ```
40
+
41
+ **That's it.** Restart your AI assistant. Both packages are downloaded automatically, ast-index binary is fetched on first run. No Rust, no Cargo, no manual setup.
42
+
43
+ ### Manual Setup
44
+
45
+ Add to your `.mcp.json` (project-level or `~/.mcp.json` for global):
23
46
 
24
47
  ```json
25
48
  {
@@ -32,9 +55,7 @@ Zero install. Add to your `.mcp.json` (project-level or `~/.mcp.json` for global
32
55
  }
33
56
  ```
34
57
 
35
- **That's it.** npx downloads the package, ast-index binary is fetched automatically on first run. No Rust, no Cargo, no manual setup.
36
-
37
- Works with: **Cursor**, **Cline**, **Continue**, **Codex**, **Antigravity**, and any MCP-compatible client.
58
+ Works with: **Claude Code**, **Cursor**, **Codex**, **Antigravity**, **Cline**, and any MCP-compatible client.
38
59
 
39
60
  #### Cursor
40
61
 
@@ -124,7 +145,7 @@ For more control, you can add rules to your project:
124
145
  - **Cursor** → `.cursorrules` in project root
125
146
  - **Codex** → `AGENTS.md` in project root
126
147
 
127
- ## MCP Tools (12)
148
+ ## MCP Tools (13)
128
149
 
129
150
  ### Core Reading
130
151
 
@@ -134,7 +155,7 @@ For more control, you can add rules to your project:
134
155
  | `read_symbol` | `Read` + scroll | Load source of a specific symbol. Supports `Class.method`. `show` param: full/head/tail/outline. |
135
156
  | `read_for_edit` | `Read` before `Edit` | Minimal RAW code around a symbol — copy directly as `old_string` for Edit tool. |
136
157
  | `read_range` | `Read` offset | Read a specific line range from a file. |
137
- | `read_diff` | re-`Read` | Show only what changed since last smart_read. Saves tokens on re-reads. |
158
+ | `read_diff` | re-`Read` | Show only changed hunks since last smart_read. Requires smart_read before editing (for baseline). Works with any edit tool. |
138
159
  | `smart_read_many` | multiple `Read` | Batch smart_read for up to 20 files in one call. |
139
160
 
140
161
  ### Search & Navigation
@@ -146,6 +167,7 @@ For more control, you can add rules to your project:
146
167
  | `related_files` | manual explore | Import graph: what a file imports, what imports it, test files. |
147
168
  | `outline` | multiple `smart_read` | Compact overview of all code files in a directory. One call instead of 5-6. |
148
169
  | `find_unused` | manual | Detect dead code — unused functions, classes, variables. |
170
+ | `code_audit` | multiple `Grep` | Code quality issues in one call: TODO/FIXME comments, deprecated symbols, structural code patterns (via ast-grep), decorator search. |
149
171
 
150
172
  ### Analytics
151
173
 
@@ -158,6 +180,7 @@ For more control, you can add rules to your project:
158
180
  ```bash
159
181
  token-pilot # Start MCP server (uses cwd as project root)
160
182
  token-pilot /path/to/project # Start with specific project root
183
+ token-pilot init [dir] # Create/update .mcp.json (token-pilot + context-mode)
161
184
  token-pilot install-ast-index # Download ast-index binary (auto on first run)
162
185
  token-pilot install-hook [root] # Install PreToolUse hook
163
186
  token-pilot uninstall-hook # Remove hook
@@ -174,7 +197,7 @@ Create `.token-pilot.json` in your project root to customize behavior:
174
197
  ```json
175
198
  {
176
199
  "smartRead": {
177
- "smallFileThreshold": 80,
200
+ "smallFileThreshold": 200,
178
201
  "advisoryReminders": true
179
202
  },
180
203
  "cache": {
@@ -210,7 +233,7 @@ All fields are optional — sensible defaults are used for anything not specifie
210
233
 
211
234
  | Option | Default | Description |
212
235
  |--------|---------|-------------|
213
- | `smartRead.smallFileThreshold` | `80` | Files with fewer lines are returned in full (no AST overhead). |
236
+ | `smartRead.smallFileThreshold` | `200` | Files with fewer lines are returned in full (no AST overhead). |
214
237
  | `cache.maxSizeMB` | `100` | Max memory for file cache. LRU eviction when exceeded. |
215
238
  | `cache.watchFiles` | `true` | Auto-invalidate cache on file changes (chokidar). |
216
239
  | `git.watchHead` | `true` | Watch `.git/HEAD` for branch switches, invalidate changed files. |
@@ -232,7 +255,6 @@ When both are configured, Token Pilot automatically:
232
255
  - Detects context-mode via `.mcp.json`
233
256
  - Suggests context-mode for large non-code files
234
257
  - Shows combined architecture info in `session_analytics`
235
- - Provides `export_ast_index` to feed AST data into context-mode's BM25 index
236
258
 
237
259
  **Combined savings: 60-80%** in a typical coding session.
238
260
 
@@ -320,6 +342,7 @@ src/
320
342
  related-files.ts — related_files handler (import graph)
321
343
  outline.ts — outline handler (directory overview)
322
344
  find-unused.ts — find_unused handler
345
+ code-audit.ts — code_audit handler (TODOs, deprecated, patterns)
323
346
  project-overview.ts — project_overview (via ast-index map + conventions)
324
347
  non-code.ts — JSON/YAML/MD/TOML structural summaries
325
348
  export-ast-index.ts — AST export for context-mode BM25
@@ -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 } 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';
3
3
  export declare class AstIndexClient {
4
4
  private static readonly MAX_INDEX_FILES;
5
5
  private binaryPath;
@@ -11,6 +11,7 @@ export declare class AstIndexClient {
11
11
  private timeout;
12
12
  private configBinaryPath;
13
13
  private autoInstall;
14
+ private astGrepAvailable;
14
15
  constructor(projectRoot: string, timeout?: number, options?: {
15
16
  binaryPath?: string | null;
16
17
  autoInstall?: boolean;
@@ -92,6 +93,25 @@ export declare class AstIndexClient {
92
93
  */
93
94
  fileImports(filePath: string): Promise<AstIndexImportEntry[]>;
94
95
  private parseImportsText;
96
+ /** Check if ast-grep (sg) is available for structural pattern search */
97
+ private checkAstGrep;
98
+ /** Structural pattern search via ast-grep. Requires ast-grep (sg) installed. */
99
+ agrep(pattern: string, options?: {
100
+ lang?: string;
101
+ limit?: number;
102
+ }): Promise<AstIndexAgrepMatch[]>;
103
+ private parseAgrepText;
104
+ /** Find TODO/FIXME/HACK comments in the project */
105
+ todo(): Promise<AstIndexTodoEntry[]>;
106
+ private parseTodoText;
107
+ /** Find @Deprecated symbols in the project */
108
+ deprecated(): Promise<AstIndexDeprecatedEntry[]>;
109
+ private parseDeprecatedText;
110
+ /** Find symbols with a specific annotation/decorator */
111
+ annotations(name: string): Promise<AstIndexAnnotationEntry[]>;
112
+ private parseAnnotationsText;
113
+ /** Trigger incremental index update (called by file watcher after edits) */
114
+ incrementalUpdate(): Promise<void>;
95
115
  isAvailable(): boolean;
96
116
  /** Returns true if the index was built but found >50k files (node_modules leak) */
97
117
  isOversized(): boolean;
@@ -16,6 +16,7 @@ export class AstIndexClient {
16
16
  timeout;
17
17
  configBinaryPath;
18
18
  autoInstall;
19
+ astGrepAvailable = null;
19
20
  constructor(projectRoot, timeout = 5000, options) {
20
21
  this.projectRoot = projectRoot;
21
22
  this.timeout = timeout;
@@ -636,6 +637,172 @@ export class AstIndexClient {
636
637
  }
637
638
  return entries;
638
639
  }
640
+ // --- Code audit commands ---
641
+ /** Check if ast-grep (sg) is available for structural pattern search */
642
+ async checkAstGrep() {
643
+ if (this.astGrepAvailable !== null)
644
+ return this.astGrepAvailable;
645
+ try {
646
+ await execFileAsync('sg', ['--version'], { timeout: 3000 });
647
+ this.astGrepAvailable = true;
648
+ }
649
+ catch {
650
+ this.astGrepAvailable = false;
651
+ }
652
+ return this.astGrepAvailable;
653
+ }
654
+ /** Structural pattern search via ast-grep. Requires ast-grep (sg) installed. */
655
+ async agrep(pattern, options) {
656
+ if (this.indexDisabled || this.indexOversized)
657
+ return [];
658
+ await this.ensureIndex();
659
+ const available = await this.checkAstGrep();
660
+ if (!available) {
661
+ throw new Error('ast-grep (sg) not installed — required for structural pattern search.\n' +
662
+ 'Install: brew install ast-grep OR npm i -g @ast-grep/cli\n' +
663
+ 'Alternative: use Grep/ripgrep for text-based pattern search.');
664
+ }
665
+ const limit = options?.limit ?? 50;
666
+ const args = ['agrep', pattern];
667
+ if (options?.lang)
668
+ args.push('--lang', options.lang);
669
+ args.push('--limit', String(limit));
670
+ try {
671
+ const result = await this.exec(args, 15000);
672
+ return this.parseAgrepText(result).slice(0, limit);
673
+ }
674
+ catch (err) {
675
+ console.error(`[token-pilot] ast-index agrep failed: ${err instanceof Error ? err.message : err}`);
676
+ return [];
677
+ }
678
+ }
679
+ parseAgrepText(text) {
680
+ const results = [];
681
+ for (const line of text.split('\n')) {
682
+ if (!line.trim())
683
+ continue;
684
+ // Format: file:line:matched_text OR file:line: matched_text
685
+ const match = line.match(/^(.+?):(\d+):(.*)$/);
686
+ if (match) {
687
+ results.push({
688
+ file: match[1],
689
+ line: parseInt(match[2], 10),
690
+ text: match[3].trim(),
691
+ });
692
+ }
693
+ }
694
+ return results;
695
+ }
696
+ /** Find TODO/FIXME/HACK comments in the project */
697
+ async todo() {
698
+ if (this.indexDisabled || this.indexOversized)
699
+ return [];
700
+ await this.ensureIndex();
701
+ try {
702
+ const result = await this.exec(['todo'], 15000);
703
+ return this.parseTodoText(result);
704
+ }
705
+ catch (err) {
706
+ console.error(`[token-pilot] ast-index todo failed: ${err instanceof Error ? err.message : err}`);
707
+ return [];
708
+ }
709
+ }
710
+ parseTodoText(text) {
711
+ const results = [];
712
+ for (const line of text.split('\n')) {
713
+ if (!line.trim())
714
+ continue;
715
+ // Try format: file:line: KIND: message OR file:line: KIND message
716
+ const match = line.match(/^(.+?):(\d+):\s*(TODO|FIXME|HACK|XXX|NOTE|WARN(?:ING)?)[:\s]+(.*)$/i);
717
+ if (match) {
718
+ results.push({
719
+ file: match[1],
720
+ line: parseInt(match[2], 10),
721
+ kind: match[3].toUpperCase(),
722
+ text: match[4].trim(),
723
+ });
724
+ }
725
+ }
726
+ return results;
727
+ }
728
+ /** Find @Deprecated symbols in the project */
729
+ async deprecated() {
730
+ if (this.indexDisabled || this.indexOversized)
731
+ return [];
732
+ await this.ensureIndex();
733
+ try {
734
+ const result = await this.exec(['deprecated'], 15000);
735
+ return this.parseDeprecatedText(result);
736
+ }
737
+ catch (err) {
738
+ console.error(`[token-pilot] ast-index deprecated failed: ${err instanceof Error ? err.message : err}`);
739
+ return [];
740
+ }
741
+ }
742
+ parseDeprecatedText(text) {
743
+ const results = [];
744
+ for (const line of text.split('\n')) {
745
+ if (!line.trim())
746
+ continue;
747
+ // Try format: kind name (file:line) - message OR kind name (file:line)
748
+ const match = line.match(/^(\w+)\s+(\S+)\s+\((.+?):(\d+)\)(?:\s*-\s*(.+))?$/);
749
+ if (match) {
750
+ results.push({
751
+ kind: match[1],
752
+ name: match[2],
753
+ file: match[3],
754
+ line: parseInt(match[4], 10),
755
+ message: match[5]?.trim(),
756
+ });
757
+ }
758
+ }
759
+ return results;
760
+ }
761
+ /** Find symbols with a specific annotation/decorator */
762
+ async annotations(name) {
763
+ if (this.indexDisabled || this.indexOversized)
764
+ return [];
765
+ await this.ensureIndex();
766
+ try {
767
+ const result = await this.exec(['annotations', name], 15000);
768
+ return this.parseAnnotationsText(result, name);
769
+ }
770
+ catch (err) {
771
+ console.error(`[token-pilot] ast-index annotations failed: ${err instanceof Error ? err.message : err}`);
772
+ return [];
773
+ }
774
+ }
775
+ parseAnnotationsText(text, annotationName) {
776
+ const results = [];
777
+ for (const line of text.split('\n')) {
778
+ if (!line.trim())
779
+ continue;
780
+ // Try format: kind name (file:line) OR @Annotation kind name (file:line)
781
+ const match = line.match(/^(?:@\S+\s+)?(\w+)\s+(\S+)\s+\((.+?):(\d+)\)$/);
782
+ if (match) {
783
+ results.push({
784
+ kind: match[1],
785
+ name: match[2],
786
+ file: match[3],
787
+ line: parseInt(match[4], 10),
788
+ annotation: annotationName,
789
+ });
790
+ }
791
+ }
792
+ return results;
793
+ }
794
+ /** Trigger incremental index update (called by file watcher after edits) */
795
+ async incrementalUpdate() {
796
+ if (!this.indexed || this.indexDisabled || this.indexOversized)
797
+ return;
798
+ try {
799
+ await this.exec(['update'], 15000);
800
+ }
801
+ catch (err) {
802
+ console.error(`[token-pilot] ast-index incremental update failed: ${err instanceof Error ? err.message : err}`);
803
+ }
804
+ }
805
+ // --- Utility methods ---
639
806
  isAvailable() {
640
807
  return this.binaryPath !== null;
641
808
  }
@@ -153,4 +153,33 @@ export interface AstIndexImportEntry {
153
153
  isDefault?: boolean;
154
154
  isNamespace?: boolean;
155
155
  }
156
+ /** ast-index agrep — structural pattern search via ast-grep */
157
+ export interface AstIndexAgrepMatch {
158
+ file: string;
159
+ line: number;
160
+ text: string;
161
+ }
162
+ /** ast-index todo — TODO/FIXME/HACK comments */
163
+ export interface AstIndexTodoEntry {
164
+ file: string;
165
+ line: number;
166
+ kind: string;
167
+ text: string;
168
+ }
169
+ /** ast-index deprecated — @Deprecated symbols */
170
+ export interface AstIndexDeprecatedEntry {
171
+ name: string;
172
+ kind: string;
173
+ file: string;
174
+ line: number;
175
+ message?: string;
176
+ }
177
+ /** ast-index annotations — symbols with specific decorator/annotation */
178
+ export interface AstIndexAnnotationEntry {
179
+ name: string;
180
+ kind: string;
181
+ file: string;
182
+ line: number;
183
+ annotation: string;
184
+ }
156
185
  //# sourceMappingURL=types.d.ts.map
@@ -76,6 +76,14 @@ export declare function validateFindUnusedArgs(args: unknown): {
76
76
  export_only?: boolean;
77
77
  limit?: number;
78
78
  };
79
+ export interface CodeAuditArgs {
80
+ check: 'pattern' | 'todo' | 'deprecated' | 'annotations' | 'all';
81
+ pattern?: string;
82
+ name?: string;
83
+ lang?: string;
84
+ limit?: number;
85
+ }
86
+ export declare function validateCodeAuditArgs(args: unknown): CodeAuditArgs;
79
87
  /** Detect roots that would cause ast-index to scan the entire filesystem */
80
88
  export declare function isDangerousRoot(root: string): boolean;
81
89
  //# sourceMappingURL=validation.d.ts.map
@@ -207,6 +207,33 @@ export function validateFindUnusedArgs(args) {
207
207
  limit: optionalNumber(a.limit, 'limit'),
208
208
  };
209
209
  }
210
+ export function validateCodeAuditArgs(args) {
211
+ if (!args || typeof args !== 'object') {
212
+ throw new Error('Arguments must be an object with a "check" parameter.');
213
+ }
214
+ const a = args;
215
+ const validChecks = ['pattern', 'todo', 'deprecated', 'annotations', 'all'];
216
+ if (typeof a.check !== 'string' || !validChecks.includes(a.check)) {
217
+ throw new Error(`Required parameter "check" must be one of: ${validChecks.join(', ')}`);
218
+ }
219
+ if (a.check === 'pattern') {
220
+ if (typeof a.pattern !== 'string' || a.pattern.length === 0) {
221
+ throw new Error('Parameter "pattern" is required when check="pattern". Example: "except:" or "print($$$ARGS)"');
222
+ }
223
+ }
224
+ if (a.check === 'annotations') {
225
+ if (typeof a.name !== 'string' || a.name.length === 0) {
226
+ throw new Error('Parameter "name" is required when check="annotations". Example: "Deprecated" or "Controller"');
227
+ }
228
+ }
229
+ return {
230
+ check: a.check,
231
+ pattern: optionalString(a.pattern, 'pattern'),
232
+ name: optionalString(a.name, 'name'),
233
+ lang: optionalString(a.lang, 'lang'),
234
+ limit: optionalNumber(a.limit, 'limit'),
235
+ };
236
+ }
210
237
  /** Detect roots that would cause ast-index to scan the entire filesystem */
211
238
  export function isDangerousRoot(root) {
212
239
  const normalized = root.replace(/\/+$/, '') || '/';
@@ -1,18 +1,27 @@
1
1
  import type { FileCache } from '../core/file-cache.js';
2
2
  import type { ContextRegistry } from '../core/context-registry.js';
3
+ import type { AstIndexClient } from '../ast-index/client.js';
3
4
  /**
4
5
  * Watches individual files for changes and auto-invalidates cache.
5
6
  * Only watches files that have been explicitly added (via watchFile()),
6
7
  * NOT the entire project root — avoids scanning thousands of files
7
8
  * and permission errors on Docker volumes, restricted dirs, etc.
9
+ *
10
+ * Also triggers debounced ast-index incremental update on file changes
11
+ * to keep the index fresh for find_usages, find_unused, code_audit.
8
12
  */
9
13
  export declare class FileWatcher {
14
+ private static readonly UPDATE_DEBOUNCE_MS;
10
15
  private fileCache;
11
16
  private contextRegistry;
17
+ private astIndex;
12
18
  private watcher;
13
19
  private watchedFiles;
14
- constructor(_projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry, _ignore: string[]);
20
+ private updateTimer;
21
+ constructor(_projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry, _ignore: string[], astIndex?: AstIndexClient);
15
22
  start(): void;
23
+ /** Debounced ast-index incremental update after file changes */
24
+ private scheduleIndexUpdate;
16
25
  /** Add a specific file to watch. Called after smart_read/read_symbol loads a file. */
17
26
  watchFile(filePath: string): void;
18
27
  stop(): void;
@@ -5,15 +5,22 @@ import { resolve } from 'node:path';
5
5
  * Only watches files that have been explicitly added (via watchFile()),
6
6
  * NOT the entire project root — avoids scanning thousands of files
7
7
  * and permission errors on Docker volumes, restricted dirs, etc.
8
+ *
9
+ * Also triggers debounced ast-index incremental update on file changes
10
+ * to keep the index fresh for find_usages, find_unused, code_audit.
8
11
  */
9
12
  export class FileWatcher {
13
+ static UPDATE_DEBOUNCE_MS = 2000;
10
14
  fileCache;
11
15
  contextRegistry;
16
+ astIndex;
12
17
  watcher = null;
13
18
  watchedFiles = new Set();
14
- constructor(_projectRoot, fileCache, contextRegistry, _ignore) {
19
+ updateTimer = null;
20
+ constructor(_projectRoot, fileCache, contextRegistry, _ignore, astIndex) {
15
21
  this.fileCache = fileCache;
16
22
  this.contextRegistry = contextRegistry;
23
+ this.astIndex = astIndex ?? null;
17
24
  }
18
25
  start() {
19
26
  // Start with an empty watcher — files are added on demand via watchFile()
@@ -30,14 +37,26 @@ export class FileWatcher {
30
37
  if (this.fileCache.get(absPath)) {
31
38
  this.fileCache.invalidate(absPath);
32
39
  }
40
+ this.scheduleIndexUpdate();
33
41
  });
34
42
  this.watcher.on('unlink', (filePath) => {
35
43
  const absPath = resolve(filePath);
36
44
  this.fileCache.invalidate(absPath);
37
45
  this.contextRegistry.forget(absPath);
38
46
  this.watchedFiles.delete(absPath);
47
+ this.scheduleIndexUpdate();
39
48
  });
40
49
  }
50
+ /** Debounced ast-index incremental update after file changes */
51
+ scheduleIndexUpdate() {
52
+ if (!this.astIndex)
53
+ return;
54
+ if (this.updateTimer)
55
+ clearTimeout(this.updateTimer);
56
+ this.updateTimer = setTimeout(() => {
57
+ this.astIndex?.incrementalUpdate().catch(() => { });
58
+ }, FileWatcher.UPDATE_DEBOUNCE_MS);
59
+ }
41
60
  /** Add a specific file to watch. Called after smart_read/read_symbol loads a file. */
42
61
  watchFile(filePath) {
43
62
  const absPath = resolve(filePath);
@@ -49,6 +68,9 @@ export class FileWatcher {
49
68
  this.watchedFiles.add(absPath);
50
69
  }
51
70
  stop() {
71
+ if (this.updateTimer)
72
+ clearTimeout(this.updateTimer);
73
+ this.updateTimer = null;
52
74
  this.watcher?.close();
53
75
  this.watcher = null;
54
76
  this.watchedFiles.clear();
@@ -0,0 +1,9 @@
1
+ import type { AstIndexClient } from '../ast-index/client.js';
2
+ import type { CodeAuditArgs } from '../core/validation.js';
3
+ export declare function handleCodeAudit(args: CodeAuditArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
4
+ content: Array<{
5
+ type: 'text';
6
+ text: string;
7
+ }>;
8
+ }>;
9
+ //# sourceMappingURL=code-audit.d.ts.map