code-auditor-mcp 2.6.2 → 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 → codeIndexDB.d.ts} +51 -224
- 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 +58 -22
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +172 -10
- 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 +1376 -1181
- 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/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 +15 -2
- package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
- package/dist/services/ProjectTaskRepository.js +16 -1
- 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 +4 -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.map +0 -1
- package/dist/codeIndexDb.js +0 -2102
- 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 -136
- 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 -1324
- 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,649 +1,103 @@
|
|
|
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
|
-
// Core Audit Tools
|
|
50
|
-
{
|
|
51
|
-
name: 'audit',
|
|
52
|
-
description: 'Fetch paginated results for a completed audit by resultId (or auditId alias). This tool never starts a new audit.',
|
|
53
|
-
parameters: [
|
|
54
|
-
{
|
|
55
|
-
name: 'limit',
|
|
56
|
-
type: 'number',
|
|
57
|
-
required: false,
|
|
58
|
-
description: 'Maximum number of violations to return per page (default: 50, max: 100)',
|
|
59
|
-
default: 50,
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: 'offset',
|
|
63
|
-
type: 'number',
|
|
64
|
-
required: false,
|
|
65
|
-
description: 'Violation offset for pagination (default: 0).',
|
|
66
|
-
default: 0,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: 'resultId',
|
|
70
|
-
type: 'string',
|
|
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 | list_tree | create | get | update | complete_task | delete. list/list_tree/create use projectPath or default to process.cwd(); create needs title; get/update/complete_task/delete need taskId; update needs patch object.',
|
|
486
|
-
enum: [
|
|
487
|
-
'list',
|
|
488
|
-
'list_tree',
|
|
489
|
-
'create',
|
|
490
|
-
'get',
|
|
491
|
-
'update',
|
|
492
|
-
'complete_task',
|
|
493
|
-
'delete'
|
|
494
|
-
],
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
name: 'projectPath',
|
|
498
|
-
type: 'string',
|
|
499
|
-
required: false,
|
|
500
|
-
description: 'Project root (resolved). Omit to use the MCP server working directory (same default idea as audit path); response includes projectPathDefaulted when omitted.',
|
|
501
|
-
},
|
|
502
|
-
{
|
|
503
|
-
name: 'taskId',
|
|
504
|
-
type: 'string',
|
|
505
|
-
required: false,
|
|
506
|
-
description: 'Stable task id (UUID). Required for get, update, complete_task, delete.',
|
|
507
|
-
},
|
|
508
|
-
{
|
|
509
|
-
name: 'title',
|
|
510
|
-
type: 'string',
|
|
511
|
-
required: false,
|
|
512
|
-
description: 'Task title. Required for create.',
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
name: 'description',
|
|
516
|
-
type: 'string',
|
|
517
|
-
required: false,
|
|
518
|
-
description: 'Longer description / notes.',
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
name: 'status',
|
|
522
|
-
type: 'string',
|
|
523
|
-
required: false,
|
|
524
|
-
description: 'pending | in_progress | blocked | done | cancelled. Filter for list; initial status for create; can be set via update patch.',
|
|
525
|
-
enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'],
|
|
526
|
-
},
|
|
527
|
-
{
|
|
528
|
-
name: 'priority',
|
|
529
|
-
type: 'string',
|
|
530
|
-
required: false,
|
|
531
|
-
description: 'Optional priority for create/update patch.',
|
|
532
|
-
enum: ['low', 'medium', 'high'],
|
|
533
|
-
},
|
|
534
|
-
{
|
|
535
|
-
name: 'labels',
|
|
536
|
-
type: 'array',
|
|
537
|
-
required: false,
|
|
538
|
-
description: 'String tags (e.g. ["audit","refactor"]).',
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
name: 'metadata',
|
|
542
|
-
type: 'object',
|
|
543
|
-
required: false,
|
|
544
|
-
description: 'Arbitrary JSON object: related file paths, links, audit IDs, etc.',
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
name: 'parentTaskId',
|
|
548
|
-
type: 'string',
|
|
549
|
-
required: false,
|
|
550
|
-
description: 'Optional parent task for subtasks.',
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
name: 'source',
|
|
554
|
-
type: 'string',
|
|
555
|
-
required: false,
|
|
556
|
-
description: 'manual | audit | mcp. Filter for list; provenance for create; can be set via update patch.',
|
|
557
|
-
enum: ['manual', 'audit', 'mcp'],
|
|
558
|
-
},
|
|
559
|
-
{
|
|
560
|
-
name: 'blockedBy',
|
|
561
|
-
type: 'array',
|
|
562
|
-
required: false,
|
|
563
|
-
description: 'Task IDs this item is blocked by (waiting-on).',
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
name: 'label',
|
|
567
|
-
type: 'string',
|
|
568
|
-
required: false,
|
|
569
|
-
description: 'Filter/search by label value.',
|
|
570
|
-
},
|
|
571
|
-
{
|
|
572
|
-
name: 'blockedByTaskId',
|
|
573
|
-
type: 'string',
|
|
574
|
-
required: false,
|
|
575
|
-
description: 'Filter tasks blocked by this task ID.',
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
name: 'query',
|
|
579
|
-
type: 'string',
|
|
580
|
-
required: false,
|
|
581
|
-
description: 'Case-insensitive text search across title/description/labels/files/symbols.',
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
name: 'hasChildren',
|
|
585
|
-
type: 'boolean',
|
|
586
|
-
required: false,
|
|
587
|
-
description: 'Filter tasks that do/do not have direct subtasks.',
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
name: 'overdueOnly',
|
|
591
|
-
type: 'boolean',
|
|
592
|
-
required: false,
|
|
593
|
-
description: 'Filter tasks with dueAt in the past and not closed.',
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
name: 'actionableOnly',
|
|
597
|
-
type: 'boolean',
|
|
598
|
-
required: false,
|
|
599
|
-
description: 'Filter tasks that are open and unblocked by open dependencies.',
|
|
600
|
-
},
|
|
601
|
-
{
|
|
602
|
-
name: 'dueAt',
|
|
603
|
-
type: 'string',
|
|
604
|
-
required: false,
|
|
605
|
-
description: 'ISO 8601 due datetime for create; omit or null in patch to clear.',
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
name: 'sortOrder',
|
|
609
|
-
type: 'number',
|
|
610
|
-
required: false,
|
|
611
|
-
description: 'Lower numbers sort first within a project (default 0).',
|
|
612
|
-
},
|
|
613
|
-
{
|
|
614
|
-
name: 'relatedFiles',
|
|
615
|
-
type: 'array',
|
|
616
|
-
required: false,
|
|
617
|
-
description: 'Repo-relative or absolute file paths tied to the task.',
|
|
618
|
-
},
|
|
619
|
-
{
|
|
620
|
-
name: 'relatedSymbols',
|
|
621
|
-
type: 'array',
|
|
622
|
-
required: false,
|
|
623
|
-
description: 'Function, class, or component names for cross-linking.',
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
name: 'patch',
|
|
627
|
-
type: 'object',
|
|
628
|
-
required: false,
|
|
629
|
-
description: 'For update: partial fields (title, description, status, priority, labels, metadata, parentTaskId, source, blockedBy, dueAt, sortOrder, relatedFiles, relatedSymbols).',
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
name: 'limit',
|
|
633
|
-
type: 'number',
|
|
634
|
-
required: false,
|
|
635
|
-
description: 'Max tasks to return for list (default 500, max 1000).',
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
name: 'mode',
|
|
639
|
-
type: 'string',
|
|
640
|
-
required: false,
|
|
641
|
-
description: 'Delete mode: reject (default), detach subtasks, or cascade descendants.',
|
|
642
|
-
enum: ['reject', 'detach', 'cascade'],
|
|
643
|
-
},
|
|
644
|
-
],
|
|
645
|
-
},
|
|
646
|
-
];
|
|
100
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
647
101
|
class RequestAbortedError extends Error {
|
|
648
102
|
constructor(message = 'MCP request aborted') {
|
|
649
103
|
super(message);
|
|
@@ -651,25 +105,21 @@ class RequestAbortedError extends Error {
|
|
|
651
105
|
}
|
|
652
106
|
}
|
|
653
107
|
function throwIfRequestAborted(signal, phase) {
|
|
654
|
-
if (
|
|
655
|
-
|
|
108
|
+
if (signal?.aborted) {
|
|
109
|
+
throw new RequestAbortedError(`Request aborted${phase ? ` during ${phase}` : ''}`);
|
|
656
110
|
}
|
|
657
|
-
throw new RequestAbortedError(`Request aborted${phase ? ` during ${phase}` : ''}`);
|
|
658
111
|
}
|
|
659
112
|
async function withAbortSignal(signal, phase, op) {
|
|
660
113
|
throwIfRequestAborted(signal, phase);
|
|
661
|
-
if (!signal)
|
|
114
|
+
if (!signal)
|
|
662
115
|
return op();
|
|
663
|
-
|
|
664
|
-
return await new Promise((resolve, reject) => {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
665
117
|
const onAbort = () => reject(new RequestAbortedError(`Request aborted during ${phase}`));
|
|
666
118
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
667
119
|
void op()
|
|
668
120
|
.then(resolve)
|
|
669
121
|
.catch(reject)
|
|
670
|
-
.finally(() =>
|
|
671
|
-
signal.removeEventListener('abort', onAbort);
|
|
672
|
-
});
|
|
122
|
+
.finally(() => signal.removeEventListener('abort', onAbort));
|
|
673
123
|
});
|
|
674
124
|
}
|
|
675
125
|
function serializeForMcp(value) {
|
|
@@ -679,7 +129,7 @@ function serializeForMcp(value) {
|
|
|
679
129
|
catch (error) {
|
|
680
130
|
return JSON.stringify({
|
|
681
131
|
success: false,
|
|
682
|
-
error: `Failed to serialize MCP response: ${error instanceof Error ? error.message : String(error)}
|
|
132
|
+
error: `Failed to serialize MCP response: ${error instanceof Error ? error.message : String(error)}`,
|
|
683
133
|
});
|
|
684
134
|
}
|
|
685
135
|
}
|
|
@@ -691,6 +141,26 @@ function safeJson(value) {
|
|
|
691
141
|
return `[unserializable: ${error instanceof Error ? error.message : String(error)}]`;
|
|
692
142
|
}
|
|
693
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
|
+
}
|
|
694
164
|
function registerProcessReliabilityHandlers() {
|
|
695
165
|
const g = globalThis;
|
|
696
166
|
const exitOnFatal = process.env.CODE_AUDITOR_EXIT_ON_FATAL === '1' ||
|
|
@@ -700,9 +170,8 @@ function registerProcessReliabilityHandlers() {
|
|
|
700
170
|
console.error(chalk.red('[ERROR]'), 'Uncaught exception:', error);
|
|
701
171
|
console.error(chalk.red('[ERROR]'), 'Stack:', error.stack);
|
|
702
172
|
console.error(chalk.red('[ERROR]'), 'Error details:', safeJson(error));
|
|
703
|
-
if (exitOnFatal)
|
|
173
|
+
if (exitOnFatal)
|
|
704
174
|
process.exit(1);
|
|
705
|
-
}
|
|
706
175
|
});
|
|
707
176
|
g.__codeAuditorUncaughtExceptionHandlerInstalled = true;
|
|
708
177
|
}
|
|
@@ -711,285 +180,826 @@ function registerProcessReliabilityHandlers() {
|
|
|
711
180
|
console.error(chalk.red('[ERROR]'), 'Unhandled rejection at:', promise);
|
|
712
181
|
console.error(chalk.red('[ERROR]'), 'Reason:', reason);
|
|
713
182
|
console.error(chalk.red('[ERROR]'), 'Rejection details:', safeJson(reason));
|
|
714
|
-
if (exitOnFatal)
|
|
183
|
+
if (exitOnFatal)
|
|
715
184
|
process.exit(1);
|
|
716
|
-
}
|
|
717
185
|
});
|
|
718
186
|
g.__codeAuditorUnhandledRejectionHandlerInstalled = true;
|
|
719
187
|
}
|
|
720
188
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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',
|
|
750
229
|
type: 'object',
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
type: param.type,
|
|
754
|
-
description: param.description,
|
|
755
|
-
...(param.default !== undefined && { default: param.default }),
|
|
756
|
-
...(param.enum && { enum: param.enum }),
|
|
757
|
-
};
|
|
758
|
-
return acc;
|
|
759
|
-
}, {}),
|
|
760
|
-
required: tool.parameters.filter(p => p.required).map(p => p.name),
|
|
230
|
+
required: false,
|
|
231
|
+
description: 'Analyzer-specific configuration overrides.',
|
|
761
232
|
},
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
tools: {},
|
|
773
|
-
},
|
|
774
|
-
serverInfo: {
|
|
775
|
-
name: 'code-auditor',
|
|
776
|
-
version: PACKAGE_VERSION,
|
|
777
|
-
},
|
|
778
|
-
};
|
|
779
|
-
mcpTraceStderr(chalk.blue('[DEBUG]'), 'initialize response', JSON.stringify(response, null, 2));
|
|
780
|
-
return response;
|
|
781
|
-
});
|
|
782
|
-
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
783
|
-
const { name, arguments: args } = request.params;
|
|
784
|
-
mcpTraceStderr(chalk.blue('[DEBUG]'), `CallTool ${name}`, JSON.stringify({
|
|
785
|
-
argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
|
|
786
|
-
hasArgs: !!args
|
|
787
|
-
}));
|
|
788
|
-
const requestSignal = extra?.signal;
|
|
789
|
-
const toolStartedAt = Date.now();
|
|
790
|
-
try {
|
|
791
|
-
if (name !== 'audit_status') {
|
|
792
|
-
logMcpDebug('tool', `call ${name}`, {
|
|
793
|
-
argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
|
|
794
|
-
pathArg: args?.path,
|
|
795
|
-
cwd: process.cwd()
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
let result;
|
|
799
|
-
throwIfRequestAborted(requestSignal, `start ${name}`);
|
|
800
|
-
switch (name) {
|
|
801
|
-
case 'start_audit': {
|
|
802
|
-
result = await withAbortSignal(requestSignal, 'start_audit', () => startAuditJob(args, {
|
|
803
|
-
defaultAnalyzers: ['solid', 'dry', 'documentation', 'react', 'data-access'],
|
|
804
|
-
defaultMinSeverity: 'warning',
|
|
805
|
-
defaultGenerateCodeMap: false,
|
|
806
|
-
}));
|
|
807
|
-
break;
|
|
808
|
-
}
|
|
809
|
-
case 'audit_status': {
|
|
810
|
-
const jobId = args.jobId;
|
|
811
|
-
if (!jobId) {
|
|
812
|
-
throw new Error('audit_status requires jobId');
|
|
813
|
-
}
|
|
814
|
-
result = getAuditJobStatus(jobId);
|
|
815
|
-
break;
|
|
816
|
-
}
|
|
817
|
-
case 'audit_results': {
|
|
818
|
-
result = await withAbortSignal(requestSignal, 'audit_results', () => getAuditResultsPage(args));
|
|
819
|
-
break;
|
|
820
|
-
}
|
|
821
|
-
case 'audit': {
|
|
822
|
-
result = await withAbortSignal(requestSignal, 'audit', () => getAuditResultsPage(args));
|
|
823
|
-
break;
|
|
824
|
-
}
|
|
825
|
-
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 () => {
|
|
826
243
|
const auditPath = path.resolve(args.path || process.cwd());
|
|
827
244
|
await assertAuditPathExists(auditPath);
|
|
828
|
-
const
|
|
829
|
-
const
|
|
830
|
-
const
|
|
831
|
-
// 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;
|
|
832
248
|
const db = CodeIndexDB.getInstance();
|
|
833
249
|
await db.initialize();
|
|
834
250
|
const storedConfigs = await db.getAllAnalyzerConfigs(auditPath);
|
|
835
|
-
// Merge stored configs with any provided configs
|
|
836
251
|
const analyzerConfigs = {
|
|
837
252
|
...storedConfigs,
|
|
838
|
-
...(args.analyzerConfigs || {})
|
|
253
|
+
...(args.analyzerConfigs || {}),
|
|
839
254
|
};
|
|
255
|
+
const scope = args.scope || 'all';
|
|
840
256
|
const runner = createAuditRunner({
|
|
841
257
|
projectRoot: auditPath,
|
|
842
|
-
enabledAnalyzers:
|
|
843
|
-
minSeverity
|
|
258
|
+
enabledAnalyzers: analyzers,
|
|
259
|
+
minSeverity,
|
|
844
260
|
verbose: false,
|
|
845
|
-
indexFunctions,
|
|
261
|
+
indexFunctions,
|
|
262
|
+
scope: scope !== 'all' ? scope : undefined,
|
|
846
263
|
...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }),
|
|
847
|
-
progressCallback: (p) => {
|
|
848
|
-
if (p.phase === 'function-indexing' &&
|
|
849
|
-
typeof p.current === 'number' &&
|
|
850
|
-
typeof p.total === 'number' &&
|
|
851
|
-
p.total > 0 &&
|
|
852
|
-
p.current % 50 !== 0 &&
|
|
853
|
-
p.current !== p.total) {
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
logMcpDebug('audit_health', p.message ?? p.phase ?? 'progress', {
|
|
857
|
-
phase: p.phase,
|
|
858
|
-
analyzer: p.analyzer,
|
|
859
|
-
current: p.current,
|
|
860
|
-
total: p.total
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
264
|
});
|
|
864
265
|
const auditResult = await runner.run();
|
|
865
|
-
const healthScore = calculateHealthScore(auditResult);
|
|
866
|
-
// Handle function indexing if enabled and functions were collected
|
|
867
|
-
let indexingResult = null;
|
|
868
266
|
if (indexFunctions && auditResult.metadata.fileToFunctionsMap) {
|
|
869
267
|
try {
|
|
870
|
-
const syncStats = { added: 0, updated: 0, removed: 0 };
|
|
871
|
-
// Sync each file's functions to handle additions, updates, and removals
|
|
872
268
|
for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) {
|
|
873
|
-
|
|
874
|
-
syncStats.added += fileStats.added;
|
|
875
|
-
syncStats.updated += fileStats.updated;
|
|
876
|
-
syncStats.removed += fileStats.removed;
|
|
269
|
+
await syncFileIndex(filePath, functions);
|
|
877
270
|
}
|
|
878
|
-
indexingResult = {
|
|
879
|
-
success: true,
|
|
880
|
-
registered: syncStats.added + syncStats.updated,
|
|
881
|
-
failed: 0,
|
|
882
|
-
syncStats
|
|
883
|
-
};
|
|
884
|
-
mcpDebugStderr(chalk.blue('[INFO]'), `Synced functions: ${syncStats.added} added, ${syncStats.updated} updated, ${syncStats.removed} removed`);
|
|
885
271
|
}
|
|
886
|
-
catch
|
|
887
|
-
|
|
272
|
+
catch {
|
|
273
|
+
// indexing is best-effort for sync audit
|
|
888
274
|
}
|
|
889
275
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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;
|
|
570
|
+
}
|
|
571
|
+
indexingResult = {
|
|
572
|
+
success: true,
|
|
573
|
+
registered: syncStats.added + syncStats.updated,
|
|
574
|
+
failed: 0,
|
|
575
|
+
syncStats,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// best-effort
|
|
580
|
+
}
|
|
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;
|
|
893
597
|
try {
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
includeDependencies: true,
|
|
899
|
-
includeUsage: false,
|
|
900
|
-
groupByDirectory: true,
|
|
901
|
-
maxDepth: 8, // Slightly smaller for health check
|
|
902
|
-
showUnusedImports: true,
|
|
903
|
-
minComplexity: 7,
|
|
904
|
-
};
|
|
905
|
-
// Generate documentation metrics
|
|
906
|
-
let documentation = undefined;
|
|
907
|
-
try {
|
|
908
|
-
// Use existing file discovery from audit result
|
|
909
|
-
const files = Object.keys(auditResult.metadata.fileToFunctionsMap || {});
|
|
910
|
-
if (files.length > 0) {
|
|
911
|
-
const docResult = await analyzeDocumentation(files);
|
|
912
|
-
documentation = docResult.metrics;
|
|
913
|
-
}
|
|
598
|
+
const files = Object.keys(auditResult.metadata.fileToFunctionsMap || {});
|
|
599
|
+
if (files.length > 0) {
|
|
600
|
+
const docResult = await analyzeDocumentation(files);
|
|
601
|
+
documentation = docResult.metrics;
|
|
914
602
|
}
|
|
915
|
-
catch (docError) {
|
|
916
|
-
console.error(chalk.yellow('[WARN]'), 'Failed to analyze documentation:', docError);
|
|
917
|
-
}
|
|
918
|
-
// Use paginated code map generation for health check too
|
|
919
|
-
const paginatedResult = await mapGenerator.generatePaginatedCodeMap(auditPath, {
|
|
920
|
-
...mapOptions,
|
|
921
|
-
includeDocumentation: !!documentation
|
|
922
|
-
});
|
|
923
|
-
codeMapResult = {
|
|
924
|
-
success: true,
|
|
925
|
-
mapId: paginatedResult.mapId,
|
|
926
|
-
summary: paginatedResult.summary,
|
|
927
|
-
quickPreview: paginatedResult.quickPreview,
|
|
928
|
-
sections: paginatedResult.summary.sectionsAvailable,
|
|
929
|
-
documentationCoverage: documentation?.coverageScore
|
|
930
|
-
};
|
|
931
|
-
mcpDebugStderr(chalk.blue('[INFO]'), `Generated paginated code map: ${paginatedResult.summary.stats.totalFiles} files, ${paginatedResult.summary.totalSections} sections`);
|
|
932
603
|
}
|
|
933
|
-
catch
|
|
934
|
-
|
|
935
|
-
codeMapResult = {
|
|
936
|
-
success: false,
|
|
937
|
-
error: error instanceof Error ? error.message : 'Failed to generate code map'
|
|
938
|
-
};
|
|
604
|
+
catch {
|
|
605
|
+
// best-effort
|
|
939
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
|
+
};
|
|
940
625
|
}
|
|
941
|
-
result = {
|
|
942
|
-
healthScore,
|
|
943
|
-
threshold,
|
|
944
|
-
passed: healthScore >= threshold,
|
|
945
|
-
status: healthScore >= threshold ? 'healthy' : 'needs-attention',
|
|
946
|
-
metrics: {
|
|
947
|
-
filesAnalyzed: auditResult.metadata.filesAnalyzed,
|
|
948
|
-
totalViolations: auditResult.summary.totalViolations,
|
|
949
|
-
criticalViolations: auditResult.summary.criticalIssues,
|
|
950
|
-
warningViolations: auditResult.summary.warnings,
|
|
951
|
-
},
|
|
952
|
-
recommendation: getHealthRecommendation(healthScore, auditResult),
|
|
953
|
-
...(indexingResult && { functionIndexing: indexingResult }),
|
|
954
|
-
...(codeMapResult && { codeMap: codeMapResult }),
|
|
955
|
-
};
|
|
956
|
-
break;
|
|
957
626
|
}
|
|
958
|
-
|
|
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) => {
|
|
959
687
|
const query = args.query;
|
|
960
688
|
const filters = args.filters;
|
|
961
689
|
const limit = args.limit || 50;
|
|
962
690
|
const offset = args.offset || 0;
|
|
963
|
-
|
|
964
|
-
if (query !== undefined && typeof query !== 'string') {
|
|
691
|
+
if (query !== undefined && typeof query !== 'string')
|
|
965
692
|
throw new Error('query must be a string');
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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) => {
|
|
976
714
|
const name = args.name;
|
|
977
715
|
const filePath = args.filePath;
|
|
978
|
-
if (!name || typeof name !== 'string')
|
|
716
|
+
if (!name || typeof name !== 'string')
|
|
979
717
|
throw new Error('name must be a non-empty string');
|
|
980
|
-
}
|
|
981
718
|
const definition = await findDefinition(name, filePath);
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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) => {
|
|
986
997
|
const tools = args.tools;
|
|
987
998
|
const serverUrl = args.serverUrl || DEFAULT_SERVER_URL;
|
|
988
999
|
const outputDir = args.outputDir || '.';
|
|
989
1000
|
const overwrite = args.overwrite || false;
|
|
990
|
-
if (!Array.isArray(tools) || tools.length === 0)
|
|
1001
|
+
if (!Array.isArray(tools) || tools.length === 0)
|
|
991
1002
|
throw new Error('tools parameter must be a non-empty array');
|
|
992
|
-
}
|
|
993
1003
|
const factory = new ConfigGeneratorFactory(serverUrl);
|
|
994
1004
|
const generatedFiles = [];
|
|
995
1005
|
const errors = [];
|
|
@@ -1002,25 +1012,21 @@ async function startMcpServer() {
|
|
|
1002
1012
|
}
|
|
1003
1013
|
const config = generator.generateConfig();
|
|
1004
1014
|
const outputPath = path.resolve(outputDir, config.filename);
|
|
1005
|
-
// Check if file exists
|
|
1006
1015
|
let fileExists = false;
|
|
1007
1016
|
try {
|
|
1008
1017
|
await fs.access(outputPath);
|
|
1009
1018
|
fileExists = true;
|
|
1010
1019
|
}
|
|
1011
1020
|
catch {
|
|
1012
|
-
//
|
|
1021
|
+
// doesn't exist
|
|
1013
1022
|
}
|
|
1014
1023
|
if (fileExists && !overwrite) {
|
|
1015
1024
|
errors.push(`File already exists: ${config.filename} (use overwrite: true to replace)`);
|
|
1016
1025
|
continue;
|
|
1017
1026
|
}
|
|
1018
|
-
// Ensure directory exists
|
|
1019
1027
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
1020
|
-
// Write main config file
|
|
1021
1028
|
await fs.writeFile(outputPath, config.content);
|
|
1022
1029
|
generatedFiles.push(config.filename);
|
|
1023
|
-
// Write additional files if any
|
|
1024
1030
|
if (config.additionalFiles) {
|
|
1025
1031
|
for (const additionalFile of config.additionalFiles) {
|
|
1026
1032
|
const additionalPath = path.resolve(outputDir, additionalFile.filename);
|
|
@@ -1034,276 +1040,516 @@ async function startMcpServer() {
|
|
|
1034
1040
|
errors.push(`Failed to generate config for ${tool}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1035
1041
|
}
|
|
1036
1042
|
}
|
|
1037
|
-
|
|
1043
|
+
return {
|
|
1038
1044
|
success: errors.length === 0,
|
|
1039
1045
|
generatedFiles,
|
|
1040
1046
|
errors: errors.length > 0 ? errors : undefined,
|
|
1041
1047
|
totalRequested: tools.length,
|
|
1042
|
-
totalGenerated: generatedFiles.length
|
|
1048
|
+
totalGenerated: generatedFiles.length,
|
|
1043
1049
|
};
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
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');
|
|
1132
1181
|
try {
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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,
|
|
1143
1194
|
};
|
|
1144
1195
|
}
|
|
1145
|
-
catch (
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1196
|
+
catch (err) {
|
|
1197
|
+
return {
|
|
1198
|
+
error: `Failed to read rules: ${err.message}`,
|
|
1199
|
+
configPath,
|
|
1200
|
+
rules: [],
|
|
1201
|
+
count: 0,
|
|
1149
1202
|
};
|
|
1150
1203
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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');
|
|
1158
1223
|
try {
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
else {
|
|
1170
|
-
const configs = await db.getAllAnalyzerConfigs(projectPath);
|
|
1171
|
-
result = {
|
|
1172
|
-
success: true,
|
|
1173
|
-
configs,
|
|
1174
|
-
scope: projectPath ? 'project' : 'global',
|
|
1175
|
-
message: `Found ${Object.keys(configs).length} analyzer configurations`
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
catch (error) {
|
|
1180
|
-
result = {
|
|
1181
|
-
success: false,
|
|
1182
|
-
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,
|
|
1183
1232
|
};
|
|
1184
1233
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
const db = CodeIndexDB.getInstance();
|
|
1191
|
-
await db.initialize();
|
|
1192
|
-
try {
|
|
1193
|
-
if (analyzerName) {
|
|
1194
|
-
const deleted = await db.deleteAnalyzerConfig(analyzerName, {
|
|
1195
|
-
projectPath,
|
|
1196
|
-
isGlobal: !projectPath
|
|
1197
|
-
});
|
|
1198
|
-
result = {
|
|
1199
|
-
success: deleted,
|
|
1200
|
-
message: deleted
|
|
1201
|
-
? `Configuration for ${analyzerName} analyzer has been reset${projectPath ? ` for project ${projectPath}` : ' globally'}`
|
|
1202
|
-
: `No configuration found for ${analyzerName} analyzer`,
|
|
1203
|
-
analyzer: analyzerName,
|
|
1204
|
-
scope: projectPath ? 'project' : 'global'
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
else {
|
|
1208
|
-
await db.resetAnalyzerConfigs(projectPath);
|
|
1209
|
-
result = {
|
|
1210
|
-
success: true,
|
|
1211
|
-
message: projectPath
|
|
1212
|
-
? `All project-specific configurations for ${projectPath} have been reset`
|
|
1213
|
-
: 'All analyzer configurations have been reset to defaults',
|
|
1214
|
-
scope: projectPath ? 'project' : 'global'
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
catch (error) {
|
|
1219
|
-
result = {
|
|
1220
|
-
success: false,
|
|
1221
|
-
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,
|
|
1222
1239
|
};
|
|
1223
1240
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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) => {
|
|
1227
1268
|
const mapId = args.mapId;
|
|
1228
1269
|
const sectionType = args.sectionType;
|
|
1229
|
-
if (!mapId || !sectionType)
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1236
|
-
try {
|
|
1237
|
-
const db = await getDatabase();
|
|
1238
|
-
const section = await db.getCodeMapSection(mapId, sectionType);
|
|
1239
|
-
if (section) {
|
|
1240
|
-
result = {
|
|
1241
|
-
success: true,
|
|
1242
|
-
mapId,
|
|
1243
|
-
sectionType,
|
|
1244
|
-
content: section.content,
|
|
1245
|
-
metadata: section.metadata
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
else {
|
|
1249
|
-
result = {
|
|
1250
|
-
success: false,
|
|
1251
|
-
error: `Section '${sectionType}' not found for map '${mapId}'`
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
catch (error) {
|
|
1256
|
-
result = {
|
|
1257
|
-
success: false,
|
|
1258
|
-
error: error instanceof Error ? error.message : 'Failed to retrieve code map section'
|
|
1259
|
-
};
|
|
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 };
|
|
1260
1276
|
}
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1263
|
-
|
|
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) => {
|
|
1264
1292
|
const mapId = args.mapId;
|
|
1265
|
-
if (!mapId)
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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');
|
|
1272
1450
|
try {
|
|
1273
|
-
const
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1451
|
+
const workflows = getWorkflowGuide(scenario);
|
|
1452
|
+
const tips = getWorkflowTips();
|
|
1453
|
+
return {
|
|
1276
1454
|
success: true,
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
totalSections: sections.length
|
|
1455
|
+
...(scenario ? { workflow: workflows } : { workflows }),
|
|
1456
|
+
tips,
|
|
1280
1457
|
};
|
|
1281
1458
|
}
|
|
1282
1459
|
catch (error) {
|
|
1283
|
-
|
|
1460
|
+
return {
|
|
1284
1461
|
success: false,
|
|
1285
|
-
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
|
+
],
|
|
1286
1471
|
};
|
|
1287
1472
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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
|
+
};
|
|
1299
1550
|
}
|
|
1300
1551
|
const response = {
|
|
1301
|
-
content: [
|
|
1302
|
-
{
|
|
1303
|
-
type: 'text',
|
|
1304
|
-
text: serializeForMcp(result),
|
|
1305
|
-
},
|
|
1306
|
-
],
|
|
1552
|
+
content: [{ type: 'text', text: serializeForMcp(result) }],
|
|
1307
1553
|
};
|
|
1308
1554
|
mcpTraceStderr(chalk.blue('[DEBUG]'), `Tool ${name} ok`, `responseBytes=${response.content[0].text.length}`);
|
|
1309
1555
|
throwIfRequestAborted(requestSignal, `before response ${name}`);
|
|
@@ -1312,9 +1558,7 @@ async function startMcpServer() {
|
|
|
1312
1558
|
catch (error) {
|
|
1313
1559
|
if (error instanceof RequestAbortedError) {
|
|
1314
1560
|
if (name !== 'audit_status') {
|
|
1315
|
-
logMcpDebug('tool', `call ${name} aborted`, {
|
|
1316
|
-
ms: Date.now() - toolStartedAt
|
|
1317
|
-
});
|
|
1561
|
+
logMcpDebug('tool', `call ${name} aborted`, { ms: Date.now() - toolStartedAt });
|
|
1318
1562
|
}
|
|
1319
1563
|
throw error;
|
|
1320
1564
|
}
|
|
@@ -1323,16 +1567,11 @@ async function startMcpServer() {
|
|
|
1323
1567
|
if (name !== 'audit_status') {
|
|
1324
1568
|
logMcpDebug('tool', `call ${name} failed`, {
|
|
1325
1569
|
ms: Date.now() - toolStartedAt,
|
|
1326
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1570
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1327
1571
|
});
|
|
1328
1572
|
}
|
|
1329
1573
|
return {
|
|
1330
|
-
content: [
|
|
1331
|
-
{
|
|
1332
|
-
type: 'text',
|
|
1333
|
-
text: serializeForMcp(formatMcpToolErrorPayload(name, error)),
|
|
1334
|
-
},
|
|
1335
|
-
],
|
|
1574
|
+
content: [{ type: 'text', text: serializeForMcp(formatMcpToolErrorPayload(name, error)) }],
|
|
1336
1575
|
isError: true,
|
|
1337
1576
|
};
|
|
1338
1577
|
}
|
|
@@ -1343,84 +1582,23 @@ async function startMcpServer() {
|
|
|
1343
1582
|
}
|
|
1344
1583
|
});
|
|
1345
1584
|
const transport = new StdioServerTransport();
|
|
1346
|
-
// Add transport event handlers if available
|
|
1347
1585
|
if (transport.onclose) {
|
|
1348
|
-
transport.onclose = () =>
|
|
1349
|
-
console.error(chalk.yellow('[WARN]'), 'Transport closed');
|
|
1350
|
-
};
|
|
1586
|
+
transport.onclose = () => console.error(chalk.yellow('[WARN]'), 'Transport closed');
|
|
1351
1587
|
}
|
|
1352
1588
|
if (transport.onerror) {
|
|
1353
|
-
transport.onerror = (error) =>
|
|
1354
|
-
console.error(chalk.red('[ERROR]'), 'Transport error:', error);
|
|
1355
|
-
};
|
|
1589
|
+
transport.onerror = (error) => console.error(chalk.red('[ERROR]'), 'Transport error:', error);
|
|
1356
1590
|
}
|
|
1357
1591
|
await server.connect(transport);
|
|
1358
1592
|
console.error(chalk.green('✓ Code Auditor MCP Server started'));
|
|
1359
|
-
console.error(chalk.gray(`Listening on stdio · ${
|
|
1593
|
+
console.error(chalk.gray(`Listening on stdio · ${toolCount} tools · ${PACKAGE_VERSION}`));
|
|
1360
1594
|
mcpDebugStderr(chalk.blue('[DEBUG]'), 'Server ready', {
|
|
1361
1595
|
name: 'code-auditor',
|
|
1362
1596
|
transport: 'stdio',
|
|
1363
|
-
toolCount
|
|
1597
|
+
toolCount,
|
|
1364
1598
|
});
|
|
1365
1599
|
}
|
|
1366
|
-
|
|
1367
|
-
const violations = [];
|
|
1368
|
-
for (const [analyzerName, analyzerResult] of Object.entries(result.analyzerResults)) {
|
|
1369
|
-
for (const violation of analyzerResult.violations) {
|
|
1370
|
-
violations.push({
|
|
1371
|
-
...violation,
|
|
1372
|
-
analyzer: analyzerName,
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
return violations;
|
|
1377
|
-
}
|
|
1378
|
-
function calculateHealthScore(result) {
|
|
1379
|
-
const filesAnalyzed = result.metadata?.filesAnalyzed || 1;
|
|
1380
|
-
const critical = result.summary.criticalIssues || 0;
|
|
1381
|
-
const warnings = result.summary.warnings || 0;
|
|
1382
|
-
const suggestions = result.summary.suggestions || 0;
|
|
1383
|
-
// Weight factors for different severity levels
|
|
1384
|
-
const weights = {
|
|
1385
|
-
critical: 10,
|
|
1386
|
-
warning: 3,
|
|
1387
|
-
suggestion: 0.5
|
|
1388
|
-
};
|
|
1389
|
-
// Calculate weighted violations per file
|
|
1390
|
-
const weightedViolations = (critical * weights.critical) +
|
|
1391
|
-
(warnings * weights.warning) +
|
|
1392
|
-
(suggestions * weights.suggestion);
|
|
1393
|
-
const violationsPerFile = weightedViolations / filesAnalyzed;
|
|
1394
|
-
// Score calculation: 100 points minus deductions
|
|
1395
|
-
// Each weighted violation per file reduces score
|
|
1396
|
-
// Critical-heavy codebases drop faster than suggestion-heavy ones
|
|
1397
|
-
let score = 100 - (violationsPerFile * 2);
|
|
1398
|
-
return Math.max(0, Math.round(Math.min(100, score)));
|
|
1399
|
-
}
|
|
1400
|
-
function generateRecommendations(result) {
|
|
1401
|
-
const recommendations = [];
|
|
1402
|
-
if (result.summary.criticalIssues > 0) {
|
|
1403
|
-
recommendations.push({
|
|
1404
|
-
priority: 'high',
|
|
1405
|
-
title: 'Fix critical violations immediately',
|
|
1406
|
-
description: `${result.summary.criticalIssues} critical issues require immediate attention`,
|
|
1407
|
-
});
|
|
1408
|
-
}
|
|
1409
|
-
// Add more recommendation logic based on patterns
|
|
1410
|
-
return recommendations;
|
|
1411
|
-
}
|
|
1412
|
-
function getHealthRecommendation(score, result) {
|
|
1413
|
-
if (score >= 90)
|
|
1414
|
-
return 'Excellent code health!';
|
|
1415
|
-
if (score >= 70)
|
|
1416
|
-
return 'Good code health with room for improvement';
|
|
1417
|
-
if (result.summary.criticalIssues > 0) {
|
|
1418
|
-
return `Fix ${result.summary.criticalIssues} critical violations first`;
|
|
1419
|
-
}
|
|
1420
|
-
return 'Code health needs attention - run detailed audit';
|
|
1421
|
-
}
|
|
1600
|
+
// ── Process setup ────────────────────────────────────────────────────────────
|
|
1422
1601
|
registerProcessReliabilityHandlers();
|
|
1423
|
-
// Add SIGTERM and SIGINT handlers
|
|
1424
1602
|
process.on('SIGTERM', () => {
|
|
1425
1603
|
console.error(chalk.yellow('[WARN]'), 'Received SIGTERM, shutting down gracefully...');
|
|
1426
1604
|
process.exitCode = 0;
|
|
@@ -1429,15 +1607,32 @@ process.on('SIGINT', () => {
|
|
|
1429
1607
|
console.error(chalk.yellow('[WARN]'), 'Received SIGINT, shutting down gracefully...');
|
|
1430
1608
|
process.exitCode = 0;
|
|
1431
1609
|
});
|
|
1432
|
-
// Log when stdin/stdout events occur
|
|
1433
1610
|
process.stdin.on('error', (error) => {
|
|
1434
1611
|
console.error(chalk.red('[ERROR]'), 'stdin error:', error);
|
|
1435
1612
|
});
|
|
1436
1613
|
process.stdout.on('error', (error) => {
|
|
1437
1614
|
console.error(chalk.red('[ERROR]'), 'stdout error:', error);
|
|
1438
1615
|
});
|
|
1439
|
-
|
|
1440
|
-
|
|
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);
|
|
1441
1636
|
console.error(chalk.red('[ERROR]'), 'Stack:', error.stack);
|
|
1442
1637
|
process.exitCode = 1;
|
|
1443
1638
|
});
|