@colbymchenry/codegraph 0.5.7 → 0.6.2

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 (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +682 -681
  3. package/dist/bin/codegraph.js +14 -14
  4. package/dist/bin/codegraph.js.map +1 -1
  5. package/dist/bin/uninstall.d.ts +15 -0
  6. package/dist/bin/uninstall.d.ts.map +1 -0
  7. package/dist/bin/uninstall.js +174 -0
  8. package/dist/bin/uninstall.js.map +1 -0
  9. package/dist/db/migrations.js +11 -11
  10. package/dist/db/queries.js +86 -86
  11. package/dist/db/schema.sql +163 -163
  12. package/dist/directory.js +16 -16
  13. package/dist/extraction/grammars.d.ts.map +1 -1
  14. package/dist/extraction/grammars.js +50 -4
  15. package/dist/extraction/grammars.js.map +1 -1
  16. package/dist/extraction/tree-sitter.d.ts +64 -0
  17. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  18. package/dist/extraction/tree-sitter.js +586 -1
  19. package/dist/extraction/tree-sitter.js.map +1 -1
  20. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  21. package/dist/installer/banner.d.ts +1 -1
  22. package/dist/installer/banner.d.ts.map +1 -1
  23. package/dist/installer/banner.js +12 -10
  24. package/dist/installer/banner.js.map +1 -1
  25. package/dist/installer/claude-md-template.js +32 -32
  26. package/dist/installer/config-writer.d.ts +0 -1
  27. package/dist/installer/config-writer.d.ts.map +1 -1
  28. package/dist/installer/config-writer.js +8 -26
  29. package/dist/installer/config-writer.js.map +1 -1
  30. package/dist/installer/index.d.ts.map +1 -1
  31. package/dist/installer/index.js +11 -26
  32. package/dist/installer/index.js.map +1 -1
  33. package/dist/resolution/import-resolver.d.ts +4 -0
  34. package/dist/resolution/import-resolver.d.ts.map +1 -1
  35. package/dist/resolution/import-resolver.js +9 -0
  36. package/dist/resolution/import-resolver.js.map +1 -1
  37. package/dist/resolution/index.d.ts +1 -1
  38. package/dist/resolution/index.d.ts.map +1 -1
  39. package/dist/resolution/index.js +39 -4
  40. package/dist/resolution/index.js.map +1 -1
  41. package/dist/sync/git-hooks.d.ts +66 -0
  42. package/dist/sync/git-hooks.d.ts.map +1 -0
  43. package/dist/sync/git-hooks.js +281 -0
  44. package/dist/sync/git-hooks.js.map +1 -0
  45. package/dist/types.d.ts +1 -1
  46. package/dist/types.d.ts.map +1 -1
  47. package/dist/types.js +11 -0
  48. package/dist/types.js.map +1 -1
  49. package/dist/vectors/search.js +33 -33
  50. package/package.json +60 -59
  51. package/scripts/patch-tree-sitter-dart.js +112 -112
  52. package/scripts/postinstall.js +68 -68
@@ -38,7 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  };
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
- exports.SvelteExtractor = exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
41
+ exports.DfmExtractor = exports.SvelteExtractor = exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
42
42
  exports.generateNodeId = generateNodeId;
43
43
  exports.extractFromSource = extractFromSource;
44
44
  const crypto = __importStar(require("crypto"));
@@ -767,6 +767,67 @@ const EXTRACTORS = {
767
767
  return false;
768
768
  },
769
769
  },
