token-pilot 0.28.3 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +75 -0
  4. package/README.md +39 -390
  5. package/agents/tp-api-surface-tracker.md +4 -2
  6. package/agents/tp-audit-scanner.md +4 -2
  7. package/agents/tp-commit-writer.md +4 -2
  8. package/agents/tp-context-engineer.md +4 -2
  9. package/agents/tp-dead-code-finder.md +4 -2
  10. package/agents/tp-debugger.md +4 -2
  11. package/agents/tp-dep-health.md +4 -2
  12. package/agents/tp-doc-writer.md +4 -2
  13. package/agents/tp-history-explorer.md +4 -2
  14. package/agents/tp-impact-analyzer.md +4 -2
  15. package/agents/tp-incident-timeline.md +4 -2
  16. package/agents/tp-incremental-builder.md +4 -2
  17. package/agents/tp-migration-scout.md +4 -2
  18. package/agents/tp-onboard.md +4 -2
  19. package/agents/tp-performance-profiler.md +4 -2
  20. package/agents/tp-pr-reviewer.md +4 -2
  21. package/agents/tp-refactor-planner.md +4 -2
  22. package/agents/tp-review-impact.md +4 -2
  23. package/agents/tp-run.md +4 -2
  24. package/agents/tp-session-restorer.md +4 -2
  25. package/agents/tp-ship-coordinator.md +4 -2
  26. package/agents/tp-spec-writer.md +4 -2
  27. package/agents/tp-test-coverage-gapper.md +4 -2
  28. package/agents/tp-test-triage.md +4 -2
  29. package/agents/tp-test-writer.md +4 -2
  30. package/dist/cli/tool-audit.d.ts +5 -0
  31. package/dist/cli/tool-audit.js +9 -1
  32. package/dist/core/policy-engine.d.ts +1 -5
  33. package/dist/core/policy-engine.js +9 -24
  34. package/dist/hooks/pre-bash.d.ts +13 -1
  35. package/dist/hooks/pre-bash.js +56 -1
  36. package/dist/hooks/pre-grep.d.ts +2 -1
  37. package/dist/hooks/pre-grep.js +3 -1
  38. package/dist/index.js +4 -2
  39. package/dist/server/enforcement-mode.d.ts +47 -0
  40. package/dist/server/enforcement-mode.js +59 -0
  41. package/dist/server/tool-definitions.d.ts +20 -0
  42. package/dist/server/tool-definitions.js +113 -10
  43. package/dist/server/tool-profiles.d.ts +19 -1
  44. package/dist/server/tool-profiles.js +38 -4
  45. package/dist/server.d.ts +2 -0
  46. package/dist/server.js +68 -16
  47. package/docs/agents.md +82 -0
  48. package/docs/configuration.md +117 -0
  49. package/docs/hooks.md +99 -0
  50. package/docs/installation.md +143 -0
  51. package/docs/tools.md +61 -0
  52. package/package.json +2 -2
@@ -5,7 +5,6 @@
5
5
  */
