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 +30 -18
- package/index.ts +16 -136
- package/package.json +4 -4
- package/src/confusable-hyphens.ts +6 -0
- package/src/edit-classify.ts +0 -2
- package/src/edit-diff.ts +3 -6
- package/src/edit-output.ts +1 -7
- package/src/edit.ts +3 -16
- package/src/grep-output.ts +0 -26
- package/src/grep.ts +1 -13
- package/src/hashline.ts +1 -10
- package/src/read-output.ts +1 -27
- package/src/read.ts +1 -10
- package/src/readseek-client.ts +1 -1
- package/src/readseek-settings.ts +6 -134
- package/src/sg-output.ts +0 -30
- package/src/sg.ts +14 -22
- package/src/tool-prompt-metadata.ts +2 -8
- package/src/write.ts +1 -13
- package/prompts/find.md +0 -18
- package/prompts/ls.md +0 -11
- package/src/context-application.ts +0 -70
- package/src/context-hygiene.ts +0 -512
- package/src/doom-loop-suggestions.ts +0 -42
- package/src/doom-loop.ts +0 -216
- package/src/find.ts +0 -613
- package/src/ls.ts +0 -293
- package/src/readseek-command.ts +0 -155
- package/src/readseek-repo.ts +0 -50
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
|
|
6
|
-
|
|
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
|
-
-
|
|
17
|
-
images are returned as attachments.
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
`search`, or `write`.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
-
|
|
27
|
-
matches
|
|
28
|
-
-
|
|
29
|
-
follow-up edits.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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.
|
|
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/
|
|
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/
|
|
70
|
+
"url": "https://github.com/jarkkojs/pi-readseek/issues"
|
|
71
71
|
},
|
|
72
|
-
"homepage": "https://github.com/
|
|
72
|
+
"homepage": "https://github.com/jarkkojs/pi-readseek#readme"
|
|
73
73
|
}
|
package/src/edit-classify.ts
CHANGED
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(
|
|
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(
|
|
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,
|
package/src/edit-output.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
});
|
package/src/grep-output.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
package/src/read-output.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|