@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.
Files changed (278) hide show
  1. package/dist/contexts/context-decomposer.d.ts +13 -0
  2. package/dist/contexts/context-decomposer.d.ts.map +1 -0
  3. package/dist/contexts/context-decomposer.js +97 -0
  4. package/dist/contexts/context-decomposer.js.map +1 -0
  5. package/dist/contexts/context-document-manager.d.ts +21 -0
  6. package/dist/contexts/context-document-manager.d.ts.map +1 -0
  7. package/dist/contexts/context-document-manager.js +69 -0
  8. package/dist/contexts/context-document-manager.js.map +1 -0
  9. package/dist/contexts/context-router.d.ts +13 -0
  10. package/dist/contexts/context-router.d.ts.map +1 -0
  11. package/dist/contexts/context-router.js +94 -0
  12. package/dist/contexts/context-router.js.map +1 -0
  13. package/dist/index.d.ts +53 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +76 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/libs/codebase/deep-analyzer.d.ts +58 -0
  18. package/dist/libs/codebase/deep-analyzer.d.ts.map +1 -0
  19. package/dist/libs/codebase/deep-analyzer.js +239 -0
  20. package/dist/libs/codebase/deep-analyzer.js.map +1 -0
  21. package/dist/libs/codebase/graph/dependency-extractor.d.ts +20 -0
  22. package/dist/libs/codebase/graph/dependency-extractor.d.ts.map +1 -0
  23. package/dist/libs/codebase/graph/dependency-extractor.js +174 -0
  24. package/dist/libs/codebase/graph/dependency-extractor.js.map +1 -0
  25. package/dist/libs/codebase/graph/graph-analysis.d.ts +42 -0
  26. package/dist/libs/codebase/graph/graph-analysis.d.ts.map +1 -0
  27. package/dist/libs/codebase/graph/graph-analysis.js +222 -0
  28. package/dist/libs/codebase/graph/graph-analysis.js.map +1 -0
  29. package/dist/libs/codebase/graph/graph-visualizer.d.ts +12 -0
  30. package/dist/libs/codebase/graph/graph-visualizer.d.ts.map +1 -0
  31. package/dist/libs/codebase/graph/graph-visualizer.js +99 -0
  32. package/dist/libs/codebase/graph/graph-visualizer.js.map +1 -0
  33. package/dist/libs/codebase/indexer/batch-processor.d.ts +18 -0
  34. package/dist/libs/codebase/indexer/batch-processor.d.ts.map +1 -0
  35. package/dist/libs/codebase/indexer/batch-processor.js +39 -0
  36. package/dist/libs/codebase/indexer/batch-processor.js.map +1 -0
  37. package/dist/libs/codebase/indexer/chunker-markdown.d.ts +19 -0
  38. package/dist/libs/codebase/indexer/chunker-markdown.d.ts.map +1 -0
  39. package/dist/libs/codebase/indexer/chunker-markdown.js +138 -0
  40. package/dist/libs/codebase/indexer/chunker-markdown.js.map +1 -0
  41. package/dist/libs/codebase/indexer/chunker.d.ts +21 -0
  42. package/dist/libs/codebase/indexer/chunker.d.ts.map +1 -0
  43. package/dist/libs/codebase/indexer/chunker.js +133 -0
  44. package/dist/libs/codebase/indexer/chunker.js.map +1 -0
  45. package/dist/libs/codebase/indexer/explorer.d.ts +29 -0
  46. package/dist/libs/codebase/indexer/explorer.d.ts.map +1 -0
  47. package/dist/libs/codebase/indexer/explorer.js +278 -0
  48. package/dist/libs/codebase/indexer/explorer.js.map +1 -0
  49. package/dist/libs/codebase/indexer/indexer.d.ts +35 -0
  50. package/dist/libs/codebase/indexer/indexer.d.ts.map +1 -0
  51. package/dist/libs/codebase/indexer/indexer.js +172 -0
  52. package/dist/libs/codebase/indexer/indexer.js.map +1 -0
  53. package/dist/libs/codebase/knowledge/cross-referencer.d.ts +32 -0
  54. package/dist/libs/codebase/knowledge/cross-referencer.d.ts.map +1 -0
  55. package/dist/libs/codebase/knowledge/cross-referencer.js +132 -0
  56. package/dist/libs/codebase/knowledge/cross-referencer.js.map +1 -0
  57. package/dist/libs/codebase/knowledge/markdown-extractor.d.ts +34 -0
  58. package/dist/libs/codebase/knowledge/markdown-extractor.d.ts.map +1 -0
  59. package/dist/libs/codebase/knowledge/markdown-extractor.js +273 -0
  60. package/dist/libs/codebase/knowledge/markdown-extractor.js.map +1 -0
  61. package/dist/libs/codebase/knowledge/summarizer.d.ts +25 -0
  62. package/dist/libs/codebase/knowledge/summarizer.d.ts.map +1 -0
  63. package/dist/libs/codebase/knowledge/summarizer.js +61 -0
  64. package/dist/libs/codebase/knowledge/summarizer.js.map +1 -0
  65. package/dist/libs/codebase/search/bm25.d.ts +23 -0
  66. package/dist/libs/codebase/search/bm25.d.ts.map +1 -0
  67. package/dist/libs/codebase/search/bm25.js +93 -0
  68. package/dist/libs/codebase/search/bm25.js.map +1 -0
  69. package/dist/libs/codebase/search/hybrid-search.d.ts +22 -0
  70. package/dist/libs/codebase/search/hybrid-search.d.ts.map +1 -0
  71. package/dist/libs/codebase/search/hybrid-search.js +80 -0
  72. package/dist/libs/codebase/search/hybrid-search.js.map +1 -0
  73. package/dist/libs/codebase-index.class.d.ts +66 -0
  74. package/dist/libs/codebase-index.class.d.ts.map +1 -0
  75. package/dist/libs/codebase-index.class.js +595 -0
  76. package/dist/libs/codebase-index.class.js.map +1 -0
  77. package/dist/tools/codebase/codebase-analyze.tool.d.ts +8 -0
  78. package/dist/tools/codebase/codebase-analyze.tool.d.ts.map +1 -0
  79. package/dist/tools/codebase/codebase-analyze.tool.js +20 -0
  80. package/dist/tools/codebase/codebase-analyze.tool.js.map +1 -0
  81. package/dist/tools/codebase/codebase-crossref.tool.d.ts +8 -0
  82. package/dist/tools/codebase/codebase-crossref.tool.d.ts.map +1 -0
  83. package/dist/tools/codebase/codebase-crossref.tool.js +20 -0
  84. package/dist/tools/codebase/codebase-crossref.tool.js.map +1 -0
  85. package/dist/tools/codebase/codebase-flow.tool.d.ts +8 -0
  86. package/dist/tools/codebase/codebase-flow.tool.d.ts.map +1 -0
  87. package/dist/tools/codebase/codebase-flow.tool.js +20 -0
  88. package/dist/tools/codebase/codebase-flow.tool.js.map +1 -0
  89. package/dist/tools/codebase/codebase-graph-circular.tool.d.ts +8 -0
  90. package/dist/tools/codebase/codebase-graph-circular.tool.d.ts.map +1 -0
  91. package/dist/tools/codebase/codebase-graph-circular.tool.js +17 -0
  92. package/dist/tools/codebase/codebase-graph-circular.tool.js.map +1 -0
  93. package/dist/tools/codebase/codebase-graph-query.tool.d.ts +8 -0
  94. package/dist/tools/codebase/codebase-graph-query.tool.d.ts.map +1 -0
  95. package/dist/tools/codebase/codebase-graph-query.tool.js +21 -0
  96. package/dist/tools/codebase/codebase-graph-query.tool.js.map +1 -0
  97. package/dist/tools/codebase/codebase-graph-stats.tool.d.ts +8 -0
  98. package/dist/tools/codebase/codebase-graph-stats.tool.d.ts.map +1 -0
  99. package/dist/tools/codebase/codebase-graph-stats.tool.js +17 -0
  100. package/dist/tools/codebase/codebase-graph-stats.tool.js.map +1 -0
  101. package/dist/tools/codebase/codebase-graph-visualize.tool.d.ts +8 -0
  102. package/dist/tools/codebase/codebase-graph-visualize.tool.d.ts.map +1 -0
  103. package/dist/tools/codebase/codebase-graph-visualize.tool.js +21 -0
  104. package/dist/tools/codebase/codebase-graph-visualize.tool.js.map +1 -0
  105. package/dist/tools/codebase/codebase-impact.tool.d.ts +8 -0
  106. package/dist/tools/codebase/codebase-impact.tool.d.ts.map +1 -0
  107. package/dist/tools/codebase/codebase-impact.tool.js +21 -0
  108. package/dist/tools/codebase/codebase-impact.tool.js.map +1 -0
  109. package/dist/tools/codebase/codebase-index.tool.d.ts +8 -0
  110. package/dist/tools/codebase/codebase-index.tool.d.ts.map +1 -0
  111. package/dist/tools/codebase/codebase-index.tool.js +21 -0
  112. package/dist/tools/codebase/codebase-index.tool.js.map +1 -0
  113. package/dist/tools/codebase/codebase-knowledge.tool.d.ts +8 -0
  114. package/dist/tools/codebase/codebase-knowledge.tool.d.ts.map +1 -0
  115. package/dist/tools/codebase/codebase-knowledge.tool.js +22 -0
  116. package/dist/tools/codebase/codebase-knowledge.tool.js.map +1 -0
  117. package/dist/tools/codebase/codebase-search.tool.d.ts +8 -0
  118. package/dist/tools/codebase/codebase-search.tool.d.ts.map +1 -0
  119. package/dist/tools/codebase/codebase-search.tool.js +24 -0
  120. package/dist/tools/codebase/codebase-search.tool.js.map +1 -0
  121. package/dist/tools/codebase/codebase-status.tool.d.ts +8 -0
  122. package/dist/tools/codebase/codebase-status.tool.d.ts.map +1 -0
  123. package/dist/tools/codebase/codebase-status.tool.js +19 -0
  124. package/dist/tools/codebase/codebase-status.tool.js.map +1 -0
  125. package/dist/tools/neural/comparative-analysis.tool.d.ts +37 -0
  126. package/dist/tools/neural/comparative-analysis.tool.d.ts.map +1 -0
  127. package/dist/tools/neural/comparative-analysis.tool.js +241 -0
  128. package/dist/tools/neural/comparative-analysis.tool.js.map +1 -0
  129. package/dist/tools/neural/index.d.ts +12 -0
  130. package/dist/tools/neural/index.d.ts.map +1 -0
  131. package/dist/tools/neural/index.js +28 -0
  132. package/dist/tools/neural/index.js.map +1 -0
  133. package/dist/tools/neural/neural-deadcode.tool.d.ts +10 -0
  134. package/dist/tools/neural/neural-deadcode.tool.d.ts.map +1 -0
  135. package/dist/tools/neural/neural-deadcode.tool.js +133 -0
  136. package/dist/tools/neural/neural-deadcode.tool.js.map +1 -0
  137. package/dist/tools/neural/neural-debug.tool.d.ts +11 -0
  138. package/dist/tools/neural/neural-debug.tool.d.ts.map +1 -0
  139. package/dist/tools/neural/neural-debug.tool.js +205 -0
  140. package/dist/tools/neural/neural-debug.tool.js.map +1 -0
  141. package/dist/tools/neural/neural-explain.tool.d.ts +9 -0
  142. package/dist/tools/neural/neural-explain.tool.d.ts.map +1 -0
  143. package/dist/tools/neural/neural-explain.tool.js +100 -0
  144. package/dist/tools/neural/neural-explain.tool.js.map +1 -0
  145. package/dist/tools/neural/neural-optimize.tool.d.ts +9 -0
  146. package/dist/tools/neural/neural-optimize.tool.d.ts.map +1 -0
  147. package/dist/tools/neural/neural-optimize.tool.js +117 -0
  148. package/dist/tools/neural/neural-optimize.tool.js.map +1 -0
  149. package/dist/tools/neural/neural-pattern.tool.d.ts +10 -0
  150. package/dist/tools/neural/neural-pattern.tool.d.ts.map +1 -0
  151. package/dist/tools/neural/neural-pattern.tool.js +169 -0
  152. package/dist/tools/neural/neural-pattern.tool.js.map +1 -0
  153. package/dist/tools/neural/neural-review.tool.d.ts +11 -0
  154. package/dist/tools/neural/neural-review.tool.d.ts.map +1 -0
  155. package/dist/tools/neural/neural-review.tool.js +152 -0
  156. package/dist/tools/neural/neural-review.tool.js.map +1 -0
  157. package/dist/tools/neural/neural-security.tool.d.ts +16 -0
  158. package/dist/tools/neural/neural-security.tool.d.ts.map +1 -0
  159. package/dist/tools/neural/neural-security.tool.js +256 -0
  160. package/dist/tools/neural/neural-security.tool.js.map +1 -0
  161. package/dist/tools/neural/neural-similar.tool.d.ts +9 -0
  162. package/dist/tools/neural/neural-similar.tool.d.ts.map +1 -0
  163. package/dist/tools/neural/neural-similar.tool.js +139 -0
  164. package/dist/tools/neural/neural-similar.tool.js.map +1 -0
  165. package/dist/tools/neural/neural-smells.tool.d.ts +9 -0
  166. package/dist/tools/neural/neural-smells.tool.d.ts.map +1 -0
  167. package/dist/tools/neural/neural-smells.tool.js +123 -0
  168. package/dist/tools/neural/neural-smells.tool.js.map +1 -0
  169. package/dist/tools/neural/neural-suggest.tool.d.ts +9 -0
  170. package/dist/tools/neural/neural-suggest.tool.d.ts.map +1 -0
  171. package/dist/tools/neural/neural-suggest.tool.js +64 -0
  172. package/dist/tools/neural/neural-suggest.tool.js.map +1 -0
  173. package/dist/tools/quality/index.d.ts +8 -0
  174. package/dist/tools/quality/index.d.ts.map +1 -0
  175. package/dist/tools/quality/index.js +24 -0
  176. package/dist/tools/quality/index.js.map +1 -0
  177. package/dist/tools/quality/quality-complexity.tool.d.ts +11 -0
  178. package/dist/tools/quality/quality-complexity.tool.d.ts.map +1 -0
  179. package/dist/tools/quality/quality-complexity.tool.js +152 -0
  180. package/dist/tools/quality/quality-complexity.tool.js.map +1 -0
  181. package/dist/tools/quality/quality-coupling.tool.d.ts +9 -0
  182. package/dist/tools/quality/quality-coupling.tool.d.ts.map +1 -0
  183. package/dist/tools/quality/quality-coupling.tool.js +51 -0
  184. package/dist/tools/quality/quality-coupling.tool.js.map +1 -0
  185. package/dist/tools/quality/quality-coverage.tool.d.ts +9 -0
  186. package/dist/tools/quality/quality-coverage.tool.d.ts.map +1 -0
  187. package/dist/tools/quality/quality-coverage.tool.js +104 -0
  188. package/dist/tools/quality/quality-coverage.tool.js.map +1 -0
  189. package/dist/tools/quality/quality-deps.tool.d.ts +11 -0
  190. package/dist/tools/quality/quality-deps.tool.d.ts.map +1 -0
  191. package/dist/tools/quality/quality-deps.tool.js +119 -0
  192. package/dist/tools/quality/quality-deps.tool.js.map +1 -0
  193. package/dist/tools/quality/quality-duplication.tool.d.ts +9 -0
  194. package/dist/tools/quality/quality-duplication.tool.d.ts.map +1 -0
  195. package/dist/tools/quality/quality-duplication.tool.js +100 -0
  196. package/dist/tools/quality/quality-duplication.tool.js.map +1 -0
  197. package/dist/tools/quality/quality-size.tool.d.ts +9 -0
  198. package/dist/tools/quality/quality-size.tool.d.ts.map +1 -0
  199. package/dist/tools/quality/quality-size.tool.js +94 -0
  200. package/dist/tools/quality/quality-size.tool.js.map +1 -0
  201. package/dist/tools/quality/quality-types.tool.d.ts +9 -0
  202. package/dist/tools/quality/quality-types.tool.d.ts.map +1 -0
  203. package/dist/tools/quality/quality-types.tool.js +95 -0
  204. package/dist/tools/quality/quality-types.tool.js.map +1 -0
  205. package/dist/tools/workspace/analyze-project.tool.d.ts +36 -0
  206. package/dist/tools/workspace/analyze-project.tool.d.ts.map +1 -0
  207. package/dist/tools/workspace/analyze-project.tool.js +454 -0
  208. package/dist/tools/workspace/analyze-project.tool.js.map +1 -0
  209. package/dist/tools/workspace/codebase-research.tool.d.ts +10 -0
  210. package/dist/tools/workspace/codebase-research.tool.d.ts.map +1 -0
  211. package/dist/tools/workspace/codebase-research.tool.js +89 -0
  212. package/dist/tools/workspace/codebase-research.tool.js.map +1 -0
  213. package/dist/tools/workspace/project-scanner.tool.d.ts +55 -0
  214. package/dist/tools/workspace/project-scanner.tool.d.ts.map +1 -0
  215. package/dist/tools/workspace/project-scanner.tool.js +978 -0
  216. package/dist/tools/workspace/project-scanner.tool.js.map +1 -0
  217. package/dist/tools/workspace/search-code.tool.d.ts +14 -0
  218. package/dist/tools/workspace/search-code.tool.d.ts.map +1 -0
  219. package/dist/tools/workspace/search-code.tool.js +286 -0
  220. package/dist/tools/workspace/search-code.tool.js.map +1 -0
  221. package/package.json +17 -0
  222. package/src/contexts/context-decomposer.ts +113 -0
  223. package/src/contexts/context-document-manager.ts +70 -0
  224. package/src/contexts/context-router.ts +121 -0
  225. package/src/index.ts +65 -0
  226. package/src/libs/codebase/deep-analyzer.ts +287 -0
  227. package/src/libs/codebase/graph/dependency-extractor.ts +165 -0
  228. package/src/libs/codebase/graph/graph-analysis.ts +231 -0
  229. package/src/libs/codebase/graph/graph-visualizer.ts +124 -0
  230. package/src/libs/codebase/indexer/batch-processor.ts +47 -0
  231. package/src/libs/codebase/indexer/chunker-markdown.ts +175 -0
  232. package/src/libs/codebase/indexer/chunker.ts +126 -0
  233. package/src/libs/codebase/indexer/explorer.ts +269 -0
  234. package/src/libs/codebase/indexer/indexer.ts +174 -0
  235. package/src/libs/codebase/knowledge/cross-referencer.ts +182 -0
  236. package/src/libs/codebase/knowledge/markdown-extractor.ts +270 -0
  237. package/src/libs/codebase/knowledge/summarizer.ts +85 -0
  238. package/src/libs/codebase/search/bm25.ts +109 -0
  239. package/src/libs/codebase/search/hybrid-search.ts +109 -0
  240. package/src/libs/codebase-index.class.ts +715 -0
  241. package/src/tools/codebase/codebase-analyze.tool.ts +18 -0
  242. package/src/tools/codebase/codebase-crossref.tool.ts +18 -0
  243. package/src/tools/codebase/codebase-flow.tool.ts +18 -0
  244. package/src/tools/codebase/codebase-graph-circular.tool.ts +15 -0
  245. package/src/tools/codebase/codebase-graph-query.tool.ts +19 -0
  246. package/src/tools/codebase/codebase-graph-stats.tool.ts +15 -0
  247. package/src/tools/codebase/codebase-graph-visualize.tool.ts +19 -0
  248. package/src/tools/codebase/codebase-impact.tool.ts +19 -0
  249. package/src/tools/codebase/codebase-index.tool.ts +19 -0
  250. package/src/tools/codebase/codebase-knowledge.tool.ts +20 -0
  251. package/src/tools/codebase/codebase-search.tool.ts +22 -0
  252. package/src/tools/codebase/codebase-status.tool.ts +17 -0
  253. package/src/tools/neural/comparative-analysis.tool.ts +309 -0
  254. package/src/tools/neural/index.ts +11 -0
  255. package/src/tools/neural/neural-deadcode.tool.ts +105 -0
  256. package/src/tools/neural/neural-debug.tool.ts +199 -0
  257. package/src/tools/neural/neural-explain.tool.ts +67 -0
  258. package/src/tools/neural/neural-optimize.tool.ts +93 -0
  259. package/src/tools/neural/neural-pattern.tool.ts +141 -0
  260. package/src/tools/neural/neural-review.tool.ts +145 -0
  261. package/src/tools/neural/neural-security.tool.ts +226 -0
  262. package/src/tools/neural/neural-similar.tool.ts +113 -0
  263. package/src/tools/neural/neural-smells.tool.ts +98 -0
  264. package/src/tools/neural/neural-suggest.tool.ts +65 -0
  265. package/src/tools/quality/index.ts +7 -0
  266. package/src/tools/quality/quality-complexity.tool.ts +132 -0
  267. package/src/tools/quality/quality-coupling.tool.ts +57 -0
  268. package/src/tools/quality/quality-coverage.tool.ts +78 -0
  269. package/src/tools/quality/quality-deps.tool.ts +89 -0
  270. package/src/tools/quality/quality-duplication.tool.ts +71 -0
  271. package/src/tools/quality/quality-size.tool.ts +66 -0
  272. package/src/tools/quality/quality-types.tool.ts +66 -0
  273. package/src/tools/workspace/analyze-project.tool.ts +429 -0
  274. package/src/tools/workspace/codebase-research.tool.ts +100 -0
  275. package/src/tools/workspace/project-scanner.tool.ts +1058 -0
  276. package/src/tools/workspace/search-code.tool.ts +344 -0
  277. package/tsconfig.build.json +12 -0
  278. package/tsconfig.json +9 -0