770
+ pascal: {
771
+ functionTypes: ['declProc'],
772
+ classTypes: ['declClass'],
773
+ methodTypes: ['declProc'],
774
+ interfaceTypes: ['declIntf'],
775
+ structTypes: [],
776
+ enumTypes: ['declEnum'],
777
+ typeAliasTypes: ['declType'],
778
+ importTypes: ['declUses'],
779
+ callTypes: ['exprCall'],
780
+ variableTypes: ['declField', 'declConst'],
781
+ nameField: 'name',
782
+ bodyField: 'body',
783
+ paramsField: 'args',
784
+ returnField: 'type',
785
+ getSignature: (node, source) => {
786
+ const args = getChildByField(node, 'args');
787
+ const returnType = node.namedChildren.find((c) => c.type === 'typeref');
788
+ if (!args && !returnType)
789
+ return undefined;
790
+ let sig = '';
791
+ if (args)
792
+ sig = getNodeText(args, source);
793
+ if (returnType) {
794
+ sig += ': ' + getNodeText(returnType, source);
795
+ }
796
+ return sig || undefined;
797
+ },
798
+ getVisibility: (node) => {
799
+ let current = node.parent;
800
+ while (current) {
801
+ if (current.type === 'declSection') {
802
+ for (let i = 0; i < current.childCount; i++) {
803
+ const child = current.child(i);
804
+ if (child?.type === 'kPublic' || child?.type === 'kPublished')
805
+ return 'public';
806
+ if (child?.type === 'kPrivate')
807
+ return 'private';
808
+ if (child?.type === 'kProtected')
809
+ return 'protected';
810
+ }
811
+ }
812
+ current = current.parent;
813
+ }
814
+ return undefined;
815
+ },
816
+ isExported: (_node, _source) => {
817
+ // In Pascal, symbols declared in the interface section are exported
818
+ return false;
819
+ },
820
+ isStatic: (node) => {
821
+ for (let i = 0; i < node.childCount; i++) {
822
+ if (node.child(i)?.type === 'kClass')
823
+ return true;
824
+ }
825
+ return false;
826
+ },
827
+ isConst: (node) => {
828
+ return node.type === 'declConst';
829
+ },
830
+ },
770
831
  };
771
832
  // TSX and JSX use the same extractors as their base languages
772
833
  EXTRACTORS.tsx = EXTRACTORS.typescript;