6
6
  export const DEFAULT_POLICIES = {
7
7
  preferCheapReads: true,
8
- requireReadForEditBeforeEdit: true,
9
8
  cacheProjectOverview: true,
10
9
  maxFullFileReads: 10,
11
10
  warnOnLargeReads: true,
@@ -14,14 +13,11 @@ export const DEFAULT_POLICIES = {
14
13
  compactionTokenThreshold: 8000,
15
14
  };
16
15
  /** Full-file read tools that count toward maxFullFileReads */
17
- const FULL_READ_TOOLS = new Set([
18
- 'smart_read',
19
- 'smart_read_many',
20
- ]);
16
+ const FULL_READ_TOOLS = new Set(["smart_read", "smart_read_many"]);
21
17
  /** Tools that indicate a cheaper alternative may exist */
22
18
  const EXPENSIVE_TOOLS = {
23
- smart_read: 'Consider read_symbol() or read_range() for targeted reads',
24
- smart_read_many: 'Consider reading files individually with read_symbol()',
19
+ smart_read: "Consider read_symbol() or read_range() for targeted reads",
20
+ smart_read_many: "Consider reading files individually with read_symbol()",
25
21
  };
26
22
  /**
27
23
  * Check policy rules and return advisory messages.
@@ -33,7 +29,7 @@ export function checkPolicy(policy, tool, context) {
33
29
  FULL_READ_TOOLS.has(tool) &&
34
30
  context.fullFileReadsCount >= policy.maxFullFileReads) {
35
31
  return {
36
- level: 'warn',
32
+ level: "warn",
37
33
  message: `POLICY: ${context.fullFileReadsCount} full-file reads this session (limit: ${policy.maxFullFileReads}). Consider read_symbol() or read_range() for targeted access.`,
38
34
  };
39
35
  }
@@ -41,7 +37,7 @@ export function checkPolicy(policy, tool, context) {
41
37
  if (policy.warnOnLargeReads &&
42
38
  context.tokensReturned > policy.largeReadThreshold) {
43
39
  return {
44
- level: 'info',
40
+ level: "info",
45
41
  message: `POLICY: Large response (~${context.tokensReturned} tokens). Future reads on this file: use read_symbol() or read_range() for targeted access.`,
46
42
  };
47
43
  }
@@ -50,29 +46,18 @@ export function checkPolicy(policy, tool, context) {
50
46
  // Only advise when token count is high enough to matter
51
47
  if (context.tokensReturned > 500) {
52
48
  return {
53
- level: 'info',
49
+ level: "info",
54
50
  message: `POLICY: ${EXPENSIVE_TOOLS[tool]}`,
55
51
  };
56
52
  }
57
53
  }
58
- // 4. Require read_for_edit before edit
59
- if (policy.requireReadForEditBeforeEdit &&
60
- tool === 'edit' &&
61
- context.editTargetPath &&
62
- context.readForEditCalled &&
63
- !context.readForEditCalled.has(context.editTargetPath)) {
64
- return {
65
- level: 'info',
66
- message: `POLICY: Consider using read_for_edit("${context.editTargetPath}") before editing to get precise edit context.`,
67
- };
68
- }
69
- // 5. Session compaction advisory — by call count
54
+ // 4. Session compaction advisory — by call count
70
55
  if (policy.compactionCallThreshold > 0 &&
71
56
  context.totalCallCount !== undefined &&
72
57
  context.totalCallCount > 0 &&
73
58
  context.totalCallCount % policy.compactionCallThreshold === 0) {
74
59
  return {
75
- level: 'info',
60
+ level: "info",
76
61
  message: `COMPACTION: ${context.totalCallCount} tool calls this session. Consider calling session_snapshot() to capture state, then compact context.`,
77
62
  };
78
63
  }
@@ -84,7 +69,7 @@ export function checkPolicy(policy, tool, context) {
84
69
  context.totalCallCount % 5 === 0 // don't spam every call, check every 5th
85
70
  ) {
86
71
  return {
87
- level: 'info',
72
+ level: "info",
88
73
  message: `COMPACTION: ~${context.totalTokensReturned} tokens returned this session. Consider calling session_snapshot() to capture state, then compact context.`,
89
74
  };
90
75
  }
@@ -24,6 +24,7 @@
24
24
  * `bash -c`, heredocs, or eval'd strings slip through. Acceptable for
25
25
  * v0.28.0; tighten only if tool-audit shows repeated escapes.
26
26
  */
27
+ import type { EnforcementMode } from "../server/enforcement-mode.js";
27
28
  export interface PreBashInput {
28
29
  tool_name?: string;
29
30
  tool_input?: {
@@ -37,7 +38,18 @@ export type PreBashDecision = {
37
38
  kind: "deny";
38
39
  reason: string;
39
40
  };
41
+ /**
42
+ * v0.29.0 — expose wrapped commands. Opus 4.7's v0.28.2 verification
43
+ * report showed escape patterns: `bash -c "cat src/foo.ts"`,
44
+ * `eval "..."`, `for f in *.ts; do cat $f; done` all slipped through
45
+ * our heuristics because the dangerous call sat inside quotes / a loop
46
+ * body. Unwrap those before matching.
47
+ *
48
+ * Returns the original command PLUS the extracted inner body for each
49
+ * wrapper found. Duplication is fine — detectHeavyPattern is pure.
50
+ */
51
+ export declare function extractWrappedCommands(command: string): string[];
40
52
  export declare function detectHeavyPattern(command: string): PreBashDecision;
41
- export declare function decidePreBash(input: PreBashInput): PreBashDecision;
53
+ export declare function decidePreBash(input: PreBashInput, mode?: EnforcementMode): PreBashDecision;
42
54
  export declare function renderPreBashOutput(decision: PreBashDecision): string | null;
43
55
  //# sourceMappingURL=pre-bash.d.ts.map
@@ -32,7 +32,60 @@ function invokes(command, utility) {
32
32
  const re = new RegExp(`(^|[;&|\\n]\\s*)${utility}(\\s|$)`, "m");
33
33
  return re.test(command);
34
34
  }
35
+ /**
36
+ * v0.29.0 — expose wrapped commands. Opus 4.7's v0.28.2 verification
37
+ * report showed escape patterns: `bash -c "cat src/foo.ts"`,
38
+ * `eval "..."`, `for f in *.ts; do cat $f; done` all slipped through
39
+ * our heuristics because the dangerous call sat inside quotes / a loop
40
+ * body. Unwrap those before matching.
41
+ *
42
+ * Returns the original command PLUS the extracted inner body for each
43
+ * wrapper found. Duplication is fine — detectHeavyPattern is pure.
44
+ */
45
+ export function extractWrappedCommands(command) {
46
+ const out = [command];
47
+ // bash -c "..." / sh -c "..." / zsh -c "..."
48
+ for (const shell of ["bash", "sh", "zsh"]) {
49
+ const re = new RegExp(`\\b${shell}\\s+-c\\s+(?:"([^"]+)"|'([^']+)')`, "g");
50
+ for (const m of command.matchAll(re)) {
51
+ const inner = m[1] ?? m[2];
52
+ if (inner)
53
+ out.push(inner);
54
+ }
55
+ }
56
+ // eval "..." / eval '...'
57
+ for (const m of command.matchAll(/\beval\s+(?:"([^"]+)"|'([^']+)')/g)) {
58
+ const inner = m[1] ?? m[2];
59
+ if (inner)
60
+ out.push(inner);
61
+ }
62
+ // for LOOP with body: `for X in Y; do BODY; done` — extract BODY
63
+ // Also covers `while COND; do BODY; done` and `until COND; do BODY; done`
64
+ for (const m of command.matchAll(/\b(?:for|while|until)\b[^;]*;\s*do\s+(.+?)\s*;?\s*done\b/gs)) {
65
+ const body = m[1];
66
+ if (body)
67
+ out.push(body);
68
+ }
69
+ return out;
70
+ }
35
71
  export function detectHeavyPattern(command) {
72
+ const cmd = command.trim();
73
+ if (!cmd)
74
+ return { kind: "allow" };
75
+ // v0.29.0: check each of the original + any unwrapped inner commands.
76
+ // First deny wins.
77
+ const candidates = extractWrappedCommands(cmd);
78
+ if (candidates.length > 1) {
79
+ // Check only the unwrapped inners; the original is handled below.
80
+ for (let i = 1; i < candidates.length; i++) {
81
+ const inner = detectHeavyPatternSingle(candidates[i]);
82
+ if (inner.kind === "deny")
83
+ return inner;
84
+ }
85
+ }
86
+ return detectHeavyPatternSingle(cmd);
87
+ }
88
+ function detectHeavyPatternSingle(command) {
36
89
  const cmd = command.trim();
37
90
  if (!cmd)
38
91
  return { kind: "allow" };
@@ -90,7 +143,9 @@ export function detectHeavyPattern(command) {
90
143
  }
91
144
  return { kind: "allow" };
92
145
  }
93
- export function decidePreBash(input) {
146
+ export function decidePreBash(input, mode = "deny") {
147
+ if (mode === "advisory")
148
+ return { kind: "allow" };
94
149
  if (input.tool_name !== "Bash")
95
150
  return { kind: "allow" };
96
151
  const cmd = input.tool_input?.command;
@@ -20,6 +20,7 @@
20
20
  * find_usages after the block, we keep it. If they bypass via `-E` or
21
21
  * raw shell, we soften to advisory.
22
22
  */
23
+ import type { EnforcementMode } from "../server/enforcement-mode.js";
23
24
  export interface PreGrepInput {
24
25
  tool_name?: string;
25
26
  tool_input?: {
@@ -51,7 +52,7 @@ export declare function isSymbolLikePattern(pattern: string): boolean;
51
52
  * Pure decision function. Given a PreToolUse hook input for Grep,
52
53
  * return whether to allow or deny (with a suggestion).
53
54
  */
54
- export declare function decidePreGrep(input: PreGrepInput): PreGrepDecision;
55
+ export declare function decidePreGrep(input: PreGrepInput, mode?: EnforcementMode): PreGrepDecision;
55
56
  /**
56
57
  * Render the Claude Code hook JSON response.
57
58
  */
@@ -64,7 +64,9 @@ export function isSymbolLikePattern(pattern) {
64
64
  * Pure decision function. Given a PreToolUse hook input for Grep,
65
65
  * return whether to allow or deny (with a suggestion).
66
66
  */
67
- export function decidePreGrep(input) {
67
+ export function decidePreGrep(input, mode = "deny") {
68
+ if (mode === "advisory")
69
+ return { kind: "allow" };
68
70
  if (input.tool_name !== "Grep")
69
71
  return { kind: "allow" };
70
72
  const pattern = input.tool_input?.pattern;
package/dist/index.js CHANGED
@@ -52,6 +52,7 @@ import { assessClaudeMd } from "./cli/claudemd-hygiene.js";
52
52
  import { decidePostBashAdvice, renderPostBashHookOutput, } from "./hooks/post-bash.js";
53
53
  import { decidePreBash, renderPreBashOutput } from "./hooks/pre-bash.js";
54
54
  import { decidePreGrep, renderPreGrepOutput } from "./hooks/pre-grep.js";
55
+ import { parseEnforcementMode } from "./server/enforcement-mode.js";
55
56
  const execFileAsync = promisify(execFile);
56
57
  export const CODE_EXTENSIONS = new Set([
57
58
  "ts",
@@ -152,7 +153,7 @@ export async function main(cliArgs = process.argv.slice(2)) {
152
153
  try {
153
154
  const stdin = readFileSync(0, "utf-8");
154
155
  const input = JSON.parse(stdin);
155
- const decision = decidePreBash(input);
156
+ const decision = decidePreBash(input, parseEnforcementMode(process.env.TOKEN_PILOT_MODE));
156
157
  const rendered = renderPreBashOutput(decision);
157
158
  if (rendered)
158
159
  process.stdout.write(rendered);
@@ -169,7 +170,7 @@ export async function main(cliArgs = process.argv.slice(2)) {
169
170
  try {
170
171
  const stdin = readFileSync(0, "utf-8");
171
172
  const input = JSON.parse(stdin);
172
- const decision = decidePreGrep(input);
173
+ const decision = decidePreGrep(input, parseEnforcementMode(process.env.TOKEN_PILOT_MODE));
173
174
  const rendered = renderPreGrepOutput(decision);
174
175
  if (rendered)
175
176
  process.stdout.write(rendered);
@@ -386,6 +387,7 @@ export async function startServer(cliArgs = process.argv.slice(2)) {
386
387
  });
387
388
  const server = await createServer(projectRoot, {
388
389
  skipAstIndex: isDangerousRoot(projectRoot),
390
+ enforcementMode: parseEnforcementMode(process.env.TOKEN_PILOT_MODE),
389
391
  });
390
392
  const transport = new StdioServerTransport();
391
393
  await server.connect(transport);
@@ -0,0 +1,47 @@
1
+ /**
2
+ * v0.30.0 — TOKEN_PILOT_MODE enforcement modes.
3
+ *
4
+ * Controls how aggressively token-pilot blocks heavy native tools and
5
+ * caps MCP tool output sizes. Three modes:
6
+ *
7
+ * advisory — hooks always allow, no MCP output caps. Observation-only.
8
+ * Use when measuring baseline token usage or debugging.
9
+ *
10
+ * deny — DEFAULT. Hooks deny heavy Bash/Grep patterns and suggest
11
+ * cheaper MCP alternatives. No auto-caps on MCP output.
12
+ * This is the "smart redirect" mode — the agent learns the
13
+ * right tool but can still produce large MCP responses.
14
+ *
15
+ * strict — deny + MCP output auto-caps. smart_read is capped at
16
+ * max_tokens=2000 when the caller doesn't set it; explore_area
17
+ * defaults include=['outline'] when the caller doesn't set it.
18
+ * Cap values are v0.30.0 initial estimates — tune from real
19
+ * tool-audit data in a follow-up PR (#8).
20
+ *
21
+ * Set via TOKEN_PILOT_MODE environment variable (case-insensitive, trimmed).
22
+ * Unknown values fall back to "deny" with a warning.
23
+ *
24
+ * Separate from `hooks.mode` (HookMode) which controls only the PreToolUse:Read
25
+ * hook (deny-enhanced vs advisory for large file reads). TOKEN_PILOT_MODE
26
+ * covers Bash and Grep hooks plus MCP output caps.
27
+ */
28
+ export type EnforcementMode = "advisory" | "deny" | "strict";
29
+ export declare const ENFORCEMENT_MODE_NAMES: readonly ["advisory", "deny", "strict"];
30
+ /**
31
+ * Parse TOKEN_PILOT_MODE from an env-var string. Returns "deny" for
32
+ * missing or empty values. Emits a warning for unrecognised values.
33
+ */
34
+ export declare function parseEnforcementMode(raw: string | undefined, warn?: (msg: string) => void): EnforcementMode;
35
+ /**
36
+ * The cap applied to smart_read max_tokens in strict mode when the
37
+ * caller has not supplied an explicit max_tokens.
38
+ * v0.30.0 initial estimate — tune from tool-audit data.
39
+ */
40
+ export declare const STRICT_SMART_READ_MAX_TOKENS = 2000;
41
+ /**
42
+ * The include sections applied to explore_area in strict mode when the
43
+ * caller has not supplied an explicit include array.
44
+ * v0.30.0 initial estimate — outline-only keeps footprint minimal.
45
+ */
46
+ export declare const STRICT_EXPLORE_AREA_INCLUDE: Array<"outline" | "imports" | "tests" | "changes">;
47
+ //# sourceMappingURL=enforcement-mode.d.ts.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * v0.30.0 — TOKEN_PILOT_MODE enforcement modes.
3
+ *
4
+ * Controls how aggressively token-pilot blocks heavy native tools and
5
+ * caps MCP tool output sizes. Three modes:
6
+ *
7
+ * advisory — hooks always allow, no MCP output caps. Observation-only.
8
+ * Use when measuring baseline token usage or debugging.
9
+ *
10
+ * deny — DEFAULT. Hooks deny heavy Bash/Grep patterns and suggest
11
+ * cheaper MCP alternatives. No auto-caps on MCP output.
12
+ * This is the "smart redirect" mode — the agent learns the
13
+ * right tool but can still produce large MCP responses.
14
+ *
15
+ * strict — deny + MCP output auto-caps. smart_read is capped at
16
+ * max_tokens=2000 when the caller doesn't set it; explore_area
17
+ * defaults include=['outline'] when the caller doesn't set it.
18
+ * Cap values are v0.30.0 initial estimates — tune from real
19
+ * tool-audit data in a follow-up PR (#8).
20
+ *
21
+ * Set via TOKEN_PILOT_MODE environment variable (case-insensitive, trimmed).
22
+ * Unknown values fall back to "deny" with a warning.
23
+ *
24
+ * Separate from `hooks.mode` (HookMode) which controls only the PreToolUse:Read
25
+ * hook (deny-enhanced vs advisory for large file reads). TOKEN_PILOT_MODE
26
+ * covers Bash and Grep hooks plus MCP output caps.
27
+ */
28
+ export const ENFORCEMENT_MODE_NAMES = [
29
+ "advisory",
30
+ "deny",
31
+ "strict",
32
+ ];
33
+ /**
34
+ * Parse TOKEN_PILOT_MODE from an env-var string. Returns "deny" for
35
+ * missing or empty values. Emits a warning for unrecognised values.
36
+ */
37
+ export function parseEnforcementMode(raw, warn = (m) => process.stderr.write(m + "\n")) {
38
+ if (!raw || raw.trim() === "")
39
+ return "deny";
40
+ const v = raw.trim().toLowerCase();
41
+ if (v === "advisory" || v === "deny" || v === "strict")
42
+ return v;
43
+ warn(`[token-pilot] Unknown TOKEN_PILOT_MODE="${raw}", falling back to "deny". ` +
44
+ `Valid values: advisory | deny | strict.`);
45
+ return "deny";
46
+ }
47
+ /**
48
+ * The cap applied to smart_read max_tokens in strict mode when the
49
+ * caller has not supplied an explicit max_tokens.
50
+ * v0.30.0 initial estimate — tune from tool-audit data.
51
+ */
52
+ export const STRICT_SMART_READ_MAX_TOKENS = 2000;
53
+ /**
54
+ * The include sections applied to explore_area in strict mode when the
55
+ * caller has not supplied an explicit include array.
56
+ * v0.30.0 initial estimate — outline-only keeps footprint minimal.
57
+ */
58
+ export const STRICT_EXPLORE_AREA_INCLUDE = ["outline"];
59
+ //# sourceMappingURL=enforcement-mode.js.map
@@ -1,6 +1,26 @@
1
1
  /**
2
2
  * MCP tool definitions and system instructions.
3
3
  * Pure static data — no runtime dependencies.
4
+ *
5
+ * v0.30.0 — Profile-specific instructions. Each profile advertises only
6
+ * the tools it includes; instructions are trimmed to match so the agent
7
+ * doesn't hallucinate tools that aren't in tools/list.
8
+ *
9
+ * minimal — 5 core tools, minimal context overhead
10
+ * nav — 10 exploration tools, no editing
11
+ * edit — nav + 6 edit-prep tools (DEFAULT)
12
+ * full — everything including audit tools
13
+ */
14
+ import type { ToolProfile } from "./tool-profiles.js";
15
+ /**
16
+ * Select MCP instructions for the given tool profile.
17
+ * Each profile only mentions tools that are actually advertised in its
18
+ * tools/list — prevents the agent from calling tools it can't see.
19
+ */
20
+ export declare function getMcpInstructions(profile: ToolProfile): string;
21
+ /**
22
+ * @deprecated Use getMcpInstructions(profile) instead.
23
+ * Kept for backward-compat — resolves to the full profile instructions.
4
24
  */
5
25
  export declare const MCP_INSTRUCTIONS: string;
6
26
  export declare const TOOL_DEFINITIONS: ({
@@ -1,8 +1,89 @@
1
- /**
2
- * MCP tool definitions and system instructions.
3
- * Pure static data — no runtime dependencies.
4
- */
5
- export const MCP_INSTRUCTIONS = [
1
+ // ---------------------------------------------------------------------------
2
+ // Minimal profile 5 essential tools, near-zero instructions overhead
3
+ // ---------------------------------------------------------------------------
4
+ const MCP_INSTRUCTIONS_MINIMAL = [
5
+ "Token Pilot token-efficient code reading. ALWAYS prefer these tools over Read/cat/grep.",
6
+ "",
7
+ "TOOLS:",
8
+ "• smart_read(path) — read a code file (NOT cat/Read — returns structure, 60-80% fewer tokens)",
9
+ "• read_symbol(path, symbol) — read ONE function/class body (NOT the whole file)",
10
+ "• find_usages(symbol) — find where a symbol is defined, imported, or used",
11
+ "• smart_diff — review git changes mapped to functions/classes (NOT git diff)",
12
+ "• smart_log — structured commit history (NOT git log)",
13
+ "",
14
+ "USE Read/Grep ONLY for: non-code configs (JSON, YAML, markdown), regex patterns.",
15
+ ].join("\n");
16
+ // ---------------------------------------------------------------------------
17
+ // Nav profile — exploration only, no edit-prep tools
18
+ // ---------------------------------------------------------------------------
19
+ const MCP_INSTRUCTIONS_NAV = [
20
+ "Token Pilot — token-efficient code reading (saves 60-80% tokens). ALWAYS prefer these tools over Read/cat/grep.",
21
+ "",
22
+ "DECISION RULES — pick the first match:",
23
+ "1. New codebase / unfamiliar project → project_overview",
24
+ "2. Starting work on a directory → explore_area (outline + imports + tests + git log in one call)",
25
+ "3. Need to read a code file → smart_read (NOT Read/cat — returns structure, 60-80% fewer tokens)",
26
+ ' - For navigation/browsing: smart_read(scope="nav") — names + lines only, 2-3x smaller',
27
+ ' - For public API overview: smart_read(scope="exports")',
28
+ "4. Need one function/class body → read_symbol (loads only that symbol, NOT the whole file)",
29
+ "5. Find where a symbol is used → find_usages (semantic: definitions + imports + usages)",
30
+ ' - For initial discovery: find_usages(mode="list") — file:line only, 5-10x smaller',
31
+ "6. Understand file dependencies → related_files (imports, importers, tests — ranked by relevance)",
32
+ "7. List all symbols in a directory → outline (classes, functions, methods in one call)",
33
+ "8. Review git changes → smart_diff (NOT git diff — maps changes to functions/classes)",
34
+ "9. Commit history → smart_log (NOT git log — structured with categories)",
35
+ "10. Module architecture → module_info (deps, dependents, public API)",
36
+ "11. Read markdown/yaml/json/csv section → read_section (loads one heading/key/row-range, NOT the whole file)",
37
+ "12. Long session / before compaction → session_snapshot (<200 token state capture)",
38
+ "",
39
+ "USE Read/Grep ONLY for: regex text search → Grep | exact raw content → Read",
40
+ "",
41
+ "WORKFLOW:",
42
+ "• Explore: project_overview → explore_area → smart_read → read_symbol",
43
+ ].join("\n");
44
+ // ---------------------------------------------------------------------------
45
+ // Edit profile — nav + batch reads + edit-prep (DEFAULT)
46
+ // ---------------------------------------------------------------------------
47
+ const MCP_INSTRUCTIONS_EDIT = [
48
+ "Token Pilot — token-efficient code reading (saves 60-80% tokens). ALWAYS prefer these tools over Read/cat/grep.",
49
+ "",
50
+ "DECISION RULES — pick the first match:",
51
+ "1. New codebase / unfamiliar project → project_overview",
52
+ "2. Starting work on a directory → explore_area (outline + imports + tests + git log in one call)",
53
+ "3. Need to read a code file → smart_read (NOT Read/cat — returns structure, 60-80% fewer tokens)",
54
+ ' - For navigation/browsing: smart_read(scope="nav") — names + lines only, 2-3x smaller',
55
+ ' - For public API overview: smart_read(scope="exports")',
56
+ "4. Need one function/class body → read_symbol (loads only that symbol, NOT the whole file)",
57
+ " - Preparing edit? Add include_edit_context=true to skip separate read_for_edit call",
58
+ "5. Need MULTIPLE function/class bodies from same file → read_symbols (batch — one call instead of N)",
59
+ "6. Preparing an edit → read_for_edit (returns exact text for Edit old_string)",
60
+ "7. Verify edits after editing → read_diff (only changed hunks — REQUIRES smart_read BEFORE editing)",
61
+ "8. Multiple files at once → smart_read_many (batch up to 20 files)",
62
+ "9. Find where a symbol is used → find_usages (semantic: definitions + imports + usages)",
63
+ ' - For initial discovery: find_usages(mode="list") — file:line only, 5-10x smaller',
64
+ "10. Understand file dependencies → related_files (imports, importers, tests — ranked by relevance)",
65
+ "11. List all symbols in a directory → outline (classes, functions, methods in one call)",
66
+ "12. Review git changes → smart_diff (NOT git diff — maps changes to functions/classes)",
67
+ "13. Commit history → smart_log (NOT git log — structured with categories)",
68
+ "14. Module architecture → module_info (deps, dependents, public API)",
69
+ "15. Read markdown/yaml/json/csv section → read_section (loads one heading/key/row-range, NOT the whole file)",
70
+ ' - For editing sections: read_for_edit(path, section="Section Name")',
71
+ "16. Long session / before compaction → session_snapshot (capture goal, decisions, confirmed facts, files, next step as <200 token block)",
72
+ " - Budget-constrained? Use smart_read(max_tokens=N) to auto-downgrade output size",
73
+ "",
74
+ "USE Read/Grep ONLY for: regex text search → Grep | exact raw content → Read",
75
+ "",
76
+ "WORKFLOWS:",
77
+ "• Explore: project_overview → explore_area → smart_read → read_symbol",
78
+ "• Edit: smart_read → read_symbol(include_edit_context=true) → Edit → read_diff",
79
+ "• Docs: smart_read (outline) → read_section → read_for_edit(section=) → Edit → read_diff",
80
+ "• Refactor: find_usages → read_symbols → read_for_edit → Edit",
81
+ "• Long session: session_snapshot → compact context → continue with minimal state",
82
+ ].join("\n");
83
+ // ---------------------------------------------------------------------------
84
+ // Full profile — all tools including audit (code_audit, find_unused, test_summary)
85
+ // ---------------------------------------------------------------------------
86
+ const MCP_INSTRUCTIONS_FULL = [
6
87
  "Token Pilot — token-efficient code reading (saves 60-80% tokens). ALWAYS prefer these tools over Read/cat/grep.",
7
88
  "",
8
89
  "DECISION RULES — pick the first match:",
@@ -28,11 +109,11 @@ export const MCP_INSTRUCTIONS = [
28
109
  "16. Dead code → find_unused (unreferenced symbols across project)",
29
110
  "17. Module architecture → module_info (deps, dependents, public API)",
30
111
  "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")',
112
+ ' - For editing sections: read_for_edit(path, section="Section Name")',
32
113
  "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",
114
+ " - Budget-constrained? Use smart_read(max_tokens=N) to auto-downgrade output size",
34
115
  "",
35
- "USE DEFAULT TOOLS ONLY FOR: regex text search → Grep | exact raw content → Read | non-code configs → Read",
116
+ "USE Read/Grep ONLY for: regex text search → Grep | exact raw content → Read | non-code configs → Read",
36
117
  "",
37
118
  "WORKFLOWS:",
38
119
  "• Explore: project_overview → explore_area → smart_read → read_symbol",
@@ -42,6 +123,28 @@ export const MCP_INSTRUCTIONS = [
42
123
  "• Audit: code_audit + find_unused + Grep (for regex patterns)",
43
124
  "• Long session: session_snapshot → compact context → continue with minimal state",
44
125
  ].join("\n");
126
+ /**
127
+ * Select MCP instructions for the given tool profile.
128
+ * Each profile only mentions tools that are actually advertised in its
129
+ * tools/list — prevents the agent from calling tools it can't see.
130
+ */
131
+ export function getMcpInstructions(profile) {
132
+ switch (profile) {
133
+ case "minimal":
134
+ return MCP_INSTRUCTIONS_MINIMAL;
135
+ case "nav":
136
+ return MCP_INSTRUCTIONS_NAV;
137
+ case "edit":
138
+ return MCP_INSTRUCTIONS_EDIT;
139
+ case "full":
140
+ return MCP_INSTRUCTIONS_FULL;
141
+ }
142
+ }
143
+ /**
144
+ * @deprecated Use getMcpInstructions(profile) instead.
145
+ * Kept for backward-compat — resolves to the full profile instructions.
146
+ */
147
+ export const MCP_INSTRUCTIONS = MCP_INSTRUCTIONS_FULL;
45
148
  export const TOOL_DEFINITIONS = [
46
149
  // --- Core reading tools ---
47
150
  {
@@ -499,7 +602,7 @@ export const TOOL_DEFINITIONS = [
499
602
  },
500
603
  {
501
604
  name: "smart_log",
502
- description: "Use INSTEAD OF raw git log. Structured commit history with category detection (feat/fix/refactor/docs), file stats, author breakdown. Filters by path and ref.",
605
+ description: "Use INSTEAD OF raw git log. Structured commit history with category detection (feat/fix/refactor/docs), file stats, author breakdown. Filters by path and ref. HEADS UP: two verification runs measured this tool at ~39% token reduction (borderline — vs 95-99% for outline/smart_diff). Cumulative data being gathered — tool may be dropped or redesigned in v0.30.0 if numbers don't improve. Prefer scoping with `path` or `count` to tighten savings.",
503
606
  inputSchema: {
504
607
  type: "object",
505
608
  properties: {
@@ -581,7 +684,7 @@ export const TOOL_DEFINITIONS = [
581
684
  },
582
685
  {
583
686
  name: "session_budget",
584
- description: "Report Read-hook pressure for this session: suppressed tokens so far, reference budget, burn fraction (0..1), and the effective denyThreshold the adaptive curve would apply right now. NOTE: burnFraction measures hook activity, not actual context-window occupancy. Useful to decide when to tighten further before a big read.",
687
+ description: "META / info-only: reports Read-hook pressure for this session (suppressed tokens, reference budget, burn fraction, effective denyThreshold). Does NOT save tokens itself — this is diagnostic, use to decide when to tighten before a big read. NOTE: burnFraction measures hook activity, not actual context-window occupancy.",
585
688
  inputSchema: {
586
689
  type: "object",
587
690
  properties: {
@@ -21,7 +21,7 @@
21
21
  * Selection: TOKEN_PILOT_PROFILE=nav|edit|full env var. Unknown values
22
22
  * fall back to full with a stderr warning. Silent on missing env.
23
23
  */
24
- export type ToolProfile = "full" | "nav" | "edit";
24
+ export type ToolProfile = "full" | "nav" | "edit" | "minimal";
25
25
  export declare const PROFILE_NAMES: readonly ToolProfile[];
26
26
  /**
27
27
  * Meta-tools — diagnostic / self-observation tools that must be visible
@@ -30,6 +30,12 @@ export declare const PROFILE_NAMES: readonly ToolProfile[];
30
30
  * would you trust the savings number?
31
31
  */
32
32
  export declare const META_TOOLS: ReadonlySet<string>;
33
+ /**
34
+ * Minimal profile — 5 core tools for emergency / context-constrained sessions.
35
+ * Token overhead: tools/list is tiny; instructions are ~80 tokens vs ~350 for full.
36
+ * Use TOKEN_PILOT_PROFILE=minimal when the agent's context budget is nearly full.
37
+ */
38
+ export declare const MINIMAL_TOOLS: ReadonlySet<string>;
33
39
  /** Minimum nav profile — exploration only, no editing support. */
34
40
  export declare const NAV_TOOLS: ReadonlySet<string>;
35
41
  /** Edit profile adds batch reads + edit-preparation tools. */
@@ -48,5 +54,17 @@ export declare function filterToolsByProfile<T extends {
48
54
  * Parse the TOKEN_PILOT_PROFILE env value. Unknown values get a warning
49
55
  * and fall back to full — we never silently apply a guess.
50
56
  */
57
+ /**
58
+ * Parse the TOKEN_PILOT_PROFILE env value.
59
+ *
60
+ * Default changed in v0.30.0: full → edit.
61
+ * Rationale: 'full' was exposing 22 tools + full instruction set on every
62
+ * session, burning ~3 k tokens before any work. 'edit' covers 99% of
63
+ * development workflows (reading + writing code). Switch to 'full' only
64
+ * when you need audit tools (code_audit, find_unused, test_summary).
65
+ *
66
+ * Unknown values fall back to 'edit' with a stderr warning — we never
67
+ * silently apply a guess.
68
+ */
51
69
  export declare function parseProfileEnv(envValue: string | undefined, warn?: (msg: string) => void): ToolProfile;
52
70
  //# sourceMappingURL=tool-profiles.d.ts.map