knodin 0.7.4 → 0.7.6

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.
Files changed (104) hide show
  1. package/README.md +4 -1
  2. package/dist/bin/cli.js +108 -1859
  3. package/dist/bin/launcher.js +3 -10
  4. package/dist/chunks/chunk-DMQAGX77.js +654 -0
  5. package/dist/chunks/chunk-F4Z3Z766.js +4 -0
  6. package/dist/chunks/chunk-SIJAQVSX.js +3 -0
  7. package/dist/chunks/chunk-X6M4HUUE.js +2 -0
  8. package/dist/chunks/chunk-YPRMY2LP.js +8 -0
  9. package/dist/chunks/pure-compression-cli-4TA2TQD5.js +5 -0
  10. package/dist/chunks/server-7EDF4CBY.js +14 -0
  11. package/dist/chunks/structural-fast-path-KD5KQSPX.js +4 -0
  12. package/dist/src/init-progress-worker.js +2 -106
  13. package/dist/src/repair-progress-worker.js +2 -120
  14. package/docs/INSTALLATION.md +17 -1
  15. package/docs/releases/0.7.6.md +25 -0
  16. package/package.json +1 -110
  17. package/dist/src/agent-integration.js +0 -238
  18. package/dist/src/artifact-refresh.js +0 -81
  19. package/dist/src/cli-args.js +0 -274
  20. package/dist/src/cli-model.js +0 -358
  21. package/dist/src/codeflow-replay.js +0 -80
  22. package/dist/src/compact-structural.js +0 -96
  23. package/dist/src/competitive-cold-mcp.js +0 -40
  24. package/dist/src/competitive-constraints.js +0 -20
  25. package/dist/src/competitive-manifest.js +0 -411
  26. package/dist/src/competitive-measurement.js +0 -183
  27. package/dist/src/competitive-runner.js +0 -487
  28. package/dist/src/competitive-sandbox.js +0 -108
  29. package/dist/src/context-export.js +0 -422
  30. package/dist/src/context.js +0 -102
  31. package/dist/src/diagnostics.js +0 -449
  32. package/dist/src/docs-sections.js +0 -141
  33. package/dist/src/doctor.js +0 -380
  34. package/dist/src/engine/ann-hnsw.js +0 -271
  35. package/dist/src/engine/embeddings.js +0 -193
  36. package/dist/src/engine/file-walker.js +0 -43
  37. package/dist/src/engine/git-history.js +0 -289
  38. package/dist/src/engine/index.js +0 -13377
  39. package/dist/src/engine/perf.js +0 -115
  40. package/dist/src/engine/prune.js +0 -112
  41. package/dist/src/engine/scip-import.js +0 -408
  42. package/dist/src/engine/source-policy.js +0 -69
  43. package/dist/src/engine/sqlite.js +0 -71
  44. package/dist/src/engine/symbol-delete.js +0 -58
  45. package/dist/src/execution-profile.js +0 -203
  46. package/dist/src/failure-diagnosis.js +0 -649
  47. package/dist/src/fleet.js +0 -7
  48. package/dist/src/git-executable.js +0 -31
  49. package/dist/src/graph-query-health.js +0 -115
  50. package/dist/src/hook-manager-integration.js +0 -156
  51. package/dist/src/index-activity.js +0 -125
  52. package/dist/src/init-progress.js +0 -155
  53. package/dist/src/init.js +0 -1254
  54. package/dist/src/lifecycle-health.js +0 -250
  55. package/dist/src/lsp-readonly.js +0 -217
  56. package/dist/src/output-compression.js +0 -629
  57. package/dist/src/output-telemetry.js +0 -363
  58. package/dist/src/pr-triage.js +0 -638
  59. package/dist/src/progressive-evidence.js +0 -473
  60. package/dist/src/pure-compression-cli.js +0 -101
  61. package/dist/src/relationship-adapters.js +0 -370
  62. package/dist/src/release-attestation.js +0 -533
  63. package/dist/src/release-preflight.js +0 -510
  64. package/dist/src/repair-progress.js +0 -262
  65. package/dist/src/repository-init-process.js +0 -173
  66. package/dist/src/repository-management.js +0 -1231
  67. package/dist/src/response-budget.js +0 -194
  68. package/dist/src/server.js +0 -73
  69. package/dist/src/structural-fast-path.js +0 -338
  70. package/dist/src/structural-snapshot.js +0 -33
  71. package/dist/src/system-config.js +0 -615
  72. package/dist/src/terminal-help.js +0 -83
  73. package/dist/src/tools/knodin-tools.js +0 -1529
  74. package/dist/src/update-ceremony.js +0 -158
  75. package/dist/src/update-policy.js +0 -944
  76. package/dist/src/update-trust.js +0 -503
  77. package/dist/src/version.js +0 -13
  78. package/dist/src/visualization.js +0 -162
  79. package/dist/src/wait-for-fresh.js +0 -98
  80. package/dist/src/worktree-lifecycle.js +0 -231
  81. package/docs/CONTAINED-EXECUTION.md +0 -77
  82. package/docs/DIAGNOSTICS.md +0 -45
  83. package/docs/GIT-HISTORY-REVIEW.md +0 -39
  84. package/docs/PROGRESSIVE-EVIDENCE.md +0 -37
  85. package/docs/PT-ACCESS-RECOMMENDATION.md +0 -91
  86. package/docs/RELEASE-0.3-EVIDENCE.md +0 -73
  87. package/docs/SCIP-IMPORT.md +0 -57
  88. package/docs/SIGNED-UPDATES.md +0 -151
  89. package/docs/TELEMETRY.md +0 -46
  90. package/docs/assets/knodin-favicon.svg +0 -4
  91. package/docs/releases/0.3.0.md +0 -46
  92. package/docs/releases/0.4.0.md +0 -68
  93. package/docs/releases/0.4.1.md +0 -28
  94. package/docs/releases/0.4.2.md +0 -27
  95. package/docs/releases/0.4.3.md +0 -23
  96. package/docs/releases/0.5.0.md +0 -29
  97. package/docs/releases/0.5.1.md +0 -17
  98. package/docs/releases/0.6.0.md +0 -18
  99. package/docs/releases/0.7.0.md +0 -24
  100. package/docs/releases/0.7.1.md +0 -21
  101. package/docs/releases/0.7.2.md +0 -21
  102. package/docs/releases/0.7.3.md +0 -23
  103. package/docs/releases/0.7.4.md +0 -17
  104. package/schemas/release-attestation-v1.schema.json +0 -210
