pi-readseek 0.3.2 → 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/README.md +24 -18
- package/index.ts +16 -136
- package/package.json +1 -1
- package/src/edit-output.ts +1 -7
- package/src/edit.ts +3 -14
- package/src/grep-output.ts +0 -26
- package/src/grep.ts +1 -13
- package/src/read-output.ts +1 -27
- package/src/read.ts +1 -10
- package/src/readseek-client.ts +1 -1
- package/src/sg-output.ts +0 -30
- package/src/sg.ts +14 -22
- package/src/write.ts +1 -13
- 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/src/context-hygiene.ts
DELETED
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
/** Metadata used to mask stale read/search results after this extension mutates a file. */
|
|
2
|
-
|
|
3
|
-
export const CONTEXT_HYGIENE_SCHEMA_VERSION = 1 as const;
|
|
4
|
-
export const DEFAULT_CONTEXT_HYGIENE_MAX_EVENTS = 1000;
|
|
5
|
-
|
|
6
|
-
export type ContextHygieneClassification = "read-context" | "search-context" | "mutation";
|
|
7
|
-
|
|
8
|
-
export type ContextHygieneResourceKind = "file" | "symbol";
|
|
9
|
-
|
|
10
|
-
export interface ContextHygieneFileResource {
|
|
11
|
-
kind: "file";
|
|
12
|
-
key: string;
|
|
13
|
-
path: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ContextHygieneSymbolResource {
|
|
17
|
-
kind: "symbol";
|
|
18
|
-
key: string;
|
|
19
|
-
path: string;
|
|
20
|
-
symbolName: string;
|
|
21
|
-
symbolKind?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type ContextHygieneResource = ContextHygieneFileResource | ContextHygieneSymbolResource;
|
|
25
|
-
|
|
26
|
-
export interface ContextHygieneReadRehydrateInput {
|
|
27
|
-
path: string;
|
|
28
|
-
offset?: number | string;
|
|
29
|
-
limit?: number | string;
|
|
30
|
-
symbol?: string;
|
|
31
|
-
map?: true;
|
|
32
|
-
bundle?: "local";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ContextHygieneGrepRehydrateInput {
|
|
36
|
-
pattern: string;
|
|
37
|
-
path?: string;
|
|
38
|
-
glob?: string;
|
|
39
|
-
literal?: true;
|
|
40
|
-
ignoreCase?: true;
|
|
41
|
-
context?: number | string;
|
|
42
|
-
summary?: true;
|
|
43
|
-
scope?: "symbol";
|
|
44
|
-
scopeContext?: number | string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ContextHygieneSearchRehydrateInput {
|
|
48
|
-
pattern: string;
|
|
49
|
-
lang?: string;
|
|
50
|
-
path?: string;
|
|
51
|
-
cached?: true;
|
|
52
|
-
others?: true;
|
|
53
|
-
ignored?: true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface ContextHygieneReadRehydrateDescriptor {
|
|
57
|
-
tool: "read";
|
|
58
|
-
input: ContextHygieneReadRehydrateInput;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface ContextHygieneGrepRehydrateDescriptor {
|
|
62
|
-
tool: "grep";
|
|
63
|
-
input: ContextHygieneGrepRehydrateInput;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface ContextHygieneSearchRehydrateDescriptor {
|
|
67
|
-
tool: "search";
|
|
68
|
-
input: ContextHygieneSearchRehydrateInput;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export type ContextHygieneRehydrateDescriptor =
|
|
72
|
-
| ContextHygieneReadRehydrateDescriptor
|
|
73
|
-
| ContextHygieneGrepRehydrateDescriptor
|
|
74
|
-
| ContextHygieneSearchRehydrateDescriptor;
|
|
75
|
-
|
|
76
|
-
export type ContextHygieneStaleInvalidationReason = "mutation-after-read";
|
|
77
|
-
|
|
78
|
-
export interface ContextHygieneStaleRecord {
|
|
79
|
-
status: "stale";
|
|
80
|
-
originalTool: string;
|
|
81
|
-
originalEventId?: number;
|
|
82
|
-
originalResultId?: string;
|
|
83
|
-
staleResourceKeys: string[];
|
|
84
|
-
invalidatingMutationEventId: number;
|
|
85
|
-
invalidatingMutationResultId?: string;
|
|
86
|
-
reason: ContextHygieneStaleInvalidationReason;
|
|
87
|
-
rehydrate?: ContextHygieneRehydrateDescriptor;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface BuildStaleContextRecordInput {
|
|
91
|
-
originalTool: string;
|
|
92
|
-
originalEventId?: number;
|
|
93
|
-
originalResultId?: string;
|
|
94
|
-
staleResourceKeys: readonly string[];
|
|
95
|
-
invalidatingMutationEventId: number;
|
|
96
|
-
invalidatingMutationResultId?: string;
|
|
97
|
-
reason?: ContextHygieneStaleInvalidationReason;
|
|
98
|
-
rehydrate?: ContextHygieneRehydrateDescriptor;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function cloneContextHygieneRehydrateDescriptor(
|
|
102
|
-
descriptor: ContextHygieneRehydrateDescriptor,
|
|
103
|
-
): ContextHygieneRehydrateDescriptor {
|
|
104
|
-
switch (descriptor.tool) {
|
|
105
|
-
case "read":
|
|
106
|
-
return { tool: "read", input: { ...descriptor.input } };
|
|
107
|
-
case "grep":
|
|
108
|
-
return { tool: "grep", input: { ...descriptor.input } };
|
|
109
|
-
case "search":
|
|
110
|
-
return { tool: "search", input: { ...descriptor.input } };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function buildStaleContextRecord(input: BuildStaleContextRecordInput): ContextHygieneStaleRecord {
|
|
115
|
-
const record: ContextHygieneStaleRecord = {
|
|
116
|
-
status: "stale",
|
|
117
|
-
originalTool: input.originalTool,
|
|
118
|
-
staleResourceKeys: sortResourceKeys(new Set(input.staleResourceKeys)),
|
|
119
|
-
invalidatingMutationEventId: input.invalidatingMutationEventId,
|
|
120
|
-
reason: input.reason ?? "mutation-after-read",
|
|
121
|
-
};
|
|
122
|
-
if (input.originalEventId !== undefined) record.originalEventId = input.originalEventId;
|
|
123
|
-
if (input.originalResultId) record.originalResultId = input.originalResultId;
|
|
124
|
-
if (input.invalidatingMutationResultId) record.invalidatingMutationResultId = input.invalidatingMutationResultId;
|
|
125
|
-
if (input.rehydrate) record.rehydrate = cloneContextHygieneRehydrateDescriptor(input.rehydrate);
|
|
126
|
-
return record;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function renderStaleReadPlaceholder(): string {
|
|
130
|
-
return "[Stale read result — this earlier read was superseded by a later file change; nothing is wrong with read. Run read again for current content.]";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function renderStaleGrepPlaceholder(): string {
|
|
134
|
-
return "[Stale grep result — this earlier grep was superseded by a later file change; nothing is wrong with grep. Run grep again for current matches.]";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function renderStaleSearchPlaceholder(): string {
|
|
138
|
-
return "[Stale search result — this earlier search was superseded by a later file change; nothing is wrong with search. Run search again for current matches.]";
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function renderStaleContextPlaceholder(record: ContextHygieneStaleRecord): string {
|
|
142
|
-
switch (record.originalTool) {
|
|
143
|
-
case "read":
|
|
144
|
-
return renderStaleReadPlaceholder();
|
|
145
|
-
case "grep":
|
|
146
|
-
return renderStaleGrepPlaceholder();
|
|
147
|
-
case "search":
|
|
148
|
-
return renderStaleSearchPlaceholder();
|
|
149
|
-
default:
|
|
150
|
-
return "[Stale tool context: resource content changed after this result. Re-run the original tool to refresh.]";
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export interface BuildReadRehydrateDescriptorInput {
|
|
155
|
-
path: string;
|
|
156
|
-
offset?: number | string;
|
|
157
|
-
limit?: number | string;
|
|
158
|
-
symbol?: string;
|
|
159
|
-
map?: boolean;
|
|
160
|
-
bundle?: "local";
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function buildReadRehydrateDescriptor(
|
|
164
|
-
input: BuildReadRehydrateDescriptorInput,
|
|
165
|
-
): ContextHygieneReadRehydrateDescriptor {
|
|
166
|
-
const descriptorInput: ContextHygieneReadRehydrateInput = { path: input.path };
|
|
167
|
-
if (input.offset !== undefined) descriptorInput.offset = input.offset;
|
|
168
|
-
if (input.limit !== undefined) descriptorInput.limit = input.limit;
|
|
169
|
-
if (input.symbol !== undefined) descriptorInput.symbol = input.symbol;
|
|
170
|
-
if (input.map === true) descriptorInput.map = true;
|
|
171
|
-
if (input.bundle !== undefined) descriptorInput.bundle = input.bundle;
|
|
172
|
-
return { tool: "read", input: descriptorInput };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export interface BuildGrepRehydrateDescriptorInput {
|
|
176
|
-
pattern: string;
|
|
177
|
-
path?: string;
|
|
178
|
-
glob?: string;
|
|
179
|
-
literal?: boolean;
|
|
180
|
-
ignoreCase?: boolean;
|
|
181
|
-
context?: number | string;
|
|
182
|
-
summary?: boolean;
|
|
183
|
-
scope?: "symbol";
|
|
184
|
-
scopeContext?: number | string;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function buildGrepRehydrateDescriptor(
|
|
188
|
-
input: BuildGrepRehydrateDescriptorInput,
|
|
189
|
-
): ContextHygieneGrepRehydrateDescriptor {
|
|
190
|
-
const descriptorInput: ContextHygieneGrepRehydrateInput = { pattern: input.pattern };
|
|
191
|
-
if (input.path !== undefined) descriptorInput.path = input.path;
|
|
192
|
-
if (input.glob !== undefined) descriptorInput.glob = input.glob;
|
|
193
|
-
if (input.literal === true) descriptorInput.literal = true;
|
|
194
|
-
if (input.ignoreCase === true) descriptorInput.ignoreCase = true;
|
|
195
|
-
if (input.context !== undefined) descriptorInput.context = input.context;
|
|
196
|
-
if (input.summary === true) descriptorInput.summary = true;
|
|
197
|
-
if (input.scope !== undefined) descriptorInput.scope = input.scope;
|
|
198
|
-
if (input.scopeContext !== undefined) descriptorInput.scopeContext = input.scopeContext;
|
|
199
|
-
return { tool: "grep", input: descriptorInput };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export interface BuildSearchRehydrateDescriptorInput {
|
|
203
|
-
pattern: string;
|
|
204
|
-
lang?: string;
|
|
205
|
-
path?: string;
|
|
206
|
-
cached?: boolean;
|
|
207
|
-
others?: boolean;
|
|
208
|
-
ignored?: boolean;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export function buildSearchRehydrateDescriptor(
|
|
212
|
-
input: BuildSearchRehydrateDescriptorInput,
|
|
213
|
-
): ContextHygieneSearchRehydrateDescriptor {
|
|
214
|
-
const descriptorInput: ContextHygieneSearchRehydrateInput = { pattern: input.pattern };
|
|
215
|
-
if (input.lang !== undefined) descriptorInput.lang = input.lang;
|
|
216
|
-
if (input.path !== undefined) descriptorInput.path = input.path;
|
|
217
|
-
if (input.cached === true) descriptorInput.cached = true;
|
|
218
|
-
if (input.others === true) descriptorInput.others = true;
|
|
219
|
-
if (input.ignored === true) descriptorInput.ignored = true;
|
|
220
|
-
return { tool: "search", input: descriptorInput };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export interface ContextHygieneMetadata {
|
|
224
|
-
schemaVersion: typeof CONTEXT_HYGIENE_SCHEMA_VERSION;
|
|
225
|
-
tool: string;
|
|
226
|
-
classification: ContextHygieneClassification;
|
|
227
|
-
resources: ContextHygieneResource[];
|
|
228
|
-
rehydrate?: ContextHygieneRehydrateDescriptor;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export interface BuildContextHygieneMetadataInput {
|
|
232
|
-
tool: string;
|
|
233
|
-
classification: ContextHygieneClassification;
|
|
234
|
-
resources?: readonly (ContextHygieneResource | null | undefined)[];
|
|
235
|
-
rehydrate?: ContextHygieneRehydrateDescriptor | null;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export function normalizePathForContextHygiene(path: string): string {
|
|
239
|
-
if (path === "") return "";
|
|
240
|
-
|
|
241
|
-
const slashPath = path.replace(/\\+/g, "/");
|
|
242
|
-
const isAbsolute = slashPath.startsWith("/");
|
|
243
|
-
const parts: string[] = [];
|
|
244
|
-
|
|
245
|
-
for (const part of slashPath.split("/")) {
|
|
246
|
-
if (!part || part === ".") continue;
|
|
247
|
-
if (part === "..") {
|
|
248
|
-
if (parts.length > 0 && parts[parts.length - 1] !== "..") {
|
|
249
|
-
parts.pop();
|
|
250
|
-
} else if (!isAbsolute) {
|
|
251
|
-
parts.push(part);
|
|
252
|
-
}
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
parts.push(part);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const normalized = `${isAbsolute ? "/" : ""}${parts.join("/")}`;
|
|
259
|
-
return normalized || (isAbsolute ? "/" : ".");
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export function buildFileResource(path: string): ContextHygieneFileResource {
|
|
263
|
-
const normalizedPath = normalizePathForContextHygiene(path);
|
|
264
|
-
return {
|
|
265
|
-
kind: "file",
|
|
266
|
-
key: `file:${normalizedPath}`,
|
|
267
|
-
path: normalizedPath,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function buildSymbolResource(
|
|
272
|
-
path: string,
|
|
273
|
-
symbolName: string,
|
|
274
|
-
symbolKind?: string,
|
|
275
|
-
): ContextHygieneSymbolResource {
|
|
276
|
-
const normalizedPath = normalizePathForContextHygiene(path);
|
|
277
|
-
const normalizedKind = symbolKind?.trim();
|
|
278
|
-
const keyPayload = JSON.stringify([normalizedPath, normalizedKind ?? "", symbolName]);
|
|
279
|
-
const resource: ContextHygieneSymbolResource = {
|
|
280
|
-
kind: "symbol",
|
|
281
|
-
key: `symbol:${keyPayload}`,
|
|
282
|
-
path: normalizedPath,
|
|
283
|
-
symbolName,
|
|
284
|
-
};
|
|
285
|
-
if (normalizedKind) resource.symbolKind = normalizedKind;
|
|
286
|
-
return resource;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export function buildContextHygieneMetadata(
|
|
290
|
-
input: BuildContextHygieneMetadataInput,
|
|
291
|
-
): ContextHygieneMetadata {
|
|
292
|
-
const resources: ContextHygieneResource[] = [];
|
|
293
|
-
const seenResourceKeys = new Set<string>();
|
|
294
|
-
|
|
295
|
-
for (const resource of input.resources ?? []) {
|
|
296
|
-
if (!resource || seenResourceKeys.has(resource.key)) continue;
|
|
297
|
-
seenResourceKeys.add(resource.key);
|
|
298
|
-
resources.push({ ...resource } as ContextHygieneResource);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const metadata: ContextHygieneMetadata = {
|
|
302
|
-
schemaVersion: CONTEXT_HYGIENE_SCHEMA_VERSION,
|
|
303
|
-
tool: input.tool,
|
|
304
|
-
classification: input.classification,
|
|
305
|
-
resources,
|
|
306
|
-
};
|
|
307
|
-
if (input.rehydrate) metadata.rehydrate = cloneContextHygieneRehydrateDescriptor(input.rehydrate);
|
|
308
|
-
return metadata;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export interface ContextHygieneRecordOptions {
|
|
312
|
-
resultId?: string;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export interface ContextHygieneEvent {
|
|
316
|
-
id: number;
|
|
317
|
-
resultId?: string;
|
|
318
|
-
tool: string;
|
|
319
|
-
classification: ContextHygieneClassification;
|
|
320
|
-
resources: ContextHygieneResource[];
|
|
321
|
-
rehydrate?: ContextHygieneRehydrateDescriptor;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export interface ContextHygieneReuseReportEntry {
|
|
325
|
-
resourceKey: string;
|
|
326
|
-
count: number;
|
|
327
|
-
eventIds: number[];
|
|
328
|
-
resultIds: string[];
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
export interface ContextHygieneMutationAfterReadReportEntry {
|
|
332
|
-
resourceKey: string;
|
|
333
|
-
readEventIds: number[];
|
|
334
|
-
mutationEventId: number;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export interface ContextHygieneStaleCandidateReportEntry {
|
|
338
|
-
resourceKey: string;
|
|
339
|
-
staleEventIds: number[];
|
|
340
|
-
mutationEventId: number;
|
|
341
|
-
reason: ContextHygieneStaleInvalidationReason;
|
|
342
|
-
staleResults: ContextHygieneStaleRecord[];
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export interface ContextHygieneReport {
|
|
346
|
-
eventCount: number;
|
|
347
|
-
resourceCount: number;
|
|
348
|
-
readReuse: ContextHygieneReuseReportEntry[];
|
|
349
|
-
mutationAfterRead: ContextHygieneMutationAfterReadReportEntry[];
|
|
350
|
-
staleCandidates: ContextHygieneStaleCandidateReportEntry[];
|
|
351
|
-
churn: {
|
|
352
|
-
byClassification: Record<ContextHygieneClassification, number>;
|
|
353
|
-
byTool: Record<string, number>;
|
|
354
|
-
uniqueResourcesSeen: number;
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
export interface ContextHygieneTracker {
|
|
359
|
-
record(metadata: ContextHygieneMetadata, options?: ContextHygieneRecordOptions): ContextHygieneEvent;
|
|
360
|
-
generateReport(): ContextHygieneReport;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
export interface CreateContextHygieneTrackerOptions {
|
|
364
|
-
maxEvents?: number;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function resultIdsForEvents(events: ContextHygieneEvent[]): string[] {
|
|
368
|
-
return events.map((event) => event.resultId).filter((resultId): resultId is string => Boolean(resultId));
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function cloneContextHygieneEvent(event: ContextHygieneEvent): ContextHygieneEvent {
|
|
372
|
-
const cloned: ContextHygieneEvent = {
|
|
373
|
-
...event,
|
|
374
|
-
resources: event.resources.map((resource) => ({ ...resource } as ContextHygieneResource)),
|
|
375
|
-
};
|
|
376
|
-
if (event.rehydrate) cloned.rehydrate = cloneContextHygieneRehydrateDescriptor(event.rehydrate);
|
|
377
|
-
return cloned;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function compareStable(left: string, right: string): number {
|
|
381
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function sortResourceKeys(keys: Iterable<string>): string[] {
|
|
385
|
-
return [...keys].sort(compareStable);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function createEmptyClassificationCounts(): Record<ContextHygieneClassification, number> {
|
|
389
|
-
return {
|
|
390
|
-
mutation: 0,
|
|
391
|
-
"read-context": 0,
|
|
392
|
-
"search-context": 0,
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
class DefaultContextHygieneTracker implements ContextHygieneTracker {
|
|
397
|
-
private readonly events: ContextHygieneEvent[] = [];
|
|
398
|
-
private readonly maxEvents: number;
|
|
399
|
-
private nextEventId = 1;
|
|
400
|
-
|
|
401
|
-
constructor(options: CreateContextHygieneTrackerOptions = {}) {
|
|
402
|
-
this.maxEvents = Math.max(1, Math.floor(options.maxEvents ?? DEFAULT_CONTEXT_HYGIENE_MAX_EVENTS));
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
record(metadata: ContextHygieneMetadata, options: ContextHygieneRecordOptions = {}): ContextHygieneEvent {
|
|
406
|
-
const event: ContextHygieneEvent = {
|
|
407
|
-
id: this.nextEventId++,
|
|
408
|
-
tool: metadata.tool,
|
|
409
|
-
classification: metadata.classification,
|
|
410
|
-
resources: metadata.resources.map((resource) => ({ ...resource } as ContextHygieneResource)),
|
|
411
|
-
};
|
|
412
|
-
if (options.resultId) event.resultId = options.resultId;
|
|
413
|
-
if (metadata.rehydrate) event.rehydrate = cloneContextHygieneRehydrateDescriptor(metadata.rehydrate);
|
|
414
|
-
this.events.push(event);
|
|
415
|
-
if (this.events.length > this.maxEvents) this.events.splice(0, this.events.length - this.maxEvents);
|
|
416
|
-
return cloneContextHygieneEvent(event);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
generateReport(): ContextHygieneReport {
|
|
420
|
-
const eventsByResource = new Map<string, ContextHygieneEvent[]>();
|
|
421
|
-
const readEventsByResource = new Map<string, ContextHygieneEvent[]>();
|
|
422
|
-
const mutationEventsByResource = new Map<string, ContextHygieneEvent[]>();
|
|
423
|
-
const byClassification = createEmptyClassificationCounts();
|
|
424
|
-
const byTool: Record<string, number> = {};
|
|
425
|
-
|
|
426
|
-
for (const event of this.events) {
|
|
427
|
-
byClassification[event.classification] += 1;
|
|
428
|
-
byTool[event.tool] = (byTool[event.tool] ?? 0) + 1;
|
|
429
|
-
|
|
430
|
-
for (const resource of event.resources) {
|
|
431
|
-
const bucket = eventsByResource.get(resource.key) ?? [];
|
|
432
|
-
bucket.push(event);
|
|
433
|
-
eventsByResource.set(resource.key, bucket);
|
|
434
|
-
|
|
435
|
-
if (event.classification === "read-context" || event.classification === "search-context") {
|
|
436
|
-
const readBucket = readEventsByResource.get(resource.key) ?? [];
|
|
437
|
-
readBucket.push(event);
|
|
438
|
-
readEventsByResource.set(resource.key, readBucket);
|
|
439
|
-
}
|
|
440
|
-
if (event.classification === "mutation") {
|
|
441
|
-
const mutationBucket = mutationEventsByResource.get(resource.key) ?? [];
|
|
442
|
-
mutationBucket.push(event);
|
|
443
|
-
mutationEventsByResource.set(resource.key, mutationBucket);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const readReuse = sortResourceKeys(readEventsByResource.keys()).flatMap((resourceKey) => {
|
|
449
|
-
const events = readEventsByResource.get(resourceKey) ?? [];
|
|
450
|
-
if (events.length < 2) return [];
|
|
451
|
-
return [{ resourceKey, count: events.length, eventIds: events.map((event) => event.id), resultIds: resultIdsForEvents(events) }];
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
const mutationAfterRead: ContextHygieneMutationAfterReadReportEntry[] = [];
|
|
455
|
-
const staleCandidates: ContextHygieneStaleCandidateReportEntry[] = [];
|
|
456
|
-
|
|
457
|
-
for (const resourceKey of sortResourceKeys(mutationEventsByResource.keys())) {
|
|
458
|
-
const reads = readEventsByResource.get(resourceKey) ?? [];
|
|
459
|
-
const mutations = mutationEventsByResource.get(resourceKey) ?? [];
|
|
460
|
-
for (const mutation of mutations) {
|
|
461
|
-
const priorReads = reads.filter((read) => read.id < mutation.id);
|
|
462
|
-
const priorReadIds = priorReads.map((read) => read.id);
|
|
463
|
-
if (priorReadIds.length === 0) continue;
|
|
464
|
-
mutationAfterRead.push({ resourceKey, readEventIds: priorReadIds, mutationEventId: mutation.id });
|
|
465
|
-
staleCandidates.push({
|
|
466
|
-
resourceKey,
|
|
467
|
-
staleEventIds: priorReadIds,
|
|
468
|
-
mutationEventId: mutation.id,
|
|
469
|
-
reason: "mutation-after-read",
|
|
470
|
-
staleResults: priorReads.map((read) => buildStaleContextRecord({
|
|
471
|
-
originalTool: read.tool,
|
|
472
|
-
originalEventId: read.id,
|
|
473
|
-
originalResultId: read.resultId,
|
|
474
|
-
staleResourceKeys: [resourceKey],
|
|
475
|
-
invalidatingMutationEventId: mutation.id,
|
|
476
|
-
invalidatingMutationResultId: mutation.resultId,
|
|
477
|
-
reason: "mutation-after-read",
|
|
478
|
-
rehydrate: read.rehydrate,
|
|
479
|
-
})),
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
return {
|
|
485
|
-
eventCount: this.events.length,
|
|
486
|
-
resourceCount: eventsByResource.size,
|
|
487
|
-
readReuse,
|
|
488
|
-
mutationAfterRead,
|
|
489
|
-
staleCandidates,
|
|
490
|
-
churn: {
|
|
491
|
-
byClassification,
|
|
492
|
-
byTool: Object.fromEntries(Object.entries(byTool).sort(([left], [right]) => compareStable(left, right))),
|
|
493
|
-
uniqueResourcesSeen: eventsByResource.size,
|
|
494
|
-
},
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
export function createContextHygieneTracker(options: CreateContextHygieneTrackerOptions = {}): ContextHygieneTracker {
|
|
500
|
-
return new DefaultContextHygieneTracker(options);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
let globalContextHygieneTracker = createContextHygieneTracker();
|
|
504
|
-
|
|
505
|
-
export function resetContextHygieneTracker(options: CreateContextHygieneTrackerOptions = {}): ContextHygieneTracker {
|
|
506
|
-
globalContextHygieneTracker = createContextHygieneTracker(options);
|
|
507
|
-
return globalContextHygieneTracker;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
export function getContextHygieneTracker(): ContextHygieneTracker {
|
|
511
|
-
return globalContextHygieneTracker;
|
|
512
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// Static per-tool suggestion table used by the doom-loop warning formatter.
|
|
2
|
-
//
|
|
3
|
-
// UPKEEP: these strings are static and must be maintained alongside any
|
|
4
|
-
// tool-schema changes in src/read.ts, src/edit.ts, src/grep.ts, src/sg.ts,
|
|
5
|
-
// src/find.ts, and src/ls.ts. When a tool adds/renames a parameter, update
|
|
6
|
-
// the matching entry below so the suggestion remains accurate.
|
|
7
|
-
|
|
8
|
-
export const SUGGESTIONS: Record<string, readonly string[]> = {
|
|
9
|
-
grep: [
|
|
10
|
-
"try ignoreCase: true",
|
|
11
|
-
"try literal: true if pattern has special characters",
|
|
12
|
-
"try a narrower glob or path",
|
|
13
|
-
"switch to search for structural patterns",
|
|
14
|
-
"try summary: true to scope broader",
|
|
15
|
-
],
|
|
16
|
-
read: [
|
|
17
|
-
"if searching for a symbol, use symbol: or map: true",
|
|
18
|
-
"if file is large, try offset + limit",
|
|
19
|
-
"if file keeps being read identically, the content may already be what you expect",
|
|
20
|
-
],
|
|
21
|
-
edit: [
|
|
22
|
-
"if hash-mismatch keeps firing, re-read the file",
|
|
23
|
-
"if no-op keeps firing, your new_text equals current content",
|
|
24
|
-
"verify the anchor came from the most recent read/grep/search",
|
|
25
|
-
],
|
|
26
|
-
search: [
|
|
27
|
-
'check the lang parameter matches file type (e.g. lang: "tsx" for JSX)',
|
|
28
|
-
"simplify the pattern with $_ or $$$ wildcards",
|
|
29
|
-
"verify readseek is installed",
|
|
30
|
-
],
|
|
31
|
-
find: [
|
|
32
|
-
"try a looser glob",
|
|
33
|
-
'try type: "any"',
|
|
34
|
-
"try a different path",
|
|
35
|
-
],
|
|
36
|
-
ls: [
|
|
37
|
-
"try a different path",
|
|
38
|
-
"remove the glob filter",
|
|
39
|
-
],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const GENERIC_SUGGESTION = "try a different approach — the repeating call is not making progress";
|