pi-readseek 0.3.2 → 0.3.4

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/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  `pi-readseek` is a pi extension for readseek-backed file reading, hash-anchored
4
4
  editing, anchored grep, structural maps, symbol lookup, and structural search.
5
- It exists to resolve conflicts between overlapping pi file-operation tools by
6
- exposing one consistent readseek-centered surface.
5
+ It resolves conflicts between overlapping pi file-operation tools by exposing
6
+ one consistent readseek-centered surface.
7
7
 
8
8
  ## Installation
9
9
 
@@ -11,25 +11,37 @@ exposing one consistent readseek-centered surface.
11
11
  pi install npm:pi-readseek
12
12
  ```
13
13
 
14
+ The structural search and map features require the `@jarkkojs/readseek` native
15
+ binary. The extension auto-installs the correct platform package, or you can
16
+ install it manually:
17
+
18
+ ```bash
19
+ # Auto-installed by the extension on supported platforms.
20
+ # Manual install (if needed):
21
+ npm install --save-dev @jarkkojs/readseek
22
+ ```
23
+
14
24
  ## Tools
15
25
 
16
- - `read` — reads text files with `LINE:HASH` anchors for later `edit` calls;
17
- images are returned as attachments. Large or symbol-scoped reads can include
18
- structural maps powered by `@jarkkojs/readseek`.
19
- - `edit` — changes existing text files using fresh anchors from `read`, `grep`,
20
- `search`, or `write`. Use anchored variants such as `set_line`; `new_text`
21
- must be plain replacement text and never include `LINE:HASH|` prefixes.
22
- Set `new_text` to `""` to delete a line. Fuzzy replacement is literal
23
- relocation, not approximate or semantic matching.
24
- - `grep` — searches text and returns edit-ready `LINE:HASH` anchors without a
26
+ - **read** — reads text files with `LINE:HASH` anchors for later `edit` calls;
27
+ images are returned as attachments. Supports `symbol`, `map`, and `bundle`
28
+ options powered by `@jarkkojs/readseek`.
29
+ - **edit** — changes existing text files using fresh anchors from `read`,
30
+ `grep`, `search`, or `write`. Variants: `set_line`, `replace_lines`,
31
+ `insert_after`, `replace_symbol`, `replace`. Set `new_text` to `""` to
32
+ delete a line.
33
+ - **grep** searches text and returns edit-ready `LINE:HASH` anchors without a
25
34
  follow-up `read`.
26
- - `search` — searches code by structural pattern and returns anchored
27
- matches; use it when syntax matters more than raw text.
28
- - `write` — creates or overwrites whole files and returns anchors for immediate
29
- follow-up edits. Create a new file with `write` when there is no existing file
30
- to edit.
31
- - `ls` — lists one directory.
32
- - `find` — recursively discovers files and directories.
35
+ - **search** — searches code by structural pattern (AST) and returns anchored
36
+ matches. Use when syntax matters more than raw text.
37
+ - **write** — creates or overwrites whole files and returns anchors for
38
+ immediate follow-up edits.
39
+
40
+ ## Related
41
+
42
+ - [readseek.vim](https://github.com/jarkkojs/readseek.vim) — Vim 9 plugin
43
+ frontend for the readseek CLI. Provides go-to-definition, references,
44
+ rename, hover, and structural search from within Vim.
33
45
 
34
46
  ## Licensing
35
47
 
package/index.ts CHANGED
@@ -4,141 +4,21 @@ import { registerEditTool } from "./src/edit.js";
4
4
  import { registerGrepTool } from "./src/grep.js";
5
5
  import { registerSgTool, isSgAvailable } from "./src/sg.js";
6
6
  import { registerWriteTool } from "./src/write.js";
7
- import { registerLsTool } from "./src/ls.js";
8
- import { registerFindTool } from "./src/find.js";
9
- import { registerReadseekCommand } from "./src/readseek-command.js";
10
- import { applyContextHygieneStaleContext } from "./src/context-application.js";
11
- import {
12
- createContextHygieneTracker,
13
- normalizePathForContextHygiene,
14
- type ContextHygieneEvent,
15
- type ContextHygieneMetadata,
16
- type ContextHygieneReport,
17
- type ContextHygieneResource,
18
- type ContextHygieneTracker,
19
- } from "./src/context-hygiene.js";
20
- import {
21
- consumeDoomLoopWarning,
22
- createDoomLoopState,
23
- formatDoomLoopMessage,
24
- recordToolCall,
25
- } from "./src/doom-loop.js";
26
-
27
- function isContextHygieneResource(value: unknown): value is ContextHygieneResource {
28
- if (!value || typeof value !== "object") return false;
29
- const resource = value as { kind?: unknown; key?: unknown };
30
- return (resource.kind === "file" || resource.kind === "symbol") && typeof resource.key === "string";
31
- }
32
-
33
- function isContextHygieneMetadata(value: unknown): value is ContextHygieneMetadata {
34
- if (!value || typeof value !== "object") return false;
35
- const metadata = value as Partial<ContextHygieneMetadata>;
36
- return (
37
- metadata.schemaVersion === 1 &&
38
- typeof metadata.tool === "string" &&
39
- (metadata.classification === "read-context" ||
40
- metadata.classification === "search-context" ||
41
- metadata.classification === "mutation") &&
42
- Array.isArray(metadata.resources) &&
43
- metadata.resources.every(isContextHygieneResource)
44
- );
45
- }
46
-
47
- function contextHygieneFromDetails(details: unknown): ContextHygieneMetadata | undefined {
48
- if (!details || typeof details !== "object") return undefined;
49
- const metadata = (details as { contextHygiene?: unknown }).contextHygiene;
50
- return isContextHygieneMetadata(metadata) ? metadata : undefined;
51
- }
52
-
53
- function recordContextHygiene(
54
- tracker: ContextHygieneTracker,
55
- metadata: ContextHygieneMetadata,
56
- toolCallId: unknown,
57
- ): ContextHygieneEvent {
58
- return tracker.record(metadata, {
59
- resultId: typeof toolCallId === "string" ? toolCallId : undefined,
60
- });
61
- }
62
-
63
7
  export default function piReadseekExtension(pi: ExtensionAPI): void {
64
- const readTurns = new Map<string, number>();
65
- const doomLoopState = createDoomLoopState();
66
- const contextHygieneTracker = createContextHygieneTracker();
67
- const readTurnKey = (absolutePath: string) => normalizePathForContextHygiene(absolutePath);
68
- const noteRead = (absolutePath: string) => {
69
- const report = contextHygieneTracker.generateReport();
70
- const eventId = report.eventCount + 1;
71
- readTurns.set(readTurnKey(absolutePath), eventId);
72
- };
73
- const wasReadInSession = (absolutePath: string) => readTurns.has(readTurnKey(absolutePath));
74
-
75
- registerReadTool(pi, { onSuccessfulRead: noteRead });
76
- registerEditTool(pi, { wasReadInSession });
77
- const sgAvailable = isSgAvailable();
78
- const searchGuideline = sgAvailable
79
- ? "Use grep summary for counts; use search for structural code patterns."
80
- : "Use grep summary for counts; install @jarkkojs/readseek to enable search.";
81
-
82
- registerGrepTool(pi, { searchGuideline, onFileAnchored: noteRead });
83
- registerSgTool(pi, { onFileAnchored: noteRead });
84
- registerWriteTool(pi, { onFileAnchored: noteRead });
85
- registerLsTool(pi);
86
- registerFindTool(pi);
87
- registerReadseekCommand(pi);
88
-
89
- pi.on("tool_call", (event: any) => {
90
- recordToolCall(
91
- doomLoopState,
92
- event.toolName,
93
- event.toolCallId,
94
- (event.input ?? {}) as Record<string, unknown>,
95
- );
96
- });
97
-
98
- const expireStaleReadTurns = (report: ContextHygieneReport) => {
99
- if (readTurns.size === 0) return;
100
- for (const candidate of report.staleCandidates) {
101
- if (!candidate.resourceKey.startsWith("file:")) continue;
102
- const resourcePath = readTurnKey(candidate.resourceKey.slice("file:".length));
103
- const recordedEventId = readTurns.get(resourcePath);
104
- if (recordedEventId === undefined) continue;
105
- if (recordedEventId < candidate.mutationEventId) {
106
- readTurns.delete(resourcePath);
107
- }
108
- }
109
- };
110
-
111
- pi.on("context", (event: any): any => {
112
- if (!Array.isArray(event.messages)) return undefined;
113
- const report = contextHygieneTracker.generateReport();
114
- const messages = applyContextHygieneStaleContext(event.messages, report);
115
- expireStaleReadTurns(report);
116
- return { messages };
117
- });
118
-
119
- pi.on("tool_result", (event: any) => {
120
- const contextHygiene = contextHygieneFromDetails(event.details);
121
- if (contextHygiene) recordContextHygiene(contextHygieneTracker, contextHygiene, event.toolCallId);
122
-
123
- const doomLoop = consumeDoomLoopWarning(doomLoopState, event.toolCallId);
124
- if (!doomLoop || !Array.isArray(event.content)) return undefined;
125
-
126
- const content = [...event.content];
127
- const prefix = `${formatDoomLoopMessage(doomLoop)}\n\n---\n`;
128
- const textIndex = content.findIndex((item) => {
129
- const maybeText = item as { type?: unknown; text?: unknown };
130
- return maybeText.type === "text" && typeof maybeText.text === "string";
131
- });
132
- if (textIndex >= 0) {
133
- const item = content[textIndex] as { type: "text"; text: string };
134
- content[textIndex] = { ...item, text: `${prefix}${item.text}` };
135
- } else {
136
- content.unshift({ type: "text" as const, text: prefix });
137
- }
138
- return {
139
- content,
140
- details: event.details,
141
- isError: event.isError,
142
- };
143
- });
8
+ const readPaths = new Set<string>();
9
+ const noteRead = (absolutePath: string) => {
10
+ readPaths.add(absolutePath);
11
+ };
12
+ const wasReadInSession = (absolutePath: string) => readPaths.has(absolutePath);
13
+
14
+ registerReadTool(pi, { onSuccessfulRead: noteRead });
15
+ registerEditTool(pi, { wasReadInSession });
16
+ const sgAvailable = isSgAvailable();
17
+ const searchGuideline = sgAvailable
18
+ ? "Use grep summary for counts; use search for structural code patterns."
19
+ : "Use grep summary for counts; install @jarkkojs/readseek to enable search.";
20
+
21
+ registerGrepTool(pi, { searchGuideline, onFileAnchored: noteRead });
22
+ registerSgTool(pi, { onFileAnchored: noteRead });
23
+ registerWriteTool(pi, { onFileAnchored: noteRead });
144
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-readseek",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Pi extension for readseek-backed hash-anchored read/edit/grep, structural code maps, structural search, and file exploration",
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "git+https://github.com/coctostan/pi-readseek.git"
18
+ "url": "git+https://github.com/jarkkojs/pi-readseek.git"
19
19
  },
20
20
  "author": "Maxwell Newman",
21
21
  "license": "MIT",
@@ -67,7 +67,7 @@
67
67
  "test": "tests"
68
68
  },
69
69
  "bugs": {
70
- "url": "https://github.com/coctostan/pi-readseek/issues"
70
+ "url": "https://github.com/jarkkojs/pi-readseek/issues"
71
71
  },
72
- "homepage": "https://github.com/coctostan/pi-readseek#readme"
72
+ "homepage": "https://github.com/jarkkojs/pi-readseek#readme"
73
73
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Unicode hyphen-like characters normalized to ASCII "-" during text
3
+ * comparison so edits with typographic hyphens match their originals.
4
+ */
5
+ export const CONFUSABLE_HYPHENS_RE =
6
+ /[\u2010\u2011\u2012\u2013\u2014\u2015\u2212\uFE63\uFF0D]/g;
@@ -146,8 +146,6 @@ export function parseDifftJson(json: any): DifftClassifyResult | null {
146
146
  }
147
147
 
148
148
  return { classification: "semantic", movedBlocks };
149
-
150
- return { classification: "semantic", movedBlocks };
151
149
  }
152
150
 
153
151
  export async function runDifftastic(
package/src/edit-diff.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as Diff from "diff";
2
2
  import { computeLineHash } from "./hashline.js";
3
+ import { CONFUSABLE_HYPHENS_RE } from "./confusable-hyphens.js";
3
4
 
4
- // ─── Line ending normalization ──────────────────────────────────────────
5
5
 
6
6
  export function detectLineEnding(content: string): "\r\n" | "\n" {
7
7
  const crlfIdx = content.indexOf("\r\n");
@@ -31,15 +31,13 @@ export function hasBareCarriageReturn(content: string): boolean {
31
31
  return content.replace(/\r\n/g, "").includes("\r");
32
32
  }
33
33
 
34
- // ─── Fuzzy text matching ────────────────────────────────────────────────
35
34
 
36
35
  const SINGLE_QUOTES_RE = /[\u2018\u2019\u201A\u201B]/g;
37
36
  const DOUBLE_QUOTES_RE = /[\u201C\u201D\u201E\u201F]/g;
38
- const HYPHENS_RE = /[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g;
39
37
  const UNICODE_SPACES_RE = /[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g;
40
38
 
41
39
  function normalizeFuzzyChar(ch: string): string {
42
- return ch.replace(SINGLE_QUOTES_RE, "'").replace(DOUBLE_QUOTES_RE, '"').replace(HYPHENS_RE, "-").replace(UNICODE_SPACES_RE, " ");
40
+ return ch.replace(SINGLE_QUOTES_RE, "'").replace(DOUBLE_QUOTES_RE, '"').replace(CONFUSABLE_HYPHENS_RE, "-").replace(UNICODE_SPACES_RE, " ");
43
41
  }
44
42
 
45
43
  function normalizeForFuzzyMatch(text: string): string {
@@ -49,7 +47,7 @@ function normalizeForFuzzyMatch(text: string): string {
49
47
  .join("\n")
50
48
  .replace(SINGLE_QUOTES_RE, "'")
51
49
  .replace(DOUBLE_QUOTES_RE, '"')
52
- .replace(HYPHENS_RE, "-")
50
+ .replace(CONFUSABLE_HYPHENS_RE, "-")
53
51
  .replace(UNICODE_SPACES_RE, " ");
54
52
  }
55
53
 
@@ -199,7 +197,6 @@ export function replaceText(
199
197
  };
200
198
  }
201
199
 
202
- // ─── Diff generation ────────────────────────────────────────────────────
203
200
 
204
201
  export function generateDiffString(
205
202
  oldContent: string,
@@ -1,6 +1,6 @@
1
1
  import { countEditTypes, parseDiffStats } from "./edit-render-helpers.js";
2
2
  import { buildReadseekEditResult, type SemanticSummary } from "./readseek-value.js";
3
- import { buildContextHygieneMetadata, buildFileResource, type ContextHygieneMetadata } from "./context-hygiene.js";
3
+
4
4
  import type { DiffData } from "./diff-data.js";
5
5
  export interface BuildEditOutputInput {
6
6
  path: string;
@@ -18,7 +18,6 @@ export interface EditOutputResult {
18
18
  text: string;
19
19
  patch: string;
20
20
  readseekValue: ReturnType<typeof buildReadseekEditResult>;
21
- contextHygiene: ContextHygieneMetadata;
22
21
  }
23
22
  function getVisibleDiffStats(diff: string): { added: number; removed: number } {
24
23
  const stats = parseDiffStats(diff);
@@ -98,10 +97,5 @@ export function buildEditOutput(input: BuildEditOutputInput): EditOutputResult {
98
97
  noopEdits: input.noopEdits,
99
98
  ...(input.semanticSummary ? { semanticSummary: input.semanticSummary } : {}),
100
99
  }),
101
- contextHygiene: buildContextHygieneMetadata({
102
- tool: "edit",
103
- classification: "mutation",
104
- resources: [buildFileResource(input.path)],
105
- }),
106
100
  };
107
101
  }
package/src/edit.ts CHANGED
@@ -21,7 +21,7 @@ import { buildEditPreviewKey, buildPendingEditPreviewData, resolvePendingDiffPre
21
21
  import { buildDiffData, type DiffBlockRange } from "./diff-data.js";
22
22
  import { clampLineToWidth, clampLinesToWidth, isRendererExpanded, linkToolPath, summaryLine } from "./tui-render-utils.js";
23
23
  import { DiffPreviewComponent } from "./tui-diff-component.js";
24
- import { buildContextHygieneMetadata, buildFileResource, type ContextHygieneMetadata } from "./context-hygiene.js";
24
+
25
25
  import { resolveEditDiffDisplay } from "./readseek-settings.js";
26
26
 
27
27
  const EDIT_PENDING_PREVIEW_STATE_KEY = "hashline-edit-pending-preview";
@@ -45,7 +45,6 @@ export function isBinaryBuffer(buf: Buffer): boolean {
45
45
  return buf.includes(0);
46
46
  }
47
47
 
48
- // ─── Schema ─────────────────────────────────────────────────────────────
49
48
 
50
49
  const hashlineEditItemSchema = Type.Union([
51
50
  Type.Object({ set_line: Type.Object({ anchor: Type.String(), new_text: Type.String() }) }, { additionalProperties: true }),
@@ -100,11 +99,10 @@ function buildEditError(
100
99
  message: string,
101
100
  hint?: string,
102
101
  errorDetails?: Record<string, unknown>,
103
- contextHygiene?: ContextHygieneMetadata,
104
102
  ): {
105
103
  content: [{ type: "text"; text: string }];
106
104
  isError: true;
107
- details: EditToolDetails & { readseekValue: any; contextHygiene?: ContextHygieneMetadata };
105
+ details: EditToolDetails & { readseekValue: any };
108
106
  } {
109
107
  return {
110
108
  content: [{ type: "text", text: message }],
@@ -119,8 +117,7 @@ function buildEditError(
119
117
  path,
120
118
  error: buildReadseekError(code, message, hint, errorDetails),
121
119
  },
122
- ...(contextHygiene ? { contextHygiene } : {}),
123
- } as EditToolDetails & { readseekValue: any; contextHygiene?: ContextHygieneMetadata },
120
+ } as EditToolDetails & { readseekValue: any },
124
121
  };
125
122
  }
126
123
 
@@ -129,7 +126,6 @@ export interface EditToolOptions {
129
126
  syntaxValidate?: SyntaxValidateOptions["syntaxValidate"];
130
127
  }
131
128
 
132
- // ─── Registration ───────────────────────────────────────────────────────
133
129
 
134
130
  export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}) {
135
131
  const toolConfig = {
@@ -491,11 +487,6 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
491
487
  }
492
488
 
493
489
  if (input.postEditVerify === true) {
494
- const postWriteMutationContextHygiene = buildContextHygieneMetadata({
495
- tool: "edit",
496
- classification: "mutation",
497
- resources: [buildFileResource(absolutePath)],
498
- });
499
490
  let verifiedContent: string;
500
491
  try {
501
492
  const verified = await fsReadFile(absolutePath, "utf-8");
@@ -506,7 +497,6 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
506
497
  fsCode: err?.code,
507
498
  fsMessage: err?.message,
508
499
  },
509
- postWriteMutationContextHygiene,
510
500
  );
511
501
  }
512
502
  if (verifiedContent !== writeContent) {
@@ -515,7 +505,6 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
515
505
  expectedLength: writeContent.length,
516
506
  actualLength: verifiedContent.length,
517
507
  },
518
- postWriteMutationContextHygiene,
519
508
  );
520
509
  }
521
510
  }
@@ -580,7 +569,6 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
580
569
  diffData,
581
570
  firstChangedLine: anchorResult.firstChangedLine ?? diffResult.firstChangedLine,
582
571
  readseekValue: builtOutput.readseekValue,
583
- contextHygiene: builtOutput.contextHygiene,
584
572
  } as EditToolDetails & {
585
573
  diffData: typeof diffData;
586
574
  readseekValue: {
@@ -594,7 +582,6 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
594
582
  warnings: string[];
595
583
  noopEdits: unknown[];
596
584
  };
597
- contextHygiene: ContextHygieneMetadata;
598
585
  },
599
586
  };
600
587
  });
@@ -4,14 +4,6 @@ import {
4
4
  truncateHead,
5
5
  } from "@earendil-works/pi-coding-agent";
6
6
  import { resolveGrepOutputBudget } from "./grep-budget.js";
7
- import {
8
- buildContextHygieneMetadata,
9
- buildFileResource,
10
- buildSymbolResource,
11
- type ContextHygieneMetadata,
12
- type ContextHygieneRehydrateDescriptor,
13
- type ContextHygieneResource,
14
- } from "./context-hygiene.js";
15
7
 
16
8
  export interface GrepOutputRecord extends ReadseekLine {
17
9
  path: string;
@@ -64,7 +56,6 @@ export interface BuildGrepOutputInput {
64
56
  scopeMode?: "symbol";
65
57
  scopeWarnings?: GrepScopeWarning[];
66
58
  passthroughLines?: string[];
67
- rehydrate?: ContextHygieneRehydrateDescriptor | null;
68
59
  }
69
60
 
70
61
  export interface GrepOutputResult {
@@ -87,7 +78,6 @@ export interface GrepOutputResult {
87
78
  warnings: GrepScopeWarning[];
88
79
  };
89
80
  };
90
- contextHygiene: ContextHygieneMetadata;
91
81
  }
92
82
 
93
83
  function renderEntry(displayPath: string, entry: GrepOutputEntry): string {
@@ -174,24 +164,8 @@ export function buildGrepOutput(input: BuildGrepOutputInput): GrepOutputResult {
174
164
  readseekValue.scopes = buildScopeMetadata(input.groups, input.scopeWarnings ?? []);
175
165
  }
176
166
 
177
- const contextHygieneResources: ContextHygieneResource[] = [];
178
- for (const record of input.records) {
179
- contextHygieneResources.push(buildFileResource(record.path));
180
- }
181
- for (const group of input.groups) {
182
- if (!group.scope) continue;
183
- contextHygieneResources.push(buildFileResource(group.absolutePath));
184
- contextHygieneResources.push(buildSymbolResource(group.absolutePath, group.scope.symbol.name, group.scope.symbol.kind));
185
- }
186
- const contextHygiene = buildContextHygieneMetadata({
187
- tool: "grep",
188
- classification: "search-context",
189
- resources: contextHygieneResources,
190
- rehydrate: input.rehydrate ?? undefined,
191
- });
192
167
  return {
193
168
  text,
194
169
  readseekValue,
195
- contextHygiene,
196
170
  };
197
171
  }
package/src/grep.ts CHANGED
@@ -9,7 +9,7 @@ import { looksLikeBinary } from "./binary-detect.js";
9
9
  import { ensureHashInit, formatHashlineDisplay, escapeControlCharsForDisplay } from "./hashline.js";
10
10
  import { buildReadseekError, buildReadseekLine } from "./readseek-value.js";
11
11
  import { buildGrepOutput } from "./grep-output.js";
12
- import { buildGrepRehydrateDescriptor } from "./context-hygiene.js";
12
+
13
13
  import { getOrGenerateMap } from "./map-cache.js";
14
14
  import { scopeGrepGroupsToSymbols } from "./grep-symbol-scope.js";
15
15
  import { resolveToCwd } from "./path-utils.js";
@@ -672,17 +672,6 @@ if (p.scope === "symbol" && !summary) {
672
672
  scopeMode: p.scope === "symbol" && !summary ? "symbol" : undefined,
673
673
  scopeWarnings,
674
674
  passthroughLines,
675
- rehydrate: buildGrepRehydrateDescriptor({
676
- pattern: p.pattern,
677
- path: p.path,
678
- glob: p.glob,
679
- literal: p.literal,
680
- ignoreCase: p.ignoreCase,
681
- context: p.context,
682
- summary: p.summary,
683
- scope: p.scope,
684
- scopeContext: p.scopeContext,
685
- }),
686
675
  });
687
676
 
688
677
  if (!summary && readseekRecords.length > 0) {
@@ -705,7 +694,6 @@ if (p.scope === "symbol" && !summary) {
705
694
  details: {
706
695
  ...compactDetails,
707
696
  readseekValue: builtOutput.readseekValue,
708
- contextHygiene: builtOutput.contextHygiene,
709
697
  },
710
698
  };
711
699
  },
package/src/hashline.ts CHANGED
@@ -8,8 +8,8 @@
8
8
  import xxhashWasm from "xxhash-wasm";
9
9
  import { throwIfAborted } from "./runtime.js";
10
10
  import type { ReadseekLine } from "./readseek-value.js";
11
+ import { CONFUSABLE_HYPHENS_RE } from "./confusable-hyphens.js";
11
12
 
12
- // ─── Types ──────────────────────────────────────────────────────────────
13
13
 
14
14
  export type HashlineEditItem =
15
15
  | { set_line: { anchor: string; new_text: string } }
@@ -52,7 +52,6 @@ interface NoopEdit {
52
52
  currentContent: string;
53
53
  }
54
54
 
55
- // ─── Hash computation ───────────────────────────────────────────────────
56
55
 
57
56
  const HASH_LEN = 3;
58
57
  const RADIX = 16;
@@ -62,7 +61,6 @@ const DICT = Array.from({ length: HASH_MOD }, (_, i) => i.toString(RADIX).padSta
62
61
  const HASHLINE_PREFIX_RE = /^\d+:[0-9a-zA-Z]{1,16}\|/;
63
62
  const DIFF_PLUS_RE = /^\+(?!\+)/;
64
63
  const HASH_ONLY_PREFIX_RE = /^[0-9a-f]{3}\|/;
65
- const CONFUSABLE_HYPHENS_RE = /[\u2010\u2011\u2012\u2013\u2014\u2212\uFE63\uFF0D]/g;
66
64
  const HASH_RELOCATION_WINDOW_BASE = 20;
67
65
  const HASH_RELOCATION_WINDOW_CAP = 100;
68
66
 
@@ -128,7 +126,6 @@ export function hashLines(content: string): string {
128
126
  .join("\n");
129
127
  }
130
128
 
131
- // ─── Parsing ────────────────────────────────────────────────────────────
132
129
 
133
130
  export function parseLineRef(ref: string): { line: number; hash: string; content?: string } {
134
131
  const contentMatch = ref.match(/^[^|]*\|(.*)$/);
@@ -142,7 +139,6 @@ export function parseLineRef(ref: string): { line: number; hash: string; content
142
139
  return { line, hash: match[2], content: contentAfterPipe };
143
140
  }
144
141
 
145
- // ─── Mismatch formatting ────────────────────────────────────────────────
146
142
 
147
143
  function tokenSimilarity(a: string, b: string): number {
148
144
  const tokA = new Set(a.trim().split(/\s+/));
@@ -241,7 +237,6 @@ function formatMismatchError(
241
237
  return { message: out.join("\n"), updatedAnchors };
242
238
  }
243
239
 
244
- // ─── DST preprocessing helpers ──────────────────────────────────────────
245
240
 
246
241
  function splitDst(dst: string): string[] {
247
242
  if (dst === "") return [];
@@ -280,7 +275,6 @@ function stripNewLinePrefixes(lines: string[]): string[] {
280
275
  );
281
276
  }
282
277
 
283
- // ─── Whitespace / format helpers ────────────────────────────────────────
284
278
 
285
279
  function stripAllWhitespace(s: string): string {
286
280
  return s.replace(/\s+/g, "");
@@ -362,7 +356,6 @@ function restoreOldWrappedLines(oldLines: string[], newLines: string[]): string[
362
356
  return out;
363
357
  }
364
358
 
365
- // ─── Echo stripping ─────────────────────────────────────────────────────
366
359
 
367
360
  function stripInsertAnchorEcho(anchorLine: string, dst: string[]): string[] {
368
361
  if (dst.length > 1 && wsEq(dst[0], anchorLine)) return dst.slice(1);
@@ -378,7 +371,6 @@ function stripRangeBoundaryEcho(fileLines: string[], start: number, end: number,
378
371
  return out;
379
372
  }
380
373
 
381
- // ─── Edit parser ────────────────────────────────────────────────────────
382
374
 
383
375
  function parseHashlineEditItem(edit: HashlineEditItem): ParsedEdit {
384
376
  if ("set_line" in edit) {
@@ -404,7 +396,6 @@ function parseHashlineEditItem(edit: HashlineEditItem): ParsedEdit {
404
396
  throw new Error("replace edits are applied separately");
405
397
  }
406
398
 
407
- // ─── Main edit engine ───────────────────────────────────────────────────
408
399
 
409
400
  export function applyHashlineEdits(
410
401
  content: string,
@@ -5,14 +5,7 @@ import {
5
5
  truncateHead,
6
6
  } from "@earendil-works/pi-coding-agent";
7
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";
8
+
16
9
 
17
10
  export interface ReadSymbolMetadata {
18
11
  query: string;
@@ -65,7 +58,6 @@ export interface ReadOutputInput {
65
58
  symbol?: ReadSymbolMetadata | null;
66
59
  map?: ReadMapMetadata;
67
60
  bundle?: ReadBundleMetadata | null;
68
- rehydrate?: ContextHygieneRehydrateDescriptor | null;
69
61
  }
70
62
 
71
63
  export interface ReadOutputResult {
@@ -101,7 +93,6 @@ export interface ReadOutputResult {
101
93
  warnings: ReadseekWarning[];
102
94
  };
103
95
  };
104
- contextHygiene: ContextHygieneMetadata;
105
96
  }
106
97
 
107
98
  export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
@@ -187,26 +178,9 @@ export function buildReadOutput(input: ReadOutputInput): ReadOutputResult {
187
178
  };
188
179
  }
189
180
 
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
181
  return {
207
182
  text,
208
183
  lines,
209
184
  readseekValue,
210
- contextHygiene,
211
185
  };
212
186
  }