token-pilot 0.17.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.
@@ -27,14 +27,20 @@ export const MCP_INSTRUCTIONS = [
27
27
  '15. Code quality → code_audit (TODOs, deprecated, structural patterns)',
28
28
  '16. Dead code → find_unused (unreferenced symbols across project)',
29
29
  '17. Module architecture → module_info (deps, dependents, public API)',
30
+ '18. Read markdown/yaml/json/csv section → read_section (loads one heading/key/row-range, NOT the whole file)',
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',
30
34
  '',
31
35
  'USE DEFAULT TOOLS ONLY FOR: regex text search → Grep | exact raw content → Read | non-code configs → Read',
32
36
  '',
33
37
  'WORKFLOWS:',
34
38
  '• Explore: project_overview → explore_area → smart_read → read_symbol',
35
39
  '• Edit: smart_read → read_symbol(include_edit_context=true) → Edit → read_diff',
40
+ '• Docs: smart_read (outline) → read_section → read_for_edit(section=) → Edit → read_diff',
36
41
  '• Refactor: find_usages → read_symbols → read_for_edit → Edit → test_summary',
37
42
  '• Audit: code_audit + find_unused + Grep (for regex patterns)',
43
+ '• Long session: session_snapshot → compact context → continue with minimal state',
38
44
  ].join('\n');
39
45
  export const TOOL_DEFINITIONS = [
40
46
  // --- Core reading tools ---
@@ -53,6 +59,7 @@ export const TOOL_DEFINITIONS = [
53
59
  enum: ['full', 'nav', 'exports'],
54
60
  description: 'Output scope: full (default, all details), nav (names + lines only, 2-3x smaller), exports (public API only)',
55
61
  },
62
+ max_tokens: { type: 'number', description: 'Token budget. If output exceeds this, auto-downgrades: full → outline → compact. Use for context-constrained sessions.' },
56
63
  },
57
64
  required: ['path'],
58
65
  },
@@ -108,6 +115,18 @@ export const TOOL_DEFINITIONS = [
108
115
  required: ['path', 'start_line', 'end_line'],
109
116
  },
110
117
  },
118
+ {
119
+ name: 'read_section',
120
+ description: 'Read a specific section from Markdown, YAML, JSON, or CSV files. Markdown: by heading name. YAML/JSON: by top-level key. CSV: by row range (rows:1-50). Much cheaper than reading the whole file.',
121
+ inputSchema: {
122
+ type: 'object',
123
+ properties: {
124
+ path: { type: 'string', description: 'Path to .md, .yaml, .yml, .json, or .csv file' },
125
+ heading: { type: 'string', description: 'Section heading (Markdown), top-level key (YAML/JSON), or row range "rows:1-50" (CSV). Case-insensitive.' },
126
+ },
127
+ required: ['path', 'heading'],
128
+ },
129
+ },
111
130
  {
112
131
  name: 'read_diff',
113
132
  description: 'Use INSTEAD OF re-reading whole file after edits. Shows only changed hunks. REQUIRES: call smart_read or read_for_edit BEFORE editing to create baseline snapshot.',
@@ -138,6 +157,10 @@ export const TOOL_DEFINITIONS = [
138
157
  include_callers: { type: 'boolean', description: 'Show top callers of this symbol (saves a separate find_usages call)' },
139
158
  include_tests: { type: 'boolean', description: 'Show related test file and test names' },
140
159
  include_changes: { type: 'boolean', description: 'Show recent git changes in the target region' },
160
+ section: {
161
+ type: 'string',
162
+ description: 'Section to edit: heading (Markdown), top-level key (YAML/JSON), or "rows:1-50" (CSV). Returns raw section content for Edit old_string.',
163
+ },
141
164
  },
142
165
  required: ['path'],
143
166
  },
@@ -153,6 +176,7 @@ export const TOOL_DEFINITIONS = [
153
176
  items: { type: 'string' },
154
177
  description: 'Array of file paths',
155
178
  },
179
+ max_tokens: { type: 'number', description: 'Token budget per file. If a file exceeds this, auto-downgrades to compact outline.' },
156
180
  },
157
181
  required: ['paths'],
158
182
  },
@@ -330,5 +354,21 @@ export const TOOL_DEFINITIONS = [
330
354
  required: ['command'],
331
355
  },
332
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
+ },
333
373
  ];
