token-pilot 0.35.0 → 0.39.1

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 (47) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +79 -0
  4. package/agents/tp-api-surface-tracker.md +1 -1
  5. package/agents/tp-audit-scanner.md +1 -1
  6. package/agents/tp-commit-writer.md +1 -1
  7. package/agents/tp-context-engineer.md +1 -1
  8. package/agents/tp-dead-code-finder.md +1 -1
  9. package/agents/tp-debugger.md +1 -1
  10. package/agents/tp-dep-health.md +1 -1
  11. package/agents/tp-doc-writer.md +1 -1
  12. package/agents/tp-history-explorer.md +1 -1
  13. package/agents/tp-impact-analyzer.md +1 -1
  14. package/agents/tp-incident-timeline.md +1 -1
  15. package/agents/tp-incremental-builder.md +1 -1
  16. package/agents/tp-migration-scout.md +1 -1
  17. package/agents/tp-onboard.md +1 -1
  18. package/agents/tp-performance-profiler.md +1 -1
  19. package/agents/tp-pr-reviewer.md +1 -1
  20. package/agents/tp-refactor-planner.md +1 -1
  21. package/agents/tp-review-impact.md +1 -1
  22. package/agents/tp-run.md +1 -1
  23. package/agents/tp-session-restorer.md +1 -1
  24. package/agents/tp-ship-coordinator.md +1 -1
  25. package/agents/tp-spec-writer.md +1 -1
  26. package/agents/tp-test-coverage-gapper.md +1 -1
  27. package/agents/tp-test-triage.md +1 -1
  28. package/agents/tp-test-writer.md +1 -1
  29. package/dist/cli/stats.d.ts +2 -0
  30. package/dist/cli/stats.js +32 -0
  31. package/dist/cli/typo-guard.d.ts +1 -1
  32. package/dist/cli/typo-guard.js +2 -0
  33. package/dist/core/event-log.d.ts +7 -0
  34. package/dist/core/event-log.js +10 -1
  35. package/dist/core/workflow.d.ts +117 -0
  36. package/dist/core/workflow.js +269 -0
  37. package/dist/hooks/post-task.d.ts +18 -3
  38. package/dist/hooks/post-task.js +44 -11
  39. package/dist/hooks/pre-task.d.ts +9 -4
  40. package/dist/hooks/pre-task.js +23 -8
  41. package/dist/hooks/session-start.js +61 -0
  42. package/dist/index.d.ts +14 -0
  43. package/dist/index.js +129 -1
  44. package/package.json +1 -1
  45. package/skills/guide/SKILL.md +11 -0
  46. package/skills/install/SKILL.md +10 -0
  47. package/skills/stats/SKILL.md +8 -0
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import { installHook, uninstallHook, cleanStaleHookEntries, isTokenPilotPluginEn
27
27
  import { runHookEntryPoint } from "./hooks/safe-runner.js";
28
28
  import { loadErrors, formatErrorList } from "./core/error-log.js";
29
29
  import { appendDiagnostic } from "./core/event-log.js";
30
+ import { startWorkflow, endWorkflow, listWorkflows, workflowStatus, formatWorkflowStatus, formatWorkflowList, } from "./core/workflow.js";
30
31
  import { findBinary, installBinary, checkBinaryUpdate, isNewerVersion, } from "./ast-index/binary-manager.js";
31
32
  import { loadConfig } from "./config/loader.js";
32
33
  import { isDangerousRoot } from "./core/validation.js";
@@ -229,7 +230,30 @@ export async function main(cliArgs = process.argv.slice(2)) {
229
230
  /* never block dispatch on telemetry */
230
231
  });
231
232
  }
232
- const rendered = renderPreTaskOutput(decision);
233
+ // v0.38.0 fleet budget guard. When a workflow is active and
234
+ // its token ceiling is within reach, append a wind-down note
235
+ // to whatever the routing decision produced. The dispatch is
236
+ // never hard-blocked on budget (a half-finished fan-out is
237
+ // worse than a small overrun) — we advise, and surface an
238
+ // over-budget diagnostic so `workflow status` reflects it.
239
+ const { activeWorkflowId, workflowStatus, isWorkflowNearBudget } = await import("./core/workflow.js");
240
+ const wfId = activeWorkflowId();
241
+ let budgetNote = "";
242
+ if (wfId) {
243
+ const st = await workflowStatus(process.cwd(), wfId);
244
+ if (st && isWorkflowNearBudget(st)) {
245
+ budgetNote =
246
+ `\n\n[token-pilot] workflow ${wfId} is at ${st.pct ?? "~"}% of its ` +
247
+ `${st.budget_tokens} token ceiling — finish in-flight work and ` +
248
+ `report rather than starting new branches.`;
249
+ appendDiagnostic(process.cwd(), {
250
+ code: "workflow_near_budget",
251
+ level: "warn",
252
+ detail: { workflow_id: wfId, pct: st.pct, used: st.used_tokens },
253
+ }).catch(() => { });
254
+ }
255
+ }
256
+ const rendered = renderPreTaskOutput(decision, budgetNote);
233
257
  if (rendered)
