pi-readseek 0.1.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.
Files changed (67) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +41 -0
  3. package/index.ts +142 -0
  4. package/package.json +73 -0
  5. package/prompts/edit.md +113 -0
  6. package/prompts/find.md +19 -0
  7. package/prompts/grep.md +26 -0
  8. package/prompts/ls.md +11 -0
  9. package/prompts/read.md +33 -0
  10. package/prompts/sg.md +25 -0
  11. package/prompts/write.md +46 -0
  12. package/src/binary-detect.ts +22 -0
  13. package/src/binary-resolution.ts +77 -0
  14. package/src/coerce-obvious-int.ts +39 -0
  15. package/src/context-application.ts +70 -0
  16. package/src/context-hygiene.ts +503 -0
  17. package/src/diff-data.ts +303 -0
  18. package/src/doom-loop-suggestions.ts +42 -0
  19. package/src/doom-loop.ts +216 -0
  20. package/src/edit-classify.ts +190 -0
  21. package/src/edit-diff.ts +354 -0
  22. package/src/edit-output.ts +107 -0
  23. package/src/edit-render-helpers.ts +141 -0
  24. package/src/edit-syntax-validate.ts +120 -0
  25. package/src/edit.ts +725 -0
  26. package/src/find-parsers.ts +89 -0
  27. package/src/find-stat.ts +36 -0
  28. package/src/find.ts +613 -0
  29. package/src/grep-budget.ts +79 -0
  30. package/src/grep-output.ts +197 -0
  31. package/src/grep-render-helpers.ts +77 -0
  32. package/src/grep-symbol-scope.ts +197 -0
  33. package/src/grep.ts +792 -0
  34. package/src/hashline.ts +747 -0
  35. package/src/ls.ts +293 -0
  36. package/src/map-cache.ts +152 -0
  37. package/src/path-utils.ts +24 -0
  38. package/src/pending-diff-preview.ts +269 -0
  39. package/src/persistent-map-cache.ts +251 -0
  40. package/src/read-local-bundle.ts +87 -0
  41. package/src/read-output.ts +212 -0
  42. package/src/read-render-helpers.ts +104 -0
  43. package/src/read.ts +748 -0
  44. package/src/readseek/constants.ts +21 -0
  45. package/src/readseek/enums.ts +38 -0
  46. package/src/readseek/formatter.ts +431 -0
  47. package/src/readseek/language-detect.ts +29 -0
  48. package/src/readseek/mapper.ts +69 -0
  49. package/src/readseek/parser-errors.ts +22 -0
  50. package/src/readseek/parser-loader.ts +83 -0
  51. package/src/readseek/symbol-error-format.ts +18 -0
  52. package/src/readseek/symbol-lookup.ts +294 -0
  53. package/src/readseek/types.ts +79 -0
  54. package/src/readseek-client.ts +343 -0
  55. package/src/readseek-error-codes.ts +54 -0
  56. package/src/readseek-settings.ts +287 -0
  57. package/src/readseek-value.ts +144 -0
  58. package/src/replace-symbol.ts +74 -0
  59. package/src/runtime.ts +3 -0
  60. package/src/sg-output.ts +88 -0
  61. package/src/sg.ts +308 -0
  62. package/src/syntax-validate-mode.ts +25 -0
  63. package/src/tool-prompt-metadata.ts +76 -0
  64. package/src/tui-diff-component.ts +86 -0
  65. package/src/tui-diff-renderer.ts +92 -0
  66. package/src/tui-render-utils.ts +129 -0
  67. package/src/write.ts +532 -0