@@ -831,6 +892,7 @@ class TreeSitterExtractor {
831
892
  errors = [];
832
893
  extractor = null;
833
894
  nodeStack = []; // Stack of parent node IDs
895
+ methodIndex = null; // lookup key → node ID for Pascal defProc lookup
834
896
  constructor(filePath, source, language) {
835
897
  this.filePath = filePath;
836
898
  this.source = source;
@@ -920,6 +982,12 @@ class TreeSitterExtractor {
920
982
  return;
921
983
  const nodeType = node.type;
922
984
  let skipChildren = false;
985
+ // Pascal-specific AST handling
986
+ if (this.language === 'pascal') {
987
+ skipChildren = this.visitPascalNode(node);
988
+ if (skipChildren)
989
+ return;
990
+ }
923
991
  // Check for function declarations
924
992
  // For Python/Ruby, function_definition inside a class should be treated as method
925
993
  if (this.extractor.functionTypes.includes(nodeType)) {
@@ -1913,6 +1981,370 @@ class TreeSitterExtractor {
1913
1981
  }
1914
1982
  }
1915
1983
  }
1984
+ /**
1985
+ * Handle Pascal-specific AST structures.
1986
+ * Returns true if the node was fully handled and children should be skipped.
1987
+ */
1988
+ visitPascalNode(node) {
1989
+ const nodeType = node.type;
1990
+ // Unit/Program/Library → module node
1991
+ if (nodeType === 'unit' || nodeType === 'program' || nodeType === 'library') {
1992
+ const moduleNameNode = node.namedChildren.find((c) => c.type === 'moduleName');
1993
+ const name = moduleNameNode ? getNodeText(moduleNameNode, this.source) : '';
1994
+ // Fallback to filename without extension if module name is empty
1995
+ const moduleName = name || path.basename(this.filePath).replace(/\.[^.]+$/, '');
1996
+ this.createNode('module', moduleName, node);
1997
+ // Continue visiting children (interface/implementation sections)
1998
+ for (let i = 0; i < node.namedChildCount; i++) {
1999
+ const child = node.namedChild(i);
2000
+ if (child)
2001
+ this.visitNode(child);
2002
+ }
2003
+ return true;
2004
+ }
2005
+ // declType wraps declClass/declIntf/declEnum/type-alias
2006
+ // The name lives on declType, the inner node determines the kind
2007
+ if (nodeType === 'declType') {
2008
+ this.extractPascalDeclType(node);
2009
+ return true;
2010
+ }
2011
+ // declUses → import nodes for each unit name
2012
+ if (nodeType === 'declUses') {
2013
+ this.extractPascalUses(node);
2014
+ return true;
2015
+ }
2016
+ // declConsts → container; visit children for individual declConst
2017
+ if (nodeType === 'declConsts') {
2018
+ for (let i = 0; i < node.namedChildCount; i++) {
2019
+ const child = node.namedChild(i);
2020
+ if (child?.type === 'declConst') {
2021
+ this.extractPascalConst(child);
2022
+ }
2023
+ }
2024
+ return true;
2025
+ }
2026
+ // declConst at top level (outside declConsts)
2027
+ if (nodeType === 'declConst') {
2028
+ this.extractPascalConst(node);
2029
+ return true;
2030
+ }
2031
+ // declTypes → container for type declarations
2032
+ if (nodeType === 'declTypes') {
2033
+ for (let i = 0; i < node.namedChildCount; i++) {
2034
+ const child = node.namedChild(i);
2035
+ if (child)
2036
+ this.visitNode(child);
2037
+ }
2038
+ return true;
2039
+ }
2040
+ // declVars → container for variable declarations
2041
+ if (nodeType === 'declVars') {
2042
+ for (let i = 0; i < node.namedChildCount; i++) {
2043
+ const child = node.namedChild(i);
2044
+ if (child?.type === 'declVar') {
2045
+ const nameNode = getChildByField(child, 'name');
2046
+ if (nameNode) {
2047
+ const name = getNodeText(nameNode, this.source);
2048
+ this.createNode('variable', name, child);
2049
+ }
2050
+ }
2051
+ }
2052
+ return true;
2053
+ }
2054
+ // defProc in implementation section → extract calls but don't create duplicate nodes
2055
+ if (nodeType === 'defProc') {
2056
+ this.extractPascalDefProc(node);
2057
+ return true;
2058
+ }
2059
+ // declProp → property node
2060
+ if (nodeType === 'declProp') {
2061
+ const nameNode = getChildByField(node, 'name');
2062
+ if (nameNode) {
2063
+ const name = getNodeText(nameNode, this.source);
2064
+ const visibility = this.extractor.getVisibility?.(node);
2065
+ this.createNode('property', name, node, { visibility });
2066
+ }
2067
+ return true;
2068
+ }
2069
+ // declField → field node
2070
+ if (nodeType === 'declField') {
2071
+ const nameNode = getChildByField(node, 'name');
2072
+ if (nameNode) {
2073
+ const name = getNodeText(nameNode, this.source);
2074
+ const visibility = this.extractor.getVisibility?.(node);
2075
+ this.createNode('field', name, node, { visibility });
2076
+ }
2077
+ return true;
2078
+ }
2079
+ // declSection → visit children (propagates visibility via getVisibility)
2080
+ if (nodeType === 'declSection') {
2081
+ for (let i = 0; i < node.namedChildCount; i++) {
2082
+ const child = node.namedChild(i);
2083
+ if (child)
2084
+ this.visitNode(child);
2085
+ }
2086
+ return true;
2087
+ }
2088
+ // exprCall → extract function call reference
2089
+ if (nodeType === 'exprCall') {
2090
+ this.extractPascalCall(node);
2091
+ return true;
2092
+ }
2093
+ // interface/implementation sections → visit children
2094
+ if (nodeType === 'interface' || nodeType === 'implementation') {
2095
+ for (let i = 0; i < node.namedChildCount; i++) {
2096
+ const child = node.namedChild(i);
2097
+ if (child)
2098
+ this.visitNode(child);
2099
+ }
2100
+ return true;
2101
+ }
2102
+ // block (begin..end) → visit for calls
2103
+ if (nodeType === 'block') {
2104
+ this.visitPascalBlock(node);
2105
+ return true;
2106
+ }
2107
+ return false;
2108
+ }
2109
+ /**
2110
+ * Extract a Pascal declType node (class, interface, enum, or type alias)
2111
+ */
2112
+ extractPascalDeclType(node) {
2113
+ const nameNode = getChildByField(node, 'name');
2114
+ if (!nameNode)
2115
+ return;
2116
+ const name = getNodeText(nameNode, this.source);
2117
+ // Find the inner type declaration
2118
+ const declClass = node.namedChildren.find((c) => c.type === 'declClass');
2119
+ const declIntf = node.namedChildren.find((c) => c.type === 'declIntf');
2120
+ const typeChild = node.namedChildren.find((c) => c.type === 'type');
2121
+ if (declClass) {
2122
+ const classNode = this.createNode('class', name, node);
2123
+ // Extract inheritance from typeref children of declClass
2124
+ this.extractPascalInheritance(declClass, classNode.id);
2125
+ // Visit class body
2126
+ this.nodeStack.push(classNode.id);
2127
+ for (let i = 0; i < declClass.namedChildCount; i++) {
2128
+ const child = declClass.namedChild(i);
2129
+ if (child)
2130
+ this.visitNode(child);
2131
+ }
2132
+ this.nodeStack.pop();
2133
+ }
2134
+ else if (declIntf) {
2135
+ const ifaceNode = this.createNode('interface', name, node);
2136
+ // Visit interface members
2137
+ this.nodeStack.push(ifaceNode.id);
2138
+ for (let i = 0; i < declIntf.namedChildCount; i++) {
2139
+ const child = declIntf.namedChild(i);
2140
+ if (child)
2141
+ this.visitNode(child);
2142
+ }
2143
+ this.nodeStack.pop();
2144
+ }
2145
+ else if (typeChild) {
2146
+ // Check if it contains a declEnum
2147
+ const declEnum = typeChild.namedChildren.find((c) => c.type === 'declEnum');
2148
+ if (declEnum) {
2149
+ const enumNode = this.createNode('enum', name, node);
2150
+ // Extract enum members
2151
+ this.nodeStack.push(enumNode.id);
2152
+ for (let i = 0; i < declEnum.namedChildCount; i++) {
2153
+ const child = declEnum.namedChild(i);
2154
+ if (child?.type === 'declEnumValue') {
2155
+ const memberName = getChildByField(child, 'name');
2156
+ if (memberName) {
2157
+ this.createNode('enum_member', getNodeText(memberName, this.source), child);
2158
+ }
2159
+ }
2160
+ }
2161
+ this.nodeStack.pop();
2162
+ }
2163
+ else {
2164
+ // Simple type alias: type TFoo = string / type TFoo = Integer
2165
+ this.createNode('type_alias', name, node);
2166
+ }
2167
+ }
2168
+ else {
2169
+ // Fallback: could be a forward declaration or simple alias
2170
+ this.createNode('type_alias', name, node);
2171
+ }
2172
+ }
2173
+ /**
2174
+ * Extract Pascal uses clause into individual import nodes
2175
+ */
2176
+ extractPascalUses(node) {
2177
+ const importText = getNodeText(node, this.source).trim();
2178
+ for (let i = 0; i < node.namedChildCount; i++) {
2179
+ const child = node.namedChild(i);
2180
+ if (child?.type === 'moduleName') {
2181
+ const unitName = getNodeText(child, this.source);
2182
+ this.createNode('import', unitName, child, {
2183
+ signature: importText,
2184
+ });
2185
+ // Create unresolved reference for resolution
2186
+ if (this.nodeStack.length > 0) {
2187
+ const parentId = this.nodeStack[this.nodeStack.length - 1];
2188
+ if (parentId) {
2189
+ this.unresolvedReferences.push({
2190
+ fromNodeId: parentId,
2191
+ referenceName: unitName,
2192
+ referenceKind: 'imports',
2193
+ line: child.startPosition.row + 1,
2194
+ column: child.startPosition.column,
2195
+ });
2196
+ }
2197
+ }
2198
+ }
2199
+ }
2200
+ }
2201
+ /**
2202
+ * Extract a Pascal constant declaration
2203
+ */
2204
+ extractPascalConst(node) {
2205
+ const nameNode = getChildByField(node, 'name');
2206
+ if (!nameNode)
2207
+ return;
2208
+ const name = getNodeText(nameNode, this.source);
2209
+ const defaultValue = node.namedChildren.find((c) => c.type === 'defaultValue');
2210
+ const sig = defaultValue ? getNodeText(defaultValue, this.source) : undefined;
2211
+ this.createNode('constant', name, node, { signature: sig });
2212
+ }
2213
+ /**
2214
+ * Extract Pascal inheritance (extends/implements) from declClass typeref children
2215
+ */
2216
+ extractPascalInheritance(declClass, classId) {
2217
+ const typerefs = declClass.namedChildren.filter((c) => c.type === 'typeref');
2218
+ for (let i = 0; i < typerefs.length; i++) {
2219
+ const ref = typerefs[i];
2220
+ const name = getNodeText(ref, this.source);
2221
+ this.unresolvedReferences.push({
2222
+ fromNodeId: classId,
2223
+ referenceName: name,
2224
+ referenceKind: i === 0 ? 'extends' : 'implements',
2225
+ line: ref.startPosition.row + 1,
2226
+ column: ref.startPosition.column,
2227
+ });
2228
+ }
2229
+ }
2230
+ /**
2231
+ * Extract calls and resolve method context from a Pascal defProc (implementation body).
2232
+ * Does not create a new node — the declaration was already captured from the interface section.
2233
+ */
2234
+ extractPascalDefProc(node) {
2235
+ // Find the matching declaration node by name to use as call parent
2236
+ const declProc = node.namedChildren.find((c) => c.type === 'declProc');
2237
+ if (!declProc)
2238
+ return;
2239
+ const nameNode = getChildByField(declProc, 'name');
2240
+ if (!nameNode)
2241
+ return;
2242
+ const fullName = getNodeText(nameNode, this.source).trim();
2243
+ // fullName is like "TAuthService.Create"
2244
+ const shortName = fullName.includes('.') ? fullName.split('.').pop() : fullName;
2245
+ const fullNameKey = fullName.toLowerCase();
2246
+ const shortNameKey = shortName.toLowerCase();
2247
+ // Build method index on first use (O(n) once, then O(1) per lookup)
2248
+ if (!this.methodIndex) {
2249
+ this.methodIndex = new Map();
2250
+ for (const n of this.nodes) {
2251
+ if (n.kind === 'method' || n.kind === 'function') {
2252
+ const nameKey = n.name.toLowerCase();
2253
+ // Keep first seen short-name mapping to avoid silently overwriting earlier entries.
2254
+ if (!this.methodIndex.has(nameKey)) {
2255
+ this.methodIndex.set(nameKey, n.id);
2256
+ }
2257
+ // For Pascal methods, also index qualified forms (e.g. TAuthService.Create).
2258
+ if (n.kind === 'method') {
2259
+ const qualifiedParts = n.qualifiedName.split('::').slice(1); // drop file path
2260
+ if (qualifiedParts.length >= 2) {
2261
+ // Create suffix keys so both "Module.Class.Method" and "Class.Method" can resolve.
2262
+ for (let i = 0; i < qualifiedParts.length - 1; i++) {
2263
+ const scopedName = qualifiedParts.slice(i).join('.').toLowerCase();
2264
+ this.methodIndex.set(scopedName, n.id);
2265
+ }
2266
+ }
2267
+ }
2268
+ }
2269
+ }
2270
+ }
2271
+ const parentId = this.methodIndex.get(fullNameKey) ||
2272
+ this.methodIndex.get(shortNameKey) ||
2273
+ this.nodeStack[this.nodeStack.length - 1];
2274
+ if (!parentId)
2275
+ return;
2276
+ // Visit the block for calls
2277
+ const block = node.namedChildren.find((c) => c.type === 'block');
2278
+ if (block) {
2279
+ this.nodeStack.push(parentId);
2280
+ this.visitPascalBlock(block);
2281
+ this.nodeStack.pop();
2282
+ }
2283
+ }
2284
+ /**
2285
+ * Extract function calls from a Pascal expression
2286
+ */
2287
+ extractPascalCall(node) {
2288
+ if (this.nodeStack.length === 0)
2289
+ return;
2290
+ const callerId = this.nodeStack[this.nodeStack.length - 1];
2291
+ if (!callerId)
2292
+ return;
2293
+ // Get the callee name — first child is typically the identifier or exprDot
2294
+ const firstChild = node.namedChild(0);
2295
+ if (!firstChild)
2296
+ return;
2297
+ let calleeName = '';
2298
+ if (firstChild.type === 'exprDot') {
2299
+ // Qualified call: Obj.Method(...)
2300
+ const identifiers = firstChild.namedChildren.filter((c) => c.type === 'identifier');
2301
+ if (identifiers.length > 0) {
2302
+ calleeName = identifiers.map((id) => getNodeText(id, this.source)).join('.');
2303
+ }
2304
+ }
2305
+ else if (firstChild.type === 'identifier') {
2306
+ calleeName = getNodeText(firstChild, this.source);
2307
+ }
2308
+ if (calleeName) {
2309
+ this.unresolvedReferences.push({
2310
+ fromNodeId: callerId,
2311
+ referenceName: calleeName,
2312
+ referenceKind: 'calls',
2313
+ line: node.startPosition.row + 1,
2314
+ column: node.startPosition.column,
2315
+ });
2316
+ }
2317
+ // Also visit arguments for nested calls
2318
+ const args = node.namedChildren.find((c) => c.type === 'exprArgs');
2319
+ if (args) {
2320
+ this.visitPascalBlock(args);
2321
+ }
2322
+ }
2323
+ /**
2324
+ * Recursively visit a Pascal block/statement tree for call expressions
2325
+ */
2326
+ visitPascalBlock(node) {
2327
+ for (let i = 0; i < node.namedChildCount; i++) {
2328
+ const child = node.namedChild(i);
2329
+ if (!child)
2330
+ continue;
2331
+ if (child.type === 'exprCall') {
2332
+ this.extractPascalCall(child);
2333
+ }
2334
+ else if (child.type === 'exprDot') {
2335
+ // Check if exprDot contains an exprCall
2336
+ for (let j = 0; j < child.namedChildCount; j++) {
2337
+ const grandchild = child.namedChild(j);
2338
+ if (grandchild?.type === 'exprCall') {
2339
+ this.extractPascalCall(grandchild);
2340
+ }
2341
+ }
2342
+ }
2343
+ else {
2344
+ this.visitPascalBlock(child);
2345
+ }
2346
+ }
2347
+ }
1916
2348
  }
1917
2349
  exports.TreeSitterExtractor = TreeSitterExtractor;
1918
2350
  /**
@@ -2377,11 +2809,158 @@ class SvelteExtractor {
2377
2809
  }
2378
2810
  }
2379
2811
  exports.SvelteExtractor = SvelteExtractor;
2812
+ /**
2813
+ * Custom extractor for Delphi DFM/FMX form files.
2814
+ *
2815
+ * DFM/FMX files describe the visual component hierarchy and event handler
2816
+ * bindings. They use a simple text format (object/end blocks) that we parse
2817
+ * with regex — no tree-sitter grammar exists for this format.
2818
+ *
2819
+ * Extracted information:
2820
+ * - Components as NodeKind `component`
2821
+ * - Nesting as EdgeKind `contains`
2822
+ * - Event handlers (OnClick = MethodName) as UnresolvedReference → EdgeKind `references`
2823
+ */
2824
+ class DfmExtractor {
2825
+ filePath;
2826
+ source;
2827
+ nodes = [];
2828
+ edges = [];
2829
+ unresolvedReferences = [];
2830
+ errors = [];
2831
+ constructor(filePath, source) {
2832
+ this.filePath = filePath;
2833
+ this.source = source;
2834
+ }
2835
+ /**
2836
+ * Extract components and event handler references from DFM/FMX source
2837
+ */
2838
+ extract() {
2839
+ const startTime = Date.now();
2840
+ try {
2841
+ const fileNode = this.createFileNode();
2842
+ this.parseComponents(fileNode.id);
2843
+ }
2844
+ catch (error) {
2845
+ (0, sentry_1.captureException)(error, { operation: 'dfm-extraction', filePath: this.filePath });
2846
+ this.errors.push({
2847
+ message: `DFM extraction error: ${error instanceof Error ? error.message : String(error)}`,
2848
+ severity: 'error',
2849
+ });
2850
+ }
2851
+ return {
2852
+ nodes: this.nodes,
2853
+ edges: this.edges,
2854
+ unresolvedReferences: this.unresolvedReferences,
2855
+ errors: this.errors,
2856
+ durationMs: Date.now() - startTime,
2857
+ };
2858
+ }
2859
+ /** Create a file node for the DFM form file */
2860
+ createFileNode() {
2861
+ const lines = this.source.split('\n');
2862
+ const id = generateNodeId(this.filePath, 'file', this.filePath, 1);
2863
+ const fileNode = {
2864
+ id,
2865
+ kind: 'file',
2866
+ name: this.filePath.split('/').pop() || this.filePath,
2867
+ qualifiedName: this.filePath,
2868
+ filePath: this.filePath,
2869
+ language: 'pascal',
2870
+ startLine: 1,
2871
+ endLine: lines.length,
2872
+ startColumn: 0,
2873
+ endColumn: lines[lines.length - 1]?.length || 0,
2874
+ updatedAt: Date.now(),
2875
+ };
2876
+ this.nodes.push(fileNode);
2877
+ return fileNode;
2878
+ }
2879
+ /** Parse object/end blocks and extract components + event handlers */
2880
+ parseComponents(fileNodeId) {
2881
+ const lines = this.source.split('\n');
2882
+ const stack = [fileNodeId];
2883
+ const objectPattern = /^\s*(object|inherited|inline)\s+(\w+)\s*:\s*(\w+)/;
2884
+ const eventPattern = /^\s*(On\w+)\s*=\s*(\w+)\s*$/;
2885
+ const endPattern = /^\s*end\s*$/;
2886
+ const multiLineStart = /=\s*\(\s*$/;
2887
+ const multiLineItemStart = /=\s*<\s*$/;
2888
+ let inMultiLine = false;
2889
+ let multiLineEndChar = ')';
2890
+ for (let i = 0; i < lines.length; i++) {
2891
+ const line = lines[i];
2892
+ const lineNum = i + 1;
2893
+ // Skip multi-line properties
2894
+ if (inMultiLine) {
2895
+ if (line.trimEnd().endsWith(multiLineEndChar))
2896
+ inMultiLine = false;
2897
+ continue;
2898
+ }
2899
+ if (multiLineStart.test(line)) {
2900
+ inMultiLine = true;
2901
+ multiLineEndChar = ')';
2902
+ continue;
2903
+ }
2904
+ if (multiLineItemStart.test(line)) {
2905
+ inMultiLine = true;
2906
+ multiLineEndChar = '>';
2907
+ continue;
2908
+ }
2909
+ // Component declaration
2910
+ const objMatch = line.match(objectPattern);
2911
+ if (objMatch) {
2912
+ const [, , name, typeName] = objMatch;
2913
+ const nodeId = generateNodeId(this.filePath, 'component', name, lineNum);
2914
+ this.nodes.push({
2915
+ id: nodeId,
2916
+ kind: 'component',
2917
+ name: name,
2918
+ qualifiedName: `${this.filePath}#${name}`,
2919
+ filePath: this.filePath,
2920
+ language: 'pascal',
2921
+ startLine: lineNum,
2922
+ endLine: lineNum,
2923
+ startColumn: 0,
2924
+ endColumn: line.length,
2925
+ signature: typeName,
2926
+ updatedAt: Date.now(),
2927
+ });
2928
+ this.edges.push({
2929
+ source: stack[stack.length - 1],
2930
+ target: nodeId,
2931
+ kind: 'contains',
2932
+ });
2933
+ stack.push(nodeId);
2934
+ continue;
2935
+ }
2936
+ // Event handler
2937
+ const eventMatch = line.match(eventPattern);
2938
+ if (eventMatch) {
2939
+ const [, , methodName] = eventMatch;
2940
+ this.unresolvedReferences.push({
2941
+ fromNodeId: stack[stack.length - 1],
2942
+ referenceName: methodName,
2943
+ referenceKind: 'references',
2944
+ line: lineNum,
2945
+ column: 0,
2946
+ });
2947
+ continue;
2948
+ }
2949
+ // Block end
2950
+ if (endPattern.test(line)) {
2951
+ if (stack.length > 1)
2952
+ stack.pop();
2953
+ }
2954
+ }
2955
+ }
2956
+ }
2957
+ exports.DfmExtractor = DfmExtractor;
2380
2958
  /**
2381
2959
  * Extract nodes and edges from source code
2382
2960
  */
2383
2961
  function extractFromSource(filePath, source, language) {
2384
2962
  const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
2963
+ const fileExtension = path.extname(filePath).toLowerCase();
2385
2964
  // Use custom extractor for Svelte
2386
2965
  if (detectedLanguage === 'svelte') {
2387
2966
  const extractor = new SvelteExtractor(filePath, source);
@@ -2392,6 +2971,12 @@ function extractFromSource(filePath, source, language) {
2392
2971
  const extractor = new LiquidExtractor(filePath, source);
2393
2972
  return extractor.extract();
2394
2973
  }
2974
+ // Use custom extractor for DFM/FMX form files
2975
+ if (detectedLanguage === 'pascal' &&
2976
+ (fileExtension === '.dfm' || fileExtension === '.fmx')) {
2977
+ const extractor = new DfmExtractor(filePath, source);
2978
+ return extractor.extract();
2979
+ }
2395
2980
  const extractor = new TreeSitterExtractor(filePath, source, detectedLanguage);
2396
2981
  return extractor.extract();
2397
2982
  }