334
374
  //# sourceMappingURL=tool-definitions.js.map
package/dist/server.js CHANGED
@@ -36,12 +36,14 @@ 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';
40
+ import { handleReadSection } from './handlers/read-section.js';
39
41
  import { detectContextMode } from './integration/context-mode-detector.js';
40
42
  import { estimateTokens } from './core/token-estimator.js';
41
43
  import { checkPolicy, isFullReadTool } from './core/policy-engine.js';
42
44
  import { MCP_INSTRUCTIONS, TOOL_DEFINITIONS } from './server/tool-definitions.js';
43
45
  import { createTokenEstimates } from './server/token-estimates.js';
44
- import { validateSmartReadArgs, validateReadSymbolArgs, validateReadSymbolsArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateSmartLogArgs, validateTestSummaryArgs, } from './core/validation.js';
46
+ import { validateSmartReadArgs, validateReadSymbolArgs, validateReadSymbolsArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateSmartLogArgs, validateTestSummaryArgs, validateReadSectionArgs, } from './core/validation.js';
45
47
  export async function createServer(projectRoot, options) {
46
48
  const config = await loadConfig(projectRoot);
47
49
  const astIndex = new AstIndexClient(projectRoot, config.astIndex.timeout, {
@@ -153,6 +155,8 @@ export async function createServer(projectRoot, options) {
153
155
  : null;
154
156
  // Policy engine state
155
157
  let fullFileReadsCount = 0;
158
+ let totalCallCount = 0;
159
+ let totalTokensReturned = 0;
156
160
  const readForEditCalled = new Set();
157
161
  // Detect context-mode companion
158
162
  const cmEnabled = config.contextMode.enabled;
@@ -223,6 +227,8 @@ export async function createServer(projectRoot, options) {
223
227
  }),
224
228
  });
225
229
  // Policy tracking
230
+ totalCallCount++;
231
+ totalTokensReturned += rest.tokensReturned;
226
232
  if (isFullReadTool(rest.tool)) {
227
233
  fullFileReadsCount++;
228
234
  }
@@ -234,6 +240,8 @@ export async function createServer(projectRoot, options) {
234
240
  fullFileReadsCount,
235
241
  tokensReturned: rest.tokensReturned,
236
242
  readForEditCalled,
243
+ totalCallCount,
244
+ totalTokensReturned,
237
245
  });
238
246
  return advisory ? `\n${advisory.message}` : null;
239
247
  }
@@ -310,6 +318,20 @@ export async function createServer(projectRoot, options) {
310
318
  recordWithTrace({ tool: 'read_range', path: rangeArgs.path, tokensReturned: rangeTokens, tokensWouldBe: fullTokensRange || rangeTokens, timestamp: Date.now(), savingsCategory: detectSavingsCategory(rangeText), absPath: resolve(projectRoot, rangeArgs.path), args: rangeArgs });
311
319
  return rangeResult;
312
320
  }
321
+ case 'read_section': {
322
+ const secArgs = validateReadSectionArgs(args);
323
+ const secResult = await handleReadSection(secArgs, projectRoot, contextRegistry);
324
+ const secText = secResult.content[0]?.text ?? '';
325
+ const secTokens = estimateTokens(secText);
326
+ const fullTokensSec = await fullFileTokens(secArgs.path);
327
+ recordWithTrace({
328
+ tool: 'read_section', path: secArgs.path,
329
+ tokensReturned: secTokens, tokensWouldBe: fullTokensSec || secTokens,
330
+ timestamp: Date.now(), savingsCategory: 'compression',
331
+ absPath: resolve(projectRoot, secArgs.path), args: secArgs,
332
+ });
333
+ return secResult;
334
+ }
313
335
  case 'read_diff': {
314
336
  const diffArgs = validateReadDiffArgs(args);
315
337
  const diffResult = await handleReadDiff(diffArgs, projectRoot, fileCache, contextRegistry);
@@ -560,6 +582,17 @@ export async function createServer(projectRoot, options) {
560
582
  recordWithTrace({ tool: 'test_summary', path: tsArgs.command, tokensReturned: tsTokens, tokensWouldBe: tsResult.rawTokens || tsTokens, timestamp: Date.now(), savingsCategory: 'compression', args: tsArgs });
561
583
  return { content: tsResult.content };
562
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
+ }
563
596
  default:
564
597
  return {
565
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.17.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",