token-pilot 0.18.0 → 0.19.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/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ 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.19.0] - 2026-04-15
9
+
10
+ ### Added
11
+ - **`session_snapshot` tool** — capture current session state (goal, confirmed facts, files, blockers, next step) as a compact markdown block (<200 tokens). Call before context compaction or when switching direction in long sessions.
12
+ - **`max_tokens` parameter** on `smart_read` and `smart_read_many` — token budget per read. Output auto-downgrades through three levels: full content → structural outline → compact (symbol names + line ranges only). Enables context-constrained sessions.
13
+ - **Session compaction advisory** — policy engine now tracks total tool calls and tokens returned. Advises calling `session_snapshot()` when thresholds are reached (default: every 15 calls or after 8,000 tokens). Configurable via `compactionCallThreshold` and `compactionTokenThreshold`.
14
+ - **"Why This Approach Works"** section in README explaining the 3-level optimization strategy.
15
+
16
+ ### Changed
17
+ - **21 tools** (was 20) — added `session_snapshot`.
18
+ - **MCP instructions** updated with `session_snapshot` workflow and `max_tokens` guidance.
19
+ - Benchmark numbers updated: 55 files, 102K raw → 9K outline tokens (91% savings).
20
+
8
21
  ## [0.18.0] - 2026-04-05
9
22
 
10
23
  ### Added
