pi-readseek 0.3.4 → 0.3.6
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/package.json +1 -1
- package/src/edit-classify.ts +0 -3
- package/src/hashline.ts +0 -12
- package/src/map-cache.ts +0 -16
- package/src/persistent-map-cache.ts +8 -29
- package/src/readseek/mapper.ts +1 -18
- package/src/readseek/parser-errors.ts +0 -3
- package/src/readseek/parser-loader.ts +0 -5
- package/src/readseek-settings.ts +2 -16
- package/src/readseek-value.ts +1 -19
- package/src/readseek-error-codes.ts +0 -54
package/package.json
CHANGED
package/src/edit-classify.ts
CHANGED
|
@@ -62,9 +62,6 @@ export function classifyEdit(oldContent: string, newContent: string): EditClassi
|
|
|
62
62
|
|
|
63
63
|
let difftCachedResult: boolean | null = null;
|
|
64
64
|
|
|
65
|
-
export function _resetDifftCache(): void {
|
|
66
|
-
difftCachedResult = null;
|
|
67
|
-
}
|
|
68
65
|
|
|
69
66
|
export async function isDifftAvailable(): Promise<boolean> {
|
|
70
67
|
if (difftCachedResult !== null) return difftCachedResult;
|
package/src/hashline.ts
CHANGED
|
@@ -115,18 +115,6 @@ export function formatHashlineDisplay(lineNumber: number, content: string): stri
|
|
|
115
115
|
return `${lineNumber}:${computeLineHash(lineNumber, content)}|${escapeControlCharsForDisplay(content)}`;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
export function hashLine(lineNumber: number, content: string): string {
|
|
119
|
-
return formatHashlineDisplay(lineNumber, content);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function hashLines(content: string): string {
|
|
123
|
-
return content
|
|
124
|
-
.split("\n")
|
|
125
|
-
.map((line, i) => formatHashlineDisplay(i + 1, line))
|
|
126
|
-
.join("\n");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
118
|
export function parseLineRef(ref: string): { line: number; hash: string; content?: string } {
|
|
131
119
|
const contentMatch = ref.match(/^[^|]*\|(.*)$/);
|
|
132
120
|
const contentAfterPipe = contentMatch ? contentMatch[1] : undefined;
|
package/src/map-cache.ts
CHANGED
|
@@ -133,20 +133,4 @@ export async function getOrGenerateMap(absPath: string): Promise<FileMap | null>
|
|
|
133
133
|
} catch {
|
|
134
134
|
return null;
|
|
135
135
|
}
|
|
136
|
-
}
|
|
137
|
-
export function setMapCacheMaxSize(size: number): void {
|
|
138
|
-
getMapCacheState().maxSize = size;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Clear the map cache. Exported for testing.
|
|
143
|
-
*/
|
|
144
|
-
export function clearMapCache(): void {
|
|
145
|
-
const state = getMapCacheState();
|
|
146
|
-
state.cache.clear();
|
|
147
|
-
state.maxSize = MAP_CACHE_MAX_SIZE;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function __getInMemoryMapCacheSizeForTest(): number {
|
|
151
|
-
return getMapCacheState().cache.size;
|
|
152
136
|
}
|
|
@@ -13,7 +13,7 @@ import { resolveReadseekJsonSettings } from "./readseek-settings.js";
|
|
|
13
13
|
* 3. `$XDG_CACHE_HOME/pi-readseek/maps` (when non-empty).
|
|
14
14
|
* 4. `~/.cache/pi-readseek/maps`.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
function resolveCacheDir(): string {
|
|
17
17
|
const explicit = process.env.PI_HASHLINE_MAP_CACHE_DIR;
|
|
18
18
|
if (explicit && explicit.length > 0) return explicit;
|
|
19
19
|
|
|
@@ -170,39 +170,22 @@ async function tryWriteCachedRaw(key: string, map: FileMap): Promise<boolean> {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
* without going through the eviction counter).
|
|
176
|
-
*/
|
|
177
|
-
export async function writeCachedRaw(key: string, map: FileMap): Promise<void> {
|
|
173
|
+
/** Low-level atomic write. Internal helper. */
|
|
174
|
+
async function writeCachedRaw(key: string, map: FileMap): Promise<void> {
|
|
178
175
|
await tryWriteCachedRaw(key, map);
|
|
179
176
|
}
|
|
180
177
|
|
|
181
|
-
const
|
|
182
|
-
const
|
|
178
|
+
const EVICTION_INTERVAL = 20;
|
|
179
|
+
const EVICTION_CAP = 5000;
|
|
183
180
|
let writeCounter = 0;
|
|
184
|
-
let evictionInterval = DEFAULT_EVICTION_INTERVAL;
|
|
185
|
-
let evictionCap = DEFAULT_EVICTION_CAP;
|
|
186
|
-
|
|
187
|
-
export function __setEvictionHooksForTest(
|
|
188
|
-
hooks: { interval?: number; cap?: number } | null,
|
|
189
|
-
): void {
|
|
190
|
-
if (hooks === null) {
|
|
191
|
-
evictionInterval = DEFAULT_EVICTION_INTERVAL;
|
|
192
|
-
evictionCap = DEFAULT_EVICTION_CAP;
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
181
|
|
|
196
|
-
if (typeof hooks.interval === "number") evictionInterval = hooks.interval;
|
|
197
|
-
if (typeof hooks.cap === "number") evictionCap = hooks.cap;
|
|
198
|
-
}
|
|
199
182
|
|
|
200
183
|
const CACHE_KEY_FILE = /^[0-9a-f]{32}\.json$/;
|
|
201
184
|
async function maybeEvict(): Promise<void> {
|
|
202
185
|
try {
|
|
203
186
|
const dir = resolveCacheDir();
|
|
204
187
|
const names = (await readdir(dir)).filter((n) => CACHE_KEY_FILE.test(n));
|
|
205
|
-
if (names.length <=
|
|
188
|
+
if (names.length <= EVICTION_CAP) return;
|
|
206
189
|
|
|
207
190
|
const entries: Array<{ path: string; age: number }> = [];
|
|
208
191
|
for (const name of names) {
|
|
@@ -217,7 +200,7 @@ async function maybeEvict(): Promise<void> {
|
|
|
217
200
|
}
|
|
218
201
|
|
|
219
202
|
entries.sort((a, b) => a.age - b.age);
|
|
220
|
-
const excess = entries.length -
|
|
203
|
+
const excess = entries.length - EVICTION_CAP;
|
|
221
204
|
for (let i = 0; i < excess; i += 1) {
|
|
222
205
|
try {
|
|
223
206
|
await unlink(entries[i].path);
|
|
@@ -236,7 +219,7 @@ async function maybeEvict(): Promise<void> {
|
|
|
236
219
|
export async function writeCached(key: string, map: FileMap): Promise<void> {
|
|
237
220
|
if (!(await tryWriteCachedRaw(key, map))) return;
|
|
238
221
|
writeCounter += 1;
|
|
239
|
-
if (writeCounter %
|
|
222
|
+
if (writeCounter % EVICTION_INTERVAL === 0) {
|
|
240
223
|
try {
|
|
241
224
|
await maybeEvict();
|
|
242
225
|
} catch {
|
|
@@ -245,7 +228,3 @@ export async function writeCached(key: string, map: FileMap): Promise<void> {
|
|
|
245
228
|
}
|
|
246
229
|
}
|
|
247
230
|
|
|
248
|
-
// Test-only: reset the write counter between suites.
|
|
249
|
-
export function __resetWriteCounter(): void {
|
|
250
|
-
writeCounter = 0;
|
|
251
|
-
}
|
package/src/readseek/mapper.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
|
|
3
3
|
import { readseekMap, readseekMapContent } from "../readseek-client.js";
|
|
4
|
-
import {
|
|
4
|
+
import { throwIfAborted } from "../runtime.js";
|
|
5
5
|
import type { FileMap, MapOptions } from "./types.js";
|
|
6
6
|
|
|
7
7
|
export const READSEEK_MAPPER_NAME = "readseek";
|
|
@@ -20,17 +20,6 @@ export const READSEEK_MAPPER_IDENTITY: MapperIdentity = {
|
|
|
20
20
|
mapperName: READSEEK_MAPPER_NAME,
|
|
21
21
|
mapperVersion: READSEEK_MAPPER_VERSION,
|
|
22
22
|
};
|
|
23
|
-
|
|
24
|
-
export const ALL_MAPPER_IDENTITIES: Record<string, MapperIdentity> = {
|
|
25
|
-
readseek: READSEEK_MAPPER_IDENTITY,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function throwIfAborted(signal?: AbortSignal): void {
|
|
29
|
-
if (!signal?.aborted) return;
|
|
30
|
-
const reason = signal.reason;
|
|
31
|
-
throw reason instanceof Error ? reason : new Error("aborted");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
23
|
export async function generateMapWithIdentity(
|
|
35
24
|
filePath: string,
|
|
36
25
|
options: MapOptions = {},
|
|
@@ -61,9 +50,3 @@ export async function generateMapFromContent(
|
|
|
61
50
|
return map;
|
|
62
51
|
}
|
|
63
52
|
|
|
64
|
-
export function shouldGenerateMap(
|
|
65
|
-
totalLines: number,
|
|
66
|
-
totalBytes: number,
|
|
67
|
-
): boolean {
|
|
68
|
-
return totalLines > THRESHOLDS.MAX_LINES || totalBytes > THRESHOLDS.MAX_BYTES;
|
|
69
|
-
}
|
package/src/readseek-settings.ts
CHANGED
|
@@ -19,18 +19,6 @@ export interface ReadseekSettingsResult {
|
|
|
19
19
|
warnings: ReadseekSettingsWarning[];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let pathOverride: { globalSettingsPath?: string; projectSettingsPath?: string } | null = null;
|
|
23
|
-
|
|
24
|
-
export function __setReadseekSettingsPathsForTest(paths: {
|
|
25
|
-
globalSettingsPath?: string;
|
|
26
|
-
projectSettingsPath?: string;
|
|
27
|
-
}): void {
|
|
28
|
-
pathOverride = { ...paths };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function __resetReadseekSettingsPathsForTest(): void {
|
|
32
|
-
pathOverride = null;
|
|
33
|
-
}
|
|
34
22
|
|
|
35
23
|
function defaultGlobalSettingsPath(): string {
|
|
36
24
|
return join(homedir(), ".pi/agent/readseek/settings.json");
|
|
@@ -137,10 +125,8 @@ function mergeSettings(base: ReadseekJsonSettings, override: ReadseekJsonSetting
|
|
|
137
125
|
}
|
|
138
126
|
|
|
139
127
|
export function resolveReadseekJsonSettings(): ReadseekSettingsResult {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const globalResult = readSettingsFile(globalPath);
|
|
143
|
-
const projectResult = readSettingsFile(projectPath);
|
|
128
|
+
const globalResult = readSettingsFile(defaultGlobalSettingsPath());
|
|
129
|
+
const projectResult = readSettingsFile(defaultProjectSettingsPath());
|
|
144
130
|
return {
|
|
145
131
|
settings: mergeSettings(globalResult.settings, projectResult.settings),
|
|
146
132
|
warnings: [...globalResult.warnings, ...projectResult.warnings],
|
package/src/readseek-value.ts
CHANGED
|
@@ -37,12 +37,6 @@ export interface ReadseekRange {
|
|
|
37
37
|
totalLines?: number;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export interface ReadseekFileGroup {
|
|
41
|
-
path: string;
|
|
42
|
-
ranges: ReadseekRange[];
|
|
43
|
-
lines: ReadseekLine[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
40
|
export interface SemanticSummary {
|
|
47
41
|
classification: "no-op" | "whitespace-only" | "semantic" | "mixed";
|
|
48
42
|
difftasticAvailable: boolean;
|
|
@@ -76,7 +70,7 @@ export function buildReadseekLines(startLine: number, rawLines: string[]): Reads
|
|
|
76
70
|
return rawLines.map((raw, index) => buildReadseekLine(startLine + index, raw));
|
|
77
71
|
}
|
|
78
72
|
|
|
79
|
-
|
|
73
|
+
function renderReadseekLine(line: ReadseekLine): string {
|
|
80
74
|
return `${line.anchor}|${line.display}`;
|
|
81
75
|
}
|
|
82
76
|
|
|
@@ -106,18 +100,6 @@ export function buildReadseekError(
|
|
|
106
100
|
};
|
|
107
101
|
}
|
|
108
102
|
|
|
109
|
-
export function buildReadseekRange(startLine: number, endLine: number, totalLines?: number): ReadseekRange {
|
|
110
|
-
return totalLines === undefined ? { startLine, endLine } : { startLine, endLine, totalLines };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function buildReadseekFileGroup(path: string, ranges: ReadseekRange[], lines: ReadseekLine[]): ReadseekFileGroup {
|
|
114
|
-
return {
|
|
115
|
-
path,
|
|
116
|
-
ranges: ranges.map((range) => ({ ...range })),
|
|
117
|
-
lines: lines.map((line) => ({ ...line })),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
103
|
export function buildReadseekEditResult(input: {
|
|
122
104
|
ok?: boolean;
|
|
123
105
|
path: string;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* READSEEK error code taxonomy — single source of truth.
|
|
3
|
-
*
|
|
4
|
-
* Every error returned via `readseekValue.error.code` MUST be a key in this map.
|
|
5
|
-
* To add a new error: extend this object with a kebab-case code, a one-line
|
|
6
|
-
* description, and the trigger condition. Codes are stable: renaming is a
|
|
7
|
-
* breaking change for downstream consumers.
|
|
8
|
-
*
|
|
9
|
-
* Distinction:
|
|
10
|
-
* - errors (this file): fatal — the tool could not produce its primary result.
|
|
11
|
-
* - warnings (ReadseekWarning): non-fatal — the tool produced a result but flagged
|
|
12
|
-
* something the agent should know.
|
|
13
|
-
*/
|
|
14
|
-
export const READSEEK_ERROR_CODES = {
|
|
15
|
-
// read
|
|
16
|
-
"file-not-found": { description: "Target file does not exist", trigger: "fs ENOENT on read" },
|
|
17
|
-
"path-is-directory": { description: "Path resolves to a directory, not a file", trigger: "fs EISDIR on read" },
|
|
18
|
-
"permission-denied": { description: "Filesystem refused access", trigger: "fs EACCES or EPERM" },
|
|
19
|
-
"fs-error": { description: "Unexpected filesystem failure outside the specific classified cases", trigger: "non-ENOENT/non-EISDIR/non-EACCES/non-EPERM fs error while reading, writing, or stat'ing a path" },
|
|
20
|
-
"offset-past-end": { description: "Requested offset exceeds file length", trigger: "offset > total lines" },
|
|
21
|
-
"invalid-params-combo": { description: "Mutually exclusive parameters combined", trigger: "e.g. symbol + offset, bundle + map, map + symbol" },
|
|
22
|
-
"invalid-offset": { description: "offset is not a positive integer", trigger: "non-int or value < 1" },
|
|
23
|
-
"invalid-limit": { description: "limit is not a positive integer", trigger: "non-int or value < 1" },
|
|
24
|
-
|
|
25
|
-
// edit
|
|
26
|
-
"file-not-read": { description: "edit called on a path that was not read in this session", trigger: "wasReadInSession returned false" },
|
|
27
|
-
"hash-mismatch": { description: "edit anchors do not verify against current file contents", trigger: "applyHashlineEdits detected stale anchors" },
|
|
28
|
-
"no-op": { description: "edits produced identical content", trigger: "originalNormalized === result after applying edits" },
|
|
29
|
-
"text-not-found": { description: "replace.old_text not present in file", trigger: "replaceText returned 0 matches" },
|
|
30
|
-
"binary-file": { description: "edit refused because file is binary", trigger: "isBinaryBuffer detected NUL bytes" },
|
|
31
|
-
"invalid-edit-variant": { description: "edits[i] is not exactly one of set_line/replace_lines/insert_after/replace", trigger: "exactly-one variant check failed" },
|
|
32
|
-
|
|
33
|
-
// grep
|
|
34
|
-
"binary-file-target": { description: "grep target is a binary file", trigger: "explicit binary path supplied" },
|
|
35
|
-
"passthrough-unparsed": { description: "builtin grep result format unrecognized", trigger: "passthrough warning from underlying tool" },
|
|
36
|
-
|
|
37
|
-
// search
|
|
38
|
-
"readseek-not-installed": { description: "readseek CLI is not installed", trigger: "ENOENT on `readseek` invocation or bundled package resolution failure" },
|
|
39
|
-
"readseek-execution-error": { description: "readseek search exited with an error", trigger: "non-zero exit or invalid search output" },
|
|
40
|
-
|
|
41
|
-
// find / ls (shared shape)
|
|
42
|
-
"path-not-found": { description: "search/target path does not exist", trigger: "stat failed on path" },
|
|
43
|
-
"path-not-directory": { description: "path is a file, not a directory", trigger: "stat returned non-directory" },
|
|
44
|
-
|
|
45
|
-
// write
|
|
46
|
-
"binary-content": { description: "content written to write tool looks binary", trigger: "looksLikeBinary on supplied content" },
|
|
47
|
-
|
|
48
|
-
"syntax-regression": {
|
|
49
|
-
description: "edit introduced new tree-sitter syntax errors compared to pre-edit content",
|
|
50
|
-
trigger: "post-write validator detected net-new ERROR or MISSING nodes (block mode)",
|
|
51
|
-
},
|
|
52
|
-
} as const;
|
|
53
|
-
|
|
54
|
-
export type ReadseekErrorCode = keyof typeof READSEEK_ERROR_CODES;
|