sift-light 1.0.1 → 1.0.3-1
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 +26 -0
- package/README.md +5 -3
- package/README.zh-CN.md +5 -3
- package/package.json +1 -1
- package/plugins/sift-light/.claude-plugin/plugin.json +1 -1
- package/plugins/sift-light/.codex-plugin/plugin.json +3 -2
- package/plugins/sift-light/.mcp.json +1 -1
- package/plugins/sift-light/kimi.plugin.json +2 -2
- package/plugins/sift-light/omp-extension.mjs +517 -72
- package/plugins/sift-light/package.json +1 -1
- package/plugins/sift-light/skills/local-search/SKILL.md +12 -0
- package/src/analysis-limits.ts +1 -1
- package/src/analysis-store.ts +16 -10
- package/src/analysis-term-pages.ts +2 -1
- package/src/analysis-types.ts +10 -1
- package/src/evidence-service.ts +118 -14
- 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 +525 -77
- package/src/prompt-guidelines.ts +11 -5
- 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/result-statistics.ts +3 -3
- package/src/runtime.ts +2 -2
- package/src/service.ts +23 -1
- package/src/session-summary.ts +4 -4
- package/src/source-document.ts +4 -2
- package/src/source-inspection.ts +27 -2
- package/src/term-count-label.ts +6 -0
- 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.1",
|
|
1825
|
+
version: "1.0.3-1",
|
|
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;
|
|
@@ -3479,8 +3482,10 @@ async function readWorkspaceDocument(path, cwd, signal, expected, readBudget = M
|
|
|
3479
3482
|
realpath3(cwd)
|
|
3480
3483
|
]);
|
|
3481
3484
|
if (!canonical)
|
|
3482
|
-
throw new SourceDocumentError("source-unavailable",
|
|
3483
|
-
const before = await getSourceRevision(absolute)
|
|
3485
|
+
throw new SourceDocumentError("source-unavailable", `Path not found: ${path}`);
|
|
3486
|
+
const before = await getSourceRevision(absolute, (error) => {
|
|
3487
|
+
throw error;
|
|
3488
|
+
});
|
|
3484
3489
|
if (!before)
|
|
3485
3490
|
throw new SourceDocumentError("source-unavailable", "Source is unavailable");
|
|
3486
3491
|
if (expected && !sameSourceRevision(before, expected.revision)) {
|
|
@@ -4038,6 +4043,15 @@ var PYTHON_LANGUAGE_CAPABILITIES = [
|
|
|
4038
4043
|
evidence: "syntax",
|
|
4039
4044
|
availability: "implemented",
|
|
4040
4045
|
load: "lazy"
|
|
4046
|
+
},
|
|
4047
|
+
{
|
|
4048
|
+
id: "python-lexical.roles",
|
|
4049
|
+
name: "roles",
|
|
4050
|
+
provider: "bounded Python lexical role scanner",
|
|
4051
|
+
providerKind: "builtin",
|
|
4052
|
+
evidence: "syntax",
|
|
4053
|
+
availability: "implemented",
|
|
4054
|
+
load: "lazy"
|
|
4041
4055
|
}
|
|
4042
4056
|
];
|
|
4043
4057
|
var DEFAULT_LANGUAGE_CAPABILITIES = [
|
|
@@ -5186,6 +5200,11 @@ var SEMANTIC_JUDGE_CLASSIFICATIONS = [
|
|
|
5186
5200
|
];
|
|
5187
5201
|
var SEMANTIC_JUDGE_NON_PROOF_CLAIM = "semantic classification only; local static and runtime verification is not asserted";
|
|
5188
5202
|
|
|
5203
|
+
// src/term-count-label.ts
|
|
5204
|
+
function termCountLabel(term, index, redact = false) {
|
|
5205
|
+
return redact ? `condition #${String(index + 1)}` : `#${String(index + 1)} ${JSON.stringify(term)}`;
|
|
5206
|
+
}
|
|
5207
|
+
|
|
5189
5208
|
// src/analysis-term-pages.ts
|
|
5190
5209
|
var MAX_INLINE_TERM_COUNT_BYTES = 8 * 1024;
|
|
5191
5210
|
function termCountRequest(id, offset, redact = false) {
|
|
@@ -5203,7 +5222,7 @@ function analysisTermPage(result, id, offset) {
|
|
|
5203
5222
|
const term = all[index];
|
|
5204
5223
|
if (!term)
|
|
5205
5224
|
throw new Error("Term inventory index unavailable");
|
|
5206
|
-
const label =
|
|
5225
|
+
const label = termCountLabel(term.term, index, result.redact);
|
|
5207
5226
|
const row = `${label}: ${String(term.retainedOccurrences)} retained occurrences`;
|
|
5208
5227
|
const size = Buffer.byteLength(row) + 2;
|
|
5209
5228
|
if (bytes + size > MAX_INLINE_TERM_COUNT_BYTES)
|
|
@@ -5339,8 +5358,8 @@ function analysisExtraGroups(counts, termCounts, items) {
|
|
|
5339
5358
|
const classification = counts ? statisticsGroup("classification", Object.entries(counts).map(([label, count]) => ({ label, count }))) : undefined;
|
|
5340
5359
|
if (classification)
|
|
5341
5360
|
groups.push(classification);
|
|
5342
|
-
const terms = termCounts ? statisticsGroup("conditions", termCounts.map((term
|
|
5343
|
-
label:
|
|
5361
|
+
const terms = termCounts ? statisticsGroup("conditions", termCounts.map((term) => ({
|
|
5362
|
+
label: term.term,
|
|
5344
5363
|
count: term.retainedOccurrences
|
|
5345
5364
|
}))) : undefined;
|
|
5346
5365
|
if (terms)
|
|
@@ -5487,15 +5506,15 @@ function publicAnalysisItem(result, item, index, storedId, modelOutput) {
|
|
|
5487
5506
|
return {
|
|
5488
5507
|
path: item.path,
|
|
5489
5508
|
line: item.line,
|
|
5490
|
-
label: result.kind === "outline" && modelOutput ? item.label : publicAnalysisLabel(result),
|
|
5509
|
+
label: result.kind === "outline" && modelOutput || result.kind === "files" ? item.label : publicAnalysisLabel(result),
|
|
5491
5510
|
index: index + 1,
|
|
5492
5511
|
...inspect ? { inspect } : {},
|
|
5493
5512
|
...publicDetails ? { details: publicDetails } : {}
|
|
5494
5513
|
};
|
|
5495
5514
|
}
|
|
5496
|
-
function safeTermCounts(termCounts) {
|
|
5515
|
+
function safeTermCounts(termCounts, redact = false) {
|
|
5497
5516
|
return termCounts?.map((entry, index) => ({
|
|
5498
|
-
term:
|
|
5517
|
+
term: termCountLabel(entry.term, index, redact),
|
|
5499
5518
|
retainedOccurrences: entry.retainedOccurrences
|
|
5500
5519
|
}));
|
|
5501
5520
|
}
|
|
@@ -5617,21 +5636,22 @@ class AnalysisStore {
|
|
|
5617
5636
|
throw new CursorError("Analysis item is outside the retained result");
|
|
5618
5637
|
return structuredClone(item);
|
|
5619
5638
|
}
|
|
5620
|
-
page(cursor, modelOutput = false) {
|
|
5639
|
+
page(cursor, modelOutput = false, redact = false) {
|
|
5621
5640
|
const { stored, offset, kind } = this.resolve(cursor);
|
|
5622
|
-
const { result }
|
|
5641
|
+
const result = redact ? { ...stored.result, redact: true } : stored.result;
|
|
5623
5642
|
if (kind === "analysis-terms")
|
|
5624
5643
|
return analysisTermPage(result, stored.id, offset);
|
|
5625
5644
|
const hybridPreview = kind === "analysis-hybrid";
|
|
5626
5645
|
const hybridMatchesRequest = hybridPreview ? { cursor: `${stored.id}.analysis.0`, ...result.redact ? { redact: true } : {} } : undefined;
|
|
5627
|
-
const
|
|
5628
|
-
const
|
|
5646
|
+
const publicTerms = safeTermCounts(result.termCounts, result.redact);
|
|
5647
|
+
const pagedTerms = publicTerms && Buffer.byteLength(JSON.stringify(publicTerms)) > MAX_INLINE_TERM_COUNT_BYTES;
|
|
5648
|
+
const inlineTerms = pagedTerms ? undefined : publicTerms;
|
|
5629
5649
|
const termsRequest = pagedTerms ? termCountRequest(stored.id, 0, result.redact) : undefined;
|
|
5630
5650
|
const items = [];
|
|
5631
5651
|
const sources = [];
|
|
5632
5652
|
const sourceIds = new Map;
|
|
5633
5653
|
const hybridInspectCursor = result.kind === "hybrid" ? `${stored.id}.analysis.0` : undefined;
|
|
5634
|
-
const statistics = statisticsForItems(result.items, result.items.length, result.unit, analysisExtraGroups(result.counts,
|
|
5654
|
+
const statistics = statisticsForItems(result.items, result.items.length, result.unit, analysisExtraGroups(result.counts, inlineTerms, result.items));
|
|
5635
5655
|
const scope = result.scope ? ` Scope: ${result.scope.assertion === "project-wide" ? "project root" : "requested path"} ${JSON.stringify(result.scope.path)}${result.scope.expandedToProjectRoot ? `, expanded after ${JSON.stringify(result.scope.requestedPath)} had no matches` : ""}.${modificationTimeBoundsText(result.scope.modifiedAfterMs, result.scope.modifiedBeforeMs)}` : "";
|
|
5636
5656
|
const coverage = result.coverage ? ` Coverage: ${JSON.stringify(result.coverage)}.` : "";
|
|
5637
5657
|
const stats = result.stats ? ` Stats: ${JSON.stringify(result.stats)}.` : "";
|
|
@@ -5698,7 +5718,8 @@ Inspect: ${JSON.stringify(inspect)}` : ""}`;
|
|
|
5698
5718
|
break;
|
|
5699
5719
|
}
|
|
5700
5720
|
} else {
|
|
5701
|
-
|
|
5721
|
+
const pageSize = result.pageSize ?? 30;
|
|
5722
|
+
for (let index = offset;index < result.items.length && items.length < pageSize; index += 1) {
|
|
5702
5723
|
if (!appendItem(index))
|
|
5703
5724
|
break;
|
|
5704
5725
|
}
|
|
@@ -5812,6 +5833,8 @@ function resolveInspectionTarget(input, cwd, snapshots) {
|
|
|
5812
5833
|
}
|
|
5813
5834
|
if (!path)
|
|
5814
5835
|
throw new SiftLightError("path is required when mode=inspect");
|
|
5836
|
+
if (line === undefined && input.cursor === undefined)
|
|
5837
|
+
line = 1;
|
|
5815
5838
|
if (line === undefined || !Number.isSafeInteger(line) || line < 1) {
|
|
5816
5839
|
throw new SiftLightError("line must be a positive integer when mode=inspect");
|
|
5817
5840
|
}
|
|
@@ -7540,6 +7563,28 @@ async function filterPathsByModificationTime(cwd, paths, modifiedAfterMs, modifi
|
|
|
7540
7563
|
// src/file-discovery.ts
|
|
7541
7564
|
var graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
7542
7565
|
var FILE_QUERY_GLOB_WILDCARD = /[*?]/u;
|
|
7566
|
+
var FILE_QUERY_MATCH_ALL = /^(?:[*/]+|\.\*|\.\+|\*\.\*)$/u;
|
|
7567
|
+
var FILE_QUERY_REGEX_SYNTAX = /[()|^$\\+]/u;
|
|
7568
|
+
var IGNORED_MATCH_SAMPLES = 5;
|
|
7569
|
+
function normalizeFileQuery(query, hasGlob) {
|
|
7570
|
+
const trimmed = query.trim();
|
|
7571
|
+
if (FILE_QUERY_MATCH_ALL.test(trimmed))
|
|
7572
|
+
return {
|
|
7573
|
+
query: "",
|
|
7574
|
+
note: `Query ${JSON.stringify(query)} was treated as listing every file under path; omit query for the same result.`
|
|
7575
|
+
};
|
|
7576
|
+
if (!FILE_QUERY_GLOB_WILDCARD.test(trimmed))
|
|
7577
|
+
return { query };
|
|
7578
|
+
if (!hasGlob && !FILE_QUERY_REGEX_SYNTAX.test(trimmed) && !/\s/u.test(trimmed)) {
|
|
7579
|
+
const glob = trimmed.includes("/") ? trimmed : `**/${trimmed}`;
|
|
7580
|
+
return {
|
|
7581
|
+
query: "",
|
|
7582
|
+
glob,
|
|
7583
|
+
note: `Query ${JSON.stringify(query)} contains glob wildcards and was applied as glob ${JSON.stringify(glob)}; mode=files query otherwise matches filename text.`
|
|
7584
|
+
};
|
|
7585
|
+
}
|
|
7586
|
+
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.`);
|
|
7587
|
+
}
|
|
7543
7588
|
function subsequenceScore(text, query) {
|
|
7544
7589
|
const characters = Array.from(graphemes.segment(text), (item) => item.segment);
|
|
7545
7590
|
const queryCharacters = Array.from(graphemes.segment(query), (item) => item.segment);
|
|
@@ -7590,39 +7635,79 @@ function pathRelativeToDiscoveryRoot(cwd, root, path) {
|
|
|
7590
7635
|
return scoped || platformBasename(absolutePath);
|
|
7591
7636
|
}
|
|
7592
7637
|
async function discoverFiles(input, cwd, signal) {
|
|
7593
|
-
const
|
|
7594
|
-
if (
|
|
7638
|
+
const rawQuery = input.query ?? "";
|
|
7639
|
+
if (rawQuery.length > 256 || !rawQuery.isWellFormed() || /[\r\n\0]/.test(rawQuery))
|
|
7595
7640
|
throw new SiftLightError("File query must be well-formed single-line text of at most 256 characters");
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
const
|
|
7641
|
+
const requestedGlobs = input.glob === undefined ? [] : Array.isArray(input.glob) ? input.glob : [input.glob];
|
|
7642
|
+
const normalizedQuery = normalizeFileQuery(rawQuery, requestedGlobs.length > 0);
|
|
7643
|
+
const query = normalizedQuery.query;
|
|
7644
|
+
const request = normalizeRequest({
|
|
7645
|
+
...input,
|
|
7646
|
+
pattern: "",
|
|
7647
|
+
...normalizedQuery.glob ? { glob: [...requestedGlobs, normalizedQuery.glob] } : {}
|
|
7648
|
+
});
|
|
7599
7649
|
const policy = new SearchPathPolicy(cwd);
|
|
7600
7650
|
const discoveryRoot = request.path ?? ".";
|
|
7601
7651
|
const scoringRoot = await policy.resolveSearchTarget(discoveryRoot);
|
|
7602
|
-
const
|
|
7652
|
+
const includeIgnored = request.ignorePolicy === "include";
|
|
7653
|
+
const listOptions = {
|
|
7603
7654
|
path: scoringRoot,
|
|
7604
7655
|
glob: request.glob,
|
|
7605
7656
|
exclude: request.exclude,
|
|
7606
7657
|
hidden: request.hidden
|
|
7658
|
+
};
|
|
7659
|
+
const files = await listWorkspaceFiles(cwd, signal, {
|
|
7660
|
+
...listOptions,
|
|
7661
|
+
...includeIgnored ? { ignore: false, ignoreParents: false } : {}
|
|
7607
7662
|
});
|
|
7608
7663
|
const filtered = await filterPathsByModificationTime(cwd, files.paths, request.modifiedAfterMs, request.modifiedBeforeMs, signal);
|
|
7664
|
+
const rank = (path) => scoreFilePath(pathRelativeToDiscoveryRoot(cwd, scoringRoot, path), query);
|
|
7609
7665
|
const selected = filtered.paths.flatMap((path) => {
|
|
7610
|
-
const
|
|
7611
|
-
return
|
|
7666
|
+
const score = rank(path);
|
|
7667
|
+
return score ? [{ path, ...score }] : [];
|
|
7612
7668
|
}).toSorted((left, right) => right.score - left.score || left.path.localeCompare(right.path));
|
|
7613
7669
|
for (let offset = 0;offset < selected.length; offset += 16) {
|
|
7614
7670
|
await Promise.all(selected.slice(offset, offset + 16).map((item) => policy.assertExistingPath(item.path)));
|
|
7615
7671
|
signal?.throwIfAborted();
|
|
7616
7672
|
}
|
|
7673
|
+
const reasons = new Set([...files.reasons, ...filtered.reasons]);
|
|
7674
|
+
if (normalizedQuery.note)
|
|
7675
|
+
reasons.add(normalizedQuery.note);
|
|
7676
|
+
let ignoredFiles = 0;
|
|
7677
|
+
let ignoredMatches = 0;
|
|
7678
|
+
let ignoredComparisonPartial = false;
|
|
7679
|
+
if (!includeIgnored) {
|
|
7680
|
+
const everything = await listWorkspaceFiles(cwd, signal, {
|
|
7681
|
+
...listOptions,
|
|
7682
|
+
ignore: false,
|
|
7683
|
+
ignoreParents: false
|
|
7684
|
+
});
|
|
7685
|
+
ignoredComparisonPartial = everything.partial;
|
|
7686
|
+
const admitted = new Set(files.paths);
|
|
7687
|
+
const ignored = everything.paths.filter((path) => !admitted.has(path));
|
|
7688
|
+
ignoredFiles = ignored.length;
|
|
7689
|
+
const ignoredCandidates = ignored.flatMap((path) => {
|
|
7690
|
+
const score = rank(path);
|
|
7691
|
+
return score ? [{ path, ...score }] : [];
|
|
7692
|
+
}).toSorted((left, right) => right.score - left.score || left.path.localeCompare(right.path));
|
|
7693
|
+
ignoredMatches = ignoredCandidates.length;
|
|
7694
|
+
if (ignoredFiles > 0) {
|
|
7695
|
+
const samples = ignoredCandidates.slice(0, IGNORED_MATCH_SAMPLES).map((item) => `${JSON.stringify(item.path)} (${item.reason})`);
|
|
7696
|
+
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.`);
|
|
7697
|
+
}
|
|
7698
|
+
if (ignoredComparisonPartial)
|
|
7699
|
+
reasons.add("Ignored-file comparison was incomplete; the ignored-file count is a lower bound");
|
|
7700
|
+
}
|
|
7701
|
+
const enumerationPartial = files.partial || filtered.partial;
|
|
7617
7702
|
return {
|
|
7618
7703
|
kind: "files",
|
|
7619
7704
|
unit: "files",
|
|
7620
|
-
partial:
|
|
7621
|
-
reasons: [...
|
|
7705
|
+
partial: enumerationPartial,
|
|
7706
|
+
reasons: [...reasons],
|
|
7622
7707
|
items: selected.map((item) => ({
|
|
7623
7708
|
path: item.path,
|
|
7624
7709
|
line: 1,
|
|
7625
|
-
label: `File candidate (${item.reason})`,
|
|
7710
|
+
label: `File candidate (score ${String(item.score)}: ${item.reason})`,
|
|
7626
7711
|
details: {
|
|
7627
7712
|
kind: "file",
|
|
7628
7713
|
score: item.score,
|
|
@@ -7630,8 +7715,13 @@ async function discoverFiles(input, cwd, signal) {
|
|
|
7630
7715
|
inspect: { mode: "inspect", path: item.path, line: 1 }
|
|
7631
7716
|
}
|
|
7632
7717
|
})),
|
|
7633
|
-
coverage: {
|
|
7634
|
-
|
|
7718
|
+
coverage: {
|
|
7719
|
+
fileEnumeration: enumerationPartial ? "partial" : ignoredFiles > 0 || ignoredComparisonPartial ? "policy-filtered" : "complete"
|
|
7720
|
+
},
|
|
7721
|
+
stats: {
|
|
7722
|
+
filesEnumerated: files.paths.length,
|
|
7723
|
+
...includeIgnored ? {} : { ignoredFiles, ignoredMatches }
|
|
7724
|
+
},
|
|
7635
7725
|
scope: {
|
|
7636
7726
|
path: request.path ?? ".",
|
|
7637
7727
|
requestedPath: request.path ?? ".",
|
|
@@ -7644,7 +7734,8 @@ async function discoverFiles(input, cwd, signal) {
|
|
|
7644
7734
|
...request.modifiedAfterMs !== undefined ? { modifiedAfterMs: request.modifiedAfterMs } : {},
|
|
7645
7735
|
...request.modifiedBeforeMs !== undefined ? { modifiedBeforeMs: request.modifiedBeforeMs } : {}
|
|
7646
7736
|
},
|
|
7647
|
-
redact: input.redact ?? false
|
|
7737
|
+
redact: input.redact ?? false,
|
|
7738
|
+
...input.limit !== undefined ? { pageSize: request.pageSize } : {}
|
|
7648
7739
|
};
|
|
7649
7740
|
}
|
|
7650
7741
|
|
|
@@ -7885,7 +7976,19 @@ async function prepare(target, access, structure) {
|
|
|
7885
7976
|
let range = target.range;
|
|
7886
7977
|
let details = { status: "no-symbol" };
|
|
7887
7978
|
const language = syntaxLanguage(document2.path);
|
|
7888
|
-
if (
|
|
7979
|
+
if (target.range && target.structure) {
|
|
7980
|
+
document2.checkRange(target.range);
|
|
7981
|
+
const lines = {
|
|
7982
|
+
startLine: document2.lineAt(target.range.start),
|
|
7983
|
+
endLine: document2.lineAt(Math.max(target.range.start, target.range.end - 1))
|
|
7984
|
+
};
|
|
7985
|
+
range = document2.lineRange(lines.startLine, lines.endLine);
|
|
7986
|
+
details = {
|
|
7987
|
+
...target.structure,
|
|
7988
|
+
range: lines,
|
|
7989
|
+
...target.structure.symbol ? { symbol: { ...target.structure.symbol, range: lines } } : {}
|
|
7990
|
+
};
|
|
7991
|
+
} else if (document2.utf8 && language && language !== "go") {
|
|
7889
7992
|
const syntax = await access.syntax(document2);
|
|
7890
7993
|
details = {
|
|
7891
7994
|
status: syntax.status === "ok" ? "no-symbol" : syntax.status === "unsupported" ? "provider-unavailable" : "parse-error",
|
|
@@ -7937,7 +8040,7 @@ async function prepare(target, access, structure) {
|
|
|
7937
8040
|
details = { status: "provider-unavailable", ...language ? { language } : {} };
|
|
7938
8041
|
}
|
|
7939
8042
|
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";
|
|
8043
|
+
const boundary = target.range ? target.structure ? "syntax" : "requested-range" : details.status === "available" && details.range ? "syntax" : "line-window";
|
|
7941
8044
|
document2.checkRange(range);
|
|
7942
8045
|
return { target, document: document2, range, structure: details, boundary, focus };
|
|
7943
8046
|
}
|
|
@@ -8296,8 +8399,10 @@ ${preview.text}`);
|
|
|
8296
8399
|
}
|
|
8297
8400
|
};
|
|
8298
8401
|
}
|
|
8299
|
-
async function continueSource(cursor, access, continuations) {
|
|
8402
|
+
async function continueSource(cursor, access, continuations, expectedPath) {
|
|
8300
8403
|
const state = continuations.resolve(cursor);
|
|
8404
|
+
if (expectedPath !== undefined && resolve18(access.cwd, expectedPath.replace(/^@/, "")) !== resolve18(access.cwd, state.source.path))
|
|
8405
|
+
throw new SiftLightError(`sourceCursor continues ${JSON.stringify(state.source.path)}, not ${JSON.stringify(expectedPath)}; copy the returned nextRequest exactly`);
|
|
8301
8406
|
const document2 = await access.load(state.source.path, state.source);
|
|
8302
8407
|
const page = sourcePage(document2, state.remaining, MAX_RESULT_BYTES - 1400);
|
|
8303
8408
|
const next = continuations.advance(cursor, page.fragment);
|
|
@@ -9174,6 +9279,221 @@ function parsePythonOutline(document2) {
|
|
|
9174
9279
|
});
|
|
9175
9280
|
}
|
|
9176
9281
|
|
|
9282
|
+
// src/python-roles.ts
|
|
9283
|
+
var KEYWORDS = new Set([
|
|
9284
|
+
"and",
|
|
9285
|
+
"as",
|
|
9286
|
+
"assert",
|
|
9287
|
+
"async",
|
|
9288
|
+
"await",
|
|
9289
|
+
"case",
|
|
9290
|
+
"del",
|
|
9291
|
+
"elif",
|
|
9292
|
+
"else",
|
|
9293
|
+
"except",
|
|
9294
|
+
"for",
|
|
9295
|
+
"from",
|
|
9296
|
+
"global",
|
|
9297
|
+
"if",
|
|
9298
|
+
"import",
|
|
9299
|
+
"in",
|
|
9300
|
+
"is",
|
|
9301
|
+
"lambda",
|
|
9302
|
+
"match",
|
|
9303
|
+
"nonlocal",
|
|
9304
|
+
"not",
|
|
9305
|
+
"or",
|
|
9306
|
+
"raise",
|
|
9307
|
+
"return",
|
|
9308
|
+
"while",
|
|
9309
|
+
"with",
|
|
9310
|
+
"yield"
|
|
9311
|
+
]);
|
|
9312
|
+
var STRING_PREFIX = /^(?:[rRbBuUfF]|[rR][bBfF]|[bBfF][rR])?$/u;
|
|
9313
|
+
var IDENTIFIER_START = /[\p{ID_Start}_]/u;
|
|
9314
|
+
var IDENTIFIER_PART = /[\p{ID_Continue}]/u;
|
|
9315
|
+
function lex(text) {
|
|
9316
|
+
const comments = [];
|
|
9317
|
+
const strings = [];
|
|
9318
|
+
const code = new Uint8Array(text.length).fill(1);
|
|
9319
|
+
const mark = (start2, end) => {
|
|
9320
|
+
code.fill(0, start2, end);
|
|
9321
|
+
};
|
|
9322
|
+
let index = 0;
|
|
9323
|
+
while (index < text.length) {
|
|
9324
|
+
const character = text[index];
|
|
9325
|
+
if (character === "#") {
|
|
9326
|
+
const newline = text.indexOf(`
|
|
9327
|
+
`, index);
|
|
9328
|
+
const end = newline < 0 ? text.length : newline;
|
|
9329
|
+
comments.push({ start: index, end });
|
|
9330
|
+
mark(index, end);
|
|
9331
|
+
index = end;
|
|
9332
|
+
continue;
|
|
9333
|
+
}
|
|
9334
|
+
if (character !== "'" && character !== '"') {
|
|
9335
|
+
index += 1;
|
|
9336
|
+
continue;
|
|
9337
|
+
}
|
|
9338
|
+
let prefixStart = index;
|
|
9339
|
+
while (prefixStart > 0 && /[rRbBuUfF]/u.test(text[prefixStart - 1] ?? ""))
|
|
9340
|
+
prefixStart -= 1;
|
|
9341
|
+
const prefix = text.slice(prefixStart, index);
|
|
9342
|
+
const beforePrefix = text[prefixStart - 1] ?? "";
|
|
9343
|
+
const start2 = STRING_PREFIX.test(prefix) && !IDENTIFIER_PART.test(beforePrefix) ? prefixStart : index;
|
|
9344
|
+
const triple = text.slice(index, index + 3) === character.repeat(3);
|
|
9345
|
+
const delimiter = triple ? character.repeat(3) : character;
|
|
9346
|
+
let cursor = index + delimiter.length;
|
|
9347
|
+
let end = text.length;
|
|
9348
|
+
while (cursor < text.length) {
|
|
9349
|
+
const current = text[cursor];
|
|
9350
|
+
if (current === "\\") {
|
|
9351
|
+
cursor += 2;
|
|
9352
|
+
continue;
|
|
9353
|
+
}
|
|
9354
|
+
if (!triple && current === `
|
|
9355
|
+
`) {
|
|
9356
|
+
end = cursor;
|
|
9357
|
+
break;
|
|
9358
|
+
}
|
|
9359
|
+
if (text.startsWith(delimiter, cursor)) {
|
|
9360
|
+
end = cursor + delimiter.length;
|
|
9361
|
+
break;
|
|
9362
|
+
}
|
|
9363
|
+
cursor += 1;
|
|
9364
|
+
}
|
|
9365
|
+
strings.push({ start: start2, end });
|
|
9366
|
+
mark(start2, end);
|
|
9367
|
+
index = end;
|
|
9368
|
+
}
|
|
9369
|
+
return { comments, strings, code };
|
|
9370
|
+
}
|
|
9371
|
+
function codeRanges(code) {
|
|
9372
|
+
const ranges = [];
|
|
9373
|
+
let start2 = -1;
|
|
9374
|
+
for (let index = 0;index <= code.length; index += 1) {
|
|
9375
|
+
const inCode = index < code.length && code[index] === 1;
|
|
9376
|
+
if (inCode && start2 < 0)
|
|
9377
|
+
start2 = index;
|
|
9378
|
+
else if (!inCode && start2 >= 0) {
|
|
9379
|
+
ranges.push({ start: start2, end: index });
|
|
9380
|
+
start2 = -1;
|
|
9381
|
+
}
|
|
9382
|
+
}
|
|
9383
|
+
return ranges;
|
|
9384
|
+
}
|
|
9385
|
+
function identifierEnd(text, start2) {
|
|
9386
|
+
let end = start2;
|
|
9387
|
+
while (end < text.length && IDENTIFIER_PART.test(text[end] ?? ""))
|
|
9388
|
+
end += 1;
|
|
9389
|
+
return end;
|
|
9390
|
+
}
|
|
9391
|
+
function skipSpaces(text, index, code) {
|
|
9392
|
+
let cursor = index;
|
|
9393
|
+
while (cursor < text.length && code[cursor] === 1 && /[ \t]/u.test(text[cursor] ?? ""))
|
|
9394
|
+
cursor += 1;
|
|
9395
|
+
return cursor;
|
|
9396
|
+
}
|
|
9397
|
+
function statementEnd(text, start2, code) {
|
|
9398
|
+
let depth = 0;
|
|
9399
|
+
for (let index = start2;index < text.length; index += 1) {
|
|
9400
|
+
if (code[index] !== 1)
|
|
9401
|
+
continue;
|
|
9402
|
+
const character = text[index];
|
|
9403
|
+
if (character === "(" || character === "[" || character === "{")
|
|
9404
|
+
depth += 1;
|
|
9405
|
+
else if (character === ")" || character === "]" || character === "}")
|
|
9406
|
+
depth = Math.max(0, depth - 1);
|
|
9407
|
+
else if (character === "\\" && text[index + 1] === `
|
|
9408
|
+
`)
|
|
9409
|
+
index += 1;
|
|
9410
|
+
else if (character === `
|
|
9411
|
+
` && depth === 0)
|
|
9412
|
+
return index;
|
|
9413
|
+
}
|
|
9414
|
+
return text.length;
|
|
9415
|
+
}
|
|
9416
|
+
function pythonRoleAnalysis(document2) {
|
|
9417
|
+
const text = document2.text;
|
|
9418
|
+
const { comments, strings, code } = lex(text);
|
|
9419
|
+
const roles = [];
|
|
9420
|
+
const push = (start2, end, role, certainty, subkind) => {
|
|
9421
|
+
if (start2 < end)
|
|
9422
|
+
roles.push({ start: start2, end, role, certainty, node: 0, ...subkind ? { subkind } : {} });
|
|
9423
|
+
};
|
|
9424
|
+
for (const span of comments)
|
|
9425
|
+
push(span.start, span.end, "comment", "syntax");
|
|
9426
|
+
for (const span of strings)
|
|
9427
|
+
push(span.start, span.end, "string", "syntax");
|
|
9428
|
+
for (const span of codeRanges(code))
|
|
9429
|
+
push(span.start, span.end, "code", "syntax");
|
|
9430
|
+
let index = 0;
|
|
9431
|
+
let lineStart = true;
|
|
9432
|
+
while (index < text.length) {
|
|
9433
|
+
const character = text[index] ?? "";
|
|
9434
|
+
if (character === `
|
|
9435
|
+
`) {
|
|
9436
|
+
lineStart = true;
|
|
9437
|
+
index += 1;
|
|
9438
|
+
continue;
|
|
9439
|
+
}
|
|
9440
|
+
if (code[index] !== 1 || !IDENTIFIER_START.test(character)) {
|
|
9441
|
+
if (!/[ \t]/u.test(character))
|
|
9442
|
+
lineStart = false;
|
|
9443
|
+
index += 1;
|
|
9444
|
+
continue;
|
|
9445
|
+
}
|
|
9446
|
+
const previous = text[index - 1] ?? "";
|
|
9447
|
+
if (IDENTIFIER_PART.test(previous)) {
|
|
9448
|
+
index += 1;
|
|
9449
|
+
continue;
|
|
9450
|
+
}
|
|
9451
|
+
const end = identifierEnd(text, index);
|
|
9452
|
+
const word = text.slice(index, end);
|
|
9453
|
+
const atStatementStart = lineStart;
|
|
9454
|
+
lineStart = false;
|
|
9455
|
+
if (atStatementStart && (word === "import" || word === "from")) {
|
|
9456
|
+
const statement = statementEnd(text, index, code);
|
|
9457
|
+
const body2 = text.slice(index, statement);
|
|
9458
|
+
if (word === "import" || /\bimport\b/u.test(body2)) {
|
|
9459
|
+
push(index, statement, "import", "syntax", word === "from" ? "from-import" : "import");
|
|
9460
|
+
index = statement;
|
|
9461
|
+
continue;
|
|
9462
|
+
}
|
|
9463
|
+
}
|
|
9464
|
+
if (word === "def" || word === "class") {
|
|
9465
|
+
const nameStart = skipSpaces(text, end, code);
|
|
9466
|
+
if (IDENTIFIER_START.test(text[nameStart] ?? "")) {
|
|
9467
|
+
const nameEnd = identifierEnd(text, nameStart);
|
|
9468
|
+
push(nameStart, nameEnd, "declaration", "syntax", word === "def" ? "function" : "class");
|
|
9469
|
+
index = nameEnd;
|
|
9470
|
+
continue;
|
|
9471
|
+
}
|
|
9472
|
+
}
|
|
9473
|
+
let chainEnd = end;
|
|
9474
|
+
while (text[chainEnd] === "." && IDENTIFIER_START.test(text[chainEnd + 1] ?? "")) {
|
|
9475
|
+
chainEnd = identifierEnd(text, chainEnd + 1);
|
|
9476
|
+
}
|
|
9477
|
+
const open = skipSpaces(text, chainEnd, code);
|
|
9478
|
+
const lastSegment = text.slice(text.lastIndexOf(".", chainEnd - 1) + 1, chainEnd);
|
|
9479
|
+
const before = text.slice(Math.max(0, index - 4), index);
|
|
9480
|
+
if (text[open] === "(" && code[open] === 1 && !KEYWORDS.has(word) && !KEYWORDS.has(lastSegment) && !/\bdef\s+$|\bclass\s+$/u.test(before)) {
|
|
9481
|
+
push(index, open + 1, "call", "candidate", "lexical-call");
|
|
9482
|
+
}
|
|
9483
|
+
index = chainEnd;
|
|
9484
|
+
}
|
|
9485
|
+
roles.sort((a, b) => a.start - b.start || a.end - b.end || a.role.localeCompare(b.role));
|
|
9486
|
+
return {
|
|
9487
|
+
status: "ok",
|
|
9488
|
+
nodes: [],
|
|
9489
|
+
children: [],
|
|
9490
|
+
symbols: [],
|
|
9491
|
+
roles,
|
|
9492
|
+
diagnostics: [],
|
|
9493
|
+
limited: false
|
|
9494
|
+
};
|
|
9495
|
+
}
|
|
9496
|
+
|
|
9177
9497
|
// src/hybrid-search.ts
|
|
9178
9498
|
import { resolve as resolve20 } from "path";
|
|
9179
9499
|
|
|
@@ -10080,7 +10400,17 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
10080
10400
|
auto: ordinaryFields,
|
|
10081
10401
|
summary: ordinaryFields,
|
|
10082
10402
|
matches: ordinaryFields,
|
|
10083
|
-
inspect: [
|
|
10403
|
+
inspect: [
|
|
10404
|
+
...commonFields,
|
|
10405
|
+
"path",
|
|
10406
|
+
"paths",
|
|
10407
|
+
"line",
|
|
10408
|
+
"cursor",
|
|
10409
|
+
"matchIndex",
|
|
10410
|
+
"matchIndices",
|
|
10411
|
+
"targets",
|
|
10412
|
+
"scope"
|
|
10413
|
+
],
|
|
10084
10414
|
outline: [...commonFields, "path", "line", "symbol", "cursor", "matchIndex", "maxFilesToParse"],
|
|
10085
10415
|
imports: [
|
|
10086
10416
|
...commonFields,
|
|
@@ -10100,7 +10430,16 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
10100
10430
|
"matchIndex",
|
|
10101
10431
|
"maxFilesToParse"
|
|
10102
10432
|
],
|
|
10103
|
-
files: [
|
|
10433
|
+
files: [
|
|
10434
|
+
...commonFields,
|
|
10435
|
+
"query",
|
|
10436
|
+
...sourceFilters,
|
|
10437
|
+
"ignorePolicy",
|
|
10438
|
+
"limit",
|
|
10439
|
+
"scope",
|
|
10440
|
+
"modifiedAfter",
|
|
10441
|
+
"modifiedBefore"
|
|
10442
|
+
],
|
|
10104
10443
|
structure: [...commonFields, "pattern", ...sourceFilters, "maxFilesToParse"],
|
|
10105
10444
|
concept: [...commonFields, "query", ...sourceFilters, "maxFilesToParse"],
|
|
10106
10445
|
hybrid: [...commonFields, "query", ...sourceFilters, "conceptLimit", "maxFilesToParse"],
|
|
@@ -10109,9 +10448,7 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
10109
10448
|
await: ["mode", "operationId"],
|
|
10110
10449
|
cancel: ["mode", "operationId"]
|
|
10111
10450
|
};
|
|
10112
|
-
var SAFE_DROP_FIELDS = {
|
|
10113
|
-
files: ["scope"]
|
|
10114
|
-
};
|
|
10451
|
+
var SAFE_DROP_FIELDS = {};
|
|
10115
10452
|
var SUPPORTED_OUTLINE_EXTENSIONS = new Set(DEFAULT_LANGUAGE_CAPABILITIES.flatMap((descriptor) => descriptor.capabilities.some((capability) => capability.name === "outline") ? descriptor.extensions : []));
|
|
10116
10453
|
function modeFields(mode) {
|
|
10117
10454
|
return MODE_FIELDS_BY_MODE[mode];
|
|
@@ -10140,8 +10477,8 @@ var REQUEST_FIELD_GUIDANCE = {
|
|
|
10140
10477
|
allOf: "allOf is case-sensitive exact-literal AND; omit pattern, anyOf, literal, ignoreCase, roles",
|
|
10141
10478
|
context: "context is an output-context budget and is never silently dropped",
|
|
10142
10479
|
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",
|
|
10480
|
+
scope: "scope applies to ordinary content search; mode=files is always strict, so it accepts only the redundant scope=strict",
|
|
10481
|
+
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
10482
|
patterns: "audit accepts named exact-literal patterns and returns one closure receipt",
|
|
10146
10483
|
maxFilesToParse: "concept/hybrid automatically batch the requested scope; this optional field sets an advanced hard file ceiling"
|
|
10147
10484
|
};
|
|
@@ -10240,12 +10577,15 @@ function schemaError(input, field, reason) {
|
|
|
10240
10577
|
function plainFilePatternRecovery(mode, input, invalid) {
|
|
10241
10578
|
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
10579
|
}
|
|
10580
|
+
function matchAllFilePatternRecovery(mode, input, invalid) {
|
|
10581
|
+
return mode === "files" && invalid.includes("pattern") && input.query === undefined && typeof input.pattern === "string" && /^(?:\.\*|\.\+|\*+)$/u.test(input.pattern.trim());
|
|
10582
|
+
}
|
|
10243
10583
|
function safeNextRequest(input, mode, invalid) {
|
|
10244
10584
|
if (input.redact === true && containsSensitiveText(input))
|
|
10245
10585
|
return;
|
|
10246
10586
|
const safe = new Set(SAFE_DROP_FIELDS[mode] ?? []);
|
|
10247
10587
|
const plainFilePattern = plainFilePatternRecovery(mode, input, invalid);
|
|
10248
|
-
if (plainFilePattern)
|
|
10588
|
+
if (plainFilePattern || matchAllFilePatternRecovery(mode, input, invalid))
|
|
10249
10589
|
safe.add("pattern");
|
|
10250
10590
|
if (invalid.some((field) => !safe.has(field)))
|
|
10251
10591
|
return;
|
|
@@ -10291,7 +10631,7 @@ function fieldsError(input, mode, invalid, selectorIssues = []) {
|
|
|
10291
10631
|
issues,
|
|
10292
10632
|
recovery: nextRequest ? {
|
|
10293
10633
|
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.`,
|
|
10634
|
+
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
10635
|
nextRequest
|
|
10296
10636
|
} : {
|
|
10297
10637
|
action: "manual",
|
|
@@ -10447,7 +10787,7 @@ function validateRequestContract(input) {
|
|
|
10447
10787
|
const allowed = new Set(MODE_FIELDS_BY_MODE[mode]);
|
|
10448
10788
|
if (mode === "inspect" && raw.sourceCursor !== undefined) {
|
|
10449
10789
|
allowed.clear();
|
|
10450
|
-
for (const field of ["mode", "sourceCursor", "redact"])
|
|
10790
|
+
for (const field of ["mode", "sourceCursor", "redact", "path"])
|
|
10451
10791
|
allowed.add(field);
|
|
10452
10792
|
}
|
|
10453
10793
|
if ((mode === "auto" || mode === "summary" || mode === "matches") && typeof raw.cursor === "string" && raw.cursor.includes(".analysis")) {
|
|
@@ -10475,6 +10815,69 @@ import { realpath as realpath6 } from "fs/promises";
|
|
|
10475
10815
|
function isEvidenceRequest(input) {
|
|
10476
10816
|
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
10817
|
}
|
|
10818
|
+
function pageInspectRequest(input) {
|
|
10819
|
+
if (input.scope === "expand")
|
|
10820
|
+
throw new SiftLightError("mode=inspect reads exact locations; scope=expand does not apply (omit scope)");
|
|
10821
|
+
const { scope: _scope, paths, ...rest } = input;
|
|
10822
|
+
let request = rest;
|
|
10823
|
+
if (paths !== undefined) {
|
|
10824
|
+
if (input.cursor !== undefined || input.targets !== undefined || input.matchIndices !== undefined || input.path !== undefined || input.line !== undefined || input.matchIndex !== undefined)
|
|
10825
|
+
throw new SiftLightError("mode=inspect paths opens files from line 1 and cannot be combined with cursor, targets, matchIndices, path, line or matchIndex");
|
|
10826
|
+
request = { ...rest, targets: paths.map((path) => ({ path, line: 1 })) };
|
|
10827
|
+
}
|
|
10828
|
+
const redact = input.redact ? { redact: true } : {};
|
|
10829
|
+
const requested = request.targets?.length ?? request.matchIndices?.length ?? 0;
|
|
10830
|
+
if (requested > MAX_INSPECT_REQUEST_TARGETS)
|
|
10831
|
+
throw new SiftLightError(`mode=inspect accepts at most ${String(MAX_INSPECT_REQUEST_TARGETS)} targets per request; ${String(MAX_INSPECT_TARGETS)} are inspected per response`);
|
|
10832
|
+
if (request.targets && request.targets.length > MAX_INSPECT_TARGETS)
|
|
10833
|
+
return {
|
|
10834
|
+
current: { ...request, targets: request.targets.slice(0, MAX_INSPECT_TARGETS) },
|
|
10835
|
+
remaining: {
|
|
10836
|
+
mode: "inspect",
|
|
10837
|
+
targets: request.targets.slice(MAX_INSPECT_TARGETS),
|
|
10838
|
+
...redact
|
|
10839
|
+
}
|
|
10840
|
+
};
|
|
10841
|
+
if (request.matchIndices && request.matchIndices.length > MAX_INSPECT_TARGETS)
|
|
10842
|
+
return {
|
|
10843
|
+
current: { ...request, matchIndices: request.matchIndices.slice(0, MAX_INSPECT_TARGETS) },
|
|
10844
|
+
remaining: {
|
|
10845
|
+
mode: "inspect",
|
|
10846
|
+
...request.cursor !== undefined ? { cursor: request.cursor } : {},
|
|
10847
|
+
matchIndices: request.matchIndices.slice(MAX_INSPECT_TARGETS),
|
|
10848
|
+
...redact
|
|
10849
|
+
}
|
|
10850
|
+
};
|
|
10851
|
+
return { current: request };
|
|
10852
|
+
}
|
|
10853
|
+
function withRemainingInspection(result, remaining) {
|
|
10854
|
+
const count = remaining.targets?.length ?? remaining.matchIndices?.length ?? 0;
|
|
10855
|
+
return {
|
|
10856
|
+
text: `${result.text}
|
|
10857
|
+
|
|
10858
|
+
[${String(count)} more target(s) were not inspected; one response inspects ${String(MAX_INSPECT_TARGETS)}.]
|
|
10859
|
+
Next request: ${JSON.stringify(remaining)}`,
|
|
10860
|
+
details: {
|
|
10861
|
+
...result.details,
|
|
10862
|
+
status: "partial",
|
|
10863
|
+
snapshotComplete: false,
|
|
10864
|
+
nextRequest: remaining
|
|
10865
|
+
}
|
|
10866
|
+
};
|
|
10867
|
+
}
|
|
10868
|
+
function analysisItemStructure(item) {
|
|
10869
|
+
const details = item.details ?? {};
|
|
10870
|
+
const language = typeof details.language === "string" ? details.language : undefined;
|
|
10871
|
+
const name2 = typeof details.name === "string" ? details.name : undefined;
|
|
10872
|
+
const scope = Array.isArray(details.scope) ? details.scope.filter((entry) => typeof entry === "string") : typeof details.scope === "string" ? [details.scope] : [];
|
|
10873
|
+
const kind = details.kind === "function" ? "function" : item.label.split(" ", 1)[0] ?? "symbol";
|
|
10874
|
+
return {
|
|
10875
|
+
status: "available",
|
|
10876
|
+
provider: language === "python" ? "python-outline" : "tree-sitter",
|
|
10877
|
+
...language ? { language } : {},
|
|
10878
|
+
...name2 ? { symbol: { name: name2, kind, scope, range: { startLine: item.line, endLine: item.line } } } : {}
|
|
10879
|
+
};
|
|
10880
|
+
}
|
|
10478
10881
|
function maxFilesToParse(value, defaultValue = MAX_STRUCTURE_FILES) {
|
|
10479
10882
|
const candidate = value ?? defaultValue;
|
|
10480
10883
|
if (!Number.isSafeInteger(candidate) || candidate < 1 || candidate > MAX_CONFIGURABLE_STRUCTURE_FILES) {
|
|
@@ -10490,8 +10893,8 @@ function validateTerms(input) {
|
|
|
10490
10893
|
}
|
|
10491
10894
|
return;
|
|
10492
10895
|
}
|
|
10493
|
-
if (!Array.isArray(terms) || terms.length <
|
|
10494
|
-
throw new SiftLightError("allOf requires
|
|
10896
|
+
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)
|
|
10897
|
+
throw new SiftLightError("allOf requires 1\u20133 distinct, nonempty, single-line literal terms");
|
|
10495
10898
|
if (input.pattern !== undefined || input.roles !== undefined || input.literal !== undefined || input.ignoreCase !== undefined || input.wholeWord !== undefined)
|
|
10496
10899
|
throw new SiftLightError("allOf is an explicit case-sensitive literal conjunction; omit pattern, roles, literal and ignoreCase");
|
|
10497
10900
|
if (input.within !== undefined && input.within !== "file" && input.within !== "function")
|
|
@@ -10689,11 +11092,13 @@ class EvidenceService {
|
|
|
10689
11092
|
throw new CursorError("A nonempty sourceCursor is required");
|
|
10690
11093
|
if (input.mode !== "inspect")
|
|
10691
11094
|
throw new SiftLightError("sourceCursor requires mode=inspect");
|
|
10692
|
-
return continueSource(input.sourceCursor, access, this.#continuations);
|
|
11095
|
+
return continueSource(input.sourceCursor, access, this.#continuations, input.path);
|
|
10693
11096
|
}
|
|
10694
11097
|
if (input.mode === "inspect") {
|
|
10695
|
-
const
|
|
10696
|
-
|
|
11098
|
+
const { current, remaining } = pageInspectRequest(input);
|
|
11099
|
+
const targets = this.#inspectionTargets(current, cwd);
|
|
11100
|
+
const result = await inspectDocuments(targets, access, this.#continuations, this.#structure);
|
|
11101
|
+
return remaining ? withRemainingInspection(result, remaining) : result;
|
|
10697
11102
|
}
|
|
10698
11103
|
if (input.mode === "concept") {
|
|
10699
11104
|
const execution = await this.#conceptSearch(input, access, options.onProgress);
|
|
@@ -10773,7 +11178,7 @@ class EvidenceService {
|
|
|
10773
11178
|
this.#analyses.resolve(input.cursor);
|
|
10774
11179
|
if (input.mode !== undefined && input.mode !== "matches" && input.mode !== "auto")
|
|
10775
11180
|
throw new CursorError("Analysis cursor cannot continue in the requested mode", "E_CURSOR_WRONG_KIND");
|
|
10776
|
-
return this.#analyses.page(input.cursor, options.modelOutput);
|
|
11181
|
+
return this.#analyses.page(input.cursor, options.modelOutput, input.redact);
|
|
10777
11182
|
}
|
|
10778
11183
|
if (input.mode === "outline" || input.mode === "imports" || input.mode === "tests")
|
|
10779
11184
|
return this.#navigate(input, access, options);
|
|
@@ -10782,7 +11187,7 @@ class EvidenceService {
|
|
|
10782
11187
|
if (input.pattern !== undefined || input.allOf !== undefined || input.within !== undefined || input.roles !== undefined || input.literal !== undefined || input.ignoreCase !== undefined || input.wholeWord !== undefined)
|
|
10783
11188
|
throw new SiftLightError("anyOf is an explicit case-sensitive literal union; omit pattern, allOf, within, roles, literal and ignoreCase");
|
|
10784
11189
|
if (input.mode !== undefined && input.mode !== "auto" && input.mode !== "matches")
|
|
10785
|
-
throw new SiftLightError("anyOf mode must be omitted, auto, or
|
|
11190
|
+
throw new SiftLightError("anyOf mode must be omitted, auto, matches or summary");
|
|
10786
11191
|
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
11192
|
const { path: _inputPath, ...unscopedInput } = input;
|
|
10788
11193
|
let chunkAccess = access;
|
|
@@ -10920,9 +11325,10 @@ class EvidenceService {
|
|
|
10920
11325
|
if (item)
|
|
10921
11326
|
result.items.push(item);
|
|
10922
11327
|
} else if (terms || input.roles) {
|
|
10923
|
-
|
|
11328
|
+
const pythonRoles = !terms && /\.py$/iu.test(file.document.path);
|
|
11329
|
+
if (pythonRoles || syntaxLanguage(file.document.path))
|
|
10924
11330
|
syntaxCapableFiles += 1;
|
|
10925
|
-
const syntax = await access.syntax(file.document);
|
|
11331
|
+
const syntax = pythonRoles ? pythonRoleAnalysis(file.document) : await access.syntax(file.document);
|
|
10926
11332
|
const classified = terms ? findFunctionConjunctions(file.document, syntax, terms, input.changes?.scope === "lines" ? file.changedRanges : undefined) : filterRoleOccurrences(file.document, syntax, file.occurrences, input.roles ?? []);
|
|
10927
11333
|
result.items.push(...classified.items);
|
|
10928
11334
|
result.partial ||= classified.partial;
|
|
@@ -10999,12 +11405,12 @@ class EvidenceService {
|
|
|
10999
11405
|
const item = this.#analyses.item(input.cursor, input.matchIndex);
|
|
11000
11406
|
if (!item.source || !item.range)
|
|
11001
11407
|
throw new CursorError("This analysis item has no verified source range");
|
|
11002
|
-
const
|
|
11408
|
+
const bounded = item.details?.kind === "symbol" || item.details?.kind === "function";
|
|
11003
11409
|
return {
|
|
11004
11410
|
path: item.path,
|
|
11005
11411
|
line: item.line,
|
|
11006
11412
|
reference: item.source,
|
|
11007
|
-
...
|
|
11413
|
+
...bounded ? { range: item.range, structure: analysisItemStructure(item) } : { absoluteFocus: item.range.start }
|
|
11008
11414
|
};
|
|
11009
11415
|
}
|
|
11010
11416
|
return {
|
|
@@ -11213,6 +11619,29 @@ import { resolve as resolve24 } from "path";
|
|
|
11213
11619
|
// src/discovery-errors.ts
|
|
11214
11620
|
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
11621
|
|
|
11622
|
+
// src/request-aliases.ts
|
|
11623
|
+
var MODE_ALIASES = ["anyOf", "allOf"];
|
|
11624
|
+
function normalizeRequestAliases(request) {
|
|
11625
|
+
const notes = [];
|
|
11626
|
+
const { mode, ...rest } = request;
|
|
11627
|
+
let input = rest;
|
|
11628
|
+
if (mode === "anyOf" || mode === "allOf") {
|
|
11629
|
+
if (request[mode] === undefined)
|
|
11630
|
+
throw new SiftLightError(`mode=${mode} is not a mode; pass ${mode}:[...terms] and omit mode`);
|
|
11631
|
+
notes.push(`mode="${mode}" is not a mode; the ${mode} field alone selects this search.`);
|
|
11632
|
+
} else if (mode === "summary" && request.anyOf !== undefined) {
|
|
11633
|
+
notes.push("anyOf returns one analysis page whose header already carries per-file statistics; mode=summary was served by that page.");
|
|
11634
|
+
} else if (mode !== undefined) {
|
|
11635
|
+
input = { ...rest, mode };
|
|
11636
|
+
}
|
|
11637
|
+
if (input.sourceCursor !== undefined && input.line !== undefined) {
|
|
11638
|
+
const { line: _line, ...withoutLine } = input;
|
|
11639
|
+
input = withoutLine;
|
|
11640
|
+
notes.push("line is ignored with sourceCursor; the continuation selects its own source range.");
|
|
11641
|
+
}
|
|
11642
|
+
return { input, notes };
|
|
11643
|
+
}
|
|
11644
|
+
|
|
11216
11645
|
// src/format.ts
|
|
11217
11646
|
import { readFile as readFile3 } from "fs/promises";
|
|
11218
11647
|
var RESULT_METADATA_RESERVE_BYTES = 1024;
|
|
@@ -12566,6 +12995,16 @@ class LanguageCapabilityCatalog {
|
|
|
12566
12995
|
}
|
|
12567
12996
|
|
|
12568
12997
|
// src/service.ts
|
|
12998
|
+
function withRequestNotes(result, notes) {
|
|
12999
|
+
if (notes.length === 0)
|
|
13000
|
+
return result;
|
|
13001
|
+
return {
|
|
13002
|
+
text: `${notes.map((note) => `[Request note: ${note}]`).join(`
|
|
13003
|
+
`)}
|
|
13004
|
+
${result.text}`,
|
|
13005
|
+
details: { ...result.details, requestNotes: [...notes] }
|
|
13006
|
+
};
|
|
13007
|
+
}
|
|
12569
13008
|
function filterList(value) {
|
|
12570
13009
|
return value === undefined ? [] : Array.isArray(value) ? [...value] : [value];
|
|
12571
13010
|
}
|
|
@@ -12794,7 +13233,11 @@ class SiftLightService {
|
|
|
12794
13233
|
this.#operations = new OperationLifecycle({ deadlineMs: resolveConceptTimeoutMs() });
|
|
12795
13234
|
this.#evidence = new EvidenceService(this.#runRipgrep, this.#snapshots, options.structure, options.conceptSearch, options.semanticJudge);
|
|
12796
13235
|
}
|
|
12797
|
-
async search(
|
|
13236
|
+
async search(request, cwd, signal, options = {}) {
|
|
13237
|
+
const { input, notes } = normalizeRequestAliases(request);
|
|
13238
|
+
return withRequestNotes(await this.#searchNormalized(input, cwd, signal, options), notes);
|
|
13239
|
+
}
|
|
13240
|
+
async#searchNormalized(input, cwd, signal, options) {
|
|
12798
13241
|
validateRawSearchInput(input);
|
|
12799
13242
|
validateRequestContract(input);
|
|
12800
13243
|
if (!this.#vectorSearchEnabled && (input.mode === "concept" || input.mode === "hybrid")) {
|
|
@@ -13199,16 +13642,18 @@ ${page.body}${rangeNote}${contextNote}${missingSelectionNote}
|
|
|
13199
13642
|
}
|
|
13200
13643
|
|
|
13201
13644
|
// src/prompt-guidelines.ts
|
|
13645
|
+
var SIFT_LIGHT_DISCOVERY_GUIDANCE = "Before reporting sift-light absent, check the complete runtime registry, including ALL_TOOLS deferred entries. Read mcp__sift_light__sift_light's declaration and call it via tools in functions.exec; use host tool search otherwise. Unexpanded does not mean absent; call failures are connection/capability failures.";
|
|
13202
13646
|
var SOURCE_OUTPUT_GUIDANCE = "Auto/summary text may include bounded source excerpts; ordinary matches text is metadata-only. Inspect may return bounded source windows covering an entire small file. Analysis text may include semantic passages; structured details may retain excerpts, names and signatures. Follow output limits, coverage and continuations.";
|
|
13203
13647
|
function siftLightPromptGuidelines(structuredOutput = true) {
|
|
13204
13648
|
return [
|
|
13649
|
+
SIFT_LIGHT_DISCOVERY_GUIDANCE,
|
|
13205
13650
|
`Use sift-light for read-only content search. ${SOURCE_OUTPUT_GUIDANCE} For routine development searches, start with fast exact content, filename or applicable structural modes when the request has a usable name, symbol, error text or other literal clue. Omitted mode is ordinary exact search and never loads the local embedding model. Vector search is disabled by default; concept/hybrid require vectorSearchEnabled:true in sift-light.json and an installed model. They can take tens of seconds on an uncached scope, so select them only when semantic recall is needed. Omit mode and limit for automatic detail/summary selection; use mode="matches" for ordinary match metadata.`,
|
|
13206
13651
|
`An omitted path searches the project cwd. Use scope:"strict" for a question restricted to one path; otherwise, if an explicit subpath has zero matches, ordinary and content-analysis searches retry from cwd and return project-wide counts with an expansion notice. Explicit absolute paths and .. traversal can search outside cwd, except protected external system areas and .git internals. Git changes mode remains cwd-scoped.`,
|
|
13207
13652
|
`Search output includes counts, categories, ranked paths, coverage and continuation metadata. Source excerpts may contain the searched text. Use mode="inspect" or the host read capability when exact source is required for an edit or verification.`,
|
|
13208
13653
|
`Use file and directory distributions to choose evidence. Reuse the visible cursor with path or paths for match metadata; mode="summary" pages the remaining file statistics. Match counts are not relevance scores.`,
|
|
13209
13654
|
`Mode="inspect" with a direct path/line or ordinary retained match returns bounded source windows and source-revision metadata. Some retained analysis selectors return only revision metadata; use direct path/line or the host read capability when source is needed. Do not use inspection merely to obtain a citation.`,
|
|
13210
13655
|
`Use allOf:["term1","term2"] for explicit same-file literal AND. Add within:"function" only together with allOf to restrict that conjunction to one own-implementation JS/TS/TSX function; omit within for ordinary single-pattern searches. Use roles:["declaration"] or roles:["call"] with a single pattern for JS/TS/TSX/Go syntactic occurrence statistics.`,
|
|
13211
|
-
`Use anyOf:["term1","term2"] when every exact occurrence of
|
|
13656
|
+
`Use anyOf:["term1","term2"] when every exact occurrence of 1-64 literals is needed in one version-bound result. It is case-sensitive, labels counts with input literals unless redact:true is requested, and runs requests above eight terms as bounded parallel chunks. Large condition inventories have separate continuation pages; copy those requests to retrieve the complete counts.`,
|
|
13212
13657
|
`For a changed-code question, add changes:{base:"HEAD",scope:"lines",side:"new"}; omit target for the working tree, use side:"old" for deleted-side statistics. Copy returned continuation requests to preserve source versions.`,
|
|
13213
13658
|
`Use mode:"capabilities" when the language or requested operation is unclear to get a compact lazy inventory. Use mode:"outline" with a concrete source file path for symbol counts and locations, mode:"imports" for static relationships, and mode:"tests" for related-test candidates. Their text pages summarize metadata; structured details can retain source evidence.`,
|
|
13214
13659
|
`Use mode:"files" plus query for unknown filenames and fuzzy paths. Multi-word filename queries require each word literally in the path; use hybrid/concept only for business concepts that cannot be located by a literal clue. Use wholeWord:true for a single-pattern whole-word search. exclude contains file globs, not content negation.`,
|
|
@@ -20586,9 +21031,9 @@ var siftLightSchema = _Object_({
|
|
|
20586
21031
|
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
21032
|
})),
|
|
20588
21033
|
allOf: Optional(_Array_(String2({ maxLength: MAX_PATH_CHARACTERS }), {
|
|
20589
|
-
minItems:
|
|
21034
|
+
minItems: 1,
|
|
20590
21035
|
maxItems: 3,
|
|
20591
|
-
description: `${fieldGuidance("allOf")}.
|
|
21036
|
+
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
21037
|
})),
|
|
20593
21038
|
within: Optional(stringEnum(["file", "function"], {
|
|
20594
21039
|
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 +21050,7 @@ var siftLightSchema = _Object_({
|
|
|
20605
21050
|
"unknown"
|
|
20606
21051
|
]), {
|
|
20607
21052
|
minItems: 1,
|
|
20608
|
-
description: "Filter each single-pattern occurrence by syntax role (JS/TS/TSX/Go). Roles may be candidates, especially Go call/conversion
|
|
21053
|
+
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
21054
|
})),
|
|
20610
21055
|
changes: Optional(_Object_({
|
|
20611
21056
|
base: Optional(String2({
|
|
@@ -20638,7 +21083,7 @@ var siftLightSchema = _Object_({
|
|
|
20638
21083
|
paths: Optional(_Array_(String2(), {
|
|
20639
21084
|
minItems: 1,
|
|
20640
21085
|
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."
|
|
21086
|
+
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
21087
|
})),
|
|
20643
21088
|
glob: Optional(Union([
|
|
20644
21089
|
String2({ maxLength: MAX_PATH_CHARACTERS }),
|
|
@@ -20662,7 +21107,7 @@ var siftLightSchema = _Object_({
|
|
|
20662
21107
|
})),
|
|
20663
21108
|
hidden: Optional(Boolean2({ description: "Search hidden files (default true; .git is always excluded)." })),
|
|
20664
21109
|
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."
|
|
21110
|
+
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
21111
|
})),
|
|
20667
21112
|
patterns: Optional(_Array_(_Object_({
|
|
20668
21113
|
id: String2({ minLength: 1, maxLength: 64 }),
|
|
@@ -20707,29 +21152,29 @@ var siftLightSchema = _Object_({
|
|
|
20707
21152
|
limit: Optional(Integer({
|
|
20708
21153
|
minimum: 1,
|
|
20709
21154
|
maximum: MAX_PAGE_SIZE,
|
|
20710
|
-
description: `${fieldGuidance("limit")}. Ordinary search
|
|
21155
|
+
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
21156
|
})),
|
|
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}
|
|
21157
|
+
mode: Optional(stringEnum([...SIFT_LIGHT_MODES, ...MODE_ALIASES], {
|
|
21158
|
+
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
21159
|
})),
|
|
20715
21160
|
line: Optional(Number2({
|
|
20716
|
-
description: "1-indexed source line for path inspection/navigation. Omit with matchIndex, matchIndices or targets."
|
|
21161
|
+
description: "1-indexed source line for path inspection/navigation; path-only inspect starts at line 1. Omit with matchIndex, matchIndices or targets."
|
|
20717
21162
|
})),
|
|
20718
21163
|
matchIndex: Optional(Number2({
|
|
20719
21164
|
description: "1-based retained match index for cursor-scoped inspect; replaces path and line."
|
|
20720
21165
|
})),
|
|
20721
21166
|
matchIndices: Optional(_Array_(Integer({ minimum: 1 }), {
|
|
20722
21167
|
minItems: 1,
|
|
20723
|
-
maxItems:
|
|
20724
|
-
description:
|
|
21168
|
+
maxItems: MAX_INSPECT_REQUEST_TARGETS,
|
|
21169
|
+
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
21170
|
})),
|
|
20726
21171
|
targets: Optional(_Array_(_Object_({
|
|
20727
21172
|
path: String2({ maxLength: MAX_PATH_CHARACTERS }),
|
|
20728
21173
|
line: Integer({ minimum: 1 })
|
|
20729
21174
|
}), {
|
|
20730
21175
|
minItems: 1,
|
|
20731
|
-
maxItems:
|
|
20732
|
-
description:
|
|
21176
|
+
maxItems: MAX_INSPECT_REQUEST_TARGETS,
|
|
21177
|
+
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
21178
|
})),
|
|
20734
21179
|
cursor: Optional(String2({ description: "Opaque cursor from a previous stable search snapshot." })),
|
|
20735
21180
|
operationId: Optional(String2({
|