code-auditor-mcp 2.6.1 → 3.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/README.md +319 -147
- package/dist/analyzers/analyzerUtils.d.ts +13 -11
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.js +22 -56
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/dataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/dataAccessAnalyzer.js +50 -24
- package/dist/analyzers/dataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts +4 -1
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +210 -111
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/dryAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/dryAnalyzer.js +190 -58
- package/dist/analyzers/dryAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts +20 -0
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/invariantsAnalyzer.js +148 -0
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +2 -2
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/schemaAnalyzer.d.ts +2 -0
- package/dist/analyzers/schemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/schemaAnalyzer.js +306 -147
- package/dist/analyzers/schemaAnalyzer.js.map +1 -1
- package/dist/analyzers/solidAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/solidAnalyzer.js +145 -119
- package/dist/analyzers/solidAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +3 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +41 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +8 -8
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +6 -6
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/api/index.js +1 -0
- package/dist/api/index.js.map +1 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +367 -24
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +740 -18
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB-enhanced.d.ts +35 -13
- package/dist/codeIndexDB-enhanced.d.ts.map +1 -1
- package/dist/codeIndexDB-enhanced.js +311 -214
- package/dist/codeIndexDB-enhanced.js.map +1 -1
- package/dist/codeIndexDB.d.ts +222 -0
- package/dist/codeIndexDB.d.ts.map +1 -0
- package/dist/codeIndexDB.js +2042 -0
- package/dist/codeIndexDB.js.map +1 -0
- package/dist/codeIndexService.d.ts.map +1 -1
- package/dist/codeIndexService.js.map +1 -1
- package/dist/componentScanner.d.ts +14 -8
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +136 -113
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +10 -7
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/fingerprint.d.ts +15 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +32 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/functionScanner.d.ts +5 -0
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +417 -341
- package/dist/functionScanner.js.map +1 -1
- package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
- package/dist/generators/AiderConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
- package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
- package/dist/generators/JetBrainsConfigGenerator.d.ts.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/invariants/index.d.ts +9 -0
- package/dist/invariants/index.d.ts.map +1 -0
- package/dist/invariants/index.js +4 -0
- package/dist/invariants/index.js.map +1 -0
- package/dist/invariants/invariant-rules.schema.json +137 -0
- package/dist/invariants/ruleEngine.d.ts +32 -0
- package/dist/invariants/ruleEngine.d.ts.map +1 -0
- package/dist/invariants/ruleEngine.js +536 -0
- package/dist/invariants/ruleEngine.js.map +1 -0
- package/dist/invariants/ruleValidator.d.ts +17 -0
- package/dist/invariants/ruleValidator.d.ts.map +1 -0
- package/dist/invariants/ruleValidator.js +264 -0
- package/dist/invariants/ruleValidator.js.map +1 -0
- package/dist/invariants/types.d.ts +78 -0
- package/dist/invariants/types.d.ts.map +1 -0
- package/dist/invariants/types.js +5 -0
- package/dist/invariants/types.js.map +1 -0
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/LanguageRegistry.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +1 -1
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +102 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -0
- package/dist/languages/adapterBridge.js +382 -0
- package/dist/languages/adapterBridge.js.map +1 -0
- package/dist/languages/go/GoAdapter.d.ts +37 -26
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +560 -281
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/index.d.ts +5 -2
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +11 -9
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts +43 -0
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -0
- package/dist/languages/tree-sitter/converter.js +150 -0
- package/dist/languages/tree-sitter/converter.js.map +1 -0
- package/dist/languages/tree-sitter/parser.d.ts +36 -0
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -0
- package/dist/languages/tree-sitter/parser.js +114 -0
- package/dist/languages/tree-sitter/parser.js.map +1 -0
- package/dist/languages/types.d.ts +11 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +92 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +951 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts +80 -11
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +279 -13
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +21 -21
- package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.js +130 -126
- package/dist/mcp-tools/workflowGuide.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.js +1 -1
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +1455 -1132
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts +4 -0
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +49 -2
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/mcpDiagnostics.d.ts.map +1 -1
- package/dist/mcpDiagnostics.js +8 -5
- package/dist/mcpDiagnostics.js.map +1 -1
- package/dist/mcpToolErrors.d.ts.map +1 -1
- package/dist/mcpToolErrors.js.map +1 -1
- package/dist/native/ast-grep-napi.darwin-arm64.node +0 -0
- package/dist/native/ast-grep-napi.darwin-x64.node +0 -0
- package/dist/native/ast-grep-napi.linux-arm64-gnu.node +0 -0
- package/dist/native/ast-grep-napi.linux-arm64-musl.node +0 -0
- package/dist/native/ast-grep-napi.linux-x64-gnu.node +0 -0
- package/dist/native/ast-grep-napi.linux-x64-musl.node +0 -0
- package/dist/native/ast-grep-napi.win32-x64-msvc.node +0 -0
- package/dist/native-bootstrap.d.ts +15 -0
- package/dist/native-bootstrap.d.ts.map +1 -0
- package/dist/native-bootstrap.js +75 -0
- package/dist/native-bootstrap.js.map +1 -0
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/reportGenerator.d.ts.map +1 -1
- package/dist/reporting/reportGenerator.js +4 -0
- package/dist/reporting/reportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts +39 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -0
- package/dist/reporting/sarifReportGenerator.js +229 -0
- package/dist/reporting/sarifReportGenerator.js.map +1 -0
- package/dist/search/QueryParser.d.ts +41 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +200 -19
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +4 -4
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/services/ProjectTaskRepository.d.ts +34 -9
- package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
- package/dist/services/ProjectTaskRepository.js +385 -10
- package/dist/services/ProjectTaskRepository.js.map +1 -1
- package/dist/services/SchemaParser.d.ts.map +1 -1
- package/dist/services/SchemaParser.js +5 -8
- package/dist/services/SchemaParser.js.map +1 -1
- package/dist/services/auditJobService.d.ts +1 -1
- package/dist/services/projectTaskHelpers.js.map +1 -1
- package/dist/services/whitelistService.d.ts.map +1 -1
- package/dist/services/whitelistService.js +1 -1
- package/dist/services/whitelistService.js.map +1 -1
- package/dist/tool-registry.d.ts +79 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +139 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/types/projectTask.d.ts +31 -0
- package/dist/types/projectTask.d.ts.map +1 -1
- package/dist/types.d.ts +26 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/InteractivePrompts.d.ts.map +1 -1
- package/dist/ui/InteractivePrompts.js.map +1 -1
- package/dist/utils/astParser.d.ts +31 -38
- package/dist/utils/astParser.d.ts.map +1 -1
- package/dist/utils/astParser.js +138 -196
- package/dist/utils/astParser.js.map +1 -1
- package/dist/utils/astUtils.d.ts +87 -58
- package/dist/utils/astUtils.d.ts.map +1 -1
- package/dist/utils/astUtils.js +852 -581
- package/dist/utils/astUtils.js.map +1 -1
- package/dist/utils/componentPatterns.js.map +1 -1
- package/dist/utils/componentResponsibility.d.ts +13 -9
- package/dist/utils/componentResponsibility.d.ts.map +1 -1
- package/dist/utils/componentResponsibility.js +236 -206
- package/dist/utils/componentResponsibility.js.map +1 -1
- package/dist/utils/dependencyExtractor.d.ts +11 -6
- package/dist/utils/dependencyExtractor.d.ts.map +1 -1
- package/dist/utils/dependencyExtractor.js +200 -119
- package/dist/utils/dependencyExtractor.js.map +1 -1
- package/dist/utils/fileDiscovery.d.ts +1 -1
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/dist/utils/jsDocParser.d.ts +27 -30
- package/dist/utils/jsDocParser.d.ts.map +1 -1
- package/dist/utils/jsDocParser.js +271 -207
- package/dist/utils/jsDocParser.js.map +1 -1
- package/dist/utils/reactDetection.d.ts +27 -12
- package/dist/utils/reactDetection.d.ts.map +1 -1
- package/dist/utils/reactDetection.js +331 -219
- package/dist/utils/reactDetection.js.map +1 -1
- package/dist/workers/auditWorker.js +17 -3
- package/dist/workers/auditWorker.js.map +1 -1
- package/package.json +54 -51
- package/dist/adapters/GoAdapter.d.ts +0 -53
- package/dist/adapters/GoAdapter.d.ts.map +0 -1
- package/dist/adapters/GoAdapter.js +0 -611
- package/dist/adapters/GoAdapter.js.map +0 -1
- package/dist/adapters/GoConfig.d.ts +0 -35
- package/dist/adapters/GoConfig.d.ts.map +0 -1
- package/dist/adapters/GoConfig.js +0 -155
- package/dist/adapters/GoConfig.js.map +0 -1
- package/dist/adapters/LanguageAdapter.d.ts +0 -135
- package/dist/adapters/LanguageAdapter.d.ts.map +0 -1
- package/dist/adapters/LanguageAdapter.js +0 -47
- package/dist/adapters/LanguageAdapter.js.map +0 -1
- package/dist/adapters/TypeScriptAdapter.d.ts +0 -43
- package/dist/adapters/TypeScriptAdapter.d.ts.map +0 -1
- package/dist/adapters/TypeScriptAdapter.js +0 -386
- package/dist/adapters/TypeScriptAdapter.js.map +0 -1
- package/dist/adapters/UniversalAnalyzer.d.ts +0 -42
- package/dist/adapters/UniversalAnalyzer.d.ts.map +0 -1
- package/dist/adapters/UniversalAnalyzer.js +0 -103
- package/dist/adapters/UniversalAnalyzer.js.map +0 -1
- package/dist/adapters/UniversalSOLIDAnalyzer.d.ts +0 -48
- package/dist/adapters/UniversalSOLIDAnalyzer.d.ts.map +0 -1
- package/dist/adapters/UniversalSOLIDAnalyzer.js +0 -178
- package/dist/adapters/UniversalSOLIDAnalyzer.js.map +0 -1
- package/dist/adapters/solidAnalyzerCompat.d.ts +0 -21
- package/dist/adapters/solidAnalyzerCompat.d.ts.map +0 -1
- package/dist/adapters/solidAnalyzerCompat.js +0 -78
- package/dist/adapters/solidAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/dataAccessAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/dataAccessAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/dataAccessAnalyzerCompat.js +0 -59
- package/dist/analyzers/dataAccessAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/documentationAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/documentationAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/documentationAnalyzerCompat.js +0 -52
- package/dist/analyzers/documentationAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/dryAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/dryAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/dryAnalyzerCompat.js +0 -44
- package/dist/analyzers/dryAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/schemaAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/schemaAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/schemaAnalyzerCompat.js +0 -69
- package/dist/analyzers/schemaAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/solidAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/solidAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/solidAnalyzerCompat.js +0 -78
- package/dist/analyzers/solidAnalyzerCompat.js.map +0 -1
- package/dist/codeIndexDb.d.ts +0 -394
- package/dist/codeIndexDb.d.ts.map +0 -1
- package/dist/codeIndexDb.js +0 -2077
- package/dist/codeIndexDb.js.map +0 -1
- package/dist/languages/typescript/TypeScriptAdapter.d.ts +0 -108
- package/dist/languages/typescript/TypeScriptAdapter.d.ts.map +0 -1
- package/dist/languages/typescript/TypeScriptAdapter.js +0 -567
- package/dist/languages/typescript/TypeScriptAdapter.js.map +0 -1
- package/dist/languages/typescript/test-extraction.d.ts +0 -2
- package/dist/languages/typescript/test-extraction.d.ts.map +0 -1
- package/dist/languages/typescript/test-extraction.js +0 -36
- package/dist/languages/typescript/test-extraction.js.map +0 -1
- package/dist/mcp-hybrid.d.ts +0 -10
- package/dist/mcp-hybrid.d.ts.map +0 -1
- package/dist/mcp-hybrid.js +0 -41
- package/dist/mcp-hybrid.js.map +0 -1
- package/dist/mcp-index.d.ts +0 -13
- package/dist/mcp-index.d.ts.map +0 -1
- package/dist/mcp-index.js +0 -123
- package/dist/mcp-index.js.map +0 -1
- package/dist/mcp-standalone.d.ts +0 -3
- package/dist/mcp-standalone.d.ts.map +0 -1
- package/dist/mcp-standalone.js +0 -1201
- package/dist/mcp-standalone.js.map +0 -1
- package/dist/mcp-ui-server.d.ts +0 -27
- package/dist/mcp-ui-server.d.ts.map +0 -1
- package/dist/mcp-ui-server.js +0 -484
- package/dist/mcp-ui-server.js.map +0 -1
- package/dist/test-multi-strategy-search.d.ts +0 -3
- package/dist/test-multi-strategy-search.d.ts.map +0 -1
- package/dist/test-multi-strategy-search.js +0 -170
- package/dist/test-multi-strategy-search.js.map +0 -1
package/dist/mcp.js
CHANGED
|
@@ -1,866 +1,1005 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
|
|
2
|
+
// MUST be first import: sets NAPI_RS_NATIVE_LIBRARY_PATH before @ast-grep/napi loads
|
|
3
|
+
import './native-bootstrap.js';
|
|
4
|
+
// ── Bootstrap: parse --data-dir before any module loads CodeIndexDB ──────────
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
let autoIndexPath;
|
|
7
|
+
let uiMode = false;
|
|
8
|
+
let stdioMode = false;
|
|
9
|
+
let showHelp = false;
|
|
10
|
+
const argv = process.argv.slice(2);
|
|
11
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12
|
+
const a = argv[i];
|
|
13
|
+
if (a === '--data-dir' || a === '--dataDir') {
|
|
14
|
+
const val = argv[i + 1];
|
|
15
|
+
if (val && !val.startsWith('-')) {
|
|
16
|
+
process.env.CODE_AUDITOR_DATA_DIR = path.resolve(val);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.error('[code-auditor] --data-dir requires a directory path (next argv). Using CODE_AUDITOR_DATA_DIR env or default.');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (a === '--auto-index') {
|
|
23
|
+
const val = argv[i + 1];
|
|
24
|
+
if (val && !val.startsWith('-')) {
|
|
25
|
+
autoIndexPath = path.resolve(val);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
autoIndexPath = process.cwd();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (a === '--ui') {
|
|
32
|
+
uiMode = true;
|
|
33
|
+
}
|
|
34
|
+
else if (a === '--stdio') {
|
|
35
|
+
stdioMode = true;
|
|
36
|
+
}
|
|
37
|
+
else if (a === '--help' || a === '-h') {
|
|
38
|
+
showHelp = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (showHelp) {
|
|
42
|
+
console.error(`code-auditor-mcp v0.0.0
|
|
43
|
+
Usage: code-auditor-mcp [--stdio] [--ui] [--auto-index <path>] [--data-dir <dir>]
|
|
44
|
+
|
|
45
|
+
--stdio Start MCP stdio server (default mode)
|
|
46
|
+
--ui Start the HTTP UI server on port 3001 (or MCP_UI_PORT)
|
|
47
|
+
--auto-index Sync the index from <path> and exit
|
|
48
|
+
--data-dir Directory for persistent data (index.db, tasks, configs)
|
|
49
|
+
--help, -h Show this message
|
|
50
|
+
`);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
// ── Imports ──────────────────────────────────────────────────────────────────
|
|
4
54
|
import chalk from 'chalk';
|
|
5
55
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
56
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
57
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
58
|
+
import { createWriteStream } from 'node:fs';
|
|
59
|
+
import fs from 'node:fs/promises';
|
|
8
60
|
import { createAuditRunner } from './auditRunner.js';
|
|
9
61
|
import { searchFunctions, findDefinition, syncFileIndex, getDatabase } from './codeIndexService.js';
|
|
10
62
|
import { CodeMapGenerator } from './services/CodeMapGenerator.js';
|
|
11
63
|
import { analyzeDocumentation } from './analyzers/documentationAnalyzer.js';
|
|
12
64
|
import { ConfigGeneratorFactory } from './generators/ConfigGeneratorFactory.js';
|
|
13
65
|
import { DEFAULT_SERVER_URL, IS_DEV_MODE, PACKAGE_VERSION } from './constants.js';
|
|
14
|
-
import { getAuditJobStatus, getAuditResultsPage, startAuditJob } from './mcpAuditJobs.js';
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
import fs from 'node:fs/promises';
|
|
17
|
-
import { createWriteStream } from 'node:fs';
|
|
66
|
+
import { getAuditJobStatus, getAuditResultsPage, getAuditResultsAsSarif, startAuditJob } from './mcpAuditJobs.js';
|
|
18
67
|
import { CodeIndexDB } from './codeIndexDB.js';
|
|
19
68
|
import { logMcpDebug, logMcpInfo, mcpDebugStderr, mcpTraceStderr } from './mcpDiagnostics.js';
|
|
20
69
|
import { assertAuditPathExists, formatMcpToolErrorPayload } from './mcpToolErrors.js';
|
|
21
|
-
|
|
70
|
+
import { ToolRegistry } from './tool-registry.js';
|
|
71
|
+
import { initParsers } from './languages/index.js';
|
|
72
|
+
// ── Console / logging setup ──────────────────────────────────────────────────
|
|
73
|
+
const originalConsoleError = console.error;
|
|
22
74
|
let logStream = null;
|
|
23
75
|
if (IS_DEV_MODE) {
|
|
24
76
|
const logFilePath = path.join(process.cwd(), 'mcp-server.log');
|
|
25
77
|
logStream = createWriteStream(logFilePath, { flags: 'a' });
|
|
26
78
|
console.error(chalk.blue('[INFO]'), `Development mode: Logging to file: ${logFilePath}`);
|
|
27
79
|
}
|
|
28
|
-
// Save original console.error (log is redirected to stderr; see below)
|
|
29
|
-
const originalConsoleError = console.error;
|
|
30
|
-
// Override console.log — MCP uses stdout only for JSON-RPC; log() must never touch stdout.
|
|
31
80
|
console.log = (...args) => {
|
|
32
81
|
if (logStream) {
|
|
33
|
-
const message = args
|
|
82
|
+
const message = args
|
|
83
|
+
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
|
84
|
+
.join(' ');
|
|
34
85
|
const timestamp = new Date().toISOString();
|
|
35
86
|
logStream.write(`[${timestamp}] [LOG] ${message}\n`);
|
|
36
87
|
}
|
|
37
88
|
originalConsoleError.apply(console, args);
|
|
38
89
|
};
|
|
39
|
-
// Override console.error (with conditional file logging)
|
|
40
90
|
console.error = (...args) => {
|
|
41
91
|
if (logStream) {
|
|
42
|
-
const message = args
|
|
92
|
+
const message = args
|
|
93
|
+
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
|
94
|
+
.join(' ');
|
|
43
95
|
const timestamp = new Date().toISOString();
|
|
44
96
|
logStream.write(`[${timestamp}] [ERROR] ${message}\n`);
|
|
45
97
|
}
|
|
46
98
|
originalConsoleError.apply(console, args);
|
|
47
99
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
required: false,
|
|
72
|
-
description: 'Result ID returned by audit_status when the background audit job completes.',
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'auditId',
|
|
76
|
-
type: 'string',
|
|
77
|
-
required: false,
|
|
78
|
-
description: 'Backward-compatible alias for resultId.',
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
name: 'start_audit',
|
|
84
|
-
description: 'Start a background audit job. Returns immediately with a jobId. Poll audit_status until completed, then fetch pages with audit or audit_results.',
|
|
85
|
-
parameters: [
|
|
86
|
-
{
|
|
87
|
-
name: 'path',
|
|
88
|
-
type: 'string',
|
|
89
|
-
required: false,
|
|
90
|
-
description: 'File or directory path to audit (defaults to current directory).',
|
|
91
|
-
default: process.cwd(),
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'analyzers',
|
|
95
|
-
type: 'array',
|
|
96
|
-
required: false,
|
|
97
|
-
description: 'Analyzers to run (default: solid, dry, documentation, react, data-access).',
|
|
98
|
-
default: ['solid', 'dry', 'documentation', 'react', 'data-access'],
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: 'minSeverity',
|
|
102
|
-
type: 'string',
|
|
103
|
-
required: false,
|
|
104
|
-
description: 'Minimum severity level to report.',
|
|
105
|
-
default: 'warning',
|
|
106
|
-
enum: ['info', 'warning', 'critical'],
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: 'indexFunctions',
|
|
110
|
-
type: 'boolean',
|
|
111
|
-
required: false,
|
|
112
|
-
description: 'Index functions during audit (default: true).',
|
|
113
|
-
default: true,
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
name: 'analyzerConfigs',
|
|
117
|
-
type: 'object',
|
|
118
|
-
required: false,
|
|
119
|
-
description: 'Analyzer-specific configuration overrides.',
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
name: 'analyzerConcurrency',
|
|
123
|
-
type: 'number',
|
|
124
|
-
required: false,
|
|
125
|
-
description: 'Max analyzers to run in parallel (default: 1).',
|
|
126
|
-
default: 1,
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: 'partitionStrategy',
|
|
130
|
-
type: 'string',
|
|
131
|
-
required: false,
|
|
132
|
-
description: 'Partition mode: none | auto | top-level. auto shards when large source trees are detected.',
|
|
133
|
-
default: 'auto',
|
|
134
|
-
enum: ['none', 'auto', 'top-level'],
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
name: 'maxPartitions',
|
|
138
|
-
type: 'number',
|
|
139
|
-
required: false,
|
|
140
|
-
description: 'Maximum number of folder partitions for sharded audits (default: 4).',
|
|
141
|
-
default: 4,
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: 'partitionThresholdFiles',
|
|
145
|
-
type: 'number',
|
|
146
|
-
required: false,
|
|
147
|
-
description: 'Minimum discovered files before auto partitioning is enabled (default: 250).',
|
|
148
|
-
default: 250,
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: 'workerCount',
|
|
152
|
-
type: 'number',
|
|
153
|
-
required: false,
|
|
154
|
-
description: 'Number of worker processes for shard execution (default: min(4, CPU-1)).',
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: 'maxRetries',
|
|
158
|
-
type: 'number',
|
|
159
|
-
required: false,
|
|
160
|
-
description: 'Retry attempts per shard for retryable failures (default: 1).',
|
|
161
|
-
default: 1,
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: 'shardTimeoutMs',
|
|
165
|
-
type: 'number',
|
|
166
|
-
required: false,
|
|
167
|
-
description: 'Per-shard timeout in milliseconds (default: 180000).',
|
|
168
|
-
default: 180000,
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
name: 'retryBackoffMs',
|
|
172
|
-
type: 'number',
|
|
173
|
-
required: false,
|
|
174
|
-
description: 'Base retry backoff in milliseconds (default: 500).',
|
|
175
|
-
default: 500,
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
name: 'jobTimeoutMs',
|
|
179
|
-
type: 'number',
|
|
180
|
-
required: false,
|
|
181
|
-
description: 'Maximum wall time for the entire audit job (default 30m, env CODE_AUDITOR_JOB_TIMEOUT_MS, cap 4h). Aborts workers cooperatively then tears down.',
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
name: 'maxFilesPerRun',
|
|
185
|
-
type: 'number',
|
|
186
|
-
required: false,
|
|
187
|
-
description: 'Per worker chunk: if more files match than this, the worker finishes one chunk and the parent queues the rest on another worker (optional).',
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: 'shardSoftBudgetMs',
|
|
191
|
-
type: 'number',
|
|
192
|
-
required: false,
|
|
193
|
-
description: 'Per worker soft wall-clock budget; aborts in-process analysis cooperatively via AbortSignal so the parent can assign the next chunk to a fresh worker.',
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
name: 'generateCodeMap',
|
|
197
|
-
type: 'boolean',
|
|
198
|
-
required: false,
|
|
199
|
-
description: 'Generate code map artifacts during audit (default: false).',
|
|
200
|
-
default: false,
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
name: 'audit_status',
|
|
206
|
-
description: 'Get current status for a previously started background audit job. Returns resultId when completed.',
|
|
207
|
-
parameters: [
|
|
208
|
-
{
|
|
209
|
-
name: 'jobId',
|
|
210
|
-
type: 'string',
|
|
211
|
-
required: true,
|
|
212
|
-
description: 'Job ID returned by start_audit.',
|
|
213
|
-
},
|
|
214
|
-
],
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
name: 'audit_results',
|
|
218
|
-
description: 'Fetch paginated violations for a completed audit result by resultId.',
|
|
219
|
-
parameters: [
|
|
220
|
-
{
|
|
221
|
-
name: 'resultId',
|
|
222
|
-
type: 'string',
|
|
223
|
-
required: true,
|
|
224
|
-
description: 'Result ID returned by audit_status when status is completed.',
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
name: 'limit',
|
|
228
|
-
type: 'number',
|
|
229
|
-
required: false,
|
|
230
|
-
description: 'Maximum number of violations to return per page (default: 50, max: 100).',
|
|
231
|
-
default: 50,
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: 'offset',
|
|
235
|
-
type: 'number',
|
|
236
|
-
required: false,
|
|
237
|
-
description: 'Violation offset for pagination (default: 0).',
|
|
238
|
-
default: 0,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
name: 'audit_health',
|
|
244
|
-
description: 'Quick health check of a codebase with key metrics',
|
|
245
|
-
parameters: [
|
|
246
|
-
{
|
|
247
|
-
name: 'path',
|
|
248
|
-
type: 'string',
|
|
249
|
-
required: false,
|
|
250
|
-
description: 'The directory path to check',
|
|
251
|
-
default: process.cwd(),
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
name: 'threshold',
|
|
255
|
-
type: 'number',
|
|
256
|
-
required: false,
|
|
257
|
-
description: 'Health score threshold (0-100) for pass/fail',
|
|
258
|
-
default: 70,
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: 'indexFunctions',
|
|
262
|
-
type: 'boolean',
|
|
263
|
-
required: false,
|
|
264
|
-
description: 'Automatically index functions during health check',
|
|
265
|
-
default: true,
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
name: 'analyzerConfigs',
|
|
269
|
-
type: 'object',
|
|
270
|
-
required: false,
|
|
271
|
-
description: 'Analyzer-specific configuration overrides (e.g., SOLID thresholds, DRY settings)',
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
name: 'generateCodeMap',
|
|
275
|
-
type: 'boolean',
|
|
276
|
-
required: false,
|
|
277
|
-
description: 'Generate and return a human-readable code map as part of the health check results',
|
|
278
|
-
default: true,
|
|
279
|
-
},
|
|
280
|
-
],
|
|
281
|
-
},
|
|
282
|
-
// Code Index Tools
|
|
283
|
-
{
|
|
284
|
-
name: 'search_code',
|
|
285
|
-
description: 'Search indexed functions and React components with natural language queries. Supports operators: entity:component, component:functional|class|memo|forwardRef, hook:useState|useEffect|etc, prop:propName, dep:packageName, dependency:lodash, uses:express, calls:functionName, calledby:functionName, dependents-of:functionName, used-by:functionName, depends-on:module, imports-from:file, unused-imports, dead-imports, type:fileType, file:path, lang:language, complexity:1-10, jsdoc:true|false',
|
|
286
|
-
parameters: [
|
|
287
|
-
{
|
|
288
|
-
name: 'query',
|
|
289
|
-
type: 'string',
|
|
290
|
-
required: true,
|
|
291
|
-
description: 'Search query with natural language and/or operators. Examples: "Button component:functional", "entity:component hook:useState", "render prop:onClick", "dep:lodash", "calls:validateUser", "unused-imports", "dependents-of:authenticate"',
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
name: 'filters',
|
|
295
|
-
type: 'object',
|
|
296
|
-
required: false,
|
|
297
|
-
description: 'Optional filters (language, filePath, dependencies, componentType, entityType, searchMode). Set searchMode to "content" to search within function bodies, "metadata" for names/signatures only, or "both" for combined search',
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
name: 'limit',
|
|
301
|
-
type: 'number',
|
|
302
|
-
required: false,
|
|
303
|
-
description: 'Maximum results to return',
|
|
304
|
-
default: 50,
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
name: 'offset',
|
|
308
|
-
type: 'number',
|
|
309
|
-
required: false,
|
|
310
|
-
description: 'Offset for pagination',
|
|
311
|
-
default: 0,
|
|
312
|
-
},
|
|
313
|
-
],
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
name: 'find_definition',
|
|
317
|
-
description: 'Find the exact definition of a specific function or React component',
|
|
318
|
-
parameters: [
|
|
319
|
-
{
|
|
320
|
-
name: 'name',
|
|
321
|
-
type: 'string',
|
|
322
|
-
required: true,
|
|
323
|
-
description: 'Function or component name to find',
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
name: 'filePath',
|
|
327
|
-
type: 'string',
|
|
328
|
-
required: false,
|
|
329
|
-
description: 'Optional file path to narrow search',
|
|
330
|
-
},
|
|
331
|
-
],
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: 'sync_index',
|
|
335
|
-
description: 'Synchronize, cleanup, or reset analysis-derived data (indexed functions, FlexSearch, cached audits, code maps, schema overlays). Project tasks, analyzer configs, and whitelist entries are preserved on reset.',
|
|
336
|
-
parameters: [
|
|
337
|
-
{
|
|
338
|
-
name: 'mode',
|
|
339
|
-
type: 'string',
|
|
340
|
-
required: false,
|
|
341
|
-
description: 'sync: update index from files; cleanup: remove index rows for deleted files; reset: clear all analysis-derived data (not tasks/config/whitelist)',
|
|
342
|
-
default: 'sync',
|
|
343
|
-
enum: ['sync', 'cleanup', 'reset'],
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
name: 'path',
|
|
347
|
-
type: 'string',
|
|
348
|
-
required: false,
|
|
349
|
-
description: 'Optional specific path to sync',
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
},
|
|
353
|
-
// AI Configuration Tool
|
|
354
|
-
{
|
|
355
|
-
name: 'generate_ai_config',
|
|
356
|
-
description: 'Generate configuration files for AI coding assistants',
|
|
357
|
-
parameters: [
|
|
358
|
-
{
|
|
359
|
-
name: 'tools',
|
|
360
|
-
type: 'array',
|
|
361
|
-
required: true,
|
|
362
|
-
description: 'AI tools to configure (cursor, continue, copilot, claude, zed, windsurf, cody, aider, cline, pearai)',
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
name: 'outputDir',
|
|
366
|
-
type: 'string',
|
|
367
|
-
required: false,
|
|
368
|
-
description: 'Output directory for configuration files',
|
|
369
|
-
default: '.',
|
|
370
|
-
},
|
|
371
|
-
],
|
|
372
|
-
},
|
|
373
|
-
// Workflow Guide Tool
|
|
374
|
-
{
|
|
375
|
-
name: 'get_workflow_guide',
|
|
376
|
-
description: 'Get recommended workflows and best practices for using code auditor tools effectively',
|
|
377
|
-
parameters: [
|
|
378
|
-
{
|
|
379
|
-
name: 'scenario',
|
|
380
|
-
type: 'string',
|
|
381
|
-
required: false,
|
|
382
|
-
description: 'Specific scenario: initial-setup, react-development, code-review, find-patterns, maintenance. Leave empty to see all.',
|
|
383
|
-
},
|
|
384
|
-
],
|
|
385
|
-
},
|
|
386
|
-
// Analyzer Configuration Tools
|
|
387
|
-
{
|
|
388
|
-
name: 'set_analyzer_config',
|
|
389
|
-
description: 'Set or update analyzer configuration that persists across audit runs',
|
|
390
|
-
parameters: [
|
|
391
|
-
{
|
|
392
|
-
name: 'analyzerName',
|
|
393
|
-
type: 'string',
|
|
394
|
-
required: true,
|
|
395
|
-
description: 'The analyzer to configure (solid, dry, security, etc.)',
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
name: 'config',
|
|
399
|
-
type: 'object',
|
|
400
|
-
required: true,
|
|
401
|
-
description: 'Configuration object for the analyzer (e.g., thresholds, rules)',
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
name: 'projectPath',
|
|
405
|
-
type: 'string',
|
|
406
|
-
required: false,
|
|
407
|
-
description: 'Optional project path for project-specific config (defaults to global)',
|
|
408
|
-
},
|
|
409
|
-
],
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
name: 'get_analyzer_config',
|
|
413
|
-
description: 'Get current configuration for an analyzer',
|
|
414
|
-
parameters: [
|
|
415
|
-
{
|
|
416
|
-
name: 'analyzerName',
|
|
417
|
-
type: 'string',
|
|
418
|
-
required: false,
|
|
419
|
-
description: 'Specific analyzer name, or omit to get all configs',
|
|
420
|
-
},
|
|
421
|
-
{
|
|
422
|
-
name: 'projectPath',
|
|
423
|
-
type: 'string',
|
|
424
|
-
required: false,
|
|
425
|
-
description: 'Optional project path to get project-specific config',
|
|
426
|
-
},
|
|
427
|
-
],
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
name: 'reset_analyzer_config',
|
|
431
|
-
description: 'Reset analyzer configuration to defaults',
|
|
432
|
-
parameters: [
|
|
433
|
-
{
|
|
434
|
-
name: 'analyzerName',
|
|
435
|
-
type: 'string',
|
|
436
|
-
required: false,
|
|
437
|
-
description: 'Specific analyzer to reset, or omit to reset all',
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
name: 'projectPath',
|
|
441
|
-
type: 'string',
|
|
442
|
-
required: false,
|
|
443
|
-
description: 'Optional project path to reset only project-specific config',
|
|
444
|
-
},
|
|
445
|
-
],
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
name: 'get_code_map_section',
|
|
449
|
-
description: 'Retrieve a specific section of a previously generated code map',
|
|
450
|
-
parameters: [
|
|
451
|
-
{
|
|
452
|
-
name: 'mapId',
|
|
453
|
-
type: 'string',
|
|
454
|
-
required: true,
|
|
455
|
-
description: 'The map ID returned from a previous audit with code map generation',
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
name: 'sectionType',
|
|
459
|
-
type: 'string',
|
|
460
|
-
required: true,
|
|
461
|
-
description: 'The section type to retrieve (e.g., overview, files, dependencies, documentation)',
|
|
462
|
-
},
|
|
463
|
-
],
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
name: 'list_code_map_sections',
|
|
467
|
-
description: 'List all available sections for a code map',
|
|
468
|
-
parameters: [
|
|
469
|
-
{
|
|
470
|
-
name: 'mapId',
|
|
471
|
-
type: 'string',
|
|
472
|
-
required: true,
|
|
473
|
-
description: 'The map ID returned from a previous audit',
|
|
474
|
-
},
|
|
475
|
-
],
|
|
476
|
-
},
|
|
477
|
-
{
|
|
478
|
-
name: 'project_tasks',
|
|
479
|
-
description: 'Manage a persistent per-project task queue. Tasks and analyzer configs survive sync_index reset; reset clears function index, cached audits, code maps, and schema overlays (no ghost code references). Use delete to remove a task.',
|
|
480
|
-
parameters: [
|
|
481
|
-
{
|
|
482
|
-
name: 'action',
|
|
483
|
-
type: 'string',
|
|
484
|
-
required: true,
|
|
485
|
-
description: 'list | create | get | update | delete. list/create use projectPath or default to process.cwd(); create also needs title; get/update/delete need taskId; update needs patch object.',
|
|
486
|
-
enum: ['list', 'create', 'get', 'update', 'delete'],
|
|
487
|
-
},
|
|
488
|
-
{
|
|
489
|
-
name: 'projectPath',
|
|
490
|
-
type: 'string',
|
|
491
|
-
required: false,
|
|
492
|
-
description: 'Project root (resolved). Omit to use the MCP server working directory (same default idea as audit path); response includes projectPathDefaulted when omitted.',
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
name: 'taskId',
|
|
496
|
-
type: 'string',
|
|
497
|
-
required: false,
|
|
498
|
-
description: 'Stable task id (UUID). Required for get, update, delete.',
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
name: 'title',
|
|
502
|
-
type: 'string',
|
|
503
|
-
required: false,
|
|
504
|
-
description: 'Task title. Required for create.',
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
name: 'description',
|
|
508
|
-
type: 'string',
|
|
509
|
-
required: false,
|
|
510
|
-
description: 'Longer description / notes.',
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
name: 'status',
|
|
514
|
-
type: 'string',
|
|
515
|
-
required: false,
|
|
516
|
-
description: 'pending | in_progress | blocked | done | cancelled. Filter for list; initial status for create; can be set via update patch.',
|
|
517
|
-
enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'],
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
name: 'priority',
|
|
521
|
-
type: 'string',
|
|
522
|
-
required: false,
|
|
523
|
-
description: 'Optional priority for create/update patch.',
|
|
524
|
-
enum: ['low', 'medium', 'high'],
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
name: 'labels',
|
|
528
|
-
type: 'array',
|
|
529
|
-
required: false,
|
|
530
|
-
description: 'String tags (e.g. ["audit","refactor"]).',
|
|
531
|
-
},
|
|
532
|
-
{
|
|
533
|
-
name: 'metadata',
|
|
534
|
-
type: 'object',
|
|
535
|
-
required: false,
|
|
536
|
-
description: 'Arbitrary JSON object: related file paths, links, audit IDs, etc.',
|
|
537
|
-
},
|
|
538
|
-
{
|
|
539
|
-
name: 'parentTaskId',
|
|
540
|
-
type: 'string',
|
|
541
|
-
required: false,
|
|
542
|
-
description: 'Optional parent task for subtasks.',
|
|
543
|
-
},
|
|
544
|
-
{
|
|
545
|
-
name: 'source',
|
|
546
|
-
type: 'string',
|
|
547
|
-
required: false,
|
|
548
|
-
description: 'manual | audit | mcp. Filter for list; provenance for create; can be set via update patch.',
|
|
549
|
-
enum: ['manual', 'audit', 'mcp'],
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
name: 'blockedBy',
|
|
553
|
-
type: 'array',
|
|
554
|
-
required: false,
|
|
555
|
-
description: 'Task IDs this item is blocked by (waiting-on).',
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
name: 'dueAt',
|
|
559
|
-
type: 'string',
|
|
560
|
-
required: false,
|
|
561
|
-
description: 'ISO 8601 due datetime for create; omit or null in patch to clear.',
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
name: 'sortOrder',
|
|
565
|
-
type: 'number',
|
|
566
|
-
required: false,
|
|
567
|
-
description: 'Lower numbers sort first within a project (default 0).',
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
name: 'relatedFiles',
|
|
571
|
-
type: 'array',
|
|
572
|
-
required: false,
|
|
573
|
-
description: 'Repo-relative or absolute file paths tied to the task.',
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
name: 'relatedSymbols',
|
|
577
|
-
type: 'array',
|
|
578
|
-
required: false,
|
|
579
|
-
description: 'Function, class, or component names for cross-linking.',
|
|
580
|
-
},
|
|
581
|
-
{
|
|
582
|
-
name: 'patch',
|
|
583
|
-
type: 'object',
|
|
584
|
-
required: false,
|
|
585
|
-
description: 'For update: partial fields (title, description, status, priority, labels, metadata, parentTaskId, source, blockedBy, dueAt, sortOrder, relatedFiles, relatedSymbols).',
|
|
586
|
-
},
|
|
587
|
-
{
|
|
588
|
-
name: 'limit',
|
|
589
|
-
type: 'number',
|
|
590
|
-
required: false,
|
|
591
|
-
description: 'Max tasks to return for list (default 500, max 1000).',
|
|
592
|
-
},
|
|
593
|
-
],
|
|
594
|
-
},
|
|
595
|
-
];
|
|
596
|
-
async function startMcpServer() {
|
|
597
|
-
logMcpInfo('startup', `code-auditor-mcp ${PACKAGE_VERSION} (stdio)`, {
|
|
598
|
-
pid: process.pid,
|
|
599
|
-
cwd: process.cwd(),
|
|
600
|
-
dataDir: process.env.CODE_AUDITOR_DATA_DIR ?? '(default)',
|
|
601
|
-
debug: process.env.CODE_AUDITOR_DEBUG ?? '0',
|
|
602
|
-
logFile: process.env.CODE_AUDITOR_LOG_FILE ?? '(none)'
|
|
603
|
-
});
|
|
604
|
-
mcpDebugStderr(chalk.blue('[DEBUG]'), `Node ${process.version} · cwd ${process.cwd()}`);
|
|
605
|
-
const server = new Server({
|
|
606
|
-
name: 'code-auditor',
|
|
607
|
-
version: PACKAGE_VERSION,
|
|
608
|
-
}, {
|
|
609
|
-
capabilities: {
|
|
610
|
-
tools: {},
|
|
611
|
-
},
|
|
100
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
101
|
+
class RequestAbortedError extends Error {
|
|
102
|
+
constructor(message = 'MCP request aborted') {
|
|
103
|
+
super(message);
|
|
104
|
+
this.name = 'RequestAbortedError';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function throwIfRequestAborted(signal, phase) {
|
|
108
|
+
if (signal?.aborted) {
|
|
109
|
+
throw new RequestAbortedError(`Request aborted${phase ? ` during ${phase}` : ''}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function withAbortSignal(signal, phase, op) {
|
|
113
|
+
throwIfRequestAborted(signal, phase);
|
|
114
|
+
if (!signal)
|
|
115
|
+
return op();
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const onAbort = () => reject(new RequestAbortedError(`Request aborted during ${phase}`));
|
|
118
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
119
|
+
void op()
|
|
120
|
+
.then(resolve)
|
|
121
|
+
.catch(reject)
|
|
122
|
+
.finally(() => signal.removeEventListener('abort', onAbort));
|
|
612
123
|
});
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
124
|
+
}
|
|
125
|
+
function serializeForMcp(value) {
|
|
126
|
+
try {
|
|
127
|
+
return JSON.stringify(value);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return JSON.stringify({
|
|
131
|
+
success: false,
|
|
132
|
+
error: `Failed to serialize MCP response: ${error instanceof Error ? error.message : String(error)}`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function safeJson(value) {
|
|
137
|
+
try {
|
|
138
|
+
return JSON.stringify(value, null, 2);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return `[unserializable: ${error instanceof Error ? error.message : String(error)}]`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function calculateHealthScore(result) {
|
|
145
|
+
const filesAnalyzed = result.metadata?.filesAnalyzed || 1;
|
|
146
|
+
const critical = result.summary.criticalIssues || 0;
|
|
147
|
+
const warnings = result.summary.warnings || 0;
|
|
148
|
+
const suggestions = result.summary.suggestions || 0;
|
|
149
|
+
const weights = { critical: 10, warning: 3, suggestion: 0.5 };
|
|
150
|
+
const weightedViolations = critical * weights.critical + warnings * weights.warning + suggestions * weights.suggestion;
|
|
151
|
+
const violationsPerFile = weightedViolations / filesAnalyzed;
|
|
152
|
+
const score = 100 - violationsPerFile * 2;
|
|
153
|
+
return Math.max(0, Math.round(Math.min(100, score)));
|
|
154
|
+
}
|
|
155
|
+
function getHealthRecommendation(score, result) {
|
|
156
|
+
if (score >= 90)
|
|
157
|
+
return 'Excellent code health!';
|
|
158
|
+
if (score >= 70)
|
|
159
|
+
return 'Good code health with room for improvement';
|
|
160
|
+
if (result.summary.criticalIssues > 0)
|
|
161
|
+
return `Fix ${result.summary.criticalIssues} critical violations first`;
|
|
162
|
+
return 'Code health needs attention - run detailed audit';
|
|
163
|
+
}
|
|
164
|
+
function registerProcessReliabilityHandlers() {
|
|
165
|
+
const g = globalThis;
|
|
166
|
+
const exitOnFatal = process.env.CODE_AUDITOR_EXIT_ON_FATAL === '1' ||
|
|
167
|
+
process.env.CODE_AUDITOR_EXIT_ON_FATAL === 'true';
|
|
168
|
+
if (!g.__codeAuditorUncaughtExceptionHandlerInstalled) {
|
|
169
|
+
process.on('uncaughtException', (error) => {
|
|
170
|
+
console.error(chalk.red('[ERROR]'), 'Uncaught exception:', error);
|
|
171
|
+
console.error(chalk.red('[ERROR]'), 'Stack:', error.stack);
|
|
172
|
+
console.error(chalk.red('[ERROR]'), 'Error details:', safeJson(error));
|
|
173
|
+
if (exitOnFatal)
|
|
174
|
+
process.exit(1);
|
|
175
|
+
});
|
|
176
|
+
g.__codeAuditorUncaughtExceptionHandlerInstalled = true;
|
|
177
|
+
}
|
|
178
|
+
if (!g.__codeAuditorUnhandledRejectionHandlerInstalled) {
|
|
179
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
180
|
+
console.error(chalk.red('[ERROR]'), 'Unhandled rejection at:', promise);
|
|
181
|
+
console.error(chalk.red('[ERROR]'), 'Reason:', reason);
|
|
182
|
+
console.error(chalk.red('[ERROR]'), 'Rejection details:', safeJson(reason));
|
|
183
|
+
if (exitOnFatal)
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|
|
186
|
+
g.__codeAuditorUnhandledRejectionHandlerInstalled = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// ── Tool registration ────────────────────────────────────────────────────────
|
|
190
|
+
const DEFAULT_ANALYZERS = ['solid', 'dry', 'documentation', 'react', 'data-access'];
|
|
191
|
+
function registerAllTools(registry) {
|
|
192
|
+
// ── audit ──────────────────────────────────────────────────────────────────
|
|
193
|
+
const auditActions = [
|
|
194
|
+
{
|
|
195
|
+
name: 'run',
|
|
196
|
+
description: 'Run a synchronous audit on the specified path. Returns a full audit result with violations.',
|
|
197
|
+
parameters: [
|
|
198
|
+
{
|
|
199
|
+
name: 'path',
|
|
200
|
+
type: 'string',
|
|
201
|
+
required: false,
|
|
202
|
+
description: 'File or directory path to audit (defaults to current directory).',
|
|
203
|
+
default: process.cwd(),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'analyzers',
|
|
207
|
+
type: 'array',
|
|
208
|
+
required: false,
|
|
209
|
+
description: `Analyzers to run (default: ${DEFAULT_ANALYZERS.join(', ')}).`,
|
|
210
|
+
default: DEFAULT_ANALYZERS,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'minSeverity',
|
|
214
|
+
type: 'string',
|
|
215
|
+
required: false,
|
|
216
|
+
description: 'Minimum severity level to report.',
|
|
217
|
+
default: 'warning',
|
|
218
|
+
enum: ['info', 'warning', 'critical'],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'indexFunctions',
|
|
222
|
+
type: 'boolean',
|
|
223
|
+
required: false,
|
|
224
|
+
description: 'Index functions during audit (default: true).',
|
|
225
|
+
default: true,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: 'analyzerConfigs',
|
|
625
229
|
type: 'object',
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
type: param.type,
|
|
629
|
-
description: param.description,
|
|
630
|
-
...(param.default !== undefined && { default: param.default }),
|
|
631
|
-
...(param.enum && { enum: param.enum }),
|
|
632
|
-
};
|
|
633
|
-
return acc;
|
|
634
|
-
}, {}),
|
|
635
|
-
required: tool.parameters.filter(p => p.required).map(p => p.name),
|
|
230
|
+
required: false,
|
|
231
|
+
description: 'Analyzer-specific configuration overrides.',
|
|
636
232
|
},
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
tools: {},
|
|
648
|
-
},
|
|
649
|
-
serverInfo: {
|
|
650
|
-
name: 'code-auditor',
|
|
651
|
-
version: PACKAGE_VERSION,
|
|
652
|
-
},
|
|
653
|
-
};
|
|
654
|
-
mcpTraceStderr(chalk.blue('[DEBUG]'), 'initialize response', JSON.stringify(response, null, 2));
|
|
655
|
-
return response;
|
|
656
|
-
});
|
|
657
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
658
|
-
mcpTraceStderr(chalk.blue('[DEBUG]'), 'CallTool raw', JSON.stringify(request, null, 2));
|
|
659
|
-
const { name, arguments: args } = request.params;
|
|
660
|
-
mcpTraceStderr(chalk.blue('[DEBUG]'), `CallTool ${name}`, JSON.stringify(args, null, 2));
|
|
661
|
-
const toolStartedAt = Date.now();
|
|
662
|
-
try {
|
|
663
|
-
if (name !== 'audit_status') {
|
|
664
|
-
logMcpDebug('tool', `call ${name}`, {
|
|
665
|
-
argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
|
|
666
|
-
pathArg: args?.path,
|
|
667
|
-
cwd: process.cwd()
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
let result;
|
|
671
|
-
switch (name) {
|
|
672
|
-
case 'start_audit': {
|
|
673
|
-
result = await startAuditJob(args, {
|
|
674
|
-
defaultAnalyzers: ['solid', 'dry', 'documentation', 'react', 'data-access'],
|
|
675
|
-
defaultMinSeverity: 'warning',
|
|
676
|
-
defaultGenerateCodeMap: false,
|
|
677
|
-
});
|
|
678
|
-
break;
|
|
679
|
-
}
|
|
680
|
-
case 'audit_status': {
|
|
681
|
-
const jobId = args.jobId;
|
|
682
|
-
if (!jobId) {
|
|
683
|
-
throw new Error('audit_status requires jobId');
|
|
684
|
-
}
|
|
685
|
-
result = getAuditJobStatus(jobId);
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
case 'audit_results': {
|
|
689
|
-
result = await getAuditResultsPage(args);
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
case 'audit': {
|
|
693
|
-
result = await getAuditResultsPage(args);
|
|
694
|
-
break;
|
|
695
|
-
}
|
|
696
|
-
case 'audit_health': {
|
|
233
|
+
{
|
|
234
|
+
name: 'scope',
|
|
235
|
+
type: 'string',
|
|
236
|
+
required: false,
|
|
237
|
+
description: 'Audit scope: "all" (default), "changed" (files differing from index), "git:<ref>" (diff against a git ref), or a comma-separated list of file paths.',
|
|
238
|
+
default: 'all',
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
handler: async (args, signal) => {
|
|
242
|
+
return withAbortSignal(signal, 'audit.run', async () => {
|
|
697
243
|
const auditPath = path.resolve(args.path || process.cwd());
|
|
698
244
|
await assertAuditPathExists(auditPath);
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
const
|
|
702
|
-
// Get stored analyzer configs from database
|
|
245
|
+
const analyzers = args.analyzers ?? DEFAULT_ANALYZERS;
|
|
246
|
+
const minSeverity = (args.minSeverity || 'warning');
|
|
247
|
+
const indexFunctions = args.indexFunctions !== false;
|
|
703
248
|
const db = CodeIndexDB.getInstance();
|
|
704
249
|
await db.initialize();
|
|
705
250
|
const storedConfigs = await db.getAllAnalyzerConfigs(auditPath);
|
|
706
|
-
// Merge stored configs with any provided configs
|
|
707
251
|
const analyzerConfigs = {
|
|
708
252
|
...storedConfigs,
|
|
709
|
-
...(args.analyzerConfigs || {})
|
|
253
|
+
...(args.analyzerConfigs || {}),
|
|
710
254
|
};
|
|
255
|
+
const scope = args.scope || 'all';
|
|
711
256
|
const runner = createAuditRunner({
|
|
712
257
|
projectRoot: auditPath,
|
|
713
|
-
enabledAnalyzers:
|
|
714
|
-
minSeverity
|
|
258
|
+
enabledAnalyzers: analyzers,
|
|
259
|
+
minSeverity,
|
|
715
260
|
verbose: false,
|
|
716
|
-
indexFunctions,
|
|
261
|
+
indexFunctions,
|
|
262
|
+
scope: scope !== 'all' ? scope : undefined,
|
|
717
263
|
...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }),
|
|
718
|
-
progressCallback: (p) => {
|
|
719
|
-
if (p.phase === 'function-indexing' &&
|
|
720
|
-
typeof p.current === 'number' &&
|
|
721
|
-
typeof p.total === 'number' &&
|
|
722
|
-
p.total > 0 &&
|
|
723
|
-
p.current % 50 !== 0 &&
|
|
724
|
-
p.current !== p.total) {
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
logMcpDebug('audit_health', p.message ?? p.phase ?? 'progress', {
|
|
728
|
-
phase: p.phase,
|
|
729
|
-
analyzer: p.analyzer,
|
|
730
|
-
current: p.current,
|
|
731
|
-
total: p.total
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
264
|
});
|
|
735
265
|
const auditResult = await runner.run();
|
|
736
|
-
const healthScore = calculateHealthScore(auditResult);
|
|
737
|
-
// Handle function indexing if enabled and functions were collected
|
|
738
|
-
let indexingResult = null;
|
|
739
266
|
if (indexFunctions && auditResult.metadata.fileToFunctionsMap) {
|
|
740
267
|
try {
|
|
741
|
-
const syncStats = { added: 0, updated: 0, removed: 0 };
|
|
742
|
-
// Sync each file's functions to handle additions, updates, and removals
|
|
743
268
|
for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) {
|
|
744
|
-
|
|
745
|
-
syncStats.added += fileStats.added;
|
|
746
|
-
syncStats.updated += fileStats.updated;
|
|
747
|
-
syncStats.removed += fileStats.removed;
|
|
269
|
+
await syncFileIndex(filePath, functions);
|
|
748
270
|
}
|
|
749
|
-
indexingResult = {
|
|
750
|
-
success: true,
|
|
751
|
-
registered: syncStats.added + syncStats.updated,
|
|
752
|
-
failed: 0,
|
|
753
|
-
syncStats
|
|
754
|
-
};
|
|
755
|
-
mcpDebugStderr(chalk.blue('[INFO]'), `Synced functions: ${syncStats.added} added, ${syncStats.updated} updated, ${syncStats.removed} removed`);
|
|
756
271
|
}
|
|
757
|
-
catch
|
|
758
|
-
|
|
272
|
+
catch {
|
|
273
|
+
// indexing is best-effort for sync audit
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
summary: auditResult.summary,
|
|
279
|
+
violations: auditResult.analyzerResults,
|
|
280
|
+
metadata: auditResult.metadata,
|
|
281
|
+
};
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: 'start',
|
|
287
|
+
description: 'Start a background audit job. Returns immediately with a jobId. Poll `audit.status` until completed, then fetch pages with `audit.results`.',
|
|
288
|
+
parameters: [
|
|
289
|
+
{
|
|
290
|
+
name: 'path',
|
|
291
|
+
type: 'string',
|
|
292
|
+
required: false,
|
|
293
|
+
description: 'File or directory path to audit (defaults to current directory).',
|
|
294
|
+
default: process.cwd(),
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'analyzers',
|
|
298
|
+
type: 'array',
|
|
299
|
+
required: false,
|
|
300
|
+
description: `Analyzers to run (default: ${DEFAULT_ANALYZERS.join(', ')}).`,
|
|
301
|
+
default: DEFAULT_ANALYZERS,
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: 'minSeverity',
|
|
305
|
+
type: 'string',
|
|
306
|
+
required: false,
|
|
307
|
+
description: 'Minimum severity level to report.',
|
|
308
|
+
default: 'warning',
|
|
309
|
+
enum: ['info', 'warning', 'critical'],
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'indexFunctions',
|
|
313
|
+
type: 'boolean',
|
|
314
|
+
required: false,
|
|
315
|
+
description: 'Index functions during audit (default: true).',
|
|
316
|
+
default: true,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: 'analyzerConfigs',
|
|
320
|
+
type: 'object',
|
|
321
|
+
required: false,
|
|
322
|
+
description: 'Analyzer-specific configuration overrides.',
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'scope',
|
|
326
|
+
type: 'string',
|
|
327
|
+
required: false,
|
|
328
|
+
description: 'Audit scope: "all" (default), "changed" (files differing from index), "git:<ref>" (diff against a git ref), or a comma-separated list of file paths.',
|
|
329
|
+
default: 'all',
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: 'analyzerConcurrency',
|
|
333
|
+
type: 'number',
|
|
334
|
+
required: false,
|
|
335
|
+
description: 'Max analyzers to run in parallel (default: 1).',
|
|
336
|
+
default: 1,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: 'partitionStrategy',
|
|
340
|
+
type: 'string',
|
|
341
|
+
required: false,
|
|
342
|
+
description: 'Partition mode: none | auto | top-level.',
|
|
343
|
+
default: 'auto',
|
|
344
|
+
enum: ['none', 'auto', 'top-level'],
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'maxPartitions',
|
|
348
|
+
type: 'number',
|
|
349
|
+
required: false,
|
|
350
|
+
description: 'Maximum number of folder partitions (default: 4).',
|
|
351
|
+
default: 4,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: 'partitionThresholdFiles',
|
|
355
|
+
type: 'number',
|
|
356
|
+
required: false,
|
|
357
|
+
description: 'Minimum files before auto partitioning (default: 250).',
|
|
358
|
+
default: 250,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: 'workerCount',
|
|
362
|
+
type: 'number',
|
|
363
|
+
required: false,
|
|
364
|
+
description: 'Number of worker processes (default: min(4, CPU-1)).',
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: 'maxRetries',
|
|
368
|
+
type: 'number',
|
|
369
|
+
required: false,
|
|
370
|
+
description: 'Retry attempts per shard (default: 1).',
|
|
371
|
+
default: 1,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: 'shardTimeoutMs',
|
|
375
|
+
type: 'number',
|
|
376
|
+
required: false,
|
|
377
|
+
description: 'Per-shard timeout in ms (default: 180000).',
|
|
378
|
+
default: 180000,
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'retryBackoffMs',
|
|
382
|
+
type: 'number',
|
|
383
|
+
required: false,
|
|
384
|
+
description: 'Base retry backoff in ms (default: 500).',
|
|
385
|
+
default: 500,
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'jobTimeoutMs',
|
|
389
|
+
type: 'number',
|
|
390
|
+
required: false,
|
|
391
|
+
description: 'Max wall time for the audit job (default 30m, cap 4h).',
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: 'maxFilesPerRun',
|
|
395
|
+
type: 'number',
|
|
396
|
+
required: false,
|
|
397
|
+
description: 'Per worker chunk size for file processing.',
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: 'shardSoftBudgetMs',
|
|
401
|
+
type: 'number',
|
|
402
|
+
required: false,
|
|
403
|
+
description: 'Per worker soft wall-clock budget.',
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: 'generateCodeMap',
|
|
407
|
+
type: 'boolean',
|
|
408
|
+
required: false,
|
|
409
|
+
description: 'Generate code map artifacts during audit (default: false).',
|
|
410
|
+
default: false,
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
handler: async (args, signal) => {
|
|
414
|
+
return withAbortSignal(signal, 'audit.start', () => startAuditJob(args, {
|
|
415
|
+
defaultAnalyzers: DEFAULT_ANALYZERS,
|
|
416
|
+
defaultMinSeverity: 'warning',
|
|
417
|
+
defaultGenerateCodeMap: false,
|
|
418
|
+
}));
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: 'status',
|
|
423
|
+
description: 'Get current status for a previously started background audit job.',
|
|
424
|
+
parameters: [
|
|
425
|
+
{
|
|
426
|
+
name: 'jobId',
|
|
427
|
+
type: 'string',
|
|
428
|
+
required: true,
|
|
429
|
+
description: 'Job ID returned by `audit start`.',
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
handler: async (args) => {
|
|
433
|
+
const jobId = args.jobId;
|
|
434
|
+
if (!jobId)
|
|
435
|
+
throw new Error('audit.status requires jobId');
|
|
436
|
+
return getAuditJobStatus(jobId);
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: 'results',
|
|
441
|
+
description: 'Fetch paginated violations for a completed audit result by resultId.',
|
|
442
|
+
parameters: [
|
|
443
|
+
{
|
|
444
|
+
name: 'resultId',
|
|
445
|
+
type: 'string',
|
|
446
|
+
required: false,
|
|
447
|
+
description: 'Result ID returned by `audit status` when completed. Also accepts legacy `auditId`.',
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: 'auditId',
|
|
451
|
+
type: 'string',
|
|
452
|
+
required: false,
|
|
453
|
+
description: 'Backward-compatible alias for resultId.',
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: 'limit',
|
|
457
|
+
type: 'number',
|
|
458
|
+
required: false,
|
|
459
|
+
description: 'Maximum violations per page (default: 50, max: 100).',
|
|
460
|
+
default: 50,
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: 'offset',
|
|
464
|
+
type: 'number',
|
|
465
|
+
required: false,
|
|
466
|
+
description: 'Violation offset for pagination (default: 0).',
|
|
467
|
+
default: 0,
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: 'format',
|
|
471
|
+
type: 'string',
|
|
472
|
+
required: false,
|
|
473
|
+
description: 'Output format. "json" (default, structured data) or "sarif" (SARIF 2.1.0 JSON string).',
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
handler: async (args, signal) => {
|
|
477
|
+
const format = args.format || 'json';
|
|
478
|
+
if (format === 'sarif') {
|
|
479
|
+
return withAbortSignal(signal, 'audit.results', () => getAuditResultsAsSarif(args));
|
|
480
|
+
}
|
|
481
|
+
return withAbortSignal(signal, 'audit.results', () => getAuditResultsPage(args));
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'health',
|
|
486
|
+
description: 'Quick health check of a codebase with key metrics and optional code map generation.',
|
|
487
|
+
parameters: [
|
|
488
|
+
{
|
|
489
|
+
name: 'path',
|
|
490
|
+
type: 'string',
|
|
491
|
+
required: false,
|
|
492
|
+
description: 'The directory path to check.',
|
|
493
|
+
default: process.cwd(),
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: 'threshold',
|
|
497
|
+
type: 'number',
|
|
498
|
+
required: false,
|
|
499
|
+
description: 'Health score threshold (0-100) for pass/fail.',
|
|
500
|
+
default: 70,
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'indexFunctions',
|
|
504
|
+
type: 'boolean',
|
|
505
|
+
required: false,
|
|
506
|
+
description: 'Automatically index functions during health check.',
|
|
507
|
+
default: true,
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
name: 'analyzerConfigs',
|
|
511
|
+
type: 'object',
|
|
512
|
+
required: false,
|
|
513
|
+
description: 'Analyzer-specific configuration overrides.',
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: 'generateCodeMap',
|
|
517
|
+
type: 'boolean',
|
|
518
|
+
required: false,
|
|
519
|
+
description: 'Generate and return a human-readable code map.',
|
|
520
|
+
default: true,
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
handler: async (args) => {
|
|
524
|
+
const auditPath = path.resolve(args.path || process.cwd());
|
|
525
|
+
await assertAuditPathExists(auditPath);
|
|
526
|
+
const threshold = args.threshold || 70;
|
|
527
|
+
const indexFunctions = args.indexFunctions !== false;
|
|
528
|
+
const generateCodeMap = args.generateCodeMap !== false;
|
|
529
|
+
const db = CodeIndexDB.getInstance();
|
|
530
|
+
await db.initialize();
|
|
531
|
+
const storedConfigs = await db.getAllAnalyzerConfigs(auditPath);
|
|
532
|
+
const analyzerConfigs = {
|
|
533
|
+
...storedConfigs,
|
|
534
|
+
...(args.analyzerConfigs || {}),
|
|
535
|
+
};
|
|
536
|
+
const runner = createAuditRunner({
|
|
537
|
+
projectRoot: auditPath,
|
|
538
|
+
enabledAnalyzers: DEFAULT_ANALYZERS,
|
|
539
|
+
minSeverity: 'warning',
|
|
540
|
+
verbose: false,
|
|
541
|
+
indexFunctions,
|
|
542
|
+
...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }),
|
|
543
|
+
progressCallback: (p) => {
|
|
544
|
+
if (p.phase === 'function-indexing' &&
|
|
545
|
+
typeof p.current === 'number' &&
|
|
546
|
+
typeof p.total === 'number' &&
|
|
547
|
+
p.total > 0 &&
|
|
548
|
+
p.current % 50 !== 0 &&
|
|
549
|
+
p.current !== p.total)
|
|
550
|
+
return;
|
|
551
|
+
logMcpDebug('audit.health', p.message ?? p.phase ?? 'progress', {
|
|
552
|
+
phase: p.phase,
|
|
553
|
+
analyzer: p.analyzer,
|
|
554
|
+
current: p.current,
|
|
555
|
+
total: p.total,
|
|
556
|
+
});
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
const auditResult = await runner.run();
|
|
560
|
+
const healthScore = calculateHealthScore(auditResult);
|
|
561
|
+
let indexingResult = null;
|
|
562
|
+
if (indexFunctions && auditResult.metadata.fileToFunctionsMap) {
|
|
563
|
+
try {
|
|
564
|
+
const syncStats = { added: 0, updated: 0, removed: 0 };
|
|
565
|
+
for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) {
|
|
566
|
+
const fileStats = await syncFileIndex(filePath, functions);
|
|
567
|
+
syncStats.added += fileStats.added;
|
|
568
|
+
syncStats.updated += fileStats.updated;
|
|
569
|
+
syncStats.removed += fileStats.removed;
|
|
759
570
|
}
|
|
571
|
+
indexingResult = {
|
|
572
|
+
success: true,
|
|
573
|
+
registered: syncStats.added + syncStats.updated,
|
|
574
|
+
failed: 0,
|
|
575
|
+
syncStats,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// best-effort
|
|
760
580
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
581
|
+
}
|
|
582
|
+
let codeMapResult = null;
|
|
583
|
+
if (generateCodeMap && indexingResult && indexingResult.success) {
|
|
584
|
+
try {
|
|
585
|
+
const mapGenerator = new CodeMapGenerator();
|
|
586
|
+
const mapOptions = {
|
|
587
|
+
includeComplexity: true,
|
|
588
|
+
includeDocumentation: true,
|
|
589
|
+
includeDependencies: true,
|
|
590
|
+
includeUsage: false,
|
|
591
|
+
groupByDirectory: true,
|
|
592
|
+
maxDepth: 8,
|
|
593
|
+
showUnusedImports: true,
|
|
594
|
+
minComplexity: 7,
|
|
595
|
+
};
|
|
596
|
+
let documentation;
|
|
764
597
|
try {
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
includeDependencies: true,
|
|
770
|
-
includeUsage: false,
|
|
771
|
-
groupByDirectory: true,
|
|
772
|
-
maxDepth: 8, // Slightly smaller for health check
|
|
773
|
-
showUnusedImports: true,
|
|
774
|
-
minComplexity: 7,
|
|
775
|
-
};
|
|
776
|
-
// Generate documentation metrics
|
|
777
|
-
let documentation = undefined;
|
|
778
|
-
try {
|
|
779
|
-
// Use existing file discovery from audit result
|
|
780
|
-
const files = Object.keys(auditResult.metadata.fileToFunctionsMap || {});
|
|
781
|
-
if (files.length > 0) {
|
|
782
|
-
const docResult = await analyzeDocumentation(files);
|
|
783
|
-
documentation = docResult.metrics;
|
|
784
|
-
}
|
|
598
|
+
const files = Object.keys(auditResult.metadata.fileToFunctionsMap || {});
|
|
599
|
+
if (files.length > 0) {
|
|
600
|
+
const docResult = await analyzeDocumentation(files);
|
|
601
|
+
documentation = docResult.metrics;
|
|
785
602
|
}
|
|
786
|
-
catch (docError) {
|
|
787
|
-
console.error(chalk.yellow('[WARN]'), 'Failed to analyze documentation:', docError);
|
|
788
|
-
}
|
|
789
|
-
// Use paginated code map generation for health check too
|
|
790
|
-
const paginatedResult = await mapGenerator.generatePaginatedCodeMap(auditPath, {
|
|
791
|
-
...mapOptions,
|
|
792
|
-
includeDocumentation: !!documentation
|
|
793
|
-
});
|
|
794
|
-
codeMapResult = {
|
|
795
|
-
success: true,
|
|
796
|
-
mapId: paginatedResult.mapId,
|
|
797
|
-
summary: paginatedResult.summary,
|
|
798
|
-
quickPreview: paginatedResult.quickPreview,
|
|
799
|
-
sections: paginatedResult.summary.sectionsAvailable,
|
|
800
|
-
documentationCoverage: documentation?.coverageScore
|
|
801
|
-
};
|
|
802
|
-
mcpDebugStderr(chalk.blue('[INFO]'), `Generated paginated code map: ${paginatedResult.summary.stats.totalFiles} files, ${paginatedResult.summary.totalSections} sections`);
|
|
803
603
|
}
|
|
804
|
-
catch
|
|
805
|
-
|
|
806
|
-
codeMapResult = {
|
|
807
|
-
success: false,
|
|
808
|
-
error: error instanceof Error ? error.message : 'Failed to generate code map'
|
|
809
|
-
};
|
|
604
|
+
catch {
|
|
605
|
+
// best-effort
|
|
810
606
|
}
|
|
607
|
+
const paginatedResult = await mapGenerator.generatePaginatedCodeMap(auditPath, {
|
|
608
|
+
...mapOptions,
|
|
609
|
+
includeDocumentation: !!documentation,
|
|
610
|
+
});
|
|
611
|
+
codeMapResult = {
|
|
612
|
+
success: true,
|
|
613
|
+
mapId: paginatedResult.mapId,
|
|
614
|
+
summary: paginatedResult.summary,
|
|
615
|
+
quickPreview: paginatedResult.quickPreview,
|
|
616
|
+
sections: paginatedResult.summary.sectionsAvailable,
|
|
617
|
+
documentationCoverage: documentation?.coverageScore,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
codeMapResult = {
|
|
622
|
+
success: false,
|
|
623
|
+
error: error instanceof Error ? error.message : 'Failed to generate code map',
|
|
624
|
+
};
|
|
811
625
|
}
|
|
812
|
-
result = {
|
|
813
|
-
healthScore,
|
|
814
|
-
threshold,
|
|
815
|
-
passed: healthScore >= threshold,
|
|
816
|
-
status: healthScore >= threshold ? 'healthy' : 'needs-attention',
|
|
817
|
-
metrics: {
|
|
818
|
-
filesAnalyzed: auditResult.metadata.filesAnalyzed,
|
|
819
|
-
totalViolations: auditResult.summary.totalViolations,
|
|
820
|
-
criticalViolations: auditResult.summary.criticalIssues,
|
|
821
|
-
warningViolations: auditResult.summary.warnings,
|
|
822
|
-
},
|
|
823
|
-
recommendation: getHealthRecommendation(healthScore, auditResult),
|
|
824
|
-
...(indexingResult && { functionIndexing: indexingResult }),
|
|
825
|
-
...(codeMapResult && { codeMap: codeMapResult }),
|
|
826
|
-
};
|
|
827
|
-
break;
|
|
828
626
|
}
|
|
829
|
-
|
|
627
|
+
return {
|
|
628
|
+
healthScore,
|
|
629
|
+
threshold,
|
|
630
|
+
passed: healthScore >= threshold,
|
|
631
|
+
status: healthScore >= threshold ? 'healthy' : 'needs-attention',
|
|
632
|
+
metrics: {
|
|
633
|
+
filesAnalyzed: auditResult.metadata.filesAnalyzed,
|
|
634
|
+
totalViolations: auditResult.summary.totalViolations,
|
|
635
|
+
criticalViolations: auditResult.summary.criticalIssues,
|
|
636
|
+
warningViolations: auditResult.summary.warnings,
|
|
637
|
+
},
|
|
638
|
+
recommendation: getHealthRecommendation(healthScore, auditResult),
|
|
639
|
+
...(indexingResult && { functionIndexing: indexingResult }),
|
|
640
|
+
...(codeMapResult && { codeMap: codeMapResult }),
|
|
641
|
+
};
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
];
|
|
645
|
+
registry.register({
|
|
646
|
+
name: 'audit',
|
|
647
|
+
description: 'Run code quality audits (synchronous or background), check status, fetch paginated results, and get health checks. Actions: run (sync audit, returns full results), start (background job), status (poll a job), results (paginated violations), health (score + code map).',
|
|
648
|
+
actions: auditActions,
|
|
649
|
+
});
|
|
650
|
+
// ── search ─────────────────────────────────────────────────────────────────
|
|
651
|
+
registry.register({
|
|
652
|
+
name: 'search',
|
|
653
|
+
description: 'Search indexed functions and components. Actions: query (natural language + operators like entity:, component:, hook:, dep:, calls:, lang:, complexity:, exported:, jsdoc:, file:, unused-imports, name:), definition (exact function/component lookup).',
|
|
654
|
+
actions: [
|
|
655
|
+
{
|
|
656
|
+
name: 'query',
|
|
657
|
+
description: 'Search indexed functions with natural language queries and filter operators.',
|
|
658
|
+
parameters: [
|
|
659
|
+
{
|
|
660
|
+
name: 'query',
|
|
661
|
+
type: 'string',
|
|
662
|
+
required: true,
|
|
663
|
+
description: 'Search query with natural language and/or operators. Examples: "Button component:functional", "calls:validateUser", "unused-imports".',
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: 'filters',
|
|
667
|
+
type: 'object',
|
|
668
|
+
required: false,
|
|
669
|
+
description: 'Optional filters (language, filePath, dependencies, componentType, entityType, searchMode).',
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: 'limit',
|
|
673
|
+
type: 'number',
|
|
674
|
+
required: false,
|
|
675
|
+
description: 'Maximum results to return.',
|
|
676
|
+
default: 50,
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: 'offset',
|
|
680
|
+
type: 'number',
|
|
681
|
+
required: false,
|
|
682
|
+
description: 'Offset for pagination.',
|
|
683
|
+
default: 0,
|
|
684
|
+
},
|
|
685
|
+
],
|
|
686
|
+
handler: async (args) => {
|
|
830
687
|
const query = args.query;
|
|
831
688
|
const filters = args.filters;
|
|
832
689
|
const limit = args.limit || 50;
|
|
833
690
|
const offset = args.offset || 0;
|
|
834
|
-
|
|
835
|
-
if (query !== undefined && typeof query !== 'string') {
|
|
691
|
+
if (query !== undefined && typeof query !== 'string')
|
|
836
692
|
throw new Error('query must be a string');
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
693
|
+
return searchFunctions({ query, filters, limit, offset });
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
name: 'definition',
|
|
698
|
+
description: 'Find the exact definition of a specific function or React component.',
|
|
699
|
+
parameters: [
|
|
700
|
+
{
|
|
701
|
+
name: 'name',
|
|
702
|
+
type: 'string',
|
|
703
|
+
required: true,
|
|
704
|
+
description: 'Function or component name to find.',
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: 'filePath',
|
|
708
|
+
type: 'string',
|
|
709
|
+
required: false,
|
|
710
|
+
description: 'Optional file path to narrow search.',
|
|
711
|
+
},
|
|
712
|
+
],
|
|
713
|
+
handler: async (args) => {
|
|
847
714
|
const name = args.name;
|
|
848
715
|
const filePath = args.filePath;
|
|
849
|
-
if (!name || typeof name !== 'string')
|
|
716
|
+
if (!name || typeof name !== 'string')
|
|
850
717
|
throw new Error('name must be a non-empty string');
|
|
851
|
-
}
|
|
852
718
|
const definition = await findDefinition(name, filePath);
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
719
|
+
return definition || { error: 'Function not found' };
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
],
|
|
723
|
+
});
|
|
724
|
+
// ── index ──────────────────────────────────────────────────────────────────
|
|
725
|
+
registry.register({
|
|
726
|
+
name: 'index',
|
|
727
|
+
description: 'Manage the code index. Actions: sync (update from files with optional auto-whitelist), cleanup (remove deleted files), reset (clear analysis data, preserve tasks/config/whitelist), status (index statistics).',
|
|
728
|
+
actions: [
|
|
729
|
+
{
|
|
730
|
+
name: 'sync',
|
|
731
|
+
description: 'Synchronize index from filesystem. Discovers TypeScript/JavaScript functions and updates the index.',
|
|
732
|
+
parameters: [
|
|
733
|
+
{
|
|
734
|
+
name: 'path',
|
|
735
|
+
type: 'string',
|
|
736
|
+
required: false,
|
|
737
|
+
description: 'Optional specific path to sync. Omit for full deep sync.',
|
|
738
|
+
},
|
|
739
|
+
],
|
|
740
|
+
handler: async (args) => {
|
|
741
|
+
const targetPath = args.path;
|
|
742
|
+
const db = CodeIndexDB.getInstance();
|
|
743
|
+
await db.initialize();
|
|
744
|
+
if (targetPath) {
|
|
745
|
+
const syncResult = await db.synchronizeFile(path.resolve(targetPath));
|
|
746
|
+
return {
|
|
747
|
+
mode: 'sync',
|
|
748
|
+
success: true,
|
|
749
|
+
path: targetPath,
|
|
750
|
+
...(syncResult || { message: 'File not found' }),
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const syncResult = await db.deepSync();
|
|
754
|
+
const result = {
|
|
755
|
+
mode: 'sync',
|
|
756
|
+
success: true,
|
|
757
|
+
syncedFiles: syncResult.syncedFiles,
|
|
758
|
+
addedFunctions: syncResult.addedFunctions,
|
|
759
|
+
updatedFunctions: syncResult.updatedFunctions,
|
|
760
|
+
removedFunctions: syncResult.removedFunctions,
|
|
761
|
+
errors: syncResult.errors,
|
|
762
|
+
message: `Synced ${syncResult.syncedFiles} files: ${syncResult.addedFunctions} added, ${syncResult.updatedFunctions} updated, ${syncResult.removedFunctions} removed`,
|
|
763
|
+
};
|
|
764
|
+
// Auto-detect and populate whitelist entries (from standalone)
|
|
765
|
+
try {
|
|
766
|
+
const { WhitelistService } = await import('./services/whitelistService.js');
|
|
767
|
+
const whitelistService = WhitelistService.getInstance();
|
|
768
|
+
const whitelistResult = await whitelistService.whitelistAllDependencies(targetPath || process.cwd());
|
|
769
|
+
if (whitelistResult.added > 0) {
|
|
770
|
+
result.whitelistAdded = whitelistResult.added;
|
|
771
|
+
result.whitelistMessage = `Auto-added ${whitelistResult.added} whitelist entries`;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
// whitelist detection is best-effort
|
|
776
|
+
}
|
|
777
|
+
return result;
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: 'cleanup',
|
|
782
|
+
description: 'Remove index entries for files that no longer exist on disk.',
|
|
783
|
+
parameters: [],
|
|
784
|
+
handler: async () => {
|
|
785
|
+
const db = CodeIndexDB.getInstance();
|
|
786
|
+
await db.initialize();
|
|
787
|
+
const cleanupResult = await db.bulkCleanup();
|
|
788
|
+
return {
|
|
789
|
+
mode: 'cleanup',
|
|
790
|
+
success: true,
|
|
791
|
+
scannedFiles: cleanupResult.scannedCount,
|
|
792
|
+
removedEntries: cleanupResult.removedCount,
|
|
793
|
+
removedFiles: cleanupResult.removedFiles,
|
|
794
|
+
errors: cleanupResult.errors,
|
|
795
|
+
message: `Cleaned up ${cleanupResult.removedCount} entries from ${cleanupResult.removedFiles.length} deleted files`,
|
|
796
|
+
};
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
name: 'reset',
|
|
801
|
+
description: 'Clear all analysis-derived data (functions, search index, cached audits, code maps, schemas). Project tasks, whitelist, and analyzer configs are preserved.',
|
|
802
|
+
parameters: [],
|
|
803
|
+
handler: async () => {
|
|
804
|
+
const db = CodeIndexDB.getInstance();
|
|
805
|
+
await db.initialize();
|
|
806
|
+
await db.clearIndex();
|
|
807
|
+
return {
|
|
808
|
+
mode: 'reset',
|
|
809
|
+
success: true,
|
|
810
|
+
message: 'Analysis-derived data cleared (functions, search index, cached audits, code maps, schemas). Project tasks, whitelist, and analyzer configs were preserved.',
|
|
811
|
+
};
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
name: 'status',
|
|
816
|
+
description: 'Get index statistics (function count, file count, last sync time).',
|
|
817
|
+
parameters: [],
|
|
818
|
+
handler: async () => {
|
|
819
|
+
const db = CodeIndexDB.getInstance();
|
|
820
|
+
await db.initialize();
|
|
821
|
+
const funcs = await db.getAllFunctions();
|
|
822
|
+
return {
|
|
823
|
+
success: true,
|
|
824
|
+
functionCount: Array.isArray(funcs) ? funcs.length : 0,
|
|
825
|
+
};
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
],
|
|
829
|
+
});
|
|
830
|
+
// ── config ─────────────────────────────────────────────────────────────────
|
|
831
|
+
registry.register({
|
|
832
|
+
name: 'config',
|
|
833
|
+
description: 'Manage analyzer configurations, generate AI tool configs, manage whitelists, and manage invariant rules. Actions: get, set, reset (analyzer configs), generate (AI tool config files), whitelist_list, whitelist_add, whitelist_update, whitelist_detect, rules_list, rules_check.',
|
|
834
|
+
actions: [
|
|
835
|
+
{
|
|
836
|
+
name: 'get',
|
|
837
|
+
description: 'Get current configuration for an analyzer (or all).',
|
|
838
|
+
parameters: [
|
|
839
|
+
{
|
|
840
|
+
name: 'analyzerName',
|
|
841
|
+
type: 'string',
|
|
842
|
+
required: false,
|
|
843
|
+
description: 'Specific analyzer name, or omit to get all configs.',
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
name: 'projectPath',
|
|
847
|
+
type: 'string',
|
|
848
|
+
required: false,
|
|
849
|
+
description: 'Optional project path for project-specific config.',
|
|
850
|
+
},
|
|
851
|
+
],
|
|
852
|
+
handler: async (args) => {
|
|
853
|
+
const analyzerName = args.analyzerName;
|
|
854
|
+
const projectPath = args.projectPath;
|
|
855
|
+
const db = CodeIndexDB.getInstance();
|
|
856
|
+
await db.initialize();
|
|
857
|
+
if (analyzerName) {
|
|
858
|
+
const config = await db.getAnalyzerConfig(analyzerName, projectPath);
|
|
859
|
+
return {
|
|
860
|
+
success: true,
|
|
861
|
+
analyzer: analyzerName,
|
|
862
|
+
config: config || null,
|
|
863
|
+
scope: projectPath ? 'project' : 'global',
|
|
864
|
+
message: config ? 'Configuration found' : 'No custom configuration found, using defaults',
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
const configs = await db.getAllAnalyzerConfigs(projectPath);
|
|
868
|
+
return {
|
|
869
|
+
success: true,
|
|
870
|
+
configs,
|
|
871
|
+
scope: projectPath ? 'project' : 'global',
|
|
872
|
+
message: `Found ${Object.keys(configs).length} analyzer configurations`,
|
|
873
|
+
};
|
|
874
|
+
},
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: 'set',
|
|
878
|
+
description: 'Set or update analyzer configuration that persists across audit runs.',
|
|
879
|
+
parameters: [
|
|
880
|
+
{
|
|
881
|
+
name: 'analyzerName',
|
|
882
|
+
type: 'string',
|
|
883
|
+
required: true,
|
|
884
|
+
description: 'The analyzer to configure (solid, dry, security, etc.).',
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
name: 'config',
|
|
888
|
+
type: 'object',
|
|
889
|
+
required: true,
|
|
890
|
+
description: 'Configuration object for the analyzer (e.g., thresholds, rules).',
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
name: 'projectPath',
|
|
894
|
+
type: 'string',
|
|
895
|
+
required: false,
|
|
896
|
+
description: 'Optional project path for project-specific config.',
|
|
897
|
+
},
|
|
898
|
+
],
|
|
899
|
+
handler: async (args) => {
|
|
900
|
+
const analyzerName = args.analyzerName;
|
|
901
|
+
const config = args.config;
|
|
902
|
+
const projectPath = args.projectPath;
|
|
903
|
+
const db = CodeIndexDB.getInstance();
|
|
904
|
+
await db.initialize();
|
|
905
|
+
await db.storeAnalyzerConfig(analyzerName, config, {
|
|
906
|
+
projectPath,
|
|
907
|
+
isGlobal: !projectPath,
|
|
908
|
+
});
|
|
909
|
+
return {
|
|
910
|
+
success: true,
|
|
911
|
+
message: `Configuration for ${analyzerName} analyzer has been saved${projectPath ? ` for project ${projectPath}` : ' globally'}`,
|
|
912
|
+
analyzer: analyzerName,
|
|
913
|
+
scope: projectPath ? 'project' : 'global',
|
|
914
|
+
config,
|
|
915
|
+
};
|
|
916
|
+
},
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
name: 'reset',
|
|
920
|
+
description: 'Reset analyzer configuration to defaults.',
|
|
921
|
+
parameters: [
|
|
922
|
+
{
|
|
923
|
+
name: 'analyzerName',
|
|
924
|
+
type: 'string',
|
|
925
|
+
required: false,
|
|
926
|
+
description: 'Specific analyzer to reset, or omit to reset all.',
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
name: 'projectPath',
|
|
930
|
+
type: 'string',
|
|
931
|
+
required: false,
|
|
932
|
+
description: 'Optional project path to reset only project-specific config.',
|
|
933
|
+
},
|
|
934
|
+
],
|
|
935
|
+
handler: async (args) => {
|
|
936
|
+
const analyzerName = args.analyzerName;
|
|
937
|
+
const projectPath = args.projectPath;
|
|
938
|
+
const db = CodeIndexDB.getInstance();
|
|
939
|
+
await db.initialize();
|
|
940
|
+
if (analyzerName) {
|
|
941
|
+
const deleted = await db.deleteAnalyzerConfig(analyzerName, {
|
|
942
|
+
projectPath,
|
|
943
|
+
isGlobal: !projectPath,
|
|
944
|
+
});
|
|
945
|
+
return {
|
|
946
|
+
success: deleted,
|
|
947
|
+
message: deleted
|
|
948
|
+
? `Configuration for ${analyzerName} analyzer has been reset${projectPath ? ` for project ${projectPath}` : ' globally'}`
|
|
949
|
+
: `No configuration found for ${analyzerName} analyzer`,
|
|
950
|
+
analyzer: analyzerName,
|
|
951
|
+
scope: projectPath ? 'project' : 'global',
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
await db.resetAnalyzerConfigs(projectPath);
|
|
955
|
+
return {
|
|
956
|
+
success: true,
|
|
957
|
+
message: projectPath
|
|
958
|
+
? `All project-specific configurations for ${projectPath} have been reset`
|
|
959
|
+
: 'All analyzer configurations have been reset to defaults',
|
|
960
|
+
scope: projectPath ? 'project' : 'global',
|
|
961
|
+
};
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
name: 'generate',
|
|
966
|
+
description: 'Generate configuration files for AI coding assistants (Cursor, Claude, Copilot, etc.).',
|
|
967
|
+
parameters: [
|
|
968
|
+
{
|
|
969
|
+
name: 'tools',
|
|
970
|
+
type: 'array',
|
|
971
|
+
required: true,
|
|
972
|
+
description: 'AI tools to configure (cursor, continue, copilot, claude, zed, windsurf, cody, aider, cline, pearai).',
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
name: 'outputDir',
|
|
976
|
+
type: 'string',
|
|
977
|
+
required: false,
|
|
978
|
+
description: 'Output directory for configuration files.',
|
|
979
|
+
default: '.',
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
name: 'serverUrl',
|
|
983
|
+
type: 'string',
|
|
984
|
+
required: false,
|
|
985
|
+
description: 'MCP server URL (default: auto-detected).',
|
|
986
|
+
default: DEFAULT_SERVER_URL,
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
name: 'overwrite',
|
|
990
|
+
type: 'boolean',
|
|
991
|
+
required: false,
|
|
992
|
+
description: 'Overwrite existing files (default: false).',
|
|
993
|
+
default: false,
|
|
994
|
+
},
|
|
995
|
+
],
|
|
996
|
+
handler: async (args) => {
|
|
857
997
|
const tools = args.tools;
|
|
858
998
|
const serverUrl = args.serverUrl || DEFAULT_SERVER_URL;
|
|
859
999
|
const outputDir = args.outputDir || '.';
|
|
860
1000
|
const overwrite = args.overwrite || false;
|
|
861
|
-
if (!Array.isArray(tools) || tools.length === 0)
|
|
1001
|
+
if (!Array.isArray(tools) || tools.length === 0)
|
|
862
1002
|
throw new Error('tools parameter must be a non-empty array');
|
|
863
|
-
}
|
|
864
1003
|
const factory = new ConfigGeneratorFactory(serverUrl);
|
|
865
1004
|
const generatedFiles = [];
|
|
866
1005
|
const errors = [];
|
|
@@ -873,326 +1012,566 @@ async function startMcpServer() {
|
|
|
873
1012
|
}
|
|
874
1013
|
const config = generator.generateConfig();
|
|
875
1014
|
const outputPath = path.resolve(outputDir, config.filename);
|
|
876
|
-
// Check if file exists
|
|
877
1015
|
let fileExists = false;
|
|
878
1016
|
try {
|
|
879
1017
|
await fs.access(outputPath);
|
|
880
1018
|
fileExists = true;
|
|
881
1019
|
}
|
|
882
1020
|
catch {
|
|
883
|
-
//
|
|
1021
|
+
// doesn't exist
|
|
884
1022
|
}
|
|
885
1023
|
if (fileExists && !overwrite) {
|
|
886
1024
|
errors.push(`File already exists: ${config.filename} (use overwrite: true to replace)`);
|
|
887
1025
|
continue;
|
|
888
1026
|
}
|
|
889
|
-
// Ensure directory exists
|
|
890
1027
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
891
|
-
// Write main config file
|
|
892
1028
|
await fs.writeFile(outputPath, config.content);
|
|
893
1029
|
generatedFiles.push(config.filename);
|
|
894
|
-
// Write additional files if any
|
|
895
1030
|
if (config.additionalFiles) {
|
|
896
1031
|
for (const additionalFile of config.additionalFiles) {
|
|
897
1032
|
const additionalPath = path.resolve(outputDir, additionalFile.filename);
|
|
898
1033
|
await fs.mkdir(path.dirname(additionalPath), { recursive: true });
|
|
899
1034
|
await fs.writeFile(additionalPath, additionalFile.content);
|
|
900
1035
|
generatedFiles.push(additionalFile.filename);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
catch (error) {
|
|
905
|
-
errors.push(`Failed to generate config for ${tool}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
result = {
|
|
909
|
-
success: errors.length === 0,
|
|
910
|
-
generatedFiles,
|
|
911
|
-
errors: errors.length > 0 ? errors : undefined,
|
|
912
|
-
totalRequested: tools.length,
|
|
913
|
-
totalGenerated: generatedFiles.length
|
|
914
|
-
};
|
|
915
|
-
break;
|
|
916
|
-
}
|
|
917
|
-
case 'get_workflow_guide': {
|
|
918
|
-
const scenario = args.scenario;
|
|
919
|
-
const { getWorkflowGuide, getWorkflowTips } = await import('./mcp-tools/workflowGuide.js');
|
|
920
|
-
try {
|
|
921
|
-
const workflows = getWorkflowGuide(scenario);
|
|
922
|
-
const tips = getWorkflowTips();
|
|
923
|
-
result = {
|
|
924
|
-
success: true,
|
|
925
|
-
...(scenario ? { workflow: workflows } : { workflows }),
|
|
926
|
-
tips
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
catch (error) {
|
|
930
|
-
result = {
|
|
931
|
-
success: false,
|
|
932
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
933
|
-
availableScenarios: ['initial-setup', 'react-development', 'code-review', 'find-patterns', 'maintenance', 'analyzer-configuration']
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
break;
|
|
937
|
-
}
|
|
938
|
-
case 'sync_index': {
|
|
939
|
-
const mode = args.mode || 'sync';
|
|
940
|
-
const targetPath = args.path;
|
|
941
|
-
const db = CodeIndexDB.getInstance();
|
|
942
|
-
await db.initialize();
|
|
943
|
-
switch (mode) {
|
|
944
|
-
case 'cleanup': {
|
|
945
|
-
const cleanupResult = await db.bulkCleanup();
|
|
946
|
-
result = {
|
|
947
|
-
mode: 'cleanup',
|
|
948
|
-
success: true,
|
|
949
|
-
scannedFiles: cleanupResult.scannedCount,
|
|
950
|
-
removedEntries: cleanupResult.removedCount,
|
|
951
|
-
removedFiles: cleanupResult.removedFiles,
|
|
952
|
-
errors: cleanupResult.errors,
|
|
953
|
-
message: `Cleaned up ${cleanupResult.removedCount} entries from ${cleanupResult.removedFiles.length} deleted files`
|
|
954
|
-
};
|
|
955
|
-
break;
|
|
956
|
-
}
|
|
957
|
-
case 'reset': {
|
|
958
|
-
await db.clearIndex();
|
|
959
|
-
result = {
|
|
960
|
-
mode: 'reset',
|
|
961
|
-
success: true,
|
|
962
|
-
message: 'Analysis-derived data cleared (functions, search index, cached audits, code maps, schemas). Project tasks, whitelist, and analyzer configs were preserved.'
|
|
963
|
-
};
|
|
964
|
-
break;
|
|
965
|
-
}
|
|
966
|
-
case 'sync':
|
|
967
|
-
default: {
|
|
968
|
-
if (targetPath) {
|
|
969
|
-
// Sync specific file
|
|
970
|
-
const syncResult = await db.synchronizeFile(path.resolve(targetPath));
|
|
971
|
-
result = {
|
|
972
|
-
mode: 'sync',
|
|
973
|
-
success: true,
|
|
974
|
-
path: targetPath,
|
|
975
|
-
...(syncResult || { message: 'File not found' })
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
else {
|
|
979
|
-
// Deep sync all files
|
|
980
|
-
const syncResult = await db.deepSync();
|
|
981
|
-
result = {
|
|
982
|
-
mode: 'sync',
|
|
983
|
-
success: true,
|
|
984
|
-
syncedFiles: syncResult.syncedFiles,
|
|
985
|
-
addedFunctions: syncResult.addedFunctions,
|
|
986
|
-
updatedFunctions: syncResult.updatedFunctions,
|
|
987
|
-
removedFunctions: syncResult.removedFunctions,
|
|
988
|
-
errors: syncResult.errors,
|
|
989
|
-
message: `Synced ${syncResult.syncedFiles} files: ${syncResult.addedFunctions} added, ${syncResult.updatedFunctions} updated, ${syncResult.removedFunctions} removed`
|
|
990
|
-
};
|
|
1036
|
+
}
|
|
991
1037
|
}
|
|
992
|
-
|
|
1038
|
+
}
|
|
1039
|
+
catch (error) {
|
|
1040
|
+
errors.push(`Failed to generate config for ${tool}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
993
1041
|
}
|
|
994
1042
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1043
|
+
return {
|
|
1044
|
+
success: errors.length === 0,
|
|
1045
|
+
generatedFiles,
|
|
1046
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
1047
|
+
totalRequested: tools.length,
|
|
1048
|
+
totalGenerated: generatedFiles.length,
|
|
1049
|
+
};
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
name: 'whitelist_list',
|
|
1054
|
+
description: 'Get current whitelist entries for dependency and class instantiation checks.',
|
|
1055
|
+
parameters: [
|
|
1056
|
+
{
|
|
1057
|
+
name: 'type',
|
|
1058
|
+
type: 'string',
|
|
1059
|
+
required: false,
|
|
1060
|
+
description: 'Filter by type: platform-api, framework-class, project-dep, shared-library, node-builtin.',
|
|
1061
|
+
enum: ['platform-api', 'framework-class', 'project-dep', 'shared-library', 'node-builtin'],
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
name: 'status',
|
|
1065
|
+
type: 'string',
|
|
1066
|
+
required: false,
|
|
1067
|
+
description: 'Filter by status: active, pending, rejected, disabled.',
|
|
1068
|
+
enum: ['active', 'pending', 'rejected', 'disabled'],
|
|
1069
|
+
},
|
|
1070
|
+
],
|
|
1071
|
+
handler: async (args) => {
|
|
1072
|
+
const { handleWhitelistGet } = await import('./mcp-tools/whitelistTools.js');
|
|
1073
|
+
return handleWhitelistGet(args);
|
|
1074
|
+
},
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
name: 'whitelist_add',
|
|
1078
|
+
description: 'Add a new entry to the whitelist.',
|
|
1079
|
+
parameters: [
|
|
1080
|
+
{
|
|
1081
|
+
name: 'name',
|
|
1082
|
+
type: 'string',
|
|
1083
|
+
required: true,
|
|
1084
|
+
description: 'Class name or import path to whitelist.',
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
name: 'type',
|
|
1088
|
+
type: 'string',
|
|
1089
|
+
required: true,
|
|
1090
|
+
description: 'Type of whitelist entry.',
|
|
1091
|
+
enum: ['platform-api', 'framework-class', 'project-dep', 'shared-library', 'node-builtin'],
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
name: 'description',
|
|
1095
|
+
type: 'string',
|
|
1096
|
+
required: false,
|
|
1097
|
+
description: 'Explanation of why this is whitelisted.',
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
name: 'patterns',
|
|
1101
|
+
type: 'array',
|
|
1102
|
+
required: false,
|
|
1103
|
+
description: 'Additional patterns to match (e.g., ["fs/*", "node:fs"]).',
|
|
1104
|
+
},
|
|
1105
|
+
],
|
|
1106
|
+
handler: async (args) => {
|
|
1107
|
+
const { handleWhitelistAdd } = await import('./mcp-tools/whitelistTools.js');
|
|
1108
|
+
return handleWhitelistAdd(args);
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
name: 'whitelist_update',
|
|
1113
|
+
description: 'Update the status of a whitelist entry.',
|
|
1114
|
+
parameters: [
|
|
1115
|
+
{
|
|
1116
|
+
name: 'name',
|
|
1117
|
+
type: 'string',
|
|
1118
|
+
required: true,
|
|
1119
|
+
description: 'Name of the whitelist entry to update.',
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
name: 'status',
|
|
1123
|
+
type: 'string',
|
|
1124
|
+
required: true,
|
|
1125
|
+
description: 'New status for the entry.',
|
|
1126
|
+
enum: ['active', 'pending', 'rejected', 'disabled'],
|
|
1127
|
+
},
|
|
1128
|
+
],
|
|
1129
|
+
handler: async (args) => {
|
|
1130
|
+
const { handleWhitelistUpdateStatus } = await import('./mcp-tools/whitelistTools.js');
|
|
1131
|
+
return handleWhitelistUpdateStatus(args);
|
|
1132
|
+
},
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
name: 'whitelist_detect',
|
|
1136
|
+
description: 'Detect potential whitelist candidates from package.json and usage patterns.',
|
|
1137
|
+
parameters: [
|
|
1138
|
+
{
|
|
1139
|
+
name: 'path',
|
|
1140
|
+
type: 'string',
|
|
1141
|
+
required: false,
|
|
1142
|
+
description: 'Project path to analyze (defaults to current directory).',
|
|
1143
|
+
default: process.cwd(),
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
name: 'includePackageJson',
|
|
1147
|
+
type: 'boolean',
|
|
1148
|
+
required: false,
|
|
1149
|
+
description: 'Include dependencies from package.json.',
|
|
1150
|
+
default: true,
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
name: 'autoPopulate',
|
|
1154
|
+
type: 'boolean',
|
|
1155
|
+
required: false,
|
|
1156
|
+
description: 'Automatically add high-confidence entries.',
|
|
1157
|
+
default: false,
|
|
1158
|
+
},
|
|
1159
|
+
],
|
|
1160
|
+
handler: async (args) => {
|
|
1161
|
+
const { handleWhitelistDetect } = await import('./mcp-tools/whitelistTools.js');
|
|
1162
|
+
return handleWhitelistDetect(args);
|
|
1163
|
+
},
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
name: 'rules_list',
|
|
1167
|
+
description: 'List all configured invariant rules from .codeauditor.json. Shows rule IDs, kinds, severity, and messages.',
|
|
1168
|
+
parameters: [
|
|
1169
|
+
{
|
|
1170
|
+
name: 'configPath',
|
|
1171
|
+
type: 'string',
|
|
1172
|
+
required: false,
|
|
1173
|
+
description: 'Path to .codeauditor.json (defaults to auto-detect in current working tree).',
|
|
1174
|
+
},
|
|
1175
|
+
],
|
|
1176
|
+
handler: async (args) => {
|
|
1177
|
+
const { readFileSync } = await import('fs');
|
|
1178
|
+
const path = await import('path');
|
|
1179
|
+
const configPath = args.configPath ||
|
|
1180
|
+
path.join(process.cwd(), '.codeauditor.json');
|
|
1003
1181
|
try {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1182
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
1183
|
+
const config = JSON.parse(raw);
|
|
1184
|
+
const rules = config?.rules ?? [];
|
|
1185
|
+
return {
|
|
1186
|
+
rules: rules.map((r) => ({
|
|
1187
|
+
id: r.id,
|
|
1188
|
+
kind: r.kind,
|
|
1189
|
+
severity: r.severity,
|
|
1190
|
+
message: r.message || null,
|
|
1191
|
+
})),
|
|
1192
|
+
count: rules.length,
|
|
1193
|
+
configPath,
|
|
1014
1194
|
};
|
|
1015
1195
|
}
|
|
1016
|
-
catch (
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1196
|
+
catch (err) {
|
|
1197
|
+
return {
|
|
1198
|
+
error: `Failed to read rules: ${err.message}`,
|
|
1199
|
+
configPath,
|
|
1200
|
+
rules: [],
|
|
1201
|
+
count: 0,
|
|
1020
1202
|
};
|
|
1021
1203
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1204
|
+
},
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
name: 'rules_check',
|
|
1208
|
+
description: 'Validate the current .codeauditor.json rules (schema, duplicate IDs, valid globs/regex, mutual exclusivity). Returns any config errors.',
|
|
1209
|
+
parameters: [
|
|
1210
|
+
{
|
|
1211
|
+
name: 'configPath',
|
|
1212
|
+
type: 'string',
|
|
1213
|
+
required: false,
|
|
1214
|
+
description: 'Path to .codeauditor.json (defaults to auto-detect in current working tree).',
|
|
1215
|
+
},
|
|
1216
|
+
],
|
|
1217
|
+
handler: async (args) => {
|
|
1218
|
+
const { validateRulesConfig } = await import('./invariants/ruleValidator.js');
|
|
1219
|
+
const { readFileSync } = await import('fs');
|
|
1220
|
+
const path = await import('path');
|
|
1221
|
+
const configPath = args.configPath ||
|
|
1222
|
+
path.join(process.cwd(), '.codeauditor.json');
|
|
1029
1223
|
try {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
else {
|
|
1041
|
-
const configs = await db.getAllAnalyzerConfigs(projectPath);
|
|
1042
|
-
result = {
|
|
1043
|
-
success: true,
|
|
1044
|
-
configs,
|
|
1045
|
-
scope: projectPath ? 'project' : 'global',
|
|
1046
|
-
message: `Found ${Object.keys(configs).length} analyzer configurations`
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
catch (error) {
|
|
1051
|
-
result = {
|
|
1052
|
-
success: false,
|
|
1053
|
-
error: error instanceof Error ? error.message : 'Failed to get configuration'
|
|
1224
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
1225
|
+
const config = JSON.parse(raw);
|
|
1226
|
+
const rulesArray = config?.rules;
|
|
1227
|
+
const errors = validateRulesConfig({ rules: rulesArray ?? [] });
|
|
1228
|
+
return {
|
|
1229
|
+
valid: errors.length === 0,
|
|
1230
|
+
errors: errors.map(e => ({ ruleId: e.ruleId || null, message: e.message })),
|
|
1231
|
+
configPath,
|
|
1054
1232
|
};
|
|
1055
1233
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const db = CodeIndexDB.getInstance();
|
|
1062
|
-
await db.initialize();
|
|
1063
|
-
try {
|
|
1064
|
-
if (analyzerName) {
|
|
1065
|
-
const deleted = await db.deleteAnalyzerConfig(analyzerName, {
|
|
1066
|
-
projectPath,
|
|
1067
|
-
isGlobal: !projectPath
|
|
1068
|
-
});
|
|
1069
|
-
result = {
|
|
1070
|
-
success: deleted,
|
|
1071
|
-
message: deleted
|
|
1072
|
-
? `Configuration for ${analyzerName} analyzer has been reset${projectPath ? ` for project ${projectPath}` : ' globally'}`
|
|
1073
|
-
: `No configuration found for ${analyzerName} analyzer`,
|
|
1074
|
-
analyzer: analyzerName,
|
|
1075
|
-
scope: projectPath ? 'project' : 'global'
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
else {
|
|
1079
|
-
await db.resetAnalyzerConfigs(projectPath);
|
|
1080
|
-
result = {
|
|
1081
|
-
success: true,
|
|
1082
|
-
message: projectPath
|
|
1083
|
-
? `All project-specific configurations for ${projectPath} have been reset`
|
|
1084
|
-
: 'All analyzer configurations have been reset to defaults',
|
|
1085
|
-
scope: projectPath ? 'project' : 'global'
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
catch (error) {
|
|
1090
|
-
result = {
|
|
1091
|
-
success: false,
|
|
1092
|
-
error: error instanceof Error ? error.message : 'Failed to reset configuration'
|
|
1234
|
+
catch (err) {
|
|
1235
|
+
return {
|
|
1236
|
+
valid: false,
|
|
1237
|
+
errors: [{ message: `Failed to read/parse config: ${err.message}` }],
|
|
1238
|
+
configPath,
|
|
1093
1239
|
};
|
|
1094
1240
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1241
|
+
},
|
|
1242
|
+
},
|
|
1243
|
+
],
|
|
1244
|
+
});
|
|
1245
|
+
// ── code_map ───────────────────────────────────────────────────────────────
|
|
1246
|
+
registry.register({
|
|
1247
|
+
name: 'code_map',
|
|
1248
|
+
description: 'Retrieve sections of previously generated code maps. Actions: get (retrieve a specific section), list (list all available sections for a map).',
|
|
1249
|
+
actions: [
|
|
1250
|
+
{
|
|
1251
|
+
name: 'get',
|
|
1252
|
+
description: 'Retrieve a specific section of a previously generated code map.',
|
|
1253
|
+
parameters: [
|
|
1254
|
+
{
|
|
1255
|
+
name: 'mapId',
|
|
1256
|
+
type: 'string',
|
|
1257
|
+
required: true,
|
|
1258
|
+
description: 'The map ID returned from a previous audit with code map generation.',
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
name: 'sectionType',
|
|
1262
|
+
type: 'string',
|
|
1263
|
+
required: true,
|
|
1264
|
+
description: 'The section type to retrieve (e.g., overview, files, dependencies, documentation).',
|
|
1265
|
+
},
|
|
1266
|
+
],
|
|
1267
|
+
handler: async (args) => {
|
|
1098
1268
|
const mapId = args.mapId;
|
|
1099
1269
|
const sectionType = args.sectionType;
|
|
1100
|
-
if (!mapId || !sectionType)
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}
|
|
1107
|
-
try {
|
|
1108
|
-
const db = await getDatabase();
|
|
1109
|
-
const section = await db.getCodeMapSection(mapId, sectionType);
|
|
1110
|
-
if (section) {
|
|
1111
|
-
result = {
|
|
1112
|
-
success: true,
|
|
1113
|
-
mapId,
|
|
1114
|
-
sectionType,
|
|
1115
|
-
content: section.content,
|
|
1116
|
-
metadata: section.metadata
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
else {
|
|
1120
|
-
result = {
|
|
1121
|
-
success: false,
|
|
1122
|
-
error: `Section '${sectionType}' not found for map '${mapId}'`
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
catch (error) {
|
|
1127
|
-
result = {
|
|
1128
|
-
success: false,
|
|
1129
|
-
error: error instanceof Error ? error.message : 'Failed to retrieve code map section'
|
|
1130
|
-
};
|
|
1270
|
+
if (!mapId || !sectionType)
|
|
1271
|
+
return { success: false, error: 'Both mapId and sectionType are required' };
|
|
1272
|
+
const db = await getDatabase();
|
|
1273
|
+
const section = await db.getCodeMapSection(mapId, sectionType);
|
|
1274
|
+
if (section) {
|
|
1275
|
+
return { success: true, mapId, sectionType, content: section.content, metadata: section.metadata };
|
|
1131
1276
|
}
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1277
|
+
return { success: false, error: `Section '${sectionType}' not found for map '${mapId}'` };
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
name: 'list',
|
|
1282
|
+
description: 'List all available sections for a code map.',
|
|
1283
|
+
parameters: [
|
|
1284
|
+
{
|
|
1285
|
+
name: 'mapId',
|
|
1286
|
+
type: 'string',
|
|
1287
|
+
required: true,
|
|
1288
|
+
description: 'The map ID returned from a previous audit.',
|
|
1289
|
+
},
|
|
1290
|
+
],
|
|
1291
|
+
handler: async (args) => {
|
|
1135
1292
|
const mapId = args.mapId;
|
|
1136
|
-
if (!mapId)
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1293
|
+
if (!mapId)
|
|
1294
|
+
return { success: false, error: 'mapId is required' };
|
|
1295
|
+
const db = await getDatabase();
|
|
1296
|
+
const sections = await db.listCodeMapSections(mapId);
|
|
1297
|
+
return { success: true, mapId, sections, totalSections: sections.length };
|
|
1298
|
+
},
|
|
1299
|
+
},
|
|
1300
|
+
],
|
|
1301
|
+
});
|
|
1302
|
+
// ── tasks ──────────────────────────────────────────────────────────────────
|
|
1303
|
+
registry.register({
|
|
1304
|
+
name: 'tasks',
|
|
1305
|
+
description: 'Manage a persistent per-project task queue. Tasks survive index resets. Actions: create, list, list_tree, get, update, complete, delete, from_audit (create tasks from audit violations with deduplication).',
|
|
1306
|
+
actions: [
|
|
1307
|
+
{
|
|
1308
|
+
name: 'create',
|
|
1309
|
+
description: 'Create a new task.',
|
|
1310
|
+
parameters: [
|
|
1311
|
+
{ name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
|
|
1312
|
+
{ name: 'title', type: 'string', required: true, description: 'Task title.' },
|
|
1313
|
+
{ name: 'description', type: 'string', required: false, description: 'Longer description.' },
|
|
1314
|
+
{ name: 'status', type: 'string', required: false, description: 'Initial status.', enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'] },
|
|
1315
|
+
{ name: 'priority', type: 'string', required: false, description: 'Priority.', enum: ['low', 'medium', 'high'] },
|
|
1316
|
+
{ name: 'labels', type: 'array', required: false, description: 'String tags.' },
|
|
1317
|
+
{ name: 'metadata', type: 'object', required: false, description: 'Arbitrary JSON.' },
|
|
1318
|
+
{ name: 'parentTaskId', type: 'string', required: false, description: 'Optional parent.' },
|
|
1319
|
+
{ name: 'source', type: 'string', required: false, description: 'Provenance.', enum: ['manual', 'audit', 'mcp'] },
|
|
1320
|
+
{ name: 'blockedBy', type: 'array', required: false, description: 'Blocking task IDs.' },
|
|
1321
|
+
{ name: 'dueAt', type: 'string', required: false, description: 'ISO 8601 due datetime.' },
|
|
1322
|
+
{ name: 'sortOrder', type: 'number', required: false, description: 'Sort position (default 0).' },
|
|
1323
|
+
{ name: 'relatedFiles', type: 'array', required: false, description: 'File paths.' },
|
|
1324
|
+
{ name: 'relatedSymbols', type: 'array', required: false, description: 'Symbol names.' },
|
|
1325
|
+
],
|
|
1326
|
+
handler: async (args, signal) => {
|
|
1327
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
1328
|
+
return handleProjectTasks({ ...args, action: 'create' }, { signal });
|
|
1329
|
+
},
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
name: 'list',
|
|
1333
|
+
description: 'List tasks (flat). Supports filtering by status, priority, labels, source, query text search.',
|
|
1334
|
+
parameters: [
|
|
1335
|
+
{ name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
|
|
1336
|
+
{ name: 'status', type: 'string', required: false, description: 'Filter by status.', enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'] },
|
|
1337
|
+
{ name: 'priority', type: 'string', required: false, description: 'Filter by priority.', enum: ['low', 'medium', 'high'] },
|
|
1338
|
+
{ name: 'label', type: 'string', required: false, description: 'Filter by label.' },
|
|
1339
|
+
{ name: 'source', type: 'string', required: false, description: 'Filter by source.', enum: ['manual', 'audit', 'mcp'] },
|
|
1340
|
+
{ name: 'query', type: 'string', required: false, description: 'Text search across title/description.' },
|
|
1341
|
+
{ name: 'hasChildren', type: 'boolean', required: false, description: 'Filter by subtask presence.' },
|
|
1342
|
+
{ name: 'overdueOnly', type: 'boolean', required: false, description: 'Only overdue tasks.' },
|
|
1343
|
+
{ name: 'actionableOnly', type: 'boolean', required: false, description: 'Only open + unblocked.' },
|
|
1344
|
+
{ name: 'limit', type: 'number', required: false, description: 'Max results (default 500).' },
|
|
1345
|
+
],
|
|
1346
|
+
handler: async (args, signal) => {
|
|
1347
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
1348
|
+
return handleProjectTasks({ ...args, action: 'list' }, { signal });
|
|
1349
|
+
},
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
name: 'list_tree',
|
|
1353
|
+
description: 'List tasks as a tree (parent-child hierarchy).',
|
|
1354
|
+
parameters: [
|
|
1355
|
+
{ name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
|
|
1356
|
+
{ name: 'status', type: 'string', required: false, description: 'Filter by status.', enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'] },
|
|
1357
|
+
{ name: 'limit', type: 'number', required: false, description: 'Max tasks (default 500).' },
|
|
1358
|
+
],
|
|
1359
|
+
handler: async (args, signal) => {
|
|
1360
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
1361
|
+
return handleProjectTasks({ ...args, action: 'list_tree' }, { signal });
|
|
1362
|
+
},
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
name: 'get',
|
|
1366
|
+
description: 'Get a single task by ID.',
|
|
1367
|
+
parameters: [
|
|
1368
|
+
{ name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
|
|
1369
|
+
{ name: 'taskId', type: 'string', required: true, description: 'Task ID.' },
|
|
1370
|
+
],
|
|
1371
|
+
handler: async (args, signal) => {
|
|
1372
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
1373
|
+
return handleProjectTasks({ ...args, action: 'get' }, { signal });
|
|
1374
|
+
},
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
name: 'update',
|
|
1378
|
+
description: 'Update a task with a patch of partial fields.',
|
|
1379
|
+
parameters: [
|
|
1380
|
+
{ name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
|
|
1381
|
+
{ name: 'taskId', type: 'string', required: true, description: 'Task ID.' },
|
|
1382
|
+
{ name: 'patch', type: 'object', required: true, description: 'Partial fields to update.' },
|
|
1383
|
+
],
|
|
1384
|
+
handler: async (args, signal) => {
|
|
1385
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
1386
|
+
return handleProjectTasks({ ...args, action: 'update' }, { signal });
|
|
1387
|
+
},
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
name: 'complete',
|
|
1391
|
+
description: 'Mark a task as completed.',
|
|
1392
|
+
parameters: [
|
|
1393
|
+
{ name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
|
|
1394
|
+
{ name: 'taskId', type: 'string', required: true, description: 'Task ID.' },
|
|
1395
|
+
],
|
|
1396
|
+
handler: async (args, signal) => {
|
|
1397
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
1398
|
+
return handleProjectTasks({ ...args, action: 'complete_task' }, { signal });
|
|
1399
|
+
},
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
name: 'delete',
|
|
1403
|
+
description: 'Delete a task. Mode: reject (default, refuse if subtasks exist), detach (move subtasks up), cascade (delete subtree).',
|
|
1404
|
+
parameters: [
|
|
1405
|
+
{ name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
|
|
1406
|
+
{ name: 'taskId', type: 'string', required: true, description: 'Task ID.' },
|
|
1407
|
+
{ name: 'mode', type: 'string', required: false, description: 'Delete mode.', enum: ['reject', 'detach', 'cascade'] },
|
|
1408
|
+
],
|
|
1409
|
+
handler: async (args, signal) => {
|
|
1410
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
1411
|
+
return handleProjectTasks({ ...args, action: 'delete' }, { signal });
|
|
1412
|
+
},
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: 'from_audit',
|
|
1416
|
+
description: 'Create tasks from audit violations with deduplication. Each violation gets a stable fingerprint; violations matching existing open tasks are skipped.',
|
|
1417
|
+
parameters: [
|
|
1418
|
+
{ name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
|
|
1419
|
+
{ name: 'auditJobId', type: 'string', required: false, description: 'Audit job ID. Omit to use most recent completed audit.' },
|
|
1420
|
+
{ name: 'severities', type: 'array', required: false, description: 'Severities to include (default: critical, warning).' },
|
|
1421
|
+
{ name: 'analyzers', type: 'array', required: false, description: 'Filter violations by analyzer name.' },
|
|
1422
|
+
{ name: 'paths', type: 'array', required: false, description: 'Filter violations by file path globs.' },
|
|
1423
|
+
],
|
|
1424
|
+
handler: async (args, signal) => {
|
|
1425
|
+
const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
|
|
1426
|
+
return handleProjectTasks({ ...args, action: 'from_audit' }, { signal });
|
|
1427
|
+
},
|
|
1428
|
+
},
|
|
1429
|
+
],
|
|
1430
|
+
});
|
|
1431
|
+
// ── guide ──────────────────────────────────────────────────────────────────
|
|
1432
|
+
registry.register({
|
|
1433
|
+
name: 'guide',
|
|
1434
|
+
description: 'Get recommended workflows and best practices for using the code auditor tools effectively. Single action: get.',
|
|
1435
|
+
actions: [
|
|
1436
|
+
{
|
|
1437
|
+
name: 'get',
|
|
1438
|
+
description: 'Get workflow guide for a specific scenario or all scenarios.',
|
|
1439
|
+
parameters: [
|
|
1440
|
+
{
|
|
1441
|
+
name: 'scenario',
|
|
1442
|
+
type: 'string',
|
|
1443
|
+
required: false,
|
|
1444
|
+
description: 'Specific scenario: initial-setup, react-development, code-review, find-patterns, maintenance. Leave empty to see all.',
|
|
1445
|
+
},
|
|
1446
|
+
],
|
|
1447
|
+
handler: async (args) => {
|
|
1448
|
+
const scenario = args.scenario;
|
|
1449
|
+
const { getWorkflowGuide, getWorkflowTips } = await import('./mcp-tools/workflowGuide.js');
|
|
1143
1450
|
try {
|
|
1144
|
-
const
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1451
|
+
const workflows = getWorkflowGuide(scenario);
|
|
1452
|
+
const tips = getWorkflowTips();
|
|
1453
|
+
return {
|
|
1147
1454
|
success: true,
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
totalSections: sections.length
|
|
1455
|
+
...(scenario ? { workflow: workflows } : { workflows }),
|
|
1456
|
+
tips,
|
|
1151
1457
|
};
|
|
1152
1458
|
}
|
|
1153
1459
|
catch (error) {
|
|
1154
|
-
|
|
1460
|
+
return {
|
|
1155
1461
|
success: false,
|
|
1156
|
-
error: error instanceof Error ? error.message : '
|
|
1462
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
1463
|
+
availableScenarios: [
|
|
1464
|
+
'initial-setup',
|
|
1465
|
+
'react-development',
|
|
1466
|
+
'code-review',
|
|
1467
|
+
'find-patterns',
|
|
1468
|
+
'maintenance',
|
|
1469
|
+
'analyzer-configuration',
|
|
1470
|
+
],
|
|
1157
1471
|
};
|
|
1158
1472
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1475
|
+
],
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
// ── MCP Server ───────────────────────────────────────────────────────────────
|
|
1479
|
+
async function startMcpServer() {
|
|
1480
|
+
logMcpInfo('startup', `code-auditor-mcp ${PACKAGE_VERSION} (stdio)`, {
|
|
1481
|
+
pid: process.pid,
|
|
1482
|
+
cwd: process.cwd(),
|
|
1483
|
+
dataDir: process.env.CODE_AUDITOR_DATA_DIR ?? '(default)',
|
|
1484
|
+
debug: process.env.CODE_AUDITOR_DEBUG ?? '0',
|
|
1485
|
+
logFile: process.env.CODE_AUDITOR_LOG_FILE ?? '(none)',
|
|
1486
|
+
});
|
|
1487
|
+
mcpDebugStderr(chalk.blue('[DEBUG]'), `Node ${process.version} · cwd ${process.cwd()}`);
|
|
1488
|
+
const registry = new ToolRegistry();
|
|
1489
|
+
registerAllTools(registry);
|
|
1490
|
+
const allTools = registry.getAllTools();
|
|
1491
|
+
const toolCount = allTools.length;
|
|
1492
|
+
const server = new Server({ name: 'code-auditor', version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
1493
|
+
server.onerror = (error) => {
|
|
1494
|
+
console.error(chalk.red('[ERROR]'), 'Server error occurred:', error);
|
|
1495
|
+
console.error(chalk.red('[ERROR]'), 'Error stack:', error.stack);
|
|
1496
|
+
};
|
|
1497
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1498
|
+
const schemas = registry.getMCPToolSchemas();
|
|
1499
|
+
mcpTraceStderr(chalk.blue('[DEBUG]'), `ListTools → ${schemas.length} tools`);
|
|
1500
|
+
return { tools: schemas };
|
|
1501
|
+
});
|
|
1502
|
+
server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
1503
|
+
mcpTraceStderr(chalk.blue('[DEBUG]'), 'initialize', JSON.stringify(request, null, 2));
|
|
1504
|
+
return {
|
|
1505
|
+
protocolVersion: '2024-11-05',
|
|
1506
|
+
capabilities: { tools: {} },
|
|
1507
|
+
serverInfo: { name: 'code-auditor', version: PACKAGE_VERSION },
|
|
1508
|
+
};
|
|
1509
|
+
});
|
|
1510
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
1511
|
+
const { name, arguments: args = {} } = request.params;
|
|
1512
|
+
mcpTraceStderr(chalk.blue('[DEBUG]'), `CallTool ${name}`, JSON.stringify({
|
|
1513
|
+
argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
|
|
1514
|
+
hasArgs: !!args,
|
|
1515
|
+
}));
|
|
1516
|
+
const requestSignal = extra?.signal;
|
|
1517
|
+
const toolStartedAt = Date.now();
|
|
1518
|
+
try {
|
|
1519
|
+
if (name !== 'notify') {
|
|
1520
|
+
logMcpDebug('tool', `call ${name}`, {
|
|
1521
|
+
argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
|
|
1522
|
+
pathArg: args?.path,
|
|
1523
|
+
cwd: process.cwd(),
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
throwIfRequestAborted(requestSignal, `start ${name}`);
|
|
1527
|
+
const tool = registry.getTool(name);
|
|
1528
|
+
if (!tool) {
|
|
1529
|
+
return {
|
|
1530
|
+
content: [
|
|
1531
|
+
{
|
|
1532
|
+
type: 'text',
|
|
1533
|
+
text: serializeForMcp({
|
|
1534
|
+
success: false,
|
|
1535
|
+
error: `Unknown tool: "${name}". Available tools: ${allTools.map((t) => t.name).join(', ')}.`,
|
|
1536
|
+
}),
|
|
1537
|
+
},
|
|
1538
|
+
],
|
|
1539
|
+
isError: true,
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
const action = args.action;
|
|
1543
|
+
const result = await registry.dispatch(name, action, args, requestSignal);
|
|
1544
|
+
// Check if registry returned a structured error
|
|
1545
|
+
if (result && typeof result === 'object' && 'success' in result && result.success === false && 'validActions' in result) {
|
|
1546
|
+
return {
|
|
1547
|
+
content: [{ type: 'text', text: serializeForMcp(result) }],
|
|
1548
|
+
isError: true,
|
|
1549
|
+
};
|
|
1168
1550
|
}
|
|
1169
1551
|
const response = {
|
|
1170
|
-
content: [
|
|
1171
|
-
{
|
|
1172
|
-
type: 'text',
|
|
1173
|
-
text: JSON.stringify(result, null, 2),
|
|
1174
|
-
},
|
|
1175
|
-
],
|
|
1552
|
+
content: [{ type: 'text', text: serializeForMcp(result) }],
|
|
1176
1553
|
};
|
|
1177
|
-
mcpTraceStderr(chalk.blue('[DEBUG]'), `Tool ${name} ok`,
|
|
1554
|
+
mcpTraceStderr(chalk.blue('[DEBUG]'), `Tool ${name} ok`, `responseBytes=${response.content[0].text.length}`);
|
|
1555
|
+
throwIfRequestAborted(requestSignal, `before response ${name}`);
|
|
1178
1556
|
return response;
|
|
1179
1557
|
}
|
|
1180
1558
|
catch (error) {
|
|
1559
|
+
if (error instanceof RequestAbortedError) {
|
|
1560
|
+
if (name !== 'audit_status') {
|
|
1561
|
+
logMcpDebug('tool', `call ${name} aborted`, { ms: Date.now() - toolStartedAt });
|
|
1562
|
+
}
|
|
1563
|
+
throw error;
|
|
1564
|
+
}
|
|
1181
1565
|
console.error(chalk.red('[ERROR]'), `Tool ${name} execution failed:`, error);
|
|
1182
1566
|
console.error(chalk.red('[ERROR]'), 'Error stack:', error instanceof Error ? error.stack : 'No stack trace');
|
|
1183
1567
|
if (name !== 'audit_status') {
|
|
1184
1568
|
logMcpDebug('tool', `call ${name} failed`, {
|
|
1185
1569
|
ms: Date.now() - toolStartedAt,
|
|
1186
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1570
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1187
1571
|
});
|
|
1188
1572
|
}
|
|
1189
1573
|
return {
|
|
1190
|
-
content: [
|
|
1191
|
-
{
|
|
1192
|
-
type: 'text',
|
|
1193
|
-
text: JSON.stringify(formatMcpToolErrorPayload(name, error), null, 2),
|
|
1194
|
-
},
|
|
1195
|
-
],
|
|
1574
|
+
content: [{ type: 'text', text: serializeForMcp(formatMcpToolErrorPayload(name, error)) }],
|
|
1196
1575
|
isError: true,
|
|
1197
1576
|
};
|
|
1198
1577
|
}
|
|
@@ -1203,114 +1582,58 @@ async function startMcpServer() {
|
|
|
1203
1582
|
}
|
|
1204
1583
|
});
|
|
1205
1584
|
const transport = new StdioServerTransport();
|
|
1206
|
-
// Add transport event handlers if available
|
|
1207
1585
|
if (transport.onclose) {
|
|
1208
|
-
transport.onclose = () =>
|
|
1209
|
-
console.error(chalk.yellow('[WARN]'), 'Transport closed');
|
|
1210
|
-
};
|
|
1586
|
+
transport.onclose = () => console.error(chalk.yellow('[WARN]'), 'Transport closed');
|
|
1211
1587
|
}
|
|
1212
1588
|
if (transport.onerror) {
|
|
1213
|
-
transport.onerror = (error) =>
|
|
1214
|
-
console.error(chalk.red('[ERROR]'), 'Transport error:', error);
|
|
1215
|
-
};
|
|
1589
|
+
transport.onerror = (error) => console.error(chalk.red('[ERROR]'), 'Transport error:', error);
|
|
1216
1590
|
}
|
|
1217
1591
|
await server.connect(transport);
|
|
1218
1592
|
console.error(chalk.green('✓ Code Auditor MCP Server started'));
|
|
1219
|
-
console.error(chalk.gray(`Listening on stdio · ${
|
|
1593
|
+
console.error(chalk.gray(`Listening on stdio · ${toolCount} tools · ${PACKAGE_VERSION}`));
|
|
1220
1594
|
mcpDebugStderr(chalk.blue('[DEBUG]'), 'Server ready', {
|
|
1221
1595
|
name: 'code-auditor',
|
|
1222
1596
|
transport: 'stdio',
|
|
1223
|
-
toolCount
|
|
1597
|
+
toolCount,
|
|
1224
1598
|
});
|
|
1225
1599
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
for (const [analyzerName, analyzerResult] of Object.entries(result.analyzerResults)) {
|
|
1229
|
-
for (const violation of analyzerResult.violations) {
|
|
1230
|
-
violations.push({
|
|
1231
|
-
...violation,
|
|
1232
|
-
analyzer: analyzerName,
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
return violations;
|
|
1237
|
-
}
|
|
1238
|
-
function calculateHealthScore(result) {
|
|
1239
|
-
const filesAnalyzed = result.metadata?.filesAnalyzed || 1;
|
|
1240
|
-
const critical = result.summary.criticalIssues || 0;
|
|
1241
|
-
const warnings = result.summary.warnings || 0;
|
|
1242
|
-
const suggestions = result.summary.suggestions || 0;
|
|
1243
|
-
// Weight factors for different severity levels
|
|
1244
|
-
const weights = {
|
|
1245
|
-
critical: 10,
|
|
1246
|
-
warning: 3,
|
|
1247
|
-
suggestion: 0.5
|
|
1248
|
-
};
|
|
1249
|
-
// Calculate weighted violations per file
|
|
1250
|
-
const weightedViolations = (critical * weights.critical) +
|
|
1251
|
-
(warnings * weights.warning) +
|
|
1252
|
-
(suggestions * weights.suggestion);
|
|
1253
|
-
const violationsPerFile = weightedViolations / filesAnalyzed;
|
|
1254
|
-
// Score calculation: 100 points minus deductions
|
|
1255
|
-
// Each weighted violation per file reduces score
|
|
1256
|
-
// Critical-heavy codebases drop faster than suggestion-heavy ones
|
|
1257
|
-
let score = 100 - (violationsPerFile * 2);
|
|
1258
|
-
return Math.max(0, Math.round(Math.min(100, score)));
|
|
1259
|
-
}
|
|
1260
|
-
function generateRecommendations(result) {
|
|
1261
|
-
const recommendations = [];
|
|
1262
|
-
if (result.summary.criticalIssues > 0) {
|
|
1263
|
-
recommendations.push({
|
|
1264
|
-
priority: 'high',
|
|
1265
|
-
title: 'Fix critical violations immediately',
|
|
1266
|
-
description: `${result.summary.criticalIssues} critical issues require immediate attention`,
|
|
1267
|
-
});
|
|
1268
|
-
}
|
|
1269
|
-
// Add more recommendation logic based on patterns
|
|
1270
|
-
return recommendations;
|
|
1271
|
-
}
|
|
1272
|
-
function getHealthRecommendation(score, result) {
|
|
1273
|
-
if (score >= 90)
|
|
1274
|
-
return 'Excellent code health!';
|
|
1275
|
-
if (score >= 70)
|
|
1276
|
-
return 'Good code health with room for improvement';
|
|
1277
|
-
if (result.summary.criticalIssues > 0) {
|
|
1278
|
-
return `Fix ${result.summary.criticalIssues} critical violations first`;
|
|
1279
|
-
}
|
|
1280
|
-
return 'Code health needs attention - run detailed audit';
|
|
1281
|
-
}
|
|
1282
|
-
// Error handlers
|
|
1283
|
-
process.on('uncaughtException', (error) => {
|
|
1284
|
-
console.error(chalk.red('[ERROR]'), 'Uncaught exception:', error);
|
|
1285
|
-
console.error(chalk.red('[ERROR]'), 'Stack:', error.stack);
|
|
1286
|
-
console.error(chalk.red('[ERROR]'), 'Error details:', JSON.stringify(error, null, 2));
|
|
1287
|
-
process.exit(1);
|
|
1288
|
-
});
|
|
1289
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
1290
|
-
console.error(chalk.red('[ERROR]'), 'Unhandled rejection at:', promise);
|
|
1291
|
-
console.error(chalk.red('[ERROR]'), 'Reason:', reason);
|
|
1292
|
-
console.error(chalk.red('[ERROR]'), 'Rejection details:', JSON.stringify(reason, null, 2));
|
|
1293
|
-
process.exit(1);
|
|
1294
|
-
});
|
|
1295
|
-
// Add SIGTERM and SIGINT handlers
|
|
1600
|
+
// ── Process setup ────────────────────────────────────────────────────────────
|
|
1601
|
+
registerProcessReliabilityHandlers();
|
|
1296
1602
|
process.on('SIGTERM', () => {
|
|
1297
1603
|
console.error(chalk.yellow('[WARN]'), 'Received SIGTERM, shutting down gracefully...');
|
|
1298
|
-
process.
|
|
1604
|
+
process.exitCode = 0;
|
|
1299
1605
|
});
|
|
1300
1606
|
process.on('SIGINT', () => {
|
|
1301
1607
|
console.error(chalk.yellow('[WARN]'), 'Received SIGINT, shutting down gracefully...');
|
|
1302
|
-
process.
|
|
1608
|
+
process.exitCode = 0;
|
|
1303
1609
|
});
|
|
1304
|
-
// Log when stdin/stdout events occur
|
|
1305
1610
|
process.stdin.on('error', (error) => {
|
|
1306
1611
|
console.error(chalk.red('[ERROR]'), 'stdin error:', error);
|
|
1307
1612
|
});
|
|
1308
1613
|
process.stdout.on('error', (error) => {
|
|
1309
1614
|
console.error(chalk.red('[ERROR]'), 'stdout error:', error);
|
|
1310
1615
|
});
|
|
1311
|
-
|
|
1312
|
-
|
|
1616
|
+
// ── Mode dispatch ────────────────────────────────────────────────────────────
|
|
1617
|
+
async function main() {
|
|
1618
|
+
await initParsers();
|
|
1619
|
+
if (autoIndexPath) {
|
|
1620
|
+
// --auto-index: sync the index and exit
|
|
1621
|
+
const { runAutoIndex } = await import('./mcpAutoIndex.js');
|
|
1622
|
+
await runAutoIndex(autoIndexPath);
|
|
1623
|
+
process.exit(0);
|
|
1624
|
+
}
|
|
1625
|
+
if (uiMode) {
|
|
1626
|
+
// --ui: start the HTTP UI server
|
|
1627
|
+
const { startMcpUIServer } = await import('./mcp-ui-simple.js');
|
|
1628
|
+
await startMcpUIServer();
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
// --stdio (default): start the MCP server
|
|
1632
|
+
await startMcpServer();
|
|
1633
|
+
}
|
|
1634
|
+
main().catch((error) => {
|
|
1635
|
+
console.error(chalk.red('[ERROR]'), 'Failed to start:', error);
|
|
1313
1636
|
console.error(chalk.red('[ERROR]'), 'Stack:', error.stack);
|
|
1314
|
-
process.
|
|
1637
|
+
process.exitCode = 1;
|
|
1315
1638
|
});
|
|
1316
1639
|
//# sourceMappingURL=mcp.js.map
|