token-pilot 0.46.0 → 0.47.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 +1 -1
- package/CHANGELOG.md +54 -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 +5 -1
- package/dist/ast-index/client.js +90 -10
- package/dist/ast-index/types.d.ts +69 -0
- package/dist/core/event-log.d.ts +7 -0
- package/dist/core/event-log.js +9 -1
- package/dist/core/validation.d.ts +6 -0
- package/dist/core/validation.js +18 -0
- package/dist/handlers/explore.d.ts +17 -0
- package/dist/handlers/explore.js +90 -0
- package/dist/handlers/test-summary.js +42 -0
- package/dist/server/tool-definitions.d.ts +150 -0
- package/dist/server/tool-definitions.js +22 -0
- package/dist/server/tool-profiles.js +1 -0
- package/dist/server.js +36 -1
- package/package.json +4 -4
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Token Pilot — save 60-90% tokens when AI reads code",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.47.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "token-pilot",
|
|
14
14
|
"source": "./",
|
|
15
|
-
"description": "Reduces token consumption by 60-90% via AST-aware lazy file reading, structural symbol navigation, and cross-session tool-usage analytics.
|
|
16
|
-
"version": "0.
|
|
15
|
+
"description": "Reduces token consumption by 60-90% via AST-aware lazy file reading, structural symbol navigation, and cross-session tool-usage analytics. 24 MCP tools + 25 subagents + budget watchdog hooks.",
|
|
16
|
+
"version": "0.47.0",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Digital-Threads"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Saves 60-90% tokens on AI code reading. AST-aware lazy reads, symbol navigation, find_usages, structural git diff/log, edit-safety guard, Task-routing matcher, cross-session telemetry (errors + diagnostics), 25 tp-* subagents tiered to haiku/sonnet/opus with budget watchdog.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Digital-Threads",
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,60 @@ All notable changes to Token Pilot will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.47.0] - 2026-06-24
|
|
9
|
+
|
|
10
|
+
### Added — `explore` tool: one-shot ranked context + graph blast-radius
|
|
11
|
+
|
|
12
|
+
New MCP tool **`explore`** wraps ast-index 3.48's `explore` command: for a query
|
|
13
|
+
it returns ranked relevant symbols, the source heads of the top files, **graph
|
|
14
|
+
neighbours (callers + subclasses — the blast radius, via RWR over the
|
|
15
|
+
call/inheritance graph)**, and related test files — in a single compact block.
|
|
16
|
+
Replaces the common `find_usages` → `read_symbol` → `call_tree` chain with one
|
|
17
|
+
call. `graph` defaults on; `max_files` caps the source heads. Falls back to a
|
|
18
|
+
clear "requires ast-index >= 3.48" message when an older binary is resolved.
|
|
19
|
+
|
|
20
|
+
### Changed — bump `@ast-index/cli` to 3.48.1
|
|
21
|
+
|
|
22
|
+
Picks up the upstream TypeScript-indexing fix, the rebuild **swap-and-restore**
|
|
23
|
+
guard (a failed rebuild no longer wipes the index), memory caps, and FUSE-safe
|
|
24
|
+
canonicalisation. `npm audit` stays at 0 vulnerabilities.
|
|
25
|
+
|
|
26
|
+
### Changed — `buildIndex` trusts swap-and-restore
|
|
27
|
+
|
|
28
|
+
When a rebuild fails, `buildIndex` now checks for the index the binary preserved
|
|
29
|
+
and uses it (instead of throwing and falling back to raw reads). The lock-case
|
|
30
|
+
and generic-failure recovery paths are unified.
|
|
31
|
+
|
|
32
|
+
_Deferred:_ `--local` / subtree query scoping (to re-enable ast-index on
|
|
33
|
+
multi-repo / worktree parents instead of disabling it) needs a rooting-model
|
|
34
|
+
rework and ships separately.
|
|
35
|
+
|
|
36
|
+
## [0.46.1] - 2026-06-18
|
|
37
|
+
|
|
38
|
+
### Fixed — node:test (`node --test`) TAP output parsing
|
|
39
|
+
|
|
40
|
+
`test_summary` did not recognise `node --test`: `detectRunner` had no `node`
|
|
41
|
+
case, so node:test output fell through to the generic parser. node:test emits a
|
|
42
|
+
TAP footer (`# pass N` / `# fail N`, number after the word) and `ok N - name`
|
|
43
|
+
points (`ok` before the number), which the generic `<N> passed` regex never
|
|
44
|
+
matches — a green 2/2 run was reported as 0 passed. Added a `node` runner:
|
|
45
|
+
detected by command (`node --test` / `node:test`) or by the TAP footer, parsing
|
|
46
|
+
pass/fail/skipped/tests from the footer with a fallback to counting `ok` /
|
|
47
|
+
`not ok` lines; failure names come from `not ok N - name`. Purely additive — no
|
|
48
|
+
existing parser touched.
|
|
49
|
+
|
|
50
|
+
### Changed — dev-dependency security + decoupled registry publish (no shipped change)
|
|
51
|
+
|
|
52
|
+
- Bump `vitest` / `@vitest/coverage-v8` to 4.x — clears the 6 remaining
|
|
53
|
+
dev-only high advisories in the vitest/vite/esbuild chain. `npm audit` now
|
|
54
|
+
reports **0 vulnerabilities** (dev + prod). No runtime/package change: dev
|
|
55
|
+
deps are not shipped to npm consumers; the full 1402-test suite is green on
|
|
56
|
+
vitest 4.
|
|
57
|
+
- `publish-mcp.yml`: the MCP Registry job no longer hard-depends on npm-job
|
|
58
|
+
**success** (`if: !cancelled()`). A failed npm publish (e.g. EOTP on a
|
|
59
|
+
manual-token release) no longer blocks the registry update; re-run the job
|
|
60
|
+
via `workflow_dispatch` after a manual `npm publish`.
|
|
61
|
+
|
|
8
62
|
## [0.46.0] - 2026-06-13
|
|
9
63
|
|
|
10
64
|
### Added — UserPromptSubmit per-turn reinforcement (caveman-style awareness)
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Token-efficient AI coding, enforced.** Cuts context consumption in AI coding assistants by up to **90%** without changing the way you work.
|
|
4
4
|
|
|
5
|
-
> **Why it matters more now:** as frontier models move up in price
|
|
5
|
+
> **Why it matters more now:** as frontier models move up in price, the tokens you *don't* spend reading code are worth more, not less. The savings are in tokens; the value is in tokens × price. Token Pilot keeps the expensive main thread lean so the premium model spends its budget on reasoning, not on re-reading files.
|
|
6
6
|
|
|
7
7
|
Three layers, each useful on its own, stronger together:
|
|
8
8
|
|
|
@@ -9,7 +9,7 @@ tools:
|
|
|
9
9
|
- mcp__token-pilot__read_symbol
|
|
10
10
|
- Bash
|
|
11
11
|
model: haiku
|
|
12
|
-
token_pilot_version: "0.
|
|
12
|
+
token_pilot_version: "0.47.0"
|
|
13
13
|
token_pilot_body_hash: dd184501203fa7f3c73f419c4ffbe33c4be75400cb64a7a51733a3fe23f6e085
|
|
14
14
|
requiredMcpServers:
|
|
15
15
|
- "token-pilot"
|
|
@@ -8,7 +8,7 @@ tools:
|
|
|
8
8
|
- mcp__token-pilot__test_summary
|
|
9
9
|
- mcp__token-pilot__outline
|
|
10
10
|
- Bash
|
|
11
|
-
token_pilot_version: "0.
|
|
11
|
+
token_pilot_version: "0.47.0"
|
|
12
12
|
token_pilot_body_hash: de64a406b5176de19f7422619c7de7949b1f28865f225402c9cea9255f377428
|
|
13
13
|
requiredMcpServers:
|
|
14
14
|
- "token-pilot"
|
package/agents/tp-debugger.md
CHANGED
package/agents/tp-dep-health.md
CHANGED
package/agents/tp-doc-writer.md
CHANGED
|
@@ -12,7 +12,7 @@ tools:
|
|
|
12
12
|
- mcp__token-pilot__read_symbols
|
|
13
13
|
- Read
|
|
14
14
|
model: sonnet
|
|
15
|
-
token_pilot_version: "0.
|
|
15
|
+
token_pilot_version: "0.47.0"
|
|
16
16
|
token_pilot_body_hash: 351a987e11eba63852f5431a16d8eb53104f4f689f82fdcc5a2bf4db948ba92f
|
|
17
17
|
requiredMcpServers:
|
|
18
18
|
- "token-pilot"
|
|
@@ -8,7 +8,7 @@ tools:
|
|
|
8
8
|
- mcp__token-pilot__read_symbol
|
|
9
9
|
- Bash
|
|
10
10
|
model: inherit
|
|
11
|
-
token_pilot_version: "0.
|
|
11
|
+
token_pilot_version: "0.47.0"
|
|
12
12
|
token_pilot_body_hash: de5722bfea374eaab096c1ae635c37879e7a91370ee3cd0532f4240be03c91eb
|
|
13
13
|
requiredMcpServers:
|
|
14
14
|
- "token-pilot"
|
package/agents/tp-onboard.md
CHANGED
|
@@ -10,7 +10,7 @@ tools:
|
|
|
10
10
|
- mcp__token-pilot__smart_read
|
|
11
11
|
- mcp__token-pilot__smart_read_many
|
|
12
12
|
- mcp__token-pilot__read_section
|
|
13
|
-
token_pilot_version: "0.
|
|
13
|
+
token_pilot_version: "0.47.0"
|
|
14
14
|
token_pilot_body_hash: 832e95633fbc8e9b0c10f3e540a327d4be062fb4b3f17a6cce6be13f414e2927
|
|
15
15
|
requiredMcpServers:
|
|
16
16
|
- "token-pilot"
|
package/agents/tp-pr-reviewer.md
CHANGED
|
@@ -11,7 +11,7 @@ tools:
|
|
|
11
11
|
- mcp__token-pilot__read_for_edit
|
|
12
12
|
- Read
|
|
13
13
|
model: sonnet
|
|
14
|
-
token_pilot_version: "0.
|
|
14
|
+
token_pilot_version: "0.47.0"
|
|
15
15
|
token_pilot_body_hash: f83f50d05b4f70285ae7afed2b1a406fc436df56e61a0aedbfb31edc7f2b6e66
|
|
16
16
|
requiredMcpServers:
|
|
17
17
|
- "token-pilot"
|
|
@@ -8,7 +8,7 @@ tools:
|
|
|
8
8
|
- mcp__token-pilot__outline
|
|
9
9
|
- mcp__token-pilot__read_symbol
|
|
10
10
|
model: sonnet
|
|
11
|
-
token_pilot_version: "0.
|
|
11
|
+
token_pilot_version: "0.47.0"
|
|
12
12
|
token_pilot_body_hash: c5f6fc122c89e16e5cf774045f92169ee3468555320b898171ba13eca5323550
|
|
13
13
|
requiredMcpServers:
|
|
14
14
|
- "token-pilot"
|
|
@@ -9,7 +9,7 @@ tools:
|
|
|
9
9
|
- mcp__token-pilot__module_info
|
|
10
10
|
- Bash
|
|
11
11
|
model: sonnet
|
|
12
|
-
token_pilot_version: "0.
|
|
12
|
+
token_pilot_version: "0.47.0"
|
|
13
13
|
token_pilot_body_hash: 8ef3c3341cbfed4eb8dd130126a9683edc57e378c92ff0ca764d584fd941c55c
|
|
14
14
|
requiredMcpServers:
|
|
15
15
|
- "token-pilot"
|
package/agents/tp-run.md
CHANGED
package/agents/tp-spec-writer.md
CHANGED
|
@@ -10,7 +10,7 @@ tools:
|
|
|
10
10
|
- mcp__token-pilot__test_summary
|
|
11
11
|
- Glob
|
|
12
12
|
- Grep
|
|
13
|
-
token_pilot_version: "0.
|
|
13
|
+
token_pilot_version: "0.47.0"
|
|
14
14
|
token_pilot_body_hash: be81eed53a3720d146cf89e4a14a7a56577633f7c84c234c412ab70d64c05b11
|
|
15
15
|
requiredMcpServers:
|
|
16
16
|
- "token-pilot"
|
package/agents/tp-test-triage.md
CHANGED
|
@@ -8,7 +8,7 @@ tools:
|
|
|
8
8
|
- mcp__token-pilot__find_usages
|
|
9
9
|
- mcp__token-pilot__read_symbol
|
|
10
10
|
model: sonnet
|
|
11
|
-
token_pilot_version: "0.
|
|
11
|
+
token_pilot_version: "0.47.0"
|
|
12
12
|
token_pilot_body_hash: 362ecf4cb03b059421ea26933473700900073dc38b3a7fe271208dfb1ae14f90
|
|
13
13
|
requiredMcpServers:
|
|
14
14
|
- "token-pilot"
|
package/agents/tp-test-writer.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FileStructure } from "../types.js";
|
|
2
|
-
import type { AstIndexSymbolDetail, AstIndexSearchResult, AstIndexUsageResult, AstIndexImplementation, AstIndexHierarchyNode, AstIndexRefsResponse, AstIndexMapResponse, AstIndexConventionsResponse, AstIndexCallerEntry, AstIndexCallTreeNode, AstIndexChangedEntry, AstIndexUnusedSymbol, AstIndexImportEntry, AstIndexAgrepMatch, AstIndexTodoEntry, AstIndexDeprecatedEntry, AstIndexAnnotationEntry, AstIndexModuleEntry, AstIndexModuleDep, AstIndexUnusedDep, AstIndexModuleApi } from "./types.js";
|
|
2
|
+
import type { AstIndexSymbolDetail, AstIndexSearchResult, AstIndexUsageResult, AstIndexImplementation, AstIndexHierarchyNode, AstIndexRefsResponse, AstIndexMapResponse, AstIndexConventionsResponse, AstIndexCallerEntry, AstIndexCallTreeNode, AstIndexChangedEntry, AstIndexUnusedSymbol, AstIndexImportEntry, AstIndexAgrepMatch, AstIndexTodoEntry, AstIndexDeprecatedEntry, AstIndexAnnotationEntry, AstIndexModuleEntry, AstIndexModuleDep, AstIndexUnusedDep, AstIndexModuleApi, AstIndexExploreResult } from "./types.js";
|
|
3
3
|
export declare class AstIndexClient {
|
|
4
4
|
private static readonly MAX_INDEX_FILES;
|
|
5
5
|
private binaryPath;
|
|
@@ -34,6 +34,10 @@ export declare class AstIndexClient {
|
|
|
34
34
|
fuzzy?: boolean;
|
|
35
35
|
}): Promise<AstIndexSearchResult[]>;
|
|
36
36
|
usages(symbolName: string): Promise<AstIndexUsageResult[]>;
|
|
37
|
+
explore(query: string, options?: {
|
|
38
|
+
maxFiles?: number;
|
|
39
|
+
graph?: boolean;
|
|
40
|
+
}): Promise<AstIndexExploreResult>;
|
|
37
41
|
implementations(name: string): Promise<AstIndexImplementation[]>;
|
|
38
42
|
hierarchy(name: string, options?: {
|
|
39
43
|
inFile?: string;
|
package/dist/ast-index/client.js
CHANGED
|
@@ -128,16 +128,22 @@ export class AstIndexClient {
|
|
|
128
128
|
}
|
|
129
129
|
catch (buildErr) {
|
|
130
130
|
const errMsg = buildErr instanceof Error ? buildErr.message : String(buildErr);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
131
|
+
// ast-index 3.46+ preserves the previous index when a rebuild aborts
|
|
132
|
+
// (swap-and-restore guard). Before giving up, query stats once: if a
|
|
133
|
+
// usable index survived, use it instead of losing all indexed tools and
|
|
134
|
+
// falling back to raw reads. Covers both the lock / already-running case
|
|
135
|
+
// and any generic rebuild failure the binary recovered from.
|
|
136
|
+
const lockCase = errMsg.includes("lock") || errMsg.includes("already running");
|
|
137
|
+
const count = parseFileCount(await this.exec(["--format", "json", "stats"]).catch(() => ""));
|
|
138
|
+
if (count > 0 && count <= AstIndexClient.MAX_INDEX_FILES) {
|
|
139
|
+
this.indexed = true;
|
|
140
|
+
console.error(lockCase
|
|
141
|
+
? `[token-pilot] ast-index: using existing index (${count} files, rebuild skipped due to lock)`
|
|
142
|
+
: `[token-pilot] ast-index: rebuild failed but previous index preserved (${count} files) — using it`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (count > AstIndexClient.MAX_INDEX_FILES) {
|
|
146
|
+
return this.handleOversizedIndex(count);
|
|
141
147
|
}
|
|
142
148
|
console.error(`[token-pilot] ast-index: rebuild failed — ${errMsg}`);
|
|
143
149
|
throw buildErr;
|
|
@@ -328,6 +334,80 @@ export class AstIndexClient {
|
|
|
328
334
|
return [];
|
|
329
335
|
}
|
|
330
336
|
}
|
|
337
|
+
async explore(query, options) {
|
|
338
|
+
await this.ensureIndex();
|
|
339
|
+
const empty = {
|
|
340
|
+
query,
|
|
341
|
+
dominantLanguage: "",
|
|
342
|
+
symbols: [],
|
|
343
|
+
files: [],
|
|
344
|
+
neighbours: [],
|
|
345
|
+
tests: [],
|
|
346
|
+
};
|
|
347
|
+
const args = ["explore", query, "--format", "json"];
|
|
348
|
+
if (options?.maxFiles)
|
|
349
|
+
args.push("-f", String(options.maxFiles));
|
|
350
|
+
// Default graph ON — call/inheritance blast-radius is the value-add;
|
|
351
|
+
// callers opt out with graph: false.
|
|
352
|
+
if (options?.graph !== false)
|
|
353
|
+
args.push("--rwr");
|
|
354
|
+
try {
|
|
355
|
+
const result = await this.exec(args);
|
|
356
|
+
const parsed = JSON.parse(result);
|
|
357
|
+
return {
|
|
358
|
+
query: typeof parsed.query === "string" ? parsed.query : query,
|
|
359
|
+
dominantLanguage: typeof parsed.dominant_language === "string"
|
|
360
|
+
? parsed.dominant_language
|
|
361
|
+
: "",
|
|
362
|
+
symbols: Array.isArray(parsed.symbols)
|
|
363
|
+
? parsed.symbols.map((s) => ({
|
|
364
|
+
name: s.name,
|
|
365
|
+
kind: s.kind,
|
|
366
|
+
path: s.path,
|
|
367
|
+
line: s.line,
|
|
368
|
+
score: s.score,
|
|
369
|
+
vendor: s.vendor === true,
|
|
370
|
+
}))
|
|
371
|
+
: [],
|
|
372
|
+
files: Array.isArray(parsed.files)
|
|
373
|
+
? parsed.files.map((f) => ({
|
|
374
|
+
path: f.path,
|
|
375
|
+
line: f.line,
|
|
376
|
+
source: f.source,
|
|
377
|
+
}))
|
|
378
|
+
: [],
|
|
379
|
+
neighbours: Array.isArray(parsed.neighbours)
|
|
380
|
+
? parsed.neighbours.map((n) => ({
|
|
381
|
+
name: n.name,
|
|
382
|
+
kind: n.kind,
|
|
383
|
+
path: n.path,
|
|
384
|
+
line: n.line,
|
|
385
|
+
link: n.link,
|
|
386
|
+
}))
|
|
387
|
+
: [],
|
|
388
|
+
tests: Array.isArray(parsed.tests)
|
|
389
|
+
? parsed.tests.map((t) => ({
|
|
390
|
+
source: t.source,
|
|
391
|
+
tests: Array.isArray(t.tests) ? t.tests : [],
|
|
392
|
+
}))
|
|
393
|
+
: [],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
398
|
+
console.error(`[token-pilot] ast-index explore failed: ${msg}`);
|
|
399
|
+
// `explore` landed in ast-index 3.48. An older resolved binary reports
|
|
400
|
+
// "unrecognized subcommand 'explore'" — surface that instead of an
|
|
401
|
+
// indistinguishable empty result, so the caller knows to update.
|
|
402
|
+
if (/unrecognized subcommand|unexpected argument/.test(msg)) {
|
|
403
|
+
return {
|
|
404
|
+
...empty,
|
|
405
|
+
error: "explore requires ast-index >= 3.48 — update the binary",
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return empty;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
331
411
|
async implementations(name) {
|
|
332
412
|
await this.ensureIndex();
|
|
333
413
|
try {
|
|
@@ -208,4 +208,73 @@ export interface AstIndexModuleApi {
|
|
|
208
208
|
file: string;
|
|
209
209
|
line: number;
|
|
210
210
|
}
|
|
211
|
+
/** ast-index explore — one ranked symbol */
|
|
212
|
+
export interface AstIndexExploreSymbol {
|
|
213
|
+
name: string;
|
|
214
|
+
kind: string;
|
|
215
|
+
path: string;
|
|
216
|
+
line: number;
|
|
217
|
+
score: number;
|
|
218
|
+
vendor: boolean;
|
|
219
|
+
}
|
|
220
|
+
/** ast-index explore — one ranked file head (source is line-numbered) */
|
|
221
|
+
export interface AstIndexExploreFile {
|
|
222
|
+
path: string;
|
|
223
|
+
line: number;
|
|
224
|
+
source: string;
|
|
225
|
+
}
|
|
226
|
+
/** ast-index explore — one graph neighbour (blast radius, requires --rwr) */
|
|
227
|
+
export interface AstIndexExploreNeighbour {
|
|
228
|
+
name: string;
|
|
229
|
+
kind: string;
|
|
230
|
+
path: string;
|
|
231
|
+
line: number;
|
|
232
|
+
/** "caller" | "subclass" | string */
|
|
233
|
+
link: string;
|
|
234
|
+
}
|
|
235
|
+
/** ast-index explore — tests grouped by source file */
|
|
236
|
+
export interface AstIndexExploreTestGroup {
|
|
237
|
+
source: string;
|
|
238
|
+
tests: string[];
|
|
239
|
+
}
|
|
240
|
+
/** ast-index explore — mapped result */
|
|
241
|
+
export interface AstIndexExploreResult {
|
|
242
|
+
query: string;
|
|
243
|
+
dominantLanguage: string;
|
|
244
|
+
symbols: AstIndexExploreSymbol[];
|
|
245
|
+
files: AstIndexExploreFile[];
|
|
246
|
+
neighbours: AstIndexExploreNeighbour[];
|
|
247
|
+
tests: AstIndexExploreTestGroup[];
|
|
248
|
+
/** Set when the run failed (e.g. binary too old for `explore`). */
|
|
249
|
+
error?: string;
|
|
250
|
+
}
|
|
251
|
+
/** ast-index explore — raw json shape from the binary (snake_case) */
|
|
252
|
+
export interface AstIndexExploreRaw {
|
|
253
|
+
query?: string;
|
|
254
|
+
dominant_language?: string;
|
|
255
|
+
symbols?: Array<{
|
|
256
|
+
name: string;
|
|
257
|
+
kind: string;
|
|
258
|
+
path: string;
|
|
259
|
+
line: number;
|
|
260
|
+
score: number;
|
|
261
|
+
vendor?: boolean;
|
|
262
|
+
}>;
|
|
263
|
+
files?: Array<{
|
|
264
|
+
path: string;
|
|
265
|
+
line: number;
|
|
266
|
+
source: string;
|
|
267
|
+
}>;
|
|
268
|
+
neighbours?: Array<{
|
|
269
|
+
name: string;
|
|
270
|
+
kind: string;
|
|
271
|
+
path: string;
|
|
272
|
+
line: number;
|
|
273
|
+
link: string;
|
|
274
|
+
}>;
|
|
275
|
+
tests?: Array<{
|
|
276
|
+
source: string;
|
|
277
|
+
tests?: string[];
|
|
278
|
+
}>;
|
|
279
|
+
}
|
|
211
280
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/core/event-log.d.ts
CHANGED
|
@@ -56,6 +56,13 @@ export interface HookEvent {
|
|
|
56
56
|
* Optional — absent when no workflow is active.
|
|
57
57
|
*/
|
|
58
58
|
workflow_id?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Loom spine link — id of the Loom task this event belongs to, when the
|
|
61
|
+
* session was launched by Loom (LOOM_TASK_ID set). Lets Loom attribute token
|
|
62
|
+
* savings to a task exactly. Optional — absent outside Loom, so standalone
|
|
63
|
+
* token-pilot events stay byte-identical to before.
|
|
64
|
+
*/
|
|
65
|
+
task_id?: string;
|
|
59
66
|
event: "denied" | "allowed" | "bypass" | "pass-through" | "task" | "diagnostic" | string;
|
|
60
67
|
file: string;
|
|
61
68
|
lines: number;
|
package/dist/core/event-log.js
CHANGED
|
@@ -115,8 +115,16 @@ export async function appendEvent(projectRoot, event) {
|
|
|
115
115
|
const wf = event.workflow_id ??
|
|
116
116
|
process.env.TOKEN_PILOT_WORKFLOW_ID ??
|
|
117
117
|
process.env.CLAUDE_CODE_WORKFLOW_ID ??
|
|
118
|
+
process.env.LOOM_WORKFLOW_ID ??
|
|
118
119
|
undefined;
|
|
119
|
-
|
|
120
|
+
// Loom spine: tag the task id when the session was launched by Loom.
|
|
121
|
+
// Env-driven so call sites stay unchanged; absent outside Loom → no field.
|
|
122
|
+
const taskId = event.task_id ?? process.env.LOOM_TASK_ID ?? undefined;
|
|
123
|
+
let tagged = event;
|
|
124
|
+
if (wf)
|
|
125
|
+
tagged = { ...tagged, workflow_id: wf };
|
|
126
|
+
if (taskId)
|
|
127
|
+
tagged = { ...tagged, task_id: taskId };
|
|
120
128
|
await ensureLogDir(projectRoot);
|
|
121
129
|
await rotateIfNeeded(projectRoot);
|
|
122
130
|
const line = JSON.stringify(tagged) + "\n";
|
|
@@ -179,6 +179,12 @@ export interface ExploreAreaArgs {
|
|
|
179
179
|
include?: Array<"outline" | "imports" | "tests" | "changes">;
|
|
180
180
|
}
|
|
181
181
|
export declare function validateExploreAreaArgs(args: unknown): ExploreAreaArgs;
|
|
182
|
+
export interface ExploreArgs {
|
|
183
|
+
query: string;
|
|
184
|
+
max_files?: number;
|
|
185
|
+
graph?: boolean;
|
|
186
|
+
}
|
|
187
|
+
export declare function validateExploreArgs(args: unknown): ExploreArgs;
|
|
182
188
|
export interface SmartLogArgs {
|
|
183
189
|
path?: string;
|
|
184
190
|
count?: number;
|
package/dist/core/validation.js
CHANGED
|
@@ -533,6 +533,24 @@ export function validateExploreAreaArgs(args) {
|
|
|
533
533
|
}
|
|
534
534
|
return { path: a.path };
|
|
535
535
|
}
|
|
536
|
+
export function validateExploreArgs(args) {
|
|
537
|
+
if (!args || typeof args !== "object") {
|
|
538
|
+
throw new Error('Arguments must be an object with a "query" parameter.');
|
|
539
|
+
}
|
|
540
|
+
const a = args;
|
|
541
|
+
if (typeof a.query !== "string" || a.query.length === 0) {
|
|
542
|
+
throw new Error('Required parameter "query" must be a non-empty string.');
|
|
543
|
+
}
|
|
544
|
+
const max_files = optionalNumber(a.max_files, "max_files");
|
|
545
|
+
if (max_files !== undefined && max_files < 1) {
|
|
546
|
+
throw new Error('"max_files" must be at least 1.');
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
query: a.query,
|
|
550
|
+
max_files,
|
|
551
|
+
graph: optionalBool(a.graph, "graph"),
|
|
552
|
+
};
|
|
553
|
+
}
|
|
536
554
|
export function validateSmartLogArgs(args) {
|
|
537
555
|
if (!args || typeof args !== "object")
|
|
538
556
|
return {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AstIndexClient } from "../ast-index/client.js";
|
|
2
|
+
import type { ExploreArgs } from "../core/validation.js";
|
|
3
|
+
export interface ExploreMeta {
|
|
4
|
+
query: string;
|
|
5
|
+
symbolCount: number;
|
|
6
|
+
fileCount: number;
|
|
7
|
+
neighbourCount: number;
|
|
8
|
+
testCount: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function handleExplore(args: ExploreArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
|
|
11
|
+
content: Array<{
|
|
12
|
+
type: "text";
|
|
13
|
+
text: string;
|
|
14
|
+
}>;
|
|
15
|
+
meta: ExploreMeta;
|
|
16
|
+
}>;
|
|
17
|
+
//# sourceMappingURL=explore.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// ──────────────────────────────────────────────
|
|
2
|
+
// Constants
|
|
3
|
+
// ──────────────────────────────────────────────
|
|
4
|
+
const MAX_RANKED_SYMBOLS = 12;
|
|
5
|
+
// ──────────────────────────────────────────────
|
|
6
|
+
// Handler — one-shot ranked context + graph blast-radius.
|
|
7
|
+
// Mirrors the shape of handleExploreArea: build a compact, token-efficient
|
|
8
|
+
// text block and return it with lightweight meta.
|
|
9
|
+
// ──────────────────────────────────────────────
|
|
10
|
+
export async function handleExplore(args, projectRoot, astIndex) {
|
|
11
|
+
void projectRoot; // explore runs against the index root, not a path
|
|
12
|
+
const result = await astIndex.explore(args.query, {
|
|
13
|
+
maxFiles: args.max_files,
|
|
14
|
+
graph: args.graph,
|
|
15
|
+
});
|
|
16
|
+
const lines = [];
|
|
17
|
+
lines.push(`# explore: "${result.query}" (lang: ${result.dominantLanguage || "?"})`);
|
|
18
|
+
// Ranked symbols
|
|
19
|
+
if (result.symbols.length > 0) {
|
|
20
|
+
lines.push("");
|
|
21
|
+
lines.push("## Ranked symbols");
|
|
22
|
+
for (const s of result.symbols.slice(0, MAX_RANKED_SYMBOLS)) {
|
|
23
|
+
const vendorTag = s.vendor ? " [vendor]" : "";
|
|
24
|
+
lines.push(`${Math.round(s.score)} ${s.kind} ${s.name} ${s.path}:${s.line}${vendorTag}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Source — file heads (source is already line-numbered)
|
|
28
|
+
if (result.files.length > 0) {
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("## Source");
|
|
31
|
+
for (const f of result.files) {
|
|
32
|
+
lines.push(`${f.path}:${f.line}`);
|
|
33
|
+
lines.push("```");
|
|
34
|
+
lines.push(f.source.replace(/\n+$/, ""));
|
|
35
|
+
lines.push("```");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Graph neighbours (blast radius) — only with --rwr
|
|
39
|
+
if (result.neighbours.length > 0) {
|
|
40
|
+
lines.push("");
|
|
41
|
+
lines.push("## Graph neighbours (blast radius)");
|
|
42
|
+
for (const n of result.neighbours) {
|
|
43
|
+
lines.push(`${n.link} ${n.kind} ${n.name} ${n.path}:${n.line}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Tests grouped by source
|
|
47
|
+
if (result.tests.length > 0) {
|
|
48
|
+
lines.push("");
|
|
49
|
+
lines.push("## Tests");
|
|
50
|
+
for (const t of result.tests) {
|
|
51
|
+
lines.push(`${t.source}:`);
|
|
52
|
+
for (const test of t.tests) {
|
|
53
|
+
lines.push(` ${test}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const empty = result.symbols.length === 0 &&
|
|
58
|
+
result.files.length === 0 &&
|
|
59
|
+
result.neighbours.length === 0 &&
|
|
60
|
+
result.tests.length === 0;
|
|
61
|
+
if (empty) {
|
|
62
|
+
const reason = result.error ?? "No results — index unavailable or query matched nothing.";
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: `# explore: "${result.query}"\n\n${reason}`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
meta: {
|
|
71
|
+
query: result.query,
|
|
72
|
+
symbolCount: 0,
|
|
73
|
+
fileCount: 0,
|
|
74
|
+
neighbourCount: 0,
|
|
75
|
+
testCount: 0,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
81
|
+
meta: {
|
|
82
|
+
query: result.query,
|
|
83
|
+
symbolCount: result.symbols.length,
|
|
84
|
+
fileCount: result.files.length,
|
|
85
|
+
neighbourCount: result.neighbours.length,
|
|
86
|
+
testCount: result.tests.reduce((n, t) => n + t.tests.length, 0),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=explore.js.map
|
|
@@ -73,6 +73,8 @@ export function detectRunner(command, output) {
|
|
|
73
73
|
return 'rspec';
|
|
74
74
|
if (cmd.includes('mocha'))
|
|
75
75
|
return 'mocha';
|
|
76
|
+
if (cmd.includes('node --test') || cmd.includes('node:test'))
|
|
77
|
+
return 'node';
|
|
76
78
|
// Detect from output
|
|
77
79
|
const lower = output.toLowerCase();
|
|
78
80
|
if (lower.includes('vitest') || lower.includes('vite'))
|
|
@@ -85,6 +87,9 @@ export function detectRunner(command, output) {
|
|
|
85
87
|
return 'phpunit';
|
|
86
88
|
if (lower.includes('--- fail:') || lower.includes('--- pass:') || lower.includes('ok \t'))
|
|
87
89
|
return 'go';
|
|
90
|
+
// node:test prints a TAP summary footer: "# tests N" + "# pass N" + "# fail N".
|
|
91
|
+
if (/^#\s*tests\s+\d+/m.test(output) && /^#\s*pass\s+\d+/m.test(output))
|
|
92
|
+
return 'node';
|
|
88
93
|
return 'generic';
|
|
89
94
|
}
|
|
90
95
|
// ──────────────────────────────────────────────
|
|
@@ -103,6 +108,8 @@ export function parseTestOutput(output, runner) {
|
|
|
103
108
|
return parseGoTest(output);
|
|
104
109
|
case 'cargo':
|
|
105
110
|
return parseCargoTest(output);
|
|
111
|
+
case 'node':
|
|
112
|
+
return parseNodeTest(output);
|
|
106
113
|
default:
|
|
107
114
|
return parseGeneric(output);
|
|
108
115
|
}
|
|
@@ -243,6 +250,41 @@ function parseGoTest(output) {
|
|
|
243
250
|
}
|
|
244
251
|
return result;
|
|
245
252
|
}
|
|
253
|
+
function parseNodeTest(output) {
|
|
254
|
+
const result = { total: 0, passed: 0, failed: 0, skipped: 0, failures: [] };
|
|
255
|
+
// node:test (`node --test`) prints a TAP summary footer:
|
|
256
|
+
// # tests 2
|
|
257
|
+
// # pass 2
|
|
258
|
+
// # fail 0
|
|
259
|
+
// # skipped 0
|
|
260
|
+
const num = (re) => {
|
|
261
|
+
const m = output.match(re);
|
|
262
|
+
return m ? parseInt(m[1], 10) : null;
|
|
263
|
+
};
|
|
264
|
+
const pass = num(/^#\s*pass\s+(\d+)/m);
|
|
265
|
+
const fail = num(/^#\s*fail\s+(\d+)/m);
|
|
266
|
+
const skip = num(/^#\s*skipped\s+(\d+)/m);
|
|
267
|
+
const tests = num(/^#\s*tests\s+(\d+)/m);
|
|
268
|
+
if (pass !== null || fail !== null) {
|
|
269
|
+
result.passed = pass ?? 0;
|
|
270
|
+
result.failed = fail ?? 0;
|
|
271
|
+
result.skipped = skip ?? 0;
|
|
272
|
+
result.total = tests ?? result.passed + result.failed + result.skipped;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// No footer (truncated output) — count the TAP point lines instead.
|
|
276
|
+
result.passed = (output.match(/^ok\s+\d+/gm) ?? []).length;
|
|
277
|
+
result.failed = (output.match(/^not ok\s+\d+/gm) ?? []).length;
|
|
278
|
+
result.total = result.passed + result.failed + result.skipped;
|
|
279
|
+
}
|
|
280
|
+
// Failure names come from the TAP point: "not ok 3 - the test name".
|
|
281
|
+
const failPattern = /^not ok\s+\d+\s*-\s*(.+)$/gm;
|
|
282
|
+
let match;
|
|
283
|
+
while ((match = failPattern.exec(output)) !== null) {
|
|
284
|
+
result.failures.push({ name: match[1].trim(), error: '' });
|
|
285
|
+
}
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
246
288
|
function parseCargoTest(output) {
|
|
247
289
|
const result = { total: 0, passed: 0, failed: 0, skipped: 0, failures: [] };
|
|
248
290
|
// test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
|
|
@@ -100,6 +100,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
100
100
|
viaKind?: undefined;
|
|
101
101
|
format?: undefined;
|
|
102
102
|
ref?: undefined;
|
|
103
|
+
query?: undefined;
|
|
104
|
+
max_files?: undefined;
|
|
105
|
+
graph?: undefined;
|
|
103
106
|
count?: undefined;
|
|
104
107
|
command?: undefined;
|
|
105
108
|
runner?: undefined;
|
|
@@ -191,6 +194,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
191
194
|
viaKind?: undefined;
|
|
192
195
|
format?: undefined;
|
|
193
196
|
ref?: undefined;
|
|
197
|
+
query?: undefined;
|
|
198
|
+
max_files?: undefined;
|
|
199
|
+
graph?: undefined;
|
|
194
200
|
count?: undefined;
|
|
195
201
|
command?: undefined;
|
|
196
202
|
runner?: undefined;
|
|
@@ -276,6 +282,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
276
282
|
viaKind?: undefined;
|
|
277
283
|
format?: undefined;
|
|
278
284
|
ref?: undefined;
|
|
285
|
+
query?: undefined;
|
|
286
|
+
max_files?: undefined;
|
|
287
|
+
graph?: undefined;
|
|
279
288
|
count?: undefined;
|
|
280
289
|
command?: undefined;
|
|
281
290
|
runner?: undefined;
|
|
@@ -357,6 +366,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
357
366
|
viaKind?: undefined;
|
|
358
367
|
format?: undefined;
|
|
359
368
|
ref?: undefined;
|
|
369
|
+
query?: undefined;
|
|
370
|
+
max_files?: undefined;
|
|
371
|
+
graph?: undefined;
|
|
360
372
|
count?: undefined;
|
|
361
373
|
command?: undefined;
|
|
362
374
|
runner?: undefined;
|
|
@@ -429,6 +441,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
429
441
|
viaKind?: undefined;
|
|
430
442
|
format?: undefined;
|
|
431
443
|
ref?: undefined;
|
|
444
|
+
query?: undefined;
|
|
445
|
+
max_files?: undefined;
|
|
446
|
+
graph?: undefined;
|
|
432
447
|
count?: undefined;
|
|
433
448
|
command?: undefined;
|
|
434
449
|
runner?: undefined;
|
|
@@ -501,6 +516,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
501
516
|
viaKind?: undefined;
|
|
502
517
|
format?: undefined;
|
|
503
518
|
ref?: undefined;
|
|
519
|
+
query?: undefined;
|
|
520
|
+
max_files?: undefined;
|
|
521
|
+
graph?: undefined;
|
|
504
522
|
count?: undefined;
|
|
505
523
|
command?: undefined;
|
|
506
524
|
runner?: undefined;
|
|
@@ -597,6 +615,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
597
615
|
viaKind?: undefined;
|
|
598
616
|
format?: undefined;
|
|
599
617
|
ref?: undefined;
|
|
618
|
+
query?: undefined;
|
|
619
|
+
max_files?: undefined;
|
|
620
|
+
graph?: undefined;
|
|
600
621
|
count?: undefined;
|
|
601
622
|
command?: undefined;
|
|
602
623
|
runner?: undefined;
|
|
@@ -678,6 +699,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
678
699
|
viaKind?: undefined;
|
|
679
700
|
format?: undefined;
|
|
680
701
|
ref?: undefined;
|
|
702
|
+
query?: undefined;
|
|
703
|
+
max_files?: undefined;
|
|
704
|
+
graph?: undefined;
|
|
681
705
|
count?: undefined;
|
|
682
706
|
command?: undefined;
|
|
683
707
|
runner?: undefined;
|
|
@@ -768,6 +792,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
768
792
|
viaKind?: undefined;
|
|
769
793
|
format?: undefined;
|
|
770
794
|
ref?: undefined;
|
|
795
|
+
query?: undefined;
|
|
796
|
+
max_files?: undefined;
|
|
797
|
+
graph?: undefined;
|
|
771
798
|
count?: undefined;
|
|
772
799
|
command?: undefined;
|
|
773
800
|
runner?: undefined;
|
|
@@ -841,6 +868,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
841
868
|
viaKind?: undefined;
|
|
842
869
|
format?: undefined;
|
|
843
870
|
ref?: undefined;
|
|
871
|
+
query?: undefined;
|
|
872
|
+
max_files?: undefined;
|
|
873
|
+
graph?: undefined;
|
|
844
874
|
count?: undefined;
|
|
845
875
|
command?: undefined;
|
|
846
876
|
runner?: undefined;
|
|
@@ -910,6 +940,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
910
940
|
viaKind?: undefined;
|
|
911
941
|
format?: undefined;
|
|
912
942
|
ref?: undefined;
|
|
943
|
+
query?: undefined;
|
|
944
|
+
max_files?: undefined;
|
|
945
|
+
graph?: undefined;
|
|
913
946
|
count?: undefined;
|
|
914
947
|
command?: undefined;
|
|
915
948
|
runner?: undefined;
|
|
@@ -985,6 +1018,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
985
1018
|
viaKind?: undefined;
|
|
986
1019
|
format?: undefined;
|
|
987
1020
|
ref?: undefined;
|
|
1021
|
+
query?: undefined;
|
|
1022
|
+
max_files?: undefined;
|
|
1023
|
+
graph?: undefined;
|
|
988
1024
|
count?: undefined;
|
|
989
1025
|
command?: undefined;
|
|
990
1026
|
runner?: undefined;
|
|
@@ -1054,6 +1090,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1054
1090
|
viaKind?: undefined;
|
|
1055
1091
|
format?: undefined;
|
|
1056
1092
|
ref?: undefined;
|
|
1093
|
+
query?: undefined;
|
|
1094
|
+
max_files?: undefined;
|
|
1095
|
+
graph?: undefined;
|
|
1057
1096
|
count?: undefined;
|
|
1058
1097
|
command?: undefined;
|
|
1059
1098
|
runner?: undefined;
|
|
@@ -1126,6 +1165,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1126
1165
|
viaKind?: undefined;
|
|
1127
1166
|
format?: undefined;
|
|
1128
1167
|
ref?: undefined;
|
|
1168
|
+
query?: undefined;
|
|
1169
|
+
max_files?: undefined;
|
|
1170
|
+
graph?: undefined;
|
|
1129
1171
|
count?: undefined;
|
|
1130
1172
|
command?: undefined;
|
|
1131
1173
|
runner?: undefined;
|
|
@@ -1201,6 +1243,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1201
1243
|
viaKind?: undefined;
|
|
1202
1244
|
format?: undefined;
|
|
1203
1245
|
ref?: undefined;
|
|
1246
|
+
query?: undefined;
|
|
1247
|
+
max_files?: undefined;
|
|
1248
|
+
graph?: undefined;
|
|
1204
1249
|
count?: undefined;
|
|
1205
1250
|
command?: undefined;
|
|
1206
1251
|
runner?: undefined;
|
|
@@ -1283,6 +1328,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1283
1328
|
viaKind?: undefined;
|
|
1284
1329
|
format?: undefined;
|
|
1285
1330
|
ref?: undefined;
|
|
1331
|
+
query?: undefined;
|
|
1332
|
+
max_files?: undefined;
|
|
1333
|
+
graph?: undefined;
|
|
1286
1334
|
count?: undefined;
|
|
1287
1335
|
command?: undefined;
|
|
1288
1336
|
runner?: undefined;
|
|
@@ -1356,6 +1404,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1356
1404
|
viaKind?: undefined;
|
|
1357
1405
|
format?: undefined;
|
|
1358
1406
|
ref?: undefined;
|
|
1407
|
+
query?: undefined;
|
|
1408
|
+
max_files?: undefined;
|
|
1409
|
+
graph?: undefined;
|
|
1359
1410
|
count?: undefined;
|
|
1360
1411
|
command?: undefined;
|
|
1361
1412
|
runner?: undefined;
|
|
@@ -1445,6 +1496,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1445
1496
|
pattern?: undefined;
|
|
1446
1497
|
name?: undefined;
|
|
1447
1498
|
ref?: undefined;
|
|
1499
|
+
query?: undefined;
|
|
1500
|
+
max_files?: undefined;
|
|
1501
|
+
graph?: undefined;
|
|
1448
1502
|
count?: undefined;
|
|
1449
1503
|
command?: undefined;
|
|
1450
1504
|
runner?: undefined;
|
|
@@ -1521,6 +1575,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1521
1575
|
maxDepth?: undefined;
|
|
1522
1576
|
viaKind?: undefined;
|
|
1523
1577
|
format?: undefined;
|
|
1578
|
+
query?: undefined;
|
|
1579
|
+
max_files?: undefined;
|
|
1580
|
+
graph?: undefined;
|
|
1524
1581
|
count?: undefined;
|
|
1525
1582
|
command?: undefined;
|
|
1526
1583
|
runner?: undefined;
|
|
@@ -1597,6 +1654,87 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1597
1654
|
viaKind?: undefined;
|
|
1598
1655
|
format?: undefined;
|
|
1599
1656
|
ref?: undefined;
|
|
1657
|
+
query?: undefined;
|
|
1658
|
+
max_files?: undefined;
|
|
1659
|
+
graph?: undefined;
|
|
1660
|
+
count?: undefined;
|
|
1661
|
+
command?: undefined;
|
|
1662
|
+
runner?: undefined;
|
|
1663
|
+
timeout?: undefined;
|
|
1664
|
+
goal?: undefined;
|
|
1665
|
+
decisions?: undefined;
|
|
1666
|
+
confirmed?: undefined;
|
|
1667
|
+
files?: undefined;
|
|
1668
|
+
blocked?: undefined;
|
|
1669
|
+
next?: undefined;
|
|
1670
|
+
sessionId?: undefined;
|
|
1671
|
+
};
|
|
1672
|
+
required: string[];
|
|
1673
|
+
};
|
|
1674
|
+
} | {
|
|
1675
|
+
name: string;
|
|
1676
|
+
description: string;
|
|
1677
|
+
inputSchema: {
|
|
1678
|
+
type: "object";
|
|
1679
|
+
properties: {
|
|
1680
|
+
query: {
|
|
1681
|
+
type: string;
|
|
1682
|
+
description: string;
|
|
1683
|
+
};
|
|
1684
|
+
max_files: {
|
|
1685
|
+
type: string;
|
|
1686
|
+
description: string;
|
|
1687
|
+
};
|
|
1688
|
+
graph: {
|
|
1689
|
+
type: string;
|
|
1690
|
+
description: string;
|
|
1691
|
+
};
|
|
1692
|
+
path?: undefined;
|
|
1693
|
+
show_imports?: undefined;
|
|
1694
|
+
show_docs?: undefined;
|
|
1695
|
+
depth?: undefined;
|
|
1696
|
+
scope?: undefined;
|
|
1697
|
+
max_tokens?: undefined;
|
|
1698
|
+
session_id?: undefined;
|
|
1699
|
+
force?: undefined;
|
|
1700
|
+
symbol?: undefined;
|
|
1701
|
+
context_before?: undefined;
|
|
1702
|
+
context_after?: undefined;
|
|
1703
|
+
show?: undefined;
|
|
1704
|
+
include_edit_context?: undefined;
|
|
1705
|
+
symbols?: undefined;
|
|
1706
|
+
start_line?: undefined;
|
|
1707
|
+
end_line?: undefined;
|
|
1708
|
+
heading?: undefined;
|
|
1709
|
+
context_lines?: undefined;
|
|
1710
|
+
line?: undefined;
|
|
1711
|
+
context?: undefined;
|
|
1712
|
+
include_callers?: undefined;
|
|
1713
|
+
include_tests?: undefined;
|
|
1714
|
+
include_changes?: undefined;
|
|
1715
|
+
section?: undefined;
|
|
1716
|
+
paths?: undefined;
|
|
1717
|
+
kind?: undefined;
|
|
1718
|
+
limit?: undefined;
|
|
1719
|
+
lang?: undefined;
|
|
1720
|
+
mode?: undefined;
|
|
1721
|
+
include?: undefined;
|
|
1722
|
+
recursive?: undefined;
|
|
1723
|
+
max_depth?: undefined;
|
|
1724
|
+
verbose?: undefined;
|
|
1725
|
+
module?: undefined;
|
|
1726
|
+
export_only?: undefined;
|
|
1727
|
+
check?: undefined;
|
|
1728
|
+
pattern?: undefined;
|
|
1729
|
+
name?: undefined;
|
|
1730
|
+
from?: undefined;
|
|
1731
|
+
to?: undefined;
|
|
1732
|
+
all?: undefined;
|
|
1733
|
+
maxPaths?: undefined;
|
|
1734
|
+
maxDepth?: undefined;
|
|
1735
|
+
viaKind?: undefined;
|
|
1736
|
+
format?: undefined;
|
|
1737
|
+
ref?: undefined;
|
|
1600
1738
|
count?: undefined;
|
|
1601
1739
|
command?: undefined;
|
|
1602
1740
|
runner?: undefined;
|
|
@@ -1673,6 +1811,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1673
1811
|
maxDepth?: undefined;
|
|
1674
1812
|
viaKind?: undefined;
|
|
1675
1813
|
format?: undefined;
|
|
1814
|
+
query?: undefined;
|
|
1815
|
+
max_files?: undefined;
|
|
1816
|
+
graph?: undefined;
|
|
1676
1817
|
command?: undefined;
|
|
1677
1818
|
runner?: undefined;
|
|
1678
1819
|
timeout?: undefined;
|
|
@@ -1751,6 +1892,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1751
1892
|
viaKind?: undefined;
|
|
1752
1893
|
format?: undefined;
|
|
1753
1894
|
ref?: undefined;
|
|
1895
|
+
query?: undefined;
|
|
1896
|
+
max_files?: undefined;
|
|
1897
|
+
graph?: undefined;
|
|
1754
1898
|
count?: undefined;
|
|
1755
1899
|
goal?: undefined;
|
|
1756
1900
|
decisions?: undefined;
|
|
@@ -1847,6 +1991,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1847
1991
|
viaKind?: undefined;
|
|
1848
1992
|
format?: undefined;
|
|
1849
1993
|
ref?: undefined;
|
|
1994
|
+
query?: undefined;
|
|
1995
|
+
max_files?: undefined;
|
|
1996
|
+
graph?: undefined;
|
|
1850
1997
|
count?: undefined;
|
|
1851
1998
|
command?: undefined;
|
|
1852
1999
|
runner?: undefined;
|
|
@@ -1911,6 +2058,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1911
2058
|
viaKind?: undefined;
|
|
1912
2059
|
format?: undefined;
|
|
1913
2060
|
ref?: undefined;
|
|
2061
|
+
query?: undefined;
|
|
2062
|
+
max_files?: undefined;
|
|
2063
|
+
graph?: undefined;
|
|
1914
2064
|
count?: undefined;
|
|
1915
2065
|
command?: undefined;
|
|
1916
2066
|
runner?: undefined;
|
|
@@ -670,6 +670,28 @@ export const TOOL_DEFINITIONS = [
|
|
|
670
670
|
required: ["path"],
|
|
671
671
|
},
|
|
672
672
|
},
|
|
673
|
+
{
|
|
674
|
+
name: "explore",
|
|
675
|
+
description: "One-shot ranked context + call/inheritance graph blast-radius for a query. Returns ranked symbols, the source heads of the top-ranked files, graph neighbours (callers + subclasses — the blast radius), and related test files in a single compact block. Use INSTEAD OF separate find_usages + read_symbol + call_tree when you need to understand an area fast — cheaper than chaining those three.",
|
|
676
|
+
inputSchema: {
|
|
677
|
+
type: "object",
|
|
678
|
+
properties: {
|
|
679
|
+
query: {
|
|
680
|
+
type: "string",
|
|
681
|
+
description: "Search terms (the binary splits the string into terms itself), e.g. \"AstIndexClient buildIndex\"",
|
|
682
|
+
},
|
|
683
|
+
max_files: {
|
|
684
|
+
type: "number",
|
|
685
|
+
description: "Cap on the number of source file heads returned (default: binary's own limit)",
|
|
686
|
+
},
|
|
687
|
+
graph: {
|
|
688
|
+
type: "boolean",
|
|
689
|
+
description: "Include call/inheritance graph neighbours (blast radius). Default: true. Set false to skip the graph walk.",
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
required: ["query"],
|
|
693
|
+
},
|
|
694
|
+
},
|
|
673
695
|
{
|
|
674
696
|
name: "smart_log",
|
|
675
697
|
description: "Use INSTEAD OF raw git log. Structured commit history with category detection (feat/fix/refactor/docs), file stats, author breakdown. Filters by path and ref. HEADS UP: two verification runs measured this tool at ~39% token reduction (borderline — vs 95-99% for outline/smart_diff). Cumulative data being gathered — tool may be dropped or redesigned in v0.30.0 if numbers don't improve. Prefer scoping with `path` or `count` to tighten savings.",
|
package/dist/server.js
CHANGED
|
@@ -38,6 +38,7 @@ import { handleModuleInfo } from "./handlers/module-info.js";
|
|
|
38
38
|
import { handleModuleRoute } from "./handlers/module-route.js";
|
|
39
39
|
import { handleSmartDiff } from "./handlers/smart-diff.js";
|
|
40
40
|
import { handleExploreArea } from "./handlers/explore-area.js";
|
|
41
|
+
import { handleExplore } from "./handlers/explore.js";
|
|
41
42
|
import { handleSmartLog } from "./handlers/smart-log.js";
|
|
42
43
|
import { handleTestSummary } from "./handlers/test-summary.js";
|
|
43
44
|
import { handleSessionSnapshot } from "./handlers/session-snapshot.js";
|
|
@@ -52,7 +53,7 @@ import { getMcpInstructions, TOOL_DEFINITIONS, } from "./server/tool-definitions
|
|
|
52
53
|
import { filterToolsByProfile, parseProfileEnv, } from "./server/tool-profiles.js";
|
|
53
54
|
import { STRICT_SMART_READ_MAX_TOKENS, STRICT_EXPLORE_AREA_INCLUDE, } from "./server/enforcement-mode.js";
|
|
54
55
|
import { createTokenEstimates } from "./server/token-estimates.js";
|
|
55
|
-
import { validateSmartReadArgs, validateReadSymbolArgs, validateReadSymbolsArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCallTreeArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateModuleRouteArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateSmartLogArgs, validateTestSummaryArgs, validateReadSectionArgs, } from "./core/validation.js";
|
|
56
|
+
import { validateSmartReadArgs, validateReadSymbolArgs, validateReadSymbolsArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCallTreeArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateModuleRouteArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateExploreArgs, validateSmartLogArgs, validateTestSummaryArgs, validateReadSectionArgs, } from "./core/validation.js";
|
|
56
57
|
export async function createServer(projectRoot, options) {
|
|
57
58
|
const mode = options?.enforcementMode ?? "deny";
|
|
58
59
|
// v0.43.0 — the real Claude Code session id. CC exports it to every
|
|
@@ -967,6 +968,40 @@ export async function createServer(projectRoot, options) {
|
|
|
967
968
|
}
|
|
968
969
|
return eaResult;
|
|
969
970
|
}
|
|
971
|
+
case "explore": {
|
|
972
|
+
const exArgs = validateExploreArgs(args);
|
|
973
|
+
const cachedEx = sessionCache?.get("explore", exArgs);
|
|
974
|
+
if (cachedEx) {
|
|
975
|
+
recordWithTrace({
|
|
976
|
+
tool: "explore",
|
|
977
|
+
path: exArgs.query,
|
|
978
|
+
tokensReturned: cachedEx.tokenEstimate,
|
|
979
|
+
tokensWouldBe: cachedEx.tokensWouldBe ?? cachedEx.tokenEstimate,
|
|
980
|
+
timestamp: Date.now(),
|
|
981
|
+
sessionCacheHit: true,
|
|
982
|
+
savingsCategory: "cache",
|
|
983
|
+
args: exArgs,
|
|
984
|
+
});
|
|
985
|
+
return cachedEx.result;
|
|
986
|
+
}
|
|
987
|
+
const exResult = await handleExplore(exArgs, projectRoot, astIndex);
|
|
988
|
+
const exText = exResult.content[0]?.text ?? "";
|
|
989
|
+
const exTokens = estimateTokens(exText);
|
|
990
|
+
// explore replaces a find_usages + read_symbol + call_tree chain;
|
|
991
|
+
// approximate that baseline as ~3x the compacted output.
|
|
992
|
+
const exWouldBe = exTokens * 3;
|
|
993
|
+
sessionCache?.set("explore", exArgs, exResult, { dependsOnAst: true }, exTokens, exWouldBe || exTokens);
|
|
994
|
+
recordWithTrace({
|
|
995
|
+
tool: "explore",
|
|
996
|
+
path: exArgs.query,
|
|
997
|
+
tokensReturned: exTokens,
|
|
998
|
+
tokensWouldBe: exWouldBe || exTokens,
|
|
999
|
+
timestamp: Date.now(),
|
|
1000
|
+
savingsCategory: "compression",
|
|
1001
|
+
args: exArgs,
|
|
1002
|
+
});
|
|
1003
|
+
return exResult;
|
|
1004
|
+
}
|
|
970
1005
|
case "smart_log": {
|
|
971
1006
|
const slArgs = validateSmartLogArgs(args);
|
|
972
1007
|
// v0.30.0 strict mode: bound count to 20 when caller didn't set it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.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",
|
|
@@ -67,15 +67,15 @@
|
|
|
67
67
|
"mcpName": "io.github.Digital-Threads/token-pilot",
|
|
68
68
|
"license": "MIT",
|
|
69
69
|
"dependencies": {
|
|
70
|
+
"@ast-index/cli": "^3.48.1",
|
|
70
71
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
71
|
-
"@ast-index/cli": "^3.44.0",
|
|
72
72
|
"chokidar": "^4.0.3"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@vitest/coverage-v8": "^3.2.4",
|
|
76
75
|
"@types/node": "^22.0.0",
|
|
76
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
77
77
|
"typescript": "^5.7.0",
|
|
78
|
-
"vitest": "^
|
|
78
|
+
"vitest": "^4.1.8"
|
|
79
79
|
},
|
|
80
80
|
"engines": {
|
|
81
81
|
"node": ">=18.0.0"
|