token-pilot 0.18.0 → 0.19.1

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,24 @@ 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.1] - 2026-04-15
9
+
10
+ ### Added
11
+ - **`decisions` field in `session_snapshot`** — stores key decisions with reasoning (e.g., "removed sysfee step — caused double counting"). Prevents the model from revisiting rejected approaches after context compaction.
12
+
13
+ ## [0.19.0] - 2026-04-15
14
+
15
+ ### Added
16
+ - **`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.
17
+ - **`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.
18
+ - **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`.
19
+ - **"Why This Approach Works"** section in README explaining the 3-level optimization strategy.
20
+
21
+ ### Changed
22
+ - **21 tools** (was 20) — added `session_snapshot`.
23
+ - **MCP instructions** updated with `session_snapshot` workflow and `max_tokens` guidance.
24
+ - Benchmark numbers updated: 55 files, 102K raw → 9K outline tokens (91% savings).
25
+
8
26
  ## [0.18.0] - 2026-04-05
9
27
 
10
28
  ### 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, decisions (with reasoning), confirmed facts, relevant files, blockers, next step. Decisions field prevents revisiting rejected approaches after compaction. |
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,15 @@
1
+ export interface SessionSnapshotArgs {
2
+ goal: string;
3
+ decisions?: string[];
4
+ confirmed?: string[];
5
+ files?: string[];
6
+ blocked?: string;
7
+ next?: string;
8
+ }
9
+ export declare function handleSessionSnapshot(args: SessionSnapshotArgs): {
10
+ content: {
11
+ type: 'text';
12
+ text: string;
13
+ }[];
14
+ };
15
+ //# sourceMappingURL=session-snapshot.d.ts.map
@@ -0,0 +1,28 @@
1
+ export function handleSessionSnapshot(args) {
2
+ const lines = ['## Session State'];
3
+ lines.push(`**Goal:** ${args.goal}`);
4
+ if (args.decisions?.length) {
5
+ lines.push('**Decisions:**');
6
+ for (const item of args.decisions) {
7
+ lines.push(`- ${item}`);
8
+ }
9
+ }
10
+ if (args.confirmed?.length) {
11
+ lines.push('**Confirmed:**');
12
+ for (const item of args.confirmed) {
13
+ lines.push(`- ${item}`);
14
+ }
15
+ }
16
+ if (args.files?.length) {
17
+ lines.push(`**Files:** ${args.files.join(', ')}`);
18
+ }
19
+ if (args.blocked) {
20
+ lines.push(`**Blocked:** ${args.blocked}`);
21
+ }
22
+ if (args.next) {
23
+ lines.push(`**Next:** ${args.next}`);
24
+ }
25
+ const text = lines.join('\n');
26
+ return { content: [{ type: 'text', text }] };
27
+ }
28
+ //# 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,12 @@ export declare const TOOL_DEFINITIONS: ({
65
69
  command?: undefined;
66
70
  runner?: undefined;
67
71
  timeout?: undefined;
72
+ goal?: undefined;
73
+ decisions?: undefined;
74
+ confirmed?: undefined;
75
+ files?: undefined;
76
+ blocked?: undefined;
77
+ next?: undefined;
68
78
  };
69
79
  required: string[];
70
80
  };
@@ -103,6 +113,7 @@ export declare const TOOL_DEFINITIONS: ({
103
113
  show_docs?: undefined;
104
114
  depth?: undefined;
105
115
  scope?: undefined;
116
+ max_tokens?: undefined;
106
117
  symbols?: undefined;
107
118
  start_line?: undefined;
108
119
  end_line?: undefined;
@@ -133,6 +144,12 @@ export declare const TOOL_DEFINITIONS: ({
133
144
  command?: undefined;
134
145
  runner?: undefined;
135
146
  timeout?: undefined;
147
+ goal?: undefined;
148
+ decisions?: undefined;
149
+ confirmed?: undefined;
150
+ files?: undefined;
151
+ blocked?: undefined;
152
+ next?: undefined;
136
153
  };
137
154
  required: string[];
138
155
  };
@@ -170,6 +187,7 @@ export declare const TOOL_DEFINITIONS: ({
170
187
  show_docs?: undefined;
171
188
  depth?: undefined;
172
189
  scope?: undefined;
190
+ max_tokens?: undefined;
173
191
  symbol?: undefined;
174
192
  include_edit_context?: undefined;
175
193
  start_line?: undefined;
@@ -201,6 +219,12 @@ export declare const TOOL_DEFINITIONS: ({
201
219
  command?: undefined;
202
220
  runner?: undefined;
203
221
  timeout?: undefined;
222
+ goal?: undefined;
223
+ decisions?: undefined;
224
+ confirmed?: undefined;
225
+ files?: undefined;
226
+ blocked?: undefined;
227
+ next?: undefined;
204
228
  };
205
229
  required: string[];
206
230
  };
@@ -226,6 +250,7 @@ export declare const TOOL_DEFINITIONS: ({
226
250
  show_docs?: undefined;
227
251
  depth?: undefined;
228
252
  scope?: undefined;
253
+ max_tokens?: undefined;
229
254
  symbol?: undefined;
230
255
  context_before?: undefined;
231
256
  context_after?: undefined;
@@ -259,6 +284,12 @@ export declare const TOOL_DEFINITIONS: ({
259
284
  command?: undefined;
260
285
  runner?: undefined;
261
286
  timeout?: undefined;
287
+ goal?: undefined;
288
+ decisions?: undefined;
289
+ confirmed?: undefined;
290
+ files?: undefined;
291
+ blocked?: undefined;
292
+ next?: undefined;
262
293
  };
263
294
  required: string[];
264
295
  };
@@ -280,6 +311,7 @@ export declare const TOOL_DEFINITIONS: ({
280
311
  show_docs?: undefined;
281
312
  depth?: undefined;
282
313
  scope?: undefined;
314
+ max_tokens?: undefined;
283
315
  symbol?: undefined;
284
316
  context_before?: undefined;
285
317
  context_after?: undefined;
@@ -314,6 +346,12 @@ export declare const TOOL_DEFINITIONS: ({
314
346
  command?: undefined;
315
347
  runner?: undefined;
316
348
  timeout?: undefined;
349
+ goal?: undefined;
350
+ decisions?: undefined;
351
+ confirmed?: undefined;
352
+ files?: undefined;
353
+ blocked?: undefined;
354
+ next?: undefined;
317
355
  };
318
356
  required: string[];
319
357
  };
@@ -335,6 +373,7 @@ export declare const TOOL_DEFINITIONS: ({
335
373
  show_docs?: undefined;
336
374
  depth?: undefined;
337
375
  scope?: undefined;
376
+ max_tokens?: undefined;
338
377
  symbol?: undefined;
339
378
  context_before?: undefined;
340
379
  context_after?: undefined;
@@ -369,6 +408,12 @@ export declare const TOOL_DEFINITIONS: ({
369
408
  command?: undefined;
370
409
  runner?: undefined;
371
410
  timeout?: undefined;
411
+ goal?: undefined;
412
+ decisions?: undefined;
413
+ confirmed?: undefined;
414
+ files?: undefined;
415
+ blocked?: undefined;
416
+ next?: undefined;
372
417
  };
373
418
  required: string[];
374
419
  };
@@ -421,6 +466,7 @@ export declare const TOOL_DEFINITIONS: ({
421
466
  show_docs?: undefined;
422
467
  depth?: undefined;
423
468
  scope?: undefined;
469
+ max_tokens?: undefined;
424
470
  context_before?: undefined;
425
471
  context_after?: undefined;
426
472
  show?: undefined;
@@ -448,6 +494,12 @@ export declare const TOOL_DEFINITIONS: ({
448
494
  command?: undefined;
449
495
  runner?: undefined;
450
496
  timeout?: undefined;
497
+ goal?: undefined;
498
+ decisions?: undefined;
499
+ confirmed?: undefined;
500
+ files?: undefined;
501
+ blocked?: undefined;
502
+ next?: undefined;
451
503
  };
452
504
  required: string[];
453
505
  };
@@ -464,6 +516,10 @@ export declare const TOOL_DEFINITIONS: ({
464
516
  };
465
517
  description: string;
466
518
  };
519
+ max_tokens: {
520
+ type: string;
521
+ description: string;
522
+ };
467
523
  path?: undefined;
468
524
  show_imports?: undefined;
469
525
  show_docs?: undefined;
@@ -503,6 +559,12 @@ export declare const TOOL_DEFINITIONS: ({
503
559
  command?: undefined;
504
560
  runner?: undefined;
505
561
  timeout?: undefined;
562
+ goal?: undefined;
563
+ decisions?: undefined;
564
+ confirmed?: undefined;
565
+ files?: undefined;
566
+ blocked?: undefined;
567
+ next?: undefined;
506
568
  };
507
569
  required: string[];
508
570
  };
@@ -547,6 +609,7 @@ export declare const TOOL_DEFINITIONS: ({
547
609
  show_imports?: undefined;
548
610
  show_docs?: undefined;
549
611
  depth?: undefined;
612
+ max_tokens?: undefined;
550
613
  context_before?: undefined;
551
614
  context_after?: undefined;
552
615
  show?: undefined;
@@ -576,6 +639,12 @@ export declare const TOOL_DEFINITIONS: ({
576
639
  command?: undefined;
577
640
  runner?: undefined;
578
641
  timeout?: undefined;
642
+ goal?: undefined;
643
+ decisions?: undefined;
644
+ confirmed?: undefined;
645
+ files?: undefined;
646
+ blocked?: undefined;
647
+ next?: undefined;
579
648
  };
580
649
  required: string[];
581
650
  };
@@ -598,6 +667,7 @@ export declare const TOOL_DEFINITIONS: ({
598
667
  show_docs?: undefined;
599
668
  depth?: undefined;
600
669
  scope?: undefined;
670
+ max_tokens?: undefined;
601
671
  symbol?: undefined;
602
672
  context_before?: undefined;
603
673
  context_after?: undefined;
@@ -632,6 +702,12 @@ export declare const TOOL_DEFINITIONS: ({
632
702
  command?: undefined;
633
703
  runner?: undefined;
634
704
  timeout?: undefined;
705
+ goal?: undefined;
706
+ decisions?: undefined;
707
+ confirmed?: undefined;
708
+ files?: undefined;
709
+ blocked?: undefined;
710
+ next?: undefined;
635
711
  };
636
712
  required?: undefined;
637
713
  };
@@ -649,6 +725,7 @@ export declare const TOOL_DEFINITIONS: ({
649
725
  show_docs?: undefined;
650
726
  depth?: undefined;
651
727
  scope?: undefined;
728
+ max_tokens?: undefined;
652
729
  symbol?: undefined;
653
730
  context_before?: undefined;
654
731
  context_after?: undefined;
@@ -684,6 +761,12 @@ export declare const TOOL_DEFINITIONS: ({
684
761
  command?: undefined;
685
762
  runner?: undefined;
686
763
  timeout?: undefined;
764
+ goal?: undefined;
765
+ decisions?: undefined;
766
+ confirmed?: undefined;
767
+ files?: undefined;
768
+ blocked?: undefined;
769
+ next?: undefined;
687
770
  };
688
771
  required: string[];
689
772
  };
@@ -709,6 +792,7 @@ export declare const TOOL_DEFINITIONS: ({
709
792
  show_docs?: undefined;
710
793
  depth?: undefined;
711
794
  scope?: undefined;
795
+ max_tokens?: undefined;
712
796
  symbol?: undefined;
713
797
  context_before?: undefined;
714
798
  context_after?: undefined;
@@ -742,6 +826,12 @@ export declare const TOOL_DEFINITIONS: ({
742
826
  command?: undefined;
743
827
  runner?: undefined;
744
828
  timeout?: undefined;
829
+ goal?: undefined;
830
+ decisions?: undefined;
831
+ confirmed?: undefined;
832
+ files?: undefined;
833
+ blocked?: undefined;
834
+ next?: undefined;
745
835
  };
746
836
  required: string[];
747
837
  };
@@ -760,6 +850,7 @@ export declare const TOOL_DEFINITIONS: ({
760
850
  show_docs?: undefined;
761
851
  depth?: undefined;
762
852
  scope?: undefined;
853
+ max_tokens?: undefined;
763
854
  symbol?: undefined;
764
855
  context_before?: undefined;
765
856
  context_after?: undefined;
@@ -794,6 +885,12 @@ export declare const TOOL_DEFINITIONS: ({
794
885
  command?: undefined;
795
886
  runner?: undefined;
796
887
  timeout?: undefined;
888
+ goal?: undefined;
889
+ decisions?: undefined;
890
+ confirmed?: undefined;
891
+ files?: undefined;
892
+ blocked?: undefined;
893
+ next?: undefined;
797
894
  };
798
895
  required?: undefined;
799
896
  };
@@ -820,6 +917,7 @@ export declare const TOOL_DEFINITIONS: ({
820
917
  show_docs?: undefined;
821
918
  depth?: undefined;
822
919
  scope?: undefined;
920
+ max_tokens?: undefined;
823
921
  symbol?: undefined;
824
922
  context_before?: undefined;
825
923
  context_after?: undefined;
@@ -852,6 +950,12 @@ export declare const TOOL_DEFINITIONS: ({
852
950
  command?: undefined;
853
951
  runner?: undefined;
854
952
  timeout?: undefined;
953
+ goal?: undefined;
954
+ decisions?: undefined;
955
+ confirmed?: undefined;
956
+ files?: undefined;
957
+ blocked?: undefined;
958
+ next?: undefined;
855
959
  };
856
960
  required?: undefined;
857
961
  };
@@ -887,6 +991,7 @@ export declare const TOOL_DEFINITIONS: ({
887
991
  show_docs?: undefined;
888
992
  depth?: undefined;
889
993
  scope?: undefined;
994
+ max_tokens?: undefined;
890
995
  symbol?: undefined;
891
996
  context_before?: undefined;
892
997
  context_after?: undefined;
@@ -917,6 +1022,12 @@ export declare const TOOL_DEFINITIONS: ({
917
1022
  command?: undefined;
918
1023
  runner?: undefined;
919
1024
  timeout?: undefined;
1025
+ goal?: undefined;
1026
+ decisions?: undefined;
1027
+ confirmed?: undefined;
1028
+ files?: undefined;
1029
+ blocked?: undefined;
1030
+ next?: undefined;
920
1031
  };
921
1032
  required: string[];
922
1033
  };
@@ -940,6 +1051,7 @@ export declare const TOOL_DEFINITIONS: ({
940
1051
  show_docs?: undefined;
941
1052
  depth?: undefined;
942
1053
  scope?: undefined;
1054
+ max_tokens?: undefined;
943
1055
  symbol?: undefined;
944
1056
  context_before?: undefined;
945
1057
  context_after?: undefined;
@@ -973,6 +1085,12 @@ export declare const TOOL_DEFINITIONS: ({
973
1085
  command?: undefined;
974
1086
  runner?: undefined;
975
1087
  timeout?: undefined;
1088
+ goal?: undefined;
1089
+ decisions?: undefined;
1090
+ confirmed?: undefined;
1091
+ files?: undefined;
1092
+ blocked?: undefined;
1093
+ next?: undefined;
976
1094
  };
977
1095
  required: string[];
978
1096
  };
@@ -998,6 +1116,7 @@ export declare const TOOL_DEFINITIONS: ({
998
1116
  show_imports?: undefined;
999
1117
  show_docs?: undefined;
1000
1118
  depth?: undefined;
1119
+ max_tokens?: undefined;
1001
1120
  symbol?: undefined;
1002
1121
  context_before?: undefined;
1003
1122
  context_after?: undefined;
@@ -1032,6 +1151,12 @@ export declare const TOOL_DEFINITIONS: ({
1032
1151
  command?: undefined;
1033
1152
  runner?: undefined;
1034
1153
  timeout?: undefined;
1154
+ goal?: undefined;
1155
+ decisions?: undefined;
1156
+ confirmed?: undefined;
1157
+ files?: undefined;
1158
+ blocked?: undefined;
1159
+ next?: undefined;
1035
1160
  };
1036
1161
  required?: undefined;
1037
1162
  };
@@ -1057,6 +1182,7 @@ export declare const TOOL_DEFINITIONS: ({
1057
1182
  show_docs?: undefined;
1058
1183
  depth?: undefined;
1059
1184
  scope?: undefined;
1185
+ max_tokens?: undefined;
1060
1186
  symbol?: undefined;
1061
1187
  context_before?: undefined;
1062
1188
  context_after?: undefined;
@@ -1091,6 +1217,12 @@ export declare const TOOL_DEFINITIONS: ({
1091
1217
  command?: undefined;
1092
1218
  runner?: undefined;
1093
1219
  timeout?: undefined;
1220
+ goal?: undefined;
1221
+ decisions?: undefined;
1222
+ confirmed?: undefined;
1223
+ files?: undefined;
1224
+ blocked?: undefined;
1225
+ next?: undefined;
1094
1226
  };
1095
1227
  required: string[];
1096
1228
  };
@@ -1116,6 +1248,7 @@ export declare const TOOL_DEFINITIONS: ({
1116
1248
  show_docs?: undefined;
1117
1249
  depth?: undefined;
1118
1250
  scope?: undefined;
1251
+ max_tokens?: undefined;
1119
1252
  symbol?: undefined;
1120
1253
  context_before?: undefined;
1121
1254
  context_after?: undefined;
@@ -1149,6 +1282,12 @@ export declare const TOOL_DEFINITIONS: ({
1149
1282
  command?: undefined;
1150
1283
  runner?: undefined;
1151
1284
  timeout?: undefined;
1285
+ goal?: undefined;
1286
+ decisions?: undefined;
1287
+ confirmed?: undefined;
1288
+ files?: undefined;
1289
+ blocked?: undefined;
1290
+ next?: undefined;
1152
1291
  };
1153
1292
  required?: undefined;
1154
1293
  };
@@ -1176,6 +1315,93 @@ export declare const TOOL_DEFINITIONS: ({
1176
1315
  show_docs?: undefined;
1177
1316
  depth?: undefined;
1178
1317
  scope?: undefined;
1318
+ max_tokens?: undefined;
1319
+ symbol?: undefined;
1320
+ context_before?: undefined;
1321
+ context_after?: undefined;
1322
+ show?: undefined;
1323
+ include_edit_context?: undefined;
1324
+ symbols?: undefined;
1325
+ start_line?: undefined;
1326
+ end_line?: undefined;
1327
+ heading?: undefined;
1328
+ context_lines?: undefined;
1329
+ line?: undefined;
1330
+ context?: undefined;
1331
+ include_callers?: undefined;
1332
+ include_tests?: undefined;
1333
+ include_changes?: undefined;
1334
+ section?: undefined;
1335
+ paths?: undefined;
1336
+ kind?: undefined;
1337
+ limit?: undefined;
1338
+ lang?: undefined;
1339
+ mode?: undefined;
1340
+ include?: undefined;
1341
+ recursive?: undefined;
1342
+ max_depth?: undefined;
1343
+ verbose?: undefined;
1344
+ module?: undefined;
1345
+ export_only?: undefined;
1346
+ check?: undefined;
1347
+ pattern?: undefined;
1348
+ name?: undefined;
1349
+ ref?: undefined;
1350
+ count?: undefined;
1351
+ goal?: undefined;
1352
+ decisions?: undefined;
1353
+ confirmed?: undefined;
1354
+ files?: undefined;
1355
+ blocked?: undefined;
1356
+ next?: undefined;
1357
+ };
1358
+ required: string[];
1359
+ };
1360
+ } | {
1361
+ name: string;
1362
+ description: string;
1363
+ inputSchema: {
1364
+ type: "object";
1365
+ properties: {
1366
+ goal: {
1367
+ type: string;
1368
+ description: string;
1369
+ };
1370
+ decisions: {
1371
+ type: string;
1372
+ items: {
1373
+ type: string;
1374
+ };
1375
+ description: string;
1376
+ };
1377
+ confirmed: {
1378
+ type: string;
1379
+ items: {
1380
+ type: string;
1381
+ };
1382
+ description: string;
1383
+ };
1384
+ files: {
1385
+ type: string;
1386
+ items: {
1387
+ type: string;
1388
+ };
1389
+ description: string;
1390
+ };
1391
+ blocked: {
1392
+ type: string;
1393
+ description: string;
1394
+ };
1395
+ next: {
1396
+ type: string;
1397
+ description: string;
1398
+ };
1399
+ path?: undefined;
1400
+ show_imports?: undefined;
1401
+ show_docs?: undefined;
1402
+ depth?: undefined;
1403
+ scope?: undefined;
1404
+ max_tokens?: undefined;
1179
1405
  symbol?: undefined;
1180
1406
  context_before?: undefined;
1181
1407
  context_after?: undefined;
@@ -1208,6 +1434,9 @@ export declare const TOOL_DEFINITIONS: ({
1208
1434
  name?: undefined;
1209
1435
  ref?: undefined;
1210
1436
  count?: undefined;
1437
+ command?: undefined;
1438
+ runner?: undefined;
1439
+ timeout?: undefined;
1211
1440
  };
1212
1441
  required: string[];
1213
1442
  };
@@ -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, decisions, 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,22 @@ 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
+ decisions: { type: 'array', items: { type: 'string' }, description: 'Key decisions made and why (e.g., "removed sysfee step — caused double counting"). Prevents revisiting rejected approaches.' },
366
+ confirmed: { type: 'array', items: { type: 'string' }, description: 'Established facts (what has been verified)' },
367
+ files: { type: 'array', items: { type: 'string' }, description: 'Relevant file paths' },
368
+ blocked: { type: 'string', description: 'Current blocker or obstacle' },
369
+ next: { type: 'string', description: 'Next step to take' },
370
+ },
371
+ required: ['goal'],
372
+ },
373
+ },
352
374
  ];
353
375
  //# 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.1",
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",