sift-light 1.1.0 → 1.1.1-2

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.
@@ -1012,7 +1012,7 @@ export class EvidenceService {
1012
1012
  if (selected.unverified)
1013
1013
  throw new SiftLightError("Snapshot source revision is unverified; refresh the search");
1014
1014
  if (selected.expectedRevision) {
1015
- const doc = await access.load(path);
1015
+ const doc = await access.load(path, selected.reference);
1016
1016
  if (
1017
1017
  doc.reference.origin.kind !== "worktree" ||
1018
1018
  !sameSourceRevision(selected.expectedRevision, doc.reference.origin.revision)
@@ -9,7 +9,7 @@ import { SourceAccess, SourceBudgetError, SyntaxQueue } from "./source-access.js
9
9
  import { SourceDocumentError, type SourceReference } from "./source-document.js";
10
10
  import { SnapshotStore } from "./snapshot-store.js";
11
11
  import type { AnalysisItem } from "./analysis-types.js";
12
- import type { SearchRequest, SearchScopeDetails, SearchSnapshot, SourceRevision } from "./types.js";
12
+ import type { SearchRequest, SearchScopeDetails, SearchSnapshot } from "./types.js";
13
13
  import type {
14
14
  EvidenceCoverageStatus,
15
15
  EvidenceComparisonTarget,
@@ -46,7 +46,6 @@ interface ValidationTarget {
46
46
  key: string;
47
47
  path: string;
48
48
  expected?: SourceReference;
49
- revision?: SourceRevision;
50
49
  }
51
50
 
52
51
  interface ClassifiedFailure {
@@ -156,10 +155,6 @@ function sourceKey(reference: SourceReference): string {
156
155
  return JSON.stringify([reference.path, reference.origin]);
157
156
  }
158
157
 
159
- function revisionKey(path: string, revision: SourceRevision): string {
160
- return JSON.stringify([path, revision]);
161
- }
162
-
163
158
  function selectedIndex(
164
159
  index: number | undefined,
165
160
  count: number,
@@ -231,8 +226,8 @@ function snapshotTargets(
231
226
  const missing: EvidenceSourceStatus[] = [];
232
227
  const seen = new Set<string>();
233
228
  for (const path of selectedPaths) {
234
- const revision = snapshot.sourceRevisions.get(path);
235
- if (!revision) {
229
+ const reference = snapshot.sourceReferences?.get(path);
230
+ if (!reference) {
236
231
  const key = JSON.stringify(["missing", path]);
237
232
  if (!seen.has(key)) {
238
233
  seen.add(key);
@@ -240,82 +235,20 @@ function snapshotTargets(
240
235
  path,
241
236
  role: "source",
242
237
  status: "unknown",
243
- reason: "Saved search has no source revision",
238
+ reason: "Saved search has no verified source content identity",
244
239
  });
245
240
  }
246
241
  continue;
247
242
  }
248
243
  addUniqueTarget(targets, seen, {
249
- key: revisionKey(path, revision),
244
+ key: sourceKey(reference),
250
245
  path,
251
- revision,
246
+ expected: reference,
252
247
  });
253
248
  }
254
249
  return { targets, missing };
255
250
  }
256
251
 
257
- async function validateSnapshotTarget(
258
- target: ValidationTarget,
259
- cwd: string,
260
- policy: SearchPathPolicy,
261
- signal?: AbortSignal,
262
- ): Promise<EvidenceSourceStatus> {
263
- if (!target.revision) throw new Error("Snapshot validation target omitted its revision");
264
- try {
265
- const absolute = resolve(cwd, target.path);
266
- const canonical = await policy.resolveExistingPath(absolute);
267
- if (!canonical) {
268
- const result = await confirmWorktreeState(target.path, cwd, signal);
269
- return { path: target.path, role: "source", ...result };
270
- }
271
- const before = await stat(absolute);
272
- const beforeCanonical = canonical;
273
- const after = await stat(absolute);
274
- const afterCanonical = await realpath(absolute);
275
- if (!before.isFile() || !after.isFile()) {
276
- return {
277
- path: target.path,
278
- role: "source",
279
- status: "stale",
280
- reason: "Source is no longer a regular file",
281
- };
282
- }
283
- if (
284
- beforeCanonical !== afterCanonical ||
285
- !sameSourceRevision(sourceRevisionFromStats(before), sourceRevisionFromStats(after))
286
- ) {
287
- return {
288
- path: target.path,
289
- role: "source",
290
- status: "stale",
291
- reason: "Source changed while checking its revision",
292
- };
293
- }
294
- const current = sourceRevisionFromStats(after);
295
- const unchanged = sameSourceRevision(target.revision, current);
296
- return {
297
- path: target.path,
298
- role: "source",
299
- status: unchanged ? "current" : "stale",
300
- ...(unchanged ? {} : { reason: "Source revision changed" }),
301
- };
302
- } catch (error) {
303
- if (isAbort(error, signal)) throw error;
304
- const code = systemErrorCode(error);
305
- if (code === "ENOENT") {
306
- const classified = await confirmWorktreeState(target.path, cwd, signal);
307
- return { path: target.path, role: "source", ...classified };
308
- }
309
- const classified = await failure(error, undefined, target.path, cwd, signal);
310
- return {
311
- path: target.path,
312
- role: "source",
313
- status: classified.status,
314
- reason: classified.reason,
315
- };
316
- }
317
- }
318
-
319
252
  async function validateAnalysisTarget(
320
253
  target: ValidationTarget,
321
254
  access: SourceAccess,
@@ -426,13 +359,16 @@ export async function validateSavedEvidence(
426
359
  const selected = snapshotTargets(snapshot, options.matchIndex);
427
360
  targets = selected.targets;
428
361
  missing.push(...selected.missing);
429
- const policy = new SearchPathPolicy(options.cwd);
430
362
  sources = [...missing];
431
- let checked = 0;
432
363
  const maxFiles = options.maxFiles ?? MAX_STRUCTURE_FILES;
364
+ const access = new SourceAccess(options.cwd, options.queue, options.signal, {
365
+ maxFiles,
366
+ });
367
+ let checked = 0;
368
+ let budgetExhausted = false;
433
369
  for (const target of targets) {
434
370
  if (options.signal?.aborted) throw abortError();
435
- if (checked >= maxFiles) {
371
+ if (budgetExhausted || checked >= maxFiles) {
436
372
  sources.push({
437
373
  path: target.path,
438
374
  role: "source",
@@ -442,7 +378,10 @@ export async function validateSavedEvidence(
442
378
  continue;
443
379
  }
444
380
  checked += 1;
445
- sources.push(await validateSnapshotTarget(target, options.cwd, policy, options.signal));
381
+ const status = await validateAnalysisTarget(target, access, options.cwd, options.signal);
382
+ const { budgetReached, ...sourceStatus } = status;
383
+ sources.push(sourceStatus);
384
+ if (budgetReached) budgetExhausted = true;
446
385
  }
447
386
  }
448
387
 
package/src/inspect.ts CHANGED
@@ -7,10 +7,12 @@ import {
7
7
  getSourceRevision,
8
8
  readSourceRange,
9
9
  sameSourceRevision,
10
+ sourceRangeFromBytes,
10
11
  SourceBudgetTooSmallError,
11
12
  SourceTooLargeError,
12
13
  type SourceRangeRead,
13
14
  } from "./source.js";
15
+ import { readWorkspaceDocument, type SourceReference } from "./source-document.js";
14
16
  import { SnapshotStore } from "./snapshot-store.js";
15
17
  import {
16
18
  MAX_LINE_CHARACTERS,
@@ -40,6 +42,7 @@ export interface InspectionTarget {
40
42
  absolutePath: string;
41
43
  line: number;
42
44
  retainedMatch?: MatchRecord;
45
+ expectedReference?: SourceReference;
43
46
  expectedRevision?: SourceRevision;
44
47
  unverified: boolean;
45
48
  }
@@ -87,6 +90,7 @@ export function resolveInspectionTarget(
87
90
  const absolutePath = retainedMatch?.absolutePath ?? resolve(cwd, path);
88
91
  new SearchPathPolicy(cwd).assertPath(absolutePath);
89
92
  let expectedRevision: SourceRevision | undefined;
93
+ let expectedReference: SourceReference | undefined;
90
94
  if (input.cursor) {
91
95
  const { snapshot } = snapshots.resolve(input.cursor);
92
96
  retainedMatch ??= snapshot.matches.find(
@@ -96,13 +100,17 @@ export function resolveInspectionTarget(
96
100
  throw new CursorError("The requested line is not a retained match in this snapshot.");
97
101
  }
98
102
  expectedRevision = snapshot.sourceRevisions.get(absolutePath);
103
+ expectedReference = snapshot.sourceReferences?.get(absolutePath);
99
104
  }
100
105
  return {
101
106
  path,
102
107
  absolutePath,
103
108
  line,
104
- unverified: input.cursor !== undefined && expectedRevision === undefined,
109
+ unverified:
110
+ input.cursor !== undefined &&
111
+ (expectedReference === undefined || expectedRevision === undefined),
105
112
  ...(retainedMatch ? { retainedMatch } : {}),
113
+ ...(expectedReference ? { expectedReference } : {}),
106
114
  ...(expectedRevision ? { expectedRevision } : {}),
107
115
  };
108
116
  }
@@ -209,19 +217,40 @@ export async function inspectSourceEvidence(
209
217
  endLine: target.line + 10,
210
218
  };
211
219
  try {
212
- evidence.source = await readSourceRange(
213
- target.absolutePath,
214
- range.startLine,
215
- range.endLine,
216
- signal,
217
- target.line,
218
- {
219
- maxBytes,
220
- ...(target.retainedMatch?.occurrences[0]
221
- ? { focus: target.retainedMatch.occurrences[0] }
222
- : {}),
223
- },
224
- );
220
+ if (target.expectedReference) {
221
+ const document = await readWorkspaceDocument(
222
+ target.path,
223
+ cwd,
224
+ signal,
225
+ target.expectedReference.origin,
226
+ );
227
+ evidence.source = sourceRangeFromBytes(
228
+ document.bytes,
229
+ range.startLine,
230
+ range.endLine,
231
+ target.line,
232
+ {
233
+ maxBytes,
234
+ ...(target.retainedMatch?.occurrences[0]
235
+ ? { focus: target.retainedMatch.occurrences[0] }
236
+ : {}),
237
+ },
238
+ );
239
+ } else {
240
+ evidence.source = await readSourceRange(
241
+ target.absolutePath,
242
+ range.startLine,
243
+ range.endLine,
244
+ signal,
245
+ target.line,
246
+ {
247
+ maxBytes,
248
+ ...(target.retainedMatch?.occurrences[0]
249
+ ? { focus: target.retainedMatch.occurrences[0] }
250
+ : {}),
251
+ },
252
+ );
253
+ }
225
254
  } catch (error) {
226
255
  if (signal?.aborted || (error instanceof Error && error.name === "AbortError"))
227
256
  throw abortError();