gitnexus 1.6.3-rc.6 → 1.6.3-rc.7
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/dist/_shared/index.d.ts +7 -1
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +4 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/position-index.d.ts +50 -0
- package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/position-index.js +134 -0
- package/dist/_shared/scope-resolution/position-index.js.map +1 -0
- package/dist/_shared/scope-resolution/scope-id.d.ts +43 -0
- package/dist/_shared/scope-resolution/scope-id.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/scope-id.js +46 -0
- package/dist/_shared/scope-resolution/scope-id.js.map +1 -0
- package/dist/_shared/scope-resolution/scope-tree.d.ts +61 -0
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/scope-tree.js +185 -0
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -0
- package/dist/_shared/scope-resolution/types.d.ts +0 -6
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/core/search/phase-timer.d.ts +72 -0
- package/dist/core/search/phase-timer.js +106 -0
- package/dist/mcp/local/local-backend.js +48 -3
- package/package.json +1 -1
package/dist/_shared/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { getLanguageFromFilename, getSyntaxLanguageFromFilename } from './langua
|
|
|
6
6
|
export type { MroStrategy } from './mro-strategy.js';
|
|
7
7
|
export type { PipelinePhase, PipelineProgress } from './pipeline.js';
|
|
8
8
|
export type { SymbolDefinition } from './scope-resolution/symbol-definition.js';
|
|
9
|
-
export type { ScopeId, DefId, ScopeKind, Range, Capture, CaptureMatch, BindingRef, ImportEdge, TypeRef, Scope, ResolutionEvidence, Resolution, Reference, ReferenceIndex, LookupParams, RegistryContributor, ParsedImport, ParsedTypeBinding, WorkspaceIndex,
|
|
9
|
+
export type { ScopeId, DefId, ScopeKind, Range, Capture, CaptureMatch, BindingRef, ImportEdge, TypeRef, Scope, ResolutionEvidence, Resolution, Reference, ReferenceIndex, LookupParams, RegistryContributor, ParsedImport, ParsedTypeBinding, WorkspaceIndex, Callsite, } from './scope-resolution/types.js';
|
|
10
10
|
export { EvidenceWeights, typeBindingWeightAtDepth } from './scope-resolution/evidence-weights.js';
|
|
11
11
|
export { ORIGIN_PRIORITY } from './scope-resolution/origin-priority.js';
|
|
12
12
|
export type { OriginForTieBreak } from './scope-resolution/origin-priority.js';
|
|
@@ -22,6 +22,12 @@ export { resolveTypeRef } from './scope-resolution/resolve-type-ref.js';
|
|
|
22
22
|
export type { ResolveTypeRefContext, ScopeLookup } from './scope-resolution/resolve-type-ref.js';
|
|
23
23
|
export { buildMethodDispatchIndex } from './scope-resolution/method-dispatch-index.js';
|
|
24
24
|
export type { MethodDispatchIndex, MethodDispatchInput, } from './scope-resolution/method-dispatch-index.js';
|
|
25
|
+
export { makeScopeId, clearScopeIdInternPool } from './scope-resolution/scope-id.js';
|
|
26
|
+
export type { ScopeIdInput } from './scope-resolution/scope-id.js';
|
|
27
|
+
export { buildScopeTree, ScopeTreeInvariantError } from './scope-resolution/scope-tree.js';
|
|
28
|
+
export type { ScopeTree } from './scope-resolution/scope-tree.js';
|
|
29
|
+
export { buildPositionIndex } from './scope-resolution/position-index.js';
|
|
30
|
+
export type { PositionIndex } from './scope-resolution/position-index.js';
|
|
25
31
|
export { diffResolutions } from './scope-resolution/shadow/diff.js';
|
|
26
32
|
export type { ShadowAgreement, ShadowCallsite, ShadowDiff, } from './scope-resolution/shadow/diff.js';
|
|
27
33
|
export { aggregateDiffs } from './scope-resolution/shadow/aggregate.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,SAAS,EACT,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,EACT,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAGzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AACjG,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAIrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAChF,YAAY,EACV,OAAO,EACP,KAAK,EACL,SAAS,EACT,KAAK,EACL,OAAO,EACP,YAAY,EACZ,UAAU,EACV,UAAU,EACV,OAAO,EACP,KAAK,EACL,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,SAAS,EACT,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,EACT,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAGzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AACjG,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAIrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAChF,YAAY,EACV,OAAO,EACP,KAAK,EACL,SAAS,EACT,KAAK,EACL,OAAO,EACP,YAAY,EACZ,UAAU,EACV,UAAU,EACV,OAAO,EACP,KAAK,EACL,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,QAAQ,GACT,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AACnG,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAG/E,OAAO,EACL,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,+CAA+C,CAAC;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AAG5F,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,YAAY,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACnG,OAAO,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AACrF,YAAY,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAGrF,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,YAAY,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAC;AAGjG,OAAO,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAC;AACvF,YAAY,EACV,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,6CAA6C,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACrF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAC3F,YAAY,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAG1E,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,YAAY,EACV,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC"}
|
package/dist/_shared/index.js
CHANGED
|
@@ -16,6 +16,10 @@ export { buildQualifiedNameIndex } from './scope-resolution/qualified-name-index
|
|
|
16
16
|
export { resolveTypeRef } from './scope-resolution/resolve-type-ref.js';
|
|
17
17
|
// Method-dispatch materialized view over HeritageMap (RFC §3.1; Ring 2 SHARED #914)
|
|
18
18
|
export { buildMethodDispatchIndex } from './scope-resolution/method-dispatch-index.js';
|
|
19
|
+
// Scope tree spine + position lookup (RFC §2.2 + §3.1; Ring 2 SHARED #912)
|
|
20
|
+
export { makeScopeId, clearScopeIdInternPool } from './scope-resolution/scope-id.js';
|
|
21
|
+
export { buildScopeTree, ScopeTreeInvariantError } from './scope-resolution/scope-tree.js';
|
|
22
|
+
export { buildPositionIndex } from './scope-resolution/position-index.js';
|
|
19
23
|
// Shadow-mode diff + aggregation (RFC §6.3; Ring 2 SHARED #918)
|
|
20
24
|
export { diffResolutions } from './scope-resolution/shadow/diff.js';
|
|
21
25
|
export { aggregateDiffs } from './scope-resolution/shadow/aggregate.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,mBAAmB;AACnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,EACT,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAGpC,mBAAmB;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,mBAAmB;AACnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,EACT,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAGpC,mBAAmB;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAgCjG,8DAA8D;AAC9D,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AACnG,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAGxE,yDAAyD;AACzD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,+CAA+C,CAAC;AAGvD,sEAAsE;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAEjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAGrF,gEAAgE;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAGxE,oFAAoF;AACpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAC;AAMvF,2EAA2E;AAC3E,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAE3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAG1E,gEAAgE;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAMpE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PositionIndex` — O(log N_file) scope-at-position lookup
|
|
3
|
+
* (RFC §3.1; Ring 2 SHARED #912).
|
|
4
|
+
*
|
|
5
|
+
* Per-file sorted array of `(range, scopeId)` entries, sorted by start
|
|
6
|
+
* position ASC (`startLine`, then `startCol`). `atPosition(filePath, line,
|
|
7
|
+
* col)` binary-searches for the last entry whose start ≤ (line, col), then
|
|
8
|
+
* scans backward through the sorted prefix and returns the first entry
|
|
9
|
+
* whose range contains the query position.
|
|
10
|
+
*
|
|
11
|
+
* **Why this works.** `ScopeTree`'s invariants (parent strictly contains
|
|
12
|
+
* child; siblings don't overlap) guarantee that the scopes containing a
|
|
13
|
+
* given point form an **ancestor chain**. When scanning backward through
|
|
14
|
+
* entries sorted by start position ASC, the first scope we find that
|
|
15
|
+
* contains the query is the innermost one — any deeper-starting scope
|
|
16
|
+
* that also contained the query would appear *later* in the sorted array,
|
|
17
|
+
* but we're only scanning entries with start ≤ query, so anything later
|
|
18
|
+
* necessarily starts after the query and can't contain it.
|
|
19
|
+
*
|
|
20
|
+
* Expected complexity: `O(log N_file + D)` where `D` is the lexical depth
|
|
21
|
+
* at the query position (typically ≤ 10). Worst-case degrades to `O(N_file)`
|
|
22
|
+
* only under pathological inputs (many scopes starting at the same line).
|
|
23
|
+
*
|
|
24
|
+
* **Line/column conventions.** Matches `Range` in `types.ts`: lines are
|
|
25
|
+
* 1-based, columns are 0-based. Ranges are **inclusive on both ends** —
|
|
26
|
+
* a scope whose `endLine:endCol` equals the query position still contains
|
|
27
|
+
* it. That matches how tree-sitter captures bodies (closing brace
|
|
28
|
+
* included) and how closed PR #902's `enclosingFunctions` behaved.
|
|
29
|
+
*/
|
|
30
|
+
import type { Scope, ScopeId } from './types.js';
|
|
31
|
+
export interface PositionIndex {
|
|
32
|
+
/** Total scope entries indexed across all files. */
|
|
33
|
+
readonly size: number;
|
|
34
|
+
/**
|
|
35
|
+
* Innermost scope containing `(line, col)` in `filePath`, or `undefined`
|
|
36
|
+
* when nothing contains it (position before file start, after file end,
|
|
37
|
+
* or filePath not indexed).
|
|
38
|
+
*/
|
|
39
|
+
atPosition(filePath: string, line: number, col: number): ScopeId | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build a `PositionIndex` from a flat list of `Scope` records.
|
|
43
|
+
*
|
|
44
|
+
* Duplicate `id`s are tolerated and deduplicated — the caller's
|
|
45
|
+
* `ScopeTree.buildScopeTree` is the authoritative validator of scope
|
|
46
|
+
* identity, and the position index does not need to re-check that
|
|
47
|
+
* invariant.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildPositionIndex(scopes: readonly Scope[]): PositionIndex;
|
|
50
|
+
//# sourceMappingURL=position-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"position-index.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/position-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAS,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CAC9E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,aAAa,CAqB1E"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PositionIndex` — O(log N_file) scope-at-position lookup
|
|
3
|
+
* (RFC §3.1; Ring 2 SHARED #912).
|
|
4
|
+
*
|
|
5
|
+
* Per-file sorted array of `(range, scopeId)` entries, sorted by start
|
|
6
|
+
* position ASC (`startLine`, then `startCol`). `atPosition(filePath, line,
|
|
7
|
+
* col)` binary-searches for the last entry whose start ≤ (line, col), then
|
|
8
|
+
* scans backward through the sorted prefix and returns the first entry
|
|
9
|
+
* whose range contains the query position.
|
|
10
|
+
*
|
|
11
|
+
* **Why this works.** `ScopeTree`'s invariants (parent strictly contains
|
|
12
|
+
* child; siblings don't overlap) guarantee that the scopes containing a
|
|
13
|
+
* given point form an **ancestor chain**. When scanning backward through
|
|
14
|
+
* entries sorted by start position ASC, the first scope we find that
|
|
15
|
+
* contains the query is the innermost one — any deeper-starting scope
|
|
16
|
+
* that also contained the query would appear *later* in the sorted array,
|
|
17
|
+
* but we're only scanning entries with start ≤ query, so anything later
|
|
18
|
+
* necessarily starts after the query and can't contain it.
|
|
19
|
+
*
|
|
20
|
+
* Expected complexity: `O(log N_file + D)` where `D` is the lexical depth
|
|
21
|
+
* at the query position (typically ≤ 10). Worst-case degrades to `O(N_file)`
|
|
22
|
+
* only under pathological inputs (many scopes starting at the same line).
|
|
23
|
+
*
|
|
24
|
+
* **Line/column conventions.** Matches `Range` in `types.ts`: lines are
|
|
25
|
+
* 1-based, columns are 0-based. Ranges are **inclusive on both ends** —
|
|
26
|
+
* a scope whose `endLine:endCol` equals the query position still contains
|
|
27
|
+
* it. That matches how tree-sitter captures bodies (closing brace
|
|
28
|
+
* included) and how closed PR #902's `enclosingFunctions` behaved.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Build a `PositionIndex` from a flat list of `Scope` records.
|
|
32
|
+
*
|
|
33
|
+
* Duplicate `id`s are tolerated and deduplicated — the caller's
|
|
34
|
+
* `ScopeTree.buildScopeTree` is the authoritative validator of scope
|
|
35
|
+
* identity, and the position index does not need to re-check that
|
|
36
|
+
* invariant.
|
|
37
|
+
*/
|
|
38
|
+
export function buildPositionIndex(scopes) {
|
|
39
|
+
const entriesByFile = new Map();
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
for (const scope of scopes) {
|
|
42
|
+
if (seen.has(scope.id))
|
|
43
|
+
continue;
|
|
44
|
+
seen.add(scope.id);
|
|
45
|
+
let bucket = entriesByFile.get(scope.filePath);
|
|
46
|
+
if (bucket === undefined) {
|
|
47
|
+
bucket = [];
|
|
48
|
+
entriesByFile.set(scope.filePath, bucket);
|
|
49
|
+
}
|
|
50
|
+
bucket.push({ id: scope.id, range: scope.range });
|
|
51
|
+
}
|
|
52
|
+
for (const bucket of entriesByFile.values()) {
|
|
53
|
+
bucket.sort(compareEntry);
|
|
54
|
+
}
|
|
55
|
+
return freezeIndex(entriesByFile, seen.size);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sort by start position ASC, breaking ties by end position DESC so that
|
|
59
|
+
* larger (outer) scopes appear before their smaller (inner) co-starting
|
|
60
|
+
* siblings in the array. Makes the backward-scan contract crisp: the
|
|
61
|
+
* first containing hit from the end of the scanned prefix is the
|
|
62
|
+
* innermost scope.
|
|
63
|
+
*/
|
|
64
|
+
function compareEntry(a, b) {
|
|
65
|
+
if (a.range.startLine !== b.range.startLine)
|
|
66
|
+
return a.range.startLine - b.range.startLine;
|
|
67
|
+
if (a.range.startCol !== b.range.startCol)
|
|
68
|
+
return a.range.startCol - b.range.startCol;
|
|
69
|
+
if (a.range.endLine !== b.range.endLine)
|
|
70
|
+
return b.range.endLine - a.range.endLine;
|
|
71
|
+
return b.range.endCol - a.range.endCol;
|
|
72
|
+
}
|
|
73
|
+
/** Whether `(line, col)` is at or after `range`'s start. */
|
|
74
|
+
function startIsAtOrBefore(range, line, col) {
|
|
75
|
+
if (range.startLine < line)
|
|
76
|
+
return true;
|
|
77
|
+
if (range.startLine > line)
|
|
78
|
+
return false;
|
|
79
|
+
return range.startCol <= col;
|
|
80
|
+
}
|
|
81
|
+
/** Whether `(line, col)` is at or before `range`'s end (inclusive). */
|
|
82
|
+
function endIsAtOrAfter(range, line, col) {
|
|
83
|
+
if (range.endLine > line)
|
|
84
|
+
return true;
|
|
85
|
+
if (range.endLine < line)
|
|
86
|
+
return false;
|
|
87
|
+
return range.endCol >= col;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Return the largest index `i` in `arr` where `arr[i].range` starts at or
|
|
91
|
+
* before `(line, col)`. Returns `-1` if no entry starts ≤ the query.
|
|
92
|
+
*
|
|
93
|
+
* Classic "upper bound - 1" binary search: find the first entry that
|
|
94
|
+
* starts *after* the query, then step back one.
|
|
95
|
+
*/
|
|
96
|
+
function findLastStartLteIndex(arr, line, col) {
|
|
97
|
+
let lo = 0;
|
|
98
|
+
let hi = arr.length;
|
|
99
|
+
while (lo < hi) {
|
|
100
|
+
const mid = (lo + hi) >>> 1;
|
|
101
|
+
if (startIsAtOrBefore(arr[mid].range, line, col)) {
|
|
102
|
+
lo = mid + 1;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
hi = mid;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return lo - 1;
|
|
109
|
+
}
|
|
110
|
+
function freezeIndex(entriesByFile, size) {
|
|
111
|
+
return {
|
|
112
|
+
get size() {
|
|
113
|
+
return size;
|
|
114
|
+
},
|
|
115
|
+
atPosition(filePath, line, col) {
|
|
116
|
+
const bucket = entriesByFile.get(filePath);
|
|
117
|
+
if (bucket === undefined || bucket.length === 0)
|
|
118
|
+
return undefined;
|
|
119
|
+
const endIdx = findLastStartLteIndex(bucket, line, col);
|
|
120
|
+
if (endIdx < 0)
|
|
121
|
+
return undefined;
|
|
122
|
+
// Scan backward; first containing hit is innermost (see file header).
|
|
123
|
+
for (let i = endIdx; i >= 0; i--) {
|
|
124
|
+
const entry = bucket[i];
|
|
125
|
+
if (endIsAtOrAfter(entry.range, line, col)) {
|
|
126
|
+
// `startIsAtOrBefore` is guaranteed true by the binary search.
|
|
127
|
+
return entry.id;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=position-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"position-index.js","sourceRoot":"","sources":["../../src/scope-resolution/position-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ScopeId` canonical constructor + string intern pool
|
|
3
|
+
* (RFC §2.2; Ring 2 SHARED #912).
|
|
4
|
+
*
|
|
5
|
+
* `ScopeId` is a deterministic string derived from the scope's file path,
|
|
6
|
+
* byte range, and kind:
|
|
7
|
+
*
|
|
8
|
+
* scope:{filePath}#{startLine}:{startCol}-{endLine}:{endCol}:{kind}
|
|
9
|
+
*
|
|
10
|
+
* Two scopes produced by reparsing the same file at the same positions are
|
|
11
|
+
* `===`-equal as strings. Beyond the canonical shape, `makeScopeId` also
|
|
12
|
+
* **interns** the string through a process-local pool, so repeated calls
|
|
13
|
+
* with structurally identical inputs return the same string reference —
|
|
14
|
+
* making `Map<ScopeId, ...>` lookups and cache keys identity-fast.
|
|
15
|
+
*
|
|
16
|
+
* The intern pool is unbounded. The number of distinct `ScopeId`s across a
|
|
17
|
+
* single indexing run is O(total scopes in workspace), which is bounded by
|
|
18
|
+
* source-text size and already in memory; interning adds no asymptotic
|
|
19
|
+
* pressure. `clearScopeIdInternPool` is exported for test isolation.
|
|
20
|
+
*/
|
|
21
|
+
import type { Range } from './types.js';
|
|
22
|
+
import type { ScopeId, ScopeKind } from './types.js';
|
|
23
|
+
/** Inputs required to construct a canonical `ScopeId`. */
|
|
24
|
+
export interface ScopeIdInput {
|
|
25
|
+
readonly filePath: string;
|
|
26
|
+
readonly range: Range;
|
|
27
|
+
readonly kind: ScopeKind;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build a canonical `ScopeId` from its structural parts and intern it.
|
|
31
|
+
*
|
|
32
|
+
* Pure + referentially transparent: given the same input shape, always
|
|
33
|
+
* returns the same string reference for the lifetime of the pool.
|
|
34
|
+
*/
|
|
35
|
+
export declare function makeScopeId(input: ScopeIdInput): ScopeId;
|
|
36
|
+
/**
|
|
37
|
+
* Drop the intern pool. Intended for test setup/teardown — production code
|
|
38
|
+
* should not need this, since the pool's memory usage is bounded by the
|
|
39
|
+
* number of live scopes and cleaning it mid-run would break identity
|
|
40
|
+
* equality for existing scope ids.
|
|
41
|
+
*/
|
|
42
|
+
export declare function clearScopeIdInternPool(): void;
|
|
43
|
+
//# sourceMappingURL=scope-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-id.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/scope-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAErD,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAMxD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ScopeId` canonical constructor + string intern pool
|
|
3
|
+
* (RFC §2.2; Ring 2 SHARED #912).
|
|
4
|
+
*
|
|
5
|
+
* `ScopeId` is a deterministic string derived from the scope's file path,
|
|
6
|
+
* byte range, and kind:
|
|
7
|
+
*
|
|
8
|
+
* scope:{filePath}#{startLine}:{startCol}-{endLine}:{endCol}:{kind}
|
|
9
|
+
*
|
|
10
|
+
* Two scopes produced by reparsing the same file at the same positions are
|
|
11
|
+
* `===`-equal as strings. Beyond the canonical shape, `makeScopeId` also
|
|
12
|
+
* **interns** the string through a process-local pool, so repeated calls
|
|
13
|
+
* with structurally identical inputs return the same string reference —
|
|
14
|
+
* making `Map<ScopeId, ...>` lookups and cache keys identity-fast.
|
|
15
|
+
*
|
|
16
|
+
* The intern pool is unbounded. The number of distinct `ScopeId`s across a
|
|
17
|
+
* single indexing run is O(total scopes in workspace), which is bounded by
|
|
18
|
+
* source-text size and already in memory; interning adds no asymptotic
|
|
19
|
+
* pressure. `clearScopeIdInternPool` is exported for test isolation.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Build a canonical `ScopeId` from its structural parts and intern it.
|
|
23
|
+
*
|
|
24
|
+
* Pure + referentially transparent: given the same input shape, always
|
|
25
|
+
* returns the same string reference for the lifetime of the pool.
|
|
26
|
+
*/
|
|
27
|
+
export function makeScopeId(input) {
|
|
28
|
+
const raw = `scope:${input.filePath}#${input.range.startLine}:${input.range.startCol}-${input.range.endLine}:${input.range.endCol}:${input.kind}`;
|
|
29
|
+
const existing = INTERN_POOL.get(raw);
|
|
30
|
+
if (existing !== undefined)
|
|
31
|
+
return existing;
|
|
32
|
+
INTERN_POOL.set(raw, raw);
|
|
33
|
+
return raw;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Drop the intern pool. Intended for test setup/teardown — production code
|
|
37
|
+
* should not need this, since the pool's memory usage is bounded by the
|
|
38
|
+
* number of live scopes and cleaning it mid-run would break identity
|
|
39
|
+
* equality for existing scope ids.
|
|
40
|
+
*/
|
|
41
|
+
export function clearScopeIdInternPool() {
|
|
42
|
+
INTERN_POOL.clear();
|
|
43
|
+
}
|
|
44
|
+
/** Internal: shared intern pool (process-local). */
|
|
45
|
+
const INTERN_POOL = new Map();
|
|
46
|
+
//# sourceMappingURL=scope-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-id.js","sourceRoot":"","sources":["../../src/scope-resolution/scope-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAYH;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAmB;IAC7C,MAAM,GAAG,GAAG,SAAS,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;IAClJ,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5C,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC1B,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB;IACpC,WAAW,CAAC,KAAK,EAAE,CAAC;AACtB,CAAC;AAED,oDAAoD;AACpD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ScopeTree` — the lexical-scope spine of the `SemanticModel`
|
|
3
|
+
* (RFC §2.2 + §3.1; Ring 2 SHARED #912).
|
|
4
|
+
*
|
|
5
|
+
* Generalizes the `enclosingFunctions` pattern from closed PR #902 to
|
|
6
|
+
* arbitrary `ScopeKind`s. Owns the (parent ↔ children) relationship
|
|
7
|
+
* derived from each `Scope.parent` pointer, and validates the structural
|
|
8
|
+
* invariants a well-formed scope tree must satisfy.
|
|
9
|
+
*
|
|
10
|
+
* Invariants enforced at build time (throw on violation):
|
|
11
|
+
*
|
|
12
|
+
* - Every non-`Module` scope has a non-null parent.
|
|
13
|
+
* - Every parent pointer references a scope that was also supplied to
|
|
14
|
+
* `buildScopeTree`.
|
|
15
|
+
* - Parent range **strictly contains** child range.
|
|
16
|
+
* - Sibling ranges under the same parent do not overlap.
|
|
17
|
+
* - Parent and child live in the same `filePath`. (Cross-file parent
|
|
18
|
+
* pointers would be a category error — a `File` scope is not the
|
|
19
|
+
* parent of another file's scopes; imports do that job.)
|
|
20
|
+
*
|
|
21
|
+
* Satisfies the `ScopeLookup` contract from #916 (`resolve-type-ref`), so
|
|
22
|
+
* `resolveTypeRef` can take a `ScopeTree` directly without adapters.
|
|
23
|
+
*
|
|
24
|
+
* Immutable surface: `byId` is a `ReadonlyMap`; children arrays are
|
|
25
|
+
* `Object.freeze`d; miss lookups return a shared frozen empty array.
|
|
26
|
+
*/
|
|
27
|
+
import type { Scope, ScopeId } from './types.js';
|
|
28
|
+
import type { ScopeLookup } from './resolve-type-ref.js';
|
|
29
|
+
export interface ScopeTree extends ScopeLookup {
|
|
30
|
+
readonly size: number;
|
|
31
|
+
readonly byId: ReadonlyMap<ScopeId, Scope>;
|
|
32
|
+
getScope(id: ScopeId): Scope | undefined;
|
|
33
|
+
getParent(id: ScopeId): Scope | undefined;
|
|
34
|
+
/** Child `ScopeId`s of `id`, in input order. Frozen empty array on miss. */
|
|
35
|
+
getChildren(id: ScopeId): readonly ScopeId[];
|
|
36
|
+
/**
|
|
37
|
+
* Ancestor chain from the immediate parent up to (and including) the
|
|
38
|
+
* root module scope. Excludes the starting scope itself. Frozen empty
|
|
39
|
+
* array on miss / for a root scope.
|
|
40
|
+
*/
|
|
41
|
+
getAncestors(id: ScopeId): readonly ScopeId[];
|
|
42
|
+
has(id: ScopeId): boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Thrown by `buildScopeTree` when the input violates a structural
|
|
46
|
+
* invariant. Carries the offending ids + the invariant name so failed
|
|
47
|
+
* extraction pipelines can report actionable diagnostics.
|
|
48
|
+
*/
|
|
49
|
+
export declare class ScopeTreeInvariantError extends Error {
|
|
50
|
+
readonly invariant: 'non-module-requires-parent' | 'parent-not-found' | 'parent-must-contain-child' | 'sibling-ranges-overlap' | 'parent-must-share-filepath' | 'duplicate-scope-id';
|
|
51
|
+
constructor(invariant: 'non-module-requires-parent' | 'parent-not-found' | 'parent-must-contain-child' | 'sibling-ranges-overlap' | 'parent-must-share-filepath' | 'duplicate-scope-id', message: string);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build an immutable `ScopeTree` from a flat list of `Scope` records.
|
|
55
|
+
*
|
|
56
|
+
* Throws `ScopeTreeInvariantError` on the first invariant violation; a
|
|
57
|
+
* malformed tree is a bug in the extraction pipeline, not a data case for
|
|
58
|
+
* consumers to handle, so fail-fast is the correct posture.
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildScopeTree(scopes: readonly Scope[]): ScopeTree;
|
|
61
|
+
//# sourceMappingURL=scope-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-tree.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/scope-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAS,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD,MAAM,WAAW,SAAU,SAAQ,WAAW;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAE3C,QAAQ,CAAC,EAAE,EAAE,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC;IACzC,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC;IAC1C,4EAA4E;IAC5E,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,SAAS,OAAO,EAAE,CAAC;IAC7C;;;;OAIG;IACH,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,SAAS,OAAO,EAAE,CAAC;IAC9C,GAAG,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC;CAC3B;AAID;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,SAAS,EACd,4BAA4B,GAC5B,kBAAkB,GAClB,2BAA2B,GAC3B,wBAAwB,GACxB,4BAA4B,GAC5B,oBAAoB;gBANf,SAAS,EACd,4BAA4B,GAC5B,kBAAkB,GAClB,2BAA2B,GAC3B,wBAAwB,GACxB,4BAA4B,GAC5B,oBAAoB,EACxB,OAAO,EAAE,MAAM;CAKlB;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAiFlE"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ScopeTree` — the lexical-scope spine of the `SemanticModel`
|
|
3
|
+
* (RFC §2.2 + §3.1; Ring 2 SHARED #912).
|
|
4
|
+
*
|
|
5
|
+
* Generalizes the `enclosingFunctions` pattern from closed PR #902 to
|
|
6
|
+
* arbitrary `ScopeKind`s. Owns the (parent ↔ children) relationship
|
|
7
|
+
* derived from each `Scope.parent` pointer, and validates the structural
|
|
8
|
+
* invariants a well-formed scope tree must satisfy.
|
|
9
|
+
*
|
|
10
|
+
* Invariants enforced at build time (throw on violation):
|
|
11
|
+
*
|
|
12
|
+
* - Every non-`Module` scope has a non-null parent.
|
|
13
|
+
* - Every parent pointer references a scope that was also supplied to
|
|
14
|
+
* `buildScopeTree`.
|
|
15
|
+
* - Parent range **strictly contains** child range.
|
|
16
|
+
* - Sibling ranges under the same parent do not overlap.
|
|
17
|
+
* - Parent and child live in the same `filePath`. (Cross-file parent
|
|
18
|
+
* pointers would be a category error — a `File` scope is not the
|
|
19
|
+
* parent of another file's scopes; imports do that job.)
|
|
20
|
+
*
|
|
21
|
+
* Satisfies the `ScopeLookup` contract from #916 (`resolve-type-ref`), so
|
|
22
|
+
* `resolveTypeRef` can take a `ScopeTree` directly without adapters.
|
|
23
|
+
*
|
|
24
|
+
* Immutable surface: `byId` is a `ReadonlyMap`; children arrays are
|
|
25
|
+
* `Object.freeze`d; miss lookups return a shared frozen empty array.
|
|
26
|
+
*/
|
|
27
|
+
// ─── Build errors ───────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Thrown by `buildScopeTree` when the input violates a structural
|
|
30
|
+
* invariant. Carries the offending ids + the invariant name so failed
|
|
31
|
+
* extraction pipelines can report actionable diagnostics.
|
|
32
|
+
*/
|
|
33
|
+
export class ScopeTreeInvariantError extends Error {
|
|
34
|
+
invariant;
|
|
35
|
+
constructor(invariant, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.invariant = invariant;
|
|
38
|
+
this.name = 'ScopeTreeInvariantError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// ─── Builder ───────────────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Build an immutable `ScopeTree` from a flat list of `Scope` records.
|
|
44
|
+
*
|
|
45
|
+
* Throws `ScopeTreeInvariantError` on the first invariant violation; a
|
|
46
|
+
* malformed tree is a bug in the extraction pipeline, not a data case for
|
|
47
|
+
* consumers to handle, so fail-fast is the correct posture.
|
|
48
|
+
*/
|
|
49
|
+
export function buildScopeTree(scopes) {
|
|
50
|
+
const byId = new Map();
|
|
51
|
+
const childrenById = new Map();
|
|
52
|
+
// ── Pass 1: collect by id + duplicate check ───────────────────────────
|
|
53
|
+
for (const scope of scopes) {
|
|
54
|
+
if (byId.has(scope.id)) {
|
|
55
|
+
throw new ScopeTreeInvariantError('duplicate-scope-id', `Two scopes share id '${scope.id}'. Scope ids must be unique per tree.`);
|
|
56
|
+
}
|
|
57
|
+
byId.set(scope.id, scope);
|
|
58
|
+
}
|
|
59
|
+
// ── Pass 2: validate parent pointers + build children buckets ─────────
|
|
60
|
+
for (const scope of scopes) {
|
|
61
|
+
if (scope.parent === null) {
|
|
62
|
+
if (scope.kind !== 'Module') {
|
|
63
|
+
throw new ScopeTreeInvariantError('non-module-requires-parent', `Scope '${scope.id}' has kind '${scope.kind}' but no parent. Only 'Module' scopes may be root-level.`);
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const parent = byId.get(scope.parent);
|
|
68
|
+
if (parent === undefined) {
|
|
69
|
+
throw new ScopeTreeInvariantError('parent-not-found', `Scope '${scope.id}' references parent '${scope.parent}' which is not part of this tree.`);
|
|
70
|
+
}
|
|
71
|
+
if (parent.filePath !== scope.filePath) {
|
|
72
|
+
throw new ScopeTreeInvariantError('parent-must-share-filepath', `Scope '${scope.id}' (${scope.filePath}) has parent '${parent.id}' in a different file (${parent.filePath}). Parent/child scopes must share filePath.`);
|
|
73
|
+
}
|
|
74
|
+
if (!rangeStrictlyContains(parent.range, scope.range)) {
|
|
75
|
+
throw new ScopeTreeInvariantError('parent-must-contain-child', `Parent scope '${parent.id}' at ${formatRange(parent.range)} does not strictly contain child '${scope.id}' at ${formatRange(scope.range)}.`);
|
|
76
|
+
}
|
|
77
|
+
let bucket = childrenById.get(parent.id);
|
|
78
|
+
if (bucket === undefined) {
|
|
79
|
+
bucket = [];
|
|
80
|
+
childrenById.set(parent.id, bucket);
|
|
81
|
+
}
|
|
82
|
+
bucket.push(scope.id);
|
|
83
|
+
}
|
|
84
|
+
// ── Pass 3: sibling-overlap check ─────────────────────────────────────
|
|
85
|
+
for (const [parentId, childIds] of childrenById) {
|
|
86
|
+
if (childIds.length < 2)
|
|
87
|
+
continue;
|
|
88
|
+
// Sort siblings by (startLine, startCol) for an O(n log n) pairwise
|
|
89
|
+
// scan instead of O(n²) all-pairs.
|
|
90
|
+
const children = childIds.map((id) => byId.get(id)).slice();
|
|
91
|
+
children.sort((a, b) => comparePosition(a.range, b.range));
|
|
92
|
+
for (let i = 1; i < children.length; i++) {
|
|
93
|
+
const prev = children[i - 1];
|
|
94
|
+
const curr = children[i];
|
|
95
|
+
if (rangesOverlap(prev.range, curr.range)) {
|
|
96
|
+
throw new ScopeTreeInvariantError('sibling-ranges-overlap', `Sibling scopes under parent '${parentId}' overlap: '${prev.id}' ${formatRange(prev.range)} and '${curr.id}' ${formatRange(curr.range)}.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Freeze children arrays so the surface is truly read-only.
|
|
101
|
+
const frozenChildren = new Map();
|
|
102
|
+
for (const [parentId, childIds] of childrenById) {
|
|
103
|
+
frozenChildren.set(parentId, Object.freeze(childIds.slice()));
|
|
104
|
+
}
|
|
105
|
+
return freezeTree(byId, frozenChildren);
|
|
106
|
+
}
|
|
107
|
+
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
108
|
+
const EMPTY_CHILDREN = Object.freeze([]);
|
|
109
|
+
function freezeTree(byId, childrenById) {
|
|
110
|
+
return {
|
|
111
|
+
byId,
|
|
112
|
+
get size() {
|
|
113
|
+
return byId.size;
|
|
114
|
+
},
|
|
115
|
+
getScope(id) {
|
|
116
|
+
return byId.get(id);
|
|
117
|
+
},
|
|
118
|
+
getParent(id) {
|
|
119
|
+
const scope = byId.get(id);
|
|
120
|
+
if (scope === undefined || scope.parent === null)
|
|
121
|
+
return undefined;
|
|
122
|
+
return byId.get(scope.parent);
|
|
123
|
+
},
|
|
124
|
+
getChildren(id) {
|
|
125
|
+
return childrenById.get(id) ?? EMPTY_CHILDREN;
|
|
126
|
+
},
|
|
127
|
+
getAncestors(id) {
|
|
128
|
+
const start = byId.get(id);
|
|
129
|
+
if (start === undefined || start.parent === null)
|
|
130
|
+
return EMPTY_CHILDREN;
|
|
131
|
+
const out = [];
|
|
132
|
+
const visited = new Set([id]);
|
|
133
|
+
let cursor = start.parent;
|
|
134
|
+
while (cursor !== null && !visited.has(cursor)) {
|
|
135
|
+
visited.add(cursor);
|
|
136
|
+
out.push(cursor);
|
|
137
|
+
const next = byId.get(cursor);
|
|
138
|
+
cursor = next === undefined ? null : next.parent;
|
|
139
|
+
}
|
|
140
|
+
return Object.freeze(out);
|
|
141
|
+
},
|
|
142
|
+
has(id) {
|
|
143
|
+
return byId.has(id);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `outer` strictly contains `inner` when `outer`'s start is at or before
|
|
149
|
+
* `inner`'s start, `outer`'s end is at or after `inner`'s end, and they are
|
|
150
|
+
* not the exact same range. Equal ranges are rejected — a child cannot
|
|
151
|
+
* occupy the exact same span as its parent.
|
|
152
|
+
*/
|
|
153
|
+
function rangeStrictlyContains(outer, inner) {
|
|
154
|
+
if (outer.startLine === inner.startLine &&
|
|
155
|
+
outer.startCol === inner.startCol &&
|
|
156
|
+
outer.endLine === inner.endLine &&
|
|
157
|
+
outer.endCol === inner.endCol) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const outerStartsAtOrBefore = outer.startLine < inner.startLine ||
|
|
161
|
+
(outer.startLine === inner.startLine && outer.startCol <= inner.startCol);
|
|
162
|
+
const outerEndsAtOrAfter = outer.endLine > inner.endLine ||
|
|
163
|
+
(outer.endLine === inner.endLine && outer.endCol >= inner.endCol);
|
|
164
|
+
return outerStartsAtOrBefore && outerEndsAtOrAfter;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Two ranges overlap when neither finishes before the other begins. Ranges
|
|
168
|
+
* that merely touch at a single boundary point (`a.end === b.start`) do
|
|
169
|
+
* NOT overlap — this matches tree-sitter's half-open-like range semantics
|
|
170
|
+
* and the typical "sibling blocks meet but don't overlap" pattern.
|
|
171
|
+
*/
|
|
172
|
+
function rangesOverlap(a, b) {
|
|
173
|
+
const aEndsBeforeB = a.endLine < b.startLine || (a.endLine === b.startLine && a.endCol <= b.startCol);
|
|
174
|
+
const bEndsBeforeA = b.endLine < a.startLine || (b.endLine === a.startLine && b.endCol <= a.startCol);
|
|
175
|
+
return !(aEndsBeforeB || bEndsBeforeA);
|
|
176
|
+
}
|
|
177
|
+
function comparePosition(a, b) {
|
|
178
|
+
if (a.startLine !== b.startLine)
|
|
179
|
+
return a.startLine - b.startLine;
|
|
180
|
+
return a.startCol - b.startCol;
|
|
181
|
+
}
|
|
182
|
+
function formatRange(r) {
|
|
183
|
+
return `${r.startLine}:${r.startCol}-${r.endLine}:${r.endCol}`;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=scope-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-tree.js","sourceRoot":"","sources":["../../src/scope-resolution/scope-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAwBH,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAErC;IADX,YACW,SAMe,EACxB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QATN,cAAS,GAAT,SAAS,CAMM;QAIxB,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAAwB;IACrD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEnD,yEAAyE;IACzE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,uBAAuB,CAC/B,oBAAoB,EACpB,wBAAwB,KAAK,CAAC,EAAE,uCAAuC,CACxE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,yEAAyE;IACzE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YAC1B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,IAAI,uBAAuB,CAC/B,4BAA4B,EAC5B,UAAU,KAAK,CAAC,EAAE,eAAe,KAAK,CAAC,IAAI,0DAA0D,CACtG,CAAC;YACJ,CAAC;YACD,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,uBAAuB,CAC/B,kBAAkB,EAClB,UAAU,KAAK,CAAC,EAAE,wBAAwB,KAAK,CAAC,MAAM,mCAAmC,CAC1F,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,EAAE,CAAC;YACvC,MAAM,IAAI,uBAAuB,CAC/B,4BAA4B,EAC5B,UAAU,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC,QAAQ,iBAAiB,MAAM,CAAC,EAAE,0BAA0B,MAAM,CAAC,QAAQ,6CAA6C,CACvJ,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,uBAAuB,CAC/B,2BAA2B,EAC3B,iBAAiB,MAAM,CAAC,EAAE,QAAQ,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,KAAK,CAAC,EAAE,QAAQ,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAC5I,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,GAAG,EAAE,CAAC;YACZ,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IAED,yEAAyE;IACzE,KAAK,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,YAAY,EAAE,CAAC;QAChD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAClC,oEAAoE;QACpE,mCAAmC;QACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QAC7D,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;YAC1B,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,uBAAuB,CAC/B,wBAAwB,EACxB,gCAAgC,QAAQ,eAAe,IAAI,CAAC,EAAE,KAAK,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,KAAK,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAC1I,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,4DAA4D;IAC5D,MAAM,cAAc,GAAG,IAAI,GAAG,EAA+B,CAAC;IAC9D,KAAK,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,YAAY,EAAE,CAAC;QAChD,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;AAC1C,CAAC;AAED,+EAA+E;AAE/E,MAAM,cAAc,GAAuB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAE7D,SAAS,UAAU,CACjB,IAAyB,EACzB,YAA8C;IAE9C,OAAO;QACL,IAAI;QACJ,IAAI,IAAI;YACN,OAAO,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;QACD,QAAQ,CAAC,EAAW;YAClB,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;QACD,SAAS,CAAC,EAAW;YACnB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;gBAAE,OAAO,SAAS,CAAC;YACnE,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,WAAW,CAAC,EAAW;YACrB,OAAO,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC;QAChD,CAAC;QACD,YAAY,CAAC,EAAW;YACtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;gBAAE,OAAO,cAAc,CAAC;YACxE,MAAM,GAAG,GAAc,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAU,CAAC,EAAE,CAAC,CAAC,CAAC;YACvC,IAAI,MAAM,GAAmB,KAAK,CAAC,MAAM,CAAC;YAC1C,OAAO,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACpB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACjB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC9B,MAAM,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;YACnD,CAAC;YACD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;QACD,GAAG,CAAC,EAAW;YACb,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,qBAAqB,CAAC,KAAY,EAAE,KAAY;IACvD,IACE,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;QACnC,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;QACjC,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;QAC/B,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAC7B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,qBAAqB,GACzB,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS;QACjC,CAAC,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC5E,MAAM,kBAAkB,GACtB,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO;QAC7B,CAAC,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;IACpE,OAAO,qBAAqB,IAAI,kBAAkB,CAAC;AACrD,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,CAAQ,EAAE,CAAQ;IACvC,MAAM,YAAY,GAChB,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;IACnF,MAAM,YAAY,GAChB,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;IACnF,OAAO,CAAC,CAAC,YAAY,IAAI,YAAY,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,eAAe,CAAC,CAAQ,EAAE,CAAQ;IACzC,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;QAAE,OAAO,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;IAClE,OAAO,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;AACjC,CAAC;AAED,SAAS,WAAW,CAAC,CAAQ;IAC3B,OAAO,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;AACjE,CAAC"}
|
|
@@ -167,12 +167,6 @@ export interface ParsedTypeBinding {
|
|
|
167
167
|
* concretely typed in Ring 2 SHARED (#915).
|
|
168
168
|
*/
|
|
169
169
|
export type WorkspaceIndex = unknown;
|
|
170
|
-
/**
|
|
171
|
-
* Scope tree handle consumed by parse-phase hooks (`bindingScopeFor`,
|
|
172
|
-
* `importOwningScope`) to navigate the in-progress scope tree. Opaque
|
|
173
|
-
* placeholder in Ring 1; concretely typed in Ring 2 SHARED (#912).
|
|
174
|
-
*/
|
|
175
|
-
export type ScopeTree = unknown;
|
|
176
170
|
/** Call-site description passed to `arityCompatibility`. */
|
|
177
171
|
export interface Callsite {
|
|
178
172
|
/** Number of arguments at the call site. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-phase wall-clock timing for the search pipeline and similar
|
|
3
|
+
* multi-stage flows. Designed to be called from query() with minimal
|
|
4
|
+
* ceremony and negligible overhead (< 0.1 ms per phase recorded).
|
|
5
|
+
*
|
|
6
|
+
* ### Sequential usage
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const t = new PhaseTimer();
|
|
10
|
+
* t.start('bm25'); await bm25Search(...); t.stop();
|
|
11
|
+
* t.start('merge'); doMerge(); t.stop();
|
|
12
|
+
* const phases = t.summary(); // { bm25: 42, merge: 3 }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* ### Concurrent usage (Promise.all)
|
|
16
|
+
*
|
|
17
|
+
* `start`/`stop` assume a single active phase at a time, which is wrong
|
|
18
|
+
* for concurrent work inside `Promise.all` — the second `start` would
|
|
19
|
+
* auto-stop the first and only one of the two would get timed. Use
|
|
20
|
+
* {@link PhaseTimer.time} to wrap each concurrent promise instead:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* const [a, b] = await Promise.all([
|
|
24
|
+
* t.time('bm25', bm25Search(...)),
|
|
25
|
+
* t.time('vector', semanticSearch(...)),
|
|
26
|
+
* ]);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ### Pre-measured durations
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* t.mark('inherited', 12.5);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare class PhaseTimer {
|
|
36
|
+
private phases;
|
|
37
|
+
private current;
|
|
38
|
+
private t0;
|
|
39
|
+
/** Start a new phase. Implicitly stops the previous one, if any. */
|
|
40
|
+
start(phase: string): void;
|
|
41
|
+
/** Stop the current phase. No-op if no phase is active. */
|
|
42
|
+
stop(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Record a pre-measured duration without touching the active phase.
|
|
45
|
+
* Use for concurrent operations inside `Promise.all` where
|
|
46
|
+
* `start`/`stop` would step on each other, or for durations imported
|
|
47
|
+
* from sub-systems. Additive across repeated calls with the same
|
|
48
|
+
* phase name. Ignores negative / non-finite inputs.
|
|
49
|
+
*/
|
|
50
|
+
mark(phase: string, durationMs: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Wrap a promise with automatic timing. Records wall time via
|
|
53
|
+
* {@link PhaseTimer.mark} regardless of which other phases are
|
|
54
|
+
* active — safe to use inside `Promise.all`.
|
|
55
|
+
*/
|
|
56
|
+
time<T>(phase: string, promise: Promise<T>): Promise<T>;
|
|
57
|
+
/**
|
|
58
|
+
* Snapshot of accumulated durations rounded to 0.1 ms. Stops the
|
|
59
|
+
* current phase if one is still running.
|
|
60
|
+
*/
|
|
61
|
+
summary(): Record<string, number>;
|
|
62
|
+
/**
|
|
63
|
+
* Sum of every recorded phase duration.
|
|
64
|
+
*
|
|
65
|
+
* Note: for phases recorded via {@link PhaseTimer.time} or
|
|
66
|
+
* {@link PhaseTimer.mark} this is the *sum*, not the wall time —
|
|
67
|
+
* concurrent work overlaps and the sum can exceed the end-to-end
|
|
68
|
+
* wall time. Record wall time separately with `mark('wall', …)` if
|
|
69
|
+
* that distinction matters.
|
|
70
|
+
*/
|
|
71
|
+
totalMs(): number;
|
|
72
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-phase wall-clock timing for the search pipeline and similar
|
|
3
|
+
* multi-stage flows. Designed to be called from query() with minimal
|
|
4
|
+
* ceremony and negligible overhead (< 0.1 ms per phase recorded).
|
|
5
|
+
*
|
|
6
|
+
* ### Sequential usage
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const t = new PhaseTimer();
|
|
10
|
+
* t.start('bm25'); await bm25Search(...); t.stop();
|
|
11
|
+
* t.start('merge'); doMerge(); t.stop();
|
|
12
|
+
* const phases = t.summary(); // { bm25: 42, merge: 3 }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* ### Concurrent usage (Promise.all)
|
|
16
|
+
*
|
|
17
|
+
* `start`/`stop` assume a single active phase at a time, which is wrong
|
|
18
|
+
* for concurrent work inside `Promise.all` — the second `start` would
|
|
19
|
+
* auto-stop the first and only one of the two would get timed. Use
|
|
20
|
+
* {@link PhaseTimer.time} to wrap each concurrent promise instead:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* const [a, b] = await Promise.all([
|
|
24
|
+
* t.time('bm25', bm25Search(...)),
|
|
25
|
+
* t.time('vector', semanticSearch(...)),
|
|
26
|
+
* ]);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ### Pre-measured durations
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* t.mark('inherited', 12.5);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export class PhaseTimer {
|
|
36
|
+
phases = new Map();
|
|
37
|
+
current = null;
|
|
38
|
+
t0 = 0;
|
|
39
|
+
/** Start a new phase. Implicitly stops the previous one, if any. */
|
|
40
|
+
start(phase) {
|
|
41
|
+
this.stop();
|
|
42
|
+
this.current = phase;
|
|
43
|
+
this.t0 = performance.now();
|
|
44
|
+
}
|
|
45
|
+
/** Stop the current phase. No-op if no phase is active. */
|
|
46
|
+
stop() {
|
|
47
|
+
if (this.current !== null) {
|
|
48
|
+
const elapsed = performance.now() - this.t0;
|
|
49
|
+
this.phases.set(this.current, (this.phases.get(this.current) ?? 0) + elapsed);
|
|
50
|
+
this.current = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Record a pre-measured duration without touching the active phase.
|
|
55
|
+
* Use for concurrent operations inside `Promise.all` where
|
|
56
|
+
* `start`/`stop` would step on each other, or for durations imported
|
|
57
|
+
* from sub-systems. Additive across repeated calls with the same
|
|
58
|
+
* phase name. Ignores negative / non-finite inputs.
|
|
59
|
+
*/
|
|
60
|
+
mark(phase, durationMs) {
|
|
61
|
+
if (!Number.isFinite(durationMs) || durationMs < 0)
|
|
62
|
+
return;
|
|
63
|
+
this.phases.set(phase, (this.phases.get(phase) ?? 0) + durationMs);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Wrap a promise with automatic timing. Records wall time via
|
|
67
|
+
* {@link PhaseTimer.mark} regardless of which other phases are
|
|
68
|
+
* active — safe to use inside `Promise.all`.
|
|
69
|
+
*/
|
|
70
|
+
async time(phase, promise) {
|
|
71
|
+
const t0 = performance.now();
|
|
72
|
+
try {
|
|
73
|
+
return await promise;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
this.mark(phase, performance.now() - t0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Snapshot of accumulated durations rounded to 0.1 ms. Stops the
|
|
81
|
+
* current phase if one is still running.
|
|
82
|
+
*/
|
|
83
|
+
summary() {
|
|
84
|
+
this.stop();
|
|
85
|
+
const out = {};
|
|
86
|
+
for (const [k, v] of this.phases)
|
|
87
|
+
out[k] = Math.round(v * 10) / 10;
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Sum of every recorded phase duration.
|
|
92
|
+
*
|
|
93
|
+
* Note: for phases recorded via {@link PhaseTimer.time} or
|
|
94
|
+
* {@link PhaseTimer.mark} this is the *sum*, not the wall time —
|
|
95
|
+
* concurrent work overlaps and the sum can exceed the end-to-end
|
|
96
|
+
* wall time. Record wall time separately with `mark('wall', …)` if
|
|
97
|
+
* that distinction matters.
|
|
98
|
+
*/
|
|
99
|
+
totalMs() {
|
|
100
|
+
this.stop();
|
|
101
|
+
let t = 0;
|
|
102
|
+
for (const v of this.phases.values())
|
|
103
|
+
t += v;
|
|
104
|
+
return Math.round(t * 10) / 10;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -18,6 +18,7 @@ import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-ma
|
|
|
18
18
|
import { GroupService } from '../../core/group/service.js';
|
|
19
19
|
import { collectBestChunks } from '../../core/embeddings/types.js';
|
|
20
20
|
import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME } from '../../core/lbug/schema.js';
|
|
21
|
+
import { PhaseTimer } from '../../core/search/phase-timer.js';
|
|
21
22
|
// AI context generation is CLI-only (gitnexus analyze)
|
|
22
23
|
// import { generateAIContextFiles } from '../../cli/ai-context.js';
|
|
23
24
|
/**
|
|
@@ -134,6 +135,25 @@ function logQueryError(context, err) {
|
|
|
134
135
|
const msg = err instanceof Error ? err.message : String(err);
|
|
135
136
|
console.error(`GitNexus [${context}]: ${msg}`);
|
|
136
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Structured per-query latency log for production aggregation (#553).
|
|
140
|
+
*
|
|
141
|
+
* Emitted on stderr — NOT stdout — because the MCP stdio transport uses
|
|
142
|
+
* stdout exclusively for JSON-RPC responses (#324), and the CLI e2e test
|
|
143
|
+
* `tool output goes to stdout via fd 1` asserts that stdout parses cleanly
|
|
144
|
+
* as JSON. Any `console.log` from inside a tool handler would corrupt the
|
|
145
|
+
* protocol. Matches the existing `logQueryError` convention above, which
|
|
146
|
+
* uses stderr for the same reason.
|
|
147
|
+
*
|
|
148
|
+
* The `GitNexus [query:timing] …` prefix keeps lines greppable; the
|
|
149
|
+
* `phases` payload is JSON so log-scraping pipelines can parse it
|
|
150
|
+
* without custom format knowledge.
|
|
151
|
+
*/
|
|
152
|
+
function logQueryTiming(query, phases) {
|
|
153
|
+
const totalMs = phases.wall ?? Object.values(phases).reduce((a, b) => a + b, 0);
|
|
154
|
+
const truncated = query.length > 80 ? `${query.slice(0, 80)}…` : query;
|
|
155
|
+
console.error(`GitNexus [query:timing] query=${JSON.stringify(truncated)} totalMs=${totalMs} phases=${JSON.stringify(phases)}`);
|
|
156
|
+
}
|
|
137
157
|
export class LocalBackend {
|
|
138
158
|
repos = new Map();
|
|
139
159
|
contextCache = new Map();
|
|
@@ -449,15 +469,26 @@ export class LocalBackend {
|
|
|
449
469
|
const maxSymbolsPerProcess = params.max_symbols || 10;
|
|
450
470
|
const includeContent = params.include_content ?? false;
|
|
451
471
|
const searchQuery = params.query.trim();
|
|
452
|
-
//
|
|
472
|
+
// Per-phase timing instrumentation (#553). Records wall time for each
|
|
473
|
+
// observable sub-step of the search pipeline so production latency can
|
|
474
|
+
// be aggregated offline for Pareto analysis and bottleneck detection.
|
|
475
|
+
// Overhead is <0.1 ms per phase; the timer is passive and never alters
|
|
476
|
+
// query behaviour.
|
|
477
|
+
const timer = new PhaseTimer();
|
|
478
|
+
const wallStart = performance.now();
|
|
479
|
+
// Step 1: Run hybrid search to get matching symbols. BM25 and vector
|
|
480
|
+
// search run concurrently via Promise.all — use `timer.time()` for
|
|
481
|
+
// each so both get independent wall-time records without fighting
|
|
482
|
+
// over a single `current` phase slot.
|
|
453
483
|
const searchLimit = processLimit * maxSymbolsPerProcess; // fetch enough raw results
|
|
454
484
|
const [bm25SearchResult, semanticResults] = await Promise.all([
|
|
455
|
-
this.bm25Search(repo, searchQuery, searchLimit),
|
|
456
|
-
this.semanticSearch(repo, searchQuery, searchLimit),
|
|
485
|
+
timer.time('bm25', this.bm25Search(repo, searchQuery, searchLimit)),
|
|
486
|
+
timer.time('vector', this.semanticSearch(repo, searchQuery, searchLimit)),
|
|
457
487
|
]);
|
|
458
488
|
const bm25Results = bm25SearchResult.results;
|
|
459
489
|
const ftsUsed = bm25SearchResult.ftsUsed;
|
|
460
490
|
// Merge via reciprocal rank fusion
|
|
491
|
+
timer.start('merge');
|
|
461
492
|
const scoreMap = new Map();
|
|
462
493
|
for (let i = 0; i < bm25Results.length; i++) {
|
|
463
494
|
const result = bm25Results[i];
|
|
@@ -486,7 +517,9 @@ export class LocalBackend {
|
|
|
486
517
|
const merged = Array.from(scoreMap.entries())
|
|
487
518
|
.sort((a, b) => b[1].score - a[1].score)
|
|
488
519
|
.slice(0, searchLimit);
|
|
520
|
+
timer.stop(); // merge
|
|
489
521
|
// Step 2: For each match with a nodeId, trace to process(es)
|
|
522
|
+
timer.start('symbol_lookup');
|
|
490
523
|
const processMap = new Map();
|
|
491
524
|
const definitions = []; // standalone symbols not in any process
|
|
492
525
|
for (const [_, item] of merged) {
|
|
@@ -590,7 +623,9 @@ export class LocalBackend {
|
|
|
590
623
|
}
|
|
591
624
|
}
|
|
592
625
|
}
|
|
626
|
+
timer.stop(); // symbol_lookup
|
|
593
627
|
// Step 3: Rank processes by aggregate score + internal cohesion boost
|
|
628
|
+
timer.start('ranking');
|
|
594
629
|
const rankedProcesses = Array.from(processMap.values())
|
|
595
630
|
.map((p) => ({
|
|
596
631
|
...p,
|
|
@@ -598,7 +633,9 @@ export class LocalBackend {
|
|
|
598
633
|
}))
|
|
599
634
|
.sort((a, b) => b.priority - a.priority)
|
|
600
635
|
.slice(0, processLimit);
|
|
636
|
+
timer.stop(); // ranking
|
|
601
637
|
// Step 4: Build response
|
|
638
|
+
timer.start('formatting');
|
|
602
639
|
const processes = rankedProcesses.map((p) => ({
|
|
603
640
|
id: p.id,
|
|
604
641
|
summary: p.heuristicLabel || p.label,
|
|
@@ -619,10 +656,18 @@ export class LocalBackend {
|
|
|
619
656
|
seen.add(s.id);
|
|
620
657
|
return true;
|
|
621
658
|
});
|
|
659
|
+
timer.stop(); // formatting
|
|
660
|
+
// End-to-end wall time — deliberately a separate mark so callers can
|
|
661
|
+
// compare sum(phases) vs wall to see how much Promise.all concurrency
|
|
662
|
+
// saved. Must come before summary() so it's included.
|
|
663
|
+
timer.mark('wall', performance.now() - wallStart);
|
|
664
|
+
const timing = timer.summary();
|
|
665
|
+
logQueryTiming(searchQuery, timing);
|
|
622
666
|
return {
|
|
623
667
|
processes,
|
|
624
668
|
process_symbols: dedupedSymbols,
|
|
625
669
|
definitions: definitions.slice(0, 20), // cap standalone definitions
|
|
670
|
+
timing,
|
|
626
671
|
...(!ftsUsed && {
|
|
627
672
|
warning: 'FTS extension unavailable - keyword search degraded. Run: gitnexus analyze --force to rebuild indexes.',
|
|
628
673
|
}),
|
package/package.json
CHANGED