token-pilot 0.31.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 +25 -9
- package/dist/core/validation.js +212 -136
- package/dist/handlers/call-tree.d.ts +35 -0
- package/dist/handlers/call-tree.js +70 -0
- 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/hooks/session-start.d.ts +2 -0
- package/dist/hooks/session-start.js +49 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +284 -63
- package/dist/server/tool-definitions.d.ts +65 -0
- package/dist/server/tool-definitions.js +18 -0
- package/dist/server.js +36 -1
- package/package.json +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.34.0 — error / diagnostic channel for token-pilot hooks + CLI.
|
|
3
|
+
*
|
|
4
|
+
* Why a separate file from `hook-events.jsonl`:
|
|
5
|
+
* - hook-events lives in `<projectRoot>/.token-pilot/`. When the hook
|
|
6
|
+
* itself fails BEFORE projectRoot is resolved (B8 WSL detection,
|
|
7
|
+
* missing dir, ENOENT), there is nowhere to write the regular log.
|
|
8
|
+
* - Errors must outlive a single project — when a user reports
|
|
9
|
+
* "nothing logs anymore" we want one absolute path to look at.
|
|
10
|
+
*
|
|
11
|
+
* Layout:
|
|
12
|
+
* ~/.token-pilot/hook-errors.jsonl
|
|
13
|
+
*
|
|
14
|
+
* Format: one JSON record per line. Schema in `HookErrorRecord` below.
|
|
15
|
+
*
|
|
16
|
+
* Discipline:
|
|
17
|
+
* - Never throws. The error logger is itself the last line of defence —
|
|
18
|
+
* a throw here would defeat the wrapper that calls it.
|
|
19
|
+
* - Cap-and-rotate: when the file passes MAX_BYTES the writer renames
|
|
20
|
+
* it to `hook-errors.<ts>.jsonl` and starts fresh. Old archives
|
|
21
|
+
* past RETENTION_MS are pruned best-effort on each append.
|
|
22
|
+
* - `TOKEN_PILOT_NO_ERROR_LOG=1` opts out entirely.
|
|
23
|
+
*
|
|
24
|
+
* Privacy:
|
|
25
|
+
* - The `input` field is whatever the hook chose to record. Callers
|
|
26
|
+
* MUST sanitize before passing it in — no full paths, no file
|
|
27
|
+
* content, no prompts. Helpers `safeBasename()` / `safePathInfo()`
|
|
28
|
+
* are provided for the common cases.
|
|
29
|
+
*/
|
|
30
|
+
import * as fs from "node:fs/promises";
|
|
31
|
+
import { existsSync } from "node:fs";
|
|
32
|
+
import { homedir } from "node:os";
|
|
33
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
34
|
+
// ─── constants ───────────────────────────────────────────────────────
|
|
35
|
+
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB before rotate
|
|
36
|
+
const RETENTION_MS = 30 * 24 * 3600 * 1000; // 30d archive retention
|
|
37
|
+
const ARCHIVE_RE = /^hook-errors\.(\d+)\.jsonl$/;
|
|
38
|
+
const CURRENT = "hook-errors.jsonl";
|
|
39
|
+
// ─── path resolution ─────────────────────────────────────────────────
|
|
40
|
+
export function errorLogDir() {
|
|
41
|
+
return join(homedir(), ".token-pilot");
|
|
42
|
+
}
|
|
43
|
+
export function errorLogPath() {
|
|
44
|
+
return join(errorLogDir(), CURRENT);
|
|
45
|
+
}
|
|
46
|
+
// ─── env opt-out ─────────────────────────────────────────────────────
|
|
47
|
+
function isOptedOut() {
|
|
48
|
+
return process.env.TOKEN_PILOT_NO_ERROR_LOG === "1";
|
|
49
|
+
}
|
|
50
|
+
// ─── sanitizers ──────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Reduce a path to its basename. Use everywhere a path could leak:
|
|
53
|
+
* the user's project tree, file content, anything not strictly an
|
|
54
|
+
* identifier. Returns `"<empty>"` for missing input rather than null
|
|
55
|
+
* so the field stays shape-stable.
|
|
56
|
+
*/
|
|
57
|
+
export function safeBasename(p) {
|
|
58
|
+
if (typeof p !== "string" || p.length === 0)
|
|
59
|
+
return "<empty>";
|
|
60
|
+
return basename(p);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Capture only the metadata about a path — basename + length + ext —
|
|
64
|
+
* dropping the absolute path entirely. Useful when the analysis
|
|
65
|
+
* benefits from "kind of file" without revealing where it lived.
|
|
66
|
+
*/
|
|
67
|
+
export function safePathInfo(p) {
|
|
68
|
+
if (typeof p !== "string" || p.length === 0) {
|
|
69
|
+
return { name: "<empty>", ext: "" };
|
|
70
|
+
}
|
|
71
|
+
const name = basename(p);
|
|
72
|
+
const dot = name.lastIndexOf(".");
|
|
73
|
+
return {
|
|
74
|
+
name,
|
|
75
|
+
ext: dot >= 0 ? name.slice(dot).toLowerCase() : "",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// ─── error classification ────────────────────────────────────────────
|
|
79
|
+
/**
|
|
80
|
+
* Map a thrown value to a stable, searchable `code`. The classifier
|
|
81
|
+
* is intentionally simple — node ErrnoException codes pass through,
|
|
82
|
+
* everything else falls into a coarse bucket. New cases land here
|
|
83
|
+
* only when a pattern shows up repeatedly in the wild.
|
|
84
|
+
*/
|
|
85
|
+
export function classifyError(err) {
|
|
86
|
+
if (err && typeof err === "object") {
|
|
87
|
+
const e = err;
|
|
88
|
+
if (typeof e.code === "string" && e.code.length > 0)
|
|
89
|
+
return e.code;
|
|
90
|
+
const name = e.name;
|
|
91
|
+
if (name === "SyntaxError")
|
|
92
|
+
return "parse_error";
|
|
93
|
+
if (name === "TypeError")
|
|
94
|
+
return "type_error";
|
|
95
|
+
}
|
|
96
|
+
if (err instanceof Error) {
|
|
97
|
+
const m = err.message.toLowerCase();
|
|
98
|
+
if (m.includes("timeout"))
|
|
99
|
+
return "timeout";
|
|
100
|
+
if (m.includes("not initialized"))
|
|
101
|
+
return "not_initialized";
|
|
102
|
+
if (m.includes("permission denied"))
|
|
103
|
+
return "EACCES";
|
|
104
|
+
}
|
|
105
|
+
return "unknown";
|
|
106
|
+
}
|
|
107
|
+
// ─── rotate + retention ──────────────────────────────────────────────
|
|
108
|
+
async function rotateIfNeeded() {
|
|
109
|
+
const p = errorLogPath();
|
|
110
|
+
try {
|
|
111
|
+
const stat = await fs.stat(p);
|
|
112
|
+
if (stat.size < MAX_BYTES)
|
|
113
|
+
return;
|
|
114
|
+
const archive = join(errorLogDir(), `hook-errors.${Date.now()}.jsonl`);
|
|
115
|
+
await fs.rename(p, archive);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* missing or stat failure — append will create */
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function pruneArchives() {
|
|
122
|
+
const dir = errorLogDir();
|
|
123
|
+
let entries;
|
|
124
|
+
try {
|
|
125
|
+
entries = await fs.readdir(dir);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const cutoff = Date.now() - RETENTION_MS;
|
|
131
|
+
for (const name of entries) {
|
|
132
|
+
const m = name.match(ARCHIVE_RE);
|
|
133
|
+
if (!m)
|
|
134
|
+
continue;
|
|
135
|
+
const ts = Number(m[1]);
|
|
136
|
+
if (!Number.isFinite(ts))
|
|
137
|
+
continue;
|
|
138
|
+
if (ts < cutoff) {
|
|
139
|
+
try {
|
|
140
|
+
await fs.unlink(join(dir, name));
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
/* best-effort */
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// ─── append ──────────────────────────────────────────────────────────
|
|
149
|
+
export async function appendError(rec) {
|
|
150
|
+
if (isOptedOut())
|
|
151
|
+
return;
|
|
152
|
+
try {
|
|
153
|
+
await fs.mkdir(errorLogDir(), { recursive: true });
|
|
154
|
+
await rotateIfNeeded();
|
|
155
|
+
await fs.appendFile(errorLogPath(), JSON.stringify(rec) + "\n");
|
|
156
|
+
// best-effort retention sweep — not awaited tightly because a slow
|
|
157
|
+
// FS shouldn't slow the hook hot-path; failures are silent.
|
|
158
|
+
pruneArchives().catch(() => { });
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* logger of last resort — never throw */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export async function loadErrors(opts = {}) {
|
|
165
|
+
const p = opts.path ?? errorLogPath();
|
|
166
|
+
let raw;
|
|
167
|
+
try {
|
|
168
|
+
raw = await fs.readFile(p, "utf-8");
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
const out = [];
|
|
174
|
+
for (const line of raw.split("\n")) {
|
|
175
|
+
if (!line.trim())
|
|
176
|
+
continue;
|
|
177
|
+
try {
|
|
178
|
+
const rec = JSON.parse(line);
|
|
179
|
+
if (opts.code && rec.code !== opts.code)
|
|
180
|
+
continue;
|
|
181
|
+
if (opts.hook && rec.hook !== opts.hook)
|
|
182
|
+
continue;
|
|
183
|
+
if (opts.level && rec.level !== opts.level)
|
|
184
|
+
continue;
|
|
185
|
+
out.push(rec);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* skip malformed */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Newest first — most useful default ordering for a tail view.
|
|
192
|
+
out.sort((a, b) => (b.ts || 0) - (a.ts || 0));
|
|
193
|
+
if (opts.tail && opts.tail > 0) {
|
|
194
|
+
return out.slice(0, opts.tail);
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
// ─── format ──────────────────────────────────────────────────────────
|
|
199
|
+
export function formatErrorList(records) {
|
|
200
|
+
if (records.length === 0) {
|
|
201
|
+
return "No errors logged.";
|
|
202
|
+
}
|
|
203
|
+
const counts = new Map();
|
|
204
|
+
for (const r of records) {
|
|
205
|
+
counts.set(r.code, (counts.get(r.code) ?? 0) + 1);
|
|
206
|
+
}
|
|
207
|
+
const top = Array.from(counts.entries())
|
|
208
|
+
.sort((a, b) => b[1] - a[1])
|
|
209
|
+
.slice(0, 10);
|
|
210
|
+
const lines = [];
|
|
211
|
+
lines.push(`token-pilot errors — ${records.length} total`);
|
|
212
|
+
lines.push("");
|
|
213
|
+
lines.push("Top codes:");
|
|
214
|
+
for (const [code, n] of top) {
|
|
215
|
+
lines.push(` ${String(n).padStart(4)}× ${code}`);
|
|
216
|
+
}
|
|
217
|
+
lines.push("");
|
|
218
|
+
lines.push("Most recent:");
|
|
219
|
+
const recent = records.slice(0, 20);
|
|
220
|
+
for (const r of recent) {
|
|
221
|
+
const when = new Date(r.ts).toISOString().slice(11, 19);
|
|
222
|
+
lines.push(` [${when}] ${r.level.toUpperCase().padEnd(5)} ${r.hook} ${r.code} — ${r.msg}`);
|
|
223
|
+
}
|
|
224
|
+
return lines.join("\n");
|
|
225
|
+
}
|
|
226
|
+
// ─── exports for indirection from index.ts ───────────────────────────
|
|
227
|
+
export { existsSync, resolve, dirname };
|
|
228
|
+
//# sourceMappingURL=error-log.js.map
|
package/dist/core/event-log.d.ts
CHANGED
|
@@ -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" | "task" | string;
|
|
31
|
+
event: "denied" | "allowed" | "bypass" | "pass-through" | "task" | "diagnostic" | string;
|
|
32
32
|
file: string;
|
|
33
33
|
lines: number;
|
|
34
34
|
estTokens: number;
|
|
@@ -36,6 +36,18 @@ export interface HookEvent {
|
|
|
36
36
|
summaryTokens: number;
|
|
37
37
|
/** estTokens - summaryTokens; 0 for allow/bypass. */
|
|
38
38
|
savedTokens: number;
|
|
39
|
+
/** "info" | "warn" | "error" — severity of the diagnostic. */
|
|
40
|
+
level?: "info" | "warn" | "error";
|
|
41
|
+
/** Stable searchable identifier — e.g. `force_subagents_no_agents`. */
|
|
42
|
+
code?: string;
|
|
43
|
+
/** Optional small map of context — must be sanitised by caller. */
|
|
44
|
+
detail?: Record<string, unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* Wall-clock duration of the hook handler that emitted this event,
|
|
47
|
+
* milliseconds. Always optional — only the safe-runner wrapper sets
|
|
48
|
+
* it, and only on the FINAL diagnostic record per hook invocation.
|
|
49
|
+
*/
|
|
50
|
+
duration_ms?: number;
|
|
39
51
|
/** The subagent_type Claude Code dispatched (`tp-*` or `general-purpose`…). */
|
|
40
52
|
subagent_type?: string;
|
|
41
53
|
/**
|
|
@@ -78,12 +90,48 @@ export declare function retentionDeletions(files: Array<{
|
|
|
78
90
|
* here must not break hook dispatch.
|
|
79
91
|
*/
|
|
80
92
|
export declare function appendEvent(projectRoot: string, event: HookEvent): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* v0.34.0 — convenience wrapper for emitting a `diagnostic` event.
|
|
95
|
+
*
|
|
96
|
+
* Diagnostics describe edge-case branches inside a normal handler
|
|
97
|
+
* run (matcher returned no agents, WSL path rejected, MCP arg
|
|
98
|
+
* coerced, etc.). They live in the project-local hook-events.jsonl
|
|
99
|
+
* alongside the regular events so `stats --diagnostics` can count
|
|
100
|
+
* them by code.
|
|
101
|
+
*
|
|
102
|
+
* If the projectRoot is not yet resolvable (the failure happened
|
|
103
|
+
* before detection), prefer `appendError` from `core/error-log.ts`
|
|
104
|
+
* — it falls back to a user-level path.
|
|
105
|
+
*
|
|
106
|
+
* Pure-ish: never throws. Same best-effort semantics as appendEvent.
|
|
107
|
+
*/
|
|
108
|
+
export declare function appendDiagnostic(projectRoot: string, args: {
|
|
109
|
+
code: string;
|
|
110
|
+
level?: "info" | "warn" | "error";
|
|
111
|
+
detail?: Record<string, unknown>;
|
|
112
|
+
sessionId?: string;
|
|
113
|
+
agentType?: string | null;
|
|
114
|
+
agentId?: string | null;
|
|
115
|
+
durationMs?: number;
|
|
116
|
+
}): Promise<void>;
|
|
81
117
|
/**
|
|
82
118
|
* Read all events from the current log file. Malformed JSONL lines are
|
|
83
119
|
* skipped silently (a corrupted line should not poison the whole
|
|
84
120
|
* dataset). Returns [] if the file is missing.
|
|
85
121
|
*/
|
|
86
122
|
export declare function loadEvents(projectRoot: string): Promise<HookEvent[]>;
|
|
123
|
+
/**
|
|
124
|
+
* v0.33.0 (B5) — load events from EVERY `.token-pilot/hook-events.jsonl`
|
|
125
|
+
* found at or below `repoRoot`. The hook writer resolves its own
|
|
126
|
+
* project root from `process.cwd()` at the moment Claude Code spawns
|
|
127
|
+
* us, which can land in a subdirectory of the actual repo (apps/admin,
|
|
128
|
+
* apps/api, packages/prisma, …). Without this, `token-pilot stats`
|
|
129
|
+
* sees only the top-level log and reports a fraction of the savings.
|
|
130
|
+
*
|
|
131
|
+
* Walks up to `maxDepth` levels and merges chronologically. Pure on
|
|
132
|
+
* filesystem read errors — missing dirs are silently skipped.
|
|
133
|
+
*/
|
|
134
|
+
export declare function loadEventsTree(repoRoot: string, maxDepth?: number): Promise<HookEvent[]>;
|
|
87
135
|
/**
|
|
88
136
|
* Apply age + size retention. Safe to call on startup; no-op when the
|
|
89
137
|
* directory does not exist.
|
package/dist/core/event-log.js
CHANGED
|
@@ -117,6 +117,40 @@ export async function appendEvent(projectRoot, event) {
|
|
|
117
117
|
/* silent — telemetry is best-effort */
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* v0.34.0 — convenience wrapper for emitting a `diagnostic` event.
|
|
122
|
+
*
|
|
123
|
+
* Diagnostics describe edge-case branches inside a normal handler
|
|
124
|
+
* run (matcher returned no agents, WSL path rejected, MCP arg
|
|
125
|
+
* coerced, etc.). They live in the project-local hook-events.jsonl
|
|
126
|
+
* alongside the regular events so `stats --diagnostics` can count
|
|
127
|
+
* them by code.
|
|
128
|
+
*
|
|
129
|
+
* If the projectRoot is not yet resolvable (the failure happened
|
|
130
|
+
* before detection), prefer `appendError` from `core/error-log.ts`
|
|
131
|
+
* — it falls back to a user-level path.
|
|
132
|
+
*
|
|
133
|
+
* Pure-ish: never throws. Same best-effort semantics as appendEvent.
|
|
134
|
+
*/
|
|
135
|
+
export async function appendDiagnostic(projectRoot, args) {
|
|
136
|
+
const rec = {
|
|
137
|
+
ts: Date.now(),
|
|
138
|
+
session_id: args.sessionId ?? "diagnostic",
|
|
139
|
+
agent_type: args.agentType ?? null,
|
|
140
|
+
agent_id: args.agentId ?? null,
|
|
141
|
+
event: "diagnostic",
|
|
142
|
+
file: "",
|
|
143
|
+
lines: 0,
|
|
144
|
+
estTokens: 0,
|
|
145
|
+
summaryTokens: 0,
|
|
146
|
+
savedTokens: 0,
|
|
147
|
+
level: args.level ?? "info",
|
|
148
|
+
code: args.code,
|
|
149
|
+
detail: args.detail,
|
|
150
|
+
duration_ms: args.durationMs,
|
|
151
|
+
};
|
|
152
|
+
await appendEvent(projectRoot, rec);
|
|
153
|
+
}
|
|
120
154
|
/**
|
|
121
155
|
* Read all events from the current log file. Malformed JSONL lines are
|
|
122
156
|
* skipped silently (a corrupted line should not poison the whole
|
|
@@ -143,6 +177,86 @@ export async function loadEvents(projectRoot) {
|
|
|
143
177
|
}
|
|
144
178
|
return out;
|
|
145
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* v0.33.0 (B5) — load events from EVERY `.token-pilot/hook-events.jsonl`
|
|
182
|
+
* found at or below `repoRoot`. The hook writer resolves its own
|
|
183
|
+
* project root from `process.cwd()` at the moment Claude Code spawns
|
|
184
|
+
* us, which can land in a subdirectory of the actual repo (apps/admin,
|
|
185
|
+
* apps/api, packages/prisma, …). Without this, `token-pilot stats`
|
|
186
|
+
* sees only the top-level log and reports a fraction of the savings.
|
|
187
|
+
*
|
|
188
|
+
* Walks up to `maxDepth` levels and merges chronologically. Pure on
|
|
189
|
+
* filesystem read errors — missing dirs are silently skipped.
|
|
190
|
+
*/
|
|
191
|
+
export async function loadEventsTree(repoRoot, maxDepth = 5) {
|
|
192
|
+
const PRUNE = new Set([
|
|
193
|
+
"node_modules",
|
|
194
|
+
".git",
|
|
195
|
+
"dist",
|
|
196
|
+
"build",
|
|
197
|
+
".next",
|
|
198
|
+
".turbo",
|
|
199
|
+
".cache",
|
|
200
|
+
"coverage",
|
|
201
|
+
".vercel",
|
|
202
|
+
".vite",
|
|
203
|
+
]);
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
const all = [];
|
|
206
|
+
async function visit(dir, depth) {
|
|
207
|
+
if (depth > maxDepth)
|
|
208
|
+
return;
|
|
209
|
+
let entries = [];
|
|
210
|
+
try {
|
|
211
|
+
entries = await fs.readdir(dir);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (entries.includes(".token-pilot")) {
|
|
217
|
+
const logPath = join(dir, ".token-pilot", CURRENT_FILE);
|
|
218
|
+
if (!seen.has(logPath)) {
|
|
219
|
+
seen.add(logPath);
|
|
220
|
+
try {
|
|
221
|
+
const raw = await fs.readFile(logPath, "utf-8");
|
|
222
|
+
for (const line of raw.split("\n")) {
|
|
223
|
+
if (!line.trim())
|
|
224
|
+
continue;
|
|
225
|
+
try {
|
|
226
|
+
all.push(JSON.parse(line));
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
/* skip malformed */
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
/* missing log */
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const name of entries) {
|
|
239
|
+
if (PRUNE.has(name))
|
|
240
|
+
continue;
|
|
241
|
+
if (name.startsWith(".") && name !== ".claude")
|
|
242
|
+
continue;
|
|
243
|
+
const full = join(dir, name);
|
|
244
|
+
try {
|
|
245
|
+
const stat = await fs.stat(full);
|
|
246
|
+
if (stat.isDirectory()) {
|
|
247
|
+
await visit(full, depth + 1);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
/* skip */
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
await visit(repoRoot, 0);
|
|
256
|
+
// Sort chronologically so per-session aggregations stay correct.
|
|
257
|
+
all.sort((a, b) => (a.ts || 0) - (b.ts || 0));
|
|
258
|
+
return all;
|
|
259
|
+
}
|
|
146
260
|
/**
|
|
147
261
|
* Enumerate all archive files (`hook-events.<ts>.jsonl`) with metadata
|
|
148
262
|
* needed by `retentionDeletions`.
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.33.0 (B9) — coerce an `unknown` argument value to an integer.
|
|
3
|
+
*
|
|
4
|
+
* MCP transports frequently round-trip numeric arguments through
|
|
5
|
+
* JSON or environment variables and re-emit them as strings (e.g.
|
|
6
|
+
* `"42"`). Accept that case and reject everything else, including
|
|
7
|
+
* non-finite numbers, decimals, and strings that don't parse cleanly.
|
|
8
|
+
*
|
|
9
|
+
* Returns the integer value or `null` when the input cannot be
|
|
10
|
+
* interpreted as one.
|
|
11
|
+
*/
|
|
12
|
+
export declare function coerceIntFromAny(value: unknown): number | null;
|
|
1
13
|
/**
|
|
2
14
|
* Resolve a user-provided path and validate it stays within projectRoot.
|
|
3
15
|
* Prevents path traversal attacks (e.g. ../../etc/passwd).
|
|
@@ -22,7 +34,7 @@ export declare function validateReadSymbolArgs(args: unknown): {
|
|
|
22
34
|
symbol: string;
|
|
23
35
|
context_before?: number;
|
|
24
36
|
context_after?: number;
|
|
25
|
-
show?:
|
|
37
|
+
show?: "full" | "head" | "tail" | "outline";
|
|
26
38
|
};
|
|
27
39
|
/**
|
|
28
40
|
* Validate read_symbols arguments (batch multi-symbol read).
|
|
@@ -32,7 +44,7 @@ export declare function validateReadSymbolsArgs(args: unknown): {
|
|
|
32
44
|
symbols: string[];
|
|
33
45
|
context_before?: number;
|
|
34
46
|
context_after?: number;
|
|
35
|
-
show?:
|
|
47
|
+
show?: "full" | "head" | "tail" | "outline";
|
|
36
48
|
};
|
|
37
49
|
/**
|
|
38
50
|
* Validate read_range arguments.
|
|
@@ -56,11 +68,11 @@ export declare function validateReadDiffArgs(args: unknown): {
|
|
|
56
68
|
export interface FindUsagesArgs {
|
|
57
69
|
symbol: string;
|
|
58
70
|
scope?: string;
|
|
59
|
-
kind?:
|
|
71
|
+
kind?: "definitions" | "imports" | "usages" | "all";
|
|
60
72
|
limit?: number;
|
|
61
73
|
lang?: string;
|
|
62
74
|
context_lines?: number;
|
|
63
|
-
mode?:
|
|
75
|
+
mode?: "full" | "list";
|
|
64
76
|
}
|
|
65
77
|
export declare function validateFindUsagesArgs(args: unknown): FindUsagesArgs;
|
|
66
78
|
/**
|
|
@@ -105,8 +117,12 @@ export declare function validateFindUnusedArgs(args: unknown): {
|
|
|
105
117
|
export_only?: boolean;
|
|
106
118
|
limit?: number;
|
|
107
119
|
};
|
|
120
|
+
export declare function validateCallTreeArgs(args: unknown): {
|
|
121
|
+
symbol: string;
|
|
122
|
+
depth?: number;
|
|
123
|
+
};
|
|
108
124
|
export interface CodeAuditArgs {
|
|
109
|
-
check:
|
|
125
|
+
check: "pattern" | "todo" | "deprecated" | "annotations" | "all";
|
|
110
126
|
pattern?: string;
|
|
111
127
|
name?: string;
|
|
112
128
|
lang?: string;
|
|
@@ -118,7 +134,7 @@ export declare function validateCodeAuditArgs(args: unknown): CodeAuditArgs;
|
|
|
118
134
|
* v1.1: added include filter.
|
|
119
135
|
*/
|
|
120
136
|
export interface ProjectOverviewArgs {
|
|
121
|
-
include?: Array<
|
|
137
|
+
include?: Array<"stack" | "ci" | "quality" | "architecture">;
|
|
122
138
|
}
|
|
123
139
|
export declare function validateProjectOverviewArgs(args: unknown): ProjectOverviewArgs;
|
|
124
140
|
/**
|
|
@@ -126,14 +142,14 @@ export declare function validateProjectOverviewArgs(args: unknown): ProjectOverv
|
|
|
126
142
|
*/
|
|
127
143
|
export interface ModuleInfoArgs {
|
|
128
144
|
module: string;
|
|
129
|
-
check?:
|
|
145
|
+
check?: "deps" | "dependents" | "api" | "unused-deps" | "all";
|
|
130
146
|
}
|
|
131
147
|
export declare function validateModuleInfoArgs(args: unknown): ModuleInfoArgs;
|
|
132
148
|
/**
|
|
133
149
|
* Validate smart_diff arguments.
|
|
134
150
|
*/
|
|
135
151
|
export interface SmartDiffArgs {
|
|
136
|
-
scope?:
|
|
152
|
+
scope?: "unstaged" | "staged" | "commit" | "branch";
|
|
137
153
|
path?: string;
|
|
138
154
|
ref?: string;
|
|
139
155
|
}
|
|
@@ -143,7 +159,7 @@ export declare function validateSmartDiffArgs(args: unknown): SmartDiffArgs;
|
|
|
143
159
|
*/
|
|
144
160
|
export interface ExploreAreaArgs {
|
|
145
161
|
path: string;
|
|
146
|
-
include?: Array<
|
|
162
|
+
include?: Array<"outline" | "imports" | "tests" | "changes">;
|
|
147
163
|
}
|
|
148
164
|
export declare function validateExploreAreaArgs(args: unknown): ExploreAreaArgs;
|
|
149
165
|
export interface SmartLogArgs {
|