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.
- package/CHANGELOG.md +11 -0
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/plugins/sift-light/.claude-plugin/plugin.json +1 -1
- package/plugins/sift-light/.codex-plugin/plugin.json +1 -1
- package/plugins/sift-light/.mcp.json +1 -1
- package/plugins/sift-light/kimi.plugin.json +2 -2
- package/plugins/sift-light/omp-extension.mjs +126 -106
- package/plugins/sift-light/package.json +1 -1
- package/src/evidence-service.ts +1 -1
- package/src/evidence-validation.ts +16 -77
- package/src/inspect.ts +43 -14
- package/src/mcp-server.mjs +122 -102
- package/src/rg.ts +11 -1
- package/src/scan-revisions.ts +78 -1
- package/src/service.ts +1 -1
- package/src/snapshot-store.ts +1 -0
- package/src/source-document.ts +2 -5
- package/src/source-inspection.ts +1 -0
- package/src/types.ts +2 -0
package/src/evidence-service.ts
CHANGED
|
@@ -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
|
|
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
|
|
235
|
-
if (!
|
|
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
|
|
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:
|
|
244
|
+
key: sourceKey(reference),
|
|
250
245
|
path,
|
|
251
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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();
|