pi-readseek 0.7.6 → 0.7.8

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 (60) hide show
  1. package/README.md +28 -46
  2. package/dist/index.js +7333 -0
  3. package/package.json +8 -7
  4. package/index.ts +0 -147
  5. package/src/binary-detect.ts +0 -22
  6. package/src/check.ts +0 -92
  7. package/src/coerce-obvious-int.ts +0 -41
  8. package/src/confusable-hyphens.ts +0 -6
  9. package/src/def.ts +0 -143
  10. package/src/diff-data.ts +0 -302
  11. package/src/edit-classify.ts +0 -58
  12. package/src/edit-diff.ts +0 -359
  13. package/src/edit-output.ts +0 -110
  14. package/src/edit-render-helpers.ts +0 -141
  15. package/src/edit-syntax-validate.ts +0 -65
  16. package/src/edit.ts +0 -686
  17. package/src/file-map.ts +0 -17
  18. package/src/fs-error.ts +0 -33
  19. package/src/grep-budget.ts +0 -35
  20. package/src/grep-output.ts +0 -181
  21. package/src/grep-render-helpers.ts +0 -77
  22. package/src/grep-symbol-scope.ts +0 -184
  23. package/src/grep.ts +0 -608
  24. package/src/hashline.ts +0 -736
  25. package/src/hover.ts +0 -140
  26. package/src/path-utils.ts +0 -24
  27. package/src/pending-diff-preview.ts +0 -269
  28. package/src/read-local-bundle.ts +0 -75
  29. package/src/read-output.ts +0 -186
  30. package/src/read-render-helpers.ts +0 -104
  31. package/src/read.ts +0 -661
  32. package/src/readseek/constants.ts +0 -17
  33. package/src/readseek/enums.ts +0 -38
  34. package/src/readseek/formatter.ts +0 -350
  35. package/src/readseek/symbol-error-format.ts +0 -18
  36. package/src/readseek/symbol-lookup.ts +0 -240
  37. package/src/readseek/symbol-tree.ts +0 -21
  38. package/src/readseek/types.ts +0 -49
  39. package/src/readseek-client.ts +0 -1250
  40. package/src/readseek-params.ts +0 -39
  41. package/src/readseek-settings.ts +0 -263
  42. package/src/readseek-value.ts +0 -184
  43. package/src/refs-output.ts +0 -47
  44. package/src/refs.ts +0 -161
  45. package/src/register-tool.ts +0 -29
  46. package/src/rename.ts +0 -182
  47. package/src/replace-symbol.ts +0 -77
  48. package/src/runtime.ts +0 -3
  49. package/src/search-output.ts +0 -50
  50. package/src/search.ts +0 -214
  51. package/src/session-anchors.ts +0 -34
  52. package/src/stat-search-path.ts +0 -29
  53. package/src/syntax-validate-mode.ts +0 -15
  54. package/src/tool-prompt-metadata.ts +0 -107
  55. package/src/tool-types.ts +0 -12
  56. package/src/tui-diff-component.ts +0 -108
  57. package/src/tui-diff-renderer.ts +0 -92
  58. package/src/tui-render-utils.ts +0 -286
  59. package/src/view.ts +0 -158
  60. package/src/write.ts +0 -357
