pi-readseek 0.3.1 → 0.3.3

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/src/sg.ts CHANGED
@@ -4,12 +4,12 @@ import { Type } from "@sinclair/typebox";
4
4
  import path from "node:path";
5
5
  import { stat as fsStat } from "node:fs/promises";
6
6
  import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
7
- import { ensureHashInit } from "./hashline.js";
8
- import { buildReadseekError, buildReadseekLine, type ReadseekLine } from "./readseek-value.js";
7
+ import { escapeControlCharsForDisplay } from "./hashline.js";
8
+ import { buildReadseekError, type ReadseekLine } from "./readseek-value.js";
9
9
  import { resolveToCwd } from "./path-utils.js";
10
- import { isReadseekAvailable, readseekSearch, type ReadseekSearchFileOutput } from "./readseek-client.js";
10
+ import { isReadseekAvailable, readseekSearch, type ReadseekHashline, type ReadseekSearchFileOutput } from "./readseek-client.js";
11
11
  import { buildSgOutput } from "./sg-output.js";
12
- import { buildSearchRehydrateDescriptor } from "./context-hygiene.js";
12
+
13
13
  import { clampLineToWidth, clampLinesToWidth, isRendererExpanded, renderToolLabel, summaryLine } from "./tui-render-utils.js";
14
14
 
15
15
  type SgParams = { pattern: string; lang?: string; path?: string; cached?: boolean; others?: boolean; ignored?: boolean };
@@ -62,8 +62,14 @@ interface SgToolOptions {
62
62
  onFileAnchored?: (absolutePath: string) => void;
63
63
  }
64
64
 
65
- function readseekLineFromSearch(line: { line: number; text: string }): ReadseekLine {
66
- return buildReadseekLine(line.line, line.text);
65
+ function readseekLineFromSearch(line: ReadseekHashline): ReadseekLine {
66
+ return {
67
+ line: line.line,
68
+ hash: line.hash,
69
+ anchor: `${line.line}:${line.hash}`,
70
+ raw: line.text,
71
+ display: escapeControlCharsForDisplay(line.text),
72
+ };
67
73
  }
68
74
 
69
75
  function linesFromSearchResult(result: ReadseekSearchFileOutput, ranges: SgRange[]): ReadseekLine[] {
@@ -119,7 +125,6 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
119
125
  }),
120
126
  ptc: toolConfig,
121
127
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
122
- await ensureHashInit();
123
128
  const p = params as SgParams;
124
129
  if (p.ignored && !p.others) {
125
130
  const message = "Error: search parameter 'ignored' requires 'others'";
@@ -135,15 +140,6 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
135
140
  },
136
141
  };
137
142
  }
138
- const rehydrate = buildSearchRehydrateDescriptor({
139
- pattern: p.pattern,
140
- lang: p.lang,
141
- path: p.path,
142
- cached: p.cached,
143
- others: p.others,
144
- ignored: p.ignored,
145
- });
146
-
147
143
  const searchPath = resolveToCwd(p.path ?? ".", ctx.cwd);
148
144
  let searchPathIsFile = false;
149
145
 
@@ -206,12 +202,11 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
206
202
  signal,
207
203
  });
208
204
  if (results.length === 0) {
209
- const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [], rehydrate });
205
+ const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [] });
210
206
  return {
211
207
  content: [{ type: "text", text: emptyOutput.text }],
212
208
  details: {
213
209
  readseekValue: emptyOutput.readseekValue,
214
- contextHygiene: emptyOutput.contextHygiene,
215
210
  },
216
211
  };
217
212
  }
@@ -240,12 +235,11 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
240
235
  }
241
236
 
242
237
  if (readseekFiles.length === 0) {
243
- const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [], rehydrate });
238
+ const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [] });
244
239
  return {
245
240
  content: [{ type: "text", text: emptyOutput.text }],
246
241
  details: {
247
242
  readseekValue: emptyOutput.readseekValue,
248
- contextHygiene: emptyOutput.contextHygiene,
249
243
  },
250
244
  };
251
245
  }
@@ -253,7 +247,6 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
253
247
  const builtOutput = buildSgOutput({
254
248
  pattern: p.pattern,
255
249
  files: readseekFiles,
256
- rehydrate,
257
250
  });
258
251
  for (const readseekFile of readseekFiles) {
259
252
  options.onFileAnchored?.(readseekFile.path);
@@ -262,7 +255,6 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
262
255
  content: [{ type: "text", text: builtOutput.text }],
263
256
  details: {
264
257
  readseekValue: builtOutput.readseekValue,
265
- contextHygiene: builtOutput.contextHygiene,
266
258
  },
267
259
  };