234
258
  process.stdout.write(rendered);
235
259
  });
@@ -348,6 +372,15 @@ export async function main(cliArgs = process.argv.slice(2)) {
348
372
  process.stdout.write(formatErrorList(records) + "\n");
349
373
  return;
350
374
  }
375
+ case "workflow": {
376
+ // v0.38.0 — fleet workflow lifecycle. token-pilot owns the
377
+ // workflow boundary (we set TOKEN_PILOT_WORKFLOW_ID ourselves),
378
+ // so this works regardless of whether Claude Code's /workflow
379
+ // propagates an env var. Subcommands: start / end / status / list.
380
+ const code = await handleWorkflowCli(cliArgs.slice(1));
381
+ process.exit(code);
382
+ return;
383
+ }
351
384
  case "migrate-hooks": {
352
385
  // v0.33.0 — clean stale npx-cache / pinned-version token-pilot
353
386
  // hook entries from user-level + project-level settings.json so
@@ -903,6 +936,101 @@ export function handleHookEdit() {
903
936
  process.stdout.write(rendered);
904
937
  process.exit(0);
905
938
  }
939
+ /**
940
+ * v0.38.0 — `token-pilot workflow <subcommand>` CLI.
941
+ *
942
+ * start <goal> [--budget=N] [--max-parallel=N]
943
+ * Create a workflow envelope and print an `export
944
+ * TOKEN_PILOT_WORKFLOW_ID=<id>` line. Wrap a fan-out batch with
945
+ * this so every hook event gets tagged with the id.
946
+ * end [<id>] Stamp the workflow ended (defaults to active env id).
947
+ * status [<id>] Show live budget + task counts (defaults to env id).
948
+ * list All recorded workflows, newest first.
949
+ *
950
+ * Returns a process exit code.
951
+ */
952
+ export async function handleWorkflowCli(argv) {
953
+ const projectRoot = process.cwd();
954
+ const sub = argv[0];
955
+ const flag = (k) => {
956
+ for (const a of argv) {
957
+ if (a.startsWith(`--${k}=`))
958
+ return a.slice(k.length + 3);
959
+ }
960
+ return undefined;
961
+ };
962
+ const envId = process.env.TOKEN_PILOT_WORKFLOW_ID ||
963
+ process.env.CLAUDE_CODE_WORKFLOW_ID ||
964
+ undefined;
965
+ switch (sub) {
966
+ case "start": {
967
+ const goal = argv.slice(1).filter((a) => !a.startsWith("--")).join(" ");
968
+ if (!goal) {
969
+ process.stderr.write('workflow start: a goal is required — `token-pilot workflow start "review last sprint"`\n');
970
+ return 1;
971
+ }
972
+ const budgetRaw = flag("budget");
973
+ const parallelRaw = flag("max-parallel");
974
+ const env = await startWorkflow({
975
+ projectRoot,
976
+ goal,
977
+ budgetTokens: budgetRaw ? Number(budgetRaw) : null,
978
+ maxParallel: parallelRaw ? Number(parallelRaw) : null,
979
+ });
980
+ // The id goes to stdout as an `export` line so a user can do
981
+ // eval "$(token-pilot workflow start '...')"
982
+ // and have the env var set for the fan-out that follows.
983
+ process.stdout.write(`export TOKEN_PILOT_WORKFLOW_ID=${env.workflow_id}\n`);
984
+ process.stderr.write(`[token-pilot] workflow ${env.workflow_id} started` +
985
+ (env.budget_tokens ? ` · ${env.budget_tokens} token ceiling` : "") +
986
+ `\n`);
987
+ return 0;
988
+ }
989
+ case "end": {
990
+ const id = argv[1] && !argv[1].startsWith("--") ? argv[1] : envId;
991
+ if (!id) {
992
+ process.stderr.write("workflow end: no id given and TOKEN_PILOT_WORKFLOW_ID not set.\n");
993
+ return 1;
994
+ }
995
+ const env = await endWorkflow(projectRoot, id);
996
+ if (!env) {
997
+ process.stderr.write(`workflow end: unknown workflow "${id}".\n`);
998
+ return 1;
999
+ }
1000
+ const status = await workflowStatus(projectRoot, id);
1001
+ if (status)
1002
+ process.stdout.write(formatWorkflowStatus(status) + "\n");
1003
+ process.stderr.write(`[token-pilot] workflow ${id} ended.\n`);
1004
+ return 0;
1005
+ }
1006
+ case "status": {
1007
+ const id = argv[1] && !argv[1].startsWith("--") ? argv[1] : envId;
1008
+ if (!id) {
1009
+ process.stderr.write("workflow status: no id given and TOKEN_PILOT_WORKFLOW_ID not set.\n");
1010
+ return 1;
1011
+ }
1012
+ const status = await workflowStatus(projectRoot, id);
1013
+ if (!status) {
1014
+ process.stderr.write(`workflow status: unknown workflow "${id}".\n`);
1015
+ return 1;
1016
+ }
1017
+ process.stdout.write(formatWorkflowStatus(status) + "\n");
1018
+ return 0;
1019
+ }
1020
+ case "list": {
1021
+ const workflows = await listWorkflows(projectRoot);
1022
+ process.stdout.write(formatWorkflowList(workflows) + "\n");
1023
+ return 0;
1024
+ }
1025
+ default:
1026
+ process.stderr.write("Usage: token-pilot workflow <start|end|status|list>\n" +
1027
+ ' start "<goal>" [--budget=N] [--max-parallel=N]\n' +
1028
+ " end [<id>]\n" +
1029
+ " status [<id>]\n" +
1030
+ " list\n");
1031
+ return sub ? 1 : 0;
1032
+ }
1033
+ }
906
1034
  export async function handleInstallHook(projectRoot) {
907
1035
  // v0.26.5 — plugin-aware early-return. If we're running as a Claude
908
1036
  // Code plugin (CLAUDE_PLUGIN_ROOT set) the hooks are already declared
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.35.0",
3
+ "version": "0.39.1",
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",
@@ -3,6 +3,17 @@ name: guide
3
3
  description: Show a quick-reference guide for all Token Pilot tools — when to use each one
4
4
  command: guide
5
5
  user_invocable: true
6
+ # v0.36.0 — disallowed-tools is a Claude Code 2.1.152+ field that
7
+ # strips the listed tools from the model's surface while this skill
8
+ # is active. The guide just renders Markdown; it never needs Edit /
9
+ # Write / Bash / Task. Defense-in-depth so a runaway model can't
10
+ # mutate the workspace while a help screen is on display.
11
+ disallowed-tools:
12
+ - Bash
13
+ - Edit
14
+ - MultiEdit
15
+ - Write
16
+ - Task
6
17
  ---
7
18
 
8
19
  Display the following Token Pilot tool reference to the user. Show it exactly as formatted below.
@@ -3,6 +3,16 @@ name: install
3
3
  description: Install or check ast-index binary (auto-downloads if missing)
4
4
  command: install
5
5
  user_invocable: true
6
+ # v0.36.0 — disallowed-tools (Claude Code 2.1.152+). The install
7
+ # skill drives one `npx token-pilot install-ast-index` Bash command;
8
+ # nothing else. Keep Bash, block all write/edit/delegation tools so
9
+ # a rogue interpretation can't extend the install into arbitrary
10
+ # workspace mutation.
11
+ disallowed-tools:
12
+ - Edit
13
+ - MultiEdit
14
+ - Write
15
+ - Task
6
16
  ---
7
17
 
8
18
  Run the following command to install or verify the ast-index binary:
@@ -3,6 +3,14 @@ name: stats
3
3
  description: Show Token Pilot session analytics — token savings, per-tool breakdown, top files, per-agent grouping
4
4
  command: stats
5
5
  user_invocable: true
6
+ # v0.36.0 — disallowed-tools (Claude Code 2.1.152+). stats only reads
7
+ # the events log and runs `token-pilot stats` variants. No
8
+ # Edit/Write/Task ever — block them defensively.
9
+ disallowed-tools:
10
+ - Edit
11
+ - MultiEdit
12
+ - Write
13
+ - Task
6
14
  ---
7
15
 
8
16
  Two entry points: