sift-light 1.0.0
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 +0 -0
- package/CONTRIBUTING.md +19 -0
- package/LICENSE +661 -0
- package/README.md +177 -0
- package/README.zh-CN.md +177 -0
- package/SECURITY.md +25 -0
- package/package.json +142 -0
- package/plugins/sift-light/.claude-plugin/plugin.json +10 -0
- package/plugins/sift-light/.codex-plugin/plugin.json +20 -0
- package/plugins/sift-light/.mcp.json +24 -0
- package/plugins/sift-light/LICENSE +661 -0
- package/plugins/sift-light/concept-worker.mjs +567 -0
- package/plugins/sift-light/hooks/THIRD_PARTY_NOTICES.txt +70 -0
- package/plugins/sift-light/hooks/hooks.json +16 -0
- package/plugins/sift-light/hooks/search-policy.mjs +4113 -0
- package/plugins/sift-light/hooks/tree-sitter-bash.wasm +0 -0
- package/plugins/sift-light/hooks/tree-sitter-powershell.wasm +0 -0
- package/plugins/sift-light/hooks/tree-sitter.wasm +0 -0
- package/plugins/sift-light/kimi.plugin.json +36 -0
- package/plugins/sift-light/mcp-server.mjs +1 -0
- package/plugins/sift-light/omp-extension.mjs +20914 -0
- package/plugins/sift-light/package.json +11 -0
- package/plugins/sift-light/syntax-worker.mjs +280 -0
- package/plugins/sift-light/syntax-worker.toml +2 -0
- package/src/analysis-evidence.ts +43 -0
- package/src/analysis-limits.ts +27 -0
- package/src/analysis-store.ts +559 -0
- package/src/analysis-term-pages.ts +73 -0
- package/src/analysis-types.ts +162 -0
- package/src/audit-search.ts +391 -0
- package/src/capped-lines.ts +112 -0
- package/src/concept-embedding-cache.ts +186 -0
- package/src/concept-inference.ts +218 -0
- package/src/concept-model.ts +96 -0
- package/src/concept-search.ts +577 -0
- package/src/concept-setup.ts +75 -0
- package/src/concept-source-generation.ts +268 -0
- package/src/concept-worker.mjs +567 -0
- package/src/concept-worker.ts +210 -0
- package/src/config-reader.ts +283 -0
- package/src/config.ts +37 -0
- package/src/context-budget.ts +26 -0
- package/src/discovery-errors.ts +2 -0
- package/src/errors.ts +95 -0
- package/src/evidence-candidates.ts +344 -0
- package/src/evidence-ranking.ts +48 -0
- package/src/evidence-service.ts +1047 -0
- package/src/evidence-validation.ts +466 -0
- package/src/evidence-validity.ts +33 -0
- package/src/excerpt.ts +53 -0
- package/src/file-discovery.ts +150 -0
- package/src/file-metadata-filter.ts +46 -0
- package/src/format.ts +407 -0
- package/src/git-diff.ts +195 -0
- package/src/git-process.ts +189 -0
- package/src/git-repository.ts +367 -0
- package/src/git-source.ts +385 -0
- package/src/historical-paths.ts +205 -0
- package/src/hybrid-search.ts +340 -0
- package/src/import-model.ts +611 -0
- package/src/import-navigation.ts +100 -0
- package/src/import-resolution.ts +240 -0
- package/src/index.ts +97 -0
- package/src/inspect-batch.ts +265 -0
- package/src/inspect.ts +271 -0
- package/src/language-capabilities.ts +157 -0
- package/src/language-capability-definitions.ts +204 -0
- package/src/literal-search.ts +25 -0
- package/src/mcp-cli.ts +21 -0
- package/src/mcp-model-output.ts +120 -0
- package/src/mcp-output.ts +9 -0
- package/src/mcp-semantic-judge.ts +39 -0
- package/src/mcp-server.mjs +14064 -0
- package/src/mcp-server.ts +157 -0
- package/src/mcp-stdio.ts +114 -0
- package/src/mcp.ts +631 -0
- package/src/model-error.ts +120 -0
- package/src/multi-term-search.ts +122 -0
- package/src/omp-index.ts +277 -0
- package/src/omp-marketplace-entry.ts +7 -0
- package/src/operation-lifecycle.ts +413 -0
- package/src/operation-output.ts +100 -0
- package/src/owned-parallel.ts +22 -0
- package/src/owned-process.ts +102 -0
- package/src/owned-task-queue.ts +38 -0
- package/src/package-version.ts +3 -0
- package/src/path-policy.ts +203 -0
- package/src/pi-search-policy.ts +33 -0
- package/src/prompt-guidelines.ts +54 -0
- package/src/python-outline.ts +231 -0
- package/src/record-value.ts +3 -0
- package/src/redaction.ts +95 -0
- package/src/request-contract-catalog.ts +215 -0
- package/src/request-contract-error.ts +96 -0
- package/src/request-contract.ts +481 -0
- package/src/request.ts +132 -0
- package/src/result-statistics.ts +178 -0
- package/src/rg.ts +574 -0
- package/src/ripgrep-diagnostics.ts +122 -0
- package/src/ripgrep-executable.ts +43 -0
- package/src/runtime.ts +53 -0
- package/src/scan-revisions.ts +148 -0
- package/src/script-runtime.ts +11 -0
- package/src/search-policy-commands.ts +170 -0
- package/src/search-policy-hook.ts +61 -0
- package/src/search-policy-recovery.ts +388 -0
- package/src/search-policy-shell.ts +280 -0
- package/src/search-policy.ts +102 -0
- package/src/search-retention.ts +77 -0
- package/src/semantic-judge-batches.ts +135 -0
- package/src/semantic-judge.ts +519 -0
- package/src/service.ts +945 -0
- package/src/session-summary.ts +100 -0
- package/src/snapshot-store.ts +203 -0
- package/src/source-access.ts +288 -0
- package/src/source-continuations.ts +168 -0
- package/src/source-document.ts +277 -0
- package/src/source-inspection.ts +731 -0
- package/src/source-pages.ts +112 -0
- package/src/source.ts +286 -0
- package/src/structural-search.ts +125 -0
- package/src/structure.ts +259 -0
- package/src/summary-previews.ts +96 -0
- package/src/summary.ts +79 -0
- package/src/syntax-facts.ts +464 -0
- package/src/syntax-fields.ts +161 -0
- package/src/syntax-search.ts +361 -0
- package/src/syntax-tree.ts +30 -0
- package/src/syntax-types.ts +72 -0
- package/src/syntax-worker.mjs +280 -0
- package/src/syntax-worker.toml +2 -0
- package/src/syntax-worker.ts +143 -0
- package/src/syntax.ts +259 -0
- package/src/test-navigation-facts.ts +261 -0
- package/src/test-navigation.ts +543 -0
- package/src/tool-schema.ts +321 -0
- package/src/tui/dashboard.ts +107 -0
- package/src/tui/layout.ts +291 -0
- package/src/tui/palette.ts +73 -0
- package/src/tui/presentation.ts +240 -0
- package/src/tui/renderers.ts +106 -0
- package/src/types.ts +368 -0
- package/src/validation-output.ts +148 -0
- package/src/validation-types.ts +48 -0
- package/src/workspace-files.ts +142 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { SiftLightLocale } from "../config.js";
|
|
3
|
+
import type { SiftLightInput } from "../service.js";
|
|
4
|
+
import type { SiftLightDetails } from "../types.js";
|
|
5
|
+
import { dashboard } from "./dashboard.js";
|
|
6
|
+
import { fit, renderDashboard, renderSiftLightCallLines, type SiftLightTheme } from "./layout.js";
|
|
7
|
+
|
|
8
|
+
export interface SiftLightToolResult {
|
|
9
|
+
content: { type: string; text?: string }[];
|
|
10
|
+
details?: SiftLightDetails;
|
|
11
|
+
isError?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface SiftLightRenderOptions {
|
|
14
|
+
expanded: boolean;
|
|
15
|
+
isError: boolean;
|
|
16
|
+
isPartial: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function component(render: (width: number) => string[], locale: SiftLightLocale): Component {
|
|
20
|
+
return {
|
|
21
|
+
render(width) {
|
|
22
|
+
try {
|
|
23
|
+
return render(width);
|
|
24
|
+
} catch {
|
|
25
|
+
return fit(
|
|
26
|
+
[locale === "zh-CN" ? "结果显示失败,请重试" : "Result display failed; retry"],
|
|
27
|
+
width,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
invalidate() {},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function failure(text: string, locale: SiftLightLocale): string {
|
|
36
|
+
const zh = locale === "zh-CN";
|
|
37
|
+
if (/expired|cursor.*invalid/i.test(text))
|
|
38
|
+
return zh ? "结果已过期,请重新搜索" : "Results expired; run the search again";
|
|
39
|
+
if (/permission|access denied|EACCES/i.test(text))
|
|
40
|
+
return zh ? "无法访问文件,请检查权限" : "Cannot access files; check permissions";
|
|
41
|
+
if (/not found|ENOENT/i.test(text))
|
|
42
|
+
return zh ? "未找到目标,请检查文件路径" : "Target not found; check the file path";
|
|
43
|
+
if (/cancel/i.test(text)) return zh ? "搜索已取消" : "Search cancelled";
|
|
44
|
+
if (/timeout|deadline/i.test(text))
|
|
45
|
+
return zh ? "搜索超时,请缩小范围重试" : "Search timed out; narrow the search";
|
|
46
|
+
return zh ? "搜索未完成,请检查搜索条件后重试" : "Search failed; check the query and retry";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function renderSiftLightCall(
|
|
50
|
+
input: SiftLightInput,
|
|
51
|
+
locale: SiftLightLocale,
|
|
52
|
+
theme: SiftLightTheme,
|
|
53
|
+
): Component {
|
|
54
|
+
return component((width) => renderSiftLightCallLines(input, locale, theme, width), locale);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function renderSiftLightResult(
|
|
58
|
+
result: SiftLightToolResult,
|
|
59
|
+
options: SiftLightRenderOptions,
|
|
60
|
+
locale: SiftLightLocale,
|
|
61
|
+
theme: SiftLightTheme,
|
|
62
|
+
): Component {
|
|
63
|
+
return component((width) => {
|
|
64
|
+
const zh = locale === "zh-CN";
|
|
65
|
+
const text = result.content
|
|
66
|
+
.filter((item) => item.type === "text")
|
|
67
|
+
.map((item) => item.text ?? "")
|
|
68
|
+
.join("\n");
|
|
69
|
+
const state = result.details?.operation?.state ?? result.details?.status;
|
|
70
|
+
if (
|
|
71
|
+
options.isError ||
|
|
72
|
+
result.isError ||
|
|
73
|
+
state === "failed" ||
|
|
74
|
+
state === "cancelled" ||
|
|
75
|
+
state === "expired"
|
|
76
|
+
) {
|
|
77
|
+
return fit(
|
|
78
|
+
[
|
|
79
|
+
theme.fg("error", zh ? "! 搜索未完成" : "! Search incomplete"),
|
|
80
|
+
failure(state === "cancelled" || state === "expired" ? state : text, locale),
|
|
81
|
+
],
|
|
82
|
+
width,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (options.isPartial || state === "running" || state === "waiting") {
|
|
86
|
+
// The worker reports phase-local counts, not an overall completion percentage.
|
|
87
|
+
return fit(
|
|
88
|
+
[
|
|
89
|
+
theme.fg("accent", zh ? "◌ 正在搜索…" : "◌ Searching…"),
|
|
90
|
+
theme.fg("muted", zh ? "等待搜索结果" : "Waiting for results"),
|
|
91
|
+
],
|
|
92
|
+
width,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const view = dashboard(text, result.details);
|
|
96
|
+
if (!view)
|
|
97
|
+
return fit(
|
|
98
|
+
[
|
|
99
|
+
theme.fg("warning", zh ? "无法展示结果统计" : "Result statistics unavailable"),
|
|
100
|
+
theme.fg("muted", zh ? "请重试搜索" : "Retry the search"),
|
|
101
|
+
],
|
|
102
|
+
width,
|
|
103
|
+
);
|
|
104
|
+
return renderDashboard(view, locale, theme, width, options.expanded);
|
|
105
|
+
}, locale);
|
|
106
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import type { AnalysisDetails } from "./analysis-types.js";
|
|
2
|
+
import type { ValidationDetails } from "./validation-types.js";
|
|
3
|
+
import type { SourceFragment } from "./source-pages.js";
|
|
4
|
+
import type { ByteRange, SourceReference } from "./source-document.js";
|
|
5
|
+
import type { SiftLightInput } from "./service.js";
|
|
6
|
+
import type { OperationProgress } from "./operation-lifecycle.js";
|
|
7
|
+
import type { RequestContractDetails } from "./request-contract.js";
|
|
8
|
+
import type { LanguageCapabilityInventory } from "./language-capabilities.js";
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_PAGE_SIZE = 100;
|
|
11
|
+
export const MAX_PAGE_SIZE = 100;
|
|
12
|
+
export const DEFAULT_RESULT_TOKEN_BUDGET = 2_000;
|
|
13
|
+
export const CONTEXT_BUDGET_POLICY = {
|
|
14
|
+
fullAboveRemainderPercent: 40,
|
|
15
|
+
criticalBelowRemainderPercent: 12,
|
|
16
|
+
resultTokenBudgets: {
|
|
17
|
+
full: DEFAULT_RESULT_TOKEN_BUDGET,
|
|
18
|
+
tight: 1_000,
|
|
19
|
+
critical: 500,
|
|
20
|
+
},
|
|
21
|
+
} as const;
|
|
22
|
+
export const ESTIMATED_CHARACTERS_PER_TOKEN = 4;
|
|
23
|
+
export const DEFAULT_SUMMARY_FILE_LIMIT = 30;
|
|
24
|
+
export const MAX_SELECTED_PATHS = 20;
|
|
25
|
+
export const MAX_INSPECT_TARGETS = 5;
|
|
26
|
+
export const MAX_DISPLAYED_OCCURRENCES = 20;
|
|
27
|
+
export const MAX_STORED_MATCHES = 50_000;
|
|
28
|
+
export const MAX_STORED_OCCURRENCES = 200_000;
|
|
29
|
+
export const MAX_SEARCH_STORAGE_BYTES = 32 * 1024 * 1024;
|
|
30
|
+
export const MAX_LINE_CHARACTERS = 500;
|
|
31
|
+
export const MAX_RESULT_BYTES = 16 * 1024;
|
|
32
|
+
export const MAX_CONTEXT_LINES = 20;
|
|
33
|
+
export const MAX_PROTOCOL_LINE_BYTES = 16 * 1024 * 1024;
|
|
34
|
+
export const MAX_SOURCE_FILE_BYTES = 5 * 1024 * 1024;
|
|
35
|
+
export const MAX_PATH_CHARACTERS = 4_096;
|
|
36
|
+
export const MAX_PATTERN_CHARACTERS = 64 * 1024;
|
|
37
|
+
export const MAX_AUDIT_LITERAL_CHARACTERS = 256;
|
|
38
|
+
export const MAX_FILE_FILTER_ITEMS = 64;
|
|
39
|
+
export const MAX_SOURCE_REVISION_CONCURRENCY = 16;
|
|
40
|
+
export const MAX_SOURCE_REVISION_FILES = 50_000;
|
|
41
|
+
|
|
42
|
+
export type SearchMode =
|
|
43
|
+
| "audit"
|
|
44
|
+
| "concept"
|
|
45
|
+
| "hybrid"
|
|
46
|
+
| "structure"
|
|
47
|
+
| "files"
|
|
48
|
+
| "auto"
|
|
49
|
+
| "summary"
|
|
50
|
+
| "matches"
|
|
51
|
+
| "inspect"
|
|
52
|
+
| "outline"
|
|
53
|
+
| "imports"
|
|
54
|
+
| "tests"
|
|
55
|
+
| "validate"
|
|
56
|
+
| "capabilities"
|
|
57
|
+
| "await"
|
|
58
|
+
| "cancel";
|
|
59
|
+
|
|
60
|
+
export type OperationResultState = "running" | "complete" | "failed" | "cancelled" | "expired";
|
|
61
|
+
|
|
62
|
+
export type OperationProgressDetails = OperationProgress;
|
|
63
|
+
|
|
64
|
+
export interface OperationDetails {
|
|
65
|
+
id: string;
|
|
66
|
+
mode: "concept" | "hybrid";
|
|
67
|
+
state: OperationResultState;
|
|
68
|
+
startedAt: number;
|
|
69
|
+
deadlineAt: number;
|
|
70
|
+
leaseExpiresAt: number;
|
|
71
|
+
progress?: OperationProgressDetails;
|
|
72
|
+
nextRequest?: SiftLightInput;
|
|
73
|
+
error?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type ContextBudgetTier = keyof typeof CONTEXT_BUDGET_POLICY.resultTokenBudgets;
|
|
77
|
+
|
|
78
|
+
export interface ContextBudget {
|
|
79
|
+
tier: ContextBudgetTier;
|
|
80
|
+
contextRemainderPercent: number;
|
|
81
|
+
resultTokenBudget: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface TextPosition {
|
|
85
|
+
line: number;
|
|
86
|
+
character: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TextRange {
|
|
90
|
+
start: TextPosition;
|
|
91
|
+
end: TextPosition;
|
|
92
|
+
encoding: "utf-8" | "utf-16";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface MatchOccurrence {
|
|
96
|
+
byteStart: number;
|
|
97
|
+
byteEnd: number;
|
|
98
|
+
range: TextRange;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SourceRevision {
|
|
102
|
+
size: number;
|
|
103
|
+
mtimeMs: number;
|
|
104
|
+
ctimeMs?: number;
|
|
105
|
+
inode?: number;
|
|
106
|
+
device?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type StructureStatus =
|
|
110
|
+
| "available"
|
|
111
|
+
| "no-symbol"
|
|
112
|
+
| "provider-unavailable"
|
|
113
|
+
| "source-unavailable"
|
|
114
|
+
| "parse-error"
|
|
115
|
+
| "file-too-large"
|
|
116
|
+
| "source-changed";
|
|
117
|
+
|
|
118
|
+
export type SourceBoundary = "syntax" | "requested-range" | "line-window" | "mixed";
|
|
119
|
+
|
|
120
|
+
export interface SymbolRange {
|
|
121
|
+
startLine: number;
|
|
122
|
+
endLine: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface StructureSymbol {
|
|
126
|
+
name: string;
|
|
127
|
+
kind: string;
|
|
128
|
+
scope: string[];
|
|
129
|
+
range: SymbolRange;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface StructureDetails {
|
|
133
|
+
status: StructureStatus;
|
|
134
|
+
provider?: string;
|
|
135
|
+
reason?: string;
|
|
136
|
+
language?: string;
|
|
137
|
+
symbol?: StructureSymbol;
|
|
138
|
+
range?: SymbolRange;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface InspectTarget {
|
|
142
|
+
path: string;
|
|
143
|
+
line: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface SourceExcerptDetails {
|
|
147
|
+
range: SymbolRange;
|
|
148
|
+
omittedBefore: number;
|
|
149
|
+
omittedAfter: number;
|
|
150
|
+
truncatedLines: number[];
|
|
151
|
+
reference?: SourceReference;
|
|
152
|
+
targetRanges?: ByteRange[];
|
|
153
|
+
fragments?: SourceFragment[];
|
|
154
|
+
remainingRanges?: ByteRange[];
|
|
155
|
+
complete?: boolean;
|
|
156
|
+
boundary?: SourceBoundary;
|
|
157
|
+
nextRequest?: SiftLightInput;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface InspectRetry {
|
|
161
|
+
mode: "inspect";
|
|
162
|
+
cursor?: string;
|
|
163
|
+
matchIndex?: number;
|
|
164
|
+
path?: string;
|
|
165
|
+
line?: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface InspectBatchItemDetails {
|
|
169
|
+
inputIndex: number;
|
|
170
|
+
path?: string;
|
|
171
|
+
line?: number;
|
|
172
|
+
matchIndex?: number;
|
|
173
|
+
status: "returned" | "deferred" | "error";
|
|
174
|
+
block?: number;
|
|
175
|
+
structure?: StructureDetails;
|
|
176
|
+
source?: SourceExcerptDetails;
|
|
177
|
+
error?: string;
|
|
178
|
+
retry?: InspectRetry;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface SearchRequest {
|
|
182
|
+
scope?: "strict" | "expand";
|
|
183
|
+
wholeWord?: boolean;
|
|
184
|
+
pattern: string;
|
|
185
|
+
path?: string;
|
|
186
|
+
expandedFromPath?: string;
|
|
187
|
+
glob: string[];
|
|
188
|
+
exclude: string[];
|
|
189
|
+
literal: boolean;
|
|
190
|
+
ignoreCase?: boolean;
|
|
191
|
+
hidden: boolean;
|
|
192
|
+
ignorePolicy?: "respect" | "include";
|
|
193
|
+
binaryAsText?: boolean;
|
|
194
|
+
context: number;
|
|
195
|
+
pageSize: number;
|
|
196
|
+
redact?: boolean;
|
|
197
|
+
modifiedAfterMs?: number;
|
|
198
|
+
modifiedBeforeMs?: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface SearchScopeDetails {
|
|
202
|
+
path: string;
|
|
203
|
+
requestedPath: string;
|
|
204
|
+
glob: string[];
|
|
205
|
+
exclude: string[];
|
|
206
|
+
hidden: boolean;
|
|
207
|
+
ignorePolicy?: "respect" | "include";
|
|
208
|
+
expandedToProjectRoot: boolean;
|
|
209
|
+
assertion: "requested-scope" | "project-wide";
|
|
210
|
+
modifiedAfterMs?: number;
|
|
211
|
+
modifiedBeforeMs?: number;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface MatchRecord {
|
|
215
|
+
absolutePath: string;
|
|
216
|
+
displayPath: string;
|
|
217
|
+
lineNumber: number;
|
|
218
|
+
lineContent: string;
|
|
219
|
+
lineTruncated: boolean;
|
|
220
|
+
occurrences: MatchOccurrence[];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface SearchScan {
|
|
224
|
+
request: SearchRequest;
|
|
225
|
+
matches: MatchRecord[];
|
|
226
|
+
totalMatches: number;
|
|
227
|
+
fileCounts: Map<string, number>;
|
|
228
|
+
sourceRevisions: Map<string, SourceRevision>;
|
|
229
|
+
snapshotComplete: boolean;
|
|
230
|
+
filesystemCoverage?: "complete" | "policy-filtered" | "partial";
|
|
231
|
+
filesystemCoverageReasons?: string[];
|
|
232
|
+
ignoredFileCount?: number;
|
|
233
|
+
ignoredFileSamples?: string[];
|
|
234
|
+
searchedFileCount?: number;
|
|
235
|
+
truncatedLines: number;
|
|
236
|
+
retention?: SearchRetentionDetails;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface SearchRetentionDetails {
|
|
240
|
+
accountedBytes: number;
|
|
241
|
+
retainedOccurrences: number;
|
|
242
|
+
maxBytes: number;
|
|
243
|
+
maxOccurrences: number;
|
|
244
|
+
reasons: string[];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface SearchSnapshot extends SearchScan {
|
|
248
|
+
id: string;
|
|
249
|
+
createdAt: number;
|
|
250
|
+
lastAccessedAt: number;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface StatisticsEntry {
|
|
254
|
+
label: string;
|
|
255
|
+
count: number;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface StatisticsGroup {
|
|
259
|
+
dimension: string;
|
|
260
|
+
entries: StatisticsEntry[];
|
|
261
|
+
omitted: number;
|
|
262
|
+
total: number;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface ResultStatistics {
|
|
266
|
+
unit: string;
|
|
267
|
+
total: number;
|
|
268
|
+
files: number;
|
|
269
|
+
directories: number;
|
|
270
|
+
groups: StatisticsGroup[];
|
|
271
|
+
topFiles: StatisticsEntry[];
|
|
272
|
+
topFilesOmitted: number;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface SiftLightDetails {
|
|
276
|
+
inspectRequest?: SiftLightInput;
|
|
277
|
+
version: 1;
|
|
278
|
+
mode: SearchMode;
|
|
279
|
+
status: "complete" | "partial" | "waiting" | "running" | "cancelled" | "failed" | "expired";
|
|
280
|
+
error?: RequestContractDetails;
|
|
281
|
+
totalMatches: number;
|
|
282
|
+
storedMatches: number;
|
|
283
|
+
totalFiles: number;
|
|
284
|
+
returnedMatches: number;
|
|
285
|
+
snapshotComplete: boolean;
|
|
286
|
+
retention?: SearchRetentionDetails;
|
|
287
|
+
cursor?: string;
|
|
288
|
+
nextRequest?: SiftLightInput;
|
|
289
|
+
analysis?: AnalysisDetails;
|
|
290
|
+
statistics?: ResultStatistics;
|
|
291
|
+
validation?: ValidationDetails;
|
|
292
|
+
sourceBlocks?: { path: string; source: SourceExcerptDetails }[];
|
|
293
|
+
summaryFilesShown?: number;
|
|
294
|
+
summaryOffset?: number;
|
|
295
|
+
selectedPaths?: string[];
|
|
296
|
+
selectionMissingPaths?: string[];
|
|
297
|
+
summaryFilesOmitted?: number;
|
|
298
|
+
lineContentTruncated?: number;
|
|
299
|
+
occurrenceRangesOmitted?: number;
|
|
300
|
+
occurrenceMatchesTruncated?: number;
|
|
301
|
+
sourceUnverifiedFileCount?: number;
|
|
302
|
+
summaryPreviewsShown?: number;
|
|
303
|
+
summaryPreviewsOmitted?: number;
|
|
304
|
+
budgetTier?: ContextBudgetTier;
|
|
305
|
+
contextRemainderPercent?: number;
|
|
306
|
+
resultTokenBudget?: number;
|
|
307
|
+
contextOmittedFiles?: string[];
|
|
308
|
+
contextChangedFiles?: string[];
|
|
309
|
+
structure?: StructureDetails;
|
|
310
|
+
source?: SourceExcerptDetails;
|
|
311
|
+
inspections?: InspectBatchItemDetails[];
|
|
312
|
+
scope?: SearchScopeDetails;
|
|
313
|
+
redactedCount?: number;
|
|
314
|
+
redactionRequested?: boolean;
|
|
315
|
+
redactionApplied?: boolean;
|
|
316
|
+
operation?: OperationDetails;
|
|
317
|
+
capabilities?: LanguageCapabilityInventory;
|
|
318
|
+
audit?: AuditReceiptDetails;
|
|
319
|
+
searchCoverage?: {
|
|
320
|
+
retainedMatches: "complete" | "partial";
|
|
321
|
+
filesystem: "complete" | "policy-filtered" | "partial";
|
|
322
|
+
ignoredFiles: number;
|
|
323
|
+
ignoredFileSamples: string[];
|
|
324
|
+
searchedFiles: number;
|
|
325
|
+
reasons: string[];
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export interface AuditPatternInput {
|
|
330
|
+
id: string;
|
|
331
|
+
literal: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface AuditFindingDetails {
|
|
335
|
+
id: string;
|
|
336
|
+
status: "present" | "absent_with_complete_coverage" | "unknown";
|
|
337
|
+
matches: number;
|
|
338
|
+
files: number;
|
|
339
|
+
evidence: { path: string; line: number }[];
|
|
340
|
+
reproduction: SiftLightInput;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export interface AuditReceiptDetails {
|
|
344
|
+
declaredScope: SearchScopeDetails;
|
|
345
|
+
coverage: {
|
|
346
|
+
filesDiscovered: number;
|
|
347
|
+
filesAdmitted: number;
|
|
348
|
+
filesSearched: number;
|
|
349
|
+
ignoredFiles: number;
|
|
350
|
+
ignoredFileSamples: string[];
|
|
351
|
+
ignoredFilesOmitted: number;
|
|
352
|
+
filesSkippedOther: number;
|
|
353
|
+
complete: boolean;
|
|
354
|
+
reasons: string[];
|
|
355
|
+
reasonsOmitted: number;
|
|
356
|
+
};
|
|
357
|
+
stability: {
|
|
358
|
+
status: "stable" | "changed_during_search" | "unknown";
|
|
359
|
+
changedFiles: string[];
|
|
360
|
+
changedFilesOmitted: number;
|
|
361
|
+
};
|
|
362
|
+
findings: AuditFindingDetails[];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export interface SiftLightResult {
|
|
366
|
+
text: string;
|
|
367
|
+
details: SiftLightDetails;
|
|
368
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { AnalysisItem, AnalysisDetails } from "./analysis-types.js";
|
|
2
|
+
import type { SiftLightDetails, SiftLightResult } from "./types.js";
|
|
3
|
+
import type {
|
|
4
|
+
ValidationDetails,
|
|
5
|
+
EvidenceSourceStatus,
|
|
6
|
+
EvidenceSourceScope,
|
|
7
|
+
} from "./validation-types.js";
|
|
8
|
+
import type { SavedEvidenceValidationResult } from "./evidence-validation.js";
|
|
9
|
+
|
|
10
|
+
function sourceItem(status: EvidenceSourceStatus, index: number): AnalysisItem {
|
|
11
|
+
return {
|
|
12
|
+
path: status.path,
|
|
13
|
+
line: 1,
|
|
14
|
+
label: `source ${status.status}`,
|
|
15
|
+
details: {
|
|
16
|
+
role: status.role,
|
|
17
|
+
status: status.status,
|
|
18
|
+
...(status.reason ? { reason: status.reason } : {}),
|
|
19
|
+
index,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function publicSourceStatus(status: EvidenceSourceStatus): EvidenceSourceStatus {
|
|
25
|
+
const reference = status.current ?? status.expected;
|
|
26
|
+
const actualTarget =
|
|
27
|
+
status.actualTarget ??
|
|
28
|
+
(reference === undefined
|
|
29
|
+
? undefined
|
|
30
|
+
: reference.origin.kind === "git"
|
|
31
|
+
? "recorded-git"
|
|
32
|
+
: "current-worktree");
|
|
33
|
+
return {
|
|
34
|
+
path: status.path,
|
|
35
|
+
role: status.role,
|
|
36
|
+
status: status.status,
|
|
37
|
+
...(actualTarget ? { actualTarget } : {}),
|
|
38
|
+
...(status.reason ? { reason: status.reason } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sourceStatusRank(status: EvidenceSourceStatus["status"]): number {
|
|
43
|
+
return status === "stale" ? 0 : status === "unknown" ? 1 : 2;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function publicValidationDetails(
|
|
47
|
+
scope: EvidenceSourceScope,
|
|
48
|
+
sources: readonly EvidenceSourceStatus[],
|
|
49
|
+
values: Partial<ValidationDetails> &
|
|
50
|
+
Pick<
|
|
51
|
+
ValidationDetails,
|
|
52
|
+
"freshness" | "coverage" | "truncation" | "comparisonTarget" | "checkInterval"
|
|
53
|
+
>,
|
|
54
|
+
): ValidationDetails {
|
|
55
|
+
const ordered = [...sources].toSorted((left, right) => {
|
|
56
|
+
return (
|
|
57
|
+
sourceStatusRank(left.status) - sourceStatusRank(right.status) ||
|
|
58
|
+
left.path.localeCompare(right.path)
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
const visible: EvidenceSourceStatus[] = [];
|
|
62
|
+
let sourceBytes = 0;
|
|
63
|
+
for (const source of ordered) {
|
|
64
|
+
const compact = publicSourceStatus(source);
|
|
65
|
+
const rowBytes = Buffer.byteLength(JSON.stringify(compact));
|
|
66
|
+
if (visible.length > 0 && sourceBytes + rowBytes > 4_096) break;
|
|
67
|
+
visible.push(compact);
|
|
68
|
+
sourceBytes += rowBytes;
|
|
69
|
+
}
|
|
70
|
+
const omitted = sources.length - visible.length;
|
|
71
|
+
return {
|
|
72
|
+
scope,
|
|
73
|
+
checked: visible.filter((source) => source.status !== "unknown"),
|
|
74
|
+
unchecked: visible.filter((source) => source.status === "unknown"),
|
|
75
|
+
...(omitted > 0 ? { sourceOmitted: omitted } : {}),
|
|
76
|
+
...values,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function base(
|
|
81
|
+
mode: "validate",
|
|
82
|
+
status: "complete" | "partial",
|
|
83
|
+
total: number,
|
|
84
|
+
files: number,
|
|
85
|
+
returned = total,
|
|
86
|
+
): SiftLightDetails {
|
|
87
|
+
return {
|
|
88
|
+
version: 1,
|
|
89
|
+
mode,
|
|
90
|
+
status,
|
|
91
|
+
totalMatches: total,
|
|
92
|
+
storedMatches: total,
|
|
93
|
+
totalFiles: files,
|
|
94
|
+
returnedMatches: returned,
|
|
95
|
+
snapshotComplete: status === "complete",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function validationResult(
|
|
100
|
+
state: SavedEvidenceValidationResult,
|
|
101
|
+
cursor: string,
|
|
102
|
+
checkInterval: { start: number; end: number; selected?: number },
|
|
103
|
+
): SiftLightResult {
|
|
104
|
+
const { recheck, comparisonTarget, coverage } = state;
|
|
105
|
+
const status =
|
|
106
|
+
state.storedPartial || coverage === "partial" || recheck.validity !== "current"
|
|
107
|
+
? "partial"
|
|
108
|
+
: "complete";
|
|
109
|
+
const reasons = [...new Set([...(state.reasons ?? []), ...recheck.reasons])];
|
|
110
|
+
const validation = publicValidationDetails(state.scope, recheck.sources, {
|
|
111
|
+
comparisonTarget,
|
|
112
|
+
freshness: recheck.validity,
|
|
113
|
+
coverage,
|
|
114
|
+
truncation: { truncated: state.storedPartial, reasons },
|
|
115
|
+
checkInterval,
|
|
116
|
+
});
|
|
117
|
+
const analysis: AnalysisDetails = {
|
|
118
|
+
kind: "validate",
|
|
119
|
+
unit: "evidence-items",
|
|
120
|
+
totalItems: recheck.sources.length,
|
|
121
|
+
returnedItems: validation.checked.length + validation.unchecked.length,
|
|
122
|
+
items: [...validation.checked, ...validation.unchecked].map((source, index) =>
|
|
123
|
+
Object.assign(sourceItem(source, index + 1), { index: index + 1 }),
|
|
124
|
+
),
|
|
125
|
+
reasons,
|
|
126
|
+
validation,
|
|
127
|
+
};
|
|
128
|
+
const details: SiftLightDetails = {
|
|
129
|
+
...base(
|
|
130
|
+
"validate",
|
|
131
|
+
status,
|
|
132
|
+
recheck.sources.length,
|
|
133
|
+
new Set(recheck.sources.map((source) => source.path)).size,
|
|
134
|
+
validation.checked.length + validation.unchecked.length,
|
|
135
|
+
),
|
|
136
|
+
cursor,
|
|
137
|
+
analysis,
|
|
138
|
+
validation,
|
|
139
|
+
};
|
|
140
|
+
const sourceRows = [...validation.checked, ...validation.unchecked].map(
|
|
141
|
+
(source, index) =>
|
|
142
|
+
`#${String(index + 1)} ${JSON.stringify(source.path)}: ${source.status}${source.reason ? ` (${source.reason})` : ""}`,
|
|
143
|
+
);
|
|
144
|
+
return {
|
|
145
|
+
text: `Evidence validation is ${recheck.validity}; checked ${String(validation.checked.length)} source(s), ${String(validation.unchecked.length)} require attention; coverage ${coverage}; target ${comparisonTarget}; check interval ${String(checkInterval.start)}-${String(checkInterval.end)}.${validation.sourceOmitted ? ` ${String(validation.sourceOmitted)} source(s) omitted from this page.` : ""}${reasons.length ? ` ${reasons.join(" ")}` : ""}${sourceRows.length ? `\n${sourceRows.join("\n")}` : ""}`,
|
|
146
|
+
details,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SourceReference } from "./source-document.js";
|
|
2
|
+
|
|
3
|
+
export type EvidenceValidity = "current" | "stale" | "unknown";
|
|
4
|
+
|
|
5
|
+
export type EvidenceComparisonTarget = "current-worktree" | "recorded-git" | "mixed";
|
|
6
|
+
|
|
7
|
+
export type EvidenceCoverageStatus = "complete" | "partial" | "skipped" | "not-applicable";
|
|
8
|
+
|
|
9
|
+
export interface EvidenceSourceScope {
|
|
10
|
+
/** Saved evidence root, expressed as an absolute or search-relative path. */
|
|
11
|
+
root: string;
|
|
12
|
+
include?: readonly string[];
|
|
13
|
+
exclude?: readonly string[];
|
|
14
|
+
/** Whether hidden files are admitted by the search source inventory. */
|
|
15
|
+
hidden?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type EvidenceDependencyRole = "source";
|
|
19
|
+
|
|
20
|
+
export interface EvidenceSourceStatus {
|
|
21
|
+
path: string;
|
|
22
|
+
role: EvidenceDependencyRole;
|
|
23
|
+
status: EvidenceValidity;
|
|
24
|
+
expected?: SourceReference;
|
|
25
|
+
current?: SourceReference;
|
|
26
|
+
/** Actual source origin used for this item when it can be determined. */
|
|
27
|
+
actualTarget?: EvidenceComparisonTarget;
|
|
28
|
+
reason?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface EvidenceRecheck {
|
|
32
|
+
validity: EvidenceValidity;
|
|
33
|
+
coverage: EvidenceCoverageStatus;
|
|
34
|
+
sources: readonly EvidenceSourceStatus[];
|
|
35
|
+
reasons: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ValidationDetails {
|
|
39
|
+
freshness: EvidenceValidity;
|
|
40
|
+
coverage: EvidenceCoverageStatus;
|
|
41
|
+
scope: EvidenceSourceScope;
|
|
42
|
+
comparisonTarget: EvidenceComparisonTarget;
|
|
43
|
+
checked: readonly EvidenceSourceStatus[];
|
|
44
|
+
unchecked: readonly EvidenceSourceStatus[];
|
|
45
|
+
sourceOmitted?: number;
|
|
46
|
+
truncation: { truncated: boolean; reasons: readonly string[] };
|
|
47
|
+
checkInterval: { start: number; end: number; selected?: number };
|
|
48
|
+
}
|