token-pilot 0.30.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -4
- package/README.md +24 -0
- 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 +17 -2
- package/dist/ast-index/client.js +233 -107
- package/dist/core/edit-prep-state.d.ts +42 -0
- package/dist/core/edit-prep-state.js +108 -0
- package/dist/handlers/explore-area.js +6 -1
- package/dist/handlers/read-for-edit.d.ts +5 -5
- package/dist/handlers/read-for-edit.js +188 -110
- package/dist/hooks/installer.js +18 -0
- package/dist/hooks/pre-bash.d.ts +9 -0
- package/dist/hooks/pre-bash.js +48 -0
- package/dist/hooks/pre-edit.d.ts +69 -0
- package/dist/hooks/pre-edit.js +104 -0
- package/dist/hooks/pre-grep.d.ts +10 -0
- package/dist/hooks/pre-grep.js +38 -2
- package/dist/index.d.ts +30 -0
- package/dist/index.js +83 -20
- package/dist/server/tool-definitions.js +18 -6
- package/dist/server.js +21 -5
- package/docs/installation.md +27 -1
- package/hooks/hooks.json +18 -0
- package/package.json +1 -1
- package/start.sh +19 -9
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared disk state so a PreToolUse:Edit hook subprocess can tell whether
|
|
3
|
+
* the main-thread agent actually called `read_for_edit` before the Edit.
|
|
4
|
+
*
|
|
5
|
+
* Why file-backed: hook subprocesses spawn fresh per-call and see no
|
|
6
|
+
* in-process state from the MCP server. Keyed by a hash of projectRoot so
|
|
7
|
+
* two concurrent projects on the same machine don't cross-contaminate.
|
|
8
|
+
* Entries expire after 30 minutes — long enough to cover a typical edit
|
|
9
|
+
* session, short enough that a stale prep from a previous conversation
|
|
10
|
+
* doesn't let a bad Edit slip through tomorrow.
|
|
11
|
+
*
|
|
12
|
+
* All operations are best-effort: any I/O error is swallowed silently.
|
|
13
|
+
* A broken state file must NEVER block an Edit — it's a soft guardrail,
|
|
14
|
+
* not a file-system invariant. If the prep file is unreadable we act as
|
|
15
|
+
* if "no prep exists" and the hook behaves per its enforcement mode.
|
|
16
|
+
*/
|
|
17
|
+
declare function stateDir(): string;
|
|
18
|
+
declare function stateFile(projectRoot: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Record that `read_for_edit` was just called for `absPath`.
|
|
21
|
+
* Safe to call in any order, from any process — atomically rewrites the
|
|
22
|
+
* state file so a racing read never sees a half-written JSON.
|
|
23
|
+
*/
|
|
24
|
+
export declare function markEditPrepared(projectRoot: string, absPath: string, now?: number): void;
|
|
25
|
+
/**
|
|
26
|
+
* Has `read_for_edit` been called for `absPath` recently enough to
|
|
27
|
+
* authorise a follow-up Edit? Auto-prunes entries older than TTL.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isEditPrepared(projectRoot: string, absPath: string, now?: number, ttlMs?: number): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Clear all prep state for a project root. Intended for tests and for the
|
|
32
|
+
* `doctor` CLI if we ever want an explicit reset button.
|
|
33
|
+
*/
|
|
34
|
+
export declare function clearEditPrep(projectRoot: string): void;
|
|
35
|
+
/** Exposed for tests so they don't need to import tmpdir themselves. */
|
|
36
|
+
export declare const __test__: {
|
|
37
|
+
stateDir: typeof stateDir;
|
|
38
|
+
stateFile: typeof stateFile;
|
|
39
|
+
DEFAULT_TTL_MS: number;
|
|
40
|
+
};
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=edit-prep-state.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared disk state so a PreToolUse:Edit hook subprocess can tell whether
|
|
3
|
+
* the main-thread agent actually called `read_for_edit` before the Edit.
|
|
4
|
+
*
|
|
5
|
+
* Why file-backed: hook subprocesses spawn fresh per-call and see no
|
|
6
|
+
* in-process state from the MCP server. Keyed by a hash of projectRoot so
|
|
7
|
+
* two concurrent projects on the same machine don't cross-contaminate.
|
|
8
|
+
* Entries expire after 30 minutes — long enough to cover a typical edit
|
|
9
|
+
* session, short enough that a stale prep from a previous conversation
|
|
10
|
+
* doesn't let a bad Edit slip through tomorrow.
|
|
11
|
+
*
|
|
12
|
+
* All operations are best-effort: any I/O error is swallowed silently.
|
|
13
|
+
* A broken state file must NEVER block an Edit — it's a soft guardrail,
|
|
14
|
+
* not a file-system invariant. If the prep file is unreadable we act as
|
|
15
|
+
* if "no prep exists" and the hook behaves per its enforcement mode.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join, resolve } from "node:path";
|
|
21
|
+
const STATE_DIR_NAME = "token-pilot-edit-prep";
|
|
22
|
+
const DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
23
|
+
function stateDir() {
|
|
24
|
+
return join(tmpdir(), STATE_DIR_NAME);
|
|
25
|
+
}
|
|
26
|
+
function stateFile(projectRoot) {
|
|
27
|
+
const hash = createHash("sha1")
|
|
28
|
+
.update(resolve(projectRoot))
|
|
29
|
+
.digest("hex")
|
|
30
|
+
.slice(0, 16);
|
|
31
|
+
return join(stateDir(), `${hash}.json`);
|
|
32
|
+
}
|
|
33
|
+
function loadState(projectRoot) {
|
|
34
|
+
try {
|
|
35
|
+
const txt = readFileSync(stateFile(projectRoot), "utf-8");
|
|
36
|
+
const parsed = JSON.parse(txt);
|
|
37
|
+
if (parsed &&
|
|
38
|
+
typeof parsed === "object" &&
|
|
39
|
+
"paths" in parsed &&
|
|
40
|
+
typeof parsed.paths === "object") {
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* corrupt / missing — fall through */
|
|
46
|
+
}
|
|
47
|
+
return { paths: {} };
|
|
48
|
+
}
|
|
49
|
+
function pruneExpired(state, ttlMs, now) {
|
|
50
|
+
const fresh = {};
|
|
51
|
+
for (const [p, ts] of Object.entries(state.paths)) {
|
|
52
|
+
if (typeof ts === "number" && now - ts < ttlMs) {
|
|
53
|
+
fresh[p] = ts;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { paths: fresh };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Record that `read_for_edit` was just called for `absPath`.
|
|
60
|
+
* Safe to call in any order, from any process — atomically rewrites the
|
|
61
|
+
* state file so a racing read never sees a half-written JSON.
|
|
62
|
+
*/
|
|
63
|
+
export function markEditPrepared(projectRoot, absPath, now = Date.now()) {
|
|
64
|
+
try {
|
|
65
|
+
const dir = stateDir();
|
|
66
|
+
if (!existsSync(dir))
|
|
67
|
+
mkdirSync(dir, { recursive: true });
|
|
68
|
+
const state = pruneExpired(loadState(projectRoot), DEFAULT_TTL_MS, now);
|
|
69
|
+
state.paths[resolve(absPath)] = now;
|
|
70
|
+
const file = stateFile(projectRoot);
|
|
71
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
72
|
+
writeFileSync(tmp, JSON.stringify(state));
|
|
73
|
+
renameSync(tmp, file);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* best-effort — see module doc */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Has `read_for_edit` been called for `absPath` recently enough to
|
|
81
|
+
* authorise a follow-up Edit? Auto-prunes entries older than TTL.
|
|
82
|
+
*/
|
|
83
|
+
export function isEditPrepared(projectRoot, absPath, now = Date.now(), ttlMs = DEFAULT_TTL_MS) {
|
|
84
|
+
const state = pruneExpired(loadState(projectRoot), ttlMs, now);
|
|
85
|
+
return typeof state.paths[resolve(absPath)] === "number";
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Clear all prep state for a project root. Intended for tests and for the
|
|
89
|
+
* `doctor` CLI if we ever want an explicit reset button.
|
|
90
|
+
*/
|
|
91
|
+
export function clearEditPrep(projectRoot) {
|
|
92
|
+
try {
|
|
93
|
+
const file = stateFile(projectRoot);
|
|
94
|
+
if (existsSync(file)) {
|
|
95
|
+
writeFileSync(file, JSON.stringify({ paths: {} }));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
/* best effort */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Exposed for tests so they don't need to import tmpdir themselves. */
|
|
103
|
+
export const __test__ = {
|
|
104
|
+
stateDir,
|
|
105
|
+
stateFile,
|
|
106
|
+
DEFAULT_TTL_MS,
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=edit-prep-state.js.map
|
|
@@ -42,7 +42,12 @@ export async function handleExploreArea(args, projectRoot, astIndex) {
|
|
|
42
42
|
absPath = dirname(absPath);
|
|
43
43
|
}
|
|
44
44
|
const relDir = relative(projectRoot, absPath) || ".";
|
|
45
|
-
|
|
45
|
+
// v0.30.0 — narrowed default from all 4 sections to the two cheap ones.
|
|
46
|
+
// Telemetry (docker-local-env, 2026-04-24) showed the all-4 default giving
|
|
47
|
+
// negative token reduction (-7%): `imports` builds a full dep graph and
|
|
48
|
+
// `tests` walks subtrees, both easily outweighing the raw-file baseline.
|
|
49
|
+
// Callers who need imports/tests now opt in explicitly via `include`.
|
|
50
|
+
const include = args.include ?? ["outline", "changes"];
|
|
46
51
|
// Collect code files for import/test analysis
|
|
47
52
|
const codeFiles = await listCodeFiles(absPath);
|
|
48
53
|
// Run all sections in parallel
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { AstIndexClient } from
|
|
2
|
-
import type { SymbolResolver } from
|
|
3
|
-
import type { FileCache } from
|
|
4
|
-
import type { ContextRegistry } from
|
|
1
|
+
import type { AstIndexClient } from "../ast-index/client.js";
|
|
2
|
+
import type { SymbolResolver } from "../core/symbol-resolver.js";
|
|
3
|
+
import type { FileCache } from "../core/file-cache.js";
|
|
4
|
+
import type { ContextRegistry } from "../core/context-registry.js";
|
|
5
5
|
export interface ReadForEditArgs {
|
|
6
6
|
path: string;
|
|
7
7
|
symbol?: string;
|
|
@@ -17,7 +17,7 @@ export declare function handleReadForEdit(args: ReadForEditArgs, projectRoot: st
|
|
|
17
17
|
actionableHints?: boolean;
|
|
18
18
|
}): Promise<{
|
|
19
19
|
content: Array<{
|
|
20
|
-
type:
|
|
20
|
+
type: "text";
|
|
21
21
|
text: string;
|
|
22
22
|
}>;
|
|
23
23
|
}>;
|