sift-light 1.0.1 → 1.0.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 +20 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- 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 +493 -58
- package/plugins/sift-light/package.json +1 -1
- package/src/analysis-limits.ts +1 -1
- package/src/analysis-store.ts +6 -2
- package/src/analysis-types.ts +10 -1
- package/src/evidence-service.ts +117 -13
- package/src/file-discovery.ts +111 -17
- package/src/inspect.ts +3 -0
- package/src/language-capability-definitions.ts +9 -0
- package/src/mcp-model-output.ts +6 -1
- package/src/mcp-server.mjs +495 -59
- package/src/python-roles.ts +247 -0
- package/src/redaction.ts +9 -2
- package/src/request-aliases.ts +48 -0
- package/src/request-contract-catalog.ts +24 -7
- package/src/request-contract.ts +21 -3
- package/src/request.ts +3 -1
- package/src/runtime.ts +2 -2
- package/src/service.ts +23 -1
- package/src/session-summary.ts +4 -4
- package/src/source-inspection.ts +27 -2
- package/src/tool-schema.ts +15 -15
- package/src/tui/layout.ts +2 -2
- package/src/tui/renderers.ts +2 -2
- package/src/types.ts +4 -0
|
@@ -229,6 +229,7 @@ var ESTIMATED_CHARACTERS_PER_TOKEN = 4;
|
|
|
229
229
|
var DEFAULT_SUMMARY_FILE_LIMIT = 30;
|
|
230
230
|
var MAX_SELECTED_PATHS = 20;
|
|
231
231
|
var MAX_INSPECT_TARGETS = 5;
|
|
232
|
+
var MAX_INSPECT_REQUEST_TARGETS = 20;
|
|
232
233
|
var MAX_DISPLAYED_OCCURRENCES = 20;
|
|
233
234
|
var MAX_STORED_MATCHES = 50000;
|
|
234
235
|
var MAX_STORED_OCCURRENCES = 200000;
|
|
@@ -808,6 +809,7 @@ var PRIVATE_KEY = /-----BEGIN ([^-\r\n]*PRIVATE KEY)-----[\s\S]*?-----END \1----
|
|
|
808
809
|
var SENSITIVE_NAME = String.raw`(?:(?:[A-Za-z][A-Za-z0-9]*[_-])*(?:password|passwd|secret|token|api[_-]?key|access[_-]?(?:key|token)|secret[_-]?access[_-]?key|private[_-]?key|service[_-]?key)(?:[_-][A-Za-z0-9]+)*)`;
|
|
809
810
|
var SENSITIVE_ASSIGNMENT = new RegExp(String.raw`((?<![A-Za-z0-9_-])(?:["']?${SENSITIVE_NAME}["']?)\s*[:=]\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^\s,;}\r\n]+)`, "gi");
|
|
810
811
|
var SENSITIVE_TOKEN = /\b(?:sk|ghp|xox[baprs])[-_][A-Za-z0-9][A-Za-z0-9_-]{7,}\b/g;
|
|
812
|
+
var CODE_EXPRESSION = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*\s*[([]/u;
|
|
811
813
|
var TYPE_ONLY_VALUES = new Set([
|
|
812
814
|
"boolean",
|
|
813
815
|
"number",
|
|
@@ -827,10 +829,11 @@ function redactString(value) {
|
|
|
827
829
|
});
|
|
828
830
|
redacted = redacted.replace(SENSITIVE_ASSIGNMENT, (match, prefix, rawValue) => {
|
|
829
831
|
const unquoted = rawValue.replace(/^["']|["']$/g, "").toLowerCase();
|
|
830
|
-
if (TYPE_ONLY_VALUES.has(unquoted))
|
|
832
|
+
if (TYPE_ONLY_VALUES.has(unquoted) || CODE_EXPRESSION.test(rawValue))
|
|
831
833
|
return match;
|
|
832
834
|
count += 1;
|
|
833
|
-
|
|
835
|
+
const quote = rawValue.startsWith('"') || rawValue.startsWith("'") ? rawValue[0] : "";
|
|
836
|
+
return `${prefix}${quote}[REDACTED]${quote}`;
|
|
834
837
|
});
|
|
835
838
|
redacted = redacted.replace(SENSITIVE_TOKEN, () => {
|
|
836
839
|
count += 1;
|
|
@@ -1819,7 +1822,7 @@ function createCtagsStructureProvider(options = {}) {
|
|
|
1819
1822
|
// package.json
|
|
1820
1823
|
var package_default = {
|
|
1821
1824
|
name: "sift-light",
|
|
1822
|
-
version: "1.0.
|
|
1825
|
+
version: "1.0.2",
|
|
1823
1826
|
description: "Context-efficient local search for files, documents, notes and logs across Pi, OMP and MCP clients",
|
|
1824
1827
|
keywords: [
|
|
1825
1828
|
"ai-agent",
|
|
@@ -2281,7 +2284,7 @@ function normalizeRequest(input) {
|
|
|
2281
2284
|
throw new SiftLightError("ignorePolicy must be respect or include");
|
|
2282
2285
|
const pattern = input.pattern;
|
|
2283
2286
|
if (pattern === undefined) {
|
|
2284
|
-
throw new SiftLightError(
|
|
2287
|
+
throw new SiftLightError('pattern is required when cursor is not provided; to list files use mode="files" (query optional), to read a known file use mode="inspect" with path');
|
|
2285
2288
|
}
|
|
2286
2289
|
const path = input.path?.replace(/^@/, "");
|
|
2287
2290
|
return {
|
|
@@ -2321,7 +2324,7 @@ var MAX_ANALYSIS_STORAGE_BYTES = 32 * 1024 * 1024;
|
|
|
2321
2324
|
var ANALYSIS_METADATA_RESERVE_BYTES = 64 * 1024;
|
|
2322
2325
|
var MAX_ANALYSIS_REASONS = 64;
|
|
2323
2326
|
var MAX_ANALYSIS_REASON_BYTES = 4 * 1024;
|
|
2324
|
-
var MIN_ANY_OF_TERMS =
|
|
2327
|
+
var MIN_ANY_OF_TERMS = 1;
|
|
2325
2328
|
var MAX_ANY_OF_TERMS = 8;
|
|
2326
2329
|
var MAX_ANY_OF_TOTAL_TERMS = 64;
|
|
2327
2330
|
var MAX_LITERAL_TERM_BYTES = 256;
|
|
@@ -4038,6 +4041,15 @@ var PYTHON_LANGUAGE_CAPABILITIES = [
|
|
|
4038
4041
|
evidence: "syntax",
|
|
4039
4042
|
availability: "implemented",
|
|
4040
4043
|
load: "lazy"
|
|
4044
|
+
},
|
|
4045
|
+
{
|
|
4046
|
+
id: "python-lexical.roles",
|
|
4047
|
+
name: "roles",
|
|
4048
|
+
provider: "bounded Python lexical role scanner",
|
|
4049
|
+
providerKind: "builtin",
|
|
4050
|
+
evidence: "syntax",
|
|
4051
|
+
availability: "implemented",
|
|
4052
|
+
load: "lazy"
|
|
4041
4053
|
}
|
|
4042
4054
|
];
|
|
4043
4055
|
var DEFAULT_LANGUAGE_CAPABILITIES = [
|
|
@@ -5487,7 +5499,7 @@ function publicAnalysisItem(result, item, index, storedId, modelOutput) {
|
|
|
5487
5499
|
return {
|
|
5488
5500
|
path: item.path,
|
|
5489
5501
|
line: item.line,
|
|
5490
|
-
label: result.kind === "outline" && modelOutput ? item.label : publicAnalysisLabel(result),
|
|
5502
|
+
label: result.kind === "outline" && modelOutput || result.kind === "files" ? item.label : publicAnalysisLabel(result),
|
|
5491
5503
|
index: index + 1,
|
|
5492
5504
|
...inspect ? { inspect } : {},
|
|
5493
5505
|
...publicDetails ? { details: publicDetails } : {}
|
|
@@ -5698,7 +5710,8 @@ Inspect: ${JSON.stringify(inspect)}` : ""}`;
|
|
|
5698
5710
|
break;
|
|
5699
5711
|
}
|
|
5700
5712
|
} else {
|
|
5701
|
-
|
|
5713
|
+
const pageSize = result.pageSize ?? 30;
|
|
5714
|
+
for (let index = offset;index < result.items.length && items.length < pageSize; index += 1) {
|
|
5702
5715
|
if (!appendItem(index))
|
|
5703
5716
|
break;
|
|
5704
5717
|
}
|
|
@@ -5812,6 +5825,8 @@ function resolveInspectionTarget(input, cwd, snapshots) {
|
|
|
5812
5825
|
}
|
|
5813
5826
|
if (!path)
|
|
5814
5827
|
throw new SiftLightError("path is required when mode=inspect");
|
|
5828
|
+
if (line === undefined && input.cursor === undefined)
|
|
5829
|
+
line = 1;
|
|
5815
5830
|
if (line === undefined || !Number.isSafeInteger(line) || line < 1) {
|
|
5816
5831
|
throw new SiftLightError("line must be a positive integer when mode=inspect");
|
|
5817
5832
|
}
|
|
@@ -7540,6 +7555,28 @@ async function filterPathsByModificationTime(cwd, paths, modifiedAfterMs, modifi
|
|
|
7540
7555
|
// src/file-discovery.ts
|
|
7541
7556
|
var graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
7542
7557
|
var FILE_QUERY_GLOB_WILDCARD = /[*?]/u;
|
|
7558
|
+
var FILE_QUERY_MATCH_ALL = /^(?:[*/]+|\.\*|\.\+|\*\.\*)$/u;
|
|
7559
|
+
var FILE_QUERY_REGEX_SYNTAX = /[()|^$\\+]/u;
|
|
7560
|
+
var IGNORED_MATCH_SAMPLES = 5;
|
|
7561
|
+
function normalizeFileQuery(query, hasGlob) {
|
|
7562
|
+
const trimmed = query.trim();
|
|
7563
|
+
if (FILE_QUERY_MATCH_ALL.test(trimmed))
|
|
7564
|
+
return {
|
|
7565
|
+
query: "",
|
|
7566
|
+
note: `Query ${JSON.stringify(query)} was treated as listing every file under path; omit query for the same result.`
|
|
7567
|
+
};
|
|
7568
|
+
if (!FILE_QUERY_GLOB_WILDCARD.test(trimmed))
|
|
7569
|
+
return { query };
|
|
7570
|
+
if (!hasGlob && !FILE_QUERY_REGEX_SYNTAX.test(trimmed) && !/\s/u.test(trimmed)) {
|
|
7571
|
+
const glob = trimmed.includes("/") ? trimmed : `**/${trimmed}`;
|
|
7572
|
+
return {
|
|
7573
|
+
query: "",
|
|
7574
|
+
glob,
|
|
7575
|
+
note: `Query ${JSON.stringify(query)} contains glob wildcards and was applied as glob ${JSON.stringify(glob)}; mode=files query otherwise matches filename text.`
|
|
7576
|
+
};
|
|
7577
|
+
}
|
|
7578
|
+
throw new SiftLightError(`File query ${JSON.stringify(query)} uses wildcard or regex syntax; mode=files matches filename and path text, not patterns. Omit query to retain every file under path, use glob for one name pattern, or run one files request per name.`);
|
|
7579
|
+
}
|
|
7543
7580
|
function subsequenceScore(text, query) {
|
|
7544
7581
|
const characters = Array.from(graphemes.segment(text), (item) => item.segment);
|
|
7545
7582
|
const queryCharacters = Array.from(graphemes.segment(query), (item) => item.segment);
|
|
@@ -7590,39 +7627,79 @@ function pathRelativeToDiscoveryRoot(cwd, root, path) {
|
|
|
7590
7627
|
return scoped || platformBasename(absolutePath);
|
|
7591
7628
|
}
|
|
7592
7629
|
async function discoverFiles(input, cwd, signal) {
|
|
7593
|
-
const
|
|
7594
|
-
if (
|
|
7630
|
+
const rawQuery = input.query ?? "";
|
|
7631
|
+
if (rawQuery.length > 256 || !rawQuery.isWellFormed() || /[\r\n\0]/.test(rawQuery))
|
|
7595
7632
|
throw new SiftLightError("File query must be well-formed single-line text of at most 256 characters");
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
const
|
|
7633
|
+
const requestedGlobs = input.glob === undefined ? [] : Array.isArray(input.glob) ? input.glob : [input.glob];
|
|
7634
|
+
const normalizedQuery = normalizeFileQuery(rawQuery, requestedGlobs.length > 0);
|
|
7635
|
+
const query = normalizedQuery.query;
|
|
7636
|
+
const request = normalizeRequest({
|
|
7637
|
+
...input,
|
|
7638
|
+
pattern: "",
|
|
7639
|
+
...normalizedQuery.glob ? { glob: [...requestedGlobs, normalizedQuery.glob] } : {}
|
|
7640
|
+
});
|
|
7599
7641
|
const policy = new SearchPathPolicy(cwd);
|
|
7600
7642
|
const discoveryRoot = request.path ?? ".";
|
|
7601
7643
|
const scoringRoot = await policy.resolveSearchTarget(discoveryRoot);
|
|
7602
|
-
const
|
|
7644
|
+
const includeIgnored = request.ignorePolicy === "include";
|
|
7645
|
+
const listOptions = {
|
|
7603
7646
|
path: scoringRoot,
|
|
7604
7647
|
glob: request.glob,
|
|
7605
7648
|
exclude: request.exclude,
|
|
7606
7649
|
hidden: request.hidden
|
|
7650
|
+
};
|
|
7651
|
+
const files = await listWorkspaceFiles(cwd, signal, {
|
|
7652
|
+
...listOptions,
|
|
7653
|
+
...includeIgnored ? { ignore: false, ignoreParents: false } : {}
|
|
7607
7654
|
});
|
|
7608
7655
|
const filtered = await filterPathsByModificationTime(cwd, files.paths, request.modifiedAfterMs, request.modifiedBeforeMs, signal);
|
|
7656
|
+
const rank = (path) => scoreFilePath(pathRelativeToDiscoveryRoot(cwd, scoringRoot, path), query);
|
|
7609
7657
|
const selected = filtered.paths.flatMap((path) => {
|
|
7610
|
-
const
|
|
7611
|
-
return
|
|
7658
|
+
const score = rank(path);
|
|
7659
|
+
return score ? [{ path, ...score }] : [];
|
|
7612
7660
|
}).toSorted((left, right) => right.score - left.score || left.path.localeCompare(right.path));
|
|
7613
7661
|
for (let offset = 0;offset < selected.length; offset += 16) {
|
|
7614
7662
|
await Promise.all(selected.slice(offset, offset + 16).map((item) => policy.assertExistingPath(item.path)));
|
|
7615
7663
|
signal?.throwIfAborted();
|
|
7616
7664
|
}
|
|
7665
|
+
const reasons = new Set([...files.reasons, ...filtered.reasons]);
|
|
7666
|
+
if (normalizedQuery.note)
|
|
7667
|
+
reasons.add(normalizedQuery.note);
|
|
7668
|
+
let ignoredFiles = 0;
|
|
7669
|
+
let ignoredMatches = 0;
|
|
7670
|
+
let ignoredComparisonPartial = false;
|
|
7671
|
+
if (!includeIgnored) {
|
|
7672
|
+
const everything = await listWorkspaceFiles(cwd, signal, {
|
|
7673
|
+
...listOptions,
|
|
7674
|
+
ignore: false,
|
|
7675
|
+
ignoreParents: false
|
|
7676
|
+
});
|
|
7677
|
+
ignoredComparisonPartial = everything.partial;
|
|
7678
|
+
const admitted = new Set(files.paths);
|
|
7679
|
+
const ignored = everything.paths.filter((path) => !admitted.has(path));
|
|
7680
|
+
ignoredFiles = ignored.length;
|
|
7681
|
+
const ignoredCandidates = ignored.flatMap((path) => {
|
|
7682
|
+
const score = rank(path);
|
|
7683
|
+
return score ? [{ path, ...score }] : [];
|
|
7684
|
+
}).toSorted((left, right) => right.score - left.score || left.path.localeCompare(right.path));
|
|
7685
|
+
ignoredMatches = ignoredCandidates.length;
|
|
7686
|
+
if (ignoredFiles > 0) {
|
|
7687
|
+
const samples = ignoredCandidates.slice(0, IGNORED_MATCH_SAMPLES).map((item) => `${JSON.stringify(item.path)} (${item.reason})`);
|
|
7688
|
+
reasons.add(ignoredMatches > 0 ? `${String(ignoredFiles)} file(s) were excluded by ignore rules; ${String(ignoredMatches)} of them match this query${samples.length ? `, e.g. ${samples.join(", ")}` : ""}. Retry with ignorePolicy "include" to list them.` : `${String(ignoredFiles)} file(s) were excluded by ignore rules; none of them match this query.`);
|
|
7689
|
+
}
|
|
7690
|
+
if (ignoredComparisonPartial)
|
|
7691
|
+
reasons.add("Ignored-file comparison was incomplete; the ignored-file count is a lower bound");
|
|
7692
|
+
}
|
|
7693
|
+
const enumerationPartial = files.partial || filtered.partial;
|
|
7617
7694
|
return {
|
|
7618
7695
|
kind: "files",
|
|
7619
7696
|
unit: "files",
|
|
7620
|
-
partial:
|
|
7621
|
-
reasons: [...
|
|
7697
|
+
partial: enumerationPartial,
|
|
7698
|
+
reasons: [...reasons],
|
|
7622
7699
|
items: selected.map((item) => ({
|
|
7623
7700
|
path: item.path,
|
|
7624
7701
|
line: 1,
|
|
7625
|
-
label: `File candidate (${item.reason})`,
|
|
7702
|
+
label: `File candidate (score ${String(item.score)}: ${item.reason})`,
|
|
7626
7703
|
details: {
|
|
7627
7704
|
kind: "file",
|
|
7628
7705
|
score: item.score,
|
|
@@ -7630,8 +7707,13 @@ async function discoverFiles(input, cwd, signal) {
|
|
|
7630
7707
|
inspect: { mode: "inspect", path: item.path, line: 1 }
|
|
7631
7708
|
}
|
|
7632
7709
|
})),
|
|
7633
|
-
coverage: {
|
|
7634
|
-
|
|
7710
|
+
coverage: {
|
|
7711
|
+
fileEnumeration: enumerationPartial ? "partial" : ignoredFiles > 0 || ignoredComparisonPartial ? "policy-filtered" : "complete"
|
|
7712
|
+
},
|
|
7713
|
+
stats: {
|
|
7714
|
+
filesEnumerated: files.paths.length,
|
|
7715
|
+
...includeIgnored ? {} : { ignoredFiles, ignoredMatches }
|
|
7716
|
+
},
|
|
7635
7717
|
scope: {
|
|
7636
7718
|
path: request.path ?? ".",
|
|
7637
7719
|
requestedPath: request.path ?? ".",
|
|
@@ -7644,7 +7726,8 @@ async function discoverFiles(input, cwd, signal) {
|
|
|
7644
7726
|
...request.modifiedAfterMs !== undefined ? { modifiedAfterMs: request.modifiedAfterMs } : {},
|
|
7645
7727
|
...request.modifiedBeforeMs !== undefined ? { modifiedBeforeMs: request.modifiedBeforeMs } : {}
|
|
7646
7728
|
},
|
|
7647
|
-
redact: input.redact ?? false
|
|
7729
|
+
redact: input.redact ?? false,
|
|
7730
|
+
...input.limit !== undefined ? { pageSize: request.pageSize } : {}
|
|
7648
7731
|
};
|
|
7649
7732
|
}
|
|
7650
7733
|
|
|
@@ -7885,7 +7968,19 @@ async function prepare(target, access, structure) {
|
|
|
7885
7968
|
let range = target.range;
|
|
7886
7969
|
let details = { status: "no-symbol" };
|
|
7887
7970
|
const language = syntaxLanguage(document2.path);
|
|
7888
|
-
if (
|
|
7971
|
+
if (target.range && target.structure) {
|
|
7972
|
+
document2.checkRange(target.range);
|
|
7973
|
+
const lines = {
|
|
7974
|
+
startLine: document2.lineAt(target.range.start),
|
|
7975
|
+
endLine: document2.lineAt(Math.max(target.range.start, target.range.end - 1))
|
|
7976
|
+
};
|
|
7977
|
+
range = document2.lineRange(lines.startLine, lines.endLine);
|
|
7978
|
+
details = {
|
|
7979
|
+
...target.structure,
|
|
7980
|
+
range: lines,
|
|
7981
|
+
...target.structure.symbol ? { symbol: { ...target.structure.symbol, range: lines } } : {}
|
|
7982
|
+
};
|
|
7983
|
+
} else if (document2.utf8 && language && language !== "go") {
|
|
7889
7984
|
const syntax = await access.syntax(document2);
|
|
7890
7985
|
details = {
|
|
7891
7986
|
status: syntax.status === "ok" ? "no-symbol" : syntax.status === "unsupported" ? "provider-unavailable" : "parse-error",
|
|
@@ -7937,7 +8032,7 @@ async function prepare(target, access, structure) {
|
|
|
7937
8032
|
details = { status: "provider-unavailable", ...language ? { language } : {} };
|
|
7938
8033
|
}
|
|
7939
8034
|
range ??= document2.lineRange(Math.max(1, target.line - 10), Math.min(document2.lineStarts.length, target.line + 10));
|
|
7940
|
-
const boundary = target.range ? "requested-range" : details.status === "available" && details.range ? "syntax" : "line-window";
|
|
8035
|
+
const boundary = target.range ? target.structure ? "syntax" : "requested-range" : details.status === "available" && details.range ? "syntax" : "line-window";
|
|
7941
8036
|
document2.checkRange(range);
|
|
7942
8037
|
return { target, document: document2, range, structure: details, boundary, focus };
|
|
7943
8038
|
}
|
|
@@ -8296,8 +8391,10 @@ ${preview.text}`);
|
|
|
8296
8391
|
}
|
|
8297
8392
|
};
|
|
8298
8393
|
}
|
|
8299
|
-
async function continueSource(cursor, access, continuations) {
|
|
8394
|
+
async function continueSource(cursor, access, continuations, expectedPath) {
|
|
8300
8395
|
const state = continuations.resolve(cursor);
|
|
8396
|
+
if (expectedPath !== undefined && resolve18(access.cwd, expectedPath.replace(/^@/, "")) !== resolve18(access.cwd, state.source.path))
|
|
8397
|
+
throw new SiftLightError(`sourceCursor continues ${JSON.stringify(state.source.path)}, not ${JSON.stringify(expectedPath)}; copy the returned nextRequest exactly`);
|
|
8301
8398
|
const document2 = await access.load(state.source.path, state.source);
|
|
8302
8399
|
const page = sourcePage(document2, state.remaining, MAX_RESULT_BYTES - 1400);
|
|
8303
8400
|
const next = continuations.advance(cursor, page.fragment);
|
|
@@ -9174,6 +9271,221 @@ function parsePythonOutline(document2) {
|
|
|
9174
9271
|
});
|
|
9175
9272
|
}
|
|
9176
9273
|
|
|
9274
|
+
// src/python-roles.ts
|
|
9275
|
+
var KEYWORDS = new Set([
|
|
9276
|
+
"and",
|
|
9277
|
+
"as",
|
|
9278
|
+
"assert",
|
|
9279
|
+
"async",
|
|
9280
|
+
"await",
|
|
9281
|
+
"case",
|
|
9282
|
+
"del",
|
|
9283
|
+
"elif",
|
|
9284
|
+
"else",
|
|
9285
|
+
"except",
|
|
9286
|
+
"for",
|
|
9287
|
+
"from",
|
|
9288
|
+
"global",
|
|
9289
|
+
"if",
|
|
9290
|
+
"import",
|
|
9291
|
+
"in",
|
|
9292
|
+
"is",
|
|
9293
|
+
"lambda",
|
|
9294
|
+
"match",
|
|
9295
|
+
"nonlocal",
|
|
9296
|
+
"not",
|
|
9297
|
+
"or",
|
|
9298
|
+
"raise",
|
|
9299
|
+
"return",
|
|
9300
|
+
"while",
|
|
9301
|
+
"with",
|
|
9302
|
+
"yield"
|
|
9303
|
+
]);
|
|
9304
|
+
var STRING_PREFIX = /^(?:[rRbBuUfF]|[rR][bBfF]|[bBfF][rR])?$/u;
|
|
9305
|
+
var IDENTIFIER_START = /[\p{ID_Start}_]/u;
|
|
9306
|
+
var IDENTIFIER_PART = /[\p{ID_Continue}]/u;
|
|
9307
|
+
function lex(text) {
|
|
9308
|
+
const comments = [];
|
|
9309
|
+
const strings = [];
|
|
9310
|
+
const code = new Uint8Array(text.length).fill(1);
|
|
9311
|
+
const mark = (start2, end) => {
|
|
9312
|
+
code.fill(0, start2, end);
|
|
9313
|
+
};
|
|
9314
|
+
let index = 0;
|
|
9315
|
+
while (index < text.length) {
|
|
9316
|
+
const character = text[index];
|
|
9317
|
+
if (character === "#") {
|
|
9318
|
+
const newline = text.indexOf(`
|
|
9319
|
+
`, index);
|
|
9320
|
+
const end = newline < 0 ? text.length : newline;
|
|
9321
|
+
comments.push({ start: index, end });
|
|
9322
|
+
mark(index, end);
|
|
9323
|
+
index = end;
|
|
9324
|
+
continue;
|
|
9325
|
+
}
|
|
9326
|
+
if (character !== "'" && character !== '"') {
|
|
9327
|
+
index += 1;
|
|
9328
|
+
continue;
|
|
9329
|
+
}
|
|
9330
|
+
let prefixStart = index;
|
|
9331
|
+
while (prefixStart > 0 && /[rRbBuUfF]/u.test(text[prefixStart - 1] ?? ""))
|
|
9332
|
+
prefixStart -= 1;
|
|
9333
|
+
const prefix = text.slice(prefixStart, index);
|
|
9334
|
+
const beforePrefix = text[prefixStart - 1] ?? "";
|
|
9335
|
+
const start2 = STRING_PREFIX.test(prefix) && !IDENTIFIER_PART.test(beforePrefix) ? prefixStart : index;
|
|
9336
|
+
const triple = text.slice(index, index + 3) === character.repeat(3);
|
|
9337
|
+
const delimiter = triple ? character.repeat(3) : character;
|
|
9338
|
+
let cursor = index + delimiter.length;
|
|
9339
|
+
let end = text.length;
|
|
9340
|
+
while (cursor < text.length) {
|
|
9341
|
+
const current = text[cursor];
|
|
9342
|
+
if (current === "\\") {
|
|
9343
|
+
cursor += 2;
|
|
9344
|
+
continue;
|
|
9345
|
+
}
|
|
9346
|
+
if (!triple && current === `
|
|
9347
|
+
`) {
|
|
9348
|
+
end = cursor;
|
|
9349
|
+
break;
|
|
9350
|
+
}
|
|
9351
|
+
if (text.startsWith(delimiter, cursor)) {
|
|
9352
|
+
end = cursor + delimiter.length;
|
|
9353
|
+
break;
|
|
9354
|
+
}
|
|
9355
|
+
cursor += 1;
|
|
9356
|
+
}
|
|
9357
|
+
strings.push({ start: start2, end });
|
|
9358
|
+
mark(start2, end);
|
|
9359
|
+
index = end;
|
|
9360
|
+
}
|
|
9361
|
+
return { comments, strings, code };
|
|
9362
|
+
}
|
|
9363
|
+
function codeRanges(code) {
|
|
9364
|
+
const ranges = [];
|
|
9365
|
+
let start2 = -1;
|
|
9366
|
+
for (let index = 0;index <= code.length; index += 1) {
|
|
9367
|
+
const inCode = index < code.length && code[index] === 1;
|
|
9368
|
+
if (inCode && start2 < 0)
|
|
9369
|
+
start2 = index;
|
|
9370
|
+
else if (!inCode && start2 >= 0) {
|
|
9371
|
+
ranges.push({ start: start2, end: index });
|
|
9372
|
+
start2 = -1;
|
|
9373
|
+
}
|
|
9374
|
+
}
|
|
9375
|
+
return ranges;
|
|
9376
|
+
}
|
|
9377
|
+
function identifierEnd(text, start2) {
|
|
9378
|
+
let end = start2;
|
|
9379
|
+
while (end < text.length && IDENTIFIER_PART.test(text[end] ?? ""))
|
|
9380
|
+
end += 1;
|
|
9381
|
+
return end;
|
|
9382
|
+
}
|
|
9383
|
+
function skipSpaces(text, index, code) {
|
|
9384
|
+
let cursor = index;
|
|
9385
|
+
while (cursor < text.length && code[cursor] === 1 && /[ \t]/u.test(text[cursor] ?? ""))
|
|
9386
|
+
cursor += 1;
|
|
9387
|
+
return cursor;
|
|
9388
|
+
}
|
|
9389
|
+
function statementEnd(text, start2, code) {
|
|
9390
|
+
let depth = 0;
|
|
9391
|
+
for (let index = start2;index < text.length; index += 1) {
|
|
9392
|
+
if (code[index] !== 1)
|
|
9393
|
+
continue;
|
|
9394
|
+
const character = text[index];
|
|
9395
|
+
if (character === "(" || character === "[" || character === "{")
|
|
9396
|
+
depth += 1;
|
|
9397
|
+
else if (character === ")" || character === "]" || character === "}")
|
|
9398
|
+
depth = Math.max(0, depth - 1);
|
|
9399
|
+
else if (character === "\\" && text[index + 1] === `
|
|
9400
|
+
`)
|
|
9401
|
+
index += 1;
|
|
9402
|
+
else if (character === `
|
|
9403
|
+
` && depth === 0)
|
|
9404
|
+
return index;
|
|
9405
|
+
}
|
|
9406
|
+
return text.length;
|
|
9407
|
+
}
|
|
9408
|
+
function pythonRoleAnalysis(document2) {
|
|
9409
|
+
const text = document2.text;
|
|
9410
|
+
const { comments, strings, code } = lex(text);
|
|
9411
|
+
const roles = [];
|
|
9412
|
+
const push = (start2, end, role, certainty, subkind) => {
|
|
9413
|
+
if (start2 < end)
|
|
9414
|
+
roles.push({ start: start2, end, role, certainty, node: 0, ...subkind ? { subkind } : {} });
|
|
9415
|
+
};
|
|
9416
|
+
for (const span of comments)
|
|
9417
|
+
push(span.start, span.end, "comment", "syntax");
|
|
9418
|
+
for (const span of strings)
|
|
9419
|
+
push(span.start, span.end, "string", "syntax");
|
|
9420
|
+
for (const span of codeRanges(code))
|
|
9421
|
+
push(span.start, span.end, "code", "syntax");
|
|
9422
|
+
let index = 0;
|
|
9423
|
+
let lineStart = true;
|
|
9424
|
+
while (index < text.length) {
|
|
9425
|
+
const character = text[index] ?? "";
|
|
9426
|
+
if (character === `
|
|
9427
|
+
`) {
|
|
9428
|
+
lineStart = true;
|
|
9429
|
+
index += 1;
|
|
9430
|
+
continue;
|
|
9431
|
+
}
|
|
9432
|
+
if (code[index] !== 1 || !IDENTIFIER_START.test(character)) {
|
|
9433
|
+
if (!/[ \t]/u.test(character))
|
|
9434
|
+
lineStart = false;
|
|
9435
|
+
index += 1;
|
|
9436
|
+
continue;
|
|
9437
|
+
}
|
|
9438
|
+
const previous = text[index - 1] ?? "";
|
|
9439
|
+
if (IDENTIFIER_PART.test(previous)) {
|
|
9440
|
+
index += 1;
|
|
9441
|
+
continue;
|
|
9442
|
+
}
|
|
9443
|
+
const end = identifierEnd(text, index);
|
|
9444
|
+
const word = text.slice(index, end);
|
|
9445
|
+
const atStatementStart = lineStart;
|
|
9446
|
+
lineStart = false;
|
|
9447
|
+
if (atStatementStart && (word === "import" || word === "from")) {
|
|
9448
|
+
const statement = statementEnd(text, index, code);
|
|
9449
|
+
const body2 = text.slice(index, statement);
|
|
9450
|
+
if (word === "import" || /\bimport\b/u.test(body2)) {
|
|
9451
|
+
push(index, statement, "import", "syntax", word === "from" ? "from-import" : "import");
|
|
9452
|
+
index = statement;
|
|
9453
|
+
continue;
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
if (word === "def" || word === "class") {
|
|
9457
|
+
const nameStart = skipSpaces(text, end, code);
|
|
9458
|
+
if (IDENTIFIER_START.test(text[nameStart] ?? "")) {
|
|
9459
|
+
const nameEnd = identifierEnd(text, nameStart);
|
|
9460
|
+
push(nameStart, nameEnd, "declaration", "syntax", word === "def" ? "function" : "class");
|
|
9461
|
+
index = nameEnd;
|
|
9462
|
+
continue;
|
|
9463
|
+
}
|
|
9464
|
+
}
|
|
9465
|
+
let chainEnd = end;
|
|
9466
|
+
while (text[chainEnd] === "." && IDENTIFIER_START.test(text[chainEnd + 1] ?? "")) {
|
|
9467
|
+
chainEnd = identifierEnd(text, chainEnd + 1);
|
|
9468
|
+
}
|
|
9469
|
+
const open = skipSpaces(text, chainEnd, code);
|
|
9470
|
+
const lastSegment = text.slice(text.lastIndexOf(".", chainEnd - 1) + 1, chainEnd);
|
|
9471
|
+
const before = text.slice(Math.max(0, index - 4), index);
|
|
9472
|
+
if (text[open] === "(" && code[open] === 1 && !KEYWORDS.has(word) && !KEYWORDS.has(lastSegment) && !/\bdef\s+$|\bclass\s+$/u.test(before)) {
|
|
9473
|
+
push(index, open + 1, "call", "candidate", "lexical-call");
|
|
9474
|
+
}
|
|
9475
|
+
index = chainEnd;
|
|
9476
|
+
}
|
|
9477
|
+
roles.sort((a, b) => a.start - b.start || a.end - b.end || a.role.localeCompare(b.role));
|
|
9478
|
+
return {
|
|
9479
|
+
status: "ok",
|
|
9480
|
+
nodes: [],
|
|
9481
|
+
children: [],
|
|
9482
|
+
symbols: [],
|
|
9483
|
+
roles,
|
|
9484
|
+
diagnostics: [],
|
|
9485
|
+
limited: false
|
|
9486
|
+
};
|
|
9487
|
+
}
|
|
9488
|
+
|
|
9177
9489
|
// src/hybrid-search.ts
|
|
9178
9490
|
import { resolve as resolve20 } from "path";
|
|
9179
9491
|
|
|
@@ -10080,7 +10392,17 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
10080
10392
|
auto: ordinaryFields,
|
|
10081
10393
|
summary: ordinaryFields,
|
|
10082
10394
|
matches: ordinaryFields,
|
|
10083
|
-
inspect: [
|
|
10395
|
+
inspect: [
|
|
10396
|
+
...commonFields,
|
|
10397
|
+
"path",
|
|
10398
|
+
"paths",
|
|
10399
|
+
"line",
|
|
10400
|
+
"cursor",
|
|
10401
|
+
"matchIndex",
|
|
10402
|
+
"matchIndices",
|
|
10403
|
+
"targets",
|
|
10404
|
+
"scope"
|
|
10405
|
+
],
|
|
10084
10406
|
outline: [...commonFields, "path", "line", "symbol", "cursor", "matchIndex", "maxFilesToParse"],
|
|
10085
10407
|
imports: [
|
|
10086
10408
|
...commonFields,
|
|
@@ -10100,7 +10422,16 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
10100
10422
|
"matchIndex",
|
|
10101
10423
|
"maxFilesToParse"
|
|
10102
10424
|
],
|
|
10103
|
-
files: [
|
|
10425
|
+
files: [
|
|
10426
|
+
...commonFields,
|
|
10427
|
+
"query",
|
|
10428
|
+
...sourceFilters,
|
|
10429
|
+
"ignorePolicy",
|
|
10430
|
+
"limit",
|
|
10431
|
+
"scope",
|
|
10432
|
+
"modifiedAfter",
|
|
10433
|
+
"modifiedBefore"
|
|
10434
|
+
],
|
|
10104
10435
|
structure: [...commonFields, "pattern", ...sourceFilters, "maxFilesToParse"],
|
|
10105
10436
|
concept: [...commonFields, "query", ...sourceFilters, "maxFilesToParse"],
|
|
10106
10437
|
hybrid: [...commonFields, "query", ...sourceFilters, "conceptLimit", "maxFilesToParse"],
|
|
@@ -10109,9 +10440,7 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
10109
10440
|
await: ["mode", "operationId"],
|
|
10110
10441
|
cancel: ["mode", "operationId"]
|
|
10111
10442
|
};
|
|
10112
|
-
var SAFE_DROP_FIELDS = {
|
|
10113
|
-
files: ["scope"]
|
|
10114
|
-
};
|
|
10443
|
+
var SAFE_DROP_FIELDS = {};
|
|
10115
10444
|
var SUPPORTED_OUTLINE_EXTENSIONS = new Set(DEFAULT_LANGUAGE_CAPABILITIES.flatMap((descriptor) => descriptor.capabilities.some((capability) => capability.name === "outline") ? descriptor.extensions : []));
|
|
10116
10445
|
function modeFields(mode) {
|
|
10117
10446
|
return MODE_FIELDS_BY_MODE[mode];
|
|
@@ -10140,8 +10469,8 @@ var REQUEST_FIELD_GUIDANCE = {
|
|
|
10140
10469
|
allOf: "allOf is case-sensitive exact-literal AND; omit pattern, anyOf, literal, ignoreCase, roles",
|
|
10141
10470
|
context: "context is an output-context budget and is never silently dropped",
|
|
10142
10471
|
limit: "limit is an output/page budget and is never silently dropped",
|
|
10143
|
-
scope: "scope applies to ordinary content search; mode=files
|
|
10144
|
-
ignorePolicy: "respect keeps repository ignore rules; include searches ignored files but always excludes .git internals and protected paths",
|
|
10472
|
+
scope: "scope applies to ordinary content search; mode=files is always strict, so it accepts only the redundant scope=strict",
|
|
10473
|
+
ignorePolicy: "respect keeps repository ignore rules and discloses ignored files; include also searches or lists ignored files but always excludes .git internals and protected paths",
|
|
10145
10474
|
patterns: "audit accepts named exact-literal patterns and returns one closure receipt",
|
|
10146
10475
|
maxFilesToParse: "concept/hybrid automatically batch the requested scope; this optional field sets an advanced hard file ceiling"
|
|
10147
10476
|
};
|
|
@@ -10240,12 +10569,15 @@ function schemaError(input, field, reason) {
|
|
|
10240
10569
|
function plainFilePatternRecovery(mode, input, invalid) {
|
|
10241
10570
|
return mode === "files" && invalid.includes("pattern") && input.query === undefined && typeof input.pattern === "string" && input.pattern.trim().length > 0 && input.pattern.length <= 256 && input.pattern.isWellFormed() && !/[\\^$.*+?()[\]{}|\r\n\0]/u.test(input.pattern);
|
|
10242
10571
|
}
|
|
10572
|
+
function matchAllFilePatternRecovery(mode, input, invalid) {
|
|
10573
|
+
return mode === "files" && invalid.includes("pattern") && input.query === undefined && typeof input.pattern === "string" && /^(?:\.\*|\.\+|\*+)$/u.test(input.pattern.trim());
|
|
10574
|
+
}
|
|
10243
10575
|
function safeNextRequest(input, mode, invalid) {
|
|
10244
10576
|
if (input.redact === true && containsSensitiveText(input))
|
|
10245
10577
|
return;
|
|
10246
10578
|
const safe = new Set(SAFE_DROP_FIELDS[mode] ?? []);
|
|
10247
10579
|
const plainFilePattern = plainFilePatternRecovery(mode, input, invalid);
|
|
10248
|
-
if (plainFilePattern)
|
|
10580
|
+
if (plainFilePattern || matchAllFilePatternRecovery(mode, input, invalid))
|
|
10249
10581
|
safe.add("pattern");
|
|
10250
10582
|
if (invalid.some((field) => !safe.has(field)))
|
|
10251
10583
|
return;
|
|
@@ -10291,7 +10623,7 @@ function fieldsError(input, mode, invalid, selectorIssues = []) {
|
|
|
10291
10623
|
issues,
|
|
10292
10624
|
recovery: nextRequest ? {
|
|
10293
10625
|
action: "retry",
|
|
10294
|
-
reason: plainFilePatternRecovery(mode, input, invalid) ? "For filename discovery, move the plain text from pattern to query and copy nextRequest; all filters are preserved." : `Remove only ${visibleFields} and copy the exact nextRequest; all other fields are preserved.`,
|
|
10626
|
+
reason: plainFilePatternRecovery(mode, input, invalid) ? "For filename discovery, move the plain text from pattern to query and copy nextRequest; all filters are preserved." : matchAllFilePatternRecovery(mode, input, invalid) ? "A match-all pattern lists every file; omitting query does that, so copy nextRequest; all filters are preserved." : `Remove only ${visibleFields} and copy the exact nextRequest; all other fields are preserved.`,
|
|
10295
10627
|
nextRequest
|
|
10296
10628
|
} : {
|
|
10297
10629
|
action: "manual",
|
|
@@ -10447,7 +10779,7 @@ function validateRequestContract(input) {
|
|
|
10447
10779
|
const allowed = new Set(MODE_FIELDS_BY_MODE[mode]);
|
|
10448
10780
|
if (mode === "inspect" && raw.sourceCursor !== undefined) {
|
|
10449
10781
|
allowed.clear();
|
|
10450
|
-
for (const field of ["mode", "sourceCursor", "redact"])
|
|
10782
|
+
for (const field of ["mode", "sourceCursor", "redact", "path"])
|
|
10451
10783
|
allowed.add(field);
|
|
10452
10784
|
}
|
|
10453
10785
|
if ((mode === "auto" || mode === "summary" || mode === "matches") && typeof raw.cursor === "string" && raw.cursor.includes(".analysis")) {
|
|
@@ -10475,6 +10807,69 @@ import { realpath as realpath6 } from "fs/promises";
|
|
|
10475
10807
|
function isEvidenceRequest(input) {
|
|
10476
10808
|
return input.mode === "concept" || input.mode === "hybrid" || input.mode === "structure" || input.mode === "files" || input.mode === "inspect" || input.mode === "outline" || input.mode === "imports" || input.mode === "tests" || input.mode === "validate" || input.sourceCursor !== undefined || input.anyOf !== undefined || input.allOf !== undefined || input.within !== undefined || input.roles !== undefined || input.changes !== undefined || input.symbol !== undefined || input.conceptLimit !== undefined || (input.cursor?.includes(".analysis") ?? false);
|
|
10477
10809
|
}
|
|
10810
|
+
function pageInspectRequest(input) {
|
|
10811
|
+
if (input.scope === "expand")
|
|
10812
|
+
throw new SiftLightError("mode=inspect reads exact locations; scope=expand does not apply (omit scope)");
|
|
10813
|
+
const { scope: _scope, paths, ...rest } = input;
|
|
10814
|
+
let request = rest;
|
|
10815
|
+
if (paths !== undefined) {
|
|
10816
|
+
if (input.cursor !== undefined || input.targets !== undefined || input.matchIndices !== undefined || input.path !== undefined || input.line !== undefined || input.matchIndex !== undefined)
|
|
10817
|
+
throw new SiftLightError("mode=inspect paths opens files from line 1 and cannot be combined with cursor, targets, matchIndices, path, line or matchIndex");
|
|
10818
|
+
request = { ...rest, targets: paths.map((path) => ({ path, line: 1 })) };
|
|
10819
|
+
}
|
|
10820
|
+
const redact = input.redact ? { redact: true } : {};
|
|
10821
|
+
const requested = request.targets?.length ?? request.matchIndices?.length ?? 0;
|
|
10822
|
+
if (requested > MAX_INSPECT_REQUEST_TARGETS)
|
|
10823
|
+
throw new SiftLightError(`mode=inspect accepts at most ${String(MAX_INSPECT_REQUEST_TARGETS)} targets per request; ${String(MAX_INSPECT_TARGETS)} are inspected per response`);
|
|
10824
|
+
if (request.targets && request.targets.length > MAX_INSPECT_TARGETS)
|
|
10825
|
+
return {
|
|
10826
|
+
current: { ...request, targets: request.targets.slice(0, MAX_INSPECT_TARGETS) },
|
|
10827
|
+
remaining: {
|
|
10828
|
+
mode: "inspect",
|
|
10829
|
+
targets: request.targets.slice(MAX_INSPECT_TARGETS),
|
|
10830
|
+
...redact
|
|
10831
|
+
}
|
|
10832
|
+
};
|
|
10833
|
+
if (request.matchIndices && request.matchIndices.length > MAX_INSPECT_TARGETS)
|
|
10834
|
+
return {
|
|
10835
|
+
current: { ...request, matchIndices: request.matchIndices.slice(0, MAX_INSPECT_TARGETS) },
|
|
10836
|
+
remaining: {
|
|
10837
|
+
mode: "inspect",
|
|
10838
|
+
...request.cursor !== undefined ? { cursor: request.cursor } : {},
|
|
10839
|
+
matchIndices: request.matchIndices.slice(MAX_INSPECT_TARGETS),
|
|
10840
|
+
...redact
|
|
10841
|
+
}
|
|
10842
|
+
};
|
|
10843
|
+
return { current: request };
|
|
10844
|
+
}
|
|
10845
|
+
function withRemainingInspection(result, remaining) {
|
|
10846
|
+
const count = remaining.targets?.length ?? remaining.matchIndices?.length ?? 0;
|
|
10847
|
+
return {
|
|
10848
|
+
text: `${result.text}
|
|
10849
|
+
|
|
10850
|
+
[${String(count)} more target(s) were not inspected; one response inspects ${String(MAX_INSPECT_TARGETS)}.]
|
|
10851
|
+
Next request: ${JSON.stringify(remaining)}`,
|
|
10852
|
+
details: {
|
|
10853
|
+
...result.details,
|
|
10854
|
+
status: "partial",
|
|
10855
|
+
snapshotComplete: false,
|
|
10856
|
+
nextRequest: remaining
|
|
10857
|
+
}
|
|
10858
|
+
};
|
|
10859
|
+
}
|
|
10860
|
+
function analysisItemStructure(item) {
|
|
10861
|
+
const details = item.details ?? {};
|
|
10862
|
+
const language = typeof details.language === "string" ? details.language : undefined;
|
|
10863
|
+
const name2 = typeof details.name === "string" ? details.name : undefined;
|
|
10864
|
+
const scope = Array.isArray(details.scope) ? details.scope.filter((entry) => typeof entry === "string") : typeof details.scope === "string" ? [details.scope] : [];
|
|
10865
|
+
const kind = details.kind === "function" ? "function" : item.label.split(" ", 1)[0] ?? "symbol";
|
|
10866
|
+
return {
|
|
10867
|
+
status: "available",
|
|
10868
|
+
provider: language === "python" ? "python-outline" : "tree-sitter",
|
|
10869
|
+
...language ? { language } : {},
|
|
10870
|
+
...name2 ? { symbol: { name: name2, kind, scope, range: { startLine: item.line, endLine: item.line } } } : {}
|
|
10871
|
+
};
|
|
10872
|
+
}
|
|
10478
10873
|
function maxFilesToParse(value, defaultValue = MAX_STRUCTURE_FILES) {
|
|
10479
10874
|
const candidate = value ?? defaultValue;
|
|
10480
10875
|
if (!Number.isSafeInteger(candidate) || candidate < 1 || candidate > MAX_CONFIGURABLE_STRUCTURE_FILES) {
|
|
@@ -10490,8 +10885,8 @@ function validateTerms(input) {
|
|
|
10490
10885
|
}
|
|
10491
10886
|
return;
|
|
10492
10887
|
}
|
|
10493
|
-
if (!Array.isArray(terms) || terms.length <
|
|
10494
|
-
throw new SiftLightError("allOf requires
|
|
10888
|
+
if (!Array.isArray(terms) || terms.length < 1 || terms.length > 3 || terms.some((term) => typeof term !== "string" || !term.trim() || /[\r\n\0]/.test(term)) || new Set(terms).size !== terms.length)
|
|
10889
|
+
throw new SiftLightError("allOf requires 1\u20133 distinct, nonempty, single-line literal terms");
|
|
10495
10890
|
if (input.pattern !== undefined || input.roles !== undefined || input.literal !== undefined || input.ignoreCase !== undefined || input.wholeWord !== undefined)
|
|
10496
10891
|
throw new SiftLightError("allOf is an explicit case-sensitive literal conjunction; omit pattern, roles, literal and ignoreCase");
|
|
10497
10892
|
if (input.within !== undefined && input.within !== "file" && input.within !== "function")
|
|
@@ -10689,11 +11084,13 @@ class EvidenceService {
|
|
|
10689
11084
|
throw new CursorError("A nonempty sourceCursor is required");
|
|
10690
11085
|
if (input.mode !== "inspect")
|
|
10691
11086
|
throw new SiftLightError("sourceCursor requires mode=inspect");
|
|
10692
|
-
return continueSource(input.sourceCursor, access, this.#continuations);
|
|
11087
|
+
return continueSource(input.sourceCursor, access, this.#continuations, input.path);
|
|
10693
11088
|
}
|
|
10694
11089
|
if (input.mode === "inspect") {
|
|
10695
|
-
const
|
|
10696
|
-
|
|
11090
|
+
const { current, remaining } = pageInspectRequest(input);
|
|
11091
|
+
const targets = this.#inspectionTargets(current, cwd);
|
|
11092
|
+
const result = await inspectDocuments(targets, access, this.#continuations, this.#structure);
|
|
11093
|
+
return remaining ? withRemainingInspection(result, remaining) : result;
|
|
10697
11094
|
}
|
|
10698
11095
|
if (input.mode === "concept") {
|
|
10699
11096
|
const execution = await this.#conceptSearch(input, access, options.onProgress);
|
|
@@ -10782,7 +11179,7 @@ class EvidenceService {
|
|
|
10782
11179
|
if (input.pattern !== undefined || input.allOf !== undefined || input.within !== undefined || input.roles !== undefined || input.literal !== undefined || input.ignoreCase !== undefined || input.wholeWord !== undefined)
|
|
10783
11180
|
throw new SiftLightError("anyOf is an explicit case-sensitive literal union; omit pattern, allOf, within, roles, literal and ignoreCase");
|
|
10784
11181
|
if (input.mode !== undefined && input.mode !== "auto" && input.mode !== "matches")
|
|
10785
|
-
throw new SiftLightError("anyOf mode must be omitted, auto, or
|
|
11182
|
+
throw new SiftLightError("anyOf mode must be omitted, auto, matches or summary");
|
|
10786
11183
|
const chunks = Array.from({ length: Math.ceil(anyOf.length / MAX_ANY_OF_TERMS) }, (_, index) => anyOf.slice(index * MAX_ANY_OF_TERMS, (index + 1) * MAX_ANY_OF_TERMS));
|
|
10787
11184
|
const { path: _inputPath, ...unscopedInput } = input;
|
|
10788
11185
|
let chunkAccess = access;
|
|
@@ -10920,9 +11317,10 @@ class EvidenceService {
|
|
|
10920
11317
|
if (item)
|
|
10921
11318
|
result.items.push(item);
|
|
10922
11319
|
} else if (terms || input.roles) {
|
|
10923
|
-
|
|
11320
|
+
const pythonRoles = !terms && /\.py$/iu.test(file.document.path);
|
|
11321
|
+
if (pythonRoles || syntaxLanguage(file.document.path))
|
|
10924
11322
|
syntaxCapableFiles += 1;
|
|
10925
|
-
const syntax = await access.syntax(file.document);
|
|
11323
|
+
const syntax = pythonRoles ? pythonRoleAnalysis(file.document) : await access.syntax(file.document);
|
|
10926
11324
|
const classified = terms ? findFunctionConjunctions(file.document, syntax, terms, input.changes?.scope === "lines" ? file.changedRanges : undefined) : filterRoleOccurrences(file.document, syntax, file.occurrences, input.roles ?? []);
|
|
10927
11325
|
result.items.push(...classified.items);
|
|
10928
11326
|
result.partial ||= classified.partial;
|
|
@@ -10999,12 +11397,12 @@ class EvidenceService {
|
|
|
10999
11397
|
const item = this.#analyses.item(input.cursor, input.matchIndex);
|
|
11000
11398
|
if (!item.source || !item.range)
|
|
11001
11399
|
throw new CursorError("This analysis item has no verified source range");
|
|
11002
|
-
const
|
|
11400
|
+
const bounded = item.details?.kind === "symbol" || item.details?.kind === "function";
|
|
11003
11401
|
return {
|
|
11004
11402
|
path: item.path,
|
|
11005
11403
|
line: item.line,
|
|
11006
11404
|
reference: item.source,
|
|
11007
|
-
...
|
|
11405
|
+
...bounded ? { range: item.range, structure: analysisItemStructure(item) } : { absoluteFocus: item.range.start }
|
|
11008
11406
|
};
|
|
11009
11407
|
}
|
|
11010
11408
|
return {
|
|
@@ -11213,6 +11611,29 @@ import { resolve as resolve24 } from "path";
|
|
|
11213
11611
|
// src/discovery-errors.ts
|
|
11214
11612
|
var DISCOVERY_MODE_REQUIRED_ERROR = 'query requires an explicit discovery mode: use mode=files for filename/path discovery or mode=concept for semantic discovery; for example {"mode":"files","query":"<filename-or-path>"}';
|
|
11215
11613
|
|
|
11614
|
+
// src/request-aliases.ts
|
|
11615
|
+
var MODE_ALIASES = ["anyOf", "allOf"];
|
|
11616
|
+
function normalizeRequestAliases(request) {
|
|
11617
|
+
const notes = [];
|
|
11618
|
+
const { mode, ...rest } = request;
|
|
11619
|
+
let input = rest;
|
|
11620
|
+
if (mode === "anyOf" || mode === "allOf") {
|
|
11621
|
+
if (request[mode] === undefined)
|
|
11622
|
+
throw new SiftLightError(`mode=${mode} is not a mode; pass ${mode}:[...terms] and omit mode`);
|
|
11623
|
+
notes.push(`mode="${mode}" is not a mode; the ${mode} field alone selects this search.`);
|
|
11624
|
+
} else if (mode === "summary" && request.anyOf !== undefined) {
|
|
11625
|
+
notes.push("anyOf returns one analysis page whose header already carries per-file statistics; mode=summary was served by that page.");
|
|
11626
|
+
} else if (mode !== undefined) {
|
|
11627
|
+
input = { ...rest, mode };
|
|
11628
|
+
}
|
|
11629
|
+
if (input.sourceCursor !== undefined && input.line !== undefined) {
|
|
11630
|
+
const { line: _line, ...withoutLine } = input;
|
|
11631
|
+
input = withoutLine;
|
|
11632
|
+
notes.push("line is ignored with sourceCursor; the continuation selects its own source range.");
|
|
11633
|
+
}
|
|
11634
|
+
return { input, notes };
|
|
11635
|
+
}
|
|
11636
|
+
|
|
11216
11637
|
// src/format.ts
|
|
11217
11638
|
import { readFile as readFile3 } from "fs/promises";
|
|
11218
11639
|
var RESULT_METADATA_RESERVE_BYTES = 1024;
|
|
@@ -12566,6 +12987,16 @@ class LanguageCapabilityCatalog {
|
|
|
12566
12987
|
}
|
|
12567
12988
|
|
|
12568
12989
|
// src/service.ts
|
|
12990
|
+
function withRequestNotes(result, notes) {
|
|
12991
|
+
if (notes.length === 0)
|
|
12992
|
+
return result;
|
|
12993
|
+
return {
|
|
12994
|
+
text: `${notes.map((note) => `[Request note: ${note}]`).join(`
|
|
12995
|
+
`)}
|
|
12996
|
+
${result.text}`,
|
|
12997
|
+
details: { ...result.details, requestNotes: [...notes] }
|
|
12998
|
+
};
|
|
12999
|
+
}
|
|
12569
13000
|
function filterList(value) {
|
|
12570
13001
|
return value === undefined ? [] : Array.isArray(value) ? [...value] : [value];
|
|
12571
13002
|
}
|
|
@@ -12794,7 +13225,11 @@ class SiftLightService {
|
|
|
12794
13225
|
this.#operations = new OperationLifecycle({ deadlineMs: resolveConceptTimeoutMs() });
|
|
12795
13226
|
this.#evidence = new EvidenceService(this.#runRipgrep, this.#snapshots, options.structure, options.conceptSearch, options.semanticJudge);
|
|
12796
13227
|
}
|
|
12797
|
-
async search(
|
|
13228
|
+
async search(request, cwd, signal, options = {}) {
|
|
13229
|
+
const { input, notes } = normalizeRequestAliases(request);
|
|
13230
|
+
return withRequestNotes(await this.#searchNormalized(input, cwd, signal, options), notes);
|
|
13231
|
+
}
|
|
13232
|
+
async#searchNormalized(input, cwd, signal, options) {
|
|
12798
13233
|
validateRawSearchInput(input);
|
|
12799
13234
|
validateRequestContract(input);
|
|
12800
13235
|
if (!this.#vectorSearchEnabled && (input.mode === "concept" || input.mode === "hybrid")) {
|
|
@@ -20586,9 +21021,9 @@ var siftLightSchema = _Object_({
|
|
|
20586
21021
|
description: `${fieldGuidance("anyOf")}. ${String(MIN_ANY_OF_TERMS)}-${String(MAX_ANY_OF_TOTAL_TERMS)} distinct case-sensitive single-line terms, at most ${String(MAX_LITERAL_TERM_BYTES)} UTF-8 bytes each. Requests above ${String(MAX_ANY_OF_TERMS)} terms are split into version-checked chunks and merged. Returns every retained occurrence attributed to its term.`
|
|
20587
21022
|
})),
|
|
20588
21023
|
allOf: Optional(_Array_(String2({ maxLength: MAX_PATH_CHARACTERS }), {
|
|
20589
|
-
minItems:
|
|
21024
|
+
minItems: 1,
|
|
20590
21025
|
maxItems: 3,
|
|
20591
|
-
description: `${fieldGuidance("allOf")}.
|
|
21026
|
+
description: `${fieldGuidance("allOf")}. 1-3 distinct terms must occur in one file (default) or one function; one term lists the files or functions containing it.`
|
|
20592
21027
|
})),
|
|
20593
21028
|
within: Optional(stringEnum(["file", "function"], {
|
|
20594
21029
|
description: "Only valid with allOf; omit for ordinary single-pattern searches. function requires JS/TS/TSX and counts only that implementation's own code, excluding nested callbacks, strings/comments/types. Not proof of a shared execution path."
|
|
@@ -20605,7 +21040,7 @@ var siftLightSchema = _Object_({
|
|
|
20605
21040
|
"unknown"
|
|
20606
21041
|
]), {
|
|
20607
21042
|
minItems: 1,
|
|
20608
|
-
description: "Filter each single-pattern occurrence by syntax role (JS/TS/TSX/Go). Roles may be candidates, especially Go call/conversion
|
|
21043
|
+
description: "Filter each single-pattern occurrence by syntax role (JS/TS/TSX/Go parsed; Python lexical: comment, string, code, declaration, import, call candidates). Roles may be candidates, especially Go call/conversion and Python calls. Cannot combine with allOf."
|
|
20609
21044
|
})),
|
|
20610
21045
|
changes: Optional(_Object_({
|
|
20611
21046
|
base: Optional(String2({
|
|
@@ -20638,7 +21073,7 @@ var siftLightSchema = _Object_({
|
|
|
20638
21073
|
paths: Optional(_Array_(String2(), {
|
|
20639
21074
|
minItems: 1,
|
|
20640
21075
|
maxItems: MAX_SELECTED_PATHS,
|
|
20641
|
-
description: "Exact retained files to select together from a cursor. A new search accepts one path; split multiple roots into separate requests."
|
|
21076
|
+
description: "Exact retained files to select together from a cursor. A new search accepts one path; split multiple roots into separate requests. With mode=inspect and no cursor, opens each file from line 1 (same as targets with line 1)."
|
|
20642
21077
|
})),
|
|
20643
21078
|
glob: Optional(Union([
|
|
20644
21079
|
String2({ maxLength: MAX_PATH_CHARACTERS }),
|
|
@@ -20662,7 +21097,7 @@ var siftLightSchema = _Object_({
|
|
|
20662
21097
|
})),
|
|
20663
21098
|
hidden: Optional(Boolean2({ description: "Search hidden files (default true; .git is always excluded)." })),
|
|
20664
21099
|
ignorePolicy: Optional(stringEnum(["respect", "include"], {
|
|
20665
|
-
description: "respect (default) honors ignore rules and reports policy-filtered coverage when files are omitted. include searches ignored files while still excluding .git internals and protected paths."
|
|
21100
|
+
description: "respect (default) honors ignore rules and reports policy-filtered coverage when files are omitted, including mode=files. include searches or lists ignored files while still excluding .git internals and protected paths."
|
|
20666
21101
|
})),
|
|
20667
21102
|
patterns: Optional(_Array_(_Object_({
|
|
20668
21103
|
id: String2({ minLength: 1, maxLength: 64 }),
|
|
@@ -20707,29 +21142,29 @@ var siftLightSchema = _Object_({
|
|
|
20707
21142
|
limit: Optional(Integer({
|
|
20708
21143
|
minimum: 1,
|
|
20709
21144
|
maximum: MAX_PAGE_SIZE,
|
|
20710
|
-
description: `${fieldGuidance("limit")}. Ordinary search
|
|
21145
|
+
description: `${fieldGuidance("limit")}. Ordinary search: explicit detail-page match limit (max 100); mode=files: files per page (default 30). Normally omit to preserve automatic summarization; other analysis and inspect modes reject it.`
|
|
20711
21146
|
})),
|
|
20712
|
-
mode: Optional(stringEnum(SIFT_LIGHT_MODES, {
|
|
20713
|
-
description: `Ordinary search defaults to auto; summary/matches request explicit pages. capabilities returns a compact names-only project inventory and per-language supported modes without loading providers. files uses query, structure uses an AST pattern, concept uses a required natural-language query, and hybrid uses one query for exact literal plus concept evidence in a single snapshot. validate rechecks saved search or analysis sources against their recorded origin. await waits for an existing long-running concept or hybrid operation without restarting it; cancel explicitly cancels one and waits for owned cleanup. Copy the returned nextRequest exactly and do not repeat the original query. Waiting is an operation state, not evidence. Validation details report the requested scope, comparison target, coverage, and freshness as current, stale, or unknown; partial coverage is retained during validation. inspect/outline/imports/tests retain their documented location selectors. Syntax results are static evidence; concept and related-test results remain candidates. ${MODE_CONTRACT_DESCRIPTION}
|
|
21147
|
+
mode: Optional(stringEnum([...SIFT_LIGHT_MODES, ...MODE_ALIASES], {
|
|
21148
|
+
description: `Ordinary search defaults to auto; summary/matches request explicit pages. capabilities returns a compact names-only project inventory and per-language supported modes without loading providers. files uses query, structure uses an AST pattern, concept uses a required natural-language query, and hybrid uses one query for exact literal plus concept evidence in a single snapshot. validate rechecks saved search or analysis sources against their recorded origin. await waits for an existing long-running concept or hybrid operation without restarting it; cancel explicitly cancels one and waits for owned cleanup. Copy the returned nextRequest exactly and do not repeat the original query. Waiting is an operation state, not evidence. Validation details report the requested scope, comparison target, coverage, and freshness as current, stale, or unknown; partial coverage is retained during validation. inspect/outline/imports/tests retain their documented location selectors. Syntax results are static evidence; concept and related-test results remain candidates. ${MODE_CONTRACT_DESCRIPTION} anyOf/allOf are field names, not modes; as mode values they are accepted aliases for omitting mode, disclosed in a request note.`
|
|
20714
21149
|
})),
|
|
20715
21150
|
line: Optional(Number2({
|
|
20716
|
-
description: "1-indexed source line for path inspection/navigation. Omit with matchIndex, matchIndices or targets."
|
|
21151
|
+
description: "1-indexed source line for path inspection/navigation; path-only inspect starts at line 1. Omit with matchIndex, matchIndices or targets."
|
|
20717
21152
|
})),
|
|
20718
21153
|
matchIndex: Optional(Number2({
|
|
20719
21154
|
description: "1-based retained match index for cursor-scoped inspect; replaces path and line."
|
|
20720
21155
|
})),
|
|
20721
21156
|
matchIndices: Optional(_Array_(Integer({ minimum: 1 }), {
|
|
20722
21157
|
minItems: 1,
|
|
20723
|
-
maxItems:
|
|
20724
|
-
description:
|
|
21158
|
+
maxItems: MAX_INSPECT_REQUEST_TARGETS,
|
|
21159
|
+
description: `Inspect visible match numbers together using the same cursor; mutually exclusive with matchIndex, path, line and targets. One response inspects ${String(MAX_INSPECT_TARGETS)}; the rest are returned as an exact nextRequest.`
|
|
20725
21160
|
})),
|
|
20726
21161
|
targets: Optional(_Array_(_Object_({
|
|
20727
21162
|
path: String2({ maxLength: MAX_PATH_CHARACTERS }),
|
|
20728
21163
|
line: Integer({ minimum: 1 })
|
|
20729
21164
|
}), {
|
|
20730
21165
|
minItems: 1,
|
|
20731
|
-
maxItems:
|
|
20732
|
-
description:
|
|
21166
|
+
maxItems: MAX_INSPECT_REQUEST_TARGETS,
|
|
21167
|
+
description: `Inspect known path/line locations together without a cursor. One response inspects ${String(MAX_INSPECT_TARGETS)} within a shared 16 KiB budget; the rest are returned as an exact nextRequest.`
|
|
20733
21168
|
})),
|
|
20734
21169
|
cursor: Optional(String2({ description: "Opaque cursor from a previous stable search snapshot." })),
|
|
20735
21170
|
operationId: Optional(String2({
|