mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
grounding and prompt enhancement
This commit is contained in:
parent
7a820068ba
commit
034f6ab37f
16 changed files with 816 additions and 80 deletions
294
package-lock.json
generated
294
package-lock.json
generated
|
|
@ -37,6 +37,7 @@
|
|||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sigma": "^3.0.2",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"uuid": "^13.0.0",
|
||||
|
|
@ -7088,6 +7089,16 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/markdown-table": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
||||
"integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "16.4.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz",
|
||||
|
|
@ -7121,6 +7132,34 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-find-and-replace": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
|
||||
"integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"unist-util-is": "^6.0.0",
|
||||
"unist-util-visit-parents": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-from-markdown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
|
||||
|
|
@ -7145,6 +7184,107 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
|
||||
"integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-gfm-autolink-literal": "^2.0.0",
|
||||
"mdast-util-gfm-footnote": "^2.0.0",
|
||||
"mdast-util-gfm-strikethrough": "^2.0.0",
|
||||
"mdast-util-gfm-table": "^2.0.0",
|
||||
"mdast-util-gfm-task-list-item": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-autolink-literal": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
|
||||
"integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"ccount": "^2.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"mdast-util-find-and-replace": "^3.0.0",
|
||||
"micromark-util-character": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-footnote": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
|
||||
"integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"devlop": "^1.1.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0",
|
||||
"micromark-util-normalize-identifier": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-strikethrough": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
|
||||
"integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-table": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
|
||||
"integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"markdown-table": "^3.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-gfm-task-list-item": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
|
||||
"integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-mdx-expression": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
|
||||
|
|
@ -7394,6 +7534,127 @@
|
|||
"micromark-util-types": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
|
||||
"integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"micromark-extension-gfm-autolink-literal": "^2.0.0",
|
||||
"micromark-extension-gfm-footnote": "^2.0.0",
|
||||
"micromark-extension-gfm-strikethrough": "^2.0.0",
|
||||
"micromark-extension-gfm-table": "^2.0.0",
|
||||
"micromark-extension-gfm-tagfilter": "^2.0.0",
|
||||
"micromark-extension-gfm-task-list-item": "^2.0.0",
|
||||
"micromark-util-combine-extensions": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-autolink-literal": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
|
||||
"integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-sanitize-uri": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-footnote": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
|
||||
"integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-core-commonmark": "^2.0.0",
|
||||
"micromark-factory-space": "^2.0.0",
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-normalize-identifier": "^2.0.0",
|
||||
"micromark-util-sanitize-uri": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-strikethrough": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
|
||||
"integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-util-chunked": "^2.0.0",
|
||||
"micromark-util-classify-character": "^2.0.0",
|
||||
"micromark-util-resolve-all": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-table": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
|
||||
"integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-factory-space": "^2.0.0",
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-tagfilter": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
|
||||
"integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-task-list-item": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
|
||||
"integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-factory-space": "^2.0.0",
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-factory-destination": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
|
||||
|
|
@ -8597,6 +8858,24 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-gfm": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
||||
"integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-gfm": "^3.0.0",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"unified": "^11.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-parse": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||
|
|
@ -8630,6 +8909,21 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-stringify": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
|
||||
"integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0",
|
||||
"unified": "^11.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sigma": "^3.0.2",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"uuid": "^13.0.0",
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ const EXAMPLE_QUERIES = [
|
|||
|
||||
export const QueryFAB = () => {
|
||||
const { setHighlightedNodeIds, setQueryResult, queryResult, clearQueryHighlights, graph, runQuery, isDatabaseReady } = useAppState();
|
||||
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showExamples, setShowExamples] = useState(false);
|
||||
const [showResults, setShowResults] = useState(true);
|
||||
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ export const QueryFAB = () => {
|
|||
|
||||
const handleRunQuery = useCallback(async () => {
|
||||
if (!query.trim() || isRunning) return;
|
||||
|
||||
|
||||
if (!graph) {
|
||||
setError('No project loaded. Load a project first.');
|
||||
return;
|
||||
|
|
@ -81,25 +81,52 @@ export const QueryFAB = () => {
|
|||
|
||||
setIsRunning(true);
|
||||
setError(null);
|
||||
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
|
||||
try {
|
||||
const rows = await runQuery(query);
|
||||
const executionTime = performance.now() - startTime;
|
||||
|
||||
// Extract node IDs - handle both array and object formats
|
||||
// First column should be the ID when using example queries
|
||||
|
||||
// Extract node IDs from results - handles various formats
|
||||
// 1. Array format: first element if it looks like a node ID
|
||||
// 2. Object format: any field ending with 'id' (case-insensitive)
|
||||
// 3. Values matching node ID pattern: Label:path:name
|
||||
const nodeIdPattern = /^(File|Function|Class|Method|Interface|Folder|CodeElement):/;
|
||||
|
||||
const nodeIds = rows
|
||||
.flatMap(row => {
|
||||
const ids: string[] = [];
|
||||
|
||||
if (Array.isArray(row)) {
|
||||
return [row[0]];
|
||||
// Array format - check all elements for node ID patterns
|
||||
row.forEach(val => {
|
||||
if (typeof val === 'string' && (nodeIdPattern.test(val) || val.includes(':'))) {
|
||||
ids.push(val);
|
||||
}
|
||||
});
|
||||
} else if (typeof row === 'object' && row !== null) {
|
||||
// Object format - check fields ending with 'id' and values matching patterns
|
||||
Object.entries(row).forEach(([key, val]) => {
|
||||
const keyLower = key.toLowerCase();
|
||||
if (typeof val === 'string') {
|
||||
// Field name contains 'id'
|
||||
if (keyLower.includes('id') || keyLower === 'id') {
|
||||
ids.push(val);
|
||||
}
|
||||
// Value matches node ID pattern
|
||||
else if (nodeIdPattern.test(val)) {
|
||||
ids.push(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return [row.id, row.ID, row['n.id'], row['a.id'], row['b.id']].filter(Boolean);
|
||||
|
||||
return ids;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter((id, index, arr) => arr.indexOf(id) === index);
|
||||
|
||||
|
||||
setQueryResult({ rows, nodeIds, executionTime });
|
||||
setHighlightedNodeIds(new Set(nodeIds));
|
||||
} catch (err) {
|
||||
|
|
@ -338,7 +365,7 @@ export const QueryFAB = () => {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{showResults && queryResult.rows.length > 0 && (
|
||||
<div className="max-h-48 overflow-auto scrollbar-thin border-t border-border-subtle">
|
||||
<table className="w-full text-xs">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { useAppState } from '../hooks/useAppState';
|
||||
import { ToolCallCard } from './ToolCallCard';
|
||||
import { isProviderConfigured } from '../core/llm/settings-service';
|
||||
|
|
@ -97,7 +98,8 @@ export const RightPanel = () => {
|
|||
let startLine1: number | undefined;
|
||||
let endLine1: number | undefined;
|
||||
|
||||
const lineMatch = raw.match(/^(.*):(\d+)(?:-(\d+))?$/);
|
||||
// Match line:num or line:num-num (supports both hyphen - and en dash –)
|
||||
const lineMatch = raw.match(/^(.*):(\d+)(?:[-–](\d+))?$/);
|
||||
if (lineMatch) {
|
||||
rawPath = lineMatch[1].trim();
|
||||
startLine1 = parseInt(lineMatch[2], 10);
|
||||
|
|
@ -120,15 +122,75 @@ export const RightPanel = () => {
|
|||
});
|
||||
}, [addCodeReference, findFileNodeIdForUI, resolveFilePathForUI]);
|
||||
|
||||
// Handler for node grounding: [[Class:View]], [[Function:trigger]], etc.
|
||||
const handleNodeGroundingClick = useCallback((nodeTypeAndName: string) => {
|
||||
const raw = nodeTypeAndName.trim();
|
||||
if (!raw || !graph) return;
|
||||
|
||||
// Parse Type:Name format
|
||||
const match = raw.match(/^(Class|Function|Method|Interface|File|Folder|Variable|Enum|Type|CodeElement):(.+)$/);
|
||||
if (!match) return;
|
||||
|
||||
const [, nodeType, nodeName] = match;
|
||||
const trimmedName = nodeName.trim();
|
||||
|
||||
// Find node in graph by type + name
|
||||
const node = graph.nodes.find(n =>
|
||||
n.label === nodeType &&
|
||||
n.properties.name === trimmedName
|
||||
);
|
||||
|
||||
if (!node) {
|
||||
console.warn(`Node not found: ${nodeType}:${trimmedName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Highlight in graph (add to AI citation highlights)
|
||||
// Note: This requires accessing the state setter from parent context
|
||||
// For now, we'll add to code references which triggers the highlight
|
||||
|
||||
// 2. Add to Code Panel (if node has file/line info)
|
||||
if (node.properties.filePath) {
|
||||
const resolvedPath = resolveFilePathForUI(node.properties.filePath);
|
||||
if (resolvedPath) {
|
||||
addCodeReference({
|
||||
filePath: resolvedPath,
|
||||
startLine: node.properties.startLine ? node.properties.startLine - 1 : undefined,
|
||||
endLine: node.properties.endLine ? node.properties.endLine - 1 : undefined,
|
||||
nodeId: node.id,
|
||||
label: node.label,
|
||||
name: node.properties.name,
|
||||
source: 'ai',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [graph, resolveFilePathForUI, addCodeReference]);
|
||||
|
||||
const formatMarkdownForDisplay = useCallback((md: string) => {
|
||||
// Avoid rewriting inside fenced code blocks.
|
||||
const parts = md.split('```');
|
||||
for (let i = 0; i < parts.length; i += 2) {
|
||||
parts[i] = parts[i].replace(/\[\[([^\]\n]+?)\]\]/g, (_m, inner: string) => {
|
||||
const trimmed = inner.trim();
|
||||
const href = `code-ref:${encodeURIComponent(trimmed)}`;
|
||||
return `[${trimmed}](${href})`;
|
||||
});
|
||||
// Pattern 1: File grounding - [[file.ext]] or [[file.ext:line]] or [[file.ext:line-line]]
|
||||
// Line numbers are optional
|
||||
parts[i] = parts[i].replace(
|
||||
/\[\[([a-zA-Z0-9_\-./\\]+\.[a-zA-Z0-9]+(?::\d+(?:[-–]\d+)?)?)\]\]/g,
|
||||
(_m, inner: string) => {
|
||||
const trimmed = inner.trim();
|
||||
const href = `code-ref:${encodeURIComponent(trimmed)}`;
|
||||
return `[${trimmed}](${href})`;
|
||||
}
|
||||
);
|
||||
|
||||
// Pattern 2: Node grounding - [[Type:Name]] or [[graph:Type:Name]]
|
||||
// Valid types: Class, Function, Method, Interface, File, Folder, Variable, Enum, Type, CodeElement
|
||||
parts[i] = parts[i].replace(
|
||||
/\[\[(?:graph:)?(Class|Function|Method|Interface|File|Folder|Variable|Enum|Type|CodeElement):([^\]]+)\]\]/g,
|
||||
(_m, nodeType: string, nodeName: string) => {
|
||||
const trimmed = `${nodeType}:${nodeName.trim()}`;
|
||||
const href = `node-ref:${encodeURIComponent(trimmed)}`;
|
||||
return `[${trimmed}](${href})`;
|
||||
}
|
||||
);
|
||||
}
|
||||
return parts.join('```');
|
||||
}, []);
|
||||
|
|
@ -143,7 +205,8 @@ export const RightPanel = () => {
|
|||
// Strip query/hash
|
||||
const cleaned = withoutScheme.split('#')[0].split('?')[0];
|
||||
|
||||
const m = cleaned.match(/^(.*):(\d+)(?:-(\d+))?$/);
|
||||
// Support both hyphen - and en dash – for line ranges
|
||||
const m = cleaned.match(/^(.*):(\d+)(?:[-–](\d+))?$/);
|
||||
const path = (m ? m[1] : cleaned).replace(/\\/g, '/');
|
||||
const base = path.split('/').pop() ?? path;
|
||||
|
||||
|
|
@ -164,8 +227,8 @@ export const RightPanel = () => {
|
|||
// Strip query/hash
|
||||
const cleaned = h.split('#')[0].split('?')[0];
|
||||
|
||||
// Looks like: path/to/file.ext or path\to\file.ext:12-34
|
||||
return /[A-Za-z0-9_\-./\\]+\.[A-Za-z0-9]+(?::\d+(?:-\d+)?)?$/.test(cleaned);
|
||||
// Looks like: path/to/file.ext or path\to\file.ext:12-34 (supports both hyphen and en dash)
|
||||
return /[A-Za-z0-9_\-./\\]+\.[A-Za-z0-9]+(?::\d+(?:[-–]\d+)?)?$/.test(cleaned);
|
||||
}, []);
|
||||
|
||||
const extractTextFromChildren = useCallback((children: any): string => {
|
||||
|
|
@ -353,6 +416,7 @@ export const RightPanel = () => {
|
|||
{step.type === 'reasoning' && step.content && (
|
||||
<div className="text-text-secondary text-sm italic border-l-2 border-text-muted/30 pl-3 mb-3">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
a: ({ href, children, ...props }) => {
|
||||
if (href && href.startsWith('code-ref:')) {
|
||||
|
|
@ -365,7 +429,7 @@ export const RightPanel = () => {
|
|||
e.preventDefault();
|
||||
handleGroundingClick(inner);
|
||||
}}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
title={`Open in Code panel • ${inner}`}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -373,6 +437,24 @@ export const RightPanel = () => {
|
|||
</a>
|
||||
);
|
||||
}
|
||||
// Handle node grounding: node-ref:Type:Name
|
||||
if (href && href.startsWith('node-ref:')) {
|
||||
const inner = decodeURIComponent(href.slice('node-ref:'.length));
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNodeGroundingClick(inner);
|
||||
}}
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-amber-300/55 bg-amber-400/10 !text-amber-200 visited:!text-amber-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-amber-400/15 hover:border-amber-200/70 transition-colors"
|
||||
title={`View ${inner} in Code panel`}
|
||||
{...props}
|
||||
>
|
||||
<span className="text-inherit">{children}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const internalRef = getInternalRefFromLink(href, children);
|
||||
if (internalRef) {
|
||||
const label = formatRefChipLabel(internalRef);
|
||||
|
|
@ -383,7 +465,7 @@ export const RightPanel = () => {
|
|||
e.preventDefault();
|
||||
handleGroundingClick(internalRef);
|
||||
}}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
title={`Open in Code panel • ${internalRef}`}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -437,6 +519,7 @@ export const RightPanel = () => {
|
|||
{step.type === 'content' && step.content && (
|
||||
<div className="text-text-primary text-sm">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
a: ({ href, children, ...props }) => {
|
||||
if (href && href.startsWith('code-ref:')) {
|
||||
|
|
@ -449,7 +532,7 @@ export const RightPanel = () => {
|
|||
e.preventDefault();
|
||||
handleGroundingClick(inner);
|
||||
}}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
title={`Open in Code panel • ${inner}`}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -457,6 +540,24 @@ export const RightPanel = () => {
|
|||
</a>
|
||||
);
|
||||
}
|
||||
// Handle node grounding: node-ref:Type:Name
|
||||
if (href && href.startsWith('node-ref:')) {
|
||||
const inner = decodeURIComponent(href.slice('node-ref:'.length));
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNodeGroundingClick(inner);
|
||||
}}
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-amber-300/55 bg-amber-400/10 !text-amber-200 visited:!text-amber-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-amber-400/15 hover:border-amber-200/70 transition-colors"
|
||||
title={`View ${inner} in Code panel`}
|
||||
{...props}
|
||||
>
|
||||
<span className="text-inherit">{children}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const internalRef = getInternalRefFromLink(href, children);
|
||||
if (internalRef) {
|
||||
const label = formatRefChipLabel(internalRef);
|
||||
|
|
@ -467,7 +568,7 @@ export const RightPanel = () => {
|
|||
e.preventDefault();
|
||||
handleGroundingClick(internalRef);
|
||||
}}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
title={`Open in Code panel • ${internalRef}`}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -529,6 +630,7 @@ export const RightPanel = () => {
|
|||
// Fallback: render content + toolCalls separately (old format)
|
||||
<div className="text-text-primary text-sm">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
a: ({ href, children, ...props }) => {
|
||||
if (href && href.startsWith('code-ref:')) {
|
||||
|
|
@ -541,7 +643,7 @@ export const RightPanel = () => {
|
|||
e.preventDefault();
|
||||
handleGroundingClick(inner);
|
||||
}}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
title={`Open in Code panel • ${inner}`}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -549,6 +651,24 @@ export const RightPanel = () => {
|
|||
</a>
|
||||
);
|
||||
}
|
||||
// Handle node grounding: node-ref:Type:Name
|
||||
if (href && href.startsWith('node-ref:')) {
|
||||
const inner = decodeURIComponent(href.slice('node-ref:'.length));
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleNodeGroundingClick(inner);
|
||||
}}
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-amber-300/55 bg-amber-400/10 !text-amber-200 visited:!text-amber-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-amber-400/15 hover:border-amber-200/70 transition-colors"
|
||||
title={`View ${inner} in Code panel`}
|
||||
{...props}
|
||||
>
|
||||
<span className="text-inherit">{children}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const internalRef = getInternalRefFromLink(href, children);
|
||||
if (internalRef) {
|
||||
const label = formatRefChipLabel(internalRef);
|
||||
|
|
@ -559,7 +679,7 @@ export const RightPanel = () => {
|
|||
e.preventDefault();
|
||||
handleGroundingClick(internalRef);
|
||||
}}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
className="code-ref-btn inline-flex items-center px-2 py-0.5 rounded-md border border-cyan-300/55 bg-cyan-400/10 !text-cyan-200 visited:!text-cyan-200 font-mono text-[12px] !no-underline hover:!no-underline hover:bg-cyan-400/15 hover:border-cyan-200/70 transition-colors"
|
||||
title={`Open in Code panel • ${internalRef}`}
|
||||
{...props}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const DEFAULT_IGNORE_LIST = new Set([
|
|||
'bower_components',
|
||||
'jspm_packages',
|
||||
'vendor', // PHP/Go
|
||||
'packages', // Sometimes used for deps
|
||||
// 'packages' removed - commonly used for monorepo source code (lerna, pnpm, yarn workspaces)
|
||||
'venv',
|
||||
'.venv',
|
||||
'env',
|
||||
|
|
|
|||
|
|
@ -7,6 +7,81 @@ import { LANGUAGE_QUERIES } from './tree-sitter-queries';
|
|||
import { generateId } from '../../lib/utils';
|
||||
import { getLanguageFromFilename } from './utils';
|
||||
|
||||
/**
|
||||
* Node types that represent function/method definitions across languages.
|
||||
* Used to find the enclosing function for a call site.
|
||||
*/
|
||||
const FUNCTION_NODE_TYPES = new Set([
|
||||
// TypeScript/JavaScript
|
||||
'function_declaration',
|
||||
'arrow_function',
|
||||
'function_expression',
|
||||
'method_definition',
|
||||
'generator_function_declaration',
|
||||
// Python
|
||||
'function_definition',
|
||||
// Common async variants
|
||||
'async_function_declaration',
|
||||
'async_arrow_function',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Walk up the AST from a node to find the enclosing function/method.
|
||||
* Returns null if the call is at module/file level (top-level code).
|
||||
*/
|
||||
const findEnclosingFunction = (
|
||||
node: any,
|
||||
filePath: string,
|
||||
symbolTable: SymbolTable
|
||||
): string | null => {
|
||||
let current = node.parent;
|
||||
|
||||
while (current) {
|
||||
if (FUNCTION_NODE_TYPES.has(current.type)) {
|
||||
// Found enclosing function - try to get its name
|
||||
let funcName: string | null = null;
|
||||
|
||||
// Different node types have different name locations
|
||||
if (current.type === 'function_declaration' ||
|
||||
current.type === 'function_definition' ||
|
||||
current.type === 'async_function_declaration' ||
|
||||
current.type === 'generator_function_declaration') {
|
||||
// Named function: function foo() {}
|
||||
const nameNode = current.childForFieldName?.('name') ||
|
||||
current.children?.find((c: any) => c.type === 'identifier' || c.type === 'property_identifier');
|
||||
funcName = nameNode?.text;
|
||||
} else if (current.type === 'method_definition') {
|
||||
// Method: foo() {} inside class
|
||||
const nameNode = current.childForFieldName?.('name') ||
|
||||
current.children?.find((c: any) => c.type === 'property_identifier');
|
||||
funcName = nameNode?.text;
|
||||
} else if (current.type === 'arrow_function' || current.type === 'function_expression') {
|
||||
// Arrow/expression: const foo = () => {} - check parent variable declarator
|
||||
const parent = current.parent;
|
||||
if (parent?.type === 'variable_declarator') {
|
||||
const nameNode = parent.childForFieldName?.('name') ||
|
||||
parent.children?.find((c: any) => c.type === 'identifier');
|
||||
funcName = nameNode?.text;
|
||||
}
|
||||
}
|
||||
|
||||
if (funcName) {
|
||||
// Look up the function in symbol table to get its node ID
|
||||
const nodeId = symbolTable.lookupExact(filePath, funcName);
|
||||
if (nodeId) return nodeId;
|
||||
|
||||
// Fallback: generate ID based on name and file
|
||||
return generateId('Function', `${filePath}:${funcName}`);
|
||||
}
|
||||
|
||||
// Couldn't determine function name - try parent (might be nested)
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return null; // Top-level call (not inside any function)
|
||||
};
|
||||
|
||||
export const processCalls = async (
|
||||
graph: KnowledgeGraph,
|
||||
files: { path: string; content: string }[],
|
||||
|
|
@ -78,9 +153,14 @@ export const processCalls = async (
|
|||
|
||||
if (!targetNodeId) return;
|
||||
|
||||
// 5. Create CALLS relationship (File -> Function/Method)
|
||||
const sourceId = generateId('File', file.path);
|
||||
const relId = generateId('CALLS', `${file.path}:${calledName}->${targetNodeId}`);
|
||||
// 5. Find the enclosing function (caller)
|
||||
const callNode = captureMap['call'];
|
||||
const enclosingFuncId = findEnclosingFunction(callNode, file.path, symbolTable);
|
||||
|
||||
// Use enclosing function as source, fallback to file for top-level calls
|
||||
const sourceId = enclosingFuncId || generateId('File', file.path);
|
||||
|
||||
const relId = generateId('CALLS', `${sourceId}:${calledName}->${targetNodeId}`);
|
||||
|
||||
graph.addRelationship({
|
||||
id: relId,
|
||||
|
|
|
|||
123
src/core/ingestion/heritage-processor.ts
Normal file
123
src/core/ingestion/heritage-processor.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* Heritage Processor
|
||||
*
|
||||
* Extracts class inheritance relationships:
|
||||
* - EXTENDS: Class extends another Class (TS, JS, Python)
|
||||
* - IMPLEMENTS: Class implements an Interface (TS only)
|
||||
*/
|
||||
|
||||
import { KnowledgeGraph } from '../graph/types';
|
||||
import { ASTCache } from './ast-cache';
|
||||
import { SymbolTable } from './symbol-table';
|
||||
import { loadParser, loadLanguage } from '../tree-sitter/parser-loader';
|
||||
import { LANGUAGE_QUERIES } from './tree-sitter-queries';
|
||||
import { generateId } from '../../lib/utils';
|
||||
import { getLanguageFromFilename } from './utils';
|
||||
|
||||
export const processHeritage = async (
|
||||
graph: KnowledgeGraph,
|
||||
files: { path: string; content: string }[],
|
||||
astCache: ASTCache,
|
||||
symbolTable: SymbolTable,
|
||||
onProgress?: (current: number, total: number) => void
|
||||
) => {
|
||||
const parser = await loadParser();
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
onProgress?.(i + 1, files.length);
|
||||
|
||||
// 1. Check language support
|
||||
const language = getLanguageFromFilename(file.path);
|
||||
if (!language) continue;
|
||||
|
||||
const queryStr = LANGUAGE_QUERIES[language];
|
||||
if (!queryStr) continue;
|
||||
|
||||
// 2. Load the language
|
||||
await loadLanguage(language, file.path);
|
||||
|
||||
// 3. Get AST
|
||||
let tree = astCache.get(file.path);
|
||||
let wasReparsed = false;
|
||||
|
||||
if (!tree) {
|
||||
tree = parser.parse(file.content);
|
||||
wasReparsed = true;
|
||||
}
|
||||
|
||||
let query;
|
||||
let matches;
|
||||
try {
|
||||
query = parser.getLanguage().query(queryStr);
|
||||
matches = query.matches(tree.rootNode);
|
||||
} catch (queryError) {
|
||||
console.warn(`Heritage query error for ${file.path}:`, queryError);
|
||||
if (wasReparsed) tree.delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Process heritage matches
|
||||
matches.forEach(match => {
|
||||
const captureMap: Record<string, any> = {};
|
||||
match.captures.forEach(c => {
|
||||
captureMap[c.name] = c.node;
|
||||
});
|
||||
|
||||
// EXTENDS: Class extends another Class
|
||||
if (captureMap['heritage.class'] && captureMap['heritage.extends']) {
|
||||
const className = captureMap['heritage.class'].text;
|
||||
const parentClassName = captureMap['heritage.extends'].text;
|
||||
|
||||
// Resolve both class IDs
|
||||
const childId = symbolTable.lookupExact(file.path, className) ||
|
||||
symbolTable.lookupFuzzy(className)[0]?.nodeId ||
|
||||
generateId('Class', `${file.path}:${className}`);
|
||||
|
||||
const parentId = symbolTable.lookupFuzzy(parentClassName)[0]?.nodeId ||
|
||||
generateId('Class', `${parentClassName}`);
|
||||
|
||||
if (childId && parentId && childId !== parentId) {
|
||||
const relId = generateId('EXTENDS', `${childId}->${parentId}`);
|
||||
|
||||
graph.addRelationship({
|
||||
id: relId,
|
||||
sourceId: childId,
|
||||
targetId: parentId,
|
||||
type: 'EXTENDS'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// IMPLEMENTS: Class implements Interface (TypeScript only)
|
||||
if (captureMap['heritage.class'] && captureMap['heritage.implements']) {
|
||||
const className = captureMap['heritage.class'].text;
|
||||
const interfaceName = captureMap['heritage.implements'].text;
|
||||
|
||||
// Resolve class and interface IDs
|
||||
const classId = symbolTable.lookupExact(file.path, className) ||
|
||||
symbolTable.lookupFuzzy(className)[0]?.nodeId ||
|
||||
generateId('Class', `${file.path}:${className}`);
|
||||
|
||||
const interfaceId = symbolTable.lookupFuzzy(interfaceName)[0]?.nodeId ||
|
||||
generateId('Interface', `${interfaceName}`);
|
||||
|
||||
if (classId && interfaceId) {
|
||||
const relId = generateId('IMPLEMENTS', `${classId}->${interfaceId}`);
|
||||
|
||||
graph.addRelationship({
|
||||
id: relId,
|
||||
sourceId: classId,
|
||||
targetId: interfaceId,
|
||||
type: 'IMPLEMENTS'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
if (wasReparsed) {
|
||||
tree.delete();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -86,8 +86,17 @@ export const processImports = async (
|
|||
try {
|
||||
query = parser.getLanguage().query(queryStr);
|
||||
matches = query.matches(tree.rootNode);
|
||||
} catch (queryError) {
|
||||
console.warn(`Query error for ${file.path}:`, queryError);
|
||||
} catch (queryError: any) {
|
||||
// Detailed debug logging for query failures
|
||||
console.group(`🔴 Query Error: ${file.path}`);
|
||||
console.log('Language:', language);
|
||||
console.log('Query (first 200 chars):', queryStr.substring(0, 200) + '...');
|
||||
console.log('Error:', queryError?.message || queryError);
|
||||
console.log('File content (first 300 chars):', file.content.substring(0, 300));
|
||||
console.log('AST root type:', tree.rootNode?.type);
|
||||
console.log('AST has errors:', tree.rootNode?.hasError);
|
||||
console.groupEnd();
|
||||
|
||||
if (wasReparsed) tree.delete();
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { processStructure } from './structure-processor';
|
|||
import { processParsing } from './parsing-processor';
|
||||
import { processImports, createImportMap } from './import-processor';
|
||||
import { processCalls } from './call-processor';
|
||||
import { processHeritage } from './heritage-processor';
|
||||
import { createSymbolTable } from './symbol-table';
|
||||
import { createASTCache } from './ast-cache';
|
||||
import { PipelineProgress, PipelineResult } from '../../types/pipeline';
|
||||
|
|
@ -131,7 +132,7 @@ export const runPipelineFromFiles = async (
|
|||
});
|
||||
|
||||
await processCalls(graph, files, astCache, symbolTable, importMap, (current, total) => {
|
||||
const callProgress = 82 + ((current / total) * 16);
|
||||
const callProgress = 82 + ((current / total) * 10);
|
||||
onProgress({
|
||||
phase: 'calls',
|
||||
percent: Math.round(callProgress),
|
||||
|
|
@ -140,6 +141,24 @@ export const runPipelineFromFiles = async (
|
|||
});
|
||||
});
|
||||
|
||||
// Phase 6: Heritage - Class inheritance (92-98%)
|
||||
onProgress({
|
||||
phase: 'heritage',
|
||||
percent: 92,
|
||||
message: 'Extracting class inheritance...',
|
||||
stats: { filesProcessed: 0, totalFiles: files.length, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
|
||||
await processHeritage(graph, files, astCache, symbolTable, (current, total) => {
|
||||
const heritageProgress = 92 + ((current / total) * 6);
|
||||
onProgress({
|
||||
phase: 'heritage',
|
||||
percent: Math.round(heritageProgress),
|
||||
message: 'Extracting class inheritance...',
|
||||
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Phase 6: Complete (100%)
|
||||
onProgress({
|
||||
|
|
|
|||
|
|
@ -53,6 +53,20 @@ export const TYPESCRIPT_QUERIES = `
|
|||
(call_expression
|
||||
function: (member_expression
|
||||
property: (property_identifier) @call.name)) @call
|
||||
|
||||
; Heritage queries - class extends
|
||||
(class_declaration
|
||||
name: (type_identifier) @heritage.class
|
||||
(class_heritage
|
||||
(extends_clause
|
||||
value: (identifier) @heritage.extends))) @heritage
|
||||
|
||||
; Heritage queries - class implements interface
|
||||
(class_declaration
|
||||
name: (type_identifier) @heritage.class
|
||||
(class_heritage
|
||||
(implements_clause
|
||||
(type_identifier) @heritage.implements))) @heritage.impl
|
||||
`;
|
||||
|
||||
// JavaScript queries - works with tree-sitter-javascript
|
||||
|
|
@ -97,6 +111,13 @@ export const JAVASCRIPT_QUERIES = `
|
|||
(call_expression
|
||||
function: (member_expression
|
||||
property: (property_identifier) @call.name)) @call
|
||||
|
||||
; Heritage queries - class extends (JavaScript uses different AST than TypeScript)
|
||||
; In tree-sitter-javascript, class_heritage directly contains the parent identifier
|
||||
(class_declaration
|
||||
name: (identifier) @heritage.class
|
||||
(class_heritage
|
||||
(identifier) @heritage.extends)) @heritage
|
||||
`;
|
||||
|
||||
// Python queries - works with tree-sitter-python
|
||||
|
|
@ -119,6 +140,12 @@ export const PYTHON_QUERIES = `
|
|||
(call
|
||||
function: (attribute
|
||||
attribute: (identifier) @call.name)) @call
|
||||
|
||||
; Heritage queries - Python class inheritance
|
||||
(class_definition
|
||||
name: (identifier) @heritage.class
|
||||
superclasses: (argument_list
|
||||
(identifier) @heritage.extends)) @heritage
|
||||
`;
|
||||
|
||||
export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export type NodeTableName = typeof NODE_TABLES[number];
|
|||
export const REL_TABLE_NAME = 'CodeRelation';
|
||||
|
||||
// Valid relation types
|
||||
export const REL_TYPES = ['CONTAINS', 'DEFINES', 'IMPORTS', 'CALLS'] as const;
|
||||
export const REL_TYPES = ['CONTAINS', 'DEFINES', 'IMPORTS', 'CALLS', 'EXTENDS', 'IMPLEMENTS'] as const;
|
||||
export type RelType = typeof REL_TYPES[number];
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -125,6 +125,8 @@ CREATE REL TABLE ${REL_TABLE_NAME} (
|
|||
FROM Function TO Method,
|
||||
FROM Class TO Method,
|
||||
FROM Class TO Function,
|
||||
FROM Class TO Class,
|
||||
FROM Class TO Interface,
|
||||
type STRING
|
||||
)`;
|
||||
|
||||
|
|
|
|||
|
|
@ -47,21 +47,17 @@ import {
|
|||
export const BASE_SYSTEM_PROMPT = `You are Nexus, a Code Analysis Agent with access to a Knowledge Graph. Your responses MUST be grounded.
|
||||
|
||||
## ⚠️ MANDATORY: GROUNDING
|
||||
Every factual claim MUST include a citation in [[file:line]] format.
|
||||
- When you mention a function, class, or file → cite where you found it
|
||||
- When you describe behavior → cite the code that proves it
|
||||
- NO citation = NO claim. If you can't cite it, say "I didn't find evidence for this."
|
||||
|
||||
Examples:
|
||||
- "The AuthService handles login [[src/services/auth.ts:45]]."
|
||||
- "This function calls validateToken [[src/utils.ts:12]] which throws on invalid tokens [[src/utils.ts:28]]."
|
||||
Every factual claim MUST include a citation.
|
||||
- File refs: [[src/auth.ts:45-60]] (line range with hyphen)
|
||||
- Node refs: [[Class:AuthService]] or [[Function:validate]]
|
||||
- NO citation = NO claim. Say "I didn't find evidence" instead of guessing.
|
||||
|
||||
## 🧠 CORE PROTOCOL
|
||||
You are an investigator. For each question:
|
||||
1. **Search** → Use search or grep to find relevant code
|
||||
2. **Read** → Use read to see the actual source
|
||||
3. **Trace** → Use cypher to follow connections in the graph
|
||||
4. **Cite** → Ground every finding with [[file:line]]
|
||||
4. **Cite** → Ground every finding with [[file:line]] or [[Type:Name]]
|
||||
5. **Highlight** → Visualize key nodes with highlight
|
||||
|
||||
## 🛠️ TOOLS
|
||||
|
|
@ -73,20 +69,35 @@ You are an investigator. For each question:
|
|||
|
||||
## 📊 GRAPH SCHEMA
|
||||
Nodes: File, Folder, Function, Class, Interface, Method, CodeElement
|
||||
Relation: \`CodeRelation\` with \`type\` property: CONTAINS, DEFINES, IMPORTS, CALLS
|
||||
Relation: \`CodeRelation\` with \`type\` property: CONTAINS, DEFINES, IMPORTS, CALLS, EXTENDS, IMPLEMENTS
|
||||
|
||||
Cypher examples:
|
||||
- \`MATCH (f:Function) RETURN f.name LIMIT 10\`
|
||||
- \`MATCH (f:File)-[:CodeRelation {type: 'IMPORTS'}]->(g:File) RETURN f.name, g.name\`
|
||||
|
||||
## <EFBFBD> RULES
|
||||
## 📝 RULES
|
||||
- **Cite or retract.** Never state something you can't ground.
|
||||
- **Read before concluding.** Don't guess from names alone.
|
||||
- **Retry on failure.** If a tool fails, fix the input and try again.`;
|
||||
- **Retry on failure.** If a tool fails, fix the input and try again.
|
||||
|
||||
/**
|
||||
* Create a chat model instance from provider configuration
|
||||
*/
|
||||
## 🎯 OUTPUT STYLE
|
||||
Think like a senior architect. Be concise—no fluff, short, precise and to the point.
|
||||
- Use tables for comparisons/rankings
|
||||
- Use mermaid diagrams for flows/dependencies
|
||||
- Surface deep insights: patterns, coupling, design decisions
|
||||
- End with **TL;DR** (1-2 sentences)
|
||||
|
||||
## MERMAID RULES
|
||||
When generating diagrams:
|
||||
- NO special characters in node labels: quotes, (), /, &, <, >
|
||||
- Wrap labels with spaces in quotes: A["My Label"]
|
||||
- Use simple IDs: A, B, C or auth, db, api
|
||||
- Flowchart: graph TD or graph LR (not flowchart)
|
||||
- Always test mentally: would this parse?
|
||||
|
||||
BAD: A[User's Data] --> B(Process & Save)
|
||||
GOOD: A["User Data"] --> B["Process and Save"]
|
||||
`;
|
||||
export const createChatModel = (config: ProviderConfig): BaseChatModel => {
|
||||
switch (config.provider) {
|
||||
case 'azure-openai': {
|
||||
|
|
|
|||
|
|
@ -186,15 +186,17 @@ export const createGraphRAGTools = (
|
|||
},
|
||||
{
|
||||
name: 'cypher',
|
||||
description: `Execute a Cypher query against the code graph. Use for structural queries like finding callers, tracing imports, or custom traversals.
|
||||
description: `Execute a Cypher query against the code graph. Use for structural queries like finding callers, tracing imports, class inheritance, or custom traversals.
|
||||
|
||||
Node tables: File, Folder, Function, Class, Interface, Method, CodeElement
|
||||
Relation: CodeRelation (single table with 'type' property: CONTAINS, DEFINES, IMPORTS, CALLS)
|
||||
Relation: CodeRelation (single table with 'type' property: CONTAINS, DEFINES, IMPORTS, CALLS, EXTENDS, IMPLEMENTS)
|
||||
|
||||
Example queries:
|
||||
- Functions calling a function: MATCH (caller:Function)-[:CodeRelation {type: 'CALLS'}]->(fn:Function {name: 'validate'}) RETURN caller.name, caller.filePath
|
||||
- Class inheritance: MATCH (child:Class)-[:CodeRelation {type: 'EXTENDS'}]->(parent:Class) RETURN child.name, parent.name
|
||||
- Classes implementing interface: MATCH (c:Class)-[:CodeRelation {type: 'IMPLEMENTS'}]->(i:Interface) RETURN c.name, i.name
|
||||
- Files importing a file: MATCH (f:File)-[:CodeRelation {type: 'IMPORTS'}]->(target:File) WHERE target.name = 'utils.ts' RETURN f.name
|
||||
- Functions defined in file: MATCH (f:File {name: 'main.ts'})-[:CodeRelation {type: 'DEFINES'}]->(fn:Function) RETURN fn.name
|
||||
- All connections: MATCH (f:File {name: 'index.ts'})-[r:CodeRelation]-(m) RETURN m.name, r.type
|
||||
- All connections: MATCH (n)-[r:CodeRelation]-(m) WHERE n.name = 'MyClass' RETURN m.name, r.type
|
||||
|
||||
For semantic+graph queries, include {{QUERY_VECTOR}} placeholder and provide a 'query' parameter:
|
||||
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx', {{QUERY_VECTOR}}, 10) YIELD node AS emb, distance
|
||||
|
|
|
|||
|
|
@ -646,47 +646,68 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => {
|
|||
}
|
||||
updateMessage();
|
||||
|
||||
// Parse inline grounding references like [[src/file.ts:10-25]]
|
||||
// and add them to the Code References panel.
|
||||
// Parse inline grounding references and add them to the Code References panel.
|
||||
// Supports: [[file.ts:10-25]] (file refs) and [[Class:View]] (node refs)
|
||||
const currentContentStep = stepsForMessage[stepsForMessage.length - 1];
|
||||
const fullText = (currentContentStep && currentContentStep.type === 'content')
|
||||
? (currentContentStep.content || '')
|
||||
: '';
|
||||
|
||||
const refRegex = /\[\[([^\]\n]+?)\]\]/g;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = refRegex.exec(fullText)) !== null) {
|
||||
const inner = match[1].trim();
|
||||
if (!inner) continue;
|
||||
|
||||
let rawPath = inner;
|
||||
let startLine1: number | undefined;
|
||||
let endLine1: number | undefined;
|
||||
|
||||
const lineMatch = inner.match(/^(.*):(\d+)(?:-(\d+))?$/);
|
||||
if (lineMatch) {
|
||||
rawPath = lineMatch[1].trim();
|
||||
startLine1 = parseInt(lineMatch[2], 10);
|
||||
endLine1 = parseInt(lineMatch[3] || lineMatch[2], 10);
|
||||
}
|
||||
// Pattern 1: File refs - [[path/file.ext]] or [[path/file.ext:line]] or [[path/file.ext:line-line]]
|
||||
// Line numbers are optional
|
||||
const fileRefRegex = /\[\[([a-zA-Z0-9_\-./\\]+\.[a-zA-Z0-9]+)(?::(\d+)(?:[-–](\d+))?)?\]\]/g;
|
||||
let fileMatch: RegExpExecArray | null;
|
||||
while ((fileMatch = fileRefRegex.exec(fullText)) !== null) {
|
||||
const rawPath = fileMatch[1].trim();
|
||||
const startLine1 = fileMatch[2] ? parseInt(fileMatch[2], 10) : undefined;
|
||||
const endLine1 = fileMatch[3] ? parseInt(fileMatch[3], 10) : startLine1;
|
||||
|
||||
const resolvedPath = resolveFilePath(rawPath);
|
||||
if (!resolvedPath) continue;
|
||||
|
||||
const startLine0 = startLine1 ? Math.max(0, startLine1 - 1) : 0;
|
||||
const endLine0 = endLine1 ? Math.max(0, endLine1 - 1) : startLine0;
|
||||
const startLine0 = startLine1 !== undefined ? Math.max(0, startLine1 - 1) : undefined;
|
||||
const endLine0 = endLine1 !== undefined ? Math.max(0, endLine1 - 1) : startLine0;
|
||||
const nodeId = findFileNodeId(resolvedPath);
|
||||
|
||||
addCodeReference({
|
||||
filePath: resolvedPath,
|
||||
startLine: startLine1 ? startLine0 : undefined,
|
||||
endLine: endLine1 ? endLine0 : (startLine1 ? startLine0 : undefined),
|
||||
startLine: startLine0,
|
||||
endLine: endLine0,
|
||||
nodeId,
|
||||
label: 'File',
|
||||
name: resolvedPath.split('/').pop() ?? resolvedPath,
|
||||
source: 'ai',
|
||||
});
|
||||
}
|
||||
|
||||
// Pattern 2: Node refs - [[Type:Name]] or [[graph:Type:Name]]
|
||||
const nodeRefRegex = /\[\[(?:graph:)?(Class|Function|Method|Interface|File|Folder|Variable|Enum|Type|CodeElement):([^\]]+)\]\]/g;
|
||||
let nodeMatch: RegExpExecArray | null;
|
||||
while ((nodeMatch = nodeRefRegex.exec(fullText)) !== null) {
|
||||
const nodeType = nodeMatch[1];
|
||||
const nodeName = nodeMatch[2].trim();
|
||||
|
||||
// Find node in graph
|
||||
if (!graph) continue;
|
||||
const node = graph.nodes.find(n =>
|
||||
n.label === nodeType &&
|
||||
n.properties.name === nodeName
|
||||
);
|
||||
if (!node || !node.properties.filePath) continue;
|
||||
|
||||
const resolvedPath = resolveFilePath(node.properties.filePath);
|
||||
if (!resolvedPath) continue;
|
||||
|
||||
addCodeReference({
|
||||
filePath: resolvedPath,
|
||||
startLine: node.properties.startLine ? node.properties.startLine - 1 : undefined,
|
||||
endLine: node.properties.endLine ? node.properties.endLine - 1 : undefined,
|
||||
nodeId: node.id,
|
||||
label: node.label,
|
||||
name: node.properties.name,
|
||||
source: 'ai',
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -305,14 +305,14 @@ body {
|
|||
border-top: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
/* Links */
|
||||
.chat-prose a {
|
||||
/* Links - but NOT grounding/citation buttons (those have their own Tailwind styles) */
|
||||
.chat-prose a:not(.code-ref-btn) {
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.chat-prose a:hover {
|
||||
.chat-prose a:not(.code-ref-btn):hover {
|
||||
color: #a78bfa;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { GraphNode, GraphRelationship, KnowledgeGraph } from '../core/graph/types';
|
||||
|
||||
export type PipelinePhase = 'idle' | 'extracting' | 'structure' | 'parsing' | 'imports' | 'calls' | 'complete' | 'error';
|
||||
export type PipelinePhase = 'idle' | 'extracting' | 'structure' | 'parsing' | 'imports' | 'calls' | 'heritage' | 'complete' | 'error';
|
||||
|
||||
export interface PipelineProgress {
|
||||
phase: PipelinePhase;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue