token-pilot 0.32.0 → 0.33.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 +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.js +17 -1
- package/dist/cli/install-agents.d.ts +18 -0
- package/dist/cli/install-agents.js +88 -1
- package/dist/cli/stats.js +9 -2
- package/dist/core/error-log.d.ts +86 -0
- package/dist/core/error-log.js +228 -0
- package/dist/core/event-log.d.ts +49 -1
- package/dist/core/event-log.js +114 -0
- package/dist/core/validation.d.ts +12 -0
- package/dist/core/validation.js +38 -8
- package/dist/handlers/smart-log.js +7 -2
- package/dist/hooks/installer.d.ts +40 -0
- package/dist/hooks/installer.js +145 -2
- package/dist/hooks/pre-task.js +44 -10
- package/dist/hooks/safe-runner.d.ts +48 -0
- package/dist/hooks/safe-runner.js +73 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +284 -63
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,7 +23,10 @@ import { execFile } from "node:child_process";
|
|
|
23
23
|
import { promisify } from "node:util";
|
|
24
24
|
import { fileURLToPath } from "node:url";
|
|
25
25
|
import { createServer } from "./server.js";
|
|
26
|
-
import { installHook, uninstallHook } from "./hooks/installer.js";
|
|
26
|
+
import { installHook, uninstallHook, cleanStaleHookEntries, isTokenPilotPluginEnabled, } from "./hooks/installer.js";
|
|
27
|
+
import { runHookEntryPoint } from "./hooks/safe-runner.js";
|
|
28
|
+
import { loadErrors, formatErrorList } from "./core/error-log.js";
|
|
29
|
+
import { appendDiagnostic } from "./core/event-log.js";
|
|
27
30
|
import { findBinary, installBinary, checkBinaryUpdate, isNewerVersion, } from "./ast-index/binary-manager.js";
|
|
28
31
|
import { loadConfig } from "./config/loader.js";
|
|
29
32
|
import { isDangerousRoot } from "./core/validation.js";
|
|
@@ -123,18 +126,27 @@ export async function main(cliArgs = process.argv.slice(2)) {
|
|
|
123
126
|
}
|
|
124
127
|
switch (cliArgs[0]) {
|
|
125
128
|
case "hook-read": {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
// v0.34.0 — wrap in runHookEntryPoint so any unexpected throw
|
|
130
|
+
// lands in `~/.token-pilot/hook-errors.jsonl` instead of being
|
|
131
|
+
// swallowed silently. handleHookRead has its own internal
|
|
132
|
+
// try/catch for known I/O failures; the wrapper is the safety
|
|
133
|
+
// net for everything else.
|
|
134
|
+
await runHookEntryPoint({ hook: "hook-read" }, async () => {
|
|
135
|
+
const cfg = await loadConfig(process.cwd());
|
|
136
|
+
await handleHookRead(cliArgs[1], cfg.hooks.mode, cfg.hooks.denyThreshold, process.cwd(), {
|
|
137
|
+
adaptiveThreshold: cfg.hooks.adaptiveThreshold,
|
|
138
|
+
adaptiveBudgetTokens: cfg.hooks.adaptiveBudgetTokens,
|
|
139
|
+
});
|
|
130
140
|
});
|
|
131
141
|
return;
|
|
132
142
|
}
|
|
133
143
|
case "hook-edit":
|
|
134
|
-
|
|
144
|
+
await runHookEntryPoint({ hook: "hook-edit" }, async () => {
|
|
145
|
+
handleHookEdit();
|
|
146
|
+
});
|
|
135
147
|
return;
|
|
136
148
|
case "hook-post-bash": {
|
|
137
|
-
|
|
149
|
+
await runHookEntryPoint({ hook: "hook-post-bash" }, async () => {
|
|
138
150
|
const stdin = readFileSync(0, "utf-8");
|
|
139
151
|
const input = JSON.parse(stdin);
|
|
140
152
|
const advice = decidePostBashAdvice(input, {
|
|
@@ -143,61 +155,53 @@ export async function main(cliArgs = process.argv.slice(2)) {
|
|
|
143
155
|
const rendered = renderPostBashHookOutput(advice);
|
|
144
156
|
if (rendered)
|
|
145
157
|
process.stdout.write(rendered);
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
/* silent — hook must not break */
|
|
149
|
-
}
|
|
150
|
-
process.exit(0);
|
|
158
|
+
});
|
|
151
159
|
return;
|
|
152
160
|
}
|
|
153
161
|
case "hook-pre-bash": {
|
|
154
162
|
// v0.28.0 — passive pre-intercept for heavy Bash commands.
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
// nudge the agent to the cheaper MCP equivalent.
|
|
158
|
-
try {
|
|
163
|
+
// v0.34.0 — runHookEntryPoint covers throw paths.
|
|
164
|
+
await runHookEntryPoint({ hook: "hook-pre-bash" }, async () => {
|
|
159
165
|
const stdin = readFileSync(0, "utf-8");
|
|
160
166
|
const input = JSON.parse(stdin);
|
|
161
167
|
const decision = decidePreBash(input, parseEnforcementMode(process.env.TOKEN_PILOT_MODE));
|
|
162
168
|
const rendered = renderPreBashOutput(decision);
|
|
163
169
|
if (rendered)
|
|
164
170
|
process.stdout.write(rendered);
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
167
|
-
/* silent — hook must not break */
|
|
168
|
-
}
|
|
169
|
-
process.exit(0);
|
|
171
|
+
});
|
|
170
172
|
return;
|
|
171
173
|
}
|
|
172
174
|
case "hook-pre-grep": {
|
|
173
175
|
// v0.28.0 — passive pre-intercept for symbol-like Grep patterns.
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
+
// v0.34.0 — runHookEntryPoint covers throw paths.
|
|
177
|
+
await runHookEntryPoint({ hook: "hook-pre-grep" }, async () => {
|
|
176
178
|
const stdin = readFileSync(0, "utf-8");
|
|
177
179
|
const input = JSON.parse(stdin);
|
|
178
180
|
const decision = decidePreGrep(input, parseEnforcementMode(process.env.TOKEN_PILOT_MODE));
|
|
179
181
|
const rendered = renderPreGrepOutput(decision);
|
|
180
182
|
if (rendered)
|
|
181
183
|
process.stdout.write(rendered);
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
/* silent — hook must not break */
|
|
185
|
-
}
|
|
186
|
-
process.exit(0);
|
|
184
|
+
});
|
|
187
185
|
return;
|
|
188
186
|
}
|
|
189
187
|
case "hook-pre-task": {
|
|
190
188
|
// v0.31.0 Pack 2 — route general-purpose Task dispatches to a
|
|
191
|
-
// `tp-*` specialist when the description clearly matches.
|
|
192
|
-
//
|
|
193
|
-
|
|
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 {
|
|
189
|
+
// `tp-*` specialist when the description clearly matches.
|
|
190
|
+
// v0.34.0 — error/diagnostic logging via runHookEntryPoint.
|
|
191
|
+
await runHookEntryPoint({ hook: "hook-pre-task" }, async () => {
|
|
197
192
|
const stdin = readFileSync(0, "utf-8");
|
|
198
193
|
const input = JSON.parse(stdin);
|
|
199
194
|
const agentIndex = await getAgentIndex();
|
|
200
195
|
const force = process.env.TOKEN_PILOT_FORCE_SUBAGENTS === "1";
|
|
196
|
+
// v0.34.0 diagnostic: B4 — empty index + force is a fail
|
|
197
|
+
// case (we deny, but record so the user can see why).
|
|
198
|
+
if (force && agentIndex.agents.length === 0) {
|
|
199
|
+
await appendDiagnostic(process.cwd(), {
|
|
200
|
+
code: "force_subagents_no_agents",
|
|
201
|
+
level: "warn",
|
|
202
|
+
detail: { hint: "run `npx token-pilot install-agents`" },
|
|
203
|
+
});
|
|
204
|
+
}
|
|
201
205
|
const decision = decidePreTask(input, {
|
|
202
206
|
mode: parseEnforcementMode(process.env.TOKEN_PILOT_MODE),
|
|
203
207
|
agentIndex,
|
|
@@ -206,15 +210,11 @@ export async function main(cliArgs = process.argv.slice(2)) {
|
|
|
206
210
|
const rendered = renderPreTaskOutput(decision);
|
|
207
211
|
if (rendered)
|
|
208
212
|
process.stdout.write(rendered);
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
/* silent — hook must not break */
|
|
212
|
-
}
|
|
213
|
-
process.exit(0);
|
|
213
|
+
});
|
|
214
214
|
return;
|
|
215
215
|
}
|
|
216
216
|
case "hook-post-task": {
|
|
217
|
-
|
|
217
|
+
await runHookEntryPoint({ hook: "hook-post-task" }, async () => {
|
|
218
218
|
const stdin = readFileSync(0, "utf-8");
|
|
219
219
|
const input = JSON.parse(stdin);
|
|
220
220
|
const message = await processPostTask(process.cwd(), homedir(), input);
|
|
@@ -226,30 +226,24 @@ export async function main(cliArgs = process.argv.slice(2)) {
|
|
|
226
226
|
},
|
|
227
227
|
}));
|
|
228
228
|
}
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
/* silent — hook must not break */
|
|
232
|
-
}
|
|
233
|
-
process.exit(0);
|
|
229
|
+
});
|
|
234
230
|
return;
|
|
235
231
|
}
|
|
236
232
|
case "hook-session-start": {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
233
|
+
await runHookEntryPoint({ hook: "hook-session-start" }, async () => {
|
|
234
|
+
const cfg = await loadConfig(process.cwd());
|
|
235
|
+
// sessionStart.enabled is independent of hooks.mode by design.
|
|
236
|
+
if (!cfg.sessionStart.enabled)
|
|
237
|
+
return;
|
|
238
|
+
const result = await handleSessionStart({
|
|
239
|
+
projectRoot: process.cwd(),
|
|
240
|
+
homeDir: homedir(),
|
|
241
|
+
sessionStartConfig: cfg.sessionStart,
|
|
242
|
+
});
|
|
243
|
+
if (result) {
|
|
244
|
+
process.stdout.write(result);
|
|
245
|
+
}
|
|
248
246
|
});
|
|
249
|
-
if (result) {
|
|
250
|
-
process.stdout.write(result);
|
|
251
|
-
}
|
|
252
|
-
process.exit(0);
|
|
253
247
|
return;
|
|
254
248
|
}
|
|
255
249
|
case "install-hook":
|
|
@@ -258,6 +252,47 @@ export async function main(cliArgs = process.argv.slice(2)) {
|
|
|
258
252
|
case "uninstall-hook":
|
|
259
253
|
await handleUninstallHook(cliArgs[1] || process.cwd());
|
|
260
254
|
return;
|
|
255
|
+
case "errors": {
|
|
256
|
+
// v0.34.0 — surface ~/.token-pilot/hook-errors.jsonl with optional
|
|
257
|
+
// filters: --tail=N --code=<x> --hook=<y> --level=<info|warn|error>
|
|
258
|
+
const args = cliArgs.slice(1);
|
|
259
|
+
const flag = (k) => {
|
|
260
|
+
for (const a of args) {
|
|
261
|
+
if (a.startsWith(`--${k}=`))
|
|
262
|
+
return a.slice(k.length + 3);
|
|
263
|
+
if (a === `--${k}` || a === `-${k}`)
|
|
264
|
+
return "true";
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
};
|
|
268
|
+
const tailRaw = flag("tail");
|
|
269
|
+
const records = await loadErrors({
|
|
270
|
+
tail: tailRaw ? Number(tailRaw) : undefined,
|
|
271
|
+
code: flag("code"),
|
|
272
|
+
hook: flag("hook"),
|
|
273
|
+
level: flag("level"),
|
|
274
|
+
});
|
|
275
|
+
process.stdout.write(formatErrorList(records) + "\n");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
case "migrate-hooks": {
|
|
279
|
+
// v0.33.0 — clean stale npx-cache / pinned-version token-pilot
|
|
280
|
+
// hook entries from user-level + project-level settings.json so
|
|
281
|
+
// the bundled plugin's hooks/hooks.json takes over via
|
|
282
|
+
// CLAUDE_PLUGIN_ROOT. Safe and idempotent.
|
|
283
|
+
const targets = [
|
|
284
|
+
resolve(homedir(), ".claude", "settings.json"),
|
|
285
|
+
resolve(cliArgs[1] || process.cwd(), ".claude", "settings.json"),
|
|
286
|
+
];
|
|
287
|
+
let total = 0;
|
|
288
|
+
for (const path of targets) {
|
|
289
|
+
const r = await cleanStaleHookEntries(path);
|
|
290
|
+
console.log(r.message);
|
|
291
|
+
total += r.staleEntriesRemoved;
|
|
292
|
+
}
|
|
293
|
+
console.log(`\nDone — removed ${total} stale entr${total === 1 ? "y" : "ies"}.`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
261
296
|
case "install-ast-index":
|
|
262
297
|
await handleInstallAstIndex();
|
|
263
298
|
return;
|
|
@@ -347,6 +382,34 @@ export function looksLikePluginCacheDir(candidate) {
|
|
|
347
382
|
return false;
|
|
348
383
|
}
|
|
349
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* v0.33.0 (B8) — reject candidates that are obviously not a project
|
|
387
|
+
* directory. Triggered by WSL launches where the shell starts in
|
|
388
|
+
* `C:\Windows\System32`, `/mnt/c/Windows/...`, or a UNC path. Without
|
|
389
|
+
* this guard, `git rev-parse --show-toplevel` either fails noisily or
|
|
390
|
+
* returns the Windows tree, leaving every subsequent git/MCP call
|
|
391
|
+
* looking at the wrong filesystem.
|
|
392
|
+
*
|
|
393
|
+
* Conservative — only matches paths we are certain are not user code.
|
|
394
|
+
*/
|
|
395
|
+
export function isWindowsSystemPath(candidate) {
|
|
396
|
+
if (!candidate)
|
|
397
|
+
return false;
|
|
398
|
+
// Native Windows: C:\Windows\... or C:/Windows/...
|
|
399
|
+
if (/^[A-Za-z]:[\\/](Windows|Program Files|ProgramData)\b/i.test(candidate)) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
// WSL view of Windows: /mnt/c/Windows/... (or any drive letter)
|
|
403
|
+
if (/^\/mnt\/[a-z]\/(windows|program files|programdata)\b/i.test(candidate)) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
// UNC path — almost never a project root and `cwd` cannot be set to
|
|
407
|
+
// one reliably anyway. Better to skip than to misroute.
|
|
408
|
+
if (/^\\\\/.test(candidate)) {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
350
413
|
export async function startServer(cliArgs = process.argv.slice(2)) {
|
|
351
414
|
// Defensive: ignore a poisoned cliArgs[0] pointing into the plugin install
|
|
352
415
|
// dir. Fall through to the INIT_CWD / PWD / cwd detection below — same
|
|
@@ -357,14 +420,38 @@ export async function startServer(cliArgs = process.argv.slice(2)) {
|
|
|
357
420
|
explicitRoot = "";
|
|
358
421
|
}
|
|
359
422
|
let projectRoot = explicitRoot || process.cwd();
|
|
360
|
-
// Detect git root for reliable project root
|
|
361
|
-
//
|
|
423
|
+
// Detect git root for reliable project root.
|
|
424
|
+
// v0.33.0 (B8) — on WSL the shell is sometimes launched with the
|
|
425
|
+
// working directory pointing into Windows' filesystem
|
|
426
|
+
// (`/mnt/c/Windows/system32` or, worse, a UNC like `\\\\wsl$\\…`).
|
|
427
|
+
// INIT_CWD/PWD/cwd then resolve to a Windows system path and
|
|
428
|
+
// every git operation lands in the wrong tree. Claude Code itself
|
|
429
|
+
// reliably exports `CLAUDE_PROJECT_DIR` — prefer it absolutely
|
|
430
|
+
// when present and reject obvious system paths regardless.
|
|
362
431
|
if (!explicitRoot) {
|
|
363
|
-
const
|
|
432
|
+
const rawCandidates = [
|
|
433
|
+
process.env.CLAUDE_PROJECT_DIR, // canonical Claude Code env (B8)
|
|
364
434
|
process.env.INIT_CWD, // npm/npx sets this to invoking directory
|
|
365
435
|
process.env.PWD, // shell working directory (may differ from cwd)
|
|
366
436
|
process.cwd(), // Node.js working directory
|
|
367
437
|
].filter((c) => !!c && c !== "/");
|
|
438
|
+
// v0.34.0 — emit a diagnostic for every Windows / UNC reject so
|
|
439
|
+
// we can see in stats how often WSL launches misroute the cwd.
|
|
440
|
+
for (const c of rawCandidates) {
|
|
441
|
+
if (isWindowsSystemPath(c)) {
|
|
442
|
+
try {
|
|
443
|
+
await appendDiagnostic(process.cwd(), {
|
|
444
|
+
code: "wsl_path_rejected",
|
|
445
|
+
level: "warn",
|
|
446
|
+
detail: { path_basename: c.split(/[\\/]/).pop() ?? "" },
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
/* logger of last resort */
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const candidates = rawCandidates.filter((c) => !isWindowsSystemPath(c));
|
|
368
455
|
let detected = false;
|
|
369
456
|
for (const candidate of candidates) {
|
|
370
457
|
if (isDangerousRoot(candidate))
|
|
@@ -705,6 +792,23 @@ export async function handleInstallHook(projectRoot) {
|
|
|
705
792
|
"Skipping to avoid duplicate hook entries.");
|
|
706
793
|
process.exit(0);
|
|
707
794
|
}
|
|
795
|
+
// v0.33.0 — detect when the user already enabled the token-pilot
|
|
796
|
+
// plugin in `~/.claude/settings.json`. Even though we're running
|
|
797
|
+
// outside CLAUDE_PLUGIN_ROOT here (CLI invocation), the plugin's
|
|
798
|
+
// own `hooks/hooks.json` is what Claude Code uses at runtime.
|
|
799
|
+
// Writing additional entries with a captured npx-cache path leads
|
|
800
|
+
// to the bug B2 (v0.33.0): hooks pinned to an old binary that
|
|
801
|
+
// never sees newer hook handlers. Surface a clear migration step
|
|
802
|
+
// instead of silently duplicating.
|
|
803
|
+
if (await isTokenPilotPluginEnabled(homedir())) {
|
|
804
|
+
const userSettings = resolve(homedir(), ".claude", "settings.json");
|
|
805
|
+
const cleanup = await cleanStaleHookEntries(userSettings);
|
|
806
|
+
console.log("token-pilot plugin is enabled in ~/.claude/settings.json —\n" +
|
|
807
|
+
"the plugin's bundled hooks/hooks.json is the source of truth.\n" +
|
|
808
|
+
"Skipping settings.json hook write to avoid pinning to a stale path.\n" +
|
|
809
|
+
cleanup.message);
|
|
810
|
+
process.exit(0);
|
|
811
|
+
}
|
|
708
812
|
let hookOptions;
|
|
709
813
|
try {
|
|
710
814
|
const rawPath = fileURLToPath(new URL("./index.js", import.meta.url));
|
|
@@ -927,6 +1031,123 @@ export async function handleDoctor() {
|
|
|
927
1031
|
catch {
|
|
928
1032
|
/* ecosystem check is best-effort; never break doctor */
|
|
929
1033
|
}
|
|
1034
|
+
// ── v0.34.0 health checks (Pack 2 of error-logging release) ──
|
|
1035
|
+
try {
|
|
1036
|
+
console.log("── runtime health ──");
|
|
1037
|
+
// Recent errors
|
|
1038
|
+
const recent = await loadErrors({ tail: 100 });
|
|
1039
|
+
if (recent.length === 0) {
|
|
1040
|
+
console.log(` errors: 0 in ~/.token-pilot/hook-errors.jsonl ✓`);
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
const counts = new Map();
|
|
1044
|
+
for (const r of recent)
|
|
1045
|
+
counts.set(r.code, (counts.get(r.code) ?? 0) + 1);
|
|
1046
|
+
const top = Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
1047
|
+
console.log(` errors: ${recent.length} recent (top codes):`);
|
|
1048
|
+
for (const [code, n] of top)
|
|
1049
|
+
console.log(` ${String(n).padStart(3)}× ${code}`);
|
|
1050
|
+
console.log(` drill-in: token-pilot errors --tail=20`);
|
|
1051
|
+
}
|
|
1052
|
+
// Stale hook entries — user-level + project-level
|
|
1053
|
+
const userSettings = join(homedir(), ".claude", "settings.json");
|
|
1054
|
+
const projectSettings = join(cwd, ".claude", "settings.json");
|
|
1055
|
+
let staleCount = 0;
|
|
1056
|
+
for (const p of [userSettings, projectSettings]) {
|
|
1057
|
+
try {
|
|
1058
|
+
if (!existsSync(p))
|
|
1059
|
+
continue;
|
|
1060
|
+
const raw = await import("node:fs/promises").then((m) => m.readFile(p, "utf-8"));
|
|
1061
|
+
const json = JSON.parse(raw);
|
|
1062
|
+
const sections = ["PreToolUse", "PostToolUse", "SessionStart"];
|
|
1063
|
+
for (const s of sections) {
|
|
1064
|
+
const arr = json.hooks?.[s];
|
|
1065
|
+
if (!Array.isArray(arr))
|
|
1066
|
+
continue;
|
|
1067
|
+
for (const entry of arr) {
|
|
1068
|
+
const inner = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
1069
|
+
for (const h of inner) {
|
|
1070
|
+
if (typeof h?.command === "string") {
|
|
1071
|
+
if (/\/_npx\/[0-9a-f]+\//.test(h.command))
|
|
1072
|
+
staleCount++;
|
|
1073
|
+
else if (/\/plugins\/cache\/token-pilot\/token-pilot\/[^/]+\//.test(h.command))
|
|
1074
|
+
staleCount++;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
catch {
|
|
1081
|
+
/* skip */
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (staleCount === 0) {
|
|
1085
|
+
console.log(` stale hooks: none ✓`);
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
console.log(` stale hooks: ${staleCount} pinned-path entries`);
|
|
1089
|
+
console.log(` fix: token-pilot migrate-hooks`);
|
|
1090
|
+
}
|
|
1091
|
+
// Installed tp-* agents vs catalog
|
|
1092
|
+
let installed = 0;
|
|
1093
|
+
let catalog = 0;
|
|
1094
|
+
try {
|
|
1095
|
+
const fsp = await import("node:fs/promises");
|
|
1096
|
+
const userAgents = join(homedir(), ".claude", "agents");
|
|
1097
|
+
const projAgents = join(cwd, ".claude", "agents");
|
|
1098
|
+
const seen = new Set();
|
|
1099
|
+
for (const dir of [userAgents, projAgents]) {
|
|
1100
|
+
try {
|
|
1101
|
+
const entries = await fsp.readdir(dir);
|
|
1102
|
+
for (const e of entries) {
|
|
1103
|
+
if (e.startsWith("tp-") && e.endsWith(".md"))
|
|
1104
|
+
seen.add(e);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
catch {
|
|
1108
|
+
/* missing */
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
installed = seen.size;
|
|
1112
|
+
const dist = new URL("../agents", import.meta.url).pathname;
|
|
1113
|
+
try {
|
|
1114
|
+
const dEntries = await fsp.readdir(dist);
|
|
1115
|
+
catalog = dEntries.filter((f) => f.startsWith("tp-") && f.endsWith(".md")).length;
|
|
1116
|
+
}
|
|
1117
|
+
catch {
|
|
1118
|
+
catalog = 0;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
catch {
|
|
1122
|
+
/* skip */
|
|
1123
|
+
}
|
|
1124
|
+
if (catalog === 0) {
|
|
1125
|
+
console.log(` tp-* agents: could not read catalog`);
|
|
1126
|
+
}
|
|
1127
|
+
else if (installed === 0) {
|
|
1128
|
+
console.log(` tp-* agents: 0 of ${catalog} installed`);
|
|
1129
|
+
console.log(` fix: token-pilot install-agents --scope=user`);
|
|
1130
|
+
}
|
|
1131
|
+
else if (installed < catalog) {
|
|
1132
|
+
console.log(` tp-* agents: ${installed} of ${catalog} installed (partial)`);
|
|
1133
|
+
console.log(` fix: token-pilot install-agents --force`);
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
console.log(` tp-* agents: ${installed}/${catalog} ✓`);
|
|
1137
|
+
}
|
|
1138
|
+
// WSL detection probe
|
|
1139
|
+
const cwdGuess = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
1140
|
+
if (isWindowsSystemPath(cwdGuess)) {
|
|
1141
|
+
console.log(` cwd: ${cwdGuess} ✗ (Windows system path — see B8)`);
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
console.log(` cwd: ${cwdGuess} ✓`);
|
|
1145
|
+
}
|
|
1146
|
+
console.log("");
|
|
1147
|
+
}
|
|
1148
|
+
catch {
|
|
1149
|
+
/* health checks are best-effort */
|
|
1150
|
+
}
|
|
930
1151
|
process.exit(0);
|
|
931
1152
|
}
|
|
932
1153
|
export async function handleInit(targetDir) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.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",
|