token-pilot 0.32.0 → 0.33.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.
Files changed (48) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/tp-api-surface-tracker.md +1 -1
  4. package/agents/tp-audit-scanner.md +1 -1
  5. package/agents/tp-commit-writer.md +1 -1
  6. package/agents/tp-context-engineer.md +1 -1
  7. package/agents/tp-dead-code-finder.md +1 -1
  8. package/agents/tp-debugger.md +1 -1
  9. package/agents/tp-dep-health.md +1 -1
  10. package/agents/tp-doc-writer.md +1 -1
  11. package/agents/tp-history-explorer.md +1 -1
  12. package/agents/tp-impact-analyzer.md +1 -1
  13. package/agents/tp-incident-timeline.md +1 -1
  14. package/agents/tp-incremental-builder.md +1 -1
  15. package/agents/tp-migration-scout.md +1 -1
  16. package/agents/tp-onboard.md +1 -1
  17. package/agents/tp-performance-profiler.md +1 -1
  18. package/agents/tp-pr-reviewer.md +1 -1
  19. package/agents/tp-refactor-planner.md +1 -1
  20. package/agents/tp-review-impact.md +1 -1
  21. package/agents/tp-run.md +1 -1
  22. package/agents/tp-session-restorer.md +1 -1
  23. package/agents/tp-ship-coordinator.md +1 -1
  24. package/agents/tp-spec-writer.md +1 -1
  25. package/agents/tp-test-coverage-gapper.md +1 -1
  26. package/agents/tp-test-triage.md +1 -1
  27. package/agents/tp-test-writer.md +1 -1
  28. package/dist/ast-index/client.js +17 -1
  29. package/dist/cli/install-agents.d.ts +18 -0
  30. package/dist/cli/install-agents.js +88 -1
  31. package/dist/cli/stats.js +9 -2
  32. package/dist/cli/typo-guard.d.ts +1 -1
  33. package/dist/cli/typo-guard.js +9 -0
  34. package/dist/core/error-log.d.ts +86 -0
  35. package/dist/core/error-log.js +228 -0
  36. package/dist/core/event-log.d.ts +49 -1
  37. package/dist/core/event-log.js +114 -0
  38. package/dist/core/validation.d.ts +12 -0
  39. package/dist/core/validation.js +38 -8
  40. package/dist/handlers/smart-log.js +7 -2
  41. package/dist/hooks/installer.d.ts +40 -0
  42. package/dist/hooks/installer.js +145 -2
  43. package/dist/hooks/pre-task.js +44 -10
  44. package/dist/hooks/safe-runner.d.ts +48 -0
  45. package/dist/hooks/safe-runner.js +73 -0
  46. package/dist/index.d.ts +11 -0
  47. package/dist/index.js +284 -63
  48. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23,7 +23,10 @@ import { execFile } from "node:child_process";
23
23
  import { promisify } from "node:util";
24
24
  import { fileURLToPath } from "node:url";
25
25
  import { createServer } from "./server.js";
26
- import { installHook, uninstallHook } from "./hooks/installer.js";
26
+ import { installHook, uninstallHook, cleanStaleHookEntries, isTokenPilotPluginEnabled, } from "./hooks/installer.js";
27
+ import { runHookEntryPoint } from "./hooks/safe-runner.js";
28
+ import { loadErrors, formatErrorList } from "./core/error-log.js";
29
+ import { appendDiagnostic } from "./core/event-log.js";
27
30
  import { findBinary, installBinary, checkBinaryUpdate, isNewerVersion, } from "./ast-index/binary-manager.js";
28
31
  import { loadConfig } from "./config/loader.js";
29
32
  import { isDangerousRoot } from "./core/validation.js";
@@ -123,18 +126,27 @@ export async function main(cliArgs = process.argv.slice(2)) {
123
126
  }
