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.
- package/LICENSE +21 -0
- package/README.md +348 -0
- package/dist/agent/agentTools.d.ts +26 -0
- package/dist/agent/agentTools.d.ts.map +1 -0
- package/dist/agent/agentTools.js +699 -0
- package/dist/agent/agentTools.js.map +1 -0
- package/dist/agent/autoValidator.d.ts +110 -0
- package/dist/agent/autoValidator.d.ts.map +1 -0
- package/dist/agent/autoValidator.js +964 -0
- package/dist/agent/autoValidator.js.map +1 -0
- package/dist/agent/fileWatcher.d.ts +28 -0
- package/dist/agent/fileWatcher.d.ts.map +1 -0
- package/dist/agent/fileWatcher.js +88 -0
- package/dist/agent/fileWatcher.js.map +1 -0
- package/dist/agent/guardianPersistence.d.ts +98 -0
- package/dist/agent/guardianPersistence.d.ts.map +1 -0
- package/dist/agent/guardianPersistence.js +296 -0
- package/dist/agent/guardianPersistence.js.map +1 -0
- package/dist/agent/mcpNotifications.d.ts +38 -0
- package/dist/agent/mcpNotifications.d.ts.map +1 -0
- package/dist/agent/mcpNotifications.js +81 -0
- package/dist/agent/mcpNotifications.js.map +1 -0
- package/dist/analyzers/aiPatterns.d.ts +16 -0
- package/dist/analyzers/aiPatterns.d.ts.map +1 -0
- package/dist/analyzers/aiPatterns.js +103 -0
- package/dist/analyzers/aiPatterns.js.map +1 -0
- package/dist/analyzers/antiPatterns.d.ts +60 -0
- package/dist/analyzers/antiPatterns.d.ts.map +1 -0
- package/dist/analyzers/antiPatterns.js +198 -0
- package/dist/analyzers/antiPatterns.js.map +1 -0
- package/dist/analyzers/builtinTypes.d.ts +18 -0
- package/dist/analyzers/builtinTypes.d.ts.map +1 -0
- package/dist/analyzers/builtinTypes.js +1275 -0
- package/dist/analyzers/builtinTypes.js.map +1 -0
- package/dist/analyzers/complexity.d.ts +14 -0
- package/dist/analyzers/complexity.d.ts.map +1 -0
- package/dist/analyzers/complexity.js +610 -0
- package/dist/analyzers/complexity.js.map +1 -0
- package/dist/analyzers/findingVerifier.d.ts +59 -0
- package/dist/analyzers/findingVerifier.d.ts.map +1 -0
- package/dist/analyzers/findingVerifier.js +1169 -0
- package/dist/analyzers/findingVerifier.js.map +1 -0
- package/dist/analyzers/impactAnalyzer.d.ts +53 -0
- package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/impactAnalyzer.js +152 -0
- package/dist/analyzers/impactAnalyzer.js.map +1 -0
- package/dist/analyzers/languageDetector.d.ts +48 -0
- package/dist/analyzers/languageDetector.d.ts.map +1 -0
- package/dist/analyzers/languageDetector.js +404 -0
- package/dist/analyzers/languageDetector.js.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.js +193 -0
- package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.js +324 -0
- package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.js +429 -0
- package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.js +709 -0
- package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
- package/dist/analyzers/relevanceScorer.d.ts +43 -0
- package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
- package/dist/analyzers/relevanceScorer.js +200 -0
- package/dist/analyzers/relevanceScorer.js.map +1 -0
- package/dist/analyzers/standardLibrary.d.ts +22 -0
- package/dist/analyzers/standardLibrary.d.ts.map +1 -0
- package/dist/analyzers/standardLibrary.js +211 -0
- package/dist/analyzers/standardLibrary.js.map +1 -0
- package/dist/analyzers/symbolGraph.d.ts +30 -0
- package/dist/analyzers/symbolGraph.d.ts.map +1 -0
- package/dist/analyzers/symbolGraph.js +380 -0
- package/dist/analyzers/symbolGraph.js.map +1 -0
- package/dist/analyzers/symbolTable.d.ts +18 -0
- package/dist/analyzers/symbolTable.d.ts.map +1 -0
- package/dist/analyzers/symbolTable.js +176 -0
- package/dist/analyzers/symbolTable.js.map +1 -0
- package/dist/analyzers/typeChecker.d.ts +13 -0
- package/dist/analyzers/typeChecker.d.ts.map +1 -0
- package/dist/analyzers/typeChecker.js +580 -0
- package/dist/analyzers/typeChecker.js.map +1 -0
- package/dist/analyzers/usagePatterns.d.ts +42 -0
- package/dist/analyzers/usagePatterns.d.ts.map +1 -0
- package/dist/analyzers/usagePatterns.js +75 -0
- package/dist/analyzers/usagePatterns.js.map +1 -0
- package/dist/api-contract/context/backend.d.ts +19 -0
- package/dist/api-contract/context/backend.d.ts.map +1 -0
- package/dist/api-contract/context/backend.js +64 -0
- package/dist/api-contract/context/backend.js.map +1 -0
- package/dist/api-contract/context/contract.d.ts +34 -0
- package/dist/api-contract/context/contract.d.ts.map +1 -0
- package/dist/api-contract/context/contract.js +306 -0
- package/dist/api-contract/context/contract.js.map +1 -0
- package/dist/api-contract/context/frontend.d.ts +19 -0
- package/dist/api-contract/context/frontend.d.ts.map +1 -0
- package/dist/api-contract/context/frontend.js +64 -0
- package/dist/api-contract/context/frontend.js.map +1 -0
- package/dist/api-contract/detector.d.ts +28 -0
- package/dist/api-contract/detector.d.ts.map +1 -0
- package/dist/api-contract/detector.js +393 -0
- package/dist/api-contract/detector.js.map +1 -0
- package/dist/api-contract/extractors/python.d.ts +32 -0
- package/dist/api-contract/extractors/python.d.ts.map +1 -0
- package/dist/api-contract/extractors/python.js +521 -0
- package/dist/api-contract/extractors/python.js.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
- package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.js +173 -0
- package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/typescript.d.ts +32 -0
- package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
- package/dist/api-contract/extractors/typescript.js +666 -0
- package/dist/api-contract/extractors/typescript.js.map +1 -0
- package/dist/api-contract/index.d.ts +104 -0
- package/dist/api-contract/index.d.ts.map +1 -0
- package/dist/api-contract/index.js +232 -0
- package/dist/api-contract/index.js.map +1 -0
- package/dist/api-contract/types.d.ts +151 -0
- package/dist/api-contract/types.d.ts.map +1 -0
- package/dist/api-contract/types.js +19 -0
- package/dist/api-contract/types.js.map +1 -0
- package/dist/api-contract/validators/endpoint.d.ts +21 -0
- package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
- package/dist/api-contract/validators/endpoint.js +224 -0
- package/dist/api-contract/validators/endpoint.js.map +1 -0
- package/dist/api-contract/validators/index.d.ts +40 -0
- package/dist/api-contract/validators/index.d.ts.map +1 -0
- package/dist/api-contract/validators/index.js +875 -0
- package/dist/api-contract/validators/index.js.map +1 -0
- package/dist/api-contract/validators/parameter.d.ts +17 -0
- package/dist/api-contract/validators/parameter.d.ts.map +1 -0
- package/dist/api-contract/validators/parameter.js +250 -0
- package/dist/api-contract/validators/parameter.js.map +1 -0
- package/dist/api-contract/validators/type.d.ts +38 -0
- package/dist/api-contract/validators/type.d.ts.map +1 -0
- package/dist/api-contract/validators/type.js +244 -0
- package/dist/api-contract/validators/type.js.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.js +665 -0
- package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
- package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
- package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
- package/dist/context/apiContract/graphqlSupport.js +671 -0
- package/dist/context/apiContract/graphqlSupport.js.map +1 -0
- package/dist/context/apiContract/index.d.ts +14 -0
- package/dist/context/apiContract/index.d.ts.map +1 -0
- package/dist/context/apiContract/index.js +17 -0
- package/dist/context/apiContract/index.js.map +1 -0
- package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
- package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
- package/dist/context/apiContract/webSocketSupport.js +465 -0
- package/dist/context/apiContract/webSocketSupport.js.map +1 -0
- package/dist/context/apiContractContext.d.ts +15 -0
- package/dist/context/apiContractContext.d.ts.map +1 -0
- package/dist/context/apiContractContext.js +979 -0
- package/dist/context/apiContractContext.js.map +1 -0
- package/dist/context/apiContractExtraction.d.ts +52 -0
- package/dist/context/apiContractExtraction.d.ts.map +1 -0
- package/dist/context/apiContractExtraction.js +438 -0
- package/dist/context/apiContractExtraction.js.map +1 -0
- package/dist/context/contextLineage.d.ts +79 -0
- package/dist/context/contextLineage.d.ts.map +1 -0
- package/dist/context/contextLineage.js +259 -0
- package/dist/context/contextLineage.js.map +1 -0
- package/dist/context/contextOrchestrator.d.ts +57 -0
- package/dist/context/contextOrchestrator.d.ts.map +1 -0
- package/dist/context/contextOrchestrator.js +162 -0
- package/dist/context/contextOrchestrator.js.map +1 -0
- package/dist/context/intentTracker.d.ts +73 -0
- package/dist/context/intentTracker.d.ts.map +1 -0
- package/dist/context/intentTracker.js +168 -0
- package/dist/context/intentTracker.js.map +1 -0
- package/dist/context/projectContext.d.ts +219 -0
- package/dist/context/projectContext.d.ts.map +1 -0
- package/dist/context/projectContext.js +1984 -0
- package/dist/context/projectContext.js.map +1 -0
- package/dist/prompts/index.d.ts +17 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +260 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/library.d.ts +51 -0
- package/dist/prompts/library.d.ts.map +1 -0
- package/dist/prompts/library.js +65 -0
- package/dist/prompts/library.js.map +1 -0
- package/dist/prompts/templates.d.ts +44 -0
- package/dist/prompts/templates.d.ts.map +1 -0
- package/dist/prompts/templates.js +97 -0
- package/dist/prompts/templates.js.map +1 -0
- package/dist/queue/jobPersistence.d.ts +46 -0
- package/dist/queue/jobPersistence.d.ts.map +1 -0
- package/dist/queue/jobPersistence.js +158 -0
- package/dist/queue/jobPersistence.js.map +1 -0
- package/dist/queue/jobQueue.d.ts +116 -0
- package/dist/queue/jobQueue.d.ts.map +1 -0
- package/dist/queue/jobQueue.js +275 -0
- package/dist/queue/jobQueue.js.map +1 -0
- package/dist/queue/validationJob.d.ts +69 -0
- package/dist/queue/validationJob.d.ts.map +1 -0
- package/dist/queue/validationJob.js +435 -0
- package/dist/queue/validationJob.js.map +1 -0
- package/dist/resources/index.d.ts +15 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +328 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/validationReportStore.d.ts +170 -0
- package/dist/resources/validationReportStore.d.ts.map +1 -0
- package/dist/resources/validationReportStore.js +515 -0
- package/dist/resources/validationReportStore.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +102 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/asyncValidation.d.ts +19 -0
- package/dist/tools/asyncValidation.d.ts.map +1 -0
- package/dist/tools/asyncValidation.js +346 -0
- package/dist/tools/asyncValidation.js.map +1 -0
- package/dist/tools/buildContext.d.ts +17 -0
- package/dist/tools/buildContext.d.ts.map +1 -0
- package/dist/tools/buildContext.js +188 -0
- package/dist/tools/buildContext.js.map +1 -0
- package/dist/tools/getDependencyGraph.d.ts +16 -0
- package/dist/tools/getDependencyGraph.d.ts.map +1 -0
- package/dist/tools/getDependencyGraph.js +436 -0
- package/dist/tools/getDependencyGraph.js.map +1 -0
- package/dist/tools/incrementalValidation.d.ts +71 -0
- package/dist/tools/incrementalValidation.d.ts.map +1 -0
- package/dist/tools/incrementalValidation.js +203 -0
- package/dist/tools/incrementalValidation.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +106 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/validateCode.d.ts +17 -0
- package/dist/tools/validateCode.d.ts.map +1 -0
- package/dist/tools/validateCode.js +368 -0
- package/dist/tools/validateCode.js.map +1 -0
- package/dist/tools/validateCodeLite.d.ts +2 -0
- package/dist/tools/validateCodeLite.d.ts.map +1 -0
- package/dist/tools/validateCodeLite.js +2 -0
- package/dist/tools/validateCodeLite.js.map +1 -0
- package/dist/tools/validation/builtins.d.ts +92 -0
- package/dist/tools/validation/builtins.d.ts.map +1 -0
- package/dist/tools/validation/builtins.js +2184 -0
- package/dist/tools/validation/builtins.js.map +1 -0
- package/dist/tools/validation/contextualNaming.d.ts +99 -0
- package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
- package/dist/tools/validation/contextualNaming.js +959 -0
- package/dist/tools/validation/contextualNaming.js.map +1 -0
- package/dist/tools/validation/deadCode.d.ts +115 -0
- package/dist/tools/validation/deadCode.d.ts.map +1 -0
- package/dist/tools/validation/deadCode.js +861 -0
- package/dist/tools/validation/deadCode.js.map +1 -0
- package/dist/tools/validation/extractors/index.d.ts +131 -0
- package/dist/tools/validation/extractors/index.d.ts.map +1 -0
- package/dist/tools/validation/extractors/index.js +233 -0
- package/dist/tools/validation/extractors/index.js.map +1 -0
- package/dist/tools/validation/extractors/javascript.d.ts +73 -0
- package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
- package/dist/tools/validation/extractors/javascript.js +1841 -0
- package/dist/tools/validation/extractors/javascript.js.map +1 -0
- package/dist/tools/validation/extractors/python.d.ts +93 -0
- package/dist/tools/validation/extractors/python.d.ts.map +1 -0
- package/dist/tools/validation/extractors/python.js +799 -0
- package/dist/tools/validation/extractors/python.js.map +1 -0
- package/dist/tools/validation/manifest.d.ts +45 -0
- package/dist/tools/validation/manifest.d.ts.map +1 -0
- package/dist/tools/validation/manifest.js +719 -0
- package/dist/tools/validation/manifest.js.map +1 -0
- package/dist/tools/validation/parser.d.ts +58 -0
- package/dist/tools/validation/parser.d.ts.map +1 -0
- package/dist/tools/validation/parser.js +232 -0
- package/dist/tools/validation/parser.js.map +1 -0
- package/dist/tools/validation/registry.d.ts +15 -0
- package/dist/tools/validation/registry.d.ts.map +1 -0
- package/dist/tools/validation/registry.js +169 -0
- package/dist/tools/validation/registry.js.map +1 -0
- package/dist/tools/validation/scoring.d.ts +54 -0
- package/dist/tools/validation/scoring.d.ts.map +1 -0
- package/dist/tools/validation/scoring.js +242 -0
- package/dist/tools/validation/scoring.js.map +1 -0
- package/dist/tools/validation/types.d.ts +120 -0
- package/dist/tools/validation/types.d.ts.map +1 -0
- package/dist/tools/validation/types.js +11 -0
- package/dist/tools/validation/types.js.map +1 -0
- package/dist/tools/validation/unusedLocals.d.ts +36 -0
- package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
- package/dist/tools/validation/unusedLocals.js +333 -0
- package/dist/tools/validation/unusedLocals.js.map +1 -0
- package/dist/tools/validation/validation.d.ts +98 -0
- package/dist/tools/validation/validation.d.ts.map +1 -0
- package/dist/tools/validation/validation.js +1837 -0
- package/dist/tools/validation/validation.js.map +1 -0
- package/dist/types/codeGraph.d.ts +163 -0
- package/dist/types/codeGraph.d.ts.map +1 -0
- package/dist/types/codeGraph.js +9 -0
- package/dist/types/codeGraph.js.map +1 -0
- package/dist/types/symbolGraph.d.ts +68 -0
- package/dist/types/symbolGraph.d.ts.map +1 -0
- package/dist/types/symbolGraph.js +10 -0
- package/dist/types/symbolGraph.js.map +1 -0
- package/dist/types/tools.d.ts +43 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +7 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/utils/fileFilter.d.ts +37 -0
- package/dist/utils/fileFilter.d.ts.map +1 -0
- package/dist/utils/fileFilter.js +91 -0
- package/dist/utils/fileFilter.js.map +1 -0
- package/dist/utils/gitUtils.d.ts +28 -0
- package/dist/utils/gitUtils.d.ts.map +1 -0
- package/dist/utils/gitUtils.js +81 -0
- package/dist/utils/gitUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +15 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +38 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/serialization.d.ts +25 -0
- package/dist/utils/serialization.d.ts.map +1 -0
- package/dist/utils/serialization.js +53 -0
- package/dist/utils/serialization.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Tools - Start/Stop Agent Mode
|
|
3
|
+
*
|
|
4
|
+
* MCP tools to control the proactive agent mode.
|
|
5
|
+
*
|
|
6
|
+
* @format
|
|
7
|
+
*/
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import { AutoValidator } from "./autoValidator.js";
|
|
10
|
+
import { pushValidationAlert } from "./mcpNotifications.js";
|
|
11
|
+
import { logger } from "../utils/logger.js";
|
|
12
|
+
import { validateApiContracts, formatValidationResults, generateValidationReport, } from "../api-contract/index.js";
|
|
13
|
+
import { guardianPersistence } from "./guardianPersistence.js";
|
|
14
|
+
// Global map of active agents (key: name)
|
|
15
|
+
const activeGuardians = new Map();
|
|
16
|
+
// Map of file paths to their latest validation alert (issues or clear)
|
|
17
|
+
const fileAlerts = new Map();
|
|
18
|
+
// Debounced alert persistence — avoid writing to disk on every single alert change
|
|
19
|
+
let alertPersistTimer = null;
|
|
20
|
+
function scheduleAlertPersist() {
|
|
21
|
+
if (alertPersistTimer)
|
|
22
|
+
clearTimeout(alertPersistTimer);
|
|
23
|
+
alertPersistTimer = setTimeout(() => {
|
|
24
|
+
guardianPersistence.saveAlerts(fileAlerts).catch((err) => {
|
|
25
|
+
logger.warn("Failed to persist alerts:", err);
|
|
26
|
+
});
|
|
27
|
+
alertPersistTimer = null;
|
|
28
|
+
}, 2000);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Callback to handle validation alerts
|
|
32
|
+
* Stores them for retrieval via get_guardian_alerts tool
|
|
33
|
+
*
|
|
34
|
+
* Note: MCP servers cannot directly push data to LLMs. The LLM must
|
|
35
|
+
* call get_guardian_alerts to retrieve pending alerts. We also send
|
|
36
|
+
* a UI notification to hint the user/LLM that alerts are available.
|
|
37
|
+
*/
|
|
38
|
+
function handleAlert(alert) {
|
|
39
|
+
// Update state for this file
|
|
40
|
+
if (alert.issues.length === 0 && !alert.isInitialScan) {
|
|
41
|
+
// Clear issues for this file
|
|
42
|
+
const hadIssues = fileAlerts.has(alert.file);
|
|
43
|
+
fileAlerts.delete(alert.file);
|
|
44
|
+
// Also scrub initial scan alerts that referenced this file.
|
|
45
|
+
// Initial scans (INITIAL_FILE_SCAN, INITIAL_SCAN, API_CONTRACT_SCAN) store
|
|
46
|
+
// issues from startup. When a file is re-validated and passes clean,
|
|
47
|
+
// those stale initial issues should be removed.
|
|
48
|
+
const scrubbed = scrubInitialScanIssuesForFile(alert.file);
|
|
49
|
+
// Only log if we actually cleared something
|
|
50
|
+
if (hadIssues || scrubbed) {
|
|
51
|
+
logger.info(`Issues cleared for: ${alert.file}${scrubbed ? " (including initial scan issues)" : ""}`);
|
|
52
|
+
scheduleAlertPersist();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// If this is an API_CONTRACT_SCAN update, replace the old one entirely.
|
|
57
|
+
// This handles re-validation of service/route files updating the contract scan.
|
|
58
|
+
if (alert.file === "API_CONTRACT_SCAN") {
|
|
59
|
+
fileAlerts.set(alert.file, alert);
|
|
60
|
+
logger.info(`API Contract scan updated: ${alert.issues.length} issues`);
|
|
61
|
+
scheduleAlertPersist();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Store new issues for LLM to retrieve via get_guardian_alerts
|
|
65
|
+
fileAlerts.set(alert.file, alert);
|
|
66
|
+
logger.info(`Alert stored for: ${alert.file} (${alert.issues.length} issues) - use get_guardian_alerts to retrieve`);
|
|
67
|
+
scheduleAlertPersist();
|
|
68
|
+
// Send UI notification as a hint (shows in client UI, not to LLM directly)
|
|
69
|
+
// The LLM still needs to call get_guardian_alerts to get the actual data
|
|
70
|
+
pushValidationAlert(alert).catch((err) => {
|
|
71
|
+
logger.warn("Failed to send UI notification:", err);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* When a file passes clean re-validation, scrub any matching issues from
|
|
77
|
+
* the initial scan alerts (INITIAL_FILE_SCAN, INITIAL_SCAN, API_CONTRACT_SCAN).
|
|
78
|
+
* These are snapshot alerts from startup that may reference files which have
|
|
79
|
+
* since been fixed.
|
|
80
|
+
*
|
|
81
|
+
* @returns true if any initial scan issues were removed
|
|
82
|
+
*/
|
|
83
|
+
function scrubInitialScanIssuesForFile(cleanFile) {
|
|
84
|
+
let scrubbed = false;
|
|
85
|
+
const INITIAL_SCAN_KEYS = ["INITIAL_FILE_SCAN", "INITIAL_SCAN", "API_CONTRACT_SCAN"];
|
|
86
|
+
for (const key of INITIAL_SCAN_KEYS) {
|
|
87
|
+
const scanAlert = fileAlerts.get(key);
|
|
88
|
+
if (!scanAlert)
|
|
89
|
+
continue;
|
|
90
|
+
const originalCount = scanAlert.issues.length;
|
|
91
|
+
// Remove issues that reference the now-clean file.
|
|
92
|
+
// Issues may have a `file` field (from the per-file scan) or the message
|
|
93
|
+
// may contain the file path. Check both.
|
|
94
|
+
// Normalize paths for comparison (strip leading ../ segments and path separators)
|
|
95
|
+
const normalizedClean = cleanFile.replace(/^(\.\.\/)+/, "").replace(/\\/g, "/");
|
|
96
|
+
scanAlert.issues = scanAlert.issues.filter((issue) => {
|
|
97
|
+
if (issue.file) {
|
|
98
|
+
const normalizedIssueFile = issue.file.replace(/^(\.\.\/)+/, "").replace(/\\/g, "/");
|
|
99
|
+
// Match by normalized paths (handles ../../ frontend/src/... vs frontend/src/...)
|
|
100
|
+
if (normalizedIssueFile === normalizedClean ||
|
|
101
|
+
normalizedIssueFile.endsWith(normalizedClean) ||
|
|
102
|
+
normalizedClean.endsWith(normalizedIssueFile)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Check if the message references this file
|
|
107
|
+
if (issue.message && issue.message.includes(normalizedClean)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
});
|
|
112
|
+
if (scanAlert.issues.length < originalCount) {
|
|
113
|
+
scrubbed = true;
|
|
114
|
+
logger.debug(`Scrubbed ${originalCount - scanAlert.issues.length} stale issues from ${key} for file: ${cleanFile}`);
|
|
115
|
+
// If no issues remain, remove the entire alert
|
|
116
|
+
if (scanAlert.issues.length === 0) {
|
|
117
|
+
fileAlerts.delete(key);
|
|
118
|
+
logger.debug(`Removed empty ${key} alert`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return scrubbed;
|
|
123
|
+
}
|
|
124
|
+
export const startGuardianTool = {
|
|
125
|
+
definition: {
|
|
126
|
+
name: "start_guardian",
|
|
127
|
+
description: `Activate a VibeGuard Agent.
|
|
128
|
+
You can start multiple Guardians to watch different parts of your codebase (e.g., one for 'Frontend', one for 'Backend').
|
|
129
|
+
Each Guardian watches its own path and language.`,
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
projectPath: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description: "Absolute path to the project root",
|
|
136
|
+
},
|
|
137
|
+
language: {
|
|
138
|
+
type: "string",
|
|
139
|
+
enum: ["javascript", "typescript", "python", "go"],
|
|
140
|
+
description: "Programming language (default: typescript)",
|
|
141
|
+
default: "typescript",
|
|
142
|
+
},
|
|
143
|
+
mode: {
|
|
144
|
+
type: "string",
|
|
145
|
+
enum: ["auto", "learning", "strict"],
|
|
146
|
+
description: "Operation mode (default: auto)",
|
|
147
|
+
default: "auto",
|
|
148
|
+
},
|
|
149
|
+
agent_name: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "Name for your Guardian (default: 'VibeGuard')",
|
|
152
|
+
default: "VibeGuard",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
required: ["projectPath"],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
async handler(args) {
|
|
159
|
+
const { projectPath, language = "typescript", mode = "auto", agent_name = "VibeGuard" } = args;
|
|
160
|
+
// Resolve relative paths (user-friendly)
|
|
161
|
+
const absolutePath = path.resolve(projectPath);
|
|
162
|
+
if (activeGuardians.has(agent_name)) {
|
|
163
|
+
const existingStatus = activeGuardians.get(agent_name)?.getStatus();
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: JSON.stringify({
|
|
169
|
+
success: true,
|
|
170
|
+
alreadyRunning: true,
|
|
171
|
+
message: `Guardian '${agent_name}' is already active (auto-restored from previous session). No action needed — it's watching your code.`,
|
|
172
|
+
status: existingStatus,
|
|
173
|
+
pendingAlerts: fileAlerts.size,
|
|
174
|
+
hint: fileAlerts.size > 0
|
|
175
|
+
? "There are pending alerts from the previous session. Use get_guardian_alerts to review them."
|
|
176
|
+
: "No pending alerts. The guardian is watching for changes.",
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const guardian = new AutoValidator(absolutePath, language, mode, agent_name);
|
|
184
|
+
guardian.setAlertHandler(handleAlert);
|
|
185
|
+
// Start in background to avoid MCP timeout
|
|
186
|
+
// processing context/initial scan can take time
|
|
187
|
+
guardian.start().catch(err => {
|
|
188
|
+
logger.error(`Failed to start guardian ${agent_name}:`, err);
|
|
189
|
+
});
|
|
190
|
+
activeGuardians.set(agent_name, guardian);
|
|
191
|
+
// Persist config so guardian survives server restarts (new LLM sessions)
|
|
192
|
+
guardianPersistence.saveGuardian({
|
|
193
|
+
agentName: agent_name,
|
|
194
|
+
projectPath: absolutePath,
|
|
195
|
+
language,
|
|
196
|
+
mode,
|
|
197
|
+
startedAt: Date.now(),
|
|
198
|
+
}).catch(err => {
|
|
199
|
+
logger.warn("Failed to persist guardian config:", err);
|
|
200
|
+
});
|
|
201
|
+
const alertsFilePath = guardianPersistence.getLLMAlertsPath(absolutePath);
|
|
202
|
+
return {
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: JSON.stringify({
|
|
207
|
+
success: true,
|
|
208
|
+
message: `Started ${agent_name} at ${absolutePath} (${language}). Initialization running in background.`,
|
|
209
|
+
alertsFile: alertsFilePath,
|
|
210
|
+
status: {
|
|
211
|
+
...guardian.getStatus(),
|
|
212
|
+
state: "initializing"
|
|
213
|
+
},
|
|
214
|
+
hint: `Alerts will be saved to ${alertsFilePath} (readable by file tools, NOT inside .codeguardian/). Use get_guardian_alerts or read the file directly to check for issues.`,
|
|
215
|
+
}),
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
logger.error("Failed to start guardian:", error);
|
|
222
|
+
return {
|
|
223
|
+
content: [
|
|
224
|
+
{
|
|
225
|
+
type: "text",
|
|
226
|
+
text: `Error activating Guardian: ${error instanceof Error ? error.message : String(error)}`,
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
isError: true,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
export const stopGuardianTool = {
|
|
235
|
+
definition: {
|
|
236
|
+
name: "stop_guardian",
|
|
237
|
+
description: "Stop a specific Guardian Agent or all active Guardians.",
|
|
238
|
+
inputSchema: {
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: {
|
|
241
|
+
agent_name: {
|
|
242
|
+
type: "string",
|
|
243
|
+
description: "Name of the Guardian to stop. Leave empty to stop ALL.",
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
async handler(args) {
|
|
249
|
+
const { agent_name } = args;
|
|
250
|
+
if (activeGuardians.size === 0) {
|
|
251
|
+
return {
|
|
252
|
+
content: [
|
|
253
|
+
{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: JSON.stringify({
|
|
256
|
+
success: false,
|
|
257
|
+
message: "No active Guardians.",
|
|
258
|
+
}),
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (agent_name) {
|
|
264
|
+
const guardian = activeGuardians.get(agent_name);
|
|
265
|
+
if (!guardian) {
|
|
266
|
+
return {
|
|
267
|
+
content: [
|
|
268
|
+
{
|
|
269
|
+
type: "text",
|
|
270
|
+
text: JSON.stringify({
|
|
271
|
+
success: false,
|
|
272
|
+
message: `Guardian '${agent_name}' not found.`,
|
|
273
|
+
activeGuardians: Array.from(activeGuardians.keys()),
|
|
274
|
+
}),
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
guardian.stop();
|
|
280
|
+
activeGuardians.delete(agent_name);
|
|
281
|
+
// Remove persisted config so it won't auto-restore
|
|
282
|
+
guardianPersistence.removeGuardianFull(agent_name, guardian.getStatus().projectPath).catch(err => {
|
|
283
|
+
logger.warn("Failed to remove persisted guardian config:", err);
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
content: [
|
|
287
|
+
{
|
|
288
|
+
type: "text",
|
|
289
|
+
text: JSON.stringify({
|
|
290
|
+
success: true,
|
|
291
|
+
message: `${agent_name} stopped.`,
|
|
292
|
+
}),
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Stop all
|
|
299
|
+
const count = activeGuardians.size;
|
|
300
|
+
for (const [name, guardian] of activeGuardians) {
|
|
301
|
+
guardian.stop();
|
|
302
|
+
// Remove persisted config
|
|
303
|
+
guardianPersistence.removeGuardianFull(name, guardian.getStatus().projectPath).catch(() => { });
|
|
304
|
+
}
|
|
305
|
+
activeGuardians.clear();
|
|
306
|
+
fileAlerts.clear(); // Clear all tracked alerts
|
|
307
|
+
guardianPersistence.clearAlerts().catch(() => { });
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: JSON.stringify({
|
|
313
|
+
success: true,
|
|
314
|
+
message: `Stopped all ${count} Guardians.`,
|
|
315
|
+
}),
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
export const getGuardianAlertsTool = {
|
|
323
|
+
definition: {
|
|
324
|
+
name: "get_guardian_alerts",
|
|
325
|
+
description: `Get pending alerts from all active Guardians. Returns a compact summary with a pointer to the full LLM-readable alerts file (codeguardian-alerts.json) in the project root.`,
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: {
|
|
329
|
+
clearAfterRead: {
|
|
330
|
+
type: "boolean",
|
|
331
|
+
description: "Deprecated: Alerts are now persistent until issues are resolved. This flag is ignored.",
|
|
332
|
+
default: false,
|
|
333
|
+
},
|
|
334
|
+
summaryOnly: {
|
|
335
|
+
type: "boolean",
|
|
336
|
+
description: "If true, returns only a compact summary with file path to full alerts. Useful to avoid LLM context overflow. Default: false.",
|
|
337
|
+
default: false,
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
async handler(args) {
|
|
343
|
+
const { summaryOnly = false } = args;
|
|
344
|
+
// Get all active alerts
|
|
345
|
+
const alerts = Array.from(fileAlerts.values());
|
|
346
|
+
// Collect LLM-readable file paths from all active guardians
|
|
347
|
+
const alertsFilePaths = [];
|
|
348
|
+
for (const guardian of activeGuardians.values()) {
|
|
349
|
+
const status = guardian.getStatus();
|
|
350
|
+
if (status.projectPath) {
|
|
351
|
+
alertsFilePaths.push(guardianPersistence.getLLMAlertsPath(status.projectPath));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (alerts.length === 0) {
|
|
355
|
+
return {
|
|
356
|
+
content: [
|
|
357
|
+
{
|
|
358
|
+
type: "text",
|
|
359
|
+
text: JSON.stringify({
|
|
360
|
+
success: true,
|
|
361
|
+
hasAlerts: false,
|
|
362
|
+
message: "No active issues. All clear! (Alerts automatically clear when issues are fixed)",
|
|
363
|
+
alertsFiles: alertsFilePaths.length > 0 ? alertsFilePaths : undefined,
|
|
364
|
+
}),
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
// Count issues by severity
|
|
370
|
+
const severityCounts = { critical: 0, high: 0, medium: 0, low: 0, warning: 0 };
|
|
371
|
+
let totalIssues = 0;
|
|
372
|
+
for (const alert of alerts) {
|
|
373
|
+
for (const issue of alert.issues) {
|
|
374
|
+
const sev = issue.severity || "low";
|
|
375
|
+
severityCounts[sev] = (severityCounts[sev] || 0) + 1;
|
|
376
|
+
totalIssues++;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// For summaryOnly mode or large alert sets, return compact summary + file path
|
|
380
|
+
if (summaryOnly || totalIssues > 50) {
|
|
381
|
+
return {
|
|
382
|
+
content: [
|
|
383
|
+
{
|
|
384
|
+
type: "text",
|
|
385
|
+
text: JSON.stringify({
|
|
386
|
+
success: true,
|
|
387
|
+
hasAlerts: true,
|
|
388
|
+
alertCount: alerts.length,
|
|
389
|
+
totalIssues,
|
|
390
|
+
bySeverity: severityCounts,
|
|
391
|
+
affectedFiles: alerts.map(a => a.file),
|
|
392
|
+
alertsFiles: alertsFilePaths,
|
|
393
|
+
message: totalIssues > 50
|
|
394
|
+
? `Large alert set (${totalIssues} issues across ${alerts.length} files). Full details available in the alertsFiles listed above. Use read_file to access them.`
|
|
395
|
+
: `${totalIssues} issues across ${alerts.length} files. Full details available in the alertsFiles listed above.`,
|
|
396
|
+
hint: "The codeguardian-alerts.json file in the project root contains the full alert details. Use your file-reading tool to access it.",
|
|
397
|
+
}),
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
// Format alerts for LLM consumption (full inline for small result sets)
|
|
403
|
+
const llmMessages = alerts.map((a) => a.llmMessage).join("\n\n---\n\n");
|
|
404
|
+
return {
|
|
405
|
+
content: [
|
|
406
|
+
{
|
|
407
|
+
type: "text",
|
|
408
|
+
text: JSON.stringify({
|
|
409
|
+
success: true,
|
|
410
|
+
hasAlerts: true,
|
|
411
|
+
alertCount: alerts.length,
|
|
412
|
+
totalIssues,
|
|
413
|
+
bySeverity: severityCounts,
|
|
414
|
+
alerts: alerts,
|
|
415
|
+
llmSummary: llmMessages,
|
|
416
|
+
alertsFiles: alertsFilePaths,
|
|
417
|
+
hint: "Full alerts are also persisted at the codeguardian-alerts.json file(s) in the project root. Use your file-reading tool to access them across sessions.",
|
|
418
|
+
}),
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
export const getGuardianStatusTool = {
|
|
425
|
+
definition: {
|
|
426
|
+
name: "get_guardian_status",
|
|
427
|
+
description: "Get the current status of VibeGuard. Use this when the user asks for 'vibeguard status', 'what is running', or 'agent health'. Lists all active agents.",
|
|
428
|
+
inputSchema: {
|
|
429
|
+
type: "object",
|
|
430
|
+
properties: {},
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
async handler() {
|
|
434
|
+
if (activeGuardians.size === 0) {
|
|
435
|
+
// Check if there are persisted configs that might be restoring
|
|
436
|
+
const persistedConfigs = await guardianPersistence.loadAllGuardians();
|
|
437
|
+
return {
|
|
438
|
+
content: [
|
|
439
|
+
{
|
|
440
|
+
type: "text",
|
|
441
|
+
text: JSON.stringify({
|
|
442
|
+
active: false,
|
|
443
|
+
message: persistedConfigs.length > 0
|
|
444
|
+
? `No Guardians are active yet, but ${persistedConfigs.length} guardian(s) are being restored from a previous session. They should be ready shortly.`
|
|
445
|
+
: "No Guardians are active.",
|
|
446
|
+
pendingRestore: persistedConfigs.length,
|
|
447
|
+
}),
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
const statuses = Array.from(activeGuardians.values()).map(g => g.getStatus());
|
|
453
|
+
return {
|
|
454
|
+
content: [
|
|
455
|
+
{
|
|
456
|
+
type: "text",
|
|
457
|
+
text: JSON.stringify({
|
|
458
|
+
active: true,
|
|
459
|
+
count: activeGuardians.size,
|
|
460
|
+
guardians: statuses,
|
|
461
|
+
pendingAlerts: fileAlerts.size,
|
|
462
|
+
hint: fileAlerts.size > 0
|
|
463
|
+
? "There are pending alerts. Use get_guardian_alerts to review them."
|
|
464
|
+
: "No pending alerts. All clear.",
|
|
465
|
+
}),
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
};
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
// ============================================================================
|
|
472
|
+
// API Contract Validation Tools
|
|
473
|
+
// ============================================================================
|
|
474
|
+
export const validateApiContractsTool = {
|
|
475
|
+
definition: {
|
|
476
|
+
name: "validate_api_contracts",
|
|
477
|
+
description: `Validate API contracts between frontend and backend. Detects mismatches in endpoints, types, and parameters before runtime errors occur.`,
|
|
478
|
+
inputSchema: {
|
|
479
|
+
type: "object",
|
|
480
|
+
properties: {
|
|
481
|
+
projectPath: {
|
|
482
|
+
type: "string",
|
|
483
|
+
description: "Absolute path to the project root (should contain both frontend and backend)",
|
|
484
|
+
},
|
|
485
|
+
includeEndpoints: {
|
|
486
|
+
type: "boolean",
|
|
487
|
+
description: "Validate endpoint existence and HTTP methods",
|
|
488
|
+
default: true,
|
|
489
|
+
},
|
|
490
|
+
includeParameters: {
|
|
491
|
+
type: "boolean",
|
|
492
|
+
description: "Validate request/response parameters",
|
|
493
|
+
default: true,
|
|
494
|
+
},
|
|
495
|
+
includeTypes: {
|
|
496
|
+
type: "boolean",
|
|
497
|
+
description: "Validate type compatibility",
|
|
498
|
+
default: true,
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
required: ["projectPath"],
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
async handler(args) {
|
|
505
|
+
const { projectPath, includeEndpoints = true, includeParameters = true, includeTypes = true, } = args;
|
|
506
|
+
const absolutePath = path.resolve(projectPath);
|
|
507
|
+
try {
|
|
508
|
+
logger.info(`Running API Contract validation for ${absolutePath}`);
|
|
509
|
+
const result = await validateApiContracts(absolutePath);
|
|
510
|
+
const formatted = formatValidationResults(result);
|
|
511
|
+
return {
|
|
512
|
+
content: [
|
|
513
|
+
{
|
|
514
|
+
type: "text",
|
|
515
|
+
text: formatted,
|
|
516
|
+
},
|
|
517
|
+
],
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
logger.error("API Contract validation failed:", error);
|
|
522
|
+
return {
|
|
523
|
+
content: [
|
|
524
|
+
{
|
|
525
|
+
type: "text",
|
|
526
|
+
text: `Error validating API contracts: ${error instanceof Error ? error.message : String(error)}`,
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
isError: true,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
export const getApiContractReportTool = {
|
|
535
|
+
definition: {
|
|
536
|
+
name: "get_api_contract_report",
|
|
537
|
+
description: `Generate a detailed API Contract validation report with recommendations.`,
|
|
538
|
+
inputSchema: {
|
|
539
|
+
type: "object",
|
|
540
|
+
properties: {
|
|
541
|
+
projectPath: {
|
|
542
|
+
type: "string",
|
|
543
|
+
description: "Absolute path to the project root",
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
required: ["projectPath"],
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
async handler(args) {
|
|
550
|
+
const { projectPath } = args;
|
|
551
|
+
const absolutePath = path.resolve(projectPath);
|
|
552
|
+
try {
|
|
553
|
+
const report = await generateValidationReport(absolutePath);
|
|
554
|
+
const lines = [];
|
|
555
|
+
lines.push("# API Contract Validation Report");
|
|
556
|
+
lines.push("");
|
|
557
|
+
lines.push(`**Generated:** ${report.timestamp}`);
|
|
558
|
+
lines.push(`**Project:** ${report.projectPath}`);
|
|
559
|
+
lines.push("");
|
|
560
|
+
lines.push("## Summary");
|
|
561
|
+
lines.push(`- **Total Issues:** ${report.summary.totalIssues}`);
|
|
562
|
+
lines.push(` - Critical: ${report.summary.critical} 🔴`);
|
|
563
|
+
lines.push(` - High: ${report.summary.high} 🟠`);
|
|
564
|
+
lines.push(`- **Matched Endpoints:** ${report.summary.matchedEndpoints}`);
|
|
565
|
+
lines.push(`- **Matched Types:** ${report.summary.matchedTypes}`);
|
|
566
|
+
lines.push("");
|
|
567
|
+
if (report.recommendations.length > 0) {
|
|
568
|
+
lines.push("## Recommendations");
|
|
569
|
+
report.recommendations.forEach((rec, i) => {
|
|
570
|
+
lines.push(`${i + 1}. ${rec}`);
|
|
571
|
+
});
|
|
572
|
+
lines.push("");
|
|
573
|
+
}
|
|
574
|
+
if (report.issues.length > 0) {
|
|
575
|
+
lines.push("## Issues");
|
|
576
|
+
lines.push("");
|
|
577
|
+
const byType = report.issues.reduce((acc, issue) => {
|
|
578
|
+
if (!acc[issue.type])
|
|
579
|
+
acc[issue.type] = [];
|
|
580
|
+
acc[issue.type].push(issue);
|
|
581
|
+
return acc;
|
|
582
|
+
}, {});
|
|
583
|
+
for (const [type, issues] of Object.entries(byType)) {
|
|
584
|
+
lines.push(`### ${type}`);
|
|
585
|
+
issues.slice(0, 5).forEach((issue) => {
|
|
586
|
+
lines.push(`- **[${issue.severity.toUpperCase()}]** ${issue.message}`);
|
|
587
|
+
lines.push(` - File: ${issue.file}:${issue.line}`);
|
|
588
|
+
lines.push(` - Suggestion: ${issue.suggestion}`);
|
|
589
|
+
});
|
|
590
|
+
if (issues.length > 5) {
|
|
591
|
+
lines.push(`- *... and ${issues.length - 5} more issues of this type*`);
|
|
592
|
+
}
|
|
593
|
+
lines.push("");
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
content: [
|
|
598
|
+
{
|
|
599
|
+
type: "text",
|
|
600
|
+
text: lines.join("\n"),
|
|
601
|
+
},
|
|
602
|
+
],
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
logger.error("Failed to generate API Contract report:", error);
|
|
607
|
+
return {
|
|
608
|
+
content: [
|
|
609
|
+
{
|
|
610
|
+
type: "text",
|
|
611
|
+
text: `Error generating report: ${error instanceof Error ? error.message : String(error)}`,
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
isError: true,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
};
|
|
619
|
+
// ============================================================================
|
|
620
|
+
// Guardian Graceful Shutdown (preserves persisted configs for auto-restore)
|
|
621
|
+
// ============================================================================
|
|
622
|
+
/**
|
|
623
|
+
* Stop all in-memory guardian watchers WITHOUT removing persisted configs.
|
|
624
|
+
* Called during server shutdown so guardians auto-restore on next startup.
|
|
625
|
+
*/
|
|
626
|
+
export async function shutdownGuardiansGracefully() {
|
|
627
|
+
for (const [name, guardian] of activeGuardians) {
|
|
628
|
+
logger.info(`Gracefully stopping guardian '${name}'...`);
|
|
629
|
+
guardian.stop();
|
|
630
|
+
}
|
|
631
|
+
activeGuardians.clear();
|
|
632
|
+
// Do NOT clear fileAlerts — they're already persisted to disk
|
|
633
|
+
// Do NOT call guardianPersistence.removeGuardian — we want auto-restore
|
|
634
|
+
}
|
|
635
|
+
// ============================================================================
|
|
636
|
+
// Guardian Auto-Restore (survives server restarts / new LLM sessions)
|
|
637
|
+
// ============================================================================
|
|
638
|
+
/**
|
|
639
|
+
* Restore guardians from persisted configs.
|
|
640
|
+
* Called once during server startup to resume any guardians that were
|
|
641
|
+
* running before the server was restarted (e.g., new LLM session).
|
|
642
|
+
*/
|
|
643
|
+
export async function restoreGuardians() {
|
|
644
|
+
try {
|
|
645
|
+
const configs = await guardianPersistence.loadAllGuardians();
|
|
646
|
+
if (configs.length === 0) {
|
|
647
|
+
logger.info("No persisted guardians to restore");
|
|
648
|
+
return 0;
|
|
649
|
+
}
|
|
650
|
+
// Load persisted alerts first so they're available immediately
|
|
651
|
+
const persistedAlerts = await guardianPersistence.loadAlerts();
|
|
652
|
+
for (const [file, alert] of persistedAlerts) {
|
|
653
|
+
fileAlerts.set(file, alert);
|
|
654
|
+
}
|
|
655
|
+
if (persistedAlerts.size > 0) {
|
|
656
|
+
logger.info(`Restored ${persistedAlerts.size} persisted alerts`);
|
|
657
|
+
}
|
|
658
|
+
let restored = 0;
|
|
659
|
+
for (const config of configs) {
|
|
660
|
+
// Skip if already running (shouldn't happen, but be safe)
|
|
661
|
+
if (activeGuardians.has(config.agentName)) {
|
|
662
|
+
logger.info(`Guardian '${config.agentName}' already active, skipping restore`);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
// Validate the project path still exists
|
|
666
|
+
const valid = await guardianPersistence.isProjectValid(config);
|
|
667
|
+
if (!valid) {
|
|
668
|
+
logger.warn(`Project path no longer exists for guardian '${config.agentName}': ${config.projectPath} — removing persisted config`);
|
|
669
|
+
await guardianPersistence.removeGuardian(config.agentName);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
logger.info(`Restoring guardian '${config.agentName}' for ${config.projectPath} (${config.language})...`);
|
|
674
|
+
const guardian = new AutoValidator(config.projectPath, config.language, config.mode, config.agentName);
|
|
675
|
+
guardian.setAlertHandler(handleAlert);
|
|
676
|
+
// Start in background (same as normal start)
|
|
677
|
+
guardian.start().catch((err) => {
|
|
678
|
+
logger.error(`Failed to restore guardian ${config.agentName}:`, err);
|
|
679
|
+
});
|
|
680
|
+
activeGuardians.set(config.agentName, guardian);
|
|
681
|
+
restored++;
|
|
682
|
+
}
|
|
683
|
+
catch (err) {
|
|
684
|
+
logger.error(`Error restoring guardian '${config.agentName}':`, err);
|
|
685
|
+
// Remove broken config so we don't keep retrying
|
|
686
|
+
await guardianPersistence.removeGuardian(config.agentName);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (restored > 0) {
|
|
690
|
+
logger.info(`Auto-restored ${restored} guardian(s) from previous session`);
|
|
691
|
+
}
|
|
692
|
+
return restored;
|
|
693
|
+
}
|
|
694
|
+
catch (error) {
|
|
695
|
+
logger.error("Failed to restore guardians:", error);
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
//# sourceMappingURL=agentTools.js.map
|