268
260
  } catch (err: any) {
package/src/write.ts CHANGED
@@ -9,7 +9,7 @@ import { buildReadseekError, buildReadseekLine, buildReadseekWarning, type Reads
9
9
  import { looksLikeBinary } from "./binary-detect.js";
10
10
  import { getOrGenerateMap } from "./map-cache.js";
11
11
  import { formatFileMapWithBudget } from "./readseek/formatter.js";
12
- import { buildContextHygieneMetadata, buildFileResource, type ContextHygieneMetadata } from "./context-hygiene.js";
12
+
13
13
  import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
14
14
  import { buildPendingWritePreviewData, buildWritePreviewKey, resolvePendingDiffPreview, type PendingDiffPreviewResult } from "./pending-diff-preview.js";
15
15
  import { generateCompactOrFullDiff, normalizeToLF, hasBareCarriageReturn } from "./edit-diff.js";
@@ -91,7 +91,6 @@ export interface WriteResult extends WriteDiffFields {
91
91
  diffData?: DiffData;
92
92
  map?: { appended: boolean };
93
93
  };
94
- contextHygiene: ContextHygieneMetadata;
95
94
  }
96
95
 
97
96
  function readPreviousTextForDiff(filePath: string): string {
@@ -185,11 +184,6 @@ export async function executeWrite(opts: {
185
184
  const { path: filePath, content, map: requestMap, cwd } = opts;
186
185
  const warnings: string[] = [];
187
186
  const readseekWarnings: ReadseekWarning[] = [];
188
- const contextHygiene = buildContextHygieneMetadata({
189
- tool: "write",
190
- classification: "mutation",
191
- resources: [buildFileResource(filePath)],
192
- });
193
187
 
194
188
  if (hasBareCarriageReturn(content)) {
195
189
  const message = "File content contains bare CR (\\r) line endings; write refuses to emit anchors that read/edit would normalize differently.";
@@ -204,7 +198,6 @@ export async function executeWrite(opts: {
204
198
  lines: [],
205
199
  warnings: readseekWarnings,
206
200
  },
207
- contextHygiene,
208
201
  };
209
202
  }
210
203
  const previousContent = readPreviousTextForDiff(filePath);
@@ -239,7 +232,6 @@ export async function executeWrite(opts: {
239
232
  lines: [],
240
233
  warnings: readseekWarnings,
241
234
  },
242
- contextHygiene,
243
235
  };
244
236
  }
245
237
 
@@ -309,7 +301,6 @@ export async function executeWrite(opts: {
309
301
  diffData,
310
302
  ...(requestMap !== undefined ? { map: { appended: mapAppended } } : {}),
311
303
  },
312
- contextHygiene,
313
304
  };
314
305
  }
315
306
 
@@ -391,7 +382,6 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
391
382
  error: buildReadseekError("binary-content", binaryWarning.message),
392
383
  },
393
384
  warnings: result.warnings,
394
- contextHygiene: result.contextHygiene,
395
385
  },
396
386
  };
397
387
  }
@@ -408,7 +398,6 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
408
398
  error: buildReadseekError("bare-cr", bareCrWarning.message),
409
399
  },
410
400
  warnings: result.warnings,
411
- contextHygiene: result.contextHygiene,
412
401
  },
413
402
  };
414
403
  }
@@ -421,7 +410,6 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
421
410
  ...(result.writeState ? { writeState: result.writeState } : {}),
422
411
  readseekValue: result.readseekValue,
423
412
  warnings: result.warnings,
424
- contextHygiene: result.contextHygiene,
425
413
  },
426
414
  };
427
415
  });
@@ -1,70 +0,0 @@
1
- import {
2
- renderStaleContextPlaceholder,
3
- type ContextHygieneReport,
4
- type ContextHygieneStaleRecord,
5
- } from "./context-hygiene.js";
6
-
7
- type ContextToolResultMessage = {
8
- role?: unknown;
9
- toolCallId?: unknown;
10
- toolName?: unknown;
11
- content?: unknown;
12
- details?: unknown;
13
- [key: string]: unknown;
14
- };
15
-
16
- function isRecord(value: unknown): value is Record<string, unknown> {
17
- return !!value && typeof value === "object";
18
- }
19
-
20
- function isMaskableStaleTool(tool: string): boolean {
21
- return tool === "read" || tool === "grep" || tool === "search";
22
- }
23
-
24
- function staleRecordsByResultId(report: ContextHygieneReport): Map<string, ContextHygieneStaleRecord> {
25
- const records = new Map<string, ContextHygieneStaleRecord>();
26
- for (const candidate of report.staleCandidates) {
27
- for (const record of candidate.staleResults) {
28
- if (!record.originalResultId || !isMaskableStaleTool(record.originalTool)) continue;
29
- const existing = records.get(record.originalResultId);
30
- if (!existing || existing.invalidatingMutationEventId < record.invalidatingMutationEventId) {
31
- records.set(record.originalResultId, record);
32
- }
33
- }
34
- }
35
- return records;
36
- }
37
-
38
- function maskStaleToolResultMessage<T extends ContextToolResultMessage>(message: T, record: ContextHygieneStaleRecord): T {
39
- const details = isRecord(message.details) ? message.details : {};
40
- return {
41
- ...message,
42
- content: [{ type: "text" as const, text: renderStaleContextPlaceholder(record) }],
43
- details: {
44
- ...details,
45
- contextHygieneStale: record,
46
- },
47
- };
48
- }
49
-
50
- export function applyContextHygieneStaleContext<T extends ContextToolResultMessage>(
51
- messages: readonly T[],
52
- report: ContextHygieneReport,
53
- ): T[] {
54
- const staleByResultId = staleRecordsByResultId(report);
55
- if (staleByResultId.size === 0) return messages as T[];
56
-
57
- let changed = false;
58
- const nextMessages = messages.map((message) => {
59
- if (message.role !== "toolResult" || typeof message.toolCallId !== "string") return message;
60
- const staleRecord = staleByResultId.get(message.toolCallId);
61
- if (staleRecord) {
62
- if (message.toolName !== staleRecord.originalTool) return message;
63
- changed = true;
64
- return maskStaleToolResultMessage(message, staleRecord);
65
- }
66
- return message;
67
- });
68
-
69
- return changed ? nextMessages : (messages as T[]);
70
- }