package/README.md CHANGED
@@ -21,16 +21,28 @@ Measured on public open-source repos using the regex fallback parser (no ast-ind
21
21
 
22
22
  | Repo | Files | Raw Tokens | Outline Tokens | Savings |
23
23
  |------|------:|----------:|--------------:|--------:|
24
- | [token-pilot](https://github.com/Digital-Threads/token-pilot) (TS) | 48 | 88,920 | 8,123 | **91%** |
24
+ | [token-pilot](https://github.com/Digital-Threads/token-pilot) (TS) | 55 | 102,086 | 8,992 | **91%** |
25
25
  | [express](https://github.com/expressjs/express) (JS) | 6 | 14,421 | 193 | **99%** |
26
26
  | [fastify](https://github.com/fastify/fastify) (JS) | 23 | 50,000 | 3,161 | **94%** |
27
27
  | [flask](https://github.com/pallets/flask) (Python) | 20 | 78,236 | 7,418 | **91%** |
28
- | **Total** | **97** | **231,577** | **18,895** | **92%** |
28
+ | **Total** | **104** | **244,743** | **19,764** | **92%** |
29
29
 
30
30
  > This measures `smart_read` structural outline savings only. Real sessions also benefit from session cache, dedup reminders, `read_symbol` targeted loading, and `read_for_edit` minimal context.
31
31
  >
32
32
  > Run the benchmark yourself: `npx tsx scripts/benchmark.ts`
33
33
 
34
+ ## Why This Approach Works
35
+
36
+ The biggest source of token waste in AI coding sessions isn't verbose prompts — it's **redundant context**. Every time a model re-reads a file, re-sends conversation history, or loads code it doesn't need, you pay for tokens that add no value.
37
+
38
+ Token Pilot attacks this at three levels:
39
+
40
+ 1. **Symbol-first reading** — load outlines instead of full files, drill into specific functions on demand. This alone saves 60-90% on most reads.
41
+ 2. **Context budget control** — `max_tokens` parameter on `smart_read` auto-downgrades output (full → outline → compact) to fit within a token budget per step.
42
+ 3. **Session state management** — `session_snapshot` captures session state as a compact markdown block (<200 tokens), enabling clean context compaction without losing track of what you're doing.
43
+
44
+ These aren't theoretical gains. In real sessions, the combination of structural reading + targeted symbol access + session snapshots consistently reduces token usage by 80-90% compared to raw file reads.
45
+
34
46
  ## Installation
35
47
 
36
48
  ### Quick Start (recommended)
@@ -152,6 +164,7 @@ WHEN TO USE TOKEN PILOT (saves up to 80% tokens):
152
164
  • Reading code files → smart_read (returns structure, not raw content)
153
165
  • Need one function/class → read_symbol (loads only that symbol)
154
166
  • Exploring a directory → outline (all symbols in one call)
167
+ • Long session? → session_snapshot (capture state before compaction)
155
168
  ...
156
169
  WHEN TO USE DEFAULT TOOLS (Token Pilot adds no value):
157
170
  • Regex/pattern search → use Grep/ripgrep, NOT find_usages
@@ -168,13 +181,13 @@ For more control, you can add rules to your project:
168
181
  - **Cursor** → `.cursorrules` in project root
169
182
  - **Codex** → `AGENTS.md` in project root
170
183
 
171
- ## MCP Tools (19)
184
+ ## MCP Tools (21)
172
185
 
173
186
  ### Core Reading
174
187
 
175
188
  | Tool | Instead of | Description |
176
189
  |------|-----------|-------------|
177
- | `smart_read` | `Read` | AST structural overview: classes, functions, methods with signatures. Up to 90% savings on large files. Framework-aware: shows HTTP routes, column types, validation rules. |
190
+ | `smart_read` | `Read` | AST structural overview: classes, functions, methods with signatures. Up to 90% savings on large files. Framework-aware: shows HTTP routes, column types, validation rules. `max_tokens` param for budget-constrained sessions. |
178
191
  | `read_symbol` | `Read` + scroll | Load source of a specific symbol. Supports `Class.method`. `show` param: full/head/tail/outline. |
179
192
  | `read_symbols` | N x `read_symbol` | Batch read multiple symbols from one file in a single call (max 10). One round-trip instead of N. |
180
193
  | `read_for_edit` | `Read` before `Edit` | Minimal RAW code around a symbol — copy directly as `old_string` for Edit tool. Batch mode: pass `symbols` array for multiple edit contexts. |
@@ -198,10 +211,11 @@ For more control, you can add rules to your project:
198
211
  | `smart_log` | raw `git log` | Structured commit history with category detection (feat/fix/refactor/docs), file stats, author breakdown. Filters by path and ref. |
199
212
  | `test_summary` | raw test output | Run tests and get structured summary: total/passed/failed + failure details. Supports vitest, jest, pytest, phpunit, go, cargo, rspec, mocha. |
200
213
 
201
- ### Analytics
214
+ ### Session & Analytics
202
215
 
203
216
  | Tool | Description |
204
217
  |------|-------------|
218
+ | `session_snapshot` | Capture session state as a compact markdown block (<200 tokens): goal, confirmed facts, relevant files, blockers, next step. Call before compaction or when switching direction. |
205
219
  | `session_analytics` | Token savings report: total saved, per-tool breakdown, top files, per-intent breakdown, decision insights, policy advisories. |
206
220
 
207
221
  ## CLI Commands
@@ -59,6 +59,8 @@ export const DEFAULT_CONFIG = {
59
59
  maxFullFileReads: 10,
60
60
  warnOnLargeReads: true,
61
61
  largeReadThreshold: 2000,
62
+ compactionCallThreshold: 15,
63
+ compactionTokenThreshold: 8000,
62
64
  },
63
65
  ignore: [
64
66
  'node_modules/**',
@@ -16,6 +16,10 @@ export interface PolicyConfig {
16
16
  warnOnLargeReads: boolean;
17
17
  /** Token threshold for large read warning */
18
18
  largeReadThreshold: number;
19
+ /** Suggest compaction after N total tool calls (0 = disabled) */
20
+ compactionCallThreshold: number;
21
+ /** Suggest compaction after N total tokens returned (0 = disabled) */
22
+ compactionTokenThreshold: number;
19
23
  }
20
24
  export declare const DEFAULT_POLICIES: PolicyConfig;
21
25
  export interface PolicyCheckContext {
@@ -23,6 +27,8 @@ export interface PolicyCheckContext {
23
27
  tokensReturned: number;
24
28
  readForEditCalled?: Set<string>;
25
29
  editTargetPath?: string;
30
+ totalCallCount?: number;
31
+ totalTokensReturned?: number;
26
32
  }
27
33
  export interface PolicyAdvisory {
28
34
  level: 'info' | 'warn';
@@ -10,6 +10,8 @@ export const DEFAULT_POLICIES = {
10
10
  maxFullFileReads: 10,
11
11
  warnOnLargeReads: true,
12
12
  largeReadThreshold: 2000,
13
+ compactionCallThreshold: 15,
14
+ compactionTokenThreshold: 8000,
13
15
  };
14
16
  /** Full-file read tools that count toward maxFullFileReads */
15
17
  const FULL_READ_TOOLS = new Set([
@@ -64,6 +66,28 @@ export function checkPolicy(policy, tool, context) {
64
66
  message: `POLICY: Consider using read_for_edit("${context.editTargetPath}") before editing to get precise edit context.`,
65
67
  };
66
68
  }
69
+ // 5. Session compaction advisory — by call count
70
+ if (policy.compactionCallThreshold > 0 &&
71
+ context.totalCallCount !== undefined &&
72
+ context.totalCallCount > 0 &&
73
+ context.totalCallCount % policy.compactionCallThreshold === 0) {
74
+ return {
75
+ level: 'info',
76
+ message: `COMPACTION: ${context.totalCallCount} tool calls this session. Consider calling session_snapshot() to capture state, then compact context.`,
77
+ };
78
+ }
79
+ // 6. Session compaction advisory — by total tokens
80
+ if (policy.compactionTokenThreshold > 0 &&
81
+ context.totalTokensReturned !== undefined &&
82
+ context.totalTokensReturned > policy.compactionTokenThreshold &&
83
+ context.totalCallCount !== undefined &&
84
+ context.totalCallCount % 5 === 0 // don't spam every call, check every 5th
85
+ ) {
86
+ return {
87
+ level: 'info',
88
+ message: `COMPACTION: ~${context.totalTokensReturned} tokens returned this session. Consider calling session_snapshot() to capture state, then compact context.`,
89
+ };
90
+ }
67
91
  return null;
68
92
  }
69
93
  /**
@@ -12,6 +12,7 @@ export declare function validateSmartReadArgs(args: unknown): {
12
12
  show_docs?: boolean;
13
13
  show_references?: boolean;
14
14
  depth?: number;
15
+ max_tokens?: number;
15
16
  };
16
17
  /**
17
18
  * Validate read_symbol arguments.
@@ -67,6 +68,7 @@ export declare function validateFindUsagesArgs(args: unknown): FindUsagesArgs;
67
68
  */
68
69
  export declare function validateSmartReadManyArgs(args: unknown): {
69
70
  paths: string[];
71
+ max_tokens?: number;
70
72
  };
71
73
  /**
72
74
  * Validate read_for_edit arguments.
@@ -28,6 +28,7 @@ export function validateSmartReadArgs(args) {
28
28
  show_docs: optionalBool(a.show_docs, 'show_docs'),
29
29
  show_references: optionalBool(a.show_references, 'show_references'),
30
30
  depth: optionalNumber(a.depth, 'depth'),
31
+ max_tokens: optionalNumber(a.max_tokens, 'max_tokens'),
31
32
  };
32
33
  }
33
34
  /**
@@ -194,7 +195,7 @@ export function validateSmartReadManyArgs(args) {
194
195
  throw new Error('Each path in "paths" must be a non-empty string.');
195
196
  }
196
197
  }
197
- return { paths: a.paths };
198
+ return { paths: a.paths, max_tokens: optionalNumber(a.max_tokens, 'max_tokens') };
198
199
  }
199
200
  function optionalString(val, name) {
200
201
  if (val === undefined || val === null)
@@ -0,0 +1,14 @@
1
+ export interface SessionSnapshotArgs {
2
+ goal: string;
3
+ confirmed?: string[];
4
+ files?: string[];
5
+ blocked?: string;
6
+ next?: string;
7
+ }
8
+ export declare function handleSessionSnapshot(args: SessionSnapshotArgs): {
9
+ content: {
10
+ type: 'text';
11
+ text: string;
12
+ }[];
13
+ };
14
+ //# sourceMappingURL=session-snapshot.d.ts.map
@@ -0,0 +1,22 @@
1
+ export function handleSessionSnapshot(args) {
2
+ const lines = ['## Session State'];
3
+ lines.push(`**Goal:** ${args.goal}`);
4
+ if (args.confirmed?.length) {
5
+ lines.push('**Confirmed:**');
6
+ for (const item of args.confirmed) {
7
+ lines.push(`- ${item}`);
8
+ }
9
+ }
10
+ if (args.files?.length) {
11
+ lines.push(`**Files:** ${args.files.join(', ')}`);
12
+ }
13
+ if (args.blocked) {
14
+ lines.push(`**Blocked:** ${args.blocked}`);
15
+ }
16
+ if (args.next) {
17
+ lines.push(`**Next:** ${args.next}`);
18
+ }
19
+ const text = lines.join('\n');
20
+ return { content: [{ type: 'text', text }] };
21
+ }
22
+ //# sourceMappingURL=session-snapshot.js.map
@@ -4,6 +4,7 @@ import type { ContextRegistry } from '../core/context-registry.js';
4
4
  import type { TokenPilotConfig } from '../types.js';
5
5
  export interface SmartReadManyArgs {
6
6
  paths: string[];
7
+ max_tokens?: number;
7
8
  }
8
9
  export declare function handleSmartReadMany(args: SmartReadManyArgs, projectRoot: string, astIndex: AstIndexClient, fileCache: FileCache, contextRegistry: ContextRegistry, config: TokenPilotConfig): Promise<{
9
10
  content: Array<{
@@ -32,7 +32,7 @@ export async function handleSmartReadMany(args, projectRoot, astIndex, fileCache
32
32
  const fullTokens = await estimateFullFileTokens(projectRoot, path);
33
33
  return { path, text: reminderText + `\nFor full re-read: smart_read("${path}")`, fullTokens };
34
34
  }
35
- const result = await handleSmartRead({ path }, projectRoot, astIndex, fileCache, contextRegistry, config);
35
+ const result = await handleSmartRead({ path, max_tokens: args.max_tokens }, projectRoot, astIndex, fileCache, contextRegistry, config);
36
36
  const text = result.content[0]?.text ?? '';
37
37
  const fullTokens = await estimateFullFileTokens(projectRoot, path);
38
38
  return { path, text, fullTokens };
@@ -9,6 +9,7 @@ export interface SmartReadArgs {
9
9
  show_references?: boolean;
10
10
  depth?: number;
11
11
  scope?: 'full' | 'nav' | 'exports';
12
+ max_tokens?: number;
12
13
  }
13
14
  export declare function handleSmartRead(args: SmartReadArgs, projectRoot: string, astIndex: AstIndexClient, fileCache: FileCache, contextRegistry: ContextRegistry, config: TokenPilotConfig): Promise<{
14
15
  content: Array<{
@@ -28,31 +28,35 @@ export async function handleSmartRead(args, projectRoot, astIndex, fileCache, co
28
28
  if (lines.length <= config.smartRead.smallFileThreshold) {
29
29
  const hash = createHash('sha256').update(content).digest('hex');
30
30
  const tokens = estimateTokens(content);
31
- contextRegistry.trackLoad(absPath, {
32
- type: 'full',
33
- startLine: 1,
34
- endLine: lines.length,
35
- tokens,
36
- });
37
- contextRegistry.setContentHash(absPath, hash);
38
- // Cache for read_diff baseline (so read_diff works after external edits)
39
- if (!fileCache.get(absPath)) {
40
- const fileStat = await stat(absPath);
41
- fileCache.set(absPath, {
42
- structure: {
43
- path: absPath, language: 'unknown',
44
- meta: { lines: lines.length, bytes: content.length, lastModified: fileStat.mtimeMs, contentHash: hash },
45
- imports: [], exports: [], symbols: [],
46
- },
47
- content, lines, mtime: fileStat.mtimeMs, hash, lastAccess: Date.now(),
31
+ // Budget check: if full content exceeds max_tokens, skip pass-through and use outline path
32
+ if (!args.max_tokens || tokens <= args.max_tokens) {
33
+ contextRegistry.trackLoad(absPath, {
34
+ type: 'full',
35
+ startLine: 1,
36
+ endLine: lines.length,
37
+ tokens,
48
38
  });
39
+ contextRegistry.setContentHash(absPath, hash);
40
+ // Cache for read_diff baseline (so read_diff works after external edits)
41
+ if (!fileCache.get(absPath)) {
42
+ const fileStat = await stat(absPath);
43
+ fileCache.set(absPath, {
44
+ structure: {
45
+ path: absPath, language: 'unknown',
46
+ meta: { lines: lines.length, bytes: content.length, lastModified: fileStat.mtimeMs, contentHash: hash },
47
+ imports: [], exports: [], symbols: [],
48
+ },
49
+ content, lines, mtime: fileStat.mtimeMs, hash, lastAccess: Date.now(),
50
+ });
51
+ }
52
+ return {
53
+ content: [{
54
+ type: 'text',
55
+ text: `FILE: ${args.path} (${lines.length} lines — returned in full, below threshold)\n\n${content}`,
56
+ }],
57
+ };
49
58
  }
50
- return {
51
- content: [{
52
- type: 'text',
53
- text: `FILE: ${args.path} (${lines.length} lines — returned in full, below threshold)\n\n${content}`,
54
- }],
55
- };
59
+ // else: fall through to outline path even for small files
56
60
  }
57
61
  // 3. Check cache
58
62
  let cached = fileCache.get(absPath);
@@ -174,7 +178,7 @@ export async function handleSmartRead(args, projectRoot, astIndex, fileCache, co
174
178
  // 6b. Adaptive fallback: if outline is not significantly smaller than raw, return raw
175
179
  const structureTokens = estimateTokens(output);
176
180
  const fullTokens = estimateTokens(content);
177
- if (structureTokens >= fullTokens * 0.7) {
181
+ if (structureTokens >= fullTokens * 0.7 && (!args.max_tokens || fullTokens <= args.max_tokens)) {
178
182
  contextRegistry.trackLoad(absPath, {
179
183
  type: 'full',
180
184
  startLine: 1,
@@ -192,11 +196,30 @@ export async function handleSmartRead(args, projectRoot, astIndex, fileCache, co
192
196
  }],
193
197
  };
194
198
  }
195
- // 7. Add token savings
199
+ // 7. Budget enforcement: if outline exceeds max_tokens, return compact version
200
+ if (args.max_tokens && structureTokens > args.max_tokens) {
201
+ const symbols = cached.structure.symbols;
202
+ const compactLines = [
203
+ `FILE: ${args.path} (${lines.length} lines — compact, budget: ${args.max_tokens} tokens)`,
204
+ `Imports: ${cached.structure.imports?.length ?? 0} | Exports: ${cached.structure.exports?.length ?? 0} | Symbols: ${symbols.length}`,
205
+ '',
206
+ ];
207
+ for (const sym of symbols) {
208
+ compactLines.push(` ${sym.kind} ${sym.name} [L${sym.location.startLine}-${sym.location.endLine}]`);
209
+ }
210
+ compactLines.push('', `Use read_symbol("${args.path}", "<name>") to drill into any symbol.`);
211
+ const compactText = compactLines.join('\n');
212
+ const compactTokens = estimateTokens(compactText);
213
+ contextRegistry.trackLoad(absPath, { type: 'structure', startLine: 1, endLine: cached.structure.meta.lines, tokens: compactTokens });
214
+ contextRegistry.setContentHash(absPath, cached.hash);
215
+ contextRegistry.trackStructureSymbols(absPath, symbols.map(s => s.name));
216
+ return { content: [{ type: 'text', text: compactText }] };
217
+ }
218
+ // 8. Add token savings
196
219
  const savings = config.display.showTokenSavings
197
220
  ? '\n' + formatSavings(structureTokens, fullTokens)
198
221
  : '';
199
- // 8. Track
222
+ // 9. Track
200
223
  contextRegistry.trackLoad(absPath, {
201
224
  type: 'structure',
202
225
  startLine: 1,
@@ -205,7 +228,7 @@ export async function handleSmartRead(args, projectRoot, astIndex, fileCache, co
205
228
  });
206
229
  contextRegistry.setContentHash(absPath, cached.hash);
207
230
  contextRegistry.trackStructureSymbols(absPath, cached.structure.symbols.map(s => s.name));
208
- // 9. Confidence metadata
231
+ // 10. Confidence metadata
209
232
  const confidenceMeta = assessConfidence({
210
233
  symbolResolved: (cached.structure.symbols?.length ?? 0) > 0,
211
234
  fullFile: false,
@@ -30,6 +30,10 @@ export declare const TOOL_DEFINITIONS: ({
30
30
  enum: string[];
31
31
  description: string;
32
32
  };
33
+ max_tokens: {
34
+ type: string;
35
+ description: string;
36
+ };
33
37
  symbol?: undefined;
34
38
  context_before?: undefined;
35
39
  context_after?: undefined;
@@ -65,6 +69,11 @@ export declare const TOOL_DEFINITIONS: ({
65
69
  command?: undefined;
66
70
  runner?: undefined;
67
71
  timeout?: undefined;
72
+ goal?: undefined;
73
+ confirmed?: undefined;
74
+ files?: undefined;
75
+ blocked?: undefined;
76
+ next?: undefined;
68
77
  };
69
78
  required: string[];
70
79
  };
@@ -103,6 +112,7 @@ export declare const TOOL_DEFINITIONS: ({
103
112
  show_docs?: undefined;
104
113
  depth?: undefined;
105
114
  scope?: undefined;
115
+ max_tokens?: undefined;
106
116
  symbols?: undefined;
107
117
  start_line?: undefined;
108
118
  end_line?: undefined;
@@ -133,6 +143,11 @@ export declare const TOOL_DEFINITIONS: ({
133
143
  command?: undefined;
134
144
  runner?: undefined;
135
145
  timeout?: undefined;
146
+ goal?: undefined;
147
+ confirmed?: undefined;
148
+ files?: undefined;
149
+ blocked?: undefined;
150
+ next?: undefined;
136
151
  };
137
152
  required: string[];
138
153
  };
@@ -170,6 +185,7 @@ export declare const TOOL_DEFINITIONS: ({
170
185
  show_docs?: undefined;
171
186
  depth?: undefined;
172
187
  scope?: undefined;
188
+ max_tokens?: undefined;
173
189
  symbol?: undefined;
174
190
  include_edit_context?: undefined;
175
191
  start_line?: undefined;
@@ -201,6 +217,11 @@ export declare const TOOL_DEFINITIONS: ({
201
217
  command?: undefined;
202
218
  runner?: undefined;
203
219
  timeout?: undefined;
220
+ goal?: undefined;
221
+ confirmed?: undefined;
222
+ files?: undefined;
223
+ blocked?: undefined;
224
+ next?: undefined;
204
225
  };
205
226
  required: string[];
206
227
  };
@@ -226,6 +247,7 @@ export declare const TOOL_DEFINITIONS: ({
226
247
  show_docs?: undefined;
227
248
  depth?: undefined;
228
249
  scope?: undefined;
250
+ max_tokens?: undefined;
229
251
  symbol?: undefined;
230
252
  context_before?: undefined;
231
253
  context_after?: undefined;
@@ -259,6 +281,11 @@ export declare const TOOL_DEFINITIONS: ({
259
281
  command?: undefined;
260
282
  runner?: undefined;
261
283
  timeout?: undefined;
284
+ goal?: undefined;
285
+ confirmed?: undefined;
286
+ files?: undefined;
287
+ blocked?: undefined;
288
+ next?: undefined;
262
289
  };
263
290
  required: string[];
264
291
  };
@@ -280,6 +307,7 @@ export declare const TOOL_DEFINITIONS: ({
280
307
  show_docs?: undefined;
281
308
  depth?: undefined;
282
309
  scope?: undefined;
310
+ max_tokens?: undefined;
283
311
  symbol?: undefined;
284
312
  context_before?: undefined;
285
313
  context_after?: undefined;
@@ -314,6 +342,11 @@ export declare const TOOL_DEFINITIONS: ({
314
342
  command?: undefined;
315
343
  runner?: undefined;
316
344
  timeout?: undefined;
345
+ goal?: undefined;
346
+ confirmed?: undefined;
347
+ files?: undefined;
348
+ blocked?: undefined;
349
+ next?: undefined;
317
350
  };
318
351
  required: string[];
319
352
  };
@@ -335,6 +368,7 @@ export declare const TOOL_DEFINITIONS: ({
335
368
  show_docs?: undefined;
336
369
  depth?: undefined;
337
370
  scope?: undefined;
371
+ max_tokens?: undefined;
338
372
  symbol?: undefined;
339
373
  context_before?: undefined;
340
374
  context_after?: undefined;
@@ -369,6 +403,11 @@ export declare const TOOL_DEFINITIONS: ({
369
403
  command?: undefined;
370
404
  runner?: undefined;
371
405
  timeout?: undefined;
406
+ goal?: undefined;
407
+ confirmed?: undefined;
408
+ files?: undefined;
409
+ blocked?: undefined;
410
+ next?: undefined;
372
411
  };
373
412
  required: string[];
374
413
  };
@@ -421,6 +460,7 @@ export declare const TOOL_DEFINITIONS: ({
421
460
  show_docs?: undefined;
422
461
  depth?: undefined;
423
462
  scope?: undefined;
463
+ max_tokens?: undefined;
424
464
  context_before?: undefined;
425
465
  context_after?: undefined;
426
466
  show?: undefined;
@@ -448,6 +488,11 @@ export declare const TOOL_DEFINITIONS: ({
448
488
  command?: undefined;
449
489
  runner?: undefined;
450
490
  timeout?: undefined;
491
+ goal?: undefined;
492
+ confirmed?: undefined;
493
+ files?: undefined;
494
+ blocked?: undefined;
495
+ next?: undefined;
451
496
  };
452
497
  required: string[];
453
498
  };
@@ -464,6 +509,10 @@ export declare const TOOL_DEFINITIONS: ({
464
509
  };
465
510
  description: string;
466
511
  };
512
+ max_tokens: {
513
+ type: string;
514
+ description: string;
515
+ };
467
516
  path?: undefined;
468
517
  show_imports?: undefined;
469
518
  show_docs?: undefined;
@@ -503,6 +552,11 @@ export declare const TOOL_DEFINITIONS: ({
503
552
  command?: undefined;
504
553
  runner?: undefined;
505
554
  timeout?: undefined;
555
+ goal?: undefined;
556
+ confirmed?: undefined;
557
+ files?: undefined;
558
+ blocked?: undefined;
559
+ next?: undefined;
506
560
  };
507
561
  required: string[];
508
562
  };
@@ -547,6 +601,7 @@ export declare const TOOL_DEFINITIONS: ({
547
601
  show_imports?: undefined;
548
602
  show_docs?: undefined;
549
603
  depth?: undefined;
604
+ max_tokens?: undefined;
550
605
  context_before?: undefined;
551
606
  context_after?: undefined;
552
607
  show?: undefined;
@@ -576,6 +631,11 @@ export declare const TOOL_DEFINITIONS: ({
576
631
  command?: undefined;
577
632
  runner?: undefined;
578
633
  timeout?: undefined;
634
+ goal?: undefined;
635
+ confirmed?: undefined;
636
+ files?: undefined;
637
+ blocked?: undefined;
638
+ next?: undefined;
579
639
  };
580
640
  required: string[];
581
641
  };
@@ -598,6 +658,7 @@ export declare const TOOL_DEFINITIONS: ({
598
658
  show_docs?: undefined;
599
659
  depth?: undefined;
600
660
  scope?: undefined;
661
+ max_tokens?: undefined;
601
662
  symbol?: undefined;
602
663
  context_before?: undefined;
603
664
  context_after?: undefined;
@@ -632,6 +693,11 @@ export declare const TOOL_DEFINITIONS: ({
632
693
  command?: undefined;
633
694
  runner?: undefined;
634
695
  timeout?: undefined;
696
+ goal?: undefined;
697
+ confirmed?: undefined;
698
+ files?: undefined;
699
+ blocked?: undefined;
700
+ next?: undefined;
635
701
  };
636
702
  required?: undefined;
637
703
  };
@@ -649,6 +715,7 @@ export declare const TOOL_DEFINITIONS: ({
649
715
  show_docs?: undefined;
650
716
  depth?: undefined;
651
717
  scope?: undefined;
718
+ max_tokens?: undefined;
652
719
  symbol?: undefined;
653
720
  context_before?: undefined;
654
721
  context_after?: undefined;
@@ -684,6 +751,11 @@ export declare const TOOL_DEFINITIONS: ({
684
751
  command?: undefined;
685
752
  runner?: undefined;
686
753
  timeout?: undefined;
754
+ goal?: undefined;
755
+ confirmed?: undefined;
756
+ files?: undefined;
757
+ blocked?: undefined;
758
+ next?: undefined;
687
759
  };
688
760
  required: string[];
689
761
  };
@@ -709,6 +781,7 @@ export declare const TOOL_DEFINITIONS: ({
709
781
  show_docs?: undefined;
710
782
  depth?: undefined;
711
783
  scope?: undefined;
784
+ max_tokens?: undefined;
712
785
  symbol?: undefined;
713
786
  context_before?: undefined;
714
787
  context_after?: undefined;
@@ -742,6 +815,11 @@ export declare const TOOL_DEFINITIONS: ({
742
815
  command?: undefined;
743
816
  runner?: undefined;
744
817
  timeout?: undefined;
818
+ goal?: undefined;
819
+ confirmed?: undefined;
820
+ files?: undefined;
821
+ blocked?: undefined;
822
+ next?: undefined;
745
823
  };
746
824
  required: string[];
747
825
  };
@@ -760,6 +838,7 @@ export declare const TOOL_DEFINITIONS: ({
760
838
  show_docs?: undefined;
761
839
  depth?: undefined;
762
840
  scope?: undefined;
841
+ max_tokens?: undefined;
763
842
  symbol?: undefined;
764
843
  context_before?: undefined;
765
844
  context_after?: undefined;
@@ -794,6 +873,11 @@ export declare const TOOL_DEFINITIONS: ({
794
873
  command?: undefined;
795
874
  runner?: undefined;
796
875
  timeout?: undefined;
876
+ goal?: undefined;
877
+ confirmed?: undefined;
878
+ files?: undefined;
879
+ blocked?: undefined;
880
+ next?: undefined;
797
881
  };
798
882
  required?: undefined;
799
883
  };
@@ -820,6 +904,7 @@ export declare const TOOL_DEFINITIONS: ({
820
904
  show_docs?: undefined;
821
905
  depth?: undefined;
822
906
  scope?: undefined;
907
+ max_tokens?: undefined;
823
908
  symbol?: undefined;
824
909
  context_before?: undefined;
825
910
  context_after?: undefined;
@@ -852,6 +937,11 @@ export declare const TOOL_DEFINITIONS: ({
852
937
  command?: undefined;
853
938
  runner?: undefined;
854
939
  timeout?: undefined;
940
+ goal?: undefined;
941
+ confirmed?: undefined;
942
+ files?: undefined;
943
+ blocked?: undefined;
944
+ next?: undefined;
855
945
  };
856
946
  required?: undefined;
857
947
  };
@@ -887,6 +977,7 @@ export declare const TOOL_DEFINITIONS: ({
887
977
  show_docs?: undefined;
888
978
  depth?: undefined;
889
979
  scope?: undefined;
980
+ max_tokens?: undefined;
890
981
  symbol?: undefined;
891
982
  context_before?: undefined;
892
983
  context_after?: undefined;
@@ -917,6 +1008,11 @@ export declare const TOOL_DEFINITIONS: ({
917
1008
  command?: undefined;
918
1009
  runner?: undefined;
919
1010
  timeout?: undefined;
1011
+ goal?: undefined;
1012
+ confirmed?: undefined;
1013
+ files?: undefined;
1014
+ blocked?: undefined;
1015
+ next?: undefined;
920
1016
  };
921
1017
  required: string[];
922
1018
  };
@@ -940,6 +1036,7 @@ export declare const TOOL_DEFINITIONS: ({
940
1036
  show_docs?: undefined;
941
1037
  depth?: undefined;
942
1038
  scope?: undefined;
1039
+ max_tokens?: undefined;
943
1040
  symbol?: undefined;
944
1041
  context_before?: undefined;
945
1042
  context_after?: undefined;
@@ -973,6 +1070,11 @@ export declare const TOOL_DEFINITIONS: ({
973
1070
  command?: undefined;
974
1071
  runner?: undefined;
975
1072
  timeout?: undefined;
1073
+ goal?: undefined;
1074
+ confirmed?: undefined;
1075
+ files?: undefined;
1076
+ blocked?: undefined;
1077
+ next?: undefined;
976
1078
  };
977
1079
  required: string[];
978
1080
  };
@@ -998,6 +1100,7 @@ export declare const TOOL_DEFINITIONS: ({
998
1100
  show_imports?: undefined;
999
1101
  show_docs?: undefined;
1000
1102
  depth?: undefined;
1103
+ max_tokens?: undefined;
1001
1104
  symbol?: undefined;
1002
1105
  context_before?: undefined;
1003
1106
  context_after?: undefined;
@@ -1032,6 +1135,11 @@ export declare const TOOL_DEFINITIONS: ({
1032
1135
  command?: undefined;
1033
1136
  runner?: undefined;
1034
1137
  timeout?: undefined;
1138
+ goal?: undefined;
1139
+ confirmed?: undefined;
1140
+ files?: undefined;
1141
+ blocked?: undefined;
1142
+ next?: undefined;
1035
1143
  };
1036
1144
  required?: undefined;
1037
1145
  };
@@ -1057,6 +1165,7 @@ export declare const TOOL_DEFINITIONS: ({
1057
1165
  show_docs?: undefined;
1058
1166
  depth?: undefined;
1059
1167
  scope?: undefined;
1168
+ max_tokens?: undefined;
1060
1169
  symbol?: undefined;
1061
1170
  context_before?: undefined;
1062
1171
  context_after?: undefined;
@@ -1091,6 +1200,11 @@ export declare const TOOL_DEFINITIONS: ({
1091
1200
  command?: undefined;
1092
1201
  runner?: undefined;
1093
1202
  timeout?: undefined;
1203
+ goal?: undefined;
1204
+ confirmed?: undefined;
1205
+ files?: undefined;
1206
+ blocked?: undefined;
1207
+ next?: undefined;
1094
1208
  };
1095
1209
  required: string[];
1096
1210
  };
@@ -1116,6 +1230,7 @@ export declare const TOOL_DEFINITIONS: ({
1116
1230
  show_docs?: undefined;
1117
1231
  depth?: undefined;
1118
1232
  scope?: undefined;
1233
+ max_tokens?: undefined;
1119
1234
  symbol?: undefined;
1120
1235
  context_before?: undefined;
1121
1236
  context_after?: undefined;
@@ -1149,6 +1264,11 @@ export declare const TOOL_DEFINITIONS: ({
1149
1264
  command?: undefined;
1150
1265
  runner?: undefined;
1151
1266
  timeout?: undefined;
1267
+ goal?: undefined;
1268
+ confirmed?: undefined;
1269
+ files?: undefined;
1270
+ blocked?: undefined;
1271
+ next?: undefined;
1152
1272
  };
1153
1273
  required?: undefined;
1154
1274
  };
@@ -1176,6 +1296,85 @@ export declare const TOOL_DEFINITIONS: ({
1176
1296
  show_docs?: undefined;
1177
1297
  depth?: undefined;
1178
1298
  scope?: undefined;
1299
+ max_tokens?: undefined;
1300
+ symbol?: undefined;
1301
+ context_before?: undefined;
1302
+ context_after?: undefined;
1303
+ show?: undefined;
1304
+ include_edit_context?: undefined;
1305
+ symbols?: undefined;
1306
+ start_line?: undefined;
1307
+ end_line?: undefined;
1308
+ heading?: undefined;
1309
+ context_lines?: undefined;
1310
+ line?: undefined;
1311
+ context?: undefined;
1312
+ include_callers?: undefined;
1313
+ include_tests?: undefined;
1314
+ include_changes?: undefined;
1315
+ section?: undefined;
1316
+ paths?: undefined;
1317
+ kind?: undefined;
1318
+ limit?: undefined;
1319
+ lang?: undefined;
1320
+ mode?: undefined;
1321
+ include?: undefined;
1322
+ recursive?: undefined;
1323
+ max_depth?: undefined;
1324
+ verbose?: undefined;
1325
+ module?: undefined;
1326
+ export_only?: undefined;
1327
+ check?: undefined;
1328
+ pattern?: undefined;
1329
+ name?: undefined;
1330
+ ref?: undefined;
1331
+ count?: undefined;
1332
+ goal?: undefined;
1333
+ confirmed?: undefined;
1334
+ files?: undefined;
1335
+ blocked?: undefined;
1336
+ next?: undefined;
1337
+ };
1338
+ required: string[];
1339
+ };
1340
+ } | {
1341
+ name: string;
1342
+ description: string;
1343
+ inputSchema: {
1344
+ type: "object";
1345
+ properties: {
1346
+ goal: {
1347
+ type: string;
1348
+ description: string;
1349
+ };
1350
+ confirmed: {
1351
+ type: string;
1352
+ items: {
1353
+ type: string;
1354
+ };
1355
+ description: string;
1356
+ };
1357
+ files: {
1358
+ type: string;
1359
+ items: {
1360
+ type: string;
1361
+ };
1362
+ description: string;
1363
+ };
1364
+ blocked: {
1365
+ type: string;
1366
+ description: string;
1367
+ };
1368
+ next: {
1369
+ type: string;
1370
+ description: string;
1371
+ };
1372
+ path?: undefined;
1373
+ show_imports?: undefined;
1374
+ show_docs?: undefined;
1375
+ depth?: undefined;
1376
+ scope?: undefined;
1377
+ max_tokens?: undefined;
1179
1378
  symbol?: undefined;
1180
1379
  context_before?: undefined;
1181
1380
  context_after?: undefined;
@@ -1208,6 +1407,9 @@ export declare const TOOL_DEFINITIONS: ({
1208
1407
  name?: undefined;
1209
1408
  ref?: undefined;
1210
1409
  count?: undefined;
1410
+ command?: undefined;
1411
+ runner?: undefined;
1412
+ timeout?: undefined;
1211
1413
  };
1212
1414
  required: string[];
1213
1415
  };
@@ -29,6 +29,8 @@ export const MCP_INSTRUCTIONS = [
29
29
  '17. Module architecture → module_info (deps, dependents, public API)',
30
30
  '18. Read markdown/yaml/json/csv section → read_section (loads one heading/key/row-range, NOT the whole file)',
31
31
  ' - For editing sections: read_for_edit(path, section="Section Name")',
32
+ '19. Long session / before compaction → session_snapshot (capture goal, confirmed facts, files, next step as <200 token block)',
33
+ ' - Budget-constrained? Use smart_read(max_tokens=N) to auto-downgrade output size',
32
34
  '',
33
35
  'USE DEFAULT TOOLS ONLY FOR: regex text search → Grep | exact raw content → Read | non-code configs → Read',
34
36
  '',
@@ -38,6 +40,7 @@ export const MCP_INSTRUCTIONS = [
38
40
  '• Docs: smart_read (outline) → read_section → read_for_edit(section=) → Edit → read_diff',
39
41
  '• Refactor: find_usages → read_symbols → read_for_edit → Edit → test_summary',
40
42
  '• Audit: code_audit + find_unused + Grep (for regex patterns)',
43
+ '• Long session: session_snapshot → compact context → continue with minimal state',
41
44
  ].join('\n');
42
45
  export const TOOL_DEFINITIONS = [
43
46
  // --- Core reading tools ---
@@ -56,6 +59,7 @@ export const TOOL_DEFINITIONS = [
56
59
  enum: ['full', 'nav', 'exports'],
57
60
  description: 'Output scope: full (default, all details), nav (names + lines only, 2-3x smaller), exports (public API only)',
58
61
  },
62
+ max_tokens: { type: 'number', description: 'Token budget. If output exceeds this, auto-downgrades: full → outline → compact. Use for context-constrained sessions.' },
59
63
  },
60
64
  required: ['path'],
61
65
  },
@@ -172,6 +176,7 @@ export const TOOL_DEFINITIONS = [
172
176
  items: { type: 'string' },
173
177
  description: 'Array of file paths',
174
178
  },
179
+ max_tokens: { type: 'number', description: 'Token budget per file. If a file exceeds this, auto-downgrades to compact outline.' },
175
180
  },
176
181
  required: ['paths'],
177
182
  },
@@ -349,5 +354,21 @@ export const TOOL_DEFINITIONS = [
349
354
  required: ['command'],
350
355
  },
351
356
  },
357
+ // --- Session ---
358
+ {
359
+ name: 'session_snapshot',
360
+ description: 'Capture current session state as a compact markdown block (<200 tokens). Call before compaction, when switching direction, or periodically in long sessions. Model provides the facts, tool formats them.',
361
+ inputSchema: {
362
+ type: 'object',
363
+ properties: {
364
+ goal: { type: 'string', description: 'Session goal — what and why' },
365
+ confirmed: { type: 'array', items: { type: 'string' }, description: 'Established facts (what has been verified)' },
366
+ files: { type: 'array', items: { type: 'string' }, description: 'Relevant file paths' },
367
+ blocked: { type: 'string', description: 'Current blocker or obstacle' },
368
+ next: { type: 'string', description: 'Next step to take' },
369
+ },
370
+ required: ['goal'],
371
+ },
372
+ },
352
373
  ];
353
374
  //# sourceMappingURL=tool-definitions.js.map
package/dist/server.js CHANGED
@@ -36,6 +36,7 @@ import { handleSmartDiff } from './handlers/smart-diff.js';
36
36
  import { handleExploreArea } from './handlers/explore-area.js';
37
37
  import { handleSmartLog } from './handlers/smart-log.js';
38
38
  import { handleTestSummary } from './handlers/test-summary.js';
39
+ import { handleSessionSnapshot } from './handlers/session-snapshot.js';
39
40
  import { handleReadSection } from './handlers/read-section.js';
40
41
  import { detectContextMode } from './integration/context-mode-detector.js';
41
42
  import { estimateTokens } from './core/token-estimator.js';
@@ -154,6 +155,8 @@ export async function createServer(projectRoot, options) {
154
155
  : null;
155
156
  // Policy engine state
156
157
  let fullFileReadsCount = 0;
158
+ let totalCallCount = 0;
159
+ let totalTokensReturned = 0;
157
160
  const readForEditCalled = new Set();
158
161
  // Detect context-mode companion
159
162
  const cmEnabled = config.contextMode.enabled;
@@ -224,6 +227,8 @@ export async function createServer(projectRoot, options) {
224
227
  }),
225
228
  });
226
229
  // Policy tracking
230
+ totalCallCount++;
231
+ totalTokensReturned += rest.tokensReturned;
227
232
  if (isFullReadTool(rest.tool)) {
228
233
  fullFileReadsCount++;
229
234
  }
@@ -235,6 +240,8 @@ export async function createServer(projectRoot, options) {
235
240
  fullFileReadsCount,
236
241
  tokensReturned: rest.tokensReturned,
237
242
  readForEditCalled,
243
+ totalCallCount,
244
+ totalTokensReturned,
238
245
  });
239
246
  return advisory ? `\n${advisory.message}` : null;
240
247
  }
@@ -575,6 +582,17 @@ export async function createServer(projectRoot, options) {
575
582
  recordWithTrace({ tool: 'test_summary', path: tsArgs.command, tokensReturned: tsTokens, tokensWouldBe: tsResult.rawTokens || tsTokens, timestamp: Date.now(), savingsCategory: 'compression', args: tsArgs });
576
583
  return { content: tsResult.content };
577
584
  }
585
+ case 'session_snapshot': {
586
+ const snapshotArgs = args;
587
+ if (!snapshotArgs.goal) {
588
+ return { content: [{ type: 'text', text: 'Error: goal is required' }], isError: true };
589
+ }
590
+ const snapshotResult = handleSessionSnapshot(snapshotArgs);
591
+ const snapshotText = snapshotResult.content[0]?.text ?? '';
592
+ const snapshotTokens = estimateTokens(snapshotText);
593
+ recordWithTrace({ tool: 'session_snapshot', tokensReturned: snapshotTokens, tokensWouldBe: snapshotTokens, timestamp: Date.now(), savingsCategory: 'compression' });
594
+ return { content: snapshotResult.content };
595
+ }
578
596
  default:
579
597
  return {
580
598
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
package/dist/types.d.ts CHANGED
@@ -137,6 +137,8 @@ export interface TokenPilotConfig {
137
137
  maxFullFileReads: number;
138
138
  warnOnLargeReads: boolean;
139
139
  largeReadThreshold: number;
140
+ compactionCallThreshold: number;
141
+ compactionTokenThreshold: number;
140
142
  };
141
143
  ignore: string[];
142
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
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",