@@ -1,115 +0,0 @@
1
- /** Opt-in phase timing used by the R43 benchmark harness. */
2
- export const PERF_PHASES = [
3
- "db_open_migration",
4
- "file_collection",
5
- "parser_wasm_init",
6
- "read",
7
- "parse_extract",
8
- "sqlite_write",
9
- "model_load",
10
- "embedding_inference_write",
11
- "search_row_fetch_decode",
12
- "similarity",
13
- "fts",
14
- "map_community",
15
- "fusion",
16
- "freshness_probe",
17
- "graph_analytics",
18
- "traversal_snapshot",
19
- "architecture_facets",
20
- "architecture_edge_materialization",
21
- "context_composition",
22
- "status_audit",
23
- "pack_walk",
24
- "pack_serialize",
25
- "artifact_read",
26
- "artifact_grep",
27
- "review_diff_discovery",
28
- "review_churn",
29
- "review_parsing",
30
- "review_flow_lookup",
31
- "review_serialization",
32
- ];
33
- let active = null;
34
- function emptyPhases() {
35
- return Object.fromEntries(PERF_PHASES.map((phase) => [phase, 0]));
36
- }
37
- function peakRss() {
38
- try {
39
- const kilobytes = process.resourceUsage().maxRSS;
40
- return Number.isFinite(kilobytes) && kilobytes >= 0 ? kilobytes * 1024 : undefined;
41
- }
42
- catch {
43
- return undefined;
44
- }
45
- }
46
- /** Start a measurement explicitly. Starting while active resets that measurement. */
47
- export function beginPerfSession() {
48
- active = { startedAt: performance.now(), phases: emptyPhases(), peakRssBytes: peakRss() };
49
- }
50
- /** Finish the active measurement, or return null when instrumentation is disabled. */
51
- export function endPerfSession() {
52
- const session = active;
53
- if (!session)
54
- return null;
55
- active = null;
56
- const currentRss = peakRss();
57
- if (currentRss !== undefined) {
58
- session.peakRssBytes = Math.max(session.peakRssBytes ?? 0, currentRss);
59
- }
60
- return {
61
- phases: { ...session.phases },
62
- totalMs: Math.max(0, performance.now() - session.startedAt),
63
- ...(session.peakRssBytes === undefined ? {} : { peakRssBytes: session.peakRssBytes }),
64
- };
65
- }
66
- function record(phase, startedAt) {
67
- const session = active;
68
- if (!session)
69
- return;
70
- session.phases[phase] += Math.max(0, performance.now() - startedAt);
71
- const currentRss = peakRss();
72
- if (currentRss !== undefined)
73
- session.peakRssBytes = Math.max(session.peakRssBytes ?? 0, currentRss);
74
- }
75
- /** Start a phase that spans an existing try/finally block. The returned closer is idempotent. */
76
- export function beginPerfPhase(phase) {
77
- if (!active)
78
- return () => undefined;
79
- const startedAt = performance.now();
80
- let closed = false;
81
- return () => {
82
- if (closed)
83
- return;
84
- closed = true;
85
- record(phase, startedAt);
86
- };
87
- }
88
- /** Time an async phase. Nested phases are retained independently and errors still close timers. */
89
- export async function measurePerfPhase(phase, work) {
90
- if (!active)
91
- return work();
92
- const startedAt = performance.now();
93
- try {
94
- return await work();
95
- }
96
- finally {
97
- record(phase, startedAt);
98
- }
99
- }
100
- /** Synchronous counterpart for filesystem, parsing, and SQLite calls. */
101
- export function measurePerfPhaseSync(phase, work) {
102
- if (!active)
103
- return work();
104
- const startedAt = performance.now();
105
- try {
106
- return work();
107
- }
108
- finally {
109
- record(phase, startedAt);
110
- }
111
- }
112
- /** Test-only state reset; deliberately does not enable timing. */
113
- export function resetPerfForTests() {
114
- active = null;
115
- }
@@ -1,112 +0,0 @@
1
- import path from "node:path";
2
- /**
3
- * Single source of truth for "which directories does knodin ignore" (R14).
4
- *
5
- * Before this module, the same intent — skip node_modules, .git, .knodin,
6
- * build output, caches, VCS internals — was encoded THREE separate times:
7
- * 1. src/__tests__/setup.ts's `PRUNE_DIRS` regex (glob-shim exclude filter)
8
- * 2. the indexer's own directory filter (`EXCLUDED_DIR_SEGMENTS` / `isIndexablePath`)
9
- * 3. the chokidar watcher's `makeWatchIgnorePredicate` (added by R13)
10
- *
11
- * They drifted. R13's bug was exactly this shape: the watcher's copy silently
12
- * stopped matching anything for an entire chokidar major version while the
13
- * indexer's copy kept working, so indexing looked correct and only the
14
- * watcher melted (it registered ~12.6k files and starved the event loop under
15
- * Bun). This module is the ONE list; every consumer below is a thin adapter
16
- * over it, so a future drift is structurally impossible rather than merely
17
- * discouraged.
18
- */
19
- /**
20
- * Directory segments never worth indexing or watching: build output, deps,
21
- * VCS/tool state, caches. Matched by whole path SEGMENT, never substring, so
22
- * a real file/dir like `distributor.ts` or `build-graph.ts` is never wrongly
23
- * pruned.
24
- */
25
- export const PRUNE_DIR_NAMES = new Set([
26
- "node_modules",
27
- ".git",
28
- ".knodin",
29
- // Local state maintained by other code-intelligence tools. It is neither
30
- // repository source nor stable project configuration, and indexing it makes
31
- // a healthy checkout look perpetually stale as those tools update databases
32
- // and caches in the background.
33
- ".gitnexus",
34
- ".codegraph",
35
- ".code-review-graph",
36
- ".aider.tags.cache.v4",
37
- "dist",
38
- "build",
39
- "coverage",
40
- ".next",
41
- ".turbo",
42
- ".svelte-kit",
43
- "vendor",
44
- "__pycache__",
45
- ]);
46
- /** True when a single path segment (one directory name) is a pruned directory. */
47
- export function isPrunedSegment(segment) {
48
- return PRUNE_DIR_NAMES.has(segment);
49
- }
50
- /**
51
- * Agent worktrees are checkouts of this same repo living under
52
- * `.claude/worktrees/*`; indexing/watching them would double-count every
53
- * symbol. `segments` is the full split relative path; `i` is the segment
54
- * index currently being tested against the two-segment `.claude/worktrees`
55
- * pattern.
56
- */
57
- function isClaudeWorktreeSegment(segments, i) {
58
- return segments[i] === ".claude" && segments[i + 1] === "worktrees";
59
- }
60
- /**
61
- * True when ANY segment of an already-split repo-relative path trips the
62
- * ignore policy (a pruned directory name, or the `.claude/worktrees` root).
63
- * This is the one walk both `isIndexablePath` (indexer) and
64
- * `makeWatchIgnorePredicate` (watcher) run — the code that used to be
65
- * duplicated between them.
66
- */
67
- export function pathSegmentsAreIgnored(segments) {
68
- for (let i = 0; i < segments.length; i++) {
69
- if (isPrunedSegment(segments[i]))
70
- return true;
71
- if (isClaudeWorktreeSegment(segments, i))
72
- return true;
73
- }
74
- return false;
75
- }
76
- /**
77
- * Indexer adapter: true when a repo-relative path (either OS separator)
78
- * should be indexed.
79
- */
80
- export function isIndexablePath(relPath) {
81
- return !pathSegmentsAreIgnored(relPath.split(/[/\\]/));
82
- }
83
- /**
84
- * Watcher adapter: builds chokidar's `ignored` predicate for a given repo
85
- * root.
86
- *
87
- * chokidar v4 removed glob support; v5 accepts only a function, a regex, or a
88
- * literal path. Matching here is done on the path RELATIVE to the repo, so a
89
- * repo that happens to live under a directory named e.g. `build/` does not
90
- * prune its own root (the R13 fix this preserves).
91
- */
92
- export function makeWatchIgnorePredicate(repoPath) {
93
- return (targetPath) => {
94
- const relative = path.relative(repoPath, targetPath);
95
- // The watch root itself ("") and anything outside it are never pruned.
96
- if (relative === "" || relative.startsWith(".."))
97
- return false;
98
- return pathSegmentsAreIgnored(relative.split(path.sep));
99
- };
100
- }
101
- /**
102
- * Policy-test adapter: a regex equivalent to the segment-based policy above.
103
- * Anchored on `/` boundaries so it only matches whole segments — same guarantee
104
- * as `isPrunedSegment` — never a substring inside an unrelated name.
105
- */
106
- export function makePruneDirsRegex() {
107
- const escaped = [...PRUNE_DIR_NAMES].map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
108
- // `.claude/worktrees` is a two-segment pattern, expressed directly rather
109
- // than through the single-segment name list.
110
- escaped.push("\\.claude/worktrees");
111
- return new RegExp(`(?:^|/)(?:${escaped.join("|")})(?:/|$)`);
112
- }
@@ -1,408 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- export const SCIP_DEFAULT_LIMITS = {
4
- maxBytes: 64 * 1024 * 1024,
5
- maxFiles: 10_000,
6
- maxFacts: 250_000,
7
- timeoutMs: 30_000,
8
- };
9
- const textDecoder = new TextDecoder("utf-8", { fatal: true });
10
- let activeDeadline = Number.POSITIVE_INFINITY;
11
- let activeTimeoutMs = 0;
12
- function fail(message) {
13
- activeDeadline = Number.POSITIVE_INFINITY;
14
- activeTimeoutMs = 0;
15
- throw new Error(`knodin SCIP import: ${message}`);
16
- }
17
- function checkDeadline() {
18
- if (performance.now() > activeDeadline)
19
- fail(`import exceeded ${activeTimeoutMs} ms limit`);
20
- }
21
- function readVarint(bytes, offset) {
22
- let value = 0;
23
- let shift = 0;
24
- for (let count = 0; count < 10 && offset < bytes.length; count++) {
25
- const byte = bytes[offset++];
26
- if (shift < 53)
27
- value += (byte & 0x7f) * 2 ** shift;
28
- if ((byte & 0x80) === 0)
29
- return [value, offset];
30
- shift += 7;
31
- }
32
- return fail("malformed protobuf varint");
33
- }
34
- function visitFields(bytes, visitor) {
35
- let offset = 0;
36
- while (offset < bytes.length) {
37
- checkDeadline();
38
- let tag;
39
- [tag, offset] = readVarint(bytes, offset);
40
- const number = Math.floor(tag / 8);
41
- const wire = tag & 7;
42
- if (number < 1)
43
- fail("malformed protobuf field number");
44
- if (wire === 0) {
45
- let value;
46
- [value, offset] = readVarint(bytes, offset);
47
- visitor(number, wire, value);
48
- }
49
- else if (wire === 2) {
50
- let length;
51
- [length, offset] = readVarint(bytes, offset);
52
- if (!Number.isSafeInteger(length) || length < 0 || offset + length > bytes.length)
53
- fail("malformed protobuf length");
54
- visitor(number, wire, bytes.subarray(offset, offset + length));
55
- offset += length;
56
- }
57
- else if (wire === 1) {
58
- offset += 8;
59
- }
60
- else if (wire === 5) {
61
- offset += 4;
62
- }
63
- else {
64
- fail(`unsupported protobuf wire type ${wire}`);
65
- }
66
- if (offset > bytes.length)
67
- fail("truncated protobuf field");
68
- }
69
- }
70
- function countLengthDelimitedFields(bytes, target, maximum) {
71
- let count = 0;
72
- let offset = 0;
73
- while (offset < bytes.length) {
74
- checkDeadline();
75
- let tag;
76
- [tag, offset] = readVarint(bytes, offset);
77
- const number = Math.floor(tag / 8);
78
- const wire = tag & 7;
79
- if (number < 1)
80
- fail("malformed protobuf field number");
81
- if (wire === 0) {
82
- [, offset] = readVarint(bytes, offset);
83
- }
84
- else if (wire === 2) {
85
- let length;
86
- [length, offset] = readVarint(bytes, offset);
87
- if (!Number.isSafeInteger(length) || length < 0 || offset + length > bytes.length)
88
- fail("malformed protobuf length");
89
- if (number === target && ++count > maximum)
90
- return count;
91
- offset += length;
92
- }
93
- else if (wire === 1)
94
- offset += 8;
95
- else if (wire === 5)
96
- offset += 4;
97
- else
98
- fail(`unsupported protobuf wire type ${wire}`);
99
- if (offset > bytes.length)
100
- fail("truncated protobuf field");
101
- }
102
- return count;
103
- }
104
- function strings(message, number) {
105
- const result = [];
106
- visitFields(message, (fieldNumber, wire, value) => {
107
- if (fieldNumber === number && wire === 2)
108
- result.push(textDecoder.decode(value));
109
- });
110
- return result;
111
- }
112
- function messages(message, number) {
113
- const result = [];
114
- visitFields(message, (fieldNumber, wire, value) => {
115
- if (fieldNumber === number && wire === 2)
116
- result.push(value);
117
- });
118
- return result;
119
- }
120
- function numberField(message, number) {
121
- let result = 0;
122
- let found = false;
123
- visitFields(message, (fieldNumber, wire, value) => {
124
- if (!found && fieldNumber === number && wire === 0) {
125
- result = value;
126
- found = true;
127
- }
128
- });
129
- return result;
130
- }
131
- function packedNumbers(message, number) {
132
- const result = [];
133
- visitFields(message, (fieldNumber, wire, value) => {
134
- if (fieldNumber !== number)
135
- return;
136
- if (wire === 0)
137
- result.push(value);
138
- if (wire === 2) {
139
- const bytes = value;
140
- let offset = 0;
141
- while (offset < bytes.length) {
142
- let packedValue;
143
- [packedValue, offset] = readVarint(bytes, offset);
144
- result.push(packedValue);
145
- }
146
- }
147
- });
148
- return result;
149
- }
150
- function parseRange(values) {
151
- if (values.length !== 3 && values.length !== 4)
152
- fail("range must contain three or four integers");
153
- const [startLine, startCol] = values;
154
- const endLine = values.length === 3 ? startLine : values[2];
155
- const endCol = values.length === 3 ? values[2] : values[3];
156
- if ([startLine, startCol, endLine, endCol].some((value) => !Number.isSafeInteger(value)))
157
- fail("range contains an unsafe integer");
158
- if (endLine < startLine || (endLine === startLine && endCol < startCol))
159
- fail("range is reversed");
160
- return { startLine, startCol, endLine, endCol };
161
- }
162
- function parseOccurrence(message) {
163
- const singleLine = messages(message, 8)[0];
164
- const multiLine = messages(message, 9)[0];
165
- const symbol = strings(message, 2)[0] ?? "";
166
- let range;
167
- if (singleLine) {
168
- const line = numberField(singleLine, 1);
169
- range = parseRange([line, numberField(singleLine, 2), numberField(singleLine, 3)]);
170
- }
171
- else if (multiLine) {
172
- range = parseRange([
173
- numberField(multiLine, 1),
174
- numberField(multiLine, 2),
175
- numberField(multiLine, 3),
176
- numberField(multiLine, 4),
177
- ]);
178
- }
179
- else {
180
- range = parseRange(packedNumbers(message, 1));
181
- }
182
- if (!symbol)
183
- fail("occurrence is missing its symbol");
184
- return { range, symbol, roles: numberField(message, 3) };
185
- }
186
- function parseSymbolInfo(message) {
187
- const symbol = strings(message, 1)[0] ?? "";
188
- if (!symbol)
189
- fail("symbol information is missing its symbol");
190
- return {
191
- symbol,
192
- displayName: strings(message, 6)[0] ?? "",
193
- kind: numberField(message, 5),
194
- relationships: messages(message, 4).map((relationship) => ({
195
- symbol: strings(relationship, 1)[0] ?? "",
196
- isReference: numberField(relationship, 2) !== 0,
197
- isImplementation: numberField(relationship, 3) !== 0,
198
- isTypeDefinition: numberField(relationship, 4) !== 0,
199
- isDefinition: numberField(relationship, 5) !== 0,
200
- })),
201
- };
202
- }
203
- function safeRelativePath(repoPath, value) {
204
- if (!value || value.includes("\0") || path.isAbsolute(value))
205
- fail(`hostile document path ${JSON.stringify(value)}`);
206
- const normalized = value.replaceAll("\\", "/");
207
- if (normalized.split("/").some((part) => part === ".." || part === ""))
208
- fail(`hostile document path ${JSON.stringify(value)}`);
209
- const absolute = path.resolve(repoPath, normalized);
210
- const relative = path.relative(repoPath, absolute);
211
- if (relative.startsWith("..") || path.isAbsolute(relative))
212
- fail(`document escapes repository: ${value}`);
213
- let stat;
214
- try {
215
- stat = fs.lstatSync(absolute);
216
- }
217
- catch {
218
- return fail(`document is missing: ${normalized}`);
219
- }
220
- if (!stat.isFile() || stat.isSymbolicLink())
221
- fail(`document is not a regular repository file: ${normalized}`);
222
- return normalized;
223
- }
224
- function symbolName(symbol, displayName) {
225
- if (displayName.trim())
226
- return displayName.trim();
227
- if (symbol.startsWith("local "))
228
- return symbol;
229
- const descriptor = symbol.trim().split(/\s+/).at(-1) ?? symbol;
230
- const withoutSuffix = descriptor.replace(/\([^)]*\)\.$|[#/.:!]$/, "");
231
- const cleaned = (withoutSuffix.split(/[/#]/).at(-1) ?? descriptor).replaceAll("`", "");
232
- return cleaned || symbol;
233
- }
234
- function symbolKind(_kind) {
235
- // Do not translate a versioned producer enum into a potentially false native
236
- // semantic kind; the original SCIP symbol remains in provenance.
237
- return "SCIP_Symbol";
238
- }
239
- function parseMetadata(index) {
240
- const metadata = messages(index, 1)[0];
241
- if (!metadata)
242
- return [];
243
- const toolInfo = messages(metadata, 2)[0];
244
- if (!toolInfo)
245
- return [];
246
- const name = strings(toolInfo, 1)[0];
247
- const version = strings(toolInfo, 2)[0];
248
- return name ? [`${name}${version ? `@${version}` : ""}`] : [];
249
- }
250
- export function readScipIndex(repoPath, options) {
251
- const started = performance.now();
252
- const limits = { ...SCIP_DEFAULT_LIMITS, ...options };
253
- for (const [name, value] of Object.entries(limits)) {
254
- if (name === "path")
255
- continue;
256
- if (!Number.isSafeInteger(value) || value < 1)
257
- fail(`${name} must be a positive integer`);
258
- }
259
- activeTimeoutMs = limits.timeoutMs;
260
- activeDeadline = started + limits.timeoutMs;
261
- const resolvedRepo = path.resolve(repoPath);
262
- const inputPath = path.resolve(resolvedRepo, options.path);
263
- const inputRelative = path.relative(resolvedRepo, inputPath);
264
- if (inputRelative.startsWith("..") || path.isAbsolute(inputRelative))
265
- fail("input path escapes repository");
266
- let inputStat;
267
- try {
268
- inputStat = fs.lstatSync(inputPath);
269
- }
270
- catch {
271
- return fail(`input is missing: ${inputRelative}`);
272
- }
273
- if (!inputStat.isFile() || inputStat.isSymbolicLink())
274
- fail("input must be a regular repository file");
275
- if (inputStat.size > limits.maxBytes)
276
- fail(`input exceeds ${limits.maxBytes} byte limit`);
277
- const bytes = fs.readFileSync(inputPath);
278
- const index = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
279
- const documents = [];
280
- let parsedFacts = 0;
281
- const documentCount = countLengthDelimitedFields(index, 2, limits.maxFiles);
282
- if (documentCount > limits.maxFiles)
283
- fail(`index exceeds ${limits.maxFiles} file limit`);
284
- for (const message of messages(index, 2)) {
285
- const filePath = safeRelativePath(resolvedRepo, strings(message, 1)[0] ?? "");
286
- const sourceStat = fs.statSync(path.join(resolvedRepo, filePath));
287
- if (sourceStat.mtimeMs > inputStat.mtimeMs + 1)
288
- fail(`input is stale for ${filePath}`);
289
- const occurrenceCount = countLengthDelimitedFields(message, 2, limits.maxFacts - parsedFacts);
290
- parsedFacts += occurrenceCount;
291
- if (parsedFacts > limits.maxFacts)
292
- fail(`index exceeds ${limits.maxFacts} fact limit`);
293
- const symbolCount = countLengthDelimitedFields(message, 3, limits.maxFacts - parsedFacts);
294
- parsedFacts += symbolCount;
295
- if (parsedFacts > limits.maxFacts)
296
- fail(`index exceeds ${limits.maxFacts} fact limit`);
297
- const occurrenceMessages = messages(message, 2);
298
- const symbolMessages = messages(message, 3);
299
- for (const symbolMessage of symbolMessages) {
300
- const remainingRelationships = Math.floor((limits.maxFacts - parsedFacts) / 4);
301
- parsedFacts += countLengthDelimitedFields(symbolMessage, 4, remainingRelationships) * 4;
302
- if (parsedFacts > limits.maxFacts)
303
- fail(`index exceeds ${limits.maxFacts} fact limit`);
304
- }
305
- documents.push({
306
- filePath,
307
- language: strings(message, 4)[0] ?? "unknown",
308
- occurrences: occurrenceMessages.map(parseOccurrence),
309
- symbols: symbolMessages.map(parseSymbolInfo),
310
- });
311
- if (performance.now() - started > limits.timeoutMs)
312
- fail(`import exceeded ${limits.timeoutMs} ms limit`);
313
- }
314
- const scopedSymbol = (document, symbol) => symbol.startsWith("local ") ? `${document.filePath}\0${symbol}` : `\0${symbol}`;
315
- const definitions = new Map();
316
- for (const document of documents) {
317
- const infos = new Map(document.symbols.map((info) => [info.symbol, info]));
318
- for (const occurrence of document.occurrences) {
319
- const key = scopedSymbol(document, occurrence.symbol);
320
- if ((occurrence.roles & 1) !== 0 && !definitions.has(key)) {
321
- definitions.set(key, {
322
- document,
323
- occurrence,
324
- info: infos.get(occurrence.symbol),
325
- });
326
- }
327
- }
328
- }
329
- const symbols = [...definitions.values()].map((definition) => ({
330
- symbol: definition.occurrence.symbol,
331
- name: symbolName(definition.occurrence.symbol, definition.info?.displayName ?? ""),
332
- kind: symbolKind(definition.info?.kind ?? 0),
333
- filePath: definition.document.filePath,
334
- startLine: definition.occurrence.range.startLine + 1,
335
- endLine: definition.occurrence.range.endLine + 1,
336
- startCol: definition.occurrence.range.startCol + 1,
337
- endCol: definition.occurrence.range.endCol + 1,
338
- }));
339
- const references = [];
340
- for (const document of documents) {
341
- for (const occurrence of document.occurrences) {
342
- if ((occurrence.roles & 1) !== 0)
343
- continue;
344
- const target = definitions.get(scopedSymbol(document, occurrence.symbol));
345
- if (!target)
346
- continue;
347
- references.push({
348
- callerSymbol: null,
349
- callerFile: document.filePath,
350
- calleeSymbol: symbolName(occurrence.symbol, target.info?.displayName ?? ""),
351
- calleeFile: target.document.filePath,
352
- line: occurrence.range.startLine + 1,
353
- column: occurrence.range.startCol + 1,
354
- kind: "scip_reference",
355
- });
356
- }
357
- for (const info of document.symbols) {
358
- const source = definitions.get(scopedSymbol(document, info.symbol));
359
- if (!source)
360
- continue;
361
- for (const relationship of info.relationships) {
362
- const target = definitions.get(scopedSymbol(document, relationship.symbol));
363
- if (!target)
364
- continue;
365
- const kinds = [];
366
- if (relationship.isReference)
367
- kinds.push("scip_relationship_reference");
368
- if (relationship.isImplementation)
369
- kinds.push("scip_implementation");
370
- if (relationship.isTypeDefinition)
371
- kinds.push("scip_type_definition");
372
- if (relationship.isDefinition)
373
- kinds.push("scip_definition");
374
- for (const kind of kinds)
375
- references.push({
376
- callerSymbol: symbolName(info.symbol, info.displayName),
377
- callerFile: source.document.filePath,
378
- calleeSymbol: symbolName(relationship.symbol, target.info?.displayName ?? ""),
379
- calleeFile: target.document.filePath,
380
- line: source.occurrence.range.startLine + 1,
381
- column: source.occurrence.range.startCol + 1,
382
- kind,
383
- });
384
- }
385
- }
386
- }
387
- const factCount = symbols.length + references.length;
388
- if (factCount > limits.maxFacts)
389
- fail(`index exceeds ${limits.maxFacts} fact limit`);
390
- if (performance.now() - started > limits.timeoutMs)
391
- fail(`import exceeded ${limits.timeoutMs} ms limit`);
392
- const result = {
393
- inputPath: inputRelative.replaceAll("\\", "/"),
394
- bytes: inputStat.size,
395
- files: documents.length,
396
- facts: factCount,
397
- languages: [...new Set(documents.map((document) => document.language))].sort(),
398
- indexers: parseMetadata(index).sort(),
399
- symbols: symbols.sort((a, b) => a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine),
400
- references: references.sort((a, b) => a.callerFile.localeCompare(b.callerFile) ||
401
- a.line - b.line ||
402
- a.calleeSymbol.localeCompare(b.calleeSymbol)),
403
- elapsedMs: Math.round((performance.now() - started) * 1000) / 1000,
404
- };
405
- activeDeadline = Number.POSITIVE_INFINITY;
406
- activeTimeoutMs = 0;
407
- return result;
408
- }