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.
Files changed (46) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +4 -2
  3. package/CHANGELOG.md +101 -0
  4. package/README.md +1 -1
  5. package/agents/tp-api-surface-tracker.md +1 -1
  6. package/agents/tp-audit-scanner.md +1 -1
  7. package/agents/tp-commit-writer.md +1 -1
  8. package/agents/tp-context-engineer.md +1 -1
  9. package/agents/tp-dead-code-finder.md +1 -1
  10. package/agents/tp-debugger.md +1 -1
  11. package/agents/tp-dep-health.md +1 -1
  12. package/agents/tp-doc-writer.md +1 -1
  13. package/agents/tp-history-explorer.md +1 -1
  14. package/agents/tp-impact-analyzer.md +1 -1
  15. package/agents/tp-incident-timeline.md +1 -1
  16. package/agents/tp-incremental-builder.md +1 -1
  17. package/agents/tp-migration-scout.md +1 -1
  18. package/agents/tp-onboard.md +1 -1
  19. package/agents/tp-performance-profiler.md +1 -1
  20. package/agents/tp-pr-reviewer.md +1 -1
  21. package/agents/tp-refactor-planner.md +1 -1
  22. package/agents/tp-review-impact.md +1 -1
  23. package/agents/tp-run.md +1 -1
  24. package/agents/tp-session-restorer.md +1 -1
  25. package/agents/tp-ship-coordinator.md +1 -1
  26. package/agents/tp-spec-writer.md +1 -1
  27. package/agents/tp-test-coverage-gapper.md +1 -1
  28. package/agents/tp-test-triage.md +1 -1
  29. package/agents/tp-test-writer.md +1 -1
  30. package/dist/ast-index/client.d.ts +16 -0
  31. package/dist/ast-index/client.js +30 -0
  32. package/dist/cli/install-statusline.d.ts +23 -4
  33. package/dist/cli/install-statusline.js +62 -55
  34. package/dist/core/validation.d.ts +17 -0
  35. package/dist/core/validation.js +54 -0
  36. package/dist/handlers/module-route.d.ts +20 -0
  37. package/dist/handlers/module-route.js +73 -0
  38. package/dist/index.js +3 -3
  39. package/dist/server/tool-definitions.d.ts +250 -0
  40. package/dist/server/tool-definitions.js +40 -0
  41. package/dist/server.js +42 -2
  42. package/docs/tools.md +2 -1
  43. package/package.json +1 -1
  44. package/skills/guide/SKILL.md +4 -0
  45. package/skills/install/SKILL.md +4 -0
  46. 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
- /** The version-agnostic chain command (auto-picks the newest plugin dir). */
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 should the
31
- * installer do? Separated from I/O for unit tests.
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, force) {
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
- return {
46
- write: true,
47
- result: {
48
- action: "upgraded",
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
- return {
54
- write: false,
55
- result: {
56
- action: "noop",
57
- message: "statusLine already uses the token-pilot chain wrapper. Nothing to do.",
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
- write: false,
72
- result: {
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
- write: false,
84
- result: {
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, force);
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: CHAIN_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
  */
@@ -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 (22):
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, session_snapshot,
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
  }