token-pilot 0.30.5 → 0.32.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 (49) 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 +10 -2
  4. package/agents/tp-audit-scanner.md +10 -2
  5. package/agents/tp-commit-writer.md +10 -2
  6. package/agents/tp-context-engineer.md +10 -2
  7. package/agents/tp-dead-code-finder.md +10 -2
  8. package/agents/tp-debugger.md +10 -2
  9. package/agents/tp-dep-health.md +10 -2
  10. package/agents/tp-doc-writer.md +10 -2
  11. package/agents/tp-history-explorer.md +10 -2
  12. package/agents/tp-impact-analyzer.md +10 -2
  13. package/agents/tp-incident-timeline.md +10 -2
  14. package/agents/tp-incremental-builder.md +10 -2
  15. package/agents/tp-migration-scout.md +10 -2
  16. package/agents/tp-onboard.md +10 -2
  17. package/agents/tp-performance-profiler.md +10 -2
  18. package/agents/tp-pr-reviewer.md +10 -2
  19. package/agents/tp-refactor-planner.md +10 -2
  20. package/agents/tp-review-impact.md +10 -2
  21. package/agents/tp-run.md +10 -2
  22. package/agents/tp-session-restorer.md +10 -2
  23. package/agents/tp-ship-coordinator.md +10 -2
  24. package/agents/tp-spec-writer.md +10 -2
  25. package/agents/tp-test-coverage-gapper.md +10 -2
  26. package/agents/tp-test-triage.md +10 -2
  27. package/agents/tp-test-writer.md +10 -2
  28. package/dist/cli/stats.d.ts +2 -0
  29. package/dist/cli/stats.js +46 -1
  30. package/dist/core/agent-matcher.d.ts +115 -0
  31. package/dist/core/agent-matcher.js +326 -0
  32. package/dist/core/event-log.d.ts +14 -1
  33. package/dist/core/validation.d.ts +13 -9
  34. package/dist/core/validation.js +180 -134
  35. package/dist/handlers/call-tree.d.ts +35 -0
  36. package/dist/handlers/call-tree.js +70 -0
  37. package/dist/hooks/installer.js +9 -0
  38. package/dist/hooks/post-task.d.ts +15 -0
  39. package/dist/hooks/post-task.js +102 -19
  40. package/dist/hooks/pre-task.d.ts +71 -0
  41. package/dist/hooks/pre-task.js +125 -0
  42. package/dist/hooks/session-start.d.ts +2 -0
  43. package/dist/hooks/session-start.js +49 -0
  44. package/dist/index.js +29 -0
  45. package/dist/server/tool-definitions.d.ts +65 -0
  46. package/dist/server/tool-definitions.js +18 -0
  47. package/dist/server.js +36 -1
  48. package/hooks/hooks.json +9 -0
  49. package/package.json +1 -1
