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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +75 -0
- package/README.md +39 -390
- package/agents/tp-api-surface-tracker.md +4 -2
- package/agents/tp-audit-scanner.md +4 -2
- package/agents/tp-commit-writer.md +4 -2
- package/agents/tp-context-engineer.md +4 -2
- package/agents/tp-dead-code-finder.md +4 -2
- package/agents/tp-debugger.md +4 -2
- package/agents/tp-dep-health.md +4 -2
- package/agents/tp-doc-writer.md +4 -2
- package/agents/tp-history-explorer.md +4 -2
- package/agents/tp-impact-analyzer.md +4 -2
- package/agents/tp-incident-timeline.md +4 -2
- package/agents/tp-incremental-builder.md +4 -2
- package/agents/tp-migration-scout.md +4 -2
- package/agents/tp-onboard.md +4 -2
- package/agents/tp-performance-profiler.md +4 -2
- package/agents/tp-pr-reviewer.md +4 -2
- package/agents/tp-refactor-planner.md +4 -2
- package/agents/tp-review-impact.md +4 -2
- package/agents/tp-run.md +4 -2
- package/agents/tp-session-restorer.md +4 -2
- package/agents/tp-ship-coordinator.md +4 -2
- package/agents/tp-spec-writer.md +4 -2
- package/agents/tp-test-coverage-gapper.md +4 -2
- package/agents/tp-test-triage.md +4 -2
- package/agents/tp-test-writer.md +4 -2
- package/dist/cli/tool-audit.d.ts +5 -0
- package/dist/cli/tool-audit.js +9 -1
- package/dist/core/policy-engine.d.ts +1 -5
- package/dist/core/policy-engine.js +9 -24
- package/dist/hooks/pre-bash.d.ts +13 -1
- package/dist/hooks/pre-bash.js +56 -1
- package/dist/hooks/pre-grep.d.ts +2 -1
- package/dist/hooks/pre-grep.js +3 -1
- package/dist/index.js +4 -2
- package/dist/server/enforcement-mode.d.ts +47 -0
- package/dist/server/enforcement-mode.js +59 -0
- package/dist/server/tool-definitions.d.ts +20 -0
- package/dist/server/tool-definitions.js +113 -10
- package/dist/server/tool-profiles.d.ts +19 -1
- package/dist/server/tool-profiles.js +38 -4
- package/dist/server.d.ts +2 -0
- package/dist/server.js +68 -16
- package/docs/agents.md +82 -0
- package/docs/configuration.md +117 -0
- package/docs/hooks.md +99 -0
- package/docs/installation.md +143 -0
- package/docs/tools.md +61 -0
- 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:
|
|
24
|
-
smart_read_many:
|
|
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:
|
|
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:
|
|
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:
|
|
49
|
+
level: "info",
|
|
54
50
|
message: `POLICY: ${EXPENSIVE_TOOLS[tool]}`,
|
|
55
51
|
};
|
|
56
52
|
}
|
|
57
53
|
}
|
|
58
|
-
// 4.
|
|
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:
|
|
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:
|
|
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
|
}
|
package/dist/hooks/pre-bash.d.ts
CHANGED
|
@@ -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
|
package/dist/hooks/pre-bash.js
CHANGED
|
@@ -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;
|
package/dist/hooks/pre-grep.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/hooks/pre-grep.js
CHANGED
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
'
|
|
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
|
-
"
|
|
114
|
+
" - Budget-constrained? Use smart_read(max_tokens=N) to auto-downgrade output size",
|
|
34
115
|
"",
|
|
35
|
-
"USE
|
|
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: "
|
|
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
|