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,361 @@
|
|
|
1
|
+
import { MAX_ANALYSIS_RESULTS } from "./analysis-limits.js";
|
|
2
|
+
import { sourceEvidence } from "./analysis-evidence.js";
|
|
3
|
+
import type { AnalysisItem } from "./analysis-types.js";
|
|
4
|
+
import { SiftLightError } from "./errors.js";
|
|
5
|
+
import type { ByteRange, SourceDocument } from "./source-document.js";
|
|
6
|
+
import type { SyntaxAnalysis, SyntaxRole, SyntaxRoleName, SyntaxSymbol } from "./syntax.js";
|
|
7
|
+
|
|
8
|
+
export interface SyntaxSearchResult {
|
|
9
|
+
items: AnalysisItem[];
|
|
10
|
+
partial: boolean;
|
|
11
|
+
reasons: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface RoleIndex {
|
|
15
|
+
roles: SyntaxRole[];
|
|
16
|
+
widest: number[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function unavailable(
|
|
20
|
+
document: SourceDocument,
|
|
21
|
+
analysis: SyntaxAnalysis,
|
|
22
|
+
): SyntaxSearchResult | undefined {
|
|
23
|
+
if (!document.utf8) {
|
|
24
|
+
return {
|
|
25
|
+
items: [],
|
|
26
|
+
partial: true,
|
|
27
|
+
reasons: [`${document.path}: syntax classification requires lossless UTF-8 source`],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (analysis.status !== "ok") {
|
|
31
|
+
return {
|
|
32
|
+
items: [],
|
|
33
|
+
partial: true,
|
|
34
|
+
reasons: [`${document.path}: syntax ${analysis.status}; this source remains unclassified`],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function byteBoundary(document: SourceDocument, offset: number): boolean {
|
|
41
|
+
const byte = document.bytes[offset];
|
|
42
|
+
return offset === document.bytes.length || (byte !== undefined && (byte & 0xc0) !== 0x80);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildRoleIndex(
|
|
46
|
+
analysis: SyntaxAnalysis,
|
|
47
|
+
selected: ReadonlySet<SyntaxRoleName>,
|
|
48
|
+
): RoleIndex[] {
|
|
49
|
+
const groups = new Map<string, SyntaxRole[]>();
|
|
50
|
+
for (const role of analysis.roles) {
|
|
51
|
+
if (!selected.has(role.role)) continue;
|
|
52
|
+
const key = JSON.stringify([role.role, role.certainty, role.subkind]);
|
|
53
|
+
const group = groups.get(key);
|
|
54
|
+
if (group) group.push(role);
|
|
55
|
+
else groups.set(key, [role]);
|
|
56
|
+
}
|
|
57
|
+
return [...groups.values()].map((roles) => {
|
|
58
|
+
roles.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
59
|
+
const widest: number[] = [];
|
|
60
|
+
let best = 0;
|
|
61
|
+
for (let index = 0; index < roles.length; index++) {
|
|
62
|
+
if ((roles[index]?.end ?? 0) > (roles[best]?.end ?? 0)) best = index;
|
|
63
|
+
widest.push(best);
|
|
64
|
+
}
|
|
65
|
+
return { roles, widest };
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function roleProofs(indices: readonly RoleIndex[], start: number, end: number): SyntaxRole[] {
|
|
70
|
+
const proofs: SyntaxRole[] = [];
|
|
71
|
+
for (const index of indices) {
|
|
72
|
+
let low = 0,
|
|
73
|
+
high = index.roles.length;
|
|
74
|
+
while (low < high) {
|
|
75
|
+
const middle = (low + high) >>> 1;
|
|
76
|
+
if ((index.roles[middle]?.start ?? Infinity) <= start) low = middle + 1;
|
|
77
|
+
else high = middle;
|
|
78
|
+
}
|
|
79
|
+
const widest = index.widest[low - 1];
|
|
80
|
+
const role = widest === undefined ? undefined : index.roles[widest];
|
|
81
|
+
if (role && end <= role.end && start < role.end) proofs.push(role);
|
|
82
|
+
}
|
|
83
|
+
return proofs;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Role union applies to whole occurrences and never duplicates an occurrence. */
|
|
87
|
+
export function filterRoleOccurrences(
|
|
88
|
+
document: SourceDocument,
|
|
89
|
+
analysis: SyntaxAnalysis,
|
|
90
|
+
occurrences: ByteRange[],
|
|
91
|
+
roles: SyntaxRoleName[],
|
|
92
|
+
): SyntaxSearchResult {
|
|
93
|
+
const missing = unavailable(document, analysis);
|
|
94
|
+
if (missing) return missing;
|
|
95
|
+
const indices = buildRoleIndex(analysis, new Set(roles));
|
|
96
|
+
const items: AnalysisItem[] = [];
|
|
97
|
+
const reasons: string[] = [];
|
|
98
|
+
const seen = new Set<string>();
|
|
99
|
+
let splitOccurrences = 0;
|
|
100
|
+
for (const range of occurrences) {
|
|
101
|
+
document.checkRange(range);
|
|
102
|
+
const key = `${range.start}:${range.end}`;
|
|
103
|
+
if (seen.has(key)) continue;
|
|
104
|
+
seen.add(key);
|
|
105
|
+
if (!byteBoundary(document, range.start) || !byteBoundary(document, range.end)) {
|
|
106
|
+
splitOccurrences++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const proofs = roleProofs(
|
|
110
|
+
indices,
|
|
111
|
+
document.toCharacterOffset(range.start),
|
|
112
|
+
document.toCharacterOffset(range.end),
|
|
113
|
+
);
|
|
114
|
+
if (proofs.length === 0) continue;
|
|
115
|
+
if (items.length === MAX_ANALYSIS_RESULTS) {
|
|
116
|
+
reasons.push(
|
|
117
|
+
`${document.path}: role result retention limit reached; additional occurrences are not retained`,
|
|
118
|
+
);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
const match = sourceEvidence(document, range);
|
|
122
|
+
items.push({
|
|
123
|
+
path: document.path,
|
|
124
|
+
line: match.line,
|
|
125
|
+
range: match.range,
|
|
126
|
+
source: document.reference,
|
|
127
|
+
label: [...new Set(proofs.map((proof) => proof.role))].join(", "),
|
|
128
|
+
excerpt: match.excerpt,
|
|
129
|
+
details: {
|
|
130
|
+
roles: proofs.map((proof) => ({
|
|
131
|
+
role: proof.role,
|
|
132
|
+
certainty: proof.certainty,
|
|
133
|
+
subkind: proof.subkind,
|
|
134
|
+
range: { ...range },
|
|
135
|
+
})),
|
|
136
|
+
excerptRange: match.excerptRange,
|
|
137
|
+
excerptTruncated: match.excerptTruncated,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (splitOccurrences > 0) {
|
|
142
|
+
reasons.push(
|
|
143
|
+
`${document.path}: ${splitOccurrences} occurrence(s) split UTF-8 characters and could not be classified`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return { items, partial: reasons.length > 0, reasons };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function merge(ranges: readonly ByteRange[]): ByteRange[] {
|
|
150
|
+
const result: ByteRange[] = [];
|
|
151
|
+
for (const range of ranges.toSorted((a, b) => a.start - b.start || b.end - a.end)) {
|
|
152
|
+
if (range.start === range.end) continue;
|
|
153
|
+
const previous = result.at(-1);
|
|
154
|
+
if (previous && range.start <= previous.end) previous.end = Math.max(previous.end, range.end);
|
|
155
|
+
else result.push({ ...range });
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function intersect(range: ByteRange, sorted: readonly ByteRange[]): ByteRange[] {
|
|
161
|
+
let low = 0,
|
|
162
|
+
high = sorted.length;
|
|
163
|
+
while (low < high) {
|
|
164
|
+
const middle = (low + high) >>> 1;
|
|
165
|
+
if ((sorted[middle]?.end ?? Infinity) <= range.start) low = middle + 1;
|
|
166
|
+
else high = middle;
|
|
167
|
+
}
|
|
168
|
+
const result: ByteRange[] = [];
|
|
169
|
+
for (let index = low; index < sorted.length; index++) {
|
|
170
|
+
const other = sorted[index];
|
|
171
|
+
if (!other || other.start >= range.end) break;
|
|
172
|
+
const start = Math.max(range.start, other.start);
|
|
173
|
+
const end = Math.min(range.end, other.end);
|
|
174
|
+
if (start < end) result.push({ start, end });
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function subtract(range: ByteRange, exclusions: readonly ByteRange[]): ByteRange[] {
|
|
180
|
+
const result: ByteRange[] = [];
|
|
181
|
+
let start = range.start;
|
|
182
|
+
for (const excluded of exclusions) {
|
|
183
|
+
if (excluded.end <= start || excluded.start >= range.end) continue;
|
|
184
|
+
if (excluded.start > start) result.push({ start, end: excluded.start });
|
|
185
|
+
start = Math.max(start, excluded.end);
|
|
186
|
+
}
|
|
187
|
+
if (start < range.end) result.push({ start, end: range.end });
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
interface ImplementationRanges {
|
|
192
|
+
symbols: SyntaxSymbol[];
|
|
193
|
+
code: ByteRange[];
|
|
194
|
+
nested: Map<number, ByteRange[]>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function implementationRanges(
|
|
198
|
+
document: SourceDocument,
|
|
199
|
+
analysis: SyntaxAnalysis,
|
|
200
|
+
): ImplementationRanges {
|
|
201
|
+
const symbols = analysis.symbols
|
|
202
|
+
.filter((symbol) => symbol.hasBody)
|
|
203
|
+
.toSorted((a, b) => a.start - b.start || b.end - a.end);
|
|
204
|
+
const stack: SyntaxSymbol[] = [];
|
|
205
|
+
const nested = new Map<number, ByteRange[]>();
|
|
206
|
+
for (const symbol of symbols) {
|
|
207
|
+
let parent = stack.at(-1);
|
|
208
|
+
while (parent && (symbol.start >= parent.end || symbol.end > parent.end)) {
|
|
209
|
+
stack.pop();
|
|
210
|
+
parent = stack.at(-1);
|
|
211
|
+
}
|
|
212
|
+
if (parent) {
|
|
213
|
+
const ranges = nested.get(parent.node) ?? [];
|
|
214
|
+
ranges.push({
|
|
215
|
+
start: document.toByteOffset(symbol.start),
|
|
216
|
+
end: document.toByteOffset(symbol.end),
|
|
217
|
+
});
|
|
218
|
+
nested.set(parent.node, ranges);
|
|
219
|
+
}
|
|
220
|
+
stack.push(symbol);
|
|
221
|
+
}
|
|
222
|
+
const code = merge(
|
|
223
|
+
analysis.roles
|
|
224
|
+
.filter((role) => role.role === "code")
|
|
225
|
+
.map((role) => ({
|
|
226
|
+
start: document.toByteOffset(role.start),
|
|
227
|
+
end: document.toByteOffset(role.end),
|
|
228
|
+
})),
|
|
229
|
+
);
|
|
230
|
+
return { symbols, code, nested };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function owned(
|
|
234
|
+
document: SourceDocument,
|
|
235
|
+
context: ImplementationRanges,
|
|
236
|
+
symbol: SyntaxSymbol,
|
|
237
|
+
changed?: readonly ByteRange[],
|
|
238
|
+
): ByteRange[] {
|
|
239
|
+
if (!symbol.hasBody || symbol.bodyStart === undefined || symbol.bodyEnd === undefined) return [];
|
|
240
|
+
const body = {
|
|
241
|
+
start: document.toByteOffset(symbol.bodyStart),
|
|
242
|
+
end: document.toByteOffset(symbol.bodyEnd),
|
|
243
|
+
};
|
|
244
|
+
const withoutNested = subtract(body, context.nested.get(symbol.node) ?? []);
|
|
245
|
+
const code = withoutNested.flatMap((range) => intersect(range, context.code));
|
|
246
|
+
return changed ? code.flatMap((range) => intersect(range, changed)) : code;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Native symbol ranges minus nested implementations and every non-code interval. */
|
|
250
|
+
export function ownImplementationRanges(
|
|
251
|
+
document: SourceDocument,
|
|
252
|
+
analysis: SyntaxAnalysis,
|
|
253
|
+
symbol: SyntaxSymbol,
|
|
254
|
+
changedRanges?: ByteRange[],
|
|
255
|
+
): ByteRange[] {
|
|
256
|
+
if (unavailable(document, analysis) || analysis.language === "go") return [];
|
|
257
|
+
for (const range of changedRanges ?? []) document.checkRange(range);
|
|
258
|
+
return owned(
|
|
259
|
+
document,
|
|
260
|
+
implementationRanges(document, analysis),
|
|
261
|
+
symbol,
|
|
262
|
+
changedRanges ? merge(changedRanges) : undefined,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function termEvidence(document: SourceDocument, ranges: readonly ByteRange[], term: string) {
|
|
267
|
+
const needle = Buffer.from(term);
|
|
268
|
+
if (needle.length === 0)
|
|
269
|
+
throw new Error("Function conjunction expects normalized non-empty terms");
|
|
270
|
+
let count = 0;
|
|
271
|
+
let first: ByteRange | undefined;
|
|
272
|
+
for (const range of ranges) {
|
|
273
|
+
const bytes = document.bytes.subarray(range.start, range.end);
|
|
274
|
+
let offset = bytes.indexOf(needle);
|
|
275
|
+
while (offset >= 0) {
|
|
276
|
+
const start = range.start + offset;
|
|
277
|
+
count++;
|
|
278
|
+
first ??= { start, end: start + needle.length };
|
|
279
|
+
offset = bytes.indexOf(needle, offset + needle.length);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return first
|
|
283
|
+
? {
|
|
284
|
+
term,
|
|
285
|
+
count,
|
|
286
|
+
evidence: sourceEvidence(document, first),
|
|
287
|
+
omittedOccurrenceEvidence: count - 1,
|
|
288
|
+
}
|
|
289
|
+
: undefined;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Literal AND within each implementation's own code, optionally within changed lines. */
|
|
293
|
+
export function findFunctionConjunctions(
|
|
294
|
+
document: SourceDocument,
|
|
295
|
+
analysis: SyntaxAnalysis,
|
|
296
|
+
terms: string[],
|
|
297
|
+
changedRanges?: ByteRange[],
|
|
298
|
+
): SyntaxSearchResult {
|
|
299
|
+
const missing = unavailable(document, analysis);
|
|
300
|
+
if (missing) return missing;
|
|
301
|
+
if (
|
|
302
|
+
analysis.language !== "javascript" &&
|
|
303
|
+
analysis.language !== "typescript" &&
|
|
304
|
+
analysis.language !== "tsx"
|
|
305
|
+
) {
|
|
306
|
+
return {
|
|
307
|
+
items: [],
|
|
308
|
+
partial: true,
|
|
309
|
+
reasons: [`${document.path}: same-function AND supports JS/TS/TSX only`],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (terms.length === 0)
|
|
313
|
+
throw new SiftLightError("Function conjunction requires normalized terms");
|
|
314
|
+
for (const range of changedRanges ?? []) document.checkRange(range);
|
|
315
|
+
const changed = changedRanges ? merge(changedRanges) : undefined;
|
|
316
|
+
const context = implementationRanges(document, analysis);
|
|
317
|
+
const items: AnalysisItem[] = [];
|
|
318
|
+
for (const symbol of context.symbols) {
|
|
319
|
+
const ranges = owned(document, context, symbol, changed);
|
|
320
|
+
const matches = terms.map((term) => termEvidence(document, ranges, term));
|
|
321
|
+
if (matches.some((match) => match === undefined)) continue;
|
|
322
|
+
if (items.length === MAX_ANALYSIS_RESULTS) {
|
|
323
|
+
return {
|
|
324
|
+
items,
|
|
325
|
+
partial: true,
|
|
326
|
+
reasons: [`${document.path}: function result retention limit reached`],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const range = {
|
|
330
|
+
start: document.toByteOffset(symbol.start),
|
|
331
|
+
end: document.toByteOffset(symbol.end),
|
|
332
|
+
};
|
|
333
|
+
items.push({
|
|
334
|
+
path: document.path,
|
|
335
|
+
line: document.lineAt(range.start),
|
|
336
|
+
source: document.reference,
|
|
337
|
+
range,
|
|
338
|
+
label: symbol.scope ? `${symbol.scope}.${symbol.name}` : symbol.name,
|
|
339
|
+
excerpt: matches.map((match) => match?.evidence.excerpt ?? "").join("\n"),
|
|
340
|
+
details: {
|
|
341
|
+
symbol: {
|
|
342
|
+
name: symbol.name,
|
|
343
|
+
kind: symbol.kind,
|
|
344
|
+
scope: symbol.scope,
|
|
345
|
+
range,
|
|
346
|
+
body:
|
|
347
|
+
symbol.bodyStart !== undefined && symbol.bodyEnd !== undefined
|
|
348
|
+
? {
|
|
349
|
+
start: document.toByteOffset(symbol.bodyStart),
|
|
350
|
+
end: document.toByteOffset(symbol.bodyEnd),
|
|
351
|
+
}
|
|
352
|
+
: undefined,
|
|
353
|
+
},
|
|
354
|
+
terms: matches,
|
|
355
|
+
relation: "same lexical implementation; not proof of a shared execution path or data flow",
|
|
356
|
+
scope: changed ? "implementation-code-intersect-changed-ranges" : "implementation-own-code",
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return { items, partial: false, reasons: [] };
|
|
361
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SyntaxAnalysis, SyntaxNode } from "./syntax-types.js";
|
|
2
|
+
|
|
3
|
+
export function syntaxField(
|
|
4
|
+
analysis: Pick<SyntaxAnalysis, "nodes" | "children">,
|
|
5
|
+
node: number,
|
|
6
|
+
field: string,
|
|
7
|
+
): number | undefined {
|
|
8
|
+
return analysis.children[node]?.find((child) => analysis.nodes[child]?.field === field);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function syntaxFields(
|
|
12
|
+
analysis: Pick<SyntaxAnalysis, "nodes" | "children">,
|
|
13
|
+
node: number,
|
|
14
|
+
field: string,
|
|
15
|
+
): number[] {
|
|
16
|
+
return analysis.children[node]?.filter((child) => analysis.nodes[child]?.field === field) ?? [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function syntaxText(node: SyntaxNode, text: string): string {
|
|
20
|
+
return text.slice(node.start, node.end);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function syntaxChildren(nodes: readonly SyntaxNode[]): number[][] {
|
|
24
|
+
const children: number[][] = Array.from({ length: nodes.length }, () => []);
|
|
25
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
26
|
+
const parent = nodes[i]?.parent;
|
|
27
|
+
if (parent !== null && parent !== undefined) children[parent]?.push(i);
|
|
28
|
+
}
|
|
29
|
+
return children;
|
|
30
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Syntax offsets are UTF-16 half-open ranges into the supplied, unchanged text. */
|
|
2
|
+
export type SyntaxLanguage = "javascript" | "typescript" | "tsx" | "go";
|
|
3
|
+
|
|
4
|
+
export interface SyntaxNode {
|
|
5
|
+
kind: string;
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
parent: number | null;
|
|
9
|
+
field?: string;
|
|
10
|
+
named: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SyntaxSymbol {
|
|
14
|
+
name: string;
|
|
15
|
+
kind: string;
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
bodyStart?: number;
|
|
19
|
+
bodyEnd?: number;
|
|
20
|
+
scope?: string;
|
|
21
|
+
hasBody: boolean;
|
|
22
|
+
/** True means a directly provable export, not runtime API accessibility. */
|
|
23
|
+
exported: boolean;
|
|
24
|
+
node: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SyntaxRoleName =
|
|
28
|
+
| "declaration"
|
|
29
|
+
| "call"
|
|
30
|
+
| "import"
|
|
31
|
+
| "export"
|
|
32
|
+
| "comment"
|
|
33
|
+
| "string"
|
|
34
|
+
| "jsx-text"
|
|
35
|
+
| "code"
|
|
36
|
+
| "unknown";
|
|
37
|
+
|
|
38
|
+
export interface SyntaxRole {
|
|
39
|
+
start: number;
|
|
40
|
+
end: number;
|
|
41
|
+
role: SyntaxRoleName;
|
|
42
|
+
certainty: "syntax" | "candidate";
|
|
43
|
+
subkind?: string;
|
|
44
|
+
node: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type SyntaxStatus = "ok" | "unsupported" | "parse-error" | "limit" | "timeout";
|
|
48
|
+
|
|
49
|
+
export interface SyntaxDiagnostic {
|
|
50
|
+
kind: string;
|
|
51
|
+
start: number;
|
|
52
|
+
end: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Lives for this request only. Never put nodes or children into a session snapshot. */
|
|
56
|
+
export interface SyntaxAnalysis {
|
|
57
|
+
patternMatches?: { start: number; end: number }[];
|
|
58
|
+
language?: SyntaxLanguage;
|
|
59
|
+
status: SyntaxStatus;
|
|
60
|
+
nodes: SyntaxNode[];
|
|
61
|
+
children: number[][];
|
|
62
|
+
symbols: SyntaxSymbol[];
|
|
63
|
+
roles: SyntaxRole[];
|
|
64
|
+
diagnostics: SyntaxDiagnostic[];
|
|
65
|
+
limited: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SyntaxWorkerResult {
|
|
69
|
+
patternMatches?: { start: number; end: number }[];
|
|
70
|
+
status: "ok" | "parse-error" | "limit";
|
|
71
|
+
nodes: SyntaxNode[];
|
|
72
|
+
}
|