package/dist/cli/stats.js CHANGED
@@ -67,7 +67,50 @@ export function formatStats(events, opts) {
67
67
  const total = sumSaved(scope);
68
68
  const sessionSuffix = sessionLabel ? ` (session ${sessionLabel})` : "";
69
69
  lines.push(`token-pilot stats${sessionSuffix} — ${scope.length} event${scope.length === 1 ? "" : "s"}, ~${total} tokens saved`);
70
- if (opts.byAgent) {
70
+ if (opts.tasks) {
71
+ // v0.31.0 — Task-routing view. Scope to event:"task" records only.
72
+ const taskEvents = scope.filter((e) => e.event === "task");
73
+ if (taskEvents.length === 0) {
74
+ return lines[0] + "\n\nNo Task events yet.";
75
+ }
76
+ const totalTasks = taskEvents.length;
77
+ const misses = taskEvents.filter((e) => typeof e.matched_tp_agent === "string" &&
78
+ e.matched_tp_agent.length > 0 &&
79
+ e.subagent_type !== e.matched_tp_agent);
80
+ const missRate = totalTasks > 0 ? Math.round((misses.length / totalTasks) * 100) : 0;
81
+ // Group by subagent_type (what Claude actually picked).
82
+ const pickGroups = groupBy(taskEvents, (e) => (e.subagent_type && e.subagent_type.length > 0
83
+ ? e.subagent_type
84
+ : "(unknown)"));
85
+ const picks = [...pickGroups.entries()]
86
+ .map(([agent, evs]) => ({ agent, count: evs.length }))
87
+ .sort((a, b) => b.count - a.count);
88
+ // Top missed routings: (picked → suggested) pairs with counts.
89
+ const missCounts = new Map();
90
+ for (const e of misses) {
91
+ const key = `${e.subagent_type} → ${e.matched_tp_agent}`;
92
+ missCounts.set(key, (missCounts.get(key) ?? 0) + 1);
93
+ }
94
+ const topMisses = [...missCounts.entries()]
95
+ .map(([pair, count]) => ({ pair, count }))
96
+ .sort((a, b) => b.count - a.count)
97
+ .slice(0, 10);
98
+ // Rewrite header for the task view (replace savings number with miss-rate).
99
+ lines[0] = `token-pilot stats — ${totalTasks} Task call${totalTasks === 1 ? "" : "s"}, miss-rate ${missRate}% (${misses.length}/${totalTasks})${sessionSuffix}`;
100
+ lines.push("");
101
+ lines.push("Picked subagents:");
102
+ for (const p of picks) {
103
+ lines.push(` ${pad(p.agent, 24)} ${p.count.toString().padStart(4)}× events`);
104
+ }
105
+ if (topMisses.length > 0) {
106
+ lines.push("");
107
+ lines.push("Top routing misses (picked → suggested tp-*):");
108
+ for (const m of topMisses) {
109
+ lines.push(` ${pad(m.pair, 48)} ${m.count.toString().padStart(4)}×`);
110
+ }
111
+ }
112
+ }
113
+ else if (opts.byAgent) {
71
114
  // Group by agent_type (null → "main").
72
115
  const groups = groupBy(scope, (e) => (e.agent_type ?? "main"));
73
116
  const rows = [...groups.entries()]
@@ -121,9 +164,11 @@ export async function handleStats(argv, opts) {
121
164
  const events = await loadEvents(projectRoot);
122
165
  const session = parseFlag(argv, "session");
123
166
  const byAgent = parseFlag(argv, "by-agent");
167
+ const tasks = parseFlag(argv, "tasks");
124
168
  const rendered = formatStats(events, {
125
169
  session: session === undefined ? undefined : session,
126
170
  byAgent: byAgent === true,
171
+ tasks: tasks === true,
127
172
  });
128
173
  process.stdout.write(rendered + "\n");
129
174
  return 0;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * v0.31.0 — tp-* subagent heuristic matcher.
3
+ *
4
+ * Goal: given a Claude Code `Task` tool invocation (subagent_type +
5
+ * description), decide which `tp-*` agent from `agents/` would be a
6
+ * better fit. Used by:
7
+ *
8
+ * 1. PostToolUse:Task telemetry — enrich each event with
9
+ * `matched_tp_agent` so `stats --tasks` can show miss-rate.
10
+ * 2. PreToolUse:Task enforcement (Pack 2, later) — advise/deny when
11
+ * the agent picked `general-purpose` but a tp-* clearly fits.
12
+ *
13
+ * Matcher philosophy — keep it BORING and EXPLAINABLE.
14
+ *
15
+ * Agent frontmatter description layout (empirically stable across all
16
+ * 24 shipped agents):
17
+ *
18
+ * description: PROACTIVELY use this when the user asks to review a
19
+ * diff, PR, commit range, or changeset ("review these changes",
20
+ * "look at my PR", "is this safe to merge"). Verdict-first output
21
+ * with Critical / Important findings. Do NOT use for writing code
22
+ * or planning.
23
+ *
24
+ * Two signal sources:
25
+ *
26
+ * - Quoted triggers: every `"…"` substring inside the description.
27
+ * These are literally the phrases the agent author expected users
28
+ * to type. Highest signal. Substring match (case-insensitive) on
29
+ * the user's description → score += 2.
30
+ *
31
+ * - Content keywords: stemmed word set from the 1st description
32
+ * sentence, minus stopwords and boilerplate ("PROACTIVELY",
33
+ * "use this", "when the user asks"). Each match on the user's
34
+ * description → score += 1.
35
+ *
36
+ * Negative filter: everything after `Do NOT use for` is excluded from
37
+ * keyword extraction AND actively penalises a match (score -= 1 per
38
+ * term present in user's description). Prevents `tp-test-writer` from
39
+ * being suggested on "diagnose failing test" (which is tp-test-triage).
40
+ *
41
+ * Confidence tiers:
42
+ * - score ≥ 3 or ≥ 1 quoted trigger → "high"
43
+ * - score in [1, 2] → "low"
44
+ * - score < 1 → no match
45
+ *
46
+ * The function is pure (deps → in-memory index + string) so it's fully
47
+ * unit-testable. File I/O (reading the agents dir) lives in
48
+ * `buildAgentIndex` which is a one-shot loader called at startup.
49
+ */
50
+ /** One parsed `tp-*` agent. Only fields the matcher needs. */
51
+ export interface ParsedAgent {
52
+ /** agent name without .md extension, e.g. "tp-pr-reviewer" */
53
+ name: string;
54
+ /** phrases found in `"…"` inside the description — highest signal */
55
+ quotedTriggers: string[];
56
+ /** stemmed content keywords from the positive side of the description */
57
+ keywords: string[];
58
+ /** negative-filter terms from `Do NOT use for …` */
59
+ negative: string[];
60
+ }
61
+ export interface AgentIndex {
62
+ agents: ParsedAgent[];
63
+ }
64
+ export interface MatchResult {
65
+ agent: string;
66
+ confidence: "high" | "low";
67
+ score: number;
68
+ }
69
+ /** Extract the `description:` value from YAML frontmatter.
70
+ * Supports multi-line values (continuation lines indented).
71
+ * Returns null when the file has no frontmatter or no description. */
72
+ export declare function extractDescription(body: string): string | null;
73
+ /** Pull every `"…"` substring out of a string. Ignores empty pairs. */
74
+ export declare function extractQuotedTriggers(s: string): string[];
75
+ /**
76
+ * Split `description` around `Do NOT use for …` — everything on the
77
+ * positive side is keyword material; everything after contributes to
78
+ * the negative filter.
79
+ */
80
+ export declare function splitAroundNegative(desc: string): {
81
+ positive: string;
82
+ negative: string;
83
+ };
84
+ /**
85
+ * Tokenise → lowercase → drop stopwords + ≤2 chars + quoted-trigger
86
+ * leftovers. Keywords stay in surface form; we do not stem. Stemming
87
+ * helps recall on English verb/noun pairs ("refactor"/"refactoring"),
88
+ * but libraries add cost for modest gain — use substring match on the
89
+ * user's description instead (covers most morphology).
90
+ */
91
+ export declare function extractKeywords(text: string): string[];
92
+ /**
93
+ * Parse one agent markdown body into its ParsedAgent representation.
94
+ * Returns null if frontmatter is missing / description is empty.
95
+ */
96
+ export declare function parseAgent(name: string, body: string): ParsedAgent | null;
97
+ /**
98
+ * Load every `tp-*.md` under a directory and build an in-memory index.
99
+ * Non-tp-* files are silently skipped. Unreadable files are skipped
100
+ * with no throw — an agent directory isn't a runtime dep.
101
+ */
102
+ export declare function buildAgentIndex(agentsDir: string): Promise<AgentIndex>;
103
+ /**
104
+ * Score a single agent against the user description. Surface the score
105
+ * so callers can inspect / threshold differently if needed.
106
+ */
107
+ export declare function scoreAgent(agent: ParsedAgent, userDescriptionLower: string): number;
108
+ /**
109
+ * Find the best `tp-*` match for a user description. Returns null when
110
+ * no agent clears the low-confidence threshold.
111
+ *
112
+ * "Best" = highest score, tiebreak alphabetical (deterministic).
113
+ */
114
+ export declare function matchTpAgent(description: string, index: AgentIndex): MatchResult | null;
115
+ //# sourceMappingURL=agent-matcher.d.ts.map
@@ -0,0 +1,326 @@
1
+ /**
2
+ * v0.31.0 — tp-* subagent heuristic matcher.
3
+ *
4
+ * Goal: given a Claude Code `Task` tool invocation (subagent_type +
5
+ * description), decide which `tp-*` agent from `agents/` would be a
6
+ * better fit. Used by:
7
+ *
8
+ * 1. PostToolUse:Task telemetry — enrich each event with
9
+ * `matched_tp_agent` so `stats --tasks` can show miss-rate.
10
+ * 2. PreToolUse:Task enforcement (Pack 2, later) — advise/deny when
11
+ * the agent picked `general-purpose` but a tp-* clearly fits.
12
+ *
13
+ * Matcher philosophy — keep it BORING and EXPLAINABLE.
14
+ *
15
+ * Agent frontmatter description layout (empirically stable across all
16
+ * 24 shipped agents):
17
+ *
18
+ * description: PROACTIVELY use this when the user asks to review a
19
+ * diff, PR, commit range, or changeset ("review these changes",
20
+ * "look at my PR", "is this safe to merge"). Verdict-first output
21
+ * with Critical / Important findings. Do NOT use for writing code
22
+ * or planning.
23
+ *
24
+ * Two signal sources:
25
+ *
26
+ * - Quoted triggers: every `"…"` substring inside the description.
27
+ * These are literally the phrases the agent author expected users
28
+ * to type. Highest signal. Substring match (case-insensitive) on
29
+ * the user's description → score += 2.
30
+ *
31
+ * - Content keywords: stemmed word set from the 1st description
32
+ * sentence, minus stopwords and boilerplate ("PROACTIVELY",
33
+ * "use this", "when the user asks"). Each match on the user's
34
+ * description → score += 1.
35
+ *
36
+ * Negative filter: everything after `Do NOT use for` is excluded from
37
+ * keyword extraction AND actively penalises a match (score -= 1 per
38
+ * term present in user's description). Prevents `tp-test-writer` from
39
+ * being suggested on "diagnose failing test" (which is tp-test-triage).
40
+ *
41
+ * Confidence tiers:
42
+ * - score ≥ 3 or ≥ 1 quoted trigger → "high"
43
+ * - score in [1, 2] → "low"
44
+ * - score < 1 → no match
45
+ *
46
+ * The function is pure (deps → in-memory index + string) so it's fully
47
+ * unit-testable. File I/O (reading the agents dir) lives in
48
+ * `buildAgentIndex` which is a one-shot loader called at startup.
49
+ */
50
+ import { promises as fs } from "node:fs";
51
+ import { join } from "node:path";
52
+ /**
53
+ * Stopwords stripped from keyword extraction. Keep tiny — aggressive
54
+ * stopword lists kill recall. Only boilerplate from agent frontmatter
55
+ * templates goes here.
56
+ */
57
+ const STOPWORDS = new Set([
58
+ "a",
59
+ "an",
60
+ "the",
61
+ "and",
62
+ "or",
63
+ "of",
64
+ "to",
65
+ "in",
66
+ "for",
67
+ "on",
68
+ "at",
69
+ "by",
70
+ "is",
71
+ "are",
72
+ "was",
73
+ "were",
74
+ "be",
75
+ "been",
76
+ "being",
77
+ "this",
78
+ "that",
79
+ "these",
80
+ "those",
81
+ "it",
82
+ "its",
83
+ "as",
84
+ "with",
85
+ "when",
86
+ "where",
87
+ "user",
88
+ "users",
89
+ "ask",
90
+ "asks",
91
+ "asked",
92
+ "asking",
93
+ "use",
94
+ "uses",
95
+ "used",
96
+ "using",
97
+ "proactively",
98
+ "please",
99
+ "any",
100
+ "all",
101
+ "some",
102
+ "get",
103
+ "gets",
104
+ "got",
105
+ "also",
106
+ "like",
107
+ "from",
108
+ "into",
109
+ "not",
110
+ "no",
111
+ "do",
112
+ "does",
113
+ "did",
114
+ "have",
115
+ "has",
116
+ "had",
117
+ "will",
118
+ "can",
119
+ "could",
120
+ "should",
121
+ "may",
122
+ "might",
123
+ "must",
124
+ "you",
125
+ "your",
126
+ "their",
127
+ "they",
128
+ "them",
129
+ "we",
130
+ "our",
131
+ "us",
132
+ ]);
133
+ /** Extract the `description:` value from YAML frontmatter.
134
+ * Supports multi-line values (continuation lines indented).
135
+ * Returns null when the file has no frontmatter or no description. */
136
+ export function extractDescription(body) {
137
+ const fmEnd = body.indexOf("\n---", 3);
138
+ if (!body.startsWith("---\n") || fmEnd === -1)
139
+ return null;
140
+ const fm = body.slice(4, fmEnd);
141
+ const lines = fm.split("\n");
142
+ let desc = "";
143
+ let inDesc = false;
144
+ for (const line of lines) {
145
+ // Top-level key detection: `key: value` at column 0
146
+ const topKey = /^([A-Za-z_][\w-]*)\s*:\s*(.*)$/.exec(line);
147
+ if (topKey && !/^\s/.test(line)) {
148
+ if (inDesc)
149
+ break;
150
+ if (topKey[1] === "description") {
151
+ desc = topKey[2] ?? "";
152
+ inDesc = true;
153
+ }
154
+ }
155
+ else if (inDesc) {
156
+ // Continuation line (indented or blank) — append with a space.
157
+ desc += " " + line.trim();
158
+ }
159
+ }
160
+ const trimmed = desc.trim();
161
+ return trimmed.length > 0 ? trimmed : null;
162
+ }
163
+ /** Pull every `"…"` substring out of a string. Ignores empty pairs. */
164
+ export function extractQuotedTriggers(s) {
165
+ const out = [];
166
+ const re = /"([^"]+)"/g;
167
+ for (const m of s.matchAll(re)) {
168
+ const inner = m[1].trim().toLowerCase();
169
+ if (inner.length > 0)
170
+ out.push(inner);
171
+ }
172
+ return out;
173
+ }
174
+ /**
175
+ * Split `description` around `Do NOT use for …` — everything on the
176
+ * positive side is keyword material; everything after contributes to
177
+ * the negative filter.
178
+ */
179
+ export function splitAroundNegative(desc) {
180
+ // Case-insensitive split on common "Do NOT use …" lead-ins. `to` catches
181
+ // "Do NOT use to write" (tp-test-triage); `for` / `on` / `during` / `when`
182
+ // cover every other shipped agent. Add terms here as new forms appear.
183
+ const re = /\bdo\s+not\s+use\s+(?:for|on|during|when|to)\b/i;
184
+ const idx = desc.search(re);
185
+ if (idx === -1)
186
+ return { positive: desc, negative: "" };
187
+ return {
188
+ positive: desc.slice(0, idx),
189
+ negative: desc.slice(idx),
190
+ };
191
+ }
192
+ /**
193
+ * Tokenise → lowercase → drop stopwords + ≤2 chars + quoted-trigger
194
+ * leftovers. Keywords stay in surface form; we do not stem. Stemming
195
+ * helps recall on English verb/noun pairs ("refactor"/"refactoring"),
196
+ * but libraries add cost for modest gain — use substring match on the
197
+ * user's description instead (covers most morphology).
198
+ */
199
+ export function extractKeywords(text) {
200
+ const out = new Set();
201
+ // Remove quoted phrases first (they're handled separately).
202
+ const cleaned = text.replace(/"[^"]+"/g, " ");
203
+ for (const raw of cleaned.toLowerCase().split(/[^a-z0-9_-]+/)) {
204
+ const tok = raw.trim();
205
+ // Keep short technical terms ("ci", "pr", "db", "io"). STOPWORDS already
206
+ // filters most 1-2 char english junk ("is", "to", "on", "a"). Drop
207
+ // single chars only — they carry ~no signal.
208
+ if (tok.length < 2)
209
+ continue;
210
+ if (STOPWORDS.has(tok))
211
+ continue;
212
+ out.add(tok);
213
+ }
214
+ return [...out];
215
+ }
216
+ /**
217
+ * Parse one agent markdown body into its ParsedAgent representation.
218
+ * Returns null if frontmatter is missing / description is empty.
219
+ */
220
+ export function parseAgent(name, body) {
221
+ const desc = extractDescription(body);
222
+ if (!desc)
223
+ return null;
224
+ const { positive, negative } = splitAroundNegative(desc);
225
+ const quotedTriggers = extractQuotedTriggers(desc);
226
+ const keywords = extractKeywords(positive);
227
+ // Negative terms: only the core ones (tp-* names, salient nouns).
228
+ const negKeywords = extractKeywords(negative);
229
+ return {
230
+ name,
231
+ quotedTriggers,
232
+ keywords,
233
+ negative: negKeywords,
234
+ };
235
+ }
236
+ /**
237
+ * Load every `tp-*.md` under a directory and build an in-memory index.
238
+ * Non-tp-* files are silently skipped. Unreadable files are skipped
239
+ * with no throw — an agent directory isn't a runtime dep.
240
+ */
241
+ export async function buildAgentIndex(agentsDir) {
242
+ let entries;
243
+ try {
244
+ entries = await fs.readdir(agentsDir);
245
+ }
246
+ catch {
247
+ return { agents: [] };
248
+ }
249
+ const agents = [];
250
+ for (const entry of entries) {
251
+ if (!entry.startsWith("tp-") || !entry.endsWith(".md"))
252
+ continue;
253
+ const name = entry.slice(0, -".md".length);
254
+ let body;
255
+ try {
256
+ body = await fs.readFile(join(agentsDir, entry), "utf-8");
257
+ }
258
+ catch {
259
+ continue;
260
+ }
261
+ const parsed = parseAgent(name, body);
262
+ if (parsed)
263
+ agents.push(parsed);
264
+ }
265
+ return { agents };
266
+ }
267
+ /**
268
+ * Score a single agent against the user description. Surface the score
269
+ * so callers can inspect / threshold differently if needed.
270
+ */
271
+ export function scoreAgent(agent, userDescriptionLower) {
272
+ let score = 0;
273
+ for (const trigger of agent.quotedTriggers) {
274
+ if (userDescriptionLower.includes(trigger))
275
+ score += 2;
276
+ }
277
+ for (const kw of agent.keywords) {
278
+ if (userDescriptionLower.includes(kw))
279
+ score += 1;
280
+ }
281
+ for (const neg of agent.negative) {
282
+ if (userDescriptionLower.includes(neg))
283
+ score -= 1;
284
+ }
285
+ return score;
286
+ }
287
+ /**
288
+ * Find the best `tp-*` match for a user description. Returns null when
289
+ * no agent clears the low-confidence threshold.
290
+ *
291
+ * "Best" = highest score, tiebreak alphabetical (deterministic).
292
+ */
293
+ export function matchTpAgent(description, index) {
294
+ if (!description || index.agents.length === 0)
295
+ return null;
296
+ const needle = description.toLowerCase();
297
+ let best = null;
298
+ for (const agent of index.agents) {
299
+ const score = scoreAgent(agent, needle);
300
+ if (!best) {
301
+ best = { agent, score };
302
+ continue;
303
+ }
304
+ if (score > best.score) {
305
+ best = { agent, score };
306
+ continue;
307
+ }
308
+ // Deterministic tiebreak: alphabetical by agent.name. Without this,
309
+ // match depends on readdir order, which is filesystem-specific.
310
+ if (score === best.score && agent.name < best.agent.name) {
311
+ best = { agent, score };
312
+ }
313
+ }
314
+ if (!best || best.score < 1)
315
+ return null;
316
+ // High confidence when score is strong OR at least one quoted trigger
317
+ // matched (quoted = explicit author-blessed phrase).
318
+ const hitQuoted = best.agent.quotedTriggers.some((t) => needle.includes(t));
319
+ const confidence = best.score >= 3 || hitQuoted ? "high" : "low";
320
+ return {
321
+ agent: best.agent.name,
322
+ confidence,
323
+ score: best.score,
324
+ };
325
+ }
326
+ //# sourceMappingURL=agent-matcher.js.map
@@ -28,7 +28,7 @@ export interface HookEvent {
28
28
  /** null for top-level session; agent_type string inside a subagent. */
29
29
  agent_type: string | null;
30
30
  agent_id: string | null;
31
- event: "denied" | "allowed" | "bypass" | "pass-through" | string;
31
+ event: "denied" | "allowed" | "bypass" | "pass-through" | "task" | string;
32
32
  file: string;
33
33
  lines: number;
34
34
  estTokens: number;
@@ -36,6 +36,19 @@ export interface HookEvent {
36
36
  summaryTokens: number;
37
37
  /** estTokens - summaryTokens; 0 for allow/bypass. */
38
38
  savedTokens: number;
39
+ /** The subagent_type Claude Code dispatched (`tp-*` or `general-purpose`…). */
40
+ subagent_type?: string;
41
+ /**
42
+ * Heuristic match against `tp-*` agent frontmatter. Set only when
43
+ * `subagent_type` is NOT already a tp-*. null when no match fires.
44
+ */
45
+ matched_tp_agent?: string | null;
46
+ /** Confidence of the heuristic match (omitted when matched_tp_agent=null). */
47
+ match_confidence?: "high" | "low";
48
+ /** Response budget declared in the agent markdown body, or null. */
49
+ budget?: number | null;
50
+ /** actualTokens > budget × (1 + tolerance). */
51
+ overBudget?: boolean;
39
52
  }
40
53
  export declare function eventLogDir(projectRoot: string): string;
41
54
  export declare function currentLogPath(projectRoot: string): string;
@@ -22,7 +22,7 @@ export declare function validateReadSymbolArgs(args: unknown): {
22
22
  symbol: string;
23
23
  context_before?: number;
24
24
  context_after?: number;
25
- show?: 'full' | 'head' | 'tail' | 'outline';
25
+ show?: "full" | "head" | "tail" | "outline";
26
26
  };
27
27
  /**
28
28
  * Validate read_symbols arguments (batch multi-symbol read).
@@ -32,7 +32,7 @@ export declare function validateReadSymbolsArgs(args: unknown): {
32
32
  symbols: string[];
33
33
  context_before?: number;
34
34
  context_after?: number;
35
- show?: 'full' | 'head' | 'tail' | 'outline';
35
+ show?: "full" | "head" | "tail" | "outline";
36
36
  };
37
37
  /**
38
38
  * Validate read_range arguments.
@@ -56,11 +56,11 @@ export declare function validateReadDiffArgs(args: unknown): {
56
56
  export interface FindUsagesArgs {
57
57
  symbol: string;
58
58
  scope?: string;
59
- kind?: 'definitions' | 'imports' | 'usages' | 'all';
59
+ kind?: "definitions" | "imports" | "usages" | "all";
60
60
  limit?: number;
61
61
  lang?: string;
62
62
  context_lines?: number;
63
- mode?: 'full' | 'list';
63
+ mode?: "full" | "list";
64
64
  }
65
65
  export declare function validateFindUsagesArgs(args: unknown): FindUsagesArgs;
66
66
  /**
@@ -105,8 +105,12 @@ export declare function validateFindUnusedArgs(args: unknown): {
105
105
  export_only?: boolean;
106
106
  limit?: number;
107
107
  };
108
+ export declare function validateCallTreeArgs(args: unknown): {
109
+ symbol: string;
110
+ depth?: number;
111
+ };
108
112
  export interface CodeAuditArgs {
109
- check: 'pattern' | 'todo' | 'deprecated' | 'annotations' | 'all';
113
+ check: "pattern" | "todo" | "deprecated" | "annotations" | "all";
110
114
  pattern?: string;
111
115
  name?: string;
112
116
  lang?: string;
@@ -118,7 +122,7 @@ export declare function validateCodeAuditArgs(args: unknown): CodeAuditArgs;
118
122
  * v1.1: added include filter.
119
123
  */
120
124
  export interface ProjectOverviewArgs {
121
- include?: Array<'stack' | 'ci' | 'quality' | 'architecture'>;
125
+ include?: Array<"stack" | "ci" | "quality" | "architecture">;
122
126
  }
123
127
  export declare function validateProjectOverviewArgs(args: unknown): ProjectOverviewArgs;
124
128
  /**
@@ -126,14 +130,14 @@ export declare function validateProjectOverviewArgs(args: unknown): ProjectOverv
126
130
  */
127
131
  export interface ModuleInfoArgs {
128
132
  module: string;
129
- check?: 'deps' | 'dependents' | 'api' | 'unused-deps' | 'all';
133
+ check?: "deps" | "dependents" | "api" | "unused-deps" | "all";
130
134
  }
131
135
  export declare function validateModuleInfoArgs(args: unknown): ModuleInfoArgs;
132
136
  /**
133
137
  * Validate smart_diff arguments.
134
138
  */
135
139
  export interface SmartDiffArgs {
136
- scope?: 'unstaged' | 'staged' | 'commit' | 'branch';
140
+ scope?: "unstaged" | "staged" | "commit" | "branch";
137
141
  path?: string;
138
142
  ref?: string;
139
143
  }
@@ -143,7 +147,7 @@ export declare function validateSmartDiffArgs(args: unknown): SmartDiffArgs;
143
147
  */
144
148
  export interface ExploreAreaArgs {
145
149
  path: string;
146
- include?: Array<'outline' | 'imports' | 'tests' | 'changes'>;
150
+ include?: Array<"outline" | "imports" | "tests" | "changes">;
147
151
  }
148
152
  export declare function validateExploreAreaArgs(args: unknown): ExploreAreaArgs;
149
153
  export interface SmartLogArgs {