@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,1058 @@
1
+ import { z } from "zod";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { ToolBase, ToolContext, ToolExecutionResult, resolveSafePath } from "@thoughtflow/core";
5
+
6
+ /* ═══════════════════════════════════════════════════════════════
7
+ RULE TYPES — stable interface for extensibility.
8
+ To support a new technology: add an entry to TECH_RULES.
9
+ To support a new monorepo tool: add an entry to MONOREPO_RULES.
10
+ ═══════════════════════════════════════════════════════════════ */
11
+
12
+ export type TechCategory =
13
+ | "language"
14
+ | "framework"
15
+ | "database"
16
+ | "infra"
17
+ | "testing"
18
+ | "build";
19
+
20
+ export interface TechRule {
21
+ id: string;
22
+ name: string;
23
+ category: TechCategory;
24
+ /** Files whose existence signals this tech (relative to scanned dir). */
25
+ fileMarkers: string[];
26
+ /** Directories whose existence signals this tech. */
27
+ dirMarkers: string[];
28
+ /** package.json / composer.json dependency names that signal this tech. */
29
+ packageDeps?: string[];
30
+ /** Directories to skip when this tech is detected. */
31
+ ignoreDirs: string[];
32
+ /** Important files worth reading (only shown when they exist). */
33
+ keyFiles: string[];
34
+ /** Glob patterns for find_files exploration. */
35
+ explorePatterns: string[];
36
+ /** Short actionable hints for the LLM. */
37
+ hints: string[];
38
+ }
39
+
40
+ export interface MonorepoRule {
41
+ id: string;
42
+ name: string;
43
+ /** Files in root that indicate this monorepo tool. */
44
+ fileMarkers: string[];
45
+ /** Conventional sub-dirs that contain sub-projects (e.g. apps, libs). */
46
+ workspaceDirs: string[];
47
+ ignoreDirs: string[];
48
+ hints: string[];
49
+ }
50
+
51
+ /* ═══════════════════════════════════════════════════════════════
52
+ TECH RULES — sorted by category, then by specificity.
53
+ Framework rules should come AFTER language rules so the
54
+ output reads naturally (language first, then framework).
55
+ ═══════════════════════════════════════════════════════════════ */
56
+
57
+ export const TECH_RULES: TechRule[] = [
58
+ // ——— Languages ———————————————————————————————————————
59
+ {
60
+ id: "typescript",
61
+ name: "TypeScript",
62
+ category: "language",
63
+ fileMarkers: ["tsconfig.json", "tsconfig.base.json"],
64
+ dirMarkers: [],
65
+ ignoreDirs: ["node_modules", "dist", "build", ".tsbuildinfo"],
66
+ keyFiles: ["tsconfig.json"],
67
+ explorePatterns: [],
68
+ hints: [],
69
+ },
70
+ {
71
+ id: "nodejs",
72
+ name: "Node.js",
73
+ category: "language",
74
+ fileMarkers: ["package.json"],
75
+ dirMarkers: [],
76
+ ignoreDirs: ["node_modules", "dist", "build", ".cache", ".parcel-cache"],
77
+ keyFiles: ["package.json"],
78
+ explorePatterns: [],
79
+ hints: ["Check 'scripts' in package.json for build/start/test commands"],
80
+ },
81
+ {
82
+ id: "python",
83
+ name: "Python",
84
+ category: "language",
85
+ fileMarkers: [
86
+ "pyproject.toml",
87
+ "setup.py",
88
+ "requirements.txt",
89
+ "Pipfile",
90
+ "setup.cfg",
91
+ ],
92
+ dirMarkers: [],
93
+ ignoreDirs: [
94
+ "__pycache__",
95
+ ".venv",
96
+ "venv",
97
+ "env",
98
+ ".eggs",
99
+ ".mypy_cache",
100
+ ".pytest_cache",
101
+ ".ruff_cache",
102
+ ],
103
+ keyFiles: ["pyproject.toml", "requirements.txt", "setup.py"],
104
+ explorePatterns: ["**/*.py"],
105
+ hints: ["Entry points in pyproject.toml [project.scripts] or setup.py"],
106
+ },
107
+ {
108
+ id: "rust",
109
+ name: "Rust",
110
+ category: "language",
111
+ fileMarkers: ["Cargo.toml"],
112
+ dirMarkers: [],
113
+ ignoreDirs: ["target"],
114
+ keyFiles: ["Cargo.toml", "src/main.rs", "src/lib.rs"],
115
+ explorePatterns: ["**/*.rs"],
116
+ hints: ["Binaries in src/bin/, library in src/lib.rs"],
117
+ },
118
+ {
119
+ id: "go",
120
+ name: "Go",
121
+ category: "language",
122
+ fileMarkers: ["go.mod"],
123
+ dirMarkers: [],
124
+ ignoreDirs: ["vendor"],
125
+ keyFiles: ["go.mod"],
126
+ explorePatterns: ["**/*.go"],
127
+ hints: ["Binaries in cmd/, private libs in internal/"],
128
+ },
129
+ {
130
+ id: "php",
131
+ name: "PHP",
132
+ category: "language",
133
+ fileMarkers: ["composer.json"],
134
+ dirMarkers: [],
135
+ ignoreDirs: ["vendor", "storage/framework"],
136
+ keyFiles: ["composer.json"],
137
+ explorePatterns: ["**/*.php"],
138
+ hints: ["PSR-4 autoloading in composer.json"],
139
+ },
140
+ {
141
+ id: "java",
142
+ name: "Java",
143
+ category: "language",
144
+ fileMarkers: ["pom.xml", "build.gradle", "build.gradle.kts"],
145
+ dirMarkers: [],
146
+ ignoreDirs: ["target", "build", ".gradle", "out"],
147
+ keyFiles: ["pom.xml", "build.gradle", "build.gradle.kts"],
148
+ explorePatterns: ["**/*.java", "**/*.kt"],
149
+ hints: ["Source in src/main/java, tests in src/test/java"],
150
+ },
151
+ {
152
+ id: "dotnet",
153
+ name: ".NET/C#",
154
+ category: "language",
155
+ fileMarkers: [], // detected via extension scan in detectTechs
156
+ dirMarkers: [],
157
+ ignoreDirs: ["bin", "obj", ".vs"],
158
+ keyFiles: [],
159
+ explorePatterns: ["**/*.cs", "**/*.csproj"],
160
+ hints: [".sln defines project structure, .csproj defines build"],
161
+ },
162
+ {
163
+ id: "ruby",
164
+ name: "Ruby",
165
+ category: "language",
166
+ fileMarkers: ["Gemfile"],
167
+ dirMarkers: [],
168
+ ignoreDirs: ["vendor/bundle", ".bundle"],
169
+ keyFiles: ["Gemfile"],
170
+ explorePatterns: ["**/*.rb"],
171
+ hints: ["Application code in lib/ or app/"],
172
+ },
173
+
174
+ // ——— Frameworks ——————————————————————————————————————
175
+ {
176
+ id: "nestjs",
177
+ name: "NestJS",
178
+ category: "framework",
179
+ fileMarkers: ["nest-cli.json"],
180
+ dirMarkers: [],
181
+ packageDeps: ["@nestjs/core"],
182
+ ignoreDirs: [],
183
+ keyFiles: ["nest-cli.json", "src/main.ts", "src/app.module.ts"],
184
+ explorePatterns: [
185
+ "**/*.controller.ts",
186
+ "**/*.module.ts",
187
+ "**/*.service.ts",
188
+ "**/*.guard.ts",
189
+ "**/*.gateway.ts",
190
+ ],
191
+ hints: [
192
+ "Controllers: @Get/@Post/@Put/@Delete define HTTP endpoints",
193
+ "Modules: imports[] shows feature dependencies",
194
+ ],
195
+ },
196
+ {
197
+ id: "angular",
198
+ name: "Angular",
199
+ category: "framework",
200
+ fileMarkers: ["angular.json"],
201
+ dirMarkers: [],
202
+ packageDeps: ["@angular/core"],
203
+ ignoreDirs: [".angular"],
204
+ keyFiles: [
205
+ "angular.json",
206
+ "src/app/app.routes.ts",
207
+ "src/app/app.module.ts",
208
+ "src/app/app.config.ts",
209
+ ],
210
+ explorePatterns: [
211
+ "**/*.component.ts",
212
+ "**/*routing*",
213
+ "**/*.module.ts",
214
+ ],
215
+ hints: [
216
+ "Routes: app.routes.ts (standalone) or *-routing.module.ts",
217
+ "Components: @Component decorator, templates inline or .html",
218
+ ],
219
+ },
220
+ {
221
+ id: "react",
222
+ name: "React",
223
+ category: "framework",
224
+ fileMarkers: [],
225
+ dirMarkers: [],
226
+ packageDeps: ["react"],
227
+ ignoreDirs: [],
228
+ keyFiles: ["src/App.tsx", "src/App.jsx", "src/index.tsx"],
229
+ explorePatterns: ["**/*.tsx", "**/*.jsx"],
230
+ hints: ["Components in src/, routing often in App.tsx or routes/"],
231
+ },
232
+ {
233
+ id: "nextjs",
234
+ name: "Next.js",
235
+ category: "framework",
236
+ fileMarkers: ["next.config.js", "next.config.mjs", "next.config.ts"],
237
+ dirMarkers: [],
238
+ packageDeps: ["next"],
239
+ ignoreDirs: [".next"],
240
+ keyFiles: ["next.config.js", "next.config.mjs", "next.config.ts"],
241
+ explorePatterns: ["app/**/page.tsx", "app/**/route.ts", "pages/**/*.tsx"],
242
+ hints: [
243
+ "App Router: app/ dir, page.tsx=pages, route.ts=API",
244
+ "Pages Router: pages/ dir, file-based routing",
245
+ ],
246
+ },
247
+ {
248
+ id: "vue",
249
+ name: "Vue.js",
250
+ category: "framework",
251
+ fileMarkers: ["vue.config.js"],
252
+ dirMarkers: [],
253
+ packageDeps: ["vue"],
254
+ ignoreDirs: [],
255
+ keyFiles: ["src/App.vue", "src/main.ts", "src/router/index.ts"],
256
+ explorePatterns: ["**/*.vue"],
257
+ hints: ["Single-file components (.vue), routing in src/router/"],
258
+ },
259
+ {
260
+ id: "nuxt",
261
+ name: "Nuxt",
262
+ category: "framework",
263
+ fileMarkers: ["nuxt.config.ts", "nuxt.config.js"],
264
+ dirMarkers: [],
265
+ packageDeps: ["nuxt"],
266
+ ignoreDirs: [".nuxt", ".output"],
267
+ keyFiles: ["nuxt.config.ts"],
268
+ explorePatterns: ["pages/**/*.vue", "server/api/**/*.ts"],
269
+ hints: ["File-based routing in pages/, server API in server/api/"],
270
+ },
271
+ {
272
+ id: "express",
273
+ name: "Express.js",
274
+ category: "framework",
275
+ fileMarkers: [],
276
+ dirMarkers: [],
277
+ packageDeps: ["express"],
278
+ ignoreDirs: [],
279
+ keyFiles: ["src/app.ts", "src/app.js", "src/server.ts"],
280
+ explorePatterns: ["**/*route*", "**/*controller*", "**/*middleware*"],
281
+ hints: ["Routes in routes/ or src/routes/"],
282
+ },
283
+ {
284
+ id: "fastify",
285
+ name: "Fastify",
286
+ category: "framework",
287
+ fileMarkers: [],
288
+ dirMarkers: [],
289
+ packageDeps: ["fastify"],
290
+ ignoreDirs: [],
291
+ keyFiles: ["src/app.ts", "src/server.ts"],
292
+ explorePatterns: ["**/*route*", "**/*plugin*"],
293
+ hints: ["Routes as plugins, registered via app.register()"],
294
+ },
295
+ {
296
+ id: "hono",
297
+ name: "Hono",
298
+ category: "framework",
299
+ fileMarkers: [],
300
+ dirMarkers: [],
301
+ packageDeps: ["hono"],
302
+ ignoreDirs: [],
303
+ keyFiles: ["src/index.ts"],
304
+ explorePatterns: ["**/*route*"],
305
+ hints: ["Lightweight HTTP framework, routes via app.get/post"],
306
+ },
307
+ {
308
+ id: "django",
309
+ name: "Django",
310
+ category: "framework",
311
+ fileMarkers: ["manage.py"],
312
+ dirMarkers: [],
313
+ ignoreDirs: ["staticfiles", "media"],
314
+ keyFiles: ["manage.py"],
315
+ explorePatterns: [
316
+ "**/views.py",
317
+ "**/models.py",
318
+ "**/urls.py",
319
+ "**/serializers.py",
320
+ ],
321
+ hints: [
322
+ "Apps in INSTALLED_APPS (settings.py)",
323
+ "URLs in urls.py, views define endpoints, models define DB",
324
+ ],
325
+ },
326
+ {
327
+ id: "fastapi",
328
+ name: "FastAPI",
329
+ category: "framework",
330
+ fileMarkers: [],
331
+ dirMarkers: [],
332
+ packageDeps: ["fastapi"],
333
+ ignoreDirs: [],
334
+ keyFiles: ["main.py", "app/main.py"],
335
+ explorePatterns: ["**/*router*", "**/*route*"],
336
+ hints: ["Routes with @app.get/@app.post, routers in separate files"],
337
+ },
338
+ {
339
+ id: "flask",
340
+ name: "Flask",
341
+ category: "framework",
342
+ fileMarkers: [],
343
+ dirMarkers: [],
344
+ packageDeps: ["flask"],
345
+ ignoreDirs: [],
346
+ keyFiles: ["app.py", "wsgi.py"],
347
+ explorePatterns: ["**/*route*", "**/*view*"],
348
+ hints: ["Routes via @app.route or blueprints"],
349
+ },
350
+ {
351
+ id: "laravel",
352
+ name: "Laravel",
353
+ category: "framework",
354
+ fileMarkers: ["artisan"],
355
+ dirMarkers: ["app/Http/Controllers"],
356
+ ignoreDirs: ["vendor", "storage/framework", "bootstrap/cache"],
357
+ keyFiles: [
358
+ "routes/web.php",
359
+ "routes/api.php",
360
+ "app/Http/Kernel.php",
361
+ ".env.example",
362
+ ],
363
+ explorePatterns: [
364
+ "app/Http/Controllers/**/*.php",
365
+ "app/Models/**/*.php",
366
+ "database/migrations/*.php",
367
+ ],
368
+ hints: [
369
+ "Routes: routes/web.php + routes/api.php",
370
+ "Controllers: app/Http/Controllers, Models: app/Models",
371
+ ],
372
+ },
373
+ {
374
+ id: "rails",
375
+ name: "Ruby on Rails",
376
+ category: "framework",
377
+ fileMarkers: ["config/routes.rb"],
378
+ dirMarkers: ["app/controllers", "app/models"],
379
+ ignoreDirs: ["vendor/bundle", "tmp", "log"],
380
+ keyFiles: ["config/routes.rb", "Gemfile", "config/database.yml"],
381
+ explorePatterns: [
382
+ "app/controllers/**/*.rb",
383
+ "app/models/**/*.rb",
384
+ "db/migrate/*.rb",
385
+ ],
386
+ hints: ["Routes in config/routes.rb, MVC in app/"],
387
+ },
388
+ {
389
+ id: "spring",
390
+ name: "Spring Boot",
391
+ category: "framework",
392
+ fileMarkers: [
393
+ "src/main/resources/application.properties",
394
+ "src/main/resources/application.yml",
395
+ ],
396
+ dirMarkers: [],
397
+ ignoreDirs: ["target", "build"],
398
+ keyFiles: [
399
+ "src/main/resources/application.yml",
400
+ "src/main/resources/application.properties",
401
+ ],
402
+ explorePatterns: [
403
+ "**/*Controller.java",
404
+ "**/*Service.java",
405
+ "**/*Repository.java",
406
+ ],
407
+ hints: ["@RestController, @Service, @Entity, JPA for data access"],
408
+ },
409
+
410
+ // ——— Database / ORM ——————————————————————————————————
411
+ {
412
+ id: "prisma",
413
+ name: "Prisma",
414
+ category: "database",
415
+ fileMarkers: ["prisma/schema.prisma"],
416
+ dirMarkers: [],
417
+ packageDeps: ["prisma", "@prisma/client"],
418
+ ignoreDirs: [],
419
+ keyFiles: ["prisma/schema.prisma"],
420
+ explorePatterns: ["prisma/migrations/**/migration.sql"],
421
+ hints: ["All models in schema.prisma, migrations in prisma/migrations/"],
422
+ },
423
+ {
424
+ id: "typeorm",
425
+ name: "TypeORM",
426
+ category: "database",
427
+ fileMarkers: ["ormconfig.json", "ormconfig.ts"],
428
+ dirMarkers: [],
429
+ packageDeps: ["typeorm"],
430
+ ignoreDirs: [],
431
+ keyFiles: ["ormconfig.json", "ormconfig.ts"],
432
+ explorePatterns: ["**/*.entity.ts", "**/migration/*.ts"],
433
+ hints: ["Entities with @Entity, migrations in migrations/"],
434
+ },
435
+ {
436
+ id: "drizzle",
437
+ name: "Drizzle ORM",
438
+ category: "database",
439
+ fileMarkers: ["drizzle.config.ts"],
440
+ dirMarkers: [],
441
+ packageDeps: ["drizzle-orm"],
442
+ ignoreDirs: [],
443
+ keyFiles: ["drizzle.config.ts"],
444
+ explorePatterns: ["**/*schema*", "**/drizzle/**"],
445
+ hints: ["Schema in schema files, migrations in drizzle/"],
446
+ },
447
+ {
448
+ id: "sequelize",
449
+ name: "Sequelize",
450
+ category: "database",
451
+ fileMarkers: [".sequelizerc"],
452
+ dirMarkers: [],
453
+ packageDeps: ["sequelize"],
454
+ ignoreDirs: [],
455
+ keyFiles: [".sequelizerc"],
456
+ explorePatterns: ["**/models/**", "**/migrations/**"],
457
+ hints: ["Models in models/, migrations in migrations/"],
458
+ },
459
+ {
460
+ id: "mongoose",
461
+ name: "Mongoose",
462
+ category: "database",
463
+ fileMarkers: [],
464
+ dirMarkers: [],
465
+ packageDeps: ["mongoose"],
466
+ ignoreDirs: [],
467
+ keyFiles: [],
468
+ explorePatterns: ["**/*schema*", "**/*model*"],
469
+ hints: ["MongoDB schemas, usually in schemas/ or models/"],
470
+ },
471
+
472
+ // ——— Infrastructure ——————————————————————————————————
473
+ {
474
+ id: "docker",
475
+ name: "Docker",
476
+ category: "infra",
477
+ fileMarkers: [
478
+ "Dockerfile",
479
+ "docker-compose.yml",
480
+ "docker-compose.yaml",
481
+ "compose.yml",
482
+ "compose.yaml",
483
+ ],
484
+ dirMarkers: [],
485
+ ignoreDirs: [],
486
+ keyFiles: [
487
+ "Dockerfile",
488
+ "docker-compose.yml",
489
+ "docker-compose.yaml",
490
+ "compose.yml",
491
+ "compose.yaml",
492
+ ".dockerignore",
493
+ ],
494
+ explorePatterns: ["**/Dockerfile*"],
495
+ hints: ["docker-compose defines services, ports, volumes, networks"],
496
+ },
497
+ {
498
+ id: "kubernetes",
499
+ name: "Kubernetes",
500
+ category: "infra",
501
+ fileMarkers: ["kustomization.yaml", "skaffold.yaml"],
502
+ dirMarkers: ["k8s", "kubernetes", "manifests"],
503
+ ignoreDirs: [],
504
+ keyFiles: ["kustomization.yaml"],
505
+ explorePatterns: ["k8s/**/*.yaml", "kubernetes/**/*.yaml"],
506
+ hints: ["Manifests: deployments, services, configmaps, secrets"],
507
+ },
508
+ {
509
+ id: "terraform",
510
+ name: "Terraform",
511
+ category: "infra",
512
+ fileMarkers: ["main.tf"],
513
+ dirMarkers: [],
514
+ ignoreDirs: [".terraform"],
515
+ keyFiles: ["main.tf", "variables.tf", "outputs.tf"],
516
+ explorePatterns: ["**/*.tf"],
517
+ hints: ["main.tf=resources, variables.tf=inputs, outputs.tf=exports"],
518
+ },
519
+
520
+ // ——— Testing —————————————————————————————————————————
521
+ {
522
+ id: "jest",
523
+ name: "Jest",
524
+ category: "testing",
525
+ fileMarkers: ["jest.config.ts", "jest.config.js", "jest.config.mjs"],
526
+ dirMarkers: [],
527
+ packageDeps: ["jest"],
528
+ ignoreDirs: [],
529
+ keyFiles: ["jest.config.ts", "jest.config.js"],
530
+ explorePatterns: ["**/*.test.ts", "**/*.spec.ts"],
531
+ hints: ["Tests in *.test.ts / *.spec.ts"],
532
+ },
533
+ {
534
+ id: "vitest",
535
+ name: "Vitest",
536
+ category: "testing",
537
+ fileMarkers: ["vitest.config.ts", "vitest.config.js"],
538
+ dirMarkers: [],
539
+ packageDeps: ["vitest"],
540
+ ignoreDirs: [],
541
+ keyFiles: ["vitest.config.ts"],
542
+ explorePatterns: ["**/*.test.ts", "**/*.spec.ts"],
543
+ hints: ["Tests in *.test.ts / *.spec.ts"],
544
+ },
545
+ {
546
+ id: "pytest",
547
+ name: "pytest",
548
+ category: "testing",
549
+ fileMarkers: ["pytest.ini", "conftest.py"],
550
+ dirMarkers: [],
551
+ ignoreDirs: [".pytest_cache"],
552
+ keyFiles: ["pytest.ini", "conftest.py"],
553
+ explorePatterns: ["**/test_*.py", "**/*_test.py"],
554
+ hints: ["Tests in test_*.py, fixtures in conftest.py"],
555
+ },
556
+ {
557
+ id: "playwright",
558
+ name: "Playwright",
559
+ category: "testing",
560
+ fileMarkers: ["playwright.config.ts", "playwright.config.js"],
561
+ dirMarkers: [],
562
+ packageDeps: ["@playwright/test"],
563
+ ignoreDirs: ["test-results", "playwright-report"],
564
+ keyFiles: ["playwright.config.ts"],
565
+ explorePatterns: ["**/*.spec.ts"],
566
+ hints: ["E2E tests, config in playwright.config.ts"],
567
+ },
568
+
569
+ // ——— Build tools —————————————————————————————————————
570
+ {
571
+ id: "webpack",
572
+ name: "Webpack",
573
+ category: "build",
574
+ fileMarkers: ["webpack.config.js", "webpack.config.ts"],
575
+ dirMarkers: [],
576
+ packageDeps: ["webpack"],
577
+ ignoreDirs: [],
578
+ keyFiles: ["webpack.config.js", "webpack.config.ts"],
579
+ explorePatterns: [],
580
+ hints: [],
581
+ },
582
+ {
583
+ id: "vite",
584
+ name: "Vite",
585
+ category: "build",
586
+ fileMarkers: ["vite.config.ts", "vite.config.js"],
587
+ dirMarkers: [],
588
+ packageDeps: ["vite"],
589
+ ignoreDirs: [],
590
+ keyFiles: ["vite.config.ts", "vite.config.js"],
591
+ explorePatterns: [],
592
+ hints: [],
593
+ },
594
+ {
595
+ id: "esbuild",
596
+ name: "esbuild",
597
+ category: "build",
598
+ fileMarkers: [],
599
+ dirMarkers: [],
600
+ packageDeps: ["esbuild"],
601
+ ignoreDirs: [],
602
+ keyFiles: [],
603
+ explorePatterns: [],
604
+ hints: [],
605
+ },
606
+ ];
607
+
608
+ /* ═══════════════════════════════════════════════════════════════
609
+ MONOREPO RULES
610
+ ═══════════════════════════════════════════════════════════════ */
611
+
612
+ export const MONOREPO_RULES: MonorepoRule[] = [
613
+ {
614
+ id: "nx",
615
+ name: "NX",
616
+ fileMarkers: ["nx.json"],
617
+ workspaceDirs: ["apps", "libs", "packages"],
618
+ ignoreDirs: [".nx", "node_modules"],
619
+ hints: [
620
+ "project.json in each sub-project defines targets (build/serve/test)",
621
+ "nx graph shows dependency graph",
622
+ ],
623
+ },
624
+ {
625
+ id: "turborepo",
626
+ name: "Turborepo",
627
+ fileMarkers: ["turbo.json"],
628
+ workspaceDirs: ["apps", "packages"],
629
+ ignoreDirs: [".turbo", "node_modules"],
630
+ hints: ["Pipeline in turbo.json, packages in packages/ and apps/"],
631
+ },
632
+ {
633
+ id: "lerna",
634
+ name: "Lerna",
635
+ fileMarkers: ["lerna.json"],
636
+ workspaceDirs: ["packages"],
637
+ ignoreDirs: ["node_modules"],
638
+ hints: ["Packages in packages/, version mgmt via lerna.json"],
639
+ },
640
+ {
641
+ id: "pnpm-workspaces",
642
+ name: "pnpm workspaces",
643
+ fileMarkers: ["pnpm-workspace.yaml"],
644
+ workspaceDirs: ["packages", "apps", "libs"],
645
+ ignoreDirs: ["node_modules"],
646
+ hints: ["Workspace members defined in pnpm-workspace.yaml"],
647
+ },
648
+ {
649
+ id: "go-workspaces",
650
+ name: "Go workspaces",
651
+ fileMarkers: ["go.work"],
652
+ workspaceDirs: ["cmd", "internal", "pkg"],
653
+ ignoreDirs: ["vendor"],
654
+ hints: ["Workspace modules in go.work"],
655
+ },
656
+ ];
657
+
658
+ /** Dirs always excluded regardless of detected tech. */
659
+ const ALWAYS_IGNORE = new Set([".git", "coverage", ".idea", ".vscode"]);
660
+
661
+ /* ═══════════════════════════════════════════════════════════════
662
+ INTERNAL TYPES
663
+ ═══════════════════════════════════════════════════════════════ */
664
+
665
+ interface DetectedTech {
666
+ rule: TechRule;
667
+ evidence: string[];
668
+ }
669
+
670
+ interface SubProject {
671
+ relPath: string;
672
+ techs: DetectedTech[];
673
+ }
674
+
675
+ interface ScanResult {
676
+ monorepo: MonorepoRule | null;
677
+ yarnWorkspaceDirs: string[];
678
+ rootTechs: DetectedTech[];
679
+ subProjects: SubProject[];
680
+ }
681
+
682
+ /* ═══════════════════════════════════════════════════════════════
683
+ TOOL
684
+ ═══════════════════════════════════════════════════════════════ */
685
+
686
+ export class ProjectScannerTool extends ToolBase {
687
+ readonly name = "projectScanner";
688
+ override readonly readonly = true;
689
+ readonly description =
690
+ "Fast structural scan of a project or monorepo. " +
691
+ "Detects languages, frameworks, databases, infrastructure, testing, build tools, " +
692
+ "and monorepo layout including sub-projects. " +
693
+ "Returns compact guidance: dirs to ignore, key files to read, " +
694
+ "patterns for find_files, and technology-specific hints. " +
695
+ "Use FIRST before any deep file exploration.";
696
+
697
+ constructor(private readonly rootDir: string) {
698
+ super(
699
+ z.object({
700
+ path: z
701
+ .string()
702
+ .optional()
703
+ .describe(
704
+ "Path to scan, relative to project root. Defaults to root."
705
+ ),
706
+ })
707
+ );
708
+ }
709
+
710
+ async run(
711
+ input: Record<string, unknown>,
712
+ _ctx: ToolContext
713
+ ): Promise<ToolExecutionResult> {
714
+ const relPath = (input.path as string) || ".";
715
+ const scanDir = resolveSafePath(this.rootDir, relPath);
716
+ if (!scanDir) {
717
+ return { success: false, error: "Path traversal not allowed." };
718
+ }
719
+ if (!fs.existsSync(scanDir) || !fs.statSync(scanDir).isDirectory()) {
720
+ return { success: false, error: `Not a directory: ${relPath}` };
721
+ }
722
+
723
+ const result = this.scan(scanDir);
724
+ return { success: true, output: this.format(result, scanDir) };
725
+ }
726
+
727
+ /* ——— Scanning ——————————————————————————————————————— */
728
+
729
+ private scan(dir: string): ScanResult {
730
+ const rootDeps = this.readDeps(dir);
731
+ const rootTechs = this.detectTechs(dir, rootDeps);
732
+
733
+ // Monorepo detection
734
+ let monorepo: MonorepoRule | null = null;
735
+ for (const rule of MONOREPO_RULES) {
736
+ if (rule.fileMarkers.some((f) => fs.existsSync(path.join(dir, f)))) {
737
+ monorepo = rule;
738
+ break;
739
+ }
740
+ }
741
+
742
+ // Yarn/npm workspaces (package.json "workspaces" field)
743
+ const yarnWorkspaceDirs: string[] = [];
744
+ const pkg = this.readPackageJson(dir);
745
+ if (pkg?.workspaces) {
746
+ const patterns: string[] = Array.isArray(pkg.workspaces)
747
+ ? pkg.workspaces
748
+ : pkg.workspaces.packages || [];
749
+ for (const pattern of patterns) {
750
+ const prefix = pattern.split(/[*{]/)[0].replace(/\/$/, "");
751
+ if (prefix && !yarnWorkspaceDirs.includes(prefix)) {
752
+ yarnWorkspaceDirs.push(prefix);
753
+ }
754
+ }
755
+ }
756
+
757
+ // Collect workspace dirs to scan
758
+ const wsDirs = new Set<string>();
759
+ if (monorepo) {
760
+ for (const d of monorepo.workspaceDirs) wsDirs.add(d);
761
+ }
762
+ for (const d of yarnWorkspaceDirs) wsDirs.add(d);
763
+
764
+ // Detect sub-projects
765
+ const subProjects: SubProject[] = [];
766
+ for (const wsDir of wsDirs) {
767
+ this.scanWorkspaceDir(dir, wsDir, rootDeps, subProjects);
768
+ }
769
+
770
+ // No monorepo tool detected — check for plain multi-project layout
771
+ if (!monorepo && yarnWorkspaceDirs.length === 0 && subProjects.length === 0) {
772
+ this.detectPlainSubProjects(dir, subProjects);
773
+ }
774
+
775
+ return { monorepo, yarnWorkspaceDirs, rootTechs, subProjects };
776
+ }
777
+
778
+ private scanWorkspaceDir(
779
+ rootDir: string,
780
+ wsDir: string,
781
+ rootDeps: Set<string>,
782
+ out: SubProject[]
783
+ ): void {
784
+ const full = path.join(rootDir, wsDir);
785
+ if (!fs.existsSync(full) || !fs.statSync(full).isDirectory()) return;
786
+
787
+ let entries: fs.Dirent[];
788
+ try {
789
+ entries = fs.readdirSync(full, { withFileTypes: true });
790
+ } catch {
791
+ return;
792
+ }
793
+
794
+ for (const entry of entries) {
795
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
796
+ const projDir = path.join(full, entry.name);
797
+ const projDeps = this.readDeps(projDir, rootDeps);
798
+ const techs = this.detectTechs(projDir, projDeps);
799
+ if (techs.length > 0) {
800
+ out.push({ relPath: path.relative(rootDir, projDir), techs });
801
+ }
802
+ }
803
+ }
804
+
805
+ /**
806
+ * Fallback: scan top-level dirs that have their own config file
807
+ * (looks like independent projects grouped in a directory).
808
+ */
809
+ private detectPlainSubProjects(
810
+ dir: string,
811
+ out: SubProject[]
812
+ ): void {
813
+ const PROJECT_CONFIGS = [
814
+ "package.json",
815
+ "Cargo.toml",
816
+ "go.mod",
817
+ "pyproject.toml",
818
+ "composer.json",
819
+ "Gemfile",
820
+ "pom.xml",
821
+ "build.gradle",
822
+ ];
823
+
824
+ let entries: fs.Dirent[];
825
+ try {
826
+ entries = fs.readdirSync(dir, { withFileTypes: true });
827
+ } catch {
828
+ return;
829
+ }
830
+
831
+ for (const entry of entries) {
832
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
833
+ if (ALWAYS_IGNORE.has(entry.name)) continue;
834
+
835
+ const subDir = path.join(dir, entry.name);
836
+ const hasOwnConfig = PROJECT_CONFIGS.some((f) =>
837
+ fs.existsSync(path.join(subDir, f))
838
+ );
839
+ if (!hasOwnConfig) continue;
840
+
841
+ const subDeps = this.readDeps(subDir);
842
+ const techs = this.detectTechs(subDir, subDeps);
843
+ if (techs.length > 0) {
844
+ out.push({ relPath: entry.name, techs });
845
+ }
846
+ }
847
+ }
848
+
849
+ /* ——— Tech detection ———————————————————————————————— */
850
+
851
+ private detectTechs(dir: string, deps: Set<string>): DetectedTech[] {
852
+ const detected: DetectedTech[] = [];
853
+
854
+ for (const rule of TECH_RULES) {
855
+ const evidence: string[] = [];
856
+
857
+ for (const f of rule.fileMarkers) {
858
+ if (fs.existsSync(path.join(dir, f))) evidence.push(f);
859
+ }
860
+ for (const d of rule.dirMarkers) {
861
+ const full = path.join(dir, d);
862
+ try {
863
+ if (fs.existsSync(full) && fs.statSync(full).isDirectory()) {
864
+ evidence.push(d + "/");
865
+ }
866
+ } catch {
867
+ /* permission error etc. */
868
+ }
869
+ }
870
+ if (rule.packageDeps) {
871
+ for (const dep of rule.packageDeps) {
872
+ if (deps.has(dep)) evidence.push(`dep:${dep}`);
873
+ }
874
+ }
875
+
876
+ if (evidence.length > 0) {
877
+ detected.push({ rule, evidence });
878
+ }
879
+ }
880
+
881
+ // Special: .NET detection via extensions (no fixed filenames)
882
+ this.detectByExtension(dir, detected);
883
+
884
+ return detected;
885
+ }
886
+
887
+ private detectByExtension(dir: string, detected: DetectedTech[]): void {
888
+ // Skip if .NET already detected
889
+ if (detected.some((d) => d.rule.id === "dotnet")) return;
890
+
891
+ let entries: string[];
892
+ try {
893
+ entries = fs.readdirSync(dir);
894
+ } catch {
895
+ return;
896
+ }
897
+ const hasSln = entries.some((e) => e.endsWith(".sln"));
898
+ const hasCsproj = entries.some((e) => e.endsWith(".csproj"));
899
+ if (hasSln || hasCsproj) {
900
+ const rule = TECH_RULES.find((r) => r.id === "dotnet")!;
901
+ const evidence = hasSln ? ["*.sln"] : ["*.csproj"];
902
+ detected.push({ rule, evidence });
903
+ }
904
+ }
905
+
906
+ /* ——— Dependency reading ————————————————————————————— */
907
+
908
+ private readPackageJson(dir: string): Record<string, any> | null {
909
+ const p = path.join(dir, "package.json");
910
+ try {
911
+ if (fs.existsSync(p)) {
912
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
913
+ }
914
+ } catch {
915
+ /* malformed JSON */
916
+ }
917
+ return null;
918
+ }
919
+
920
+ private readDeps(dir: string, fallback?: Set<string>): Set<string> {
921
+ const deps = new Set<string>(fallback ?? []);
922
+
923
+ // package.json
924
+ const pkg = this.readPackageJson(dir);
925
+ if (pkg) {
926
+ for (const section of [
927
+ "dependencies",
928
+ "devDependencies",
929
+ "peerDependencies",
930
+ ]) {
931
+ if (pkg[section]) {
932
+ for (const name of Object.keys(pkg[section])) deps.add(name);
933
+ }
934
+ }
935
+ }
936
+
937
+ // Python: read requirements.txt package names
938
+ const reqTxt = path.join(dir, "requirements.txt");
939
+ try {
940
+ if (fs.existsSync(reqTxt)) {
941
+ const lines = fs.readFileSync(reqTxt, "utf-8").split("\n");
942
+ for (const line of lines) {
943
+ const trimmed = line.trim();
944
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
945
+ const name = trimmed.split(/[>=<!~\[;]/)[0].trim().toLowerCase();
946
+ if (name) deps.add(name);
947
+ }
948
+ }
949
+ } catch {
950
+ /* read error */
951
+ }
952
+
953
+ return deps;
954
+ }
955
+
956
+ /* ——— Output formatting ————————————————————————————— */
957
+
958
+ private format(result: ScanResult, scanDir: string): string {
959
+ const lines: string[] = [];
960
+ const hasSubProjects = result.subProjects.length > 0;
961
+
962
+ // Structure
963
+ if (result.monorepo) {
964
+ lines.push(`Structure: monorepo (${result.monorepo.name})`);
965
+ } else if (result.yarnWorkspaceDirs.length > 0) {
966
+ lines.push(`Structure: monorepo (npm/yarn workspaces)`);
967
+ } else if (hasSubProjects) {
968
+ lines.push(`Structure: multi-project`);
969
+ } else {
970
+ lines.push(`Structure: single project`);
971
+ }
972
+
973
+ // Aggregate ignore dirs
974
+ const ignoreSet = new Set<string>(ALWAYS_IGNORE);
975
+ const allTechs = [...result.rootTechs];
976
+ for (const sp of result.subProjects) {
977
+ for (const t of sp.techs) allTechs.push(t);
978
+ }
979
+ if (result.monorepo) {
980
+ for (const d of result.monorepo.ignoreDirs) ignoreSet.add(d);
981
+ }
982
+ for (const t of allTechs) {
983
+ for (const d of t.rule.ignoreDirs) ignoreSet.add(d);
984
+ }
985
+ lines.push(`Ignore: ${[...ignoreSet].sort().join(", ")}`);
986
+ lines.push("");
987
+
988
+ // Root technologies
989
+ if (result.rootTechs.length > 0) {
990
+ const label = hasSubProjects ? "Root" : "Detected";
991
+ lines.push(this.formatSection(label, result.rootTechs, scanDir));
992
+ }
993
+
994
+ // Sub-projects
995
+ for (const sp of result.subProjects) {
996
+ const spDir = path.join(scanDir, sp.relPath);
997
+ lines.push(this.formatSection(sp.relPath, sp.techs, spDir));
998
+ }
999
+
1000
+ // Monorepo-specific hints
1001
+ if (result.monorepo && result.monorepo.hints.length > 0) {
1002
+ lines.push("Monorepo:");
1003
+ for (const h of result.monorepo.hints) lines.push(` ${h}`);
1004
+ }
1005
+
1006
+ return lines.join("\n").trimEnd();
1007
+ }
1008
+
1009
+ private formatSection(
1010
+ label: string,
1011
+ techs: DetectedTech[],
1012
+ dir: string
1013
+ ): string {
1014
+ const lines: string[] = [];
1015
+
1016
+ // Tech names
1017
+ const names = techs.map((t) => t.rule.name).join(", ");
1018
+ lines.push(`${label}: ${names}`);
1019
+
1020
+ // Key files (only those that actually exist on disk)
1021
+ const keyFiles: string[] = [];
1022
+ for (const t of techs) {
1023
+ for (const f of t.rule.keyFiles) {
1024
+ if (!keyFiles.includes(f) && fs.existsSync(path.join(dir, f))) {
1025
+ keyFiles.push(f);
1026
+ }
1027
+ }
1028
+ }
1029
+ if (keyFiles.length > 0) {
1030
+ lines.push(` read: ${keyFiles.join(", ")}`);
1031
+ }
1032
+
1033
+ // Explore patterns (deduplicated)
1034
+ const patterns: string[] = [];
1035
+ for (const t of techs) {
1036
+ for (const p of t.rule.explorePatterns) {
1037
+ if (!patterns.includes(p)) patterns.push(p);
1038
+ }
1039
+ }
1040
+ if (patterns.length > 0) {
1041
+ lines.push(` find: ${patterns.join(", ")}`);
1042
+ }
1043
+
1044
+ // Hints — only from framework, database, and infra categories (skip generic language/build hints)
1045
+ const hints: string[] = [];
1046
+ for (const t of techs) {
1047
+ if (["framework", "database", "infra"].includes(t.rule.category)) {
1048
+ for (const h of t.rule.hints) hints.push(h);
1049
+ }
1050
+ }
1051
+ if (hints.length > 0) {
1052
+ lines.push(` hints: ${hints.join(" · ")}`);
1053
+ }
1054
+
1055
+ lines.push("");
1056
+ return lines.join("\n");
1057
+ }
1058
+ }