token-pilot 0.42.2 → 0.43.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 +3 -3
- package/.claude-plugin/plugin.json +4 -2
- package/CHANGELOG.md +101 -0
- package/README.md +1 -1
- package/agents/tp-api-surface-tracker.md +1 -1
- package/agents/tp-audit-scanner.md +1 -1
- package/agents/tp-commit-writer.md +1 -1
- package/agents/tp-context-engineer.md +1 -1
- package/agents/tp-dead-code-finder.md +1 -1
- package/agents/tp-debugger.md +1 -1
- package/agents/tp-dep-health.md +1 -1
- package/agents/tp-doc-writer.md +1 -1
- package/agents/tp-history-explorer.md +1 -1
- package/agents/tp-impact-analyzer.md +1 -1
- package/agents/tp-incident-timeline.md +1 -1
- package/agents/tp-incremental-builder.md +1 -1
- package/agents/tp-migration-scout.md +1 -1
- package/agents/tp-onboard.md +1 -1
- package/agents/tp-performance-profiler.md +1 -1
- package/agents/tp-pr-reviewer.md +1 -1
- package/agents/tp-refactor-planner.md +1 -1
- package/agents/tp-review-impact.md +1 -1
- package/agents/tp-run.md +1 -1
- package/agents/tp-session-restorer.md +1 -1
- package/agents/tp-ship-coordinator.md +1 -1
- package/agents/tp-spec-writer.md +1 -1
- package/agents/tp-test-coverage-gapper.md +1 -1
- package/agents/tp-test-triage.md +1 -1
- package/agents/tp-test-writer.md +1 -1
- package/dist/ast-index/client.d.ts +16 -0
- package/dist/ast-index/client.js +30 -0
- package/dist/cli/install-statusline.d.ts +23 -4
- package/dist/cli/install-statusline.js +62 -55
- package/dist/core/validation.d.ts +17 -0
- package/dist/core/validation.js +54 -0
- package/dist/handlers/module-route.d.ts +20 -0
- package/dist/handlers/module-route.js +73 -0
- package/dist/index.js +3 -3
- package/dist/server/tool-definitions.d.ts +250 -0
- package/dist/server/tool-definitions.js +40 -0
- package/dist/server.js +42 -2
- package/docs/tools.md +2 -1
- package/package.json +1 -1
- package/skills/guide/SKILL.md +4 -0
- package/skills/install/SKILL.md +4 -0
- package/skills/stats/SKILL.md +3 -0
|
@@ -24,70 +24,73 @@
|
|
|
24
24
|
import { readFile, writeFile, mkdir, access } from "node:fs/promises";
|
|
25
25
|
import { homedir } from "node:os";
|
|
26
26
|
import { dirname, join } from "node:path";
|
|
27
|
-
/**
|
|
27
|
+
/** TP-only badge command (DEFAULT — just token-pilot's [TP] badge). */
|
|
28
|
+
export const TP_ONLY_COMMAND = 'bash "$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/tp-statusline.sh 2>/dev/null | head -1)"';
|
|
29
|
+
/**
|
|
30
|
+
* Chain command — renders token-pilot's badge AND any other ecosystem
|
|
31
|
+
* badge (caveman) side by side. Opt-in via `--chain`.
|
|
32
|
+
*/
|
|
28
33
|
export const CHAIN_COMMAND = 'bash "$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)"';
|
|
29
34
|
/**
|
|
30
|
-
* Pure decision: given the current statusline status, what
|
|
31
|
-
* installer do
|
|
35
|
+
* Pure decision: given the current statusline status + options, what
|
|
36
|
+
* should the installer do and which command should it write?
|
|
37
|
+
*
|
|
38
|
+
* v0.42.3 — DEFAULT is now the TP-only badge. The previous default
|
|
39
|
+
* silently installed the chain wrapper, which also rendered caveman's
|
|
40
|
+
* badge — presumptuous (we install OUR badge, we don't decide that a
|
|
41
|
+
* third-party tool's badge belongs in your status bar). `--chain`
|
|
42
|
+
* opts into the combined view. token-pilot's own previous choices
|
|
43
|
+
* (tp-only ↔ chain) are switched freely; a non-token-pilot statusLine
|
|
44
|
+
* (caveman-only, custom) is never clobbered without `--force`.
|
|
32
45
|
*/
|
|
33
|
-
export function decideStatuslineAction(status,
|
|
46
|
+
export function decideStatuslineAction(status, opts) {
|
|
47
|
+
const command = opts.chain ? CHAIN_COMMAND : TP_ONLY_COMMAND;
|
|
48
|
+
const badgeDesc = opts.chain ? "caveman + [TP] badges" : "[TP] badge";
|
|
49
|
+
const noWrite = (action, message) => ({
|
|
50
|
+
write: false,
|
|
51
|
+
command,
|
|
52
|
+
result: { action, message },
|
|
53
|
+
});
|
|
54
|
+
const doWrite = (action, message) => ({
|
|
55
|
+
write: true,
|
|
56
|
+
command,
|
|
57
|
+
result: { action, message },
|
|
58
|
+
});
|
|
34
59
|
switch (status) {
|
|
35
60
|
case "not-configured":
|
|
36
|
-
return {
|
|
37
|
-
write: true,
|
|
38
|
-
result: {
|
|
39
|
-
action: "installed",
|
|
40
|
-
message: "statusLine configured — the [TP] badge will show in your status bar (restart Claude Code).",
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
case "configured-caveman-only":
|
|
61
|
+
return doWrite("installed", `statusLine configured — the ${badgeDesc} will show in your status bar (restart Claude Code).`);
|
|
44
62
|
case "configured-tp-only":
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
message: "statusLine upgraded to the chain wrapper — both caveman and [TP] badges now render side by side.",
|
|
50
|
-
},
|
|
51
|
-
};
|
|
63
|
+
// Already ours. Switch only if the user asked for the chain.
|
|
64
|
+
return opts.chain
|
|
65
|
+
? doWrite("upgraded", "statusLine upgraded to the chain wrapper — caveman + [TP] now render side by side.")
|
|
66
|
+
: noWrite("noop", "statusLine already shows the [TP] badge. Nothing to do.");
|
|
52
67
|
case "configured-chain":
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
// Also ours. Switch to tp-only unless the user wants the chain.
|
|
69
|
+
return opts.chain
|
|
70
|
+
? noWrite("noop", "statusLine already uses the chain wrapper. Nothing to do.")
|
|
71
|
+
: doWrite("installed", "statusLine switched to the [TP]-only badge (caveman badge removed from the status bar).");
|
|
72
|
+
case "configured-caveman-only":
|
|
73
|
+
// Caveman's own statusline — NOT ours. Don't replace it silently.
|
|
74
|
+
if (opts.chain) {
|
|
75
|
+
return doWrite("upgraded", "statusLine upgraded to the chain wrapper — keeps caveman and adds [TP].");
|
|
76
|
+
}
|
|
77
|
+
if (opts.force) {
|
|
78
|
+
return doWrite("installed", "Replaced caveman's statusLine with the [TP]-only badge (--force).");
|
|
79
|
+
}
|
|
80
|
+
return noWrite("skipped", "Your statusLine is caveman's. Left untouched. Run with --chain to show " +
|
|
81
|
+
"both badges, or --force to replace it with [TP] only.");
|
|
60
82
|
case "configured-other":
|
|
61
|
-
if (force) {
|
|
62
|
-
return {
|
|
63
|
-
write: true,
|
|
64
|
-
result: {
|
|
65
|
-
action: "installed",
|
|
66
|
-
message: "Replaced your custom statusLine with the token-pilot chain wrapper (--force).",
|
|
67
|
-
},
|
|
68
|
-
};
|
|
83
|
+
if (opts.force) {
|
|
84
|
+
return doWrite("installed", `Replaced your custom statusLine with the ${badgeDesc} (--force).`);
|
|
69
85
|
}
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
action: "skipped",
|
|
74
|
-
message: "You already have a custom statusLine — left untouched. " +
|
|
75
|
-
"To show the [TP] badge too, set statusLine.command to:\n " +
|
|
76
|
-
CHAIN_COMMAND +
|
|
77
|
-
"\nor re-run with --force to replace it.",
|
|
78
|
-
},
|
|
79
|
-
};
|
|
86
|
+
return noWrite("skipped", "You already have a custom statusLine — left untouched. To use the " +
|
|
87
|
+
`${badgeDesc}, re-run with --force, or set statusLine.command to:\n ` +
|
|
88
|
+
command);
|
|
80
89
|
case "unknown":
|
|
81
90
|
default:
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
action: "skipped",
|
|
86
|
-
message: "Could not read ~/.claude/settings.json as JSON — not modifying it. " +
|
|
87
|
-
"Add this manually under \"statusLine\":\n " +
|
|
88
|
-
CHAIN_COMMAND,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
+
return noWrite("skipped", 'Could not read ~/.claude/settings.json as JSON — not modifying it. ' +
|
|
92
|
+
'Add this manually under "statusLine":\n ' +
|
|
93
|
+
command);
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
/**
|
|
@@ -126,9 +129,13 @@ export async function classifyStatuslineAt(settingsPath) {
|
|
|
126
129
|
*/
|
|
127
130
|
export async function handleInstallStatusline(argv, opts) {
|
|
128
131
|
const force = argv.includes("--force");
|
|
132
|
+
const chain = argv.includes("--chain");
|
|
129
133
|
const settingsPath = opts?.settingsPath ?? join(homedir(), ".claude", "settings.json");
|
|
130
134
|
const status = await classifyStatuslineAt(settingsPath);
|
|
131
|
-
const { write, result } = decideStatuslineAction(status,
|
|
135
|
+
const { write, command, result } = decideStatuslineAction(status, {
|
|
136
|
+
force,
|
|
137
|
+
chain,
|
|
138
|
+
});
|
|
132
139
|
if (!write) {
|
|
133
140
|
process.stdout.write(`[token-pilot] ${result.message}\n`);
|
|
134
141
|
return 0;
|
|
@@ -145,7 +152,7 @@ export async function handleInstallStatusline(argv, opts) {
|
|
|
145
152
|
catch {
|
|
146
153
|
/* fresh file — start clean (only reached when status was safe) */
|
|
147
154
|
}
|
|
148
|
-
settings.statusLine = { type: "command", command
|
|
155
|
+
settings.statusLine = { type: "command", command };
|
|
149
156
|
try {
|
|
150
157
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
151
158
|
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
@@ -145,6 +145,23 @@ export interface ModuleInfoArgs {
|
|
|
145
145
|
check?: "deps" | "dependents" | "api" | "unused-deps" | "all";
|
|
146
146
|
}
|
|
147
147
|
export declare function validateModuleInfoArgs(args: unknown): ModuleInfoArgs;
|
|
148
|
+
/**
|
|
149
|
+
* Validate module_route arguments.
|
|
150
|
+
*
|
|
151
|
+
* Wraps ast-index 3.44 `module-route` — the transitive dependency path
|
|
152
|
+
* between two modules. `from`/`to` are required; everything else mirrors
|
|
153
|
+
* the CLI flags with sane caps.
|
|
154
|
+
*/
|
|
155
|
+
export interface ModuleRouteArgs {
|
|
156
|
+
from: string;
|
|
157
|
+
to: string;
|
|
158
|
+
all?: boolean;
|
|
159
|
+
maxPaths?: number;
|
|
160
|
+
maxDepth?: number;
|
|
161
|
+
viaKind?: "api" | "implementation" | "all";
|
|
162
|
+
format?: "text" | "json" | "mermaid" | "dot";
|
|
163
|
+
}
|
|
164
|
+
export declare function validateModuleRouteArgs(args: unknown): ModuleRouteArgs;
|
|
148
165
|
/**
|
|
149
166
|
* Validate smart_diff arguments.
|
|
150
167
|
*/
|
package/dist/core/validation.js
CHANGED
|
@@ -428,6 +428,60 @@ export function validateModuleInfoArgs(args) {
|
|
|
428
428
|
check: check ?? "all",
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
|
+
export function validateModuleRouteArgs(args) {
|
|
432
|
+
if (!args || typeof args !== "object") {
|
|
433
|
+
throw new Error('Arguments must be an object with "from" and "to" parameters.');
|
|
434
|
+
}
|
|
435
|
+
const a = args;
|
|
436
|
+
if (typeof a.from !== "string" || a.from.length === 0) {
|
|
437
|
+
throw new Error('Required parameter "from" must be a non-empty string.');
|
|
438
|
+
}
|
|
439
|
+
if (typeof a.to !== "string" || a.to.length === 0) {
|
|
440
|
+
throw new Error('Required parameter "to" must be a non-empty string.');
|
|
441
|
+
}
|
|
442
|
+
let viaKind;
|
|
443
|
+
if (a.viaKind !== undefined && a.viaKind !== null) {
|
|
444
|
+
const valid = ["api", "implementation", "all"];
|
|
445
|
+
if (typeof a.viaKind !== "string" || !valid.includes(a.viaKind)) {
|
|
446
|
+
throw new Error(`"viaKind" must be one of: ${valid.join(", ")}`);
|
|
447
|
+
}
|
|
448
|
+
viaKind = a.viaKind;
|
|
449
|
+
}
|
|
450
|
+
let format;
|
|
451
|
+
if (a.format !== undefined && a.format !== null) {
|
|
452
|
+
const valid = ["text", "json", "mermaid", "dot"];
|
|
453
|
+
if (typeof a.format !== "string" || !valid.includes(a.format)) {
|
|
454
|
+
throw new Error(`"format" must be one of: ${valid.join(", ")}`);
|
|
455
|
+
}
|
|
456
|
+
format = a.format;
|
|
457
|
+
}
|
|
458
|
+
// Clamp numeric caps to safe ranges — never trust the caller blindly.
|
|
459
|
+
let maxPaths;
|
|
460
|
+
if (a.maxPaths !== undefined && a.maxPaths !== null) {
|
|
461
|
+
const n = Number(a.maxPaths);
|
|
462
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
463
|
+
throw new Error('"maxPaths" must be a positive number.');
|
|
464
|
+
}
|
|
465
|
+
maxPaths = Math.min(Math.floor(n), 200);
|
|
466
|
+
}
|
|
467
|
+
let maxDepth;
|
|
468
|
+
if (a.maxDepth !== undefined && a.maxDepth !== null) {
|
|
469
|
+
const n = Number(a.maxDepth);
|
|
470
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
471
|
+
throw new Error('"maxDepth" must be a positive number.');
|
|
472
|
+
}
|
|
473
|
+
maxDepth = Math.min(Math.floor(n), 50);
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
from: a.from,
|
|
477
|
+
to: a.to,
|
|
478
|
+
all: a.all === true,
|
|
479
|
+
maxPaths,
|
|
480
|
+
maxDepth,
|
|
481
|
+
viaKind,
|
|
482
|
+
format,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
431
485
|
export function validateSmartDiffArgs(args) {
|
|
432
486
|
if (!args || typeof args !== "object")
|
|
433
487
|
return { scope: "unstaged" };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AstIndexClient } from "../ast-index/client.js";
|
|
2
|
+
import type { ModuleRouteArgs } from "../core/validation.js";
|
|
3
|
+
/**
|
|
4
|
+
* module_route — transitive dependency path(s) between two modules.
|
|
5
|
+
*
|
|
6
|
+
* Thin wrapper over ast-index 3.44 `module-route`. The CLI already
|
|
7
|
+
* produces compact, purpose-built output (a path listing, or
|
|
8
|
+
* mermaid/dot/json for diagramming), so the handler only frames it and
|
|
9
|
+
* handles the empty / degraded cases — it does not re-parse the graph.
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleModuleRoute(args: ModuleRouteArgs, _projectRoot: string, astIndex: AstIndexClient): Promise<{
|
|
12
|
+
content: Array<{
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}>;
|
|
16
|
+
meta: {
|
|
17
|
+
files: string[];
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
//# sourceMappingURL=module-route.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* module_route — transitive dependency path(s) between two modules.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper over ast-index 3.44 `module-route`. The CLI already
|
|
5
|
+
* produces compact, purpose-built output (a path listing, or
|
|
6
|
+
* mermaid/dot/json for diagramming), so the handler only frames it and
|
|
7
|
+
* handles the empty / degraded cases — it does not re-parse the graph.
|
|
8
|
+
*/
|
|
9
|
+
export async function handleModuleRoute(args, _projectRoot, astIndex) {
|
|
10
|
+
// Degradation check — same contract as module_info.
|
|
11
|
+
if (astIndex.isDisabled() || astIndex.isOversized()) {
|
|
12
|
+
return {
|
|
13
|
+
content: [
|
|
14
|
+
{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: "⚠ ast-index unavailable — module_route requires ast-index.\n" +
|
|
17
|
+
"DEGRADED: Use module_info() on each module + related_files() to trace dependencies manually.",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
meta: { files: [] },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const output = await astIndex.moduleRoute({
|
|
24
|
+
from: args.from,
|
|
25
|
+
to: args.to,
|
|
26
|
+
all: args.all,
|
|
27
|
+
maxPaths: args.maxPaths,
|
|
28
|
+
maxDepth: args.maxDepth,
|
|
29
|
+
viaKind: args.viaKind,
|
|
30
|
+
format: args.format,
|
|
31
|
+
});
|
|
32
|
+
const header = `MODULE ROUTE: ${args.from} → ${args.to}`;
|
|
33
|
+
if (output == null) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `${header}\n\n` +
|
|
39
|
+
"⚠ module-route failed (index unavailable or command error).\n" +
|
|
40
|
+
"HINT: run `npx token-pilot doctor` to check ast-index, or fall back to module_info().",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
meta: { files: [] },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const body = output.trim();
|
|
47
|
+
if (body.length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: `${header}\n\n` +
|
|
53
|
+
`No dependency path returned from "${args.from}" to "${args.to}" ` +
|
|
54
|
+
`(within ${args.maxDepth ?? 20} hops).\n` +
|
|
55
|
+
"This means one of:\n" +
|
|
56
|
+
" • the modules are genuinely unrelated, or\n" +
|
|
57
|
+
" • the module-dependency graph isn't indexed yet — run `ast-index rebuild`.\n" +
|
|
58
|
+
'HINT: also try a higher "maxDepth" / wider "viaKind", or module_info() to confirm each module resolves.',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
meta: { files: [] },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// For machine formats (json/mermaid/dot) pass the payload through clean —
|
|
65
|
+
// a header would corrupt a diagram/parse. Text format gets the header.
|
|
66
|
+
const isMachineFormat = args.format === "json" || args.format === "mermaid" || args.format === "dot";
|
|
67
|
+
const text = isMachineFormat ? body : `${header}\n\n${body}`;
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text }],
|
|
70
|
+
meta: { files: [] },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=module-route.js.map
|
package/dist/index.js
CHANGED
|
@@ -1625,12 +1625,12 @@ Usage:
|
|
|
1625
1625
|
Quick start:
|
|
1626
1626
|
npx token-pilot init Setup .mcp.json (token-pilot + context-mode)
|
|
1627
1627
|
|
|
1628
|
-
MCP Tools (
|
|
1628
|
+
MCP Tools (23):
|
|
1629
1629
|
smart_read, read_symbol, read_symbols, read_range, read_section, read_diff,
|
|
1630
1630
|
read_for_edit, smart_read_many, find_usages, find_unused, related_files,
|
|
1631
1631
|
outline, project_overview, session_analytics, code_audit, module_info,
|
|
1632
|
-
smart_diff, explore_area, smart_log, test_summary,
|
|
1633
|
-
session_budget
|
|
1632
|
+
module_route, smart_diff, explore_area, smart_log, test_summary,
|
|
1633
|
+
session_snapshot, session_budget
|
|
1634
1634
|
`);
|
|
1635
1635
|
process.exit(0);
|
|
1636
1636
|
}
|