homegraph 1.6.3 → 1.6.4

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.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Spec 0066 — parse JSDoc `@returns` / `@param` type tags as a fallback when
3
+ * the signature has no usable return/param type name. Docstring text still
4
+ * lives on `nodes.docstring` (Spec 0020); this only extracts type *names*.
5
+ */
6
+ /**
7
+ * Unwrap Promise/Array/… and strip arrays/generics to a bare type identifier.
8
+ * Returns undefined for empty / primitive / void-like results.
9
+ */
10
+ export declare function extractBaseTypeName(raw: string | undefined | null): string | undefined;
11
+ /** `@returns {Type}` or `@return Type` → base type name. */
12
+ export declare function parseJsdocReturns(docstring: string | undefined | null): string | undefined;
13
+ export interface JsdocParamType {
14
+ paramName: string;
15
+ typeName: string;
16
+ }
17
+ /** `@param {Type} name` entries with a resolvable type name. */
18
+ export declare function parseJsdocParams(docstring: string | undefined | null): JsdocParamType[];
19
+ //# sourceMappingURL=jsdoc-type-tags.d.ts.map
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * Spec 0066 — parse JSDoc `@returns` / `@param` type tags as a fallback when
4
+ * the signature has no usable return/param type name. Docstring text still
5
+ * lives on `nodes.docstring` (Spec 0020); this only extracts type *names*.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.extractBaseTypeName = extractBaseTypeName;
9
+ exports.parseJsdocReturns = parseJsdocReturns;
10
+ exports.parseJsdocParams = parseJsdocParams;
11
+ const PRIMITIVE_OR_VOID = new Set([
12
+ 'void',
13
+ 'undefined',
14
+ 'null',
15
+ 'never',
16
+ 'any',
17
+ 'unknown',
18
+ 'object',
19
+ 'Object',
20
+ 'string',
21
+ 'String',
22
+ 'number',
23
+ 'Number',
24
+ 'boolean',
25
+ 'Boolean',
26
+ 'bigint',
27
+ 'symbol',
28
+ 'Symbol',
29
+ 'this',
30
+ 'self',
31
+ 'static',
32
+ ]);
33
+ /**
34
+ * Unwrap Promise/Array/… and strip arrays/generics to a bare type identifier.
35
+ * Returns undefined for empty / primitive / void-like results.
36
+ */
37
+ function extractBaseTypeName(raw) {
38
+ if (!raw)
39
+ return undefined;
40
+ let t = raw.trim();
41
+ if (!t)
42
+ return undefined;
43
+ // Drop leading `readonly` / `export` noise occasionally glued into tags.
44
+ t = t.replace(/^(?:readonly|export|declare)\s+/i, '').trim();
45
+ // Take the first union arm: `Foo | null` → `Foo`.
46
+ if (t.includes('|')) {
47
+ t = t.split('|')[0].trim();
48
+ }
49
+ // Function type → skip (no single type node).
50
+ if (t.includes('=>'))
51
+ return undefined;
52
+ if (t.endsWith('[]')) {
53
+ t = t.slice(0, -2).trim();
54
+ }
55
+ const wrapper = t.match(/^(?:Promise|Array|ReadonlyArray|Callback|Set|Iterable|Partial|Required|Readonly)\s*<\s*(.+)\s*>$/i);
56
+ if (wrapper?.[1]) {
57
+ t = wrapper[1].trim();
58
+ // Nested commas in Map/Record — take last type arg when present.
59
+ if (t.includes(',')) {
60
+ const mapLike = t.match(/^(?:Map|Record)\s*<[^,]+,\s*(.+)>$/i);
61
+ if (mapLike?.[1])
62
+ t = mapLike[1].trim();
63
+ else
64
+ t = t.split(',').pop().trim();
65
+ }
66
+ }
67
+ const mapMatch = t.match(/^(?:Map|Record)\s*<[^,]+,\s*(.+)>$/i);
68
+ if (mapMatch?.[1])
69
+ t = mapMatch[1].trim();
70
+ // Strip remaining generics: `Foo<T>` → `Foo`
71
+ t = t.replace(/<[^>]*>/g, '').replace(/\[\]/g, '').trim();
72
+ if (!t)
73
+ return undefined;
74
+ // Qualified → trailing simple name (`ns.Bar` → `Bar`)
75
+ const simple = t.split('.').filter(Boolean).pop() ?? t;
76
+ if (!simple || PRIMITIVE_OR_VOID.has(simple))
77
+ return undefined;
78
+ // Must look like an identifier (PascalCase preferred but allow any word).
79
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(simple))
80
+ return undefined;
81
+ return simple;
82
+ }
83
+ /** `@returns {Type}` or `@return Type` → base type name. */
84
+ function parseJsdocReturns(docstring) {
85
+ if (!docstring)
86
+ return undefined;
87
+ const re = /@returns?\s+(?:\{([^}]+)\}|([A-Za-z_][\w.<>[\],\s|]*))/gi;
88
+ let m;
89
+ while ((m = re.exec(docstring)) !== null) {
90
+ const raw = (m[1] ?? m[2] ?? '').trim();
91
+ const base = extractBaseTypeName(raw);
92
+ if (base)
93
+ return base;
94
+ }
95
+ return undefined;
96
+ }
97
+ /** `@param {Type} name` entries with a resolvable type name. */
98
+ function parseJsdocParams(docstring) {
99
+ if (!docstring)
100
+ return [];
101
+ const out = [];
102
+ const re = /@param(?:eter)?\s+(?:\{([^}]+)\}\s+)?\[?(\w+)\]?/gi;
103
+ let m;
104
+ while ((m = re.exec(docstring)) !== null) {
105
+ const typeRaw = m[1]?.trim();
106
+ const paramName = m[2];
107
+ if (!typeRaw || !paramName)
108
+ continue;
109
+ const typeName = extractBaseTypeName(typeRaw);
110
+ if (typeName)
111
+ out.push({ paramName, typeName });
112
+ }
113
+ return out;
114
+ }
115
+ //# sourceMappingURL=jsdoc-type-tags.js.map
@@ -141,6 +141,7 @@ const liveness_watchdog_1 = require("../../mcp/liveness-watchdog");
141
141
  const migrate_semantics_1 = require("../../arkui/migrate-semantics");