@@ -0,0 +1,212 @@
1
+ import {
2
+ DEFAULT_MAX_BYTES,
3
+ DEFAULT_MAX_LINES,
4
+ formatSize,
5
+ truncateHead,
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { buildReadseekLines, renderReadseekLines, type ReadseekLine, type ReadseekWarning } from "./readseek-value.js";
8
+ import {
9
+ buildContextHygieneMetadata,
10
+ buildFileResource,
11
+ buildSymbolResource,
12
+ type ContextHygieneMetadata,
13
+ type ContextHygieneRehydrateDescriptor,
14
+ type ContextHygieneResource,
15
+ } from "./context-hygiene.js";
16
+
17
+ export interface ReadSymbolMetadata {
18
+ query: string;
19
+ name: string;
20
+ kind: string;
21
+ parentName?: string;
22
+ startLine: number;
23
+ endLine: number;
24
+ }
25
+
26
+ export interface ReadTruncationMetadata {
27
+ outputLines: number;
28
+ totalLines: number;
29
+ outputBytes: number;
30
+ totalBytes: number;
31
+ }
32
+
33
+ export interface ReadMapMetadata {
34
+ requested: boolean;
35
+ appended: boolean;
36
+ text?: string | null;
37
+ }
38
+
39
+ export interface ReadContinuationMetadata {
40
+ nextOffset: number;
41
+ }
42
+
43
+ export interface ReadBundleSupportItem {
44
+ symbol: ReadSymbolMetadata;
45
+ lines: string[];
46
+ }
47
+
48
+ export interface ReadBundleMetadata {
49
+ mode: "local";
50
+ applied: boolean;
51
+ localSupport: ReadBundleSupportItem[];
52
+ warnings?: ReadseekWarning[];
53
+ }
54
+
55
+ export interface ReadOutputInput {
56
+ path: string;
57
+ startLine: number;
58
+ endLine: number;
59
+ totalLines: number;
60
+ selectedLines?: string[];
61
+ lines?: ReadseekLine[];
62
+ warnings?: ReadseekWarning[];
63
+ truncation?: ReadTruncationMetadata | null;
64
+ continuation?: ReadContinuationMetadata | null;
65
+ symbol?: ReadSymbolMetadata | null;
66
+ map?: ReadMapMetadata;
67
+ bundle?: ReadBundleMetadata | null;
68
+ rehydrate?: ContextHygieneRehydrateDescriptor | null;
69
+ }
70
+
71
+ export interface ReadOutputResult {
72
+ text: string;
73
+ lines: ReadseekLine[];
74
+ readseekValue: {
75
+ tool: "read";
76
+ path: string;
77
+ range: {
78
+ startLine: number;
79
+ endLine: number;
80
+ totalLines: number;
81
+ };
82
+ warnings: ReadseekWarning[];
83
+ truncation: ReadTruncationMetadata | null;
84
+ symbol: ReadSymbolMetadata | null;
85
+ map: {
86
+ requested: boolean;
87
+ appended: boolean;
88
+ };
89
+ lines: ReadseekLine[];
90
+ bundle?: {
91
+ mode: "local";
92
+ applied: boolean;
93
+ localSupport: Array<{
94
+ name: string;
95
+ kind: string;
96
+ parentName?: string;
97
+ startLine: number;
98
+ endLine: number;
99
+ lineAnchors: string[];
100
+ }>;
101
+ warnings: ReadseekWarning[];
102
+ };
103
+ };
104
+ contextHygiene: ContextHygieneMetadata;
105
+ }
106
+
107
+ export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
108
+ const selectedLines = input.selectedLines ?? input.lines?.map((line) => line.raw) ?? [];
109
+ const lines = input.lines ?? buildReadseekLines(input.startLine, selectedLines);
110
+ const warnings = input.warnings ?? [];
111
+ const renderedLines = renderReadseekLines(lines);
112
+ const truncated = truncateHead(renderedLines, {
113
+ maxLines: DEFAULT_MAX_LINES,
114
+ maxBytes: DEFAULT_MAX_BYTES,
115
+ });
116
+
117
+ let text = input.truncation ? truncated.content : renderedLines;
118
+
119
+ if (input.truncation) {
120
+ text += `\n\n[Output truncated: showing ${input.truncation.outputLines} of ${input.totalLines} lines (${formatSize(input.truncation.outputBytes)} of ${formatSize(input.truncation.totalBytes)}). Use offset=${input.startLine + input.truncation.outputLines} to continue.]`;
121
+ } else if (input.continuation) {
122
+ text += `\n\n[Showing lines ${input.startLine}-${input.endLine} of ${input.totalLines}. Use offset=${input.continuation.nextOffset} to continue.]`;
123
+ }
124
+
125
+ if (input.bundle?.applied) {
126
+ const supportBlocks = input.bundle.localSupport.map((item) => {
127
+ const supportLines = buildReadseekLines(item.symbol.startLine, item.lines);
128
+ return renderReadseekLines(supportLines);
129
+ });
130
+
131
+ text = [
132
+ "## Requested symbol",
133
+ text,
134
+ "",
135
+ "## Local support",
136
+ ...supportBlocks,
137
+ ].join("\n");
138
+ }
139
+
140
+ if (input.map?.appended && input.map.text) {
141
+ text += `\n\n${input.map.text}`;
142
+ }
143
+
144
+ if (input.symbol) {
145
+ const parentInfo = input.symbol.parentName ? ` in ${input.symbol.parentName}` : "";
146
+ text = `[Symbol: ${input.symbol.name} (${input.symbol.kind})${parentInfo}, lines ${input.symbol.startLine}-${input.symbol.endLine} of ${input.totalLines}]\n\n${text}`;
147
+ }
148
+
149
+ if (warnings.length) {
150
+ text = `${warnings.map((warning) => warning.message).join("\n\n")}\n\n${text}`;
151
+ }
152
+
153
+ const readseekValue: ReadOutputResult["readseekValue"] = {
154
+ tool: "read",
155
+ path: input.path,
156
+ range: {
157
+ startLine: input.startLine,
158
+ endLine: input.endLine,
159
+ totalLines: input.totalLines,
160
+ },
161
+ warnings,
162
+ truncation: input.truncation ?? null,
163
+ symbol: input.symbol ?? null,
164
+ map: {
165
+ requested: input.map?.requested ?? false,
166
+ appended: input.map?.appended ?? false,
167
+ },
168
+ lines,
169
+ };
170
+
171
+ if (input.bundle) {
172
+ readseekValue.bundle = {
173
+ mode: input.bundle.mode,
174
+ applied: input.bundle.applied,
175
+ localSupport: input.bundle.localSupport.map((item) => {
176
+ const supportLines = buildReadseekLines(item.symbol.startLine, item.lines);
177
+ return {
178
+ name: item.symbol.name,
179
+ kind: item.symbol.kind,
180
+ parentName: item.symbol.parentName,
181
+ startLine: item.symbol.startLine,
182
+ endLine: item.symbol.endLine,
183
+ lineAnchors: supportLines.map((line) => line.anchor),
184
+ };
185
+ }),
186
+ warnings: input.bundle.warnings ?? [],
187
+ };
188
+ }
189
+
190
+ const contextHygieneResources: ContextHygieneResource[] = [buildFileResource(input.path)];
191
+ if (input.symbol) {
192
+ contextHygieneResources.push(buildSymbolResource(input.path, input.symbol.name, input.symbol.kind));
193
+ }
194
+ if (input.bundle?.applied) {
195
+ for (const support of input.bundle.localSupport) {
196
+ contextHygieneResources.push(buildSymbolResource(input.path, support.symbol.name, support.symbol.kind));
197
+ }
198
+ }
199
+ const contextHygiene = buildContextHygieneMetadata({
200
+ tool: "read",
201
+ classification: "read-context",
202
+ resources: contextHygieneResources,
203
+ rehydrate: input.rehydrate ?? undefined,
204
+ });
205
+
206
+ return {
207
+ text,
208
+ lines,
209
+ readseekValue,
210
+ contextHygiene,
211
+ };
212
+ }
@@ -0,0 +1,104 @@
1
+ import type { ReadseekWarning } from "./readseek-value.js";
2
+ export interface ReadCallTextResult {
3
+ path: string | null;
4
+ suffix: string | undefined;
5
+ }
6
+
7
+ export function formatReadCallText(
8
+ args: Record<string, unknown> | undefined,
9
+ ): ReadCallTextResult {
10
+ const rawPath = typeof args?.path === "string" ? args.path : null;
11
+
12
+ if (typeof args?.symbol === "string" && args.symbol) {
13
+ return { path: rawPath, suffix: `→ symbol ${args.symbol}` };
14
+ }
15
+
16
+ if (args?.map === true) {
17
+ return { path: rawPath, suffix: "+ map" };
18
+ }
19
+
20
+ if (typeof args?.offset === "number") {
21
+ const offset = args.offset as number;
22
+ const limit = typeof args?.limit === "number" ? (args.limit as number) : undefined;
23
+ if (offset < 1) {
24
+ return { path: rawPath, suffix: undefined };
25
+ }
26
+ if (limit !== undefined) {
27
+ if (limit < 1) {
28
+ return { path: rawPath, suffix: undefined };
29
+ }
30
+ return { path: rawPath, suffix: `lines ${offset}-${offset + limit - 1}` };
31
+ }
32
+ return { path: rawPath, suffix: `from line ${offset}` };
33
+ }
34
+
35
+ return { path: rawPath, suffix: undefined };
36
+ }
37
+
38
+
39
+ export interface ReadResultTextInput {
40
+ range: { startLine: number; endLine: number; totalLines: number };
41
+ truncation: { outputLines: number; totalLines: number; outputBytes: number; totalBytes: number } | null;
42
+ symbol: { query: string; name: string; kind: string; parentName?: string; startLine: number; endLine: number } | null;
43
+ map: { requested: boolean; appended: boolean };
44
+ warnings: ReadseekWarning[];
45
+ isError?: boolean;
46
+ errorText?: string;
47
+ }
48
+
49
+ export interface ReadResultTextOutput {
50
+ summary: string;
51
+ symbolBadge: string | undefined;
52
+ badges: string[];
53
+ truncated: boolean;
54
+ errorText: string | undefined;
55
+ }
56
+
57
+ export function formatReadResultText(input: ReadResultTextInput): ReadResultTextOutput {
58
+ const { range, truncation, symbol, map, warnings } = input;
59
+
60
+ // Error case
61
+ if (input.isError && input.errorText) {
62
+ return {
63
+ summary: "",
64
+ symbolBadge: undefined,
65
+ badges: [],
66
+ truncated: false,
67
+ errorText: input.errorText,
68
+ };
69
+ }
70
+
71
+ // Line count
72
+ const lineCount = range.endLine - range.startLine + 1;
73
+ const isFullFile = range.startLine === 1 && range.endLine === range.totalLines;
74
+ const isTruncated = !!truncation;
75
+
76
+ let summary: string;
77
+ if (isTruncated) {
78
+ summary = `\u2713 ${truncation!.outputLines} of ${truncation!.totalLines} lines (truncated)`;
79
+ } else if (isFullFile) {
80
+ summary = `\u2713 ${lineCount} lines`;
81
+ } else {
82
+ summary = `\u2713 ${lineCount} lines (${range.startLine}-${range.endLine} of ${range.totalLines})`;
83
+ }
84
+
85
+ // Symbol badge
86
+ const symbolBadge = symbol ? `${symbol.kind} ${symbol.name}` : undefined;
87
+
88
+ // Badges
89
+ const badges: string[] = [];
90
+ if (map.appended) badges.push("\ud83d\udcd0 map");
91
+ for (const w of warnings) {
92
+ if (w.code === "binary-content") badges.push("\u26a0 binary");
93
+ if (w.code === "bare-cr") badges.push("\u26a0 bare CR");
94
+ if (w.code === "fuzzy-symbol-match") badges.push("⚠ fuzzy match");
95
+ }
96
+
97
+ return {
98
+ summary,
99
+ symbolBadge,
100
+ badges,
101
+ truncated: isTruncated,
102
+ errorText: undefined,
103
+ };
104
+ }