@@ -1,39 +0,0 @@
1
- import { Type } from "@sinclair/typebox";
2
-
3
- import { buildToolErrorResult, type ToolErrorResult } from "./readseek-value.js";
4
-
5
- /** Optional search path shared by the readseek search tools. */
6
- export function searchPathParam() {
7
- return Type.Optional(Type.String({ description: "File or directory to search" }));
8
- }
9
-
10
- /** Optional language hint shared by the readseek search tools. */
11
- export function langParam() {
12
- return Type.Optional(Type.String({ description: "Language override when auto-detection is ambiguous" }));
13
- }
14
-
15
- /**
16
- * TypeBox fragment for the Git-scope parameters shared by every readseek
17
- * search tool. Returns fresh schema instances so each tool spreads its own.
18
- */
19
- export function readSeekGitSearchParams() {
20
- return {
21
- cached: Type.Optional(Type.Boolean({ description: "In a Git repository, search tracked/indexed files" })),
22
- others: Type.Optional(Type.Boolean({ description: "In a Git repository, search untracked files" })),
23
- ignored: Type.Optional(Type.Boolean({ description: "With others=true, include ignored untracked files" })),
24
- };
25
- }
26
-
27
- /**
28
- * Enforce that `ignored` is only meaningful alongside `others`. Returns an
29
- * error envelope tagged with {@link tool} when the rule is violated, else null.
30
- */
31
- export function validateIgnoredRequiresOthers(
32
- tool: string,
33
- params: { others?: boolean; ignored?: boolean },
34
- ): ToolErrorResult | null {
35
- if (params.ignored && !params.others) {
36
- return buildToolErrorResult(tool, "invalid-parameter", `${tool} parameter 'ignored' requires 'others'`);
37
- }
38
- return null;
39
- }
@@ -1,263 +0,0 @@
1
- import { readFileSync, statSync, type Stats } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- export type ReadSeekImageAnalysisMode = "on" | "off" | "auto";
6
- export type ReadSeekSyntaxValidationMode = "warn" | "block" | "off";
7
- interface ReadSeekGrepSettings {
8
- maxLines?: number;
9
- maxBytes?: number;
10
- }
11
-
12
- const READSEEK_TOOL_REPLACEMENTS = {
13
- read: "readSeek_read",
14
- edit: "readSeek_edit",
15
- grep: "readSeek_grep",
16
- write: "readSeek_write",
17
- } as const;
18
-
19
- type ReadSeekReplacementTool = keyof typeof READSEEK_TOOL_REPLACEMENTS;
20
-
21
- interface ReadSeekJsonSettings {
22
- replacedTools?: ReadSeekReplacementTool[];
23
- imageMode?: ReadSeekImageAnalysisMode;
24
- syntaxValidation?: ReadSeekSyntaxValidationMode;
25
- timeoutMs?: number;
26
- grep?: ReadSeekGrepSettings;
27
- }
28
-
29
- export interface ReadSeekSettingsWarning {
30
- source: string;
31
- message: string;
32
- path?: string;
33
- }
34
-
35
- export interface ReadSeekSettingsResult {
36
- settings: ReadSeekJsonSettings;
37
- warnings: ReadSeekSettingsWarning[];
38
- }
39
-
40
- const READSEEK_KEYS = ["replacedTools", "imageMode", "syntaxValidation", "timeoutMs", "grep"];
41
- const READSEEK_GREP_KEYS = ["maxLines", "maxBytes"];
42
-
43
- function globalSettingsPath(): string {
44
- return join(homedir(), ".pi/agent/settings.json");
45
- }
46
-
47
- function projectSettingsPath(): string {
48
- return join(process.cwd(), ".pi/settings.json");
49
- }
50
-
51
- function isRecord(value: unknown): value is Record<string, unknown> {
52
- return typeof value === "object" && value !== null && !Array.isArray(value);
53
- }
54
-
55
- function invalid(source: string, path: string): ReadSeekSettingsWarning {
56
- return { source, path, message: `Invalid readseek setting at ${path}` };
57
- }
58
-
59
- function warnUnknownKeys(
60
- raw: Record<string, unknown>,
61
- known: string[],
62
- prefix: string,
63
- source: string,
64
- warnings: ReadSeekSettingsWarning[],
65
- ): void {
66
- for (const key of Object.keys(raw)) {
67
- if (known.includes(key)) continue;
68
- const path = `${prefix}.${key}`;
69
- warnings.push({ source, path, message: `Unknown readseek setting at ${path}` });
70
- }
71
- }
72
-
73
- function readPositive(
74
- raw: Record<string, unknown>,
75
- key: string,
76
- path: string,
77
- source: string,
78
- warnings: ReadSeekSettingsWarning[],
79
- ): number | undefined {
80
- if (!(key in raw)) return undefined;
81
- const val = raw[key];
82
- if (typeof val === "number" && Number.isSafeInteger(val) && val > 0) return val;
83
- warnings.push(invalid(source, path));
84
- return undefined;
85
- }
86
-
87
- function readEnum<T extends string>(
88
- raw: Record<string, unknown>,
89
- key: string,
90
- values: readonly T[],
91
- path: string,
92
- source: string,
93
- warnings: ReadSeekSettingsWarning[],
94
- ): T | undefined {
95
- if (!(key in raw)) return undefined;
96
- const val = raw[key];
97
- if (typeof val === "string" && (values as readonly string[]).includes(val)) return val as T;
98
- warnings.push(invalid(source, path));
99
- return undefined;
100
- }
101
-
102
- function readImageMode(
103
- raw: Record<string, unknown>,
104
- source: string,
105
- warnings: ReadSeekSettingsWarning[],
106
- ): ReadSeekImageAnalysisMode | undefined {
107
- if (!("imageMode" in raw)) return undefined;
108
- const val = raw.imageMode;
109
- if (val === "on" || val === "off" || val === "auto") return val;
110
- warnings.push(invalid(source, "readseek.imageMode"));
111
- return undefined;
112
- }
113
-
114
- function readReplacedTools(
115
- raw: Record<string, unknown>,
116
- source: string,
117
- warnings: ReadSeekSettingsWarning[],
118
- ): ReadSeekReplacementTool[] | undefined {
119
- if (!("replacedTools" in raw)) return undefined;
120
- const value = raw.replacedTools;
121
- if (!Array.isArray(value)) {
122
- warnings.push(invalid(source, "readseek.replacedTools"));
123
- return undefined;
124
- }
125
-
126
- const tools: ReadSeekReplacementTool[] = [];
127
- value.forEach((tool, index) => {
128
- if (typeof tool === "string" && Object.hasOwn(READSEEK_TOOL_REPLACEMENTS, tool)) {
129
- tools.push(tool as ReadSeekReplacementTool);
130
- } else {
131
- warnings.push(invalid(source, `readseek.replacedTools[${index}]`));
132
- }
133
- });
134
- return tools;
135
- }
136
-
137
- function validateSettings(raw: unknown, source: string): ReadSeekSettingsResult {
138
- const settings: ReadSeekJsonSettings = {};
139
- const warnings: ReadSeekSettingsWarning[] = [];
140
- if (!isRecord(raw)) {
141
- warnings.push({ source, message: "Invalid readseek settings: expected a JSON object" });
142
- return { settings, warnings };
143
- }
144
- if (!("readseek" in raw)) {
145
- const misplaced = READSEEK_KEYS.filter((key) => key in raw);
146
- if (misplaced.length > 0) {
147
- warnings.push({
148
- source,
149
- path: misplaced[0],
150
- message: `Readseek setting at top level: move ${misplaced.join(", ")} under "readseek"`,
151
- });
152
- }
153
- return { settings, warnings };
154
- }
155
- if (!isRecord(raw.readseek)) {
156
- warnings.push(invalid(source, "readseek"));
157
- return { settings, warnings };
158
- }
159
-
160
- const section = raw.readseek;
161
- warnUnknownKeys(section, READSEEK_KEYS, "readseek", source, warnings);
162
-
163
- const replacedTools = readReplacedTools(section, source, warnings);
164
- if (replacedTools !== undefined) settings.replacedTools = replacedTools;
165
-
166
- const imageMode = readImageMode(section, source, warnings);
167
- if (imageMode !== undefined) settings.imageMode = imageMode;
168
-
169
- const syntaxValidation = readEnum(
170
- section,
171
- "syntaxValidation",
172
- ["warn", "block", "off"] as const,
173
- "readseek.syntaxValidation",
174
- source,
175
- warnings,
176
- );
177
- if (syntaxValidation !== undefined) settings.syntaxValidation = syntaxValidation;
178
-
179
- const timeoutMs = readPositive(section, "timeoutMs", "readseek.timeoutMs", source, warnings);
180
- if (timeoutMs !== undefined) settings.timeoutMs = timeoutMs;
181
-
182
- if ("grep" in section) {
183
- if (isRecord(section.grep)) {
184
- warnUnknownKeys(section.grep, READSEEK_GREP_KEYS, "readseek.grep", source, warnings);
185
- const grep: ReadSeekGrepSettings = {};
186
- const maxLines = readPositive(section.grep, "maxLines", "readseek.grep.maxLines", source, warnings);
187
- if (maxLines !== undefined) grep.maxLines = maxLines;
188
- const maxBytes = readPositive(section.grep, "maxBytes", "readseek.grep.maxBytes", source, warnings);
189
- if (maxBytes !== undefined) grep.maxBytes = maxBytes;
190
- if (Object.keys(grep).length > 0) settings.grep = grep;
191
- } else {
192
- warnings.push(invalid(source, "readseek.grep"));
193
- }
194
- }
195
-
196
- return { settings, warnings };
197
- }
198
-
199
- interface SettingsFileCacheEntry {
200
- mtimeMs: number;
201
- size: number;
202
- result: ReadSeekSettingsResult;
203
- }
204
-
205
- const settingsFileCache = new Map<string, SettingsFileCacheEntry>();
206
-
207
- function statFileOrNull(path: string): Stats | null {
208
- try {
209
- return statSync(path);
210
- } catch {
211
- return null;
212
- }
213
- }
214
-
215
- function readSettingsFile(path: string): ReadSeekSettingsResult {
216
- const stats = statFileOrNull(path);
217
- if (!stats) {
218
- settingsFileCache.delete(path);
219
- return { settings: {}, warnings: [] };
220
- }
221
-
222
- const cached = settingsFileCache.get(path);
223
- if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size) return cached.result;
224
-
225
- let result: ReadSeekSettingsResult;
226
- try {
227
- const text = readFileSync(path, "utf8");
228
- result = validateSettings(JSON.parse(text) as unknown, path);
229
- } catch (error) {
230
- const message = error instanceof Error ? error.message : String(error);
231
- result = { settings: {}, warnings: [{ source: path, message: `Invalid JSON: ${message}` }] };
232
- }
233
- settingsFileCache.set(path, { mtimeMs: stats.mtimeMs, size: stats.size, result });
234
- return result;
235
- }
236
-
237
- function mergeSettings(base: ReadSeekJsonSettings, override: ReadSeekJsonSettings): ReadSeekJsonSettings {
238
- const settings: ReadSeekJsonSettings = { ...base, ...override };
239
- const grep = { ...(base.grep ?? {}), ...(override.grep ?? {}) };
240
- if (Object.keys(grep).length > 0) settings.grep = grep;
241
- return settings;
242
- }
243
-
244
- export function resolveReadSeekJsonSettings(): ReadSeekSettingsResult {
245
- const globalResult = readSettingsFile(globalSettingsPath());
246
- const projectResult = readSettingsFile(projectSettingsPath());
247
- return {
248
- settings: mergeSettings(globalResult.settings, projectResult.settings),
249
- warnings: [...globalResult.warnings, ...projectResult.warnings],
250
- };
251
- }
252
-
253
- export function resolveReadSeekImageMode(): ReadSeekImageAnalysisMode {
254
- return resolveReadSeekJsonSettings().settings.imageMode ?? "auto";
255
- }
256
-
257
- export function resolveReadSeekSyntaxValidation(): ReadSeekSyntaxValidationMode | undefined {
258
- return resolveReadSeekJsonSettings().settings.syntaxValidation;
259
- }
260
-
261
- export function resolveReadSeekTimeoutMs(): number | undefined {
262
- return resolveReadSeekJsonSettings().settings.timeoutMs;
263
- }
@@ -1,184 +0,0 @@
1
- import { computeLineHash, escapeControlCharsForDisplay } from "./hashline.js";
2
- import type { DiffData } from "./diff-data.js";
3
-
4
- export interface ReadSeekLine {
5
- line: number;
6
- hash: string;
7
- anchor: string;
8
- raw: string;
9
- display: string;
10
- }
11
-
12
- export interface ReadSeekWarningSymbol {
13
- name: string;
14
- kind: string;
15
- startLine: number;
16
- endLine: number;
17
- parentName?: string;
18
- }
19
- export interface ReadSeekWarning {
20
- code: string;
21
- message: string;
22
- tier?: "camelCase" | "substring";
23
- symbol?: ReadSeekWarningSymbol;
24
- otherCandidates?: ReadSeekWarningSymbol[];
25
- }
26
-
27
- export interface ReadSeekError {
28
- code: string;
29
- message: string;
30
- hint?: string;
31
- details?: unknown;
32
- }
33
-
34
- export interface ReadSeekRange {
35
- startLine: number;
36
- endLine: number;
37
- totalLines?: number;
38
- }
39
-
40
- export interface SemanticSummary {
41
- classification: "no-op" | "whitespace-only" | "semantic" | "mixed";
42
- }
43
- export interface ReadSeekEditResult {
44
- tool: "edit";
45
- ok: boolean;
46
- path: string;
47
- summary: string;
48
- diff: string;
49
- diffData?: DiffData;
50
- firstChangedLine: number | undefined;
51
- warnings: string[];
52
- noopEdits: unknown[];
53
- semanticSummary?: SemanticSummary;
54
- }
55
-
56
- /**
57
- * Build a {@link ReadSeekLine} from an already-known hash. Use this when the
58
- * hash is supplied by readseek (search, refs) rather than computed from `raw`;
59
- * {@link buildReadSeekLine} delegates here after hashing.
60
- */
61
- export function buildReadSeekLineWithHash(line: number, hash: string, raw: string): ReadSeekLine {
62
- return {
63
- line,
64
- hash,
65
- anchor: `${line}:${hash}`,
66
- raw,
67
- display: escapeControlCharsForDisplay(raw),
68
- };
69
- }
70
-
71
- export function buildReadSeekLine(line: number, raw: string): ReadSeekLine {
72
- return buildReadSeekLineWithHash(line, computeLineHash(raw), raw);
73
- }
74
-
75
- export function buildReadSeekLines(startLine: number, rawLines: string[]): ReadSeekLine[] {
76
- return rawLines.map((raw, index) => buildReadSeekLine(startLine + index, raw));
77
- }
78
-
79
- export function renderReadSeekLines(lines: ReadSeekLine[]): string {
80
- return lines.map((line) => `${line.anchor}|${line.display}`).join("\n");
81
- }
82
-
83
- /**
84
- * Render the anchored-file block format shared by the `search` and `refs`
85
- * tools: a `--- <displayPath> ---` header per file followed by one
86
- * `>><anchor>|<display>` line per match. {@link lineSuffix} appends tool
87
- * specific trailers (e.g. refs' enclosing-symbol annotation).
88
- */
89
- export function formatAnchoredFileBlocks<T extends ReadSeekLine>(
90
- files: Array<{ displayPath: string; lines: T[] }>,
91
- lineSuffix?: (line: T) => string,
92
- ): string {
93
- const blocks: string[] = [];
94
- for (const file of files) {
95
- blocks.push(`--- ${file.displayPath} ---`);
96
- for (const line of file.lines) {
97
- blocks.push(`>>${line.anchor}|${line.display}${lineSuffix?.(line) ?? ""}`);
98
- }
99
- }
100
- return blocks.join("\n");
101
- }
102
-
103
- export function buildReadSeekWarning(
104
- code: string,
105
- message: string,
106
- metadata: Omit<ReadSeekWarning, "code" | "message"> = {},
107
- ): ReadSeekWarning {
108
- return { code, message, ...metadata };
109
- }
110
-
111
- export function buildReadSeekError(
112
- code: string,
113
- message: string,
114
- hint?: string,
115
- details?: unknown,
116
- ): ReadSeekError {
117
- return {
118
- code,
119
- message,
120
- ...(hint !== undefined ? { hint } : {}),
121
- ...(details !== undefined ? { details } : {}),
122
- };
123
- }
124
-
125
- export interface ToolErrorResult {
126
- content: [{ type: "text"; text: string }];
127
- isError: true;
128
- details: { readSeekValue: Record<string, unknown> };
129
- }
130
-
131
- /**
132
- * Build the standard failure envelope shared by every read-family tool: a text
133
- * content block plus a `readSeekValue` carrying `ok: false` and a
134
- * {@link buildReadSeekError} payload.
135
- *
136
- * `path` is included only when provided, and `extra` is merged into
137
- * `readSeekValue` so callers can attach tool-specific fields (e.g. write's
138
- * `lines`/`warnings`).
139
- */
140
- export function buildToolErrorResult(
141
- tool: string,
142
- code: string,
143
- message: string,
144
- opts: { path?: string; hint?: string; details?: unknown; extra?: Record<string, unknown> } = {},
145
- ): ToolErrorResult {
146
- return {
147
- content: [{ type: "text", text: message }],
148
- isError: true,
149
- details: {
150
- readSeekValue: {
151
- tool,
152
- ...(opts.extra ?? {}),
153
- ok: false,
154
- ...(opts.path !== undefined ? { path: opts.path } : {}),
155
- error: buildReadSeekError(code, message, opts.hint, opts.details),
156
- },
157
- },
158
- };
159
- }
160
-
161
- export function buildReadSeekEditResult(input: {
162
- ok?: boolean;
163
- path: string;
164
- summary: string;
165
- diff: string;
166
- diffData?: DiffData;
167
- firstChangedLine: number | undefined;
168
- warnings: string[];
169
- noopEdits: unknown[];
170
- semanticSummary?: SemanticSummary;
171
- }): ReadSeekEditResult {
172
- return {
173
- tool: "edit",
174
- ok: input.ok ?? true,
175
- path: input.path,
176
- summary: input.summary,
177
- diff: input.diff,
178
- ...(input.diffData ? { diffData: input.diffData } : {}),
179
- firstChangedLine: input.firstChangedLine,
180
- warnings: [...input.warnings],
181
- noopEdits: [...input.noopEdits],
182
- ...(input.semanticSummary ? { semanticSummary: input.semanticSummary } : {}),
183
- };
184
- }
@@ -1,47 +0,0 @@
1
- import { formatAnchoredFileBlocks, type ReadSeekLine } from "./readseek-value.js";
2
-
3
- export interface RefsOutputLine extends ReadSeekLine {
4
- enclosingSymbol?: string;
5
- }
6
-
7
- export interface RefsOutputFile {
8
- displayPath: string;
9
- path: string;
10
- lines: RefsOutputLine[];
11
- }
12
-
13
- interface BuildRefsOutputInput {
14
- name: string;
15
- files: RefsOutputFile[];
16
- }
17
-
18
- interface RefsOutputResult {
19
- text: string;
20
- readSeekValue: {
21
- tool: "refs";
22
- files: Array<{
23
- path: string;
24
- lines: RefsOutputLine[];
25
- }>;
26
- };
27
- }
28
-
29
- export function buildRefsOutput(input: BuildRefsOutputInput): RefsOutputResult {
30
- if (input.files.length === 0) {
31
- return {
32
- text: `No references found for: ${input.name}`,
33
- readSeekValue: { tool: "refs", files: [] },
34
- };
35
- }
36
-
37
- return {
38
- text: formatAnchoredFileBlocks(input.files, (line) => (line.enclosingSymbol ? ` (in ${line.enclosingSymbol})` : "")),
39
- readSeekValue: {
40
- tool: "refs",
41
- files: input.files.map((file) => ({
42
- path: file.path,
43
- lines: file.lines.map((line) => ({ ...line })),
44
- })),
45
- },
46
- };
47
- }
package/src/refs.ts DELETED
@@ -1,161 +0,0 @@
1
- import type { ExtensionAPI, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
2
- import { Type } from "@sinclair/typebox";
3
- import path from "node:path";
4
- import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
5
- import { statSearchPathOrError } from "./stat-search-path.js";
6
- import { buildReadSeekLineWithHash, buildToolErrorResult } from "./readseek-value.js";
7
- import { resolveToCwd } from "./path-utils.js";
8
- import { classifyReadSeekFailure, readSeekRefs, type ReadSeekReference } from "./readseek-client.js";
9
- import { buildRefsOutput, type RefsOutputFile, type RefsOutputLine } from "./refs-output.js";
10
- import type { FileAnchoredCallback } from "./tool-types.js";
11
- import { langParam, readSeekGitSearchParams, searchPathParam, validateIgnoredRequiresOthers } from "./readseek-params.js";
12
- import { registerReadSeekTool } from "./register-tool.js";
13
-
14
- import { renderAnchoredFilesResult, renderReadSeekSearchCall } from "./tui-render-utils.js";
15
-
16
- type RefsParams = {
17
- name: string;
18
- path?: string;
19
- lang?: string;
20
- scope?: boolean;
21
- line?: number;
22
- column?: number;
23
- cached?: boolean;
24
- others?: boolean;
25
- ignored?: boolean;
26
- };
27
-
28
- const REFS_PROMPT_METADATA = defineToolPromptMetadata({
29
- promptUrl: new URL("../prompts/refs.md", import.meta.url),
30
- promptSnippet: "Find usages of an identifier or cursor binding",
31
- });
32
-
33
- interface RefsToolOptions {
34
- onFileAnchored?: FileAnchoredCallback;
35
- }
36
-
37
- /**
38
- * Inputs for executing the references tool without registering it.
39
- */
40
- export interface ExecuteRefsOptions {
41
- params: unknown;
42
- signal: AbortSignal | undefined;
43
- cwd: string;
44
- onFileAnchored?: FileAnchoredCallback;
45
- }
46
-
47
- function refsLine(reference: ReadSeekReference): RefsOutputLine {
48
- return {
49
- ...buildReadSeekLineWithHash(reference.line, reference.line_hash, reference.text),
50
- enclosingSymbol: reference.enclosingSymbol,
51
- };
52
- }
53
-
54
- function groupReferences(references: ReadSeekReference[], cwd: string): RefsOutputFile[] {
55
- const files = new Map<string, RefsOutputFile>();
56
- const seen = new Set<string>();
57
- for (const reference of references) {
58
- const abs = path.isAbsolute(reference.file) ? reference.file : path.resolve(cwd, reference.file);
59
- const dedupeKey = `${abs}:${reference.line}:${reference.column}`;
60
- if (seen.has(dedupeKey)) continue;
61
- seen.add(dedupeKey);
62
- let file = files.get(abs);
63
- if (!file) {
64
- file = { displayPath: path.relative(cwd, abs) || abs, path: abs, lines: [] };
65
- files.set(abs, file);
66
- }
67
- file.lines.push(refsLine(reference));
68
- }
69
- return [...files.values()];
70
- }
71
-
72
- function isReadSeekCursorValidationFailure(message: string): boolean {
73
- return (
74
- /line and column must be greater than zero/i.test(message) ||
75
- /line \d+ not found/i.test(message) ||
76
- /column \d+ exceeds maximum column \d+ for line \d+/i.test(message)
77
- );
78
- }
79
-
80
- /**
81
- * Executes identifier reference lookup and returns readseek-anchored matches.
82
- */
83
- export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
84
- const { params, signal, cwd, onFileAnchored } = opts;
85
- const p = params as RefsParams;
86
- const ignoredError = validateIgnoredRequiresOthers("refs", p);
87
- if (ignoredError) return ignoredError;
88
- if (p.scope && p.line === undefined) {
89
- return buildToolErrorResult("refs", "invalid-parameter", "refs parameter 'scope' requires 'line'");
90
- }
91
- const searchPath = resolveToCwd(p.path ?? ".", cwd);
92
-
93
- const statResult = await statSearchPathOrError("refs", p.path, searchPath);
94
- if (!statResult.ok) return statResult.error;
95
-
96
- try {
97
- const references = await readSeekRefs(searchPath, p.name, {
98
- scope: p.scope,
99
- line: p.line,
100
- column: p.column,
101
- language: p.lang,
102
- cached: p.cached,
103
- others: p.others,
104
- ignored: p.ignored,
105
- signal,
106
- });
107
- const files = groupReferences(references, cwd);
108
- const builtOutput = buildRefsOutput({ name: p.name, files });
109
- for (const file of files) {
110
- onFileAnchored?.(file.path);
111
- }
112
- return {
113
- content: [{ type: "text", text: builtOutput.text }],
114
- details: { readSeekValue: builtOutput.readSeekValue },
115
- };
116
- } catch (err: any) {
117
- const failure = classifyReadSeekFailure(err);
118
- if (p.scope && isReadSeekCursorValidationFailure(failure.message)) {
119
- return buildToolErrorResult("refs", "invalid-parameter", failure.message);
120
- }
121
- return buildToolErrorResult("refs", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
122
- }
123
- }
124
-
125
- export function registerRefsTool(pi: ExtensionAPI, options: RefsToolOptions = {}) {
126
- const tool = registerReadSeekTool(pi, {
127
- name: "readSeek_refs",
128
- label: "References",
129
- description: REFS_PROMPT_METADATA.description,
130
- promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
131
- promptGuidelines: REFS_PROMPT_METADATA.promptGuidelines,
132
- parameters: Type.Object({
133
- name: Type.String({ description: "Identifier to find references for" }),
134
- path: searchPathParam(),
135
- lang: langParam(),
136
- scope: Type.Optional(Type.Boolean({ description: "Restrict results to the binding at the cursor" })),
137
- line: Type.Optional(Type.Number({ description: "Cursor line (1-indexed), required with scope" })),
138
- column: Type.Optional(Type.Number({ description: "Cursor byte column (1-indexed) for disambiguation" })),
139
- ...readSeekGitSearchParams(),
140
- }),
141
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
142
- return executeRefs({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
143
- },
144
- renderCall(args: any, theme: any, ...rest: any[]) {
145
- return renderReadSeekSearchCall(args, theme, rest, {
146
- label: "refs",
147
- accent: args.name,
148
- flags: [args.scope && "scope", args.cached && "cached", args.others && "others", args.ignored && "ignored"],
149
- });
150
- },
151
- renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
152
- return renderAnchoredFilesResult(result, options, theme, rest, {
153
- pendingLabel: "pending refs",
154
- emptyLabel: "no references",
155
- unitSingular: "reference",
156
- unitPlural: "references",
157
- });
158
- },
159
- });
160
- return tool;
161
- }