codeguardian-mcp 1.0.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 (335) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/dist/agent/agentTools.d.ts +26 -0
  4. package/dist/agent/agentTools.d.ts.map +1 -0
  5. package/dist/agent/agentTools.js +699 -0
  6. package/dist/agent/agentTools.js.map +1 -0
  7. package/dist/agent/autoValidator.d.ts +110 -0
  8. package/dist/agent/autoValidator.d.ts.map +1 -0
  9. package/dist/agent/autoValidator.js +964 -0
  10. package/dist/agent/autoValidator.js.map +1 -0
  11. package/dist/agent/fileWatcher.d.ts +28 -0
  12. package/dist/agent/fileWatcher.d.ts.map +1 -0
  13. package/dist/agent/fileWatcher.js +88 -0
  14. package/dist/agent/fileWatcher.js.map +1 -0
  15. package/dist/agent/guardianPersistence.d.ts +98 -0
  16. package/dist/agent/guardianPersistence.d.ts.map +1 -0
  17. package/dist/agent/guardianPersistence.js +296 -0
  18. package/dist/agent/guardianPersistence.js.map +1 -0
  19. package/dist/agent/mcpNotifications.d.ts +38 -0
  20. package/dist/agent/mcpNotifications.d.ts.map +1 -0
  21. package/dist/agent/mcpNotifications.js +81 -0
  22. package/dist/agent/mcpNotifications.js.map +1 -0
  23. package/dist/analyzers/aiPatterns.d.ts +16 -0
  24. package/dist/analyzers/aiPatterns.d.ts.map +1 -0
  25. package/dist/analyzers/aiPatterns.js +103 -0
  26. package/dist/analyzers/aiPatterns.js.map +1 -0
  27. package/dist/analyzers/antiPatterns.d.ts +60 -0
  28. package/dist/analyzers/antiPatterns.d.ts.map +1 -0
  29. package/dist/analyzers/antiPatterns.js +198 -0
  30. package/dist/analyzers/antiPatterns.js.map +1 -0
  31. package/dist/analyzers/builtinTypes.d.ts +18 -0
  32. package/dist/analyzers/builtinTypes.d.ts.map +1 -0
  33. package/dist/analyzers/builtinTypes.js +1275 -0
  34. package/dist/analyzers/builtinTypes.js.map +1 -0
  35. package/dist/analyzers/complexity.d.ts +14 -0
  36. package/dist/analyzers/complexity.d.ts.map +1 -0
  37. package/dist/analyzers/complexity.js +610 -0
  38. package/dist/analyzers/complexity.js.map +1 -0
  39. package/dist/analyzers/findingVerifier.d.ts +59 -0
  40. package/dist/analyzers/findingVerifier.d.ts.map +1 -0
  41. package/dist/analyzers/findingVerifier.js +1169 -0
  42. package/dist/analyzers/findingVerifier.js.map +1 -0
  43. package/dist/analyzers/impactAnalyzer.d.ts +53 -0
  44. package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
  45. package/dist/analyzers/impactAnalyzer.js +152 -0
  46. package/dist/analyzers/impactAnalyzer.js.map +1 -0
  47. package/dist/analyzers/languageDetector.d.ts +48 -0
  48. package/dist/analyzers/languageDetector.d.ts.map +1 -0
  49. package/dist/analyzers/languageDetector.js +404 -0
  50. package/dist/analyzers/languageDetector.js.map +1 -0
  51. package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
  52. package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
  53. package/dist/analyzers/parsers/incrementalParser.js +193 -0
  54. package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
  55. package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
  56. package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
  57. package/dist/analyzers/parsers/scopeResolver.js +324 -0
  58. package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
  59. package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
  60. package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
  61. package/dist/analyzers/parsers/semanticIndex.js +429 -0
  62. package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
  63. package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
  64. package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
  65. package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
  66. package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
  67. package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
  68. package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
  69. package/dist/analyzers/parsers/treeSitterParser.js +709 -0
  70. package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
  71. package/dist/analyzers/relevanceScorer.d.ts +43 -0
  72. package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
  73. package/dist/analyzers/relevanceScorer.js +200 -0
  74. package/dist/analyzers/relevanceScorer.js.map +1 -0
  75. package/dist/analyzers/standardLibrary.d.ts +22 -0
  76. package/dist/analyzers/standardLibrary.d.ts.map +1 -0
  77. package/dist/analyzers/standardLibrary.js +211 -0
  78. package/dist/analyzers/standardLibrary.js.map +1 -0
  79. package/dist/analyzers/symbolGraph.d.ts +30 -0
  80. package/dist/analyzers/symbolGraph.d.ts.map +1 -0
  81. package/dist/analyzers/symbolGraph.js +380 -0
  82. package/dist/analyzers/symbolGraph.js.map +1 -0
  83. package/dist/analyzers/symbolTable.d.ts +18 -0
  84. package/dist/analyzers/symbolTable.d.ts.map +1 -0
  85. package/dist/analyzers/symbolTable.js +176 -0
  86. package/dist/analyzers/symbolTable.js.map +1 -0
  87. package/dist/analyzers/typeChecker.d.ts +13 -0
  88. package/dist/analyzers/typeChecker.d.ts.map +1 -0
  89. package/dist/analyzers/typeChecker.js +580 -0
  90. package/dist/analyzers/typeChecker.js.map +1 -0
  91. package/dist/analyzers/usagePatterns.d.ts +42 -0
  92. package/dist/analyzers/usagePatterns.d.ts.map +1 -0
  93. package/dist/analyzers/usagePatterns.js +75 -0
  94. package/dist/analyzers/usagePatterns.js.map +1 -0
  95. package/dist/api-contract/context/backend.d.ts +19 -0
  96. package/dist/api-contract/context/backend.d.ts.map +1 -0
  97. package/dist/api-contract/context/backend.js +64 -0
  98. package/dist/api-contract/context/backend.js.map +1 -0
  99. package/dist/api-contract/context/contract.d.ts +34 -0
  100. package/dist/api-contract/context/contract.d.ts.map +1 -0
  101. package/dist/api-contract/context/contract.js +306 -0
  102. package/dist/api-contract/context/contract.js.map +1 -0
  103. package/dist/api-contract/context/frontend.d.ts +19 -0
  104. package/dist/api-contract/context/frontend.d.ts.map +1 -0
  105. package/dist/api-contract/context/frontend.js +64 -0
  106. package/dist/api-contract/context/frontend.js.map +1 -0
  107. package/dist/api-contract/detector.d.ts +28 -0
  108. package/dist/api-contract/detector.d.ts.map +1 -0
  109. package/dist/api-contract/detector.js +393 -0
  110. package/dist/api-contract/detector.js.map +1 -0
  111. package/dist/api-contract/extractors/python.d.ts +32 -0
  112. package/dist/api-contract/extractors/python.d.ts.map +1 -0
  113. package/dist/api-contract/extractors/python.js +521 -0
  114. package/dist/api-contract/extractors/python.js.map +1 -0
  115. package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
  116. package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
  117. package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
  118. package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
  119. package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
  120. package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
  121. package/dist/api-contract/extractors/tsAstUtils.js +173 -0
  122. package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
  123. package/dist/api-contract/extractors/typescript.d.ts +32 -0
  124. package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
  125. package/dist/api-contract/extractors/typescript.js +666 -0
  126. package/dist/api-contract/extractors/typescript.js.map +1 -0
  127. package/dist/api-contract/index.d.ts +104 -0
  128. package/dist/api-contract/index.d.ts.map +1 -0
  129. package/dist/api-contract/index.js +232 -0
  130. package/dist/api-contract/index.js.map +1 -0
  131. package/dist/api-contract/types.d.ts +151 -0
  132. package/dist/api-contract/types.d.ts.map +1 -0
  133. package/dist/api-contract/types.js +19 -0
  134. package/dist/api-contract/types.js.map +1 -0
  135. package/dist/api-contract/validators/endpoint.d.ts +21 -0
  136. package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
  137. package/dist/api-contract/validators/endpoint.js +224 -0
  138. package/dist/api-contract/validators/endpoint.js.map +1 -0
  139. package/dist/api-contract/validators/index.d.ts +40 -0
  140. package/dist/api-contract/validators/index.d.ts.map +1 -0
  141. package/dist/api-contract/validators/index.js +875 -0
  142. package/dist/api-contract/validators/index.js.map +1 -0
  143. package/dist/api-contract/validators/parameter.d.ts +17 -0
  144. package/dist/api-contract/validators/parameter.d.ts.map +1 -0
  145. package/dist/api-contract/validators/parameter.js +250 -0
  146. package/dist/api-contract/validators/parameter.js.map +1 -0
  147. package/dist/api-contract/validators/type.d.ts +38 -0
  148. package/dist/api-contract/validators/type.d.ts.map +1 -0
  149. package/dist/api-contract/validators/type.js +244 -0
  150. package/dist/api-contract/validators/type.js.map +1 -0
  151. package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
  152. package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
  153. package/dist/context/apiContract/complexTypeSupport.js +665 -0
  154. package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
  155. package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
  156. package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
  157. package/dist/context/apiContract/graphqlSupport.js +671 -0
  158. package/dist/context/apiContract/graphqlSupport.js.map +1 -0
  159. package/dist/context/apiContract/index.d.ts +14 -0
  160. package/dist/context/apiContract/index.d.ts.map +1 -0
  161. package/dist/context/apiContract/index.js +17 -0
  162. package/dist/context/apiContract/index.js.map +1 -0
  163. package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
  164. package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
  165. package/dist/context/apiContract/webSocketSupport.js +465 -0
  166. package/dist/context/apiContract/webSocketSupport.js.map +1 -0
  167. package/dist/context/apiContractContext.d.ts +15 -0
  168. package/dist/context/apiContractContext.d.ts.map +1 -0
  169. package/dist/context/apiContractContext.js +979 -0
  170. package/dist/context/apiContractContext.js.map +1 -0
  171. package/dist/context/apiContractExtraction.d.ts +52 -0
  172. package/dist/context/apiContractExtraction.d.ts.map +1 -0
  173. package/dist/context/apiContractExtraction.js +438 -0
  174. package/dist/context/apiContractExtraction.js.map +1 -0
  175. package/dist/context/contextLineage.d.ts +79 -0
  176. package/dist/context/contextLineage.d.ts.map +1 -0
  177. package/dist/context/contextLineage.js +259 -0
  178. package/dist/context/contextLineage.js.map +1 -0
  179. package/dist/context/contextOrchestrator.d.ts +57 -0
  180. package/dist/context/contextOrchestrator.d.ts.map +1 -0
  181. package/dist/context/contextOrchestrator.js +162 -0
  182. package/dist/context/contextOrchestrator.js.map +1 -0
  183. package/dist/context/intentTracker.d.ts +73 -0
  184. package/dist/context/intentTracker.d.ts.map +1 -0
  185. package/dist/context/intentTracker.js +168 -0
  186. package/dist/context/intentTracker.js.map +1 -0
  187. package/dist/context/projectContext.d.ts +219 -0
  188. package/dist/context/projectContext.d.ts.map +1 -0
  189. package/dist/context/projectContext.js +1984 -0
  190. package/dist/context/projectContext.js.map +1 -0
  191. package/dist/prompts/index.d.ts +17 -0
  192. package/dist/prompts/index.d.ts.map +1 -0
  193. package/dist/prompts/index.js +260 -0
  194. package/dist/prompts/index.js.map +1 -0
  195. package/dist/prompts/library.d.ts +51 -0
  196. package/dist/prompts/library.d.ts.map +1 -0
  197. package/dist/prompts/library.js +65 -0
  198. package/dist/prompts/library.js.map +1 -0
  199. package/dist/prompts/templates.d.ts +44 -0
  200. package/dist/prompts/templates.d.ts.map +1 -0
  201. package/dist/prompts/templates.js +97 -0
  202. package/dist/prompts/templates.js.map +1 -0
  203. package/dist/queue/jobPersistence.d.ts +46 -0
  204. package/dist/queue/jobPersistence.d.ts.map +1 -0
  205. package/dist/queue/jobPersistence.js +158 -0
  206. package/dist/queue/jobPersistence.js.map +1 -0
  207. package/dist/queue/jobQueue.d.ts +116 -0
  208. package/dist/queue/jobQueue.d.ts.map +1 -0
  209. package/dist/queue/jobQueue.js +275 -0
  210. package/dist/queue/jobQueue.js.map +1 -0
  211. package/dist/queue/validationJob.d.ts +69 -0
  212. package/dist/queue/validationJob.d.ts.map +1 -0
  213. package/dist/queue/validationJob.js +435 -0
  214. package/dist/queue/validationJob.js.map +1 -0
  215. package/dist/resources/index.d.ts +15 -0
  216. package/dist/resources/index.d.ts.map +1 -0
  217. package/dist/resources/index.js +328 -0
  218. package/dist/resources/index.js.map +1 -0
  219. package/dist/resources/validationReportStore.d.ts +170 -0
  220. package/dist/resources/validationReportStore.d.ts.map +1 -0
  221. package/dist/resources/validationReportStore.js +515 -0
  222. package/dist/resources/validationReportStore.js.map +1 -0
  223. package/dist/server.d.ts +12 -0
  224. package/dist/server.d.ts.map +1 -0
  225. package/dist/server.js +102 -0
  226. package/dist/server.js.map +1 -0
  227. package/dist/tools/asyncValidation.d.ts +19 -0
  228. package/dist/tools/asyncValidation.d.ts.map +1 -0
  229. package/dist/tools/asyncValidation.js +346 -0
  230. package/dist/tools/asyncValidation.js.map +1 -0
  231. package/dist/tools/buildContext.d.ts +17 -0
  232. package/dist/tools/buildContext.d.ts.map +1 -0
  233. package/dist/tools/buildContext.js +188 -0
  234. package/dist/tools/buildContext.js.map +1 -0
  235. package/dist/tools/getDependencyGraph.d.ts +16 -0
  236. package/dist/tools/getDependencyGraph.d.ts.map +1 -0
  237. package/dist/tools/getDependencyGraph.js +436 -0
  238. package/dist/tools/getDependencyGraph.js.map +1 -0
  239. package/dist/tools/incrementalValidation.d.ts +71 -0
  240. package/dist/tools/incrementalValidation.d.ts.map +1 -0
  241. package/dist/tools/incrementalValidation.js +203 -0
  242. package/dist/tools/incrementalValidation.js.map +1 -0
  243. package/dist/tools/index.d.ts +24 -0
  244. package/dist/tools/index.d.ts.map +1 -0
  245. package/dist/tools/index.js +106 -0
  246. package/dist/tools/index.js.map +1 -0
  247. package/dist/tools/validateCode.d.ts +17 -0
  248. package/dist/tools/validateCode.d.ts.map +1 -0
  249. package/dist/tools/validateCode.js +368 -0
  250. package/dist/tools/validateCode.js.map +1 -0
  251. package/dist/tools/validateCodeLite.d.ts +2 -0
  252. package/dist/tools/validateCodeLite.d.ts.map +1 -0
  253. package/dist/tools/validateCodeLite.js +2 -0
  254. package/dist/tools/validateCodeLite.js.map +1 -0
  255. package/dist/tools/validation/builtins.d.ts +92 -0
  256. package/dist/tools/validation/builtins.d.ts.map +1 -0
  257. package/dist/tools/validation/builtins.js +2184 -0
  258. package/dist/tools/validation/builtins.js.map +1 -0
  259. package/dist/tools/validation/contextualNaming.d.ts +99 -0
  260. package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
  261. package/dist/tools/validation/contextualNaming.js +959 -0
  262. package/dist/tools/validation/contextualNaming.js.map +1 -0
  263. package/dist/tools/validation/deadCode.d.ts +115 -0
  264. package/dist/tools/validation/deadCode.d.ts.map +1 -0
  265. package/dist/tools/validation/deadCode.js +861 -0
  266. package/dist/tools/validation/deadCode.js.map +1 -0
  267. package/dist/tools/validation/extractors/index.d.ts +131 -0
  268. package/dist/tools/validation/extractors/index.d.ts.map +1 -0
  269. package/dist/tools/validation/extractors/index.js +233 -0
  270. package/dist/tools/validation/extractors/index.js.map +1 -0
  271. package/dist/tools/validation/extractors/javascript.d.ts +73 -0
  272. package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
  273. package/dist/tools/validation/extractors/javascript.js +1841 -0
  274. package/dist/tools/validation/extractors/javascript.js.map +1 -0
  275. package/dist/tools/validation/extractors/python.d.ts +93 -0
  276. package/dist/tools/validation/extractors/python.d.ts.map +1 -0
  277. package/dist/tools/validation/extractors/python.js +799 -0
  278. package/dist/tools/validation/extractors/python.js.map +1 -0
  279. package/dist/tools/validation/manifest.d.ts +45 -0
  280. package/dist/tools/validation/manifest.d.ts.map +1 -0
  281. package/dist/tools/validation/manifest.js +719 -0
  282. package/dist/tools/validation/manifest.js.map +1 -0
  283. package/dist/tools/validation/parser.d.ts +58 -0
  284. package/dist/tools/validation/parser.d.ts.map +1 -0
  285. package/dist/tools/validation/parser.js +232 -0
  286. package/dist/tools/validation/parser.js.map +1 -0
  287. package/dist/tools/validation/registry.d.ts +15 -0
  288. package/dist/tools/validation/registry.d.ts.map +1 -0
  289. package/dist/tools/validation/registry.js +169 -0
  290. package/dist/tools/validation/registry.js.map +1 -0
  291. package/dist/tools/validation/scoring.d.ts +54 -0
  292. package/dist/tools/validation/scoring.d.ts.map +1 -0
  293. package/dist/tools/validation/scoring.js +242 -0
  294. package/dist/tools/validation/scoring.js.map +1 -0
  295. package/dist/tools/validation/types.d.ts +120 -0
  296. package/dist/tools/validation/types.d.ts.map +1 -0
  297. package/dist/tools/validation/types.js +11 -0
  298. package/dist/tools/validation/types.js.map +1 -0
  299. package/dist/tools/validation/unusedLocals.d.ts +36 -0
  300. package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
  301. package/dist/tools/validation/unusedLocals.js +333 -0
  302. package/dist/tools/validation/unusedLocals.js.map +1 -0
  303. package/dist/tools/validation/validation.d.ts +98 -0
  304. package/dist/tools/validation/validation.d.ts.map +1 -0
  305. package/dist/tools/validation/validation.js +1837 -0
  306. package/dist/tools/validation/validation.js.map +1 -0
  307. package/dist/types/codeGraph.d.ts +163 -0
  308. package/dist/types/codeGraph.d.ts.map +1 -0
  309. package/dist/types/codeGraph.js +9 -0
  310. package/dist/types/codeGraph.js.map +1 -0
  311. package/dist/types/symbolGraph.d.ts +68 -0
  312. package/dist/types/symbolGraph.d.ts.map +1 -0
  313. package/dist/types/symbolGraph.js +10 -0
  314. package/dist/types/symbolGraph.js.map +1 -0
  315. package/dist/types/tools.d.ts +43 -0
  316. package/dist/types/tools.d.ts.map +1 -0
  317. package/dist/types/tools.js +7 -0
  318. package/dist/types/tools.js.map +1 -0
  319. package/dist/utils/fileFilter.d.ts +37 -0
  320. package/dist/utils/fileFilter.d.ts.map +1 -0
  321. package/dist/utils/fileFilter.js +91 -0
  322. package/dist/utils/fileFilter.js.map +1 -0
  323. package/dist/utils/gitUtils.d.ts +28 -0
  324. package/dist/utils/gitUtils.d.ts.map +1 -0
  325. package/dist/utils/gitUtils.js +81 -0
  326. package/dist/utils/gitUtils.js.map +1 -0
  327. package/dist/utils/logger.d.ts +15 -0
  328. package/dist/utils/logger.d.ts.map +1 -0
  329. package/dist/utils/logger.js +38 -0
  330. package/dist/utils/logger.js.map +1 -0
  331. package/dist/utils/serialization.d.ts +25 -0
  332. package/dist/utils/serialization.d.ts.map +1 -0
  333. package/dist/utils/serialization.js +53 -0
  334. package/dist/utils/serialization.js.map +1 -0
  335. package/package.json +90 -0