124
127
  switch (cliArgs[0]) {
125
128
  case "hook-read": {
126
- const cfg = await loadConfig(process.cwd());
127
- await handleHookRead(cliArgs[1], cfg.hooks.mode, cfg.hooks.denyThreshold, process.cwd(), {
128
- adaptiveThreshold: cfg.hooks.adaptiveThreshold,
129
- adaptiveBudgetTokens: cfg.hooks.adaptiveBudgetTokens,
129
+ // v0.34.0 wrap in runHookEntryPoint so any unexpected throw
130
+ // lands in `~/.token-pilot/hook-errors.jsonl` instead of being
131
+ // swallowed silently. handleHookRead has its own internal
132
+ // try/catch for known I/O failures; the wrapper is the safety
133
+ // net for everything else.
134
+ await runHookEntryPoint({ hook: "hook-read" }, async () => {
135
+ const cfg = await loadConfig(process.cwd());
136
+ await handleHookRead(cliArgs[1], cfg.hooks.mode, cfg.hooks.denyThreshold, process.cwd(), {
137
+ adaptiveThreshold: cfg.hooks.adaptiveThreshold,
138
+ adaptiveBudgetTokens: cfg.hooks.adaptiveBudgetTokens,
139
+ });
130
140
  });
131
141
  return;
132
142
  }
133
143
  case "hook-edit":
134
- handleHookEdit();
144
+ await runHookEntryPoint({ hook: "hook-edit" }, async () => {
145
+ handleHookEdit();
146
+ });
135
147
  return;
136
148
  case "hook-post-bash": {
137
- try {
149
+ await runHookEntryPoint({ hook: "hook-post-bash" }, async () => {
138
150
  const stdin = readFileSync(0, "utf-8");
139
151
  const input = JSON.parse(stdin);
140
152
  const advice = decidePostBashAdvice(input, {
@@ -143,61 +155,53 @@ export async function main(cliArgs = process.argv.slice(2)) {
143
155
  const rendered = renderPostBashHookOutput(advice);
144
156
  if (rendered)
145
157
  process.stdout.write(rendered);
146
- }
147
- catch {
148
- /* silent — hook must not break */
149
- }
150
- process.exit(0);
158
+ });
151
159
  return;
152
160
  }
153
161
  case "hook-pre-bash": {
154
162
  // v0.28.0 — passive pre-intercept for heavy Bash commands.
155
- // PostToolUse can't truncate tool_response (API limit), so the only
156
- // way to actually save tokens is to deny the call up front and
157
- // nudge the agent to the cheaper MCP equivalent.
158
- try {
163
+ // v0.34.0 runHookEntryPoint covers throw paths.
164
+ await runHookEntryPoint({ hook: "hook-pre-bash" }, async () => {
159
165
  const stdin = readFileSync(0, "utf-8");
160
166
  const input = JSON.parse(stdin);
161
167
  const decision = decidePreBash(input, parseEnforcementMode(process.env.TOKEN_PILOT_MODE));
162
168
  const rendered = renderPreBashOutput(decision);
163
169
  if (rendered)
164
170
  process.stdout.write(rendered);
165
- }
166
- catch {
167
- /* silent — hook must not break */
168
- }
169
- process.exit(0);
171
+ });
170
172
  return;
171
173
  }
172
174
  case "hook-pre-grep": {
173
175
  // v0.28.0 — passive pre-intercept for symbol-like Grep patterns.
174
- // Redirects identifier searches to mcp__token-pilot__find_usages.
175
- try {
176
+ // v0.34.0 runHookEntryPoint covers throw paths.
177
+ await runHookEntryPoint({ hook: "hook-pre-grep" }, async () => {
176
178
  const stdin = readFileSync(0, "utf-8");
177
179
  const input = JSON.parse(stdin);
178
180
  const decision = decidePreGrep(input, parseEnforcementMode(process.env.TOKEN_PILOT_MODE));
179
181
  const rendered = renderPreGrepOutput(decision);
180
182
  if (rendered)
181
183
  process.stdout.write(rendered);
182
- }
183
- catch {
184
- /* silent — hook must not break */
185
- }
186
- process.exit(0);
184
+ });
187
185
  return;
188
186
  }
189
187
  case "hook-pre-task": {
190
188
  // v0.31.0 Pack 2 — route general-purpose Task dispatches to a
191
- // `tp-*` specialist when the description clearly matches. Default
192
- // (deny / advisory mode) is a non-blocking advise; strict mode or
193
- // TOKEN_PILOT_FORCE_SUBAGENTS=1 hard-denies on a high-confidence
194
- // match. The matcher is lenient by design (false deny is much
195
- // worse than a missed nudge — see pre-edit v0.30.4 rollback).
196
- try {
189
+ // `tp-*` specialist when the description clearly matches.
190
+ // v0.34.0 — error/diagnostic logging via runHookEntryPoint.
191
+ await runHookEntryPoint({ hook: "hook-pre-task" }, async () => {
197
192
  const stdin = readFileSync(0, "utf-8");
198
193
  const input = JSON.parse(stdin);
199
194
  const agentIndex = await getAgentIndex();
200
195
  const force = process.env.TOKEN_PILOT_FORCE_SUBAGENTS === "1";
196
+ // v0.34.0 diagnostic: B4 — empty index + force is a fail
197
+ // case (we deny, but record so the user can see why).
198
+ if (force && agentIndex.agents.length === 0) {
199
+ await appendDiagnostic(process.cwd(), {
200
+ code: "force_subagents_no_agents",
201
+ level: "warn",
202
+ detail: { hint: "run `npx token-pilot install-agents`" },
203
+ });
204
+ }
201
205
  const decision = decidePreTask(input, {
202
206
  mode: parseEnforcementMode(process.env.TOKEN_PILOT_MODE),
203
207
  agentIndex,
@@ -206,15 +210,11 @@ export async function main(cliArgs = process.argv.slice(2)) {
206
210
  const rendered = renderPreTaskOutput(decision);
207
211
  if (rendered)
208
212
  process.stdout.write(rendered);
209
- }
210
- catch {
211
- /* silent — hook must not break */
212
- }
213
- process.exit(0);
213
+ });
214
214
  return;
215
215
  }
216
216
  case "hook-post-task": {
217
- try {
217
+ await runHookEntryPoint({ hook: "hook-post-task" }, async () => {
218
218
  const stdin = readFileSync(0, "utf-8");
219
219
  const input = JSON.parse(stdin);
220
220
  const message = await processPostTask(process.cwd(), homedir(), input);
@@ -226,30 +226,24 @@ export async function main(cliArgs = process.argv.slice(2)) {
226
226
  },
227
227
  }));
228
228
  }
229
- }
230
- catch {
231
- /* silent — hook must not break */
232
- }
233
- process.exit(0);
229
+ });
234
230
  return;
235
231
  }
236
232
  case "hook-session-start": {
237
- const cfg = await loadConfig(process.cwd());
238
- // `sessionStart.enabled` is independent of `hooks.mode` by design —
239
- // a user may want the Read-blocking hook off (mode:"off") while still
240
- // getting the tool-rules reminder at session start, or vice versa.
241
- if (!cfg.sessionStart.enabled) {
242
- process.exit(0);
243
- }
244
- const result = await handleSessionStart({
245
- projectRoot: process.cwd(),
246
- homeDir: homedir(),
247
- sessionStartConfig: cfg.sessionStart,
233
+ await runHookEntryPoint({ hook: "hook-session-start" }, async () => {
234
+ const cfg = await loadConfig(process.cwd());
235
+ // sessionStart.enabled is independent of hooks.mode by design.
236
+ if (!cfg.sessionStart.enabled)
237
+ return;
238
+ const result = await handleSessionStart({
239
+ projectRoot: process.cwd(),
240
+ homeDir: homedir(),
241
+ sessionStartConfig: cfg.sessionStart,
242
+ });
243
+ if (result) {
244
+ process.stdout.write(result);
245
+ }
248
246
  });
249
- if (result) {
250
- process.stdout.write(result);
251
- }
252
- process.exit(0);
253
247
  return;
254
248
  }
255
249
  case "install-hook":
@@ -258,6 +252,47 @@ export async function main(cliArgs = process.argv.slice(2)) {
258
252
  case "uninstall-hook":
259
253
  await handleUninstallHook(cliArgs[1] || process.cwd());
260
254
  return;
255
+ case "errors": {
256
+ // v0.34.0 — surface ~/.token-pilot/hook-errors.jsonl with optional
257
+ // filters: --tail=N --code=<x> --hook=<y> --level=<info|warn|error>
258
+ const args = cliArgs.slice(1);
259
+ const flag = (k) => {
260
+ for (const a of args) {
261
+ if (a.startsWith(`--${k}=`))
262
+ return a.slice(k.length + 3);
263
+ if (a === `--${k}` || a === `-${k}`)
264
+ return "true";
265
+ }
266
+ return undefined;
267
+ };
268
+ const tailRaw = flag("tail");
269
+ const records = await loadErrors({
270
+ tail: tailRaw ? Number(tailRaw) : undefined,
271
+ code: flag("code"),
272
+ hook: flag("hook"),
273
+ level: flag("level"),
274
+ });
275
+ process.stdout.write(formatErrorList(records) + "\n");
276
+ return;
277
+ }
278
+ case "migrate-hooks": {
279
+ // v0.33.0 — clean stale npx-cache / pinned-version token-pilot
280
+ // hook entries from user-level + project-level settings.json so
281
+ // the bundled plugin's hooks/hooks.json takes over via
282
+ // CLAUDE_PLUGIN_ROOT. Safe and idempotent.
283
+ const targets = [
284
+ resolve(homedir(), ".claude", "settings.json"),
285
+ resolve(cliArgs[1] || process.cwd(), ".claude", "settings.json"),
286
+ ];
287
+ let total = 0;
288
+ for (const path of targets) {
289
+ const r = await cleanStaleHookEntries(path);
290
+ console.log(r.message);
291
+ total += r.staleEntriesRemoved;
292
+ }
293
+ console.log(`\nDone — removed ${total} stale entr${total === 1 ? "y" : "ies"}.`);
294
+ return;
295
+ }
261
296
  case "install-ast-index":
262
297
  await handleInstallAstIndex();
263
298
  return;
@@ -347,6 +382,34 @@ export function looksLikePluginCacheDir(candidate) {
347
382
  return false;
348
383
  }
349
384
  }
385
+ /**
386
+ * v0.33.0 (B8) — reject candidates that are obviously not a project
387
+ * directory. Triggered by WSL launches where the shell starts in
388
+ * `C:\Windows\System32`, `/mnt/c/Windows/...`, or a UNC path. Without
389
+ * this guard, `git rev-parse --show-toplevel` either fails noisily or
390
+ * returns the Windows tree, leaving every subsequent git/MCP call
391
+ * looking at the wrong filesystem.
392
+ *
393
+ * Conservative — only matches paths we are certain are not user code.
394
+ */
395
+ export function isWindowsSystemPath(candidate) {
396
+ if (!candidate)
397
+ return false;
398
+ // Native Windows: C:\Windows\... or C:/Windows/...
399
+ if (/^[A-Za-z]:[\\/](Windows|Program Files|ProgramData)\b/i.test(candidate)) {
400
+ return true;
401
+ }
402
+ // WSL view of Windows: /mnt/c/Windows/... (or any drive letter)
403
+ if (/^\/mnt\/[a-z]\/(windows|program files|programdata)\b/i.test(candidate)) {
404
+ return true;
405
+ }
406
+ // UNC path — almost never a project root and `cwd` cannot be set to
407
+ // one reliably anyway. Better to skip than to misroute.
408
+ if (/^\\\\/.test(candidate)) {
409
+ return true;
410
+ }
411
+ return false;
412
+ }
350
413
  export async function startServer(cliArgs = process.argv.slice(2)) {
351
414
  // Defensive: ignore a poisoned cliArgs[0] pointing into the plugin install
352
415
  // dir. Fall through to the INIT_CWD / PWD / cwd detection below — same
@@ -357,14 +420,38 @@ export async function startServer(cliArgs = process.argv.slice(2)) {
357
420
  explicitRoot = "";
358
421
  }
359
422
  let projectRoot = explicitRoot || process.cwd();
360
- // Detect git root for reliable project root
361
- // Try multiple sources: args[0] INIT_CWD (npm/npx invoking dir) PWD → cwd
423
+ // Detect git root for reliable project root.
424
+ // v0.33.0 (B8) on WSL the shell is sometimes launched with the
425
+ // working directory pointing into Windows' filesystem
426
+ // (`/mnt/c/Windows/system32` or, worse, a UNC like `\\\\wsl$\\…`).
427
+ // INIT_CWD/PWD/cwd then resolve to a Windows system path and
428
+ // every git operation lands in the wrong tree. Claude Code itself
429
+ // reliably exports `CLAUDE_PROJECT_DIR` — prefer it absolutely
430
+ // when present and reject obvious system paths regardless.
362
431
  if (!explicitRoot) {
363
- const candidates = [
432
+ const rawCandidates = [
433
+ process.env.CLAUDE_PROJECT_DIR, // canonical Claude Code env (B8)
364
434
  process.env.INIT_CWD, // npm/npx sets this to invoking directory
365
435
  process.env.PWD, // shell working directory (may differ from cwd)
366
436
  process.cwd(), // Node.js working directory
367
437
  ].filter((c) => !!c && c !== "/");
438
+ // v0.34.0 — emit a diagnostic for every Windows / UNC reject so
439
+ // we can see in stats how often WSL launches misroute the cwd.
440
+ for (const c of rawCandidates) {
441
+ if (isWindowsSystemPath(c)) {
442
+ try {
443
+ await appendDiagnostic(process.cwd(), {
444
+ code: "wsl_path_rejected",
445
+ level: "warn",
446
+ detail: { path_basename: c.split(/[\\/]/).pop() ?? "" },
447
+ });
448
+ }
449
+ catch {
450
+ /* logger of last resort */
451
+ }
452
+ }
453
+ }
454
+ const candidates = rawCandidates.filter((c) => !isWindowsSystemPath(c));
368
455
  let detected = false;
369
456
  for (const candidate of candidates) {
370
457
  if (isDangerousRoot(candidate))
@@ -705,6 +792,23 @@ export async function handleInstallHook(projectRoot) {
705
792
  "Skipping to avoid duplicate hook entries.");
706
793
  process.exit(0);
707
794
  }
795
+ // v0.33.0 — detect when the user already enabled the token-pilot
796
+ // plugin in `~/.claude/settings.json`. Even though we're running
797
+ // outside CLAUDE_PLUGIN_ROOT here (CLI invocation), the plugin's
798
+ // own `hooks/hooks.json` is what Claude Code uses at runtime.
799
+ // Writing additional entries with a captured npx-cache path leads
800
+ // to the bug B2 (v0.33.0): hooks pinned to an old binary that
801
+ // never sees newer hook handlers. Surface a clear migration step
802
+ // instead of silently duplicating.
803
+ if (await isTokenPilotPluginEnabled(homedir())) {
804
+ const userSettings = resolve(homedir(), ".claude", "settings.json");
805
+ const cleanup = await cleanStaleHookEntries(userSettings);
806
+ console.log("token-pilot plugin is enabled in ~/.claude/settings.json —\n" +
807
+ "the plugin's bundled hooks/hooks.json is the source of truth.\n" +
808
+ "Skipping settings.json hook write to avoid pinning to a stale path.\n" +
809
+ cleanup.message);
810
+ process.exit(0);
811
+ }
708
812
  let hookOptions;
709
813
  try {
710
814
  const rawPath = fileURLToPath(new URL("./index.js", import.meta.url));
@@ -927,6 +1031,123 @@ export async function handleDoctor() {
927
1031
  catch {
928
1032
  /* ecosystem check is best-effort; never break doctor */
929
1033
  }
1034
+ // ── v0.34.0 health checks (Pack 2 of error-logging release) ──
1035
+ try {
1036
+ console.log("── runtime health ──");
1037
+ // Recent errors
1038
+ const recent = await loadErrors({ tail: 100 });
1039
+ if (recent.length === 0) {
1040
+ console.log(` errors: 0 in ~/.token-pilot/hook-errors.jsonl ✓`);
1041
+ }
1042
+ else {
1043
+ const counts = new Map();
1044
+ for (const r of recent)
1045
+ counts.set(r.code, (counts.get(r.code) ?? 0) + 1);
1046
+ const top = Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5);
1047
+ console.log(` errors: ${recent.length} recent (top codes):`);
1048
+ for (const [code, n] of top)
1049
+ console.log(` ${String(n).padStart(3)}× ${code}`);
1050
+ console.log(` drill-in: token-pilot errors --tail=20`);
1051
+ }
1052
+ // Stale hook entries — user-level + project-level
1053
+ const userSettings = join(homedir(), ".claude", "settings.json");
1054
+ const projectSettings = join(cwd, ".claude", "settings.json");
1055
+ let staleCount = 0;
1056
+ for (const p of [userSettings, projectSettings]) {
1057
+ try {
1058
+ if (!existsSync(p))
1059
+ continue;
1060
+ const raw = await import("node:fs/promises").then((m) => m.readFile(p, "utf-8"));
1061
+ const json = JSON.parse(raw);
1062
+ const sections = ["PreToolUse", "PostToolUse", "SessionStart"];
1063
+ for (const s of sections) {
1064
+ const arr = json.hooks?.[s];
1065
+ if (!Array.isArray(arr))
1066
+ continue;
1067
+ for (const entry of arr) {
1068
+ const inner = Array.isArray(entry?.hooks) ? entry.hooks : [];
1069
+ for (const h of inner) {
1070
+ if (typeof h?.command === "string") {
1071
+ if (/\/_npx\/[0-9a-f]+\//.test(h.command))
1072
+ staleCount++;
1073
+ else if (/\/plugins\/cache\/token-pilot\/token-pilot\/[^/]+\//.test(h.command))
1074
+ staleCount++;
1075
+ }
1076
+ }
1077
+ }
1078
+ }
1079
+ }
1080
+ catch {
1081
+ /* skip */
1082
+ }
1083
+ }
1084
+ if (staleCount === 0) {
1085
+ console.log(` stale hooks: none ✓`);
1086
+ }
1087
+ else {
1088
+ console.log(` stale hooks: ${staleCount} pinned-path entries`);
1089
+ console.log(` fix: token-pilot migrate-hooks`);
1090
+ }
1091
+ // Installed tp-* agents vs catalog
1092
+ let installed = 0;
1093
+ let catalog = 0;
1094
+ try {
1095
+ const fsp = await import("node:fs/promises");
1096
+ const userAgents = join(homedir(), ".claude", "agents");
1097
+ const projAgents = join(cwd, ".claude", "agents");
1098
+ const seen = new Set();
1099
+ for (const dir of [userAgents, projAgents]) {
1100
+ try {
1101
+ const entries = await fsp.readdir(dir);
1102
+ for (const e of entries) {
1103
+ if (e.startsWith("tp-") && e.endsWith(".md"))
1104
+ seen.add(e);
1105
+ }
1106
+ }
1107
+ catch {
1108
+ /* missing */
1109
+ }
1110
+ }
1111
+ installed = seen.size;
1112
+ const dist = new URL("../agents", import.meta.url).pathname;
1113
+ try {
1114
+ const dEntries = await fsp.readdir(dist);
1115
+ catalog = dEntries.filter((f) => f.startsWith("tp-") && f.endsWith(".md")).length;
1116
+ }
1117
+ catch {
1118
+ catalog = 0;
1119
+ }
1120
+ }
1121
+ catch {
1122
+ /* skip */
1123
+ }
1124
+ if (catalog === 0) {
1125
+ console.log(` tp-* agents: could not read catalog`);
1126
+ }
1127
+ else if (installed === 0) {
1128
+ console.log(` tp-* agents: 0 of ${catalog} installed`);
1129
+ console.log(` fix: token-pilot install-agents --scope=user`);
1130
+ }
1131
+ else if (installed < catalog) {
1132
+ console.log(` tp-* agents: ${installed} of ${catalog} installed (partial)`);
1133
+ console.log(` fix: token-pilot install-agents --force`);
1134
+ }
1135
+ else {
1136
+ console.log(` tp-* agents: ${installed}/${catalog} ✓`);
1137
+ }
1138
+ // WSL detection probe
1139
+ const cwdGuess = process.env.CLAUDE_PROJECT_DIR || process.cwd();
1140
+ if (isWindowsSystemPath(cwdGuess)) {
1141
+ console.log(` cwd: ${cwdGuess} ✗ (Windows system path — see B8)`);
1142
+ }
1143
+ else {
1144
+ console.log(` cwd: ${cwdGuess} ✓`);
1145
+ }
1146
+ console.log("");
1147
+ }
1148
+ catch {
1149
+ /* health checks are best-effort */
1150
+ }
930
1151
  process.exit(0);
931
1152
  }
932
1153
  export async function handleInit(targetDir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.32.0",
3
+ "version": "0.33.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",