sift-light 1.0.2 → 1.0.3-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/plugins/sift-light/.claude-plugin/plugin.json +1 -1
- package/plugins/sift-light/.codex-plugin/plugin.json +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 +31 -16
- package/plugins/sift-light/package.json +1 -1
- package/plugins/sift-light/skills/local-search/SKILL.md +12 -0
- package/src/analysis-store.ts +10 -8
- package/src/analysis-term-pages.ts +2 -1
- package/src/evidence-service.ts +1 -1
- package/src/mcp-server.mjs +37 -20
- package/src/prompt-guidelines.ts +11 -5
- package/src/request-aliases.ts +5 -0
- package/src/result-statistics.ts +3 -3
- package/src/source-document.ts +4 -2
- package/src/term-count-label.ts +6 -0
- package/src/tool-schema.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.3-2 — 2026-09-30
|
|
4
|
+
|
|
5
|
+
- Accept an empty `sourceCursor` placeholder outside `mode: "inspect"` by removing the key before mode validation, with the normalization disclosed in text and `details.requestNotes` ([#101](https://github.com/lightsifter/sift-light/issues/101)). Nonempty source continuation tokens still require inspection mode; empty inspection tokens and ordinary `cursor` validation retain their existing failure behavior.
|
|
6
|
+
- Apply the same normalization to Pi, OMP and MCP through the shared request boundary. This fixes the reported rejected request shape without attributing the placeholder to a particular model or host.
|
|
7
|
+
|
|
8
|
+
## 1.0.3-1 — 2026-09-30
|
|
9
|
+
|
|
10
|
+
- Explain missing inspection paths explicitly while retaining the `source-unavailable` status for compatibility.
|
|
11
|
+
- Label term counts with numbered input literals in text, structured output and continuation pages; keep anonymized labels when redaction is requested.
|
|
12
|
+
- Bundle a Codex local-search skill and discovery guidance that checks the complete runtime registry, including deferred tools, before reporting sift-light unavailable.
|
|
13
|
+
|
|
3
14
|
## 1.0.2 — 2026-09-29
|
|
4
15
|
|
|
5
16
|
### Fixed
|
package/README.md
CHANGED
|
@@ -167,6 +167,8 @@ MCP returns readable text plus structured evidence by default. If a host seriali
|
|
|
167
167
|
|
|
168
168
|
### Native plugins
|
|
169
169
|
|
|
170
|
+
The Codex plugin includes a `local-search` skill for discovering and calling deferred MCP tools. Before declaring the tool unavailable, inspect the host's complete runtime registry (including `ALL_TOOLS` where `functions.exec` is available), then call `mcp__sift_light__sift_light` when registered. Omission from the initial expanded declarations does not mean absence. Restart or start a new session after updating the plugin to load the new skill. MCP-only clients receive the same server guidance, but the skill discovery entry requires the native plugin.
|
|
171
|
+
|
|
170
172
|
For conventional search enforcement in other hosts:
|
|
171
173
|
|
|
172
174
|
- **Claude Code:** `/plugin marketplace add lightsifter/sift-light`, then `/plugin install sift-light@sift-light`.
|
package/README.zh-CN.md
CHANGED
|
@@ -167,6 +167,8 @@ MCP 默认同时返回可读文本和结构化证据。如果宿主会把两种
|
|
|
167
167
|
|
|
168
168
|
### 原生插件
|
|
169
169
|
|
|
170
|
+
Codex 插件包含 `local-search` 技能,用于发现和调用延迟加载的 MCP 工具。判断工具不可用前,先检查宿主完整运行时注册表(在提供 `functions.exec` 的环境中包括 `ALL_TOOLS`),找到 `mcp__sift_light__sift_light` 后直接调用;初始声明未展开不代表工具缺失。更新插件后重启或新建会话,让宿主加载新版技能。仅配置 MCP 的客户端会收到同样的服务说明,但技能发现入口需要安装原生插件。
|
|
171
|
+
|
|
170
172
|
如果希望在其他宿主中也强制常规搜索使用本插件:
|
|
171
173
|
|
|
172
174
|
- **Claude Code:** 先运行 `/plugin marketplace add lightsifter/sift-light`,再运行 `/plugin install sift-light@sift-light`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sift-light",
|
|
3
|
-
"version": "1.0.2",
|
|
3
|
+
"version": "1.0.3-2",
|
|
4
4
|
"description": "Require sift-light for conventional local searches while keeping development tools available.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "baoer"
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
"homepage": "https://github.com/lightsifter/sift-light#readme",
|
|
9
9
|
"license": "AGPL-3.0-only",
|
|
10
10
|
"mcpServers": "./.mcp.json",
|
|
11
|
+
"skills": "./skills/",
|
|
11
12
|
"interface": {
|
|
12
13
|
"displayName": "sift-light",
|
|
13
14
|
"shortDescription": "Enforced local code search",
|
|
14
15
|
"longDescription": "Require sift-light for conventional local searches while keeping development tools available.",
|
|
15
|
-
"defaultPrompt": "Search this project's code using sift-light.",
|
|
16
|
+
"defaultPrompt": "Search this project's code using sift-light. 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.",
|
|
16
17
|
"developerName": "baoer",
|
|
17
18
|
"category": "Productivity",
|
|
18
19
|
"capabilities": []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sift-light",
|
|
3
|
-
"version": "1.0.2",
|
|
3
|
+
"version": "1.0.3-2",
|
|
4
4
|
"description": "Require sift-light for conventional local searches while keeping development tools available.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "baoer"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"args": [
|
|
14
14
|
"--yes",
|
|
15
15
|
"--package",
|
|
16
|
-
"sift-light-runtime@npm:sift-light@1.0.2",
|
|
16
|
+
"sift-light-runtime@npm:sift-light@1.0.3-2",
|
|
17
17
|
"sift-light-mcp",
|
|
18
18
|
"--stdio"
|
|
19
19
|
],
|
|
@@ -1822,7 +1822,7 @@ function createCtagsStructureProvider(options = {}) {
|
|
|
1822
1822
|
// package.json
|
|
1823
1823
|
var package_default = {
|
|
1824
1824
|
name: "sift-light",
|
|
1825
|
-
version: "1.0.2",
|
|
1825
|
+
version: "1.0.3-2",
|
|
1826
1826
|
description: "Context-efficient local search for files, documents, notes and logs across Pi, OMP and MCP clients",
|
|
1827
1827
|
keywords: [
|
|
1828
1828
|
"ai-agent",
|
|
@@ -3482,8 +3482,10 @@ async function readWorkspaceDocument(path, cwd, signal, expected, readBudget = M
|
|
|
3482
3482
|
realpath3(cwd)
|
|
3483
3483
|
]);
|
|
3484
3484
|
if (!canonical)
|
|
3485
|
-
throw new SourceDocumentError("source-unavailable",
|
|
3486
|
-
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
|
+
});
|
|
3487
3489
|
if (!before)
|
|
3488
3490
|
throw new SourceDocumentError("source-unavailable", "Source is unavailable");
|
|
3489
3491
|
if (expected && !sameSourceRevision(before, expected.revision)) {
|
|
@@ -5198,6 +5200,11 @@ var SEMANTIC_JUDGE_CLASSIFICATIONS = [
|
|
|
5198
5200
|
];
|
|
5199
5201
|
var SEMANTIC_JUDGE_NON_PROOF_CLAIM = "semantic classification only; local static and runtime verification is not asserted";
|
|
5200
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
|
+
|
|
5201
5208
|
// src/analysis-term-pages.ts
|
|
5202
5209
|
var MAX_INLINE_TERM_COUNT_BYTES = 8 * 1024;
|
|
5203
5210
|
function termCountRequest(id, offset, redact = false) {
|
|
@@ -5215,7 +5222,7 @@ function analysisTermPage(result, id, offset) {
|
|
|
5215
5222
|
const term = all[index];
|
|
5216
5223
|
if (!term)
|
|
5217
5224
|
throw new Error("Term inventory index unavailable");
|
|
5218
|
-
const label =
|
|
5225
|
+
const label = termCountLabel(term.term, index, result.redact);
|
|
5219
5226
|
const row = `${label}: ${String(term.retainedOccurrences)} retained occurrences`;
|
|
5220
5227
|
const size = Buffer.byteLength(row) + 2;
|
|
5221
5228
|
if (bytes + size > MAX_INLINE_TERM_COUNT_BYTES)
|
|
@@ -5351,8 +5358,8 @@ function analysisExtraGroups(counts, termCounts, items) {
|
|
|
5351
5358
|
const classification = counts ? statisticsGroup("classification", Object.entries(counts).map(([label, count]) => ({ label, count }))) : undefined;
|
|
5352
5359
|
if (classification)
|
|
5353
5360
|
groups.push(classification);
|
|
5354
|
-
const terms = termCounts ? statisticsGroup("conditions", termCounts.map((term
|
|
5355
|
-
label:
|
|
5361
|
+
const terms = termCounts ? statisticsGroup("conditions", termCounts.map((term) => ({
|
|
5362
|
+
label: term.term,
|
|
5356
5363
|
count: term.retainedOccurrences
|
|
5357
5364
|
}))) : undefined;
|
|
5358
5365
|
if (terms)
|
|
@@ -5505,9 +5512,9 @@ function publicAnalysisItem(result, item, index, storedId, modelOutput) {
|
|
|
5505
5512
|
...publicDetails ? { details: publicDetails } : {}
|
|
5506
5513
|
};
|
|
5507
5514
|
}
|
|
5508
|
-
function safeTermCounts(termCounts) {
|
|
5515
|
+
function safeTermCounts(termCounts, redact = false) {
|
|
5509
5516
|
return termCounts?.map((entry, index) => ({
|
|
5510
|
-
term:
|
|
5517
|
+
term: termCountLabel(entry.term, index, redact),
|
|
5511
5518
|
retainedOccurrences: entry.retainedOccurrences
|
|
5512
5519
|
}));
|
|
5513
5520
|
}
|
|
@@ -5629,21 +5636,22 @@ class AnalysisStore {
|
|
|
5629
5636
|
throw new CursorError("Analysis item is outside the retained result");
|
|
5630
5637
|
return structuredClone(item);
|
|
5631
5638
|
}
|
|
5632
|
-
page(cursor, modelOutput = false) {
|
|
5639
|
+
page(cursor, modelOutput = false, redact = false) {
|
|
5633
5640
|
const { stored, offset, kind } = this.resolve(cursor);
|
|
5634
|
-
const { result }
|
|
5641
|
+
const result = redact ? { ...stored.result, redact: true } : stored.result;
|
|
5635
5642
|
if (kind === "analysis-terms")
|
|
5636
5643
|
return analysisTermPage(result, stored.id, offset);
|
|
5637
5644
|
const hybridPreview = kind === "analysis-hybrid";
|
|
5638
5645
|
const hybridMatchesRequest = hybridPreview ? { cursor: `${stored.id}.analysis.0`, ...result.redact ? { redact: true } : {} } : undefined;
|
|
5639
|
-
const
|
|
5640
|
-
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;
|
|
5641
5649
|
const termsRequest = pagedTerms ? termCountRequest(stored.id, 0, result.redact) : undefined;
|
|
5642
5650
|
const items = [];
|
|
5643
5651
|
const sources = [];
|
|
5644
5652
|
const sourceIds = new Map;
|
|
5645
5653
|
const hybridInspectCursor = result.kind === "hybrid" ? `${stored.id}.analysis.0` : undefined;
|
|
5646
|
-
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));
|
|
5647
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)}` : "";
|
|
5648
5656
|
const coverage = result.coverage ? ` Coverage: ${JSON.stringify(result.coverage)}.` : "";
|
|
5649
5657
|
const stats = result.stats ? ` Stats: ${JSON.stringify(result.stats)}.` : "";
|
|
@@ -11170,7 +11178,7 @@ class EvidenceService {
|
|
|
11170
11178
|
this.#analyses.resolve(input.cursor);
|
|
11171
11179
|
if (input.mode !== undefined && input.mode !== "matches" && input.mode !== "auto")
|
|
11172
11180
|
throw new CursorError("Analysis cursor cannot continue in the requested mode", "E_CURSOR_WRONG_KIND");
|
|
11173
|
-
return this.#analyses.page(input.cursor, options.modelOutput);
|
|
11181
|
+
return this.#analyses.page(input.cursor, options.modelOutput, input.redact);
|
|
11174
11182
|
}
|
|
11175
11183
|
if (input.mode === "outline" || input.mode === "imports" || input.mode === "tests")
|
|
11176
11184
|
return this.#navigate(input, access, options);
|
|
@@ -11626,6 +11634,11 @@ function normalizeRequestAliases(request) {
|
|
|
11626
11634
|
} else if (mode !== undefined) {
|
|
11627
11635
|
input = { ...rest, mode };
|
|
11628
11636
|
}
|
|
11637
|
+
if (input.mode !== "inspect" && input.sourceCursor === "") {
|
|
11638
|
+
const { sourceCursor: _sourceCursor, ...withoutSourceCursor } = input;
|
|
11639
|
+
input = withoutSourceCursor;
|
|
11640
|
+
notes.push("Empty sourceCursor is ignored outside mode=inspect.");
|
|
11641
|
+
}
|
|
11629
11642
|
if (input.sourceCursor !== undefined && input.line !== undefined) {
|
|
11630
11643
|
const { line: _line, ...withoutLine } = input;
|
|
11631
11644
|
input = withoutLine;
|
|
@@ -13634,16 +13647,18 @@ ${page.body}${rangeNote}${contextNote}${missingSelectionNote}
|
|
|
13634
13647
|
}
|
|
13635
13648
|
|
|
13636
13649
|
// src/prompt-guidelines.ts
|
|
13650
|
+
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.";
|
|
13637
13651
|
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.";
|
|
13638
13652
|
function siftLightPromptGuidelines(structuredOutput = true) {
|
|
13639
13653
|
return [
|
|
13654
|
+
SIFT_LIGHT_DISCOVERY_GUIDANCE,
|
|
13640
13655
|
`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.`,
|
|
13641
13656
|
`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.`,
|
|
13642
13657
|
`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.`,
|
|
13643
13658
|
`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.`,
|
|
13644
13659
|
`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.`,
|
|
13645
13660
|
`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.`,
|
|
13646
|
-
`Use anyOf:["term1","term2"] when every exact occurrence of
|
|
13661
|
+
`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.`,
|
|
13647
13662
|
`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.`,
|
|
13648
13663
|
`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.`,
|
|
13649
13664
|
`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.`,
|
|
@@ -21057,7 +21072,7 @@ var siftLightSchema = _Object_({
|
|
|
21057
21072
|
})
|
|
21058
21073
|
})),
|
|
21059
21074
|
sourceCursor: Optional(String2({
|
|
21060
|
-
description: "Missing-source continuation token. Copy nextRequest exactly: mode=inspect plus sourceCursor only. Same token replays the same page; changed or expired sources fail clearly."
|
|
21075
|
+
description: "Missing-source continuation token. Copy nextRequest exactly: mode=inspect plus sourceCursor only. Same token replays the same page; changed or expired sources fail clearly. An empty string outside mode=inspect is treated as an omitted placeholder and disclosed in request notes; nonempty tokens still require mode=inspect."
|
|
21061
21076
|
})),
|
|
21062
21077
|
symbol: Optional(String2({
|
|
21063
21078
|
description: "Syntax name for outline/imports/tests; use a concrete source file and line to narrow repeated names."
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-search
|
|
3
|
+
description: Use sift-light for local project content, filenames, symbols and related tests. Before reporting its MCP unavailable, inspect the complete runtime registry including ALL_TOOLS deferred entries and load mcp__sift_light__sift_light. Applies when searching local files or diagnosing sift-light discovery; ordinary exact-path reads, edits, builds and tests do not require a search.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Local search with sift-light
|
|
7
|
+
|
|
8
|
+
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.
|
|
9
|
+
|
|
10
|
+
Use exact pattern searches for names and error text; files + query for unknown filenames; anyOf/allOf for several literal conditions. Use applicable structural modes for symbols and relationships. Select concept/hybrid only when semantic recall is needed and enabled.
|
|
11
|
+
|
|
12
|
+
Read the live tool declaration before constructing the request. Copy continuation and argument-repair requests exactly; repair rejected arguments once. Inspect exact source when it is needed for the authorized task. Report partial coverage and connection failures accurately. This skill does not authorize edits, installations, configuration changes or remote actions.
|
package/src/analysis-store.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "./analysis-term-pages.js";
|
|
26
26
|
|
|
27
27
|
import { analysisExtraGroups, statisticsForItems } from "./result-statistics.js";
|
|
28
|
+
import { termCountLabel } from "./term-count-label.js";
|
|
28
29
|
interface StoredAnalysis {
|
|
29
30
|
id: string;
|
|
30
31
|
result: AnalysisResultSet;
|
|
@@ -217,10 +218,11 @@ function publicAnalysisItem(
|
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
function safeTermCounts(
|
|
220
|
-
termCounts: readonly { retainedOccurrences: number }[] | undefined,
|
|
221
|
+
termCounts: readonly { term: string; retainedOccurrences: number }[] | undefined,
|
|
222
|
+
redact = false,
|
|
221
223
|
): { term: string; retainedOccurrences: number }[] | undefined {
|
|
222
224
|
return termCounts?.map((entry, index) => ({
|
|
223
|
-
term:
|
|
225
|
+
term: termCountLabel(entry.term, index, redact),
|
|
224
226
|
retainedOccurrences: entry.retainedOccurrences,
|
|
225
227
|
}));
|
|
226
228
|
}
|
|
@@ -368,18 +370,18 @@ export class AnalysisStore {
|
|
|
368
370
|
return structuredClone(item);
|
|
369
371
|
}
|
|
370
372
|
|
|
371
|
-
page(cursor: string, modelOutput = false): SiftLightResult {
|
|
373
|
+
page(cursor: string, modelOutput = false, redact = false): SiftLightResult {
|
|
372
374
|
const { stored, offset, kind } = this.resolve(cursor);
|
|
373
|
-
const { result }
|
|
375
|
+
const result = redact ? { ...stored.result, redact: true } : stored.result;
|
|
374
376
|
if (kind === "analysis-terms") return analysisTermPage(result, stored.id, offset);
|
|
375
377
|
const hybridPreview = kind === "analysis-hybrid";
|
|
376
378
|
const hybridMatchesRequest = hybridPreview
|
|
377
379
|
? { cursor: `${stored.id}.analysis.0`, ...(result.redact ? { redact: true } : {}) }
|
|
378
380
|
: undefined;
|
|
381
|
+
const publicTerms = safeTermCounts(result.termCounts, result.redact);
|
|
379
382
|
const pagedTerms =
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const inlineTerms = pagedTerms ? undefined : safeTermCounts(result.termCounts);
|
|
383
|
+
publicTerms && Buffer.byteLength(JSON.stringify(publicTerms)) > MAX_INLINE_TERM_COUNT_BYTES;
|
|
384
|
+
const inlineTerms = pagedTerms ? undefined : publicTerms;
|
|
383
385
|
const termsRequest = pagedTerms ? termCountRequest(stored.id, 0, result.redact) : undefined;
|
|
384
386
|
const items: NonNullable<SiftLightResult["details"]["analysis"]>["items"] = [];
|
|
385
387
|
const sources: NonNullable<SiftLightResult["details"]["analysis"]>["sources"] = [];
|
|
@@ -389,7 +391,7 @@ export class AnalysisStore {
|
|
|
389
391
|
result.items,
|
|
390
392
|
result.items.length,
|
|
391
393
|
result.unit,
|
|
392
|
-
analysisExtraGroups(result.counts,
|
|
394
|
+
analysisExtraGroups(result.counts, inlineTerms, result.items),
|
|
393
395
|
);
|
|
394
396
|
const scope = result.scope
|
|
395
397
|
? ` 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)}`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AnalysisResultSet } from "./analysis-types.js";
|
|
2
2
|
import type { SiftLightResult } from "./types.js";
|
|
3
|
+
import { termCountLabel } from "./term-count-label.js";
|
|
3
4
|
|
|
4
5
|
export const MAX_INLINE_TERM_COUNT_BYTES = 8 * 1024;
|
|
5
6
|
|
|
@@ -23,7 +24,7 @@ export function analysisTermPage(
|
|
|
23
24
|
for (let index = offset; index < all.length; index++) {
|
|
24
25
|
const term = all[index];
|
|
25
26
|
if (!term) throw new Error("Term inventory index unavailable");
|
|
26
|
-
const label =
|
|
27
|
+
const label = termCountLabel(term.term, index, result.redact);
|
|
27
28
|
const row = `${label}: ${String(term.retainedOccurrences)} retained occurrences`;
|
|
28
29
|
const size = Buffer.byteLength(row) + 2;
|
|
29
30
|
if (bytes + size > MAX_INLINE_TERM_COUNT_BYTES) break;
|
package/src/evidence-service.ts
CHANGED
|
@@ -595,7 +595,7 @@ export class EvidenceService {
|
|
|
595
595
|
"Analysis cursor cannot continue in the requested mode",
|
|
596
596
|
"E_CURSOR_WRONG_KIND",
|
|
597
597
|
);
|
|
598
|
-
return this.#analyses.page(input.cursor, options.modelOutput);
|
|
598
|
+
return this.#analyses.page(input.cursor, options.modelOutput, input.redact);
|
|
599
599
|
}
|
|
600
600
|
if (input.mode === "outline" || input.mode === "imports" || input.mode === "tests")
|
|
601
601
|
return this.#navigate(input, access, options);
|
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.2",
|
|
10
|
+
version: "1.0.3-2",
|
|
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)
|
|
@@ -4152,8 +4152,10 @@ async function readWorkspaceDocument(path, cwd, signal, expected, readBudget = M
|
|
|
4152
4152
|
realpath3(cwd)
|
|
4153
4153
|
]);
|
|
4154
4154
|
if (!canonical)
|
|
4155
|
-
throw new SourceDocumentError("source-unavailable",
|
|
4156
|
-
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
|
+
});
|
|
4157
4159
|
if (!before)
|
|
4158
4160
|
throw new SourceDocumentError("source-unavailable", "Source is unavailable");
|
|
4159
4161
|
if (expected && !sameSourceRevision(before, expected.revision)) {
|
|
@@ -5707,6 +5709,11 @@ var SEMANTIC_JUDGE_CLASSIFICATIONS = [
|
|
|
5707
5709
|
];
|
|
5708
5710
|
var SEMANTIC_JUDGE_NON_PROOF_CLAIM = "semantic classification only; local static and runtime verification is not asserted";
|
|
5709
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
|
+
|
|
5710
5717
|
// src/analysis-term-pages.ts
|
|
5711
5718
|
var MAX_INLINE_TERM_COUNT_BYTES = 8 * 1024;
|
|
5712
5719
|
function termCountRequest(id, offset, redact = false) {
|
|
@@ -5724,7 +5731,7 @@ function analysisTermPage(result, id, offset) {
|
|
|
5724
5731
|
const term = all[index];
|
|
5725
5732
|
if (!term)
|
|
5726
5733
|
throw new Error("Term inventory index unavailable");
|
|
5727
|
-
const label =
|
|
5734
|
+
const label = termCountLabel(term.term, index, result.redact);
|
|
5728
5735
|
const row = `${label}: ${String(term.retainedOccurrences)} retained occurrences`;
|
|
5729
5736
|
const size = Buffer.byteLength(row) + 2;
|
|
5730
5737
|
if (bytes + size > MAX_INLINE_TERM_COUNT_BYTES)
|
|
@@ -5911,9 +5918,9 @@ function publicAnalysisItem(result, item, index, storedId, modelOutput) {
|
|
|
5911
5918
|
...publicDetails ? { details: publicDetails } : {}
|
|
5912
5919
|
};
|
|
5913
5920
|
}
|
|
5914
|
-
function safeTermCounts(termCounts) {
|
|
5921
|
+
function safeTermCounts(termCounts, redact = false) {
|
|
5915
5922
|
return termCounts?.map((entry, index) => ({
|
|
5916
|
-
term:
|
|
5923
|
+
term: termCountLabel(entry.term, index, redact),
|
|
5917
5924
|
retainedOccurrences: entry.retainedOccurrences
|
|
5918
5925
|
}));
|
|
5919
5926
|
}
|
|
@@ -6035,21 +6042,22 @@ class AnalysisStore {
|
|
|
6035
6042
|
throw new CursorError("Analysis item is outside the retained result");
|
|
6036
6043
|
return structuredClone(item);
|
|
6037
6044
|
}
|
|
6038
|
-
page(cursor, modelOutput = false) {
|
|
6045
|
+
page(cursor, modelOutput = false, redact = false) {
|
|
6039
6046
|
const { stored, offset, kind } = this.resolve(cursor);
|
|
6040
|
-
const { result }
|
|
6047
|
+
const result = redact ? { ...stored.result, redact: true } : stored.result;
|
|
6041
6048
|
if (kind === "analysis-terms")
|
|
6042
6049
|
return analysisTermPage(result, stored.id, offset);
|
|
6043
6050
|
const hybridPreview = kind === "analysis-hybrid";
|
|
6044
6051
|
const hybridMatchesRequest = hybridPreview ? { cursor: `${stored.id}.analysis.0`, ...result.redact ? { redact: true } : {} } : undefined;
|
|
6045
|
-
const
|
|
6046
|
-
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;
|
|
6047
6055
|
const termsRequest = pagedTerms ? termCountRequest(stored.id, 0, result.redact) : undefined;
|
|
6048
6056
|
const items = [];
|
|
6049
6057
|
const sources = [];
|
|
6050
6058
|
const sourceIds = new Map;
|
|
6051
6059
|
const hybridInspectCursor = result.kind === "hybrid" ? `${stored.id}.analysis.0` : undefined;
|
|
6052
|
-
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));
|
|
6053
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)}` : "";
|
|
6054
6062
|
const coverage = result.coverage ? ` Coverage: ${JSON.stringify(result.coverage)}.` : "";
|
|
6055
6063
|
const stats = result.stats ? ` Stats: ${JSON.stringify(result.stats)}.` : "";
|
|
@@ -11202,7 +11210,7 @@ class EvidenceService {
|
|
|
11202
11210
|
this.#analyses.resolve(input.cursor);
|
|
11203
11211
|
if (input.mode !== undefined && input.mode !== "matches" && input.mode !== "auto")
|
|
11204
11212
|
throw new CursorError("Analysis cursor cannot continue in the requested mode", "E_CURSOR_WRONG_KIND");
|
|
11205
|
-
return this.#analyses.page(input.cursor, options.modelOutput);
|
|
11213
|
+
return this.#analyses.page(input.cursor, options.modelOutput, input.redact);
|
|
11206
11214
|
}
|
|
11207
11215
|
if (input.mode === "outline" || input.mode === "imports" || input.mode === "tests")
|
|
11208
11216
|
return this.#navigate(input, access, options);
|
|
@@ -11658,6 +11666,11 @@ function normalizeRequestAliases(request) {
|
|
|
11658
11666
|
} else if (mode !== undefined) {
|
|
11659
11667
|
input = { ...rest, mode };
|
|
11660
11668
|
}
|
|
11669
|
+
if (input.mode !== "inspect" && input.sourceCursor === "") {
|
|
11670
|
+
const { sourceCursor: _sourceCursor, ...withoutSourceCursor } = input;
|
|
11671
|
+
input = withoutSourceCursor;
|
|
11672
|
+
notes.push("Empty sourceCursor is ignored outside mode=inspect.");
|
|
11673
|
+
}
|
|
11661
11674
|
if (input.sourceCursor !== undefined && input.line !== undefined) {
|
|
11662
11675
|
const { line: _line, ...withoutLine } = input;
|
|
11663
11676
|
input = withoutLine;
|
|
@@ -13666,16 +13679,18 @@ ${page.body}${rangeNote}${contextNote}${missingSelectionNote}
|
|
|
13666
13679
|
}
|
|
13667
13680
|
|
|
13668
13681
|
// src/prompt-guidelines.ts
|
|
13682
|
+
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.";
|
|
13669
13683
|
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.";
|
|
13670
13684
|
function siftLightPromptGuidelines(structuredOutput = true) {
|
|
13671
13685
|
return [
|
|
13686
|
+
SIFT_LIGHT_DISCOVERY_GUIDANCE,
|
|
13672
13687
|
`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.`,
|
|
13673
13688
|
`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.`,
|
|
13674
13689
|
`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.`,
|
|
13675
13690
|
`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.`,
|
|
13676
13691
|
`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.`,
|
|
13677
13692
|
`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.`,
|
|
13678
|
-
`Use anyOf:["term1","term2"] when every exact occurrence of
|
|
13693
|
+
`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.`,
|
|
13679
13694
|
`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.`,
|
|
13680
13695
|
`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.`,
|
|
13681
13696
|
`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.`,
|
|
@@ -13689,10 +13704,12 @@ function siftLightPromptGuidelines(structuredOutput = true) {
|
|
|
13689
13704
|
}
|
|
13690
13705
|
function siftLightModelGuidelines() {
|
|
13691
13706
|
return [
|
|
13692
|
-
|
|
13693
|
-
`
|
|
13694
|
-
`
|
|
13695
|
-
`
|
|
13707
|
+
SIFT_LIGHT_DISCOVERY_GUIDANCE,
|
|
13708
|
+
`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.`,
|
|
13709
|
+
`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.`,
|
|
13710
|
+
`Use ranked counts and paths. Cursor continuation pages retained results; inspect exact source before editing.`,
|
|
13711
|
+
`files+query: filenames; anyOf/allOf: literals; outline/imports/tests: static code; structure: AST; concept/hybrid: semantic candidates, not proof.`,
|
|
13712
|
+
`On rejection keep the strongest mode; repair once, without repeating request/error. Only explicit capability-unavailable permits a visibly partial alternative.`
|
|
13696
13713
|
];
|
|
13697
13714
|
}
|
|
13698
13715
|
function siftLightMcpInstructions(outputMode = DEFAULT_MCP_OUTPUT_MODE) {
|
|
@@ -13769,7 +13786,7 @@ var siftLightSchema = Type.Object({
|
|
|
13769
13786
|
})
|
|
13770
13787
|
})),
|
|
13771
13788
|
sourceCursor: Type.Optional(Type.String({
|
|
13772
|
-
description: "Missing-source continuation token. Copy nextRequest exactly: mode=inspect plus sourceCursor only. Same token replays the same page; changed or expired sources fail clearly."
|
|
13789
|
+
description: "Missing-source continuation token. Copy nextRequest exactly: mode=inspect plus sourceCursor only. Same token replays the same page; changed or expired sources fail clearly. An empty string outside mode=inspect is treated as an omitted placeholder and disclosed in request notes; nonempty tokens still require mode=inspect."
|
|
13773
13790
|
})),
|
|
13774
13791
|
symbol: Type.Optional(Type.String({
|
|
13775
13792
|
description: "Syntax name for outline/imports/tests; use a concrete source file and line to narrow repeated names."
|
package/src/prompt-guidelines.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { DEFAULT_MCP_OUTPUT_MODE, type SiftLightMcpOutputMode } from "./mcp-output.js";
|
|
2
2
|
|
|
3
|
+
export const SIFT_LIGHT_DISCOVERY_GUIDANCE =
|
|
4
|
+
"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.";
|
|
5
|
+
|
|
3
6
|
const SOURCE_OUTPUT_GUIDANCE =
|
|
4
7
|
"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.";
|
|
5
8
|
|
|
6
9
|
export function siftLightPromptGuidelines(structuredOutput = true): string[] {
|
|
7
10
|
return [
|
|
11
|
+
SIFT_LIGHT_DISCOVERY_GUIDANCE,
|
|
8
12
|
`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.`,
|
|
9
13
|
`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.`,
|
|
10
14
|
`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.`,
|
|
11
15
|
`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.`,
|
|
12
16
|
`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.`,
|
|
13
17
|
`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.`,
|
|
14
|
-
`Use anyOf:["term1","term2"] when every exact occurrence of
|
|
18
|
+
`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.`,
|
|
15
19
|
`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.`,
|
|
16
20
|
`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.`,
|
|
17
21
|
`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.`,
|
|
@@ -28,10 +32,12 @@ export function siftLightPromptGuidelines(structuredOutput = true): string[] {
|
|
|
28
32
|
|
|
29
33
|
function siftLightModelGuidelines(): string[] {
|
|
30
34
|
return [
|
|
31
|
-
|
|
32
|
-
`
|
|
33
|
-
`
|
|
34
|
-
`
|
|
35
|
+
SIFT_LIGHT_DISCOVERY_GUIDANCE,
|
|
36
|
+
`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.`,
|
|
37
|
+
`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.`,
|
|
38
|
+
`Use ranked counts and paths. Cursor continuation pages retained results; inspect exact source before editing.`,
|
|
39
|
+
`files+query: filenames; anyOf/allOf: literals; outline/imports/tests: static code; structure: AST; concept/hybrid: semantic candidates, not proof.`,
|
|
40
|
+
`On rejection keep the strongest mode; repair once, without repeating request/error. Only explicit capability-unavailable permits a visibly partial alternative.`,
|
|
35
41
|
];
|
|
36
42
|
}
|
|
37
43
|
|
package/src/request-aliases.ts
CHANGED
|
@@ -39,6 +39,11 @@ export function normalizeRequestAliases(request: SiftLightRequest): NormalizedRe
|
|
|
39
39
|
} else if (mode !== undefined) {
|
|
40
40
|
input = { ...rest, mode };
|
|
41
41
|
}
|
|
42
|
+
if (input.mode !== "inspect" && input.sourceCursor === "") {
|
|
43
|
+
const { sourceCursor: _sourceCursor, ...withoutSourceCursor } = input;
|
|
44
|
+
input = withoutSourceCursor;
|
|
45
|
+
notes.push("Empty sourceCursor is ignored outside mode=inspect.");
|
|
46
|
+
}
|
|
42
47
|
if (input.sourceCursor !== undefined && input.line !== undefined) {
|
|
43
48
|
const { line: _line, ...withoutLine } = input;
|
|
44
49
|
input = withoutLine;
|
package/src/result-statistics.ts
CHANGED
|
@@ -145,7 +145,7 @@ export function formatStatistics(
|
|
|
145
145
|
|
|
146
146
|
export function analysisExtraGroups(
|
|
147
147
|
counts: Record<string, number> | undefined,
|
|
148
|
-
termCounts: readonly { retainedOccurrences: number }[] | undefined,
|
|
148
|
+
termCounts: readonly { term: string; retainedOccurrences: number }[] | undefined,
|
|
149
149
|
items: readonly AnalysisItem[],
|
|
150
150
|
): StatisticsGroup[] {
|
|
151
151
|
const groups: StatisticsGroup[] = [];
|
|
@@ -159,8 +159,8 @@ export function analysisExtraGroups(
|
|
|
159
159
|
const terms = termCounts
|
|
160
160
|
? statisticsGroup(
|
|
161
161
|
"conditions",
|
|
162
|
-
termCounts.map((term
|
|
163
|
-
label:
|
|
162
|
+
termCounts.map((term) => ({
|
|
163
|
+
label: term.term,
|
|
164
164
|
count: term.retainedOccurrences,
|
|
165
165
|
})),
|
|
166
166
|
)
|
package/src/source-document.ts
CHANGED
|
@@ -216,8 +216,10 @@ export async function readWorkspaceDocument(
|
|
|
216
216
|
new SearchPathPolicy(cwd).resolveExistingPath(absolute),
|
|
217
217
|
realpath(cwd),
|
|
218
218
|
]);
|
|
219
|
-
if (!canonical) throw new SourceDocumentError("source-unavailable",
|
|
220
|
-
const before = await getSourceRevision(absolute)
|
|
219
|
+
if (!canonical) throw new SourceDocumentError("source-unavailable", `Path not found: ${path}`);
|
|
220
|
+
const before = await getSourceRevision(absolute, (error) => {
|
|
221
|
+
throw error;
|
|
222
|
+
});
|
|
221
223
|
if (!before) throw new SourceDocumentError("source-unavailable", "Source is unavailable");
|
|
222
224
|
if (expected && !sameSourceRevision(before, expected.revision)) {
|
|
223
225
|
throw new SourceDocumentError("source-changed", "Source changed; start a new inspection");
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Input literals are query metadata; hide them only when redaction is requested. */
|
|
2
|
+
export function termCountLabel(term: string, index: number, redact = false): string {
|
|
3
|
+
return redact
|
|
4
|
+
? `condition #${String(index + 1)}`
|
|
5
|
+
: `#${String(index + 1)} ${JSON.stringify(term)}`;
|
|
6
|
+
}
|
package/src/tool-schema.ts
CHANGED
|
@@ -127,7 +127,7 @@ export const siftLightSchema = Type.Object({
|
|
|
127
127
|
sourceCursor: Type.Optional(
|
|
128
128
|
Type.String({
|
|
129
129
|
description:
|
|
130
|
-
"Missing-source continuation token. Copy nextRequest exactly: mode=inspect plus sourceCursor only. Same token replays the same page; changed or expired sources fail clearly.",
|
|
130
|
+
"Missing-source continuation token. Copy nextRequest exactly: mode=inspect plus sourceCursor only. Same token replays the same page; changed or expired sources fail clearly. An empty string outside mode=inspect is treated as an omitted placeholder and disclosed in request notes; nonempty tokens still require mode=inspect.",
|
|
131
131
|
}),
|
|
132
132
|
),
|
|
133
133
|
symbol: Type.Optional(
|