@@ -0,0 +1,964 @@
1
+ /**
2
+ * Auto Validator - Proactive Agent Mode
3
+ *
4
+ * Automatically validates code when files change.
5
+ * Formats issues for LLM consumption and triggers MCP sampling.
6
+ *
7
+ * @format
8
+ */
9
+ import * as fs from "fs/promises";
10
+ import * as path from "path";
11
+ import { FileWatcher } from "./fileWatcher.js";
12
+ import { orchestrateContext } from "../context/contextOrchestrator.js";
13
+ import { refreshFileContext, markGuardianActive, markGuardianInactive } from "../context/projectContext.js";
14
+ import { extractUsagesAST, extractImportsAST, extractImportsASTWithOptions, extractTypeReferencesAST, } from "../tools/validation/extractors/index.js";
15
+ import { loadManifestDependencies, loadPythonModuleExports, } from "../tools/validation/manifest.js";
16
+ import { extractSymbolsAST, } from "../tools/validation/extractors/index.js";
17
+ import { validateManifest, validateSymbols, buildSymbolTable, validateUsagePatterns, } from "../tools/validation/validation.js";
18
+ import { detectDeadCode, detectUnusedLocals } from "../tools/validation/deadCode.js";
19
+ import { impactAnalyzer } from "../analyzers/impactAnalyzer.js";
20
+ import { logger } from "../utils/logger.js";
21
+ import { PROMPT_PATTERNS, VALIDATION_CONSTRAINTS } from "../prompts/library.js";
22
+ import { generateAntiPatternContext, enrichIssuesWithAntiPatterns } from "../analyzers/antiPatterns.js";
23
+ import { verifyFindingsAutomatically, getConfirmedFindings, } from "../analyzers/findingVerifier.js";
24
+ import { validateApiContracts, } from "../api-contract/index.js";
25
+ export class AutoValidator {
26
+ watcher;
27
+ projectPath;
28
+ language;
29
+ onAlert = null;
30
+ debounceTimers = new Map();
31
+ mode = "auto";
32
+ projectFileCount = 0;
33
+ newFilesTracked = new Set();
34
+ manifest = null;
35
+ pythonExports = new Map();
36
+ agentName;
37
+ projectStructure;
38
+ isFullStack = false;
39
+ tsManifest = null;
40
+ pyManifest = null;
41
+ pendingRefreshes = new Map();
42
+ lastApiContractValidation = 0;
43
+ static API_CONTRACT_DEBOUNCE_MS = 30_000; // At most once per 30s
44
+ activeValidationCount = 0;
45
+ validationQueue = new Set();
46
+ activeRefreshCount = 0;
47
+ static MAX_CONCURRENT_VALIDATIONS = 2;
48
+ static MAX_CONCURRENT_REFRESHES = 3;
49
+ // Thresholds for smart mode
50
+ static NEW_PROJECT_THRESHOLD = 5; // Less than 5 files = new project
51
+ static LEARNING_MODE_FILE_COUNT = 10; // After 10 files created, switch to strict
52
+ // Common path patterns for scope detection
53
+ static FRONTEND_PATTERNS = [
54
+ '/frontend/', '/client/', '/web/', '/app/', '/src/',
55
+ '/components/', '/pages/', '/views/', '/hooks/', '/services/'
56
+ ];
57
+ static BACKEND_PATTERNS = [
58
+ '/backend/', '/server/', '/api/', '/services/',
59
+ '/routes/', '/routers/', '/controllers/', '/models/', '/db/'
60
+ ];
61
+ static SHARED_PATTERNS = [
62
+ '/shared/', '/common/', '/types/', '/interfaces/', '/utils/'
63
+ ];
64
+ /**
65
+ * Detect language from file extension for per-file validation
66
+ */
67
+ static detectFileLanguage(filePath) {
68
+ const ext = path.extname(filePath).toLowerCase();
69
+ const map = {
70
+ ".js": "javascript",
71
+ ".jsx": "javascript",
72
+ ".mjs": "javascript",
73
+ ".cjs": "javascript",
74
+ ".ts": "typescript",
75
+ ".tsx": "typescript",
76
+ ".mts": "typescript",
77
+ ".cts": "typescript",
78
+ ".py": "python",
79
+ };
80
+ return map[ext] || "unknown";
81
+ }
82
+ constructor(projectPath, language = "typescript", mode = "auto", agentName = "The Guardian") {
83
+ this.projectPath = projectPath;
84
+ this.language = language;
85
+ this.mode = mode;
86
+ this.agentName = agentName;
87
+ this.watcher = new FileWatcher(projectPath);
88
+ this.watcher.on("fileChange", (event) => {
89
+ this.handleFileChange(event);
90
+ });
91
+ }
92
+ setAlertHandler(handler) {
93
+ this.onAlert = handler;
94
+ }
95
+ async start() {
96
+ logger.info(`Starting AutoValidator for: ${this.projectPath}`);
97
+ // Silent initialization by default to avoid UI clutter
98
+ /*
99
+ await sendNotification("guardian_starting", {
100
+ message: `🔄 ${this.agentName}: Initialization started...`,
101
+ projectPath: this.projectPath,
102
+ language: this.language,
103
+ });
104
+ */
105
+ // Pre-build context: detect full-stack and use "all" to include every language
106
+ logger.info("Loading project context...");
107
+ this.isFullStack = await this.detectFullStackProject();
108
+ const contextLanguage = this.isFullStack ? "all" : this.language;
109
+ if (this.isFullStack) {
110
+ logger.info("Full-stack project detected — building unified multi-language context");
111
+ }
112
+ const orchestration = await orchestrateContext({
113
+ projectPath: this.projectPath,
114
+ language: contextLanguage,
115
+ });
116
+ const context = orchestration.projectContext;
117
+ logger.info("Context built.");
118
+ // Mark this project as guardian-managed — all tools will now
119
+ // use this cached context without TTL/staleness rebuilds
120
+ markGuardianActive(this.projectPath);
121
+ // Detect project type
122
+ this.projectFileCount = context.files?.size || 0;
123
+ const detectedMode = this.detectProjectMode();
124
+ logger.info(`VibeGuard Initialized: Found ${this.projectFileCount} files in ${this.projectPath}`);
125
+ logger.info(`Operating Mode: ${detectedMode} (Language: ${this.isFullStack ? "all (full-stack)" : this.language})`);
126
+ // Load manifests for all detected languages
127
+ logger.info("Loading dependency manifests...");
128
+ if (this.isFullStack) {
129
+ // Full-stack: load manifests from the correct subdirectories
130
+ // (e.g., frontend/package.json and backend/requirements.txt)
131
+ const tsRoot = this.projectStructure?.frontend || this.projectPath;
132
+ const pyRoot = this.projectStructure?.backend || this.projectPath;
133
+ this.tsManifest = await loadManifestDependencies(tsRoot, "typescript");
134
+ this.pyManifest = await loadManifestDependencies(pyRoot, "python");
135
+ this.pythonExports = await loadPythonModuleExports(pyRoot);
136
+ // Keep this.manifest as the "primary" for backward compat
137
+ this.manifest = this.language === "python" ? this.pyManifest : this.tsManifest;
138
+ }
139
+ else {
140
+ this.manifest = await loadManifestDependencies(this.projectPath, this.language);
141
+ if (this.language === "python") {
142
+ this.pythonExports = await loadPythonModuleExports(this.projectPath);
143
+ }
144
+ }
145
+ // Run initial health check on existing codebase (skip for new projects)
146
+ if (detectedMode !== "learning") {
147
+ logger.info("Running initial health check...");
148
+ await this.runInitialHealthCheck(context);
149
+ }
150
+ else {
151
+ logger.info("New project detected - skipping initial health check, entering Learning Mode");
152
+ this.pushLearningModeNotification();
153
+ }
154
+ logger.info("Starting file watcher...");
155
+ this.watcher.start();
156
+ // Silent ready status to avoid UI clutter
157
+ /*
158
+ await sendNotification("guardian_ready", {
159
+ message: `✅ ${this.agentName}: Ready and watching ${this.projectFileCount} files!`,
160
+ fileCount: this.projectFileCount,
161
+ mode: detectedMode,
162
+ projectPath: this.projectPath,
163
+ });
164
+ */
165
+ }
166
+ detectProjectMode() {
167
+ if (this.mode !== "auto")
168
+ return this.mode;
169
+ if (this.projectFileCount < AutoValidator.NEW_PROJECT_THRESHOLD) {
170
+ return "learning";
171
+ }
172
+ return "strict";
173
+ }
174
+ /**
175
+ * Detect if this is a full-stack project with both frontend (TS/JS) and backend (Python) code.
176
+ * Checks for common directory structures and file extensions.
177
+ */
178
+ async detectFullStackProject() {
179
+ const fs = await import("fs/promises");
180
+ let hasPython = false;
181
+ let hasTypeScript = false;
182
+ let pythonRoot;
183
+ let tsRoot;
184
+ // Quick heuristic: check for common full-stack markers at project root
185
+ const markers = [
186
+ { path: "requirements.txt", lang: "python" },
187
+ { path: "pyproject.toml", lang: "python" },
188
+ { path: "Pipfile", lang: "python" },
189
+ { path: "package.json", lang: "typescript" },
190
+ { path: "tsconfig.json", lang: "typescript" },
191
+ ];
192
+ for (const marker of markers) {
193
+ try {
194
+ await fs.access(path.join(this.projectPath, marker.path));
195
+ if (marker.lang === "python")
196
+ hasPython = true;
197
+ if (marker.lang === "typescript")
198
+ hasTypeScript = true;
199
+ }
200
+ catch {
201
+ // Not found
202
+ }
203
+ }
204
+ // Helper to detect language of a subdirectory based on its manifest files
205
+ const detectDirLanguage = async (dirPath) => {
206
+ let dirHasPython = false;
207
+ let dirHasTS = false;
208
+ for (const m of ["requirements.txt", "pyproject.toml", "Pipfile"]) {
209
+ try {
210
+ await fs.access(path.join(dirPath, m));
211
+ dirHasPython = true;
212
+ break;
213
+ }
214
+ catch { /* Not found */ }
215
+ }
216
+ for (const m of ["package.json", "tsconfig.json"]) {
217
+ try {
218
+ await fs.access(path.join(dirPath, m));
219
+ dirHasTS = true;
220
+ break;
221
+ }
222
+ catch { /* Not found */ }
223
+ }
224
+ if (dirHasPython && dirHasTS)
225
+ return "both";
226
+ if (dirHasPython)
227
+ return "python";
228
+ if (dirHasTS)
229
+ return "typescript";
230
+ return "unknown";
231
+ };
232
+ // Check common subdirectory patterns — detect ACTUAL language from manifests,
233
+ // not from directory name (a "backend/" can be Python OR TypeScript)
234
+ const dirCandidates = [
235
+ { path: "backend", role: "backend" },
236
+ { path: "server", role: "backend" },
237
+ { path: "frontend", role: "frontend" },
238
+ { path: "client", role: "frontend" },
239
+ ];
240
+ for (const check of dirCandidates) {
241
+ try {
242
+ const dirPath = path.join(this.projectPath, check.path);
243
+ const stat = await fs.stat(dirPath);
244
+ if (stat.isDirectory()) {
245
+ const lang = await detectDirLanguage(dirPath);
246
+ if (lang === "python" || lang === "both") {
247
+ hasPython = true;
248
+ if (!pythonRoot)
249
+ pythonRoot = dirPath;
250
+ }
251
+ if (lang === "typescript" || lang === "both") {
252
+ hasTypeScript = true;
253
+ // For TS: prefer frontend-role dirs as tsRoot, backend-role dirs as separate
254
+ if (check.role === "frontend" && !tsRoot) {
255
+ tsRoot = dirPath;
256
+ }
257
+ else if (check.role === "backend" && !tsRoot) {
258
+ // Backend is also TS — tsRoot tracks the frontend for structure
259
+ // but we still know it's TypeScript on both sides (NOT full-stack Python+TS)
260
+ tsRoot = tsRoot || dirPath;
261
+ }
262
+ }
263
+ }
264
+ }
265
+ catch {
266
+ // Not found
267
+ }
268
+ }
269
+ // Store detected structure for manifest loading and scope resolution
270
+ if (hasPython && hasTypeScript) {
271
+ this.projectStructure = {
272
+ backend: pythonRoot || this.projectPath,
273
+ frontend: tsRoot || this.projectPath,
274
+ };
275
+ logger.info(`Full-stack structure detected: backend=${pythonRoot || this.projectPath}, frontend=${tsRoot || this.projectPath}`);
276
+ }
277
+ return hasPython && hasTypeScript;
278
+ }
279
+ /**
280
+ * Get the correct manifest for a given file language
281
+ */
282
+ getManifestForLanguage(fileLang) {
283
+ if (!this.isFullStack)
284
+ return this.manifest;
285
+ if (fileLang === "python")
286
+ return this.pyManifest;
287
+ return this.tsManifest; // typescript, javascript
288
+ }
289
+ /**
290
+ * Detect the scope of a file (frontend, backend, shared, or unknown)
291
+ * Used for smart filtering of validation issues
292
+ */
293
+ detectFileScope(filePath) {
294
+ const normalizedPath = filePath.toLowerCase();
295
+ // Check for shared patterns first (highest priority)
296
+ for (const pattern of AutoValidator.SHARED_PATTERNS) {
297
+ if (normalizedPath.includes(pattern)) {
298
+ return "shared";
299
+ }
300
+ }
301
+ // Check for frontend patterns
302
+ for (const pattern of AutoValidator.FRONTEND_PATTERNS) {
303
+ if (normalizedPath.includes(pattern)) {
304
+ // But make sure it's not in a backend directory
305
+ const isBackend = AutoValidator.BACKEND_PATTERNS.some(p => normalizedPath.includes(p));
306
+ if (!isBackend) {
307
+ return "frontend";
308
+ }
309
+ }
310
+ }
311
+ // Check for backend patterns
312
+ for (const pattern of AutoValidator.BACKEND_PATTERNS) {
313
+ if (normalizedPath.includes(pattern)) {
314
+ return "backend";
315
+ }
316
+ }
317
+ // Detect by file extension and content patterns
318
+ if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx')) {
319
+ return "frontend";
320
+ }
321
+ if (filePath.endsWith('.py') && !filePath.includes('test')) {
322
+ // Check if it's clearly a backend file
323
+ if (normalizedPath.includes('main.py') || normalizedPath.includes('app.py')) {
324
+ return "backend";
325
+ }
326
+ }
327
+ return "unknown";
328
+ }
329
+ /**
330
+ * Filter issues based on file scope for smart alerting
331
+ * Shows all critical issues, but filters lower severity by relevance
332
+ */
333
+ filterIssuesByScope(issues, fileScope, isLenient) {
334
+ if (isLenient) {
335
+ // In lenient mode, show critical/high severity issues
336
+ // PLUS keep issue types that the later lenient filter explicitly preserves:
337
+ // - unusedImport, unusedFunction, unusedExport (per-file code hygiene)
338
+ // - dependencyHallucination, missingDependency (build-breaking or suspicious)
339
+ return issues.filter(issue => issue.severity === 'critical' ||
340
+ issue.severity === 'high' ||
341
+ issue.type === 'unusedImport' ||
342
+ issue.type === 'unusedFunction' ||
343
+ issue.type === 'unusedExport' ||
344
+ issue.type === 'dependencyHallucination' ||
345
+ issue.type === 'missingDependency');
346
+ }
347
+ return issues.filter(issue => {
348
+ // Always show critical issues
349
+ if (issue.severity === 'critical')
350
+ return true;
351
+ // Always show API contract mismatches (they affect both sides)
352
+ if (issue.type === 'apiContractMismatch')
353
+ return true;
354
+ // For high severity, show if relevant to scope
355
+ if (issue.severity === 'high') {
356
+ // If we can't determine scope, show all high severity
357
+ if (fileScope === 'unknown')
358
+ return true;
359
+ // Dead code in shared files affects everyone
360
+ if (issue.type === 'deadCode' && fileScope === 'shared')
361
+ return true;
362
+ // Unused imports are always relevant
363
+ if (issue.type === 'unusedImport')
364
+ return true;
365
+ return true; // Show other high severity issues
366
+ }
367
+ // For medium/low severity, be more selective
368
+ if (fileScope === 'frontend') {
369
+ // In frontend files, prioritize frontend-specific issues
370
+ if (issue.type === 'unusedImport')
371
+ return true;
372
+ if (issue.type === 'nonExistentFunction')
373
+ return true;
374
+ return false; // Filter out less relevant issues
375
+ }
376
+ if (fileScope === 'backend') {
377
+ // In backend files, prioritize backend-specific issues
378
+ if (issue.type === 'unusedImport')
379
+ return true;
380
+ if (issue.type === 'nonExistentFunction')
381
+ return true;
382
+ return false;
383
+ }
384
+ return true;
385
+ });
386
+ }
387
+ pushLearningModeNotification() {
388
+ const alert = {
389
+ file: "SYSTEM",
390
+ issues: [],
391
+ timestamp: Date.now(),
392
+ llmMessage: `📚 **${this.agentName}: Learning Mode Active**\n\nThis appears to be a new project (${this.projectFileCount} files). I'll be lenient and learn your patterns as you build.\n\nOnce you have more than ${AutoValidator.LEARNING_MODE_FILE_COUNT} files, I'll switch to strict validation.`,
393
+ isInitialScan: true,
394
+ };
395
+ if (this.onAlert) {
396
+ this.onAlert(alert);
397
+ }
398
+ }
399
+ async runInitialHealthCheck(context) {
400
+ try {
401
+ // Run API Contract validation
402
+ logger.info("Running API Contract validation...");
403
+ const apiContractResult = await validateApiContracts(this.projectPath);
404
+ if (apiContractResult.issues.length > 0) {
405
+ const apiContractAlert = {
406
+ file: "API_CONTRACT_SCAN",
407
+ issues: apiContractResult.issues.map((issue) => ({
408
+ type: issue.type,
409
+ severity: issue.severity,
410
+ message: issue.message,
411
+ suggestion: issue.suggestion,
412
+ line: issue.line,
413
+ file: issue.file ? path.relative(this.projectPath, issue.file) : undefined,
414
+ })),
415
+ timestamp: Date.now(),
416
+ llmMessage: this.createApiContractMessage(apiContractResult),
417
+ isInitialScan: true,
418
+ };
419
+ logger.info(`API Contract scan found ${apiContractResult.issues.length} issues`);
420
+ if (this.onAlert) {
421
+ this.onAlert(apiContractAlert);
422
+ }
423
+ }
424
+ // Run dead code detection on existing codebase
425
+ // For full-stack projects, run per-scope to avoid hitting the export cap
426
+ let deadCodeIssues;
427
+ if (this.isFullStack && this.projectStructure) {
428
+ const backendRoot = this.projectStructure.backend;
429
+ const frontendRoot = this.projectStructure.frontend;
430
+ logger.info(`Running dead code detection per-scope: backend=${backendRoot}, frontend=${frontendRoot}`);
431
+ const [backendDead, frontendDead] = await Promise.all([
432
+ detectDeadCode(context, undefined, (fp) => fp.startsWith(backendRoot)),
433
+ detectDeadCode(context, undefined, (fp) => fp.startsWith(frontendRoot)),
434
+ ]);
435
+ deadCodeIssues = [...backendDead, ...frontendDead];
436
+ logger.info(`Dead code per-scope: backend=${backendDead.length}, frontend=${frontendDead.length}`);
437
+ }
438
+ else {
439
+ deadCodeIssues = await detectDeadCode(context);
440
+ }
441
+ // Verify findings to eliminate false positives
442
+ // Use "all" for full-stack projects so verification understands both languages
443
+ let confirmedDeadCode = deadCodeIssues;
444
+ const verifyLang = this.isFullStack ? "all" : this.language;
445
+ if (deadCodeIssues.length > 0) {
446
+ logger.debug(`Verifying ${deadCodeIssues.length} dead code findings...`);
447
+ const verificationResult = await verifyFindingsAutomatically([], deadCodeIssues, context, this.projectPath, verifyLang);
448
+ confirmedDeadCode = getConfirmedFindings(verificationResult).deadCode;
449
+ logger.debug(`Verification complete: ${confirmedDeadCode.length} confirmed (filtered ${verificationResult.stats.falsePositiveCount} false positives)`);
450
+ }
451
+ if (confirmedDeadCode.length > 0) {
452
+ const alert = {
453
+ file: "INITIAL_SCAN",
454
+ issues: confirmedDeadCode.map((dc) => ({
455
+ type: "deadCode",
456
+ severity: dc.severity,
457
+ message: dc.message,
458
+ suggestion: dc.suggestion,
459
+ line: dc.line,
460
+ file: dc.file ? path.relative(this.projectPath, dc.file) : undefined,
461
+ })),
462
+ timestamp: Date.now(),
463
+ llmMessage: this.createInitialScanMessage(confirmedDeadCode),
464
+ isInitialScan: true,
465
+ };
466
+ logger.info(`Initial scan found ${confirmedDeadCode.length} confirmed issues in existing codebase`);
467
+ if (this.onAlert) {
468
+ this.onAlert(alert);
469
+ }
470
+ }
471
+ else {
472
+ logger.info("Initial scan: No issues found in existing codebase");
473
+ }
474
+ // Phase 3: Per-file hallucination and local dead code scan
475
+ // This catches issues that project-wide scans miss:
476
+ // - Hallucinated imports (packages not in package.json / requirements.txt)
477
+ // - Unused local functions/constants (non-exported dead code)
478
+ // The project-wide scan only finds unused EXPORTS and orphaned files.
479
+ // This per-file scan finds hallucinated dependencies and local dead code.
480
+ logger.info("Running per-file hallucination and local dead code scan...");
481
+ const perFileHallucinations = [];
482
+ const perFileDeadCode = [];
483
+ const MAX_INITIAL_FILES_TO_SCAN = 100;
484
+ let filesScanned = 0;
485
+ for (const [filePath, fileInfo] of context.files) {
486
+ if (filesScanned >= MAX_INITIAL_FILES_TO_SCAN)
487
+ break;
488
+ if (fileInfo.isTest || fileInfo.isConfig || fileInfo.isEntryPoint)
489
+ continue;
490
+ const fileLang = AutoValidator.detectFileLanguage(filePath);
491
+ if (fileLang === "unknown")
492
+ continue;
493
+ try {
494
+ const content = await fs.readFile(filePath, "utf-8");
495
+ filesScanned++;
496
+ // Tier 0: Manifest validation — catches hallucinated imports
497
+ const imports = extractImportsAST(content, fileLang);
498
+ const fileManifest = this.getManifestForLanguage(fileLang);
499
+ if (fileManifest) {
500
+ const manifestIssues = await validateManifest(imports, fileManifest, content, fileLang, filePath);
501
+ for (const issue of manifestIssues) {
502
+ // Attach file path so the verifier can read the file
503
+ issue.file = filePath;
504
+ perFileHallucinations.push(issue);
505
+ }
506
+ }
507
+ // Tier 2: Local dead code — catches unused non-exported functions/constants
508
+ const localDeadCode = detectUnusedLocals(content, filePath);
509
+ perFileDeadCode.push(...localDeadCode);
510
+ }
511
+ catch (err) {
512
+ logger.debug(`Skipping initial scan of ${filePath}: ${err}`);
513
+ }
514
+ }
515
+ logger.info(`Per-file scan complete: ${filesScanned} files, ${perFileHallucinations.length} hallucinations, ${perFileDeadCode.length} local dead code`);
516
+ // Verify per-file findings to eliminate false positives
517
+ const allPerFileFindings = [...perFileHallucinations, ...perFileDeadCode];
518
+ if (allPerFileFindings.length > 0) {
519
+ logger.debug(`Verifying ${allPerFileFindings.length} per-file findings...`);
520
+ const perFileVerification = await verifyFindingsAutomatically(perFileHallucinations, perFileDeadCode, context, this.projectPath, verifyLang);
521
+ const confirmedPerFile = getConfirmedFindings(perFileVerification);
522
+ const confirmedPerFileAll = [...confirmedPerFile.hallucinations, ...confirmedPerFile.deadCode];
523
+ logger.debug(`Per-file verification: ${confirmedPerFileAll.length} confirmed (filtered ${perFileVerification.stats.falsePositiveCount} false positives)`);
524
+ if (confirmedPerFileAll.length > 0) {
525
+ const perFileAlert = {
526
+ file: "INITIAL_FILE_SCAN",
527
+ issues: confirmedPerFileAll.map((issue) => ({
528
+ type: issue.type || "unknown",
529
+ severity: issue.severity || "medium",
530
+ message: issue.message || "",
531
+ suggestion: issue.suggestion,
532
+ line: issue.line,
533
+ file: issue.file ? path.relative(this.projectPath, issue.file) : undefined,
534
+ })),
535
+ timestamp: Date.now(),
536
+ llmMessage: this.createPerFileScanMessage(confirmedPerFile.hallucinations, confirmedPerFile.deadCode),
537
+ isInitialScan: true,
538
+ };
539
+ logger.info(`Per-file scan found ${confirmedPerFileAll.length} confirmed issues`);
540
+ if (this.onAlert) {
541
+ this.onAlert(perFileAlert);
542
+ }
543
+ }
544
+ }
545
+ }
546
+ catch (err) {
547
+ logger.error("Error running initial health check:", err);
548
+ }
549
+ }
550
+ createInitialScanMessage(deadCodeIssues) {
551
+ return `📋 **${this.agentName}: Initial Scan Complete** - Found **${deadCodeIssues.length} potential issues** in existing codebase. Use 'get_guardian_alerts' to see details.`;
552
+ }
553
+ createPerFileScanMessage(hallucinations, deadCode) {
554
+ const parts = [];
555
+ parts.push(`🔍 **${this.agentName}: Per-File Scan Complete**`);
556
+ parts.push("");
557
+ if (hallucinations.length > 0) {
558
+ parts.push(`**${hallucinations.length} Hallucinated Import(s):**`);
559
+ for (const h of hallucinations.slice(0, 5)) {
560
+ const relFile = path.relative(this.projectPath, h.file || "");
561
+ parts.push(`- [${h.severity?.toUpperCase()}] ${h.message}${relFile ? ` in \`${relFile}\`` : ""}`);
562
+ if (h.suggestion)
563
+ parts.push(` Suggestion: ${h.suggestion}`);
564
+ }
565
+ if (hallucinations.length > 5) {
566
+ parts.push(` ... and ${hallucinations.length - 5} more`);
567
+ }
568
+ parts.push("");
569
+ }
570
+ if (deadCode.length > 0) {
571
+ parts.push(`**${deadCode.length} Unused Local Function(s)/Constant(s):**`);
572
+ for (const dc of deadCode.slice(0, 5)) {
573
+ const relFile = path.relative(this.projectPath, dc.file || "");
574
+ parts.push(`- [${dc.severity?.toUpperCase()}] ${dc.message}${relFile ? ` in \`${relFile}\`` : ""}`);
575
+ }
576
+ if (deadCode.length > 5) {
577
+ parts.push(` ... and ${deadCode.length - 5} more`);
578
+ }
579
+ parts.push("");
580
+ }
581
+ parts.push("Use 'get_guardian_alerts' to see all details.");
582
+ return parts.join("\n");
583
+ }
584
+ createApiContractMessage(result) {
585
+ const lines = [];
586
+ lines.push(`🔗 **${this.agentName}: API Contract Validation**`);
587
+ lines.push("");
588
+ lines.push(`Found **${result.issues.length}** API contract issues:`);
589
+ lines.push(`- 🔴 Critical: ${result.summary.critical}`);
590
+ lines.push(`- 🟠 High: ${result.summary.high}`);
591
+ lines.push(`- 🟡 Medium: ${result.summary.medium}`);
592
+ lines.push(`- 🟢 Low: ${result.summary.low}`);
593
+ lines.push("");
594
+ lines.push(`📊 Matched Endpoints: ${result.summary.matchedEndpoints}`);
595
+ lines.push(`📊 Matched Types: ${result.summary.matchedTypes}`);
596
+ lines.push("");
597
+ if (result.summary.critical > 0) {
598
+ lines.push("**Critical Issues (Fix Immediately):**");
599
+ const critical = result.issues.filter((i) => i.severity === "critical").slice(0, 3);
600
+ critical.forEach((issue) => {
601
+ lines.push(`- ${issue.message}`);
602
+ lines.push(` Suggestion: ${issue.suggestion}`);
603
+ });
604
+ lines.push("");
605
+ }
606
+ if (result.summary.unmatchedFrontend > 0) {
607
+ lines.push(`⚠️ **${result.summary.unmatchedFrontend} frontend services** don't have matching backend routes`);
608
+ }
609
+ if (result.summary.unmatchedBackend > 0) {
610
+ lines.push(`⚠️ **${result.summary.unmatchedBackend} backend routes** are not used by frontend`);
611
+ }
612
+ return lines.join("\n");
613
+ }
614
+ stop() {
615
+ this.watcher.stop();
616
+ this.debounceTimers.forEach((timer) => clearTimeout(timer));
617
+ this.debounceTimers.clear();
618
+ markGuardianInactive(this.projectPath);
619
+ }
620
+ handleFileChange(event) {
621
+ // Detect language for this specific file (full-stack aware)
622
+ const fileLang = AutoValidator.detectFileLanguage(event.path);
623
+ const refreshLang = this.isFullStack ? "all" : this.language;
624
+ // Track new files and refresh context — store promise so validateFile can await it.
625
+ // Throttle concurrent refreshes to avoid spawning too many git subprocesses.
626
+ let refreshPromise = null;
627
+ if (event.type === "add") {
628
+ this.newFilesTracked.add(event.path);
629
+ this.projectFileCount++;
630
+ logger.debug(`New file detected: ${event.path} (${fileLang}) - refreshing context...`);
631
+ refreshPromise = this.throttledRefresh(event.path, refreshLang);
632
+ // Check if we should exit learning mode
633
+ if (this.mode === "auto" && this.projectFileCount >= AutoValidator.LEARNING_MODE_FILE_COUNT) {
634
+ // Only switch if we were previously in learning mode (implied by auto + threshold)
635
+ // But for simplicity, we just let the next detectProjectMode call handle it
636
+ }
637
+ }
638
+ else if (event.type === "change") {
639
+ logger.debug(`File modified: ${event.path} (${fileLang}) - refreshing context...`);
640
+ refreshPromise = this.throttledRefresh(event.path, refreshLang);
641
+ }
642
+ else if (event.type === "unlink") {
643
+ this.newFilesTracked.delete(event.path);
644
+ this.projectFileCount = Math.max(0, this.projectFileCount - 1);
645
+ logger.debug(`File deleted: ${event.path} (${fileLang}) - removing from context...`);
646
+ refreshPromise = this.throttledRefresh(event.path, refreshLang);
647
+ // File is deleted — emit a clear alert immediately instead of trying to validate.
648
+ // This clears both the per-file alert and any matching initial scan issues.
649
+ const relativePath = path.relative(this.projectPath, event.path);
650
+ if (this.onAlert) {
651
+ const clearAlert = {
652
+ file: relativePath,
653
+ issues: [],
654
+ timestamp: Date.now(),
655
+ llmMessage: `🗑️ ${this.agentName}: File deleted - ${relativePath}. Issues cleared.`,
656
+ };
657
+ this.onAlert(clearAlert);
658
+ }
659
+ // Skip validation queueing for deleted files — nothing to validate
660
+ return;
661
+ }
662
+ // Track the pending refresh so validateFile can await ALL pending refreshes
663
+ if (refreshPromise) {
664
+ this.pendingRefreshes.set(event.path, refreshPromise);
665
+ refreshPromise.finally(() => this.pendingRefreshes.delete(event.path));
666
+ }
667
+ // Debounce rapid changes to the same file
668
+ const existingTimer = this.debounceTimers.get(event.path);
669
+ if (existingTimer) {
670
+ clearTimeout(existingTimer);
671
+ }
672
+ const timer = setTimeout(() => {
673
+ this.enqueueValidation(event.path);
674
+ this.debounceTimers.delete(event.path);
675
+ }, 500);
676
+ this.debounceTimers.set(event.path, timer);
677
+ }
678
+ /**
679
+ * Throttle concurrent refreshFileContext calls.
680
+ * Without this, 30 file changes = 30 concurrent refreshes, each spawning
681
+ * git subprocesses and writing to disk simultaneously.
682
+ */
683
+ async throttledRefresh(filePath, language) {
684
+ // Wait if too many refreshes are already running
685
+ while (this.activeRefreshCount >= AutoValidator.MAX_CONCURRENT_REFRESHES) {
686
+ await new Promise(resolve => setTimeout(resolve, 50));
687
+ }
688
+ this.activeRefreshCount++;
689
+ try {
690
+ await refreshFileContext(this.projectPath, filePath, { language });
691
+ }
692
+ catch (err) {
693
+ logger.warn(`Failed to refresh context for ${filePath}:`, err);
694
+ }
695
+ finally {
696
+ this.activeRefreshCount--;
697
+ }
698
+ }
699
+ /**
700
+ * Enqueue a file for validation. Only one validation runs at a time.
701
+ * Without this, N concurrent validateFile calls each spawn git subprocesses,
702
+ * AST parsing, and verification — causing a subprocess storm that can hang the system.
703
+ */
704
+ enqueueValidation(filePath) {
705
+ if (this.activeValidationCount >= AutoValidator.MAX_CONCURRENT_VALIDATIONS) {
706
+ this.validationQueue.add(filePath);
707
+ return;
708
+ }
709
+ this.runValidation(filePath);
710
+ }
711
+ async runValidation(filePath) {
712
+ this.activeValidationCount++;
713
+ try {
714
+ await this.validateFile(filePath);
715
+ }
716
+ finally {
717
+ this.activeValidationCount--;
718
+ this.processValidationQueue();
719
+ }
720
+ }
721
+ processValidationQueue() {
722
+ // Drain queue up to concurrency limit
723
+ while (this.validationQueue.size > 0 && this.activeValidationCount < AutoValidator.MAX_CONCURRENT_VALIDATIONS) {
724
+ const next = this.validationQueue.values().next().value;
725
+ if (!next)
726
+ break;
727
+ this.validationQueue.delete(next);
728
+ this.runValidation(next);
729
+ }
730
+ }
731
+ async validateFile(filePath) {
732
+ try {
733
+ // Wait for ALL pending context refreshes to complete before validating.
734
+ // This prevents race conditions where validation runs on stale/partial context
735
+ // (e.g., reverseImportGraph missing entries because refreshFileContext hasn't finished).
736
+ if (this.pendingRefreshes.size > 0) {
737
+ logger.debug(`Waiting for ${this.pendingRefreshes.size} pending context refreshes...`);
738
+ await Promise.all(this.pendingRefreshes.values());
739
+ }
740
+ const isNewFile = this.newFilesTracked.has(filePath);
741
+ const mode = this.detectProjectMode();
742
+ const isLenient = mode === "learning" || isNewFile;
743
+ // Detect the correct language for THIS file (not the project-level language)
744
+ const fileLang = AutoValidator.detectFileLanguage(filePath);
745
+ if (fileLang === "unknown") {
746
+ logger.debug(`Skipping unknown language file: ${filePath}`);
747
+ return;
748
+ }
749
+ const content = await fs.readFile(filePath, "utf-8");
750
+ const relativePath = path.relative(this.projectPath, filePath);
751
+ logger.info(`Auto-validating (${isLenient ? "Lenient" : "Strict"}) [${fileLang}]: ${relativePath}`);
752
+ // Use orchestrateContext with "all" for full-stack, or the file's language
753
+ const contextLanguage = this.isFullStack ? "all" : this.language;
754
+ const orchestration = await orchestrateContext({
755
+ projectPath: this.projectPath,
756
+ language: contextLanguage,
757
+ currentFile: filePath,
758
+ });
759
+ const context = orchestration.projectContext;
760
+ // Extract symbols and imports using the FILE's language (not the project language)
761
+ const imports = extractImportsASTWithOptions(content, fileLang, {
762
+ filePath,
763
+ });
764
+ const usedSymbols = extractUsagesAST(content, fileLang, imports, { filePath });
765
+ const symbolTable = buildSymbolTable(context, orchestration.relevantSymbols);
766
+ // Extract type references for unused import detection
767
+ // This is essential for TypeScript where imports might only be used as types
768
+ const typeReferences = fileLang === "typescript" || fileLang === "javascript" ?
769
+ extractTypeReferencesAST(content, fileLang, { filePath })
770
+ : [];
771
+ // Tier 0: Check manifest dependencies (use the correct manifest for this file's language)
772
+ let manifestIssues = [];
773
+ const fileManifest = this.getManifestForLanguage(fileLang);
774
+ if (fileManifest) {
775
+ manifestIssues = await validateManifest(imports, fileManifest, content, fileLang, filePath);
776
+ }
777
+ // Tier 1: Validate symbols (hallucination detection)
778
+ let symbolIssues = validateSymbols(usedSymbols, symbolTable, content, fileLang, false, // strictMode
779
+ imports, this.pythonExports, context, filePath, undefined, // missingPackages (calculated internally)
780
+ typeReferences);
781
+ // Validate usage patterns (architectural consistency)
782
+ let patternIssues = validateUsagePatterns(usedSymbols, context);
783
+ // Tier 1.5: Change Impact Analysis (Blast Radius) - Proactive Alerting
784
+ let impactIssues = [];
785
+ if (context.symbolGraph) {
786
+ const symbolsInFile = extractSymbolsAST(content, filePath, fileLang);
787
+ for (const sym of symbolsInFile) {
788
+ // Skip non-exported symbols and very short names to avoid noise
789
+ if (!sym.isExported || sym.name.length <= 2) {
790
+ continue;
791
+ }
792
+ if (context.symbolIndex.has(sym.name)) {
793
+ const blast = impactAnalyzer.traceBlastRadius(sym.name, context.symbolGraph, 2);
794
+ if (blast.severity === "high") {
795
+ impactIssues.push({
796
+ type: "architecturalDeviation",
797
+ severity: "high",
798
+ message: `Modifying '${sym.name}' has a HIGH project-wide impact affecting ${blast.affectedFiles.length} files.`,
799
+ line: sym.line,
800
+ suggestion: `Run 'get_dependency_graph' for '${sym.name}' to see the full list of affected symbols.`,
801
+ });
802
+ }
803
+ }
804
+ }
805
+ }
806
+ // Tier 2: Per-file dead code checks (unused functions/constants in THIS file only).
807
+ // Project-wide checks (orphaned files, unused exports) run in the initial health check,
808
+ // NOT on every file change — they are slow, race-prone during rapid vibecoding,
809
+ // and produce false positives when the dependency graph is mid-update.
810
+ //
811
+ // Use detectUnusedLocals directly for per-file dead code detection.
812
+ // This finds unused local functions and constants within the file.
813
+ const deadCodeIssues = detectUnusedLocals(content, filePath);
814
+ // Tier 3: API Contract Validation (for service/route files)
815
+ // Debounced to at most once per 30s to avoid full project-wide scans on every keystroke
816
+ let apiContractIssues = [];
817
+ const isServiceFile = filePath.includes('/services/') || filePath.includes('/api/');
818
+ const isRouteFile = filePath.includes('/api/') && filePath.endsWith('.py');
819
+ const apiContractCooldown = Date.now() - this.lastApiContractValidation >= AutoValidator.API_CONTRACT_DEBOUNCE_MS;
820
+ if ((isServiceFile || isRouteFile) && apiContractCooldown) {
821
+ logger.debug(`API Contract file changed: ${relativePath} - running contract validation...`);
822
+ this.lastApiContractValidation = Date.now();
823
+ const apiContractResult = await validateApiContracts(this.projectPath);
824
+ // Only report critical and high severity issues for real-time validation
825
+ apiContractIssues = apiContractResult.issues
826
+ .filter(issue => issue.severity === 'critical' || issue.severity === 'high')
827
+ .map(issue => ({
828
+ type: issue.type,
829
+ severity: issue.severity,
830
+ message: issue.message,
831
+ line: issue.line,
832
+ suggestion: issue.suggestion,
833
+ }));
834
+ if (apiContractIssues.length > 0) {
835
+ logger.info(`API Contract validation found ${apiContractIssues.length} issues in ${relativePath}`);
836
+ }
837
+ }
838
+ // Combine all issues for verification
839
+ let allIssues = [...manifestIssues, ...symbolIssues, ...patternIssues, ...deadCodeIssues, ...impactIssues, ...apiContractIssues];
840
+ // === SMART SCOPE FILTERING (NEW) ===
841
+ // Detect file scope and filter issues by relevance
842
+ const fileScope = this.detectFileScope(filePath);
843
+ logger.debug(`File scope detected: ${fileScope} for ${relativePath}`);
844
+ // Apply scope-based filtering
845
+ allIssues = this.filterIssuesByScope(allIssues, fileScope, isLenient);
846
+ logger.debug(`After scope filtering: ${allIssues.length} relevant issues`);
847
+ // === AUTOMATED VERIFICATION (eliminates false positives) ===
848
+ if (allIssues.length > 0) {
849
+ logger.debug(`Verifying ${allIssues.length} findings to eliminate false positives...`);
850
+ const verificationResult = await verifyFindingsAutomatically(allIssues.filter(i => i.type !== 'deadCode' && i.type !== 'unusedExport' && i.type !== 'unusedFunction' && i.type !== 'orphanedFile'), allIssues.filter(i => i.type === 'deadCode' || i.type === 'unusedExport' || i.type === 'unusedFunction' || i.type === 'orphanedFile'), context, this.projectPath, fileLang);
851
+ // Replace with confirmed findings only
852
+ const confirmed = getConfirmedFindings(verificationResult);
853
+ // Reconstruct allIssues with verified findings
854
+ allIssues = [
855
+ ...confirmed.hallucinations,
856
+ ...confirmed.deadCode,
857
+ ];
858
+ logger.debug(`Verification complete: ${allIssues.length} confirmed (filtered ${verificationResult.stats.falsePositiveCount} false positives)`);
859
+ }
860
+ // === SMART MODE FILTERING ===
861
+ if (isLenient) {
862
+ // In learning/new file mode:
863
+ // 1. Ignore architectural deviations (we are learning patterns)
864
+ allIssues = allIssues.filter(i => i.type !== 'architecturalDeviation');
865
+ // 2. Ignore PROJECT-WIDE dead code in new/changing files (orphaned files, etc.)
866
+ // but KEEP per-file local dead code (unusedFunction, unusedExport from detectUnusedLocals)
867
+ // because local unused functions/constants are genuine code hygiene issues
868
+ // that are cheap to detect and valuable to report.
869
+ allIssues = allIssues.filter(i => i.type !== 'deadCode' && i.type !== 'orphanedFile');
870
+ // 3. Filter out "Medium" severity symbol issues
871
+ // BUT keep unusedImport, unusedFunction, and unusedExport warnings
872
+ // - unusedImport: the #1 vibecoder mistake
873
+ // - unusedFunction/unusedExport: local dead code (cheap to detect, high signal)
874
+ allIssues = allIssues.filter(i => i.severity === "critical" ||
875
+ i.severity === "high" ||
876
+ i.type === "unusedImport" ||
877
+ i.type === "unusedFunction" ||
878
+ i.type === "unusedExport");
879
+ }
880
+ if (allIssues.length > 0) {
881
+ const alert = await this.formatAlert(filePath, allIssues, fileLang);
882
+ logger.warn(`Found ${allIssues.length} issues in ${relativePath}`);
883
+ if (this.onAlert) {
884
+ this.onAlert(alert);
885
+ }
886
+ }
887
+ else {
888
+ logger.info(`No issues found in ${relativePath}`);
889
+ // Notify handler that issues are cleared for this file
890
+ if (this.onAlert) {
891
+ const clearAlert = {
892
+ file: relativePath,
893
+ issues: [],
894
+ timestamp: Date.now(),
895
+ llmMessage: `✅ ${this.agentName}: Issues resolved in ${relativePath}`,
896
+ };
897
+ this.onAlert(clearAlert);
898
+ }
899
+ }
900
+ }
901
+ catch (err) {
902
+ logger.error(`Error validating ${filePath}:`, err);
903
+ }
904
+ }
905
+ async formatAlert(filePath, issues, fileLang) {
906
+ const relativePath = path.relative(this.projectPath, filePath);
907
+ // Enrich issues with anti-pattern context (async)
908
+ const enrichedIssues = await enrichIssuesWithAntiPatterns(issues, fileLang);
909
+ // Format issues for LLM consumption
910
+ const issueList = enrichedIssues.map((issue) => ({
911
+ type: issue.type,
912
+ severity: issue.severity,
913
+ message: issue.message,
914
+ suggestion: issue.suggestion,
915
+ line: issue.line,
916
+ antiPattern: issue.antiPattern,
917
+ }));
918
+ // Create a natural language message for the LLM (async for anti-pattern context)
919
+ const llmMessage = await this.createLLMMessage(relativePath, enrichedIssues, fileLang);
920
+ return {
921
+ file: relativePath,
922
+ issues: issueList,
923
+ timestamp: Date.now(),
924
+ llmMessage,
925
+ };
926
+ }
927
+ async createLLMMessage(file, issues, fileLang) {
928
+ const critical = issues.filter((i) => i.severity === "critical" || i.severity === "high");
929
+ const count = issues.length;
930
+ const task = `There are ${count} issue(s) detected in \`${file}\`. ${critical.length > 0 ? `**${critical.length} issues are CRITICAL.**` : ""} Review the following issues and provide fixes according to the project context.`;
931
+ // Use role-based prompting
932
+ const roleMsg = PROMPT_PATTERNS.role(this.agentName, task);
933
+ // Include validation constraints from the library
934
+ const constrainedMsg = PROMPT_PATTERNS.withConstraints(roleMsg, VALIDATION_CONSTRAINTS);
935
+ // Add specific issues
936
+ const issuesList = issues.map((i, idx) => {
937
+ let item = `${idx + 1}. [${i.severity.toUpperCase()}] ${i.type}: ${i.message}`;
938
+ if (i.line)
939
+ item += ` (Line ${i.line})`;
940
+ if (i.suggestion)
941
+ item += `\n Suggestion: ${i.suggestion}`;
942
+ // Include anti-pattern context if available
943
+ if (i.antiPattern) {
944
+ item += `\n 📚 ${i.antiPattern.id}: ${i.antiPattern.name} - ${i.antiPattern.description}`;
945
+ }
946
+ return item;
947
+ }).join("\n");
948
+ // Generate anti-pattern context for LLM
949
+ const antiPatternContext = await generateAntiPatternContext(issues, fileLang);
950
+ let message = `${constrainedMsg}\n\nDETECTED ISSUES:\n${issuesList}`;
951
+ // Append anti-pattern guidance if relevant
952
+ if (antiPatternContext) {
953
+ message += `\n\n${antiPatternContext}`;
954
+ }
955
+ return message;
956
+ }
957
+ getStatus() {
958
+ return {
959
+ ...this.watcher.getStatus(),
960
+ name: this.agentName,
961
+ };
962
+ }
963
+ }
964
+ //# sourceMappingURL=autoValidator.js.map