token-pilot 0.30.5 → 0.31.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/agents/tp-api-surface-tracker.md +10 -2
- package/agents/tp-audit-scanner.md +10 -2
- package/agents/tp-commit-writer.md +10 -2
- package/agents/tp-context-engineer.md +10 -2
- package/agents/tp-dead-code-finder.md +10 -2
- package/agents/tp-debugger.md +10 -2
- package/agents/tp-dep-health.md +10 -2
- package/agents/tp-doc-writer.md +10 -2
- package/agents/tp-history-explorer.md +10 -2
- package/agents/tp-impact-analyzer.md +10 -2
- package/agents/tp-incident-timeline.md +10 -2
- package/agents/tp-incremental-builder.md +10 -2
- package/agents/tp-migration-scout.md +10 -2
- package/agents/tp-onboard.md +10 -2
- package/agents/tp-performance-profiler.md +10 -2
- package/agents/tp-pr-reviewer.md +10 -2
- package/agents/tp-refactor-planner.md +10 -2
- package/agents/tp-review-impact.md +10 -2
- package/agents/tp-run.md +10 -2
- package/agents/tp-session-restorer.md +10 -2
- package/agents/tp-ship-coordinator.md +10 -2
- package/agents/tp-spec-writer.md +10 -2
- package/agents/tp-test-coverage-gapper.md +10 -2
- package/agents/tp-test-triage.md +10 -2
- package/agents/tp-test-writer.md +10 -2
- package/dist/cli/stats.d.ts +2 -0
- package/dist/cli/stats.js +46 -1
- package/dist/core/agent-matcher.d.ts +115 -0
- package/dist/core/agent-matcher.js +326 -0
- package/dist/core/event-log.d.ts +14 -1
- package/dist/hooks/installer.js +9 -0
- package/dist/hooks/post-task.d.ts +15 -0
- package/dist/hooks/post-task.js +102 -19
- package/dist/hooks/pre-task.d.ts +71 -0
- package/dist/hooks/pre-task.js +125 -0
- package/dist/index.js +29 -0
- package/hooks/hooks.json +9 -0
- package/package.json +1 -1
package/dist/hooks/post-task.js
CHANGED
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* Non-tp-* subagents are ignored (we only enforce our own contracts).
|
|
15
15
|
*/
|
|
16
16
|
import { promises as fs } from "node:fs";
|
|
17
|
-
import { join } from "node:path";
|
|
17
|
+
import { dirname, join, resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { buildAgentIndex, matchTpAgent, } from "../core/agent-matcher.js";
|
|
20
|
+
import { appendEvent } from "../core/event-log.js";
|
|
18
21
|
export const OVER_BUDGET_LOG = "over-budget.log";
|
|
19
22
|
/** Ratio above which we flag — 0.1 = 10 % grace. */
|
|
20
23
|
export const OVER_BUDGET_TOLERANCE = 0.1;
|
|
@@ -100,6 +103,43 @@ export async function loadAgentBody(projectRoot, homeDir, agentName) {
|
|
|
100
103
|
}
|
|
101
104
|
return null;
|
|
102
105
|
}
|
|
106
|
+
// ─── Cached tp-* agent index ─────────────────────────────────────────
|
|
107
|
+
// The hook subprocess is cold-started per Task post-event, but within
|
|
108
|
+
// that process we parse the agents directory once. Lookup cost is ~1 FS
|
|
109
|
+
// listing + 24 file reads, ~5-15 ms — below the noise floor of the hook
|
|
110
|
+
// round-trip. Kept as a process-level cache anyway for Pack 2 when the
|
|
111
|
+
// pre-task hook re-uses the same index on hot paths.
|
|
112
|
+
let _agentIndexCache = null;
|
|
113
|
+
/**
|
|
114
|
+
* Resolve the plugin's own `agents/` directory. The hook binary lives
|
|
115
|
+
* at `<plugin>/dist/index.js`, so agents/ is `../agents` from here.
|
|
116
|
+
* Allow an override for tests that want an isolated fixture dir.
|
|
117
|
+
*/
|
|
118
|
+
export function defaultAgentsDir() {
|
|
119
|
+
// `import.meta.url` resolves to the bundled dist location, which is
|
|
120
|
+
// already one step below the repo root (`dist/hooks/post-task.js`).
|
|
121
|
+
// Walk up twice: `hooks` → `dist` → plugin root, then join `agents`.
|
|
122
|
+
try {
|
|
123
|
+
const here = fileURLToPath(import.meta.url);
|
|
124
|
+
return resolve(dirname(here), "..", "..", "agents");
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Not running as a bundled module (eg. vitest in-source) — fall
|
|
128
|
+
// back to CWD/agents. Production path uses the URL resolver above.
|
|
129
|
+
return resolve(process.cwd(), "agents");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** Resolve (and cache) the tp-* agent index. Safe to call repeatedly. */
|
|
133
|
+
export async function getAgentIndex(dir = defaultAgentsDir()) {
|
|
134
|
+
if (_agentIndexCache)
|
|
135
|
+
return _agentIndexCache;
|
|
136
|
+
_agentIndexCache = await buildAgentIndex(dir);
|
|
137
|
+
return _agentIndexCache;
|
|
138
|
+
}
|
|
139
|
+
/** Test-only: clear the module-level cache between fixtures. */
|
|
140
|
+
export function _resetAgentIndexCache() {
|
|
141
|
+
_agentIndexCache = null;
|
|
142
|
+
}
|
|
103
143
|
/**
|
|
104
144
|
* Full post-Task processing: read frontmatter, count tokens, log over-budget.
|
|
105
145
|
* Returns the advice message (or null) so the caller can optionally emit
|
|
@@ -108,29 +148,72 @@ export async function loadAgentBody(projectRoot, homeDir, agentName) {
|
|
|
108
148
|
export async function processPostTask(projectRoot, homeDir, input) {
|
|
109
149
|
if (input.tool_name !== "Task")
|
|
110
150
|
return null;
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
151
|
+
const subagentType = input.tool_input?.subagent_type;
|
|
152
|
+
const description = input.tool_input?.description ?? "";
|
|
153
|
+
const actualTokens = extractSubagentTokens(input) ?? 0;
|
|
154
|
+
const isTpAgent = typeof subagentType === "string" && subagentType.startsWith("tp-");
|
|
155
|
+
// ─── existing tp-* budget logic (unchanged) ─────────────────────
|
|
156
|
+
let budget = null;
|
|
157
|
+
let decision = {
|
|
158
|
+
overBudget: false,
|
|
159
|
+
overByRatio: 0,
|
|
160
|
+
message: null,
|
|
161
|
+
};
|
|
162
|
+
if (isTpAgent && actualTokens > 0) {
|
|
163
|
+
const body = await loadAgentBody(projectRoot, homeDir, subagentType);
|
|
164
|
+
budget = body ? parseAgentBudget(body) : null;
|
|
165
|
+
decision = decideBudgetAdvice({
|
|
166
|
+
agentName: subagentType,
|
|
167
|
+
budget,
|
|
168
|
+
actualTokens,
|
|
169
|
+
});
|
|
170
|
+
if (decision.overBudget && budget != null) {
|
|
171
|
+
await appendOverBudgetLog(projectRoot, {
|
|
172
|
+
ts: Date.now(),
|
|
173
|
+
agent: subagentType,
|
|
174
|
+
budget,
|
|
175
|
+
actualTokens,
|
|
176
|
+
overByRatio: decision.overByRatio,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
114
179
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
180
|
+
// ─── v0.31.0 Task telemetry ────────────────────────────────────
|
|
181
|
+
// One event per Task call, regardless of tp-*. For non-tp agents we
|
|
182
|
+
// run the heuristic matcher so `stats --tasks` can surface routing
|
|
183
|
+
// misses (general-purpose picked when a tp-* would have fit).
|
|
184
|
+
// Silent on any error — telemetry must never break hook dispatch.
|
|
185
|
+
try {
|
|
186
|
+
let matched = null;
|
|
187
|
+
let matchConfidence;
|
|
188
|
+
if (!isTpAgent && description.length > 0) {
|
|
189
|
+
const index = await getAgentIndex();
|
|
190
|
+
const hit = matchTpAgent(description, index);
|
|
191
|
+
if (hit) {
|
|
192
|
+
matched = hit.agent;
|
|
193
|
+
matchConfidence = hit.confidence;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
await appendEvent(projectRoot, {
|
|
127
197
|
ts: Date.now(),
|
|
128
|
-
|
|
198
|
+
session_id: input.session_id ?? "",
|
|
199
|
+
agent_type: input.agent_type ?? null,
|
|
200
|
+
agent_id: input.agent_id ?? null,
|
|
201
|
+
event: "task",
|
|
202
|
+
file: "",
|
|
203
|
+
lines: 0,
|
|
204
|
+
estTokens: actualTokens,
|
|
205
|
+
summaryTokens: 0,
|
|
206
|
+
savedTokens: 0,
|
|
207
|
+
subagent_type: typeof subagentType === "string" ? subagentType : "",
|
|
208
|
+
matched_tp_agent: matched,
|
|
209
|
+
...(matchConfidence ? { match_confidence: matchConfidence } : {}),
|
|
129
210
|
budget,
|
|
130
|
-
|
|
131
|
-
overByRatio: decision.overByRatio,
|
|
211
|
+
overBudget: decision.overBudget,
|
|
132
212
|
});
|
|
133
213
|
}
|
|
214
|
+
catch {
|
|
215
|
+
/* silent */
|
|
216
|
+
}
|
|
134
217
|
return decision.message;
|
|
135
218
|
}
|
|
136
219
|
//# sourceMappingURL=post-task.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.31.0 Pack 2 — PreToolUse:Task routing enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Pack 1 (already shipped) built the matcher and telemetry. Pack 2 acts
|
|
5
|
+
* on that matcher: BEFORE a Task dispatch fires, we inspect
|
|
6
|
+
* `tool_input.subagent_type` + `tool_input.description`, heuristically
|
|
7
|
+
* match against the shipped `tp-*` catalog, and redirect (advise / deny)
|
|
8
|
+
* general-purpose calls that clearly fit a specialised agent.
|
|
9
|
+
*
|
|
10
|
+
* Why not straight-deny:
|
|
11
|
+
* - The pre-edit rollback in v0.30.4 taught us the cost of a false
|
|
12
|
+
* hard-block (stuck sessions, BYPASS env creep). Task routing has
|
|
13
|
+
* MORE ambiguity than Edit (descriptions are terse; recall on
|
|
14
|
+
* keyword match is imperfect), so the default mode = advise.
|
|
15
|
+
*
|
|
16
|
+
* Tier logic (first match wins):
|
|
17
|
+
*
|
|
18
|
+
* 1. tool_name !== "Task" → allow
|
|
19
|
+
* 2. subagent_type ∈ tp-* → allow
|
|
20
|
+
* 3. description contains an ESCAPE phrase → allow
|
|
21
|
+
* (ad-hoc / research / explore / multi-step / across the codebase)
|
|
22
|
+
* 4. matchTpAgent returns null → allow
|
|
23
|
+
* 5. TOKEN_PILOT_FORCE_SUBAGENTS=1 OR mode=strict → deny
|
|
24
|
+
* (hard-block: agent author opted into pedantic routing)
|
|
25
|
+
* 6. confidence=high → advise
|
|
26
|
+
* 7. confidence=low → advise (softer msg)
|
|
27
|
+
*
|
|
28
|
+
* Pure decide — all context (agent index, env, mode) is pre-resolved
|
|
29
|
+
* by the caller so the function stays deterministic and unit-testable.
|
|
30
|
+
*/
|
|
31
|
+
import type { EnforcementMode } from "../server/enforcement-mode.js";
|
|
32
|
+
import type { AgentIndex } from "../core/agent-matcher.js";
|
|
33
|
+
export interface PreTaskInput {
|
|
34
|
+
tool_name?: string;
|
|
35
|
+
tool_input?: {
|
|
36
|
+
subagent_type?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
[k: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export type PreTaskDecision = {
|
|
42
|
+
kind: "allow";
|
|
43
|
+
} | {
|
|
44
|
+
kind: "advise";
|
|
45
|
+
message: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "deny";
|
|
48
|
+
reason: string;
|
|
49
|
+
};
|
|
50
|
+
export interface PreTaskContext {
|
|
51
|
+
/** Parsed enforcement mode. `strict` is the only hard-block tier. */
|
|
52
|
+
mode: EnforcementMode;
|
|
53
|
+
/** Agent catalog built at startup by buildAgentIndex. */
|
|
54
|
+
agentIndex: AgentIndex;
|
|
55
|
+
/** TOKEN_PILOT_FORCE_SUBAGENTS=1 — opt-in strictness regardless of mode. */
|
|
56
|
+
force: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Pure decision function. Caller resolves all context (env, mode,
|
|
60
|
+
* agent index) up front so this stays a plain input → output mapping.
|
|
61
|
+
*/
|
|
62
|
+
export declare function decidePreTask(input: PreTaskInput, ctx: PreTaskContext): PreTaskDecision;
|
|
63
|
+
/**
|
|
64
|
+
* Render the Claude Code hook JSON response.
|
|
65
|
+
*
|
|
66
|
+
* - allow → no output (pass-through)
|
|
67
|
+
* - advise → permissionDecision=allow + additionalContext
|
|
68
|
+
* - deny → permissionDecision=deny + reason
|
|
69
|
+
*/
|
|
70
|
+
export declare function renderPreTaskOutput(decision: PreTaskDecision): string | null;
|
|
71
|
+
//# sourceMappingURL=pre-task.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.31.0 Pack 2 — PreToolUse:Task routing enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Pack 1 (already shipped) built the matcher and telemetry. Pack 2 acts
|
|
5
|
+
* on that matcher: BEFORE a Task dispatch fires, we inspect
|
|
6
|
+
* `tool_input.subagent_type` + `tool_input.description`, heuristically
|
|
7
|
+
* match against the shipped `tp-*` catalog, and redirect (advise / deny)
|
|
8
|
+
* general-purpose calls that clearly fit a specialised agent.
|
|
9
|
+
*
|
|
10
|
+
* Why not straight-deny:
|
|
11
|
+
* - The pre-edit rollback in v0.30.4 taught us the cost of a false
|
|
12
|
+
* hard-block (stuck sessions, BYPASS env creep). Task routing has
|
|
13
|
+
* MORE ambiguity than Edit (descriptions are terse; recall on
|
|
14
|
+
* keyword match is imperfect), so the default mode = advise.
|
|
15
|
+
*
|
|
16
|
+
* Tier logic (first match wins):
|
|
17
|
+
*
|
|
18
|
+
* 1. tool_name !== "Task" → allow
|
|
19
|
+
* 2. subagent_type ∈ tp-* → allow
|
|
20
|
+
* 3. description contains an ESCAPE phrase → allow
|
|
21
|
+
* (ad-hoc / research / explore / multi-step / across the codebase)
|
|
22
|
+
* 4. matchTpAgent returns null → allow
|
|
23
|
+
* 5. TOKEN_PILOT_FORCE_SUBAGENTS=1 OR mode=strict → deny
|
|
24
|
+
* (hard-block: agent author opted into pedantic routing)
|
|
25
|
+
* 6. confidence=high → advise
|
|
26
|
+
* 7. confidence=low → advise (softer msg)
|
|
27
|
+
*
|
|
28
|
+
* Pure decide — all context (agent index, env, mode) is pre-resolved
|
|
29
|
+
* by the caller so the function stays deterministic and unit-testable.
|
|
30
|
+
*/
|
|
31
|
+
import { matchTpAgent } from "../core/agent-matcher.js";
|
|
32
|
+
/**
|
|
33
|
+
* Escape phrases that tell us the user genuinely wants open-ended
|
|
34
|
+
* general-purpose work. Short list of boilerplate — keeping it tight
|
|
35
|
+
* prevents the escape from eating otherwise-valid routing.
|
|
36
|
+
*
|
|
37
|
+
* All checks are lowercased substring matches. Author new entries here
|
|
38
|
+
* only when tool-audit shows a legitimate pattern getting false-flagged.
|
|
39
|
+
*/
|
|
40
|
+
const ESCAPE_PHRASES = [
|
|
41
|
+
"ad-hoc",
|
|
42
|
+
"ad hoc",
|
|
43
|
+
"one-off",
|
|
44
|
+
"one off",
|
|
45
|
+
"open-ended",
|
|
46
|
+
"research across",
|
|
47
|
+
"explore multiple",
|
|
48
|
+
"multi-step",
|
|
49
|
+
"across the codebase",
|
|
50
|
+
"across the repo",
|
|
51
|
+
"general purpose",
|
|
52
|
+
];
|
|
53
|
+
function containsEscape(description) {
|
|
54
|
+
const n = description.toLowerCase();
|
|
55
|
+
return ESCAPE_PHRASES.some((p) => n.includes(p));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pure decision function. Caller resolves all context (env, mode,
|
|
59
|
+
* agent index) up front so this stays a plain input → output mapping.
|
|
60
|
+
*/
|
|
61
|
+
export function decidePreTask(input, ctx) {
|
|
62
|
+
if (input.tool_name !== "Task")
|
|
63
|
+
return { kind: "allow" };
|
|
64
|
+
const subagentType = input.tool_input?.subagent_type ?? "";
|
|
65
|
+
const description = input.tool_input?.description ?? "";
|
|
66
|
+
// Already a tp-* — routing intent matches catalog. Let it run.
|
|
67
|
+
if (typeof subagentType === "string" && subagentType.startsWith("tp-")) {
|
|
68
|
+
return { kind: "allow" };
|
|
69
|
+
}
|
|
70
|
+
// No description → nothing to match against. Allow (Claude Code
|
|
71
|
+
// sometimes dispatches Task with only a subagent_type + session id).
|
|
72
|
+
if (!description || description.length === 0)
|
|
73
|
+
return { kind: "allow" };
|
|
74
|
+
// Author-blessed escape clauses — user is explicitly saying
|
|
75
|
+
// "this is broad". Respect that.
|
|
76
|
+
if (containsEscape(description))
|
|
77
|
+
return { kind: "allow" };
|
|
78
|
+
const hit = matchTpAgent(description, ctx.agentIndex);
|
|
79
|
+
if (!hit)
|
|
80
|
+
return { kind: "allow" };
|
|
81
|
+
const suggestion = `Consider dispatching \`${hit.agent}\` instead of \`${subagentType || "general-purpose"}\` — ` +
|
|
82
|
+
`the description matches its trigger phrases (confidence: ${hit.confidence}). ` +
|
|
83
|
+
`tp-* agents run under a tighter budget and output in terse style, typically ` +
|
|
84
|
+
`~50-70 % fewer tokens than general-purpose. ` +
|
|
85
|
+
`Escape: add "ad-hoc" or "open-ended" to the description to bypass, or set ` +
|
|
86
|
+
`TOKEN_PILOT_MODE=advisory for warn-only behaviour.`;
|
|
87
|
+
const hardBlock = ctx.force ||
|
|
88
|
+
ctx.mode === "strict" ||
|
|
89
|
+
(ctx.mode === "deny" && hit.confidence === "high" && ctx.force);
|
|
90
|
+
if (hardBlock) {
|
|
91
|
+
return {
|
|
92
|
+
kind: "deny",
|
|
93
|
+
reason: suggestion,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { kind: "advise", message: suggestion };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Render the Claude Code hook JSON response.
|
|
100
|
+
*
|
|
101
|
+
* - allow → no output (pass-through)
|
|
102
|
+
* - advise → permissionDecision=allow + additionalContext
|
|
103
|
+
* - deny → permissionDecision=deny + reason
|
|
104
|
+
*/
|
|
105
|
+
export function renderPreTaskOutput(decision) {
|
|
106
|
+
if (decision.kind === "allow")
|
|
107
|
+
return null;
|
|
108
|
+
if (decision.kind === "advise") {
|
|
109
|
+
return JSON.stringify({
|
|
110
|
+
hookSpecificOutput: {
|
|
111
|
+
hookEventName: "PreToolUse",
|
|
112
|
+
permissionDecision: "allow",
|
|
113
|
+
additionalContext: decision.message,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return JSON.stringify({
|
|
118
|
+
hookSpecificOutput: {
|
|
119
|
+
hookEventName: "PreToolUse",
|
|
120
|
+
permissionDecision: "deny",
|
|
121
|
+
permissionDecisionReason: decision.reason,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=pre-task.js.map
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,8 @@ 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 { decidePreTask, renderPreTaskOutput } from "./hooks/pre-task.js";
|
|
56
|
+
import { getAgentIndex } from "./hooks/post-task.js";
|
|
55
57
|
import { decidePreEdit, renderPreEditOutput, } from "./hooks/pre-edit.js";
|
|
56
58
|
import { isEditPrepared as isEditPreparedFn } from "./core/edit-prep-state.js";
|
|
57
59
|
import { maybeEmitEcosystemReminder } from "./cli/ecosystem-reminder.js";
|
|
@@ -184,6 +186,33 @@ export async function main(cliArgs = process.argv.slice(2)) {
|
|
|
184
186
|
process.exit(0);
|
|
185
187
|
return;
|
|
186
188
|
}
|
|
189
|
+
case "hook-pre-task": {
|
|
190
|
+
// 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 {
|
|
197
|
+
const stdin = readFileSync(0, "utf-8");
|
|
198
|
+
const input = JSON.parse(stdin);
|
|
199
|
+
const agentIndex = await getAgentIndex();
|
|
200
|
+
const force = process.env.TOKEN_PILOT_FORCE_SUBAGENTS === "1";
|
|
201
|
+
const decision = decidePreTask(input, {
|
|
202
|
+
mode: parseEnforcementMode(process.env.TOKEN_PILOT_MODE),
|
|
203
|
+
agentIndex,
|
|
204
|
+
force,
|
|
205
|
+
});
|
|
206
|
+
const rendered = renderPreTaskOutput(decision);
|
|
207
|
+
if (rendered)
|
|
208
|
+
process.stdout.write(rendered);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
/* silent — hook must not break */
|
|
212
|
+
}
|
|
213
|
+
process.exit(0);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
187
216
|
case "hook-post-task": {
|
|
188
217
|
try {
|
|
189
218
|
const stdin = readFileSync(0, "utf-8");
|
package/hooks/hooks.json
CHANGED
|
@@ -45,6 +45,15 @@
|
|
|
45
45
|
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js hook-pre-grep"
|
|
46
46
|
}
|
|
47
47
|
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"matcher": "Task",
|
|
51
|
+
"hooks": [
|
|
52
|
+
{
|
|
53
|
+
"type": "command",
|
|
54
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js hook-pre-task"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
48
57
|
}
|
|
49
58
|
],
|
|
50
59
|
"SessionStart": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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",
|