142
142
  const migrate_passage_1 = require("../../arkui/migrate-passage");
143
143
  const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
144
+ const jsdoc_type_tags_1 = require("../jsdoc-type-tags");
144
145
  const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
145
146
  const ignore_1 = __importDefault(require("ignore"));
146
147
  /** Primitive / void return types — not stored in nodes.return_type (inference-only column). */
@@ -3995,29 +3996,18 @@ class ArkTSAdapter {
3995
3996
  this.crossFileEdges.push(edge);
3996
3997
  }
3997
3998
  }
3998
- ensureMethodNode(method, relativePath, result, parentId) {
3999
- const existing = this.methodToId.get(method);
4000
- if (existing)
4001
- return existing;
4002
- try {
4003
- const bySig = this.methodSigToId.get(method.getSignature().toString());
4004
- if (bySig) {
4005
- this.methodToId.set(method, bySig);
4006
- return bySig;
4007
- }
4008
- }
4009
- catch {
4010
- // fall through
4011
- }
4012
- if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
4013
- return null;
4014
- const displayName = arkMethodDisplayName(method);
4015
- if (!displayName)
4016
- return null;
4017
- const cls = method.getDeclaringArkClass();
4018
- const kind = cls.isDefaultArkClass() ? 'function' : 'method';
4019
- this.indexMethod(relativePath, languageForArkAnalyzerPath(relativePath), result, method, parentId, kind, displayName);
4020
- return this.methodToId.get(method) ?? null;
3999
+ /**
4000
+ * Lazily index a method for ViewTree (and any caller that used to pass the
4001
+ * ViewTree owner path). Attribution always follows the method's declaring
4002
+ * ArkFile / class — same as {@link resolveMethodNodeId} — so nested ViewTree
4003
+ * walks cannot pin `%AM*` handlers onto the importer (Spec 0064).
4004
+ *
4005
+ * `relativePath` / `result` / `parentId` from the caller are ignored for the
4006
+ * node row (kept on the signature so ViewTree call sites stay unchanged);
4007
+ * ViewTree *edges* still use the importer via {@link addViewTreeEdge}.
4008
+ */
4009
+ ensureMethodNode(method, _relativePath, _result, _parentId) {
4010
+ return this.resolveMethodNodeId(method);
4021
4011
  }
4022
4012
  indexMethod(relativePath, language, result, method, parentId, forcedKind, forcedDisplayName) {
4023
4013
  const displayName = forcedDisplayName ?? resolveMethodDisplayName(method);
@@ -4057,6 +4047,52 @@ class ArkTSAdapter {
4057
4047
  result.edges.push(arkEdge(parentId, methodNode.id, 'contains'));
4058
4048
  this.indexDecorators(result, methodNode.id, method, relativePath, language, line);
4059
4049
  this.indexNativeNapiCalls(method, methodNode.id, relativePath, language, result);
4050
+ // Spec 0066: return/param types as resolvable `returns` / `type_of` refs.
4051
+ this.emitMethodTypeEdges(method, methodNode.id, relativePath, language, line, returnType, methodNode.docstring, result);
4052
+ }
4053
+ /**
4054
+ * Spec 0066 — push unresolved `returns` / `type_of` from signature types,
4055
+ * falling back to JSDoc `@returns` / `@param` when the signature lacks them.
4056
+ */
4057
+ emitMethodTypeEdges(method, fromNodeId, relativePath, language, line, returnType, docstring, result) {
4058
+ const push = (referenceName, referenceKind) => {
4059
+ result.unresolvedReferences.push({
4060
+ fromNodeId,
4061
+ referenceName,
4062
+ referenceKind,
4063
+ line,
4064
+ column: 0,
4065
+ filePath: relativePath,
4066
+ language,
4067
+ });
4068
+ };
4069
+ if (returnType) {
4070
+ push(returnType, 'returns');
4071
+ }
4072
+ else {
4073
+ const fromDoc = (0, jsdoc_type_tags_1.parseJsdocReturns)(docstring);
4074
+ if (fromDoc)
4075
+ push(fromDoc, 'returns');
4076
+ }
4077
+ let emittedParam = false;
4078
+ try {
4079
+ for (const p of method.getParameters()) {
4080
+ const raw = p.getType()?.toString();
4081
+ const base = normalizeArktsReturnType(raw);
4082
+ if (base) {
4083
+ push(base, 'type_of');
4084
+ emittedParam = true;
4085
+ }
4086
+ }
4087
+ }
4088
+ catch {
4089
+ // parameters unavailable
4090
+ }
4091
+ if (!emittedParam) {
4092
+ for (const p of (0, jsdoc_type_tags_1.parseJsdocParams)(docstring)) {
4093
+ push(p.typeName, 'type_of');
4094
+ }
4095
+ }
4060
4096
  }
4061
4097
  indexNativeNapiCalls(method, fromNodeId, relativePath, language, result) {
4062
4098
  const code = method.getCode();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Store-phase flush / backpressure guards (Spec 0065).
3
+ *
4
+ * When `flushOrdered` records a store failure in `flushError` but returns a
5
+ * resolved promise, the feed backpressure `while` would otherwise spin on
6
+ * `await Promise.resolve()` forever (100% CPU hang). Call this at the top of
7
+ * that loop (and anywhere else that waits on flush progress).
8
+ */
9
+ /** Throw if the ordered store flush already failed or the run was aborted. */
10
+ export declare function throwIfStoreFlushFailed(aborted: boolean, flushError: unknown): void;
11
+ //# sourceMappingURL=store-flush-guard.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /**
3
+ * Store-phase flush / backpressure guards (Spec 0065).
4
+ *
5
+ * When `flushOrdered` records a store failure in `flushError` but returns a
6
+ * resolved promise, the feed backpressure `while` would otherwise spin on
7
+ * `await Promise.resolve()` forever (100% CPU hang). Call this at the top of
8
+ * that loop (and anywhere else that waits on flush progress).
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.throwIfStoreFlushFailed = throwIfStoreFlushFailed;
12
+ /** Throw if the ordered store flush already failed or the run was aborted. */
13
+ function throwIfStoreFlushFailed(aborted, flushError) {
14
+ if (flushError)
15
+ throw flushError;
16
+ if (aborted)
17
+ throw new Error('Aborted');
18
+ }
19
+ //# sourceMappingURL=store-flush-guard.js.map
@@ -6,8 +6,9 @@
6
6
  * connection: the orchestrator posts one message per file (in file order) and
7
7
  * the worker applies them in arrival order, which preserves the #1015
8
8
  * insertion-order determinism exactly as if the main thread had run the same
9
- * calls. The main thread performs NO database access while the writer is
10
- * active (fresh-DB path only), so there is no cross-connection contention.
9
+ * calls. Spec 0065: the main connection is closed for the worker's lifetime
10
+ * (`exclusiveHandoff`) — an idle open handle on MEMORY/non-WAL backends was
11
+ * enough to surface SQLITE_BUSY during ArkTS→store handoff.
11
12
  *
12
13
  * Protocol (main → worker):
13
14
  * {type:'open', dbPath, fastInit} → open connection, reply {type:'ready'}
@@ -7,8 +7,9 @@
7
7
  * connection: the orchestrator posts one message per file (in file order) and
8
8
  * the worker applies them in arrival order, which preserves the #1015
9
9
  * insertion-order determinism exactly as if the main thread had run the same
10
- * calls. The main thread performs NO database access while the writer is
11
- * active (fresh-DB path only), so there is no cross-connection contention.
10
+ * calls. Spec 0065: the main connection is closed for the worker's lifetime
11
+ * (`exclusiveHandoff`) — an idle open handle on MEMORY/non-WAL backends was
12
+ * enough to surface SQLITE_BUSY during ArkTS→store handoff.
12
13
  *
13
14
  * Protocol (main → worker):
14
15
  * {type:'open', dbPath, fastInit} → open connection, reply {type:'ready'}
@@ -595,9 +595,15 @@ export declare class TreeSitterExtractor {
595
595
  private readonly BUILTIN_TYPES;
596
596
  /**
597
597
  * Extract type references from type annotations on a function/method/field node.
598
- * Creates 'references' edges for parameter types, return types, and field types.
598
+ * Spec 0066: parameter / field types → `type_of`; return types → `returns`.
599
+ * Dart / Scala mixed signature walks stay `references` (out of this Spec).
599
600
  */
600
601
  private extractTypeAnnotations;
602
+ /**
603
+ * Spec 0066: when the AST omitted return/param types, emit unresolved
604
+ * `returns` / `type_of` refs from JSDoc `@returns` / `@param {Type}`.
605
+ */
606
+ private emitJsdocTypeFallbacks;
601
607
  /**
602
608
  * Extract C# type references from a node that owns a type position —
603
609
  * a method/constructor declaration, a property declaration, or a
@@ -647,6 +653,7 @@ export declare class TreeSitterExtractor {
647
653
  /**
648
654
  * Recursively walk a subtree and extract all type_identifier references.
649
655
  * Handles unions, intersections, generics, arrays, etc.
656
+ * Spec 0066: callers pass `returns` / `type_of` for signature positions.
650
657
  */
651
658
  private extractTypeRefsFromSubtree;
652
659
  /**
@@ -47,6 +47,7 @@ const function_ref_1 = require("./function-ref");
47
47
  const generated_detection_1 = require("./generated-detection");
48
48
  const languages_1 = require("./languages");
49
49
  const c_cpp_1 = require("./languages/c-cpp");
50
+ const jsdoc_type_tags_1 = require("./jsdoc-type-tags");
50
51
  const liquid_extractor_1 = require("./liquid-extractor");
51
52
  const razor_extractor_1 = require("./razor-extractor");
52
53
  const svelte_extractor_1 = require("./svelte-extractor");
@@ -1511,6 +1512,8 @@ class TreeSitterExtractor {
1511
1512
  return;
1512
1513
  // Extract type annotations (parameter types and return type)
1513
1514
  this.extractTypeAnnotations(node, funcNode.id);
1515
+ // Spec 0066: JSDoc @returns/@param when the signature omitted types.
1516
+ this.emitJsdocTypeFallbacks(funcNode.id, docstring);
1514
1517
  // Extract decorators applied to the function (rare in JS/TS but
1515
1518
  // present in Python `@decorator def f():` and Java/Kotlin
1516
1519
  // annotations on free functions).
@@ -1712,6 +1715,8 @@ class TreeSitterExtractor {
1712
1715
  }
1713
1716
  // Extract type annotations (parameter types and return type)
1714
1717
  this.extractTypeAnnotations(node, methodNode.id);
1718
+ // Spec 0066: JSDoc @returns/@param when the signature omitted types.
1719
+ this.emitJsdocTypeFallbacks(methodNode.id, docstring);
1715
1720
  // Extract decorators (`@Get('/list') list() {}`).
1716
1721
  this.extractDecoratorsFor(node, methodNode.id);
1717
1722
  // Push to stack and visit body
@@ -5636,7 +5641,8 @@ class TreeSitterExtractor {
5636
5641
  ]);
5637
5642
  /**
5638
5643
  * Extract type references from type annotations on a function/method/field node.
5639
- * Creates 'references' edges for parameter types, return types, and field types.
5644
+ * Spec 0066: parameter / field types → `type_of`; return types → `returns`.
5645
+ * Dart / Scala mixed signature walks stay `references` (out of this Spec).
5640
5646
  */
5641
5647
  extractTypeAnnotations(node, nodeId) {
5642
5648
  if (!this.extractor)
@@ -5668,6 +5674,7 @@ class TreeSitterExtractor {
5668
5674
  // `type_identifier` child, not a `type` field — so getChildByField below
5669
5675
  // finds neither. Walk the inner signature: param names / the method name are
5670
5676
  // `identifier` (not `type_identifier`), so only types surface.
5677
+ // Spec 0066: leave as `references` (mixed return+param walk).
5671
5678
  if (this.language === 'dart') {
5672
5679
  let sig = node;
5673
5680
  if (node.type === 'method_signature') {
@@ -5677,7 +5684,7 @@ class TreeSitterExtractor {
5677
5684
  c.type === 'constructor_signature' ||
5678
5685
  c.type === 'factory_constructor_signature') ?? node;
5679
5686
  }
5680
- this.extractTypeRefsFromSubtree(sig, nodeId);
5687
+ this.extractTypeRefsFromSubtree(sig, nodeId, 'references');
5681
5688
  return;
5682
5689
  }
5683
5690
  // Extract parameter type annotations. Scala curries — `def f(a)(implicit
@@ -5687,19 +5694,19 @@ class TreeSitterExtractor {
5687
5694
  if (this.language === 'scala') {
5688
5695
  for (const pc of node.namedChildren) {
5689
5696
  if (pc.type === 'parameters')
5690
- this.extractTypeRefsFromSubtree(pc, nodeId);
5697
+ this.extractTypeRefsFromSubtree(pc, nodeId, 'type_of');
5691
5698
  }
5692
5699
  }
5693
5700
  else {
5694
5701
  const params = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.paramsField || 'parameters');
5695
5702
  if (params) {
5696
- this.extractTypeRefsFromSubtree(params, nodeId);
5703
+ this.extractTypeRefsFromSubtree(params, nodeId, 'type_of');
5697
5704
  }
5698
5705
  }
5699
5706
  // Extract return type annotation
5700
5707
  const returnType = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.returnField || 'return_type');
5701
5708
  if (returnType) {
5702
- this.extractTypeRefsFromSubtree(returnType, nodeId);
5709
+ this.extractTypeRefsFromSubtree(returnType, nodeId, 'returns');
5703
5710
  }
5704
5711
  // Scala context bounds / type-parameter bounds: `def f[A: Monoid]`,
5705
5712
  // `[F[_]: Monad]`, `[A <: Foo]` carry the bound type inside `type_parameters`.
@@ -5711,13 +5718,46 @@ class TreeSitterExtractor {
5711
5718
  if (this.language === 'scala') {
5712
5719
  const typeParams = node.namedChildren.find((c) => c.type === 'type_parameters');
5713
5720
  if (typeParams) {
5714
- this.extractTypeRefsFromSubtree(typeParams, nodeId);
5721
+ this.extractTypeRefsFromSubtree(typeParams, nodeId, 'type_of');
5715
5722
  }
5716
5723
  }
5717
5724
  // Extract direct type annotation (for class fields like `model: ITextModel`)
5718
5725
  const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
5719
5726
  if (typeAnnotation) {
5720
- this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
5727
+ this.extractTypeRefsFromSubtree(typeAnnotation, nodeId, 'type_of');
5728
+ }
5729
+ }
5730
+ /**
5731
+ * Spec 0066: when the AST omitted return/param types, emit unresolved
5732
+ * `returns` / `type_of` refs from JSDoc `@returns` / `@param {Type}`.
5733
+ */
5734
+ emitJsdocTypeFallbacks(fromNodeId, docstring) {
5735
+ if (!docstring)
5736
+ return;
5737
+ const hasReturns = this.unresolvedReferences.some((r) => r.fromNodeId === fromNodeId && r.referenceKind === 'returns');
5738
+ if (!hasReturns) {
5739
+ const ret = (0, jsdoc_type_tags_1.parseJsdocReturns)(docstring);
5740
+ if (ret) {
5741
+ this.unresolvedReferences.push({
5742
+ fromNodeId,
5743
+ referenceName: ret,
5744
+ referenceKind: 'returns',
5745
+ line: 1,
5746
+ column: 0,
5747
+ });
5748
+ }
5749
+ }
5750
+ const hasTypeOf = this.unresolvedReferences.some((r) => r.fromNodeId === fromNodeId && r.referenceKind === 'type_of');
5751
+ if (!hasTypeOf) {
5752
+ for (const p of (0, jsdoc_type_tags_1.parseJsdocParams)(docstring)) {
5753
+ this.unresolvedReferences.push({
5754
+ fromNodeId,
5755
+ referenceName: p.typeName,
5756
+ referenceKind: 'type_of',
5757
+ line: 1,
5758
+ column: 0,
5759
+ });
5760
+ }
5721
5761
  }
5722
5762
  }
5723
5763
  /**
@@ -5923,21 +5963,22 @@ class TreeSitterExtractor {
5923
5963
  // Find type_annotation child (covers TS `: Type`, Rust `: Type`, etc.)
5924
5964
  const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
5925
5965
  if (typeAnnotation) {
5926
- this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
5966
+ this.extractTypeRefsFromSubtree(typeAnnotation, nodeId, 'type_of');
5927
5967
  }
5928
5968
  }
5929
5969
  /**
5930
5970
  * Recursively walk a subtree and extract all type_identifier references.
5931
5971
  * Handles unions, intersections, generics, arrays, etc.
5972
+ * Spec 0066: callers pass `returns` / `type_of` for signature positions.
5932
5973
  */
5933
- extractTypeRefsFromSubtree(node, fromNodeId) {
5974
+ extractTypeRefsFromSubtree(node, fromNodeId, referenceKind = 'references') {
5934
5975
  if (node.type === 'type_identifier') {
5935
5976
  const typeName = (0, tree_sitter_helpers_1.getNodeText)(node, this.source);
5936
5977
  if (typeName && !this.BUILTIN_TYPES.has(typeName)) {
5937
5978
  this.unresolvedReferences.push({
5938
5979
  fromNodeId,
5939
5980
  referenceName: typeName,
5940
- referenceKind: 'references',
5981
+ referenceKind,
5941
5982
  line: node.startPosition.row + 1,
5942
5983
  column: node.startPosition.column,
5943
5984
  });
@@ -5948,7 +5989,7 @@ class TreeSitterExtractor {
5948
5989
  for (let i = 0; i < node.namedChildCount; i++) {
5949
5990
  const child = node.namedChild(i);
5950
5991
  if (child) {
5951
- this.extractTypeRefsFromSubtree(child, fromNodeId);
5992
+ this.extractTypeRefsFromSubtree(child, fromNodeId, referenceKind);
5952
5993
  }
5953
5994
  }
5954
5995
  }
package/dist/index.js CHANGED
@@ -490,7 +490,37 @@ class HomeGraph {
490
490
  this.db.beginBulkParseLoad();
491
491
  let result;
492
492
  try {
493
- result = await this.orchestrator.indexAll(options.onProgress, options.signal, options.verbose, walValve ? () => walValve.backpressure() : undefined, freshDb ? { dbPath: this.db.getPath(), fastInit } : null);
493
+ result = await this.orchestrator.indexAll(options.onProgress, options.signal, options.verbose, walValve ? () => walValve.backpressure() : undefined, freshDb
494
+ ? {
495
+ dbPath: this.db.getPath(),
496
+ fastInit,
497
+ // Spec 0065: exclusive store-worker handoff (close main → worker → reopen).
498
+ exclusiveHandoff: {
499
+ release: () => {
500
+ this.db.closeForExclusiveWriter();
501
+ },
502
+ reclaim: () => {
503
+ this.db.reopenAfterExclusiveWriter({ fastInit });
504
+ this.queries.rebind(this.db.getDb());
505
+ (0, context_1.bindExtractionContext)(this.projectRoot, this.queries);
506
+ // ATTACH is per-connection. ohosApiDbPath survives rebind and
507
+ // would make attachOhosApiDb early-return while the schema
508
+ // is gone → "no such table: ohos_api.nodes". Clear then reattach.
509
+ const priorOhos = this.queries.getOhosApiDbPath();
510
+ this.queries.attachOhosApiDb(null);
511
+ if (priorOhos) {
512
+ this.queries.attachOhosApiDb(priorOhos);
513
+ }
514
+ else if ((0, graph_sources_1.graphSourceFlags)(this.graphSources).sdk) {
515
+ (0, arkts_1.restoreOhosApiDbAttach)(this.queries);
516
+ if (!this.queries.getOhosApiDbPath()) {
517
+ (0, arkts_1.attachExistingOhosApiDbForProject)(this.projectRoot, this.queries);
518
+ }
519
+ }
520
+ },
521
+ },
522
+ }
523
+ : null);
494
524
  }
495
525
  finally {
496
526
  if (freshDb) {
@@ -25,6 +25,18 @@ import type { Edge } from '../types';
25
25
  import type { QueryBuilder } from '../db/queries';
26
26
  import type { ResolutionContext } from './types';
27
27
  import { type MaybeYield } from './cooperative-yield';
28
+ /**
29
+ * Spec 0045 A — ArkUI parent build/@Builder → child @Component/@Builder.
30
+ * Analog of react jsx-child: only static PascalCase call names that resolve to
31
+ * a unique in-repo component/builder; ambiguous names are dropped.
32
+ *
33
+ * Scan the enclosing @Component/@Builder struct (or Builder function) body —
34
+ * not only the `build()` method span — because ArkUI trailing lambdas
35
+ * (`Column() { Child() }`) are often attributed with tight method ranges that
36
+ * omit nested call sites.
37
+ */
38
+ /** @internal exported for PERF kind-scan regression tests (resolveChild name-miss path). */
39
+ export declare function arkuiChildComponentEdges(ctx: ResolutionContext, onYield: MaybeYield): Promise<Edge[]>;
28
40
  /** `has(...)` shape passed to pass gates — true when the project contains any of the languages. */
29
41
  type HasLang = (...ls: string[]) => boolean;
30
42
  /**
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.SYNTH_PROGRESS_STEPS = exports.SYNTH_PASSES = void 0;
37
+ exports.arkuiChildComponentEdges = arkuiChildComponentEdges;
37
38
  exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
38
39
  const generated_detection_1 = require("../extraction/generated-detection");
39
40
  const strip_comments_1 = require("./strip-comments");
@@ -1018,6 +1019,7 @@ function isArkuiBuildParent(n) {
1018
1019
  * (`Column() { Child() }`) are often attributed with tight method ranges that
1019
1020
  * omit nested call sites.
1020
1021
  */
1022
+ /** @internal exported for PERF kind-scan regression tests (resolveChild name-miss path). */
1021
1023
  async function arkuiChildComponentEdges(ctx, onYield) {
1022
1024
  const edges = [];
1023
1025
  const seen = new Set();
@@ -1033,15 +1035,10 @@ async function arkuiChildComponentEdges(ctx, onYield) {
1033
1035
  return '';
1034
1036
  };
1035
1037
  const resolveChild = (name, fromFile, parentId) => {
1038
+ // Name index only — miss returns null. Framework PascalCase calls
1039
+ // (Column/Button/Text/…) dominate misses and must not full-scan kinds
1040
+ // (Spec 0063). Same drop policy as ambiguous multi-match below.
1036
1041
  let candidates = ctx.getNodesByName(name).filter(isArkuiChildTarget);
1037
- if (candidates.length === 0) {
1038
- candidates = [
1039
- ...ctx.getNodesByKind('struct'),
1040
- ...ctx.getNodesByKind('component'),
1041
- ...ctx.getNodesByKind('function'),
1042
- ...ctx.getNodesByKind('method'),
1043
- ].filter((n) => n.name === name && isArkuiChildTarget(n));
1044
- }
1045
1042
  candidates = candidates.filter((n) => n.id !== parentId);
1046
1043
  // Dedupe by id — name index can surface the same row twice across caches.
1047
1044
  candidates = [...new Map(candidates.map((n) => [n.id, n])).values()];
@@ -30,6 +30,11 @@ export declare function isKnownLanguageFamily(lang: string): boolean;
30
30
  * returns `false` here and is left alone.
31
31
  */
32
32
  export declare function crossesKnownFamily(a: string, b: string): boolean;
33
+ /**
34
+ * Spec 0066: resolve a return/param type name to a unique type node.
35
+ * Ambiguous same-named types → no edge (wrong type hub is worse than none).
36
+ */
37
+ export declare function matchTypeEdge(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
33
38
  /**
34
39
  * Resolve a function-as-value reference (#756) — a function name used as a
35
40
  * callback/function-pointer value (`register(handler)`, `o->cb = handler`,