@@ -0,0 +1,226 @@
1
+ import { z } from "zod";
2
+ import { ToolBase, ToolContext, ToolExecutionResult } from "@thoughtflow/core";
3
+ import { CodebaseExplorer } from "../../libs/codebase/indexer/explorer";
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+
7
+ const SecurityIssueSchema = z.object({
8
+ severity: z.enum(["critical", "high", "medium", "low"]),
9
+ cwe: z.string(),
10
+ category: z.string(),
11
+ message: z.string(),
12
+ remediation: z.string(),
13
+ });
14
+
15
+ const SecurityResultSchema = z.object({
16
+ summary: z.string(),
17
+ findings: z.array(SecurityIssueSchema),
18
+ riskLevel: z.enum(["critical", "high", "medium", "low", "none"]),
19
+ });
20
+
21
+ type SecurityFinding = z.infer<typeof SecurityIssueSchema>;
22
+
23
+ export class NeuralSecurityTool extends ToolBase {
24
+ readonly name = "neuralSecurity";
25
+ readonly description = "Scan source code for security vulnerabilities: hardcoded secrets, injection risks, unsafe crypto, XSS, CSRF, path traversal. LLM-powered deep analysis with CWE references.";
26
+ readonly = true;
27
+
28
+ constructor() {
29
+ super(z.object({
30
+ target: z.string().optional().describe("File or directory to scan"),
31
+ scope: z.enum(["all", "secrets", "injection", "crypto", "xss", "auth"]).optional(),
32
+ projectPath: z.string().optional(),
33
+ }));
34
+ }
35
+
36
+ async run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolExecutionResult> {
37
+ const rootPath = (input.projectPath as string) ?? process.cwd();
38
+ const scope = (input.scope as string) ?? "all";
39
+ const target = (input.target as string) || undefined;
40
+
41
+ const explorer = new CodebaseExplorer();
42
+ const files = explorer.explore({ rootPath });
43
+
44
+ const scanFiles = target
45
+ ? files.filter((f) => f.relativePath.includes(target) || f.absolutePath.includes(target))
46
+ : files.filter((f) => f.classification === "source-code" || f.classification === "config");
47
+
48
+ const staticFindings: SecurityFinding[] = [];
49
+
50
+ for (const file of scanFiles.slice(0, 100)) {
51
+ try {
52
+ const content = fs.readFileSync(file.absolutePath, "utf8");
53
+ const lines = content.split("\n");
54
+ for (let i = 0; i < lines.length; i++) {
55
+ const line = lines[i];
56
+ if (scope === "all" || scope === "secrets") this.scanSecrets(line, file.relativePath, i + 1, staticFindings);
57
+ if (scope === "all" || scope === "injection") this.scanInjection(line, file.relativePath, i + 1, staticFindings);
58
+ if (scope === "all" || scope === "crypto") this.scanCrypto(line, file.relativePath, i + 1, staticFindings);
59
+ if (scope === "all" || scope === "xss") this.scanXss(line, file.relativePath, i + 1, staticFindings);
60
+ if (scope === "all" || scope === "auth") this.scanAuth(line, file.relativePath, i + 1, content, staticFindings);
61
+ }
62
+ } catch {
63
+ continue;
64
+ }
65
+ }
66
+
67
+ const cs = ctx.conversation?.callStructured;
68
+ if (!cs) {
69
+ const severityCounts = this.countBySeverity(staticFindings);
70
+ return {
71
+ success: true,
72
+ output: JSON.stringify({
73
+ summary: `Static scan: ${staticFindings.length} findings across ${scanFiles.length} files`,
74
+ filesScanned: scanFiles.length,
75
+ severityCounts,
76
+ findings: staticFindings.slice(0, 30),
77
+ riskLevel: this.computeRiskLevel(staticFindings),
78
+ }),
79
+ };
80
+ }
81
+
82
+ const suspiciousFiles = scanFiles.filter((f) => {
83
+ try {
84
+ const c = fs.readFileSync(f.absolutePath, "utf8");
85
+ return c.includes("secret") || c.includes("token") || c.includes("password") || c.includes("eval") || c.includes("innerHTML") || c.includes("sql") || c.includes("crypto");
86
+ } catch { return false; }
87
+ }).slice(0, 5);
88
+
89
+ if (suspiciousFiles.length === 0) {
90
+ const severityCounts = this.countBySeverity(staticFindings);
91
+ return {
92
+ success: true,
93
+ output: JSON.stringify({
94
+ summary: "No suspicious files for deep scan",
95
+ filesScanned: scanFiles.length,
96
+ severityCounts,
97
+ findings: staticFindings.slice(0, 30),
98
+ riskLevel: this.computeRiskLevel(staticFindings),
99
+ }),
100
+ };
101
+ }
102
+
103
+ const codeSnippets = suspiciousFiles
104
+ .map((f) => {
105
+ try {
106
+ return `=== ${f.relativePath} ===\n${fs.readFileSync(f.absolutePath, "utf8").slice(0, 4000)}`;
107
+ } catch { return ""; }
108
+ })
109
+ .filter(Boolean)
110
+ .join("\n\n");
111
+
112
+ const staticHints = staticFindings
113
+ .slice(0, 20)
114
+ .map((f) => `${f.severity.toUpperCase()}: ${f.cwe} - ${f.message}`)
115
+ .join("\n");
116
+
117
+ const prompt = `You are a security auditor specializing in OWASP Top 10 vulnerabilities. Analyze the provided source code and return structured findings with CWE references, severity ratings, and actionable remediation steps.\n\nScan the following source code for security vulnerabilities. Focus on: ${scope}.\n\nSTATIC SCAN HIGHLIGHTS (use these as hints, but perform your own deeper analysis):\n${staticHints || "No static findings"}\n\nSOURCE CODE:\n${codeSnippets.slice(0, 12000)}`;
118
+
119
+ try {
120
+ const result = await cs(prompt, SecurityResultSchema);
121
+
122
+ const allFindings = [...staticFindings, ...(result.findings ?? [])];
123
+ const severityCounts = this.countBySeverity(allFindings);
124
+
125
+ return {
126
+ success: true,
127
+ output: JSON.stringify({
128
+ summary: result.summary ?? `${allFindings.length} total findings`,
129
+ filesScanned: scanFiles.length,
130
+ severityCounts,
131
+ findings: allFindings.slice(0, 50),
132
+ riskLevel: result.riskLevel ?? this.computeRiskLevel(allFindings),
133
+ }),
134
+ };
135
+ } catch {
136
+ const severityCounts = this.countBySeverity(staticFindings);
137
+ return {
138
+ success: true,
139
+ output: JSON.stringify({
140
+ summary: "LLM scan failed — static analysis only",
141
+ filesScanned: scanFiles.length,
142
+ severityCounts,
143
+ findings: staticFindings.slice(0, 30),
144
+ riskLevel: this.computeRiskLevel(staticFindings),
145
+ }),
146
+ };
147
+ }
148
+ }
149
+
150
+ private scanSecrets(line: string, file: string, lineNum: number, findings: SecurityFinding[]): void {
151
+ if (line.match(/(api[_-]?key|secret|password|token|auth)\s*[:=]\s*['"][^$`{].{6,}['"]/i) && !line.includes("process.env") && !line.includes("import")) {
152
+ if (!line.trim().startsWith("//") && !line.trim().startsWith("/*")) {
153
+ findings.push({ severity: "critical", cwe: "CWE-798", category: "secrets", message: `Hardcoded credential in ${file}:${lineNum}`, remediation: "Move to environment variable or secret manager" });
154
+ }
155
+ }
156
+ if (line.match(/['"]((?:sk-[A-Za-z0-9]{20,})|(?:ghp_[A-Za-z0-9]{20,})|(?:xox[bpras]-[A-Za-z0-9-]{10,}))['"]/)) {
157
+ findings.push({ severity: "critical", cwe: "CWE-798", category: "secrets", message: `API key detected in ${file}:${lineNum}`, remediation: "Use environment variables or a secrets vault — revoke the exposed key immediately" });
158
+ }
159
+ }
160
+
161
+ private scanInjection(line: string, file: string, lineNum: number, findings: SecurityFinding[]): void {
162
+ if (line.match(/eval\s*\(/)) findings.push({ severity: "critical", cwe: "CWE-95", category: "injection", message: `eval() usage in ${file}:${lineNum}`, remediation: "Remove eval() — use JSON.parse or safer alternatives" });
163
+ if (line.match(/exec\s*\(\s*['"`][^'"]*\$\{/)) findings.push({ severity: "critical", cwe: "CWE-78", category: "injection", message: `Command injection risk in ${file}:${lineNum}`, remediation: "Use execFile() with argument arrays, or sanitize inputs" });
164
+ if (line.match(/(?:execute|query|run)\s*\(\s*['"`][^'"]*\$/) && !line.includes("?") && !line.includes("$1")) {
165
+ findings.push({ severity: "high", cwe: "CWE-89", category: "injection", message: `Potential SQL injection in ${file}:${lineNum}`, remediation: "Use parameterized queries or an ORM with input binding" });
166
+ }
167
+ if (line.match(/new Function\s*\(/)) findings.push({ severity: "critical", cwe: "CWE-95", category: "injection", message: `new Function() in ${file}:${lineNum}`, remediation: "Avoid new Function() — refactor to use proper logic constructs" });
168
+ if (line.match(/require\s*\(\s*.+\s*\+\s*.+/) || line.match(/import\s*\(\s*.+\s*\+\s*.+\)/)) {
169
+ findings.push({ severity: "high", cwe: "CWE-641", category: "injection", message: `Dynamic import with concatenation in ${file}:${lineNum}`, remediation: "Use a static import map instead of dynamic concatenation" });
170
+ }
171
+ }
172
+
173
+ private scanCrypto(line: string, file: string, lineNum: number, findings: SecurityFinding[]): void {
174
+ if (line.match(/Math\.random\s*\(\s*\)/) && (line.includes("token") || line.includes("password") || line.includes("secret") || line.includes("nonce"))) {
175
+ findings.push({ severity: "high", cwe: "CWE-338", category: "crypto", message: `Math.random() for security in ${file}:${lineNum}`, remediation: "Use crypto.randomBytes() or crypto.randomUUID()" });
176
+ }
177
+ if (line.match(/md5|crc32|sha1(?!256|512)/i) && !line.includes("import") && !line.includes("type")) {
178
+ findings.push({ severity: "medium", cwe: "CWE-328", category: "crypto", message: `Weak hash (MD5/SHA1/CRC32) in ${file}:${lineNum}`, remediation: "Use SHA-256, SHA-512, or bcrypt/scrypt for passwords" });
179
+ }
180
+ if (line.match(/crypto\.createCipher\s*\(/) || line.match(/new\s+Cipher\s*\(/)) {
181
+ findings.push({ severity: "medium", cwe: "CWE-327", category: "crypto", message: `Insecure cipher usage in ${file}:${lineNum}`, remediation: "Use AES-256-GCM or ChaCha20-Poly1305 with authenticated encryption" });
182
+ }
183
+ }
184
+
185
+ private scanXss(line: string, file: string, lineNum: number, findings: SecurityFinding[]): void {
186
+ if (line.match(/innerHTML\s*=|dangerouslySetInnerHTML|v-html/)) {
187
+ findings.push({ severity: "high", cwe: "CWE-79", category: "xss", message: `Potential XSS in ${file}:${lineNum}`, remediation: "Sanitize input with DOMPurify or use textContent instead" });
188
+ }
189
+ if (line.match(/document\.write\s*\(/)) {
190
+ findings.push({ severity: "high", cwe: "CWE-79", category: "xss", message: `document.write() in ${file}:${lineNum}`, remediation: "Use DOM manipulation methods like appendChild or innerHTML with sanitization" });
191
+ }
192
+ if (line.match(/setAttribute\s*\(\s*['"]on\w+['"]/) || line.match(/\.on\w+\s*=\s*/)) {
193
+ findings.push({ severity: "medium", cwe: "CWE-79", category: "xss", message: `Inline event handler in ${file}:${lineNum}`, remediation: "Use addEventListener instead of inline handlers" });
194
+ }
195
+ }
196
+
197
+ private scanAuth(line: string, file: string, lineNum: number, content: string, findings: SecurityFinding[]): void {
198
+ if (line.match(/http\.createServer|app\.listen|express\s*\(/) && !line.includes("https")) {
199
+ if (!content.includes("https") && !content.includes("ssl") && !content.includes("tls") && !content.includes("secure")) {
200
+ findings.push({ severity: "medium", cwe: "CWE-319", category: "auth", message: `Server without HTTPS in ${file}:${lineNum}`, remediation: "Configure TLS/SSL with a valid certificate" });
201
+ }
202
+ }
203
+ if (line.match(/jwt\.sign\(/) || line.match(/jsonwebtoken\.sign/)) {
204
+ if (!content.includes("expiresIn") && !content.includes("expires")) {
205
+ findings.push({ severity: "medium", cwe: "CWE-307", category: "auth", message: `JWT without expiration in ${file}:${lineNum}`, remediation: "Set expiresIn option when signing JWTs" });
206
+ }
207
+ }
208
+ }
209
+
210
+ private countBySeverity(findings: SecurityFinding[]): Record<string, number> {
211
+ return {
212
+ critical: findings.filter((f) => f.severity === "critical").length,
213
+ high: findings.filter((f) => f.severity === "high").length,
214
+ medium: findings.filter((f) => f.severity === "medium").length,
215
+ low: findings.filter((f) => f.severity === "low").length,
216
+ };
217
+ }
218
+
219
+ private computeRiskLevel(findings: SecurityFinding[]): string {
220
+ if (findings.some((f) => f.severity === "critical")) return "critical";
221
+ if (findings.some((f) => f.severity === "high")) return "high";
222
+ if (findings.some((f) => f.severity === "medium")) return "medium";
223
+ if (findings.length > 0) return "low";
224
+ return "none";
225
+ }
226
+ }
@@ -0,0 +1,113 @@
1
+ import { z } from "zod";
2
+ import { ToolBase, ToolContext, ToolExecutionResult, MemoryVectorStore, MockEmbeddingProvider } from "@thoughtflow/core";
3
+ import { CodebaseExplorer } from "../../libs/codebase/indexer/explorer";
4
+ import { CodeChunker } from "../../libs/codebase/indexer/chunker";
5
+ import * as fs from "node:fs";
6
+
7
+ export class NeuralSimilarTool extends ToolBase {
8
+ readonly name = "neuralSimilar";
9
+ readonly description = "Find semantically similar or duplicate code fragments. Uses embeddings to detect copy-pasted code that text-based diff tools would miss.";
10
+ override readonly readonly = true;
11
+
12
+ constructor() {
13
+ super(z.object({
14
+ target: z.string().optional().describe("File to compare against codebase"),
15
+ projectPath: z.string().optional(),
16
+ threshold: z.number().optional().describe("Similarity threshold (0-1, default 0.85)"),
17
+ limit: z.number().optional(),
18
+ }));
19
+ }
20
+
21
+ async run(input: Record<string, unknown>, _ctx: ToolContext): Promise<ToolExecutionResult> {
22
+ const target = (input.target as string) || undefined;
23
+ const rootPath = (input.projectPath as string) ?? process.cwd();
24
+ const threshold = (input.threshold as number) ?? 0.85;
25
+ const limit = (input.limit as number) ?? 10;
26
+
27
+ const vectorStore = new MemoryVectorStore();
28
+ await vectorStore.initialize();
29
+
30
+ const embedder = new MockEmbeddingProvider();
31
+
32
+ const explorer = new CodebaseExplorer();
33
+ const files = explorer.explore({ rootPath });
34
+
35
+ const codeFiles = files.filter((f) => f.classification === "source-code" || f.classification === "test");
36
+ const filesToIndex = codeFiles;
37
+
38
+ const chunker = new CodeChunker();
39
+ const allChunks: Array<{ id: string; text: string; file: string; lines: [number, number] }> = [];
40
+ const results: Array<{ file1: string; file2: string; lines1: string; lines2: string; score: number; snippet1: string; snippet2: string }> = [];
41
+
42
+ for (const file of filesToIndex) {
43
+ const content = fs.readFileSync(file.absolutePath, "utf8");
44
+ const result = await chunker.chunkFile(file, content);
45
+ for (const chunk of result.chunks) {
46
+ const vec = await embedder.embedQuery(chunk.text);
47
+ const id = `${file.relativePath}:${chunk.startLine}-${chunk.endLine}`;
48
+ await vectorStore.insert([{
49
+ id,
50
+ vector: vec,
51
+ text: chunk.text.slice(0, 500),
52
+ metadata: {
53
+ projectPath: rootPath,
54
+ filePath: file.relativePath,
55
+ startLine: chunk.startLine,
56
+ endLine: chunk.endLine,
57
+ chunkType: "code",
58
+ },
59
+ }]);
60
+ allChunks.push({
61
+ id,
62
+ text: chunk.text,
63
+ file: file.relativePath,
64
+ lines: [chunk.startLine, chunk.endLine],
65
+ });
66
+ }
67
+ }
68
+
69
+ const pairs = new Set<string>();
70
+ for (const chunk of allChunks) {
71
+ const vec = await embedder.embedQuery(chunk.text);
72
+ const similar = await vectorStore.search(vec, { limit: limit * 2 });
73
+
74
+ for (const match of similar) {
75
+ if (match.item.id === chunk.id) continue;
76
+ if (match.score < threshold) continue;
77
+
78
+ const pairKey = [chunk.file, match.item.metadata.filePath].sort().join("||") +
79
+ "||" +
80
+ [chunk.id, match.item.id].sort().join("::");
81
+ if (pairs.has(pairKey)) continue;
82
+ pairs.add(pairKey);
83
+
84
+ results.push({
85
+ file1: chunk.file,
86
+ file2: match.item.metadata.filePath,
87
+ lines1: `${chunk.lines[0]}-${chunk.lines[1]}`,
88
+ lines2: `${match.item.metadata.startLine}-${match.item.metadata.endLine}`,
89
+ score: Math.round(match.score * 100) / 100,
90
+ snippet1: chunk.text.slice(0, 100),
91
+ snippet2: match.item.text?.slice(0, 100) ?? "",
92
+ });
93
+ }
94
+
95
+ if (results.length >= limit) break;
96
+ }
97
+
98
+ results.sort((a, b) => b.score - a.score);
99
+ const top = results.slice(0, limit);
100
+
101
+ return {
102
+ success: true,
103
+ output: JSON.stringify({
104
+ filesScanned: codeFiles.length,
105
+ chunksAnalyzed: allChunks.length,
106
+ duplicatesFound: top.length,
107
+ threshold,
108
+ embeddingModel: embedder.modelName,
109
+ duplicates: top,
110
+ }),
111
+ };
112
+ }
113
+ }
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ import { ToolBase, ToolContext, ToolExecutionResult } from "@thoughtflow/core";
3
+ import { CodebaseExplorer } from "../../libs/codebase/indexer/explorer";
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+
7
+ const SmellSchema = z.object({
8
+ smell: z.string(),
9
+ severity: z.enum(["critical", "warning", "info"]),
10
+ location: z.string().optional(),
11
+ description: z.string(),
12
+ refactoring: z.string(),
13
+ });
14
+
15
+ export class NeuralSmellsTool extends ToolBase {
16
+ readonly name = "neuralSmells";
17
+ readonly description = "Detect code smells using static analysis and LLM-powered semantic review: God class, long method, feature envy, primitive obsession, switch statements, deep nesting, duplicate code, and more. Returns structured findings with refactoring suggestions.";
18
+ override readonly readonly = true;
19
+
20
+ constructor() {
21
+ super(z.object({
22
+ target: z.string().optional().describe("File path or leave empty for whole project"),
23
+ projectPath: z.string().optional(),
24
+ limit: z.number().optional().describe("Max smells to return"),
25
+ }));
26
+ }
27
+
28
+ async run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolExecutionResult> {
29
+ const rootPath = (input.projectPath as string) ?? process.cwd();
30
+ const target = (input.target as string) || undefined;
31
+ const limit = (input.limit as number) ?? 15;
32
+
33
+ const explorer = new CodebaseExplorer();
34
+ const files = explorer.explore({ rootPath });
35
+ const scanFiles = target
36
+ ? files.filter((f) => f.relativePath.includes(target)).slice(0, 5)
37
+ : files.filter((f) => f.classification === "source-code").slice(0, 20);
38
+
39
+ const staticSmells: Array<{ smell: string; severity: string; file: string; line: number; description: string; refactoring: string }> = [];
40
+
41
+ for (const file of scanFiles) {
42
+ const content = fs.readFileSync(file.absolutePath, "utf8");
43
+ const lines = content.split("\n");
44
+
45
+ if (lines.length > 500) {
46
+ staticSmells.push({ smell: "large-file", severity: "warning", file: file.relativePath, line: 1, description: `${lines.length} lines — consider splitting`, refactoring: "Split into smaller modules by concern" });
47
+ }
48
+
49
+ let maxDepth = 0;
50
+ let depth = 0;
51
+ for (const line of lines) {
52
+ if (line.match(/^\s*(if|for|while|switch)\s*[({]/)) depth++;
53
+ if (line.trim() === "}" || line.trim() === "});") depth = Math.max(0, depth - 1);
54
+ maxDepth = Math.max(maxDepth, depth);
55
+ }
56
+ if (maxDepth > 4) {
57
+ staticSmells.push({ smell: "deep-nesting", severity: "warning", file: file.relativePath, line: 1, description: `Max nesting depth: ${maxDepth}`, refactoring: "Use early returns, guard clauses, or extract to functions" });
58
+ }
59
+
60
+ const anyCount = (content.match(/\bany\b/g) || []).length;
61
+ if (anyCount > 10) {
62
+ staticSmells.push({ smell: "excessive-any", severity: "warning", file: file.relativePath, line: 1, description: `${anyCount} 'any' type usages`, refactoring: "Replace with explicit types or 'unknown'" });
63
+ }
64
+ }
65
+
66
+ const cs = ctx.conversation?.callStructured;
67
+ if (!cs) {
68
+ return { success: true, output: JSON.stringify({ filesScanned: scanFiles.length, smellsFound: staticSmells.length, smells: staticSmells }) };
69
+ }
70
+
71
+ const codeSnippets = scanFiles.slice(0, 3).map((f) => {
72
+ const c = fs.readFileSync(f.absolutePath, "utf8").slice(0, 3000);
73
+ return `--- ${f.relativePath} ---\n${c}`;
74
+ }).join("\n\n");
75
+
76
+ if (!codeSnippets) {
77
+ return { success: true, output: JSON.stringify({ filesScanned: scanFiles.length, smellsFound: staticSmells.length, smells: staticSmells.slice(0, limit) }) };
78
+ }
79
+
80
+ const prompt = `Analyze this code for design smells: God class, long method, feature envy, primitive obsession, switch statement abuse, refused bequest, shotgun surgery, divergent change, data clumps, lazy class, speculative generality, middle man, inappropriate intimacy, message chains, duplicate code.\n\n${codeSnippets}\n\nReturn structured findings with specific file locations and refactoring suggestions. Be specific about file path and line number.`;
81
+
82
+ try {
83
+ const result = await cs(prompt, z.object({ smells: z.array(SmellSchema) }));
84
+ const llmSmells = (result.smells ?? []).map((s) => ({
85
+ smell: s.smell,
86
+ severity: s.severity,
87
+ file: s.location ?? target ?? "",
88
+ line: 1,
89
+ description: s.description,
90
+ refactoring: s.refactoring,
91
+ }));
92
+ const allSmells = [...staticSmells, ...llmSmells];
93
+ return { success: true, output: JSON.stringify({ filesScanned: scanFiles.length, smellsFound: allSmells.length, smells: allSmells.slice(0, limit) }) };
94
+ } catch {
95
+ return { success: true, output: JSON.stringify({ filesScanned: scanFiles.length, smellsFound: staticSmells.length, smells: staticSmells }) };
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import { ToolBase, ToolContext, ToolExecutionResult } from "@thoughtflow/core";
3
+ import { DependencyExtractor } from "../../libs/codebase/graph/dependency-extractor";
4
+ import { CodebaseExplorer } from "../../libs/codebase/indexer/explorer";
5
+
6
+ export class NeuralSuggestTool extends ToolBase {
7
+ readonly name = "neuralSuggest";
8
+ readonly description = "Suggest the best files/modules to implement a given feature. Uses dependency graph, codebase structure, and LLM reasoning to rank candidates.";
9
+ override readonly readonly = true;
10
+
11
+ constructor() {
12
+ super(z.object({
13
+ feature: z.string().describe("Description of the feature to implement"),
14
+ projectPath: z.string().optional(),
15
+ maxSuggestions: z.number().optional(),
16
+ }));
17
+ }
18
+
19
+ async run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolExecutionResult> {
20
+ const { feature, maxSuggestions } = input as { feature: string; maxSuggestions?: number };
21
+ const rootPath = (input.projectPath as string) ?? process.cwd();
22
+ const limit = maxSuggestions ?? 5;
23
+
24
+ const explorer = new CodebaseExplorer();
25
+ const files = explorer.explore({ rootPath });
26
+ const codeFiles = files.filter((f) => f.classification === "source-code")
27
+ .map((f) => ({ relativePath: f.relativePath, absolutePath: f.absolutePath }));
28
+
29
+ const depExtractor = new DependencyExtractor();
30
+ const graph = await depExtractor.buildGraph(codeFiles, rootPath);
31
+
32
+ const featureTokens = feature.toLowerCase().split(/\s+/);
33
+ const scored = codeFiles.map((f) => {
34
+ const rel = f.relativePath.toLowerCase();
35
+ let score = 0;
36
+ for (const token of featureTokens) {
37
+ if (rel.includes(token)) score += 3;
38
+ if (rel.split("/").some((p) => p.includes(token))) score += 2;
39
+ }
40
+ const inEdges = graph.edges.filter((e) => e.to === f.relativePath).length;
41
+ score += Math.min(inEdges, 5);
42
+ const outEdges = graph.edges.filter((e) => e.from === f.relativePath).length;
43
+ score += Math.min(outEdges, 3);
44
+ return { path: f.relativePath, score };
45
+ });
46
+
47
+ scored.sort((a, b) => b.score - a.score);
48
+ const top = scored.slice(0, limit);
49
+
50
+ const cs = ctx.conversation?.callStructured;
51
+ if (cs && top.length > 0) {
52
+ const topList = top.map((t) => `- ${t.path}`).join("\n");
53
+ const prompt = `Given the feature: "${feature}", these are the top file candidates:\n${topList}\n\nSuggest which file(s) would be the best starting point and explain why. Return JSON: { reasoning: string, recommended: string[] }`;
54
+ try {
55
+ const llmResult = await cs(prompt, z.object({ reasoning: z.string(), recommended: z.array(z.string()) }));
56
+ const data = llmResult as { reasoning?: string; recommended?: string[] };
57
+ return { success: true, output: JSON.stringify({ feature, suggestions: top, llmReasoning: data.reasoning, llmRecommended: data.recommended }) };
58
+ } catch {
59
+ /* fall back to static output */
60
+ }
61
+ }
62
+
63
+ return { success: true, output: JSON.stringify({ feature, suggestions: top, totalFiles: codeFiles.length }) };
64
+ }
65
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./quality-complexity.tool";
2
+ export * from "./quality-coupling.tool";
3
+ export * from "./quality-coverage.tool";
4
+ export * from "./quality-deps.tool";
5
+ export * from "./quality-duplication.tool";
6
+ export * from "./quality-size.tool";
7
+ export * from "./quality-types.tool";
@@ -0,0 +1,132 @@
1
+ import { z } from "zod";
2
+ import { ToolBase, ToolContext, ToolExecutionResult } from "@thoughtflow/core";
3
+ import { CodebaseExplorer } from "../../libs/codebase/indexer/explorer";
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+
7
+ export class QualityComplexityTool extends ToolBase {
8
+ readonly name = "qualityComplexity";
9
+ readonly description = "Calculate cyclomatic complexity per file and function. Identifies complex code that is hard to test and maintain. Returns rankings and risk levels.";
10
+ override readonly readonly = true;
11
+
12
+ constructor() {
13
+ super(z.object({
14
+ target: z.string().optional(),
15
+ projectPath: z.string().optional(),
16
+ limit: z.number().optional(),
17
+ }));
18
+ }
19
+
20
+ async run(input: Record<string, unknown>, _ctx: ToolContext): Promise<ToolExecutionResult> {
21
+ const target = (input.target as string) || undefined;
22
+ const rootPath = (input.projectPath as string) ?? process.cwd();
23
+ const limit = (input.limit as number) ?? 20;
24
+
25
+ let filesToScan: Array<{ relativePath: string; absolutePath: string }>;
26
+ if (target) {
27
+ const abs = path.join(rootPath, target);
28
+ filesToScan = fs.existsSync(abs) ? [{ relativePath: target, absolutePath: abs }] : [];
29
+ } else {
30
+ const explorer = new CodebaseExplorer();
31
+ filesToScan = explorer.explore({ rootPath })
32
+ .filter((f) => f.classification === "source-code")
33
+ .map((f) => ({ relativePath: f.relativePath, absolutePath: f.absolutePath }));
34
+ }
35
+
36
+ const results: Array<{ file: string; complexity: number; risk: string; topFunctions: Array<{ name: string; complexity: number; line: number }> }> = [];
37
+
38
+ for (const file of filesToScan) {
39
+ const content = fs.readFileSync(file.absolutePath, "utf8");
40
+ const fileComplexity = this.calculateFileComplexity(content);
41
+ const functions = this.calculateFunctionComplexity(content);
42
+
43
+ if (fileComplexity > 5 || functions.some((f) => f.complexity > 5)) {
44
+ results.push({
45
+ file: file.relativePath,
46
+ complexity: fileComplexity,
47
+ risk: fileComplexity > 50 ? "critical" : fileComplexity > 20 ? "high" : fileComplexity > 10 ? "medium" : "low",
48
+ topFunctions: functions.filter((f) => f.complexity > 5).sort((a, b) => b.complexity - a.complexity).slice(0, 5),
49
+ });
50
+ }
51
+ }
52
+
53
+ results.sort((a, b) => b.complexity - a.complexity);
54
+
55
+ return {
56
+ success: true,
57
+ output: JSON.stringify({
58
+ filesScanned: filesToScan.length,
59
+ highComplexityFiles: results.length,
60
+ topComplexFiles: results.slice(0, limit),
61
+ }),
62
+ };
63
+ }
64
+
65
+ private calculateFileComplexity(content: string): number {
66
+ let complexity = 1;
67
+ const patterns = [
68
+ /\bif\s*\(/g, /\belse\s+if\b/g, /\bwhile\s*\(/g,
69
+ /\bfor\s*\(/g, /\bcase\b/g, /\bcatch\s*\(/g,
70
+ /\?\s*\w/g, /\&\&/g, /\|\|/g,
71
+ ];
72
+
73
+ for (const pattern of patterns) {
74
+ const matches = content.match(pattern);
75
+ if (matches) complexity += matches.length;
76
+ }
77
+
78
+ return complexity;
79
+ }
80
+
81
+ private calculateFunctionComplexity(content: string): Array<{ name: string; complexity: number; line: number }> {
82
+ const functions: Array<{ name: string; complexity: number; line: number }> = [];
83
+ const lines = content.split("\n");
84
+
85
+ const fnPattern = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)/;
86
+ const methodPattern = /(?:public|private|protected|static|async)?\s+(\w+)\s*\([^)]*\)\s*\{/;
87
+ const arrowPattern = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>\s*\{/;
88
+
89
+ let inFunction = false;
90
+ let fnName = "";
91
+ let fnStart = 0;
92
+ let fnComplexity = 1;
93
+ let braceDepth = 0;
94
+
95
+ for (let i = 0; i < lines.length; i++) {
96
+ const line = lines[i];
97
+ const fnMatch = line.match(fnPattern);
98
+ const methodMatch = line.match(methodPattern);
99
+ const arrowMatch = line.match(arrowPattern);
100
+
101
+ if (!inFunction && (fnMatch || methodMatch || arrowMatch)) {
102
+ inFunction = true;
103
+ fnName = (fnMatch?.[1] ?? methodMatch?.[1] ?? arrowMatch?.[1] ?? "anonymous");
104
+ fnStart = i + 1;
105
+ fnComplexity = 1;
106
+ }
107
+
108
+ if (inFunction) {
109
+ braceDepth += (line.match(/\{/g) || []).length;
110
+ braceDepth -= (line.match(/\}/g) || []).length;
111
+
112
+ const complexityAdders = [
113
+ /\bif\s*\(/g, /\belse\s+if\b/g, /\bwhile\s*\(/g,
114
+ /\bfor\s*\(/g, /\bcase\b/g, /\bcatch\s*\(/g,
115
+ /\?\s*\w/g, /\&\&/g, /\|\|/g,
116
+ ];
117
+ for (const pattern of complexityAdders) {
118
+ const matches = line.match(pattern);
119
+ if (matches) fnComplexity += matches.length;
120
+ }
121
+
122
+ if (braceDepth <= 0 && inFunction) {
123
+ functions.push({ name: fnName, complexity: fnComplexity, line: fnStart });
124
+ inFunction = false;
125
+ braceDepth = 0;
126
+ }
127
+ }
128
+ }
129
+
130
+ return functions;
131
+ }
132
+ }