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.
Files changed (50) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -4
  3. package/README.md +24 -0
  4. package/agents/tp-api-surface-tracker.md +1 -1
  5. package/agents/tp-audit-scanner.md +1 -1
  6. package/agents/tp-commit-writer.md +1 -1
  7. package/agents/tp-context-engineer.md +1 -1
  8. package/agents/tp-dead-code-finder.md +1 -1
  9. package/agents/tp-debugger.md +1 -1
  10. package/agents/tp-dep-health.md +1 -1
  11. package/agents/tp-doc-writer.md +1 -1
  12. package/agents/tp-history-explorer.md +1 -1
  13. package/agents/tp-impact-analyzer.md +1 -1
  14. package/agents/tp-incident-timeline.md +1 -1
  15. package/agents/tp-incremental-builder.md +1 -1
  16. package/agents/tp-migration-scout.md +1 -1
  17. package/agents/tp-onboard.md +1 -1
  18. package/agents/tp-performance-profiler.md +1 -1
  19. package/agents/tp-pr-reviewer.md +1 -1
  20. package/agents/tp-refactor-planner.md +1 -1
  21. package/agents/tp-review-impact.md +1 -1
  22. package/agents/tp-run.md +1 -1
  23. package/agents/tp-session-restorer.md +1 -1
  24. package/agents/tp-ship-coordinator.md +1 -1
  25. package/agents/tp-spec-writer.md +1 -1
  26. package/agents/tp-test-coverage-gapper.md +1 -1
  27. package/agents/tp-test-triage.md +1 -1
  28. package/agents/tp-test-writer.md +1 -1
  29. package/dist/ast-index/client.d.ts +17 -2
  30. package/dist/ast-index/client.js +233 -107
  31. package/dist/core/edit-prep-state.d.ts +42 -0
  32. package/dist/core/edit-prep-state.js +108 -0
  33. package/dist/handlers/explore-area.js +6 -1
  34. package/dist/handlers/read-for-edit.d.ts +5 -5
  35. package/dist/handlers/read-for-edit.js +188 -110
  36. package/dist/hooks/installer.js +18 -0
  37. package/dist/hooks/pre-bash.d.ts +9 -0
  38. package/dist/hooks/pre-bash.js +48 -0
  39. package/dist/hooks/pre-edit.d.ts +69 -0
  40. package/dist/hooks/pre-edit.js +104 -0
  41. package/dist/hooks/pre-grep.d.ts +10 -0
  42. package/dist/hooks/pre-grep.js +38 -2
  43. package/dist/index.d.ts +30 -0
  44. package/dist/index.js +83 -20
  45. package/dist/server/tool-definitions.js +18 -6
  46. package/dist/server.js +21 -5
  47. package/docs/installation.md +27 -1
  48. package/hooks/hooks.json +18 -0
  49. package/package.json +1 -1
  50. 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
- const include = args.include ?? ["outline", "imports", "tests", "changes"];
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 '../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';
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: 'text';
20
+ type: "text";
21
21
  text: string;
22
22
  }>;
23
23
  }>;