token-pilot 0.19.2 → 0.22.2

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 (89) hide show
  1. package/.claude-plugin/hooks/hooks.json +21 -0
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/CHANGELOG.md +129 -0
  4. package/README.md +172 -315
  5. package/dist/agents/tp-commit-writer.md +41 -0
  6. package/dist/agents/tp-dead-code-finder.md +43 -0
  7. package/dist/agents/tp-debugger.md +45 -0
  8. package/dist/agents/tp-impact-analyzer.md +44 -0
  9. package/dist/agents/tp-migration-scout.md +43 -0
  10. package/dist/agents/tp-onboard.md +40 -0
  11. package/dist/agents/tp-pr-reviewer.md +41 -0
  12. package/dist/agents/tp-refactor-planner.md +42 -0
  13. package/dist/agents/tp-run.md +48 -0
  14. package/dist/agents/tp-test-triage.md +40 -0
  15. package/dist/agents/tp-test-writer.md +46 -0
  16. package/dist/cli/agent-frontmatter.d.ts +48 -0
  17. package/dist/cli/agent-frontmatter.js +189 -0
  18. package/dist/cli/bless-agents.d.ts +65 -0
  19. package/dist/cli/bless-agents.js +307 -0
  20. package/dist/cli/claudeignore.d.ts +33 -0
  21. package/dist/cli/claudeignore.js +88 -0
  22. package/dist/cli/claudemd-hygiene.d.ts +26 -0
  23. package/dist/cli/claudemd-hygiene.js +43 -0
  24. package/dist/cli/doctor-drift.d.ts +31 -0
  25. package/dist/cli/doctor-drift.js +130 -0
  26. package/dist/cli/doctor-env-check.d.ts +25 -0
  27. package/dist/cli/doctor-env-check.js +91 -0
  28. package/dist/cli/install-agents.d.ts +108 -0
  29. package/dist/cli/install-agents.js +402 -0
  30. package/dist/cli/save-doc.d.ts +42 -0
  31. package/dist/cli/save-doc.js +145 -0
  32. package/dist/cli/scan-agents.d.ts +46 -0
  33. package/dist/cli/scan-agents.js +227 -0
  34. package/dist/cli/stats.d.ts +36 -0
  35. package/dist/cli/stats.js +131 -0
  36. package/dist/cli/unbless-agents.d.ts +33 -0
  37. package/dist/cli/unbless-agents.js +85 -0
  38. package/dist/cli/uninstall-agents.d.ts +36 -0
  39. package/dist/cli/uninstall-agents.js +117 -0
  40. package/dist/config/defaults.d.ts +1 -1
  41. package/dist/config/defaults.js +14 -8
  42. package/dist/config/loader.d.ts +1 -1
  43. package/dist/config/loader.js +105 -11
  44. package/dist/core/context-registry.d.ts +16 -1
  45. package/dist/core/context-registry.js +60 -28
  46. package/dist/core/event-log.d.ts +79 -0
  47. package/dist/core/event-log.js +190 -0
  48. package/dist/core/session-registry.d.ts +43 -0
  49. package/dist/core/session-registry.js +113 -0
  50. package/dist/core/session-savings.d.ts +19 -0
  51. package/dist/core/session-savings.js +60 -0
  52. package/dist/handlers/session-budget.d.ts +32 -0
  53. package/dist/handlers/session-budget.js +61 -0
  54. package/dist/handlers/session-snapshot-persist.d.ts +22 -0
  55. package/dist/handlers/session-snapshot-persist.js +76 -0
  56. package/dist/hooks/adaptive-threshold.d.ts +27 -0
  57. package/dist/hooks/adaptive-threshold.js +46 -0
  58. package/dist/hooks/format-deny-message.d.ts +21 -0
  59. package/dist/hooks/format-deny-message.js +147 -0
  60. package/dist/hooks/installer.js +121 -31
  61. package/dist/hooks/path-safety.d.ts +16 -0
  62. package/dist/hooks/path-safety.js +34 -0
  63. package/dist/hooks/post-bash.d.ts +46 -0
  64. package/dist/hooks/post-bash.js +77 -0
  65. package/dist/hooks/session-start.d.ts +45 -0
  66. package/dist/hooks/session-start.js +179 -0
  67. package/dist/hooks/summary-ast-index.d.ts +28 -0
  68. package/dist/hooks/summary-ast-index.js +122 -0
  69. package/dist/hooks/summary-head-tail.d.ts +15 -0
  70. package/dist/hooks/summary-head-tail.js +78 -0
  71. package/dist/hooks/summary-pipeline.d.ts +35 -0
  72. package/dist/hooks/summary-pipeline.js +63 -0
  73. package/dist/hooks/summary-regex.d.ts +14 -0
  74. package/dist/hooks/summary-regex.js +130 -0
  75. package/dist/hooks/summary-types.d.ts +29 -0
  76. package/dist/hooks/summary-types.js +9 -0
  77. package/dist/index.d.ts +15 -3
  78. package/dist/index.js +509 -149
  79. package/dist/integration/context-mode-detector.d.ts +7 -1
  80. package/dist/integration/context-mode-detector.js +51 -15
  81. package/dist/server/tool-definitions.d.ts +149 -0
  82. package/dist/server/tool-definitions.js +424 -202
  83. package/dist/server.d.ts +1 -1
  84. package/dist/server.js +456 -179
  85. package/dist/templates/agent-builder.d.ts +49 -0
  86. package/dist/templates/agent-builder.js +104 -0
  87. package/dist/types.d.ts +38 -4
  88. package/package.json +4 -2
  89. package/skills/stats/SKILL.md +13 -2
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Primary hook-summary parser: spawns the bundled `ast-index` binary with
3
+ * `ast-index outline <path>` and maps the returned outline entries to
4
+ * SignalLine[]. Returns null when the binary is unavailable or the
5
+ * subprocess fails — the pipeline then falls back to regex / head+tail.
6
+ *
7
+ * Short-lived: the hook process spawns the binary once per invocation.
8
+ * The long-running AstIndexClient used by the MCP server is intentionally
9
+ * NOT reused here to keep the hook's startup cost minimal.
10
+ */
11
+ import { execFile } from "node:child_process";
12
+ import { promisify } from "node:util";
13
+ import { findBinary } from "../ast-index/binary-manager.js";
14
+ import { parseOutlineText } from "../ast-index/parser.js";
15
+ const execFileAsync = promisify(execFile);
16
+ const DEFAULT_TIMEOUT_MS = 4000;
17
+ const MAX_TEXT_LEN = 140;
18
+ function extractExtension(filePath) {
19
+ const lastDot = filePath.lastIndexOf(".");
20
+ if (lastDot === -1 || lastDot === filePath.length - 1)
21
+ return "";
22
+ const ext = filePath.slice(lastDot + 1).toLowerCase();
23
+ if (ext.includes("/") || ext.includes("\\"))
24
+ return "";
25
+ return ext;
26
+ }
27
+ function truncate(text) {
28
+ const trimmed = text.trim();
29
+ if (trimmed.length <= MAX_TEXT_LEN)
30
+ return trimmed;
31
+ return trimmed.slice(0, MAX_TEXT_LEN - 1) + "…";
32
+ }
33
+ function estimateTokens(text) {
34
+ if (text.length === 0)
35
+ return 0;
36
+ const charEstimate = Math.ceil(text.length / 4);
37
+ const whitespaceRatio = (text.match(/\s/g)?.length ?? 0) / text.length;
38
+ const adjustment = 1 - whitespaceRatio * 0.3;
39
+ return Math.ceil(charEstimate * adjustment);
40
+ }
41
+ const defaultExec = async (binary, args, opts) => {
42
+ const { stdout, stderr } = await execFileAsync(binary, args, {
43
+ timeout: opts.timeout,
44
+ });
45
+ return { stdout: String(stdout), stderr: String(stderr) };
46
+ };
47
+ /**
48
+ * Resolve the binary path unless the caller already supplied one (including
49
+ * `null` to force "not available" for tests).
50
+ */
51
+ async function resolveBinaryPath(explicit) {
52
+ if (explicit !== undefined)
53
+ return explicit;
54
+ try {
55
+ const status = await findBinary(null);
56
+ return status?.available ? status.path : null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ function flattenEntries(entries) {
63
+ const signals = [];
64
+ function walk(entry, depth) {
65
+ const indent = depth > 0 ? " ".repeat(depth) : "";
66
+ const label = entry.signature && entry.signature.length > 0
67
+ ? entry.signature
68
+ : entry.name;
69
+ const text = truncate(`${indent}${entry.kind} ${label}`);
70
+ signals.push({
71
+ line: entry.start_line,
72
+ kind: entry.visibility === "public" ? "export" : "declaration",
73
+ text,
74
+ });
75
+ if (entry.children && entry.children.length > 0) {
76
+ for (const child of entry.children)
77
+ walk(child, depth + 1);
78
+ }
79
+ }
80
+ for (const entry of entries)
81
+ walk(entry, 0);
82
+ return signals;
83
+ }
84
+ export async function parseAstIndexSummary(content, filePath, options = {}) {
85
+ const binaryPath = await resolveBinaryPath(options.binaryPath);
86
+ if (!binaryPath)
87
+ return null;
88
+ const exec = options.exec ?? defaultExec;
89
+ const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
90
+ let outlineText;
91
+ try {
92
+ const { stdout } = await exec(binaryPath, ["outline", filePath], {
93
+ timeout,
94
+ });
95
+ outlineText = stdout;
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ let entries;
101
+ try {
102
+ entries = parseOutlineText(outlineText);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ if (!entries || entries.length === 0)
108
+ return null;
109
+ const signals = flattenEntries(entries);
110
+ if (signals.length === 0)
111
+ return null;
112
+ const language = extractExtension(filePath);
113
+ const totalLines = content.split("\n").length;
114
+ const estimatedTokens = estimateTokens(content);
115
+ return {
116
+ signals,
117
+ totalLines,
118
+ estimatedTokens,
119
+ language,
120
+ };
121
+ }
122
+ //# sourceMappingURL=summary-ast-index.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Last-resort head+tail summary.
3
+ *
4
+ * When both ast-index and the regex parser fail (crashed, unsupported language,
5
+ * malformed content), we still owe the caller *something* that respects the
6
+ * summary shape. This module produces a degraded HookSummary showing the first
7
+ * HEAD_LINES and last TAIL_LINES of the file as raw text, tagged with a note
8
+ * so the formatter can explain to the reader why the output is coarse.
9
+ *
10
+ * The function is intentionally total: empty input, unicode-heavy input, and
11
+ * absurdly large input all return a well-formed summary without throwing.
12
+ */
13
+ import type { HookSummary } from "./summary-types.js";
14
+ export declare function parseHeadTailSummary(content: string, filePath: string): HookSummary;
15
+ //# sourceMappingURL=summary-head-tail.d.ts.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Last-resort head+tail summary.
3
+ *
4
+ * When both ast-index and the regex parser fail (crashed, unsupported language,
5
+ * malformed content), we still owe the caller *something* that respects the
6
+ * summary shape. This module produces a degraded HookSummary showing the first
7
+ * HEAD_LINES and last TAIL_LINES of the file as raw text, tagged with a note
8
+ * so the formatter can explain to the reader why the output is coarse.
9
+ *
10
+ * The function is intentionally total: empty input, unicode-heavy input, and
11
+ * absurdly large input all return a well-formed summary without throwing.
12
+ */
13
+ const HEAD_LINES = 40;
14
+ const TAIL_LINES = 20;
15
+ const MAX_TEXT_LEN = 140;
16
+ function extractExtension(filePath) {
17
+ const lastDot = filePath.lastIndexOf(".");
18
+ if (lastDot === -1 || lastDot === filePath.length - 1)
19
+ return "";
20
+ const ext = filePath.slice(lastDot + 1).toLowerCase();
21
+ if (ext.includes("/") || ext.includes("\\"))
22
+ return "";
23
+ return ext;
24
+ }
25
+ function truncate(text) {
26
+ const trimmed = text.trimEnd();
27
+ if (trimmed.length <= MAX_TEXT_LEN)
28
+ return trimmed;
29
+ return trimmed.slice(0, MAX_TEXT_LEN - 1) + "…";
30
+ }
31
+ function estimateTokens(text) {
32
+ if (text.length === 0)
33
+ return 0;
34
+ const charEstimate = Math.ceil(text.length / 4);
35
+ const whitespaceRatio = (text.match(/\s/g)?.length ?? 0) / text.length;
36
+ const adjustment = 1 - whitespaceRatio * 0.3;
37
+ return Math.ceil(charEstimate * adjustment);
38
+ }
39
+ export function parseHeadTailSummary(content, filePath) {
40
+ const lines = content.split("\n");
41
+ const totalLines = lines.length;
42
+ const language = extractExtension(filePath);
43
+ const estimatedTokens = estimateTokens(content);
44
+ // When the file fits within HEAD_LINES + TAIL_LINES we include everything
45
+ // and omit the degradation note — no truncation actually happened.
46
+ if (totalLines <= HEAD_LINES + TAIL_LINES) {
47
+ const signals = lines.map((line, i) => ({
48
+ line: i + 1,
49
+ kind: "raw",
50
+ text: truncate(line),
51
+ }));
52
+ return {
53
+ signals,
54
+ totalLines,
55
+ estimatedTokens,
56
+ language,
57
+ };
58
+ }
59
+ const head = lines.slice(0, HEAD_LINES).map((line, i) => ({
60
+ line: i + 1,
61
+ kind: "raw",
62
+ text: truncate(line),
63
+ }));
64
+ const tailStart = totalLines - TAIL_LINES;
65
+ const tail = lines.slice(tailStart).map((line, i) => ({
66
+ line: tailStart + i + 1,
67
+ kind: "raw",
68
+ text: truncate(line),
69
+ }));
70
+ return {
71
+ signals: [...head, ...tail],
72
+ totalLines,
73
+ estimatedTokens,
74
+ language,
75
+ note: `parser unavailable — showing head+tail (first ${HEAD_LINES} and last ${TAIL_LINES} lines of ${totalLines})`,
76
+ };
77
+ }
78
+ //# sourceMappingURL=summary-head-tail.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Summary-generation pipeline for the deny-enhanced hook.
3
+ *
4
+ * Tries three parsers in order — ast-index subprocess → regex → head+tail —
5
+ * and returns the first result with non-empty signals. If every parser
6
+ * fails to produce useful output (or throws), the pipeline reports
7
+ * `pass-through`, at which point the handler lets the original Read
8
+ * proceed unmodified rather than emitting an empty / misleading denial.
9
+ *
10
+ * Each parser is injectable for tests. The default wiring uses the
11
+ * real implementations from sibling modules.
12
+ */
13
+ import type { HookSummary } from "./summary-types.js";
14
+ export type PipelineTier = "ast-index" | "regex" | "head-tail";
15
+ export type PipelineResult = {
16
+ kind: "summary";
17
+ summary: HookSummary;
18
+ tier: PipelineTier;
19
+ } | {
20
+ kind: "pass-through";
21
+ reason: string;
22
+ };
23
+ type AstIndexFn = (content: string, filePath: string) => Promise<HookSummary | null>;
24
+ type SyncSummaryFn = (content: string, filePath: string) => HookSummary;
25
+ export interface PipelineOptions {
26
+ /** Primary parser — ast-index subprocess. Returns null on soft fail. */
27
+ astIndex?: AstIndexFn;
28
+ /** Fallback parser — regex. Expected to always return a HookSummary. */
29
+ regex?: SyncSummaryFn;
30
+ /** Last-resort parser — head+tail. Expected to always return a HookSummary. */
31
+ headTail?: SyncSummaryFn;
32
+ }
33
+ export declare function runSummaryPipeline(content: string, filePath: string, options?: PipelineOptions): Promise<PipelineResult>;
34
+ export {};
35
+ //# sourceMappingURL=summary-pipeline.d.ts.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Summary-generation pipeline for the deny-enhanced hook.
3
+ *
4
+ * Tries three parsers in order — ast-index subprocess → regex → head+tail —
5
+ * and returns the first result with non-empty signals. If every parser
6
+ * fails to produce useful output (or throws), the pipeline reports
7
+ * `pass-through`, at which point the handler lets the original Read
8
+ * proceed unmodified rather than emitting an empty / misleading denial.
9
+ *
10
+ * Each parser is injectable for tests. The default wiring uses the
11
+ * real implementations from sibling modules.
12
+ */
13
+ import { parseAstIndexSummary } from "./summary-ast-index.js";
14
+ import { parseRegexSummary } from "./summary-regex.js";
15
+ import { parseHeadTailSummary } from "./summary-head-tail.js";
16
+ const defaultAstIndex = async (content, filePath) => parseAstIndexSummary(content, filePath);
17
+ const defaultRegex = parseRegexSummary;
18
+ const defaultHeadTail = parseHeadTailSummary;
19
+ function hasSignals(summary) {
20
+ return !!summary && summary.signals.length > 0;
21
+ }
22
+ async function tryAsync(fn) {
23
+ try {
24
+ return await fn();
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ function trySync(fn) {
31
+ try {
32
+ return fn();
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ export async function runSummaryPipeline(content, filePath, options = {}) {
39
+ const astIndex = options.astIndex ?? defaultAstIndex;
40
+ const regex = options.regex ?? defaultRegex;
41
+ const headTail = options.headTail ?? defaultHeadTail;
42
+ // Tier 1 — ast-index. Soft-fails on null or throw.
43
+ const astResult = await tryAsync(() => astIndex(content, filePath));
44
+ if (hasSignals(astResult)) {
45
+ return { kind: "summary", summary: astResult, tier: "ast-index" };
46
+ }
47
+ // Tier 2 — regex. Empty signals means "nothing useful here, try next".
48
+ const regexResult = trySync(() => regex(content, filePath));
49
+ if (hasSignals(regexResult)) {
50
+ return { kind: "summary", summary: regexResult, tier: "regex" };
51
+ }
52
+ // Tier 3 — head+tail. Always produces *something*, unless the parser
53
+ // itself crashes (in which case we pass-through).
54
+ const headTailResult = trySync(() => headTail(content, filePath));
55
+ if (hasSignals(headTailResult)) {
56
+ return { kind: "summary", summary: headTailResult, tier: "head-tail" };
57
+ }
58
+ return {
59
+ kind: "pass-through",
60
+ reason: "all parsers returned empty or threw",
61
+ };
62
+ }
63
+ //# sourceMappingURL=summary-pipeline.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * In-process regex-based structural summary parser.
3
+ *
4
+ * Fallback when the bundled ast-index binary is unavailable. Intentionally
5
+ * coarse: extracts imports, exports, and major top-level declarations per
6
+ * language using line-oriented regex. Never throws — worst case returns
7
+ * empty signals.
8
+ *
9
+ * Used by the hook summary pipeline (Phase 1 subtask 1.6).
10
+ */
11
+ import type { HookSummary, SignalKind, SignalLine } from "./summary-types.js";
12
+ export type { HookSummary, SignalKind, SignalLine };
13
+ export declare function parseRegexSummary(content: string, filePath: string): HookSummary;
14
+ //# sourceMappingURL=summary-regex.d.ts.map
@@ -0,0 +1,130 @@
1
+ /**
2
+ * In-process regex-based structural summary parser.
3
+ *
4
+ * Fallback when the bundled ast-index binary is unavailable. Intentionally
5
+ * coarse: extracts imports, exports, and major top-level declarations per
6
+ * language using line-oriented regex. Never throws — worst case returns
7
+ * empty signals.
8
+ *
9
+ * Used by the hook summary pipeline (Phase 1 subtask 1.6).
10
+ */
11
+ const MAX_TEXT_LEN = 140;
12
+ const EXTENSIONS = {
13
+ ts: tsJsPattern(),
14
+ tsx: tsJsPattern(),
15
+ js: tsJsPattern(),
16
+ jsx: tsJsPattern(),
17
+ mjs: tsJsPattern(),
18
+ cjs: tsJsPattern(),
19
+ py: pythonPattern(),
20
+ go: goPattern(),
21
+ rs: rustPattern(),
22
+ };
23
+ function tsJsPattern() {
24
+ return {
25
+ // Covers ES modules and CommonJS: `import ...`, `const x = require(...)`
26
+ import: /^\s*(import\s|const\s+\w+\s*=\s*require\s*\()/,
27
+ // export keyword, module.exports, exports.foo =
28
+ export: /^\s*(export\s|module\.exports\s*=|exports\.\w+\s*=)/,
29
+ // top-level declarations not prefixed with export — function/class/interface/type/enum
30
+ declaration: /^\s*(async\s+)?(function|class|interface|type|enum)\s+\w+/,
31
+ };
32
+ }
33
+ function pythonPattern() {
34
+ return {
35
+ import: /^\s*(import\s|from\s+\S+\s+import\s)/,
36
+ declaration: /^\s*(async\s+)?(def\s+\w+|class\s+\w+)/,
37
+ };
38
+ }
39
+ function goPattern() {
40
+ return {
41
+ import: /^\s*import\s/,
42
+ declaration: /^\s*(func\s|type\s+\w+\s+(struct|interface|func))/,
43
+ };
44
+ }
45
+ function rustPattern() {
46
+ return {
47
+ import: /^\s*use\s/,
48
+ export: /^\s*pub\s+(fn|struct|trait|enum|type|mod|const|static)\s/,
49
+ declaration: /^\s*(async\s+)?(fn|struct|trait|enum|type|mod)\s+\w+/,
50
+ };
51
+ }
52
+ /**
53
+ * Derive the lower-case extension for a file path.
54
+ * Returns an empty string if the path has no dot.
55
+ */
56
+ function extractExtension(filePath) {
57
+ const lastDot = filePath.lastIndexOf(".");
58
+ if (lastDot === -1 || lastDot === filePath.length - 1)
59
+ return "";
60
+ const ext = filePath.slice(lastDot + 1).toLowerCase();
61
+ // Guard against extensions containing path separators (e.g., "/foo.d/bar").
62
+ if (ext.includes("/") || ext.includes("\\"))
63
+ return "";
64
+ return ext;
65
+ }
66
+ function truncate(text) {
67
+ const trimmed = text.trim();
68
+ if (trimmed.length <= MAX_TEXT_LEN)
69
+ return trimmed;
70
+ return trimmed.slice(0, MAX_TEXT_LEN - 1) + "…";
71
+ }
72
+ /**
73
+ * Very rough token estimate mirroring the project-wide heuristic
74
+ * (see src/core/token-estimator.ts). Duplicated here to avoid a hard
75
+ * dependency in the hook hot-path.
76
+ */
77
+ function estimateTokens(text) {
78
+ if (text.length === 0)
79
+ return 0;
80
+ const charEstimate = Math.ceil(text.length / 4);
81
+ const whitespaceRatio = (text.match(/\s/g)?.length ?? 0) / text.length;
82
+ const adjustment = 1 - whitespaceRatio * 0.3;
83
+ return Math.ceil(charEstimate * adjustment);
84
+ }
85
+ export function parseRegexSummary(content, filePath) {
86
+ const language = extractExtension(filePath);
87
+ const totalLines = content.split("\n").length;
88
+ const estimatedTokens = estimateTokens(content);
89
+ const patterns = EXTENSIONS[language];
90
+ if (!patterns) {
91
+ return {
92
+ signals: [],
93
+ totalLines,
94
+ estimatedTokens,
95
+ language,
96
+ };
97
+ }
98
+ const lines = content.split("\n");
99
+ const signals = [];
100
+ for (let i = 0; i < lines.length; i++) {
101
+ const line = lines[i];
102
+ if (!line || line.trim().length === 0)
103
+ continue;
104
+ // Classification order: import → export → declaration. First match wins.
105
+ let kind = null;
106
+ if (patterns.import?.test(line)) {
107
+ kind = "import";
108
+ }
109
+ else if (patterns.export?.test(line)) {
110
+ kind = "export";
111
+ }
112
+ else if (patterns.declaration?.test(line)) {
113
+ kind = "declaration";
114
+ }
115
+ if (kind !== null) {
116
+ signals.push({
117
+ line: i + 1,
118
+ kind,
119
+ text: truncate(line),
120
+ });
121
+ }
122
+ }
123
+ return {
124
+ signals,
125
+ totalLines,
126
+ estimatedTokens,
127
+ language,
128
+ };
129
+ }
130
+ //# sourceMappingURL=summary-regex.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared types for the hook summary pipeline (Phase 1).
3
+ *
4
+ * Every parser in the fallback chain (ast-index subprocess, regex, head+tail)
5
+ * returns a HookSummary. The downstream formatter in handleHookRead renders
6
+ * this structure into the `permissionDecisionReason` body.
7
+ */
8
+ export type SignalKind = "import" | "export" | "declaration" | "raw";
9
+ export interface SignalLine {
10
+ /** 1-based line number in the original source. */
11
+ line: number;
12
+ kind: SignalKind;
13
+ /** Trimmed source line, truncated to a parser-defined character cap. */
14
+ text: string;
15
+ }
16
+ export interface HookSummary {
17
+ signals: SignalLine[];
18
+ totalLines: number;
19
+ estimatedTokens: number;
20
+ /** Lower-case extension without the dot; empty string if none. */
21
+ language: string;
22
+ /**
23
+ * Optional human-readable note explaining a non-standard summary
24
+ * (e.g. "parser unavailable — showing head+tail only"). Rendered by the
25
+ * formatter above the signals section.
26
+ */
27
+ note?: string;
28
+ }
29
+ //# sourceMappingURL=summary-types.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared types for the hook summary pipeline (Phase 1).
3
+ *
4
+ * Every parser in the fallback chain (ast-index subprocess, regex, head+tail)
5
+ * returns a HookSummary. The downstream formatter in handleHookRead renders
6
+ * this structure into the `permissionDecisionReason` body.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=summary-types.js.map
package/dist/index.d.ts CHANGED
@@ -1,9 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import type { HookMode } from "./types.js";
2
3
  export declare const CODE_EXTENSIONS: Set<string>;
3
4
  export declare function getVersion(): string;
4
5
  export declare function main(cliArgs?: string[]): Promise<void>;
5
6
  export declare function startServer(cliArgs?: string[]): Promise<void>;
6
- export declare function handleHookRead(filePathArg?: string, denyThreshold?: number): void;
7
+ export interface HookReadAdaptiveOptions {
8
+ adaptiveThreshold?: boolean;
9
+ adaptiveBudgetTokens?: number;
10
+ }
11
+ export declare function handleHookRead(filePathArg?: string, mode?: HookMode, denyThreshold?: number, projectRoot?: string, adaptive?: HookReadAdaptiveOptions): Promise<void>;
12
+ /**
13
+ * Pure implementation of the hook-read dispatch — returns the JSON payload
14
+ * to write to stdout, or null when we should pass-through (no output).
15
+ * Extracted for testability; the outer handleHookRead adds the process.exit
16
+ * wrapping.
17
+ */
18
+ export declare function runHookReadDispatch(filePathArg: string | undefined, mode: HookMode, denyThresholdArg?: number, projectRootArg?: string, adaptive?: HookReadAdaptiveOptions): Promise<string | null>;
7
19
  export declare function handleHookEdit(): void;
8
20
  export declare function handleInstallHook(projectRoot: string): Promise<void>;
9
21
  export declare function handleUninstallHook(projectRoot: string): Promise<void>;
@@ -11,8 +23,8 @@ export declare function handleInstallAstIndex(): Promise<void>;
11
23
  export declare function handleDoctor(): Promise<void>;
12
24
  export declare function handleInit(targetDir: string): Promise<void>;
13
25
  export declare function checkNpmLatest(packageName: string): Promise<string | null>;
14
- import type { TokenPilotConfig } from './types.js';
15
- import type { BinaryStatus } from './ast-index/binary-manager.js';
26
+ import type { TokenPilotConfig } from "./types.js";
27
+ import type { BinaryStatus } from "./ast-index/binary-manager.js";
16
28
  export declare function checkAllUpdates(config: TokenPilotConfig, binaryStatus: BinaryStatus): Promise<void>;
17
29
  export declare function printHelp(): void;
18
30
  //# sourceMappingURL=index.d.ts.map