@thoughtflow/codebase 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contexts/context-decomposer.d.ts +13 -0
- package/dist/contexts/context-decomposer.d.ts.map +1 -0
- package/dist/contexts/context-decomposer.js +97 -0
- package/dist/contexts/context-decomposer.js.map +1 -0
- package/dist/contexts/context-document-manager.d.ts +21 -0
- package/dist/contexts/context-document-manager.d.ts.map +1 -0
- package/dist/contexts/context-document-manager.js +69 -0
- package/dist/contexts/context-document-manager.js.map +1 -0
- package/dist/contexts/context-router.d.ts +13 -0
- package/dist/contexts/context-router.d.ts.map +1 -0
- package/dist/contexts/context-router.js +94 -0
- package/dist/contexts/context-router.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/libs/codebase/deep-analyzer.d.ts +58 -0
- package/dist/libs/codebase/deep-analyzer.d.ts.map +1 -0
- package/dist/libs/codebase/deep-analyzer.js +239 -0
- package/dist/libs/codebase/deep-analyzer.js.map +1 -0
- package/dist/libs/codebase/graph/dependency-extractor.d.ts +20 -0
- package/dist/libs/codebase/graph/dependency-extractor.d.ts.map +1 -0
- package/dist/libs/codebase/graph/dependency-extractor.js +174 -0
- package/dist/libs/codebase/graph/dependency-extractor.js.map +1 -0
- package/dist/libs/codebase/graph/graph-analysis.d.ts +42 -0
- package/dist/libs/codebase/graph/graph-analysis.d.ts.map +1 -0
- package/dist/libs/codebase/graph/graph-analysis.js +222 -0
- package/dist/libs/codebase/graph/graph-analysis.js.map +1 -0
- package/dist/libs/codebase/graph/graph-visualizer.d.ts +12 -0
- package/dist/libs/codebase/graph/graph-visualizer.d.ts.map +1 -0
- package/dist/libs/codebase/graph/graph-visualizer.js +99 -0
- package/dist/libs/codebase/graph/graph-visualizer.js.map +1 -0
- package/dist/libs/codebase/indexer/batch-processor.d.ts +18 -0
- package/dist/libs/codebase/indexer/batch-processor.d.ts.map +1 -0
- package/dist/libs/codebase/indexer/batch-processor.js +39 -0
- package/dist/libs/codebase/indexer/batch-processor.js.map +1 -0
- package/dist/libs/codebase/indexer/chunker-markdown.d.ts +19 -0
- package/dist/libs/codebase/indexer/chunker-markdown.d.ts.map +1 -0
- package/dist/libs/codebase/indexer/chunker-markdown.js +138 -0
- package/dist/libs/codebase/indexer/chunker-markdown.js.map +1 -0
- package/dist/libs/codebase/indexer/chunker.d.ts +21 -0
- package/dist/libs/codebase/indexer/chunker.d.ts.map +1 -0
- package/dist/libs/codebase/indexer/chunker.js +133 -0
- package/dist/libs/codebase/indexer/chunker.js.map +1 -0
- package/dist/libs/codebase/indexer/explorer.d.ts +29 -0
- package/dist/libs/codebase/indexer/explorer.d.ts.map +1 -0
- package/dist/libs/codebase/indexer/explorer.js +278 -0
- package/dist/libs/codebase/indexer/explorer.js.map +1 -0
- package/dist/libs/codebase/indexer/indexer.d.ts +35 -0
- package/dist/libs/codebase/indexer/indexer.d.ts.map +1 -0
- package/dist/libs/codebase/indexer/indexer.js +172 -0
- package/dist/libs/codebase/indexer/indexer.js.map +1 -0
- package/dist/libs/codebase/knowledge/cross-referencer.d.ts +32 -0
- package/dist/libs/codebase/knowledge/cross-referencer.d.ts.map +1 -0
- package/dist/libs/codebase/knowledge/cross-referencer.js +132 -0
- package/dist/libs/codebase/knowledge/cross-referencer.js.map +1 -0
- package/dist/libs/codebase/knowledge/markdown-extractor.d.ts +34 -0
- package/dist/libs/codebase/knowledge/markdown-extractor.d.ts.map +1 -0
- package/dist/libs/codebase/knowledge/markdown-extractor.js +273 -0
- package/dist/libs/codebase/knowledge/markdown-extractor.js.map +1 -0
- package/dist/libs/codebase/knowledge/summarizer.d.ts +25 -0
- package/dist/libs/codebase/knowledge/summarizer.d.ts.map +1 -0
- package/dist/libs/codebase/knowledge/summarizer.js +61 -0
- package/dist/libs/codebase/knowledge/summarizer.js.map +1 -0
- package/dist/libs/codebase/search/bm25.d.ts +23 -0
- package/dist/libs/codebase/search/bm25.d.ts.map +1 -0
- package/dist/libs/codebase/search/bm25.js +93 -0
- package/dist/libs/codebase/search/bm25.js.map +1 -0
- package/dist/libs/codebase/search/hybrid-search.d.ts +22 -0
- package/dist/libs/codebase/search/hybrid-search.d.ts.map +1 -0
- package/dist/libs/codebase/search/hybrid-search.js +80 -0
- package/dist/libs/codebase/search/hybrid-search.js.map +1 -0
- package/dist/libs/codebase-index.class.d.ts +66 -0
- package/dist/libs/codebase-index.class.d.ts.map +1 -0
- package/dist/libs/codebase-index.class.js +595 -0
- package/dist/libs/codebase-index.class.js.map +1 -0
- package/dist/tools/codebase/codebase-analyze.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-analyze.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-analyze.tool.js +20 -0
- package/dist/tools/codebase/codebase-analyze.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-crossref.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-crossref.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-crossref.tool.js +20 -0
- package/dist/tools/codebase/codebase-crossref.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-flow.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-flow.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-flow.tool.js +20 -0
- package/dist/tools/codebase/codebase-flow.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-graph-circular.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-graph-circular.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-graph-circular.tool.js +17 -0
- package/dist/tools/codebase/codebase-graph-circular.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-graph-query.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-graph-query.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-graph-query.tool.js +21 -0
- package/dist/tools/codebase/codebase-graph-query.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-graph-stats.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-graph-stats.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-graph-stats.tool.js +17 -0
- package/dist/tools/codebase/codebase-graph-stats.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-graph-visualize.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-graph-visualize.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-graph-visualize.tool.js +21 -0
- package/dist/tools/codebase/codebase-graph-visualize.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-impact.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-impact.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-impact.tool.js +21 -0
- package/dist/tools/codebase/codebase-impact.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-index.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-index.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-index.tool.js +21 -0
- package/dist/tools/codebase/codebase-index.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-knowledge.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-knowledge.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-knowledge.tool.js +22 -0
- package/dist/tools/codebase/codebase-knowledge.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-search.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-search.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-search.tool.js +24 -0
- package/dist/tools/codebase/codebase-search.tool.js.map +1 -0
- package/dist/tools/codebase/codebase-status.tool.d.ts +8 -0
- package/dist/tools/codebase/codebase-status.tool.d.ts.map +1 -0
- package/dist/tools/codebase/codebase-status.tool.js +19 -0
- package/dist/tools/codebase/codebase-status.tool.js.map +1 -0
- package/dist/tools/neural/comparative-analysis.tool.d.ts +37 -0
- package/dist/tools/neural/comparative-analysis.tool.d.ts.map +1 -0
- package/dist/tools/neural/comparative-analysis.tool.js +241 -0
- package/dist/tools/neural/comparative-analysis.tool.js.map +1 -0
- package/dist/tools/neural/index.d.ts +12 -0
- package/dist/tools/neural/index.d.ts.map +1 -0
- package/dist/tools/neural/index.js +28 -0
- package/dist/tools/neural/index.js.map +1 -0
- package/dist/tools/neural/neural-deadcode.tool.d.ts +10 -0
- package/dist/tools/neural/neural-deadcode.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-deadcode.tool.js +133 -0
- package/dist/tools/neural/neural-deadcode.tool.js.map +1 -0
- package/dist/tools/neural/neural-debug.tool.d.ts +11 -0
- package/dist/tools/neural/neural-debug.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-debug.tool.js +205 -0
- package/dist/tools/neural/neural-debug.tool.js.map +1 -0
- package/dist/tools/neural/neural-explain.tool.d.ts +9 -0
- package/dist/tools/neural/neural-explain.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-explain.tool.js +100 -0
- package/dist/tools/neural/neural-explain.tool.js.map +1 -0
- package/dist/tools/neural/neural-optimize.tool.d.ts +9 -0
- package/dist/tools/neural/neural-optimize.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-optimize.tool.js +117 -0
- package/dist/tools/neural/neural-optimize.tool.js.map +1 -0
- package/dist/tools/neural/neural-pattern.tool.d.ts +10 -0
- package/dist/tools/neural/neural-pattern.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-pattern.tool.js +169 -0
- package/dist/tools/neural/neural-pattern.tool.js.map +1 -0
- package/dist/tools/neural/neural-review.tool.d.ts +11 -0
- package/dist/tools/neural/neural-review.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-review.tool.js +152 -0
- package/dist/tools/neural/neural-review.tool.js.map +1 -0
- package/dist/tools/neural/neural-security.tool.d.ts +16 -0
- package/dist/tools/neural/neural-security.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-security.tool.js +256 -0
- package/dist/tools/neural/neural-security.tool.js.map +1 -0
- package/dist/tools/neural/neural-similar.tool.d.ts +9 -0
- package/dist/tools/neural/neural-similar.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-similar.tool.js +139 -0
- package/dist/tools/neural/neural-similar.tool.js.map +1 -0
- package/dist/tools/neural/neural-smells.tool.d.ts +9 -0
- package/dist/tools/neural/neural-smells.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-smells.tool.js +123 -0
- package/dist/tools/neural/neural-smells.tool.js.map +1 -0
- package/dist/tools/neural/neural-suggest.tool.d.ts +9 -0
- package/dist/tools/neural/neural-suggest.tool.d.ts.map +1 -0
- package/dist/tools/neural/neural-suggest.tool.js +64 -0
- package/dist/tools/neural/neural-suggest.tool.js.map +1 -0
- package/dist/tools/quality/index.d.ts +8 -0
- package/dist/tools/quality/index.d.ts.map +1 -0
- package/dist/tools/quality/index.js +24 -0
- package/dist/tools/quality/index.js.map +1 -0
- package/dist/tools/quality/quality-complexity.tool.d.ts +11 -0
- package/dist/tools/quality/quality-complexity.tool.d.ts.map +1 -0
- package/dist/tools/quality/quality-complexity.tool.js +152 -0
- package/dist/tools/quality/quality-complexity.tool.js.map +1 -0
- package/dist/tools/quality/quality-coupling.tool.d.ts +9 -0
- package/dist/tools/quality/quality-coupling.tool.d.ts.map +1 -0
- package/dist/tools/quality/quality-coupling.tool.js +51 -0
- package/dist/tools/quality/quality-coupling.tool.js.map +1 -0
- package/dist/tools/quality/quality-coverage.tool.d.ts +9 -0
- package/dist/tools/quality/quality-coverage.tool.d.ts.map +1 -0
- package/dist/tools/quality/quality-coverage.tool.js +104 -0
- package/dist/tools/quality/quality-coverage.tool.js.map +1 -0
- package/dist/tools/quality/quality-deps.tool.d.ts +11 -0
- package/dist/tools/quality/quality-deps.tool.d.ts.map +1 -0
- package/dist/tools/quality/quality-deps.tool.js +119 -0
- package/dist/tools/quality/quality-deps.tool.js.map +1 -0
- package/dist/tools/quality/quality-duplication.tool.d.ts +9 -0
- package/dist/tools/quality/quality-duplication.tool.d.ts.map +1 -0
- package/dist/tools/quality/quality-duplication.tool.js +100 -0
- package/dist/tools/quality/quality-duplication.tool.js.map +1 -0
- package/dist/tools/quality/quality-size.tool.d.ts +9 -0
- package/dist/tools/quality/quality-size.tool.d.ts.map +1 -0
- package/dist/tools/quality/quality-size.tool.js +94 -0
- package/dist/tools/quality/quality-size.tool.js.map +1 -0
- package/dist/tools/quality/quality-types.tool.d.ts +9 -0
- package/dist/tools/quality/quality-types.tool.d.ts.map +1 -0
- package/dist/tools/quality/quality-types.tool.js +95 -0
- package/dist/tools/quality/quality-types.tool.js.map +1 -0
- package/dist/tools/workspace/analyze-project.tool.d.ts +36 -0
- package/dist/tools/workspace/analyze-project.tool.d.ts.map +1 -0
- package/dist/tools/workspace/analyze-project.tool.js +454 -0
- package/dist/tools/workspace/analyze-project.tool.js.map +1 -0
- package/dist/tools/workspace/codebase-research.tool.d.ts +10 -0
- package/dist/tools/workspace/codebase-research.tool.d.ts.map +1 -0
- package/dist/tools/workspace/codebase-research.tool.js +89 -0
- package/dist/tools/workspace/codebase-research.tool.js.map +1 -0
- package/dist/tools/workspace/project-scanner.tool.d.ts +55 -0
- package/dist/tools/workspace/project-scanner.tool.d.ts.map +1 -0
- package/dist/tools/workspace/project-scanner.tool.js +978 -0
- package/dist/tools/workspace/project-scanner.tool.js.map +1 -0
- package/dist/tools/workspace/search-code.tool.d.ts +14 -0
- package/dist/tools/workspace/search-code.tool.d.ts.map +1 -0
- package/dist/tools/workspace/search-code.tool.js +286 -0
- package/dist/tools/workspace/search-code.tool.js.map +1 -0
- package/package.json +17 -0
- package/src/contexts/context-decomposer.ts +113 -0
- package/src/contexts/context-document-manager.ts +70 -0
- package/src/contexts/context-router.ts +121 -0
- package/src/index.ts +65 -0
- package/src/libs/codebase/deep-analyzer.ts +287 -0
- package/src/libs/codebase/graph/dependency-extractor.ts +165 -0
- package/src/libs/codebase/graph/graph-analysis.ts +231 -0
- package/src/libs/codebase/graph/graph-visualizer.ts +124 -0
- package/src/libs/codebase/indexer/batch-processor.ts +47 -0
- package/src/libs/codebase/indexer/chunker-markdown.ts +175 -0
- package/src/libs/codebase/indexer/chunker.ts +126 -0
- package/src/libs/codebase/indexer/explorer.ts +269 -0
- package/src/libs/codebase/indexer/indexer.ts +174 -0
- package/src/libs/codebase/knowledge/cross-referencer.ts +182 -0
- package/src/libs/codebase/knowledge/markdown-extractor.ts +270 -0
- package/src/libs/codebase/knowledge/summarizer.ts +85 -0
- package/src/libs/codebase/search/bm25.ts +109 -0
- package/src/libs/codebase/search/hybrid-search.ts +109 -0
- package/src/libs/codebase-index.class.ts +715 -0
- package/src/tools/codebase/codebase-analyze.tool.ts +18 -0
- package/src/tools/codebase/codebase-crossref.tool.ts +18 -0
- package/src/tools/codebase/codebase-flow.tool.ts +18 -0
- package/src/tools/codebase/codebase-graph-circular.tool.ts +15 -0
- package/src/tools/codebase/codebase-graph-query.tool.ts +19 -0
- package/src/tools/codebase/codebase-graph-stats.tool.ts +15 -0
- package/src/tools/codebase/codebase-graph-visualize.tool.ts +19 -0
- package/src/tools/codebase/codebase-impact.tool.ts +19 -0
- package/src/tools/codebase/codebase-index.tool.ts +19 -0
- package/src/tools/codebase/codebase-knowledge.tool.ts +20 -0
- package/src/tools/codebase/codebase-search.tool.ts +22 -0
- package/src/tools/codebase/codebase-status.tool.ts +17 -0
- package/src/tools/neural/comparative-analysis.tool.ts +309 -0
- package/src/tools/neural/index.ts +11 -0
- package/src/tools/neural/neural-deadcode.tool.ts +105 -0
- package/src/tools/neural/neural-debug.tool.ts +199 -0
- package/src/tools/neural/neural-explain.tool.ts +67 -0
- package/src/tools/neural/neural-optimize.tool.ts +93 -0
- package/src/tools/neural/neural-pattern.tool.ts +141 -0
- package/src/tools/neural/neural-review.tool.ts +145 -0
- package/src/tools/neural/neural-security.tool.ts +226 -0
- package/src/tools/neural/neural-similar.tool.ts +113 -0
- package/src/tools/neural/neural-smells.tool.ts +98 -0
- package/src/tools/neural/neural-suggest.tool.ts +65 -0
- package/src/tools/quality/index.ts +7 -0
- package/src/tools/quality/quality-complexity.tool.ts +132 -0
- package/src/tools/quality/quality-coupling.tool.ts +57 -0
- package/src/tools/quality/quality-coverage.tool.ts +78 -0
- package/src/tools/quality/quality-deps.tool.ts +89 -0
- package/src/tools/quality/quality-duplication.tool.ts +71 -0
- package/src/tools/quality/quality-size.tool.ts +66 -0
- package/src/tools/quality/quality-types.tool.ts +66 -0
- package/src/tools/workspace/analyze-project.tool.ts +429 -0
- package/src/tools/workspace/codebase-research.tool.ts +100 -0
- package/src/tools/workspace/project-scanner.tool.ts +1058 -0
- package/src/tools/workspace/search-code.tool.ts +344 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { ContextRoutingConfig, ContextDocumentCache, LlmAdapter } from "@thoughtflow/core"
|
|
2
|
+
import { callStructured } from "@thoughtflow/core"
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
const STOP_WORDS = new Set([
|
|
6
|
+
'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
7
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'shall',
|
|
8
|
+
'should', 'may', 'might', 'must', 'can', 'could', 'i', 'me', 'my',
|
|
9
|
+
'we', 'our', 'you', 'your', 'he', 'she', 'it', 'they', 'them', 'this',
|
|
10
|
+
'that', 'these', 'those', 'in', 'on', 'at', 'to', 'for', 'of', 'with',
|
|
11
|
+
'by', 'from', 'and', 'or', 'not', 'but', 'if', 'so', 'no', 'yes',
|
|
12
|
+
'what', 'which', 'who', 'whom', 'when', 'where', 'why', 'how'
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
function tokenize(text: string): Set<string> {
|
|
16
|
+
return new Set(
|
|
17
|
+
text.toLowerCase()
|
|
18
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
19
|
+
.filter(w => w.length > 0 && !STOP_WORDS.has(w))
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function computeScore(
|
|
24
|
+
promptTokens: Set<string>,
|
|
25
|
+
doc: ContextDocumentCache
|
|
26
|
+
): number {
|
|
27
|
+
if (doc.keywords.length === 0) return 0
|
|
28
|
+
|
|
29
|
+
let matches = 0
|
|
30
|
+
for (const kw of doc.keywords) {
|
|
31
|
+
let hasMatch = false
|
|
32
|
+
for (const t of tokenize(kw)) {
|
|
33
|
+
if (promptTokens.has(t)) {
|
|
34
|
+
hasMatch = true
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (hasMatch) matches++
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let categoryBonus = 0
|
|
42
|
+
if (doc.category && promptTokens.has(doc.category)) {
|
|
43
|
+
categoryBonus = 0.3
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (matches / doc.keywords.length) + categoryBonus
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const routingSchema = z.object({
|
|
50
|
+
relevant: z.array(z.string())
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export class ContextRouter {
|
|
54
|
+
constructor(
|
|
55
|
+
private readonly config: ContextRoutingConfig,
|
|
56
|
+
private readonly adapter: LlmAdapter,
|
|
57
|
+
private readonly model: string
|
|
58
|
+
) {}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Route the given prompt against cached document metadata.
|
|
62
|
+
* Returns the list of document names that are relevant.
|
|
63
|
+
*/
|
|
64
|
+
async route(
|
|
65
|
+
prompt: string,
|
|
66
|
+
documents: ContextDocumentCache[]
|
|
67
|
+
): Promise<string[]> {
|
|
68
|
+
const promptTokens = tokenize(prompt)
|
|
69
|
+
const strategy = this.config.strategy ?? 'hybrid'
|
|
70
|
+
const threshold = this.config.relevanceThreshold ?? 0.5
|
|
71
|
+
|
|
72
|
+
// ── Step 1: keyword matching ────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
const scored = documents.map(doc => ({
|
|
75
|
+
name: doc.name,
|
|
76
|
+
score: computeScore(promptTokens, doc)
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
const maxScore = Math.max(...scored.map(s => s.score), 0)
|
|
80
|
+
const normalized = scored.map(s => ({
|
|
81
|
+
...s,
|
|
82
|
+
score: maxScore > 0 ? s.score / maxScore : 0
|
|
83
|
+
}))
|
|
84
|
+
|
|
85
|
+
const keywordHits = normalized
|
|
86
|
+
.filter(s => s.score >= threshold)
|
|
87
|
+
.map(s => s.name)
|
|
88
|
+
|
|
89
|
+
if (strategy === 'keyword') {
|
|
90
|
+
return keywordHits
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (strategy === 'hybrid' && keywordHits.length > 0) {
|
|
94
|
+
return keywordHits
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Step 2: LLM routing ────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
if (documents.length === 0) {
|
|
100
|
+
return []
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const routingModel = this.config.model?.model ?? this.model
|
|
104
|
+
|
|
105
|
+
const docLines = documents
|
|
106
|
+
.map(d => `- ${d.name}: ${d.summary}`)
|
|
107
|
+
.join('\n')
|
|
108
|
+
|
|
109
|
+
const userPrompt = `Given the user query: "${prompt}"\n\nSelect which of the following documents are relevant:\n${docLines}`
|
|
110
|
+
|
|
111
|
+
const result = await callStructured(
|
|
112
|
+
this.adapter,
|
|
113
|
+
routingModel,
|
|
114
|
+
'You are a document routing assistant. Select relevant documents based on the user query.',
|
|
115
|
+
userPrompt,
|
|
116
|
+
routingSchema
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return result.result.relevant
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Codebase index
|
|
2
|
+
export * from "./libs/codebase-index.class";
|
|
3
|
+
|
|
4
|
+
// Codebase libs — deep analyzer, graph, indexer, knowledge, search
|
|
5
|
+
export * from "./libs/codebase/deep-analyzer";
|
|
6
|
+
export * from "./libs/codebase/graph/dependency-extractor";
|
|
7
|
+
export * from "./libs/codebase/graph/graph-analysis";
|
|
8
|
+
export * from "./libs/codebase/graph/graph-visualizer";
|
|
9
|
+
export * from "./libs/codebase/indexer/batch-processor";
|
|
10
|
+
export * from "./libs/codebase/indexer/chunker-markdown";
|
|
11
|
+
export * from "./libs/codebase/indexer/chunker";
|
|
12
|
+
export * from "./libs/codebase/indexer/explorer";
|
|
13
|
+
export * from "./libs/codebase/indexer/indexer";
|
|
14
|
+
export * from "./libs/codebase/knowledge/cross-referencer";
|
|
15
|
+
export * from "./libs/codebase/knowledge/markdown-extractor";
|
|
16
|
+
export * from "./libs/codebase/knowledge/summarizer";
|
|
17
|
+
export * from "./libs/codebase/search/bm25";
|
|
18
|
+
export * from "./libs/codebase/search/hybrid-search";
|
|
19
|
+
|
|
20
|
+
// Tools — codebase
|
|
21
|
+
export * from "./tools/codebase/codebase-analyze.tool";
|
|
22
|
+
export * from "./tools/codebase/codebase-crossref.tool";
|
|
23
|
+
export * from "./tools/codebase/codebase-flow.tool";
|
|
24
|
+
export * from "./tools/codebase/codebase-graph-circular.tool";
|
|
25
|
+
export * from "./tools/codebase/codebase-graph-query.tool";
|
|
26
|
+
export * from "./tools/codebase/codebase-graph-stats.tool";
|
|
27
|
+
export * from "./tools/codebase/codebase-graph-visualize.tool";
|
|
28
|
+
export * from "./tools/codebase/codebase-impact.tool";
|
|
29
|
+
export * from "./tools/codebase/codebase-index.tool";
|
|
30
|
+
export * from "./tools/codebase/codebase-knowledge.tool";
|
|
31
|
+
export * from "./tools/codebase/codebase-search.tool";
|
|
32
|
+
export * from "./tools/codebase/codebase-status.tool";
|
|
33
|
+
|
|
34
|
+
// Tools — quality
|
|
35
|
+
export * from "./tools/quality/quality-complexity.tool";
|
|
36
|
+
export * from "./tools/quality/quality-coupling.tool";
|
|
37
|
+
export * from "./tools/quality/quality-coverage.tool";
|
|
38
|
+
export * from "./tools/quality/quality-deps.tool";
|
|
39
|
+
export * from "./tools/quality/quality-duplication.tool";
|
|
40
|
+
export * from "./tools/quality/quality-size.tool";
|
|
41
|
+
export * from "./tools/quality/quality-types.tool";
|
|
42
|
+
|
|
43
|
+
// Tools — neural
|
|
44
|
+
export * from "./tools/neural/comparative-analysis.tool";
|
|
45
|
+
export * from "./tools/neural/neural-deadcode.tool";
|
|
46
|
+
export * from "./tools/neural/neural-debug.tool";
|
|
47
|
+
export * from "./tools/neural/neural-explain.tool";
|
|
48
|
+
export * from "./tools/neural/neural-optimize.tool";
|
|
49
|
+
export * from "./tools/neural/neural-pattern.tool";
|
|
50
|
+
export * from "./tools/neural/neural-review.tool";
|
|
51
|
+
export * from "./tools/neural/neural-security.tool";
|
|
52
|
+
export * from "./tools/neural/neural-similar.tool";
|
|
53
|
+
export * from "./tools/neural/neural-smells.tool";
|
|
54
|
+
export * from "./tools/neural/neural-suggest.tool";
|
|
55
|
+
|
|
56
|
+
// Tools — workspace (codebase-related)
|
|
57
|
+
export * from "./tools/workspace/search-code.tool";
|
|
58
|
+
export * from "./tools/workspace/codebase-research.tool";
|
|
59
|
+
export * from "./tools/workspace/project-scanner.tool";
|
|
60
|
+
export * from "./tools/workspace/analyze-project.tool";
|
|
61
|
+
|
|
62
|
+
// Contexts
|
|
63
|
+
export * from "./contexts/context-decomposer";
|
|
64
|
+
export * from "./contexts/context-document-manager";
|
|
65
|
+
export * from "./contexts/context-router";
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type { IVectorStore, IEmbeddingProvider } from "@thoughtflow/core";
|
|
2
|
+
import { CodebaseExplorer, DiscoveredFile } from "./indexer/explorer";
|
|
3
|
+
import { CodebaseIndexer, IndexerOptions } from "./indexer/indexer";
|
|
4
|
+
import { DependencyExtractor, DependencyGraph } from "./graph/dependency-extractor";
|
|
5
|
+
import { CircularDetector, ImpactAnalyzer } from "./graph/graph-analysis";
|
|
6
|
+
import { GraphVisualizer } from "./graph/graph-visualizer";
|
|
7
|
+
import { MarkdownExtractor, ExtractedDocument } from "./knowledge/markdown-extractor";
|
|
8
|
+
import { CrossReferencer, CrossReferenceGraph } from "./knowledge/cross-referencer";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
|
|
11
|
+
export type AnalysisPhase =
|
|
12
|
+
| 'discover'
|
|
13
|
+
| 'index'
|
|
14
|
+
| 'graph'
|
|
15
|
+
| 'extract-knowledge'
|
|
16
|
+
| 'summarize'
|
|
17
|
+
| 'cross-reference'
|
|
18
|
+
| 'complete';
|
|
19
|
+
|
|
20
|
+
export interface DeepAnalysisConfig {
|
|
21
|
+
repoPath: string;
|
|
22
|
+
vectorStore: IVectorStore;
|
|
23
|
+
embeddingProvider: IEmbeddingProvider;
|
|
24
|
+
phases?: AnalysisPhase[];
|
|
25
|
+
onProgress?: (phase: AnalysisPhase, message: string, percent: number) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DeepAnalysisReport {
|
|
29
|
+
repoName: string;
|
|
30
|
+
repoPath: string;
|
|
31
|
+
analyzedAt: string;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
stats: {
|
|
34
|
+
totalFiles: number;
|
|
35
|
+
codeFiles: number;
|
|
36
|
+
docFiles: number;
|
|
37
|
+
configFiles: number;
|
|
38
|
+
testFiles: number;
|
|
39
|
+
totalChunks: number;
|
|
40
|
+
};
|
|
41
|
+
architecture: {
|
|
42
|
+
techStack: string[];
|
|
43
|
+
topModules: string[];
|
|
44
|
+
entryPoints: string[];
|
|
45
|
+
circularDependencies: string[][];
|
|
46
|
+
};
|
|
47
|
+
knowledge: {
|
|
48
|
+
documentsExtracted: number;
|
|
49
|
+
usefulDocuments: number;
|
|
50
|
+
noiseDocuments: number;
|
|
51
|
+
decisions: number;
|
|
52
|
+
qualityScore: number;
|
|
53
|
+
};
|
|
54
|
+
traceability: {
|
|
55
|
+
totalReferences: number;
|
|
56
|
+
featuresWithCode: number;
|
|
57
|
+
featuresWithoutCode: number;
|
|
58
|
+
orphanDocuments: string[];
|
|
59
|
+
undocumentedModules: string[];
|
|
60
|
+
};
|
|
61
|
+
summary: string;
|
|
62
|
+
recommendations: string[];
|
|
63
|
+
mermaidDiagram: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class DeepAnalyzer {
|
|
67
|
+
private config: DeepAnalysisConfig;
|
|
68
|
+
private startTime = 0;
|
|
69
|
+
|
|
70
|
+
constructor(config: DeepAnalysisConfig) {
|
|
71
|
+
this.config = {
|
|
72
|
+
phases: ['discover', 'index', 'graph', 'extract-knowledge', 'summarize', 'cross-reference', 'complete'],
|
|
73
|
+
...config,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async analyze(): Promise<DeepAnalysisReport> {
|
|
78
|
+
this.startTime = Date.now();
|
|
79
|
+
const phases = this.config.phases!;
|
|
80
|
+
let files: DiscoveredFile[] = [];
|
|
81
|
+
let graph: DependencyGraph = { nodes: new Map(), edges: [] };
|
|
82
|
+
let documents: ExtractedDocument[] = [];
|
|
83
|
+
let crossRefGraph: CrossReferenceGraph = { nodes: new Map(), edges: [] };
|
|
84
|
+
let mermaidDiagram = "";
|
|
85
|
+
|
|
86
|
+
if (phases.includes('discover')) {
|
|
87
|
+
this.emit('discover', 'Discovering files...', 0);
|
|
88
|
+
const explorer = new CodebaseExplorer();
|
|
89
|
+
files = explorer.explore({ rootPath: this.config.repoPath });
|
|
90
|
+
this.emit('discover', `Found ${files.length} files`, 100);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (phases.includes('index')) {
|
|
94
|
+
this.emit('index', 'Indexing codebase...', 0);
|
|
95
|
+
const indexer = new CodebaseIndexer(
|
|
96
|
+
this.config.vectorStore,
|
|
97
|
+
this.config.embeddingProvider,
|
|
98
|
+
{ projectPath: this.config.repoPath, onProgress: (p) => {
|
|
99
|
+
this.emit('index', `${p.phase}: ${p.chunksEmbedded}/${p.chunksCreated} chunks`,
|
|
100
|
+
p.chunksCreated > 0 ? (p.chunksEmbedded / p.chunksCreated) * 100 : 50);
|
|
101
|
+
}}
|
|
102
|
+
);
|
|
103
|
+
await indexer.index();
|
|
104
|
+
this.emit('index', 'Indexing complete', 100);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (phases.includes('graph')) {
|
|
108
|
+
this.emit('graph', 'Building dependency graph...', 0);
|
|
109
|
+
if (files.length === 0) {
|
|
110
|
+
const explorer = new CodebaseExplorer();
|
|
111
|
+
files = explorer.explore({ rootPath: this.config.repoPath });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const codeFiles = files.filter(
|
|
115
|
+
(f) => f.classification === 'source-code' || f.classification === 'test'
|
|
116
|
+
).map((f) => ({ relativePath: f.relativePath, absolutePath: f.absolutePath }));
|
|
117
|
+
|
|
118
|
+
const depExtractor = new DependencyExtractor();
|
|
119
|
+
graph = await depExtractor.buildGraph(codeFiles, this.config.repoPath);
|
|
120
|
+
this.emit('graph', `Built graph: ${graph.nodes.size} nodes, ${graph.edges.length} edges`, 100);
|
|
121
|
+
|
|
122
|
+
const visualizer = new GraphVisualizer();
|
|
123
|
+
mermaidDiagram = visualizer.toMermaid(graph, { maxNodes: 100 });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (phases.includes('extract-knowledge')) {
|
|
127
|
+
this.emit('extract-knowledge', 'Extracting knowledge from documentation...', 0);
|
|
128
|
+
if (files.length === 0) {
|
|
129
|
+
const explorer = new CodebaseExplorer();
|
|
130
|
+
files = explorer.explore({ rootPath: this.config.repoPath });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const docFiles = files.filter((f) => f.classification === 'documentation');
|
|
134
|
+
const extractor = new MarkdownExtractor();
|
|
135
|
+
documents = docFiles.map((f) => extractor.extract(f.absolutePath));
|
|
136
|
+
this.emit('extract-knowledge', `Extracted ${documents.length} documents`, 100);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (phases.includes('cross-reference')) {
|
|
140
|
+
this.emit('cross-reference', 'Building cross-references...', 0);
|
|
141
|
+
const crossRef = new CrossReferencer();
|
|
142
|
+
const codeFiles = files.filter(
|
|
143
|
+
(f) => f.classification === 'source-code'
|
|
144
|
+
).map((f) => ({ relativePath: f.relativePath }));
|
|
145
|
+
|
|
146
|
+
crossRefGraph = crossRef.buildReferences(documents, codeFiles, graph.edges as any);
|
|
147
|
+
this.emit('cross-reference', `Built ${crossRefGraph.edges.length} references`, 100);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Build report
|
|
151
|
+
this.emit('complete', 'Generating report...', 90);
|
|
152
|
+
|
|
153
|
+
const cycles = new CircularDetector().findCycles(graph);
|
|
154
|
+
const usefulDocs = documents.filter((d) => d.qualityScore >= 0.3);
|
|
155
|
+
const noiseDocs = documents.filter((d) => d.qualityScore < 0.3);
|
|
156
|
+
const decisions = documents.filter((d) => d.docType === 'decision');
|
|
157
|
+
|
|
158
|
+
const techStack = this.detectTechStack(files, graph);
|
|
159
|
+
|
|
160
|
+
const report: DeepAnalysisReport = {
|
|
161
|
+
repoName: path.basename(this.config.repoPath),
|
|
162
|
+
repoPath: this.config.repoPath,
|
|
163
|
+
analyzedAt: new Date().toISOString(),
|
|
164
|
+
durationMs: Date.now() - this.startTime,
|
|
165
|
+
stats: {
|
|
166
|
+
totalFiles: files.length,
|
|
167
|
+
codeFiles: files.filter((f) => f.classification === 'source-code').length,
|
|
168
|
+
docFiles: files.filter((f) => f.classification === 'documentation').length,
|
|
169
|
+
configFiles: files.filter((f) => f.classification === 'config').length,
|
|
170
|
+
testFiles: files.filter((f) => f.classification === 'test').length,
|
|
171
|
+
totalChunks: 0,
|
|
172
|
+
},
|
|
173
|
+
architecture: {
|
|
174
|
+
techStack,
|
|
175
|
+
topModules: this.findTopModules(graph, 10),
|
|
176
|
+
entryPoints: this.findEntryPoints(graph),
|
|
177
|
+
circularDependencies: cycles,
|
|
178
|
+
},
|
|
179
|
+
knowledge: {
|
|
180
|
+
documentsExtracted: documents.length,
|
|
181
|
+
usefulDocuments: usefulDocs.length,
|
|
182
|
+
noiseDocuments: noiseDocs.length,
|
|
183
|
+
decisions: decisions.length,
|
|
184
|
+
qualityScore: documents.length > 0
|
|
185
|
+
? usefulDocs.reduce((sum, d) => sum + d.qualityScore, 0) / usefulDocs.length
|
|
186
|
+
: 0,
|
|
187
|
+
},
|
|
188
|
+
traceability: {
|
|
189
|
+
totalReferences: crossRefGraph.edges.length,
|
|
190
|
+
featuresWithCode: 0,
|
|
191
|
+
featuresWithoutCode: 0,
|
|
192
|
+
orphanDocuments: crossRefGraph ? (new CrossReferencer() as any).findOrphanDocuments?.(crossRefGraph) ?? [] : [],
|
|
193
|
+
undocumentedModules: crossRefGraph ? (new CrossReferencer() as any).findUndocumentedCode?.(crossRefGraph) ?? [] : [],
|
|
194
|
+
},
|
|
195
|
+
summary: `This repository contains ${files.length} files across ${techStack.join(", ")} technologies.`,
|
|
196
|
+
recommendations: this.generateRecommendations(graph, cycles, usefulDocs, noiseDocs),
|
|
197
|
+
mermaidDiagram,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
this.emit('complete', 'Analysis complete', 100);
|
|
201
|
+
return report;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private emit(phase: AnalysisPhase, message: string, percent: number): void {
|
|
205
|
+
this.config.onProgress?.(phase, message, percent);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private detectTechStack(files: DiscoveredFile[], graph: DependencyGraph): string[] {
|
|
209
|
+
const tech: string[] = [];
|
|
210
|
+
const langs = new Set<string>();
|
|
211
|
+
const configPaths = new Set<string>();
|
|
212
|
+
|
|
213
|
+
for (const file of files) {
|
|
214
|
+
if (file.language) langs.add(file.language);
|
|
215
|
+
configPaths.add(file.relativePath.toLowerCase());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (langs.has("typescript") || langs.has("javascript")) tech.push("TypeScript");
|
|
219
|
+
if (langs.has("python")) tech.push("Python");
|
|
220
|
+
if (langs.has("go")) tech.push("Go");
|
|
221
|
+
if (langs.has("rust")) tech.push("Rust");
|
|
222
|
+
if (langs.has("java")) tech.push("Java");
|
|
223
|
+
|
|
224
|
+
const pathStr = Array.from(configPaths).join(" ");
|
|
225
|
+
if (pathStr.includes("package.json")) tech.push("Node.js");
|
|
226
|
+
if (pathStr.includes("docker")) tech.push("Docker");
|
|
227
|
+
if (pathStr.includes("kubernetes") || pathStr.includes("k8s")) tech.push("Kubernetes");
|
|
228
|
+
if (pathStr.includes("prisma")) tech.push("Prisma");
|
|
229
|
+
if (pathStr.includes(".github/workflows")) tech.push("GitHub Actions");
|
|
230
|
+
if (pathStr.includes("angular.json")) tech.push("Angular");
|
|
231
|
+
if (pathStr.includes("nest-cli.json") || pathStr.includes("@nestjs")) tech.push("NestJS");
|
|
232
|
+
if (pathStr.includes("next.config")) tech.push("Next.js");
|
|
233
|
+
if (pathStr.includes("react")) tech.push("React");
|
|
234
|
+
|
|
235
|
+
return tech;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private findTopModules(graph: DependencyGraph, limit: number): string[] {
|
|
239
|
+
const dependencyCount = new Map<string, number>();
|
|
240
|
+
for (const edge of graph.edges) {
|
|
241
|
+
if (!edge.to.startsWith(".")) continue;
|
|
242
|
+
dependencyCount.set(edge.to, (dependencyCount.get(edge.to) ?? 0) + 1);
|
|
243
|
+
}
|
|
244
|
+
return Array.from(dependencyCount.entries())
|
|
245
|
+
.sort((a, b) => b[1] - a[1])
|
|
246
|
+
.slice(0, limit)
|
|
247
|
+
.map(([mod]) => mod);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private findEntryPoints(graph: DependencyGraph): string[] {
|
|
251
|
+
const hasIncomingEdge = new Set<string>();
|
|
252
|
+
for (const edge of graph.edges) {
|
|
253
|
+
hasIncomingEdge.add(edge.to);
|
|
254
|
+
}
|
|
255
|
+
return Array.from(graph.nodes.keys()).filter((n) => !hasIncomingEdge.has(n)).slice(0, 20);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private generateRecommendations(
|
|
259
|
+
graph: DependencyGraph,
|
|
260
|
+
cycles: string[][],
|
|
261
|
+
usefulDocs: ExtractedDocument[],
|
|
262
|
+
noiseDocs: ExtractedDocument[]
|
|
263
|
+
): string[] {
|
|
264
|
+
const recs: string[] = [];
|
|
265
|
+
|
|
266
|
+
if (cycles.length > 0) {
|
|
267
|
+
recs.push(`Found ${cycles.length} circular dependency group(s) — review and break cycles`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (noiseDocs.length > usefulDocs.length) {
|
|
271
|
+
recs.push(`High documentation noise ratio (${noiseDocs.length} low-quality vs ${usefulDocs.length} useful docs) — consider cleanup`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const entryPoints = this.findEntryPoints(graph);
|
|
275
|
+
if (entryPoints.length === 0 && graph.nodes.size > 10) {
|
|
276
|
+
recs.push("No clear entry points detected — consider documenting the application entry");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const totalEdges = graph.edges.length;
|
|
280
|
+
const externalEdges = graph.edges.filter((e) => !e.to.startsWith(".")).length;
|
|
281
|
+
if (externalEdges > totalEdges * 0.5) {
|
|
282
|
+
recs.push("High external dependency ratio — review for potential vendor lock-in");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return recs;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface DependencyEdge {
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
type: 'import' | 'require' | 'dynamic-import';
|
|
8
|
+
symbols?: string[];
|
|
9
|
+
lineNumber: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DependencyGraph {
|
|
13
|
+
nodes: Map<string, string[]>;
|
|
14
|
+
edges: DependencyEdge[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const LANG_MAP: Record<string, string> = {
|
|
18
|
+
".ts": "TypeScript", ".tsx": "Tsx", ".mts": "TypeScript", ".cts": "TypeScript",
|
|
19
|
+
".js": "JavaScript", ".jsx": "JavaScript", ".mjs": "JavaScript", ".cjs": "JavaScript",
|
|
20
|
+
".html": "Html",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
async function parseWithAstGrep(lang: string, content: string) {
|
|
24
|
+
const { Lang, parse } = await import("@ast-grep/napi");
|
|
25
|
+
return parse(Lang[lang as keyof typeof Lang], content);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class DependencyExtractor {
|
|
29
|
+
async extractImports(filePath: string, content: string): Promise<DependencyEdge[]> {
|
|
30
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
31
|
+
const lang = LANG_MAP[ext];
|
|
32
|
+
if (!lang) return [];
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const root = (await parseWithAstGrep(lang, content)).root();
|
|
36
|
+
const edges: DependencyEdge[] = [];
|
|
37
|
+
|
|
38
|
+
const namedPatterns = [
|
|
39
|
+
'import { $NAME } from "$SOURCE"',
|
|
40
|
+
"import { $NAME } from '$SOURCE'",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const defaultPatterns = [
|
|
44
|
+
'import $NAME from "$SOURCE"',
|
|
45
|
+
"import $NAME from '$SOURCE'",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const pattern of namedPatterns) {
|
|
49
|
+
for (const node of root.findAll(pattern)) {
|
|
50
|
+
const source = node.getMatch('SOURCE')?.text();
|
|
51
|
+
if (!source) continue;
|
|
52
|
+
|
|
53
|
+
const cleanSource = source.replace(/^["']|["']$/g, "");
|
|
54
|
+
if (!cleanSource) continue;
|
|
55
|
+
|
|
56
|
+
const fullText = node.text();
|
|
57
|
+
const braceMatch = fullText.match(/\{([^}]+)\}/);
|
|
58
|
+
const symbols: string[] = [];
|
|
59
|
+
if (braceMatch) {
|
|
60
|
+
const names = braceMatch[1].match(/[A-Za-z_$][\w$]*/g) ?? [];
|
|
61
|
+
symbols.push(...names);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (symbols.length === 0) {
|
|
65
|
+
const name = node.getMatch('NAME')?.text();
|
|
66
|
+
if (name) symbols.push(name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
edges.push({
|
|
70
|
+
from: filePath,
|
|
71
|
+
to: cleanSource,
|
|
72
|
+
type: 'import',
|
|
73
|
+
symbols: symbols.length > 0 ? symbols : undefined,
|
|
74
|
+
lineNumber: node.range().start.line + 1,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const pattern of defaultPatterns) {
|
|
80
|
+
for (const node of root.findAll(pattern)) {
|
|
81
|
+
const source = node.getMatch('SOURCE')?.text();
|
|
82
|
+
if (!source) continue;
|
|
83
|
+
|
|
84
|
+
const cleanSource = source.replace(/^["']|["']$/g, "");
|
|
85
|
+
if (!cleanSource) continue;
|
|
86
|
+
|
|
87
|
+
const name = node.getMatch('NAME')?.text();
|
|
88
|
+
edges.push({
|
|
89
|
+
from: filePath,
|
|
90
|
+
to: cleanSource,
|
|
91
|
+
type: 'import',
|
|
92
|
+
symbols: name ? [name] : undefined,
|
|
93
|
+
lineNumber: node.range().start.line + 1,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const pattern of ["const $NAME = require(\"$SOURCE\")", "const $NAME = require('$SOURCE')"]) {
|
|
99
|
+
for (const node of root.findAll(pattern)) {
|
|
100
|
+
const source = node.getMatch('SOURCE')?.text();
|
|
101
|
+
if (!source) continue;
|
|
102
|
+
|
|
103
|
+
const cleanSource = source.replace(/^["']|["']$/g, "");
|
|
104
|
+
if (!cleanSource) continue;
|
|
105
|
+
|
|
106
|
+
edges.push({
|
|
107
|
+
from: filePath,
|
|
108
|
+
to: cleanSource,
|
|
109
|
+
type: 'require',
|
|
110
|
+
lineNumber: node.range().start.line + 1,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return edges;
|
|
116
|
+
} catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async buildGraph(files: Array<{ relativePath: string; absolutePath: string }>, rootPath: string): Promise<DependencyGraph> {
|
|
122
|
+
const nodes = new Map<string, string[]>();
|
|
123
|
+
const edges: DependencyEdge[] = [];
|
|
124
|
+
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
let content: string;
|
|
127
|
+
try {
|
|
128
|
+
content = fs.readFileSync(file.absolutePath, "utf8");
|
|
129
|
+
} catch {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const fileEdges = await this.extractImports(file.relativePath, content);
|
|
134
|
+
for (const edge of fileEdges) {
|
|
135
|
+
const resolved = this.resolvePath(edge.to, file.relativePath, rootPath);
|
|
136
|
+
edge.to = resolved;
|
|
137
|
+
edges.push(edge);
|
|
138
|
+
|
|
139
|
+
if (!nodes.has(edge.from)) {
|
|
140
|
+
nodes.set(edge.from, []);
|
|
141
|
+
}
|
|
142
|
+
nodes.get(edge.from)!.push(resolved);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { nodes, edges };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private resolvePath(importPath: string, fromFile: string, rootPath: string): string {
|
|
150
|
+
if (!importPath.startsWith(".")) return importPath;
|
|
151
|
+
|
|
152
|
+
const fromDir = path.dirname(fromFile);
|
|
153
|
+
const resolved = path.join(fromDir, importPath).replace(/\\/g, "/");
|
|
154
|
+
|
|
155
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", "/index.ts", "/index.js"];
|
|
156
|
+
for (const ext of extensions) {
|
|
157
|
+
const fullPath = path.join(rootPath, resolved + ext);
|
|
158
|
+
if (fs.existsSync(fullPath)) {
|
|
159
|
+
return resolved + ext;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return resolved;
|
|
164
|
+
}
|
|
165
|
+
}
|