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
package/src/mcp-server.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { URL as URL2 } from "node:url";
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "sift-light",
|
|
10
|
-
version: "1.0.1",
|
|
10
|
+
version: "1.0.3-1",
|
|
11
11
|
description: "Context-efficient local search for files, documents, notes and logs across Pi, OMP and MCP clients",
|
|
12
12
|
keywords: [
|
|
13
13
|
"ai-agent",
|
|
@@ -270,8 +270,8 @@ function analysisExtraGroups(counts, termCounts, items) {
|
|
|
270
270
|
const classification = counts ? statisticsGroup("classification", Object.entries(counts).map(([label, count]) => ({ label, count }))) : undefined;
|
|
271
271
|
if (classification)
|
|
272
272
|
groups.push(classification);
|
|
273
|
-
const terms = termCounts ? statisticsGroup("conditions", termCounts.map((term
|
|
274
|
-
label:
|
|
273
|
+
const terms = termCounts ? statisticsGroup("conditions", termCounts.map((term) => ({
|
|
274
|
+
label: term.term,
|
|
275
275
|
count: term.retainedOccurrences
|
|
276
276
|
}))) : undefined;
|
|
277
277
|
if (terms)
|
|
@@ -346,7 +346,7 @@ function compactRows(analysis) {
|
|
|
346
346
|
rows.push(JSON.stringify(item.path));
|
|
347
347
|
previousPath = item.path;
|
|
348
348
|
}
|
|
349
|
-
const label = analysis.kind === "outline" && analysis.modelOutput ? item.label : "metadata";
|
|
349
|
+
const label = analysis.kind === "outline" && analysis.modelOutput || analysis.kind === "files" ? item.label : "metadata";
|
|
350
350
|
const semanticJudge = item.details?.semanticJudge;
|
|
351
351
|
const judgment = isRecord(semanticJudge) && typeof semanticJudge.classification === "string" && typeof semanticJudge.probability === "number" && typeof semanticJudge.model === "string" ? ` Jev: ${semanticJudge.classification}; probability=${String(semanticJudge.probability)};${typeof semanticJudge.confidence === "number" ? ` confidence=${String(semanticJudge.confidence)};` : ""} model=${JSON.stringify(semanticJudge.model)}.` : "";
|
|
352
352
|
rows.push(`#${String(item.index)} L${String(item.line)} ${label}${judgment}`);
|
|
@@ -386,6 +386,7 @@ function compactMcpModelText(result) {
|
|
|
386
386
|
const inspect = compactInspectInstruction(analysis);
|
|
387
387
|
const nextRequest = distinctNextRequest(result.details, analysis);
|
|
388
388
|
const compact = [
|
|
389
|
+
...(result.details.requestNotes ?? []).map((note) => `[Request note: ${note}]`),
|
|
389
390
|
header,
|
|
390
391
|
...compactMetadata(result.details, analysis),
|
|
391
392
|
...compactRows(analysis),
|
|
@@ -417,7 +418,7 @@ var MAX_ANALYSIS_STORAGE_BYTES = 32 * 1024 * 1024;
|
|
|
417
418
|
var ANALYSIS_METADATA_RESERVE_BYTES = 64 * 1024;
|
|
418
419
|
var MAX_ANALYSIS_REASONS = 64;
|
|
419
420
|
var MAX_ANALYSIS_REASON_BYTES = 4 * 1024;
|
|
420
|
-
var MIN_ANY_OF_TERMS =
|
|
421
|
+
var MIN_ANY_OF_TERMS = 1;
|
|
421
422
|
var MAX_ANY_OF_TERMS = 8;
|
|
422
423
|
var MAX_ANY_OF_TOTAL_TERMS = 64;
|
|
423
424
|
var MAX_LITERAL_TERM_BYTES = 256;
|
|
@@ -437,6 +438,7 @@ var ESTIMATED_CHARACTERS_PER_TOKEN = 4;
|
|
|
437
438
|
var DEFAULT_SUMMARY_FILE_LIMIT = 30;
|
|
438
439
|
var MAX_SELECTED_PATHS = 20;
|
|
439
440
|
var MAX_INSPECT_TARGETS = 5;
|
|
441
|
+
var MAX_INSPECT_REQUEST_TARGETS = 20;
|
|
440
442
|
var MAX_DISPLAYED_OCCURRENCES = 20;
|
|
441
443
|
var MAX_STORED_MATCHES = 50000;
|
|
442
444
|
var MAX_STORED_OCCURRENCES = 200000;
|
|
@@ -458,6 +460,7 @@ var PRIVATE_KEY = /-----BEGIN ([^-\r\n]*PRIVATE KEY)-----[\s\S]*?-----END \1----
|
|
|
458
460
|
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]+)*)`;
|
|
459
461
|
var SENSITIVE_ASSIGNMENT = new RegExp(String.raw`((?<![A-Za-z0-9_-])(?:["']?${SENSITIVE_NAME}["']?)\s*[:=]\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^\s,;}\r\n]+)`, "gi");
|
|
460
462
|
var SENSITIVE_TOKEN = /\b(?:sk|ghp|xox[baprs])[-_][A-Za-z0-9][A-Za-z0-9_-]{7,}\b/g;
|
|
463
|
+
var CODE_EXPRESSION = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*\s*[([]/u;
|
|
461
464
|
var TYPE_ONLY_VALUES = new Set([
|
|
462
465
|
"boolean",
|
|
463
466
|
"number",
|
|
@@ -477,10 +480,11 @@ function redactString(value) {
|
|
|
477
480
|
});
|
|
478
481
|
redacted = redacted.replace(SENSITIVE_ASSIGNMENT, (match, prefix, rawValue) => {
|
|
479
482
|
const unquoted = rawValue.replace(/^["']|["']$/g, "").toLowerCase();
|
|
480
|
-
if (TYPE_ONLY_VALUES.has(unquoted))
|
|
483
|
+
if (TYPE_ONLY_VALUES.has(unquoted) || CODE_EXPRESSION.test(rawValue))
|
|
481
484
|
return match;
|
|
482
485
|
count += 1;
|
|
483
|
-
|
|
486
|
+
const quote = rawValue.startsWith('"') || rawValue.startsWith("'") ? rawValue[0] : "";
|
|
487
|
+
return `${prefix}${quote}[REDACTED]${quote}`;
|
|
484
488
|
});
|
|
485
489
|
redacted = redacted.replace(SENSITIVE_TOKEN, () => {
|
|
486
490
|
count += 1;
|
|
@@ -738,6 +742,15 @@ var PYTHON_LANGUAGE_CAPABILITIES = [
|
|
|
738
742
|
evidence: "syntax",
|
|
739
743
|
availability: "implemented",
|
|
740
744
|
load: "lazy"
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
id: "python-lexical.roles",
|
|
748
|
+
name: "roles",
|
|
749
|
+
provider: "bounded Python lexical role scanner",
|
|
750
|
+
providerKind: "builtin",
|
|
751
|
+
evidence: "syntax",
|
|
752
|
+
availability: "implemented",
|
|
753
|
+
load: "lazy"
|
|
741
754
|
}
|
|
742
755
|
];
|
|
743
756
|
var DEFAULT_LANGUAGE_CAPABILITIES = [
|
|
@@ -865,7 +878,17 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
865
878
|
auto: ordinaryFields,
|
|
866
879
|
summary: ordinaryFields,
|
|
867
880
|
matches: ordinaryFields,
|
|
868
|
-
inspect: [
|
|
881
|
+
inspect: [
|
|
882
|
+
...commonFields,
|
|
883
|
+
"path",
|
|
884
|
+
"paths",
|
|
885
|
+
"line",
|
|
886
|
+
"cursor",
|
|
887
|
+
"matchIndex",
|
|
888
|
+
"matchIndices",
|
|
889
|
+
"targets",
|
|
890
|
+
"scope"
|
|
891
|
+
],
|
|
869
892
|
outline: [...commonFields, "path", "line", "symbol", "cursor", "matchIndex", "maxFilesToParse"],
|
|
870
893
|
imports: [
|
|
871
894
|
...commonFields,
|
|
@@ -885,7 +908,16 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
885
908
|
"matchIndex",
|
|
886
909
|
"maxFilesToParse"
|
|
887
910
|
],
|
|
888
|
-
files: [
|
|
911
|
+
files: [
|
|
912
|
+
...commonFields,
|
|
913
|
+
"query",
|
|
914
|
+
...sourceFilters,
|
|
915
|
+
"ignorePolicy",
|
|
916
|
+
"limit",
|
|
917
|
+
"scope",
|
|
918
|
+
"modifiedAfter",
|
|
919
|
+
"modifiedBefore"
|
|
920
|
+
],
|
|
889
921
|
structure: [...commonFields, "pattern", ...sourceFilters, "maxFilesToParse"],
|
|
890
922
|
concept: [...commonFields, "query", ...sourceFilters, "maxFilesToParse"],
|
|
891
923
|
hybrid: [...commonFields, "query", ...sourceFilters, "conceptLimit", "maxFilesToParse"],
|
|
@@ -894,9 +926,7 @@ var MODE_FIELDS_BY_MODE = {
|
|
|
894
926
|
await: ["mode", "operationId"],
|
|
895
927
|
cancel: ["mode", "operationId"]
|
|
896
928
|
};
|
|
897
|
-
var SAFE_DROP_FIELDS = {
|
|
898
|
-
files: ["scope"]
|
|
899
|
-
};
|
|
929
|
+
var SAFE_DROP_FIELDS = {};
|
|
900
930
|
var SUPPORTED_OUTLINE_EXTENSIONS = new Set(DEFAULT_LANGUAGE_CAPABILITIES.flatMap((descriptor) => descriptor.capabilities.some((capability) => capability.name === "outline") ? descriptor.extensions : []));
|
|
901
931
|
function modeFields(mode) {
|
|
902
932
|
return MODE_FIELDS_BY_MODE[mode];
|
|
@@ -925,8 +955,8 @@ var REQUEST_FIELD_GUIDANCE = {
|
|
|
925
955
|
allOf: "allOf is case-sensitive exact-literal AND; omit pattern, anyOf, literal, ignoreCase, roles",
|
|
926
956
|
context: "context is an output-context budget and is never silently dropped",
|
|
927
957
|
limit: "limit is an output/page budget and is never silently dropped",
|
|
928
|
-
scope: "scope applies to ordinary content search; mode=files
|
|
929
|
-
ignorePolicy: "respect keeps repository ignore rules; include searches ignored files but always excludes .git internals and protected paths",
|
|
958
|
+
scope: "scope applies to ordinary content search; mode=files is always strict, so it accepts only the redundant scope=strict",
|
|
959
|
+
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",
|
|
930
960
|
patterns: "audit accepts named exact-literal patterns and returns one closure receipt",
|
|
931
961
|
maxFilesToParse: "concept/hybrid automatically batch the requested scope; this optional field sets an advanced hard file ceiling"
|
|
932
962
|
};
|
|
@@ -1025,12 +1055,15 @@ function schemaError(input, field, reason) {
|
|
|
1025
1055
|
function plainFilePatternRecovery(mode, input, invalid) {
|
|
1026
1056
|
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);
|
|
1027
1057
|
}
|
|
1058
|
+
function matchAllFilePatternRecovery(mode, input, invalid) {
|
|
1059
|
+
return mode === "files" && invalid.includes("pattern") && input.query === undefined && typeof input.pattern === "string" && /^(?:\.\*|\.\+|\*+)$/u.test(input.pattern.trim());
|
|
1060
|
+
}
|
|
1028
1061
|
function safeNextRequest(input, mode, invalid) {
|
|
1029
1062
|
if (input.redact === true && containsSensitiveText(input))
|
|
1030
1063
|
return;
|
|
1031
1064
|
const safe = new Set(SAFE_DROP_FIELDS[mode] ?? []);
|
|
1032
1065
|
const plainFilePattern = plainFilePatternRecovery(mode, input, invalid);
|
|
1033
|
-
if (plainFilePattern)
|
|
1066
|
+
if (plainFilePattern || matchAllFilePatternRecovery(mode, input, invalid))
|
|
1034
1067
|
safe.add("pattern");
|
|
1035
1068
|
if (invalid.some((field) => !safe.has(field)))
|
|
1036
1069
|
return;
|
|
@@ -1076,7 +1109,7 @@ function fieldsError(input, mode, invalid, selectorIssues = []) {
|
|
|
1076
1109
|
issues,
|
|
1077
1110
|
recovery: nextRequest ? {
|
|
1078
1111
|
action: "retry",
|
|
1079
|
-
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.`,
|
|
1112
|
+
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.`,
|
|
1080
1113
|
nextRequest
|
|
1081
1114
|
} : {
|
|
1082
1115
|
action: "manual",
|
|
@@ -1232,7 +1265,7 @@ function validateRequestContract(input) {
|
|
|
1232
1265
|
const allowed = new Set(MODE_FIELDS_BY_MODE[mode]);
|
|
1233
1266
|
if (mode === "inspect" && raw.sourceCursor !== undefined) {
|
|
1234
1267
|
allowed.clear();
|
|
1235
|
-
for (const field of ["mode", "sourceCursor", "redact"])
|
|
1268
|
+
for (const field of ["mode", "sourceCursor", "redact", "path"])
|
|
1236
1269
|
allowed.add(field);
|
|
1237
1270
|
}
|
|
1238
1271
|
if ((mode === "auto" || mode === "summary" || mode === "matches") && typeof raw.cursor === "string" && raw.cursor.includes(".analysis")) {
|
|
@@ -2948,7 +2981,7 @@ function normalizeRequest(input) {
|
|
|
2948
2981
|
throw new SiftLightError("ignorePolicy must be respect or include");
|
|
2949
2982
|
const pattern = input.pattern;
|
|
2950
2983
|
if (pattern === undefined) {
|
|
2951
|
-
throw new SiftLightError(
|
|
2984
|
+
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');
|
|
2952
2985
|
}
|
|
2953
2986
|
const path = input.path?.replace(/^@/, "");
|
|
2954
2987
|
return {
|
|
@@ -4119,8 +4152,10 @@ async function readWorkspaceDocument(path, cwd, signal, expected, readBudget = M
|
|
|
4119
4152
|
realpath3(cwd)
|
|
4120
4153
|
]);
|
|
4121
4154
|
if (!canonical)
|
|
4122
|
-
throw new SourceDocumentError("source-unavailable",
|
|
4123
|
-
const before = await getSourceRevision(absolute)
|
|
4155
|
+
throw new SourceDocumentError("source-unavailable", `Path not found: ${path}`);
|
|
4156
|
+
const before = await getSourceRevision(absolute, (error) => {
|
|
4157
|
+
throw error;
|
|
4158
|
+
});
|
|
4124
4159
|
if (!before)
|
|
4125
4160
|
throw new SourceDocumentError("source-unavailable", "Source is unavailable");
|
|
4126
4161
|
if (expected && !sameSourceRevision(before, expected.revision)) {
|
|
@@ -5674,6 +5709,11 @@ var SEMANTIC_JUDGE_CLASSIFICATIONS = [
|
|
|
5674
5709
|
];
|
|
5675
5710
|
var SEMANTIC_JUDGE_NON_PROOF_CLAIM = "semantic classification only; local static and runtime verification is not asserted";
|
|
5676
5711
|
|
|
5712
|
+
// src/term-count-label.ts
|
|
5713
|
+
function termCountLabel(term, index, redact = false) {
|
|
5714
|
+
return redact ? `condition #${String(index + 1)}` : `#${String(index + 1)} ${JSON.stringify(term)}`;
|
|
5715
|
+
}
|
|
5716
|
+
|
|
5677
5717
|
// src/analysis-term-pages.ts
|
|
5678
5718
|
var MAX_INLINE_TERM_COUNT_BYTES = 8 * 1024;
|
|
5679
5719
|
function termCountRequest(id, offset, redact = false) {
|
|
@@ -5691,7 +5731,7 @@ function analysisTermPage(result, id, offset) {
|
|
|
5691
5731
|
const term = all[index];
|
|
5692
5732
|
if (!term)
|
|
5693
5733
|
throw new Error("Term inventory index unavailable");
|
|
5694
|
-
const label =
|
|
5734
|
+
const label = termCountLabel(term.term, index, result.redact);
|
|
5695
5735
|
const row = `${label}: ${String(term.retainedOccurrences)} retained occurrences`;
|
|
5696
5736
|
const size = Buffer.byteLength(row) + 2;
|
|
5697
5737
|
if (bytes + size > MAX_INLINE_TERM_COUNT_BYTES)
|
|
@@ -5872,15 +5912,15 @@ function publicAnalysisItem(result, item, index, storedId, modelOutput) {
|
|
|
5872
5912
|
return {
|
|
5873
5913
|
path: item.path,
|
|
5874
5914
|
line: item.line,
|
|
5875
|
-
label: result.kind === "outline" && modelOutput ? item.label : publicAnalysisLabel(result),
|
|
5915
|
+
label: result.kind === "outline" && modelOutput || result.kind === "files" ? item.label : publicAnalysisLabel(result),
|
|
5876
5916
|
index: index + 1,
|
|
5877
5917
|
...inspect ? { inspect } : {},
|
|
5878
5918
|
...publicDetails ? { details: publicDetails } : {}
|
|
5879
5919
|
};
|
|
5880
5920
|
}
|
|
5881
|
-
function safeTermCounts(termCounts) {
|
|
5921
|
+
function safeTermCounts(termCounts, redact = false) {
|
|
5882
5922
|
return termCounts?.map((entry, index) => ({
|
|
5883
|
-
term:
|
|
5923
|
+
term: termCountLabel(entry.term, index, redact),
|
|
5884
5924
|
retainedOccurrences: entry.retainedOccurrences
|
|
5885
5925
|
}));
|
|
5886
5926
|
}
|
|
@@ -6002,21 +6042,22 @@ class AnalysisStore {
|
|
|
6002
6042
|
throw new CursorError("Analysis item is outside the retained result");
|
|
6003
6043
|
return structuredClone(item);
|
|
6004
6044
|
}
|
|
6005
|
-
page(cursor, modelOutput = false) {
|
|
6045
|
+
page(cursor, modelOutput = false, redact = false) {
|
|
6006
6046
|
const { stored, offset, kind } = this.resolve(cursor);
|
|
6007
|
-
const { result }
|
|
6047
|
+
const result = redact ? { ...stored.result, redact: true } : stored.result;
|
|
6008
6048
|
if (kind === "analysis-terms")
|
|
6009
6049
|
return analysisTermPage(result, stored.id, offset);
|
|
6010
6050
|
const hybridPreview = kind === "analysis-hybrid";
|
|
6011
6051
|
const hybridMatchesRequest = hybridPreview ? { cursor: `${stored.id}.analysis.0`, ...result.redact ? { redact: true } : {} } : undefined;
|
|
6012
|
-
const
|
|
6013
|
-
const
|
|
6052
|
+
const publicTerms = safeTermCounts(result.termCounts, result.redact);
|
|
6053
|
+
const pagedTerms = publicTerms && Buffer.byteLength(JSON.stringify(publicTerms)) > MAX_INLINE_TERM_COUNT_BYTES;
|
|
6054
|
+
const inlineTerms = pagedTerms ? undefined : publicTerms;
|
|
6014
6055
|
const termsRequest = pagedTerms ? termCountRequest(stored.id, 0, result.redact) : undefined;
|
|
6015
6056
|
const items = [];
|
|
6016
6057
|
const sources = [];
|
|
6017
6058
|
const sourceIds = new Map;
|
|
6018
6059
|
const hybridInspectCursor = result.kind === "hybrid" ? `${stored.id}.analysis.0` : undefined;
|
|
6019
|
-
const statistics = statisticsForItems(result.items, result.items.length, result.unit, analysisExtraGroups(result.counts,
|
|
6060
|
+
const statistics = statisticsForItems(result.items, result.items.length, result.unit, analysisExtraGroups(result.counts, inlineTerms, result.items));
|
|
6020
6061
|
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)}` : "";
|
|
6021
6062
|
const coverage = result.coverage ? ` Coverage: ${JSON.stringify(result.coverage)}.` : "";
|
|
6022
6063
|
const stats = result.stats ? ` Stats: ${JSON.stringify(result.stats)}.` : "";
|
|
@@ -6083,7 +6124,8 @@ Inspect: ${JSON.stringify(inspect)}` : ""}`;
|
|
|
6083
6124
|
break;
|
|
6084
6125
|
}
|
|
6085
6126
|
} else {
|
|
6086
|
-
|
|
6127
|
+
const pageSize = result.pageSize ?? 30;
|
|
6128
|
+
for (let index = offset;index < result.items.length && items.length < pageSize; index += 1) {
|
|
6087
6129
|
if (!appendItem(index))
|
|
6088
6130
|
break;
|
|
6089
6131
|
}
|
|
@@ -6197,6 +6239,8 @@ function resolveInspectionTarget(input, cwd, snapshots) {
|
|
|
6197
6239
|
}
|
|
6198
6240
|
if (!path)
|
|
6199
6241
|
throw new SiftLightError("path is required when mode=inspect");
|
|
6242
|
+
if (line === undefined && input.cursor === undefined)
|
|
6243
|
+
line = 1;
|
|
6200
6244
|
if (line === undefined || !Number.isSafeInteger(line) || line < 1) {
|
|
6201
6245
|
throw new SiftLightError("line must be a positive integer when mode=inspect");
|
|
6202
6246
|
}
|
|
@@ -7925,6 +7969,28 @@ async function filterPathsByModificationTime(cwd, paths, modifiedAfterMs, modifi
|
|
|
7925
7969
|
// src/file-discovery.ts
|
|
7926
7970
|
var graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
7927
7971
|
var FILE_QUERY_GLOB_WILDCARD = /[*?]/u;
|
|
7972
|
+
var FILE_QUERY_MATCH_ALL = /^(?:[*/]+|\.\*|\.\+|\*\.\*)$/u;
|
|
7973
|
+
var FILE_QUERY_REGEX_SYNTAX = /[()|^$\\+]/u;
|
|
7974
|
+
var IGNORED_MATCH_SAMPLES = 5;
|
|
7975
|
+
function normalizeFileQuery(query, hasGlob) {
|
|
7976
|
+
const trimmed = query.trim();
|
|
7977
|
+
if (FILE_QUERY_MATCH_ALL.test(trimmed))
|
|
7978
|
+
return {
|
|
7979
|
+
query: "",
|
|
7980
|
+
note: `Query ${JSON.stringify(query)} was treated as listing every file under path; omit query for the same result.`
|
|
7981
|
+
};
|
|
7982
|
+
if (!FILE_QUERY_GLOB_WILDCARD.test(trimmed))
|
|
7983
|
+
return { query };
|
|
7984
|
+
if (!hasGlob && !FILE_QUERY_REGEX_SYNTAX.test(trimmed) && !/\s/u.test(trimmed)) {
|
|
7985
|
+
const glob = trimmed.includes("/") ? trimmed : `**/${trimmed}`;
|
|
7986
|
+
return {
|
|
7987
|
+
query: "",
|
|
7988
|
+
glob,
|
|
7989
|
+
note: `Query ${JSON.stringify(query)} contains glob wildcards and was applied as glob ${JSON.stringify(glob)}; mode=files query otherwise matches filename text.`
|
|
7990
|
+
};
|
|
7991
|
+
}
|
|
7992
|
+
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.`);
|
|
7993
|
+
}
|
|
7928
7994
|
function subsequenceScore(text, query) {
|
|
7929
7995
|
const characters = Array.from(graphemes.segment(text), (item) => item.segment);
|
|
7930
7996
|
const queryCharacters = Array.from(graphemes.segment(query), (item) => item.segment);
|
|
@@ -7975,39 +8041,79 @@ function pathRelativeToDiscoveryRoot(cwd, root, path) {
|
|
|
7975
8041
|
return scoped || platformBasename(absolutePath);
|
|
7976
8042
|
}
|
|
7977
8043
|
async function discoverFiles(input, cwd, signal) {
|
|
7978
|
-
const
|
|
7979
|
-
if (
|
|
8044
|
+
const rawQuery = input.query ?? "";
|
|
8045
|
+
if (rawQuery.length > 256 || !rawQuery.isWellFormed() || /[\r\n\0]/.test(rawQuery))
|
|
7980
8046
|
throw new SiftLightError("File query must be well-formed single-line text of at most 256 characters");
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
const
|
|
8047
|
+
const requestedGlobs = input.glob === undefined ? [] : Array.isArray(input.glob) ? input.glob : [input.glob];
|
|
8048
|
+
const normalizedQuery = normalizeFileQuery(rawQuery, requestedGlobs.length > 0);
|
|
8049
|
+
const query = normalizedQuery.query;
|
|
8050
|
+
const request = normalizeRequest({
|
|
8051
|
+
...input,
|
|
8052
|
+
pattern: "",
|
|
8053
|
+
...normalizedQuery.glob ? { glob: [...requestedGlobs, normalizedQuery.glob] } : {}
|
|
8054
|
+
});
|
|
7984
8055
|
const policy = new SearchPathPolicy(cwd);
|
|
7985
8056
|
const discoveryRoot = request.path ?? ".";
|
|
7986
8057
|
const scoringRoot = await policy.resolveSearchTarget(discoveryRoot);
|
|
7987
|
-
const
|
|
8058
|
+
const includeIgnored = request.ignorePolicy === "include";
|
|
8059
|
+
const listOptions = {
|
|
7988
8060
|
path: scoringRoot,
|
|
7989
8061
|
glob: request.glob,
|
|
7990
8062
|
exclude: request.exclude,
|
|
7991
8063
|
hidden: request.hidden
|
|
8064
|
+
};
|
|
8065
|
+
const files = await listWorkspaceFiles(cwd, signal, {
|
|
8066
|
+
...listOptions,
|
|
8067
|
+
...includeIgnored ? { ignore: false, ignoreParents: false } : {}
|
|
7992
8068
|
});
|
|
7993
8069
|
const filtered = await filterPathsByModificationTime(cwd, files.paths, request.modifiedAfterMs, request.modifiedBeforeMs, signal);
|
|
8070
|
+
const rank = (path) => scoreFilePath(pathRelativeToDiscoveryRoot(cwd, scoringRoot, path), query);
|
|
7994
8071
|
const selected = filtered.paths.flatMap((path) => {
|
|
7995
|
-
const
|
|
7996
|
-
return
|
|
8072
|
+
const score = rank(path);
|
|
8073
|
+
return score ? [{ path, ...score }] : [];
|
|
7997
8074
|
}).toSorted((left, right) => right.score - left.score || left.path.localeCompare(right.path));
|
|
7998
8075
|
for (let offset = 0;offset < selected.length; offset += 16) {
|
|
7999
8076
|
await Promise.all(selected.slice(offset, offset + 16).map((item) => policy.assertExistingPath(item.path)));
|
|
8000
8077
|
signal?.throwIfAborted();
|
|
8001
8078
|
}
|
|
8079
|
+
const reasons = new Set([...files.reasons, ...filtered.reasons]);
|
|
8080
|
+
if (normalizedQuery.note)
|
|
8081
|
+
reasons.add(normalizedQuery.note);
|
|
8082
|
+
let ignoredFiles = 0;
|
|
8083
|
+
let ignoredMatches = 0;
|
|
8084
|
+
let ignoredComparisonPartial = false;
|
|
8085
|
+
if (!includeIgnored) {
|
|
8086
|
+
const everything = await listWorkspaceFiles(cwd, signal, {
|
|
8087
|
+
...listOptions,
|
|
8088
|
+
ignore: false,
|
|
8089
|
+
ignoreParents: false
|
|
8090
|
+
});
|
|
8091
|
+
ignoredComparisonPartial = everything.partial;
|
|
8092
|
+
const admitted = new Set(files.paths);
|
|
8093
|
+
const ignored = everything.paths.filter((path) => !admitted.has(path));
|
|
8094
|
+
ignoredFiles = ignored.length;
|
|
8095
|
+
const ignoredCandidates = ignored.flatMap((path) => {
|
|
8096
|
+
const score = rank(path);
|
|
8097
|
+
return score ? [{ path, ...score }] : [];
|
|
8098
|
+
}).toSorted((left, right) => right.score - left.score || left.path.localeCompare(right.path));
|
|
8099
|
+
ignoredMatches = ignoredCandidates.length;
|
|
8100
|
+
if (ignoredFiles > 0) {
|
|
8101
|
+
const samples = ignoredCandidates.slice(0, IGNORED_MATCH_SAMPLES).map((item) => `${JSON.stringify(item.path)} (${item.reason})`);
|
|
8102
|
+
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.`);
|
|
8103
|
+
}
|
|
8104
|
+
if (ignoredComparisonPartial)
|
|
8105
|
+
reasons.add("Ignored-file comparison was incomplete; the ignored-file count is a lower bound");
|
|
8106
|
+
}
|
|
8107
|
+
const enumerationPartial = files.partial || filtered.partial;
|
|
8002
8108
|
return {
|
|
8003
8109
|
kind: "files",
|
|
8004
8110
|
unit: "files",
|
|
8005
|
-
partial:
|
|
8006
|
-
reasons: [...
|
|
8111
|
+
partial: enumerationPartial,
|
|
8112
|
+
reasons: [...reasons],
|
|
8007
8113
|
items: selected.map((item) => ({
|
|
8008
8114
|
path: item.path,
|
|
8009
8115
|
line: 1,
|
|
8010
|
-
label: `File candidate (${item.reason})`,
|
|
8116
|
+
label: `File candidate (score ${String(item.score)}: ${item.reason})`,
|
|
8011
8117
|
details: {
|
|
8012
8118
|
kind: "file",
|
|
8013
8119
|
score: item.score,
|
|
@@ -8015,8 +8121,13 @@ async function discoverFiles(input, cwd, signal) {
|
|
|
8015
8121
|
inspect: { mode: "inspect", path: item.path, line: 1 }
|
|
8016
8122
|
}
|
|
8017
8123
|
})),
|
|
8018
|
-
coverage: {
|
|
8019
|
-
|
|
8124
|
+
coverage: {
|
|
8125
|
+
fileEnumeration: enumerationPartial ? "partial" : ignoredFiles > 0 || ignoredComparisonPartial ? "policy-filtered" : "complete"
|
|
8126
|
+
},
|
|
8127
|
+
stats: {
|
|
8128
|
+
filesEnumerated: files.paths.length,
|
|
8129
|
+
...includeIgnored ? {} : { ignoredFiles, ignoredMatches }
|
|
8130
|
+
},
|
|
8020
8131
|
scope: {
|
|
8021
8132
|
path: request.path ?? ".",
|
|
8022
8133
|
requestedPath: request.path ?? ".",
|
|
@@ -8029,7 +8140,8 @@ async function discoverFiles(input, cwd, signal) {
|
|
|
8029
8140
|
...request.modifiedAfterMs !== undefined ? { modifiedAfterMs: request.modifiedAfterMs } : {},
|
|
8030
8141
|
...request.modifiedBeforeMs !== undefined ? { modifiedBeforeMs: request.modifiedBeforeMs } : {}
|
|
8031
8142
|
},
|
|
8032
|
-
redact: input.redact ?? false
|
|
8143
|
+
redact: input.redact ?? false,
|
|
8144
|
+
...input.limit !== undefined ? { pageSize: request.pageSize } : {}
|
|
8033
8145
|
};
|
|
8034
8146
|
}
|
|
8035
8147
|
|
|
@@ -8270,7 +8382,19 @@ async function prepare(target, access, structure) {
|
|
|
8270
8382
|
let range = target.range;
|
|
8271
8383
|
let details = { status: "no-symbol" };
|
|
8272
8384
|
const language = syntaxLanguage(document.path);
|
|
8273
|
-
if (
|
|
8385
|
+
if (target.range && target.structure) {
|
|
8386
|
+
document.checkRange(target.range);
|
|
8387
|
+
const lines = {
|
|
8388
|
+
startLine: document.lineAt(target.range.start),
|
|
8389
|
+
endLine: document.lineAt(Math.max(target.range.start, target.range.end - 1))
|
|
8390
|
+
};
|
|
8391
|
+
range = document.lineRange(lines.startLine, lines.endLine);
|
|
8392
|
+
details = {
|
|
8393
|
+
...target.structure,
|
|
8394
|
+
range: lines,
|
|
8395
|
+
...target.structure.symbol ? { symbol: { ...target.structure.symbol, range: lines } } : {}
|
|
8396
|
+
};
|
|
8397
|
+
} else if (document.utf8 && language && language !== "go") {
|
|
8274
8398
|
const syntax = await access.syntax(document);
|
|
8275
8399
|
details = {
|
|
8276
8400
|
status: syntax.status === "ok" ? "no-symbol" : syntax.status === "unsupported" ? "provider-unavailable" : "parse-error",
|
|
@@ -8322,7 +8446,7 @@ async function prepare(target, access, structure) {
|
|
|
8322
8446
|
details = { status: "provider-unavailable", ...language ? { language } : {} };
|
|
8323
8447
|
}
|
|
8324
8448
|
range ??= document.lineRange(Math.max(1, target.line - 10), Math.min(document.lineStarts.length, target.line + 10));
|
|
8325
|
-
const boundary = target.range ? "requested-range" : details.status === "available" && details.range ? "syntax" : "line-window";
|
|
8449
|
+
const boundary = target.range ? target.structure ? "syntax" : "requested-range" : details.status === "available" && details.range ? "syntax" : "line-window";
|
|
8326
8450
|
document.checkRange(range);
|
|
8327
8451
|
return { target, document, range, structure: details, boundary, focus };
|
|
8328
8452
|
}
|
|
@@ -8681,8 +8805,10 @@ ${preview.text}`);
|
|
|
8681
8805
|
}
|
|
8682
8806
|
};
|
|
8683
8807
|
}
|
|
8684
|
-
async function continueSource(cursor, access, continuations) {
|
|
8808
|
+
async function continueSource(cursor, access, continuations, expectedPath) {
|
|
8685
8809
|
const state = continuations.resolve(cursor);
|
|
8810
|
+
if (expectedPath !== undefined && resolve18(access.cwd, expectedPath.replace(/^@/, "")) !== resolve18(access.cwd, state.source.path))
|
|
8811
|
+
throw new SiftLightError(`sourceCursor continues ${JSON.stringify(state.source.path)}, not ${JSON.stringify(expectedPath)}; copy the returned nextRequest exactly`);
|
|
8686
8812
|
const document = await access.load(state.source.path, state.source);
|
|
8687
8813
|
const page = sourcePage(document, state.remaining, MAX_RESULT_BYTES - 1400);
|
|
8688
8814
|
const next = continuations.advance(cursor, page.fragment);
|
|
@@ -9559,6 +9685,221 @@ function parsePythonOutline(document) {
|
|
|
9559
9685
|
});
|
|
9560
9686
|
}
|
|
9561
9687
|
|
|
9688
|
+
// src/python-roles.ts
|
|
9689
|
+
var KEYWORDS = new Set([
|
|
9690
|
+
"and",
|
|
9691
|
+
"as",
|
|
9692
|
+
"assert",
|
|
9693
|
+
"async",
|
|
9694
|
+
"await",
|
|
9695
|
+
"case",
|
|
9696
|
+
"del",
|
|
9697
|
+
"elif",
|
|
9698
|
+
"else",
|
|
9699
|
+
"except",
|
|
9700
|
+
"for",
|
|
9701
|
+
"from",
|
|
9702
|
+
"global",
|
|
9703
|
+
"if",
|
|
9704
|
+
"import",
|
|
9705
|
+
"in",
|
|
9706
|
+
"is",
|
|
9707
|
+
"lambda",
|
|
9708
|
+
"match",
|
|
9709
|
+
"nonlocal",
|
|
9710
|
+
"not",
|
|
9711
|
+
"or",
|
|
9712
|
+
"raise",
|
|
9713
|
+
"return",
|
|
9714
|
+
"while",
|
|
9715
|
+
"with",
|
|
9716
|
+
"yield"
|
|
9717
|
+
]);
|
|
9718
|
+
var STRING_PREFIX = /^(?:[rRbBuUfF]|[rR][bBfF]|[bBfF][rR])?$/u;
|
|
9719
|
+
var IDENTIFIER_START = /[\p{ID_Start}_]/u;
|
|
9720
|
+
var IDENTIFIER_PART = /[\p{ID_Continue}]/u;
|
|
9721
|
+
function lex(text) {
|
|
9722
|
+
const comments = [];
|
|
9723
|
+
const strings = [];
|
|
9724
|
+
const code = new Uint8Array(text.length).fill(1);
|
|
9725
|
+
const mark = (start, end) => {
|
|
9726
|
+
code.fill(0, start, end);
|
|
9727
|
+
};
|
|
9728
|
+
let index = 0;
|
|
9729
|
+
while (index < text.length) {
|
|
9730
|
+
const character = text[index];
|
|
9731
|
+
if (character === "#") {
|
|
9732
|
+
const newline = text.indexOf(`
|
|
9733
|
+
`, index);
|
|
9734
|
+
const end = newline < 0 ? text.length : newline;
|
|
9735
|
+
comments.push({ start: index, end });
|
|
9736
|
+
mark(index, end);
|
|
9737
|
+
index = end;
|
|
9738
|
+
continue;
|
|
9739
|
+
}
|
|
9740
|
+
if (character !== "'" && character !== '"') {
|
|
9741
|
+
index += 1;
|
|
9742
|
+
continue;
|
|
9743
|
+
}
|
|
9744
|
+
let prefixStart = index;
|
|
9745
|
+
while (prefixStart > 0 && /[rRbBuUfF]/u.test(text[prefixStart - 1] ?? ""))
|
|
9746
|
+
prefixStart -= 1;
|
|
9747
|
+
const prefix = text.slice(prefixStart, index);
|
|
9748
|
+
const beforePrefix = text[prefixStart - 1] ?? "";
|
|
9749
|
+
const start = STRING_PREFIX.test(prefix) && !IDENTIFIER_PART.test(beforePrefix) ? prefixStart : index;
|
|
9750
|
+
const triple = text.slice(index, index + 3) === character.repeat(3);
|
|
9751
|
+
const delimiter = triple ? character.repeat(3) : character;
|
|
9752
|
+
let cursor = index + delimiter.length;
|
|
9753
|
+
let end = text.length;
|
|
9754
|
+
while (cursor < text.length) {
|
|
9755
|
+
const current = text[cursor];
|
|
9756
|
+
if (current === "\\") {
|
|
9757
|
+
cursor += 2;
|
|
9758
|
+
continue;
|
|
9759
|
+
}
|
|
9760
|
+
if (!triple && current === `
|
|
9761
|
+
`) {
|
|
9762
|
+
end = cursor;
|
|
9763
|
+
break;
|
|
9764
|
+
}
|
|
9765
|
+
if (text.startsWith(delimiter, cursor)) {
|
|
9766
|
+
end = cursor + delimiter.length;
|
|
9767
|
+
break;
|
|
9768
|
+
}
|
|
9769
|
+
cursor += 1;
|
|
9770
|
+
}
|
|
9771
|
+
strings.push({ start, end });
|
|
9772
|
+
mark(start, end);
|
|
9773
|
+
index = end;
|
|
9774
|
+
}
|
|
9775
|
+
return { comments, strings, code };
|
|
9776
|
+
}
|
|
9777
|
+
function codeRanges(code) {
|
|
9778
|
+
const ranges = [];
|
|
9779
|
+
let start = -1;
|
|
9780
|
+
for (let index = 0;index <= code.length; index += 1) {
|
|
9781
|
+
const inCode = index < code.length && code[index] === 1;
|
|
9782
|
+
if (inCode && start < 0)
|
|
9783
|
+
start = index;
|
|
9784
|
+
else if (!inCode && start >= 0) {
|
|
9785
|
+
ranges.push({ start, end: index });
|
|
9786
|
+
start = -1;
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9789
|
+
return ranges;
|
|
9790
|
+
}
|
|
9791
|
+
function identifierEnd(text, start) {
|
|
9792
|
+
let end = start;
|
|
9793
|
+
while (end < text.length && IDENTIFIER_PART.test(text[end] ?? ""))
|
|
9794
|
+
end += 1;
|
|
9795
|
+
return end;
|
|
9796
|
+
}
|
|
9797
|
+
function skipSpaces(text, index, code) {
|
|
9798
|
+
let cursor = index;
|
|
9799
|
+
while (cursor < text.length && code[cursor] === 1 && /[ \t]/u.test(text[cursor] ?? ""))
|
|
9800
|
+
cursor += 1;
|
|
9801
|
+
return cursor;
|
|
9802
|
+
}
|
|
9803
|
+
function statementEnd(text, start, code) {
|
|
9804
|
+
let depth = 0;
|
|
9805
|
+
for (let index = start;index < text.length; index += 1) {
|
|
9806
|
+
if (code[index] !== 1)
|
|
9807
|
+
continue;
|
|
9808
|
+
const character = text[index];
|
|
9809
|
+
if (character === "(" || character === "[" || character === "{")
|
|
9810
|
+
depth += 1;
|
|
9811
|
+
else if (character === ")" || character === "]" || character === "}")
|
|
9812
|
+
depth = Math.max(0, depth - 1);
|
|
9813
|
+
else if (character === "\\" && text[index + 1] === `
|
|
9814
|
+
`)
|
|
9815
|
+
index += 1;
|
|
9816
|
+
else if (character === `
|
|
9817
|
+
` && depth === 0)
|
|
9818
|
+
return index;
|
|
9819
|
+
}
|
|
9820
|
+
return text.length;
|
|
9821
|
+
}
|
|
9822
|
+
function pythonRoleAnalysis(document) {
|
|
9823
|
+
const text = document.text;
|
|
9824
|
+
const { comments, strings, code } = lex(text);
|
|
9825
|
+
const roles = [];
|
|
9826
|
+
const push = (start, end, role, certainty, subkind) => {
|
|
9827
|
+
if (start < end)
|
|
9828
|
+
roles.push({ start, end, role, certainty, node: 0, ...subkind ? { subkind } : {} });
|
|
9829
|
+
};
|
|
9830
|
+
for (const span of comments)
|
|
9831
|
+
push(span.start, span.end, "comment", "syntax");
|
|
9832
|
+
for (const span of strings)
|
|
9833
|
+
push(span.start, span.end, "string", "syntax");
|
|
9834
|
+
for (const span of codeRanges(code))
|
|
9835
|
+
push(span.start, span.end, "code", "syntax");
|
|
9836
|
+
let index = 0;
|
|
9837
|
+
let lineStart = true;
|
|
9838
|
+
while (index < text.length) {
|
|
9839
|
+
const character = text[index] ?? "";
|
|
9840
|
+
if (character === `
|
|
9841
|
+
`) {
|
|
9842
|
+
lineStart = true;
|
|
9843
|
+
index += 1;
|
|
9844
|
+
continue;
|
|
9845
|
+
}
|
|
9846
|
+
if (code[index] !== 1 || !IDENTIFIER_START.test(character)) {
|
|
9847
|
+
if (!/[ \t]/u.test(character))
|
|
9848
|
+
lineStart = false;
|
|
9849
|
+
index += 1;
|
|
9850
|
+
continue;
|
|
9851
|
+
}
|
|
9852
|
+
const previous = text[index - 1] ?? "";
|
|
9853
|
+
if (IDENTIFIER_PART.test(previous)) {
|
|
9854
|
+
index += 1;
|
|
9855
|
+
continue;
|
|
9856
|
+
}
|
|
9857
|
+
const end = identifierEnd(text, index);
|
|
9858
|
+
const word = text.slice(index, end);
|
|
9859
|
+
const atStatementStart = lineStart;
|
|
9860
|
+
lineStart = false;
|
|
9861
|
+
if (atStatementStart && (word === "import" || word === "from")) {
|
|
9862
|
+
const statement = statementEnd(text, index, code);
|
|
9863
|
+
const body = text.slice(index, statement);
|
|
9864
|
+
if (word === "import" || /\bimport\b/u.test(body)) {
|
|
9865
|
+
push(index, statement, "import", "syntax", word === "from" ? "from-import" : "import");
|
|
9866
|
+
index = statement;
|
|
9867
|
+
continue;
|
|
9868
|
+
}
|
|
9869
|
+
}
|
|
9870
|
+
if (word === "def" || word === "class") {
|
|
9871
|
+
const nameStart = skipSpaces(text, end, code);
|
|
9872
|
+
if (IDENTIFIER_START.test(text[nameStart] ?? "")) {
|
|
9873
|
+
const nameEnd = identifierEnd(text, nameStart);
|
|
9874
|
+
push(nameStart, nameEnd, "declaration", "syntax", word === "def" ? "function" : "class");
|
|
9875
|
+
index = nameEnd;
|
|
9876
|
+
continue;
|
|
9877
|
+
}
|
|
9878
|
+
}
|
|
9879
|
+
let chainEnd = end;
|
|
9880
|
+
while (text[chainEnd] === "." && IDENTIFIER_START.test(text[chainEnd + 1] ?? "")) {
|
|
9881
|
+
chainEnd = identifierEnd(text, chainEnd + 1);
|
|
9882
|
+
}
|
|
9883
|
+
const open = skipSpaces(text, chainEnd, code);
|
|
9884
|
+
const lastSegment = text.slice(text.lastIndexOf(".", chainEnd - 1) + 1, chainEnd);
|
|
9885
|
+
const before = text.slice(Math.max(0, index - 4), index);
|
|
9886
|
+
if (text[open] === "(" && code[open] === 1 && !KEYWORDS.has(word) && !KEYWORDS.has(lastSegment) && !/\bdef\s+$|\bclass\s+$/u.test(before)) {
|
|
9887
|
+
push(index, open + 1, "call", "candidate", "lexical-call");
|
|
9888
|
+
}
|
|
9889
|
+
index = chainEnd;
|
|
9890
|
+
}
|
|
9891
|
+
roles.sort((a, b) => a.start - b.start || a.end - b.end || a.role.localeCompare(b.role));
|
|
9892
|
+
return {
|
|
9893
|
+
status: "ok",
|
|
9894
|
+
nodes: [],
|
|
9895
|
+
children: [],
|
|
9896
|
+
symbols: [],
|
|
9897
|
+
roles,
|
|
9898
|
+
diagnostics: [],
|
|
9899
|
+
limited: false
|
|
9900
|
+
};
|
|
9901
|
+
}
|
|
9902
|
+
|
|
9562
9903
|
// src/hybrid-search.ts
|
|
9563
9904
|
import { resolve as resolve20 } from "node:path";
|
|
9564
9905
|
|
|
@@ -10506,6 +10847,69 @@ import { realpath as realpath6 } from "node:fs/promises";
|
|
|
10506
10847
|
function isEvidenceRequest(input) {
|
|
10507
10848
|
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);
|
|
10508
10849
|
}
|
|
10850
|
+
function pageInspectRequest(input) {
|
|
10851
|
+
if (input.scope === "expand")
|
|
10852
|
+
throw new SiftLightError("mode=inspect reads exact locations; scope=expand does not apply (omit scope)");
|
|
10853
|
+
const { scope: _scope, paths, ...rest } = input;
|
|
10854
|
+
let request = rest;
|
|
10855
|
+
if (paths !== undefined) {
|
|
10856
|
+
if (input.cursor !== undefined || input.targets !== undefined || input.matchIndices !== undefined || input.path !== undefined || input.line !== undefined || input.matchIndex !== undefined)
|
|
10857
|
+
throw new SiftLightError("mode=inspect paths opens files from line 1 and cannot be combined with cursor, targets, matchIndices, path, line or matchIndex");
|
|
10858
|
+
request = { ...rest, targets: paths.map((path) => ({ path, line: 1 })) };
|
|
10859
|
+
}
|
|
10860
|
+
const redact = input.redact ? { redact: true } : {};
|
|
10861
|
+
const requested = request.targets?.length ?? request.matchIndices?.length ?? 0;
|
|
10862
|
+
if (requested > MAX_INSPECT_REQUEST_TARGETS)
|
|
10863
|
+
throw new SiftLightError(`mode=inspect accepts at most ${String(MAX_INSPECT_REQUEST_TARGETS)} targets per request; ${String(MAX_INSPECT_TARGETS)} are inspected per response`);
|
|
10864
|
+
if (request.targets && request.targets.length > MAX_INSPECT_TARGETS)
|
|
10865
|
+
return {
|
|
10866
|
+
current: { ...request, targets: request.targets.slice(0, MAX_INSPECT_TARGETS) },
|
|
10867
|
+
remaining: {
|
|
10868
|
+
mode: "inspect",
|
|
10869
|
+
targets: request.targets.slice(MAX_INSPECT_TARGETS),
|
|
10870
|
+
...redact
|
|
10871
|
+
}
|
|
10872
|
+
};
|
|
10873
|
+
if (request.matchIndices && request.matchIndices.length > MAX_INSPECT_TARGETS)
|
|
10874
|
+
return {
|
|
10875
|
+
current: { ...request, matchIndices: request.matchIndices.slice(0, MAX_INSPECT_TARGETS) },
|
|
10876
|
+
remaining: {
|
|
10877
|
+
mode: "inspect",
|
|
10878
|
+
...request.cursor !== undefined ? { cursor: request.cursor } : {},
|
|
10879
|
+
matchIndices: request.matchIndices.slice(MAX_INSPECT_TARGETS),
|
|
10880
|
+
...redact
|
|
10881
|
+
}
|
|
10882
|
+
};
|
|
10883
|
+
return { current: request };
|
|
10884
|
+
}
|
|
10885
|
+
function withRemainingInspection(result, remaining) {
|
|
10886
|
+
const count = remaining.targets?.length ?? remaining.matchIndices?.length ?? 0;
|
|
10887
|
+
return {
|
|
10888
|
+
text: `${result.text}
|
|
10889
|
+
|
|
10890
|
+
[${String(count)} more target(s) were not inspected; one response inspects ${String(MAX_INSPECT_TARGETS)}.]
|
|
10891
|
+
Next request: ${JSON.stringify(remaining)}`,
|
|
10892
|
+
details: {
|
|
10893
|
+
...result.details,
|
|
10894
|
+
status: "partial",
|
|
10895
|
+
snapshotComplete: false,
|
|
10896
|
+
nextRequest: remaining
|
|
10897
|
+
}
|
|
10898
|
+
};
|
|
10899
|
+
}
|
|
10900
|
+
function analysisItemStructure(item) {
|
|
10901
|
+
const details = item.details ?? {};
|
|
10902
|
+
const language = typeof details.language === "string" ? details.language : undefined;
|
|
10903
|
+
const name = typeof details.name === "string" ? details.name : undefined;
|
|
10904
|
+
const scope = Array.isArray(details.scope) ? details.scope.filter((entry) => typeof entry === "string") : typeof details.scope === "string" ? [details.scope] : [];
|
|
10905
|
+
const kind = details.kind === "function" ? "function" : item.label.split(" ", 1)[0] ?? "symbol";
|
|
10906
|
+
return {
|
|
10907
|
+
status: "available",
|
|
10908
|
+
provider: language === "python" ? "python-outline" : "tree-sitter",
|
|
10909
|
+
...language ? { language } : {},
|
|
10910
|
+
...name ? { symbol: { name, kind, scope, range: { startLine: item.line, endLine: item.line } } } : {}
|
|
10911
|
+
};
|
|
10912
|
+
}
|
|
10509
10913
|
function maxFilesToParse(value, defaultValue = MAX_STRUCTURE_FILES) {
|
|
10510
10914
|
const candidate = value ?? defaultValue;
|
|
10511
10915
|
if (!Number.isSafeInteger(candidate) || candidate < 1 || candidate > MAX_CONFIGURABLE_STRUCTURE_FILES) {
|
|
@@ -10521,8 +10925,8 @@ function validateTerms(input) {
|
|
|
10521
10925
|
}
|
|
10522
10926
|
return;
|
|
10523
10927
|
}
|
|
10524
|
-
if (!Array.isArray(terms) || terms.length <
|
|
10525
|
-
throw new SiftLightError("allOf requires
|
|
10928
|
+
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)
|
|
10929
|
+
throw new SiftLightError("allOf requires 1–3 distinct, nonempty, single-line literal terms");
|
|
10526
10930
|
if (input.pattern !== undefined || input.roles !== undefined || input.literal !== undefined || input.ignoreCase !== undefined || input.wholeWord !== undefined)
|
|
10527
10931
|
throw new SiftLightError("allOf is an explicit case-sensitive literal conjunction; omit pattern, roles, literal and ignoreCase");
|
|
10528
10932
|
if (input.within !== undefined && input.within !== "file" && input.within !== "function")
|
|
@@ -10720,11 +11124,13 @@ class EvidenceService {
|
|
|
10720
11124
|
throw new CursorError("A nonempty sourceCursor is required");
|
|
10721
11125
|
if (input.mode !== "inspect")
|
|
10722
11126
|
throw new SiftLightError("sourceCursor requires mode=inspect");
|
|
10723
|
-
return continueSource(input.sourceCursor, access, this.#continuations);
|
|
11127
|
+
return continueSource(input.sourceCursor, access, this.#continuations, input.path);
|
|
10724
11128
|
}
|
|
10725
11129
|
if (input.mode === "inspect") {
|
|
10726
|
-
const
|
|
10727
|
-
|
|
11130
|
+
const { current, remaining } = pageInspectRequest(input);
|
|
11131
|
+
const targets = this.#inspectionTargets(current, cwd);
|
|
11132
|
+
const result = await inspectDocuments(targets, access, this.#continuations, this.#structure);
|
|
11133
|
+
return remaining ? withRemainingInspection(result, remaining) : result;
|
|
10728
11134
|
}
|
|
10729
11135
|
if (input.mode === "concept") {
|
|
10730
11136
|
const execution = await this.#conceptSearch(input, access, options.onProgress);
|
|
@@ -10804,7 +11210,7 @@ class EvidenceService {
|
|
|
10804
11210
|
this.#analyses.resolve(input.cursor);
|
|
10805
11211
|
if (input.mode !== undefined && input.mode !== "matches" && input.mode !== "auto")
|
|
10806
11212
|
throw new CursorError("Analysis cursor cannot continue in the requested mode", "E_CURSOR_WRONG_KIND");
|
|
10807
|
-
return this.#analyses.page(input.cursor, options.modelOutput);
|
|
11213
|
+
return this.#analyses.page(input.cursor, options.modelOutput, input.redact);
|
|
10808
11214
|
}
|
|
10809
11215
|
if (input.mode === "outline" || input.mode === "imports" || input.mode === "tests")
|
|
10810
11216
|
return this.#navigate(input, access, options);
|
|
@@ -10813,7 +11219,7 @@ class EvidenceService {
|
|
|
10813
11219
|
if (input.pattern !== undefined || input.allOf !== undefined || input.within !== undefined || input.roles !== undefined || input.literal !== undefined || input.ignoreCase !== undefined || input.wholeWord !== undefined)
|
|
10814
11220
|
throw new SiftLightError("anyOf is an explicit case-sensitive literal union; omit pattern, allOf, within, roles, literal and ignoreCase");
|
|
10815
11221
|
if (input.mode !== undefined && input.mode !== "auto" && input.mode !== "matches")
|
|
10816
|
-
throw new SiftLightError("anyOf mode must be omitted, auto, or
|
|
11222
|
+
throw new SiftLightError("anyOf mode must be omitted, auto, matches or summary");
|
|
10817
11223
|
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));
|
|
10818
11224
|
const { path: _inputPath, ...unscopedInput } = input;
|
|
10819
11225
|
let chunkAccess = access;
|
|
@@ -10951,9 +11357,10 @@ class EvidenceService {
|
|
|
10951
11357
|
if (item)
|
|
10952
11358
|
result.items.push(item);
|
|
10953
11359
|
} else if (terms || input.roles) {
|
|
10954
|
-
|
|
11360
|
+
const pythonRoles = !terms && /\.py$/iu.test(file.document.path);
|
|
11361
|
+
if (pythonRoles || syntaxLanguage(file.document.path))
|
|
10955
11362
|
syntaxCapableFiles += 1;
|
|
10956
|
-
const syntax = await access.syntax(file.document);
|
|
11363
|
+
const syntax = pythonRoles ? pythonRoleAnalysis(file.document) : await access.syntax(file.document);
|
|
10957
11364
|
const classified = terms ? findFunctionConjunctions(file.document, syntax, terms, input.changes?.scope === "lines" ? file.changedRanges : undefined) : filterRoleOccurrences(file.document, syntax, file.occurrences, input.roles ?? []);
|
|
10958
11365
|
result.items.push(...classified.items);
|
|
10959
11366
|
result.partial ||= classified.partial;
|
|
@@ -11030,12 +11437,12 @@ class EvidenceService {
|
|
|
11030
11437
|
const item = this.#analyses.item(input.cursor, input.matchIndex);
|
|
11031
11438
|
if (!item.source || !item.range)
|
|
11032
11439
|
throw new CursorError("This analysis item has no verified source range");
|
|
11033
|
-
const
|
|
11440
|
+
const bounded = item.details?.kind === "symbol" || item.details?.kind === "function";
|
|
11034
11441
|
return {
|
|
11035
11442
|
path: item.path,
|
|
11036
11443
|
line: item.line,
|
|
11037
11444
|
reference: item.source,
|
|
11038
|
-
...
|
|
11445
|
+
...bounded ? { range: item.range, structure: analysisItemStructure(item) } : { absoluteFocus: item.range.start }
|
|
11039
11446
|
};
|
|
11040
11447
|
}
|
|
11041
11448
|
return {
|
|
@@ -11244,6 +11651,29 @@ import { resolve as resolve24 } from "node:path";
|
|
|
11244
11651
|
// src/discovery-errors.ts
|
|
11245
11652
|
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>"}';
|
|
11246
11653
|
|
|
11654
|
+
// src/request-aliases.ts
|
|
11655
|
+
var MODE_ALIASES = ["anyOf", "allOf"];
|
|
11656
|
+
function normalizeRequestAliases(request) {
|
|
11657
|
+
const notes = [];
|
|
11658
|
+
const { mode, ...rest } = request;
|
|
11659
|
+
let input = rest;
|
|
11660
|
+
if (mode === "anyOf" || mode === "allOf") {
|
|
11661
|
+
if (request[mode] === undefined)
|
|
11662
|
+
throw new SiftLightError(`mode=${mode} is not a mode; pass ${mode}:[...terms] and omit mode`);
|
|
11663
|
+
notes.push(`mode="${mode}" is not a mode; the ${mode} field alone selects this search.`);
|
|
11664
|
+
} else if (mode === "summary" && request.anyOf !== undefined) {
|
|
11665
|
+
notes.push("anyOf returns one analysis page whose header already carries per-file statistics; mode=summary was served by that page.");
|
|
11666
|
+
} else if (mode !== undefined) {
|
|
11667
|
+
input = { ...rest, mode };
|
|
11668
|
+
}
|
|
11669
|
+
if (input.sourceCursor !== undefined && input.line !== undefined) {
|
|
11670
|
+
const { line: _line, ...withoutLine } = input;
|
|
11671
|
+
input = withoutLine;
|
|
11672
|
+
notes.push("line is ignored with sourceCursor; the continuation selects its own source range.");
|
|
11673
|
+
}
|
|
11674
|
+
return { input, notes };
|
|
11675
|
+
}
|
|
11676
|
+
|
|
11247
11677
|
// src/format.ts
|
|
11248
11678
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
11249
11679
|
var RESULT_METADATA_RESERVE_BYTES = 1024;
|
|
@@ -12597,6 +13027,16 @@ class LanguageCapabilityCatalog {
|
|
|
12597
13027
|
}
|
|
12598
13028
|
|
|
12599
13029
|
// src/service.ts
|
|
13030
|
+
function withRequestNotes(result, notes) {
|
|
13031
|
+
if (notes.length === 0)
|
|
13032
|
+
return result;
|
|
13033
|
+
return {
|
|
13034
|
+
text: `${notes.map((note) => `[Request note: ${note}]`).join(`
|
|
13035
|
+
`)}
|
|
13036
|
+
${result.text}`,
|
|
13037
|
+
details: { ...result.details, requestNotes: [...notes] }
|
|
13038
|
+
};
|
|
13039
|
+
}
|
|
12600
13040
|
function filterList(value) {
|
|
12601
13041
|
return value === undefined ? [] : Array.isArray(value) ? [...value] : [value];
|
|
12602
13042
|
}
|
|
@@ -12825,7 +13265,11 @@ class SiftLightService {
|
|
|
12825
13265
|
this.#operations = new OperationLifecycle({ deadlineMs: resolveConceptTimeoutMs() });
|
|
12826
13266
|
this.#evidence = new EvidenceService(this.#runRipgrep, this.#snapshots, options.structure, options.conceptSearch, options.semanticJudge);
|
|
12827
13267
|
}
|
|
12828
|
-
async search(
|
|
13268
|
+
async search(request, cwd, signal, options = {}) {
|
|
13269
|
+
const { input, notes } = normalizeRequestAliases(request);
|
|
13270
|
+
return withRequestNotes(await this.#searchNormalized(input, cwd, signal, options), notes);
|
|
13271
|
+
}
|
|
13272
|
+
async#searchNormalized(input, cwd, signal, options) {
|
|
12829
13273
|
validateRawSearchInput(input);
|
|
12830
13274
|
validateRequestContract(input);
|
|
12831
13275
|
if (!this.#vectorSearchEnabled && (input.mode === "concept" || input.mode === "hybrid")) {
|
|
@@ -13230,16 +13674,18 @@ ${page.body}${rangeNote}${contextNote}${missingSelectionNote}
|
|
|
13230
13674
|
}
|
|
13231
13675
|
|
|
13232
13676
|
// src/prompt-guidelines.ts
|
|
13677
|
+
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.";
|
|
13233
13678
|
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.";
|
|
13234
13679
|
function siftLightPromptGuidelines(structuredOutput = true) {
|
|
13235
13680
|
return [
|
|
13681
|
+
SIFT_LIGHT_DISCOVERY_GUIDANCE,
|
|
13236
13682
|
`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.`,
|
|
13237
13683
|
`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.`,
|
|
13238
13684
|
`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.`,
|
|
13239
13685
|
`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.`,
|
|
13240
13686
|
`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.`,
|
|
13241
13687
|
`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.`,
|
|
13242
|
-
`Use anyOf:["term1","term2"] when every exact occurrence of
|
|
13688
|
+
`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.`,
|
|
13243
13689
|
`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.`,
|
|
13244
13690
|
`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.`,
|
|
13245
13691
|
`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.`,
|
|
@@ -13253,10 +13699,12 @@ function siftLightPromptGuidelines(structuredOutput = true) {
|
|
|
13253
13699
|
}
|
|
13254
13700
|
function siftLightModelGuidelines() {
|
|
13255
13701
|
return [
|
|
13256
|
-
|
|
13257
|
-
`
|
|
13258
|
-
`
|
|
13259
|
-
`
|
|
13702
|
+
SIFT_LIGHT_DISCOVERY_GUIDANCE,
|
|
13703
|
+
`pattern + path: exact, model-free; omit mode/limit for auto. concept/hybrid need vectorSearchEnabled:true and an installed model; cold runs take tens of seconds.`,
|
|
13704
|
+
`Auto/summary may quote source; matches: metadata-only. Inspect: bounded source, including an entire small file. Analysis may retain passages/names/signatures. Follow limits and coverage.`,
|
|
13705
|
+
`Use ranked counts and paths. Cursor continuation pages retained results; inspect exact source before editing.`,
|
|
13706
|
+
`files+query: filenames; anyOf/allOf: literals; outline/imports/tests: static code; structure: AST; concept/hybrid: semantic candidates, not proof.`,
|
|
13707
|
+
`On rejection keep the strongest mode; repair once, without repeating request/error. Only explicit capability-unavailable permits a visibly partial alternative.`
|
|
13260
13708
|
];
|
|
13261
13709
|
}
|
|
13262
13710
|
function siftLightMcpInstructions(outputMode = DEFAULT_MCP_OUTPUT_MODE) {
|
|
@@ -13297,9 +13745,9 @@ var siftLightSchema = Type.Object({
|
|
|
13297
13745
|
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.`
|
|
13298
13746
|
})),
|
|
13299
13747
|
allOf: Type.Optional(Type.Array(Type.String({ maxLength: MAX_PATH_CHARACTERS }), {
|
|
13300
|
-
minItems:
|
|
13748
|
+
minItems: 1,
|
|
13301
13749
|
maxItems: 3,
|
|
13302
|
-
description: `${fieldGuidance("allOf")}.
|
|
13750
|
+
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.`
|
|
13303
13751
|
})),
|
|
13304
13752
|
within: Type.Optional(stringEnum(["file", "function"], {
|
|
13305
13753
|
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."
|
|
@@ -13316,7 +13764,7 @@ var siftLightSchema = Type.Object({
|
|
|
13316
13764
|
"unknown"
|
|
13317
13765
|
]), {
|
|
13318
13766
|
minItems: 1,
|
|
13319
|
-
description: "Filter each single-pattern occurrence by syntax role (JS/TS/TSX/Go). Roles may be candidates, especially Go call/conversion
|
|
13767
|
+
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."
|
|
13320
13768
|
})),
|
|
13321
13769
|
changes: Type.Optional(Type.Object({
|
|
13322
13770
|
base: Type.Optional(Type.String({
|
|
@@ -13349,7 +13797,7 @@ var siftLightSchema = Type.Object({
|
|
|
13349
13797
|
paths: Type.Optional(Type.Array(Type.String(), {
|
|
13350
13798
|
minItems: 1,
|
|
13351
13799
|
maxItems: MAX_SELECTED_PATHS,
|
|
13352
|
-
description: "Exact retained files to select together from a cursor. A new search accepts one path; split multiple roots into separate requests."
|
|
13800
|
+
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)."
|
|
13353
13801
|
})),
|
|
13354
13802
|
glob: Type.Optional(Type.Union([
|
|
13355
13803
|
Type.String({ maxLength: MAX_PATH_CHARACTERS }),
|
|
@@ -13373,7 +13821,7 @@ var siftLightSchema = Type.Object({
|
|
|
13373
13821
|
})),
|
|
13374
13822
|
hidden: Type.Optional(Type.Boolean({ description: "Search hidden files (default true; .git is always excluded)." })),
|
|
13375
13823
|
ignorePolicy: Type.Optional(stringEnum(["respect", "include"], {
|
|
13376
|
-
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."
|
|
13824
|
+
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."
|
|
13377
13825
|
})),
|
|
13378
13826
|
patterns: Type.Optional(Type.Array(Type.Object({
|
|
13379
13827
|
id: Type.String({ minLength: 1, maxLength: 64 }),
|
|
@@ -13418,29 +13866,29 @@ var siftLightSchema = Type.Object({
|
|
|
13418
13866
|
limit: Type.Optional(Type.Integer({
|
|
13419
13867
|
minimum: 1,
|
|
13420
13868
|
maximum: MAX_PAGE_SIZE,
|
|
13421
|
-
description: `${fieldGuidance("limit")}. Ordinary search
|
|
13869
|
+
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.`
|
|
13422
13870
|
})),
|
|
13423
|
-
mode: Type.Optional(stringEnum(SIFT_LIGHT_MODES, {
|
|
13424
|
-
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}
|
|
13871
|
+
mode: Type.Optional(stringEnum([...SIFT_LIGHT_MODES, ...MODE_ALIASES], {
|
|
13872
|
+
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.`
|
|
13425
13873
|
})),
|
|
13426
13874
|
line: Type.Optional(Type.Number({
|
|
13427
|
-
description: "1-indexed source line for path inspection/navigation. Omit with matchIndex, matchIndices or targets."
|
|
13875
|
+
description: "1-indexed source line for path inspection/navigation; path-only inspect starts at line 1. Omit with matchIndex, matchIndices or targets."
|
|
13428
13876
|
})),
|
|
13429
13877
|
matchIndex: Type.Optional(Type.Number({
|
|
13430
13878
|
description: "1-based retained match index for cursor-scoped inspect; replaces path and line."
|
|
13431
13879
|
})),
|
|
13432
13880
|
matchIndices: Type.Optional(Type.Array(Type.Integer({ minimum: 1 }), {
|
|
13433
13881
|
minItems: 1,
|
|
13434
|
-
maxItems:
|
|
13435
|
-
description:
|
|
13882
|
+
maxItems: MAX_INSPECT_REQUEST_TARGETS,
|
|
13883
|
+
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.`
|
|
13436
13884
|
})),
|
|
13437
13885
|
targets: Type.Optional(Type.Array(Type.Object({
|
|
13438
13886
|
path: Type.String({ maxLength: MAX_PATH_CHARACTERS }),
|
|
13439
13887
|
line: Type.Integer({ minimum: 1 })
|
|
13440
13888
|
}), {
|
|
13441
13889
|
minItems: 1,
|
|
13442
|
-
maxItems:
|
|
13443
|
-
description:
|
|
13890
|
+
maxItems: MAX_INSPECT_REQUEST_TARGETS,
|
|
13891
|
+
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.`
|
|
13444
13892
|
})),
|
|
13445
13893
|
cursor: Type.Optional(Type.String({ description: "Opaque cursor from a previous stable search snapshot." })),
|
|
13446
13894
|
operationId: Type.Optional(Type.String({
|