code-auditor-mcp 2.6.2 → 3.0.1
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 +23 -196
- 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 +15 -6
- 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/codeIndexDb.js
DELETED
|
@@ -1,2102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Code Index Database using LokiJS + FlexSearch
|
|
3
|
-
* Pure JavaScript implementation with no native dependencies
|
|
4
|
-
*/
|
|
5
|
-
import Loki from 'lokijs';
|
|
6
|
-
import * as FlexSearch from 'flexsearch';
|
|
7
|
-
import { promises as fs } from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import { ProjectTaskRepository } from './services/ProjectTaskRepository.js';
|
|
10
|
-
import { QueryParser } from './search/QueryParser.js';
|
|
11
|
-
import { getPersistedStorageRoot, resolvePersistedIndexPath } from './dataPaths.js';
|
|
12
|
-
import { ContextualError, getErrnoCode } from './mcpToolErrors.js';
|
|
13
|
-
import { WhitelistType, WhitelistStatus } from './types/whitelist.js';
|
|
14
|
-
// Using SearchOptions and SearchResult from types.ts
|
|
15
|
-
export class CodeIndexDB {
|
|
16
|
-
static instance;
|
|
17
|
-
/** Subclasses (e.g. EnhancedCodeIndexDB) need access for extra collections without `as any`. */
|
|
18
|
-
db;
|
|
19
|
-
functionsCollection = null;
|
|
20
|
-
whitelistCollection = null;
|
|
21
|
-
auditResultsCollection = null;
|
|
22
|
-
analyzerConfigCollection = null;
|
|
23
|
-
codeMapCollection = null;
|
|
24
|
-
schemaCollection = null;
|
|
25
|
-
schemaUsageCollection = null;
|
|
26
|
-
/** User tasks: NOT cleared by clearIndex() — see clearIndex() */
|
|
27
|
-
tasksCollection = null;
|
|
28
|
-
taskRepository = null;
|
|
29
|
-
searchIndex; // FlexSearch Document instance
|
|
30
|
-
dbPath;
|
|
31
|
-
isInitialized = false;
|
|
32
|
-
initializePromise = null;
|
|
33
|
-
constructor(dbPath = ':memory:') {
|
|
34
|
-
this.dbPath = dbPath;
|
|
35
|
-
this.db = new Loki(dbPath, {
|
|
36
|
-
autosave: false,
|
|
37
|
-
autosaveInterval: 0,
|
|
38
|
-
autoload: false
|
|
39
|
-
});
|
|
40
|
-
// Initialize FlexSearch with full-text search configuration
|
|
41
|
-
const { Document } = FlexSearch;
|
|
42
|
-
this.searchIndex = new Document({
|
|
43
|
-
document: {
|
|
44
|
-
id: '$loki',
|
|
45
|
-
index: [
|
|
46
|
-
{
|
|
47
|
-
field: 'name',
|
|
48
|
-
tokenize: 'full',
|
|
49
|
-
optimize: true,
|
|
50
|
-
resolution: 9,
|
|
51
|
-
bidirectional: true,
|
|
52
|
-
weight: 10 // Highest weight for function name matches
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
field: 'tokenizedName',
|
|
56
|
-
tokenize: 'full',
|
|
57
|
-
optimize: true,
|
|
58
|
-
resolution: 9,
|
|
59
|
-
bidirectional: true,
|
|
60
|
-
weight: 9 // High weight for tokenized name matches
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
field: 'signature',
|
|
64
|
-
tokenize: 'full',
|
|
65
|
-
optimize: true,
|
|
66
|
-
resolution: 9,
|
|
67
|
-
weight: 8 // High weight for signature matches
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
field: 'purpose',
|
|
71
|
-
tokenize: 'full',
|
|
72
|
-
optimize: true,
|
|
73
|
-
resolution: 9,
|
|
74
|
-
weight: 7 // Important for semantic search
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
field: 'context',
|
|
78
|
-
tokenize: 'full',
|
|
79
|
-
optimize: true,
|
|
80
|
-
resolution: 9,
|
|
81
|
-
weight: 6 // Contextual information
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
field: 'jsDoc.description',
|
|
85
|
-
tokenize: 'full',
|
|
86
|
-
optimize: true,
|
|
87
|
-
resolution: 9,
|
|
88
|
-
weight: 5 // Documentation matches
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
field: 'parameters[].name',
|
|
92
|
-
tokenize: 'full',
|
|
93
|
-
optimize: true,
|
|
94
|
-
resolution: 7,
|
|
95
|
-
weight: 4 // Parameter name matches
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
field: 'parameters[].description',
|
|
99
|
-
tokenize: 'full',
|
|
100
|
-
optimize: true,
|
|
101
|
-
resolution: 7,
|
|
102
|
-
weight: 3 // Parameter description matches
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
field: 'returnType',
|
|
106
|
-
tokenize: 'full',
|
|
107
|
-
optimize: true,
|
|
108
|
-
resolution: 7,
|
|
109
|
-
weight: 2 // Return type matches
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
field: 'body',
|
|
113
|
-
tokenize: 'full',
|
|
114
|
-
optimize: true,
|
|
115
|
-
resolution: 9,
|
|
116
|
-
weight: 1 // Body content matches (lower weight to prioritize metadata matches)
|
|
117
|
-
}
|
|
118
|
-
]
|
|
119
|
-
},
|
|
120
|
-
tokenize: 'full', // Global full-text tokenization
|
|
121
|
-
optimize: true,
|
|
122
|
-
resolution: 9,
|
|
123
|
-
cache: 100,
|
|
124
|
-
context: {
|
|
125
|
-
depth: 3, // Enable contextual scoring
|
|
126
|
-
bidirectional: true, // Search in both directions
|
|
127
|
-
resolution: 9
|
|
128
|
-
},
|
|
129
|
-
threshold: 7, // Lower threshold for fuzzy matching
|
|
130
|
-
depth: 3 // Search depth for better fuzzy matching
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
static getInstance(dbPath) {
|
|
134
|
-
if (!CodeIndexDB.instance) {
|
|
135
|
-
const resolved = dbPath !== undefined && dbPath !== ''
|
|
136
|
-
? path.resolve(dbPath)
|
|
137
|
-
: resolvePersistedIndexPath();
|
|
138
|
-
CodeIndexDB.instance = new CodeIndexDB(resolved);
|
|
139
|
-
}
|
|
140
|
-
return CodeIndexDB.instance;
|
|
141
|
-
}
|
|
142
|
-
async initialize() {
|
|
143
|
-
if (this.isInitialized)
|
|
144
|
-
return;
|
|
145
|
-
if (this.initializePromise) {
|
|
146
|
-
await this.initializePromise;
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
this.initializePromise = this.initializeInternal();
|
|
150
|
-
try {
|
|
151
|
-
await this.initializePromise;
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
this.initializePromise = null;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
async initializeInternal() {
|
|
158
|
-
if (this.isInitialized)
|
|
159
|
-
return;
|
|
160
|
-
const dir = path.dirname(this.dbPath);
|
|
161
|
-
const storageRoot = getPersistedStorageRoot();
|
|
162
|
-
try {
|
|
163
|
-
let st;
|
|
164
|
-
try {
|
|
165
|
-
st = await fs.stat(dir);
|
|
166
|
-
}
|
|
167
|
-
catch (e) {
|
|
168
|
-
const code = getErrnoCode(e);
|
|
169
|
-
if (code && code !== 'ENOENT') {
|
|
170
|
-
throw new ContextualError(`Cannot access code index storage directory (${code}): ${dir}`, {
|
|
171
|
-
errnoCode: code,
|
|
172
|
-
storageRoot,
|
|
173
|
-
dbPath: this.dbPath,
|
|
174
|
-
hint: 'Fix permissions or set CODE_AUDITOR_DATA_DIR / --data-dir to a writable directory.',
|
|
175
|
-
}, e instanceof Error ? e : undefined);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (st && !st.isDirectory()) {
|
|
179
|
-
throw new ContextualError(`Code index storage path exists but is not a directory: ${dir}`, {
|
|
180
|
-
storageRoot,
|
|
181
|
-
dbPath: this.dbPath,
|
|
182
|
-
hint: 'Remove the conflicting file/path or choose a different CODE_AUDITOR_DATA_DIR / --data-dir.',
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
await fs.mkdir(dir, { recursive: true });
|
|
186
|
-
}
|
|
187
|
-
catch (e) {
|
|
188
|
-
if (e instanceof ContextualError)
|
|
189
|
-
throw e;
|
|
190
|
-
const code = getErrnoCode(e);
|
|
191
|
-
throw new ContextualError(`Failed to prepare code index storage: ${e instanceof Error ? e.message : String(e)}`, {
|
|
192
|
-
...(code && { errnoCode: code }),
|
|
193
|
-
storageRoot,
|
|
194
|
-
dbPath: this.dbPath,
|
|
195
|
-
hint: 'Ensure the storage directory is writable (default: <cwd>/.code-index when CODE_AUDITOR_DATA_DIR is unset).',
|
|
196
|
-
}, e instanceof Error ? e : undefined);
|
|
197
|
-
}
|
|
198
|
-
try {
|
|
199
|
-
await new Promise((resolve, reject) => {
|
|
200
|
-
this.db.loadDatabase({}, (err) => {
|
|
201
|
-
if (err)
|
|
202
|
-
reject(err);
|
|
203
|
-
else
|
|
204
|
-
resolve();
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
catch (e) {
|
|
209
|
-
const code = getErrnoCode(e);
|
|
210
|
-
throw new ContextualError(`Failed to load code index database: ${e instanceof Error ? e.message : String(e)}`, {
|
|
211
|
-
...(code && { errnoCode: code }),
|
|
212
|
-
storageRoot,
|
|
213
|
-
dbPath: this.dbPath,
|
|
214
|
-
hint: 'The index file may be corrupted, locked by another process, or on a read-only volume. Try a different CODE_AUDITOR_DATA_DIR or delete index.db if safe.',
|
|
215
|
-
}, e instanceof Error ? e : undefined);
|
|
216
|
-
}
|
|
217
|
-
// Get or create functions collection
|
|
218
|
-
this.functionsCollection = this.db.getCollection('functions') ||
|
|
219
|
-
this.db.addCollection('functions', {
|
|
220
|
-
indices: ['name', 'filePath', 'language']
|
|
221
|
-
});
|
|
222
|
-
// Get or create whitelist collection
|
|
223
|
-
this.whitelistCollection = this.db.getCollection('whitelist') ||
|
|
224
|
-
this.db.addCollection('whitelist', {
|
|
225
|
-
indices: ['name', 'type', 'status']
|
|
226
|
-
});
|
|
227
|
-
// Get or create audit results collection
|
|
228
|
-
this.auditResultsCollection = this.db.getCollection('auditResults') ||
|
|
229
|
-
this.db.addCollection('auditResults', {
|
|
230
|
-
indices: ['auditId', 'timestamp', 'projectPath']
|
|
231
|
-
});
|
|
232
|
-
// Get or create analyzer config collection
|
|
233
|
-
this.analyzerConfigCollection = this.db.getCollection('analyzerConfigs') ||
|
|
234
|
-
this.db.addCollection('analyzerConfigs', {
|
|
235
|
-
indices: ['analyzerName', 'projectPath', 'isGlobal']
|
|
236
|
-
});
|
|
237
|
-
// Get or create code map collection
|
|
238
|
-
this.codeMapCollection = this.db.getCollection('codeMaps') ||
|
|
239
|
-
this.db.addCollection('codeMaps', {
|
|
240
|
-
indices: ['mapId', 'sectionType', 'timestamp']
|
|
241
|
-
});
|
|
242
|
-
// Get or create schema collection
|
|
243
|
-
this.schemaCollection = this.db.getCollection('schemas') ||
|
|
244
|
-
this.db.addCollection('schemas', {
|
|
245
|
-
indices: ['schemaId', 'schemaName', 'indexedAt']
|
|
246
|
-
});
|
|
247
|
-
// Get or create schema usage collection
|
|
248
|
-
this.schemaUsageCollection = this.db.getCollection('schemaUsage') ||
|
|
249
|
-
this.db.addCollection('schemaUsage', {
|
|
250
|
-
indices: ['tableName', 'filePath', 'functionName', 'usageType']
|
|
251
|
-
});
|
|
252
|
-
this.tasksCollection = this.db.getCollection('projectTasks') ||
|
|
253
|
-
this.db.addCollection('projectTasks', {
|
|
254
|
-
indices: ['taskId', 'projectPath', 'status', 'source', 'sortOrder']
|
|
255
|
-
});
|
|
256
|
-
// Initialize default whitelists if empty
|
|
257
|
-
if (this.whitelistCollection.count() === 0) {
|
|
258
|
-
await this.initializeDefaultWhitelists();
|
|
259
|
-
}
|
|
260
|
-
// Rebuild search index from existing data
|
|
261
|
-
const existingDocs = this.functionsCollection.find();
|
|
262
|
-
for (const doc of existingDocs) {
|
|
263
|
-
const normalizedData = this.normalizeFunctionData(doc);
|
|
264
|
-
this.searchIndex.add(normalizedData);
|
|
265
|
-
}
|
|
266
|
-
this.isInitialized = true;
|
|
267
|
-
}
|
|
268
|
-
ensureInitialized() {
|
|
269
|
-
if (!this.isInitialized || !this.functionsCollection || !this.tasksCollection) {
|
|
270
|
-
throw new Error('Database not initialized. Call initialize() first.');
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Normalize function metadata for indexing
|
|
275
|
-
* Converts FunctionMetadata to have required fields for FlexSearch
|
|
276
|
-
*/
|
|
277
|
-
normalizeFunctionData(func) {
|
|
278
|
-
// If it's already enhanced, return as-is with $loki if present
|
|
279
|
-
if ('signature' in func && 'parameters' in func) {
|
|
280
|
-
const normalized = { ...func };
|
|
281
|
-
// Ensure nested jsDoc structure exists
|
|
282
|
-
if (!normalized.jsDoc) {
|
|
283
|
-
normalized.jsDoc = { description: func.purpose || '' };
|
|
284
|
-
}
|
|
285
|
-
// Add tokenized name to improve searchability
|
|
286
|
-
normalized.tokenizedName = this.tokenizeFunctionName(func.name);
|
|
287
|
-
return normalized;
|
|
288
|
-
}
|
|
289
|
-
// Convert basic FunctionMetadata to have searchable fields
|
|
290
|
-
const enhanced = {
|
|
291
|
-
...func,
|
|
292
|
-
signature: func.name, // Use name as signature for now
|
|
293
|
-
parameters: [], // Empty parameters array
|
|
294
|
-
jsDoc: {
|
|
295
|
-
description: func.purpose || ''
|
|
296
|
-
},
|
|
297
|
-
returnType: 'unknown',
|
|
298
|
-
// Add tokenized name for better searching
|
|
299
|
-
tokenizedName: this.tokenizeFunctionName(func.name),
|
|
300
|
-
// Extract body from metadata if present
|
|
301
|
-
body: func.metadata?.body
|
|
302
|
-
};
|
|
303
|
-
return enhanced;
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Tokenize function name for better search
|
|
307
|
-
* Splits camelCase, PascalCase, and snake_case into individual words
|
|
308
|
-
*/
|
|
309
|
-
tokenizeFunctionName(name) {
|
|
310
|
-
// Remove class prefix if present (e.g., "UserService.authenticate" -> "authenticate")
|
|
311
|
-
const functionName = name.includes('.') ? name.split('.').pop() : name;
|
|
312
|
-
// Split camelCase and PascalCase
|
|
313
|
-
const camelSplit = functionName
|
|
314
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
315
|
-
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2');
|
|
316
|
-
// Split snake_case and kebab-case
|
|
317
|
-
const allSplit = camelSplit
|
|
318
|
-
.replace(/[_-]/g, ' ')
|
|
319
|
-
.toLowerCase()
|
|
320
|
-
.trim();
|
|
321
|
-
return allSplit;
|
|
322
|
-
}
|
|
323
|
-
async registerFunction(func) {
|
|
324
|
-
this.ensureInitialized();
|
|
325
|
-
try {
|
|
326
|
-
// Check if function already exists (use composite key: name + filePath + lineNumber)
|
|
327
|
-
const existing = this.functionsCollection.findOne({
|
|
328
|
-
name: func.name,
|
|
329
|
-
filePath: func.filePath,
|
|
330
|
-
lineNumber: func.lineNumber
|
|
331
|
-
});
|
|
332
|
-
if (existing) {
|
|
333
|
-
// Update existing
|
|
334
|
-
Object.assign(existing, func);
|
|
335
|
-
this.functionsCollection.update(existing);
|
|
336
|
-
const normalizedData = this.normalizeFunctionData(existing);
|
|
337
|
-
this.searchIndex.update(normalizedData);
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
// Insert new
|
|
341
|
-
const doc = this.functionsCollection.insert(func);
|
|
342
|
-
const normalizedData = this.normalizeFunctionData(doc);
|
|
343
|
-
this.searchIndex.add(normalizedData);
|
|
344
|
-
}
|
|
345
|
-
// Force save
|
|
346
|
-
this.db.saveDatabase();
|
|
347
|
-
}
|
|
348
|
-
catch (error) {
|
|
349
|
-
throw new Error(`Failed to register function: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
async registerFunctions(functions) {
|
|
353
|
-
this.ensureInitialized();
|
|
354
|
-
let registered = 0;
|
|
355
|
-
let failed = 0;
|
|
356
|
-
const errors = [];
|
|
357
|
-
for (const func of functions) {
|
|
358
|
-
try {
|
|
359
|
-
await this.registerFunction(func);
|
|
360
|
-
registered++;
|
|
361
|
-
}
|
|
362
|
-
catch (error) {
|
|
363
|
-
failed++;
|
|
364
|
-
errors.push({
|
|
365
|
-
function: func.name || 'unknown',
|
|
366
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return {
|
|
371
|
-
success: failed === 0,
|
|
372
|
-
registered,
|
|
373
|
-
failed,
|
|
374
|
-
errors: errors.length > 0 ? errors : undefined
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Synchronize file index - add new functions, update existing, remove deleted
|
|
379
|
-
* @param filePath The file path to synchronize
|
|
380
|
-
* @param currentFunctions The current functions found in the file
|
|
381
|
-
* @returns Sync statistics
|
|
382
|
-
*/
|
|
383
|
-
async syncFileIndex(filePath, currentFunctions) {
|
|
384
|
-
this.ensureInitialized();
|
|
385
|
-
const stats = {
|
|
386
|
-
added: 0,
|
|
387
|
-
updated: 0,
|
|
388
|
-
removed: 0
|
|
389
|
-
};
|
|
390
|
-
try {
|
|
391
|
-
// Get all currently indexed functions for this file
|
|
392
|
-
const indexedFunctions = this.functionsCollection.find({ filePath });
|
|
393
|
-
// Create a map of current functions by composite key for quick lookup
|
|
394
|
-
const createKey = (f) => `${f.name}:${f.filePath}:${f.lineNumber}`;
|
|
395
|
-
const currentFunctionMap = new Map(currentFunctions.map(f => [createKey(f), f]));
|
|
396
|
-
// Update or add functions
|
|
397
|
-
for (const func of currentFunctions) {
|
|
398
|
-
const existing = indexedFunctions.find(f => f.name === func.name &&
|
|
399
|
-
f.filePath === func.filePath &&
|
|
400
|
-
f.lineNumber === func.lineNumber);
|
|
401
|
-
if (existing) {
|
|
402
|
-
// Update existing function
|
|
403
|
-
Object.assign(existing, func);
|
|
404
|
-
this.functionsCollection.update(existing);
|
|
405
|
-
const normalizedData = this.normalizeFunctionData(existing);
|
|
406
|
-
this.searchIndex.update(normalizedData);
|
|
407
|
-
stats.updated++;
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
// Add new function
|
|
411
|
-
const doc = this.functionsCollection.insert(func);
|
|
412
|
-
const normalizedData = this.normalizeFunctionData(doc);
|
|
413
|
-
this.searchIndex.add(normalizedData);
|
|
414
|
-
stats.added++;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
// Remove functions that no longer exist in the file
|
|
418
|
-
for (const indexed of indexedFunctions) {
|
|
419
|
-
const indexedKey = createKey(indexed);
|
|
420
|
-
if (!currentFunctionMap.has(indexedKey)) {
|
|
421
|
-
// Function no longer exists, remove it
|
|
422
|
-
this.functionsCollection.remove(indexed);
|
|
423
|
-
this.searchIndex.remove(indexed.$loki);
|
|
424
|
-
stats.removed++;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
// Update dependency graph for the affected functions
|
|
428
|
-
await this.updateDependencyGraph(filePath);
|
|
429
|
-
// Force save
|
|
430
|
-
this.db.saveDatabase();
|
|
431
|
-
}
|
|
432
|
-
catch (error) {
|
|
433
|
-
throw new Error(`Failed to sync file index: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
434
|
-
}
|
|
435
|
-
return stats;
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Update dependency graph - build reverse mappings for calledBy relationships
|
|
439
|
-
* @param filePath Optional file path to limit the update scope
|
|
440
|
-
*/
|
|
441
|
-
async updateDependencyGraph(filePath) {
|
|
442
|
-
this.ensureInitialized();
|
|
443
|
-
try {
|
|
444
|
-
// Get all functions (or just those in the specified file)
|
|
445
|
-
const functions = filePath
|
|
446
|
-
? this.functionsCollection.find({ filePath })
|
|
447
|
-
: this.functionsCollection.find();
|
|
448
|
-
// Clear existing calledBy relationships for affected functions
|
|
449
|
-
for (const func of functions) {
|
|
450
|
-
if (func.metadata) {
|
|
451
|
-
func.metadata.calledBy = [];
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
// Build calledBy relationships by iterating through all functions
|
|
455
|
-
const allFunctions = this.functionsCollection.find();
|
|
456
|
-
for (const caller of allFunctions) {
|
|
457
|
-
if (caller.metadata?.functionCalls) {
|
|
458
|
-
for (const callee of caller.metadata.functionCalls) {
|
|
459
|
-
// Find the called function
|
|
460
|
-
const calledFunc = this.findFunctionByQualifiedName(callee);
|
|
461
|
-
if (calledFunc && calledFunc.metadata) {
|
|
462
|
-
if (!calledFunc.metadata.calledBy) {
|
|
463
|
-
calledFunc.metadata.calledBy = [];
|
|
464
|
-
}
|
|
465
|
-
// Add caller to calledBy list if not already present
|
|
466
|
-
const callerName = this.getQualifiedFunctionName(caller);
|
|
467
|
-
if (!calledFunc.metadata.calledBy.includes(callerName)) {
|
|
468
|
-
calledFunc.metadata.calledBy.push(callerName);
|
|
469
|
-
this.functionsCollection.update(calledFunc);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
catch (error) {
|
|
477
|
-
throw new Error(`Failed to update dependency graph: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Find a function by its qualified name (e.g., "filePath#functionName")
|
|
482
|
-
*/
|
|
483
|
-
findFunctionByQualifiedName(qualifiedName) {
|
|
484
|
-
// Handle different name formats
|
|
485
|
-
if (qualifiedName.includes('#')) {
|
|
486
|
-
const [filePath, functionName] = qualifiedName.split('#');
|
|
487
|
-
return this.functionsCollection.findOne({
|
|
488
|
-
filePath: { $regex: filePath },
|
|
489
|
-
name: functionName
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
// Simple function name - search across all files
|
|
494
|
-
return this.functionsCollection.findOne({ name: qualifiedName });
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Get qualified name for a function
|
|
499
|
-
*/
|
|
500
|
-
getQualifiedFunctionName(func) {
|
|
501
|
-
return `${func.filePath}#${func.name}`;
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Get transitive dependencies for a function (functions it calls, directly and indirectly)
|
|
505
|
-
* @param functionName The function to analyze
|
|
506
|
-
* @param maxDepth Maximum depth to traverse (default 10)
|
|
507
|
-
* @returns Array of function names with their depth
|
|
508
|
-
*/
|
|
509
|
-
async getTransitiveDependencies(functionName, maxDepth = 10) {
|
|
510
|
-
this.ensureInitialized();
|
|
511
|
-
const dependencies = [];
|
|
512
|
-
const visited = new Set();
|
|
513
|
-
const traverse = (funcName, depth) => {
|
|
514
|
-
if (depth > maxDepth || visited.has(funcName))
|
|
515
|
-
return;
|
|
516
|
-
visited.add(funcName);
|
|
517
|
-
const func = this.findFunctionByQualifiedName(funcName);
|
|
518
|
-
if (!func?.metadata?.functionCalls)
|
|
519
|
-
return;
|
|
520
|
-
for (const callee of func.metadata.functionCalls) {
|
|
521
|
-
if (!visited.has(callee)) {
|
|
522
|
-
dependencies.push({ name: callee, depth });
|
|
523
|
-
traverse(callee, depth + 1);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
traverse(functionName, 1);
|
|
528
|
-
return dependencies;
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Get transitive callers for a function (functions that call it, directly and indirectly)
|
|
532
|
-
* @param functionName The function to analyze
|
|
533
|
-
* @param maxDepth Maximum depth to traverse (default 10)
|
|
534
|
-
* @returns Array of function names with their depth
|
|
535
|
-
*/
|
|
536
|
-
async getTransitiveCallers(functionName, maxDepth = 10) {
|
|
537
|
-
this.ensureInitialized();
|
|
538
|
-
const callers = [];
|
|
539
|
-
const visited = new Set();
|
|
540
|
-
const traverse = (funcName, depth) => {
|
|
541
|
-
if (depth > maxDepth || visited.has(funcName))
|
|
542
|
-
return;
|
|
543
|
-
visited.add(funcName);
|
|
544
|
-
const func = this.findFunctionByQualifiedName(funcName);
|
|
545
|
-
if (!func?.metadata?.calledBy)
|
|
546
|
-
return;
|
|
547
|
-
for (const caller of func.metadata.calledBy) {
|
|
548
|
-
if (!visited.has(caller)) {
|
|
549
|
-
callers.push({ name: caller, depth });
|
|
550
|
-
traverse(caller, depth + 1);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
traverse(functionName, 1);
|
|
555
|
-
return callers;
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Detect circular dependencies in the codebase
|
|
559
|
-
* @returns Array of circular dependency chains
|
|
560
|
-
*/
|
|
561
|
-
async detectCircularDependencies() {
|
|
562
|
-
this.ensureInitialized();
|
|
563
|
-
const cycles = [];
|
|
564
|
-
const allFunctions = this.functionsCollection.find();
|
|
565
|
-
for (const func of allFunctions) {
|
|
566
|
-
if (!func.metadata?.functionCalls)
|
|
567
|
-
continue;
|
|
568
|
-
const funcName = this.getQualifiedFunctionName(func);
|
|
569
|
-
const visited = new Set();
|
|
570
|
-
const path = [];
|
|
571
|
-
const hasCycle = (current) => {
|
|
572
|
-
if (path.includes(current)) {
|
|
573
|
-
// Found a cycle - extract it
|
|
574
|
-
const cycleStart = path.indexOf(current);
|
|
575
|
-
const cycle = [...path.slice(cycleStart), current];
|
|
576
|
-
// Check if we already have this cycle (in any rotation)
|
|
577
|
-
const isNewCycle = !cycles.some(existing => existing.length === cycle.length &&
|
|
578
|
-
existing.every(f => cycle.includes(f)));
|
|
579
|
-
if (isNewCycle) {
|
|
580
|
-
cycles.push(cycle);
|
|
581
|
-
}
|
|
582
|
-
return true;
|
|
583
|
-
}
|
|
584
|
-
if (visited.has(current))
|
|
585
|
-
return false;
|
|
586
|
-
visited.add(current);
|
|
587
|
-
path.push(current);
|
|
588
|
-
const currentFunc = this.findFunctionByQualifiedName(current);
|
|
589
|
-
if (currentFunc?.metadata?.functionCalls) {
|
|
590
|
-
for (const callee of currentFunc.metadata.functionCalls) {
|
|
591
|
-
if (hasCycle(callee))
|
|
592
|
-
return true;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
path.pop();
|
|
596
|
-
return false;
|
|
597
|
-
};
|
|
598
|
-
hasCycle(funcName);
|
|
599
|
-
}
|
|
600
|
-
return cycles;
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Calculate the maximum dependency depth for each function
|
|
604
|
-
* Updates the dependencyDepth field in metadata
|
|
605
|
-
*/
|
|
606
|
-
async calculateDependencyDepths() {
|
|
607
|
-
this.ensureInitialized();
|
|
608
|
-
const allFunctions = this.functionsCollection.find();
|
|
609
|
-
for (const func of allFunctions) {
|
|
610
|
-
const funcName = this.getQualifiedFunctionName(func);
|
|
611
|
-
const dependencies = await this.getTransitiveDependencies(funcName);
|
|
612
|
-
const maxDepth = dependencies.length > 0
|
|
613
|
-
? Math.max(...dependencies.map(d => d.depth))
|
|
614
|
-
: 0;
|
|
615
|
-
if (!func.metadata) {
|
|
616
|
-
func.metadata = {};
|
|
617
|
-
}
|
|
618
|
-
func.metadata.dependencyDepth = maxDepth;
|
|
619
|
-
this.functionsCollection.update(func);
|
|
620
|
-
}
|
|
621
|
-
this.db.saveDatabase();
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Get all functions from the index
|
|
625
|
-
*/
|
|
626
|
-
async getAllFunctions() {
|
|
627
|
-
this.ensureInitialized();
|
|
628
|
-
const allFunctions = this.functionsCollection.find();
|
|
629
|
-
// Return the full enhanced metadata
|
|
630
|
-
return allFunctions.map(func => ({
|
|
631
|
-
// Base metadata
|
|
632
|
-
name: func.name,
|
|
633
|
-
filePath: func.filePath,
|
|
634
|
-
lineNumber: func.lineNumber,
|
|
635
|
-
startLine: func.startLine,
|
|
636
|
-
endLine: func.endLine,
|
|
637
|
-
language: func.language,
|
|
638
|
-
dependencies: func.dependencies,
|
|
639
|
-
purpose: func.purpose,
|
|
640
|
-
context: func.context,
|
|
641
|
-
metadata: func.metadata,
|
|
642
|
-
// Enhanced metadata
|
|
643
|
-
signature: func.signature || '',
|
|
644
|
-
parameters: func.parameters || [],
|
|
645
|
-
jsDoc: typeof func.jsDoc === 'string' ? { description: func.jsDoc } : (func.jsDoc || {}),
|
|
646
|
-
imports: func.imports || [],
|
|
647
|
-
body: func.body || '',
|
|
648
|
-
comments: func.comments || [],
|
|
649
|
-
isAsync: func.isAsync || false,
|
|
650
|
-
isGenerator: func.isGenerator || false,
|
|
651
|
-
returnType: func.returnType,
|
|
652
|
-
visibility: func.visibility || 'public',
|
|
653
|
-
// Additional fields from metadata if available
|
|
654
|
-
...(func.metadata || {})
|
|
655
|
-
}));
|
|
656
|
-
}
|
|
657
|
-
async searchFunctions(options) {
|
|
658
|
-
this.ensureInitialized();
|
|
659
|
-
const startTime = Date.now();
|
|
660
|
-
let results = [];
|
|
661
|
-
let searchScores = new Map(); // Map of $loki id to relevance score
|
|
662
|
-
// Parse the query if provided
|
|
663
|
-
const queryParser = new QueryParser();
|
|
664
|
-
let parsedQuery;
|
|
665
|
-
// Determine search mode
|
|
666
|
-
const searchMode = options.searchMode || 'metadata';
|
|
667
|
-
if (options.query) {
|
|
668
|
-
parsedQuery = options.parsedQuery || queryParser.parse(options.query);
|
|
669
|
-
// Check if there are search terms or just filters
|
|
670
|
-
if (parsedQuery.terms.length > 0 || parsedQuery.phrases.length > 0) {
|
|
671
|
-
if (searchMode === 'content') {
|
|
672
|
-
// Content search only
|
|
673
|
-
results = await this.executeContentSearch(parsedQuery, searchScores);
|
|
674
|
-
}
|
|
675
|
-
else if (searchMode === 'both') {
|
|
676
|
-
// Both metadata and content search
|
|
677
|
-
const metadataResults = await this.executeMultiStrategySearch(parsedQuery, searchScores);
|
|
678
|
-
const contentResults = await this.executeContentSearch(parsedQuery, searchScores);
|
|
679
|
-
// Merge results, combining scores for duplicates
|
|
680
|
-
const resultMap = new Map();
|
|
681
|
-
metadataResults.forEach(doc => {
|
|
682
|
-
if (doc.$loki !== undefined) {
|
|
683
|
-
resultMap.set(doc.$loki, doc);
|
|
684
|
-
}
|
|
685
|
-
});
|
|
686
|
-
contentResults.forEach(doc => {
|
|
687
|
-
if (doc.$loki !== undefined) {
|
|
688
|
-
resultMap.set(doc.$loki, doc);
|
|
689
|
-
// Combine scores
|
|
690
|
-
const existingScore = searchScores.get(doc.$loki) || 0;
|
|
691
|
-
searchScores.set(doc.$loki, existingScore);
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
results = Array.from(resultMap.values());
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
// Metadata search only (default)
|
|
698
|
-
results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
else {
|
|
702
|
-
// No search terms, just filters - get all
|
|
703
|
-
results = this.functionsCollection.find();
|
|
704
|
-
results.forEach((doc, index) => {
|
|
705
|
-
if (doc.$loki !== undefined) {
|
|
706
|
-
searchScores.set(doc.$loki, 50); // Base score for browse mode
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
else if (options.parsedQuery) {
|
|
712
|
-
parsedQuery = options.parsedQuery;
|
|
713
|
-
// Check if there are search terms or just filters
|
|
714
|
-
if (parsedQuery.terms.length > 0 || parsedQuery.phrases.length > 0) {
|
|
715
|
-
if (searchMode === 'content') {
|
|
716
|
-
results = await this.executeContentSearch(parsedQuery, searchScores);
|
|
717
|
-
}
|
|
718
|
-
else if (searchMode === 'both') {
|
|
719
|
-
const metadataResults = await this.executeMultiStrategySearch(parsedQuery, searchScores);
|
|
720
|
-
const contentResults = await this.executeContentSearch(parsedQuery, searchScores);
|
|
721
|
-
// Merge results
|
|
722
|
-
const resultMap = new Map();
|
|
723
|
-
metadataResults.forEach(doc => {
|
|
724
|
-
if (doc.$loki !== undefined) {
|
|
725
|
-
resultMap.set(doc.$loki, doc);
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
contentResults.forEach(doc => {
|
|
729
|
-
if (doc.$loki !== undefined) {
|
|
730
|
-
resultMap.set(doc.$loki, doc);
|
|
731
|
-
// Combine scores
|
|
732
|
-
const existingScore = searchScores.get(doc.$loki) || 0;
|
|
733
|
-
searchScores.set(doc.$loki, existingScore);
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
results = Array.from(resultMap.values());
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
else {
|
|
743
|
-
// No search terms, just filters - get all
|
|
744
|
-
results = this.functionsCollection.find();
|
|
745
|
-
results.forEach((doc, index) => {
|
|
746
|
-
if (doc.$loki !== undefined) {
|
|
747
|
-
searchScores.set(doc.$loki, 50); // Base score for browse mode
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
else {
|
|
753
|
-
// No query, get all
|
|
754
|
-
results = this.functionsCollection.find();
|
|
755
|
-
// Give all results a base score
|
|
756
|
-
results.forEach((doc, index) => {
|
|
757
|
-
if (doc.$loki !== undefined) {
|
|
758
|
-
searchScores.set(doc.$loki, 50); // Base score for browse mode
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
// Apply filters from both options and parsedQuery
|
|
763
|
-
const combinedFilters = this.mergeFilters(options.filters, parsedQuery?.filters);
|
|
764
|
-
results = this.applyFilters(results, combinedFilters);
|
|
765
|
-
// Sort by relevance score
|
|
766
|
-
results.sort((a, b) => {
|
|
767
|
-
const scoreA = searchScores.get(a.$loki) || 0;
|
|
768
|
-
const scoreB = searchScores.get(b.$loki) || 0;
|
|
769
|
-
return scoreB - scoreA; // Higher scores first
|
|
770
|
-
});
|
|
771
|
-
// Apply pagination
|
|
772
|
-
const totalCount = results.length;
|
|
773
|
-
const limit = options.limit || 50;
|
|
774
|
-
const offset = options.offset || 0;
|
|
775
|
-
results = results.slice(offset, offset + limit);
|
|
776
|
-
// Clean up results and add scores
|
|
777
|
-
const functions = results.map((doc) => {
|
|
778
|
-
const { $loki, meta, ...functionData } = doc;
|
|
779
|
-
const score = searchScores.get($loki) || 0;
|
|
780
|
-
return {
|
|
781
|
-
...functionData,
|
|
782
|
-
score
|
|
783
|
-
};
|
|
784
|
-
});
|
|
785
|
-
return {
|
|
786
|
-
functions,
|
|
787
|
-
totalCount,
|
|
788
|
-
query: options.query,
|
|
789
|
-
parsedQuery,
|
|
790
|
-
executionTime: Date.now() - startTime
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Execute multi-strategy search with different approaches
|
|
795
|
-
* @param parsedQuery The parsed query object
|
|
796
|
-
* @param searchScores Map to store relevance scores
|
|
797
|
-
* @returns Array of matching documents
|
|
798
|
-
*/
|
|
799
|
-
async executeMultiStrategySearch(parsedQuery, searchScores) {
|
|
800
|
-
const resultsMap = new Map(); // Map of $loki id to document
|
|
801
|
-
// Strategy 1: Exact phrase matching (highest weight)
|
|
802
|
-
if (parsedQuery.phrases.length > 0) {
|
|
803
|
-
for (const phrase of parsedQuery.phrases) {
|
|
804
|
-
const phraseResults = await this.searchWithFlexSearch(phrase, 1000);
|
|
805
|
-
this.mergeSearchResults(phraseResults, resultsMap, searchScores, 100); // High weight for exact phrases
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
// Strategy 2: All terms must match (AND logic) - use original terms
|
|
809
|
-
const originalTerms = parsedQuery.originalTerms || parsedQuery.terms;
|
|
810
|
-
if (originalTerms.length > 0) {
|
|
811
|
-
// For each original term, search including its synonyms
|
|
812
|
-
const termResultSets = new Map();
|
|
813
|
-
for (const originalTerm of originalTerms) {
|
|
814
|
-
// Get synonyms for this term
|
|
815
|
-
const queryParser = new QueryParser();
|
|
816
|
-
const synonyms = queryParser.getSynonyms(originalTerm);
|
|
817
|
-
const searchTerms = [originalTerm, ...synonyms];
|
|
818
|
-
// Find documents containing ANY of the synonyms
|
|
819
|
-
const termIds = new Set();
|
|
820
|
-
for (const searchTerm of searchTerms) {
|
|
821
|
-
const termResults = await this.searchWithFlexSearch(searchTerm, 1000);
|
|
822
|
-
termResults.forEach(doc => {
|
|
823
|
-
if (doc.$loki !== undefined)
|
|
824
|
-
termIds.add(doc.$loki);
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
termResultSets.set(originalTerm, termIds);
|
|
828
|
-
}
|
|
829
|
-
// Find documents that contain ALL original terms (or their synonyms)
|
|
830
|
-
if (termResultSets.size > 0) {
|
|
831
|
-
const allTermIds = this.intersectSets(Array.from(termResultSets.values()));
|
|
832
|
-
if (allTermIds.size > 0) {
|
|
833
|
-
const allTermDocs = this.functionsCollection.find({
|
|
834
|
-
$loki: { $in: Array.from(allTermIds) }
|
|
835
|
-
});
|
|
836
|
-
// Calculate score based on how many fields matched
|
|
837
|
-
allTermDocs.forEach(doc => {
|
|
838
|
-
const baseScore = 80; // Base score for AND matches
|
|
839
|
-
const fieldMatchBonus = this.calculateFieldMatchScore(doc, originalTerms);
|
|
840
|
-
this.mergeSearchResults([doc], resultsMap, searchScores, baseScore + fieldMatchBonus);
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
// Strategy 3: Any term can match (OR logic) - lower weight
|
|
846
|
-
if (parsedQuery.terms.length > 0) {
|
|
847
|
-
const orQuery = parsedQuery.terms.join(' ');
|
|
848
|
-
const orResults = await this.searchWithFlexSearch(orQuery, 1000);
|
|
849
|
-
// Score based on how many terms matched
|
|
850
|
-
orResults.forEach(doc => {
|
|
851
|
-
const matchCount = this.countMatchingTerms(doc, parsedQuery.terms);
|
|
852
|
-
const score = 40 + (matchCount * 10); // Base score + bonus per matched term
|
|
853
|
-
this.mergeSearchResults([doc], resultsMap, searchScores, score);
|
|
854
|
-
});
|
|
855
|
-
}
|
|
856
|
-
// Strategy 4: Fuzzy search if enabled
|
|
857
|
-
if (parsedQuery.fuzzy && parsedQuery.terms.length > 0) {
|
|
858
|
-
// FlexSearch already provides fuzzy matching with threshold setting
|
|
859
|
-
const fuzzyQuery = parsedQuery.terms.join(' ');
|
|
860
|
-
const fuzzyResults = await this.searchWithFlexSearch(fuzzyQuery, 1000, true);
|
|
861
|
-
this.mergeSearchResults(fuzzyResults, resultsMap, searchScores, 30); // Lower weight for fuzzy
|
|
862
|
-
}
|
|
863
|
-
// Apply excluded terms filter
|
|
864
|
-
let finalResults = Array.from(resultsMap.values());
|
|
865
|
-
if (parsedQuery.excludedTerms.length > 0) {
|
|
866
|
-
finalResults = this.excludeTermsFromResults(finalResults, parsedQuery.excludedTerms);
|
|
867
|
-
}
|
|
868
|
-
return finalResults;
|
|
869
|
-
}
|
|
870
|
-
/**
|
|
871
|
-
* Search using FlexSearch and return document results
|
|
872
|
-
*/
|
|
873
|
-
async searchWithFlexSearch(query, limit, fuzzy = false) {
|
|
874
|
-
const searchOptions = { limit };
|
|
875
|
-
const searchResults = await this.searchIndex.search(query, searchOptions);
|
|
876
|
-
// Extract loki IDs from FlexSearch results
|
|
877
|
-
const lokiIds = [];
|
|
878
|
-
for (const fieldResult of searchResults) {
|
|
879
|
-
if (fieldResult.result && Array.isArray(fieldResult.result)) {
|
|
880
|
-
lokiIds.push(...fieldResult.result);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
// Remove duplicates and fetch documents
|
|
884
|
-
const uniqueLokiIds = [...new Set(lokiIds)];
|
|
885
|
-
if (uniqueLokiIds.length === 0) {
|
|
886
|
-
return [];
|
|
887
|
-
}
|
|
888
|
-
return this.functionsCollection.find({
|
|
889
|
-
$loki: { $in: uniqueLokiIds }
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* Merge search results with scoring
|
|
894
|
-
*/
|
|
895
|
-
mergeSearchResults(newResults, resultsMap, searchScores, weight) {
|
|
896
|
-
newResults.forEach(doc => {
|
|
897
|
-
if (doc.$loki === undefined)
|
|
898
|
-
return;
|
|
899
|
-
// Add or update document in results map
|
|
900
|
-
resultsMap.set(doc.$loki, doc);
|
|
901
|
-
// Update score (accumulate if already exists)
|
|
902
|
-
const currentScore = searchScores.get(doc.$loki) || 0;
|
|
903
|
-
searchScores.set(doc.$loki, currentScore + weight);
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
/**
|
|
907
|
-
* Calculate bonus score based on which fields matched
|
|
908
|
-
*/
|
|
909
|
-
calculateFieldMatchScore(doc, terms) {
|
|
910
|
-
let score = 0;
|
|
911
|
-
const lowerTerms = terms.map(t => t.toLowerCase());
|
|
912
|
-
// Check each field with different weights
|
|
913
|
-
if (doc.name && lowerTerms.some(t => doc.name.toLowerCase().includes(t))) {
|
|
914
|
-
score += 15; // High bonus for name match
|
|
915
|
-
}
|
|
916
|
-
if (doc.signature && lowerTerms.some(t => doc.signature.toLowerCase().includes(t))) {
|
|
917
|
-
score += 10;
|
|
918
|
-
}
|
|
919
|
-
if (doc.purpose && lowerTerms.some(t => doc.purpose.toLowerCase().includes(t))) {
|
|
920
|
-
score += 8;
|
|
921
|
-
}
|
|
922
|
-
if (doc.jsDoc?.description && lowerTerms.some(t => doc.jsDoc.description.toLowerCase().includes(t))) {
|
|
923
|
-
score += 5;
|
|
924
|
-
}
|
|
925
|
-
return score;
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Count how many terms match in a document
|
|
929
|
-
*/
|
|
930
|
-
countMatchingTerms(doc, terms) {
|
|
931
|
-
let count = 0;
|
|
932
|
-
const documentText = this.getDocumentSearchText(doc).toLowerCase();
|
|
933
|
-
terms.forEach(term => {
|
|
934
|
-
if (documentText.includes(term.toLowerCase())) {
|
|
935
|
-
count++;
|
|
936
|
-
}
|
|
937
|
-
});
|
|
938
|
-
return count;
|
|
939
|
-
}
|
|
940
|
-
/**
|
|
941
|
-
* Get searchable text from document
|
|
942
|
-
*/
|
|
943
|
-
getDocumentSearchText(doc) {
|
|
944
|
-
const parts = [
|
|
945
|
-
doc.name,
|
|
946
|
-
doc.signature,
|
|
947
|
-
doc.purpose,
|
|
948
|
-
doc.context,
|
|
949
|
-
doc.jsDoc?.description,
|
|
950
|
-
doc.returnType,
|
|
951
|
-
...(doc.parameters || []).map(p => `${p.name} ${p.description || ''}`),
|
|
952
|
-
...doc.dependencies
|
|
953
|
-
];
|
|
954
|
-
return parts.filter(Boolean).join(' ');
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* Get synonyms for a term using QueryParser
|
|
958
|
-
*/
|
|
959
|
-
getSynonyms(term) {
|
|
960
|
-
const queryParser = new QueryParser();
|
|
961
|
-
return queryParser.getSynonyms(term);
|
|
962
|
-
}
|
|
963
|
-
/**
|
|
964
|
-
* Exclude documents containing any of the excluded terms
|
|
965
|
-
*/
|
|
966
|
-
excludeTermsFromResults(results, excludedTerms) {
|
|
967
|
-
return results.filter(doc => {
|
|
968
|
-
const searchText = this.getDocumentSearchText(doc).toLowerCase();
|
|
969
|
-
return !excludedTerms.some(term => searchText.includes(term.toLowerCase()));
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
/**
|
|
973
|
-
* Intersect multiple sets to find common elements
|
|
974
|
-
*/
|
|
975
|
-
intersectSets(sets) {
|
|
976
|
-
if (sets.length === 0)
|
|
977
|
-
return new Set();
|
|
978
|
-
if (sets.length === 1)
|
|
979
|
-
return sets[0];
|
|
980
|
-
const result = new Set(sets[0]);
|
|
981
|
-
for (let i = 1; i < sets.length; i++) {
|
|
982
|
-
for (const item of result) {
|
|
983
|
-
if (!sets[i].has(item)) {
|
|
984
|
-
result.delete(item);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
return result;
|
|
989
|
-
}
|
|
990
|
-
/**
|
|
991
|
-
* Merge filters from options and parsed query
|
|
992
|
-
*/
|
|
993
|
-
mergeFilters(optionsFilters, queryFilters) {
|
|
994
|
-
const merged = {};
|
|
995
|
-
// Copy options filters
|
|
996
|
-
if (optionsFilters) {
|
|
997
|
-
Object.assign(merged, optionsFilters);
|
|
998
|
-
}
|
|
999
|
-
// Merge query filters (query filters take precedence)
|
|
1000
|
-
if (queryFilters) {
|
|
1001
|
-
if (queryFilters.language)
|
|
1002
|
-
merged.language = queryFilters.language;
|
|
1003
|
-
if (queryFilters.filePath)
|
|
1004
|
-
merged.filePath = queryFilters.filePath;
|
|
1005
|
-
if (queryFilters.fileType)
|
|
1006
|
-
merged.fileType = queryFilters.fileType;
|
|
1007
|
-
if (queryFilters.hasJsDoc !== undefined)
|
|
1008
|
-
merged.hasJsDoc = queryFilters.hasJsDoc;
|
|
1009
|
-
if (queryFilters.complexity)
|
|
1010
|
-
merged.complexity = queryFilters.complexity;
|
|
1011
|
-
if (queryFilters.dateRange)
|
|
1012
|
-
merged.dateRange = queryFilters.dateRange;
|
|
1013
|
-
if (queryFilters.metadata)
|
|
1014
|
-
merged.metadata = queryFilters.metadata;
|
|
1015
|
-
}
|
|
1016
|
-
return merged;
|
|
1017
|
-
}
|
|
1018
|
-
/**
|
|
1019
|
-
* Apply filters to search results
|
|
1020
|
-
*/
|
|
1021
|
-
applyFilters(results, filters) {
|
|
1022
|
-
if (!filters)
|
|
1023
|
-
return results;
|
|
1024
|
-
let filtered = results;
|
|
1025
|
-
if (filters.language) {
|
|
1026
|
-
filtered = filtered.filter(doc => doc.language === filters.language);
|
|
1027
|
-
}
|
|
1028
|
-
if (filters.filePath) {
|
|
1029
|
-
// Support both exact match and includes based on the filter format
|
|
1030
|
-
if (filters.filePath.includes('*') || filters.filePath.includes('?')) {
|
|
1031
|
-
// Glob pattern - convert to regex
|
|
1032
|
-
const pattern = filters.filePath
|
|
1033
|
-
.replace(/\*/g, '.*')
|
|
1034
|
-
.replace(/\?/g, '.')
|
|
1035
|
-
.replace(/\//g, '\\/');
|
|
1036
|
-
const regex = new RegExp(pattern);
|
|
1037
|
-
filtered = filtered.filter(doc => regex.test(doc.filePath));
|
|
1038
|
-
}
|
|
1039
|
-
else if (filters.filePath.endsWith('.ts') || filters.filePath.endsWith('.tsx') ||
|
|
1040
|
-
filters.filePath.endsWith('.js') || filters.filePath.endsWith('.jsx')) {
|
|
1041
|
-
// If it looks like a full filename, match the end of the path
|
|
1042
|
-
filtered = filtered.filter(doc => doc.filePath.endsWith(filters.filePath));
|
|
1043
|
-
}
|
|
1044
|
-
else {
|
|
1045
|
-
// Otherwise, do substring match (for directory paths)
|
|
1046
|
-
filtered = filtered.filter(doc => doc.filePath.includes(filters.filePath));
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
if (filters.fileType) {
|
|
1050
|
-
filtered = filtered.filter(doc => doc.filePath.endsWith(filters.fileType));
|
|
1051
|
-
}
|
|
1052
|
-
if (filters.hasJsDoc !== undefined) {
|
|
1053
|
-
filtered = filtered.filter(doc => {
|
|
1054
|
-
const hasJsDoc = doc.jsDoc && doc.jsDoc.description && doc.jsDoc.description.length > 0;
|
|
1055
|
-
return filters.hasJsDoc ? hasJsDoc : !hasJsDoc;
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
if (filters.complexity) {
|
|
1059
|
-
filtered = filtered.filter(doc => {
|
|
1060
|
-
if (!doc.complexity)
|
|
1061
|
-
return false;
|
|
1062
|
-
const complexity = doc.complexity;
|
|
1063
|
-
const min = filters.complexity.min || 0;
|
|
1064
|
-
const max = filters.complexity.max || Infinity;
|
|
1065
|
-
return complexity >= min && complexity <= max;
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
if (filters.hasAnyDependency && filters.hasAnyDependency.length > 0) {
|
|
1069
|
-
filtered = filtered.filter(doc => filters.hasAnyDependency.some(dep => doc.dependencies.includes(dep)));
|
|
1070
|
-
}
|
|
1071
|
-
// Apply metadata filters
|
|
1072
|
-
if (filters.metadata) {
|
|
1073
|
-
filtered = filtered.filter(doc => {
|
|
1074
|
-
if (!doc.metadata)
|
|
1075
|
-
return false;
|
|
1076
|
-
// Check entityType
|
|
1077
|
-
if (filters.metadata.entityType &&
|
|
1078
|
-
doc.metadata.entityType !== filters.metadata.entityType) {
|
|
1079
|
-
return false;
|
|
1080
|
-
}
|
|
1081
|
-
// Check componentType
|
|
1082
|
-
if (filters.metadata.componentType &&
|
|
1083
|
-
doc.metadata.componentType !== filters.metadata.componentType) {
|
|
1084
|
-
return false;
|
|
1085
|
-
}
|
|
1086
|
-
// Check hasHook
|
|
1087
|
-
if (filters.metadata.hasHook && doc.metadata.hooks) {
|
|
1088
|
-
const hasHook = doc.metadata.hooks.some(hook => hook.name.toLowerCase().includes(filters.metadata.hasHook.toLowerCase()));
|
|
1089
|
-
if (!hasHook)
|
|
1090
|
-
return false;
|
|
1091
|
-
}
|
|
1092
|
-
else if (filters.metadata.hasHook) {
|
|
1093
|
-
return false; // No hooks but filter requires one
|
|
1094
|
-
}
|
|
1095
|
-
// Check hasProp
|
|
1096
|
-
if (filters.metadata.hasProp && doc.metadata.props) {
|
|
1097
|
-
const hasProp = doc.metadata.props.some(prop => prop.name.toLowerCase().includes(filters.metadata.hasProp.toLowerCase()));
|
|
1098
|
-
if (!hasProp)
|
|
1099
|
-
return false;
|
|
1100
|
-
}
|
|
1101
|
-
else if (filters.metadata.hasProp) {
|
|
1102
|
-
return false; // No props but filter requires one
|
|
1103
|
-
}
|
|
1104
|
-
// Check usesDependency
|
|
1105
|
-
if (filters.metadata.usesDependency) {
|
|
1106
|
-
const dep = filters.metadata.usesDependency.toLowerCase();
|
|
1107
|
-
// Check in both file-level dependencies and function-specific usedImports
|
|
1108
|
-
const usesDepInFile = doc.dependencies.some(d => d.toLowerCase().includes(dep));
|
|
1109
|
-
const usesDepInFunction = doc.metadata.usedImports?.some(imp => imp.toLowerCase().includes(dep)) || false;
|
|
1110
|
-
if (!usesDepInFile && !usesDepInFunction)
|
|
1111
|
-
return false;
|
|
1112
|
-
}
|
|
1113
|
-
// Check callsFunction
|
|
1114
|
-
if (filters.metadata.callsFunction && doc.metadata.functionCalls) {
|
|
1115
|
-
const targetFunc = filters.metadata.callsFunction.toLowerCase();
|
|
1116
|
-
const callsFunc = doc.metadata.functionCalls.some(call => call.toLowerCase().includes(targetFunc));
|
|
1117
|
-
if (!callsFunc)
|
|
1118
|
-
return false;
|
|
1119
|
-
}
|
|
1120
|
-
else if (filters.metadata.callsFunction) {
|
|
1121
|
-
return false; // No function calls but filter requires one
|
|
1122
|
-
}
|
|
1123
|
-
// Check calledByFunction
|
|
1124
|
-
if (filters.metadata.calledByFunction && doc.metadata.calledBy) {
|
|
1125
|
-
const callerFunc = filters.metadata.calledByFunction.toLowerCase();
|
|
1126
|
-
const isCalledBy = doc.metadata.calledBy.some(caller => caller.toLowerCase().includes(callerFunc));
|
|
1127
|
-
if (!isCalledBy)
|
|
1128
|
-
return false;
|
|
1129
|
-
}
|
|
1130
|
-
else if (filters.metadata.calledByFunction) {
|
|
1131
|
-
return false; // Not called by any function but filter requires one
|
|
1132
|
-
}
|
|
1133
|
-
// Check dependsOnModule
|
|
1134
|
-
if (filters.metadata.dependsOnModule) {
|
|
1135
|
-
const module = filters.metadata.dependsOnModule.toLowerCase();
|
|
1136
|
-
// Check if any import or function call references this module
|
|
1137
|
-
const dependsOnFile = doc.filePath.toLowerCase().includes(module);
|
|
1138
|
-
const dependsOnImport = doc.dependencies.some(dep => dep.toLowerCase().includes(module));
|
|
1139
|
-
const dependsOnCall = doc.metadata.functionCalls?.some(call => call.toLowerCase().includes(module)) || false;
|
|
1140
|
-
if (!dependsOnFile && !dependsOnImport && !dependsOnCall)
|
|
1141
|
-
return false;
|
|
1142
|
-
}
|
|
1143
|
-
// Check hasUnusedImports
|
|
1144
|
-
if (filters.metadata.hasUnusedImports) {
|
|
1145
|
-
const hasUnused = doc.metadata.unusedImports &&
|
|
1146
|
-
doc.metadata.unusedImports.length > 0;
|
|
1147
|
-
if (!hasUnused)
|
|
1148
|
-
return false;
|
|
1149
|
-
}
|
|
1150
|
-
return true;
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
return filtered;
|
|
1154
|
-
}
|
|
1155
|
-
async findDefinition(name, filePath) {
|
|
1156
|
-
this.ensureInitialized();
|
|
1157
|
-
const query = { name };
|
|
1158
|
-
if (filePath) {
|
|
1159
|
-
query.filePath = filePath;
|
|
1160
|
-
}
|
|
1161
|
-
const result = this.functionsCollection.findOne(query);
|
|
1162
|
-
if (!result)
|
|
1163
|
-
return null;
|
|
1164
|
-
const { $loki, meta, ...functionData } = result;
|
|
1165
|
-
return functionData;
|
|
1166
|
-
}
|
|
1167
|
-
async getStats() {
|
|
1168
|
-
this.ensureInitialized();
|
|
1169
|
-
const allFunctions = this.functionsCollection.find();
|
|
1170
|
-
// Calculate stats
|
|
1171
|
-
const languages = {};
|
|
1172
|
-
const dependencies = {};
|
|
1173
|
-
const files = new Set();
|
|
1174
|
-
for (const func of allFunctions) {
|
|
1175
|
-
// Language stats
|
|
1176
|
-
if (func.language) {
|
|
1177
|
-
languages[func.language] = (languages[func.language] || 0) + 1;
|
|
1178
|
-
}
|
|
1179
|
-
// Dependency stats
|
|
1180
|
-
for (const dep of func.dependencies) {
|
|
1181
|
-
dependencies[dep] = (dependencies[dep] || 0) + 1;
|
|
1182
|
-
}
|
|
1183
|
-
// File stats
|
|
1184
|
-
files.add(func.filePath);
|
|
1185
|
-
}
|
|
1186
|
-
// Top dependencies
|
|
1187
|
-
const topDependencies = Object.entries(dependencies)
|
|
1188
|
-
.sort((a, b) => b[1] - a[1])
|
|
1189
|
-
.slice(0, 10)
|
|
1190
|
-
.map(([name, count]) => ({ name, count }));
|
|
1191
|
-
return {
|
|
1192
|
-
totalFunctions: allFunctions.length,
|
|
1193
|
-
languages,
|
|
1194
|
-
topDependencies,
|
|
1195
|
-
filesIndexed: files.size,
|
|
1196
|
-
lastUpdated: new Date()
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* Drops analysis-derived data so search, cached audits, code maps, and schema overlays
|
|
1201
|
-
* cannot reference removed or stale code ("ghost" analysis).
|
|
1202
|
-
*
|
|
1203
|
-
* Clears: indexed functions, FlexSearch, auditResults, codeMaps, schemas, schemaUsage.
|
|
1204
|
-
* Preserves: projectTasks, analyzerConfigs, whitelist (user rules, not mirrored code).
|
|
1205
|
-
*
|
|
1206
|
-
* When adding collections that store derived code intelligence, clear them here unless
|
|
1207
|
-
* they are durable user preferences (same category as analyzerConfigs).
|
|
1208
|
-
*/
|
|
1209
|
-
async clearIndex() {
|
|
1210
|
-
this.ensureInitialized();
|
|
1211
|
-
this.functionsCollection.clear();
|
|
1212
|
-
this.searchIndex.clear();
|
|
1213
|
-
this.auditResultsCollection.clear();
|
|
1214
|
-
this.codeMapCollection.clear();
|
|
1215
|
-
this.schemaCollection.clear();
|
|
1216
|
-
this.schemaUsageCollection.clear();
|
|
1217
|
-
this.db.saveDatabase();
|
|
1218
|
-
}
|
|
1219
|
-
async close() {
|
|
1220
|
-
if (this.isInitialized) {
|
|
1221
|
-
this.taskRepository = null;
|
|
1222
|
-
this.db.close();
|
|
1223
|
-
this.isInitialized = false;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
getTaskRepository() {
|
|
1227
|
-
this.ensureInitialized();
|
|
1228
|
-
if (!this.taskRepository) {
|
|
1229
|
-
this.taskRepository = new ProjectTaskRepository(() => this.tasksCollection, () => this.db.saveDatabase());
|
|
1230
|
-
}
|
|
1231
|
-
return this.taskRepository;
|
|
1232
|
-
}
|
|
1233
|
-
/**
|
|
1234
|
-
* Synchronize a single file's functions with the index
|
|
1235
|
-
* Adds new functions, updates existing ones, and removes deleted ones
|
|
1236
|
-
*/
|
|
1237
|
-
async synchronizeFile(filePath) {
|
|
1238
|
-
this.ensureInitialized();
|
|
1239
|
-
// fs is already imported at the top of the file
|
|
1240
|
-
try {
|
|
1241
|
-
// Check if file exists
|
|
1242
|
-
await fs.access(filePath);
|
|
1243
|
-
}
|
|
1244
|
-
catch {
|
|
1245
|
-
// File doesn't exist, remove all its functions
|
|
1246
|
-
const existingFuncs = this.functionsCollection.find({ filePath });
|
|
1247
|
-
for (const func of existingFuncs) {
|
|
1248
|
-
this.functionsCollection.remove(func);
|
|
1249
|
-
if (func.$loki !== undefined) {
|
|
1250
|
-
await this.searchIndex.remove(func.$loki);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
this.db.saveDatabase();
|
|
1254
|
-
return { added: 0, updated: 0, removed: existingFuncs.length };
|
|
1255
|
-
}
|
|
1256
|
-
// Parse the file for functions
|
|
1257
|
-
const { FunctionScanner } = await import('./functionScanner.js');
|
|
1258
|
-
const scanner = new FunctionScanner();
|
|
1259
|
-
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
1260
|
-
const language = filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'javascript';
|
|
1261
|
-
const parsedFunctions = await scanner.scanFunctions(fileContent, filePath, language);
|
|
1262
|
-
// Get existing functions
|
|
1263
|
-
const existingFuncs = this.functionsCollection.find({ filePath });
|
|
1264
|
-
const existingMap = new Map(existingFuncs.map(f => [f.name, f]));
|
|
1265
|
-
const parsedMap = new Map(parsedFunctions.map(f => [f.name, f]));
|
|
1266
|
-
let added = 0, updated = 0, removed = 0;
|
|
1267
|
-
// Remove functions that no longer exist
|
|
1268
|
-
for (const existing of existingFuncs) {
|
|
1269
|
-
if (!parsedMap.has(existing.name)) {
|
|
1270
|
-
this.functionsCollection.remove(existing);
|
|
1271
|
-
if (existing.$loki !== undefined) {
|
|
1272
|
-
await this.searchIndex.remove(existing.$loki);
|
|
1273
|
-
}
|
|
1274
|
-
removed++;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
// Add or update functions
|
|
1278
|
-
for (const parsed of parsedFunctions) {
|
|
1279
|
-
const existing = existingMap.get(parsed.name);
|
|
1280
|
-
if (existing) {
|
|
1281
|
-
// Update existing
|
|
1282
|
-
Object.assign(existing, parsed);
|
|
1283
|
-
this.functionsCollection.update(existing);
|
|
1284
|
-
const normalizedData = this.normalizeFunctionData(existing);
|
|
1285
|
-
await this.searchIndex.update(normalizedData);
|
|
1286
|
-
updated++;
|
|
1287
|
-
}
|
|
1288
|
-
else {
|
|
1289
|
-
// Add new
|
|
1290
|
-
await this.registerFunction(parsed);
|
|
1291
|
-
added++;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
if (added > 0 || updated > 0 || removed > 0) {
|
|
1295
|
-
this.db.saveDatabase();
|
|
1296
|
-
}
|
|
1297
|
-
return { added, updated, removed };
|
|
1298
|
-
}
|
|
1299
|
-
/**
|
|
1300
|
-
* Performs bulk cleanup of the index by verifying all indexed files still exist
|
|
1301
|
-
* and removing entries for deleted files
|
|
1302
|
-
*/
|
|
1303
|
-
async bulkCleanup() {
|
|
1304
|
-
this.ensureInitialized();
|
|
1305
|
-
// fs is already imported at the top of the file
|
|
1306
|
-
const allFunctions = this.functionsCollection.find();
|
|
1307
|
-
const fileMap = new Map();
|
|
1308
|
-
// Group functions by file
|
|
1309
|
-
for (const func of allFunctions) {
|
|
1310
|
-
if (!fileMap.has(func.filePath)) {
|
|
1311
|
-
fileMap.set(func.filePath, []);
|
|
1312
|
-
}
|
|
1313
|
-
fileMap.get(func.filePath).push(func);
|
|
1314
|
-
}
|
|
1315
|
-
const removedFiles = [];
|
|
1316
|
-
const errors = [];
|
|
1317
|
-
let removedCount = 0;
|
|
1318
|
-
// Check each file
|
|
1319
|
-
for (const [filePath, functions] of fileMap) {
|
|
1320
|
-
try {
|
|
1321
|
-
await fs.access(filePath);
|
|
1322
|
-
// File exists, no action needed
|
|
1323
|
-
}
|
|
1324
|
-
catch (error) {
|
|
1325
|
-
// File doesn't exist, remove all its functions
|
|
1326
|
-
for (const func of functions) {
|
|
1327
|
-
this.functionsCollection.remove(func);
|
|
1328
|
-
// Remove from search index
|
|
1329
|
-
if (func.$loki !== undefined) {
|
|
1330
|
-
await this.searchIndex.remove(func.$loki);
|
|
1331
|
-
}
|
|
1332
|
-
removedCount++;
|
|
1333
|
-
}
|
|
1334
|
-
removedFiles.push(filePath);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
// Save changes
|
|
1338
|
-
if (removedCount > 0) {
|
|
1339
|
-
this.db.saveDatabase();
|
|
1340
|
-
}
|
|
1341
|
-
return {
|
|
1342
|
-
scannedCount: fileMap.size,
|
|
1343
|
-
removedCount,
|
|
1344
|
-
removedFiles,
|
|
1345
|
-
errors
|
|
1346
|
-
};
|
|
1347
|
-
}
|
|
1348
|
-
/**
|
|
1349
|
-
* Performs deep synchronization by re-scanning all indexed files
|
|
1350
|
-
* Updates function signatures and removes stale entries
|
|
1351
|
-
*/
|
|
1352
|
-
async deepSync(progressCallback) {
|
|
1353
|
-
this.ensureInitialized();
|
|
1354
|
-
const allFunctions = this.functionsCollection.find();
|
|
1355
|
-
const fileMap = new Map();
|
|
1356
|
-
// Group functions by file
|
|
1357
|
-
for (const func of allFunctions) {
|
|
1358
|
-
if (!fileMap.has(func.filePath)) {
|
|
1359
|
-
fileMap.set(func.filePath, []);
|
|
1360
|
-
}
|
|
1361
|
-
fileMap.get(func.filePath).push(func);
|
|
1362
|
-
}
|
|
1363
|
-
let syncedFiles = 0;
|
|
1364
|
-
let totalAdded = 0;
|
|
1365
|
-
let totalUpdated = 0;
|
|
1366
|
-
let totalRemoved = 0;
|
|
1367
|
-
const errors = [];
|
|
1368
|
-
let current = 0;
|
|
1369
|
-
const total = fileMap.size;
|
|
1370
|
-
// Sync each file
|
|
1371
|
-
for (const [filePath, _] of fileMap) {
|
|
1372
|
-
current++;
|
|
1373
|
-
if (progressCallback) {
|
|
1374
|
-
progressCallback({ current, total, file: filePath });
|
|
1375
|
-
}
|
|
1376
|
-
try {
|
|
1377
|
-
const result = await this.synchronizeFile(filePath);
|
|
1378
|
-
if (result) {
|
|
1379
|
-
syncedFiles++;
|
|
1380
|
-
totalAdded += result.added;
|
|
1381
|
-
totalUpdated += result.updated;
|
|
1382
|
-
totalRemoved += result.removed;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
catch (error) {
|
|
1386
|
-
errors.push({
|
|
1387
|
-
file: filePath,
|
|
1388
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
return {
|
|
1393
|
-
syncedFiles,
|
|
1394
|
-
addedFunctions: totalAdded,
|
|
1395
|
-
updatedFunctions: totalUpdated,
|
|
1396
|
-
removedFunctions: totalRemoved,
|
|
1397
|
-
errors
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
/**
|
|
1401
|
-
* Execute content search - search within function bodies
|
|
1402
|
-
*/
|
|
1403
|
-
async executeContentSearch(parsedQuery, searchScores) {
|
|
1404
|
-
const resultsMap = new Map();
|
|
1405
|
-
// Get all functions (we'll filter them)
|
|
1406
|
-
const allFunctions = this.functionsCollection.find();
|
|
1407
|
-
// Search in function bodies
|
|
1408
|
-
for (const doc of allFunctions) {
|
|
1409
|
-
// Check for body in multiple possible locations
|
|
1410
|
-
const body = doc.body || doc.metadata?.body;
|
|
1411
|
-
if (!body)
|
|
1412
|
-
continue;
|
|
1413
|
-
let score = 0;
|
|
1414
|
-
const bodyLower = body.toLowerCase();
|
|
1415
|
-
const matches = [];
|
|
1416
|
-
// Split body into lines for line-level matching
|
|
1417
|
-
const lines = body.split('\n');
|
|
1418
|
-
// Check for exact phrases
|
|
1419
|
-
for (const phrase of parsedQuery.phrases) {
|
|
1420
|
-
const phraseLower = phrase.toLowerCase();
|
|
1421
|
-
lines.forEach((line, lineIndex) => {
|
|
1422
|
-
const lineLower = line.toLowerCase();
|
|
1423
|
-
let columnIndex = lineLower.indexOf(phraseLower);
|
|
1424
|
-
while (columnIndex !== -1) {
|
|
1425
|
-
matches.push({
|
|
1426
|
-
term: phrase,
|
|
1427
|
-
line: (doc.lineNumber || 0) + lineIndex,
|
|
1428
|
-
column: columnIndex + 1
|
|
1429
|
-
});
|
|
1430
|
-
score += 100; // High score for exact phrase match
|
|
1431
|
-
columnIndex = lineLower.indexOf(phraseLower, columnIndex + 1);
|
|
1432
|
-
}
|
|
1433
|
-
});
|
|
1434
|
-
}
|
|
1435
|
-
// Check for individual terms
|
|
1436
|
-
for (const term of parsedQuery.terms) {
|
|
1437
|
-
const termLower = term.toLowerCase();
|
|
1438
|
-
lines.forEach((line, lineIndex) => {
|
|
1439
|
-
const lineLower = line.toLowerCase();
|
|
1440
|
-
let columnIndex = lineLower.indexOf(termLower);
|
|
1441
|
-
while (columnIndex !== -1) {
|
|
1442
|
-
matches.push({
|
|
1443
|
-
term: term,
|
|
1444
|
-
line: (doc.lineNumber || 0) + lineIndex,
|
|
1445
|
-
column: columnIndex + 1
|
|
1446
|
-
});
|
|
1447
|
-
score += 20; // Score for term match
|
|
1448
|
-
columnIndex = lineLower.indexOf(termLower, columnIndex + 1);
|
|
1449
|
-
}
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
// Check for excluded terms
|
|
1453
|
-
let excluded = false;
|
|
1454
|
-
for (const excludedTerm of parsedQuery.excludedTerms) {
|
|
1455
|
-
if (bodyLower.includes(excludedTerm.toLowerCase())) {
|
|
1456
|
-
excluded = true;
|
|
1457
|
-
break;
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
// Add to results if score > 0 and not excluded
|
|
1461
|
-
if (score > 0 && !excluded && doc.$loki !== undefined) {
|
|
1462
|
-
// Store match information in metadata
|
|
1463
|
-
const resultDoc = { ...doc };
|
|
1464
|
-
if (!resultDoc.metadata)
|
|
1465
|
-
resultDoc.metadata = {};
|
|
1466
|
-
resultDoc.metadata.contentMatches = matches;
|
|
1467
|
-
// Add match context (surrounding lines)
|
|
1468
|
-
const contextLines = 2; // Number of lines before and after to include
|
|
1469
|
-
const matchContexts = [];
|
|
1470
|
-
// Group matches by line to avoid duplicate context
|
|
1471
|
-
const matchesByLine = new Map();
|
|
1472
|
-
for (const match of matches) {
|
|
1473
|
-
const relativeLineNum = match.line - (doc.lineNumber || 0);
|
|
1474
|
-
if (!matchesByLine.has(relativeLineNum)) {
|
|
1475
|
-
matchesByLine.set(relativeLineNum, []);
|
|
1476
|
-
}
|
|
1477
|
-
matchesByLine.get(relativeLineNum).push(match);
|
|
1478
|
-
}
|
|
1479
|
-
// Build context for each unique line
|
|
1480
|
-
for (const [relativeLineNum, lineMatches] of matchesByLine) {
|
|
1481
|
-
if (relativeLineNum >= 0 && relativeLineNum < lines.length) {
|
|
1482
|
-
const before = [];
|
|
1483
|
-
const after = [];
|
|
1484
|
-
// Get lines before
|
|
1485
|
-
for (let i = Math.max(0, relativeLineNum - contextLines); i < relativeLineNum; i++) {
|
|
1486
|
-
before.push(lines[i]);
|
|
1487
|
-
}
|
|
1488
|
-
// Get lines after
|
|
1489
|
-
for (let i = relativeLineNum + 1; i < Math.min(lines.length, relativeLineNum + contextLines + 1); i++) {
|
|
1490
|
-
after.push(lines[i]);
|
|
1491
|
-
}
|
|
1492
|
-
// Add context for each match on this line
|
|
1493
|
-
for (const match of lineMatches) {
|
|
1494
|
-
matchContexts.push({
|
|
1495
|
-
match,
|
|
1496
|
-
context: {
|
|
1497
|
-
before,
|
|
1498
|
-
line: lines[relativeLineNum],
|
|
1499
|
-
after
|
|
1500
|
-
}
|
|
1501
|
-
});
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
resultDoc.metadata.matchContexts = matchContexts;
|
|
1506
|
-
resultsMap.set(doc.$loki, resultDoc);
|
|
1507
|
-
searchScores.set(doc.$loki, score);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
return Array.from(resultsMap.values());
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* Initialize default whitelist entries
|
|
1514
|
-
*/
|
|
1515
|
-
async initializeDefaultWhitelists() {
|
|
1516
|
-
const defaultEntries = [
|
|
1517
|
-
// JavaScript built-ins
|
|
1518
|
-
{ name: 'Date', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
1519
|
-
{ name: 'Error', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
1520
|
-
{ name: 'Array', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
1521
|
-
{ name: 'Map', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
1522
|
-
{ name: 'Set', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
1523
|
-
{ name: 'Promise', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
1524
|
-
{ name: 'RegExp', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
1525
|
-
// DOM/Browser APIs
|
|
1526
|
-
{ name: 'URL', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
|
|
1527
|
-
{ name: 'URLSearchParams', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
|
|
1528
|
-
{ name: 'FormData', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
|
|
1529
|
-
{ name: 'Headers', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
|
|
1530
|
-
// Node.js built-ins
|
|
1531
|
-
{ name: 'fs', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['fs', 'node:fs', 'fs/promises'], addedBy: 'system', addedAt: new Date() },
|
|
1532
|
-
{ name: 'path', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['path', 'node:path'], addedBy: 'system', addedAt: new Date() },
|
|
1533
|
-
{ name: 'crypto', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['crypto', 'node:crypto'], addedBy: 'system', addedAt: new Date() },
|
|
1534
|
-
// Next.js framework classes
|
|
1535
|
-
{ name: 'NextResponse', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
|
|
1536
|
-
{ name: 'NextRequest', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
|
|
1537
|
-
{ name: 'Response', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
|
|
1538
|
-
{ name: 'Request', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
|
|
1539
|
-
// React core classes and functions
|
|
1540
|
-
{ name: 'Component', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
1541
|
-
{ name: 'PureComponent', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
1542
|
-
{ name: 'Fragment', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
1543
|
-
{ name: 'StrictMode', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
1544
|
-
{ name: 'Suspense', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
1545
|
-
// Database clients
|
|
1546
|
-
{ name: 'Pool', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
|
|
1547
|
-
{ name: 'Client', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
|
|
1548
|
-
{ name: 'MongoClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['mongodb'], addedBy: 'system', addedAt: new Date() },
|
|
1549
|
-
{ name: 'PrismaClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['@prisma/client'], addedBy: 'system', addedAt: new Date() },
|
|
1550
|
-
// Auth libraries
|
|
1551
|
-
{ name: 'StackServerApp', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
|
|
1552
|
-
{ name: 'StackClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
|
|
1553
|
-
{ name: 'ClerkProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@clerk/nextjs'], addedBy: 'system', addedAt: new Date() },
|
|
1554
|
-
{ name: 'Auth0Provider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@auth0/nextjs-auth0'], addedBy: 'system', addedAt: new Date() },
|
|
1555
|
-
// HTTP/API clients
|
|
1556
|
-
{ name: 'Axios', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', patterns: ['axios'], addedBy: 'system', addedAt: new Date() },
|
|
1557
|
-
{ name: 'HttpClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', addedBy: 'system', addedAt: new Date() },
|
|
1558
|
-
// Express/Node.js frameworks
|
|
1559
|
-
{ name: 'Router', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
|
|
1560
|
-
{ name: 'Application', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
|
|
1561
|
-
// Testing frameworks
|
|
1562
|
-
{ name: 'TestingModule', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@nestjs/testing'], addedBy: 'system', addedAt: new Date() },
|
|
1563
|
-
{ name: 'MockedProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@apollo/client/testing'], addedBy: 'system', addedAt: new Date() },
|
|
1564
|
-
// Node.js core modules
|
|
1565
|
-
{ name: 'EventEmitter', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['events', 'node:events'], addedBy: 'system', addedAt: new Date() },
|
|
1566
|
-
{ name: 'Readable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
|
|
1567
|
-
{ name: 'Writable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
|
|
1568
|
-
{ name: 'Transform', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
|
|
1569
|
-
{ name: 'Buffer', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', addedBy: 'system', addedAt: new Date() },
|
|
1570
|
-
];
|
|
1571
|
-
for (const entry of defaultEntries) {
|
|
1572
|
-
this.whitelistCollection.insert(entry);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
/**
|
|
1576
|
-
* Get all whitelist entries
|
|
1577
|
-
*/
|
|
1578
|
-
async getWhitelist(type, status) {
|
|
1579
|
-
this.ensureInitialized();
|
|
1580
|
-
const query = {};
|
|
1581
|
-
if (type)
|
|
1582
|
-
query.type = type;
|
|
1583
|
-
if (status)
|
|
1584
|
-
query.status = status;
|
|
1585
|
-
return this.whitelistCollection.find(query);
|
|
1586
|
-
}
|
|
1587
|
-
/**
|
|
1588
|
-
* Add a whitelist entry
|
|
1589
|
-
*/
|
|
1590
|
-
async addWhitelistEntry(entry) {
|
|
1591
|
-
this.ensureInitialized();
|
|
1592
|
-
const newEntry = {
|
|
1593
|
-
...entry,
|
|
1594
|
-
addedAt: new Date()
|
|
1595
|
-
};
|
|
1596
|
-
return this.whitelistCollection.insert(newEntry);
|
|
1597
|
-
}
|
|
1598
|
-
/**
|
|
1599
|
-
* Update whitelist entry status
|
|
1600
|
-
*/
|
|
1601
|
-
async updateWhitelistStatus(name, status) {
|
|
1602
|
-
this.ensureInitialized();
|
|
1603
|
-
const entry = this.whitelistCollection.findOne({ name });
|
|
1604
|
-
if (entry) {
|
|
1605
|
-
entry.status = status;
|
|
1606
|
-
entry.updatedAt = new Date();
|
|
1607
|
-
this.whitelistCollection.update(entry);
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
/**
|
|
1611
|
-
* Check if a class/import is whitelisted
|
|
1612
|
-
*/
|
|
1613
|
-
isWhitelisted(name, type) {
|
|
1614
|
-
if (!this.isInitialized)
|
|
1615
|
-
return false;
|
|
1616
|
-
const entries = this.whitelistCollection.find({
|
|
1617
|
-
type,
|
|
1618
|
-
status: WhitelistStatus.Active
|
|
1619
|
-
});
|
|
1620
|
-
return entries.some(entry => {
|
|
1621
|
-
// Direct name match
|
|
1622
|
-
if (entry.name === name)
|
|
1623
|
-
return true;
|
|
1624
|
-
// Pattern match
|
|
1625
|
-
if (entry.patterns) {
|
|
1626
|
-
return entry.patterns.some(pattern => {
|
|
1627
|
-
if (pattern.includes('*')) {
|
|
1628
|
-
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
1629
|
-
return regex.test(name);
|
|
1630
|
-
}
|
|
1631
|
-
return pattern === name;
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1634
|
-
return false;
|
|
1635
|
-
});
|
|
1636
|
-
}
|
|
1637
|
-
/**
|
|
1638
|
-
* Detect and suggest new whitelist entries based on usage patterns
|
|
1639
|
-
*/
|
|
1640
|
-
async detectWhitelistCandidates() {
|
|
1641
|
-
this.ensureInitialized();
|
|
1642
|
-
// This would analyze the codebase for patterns
|
|
1643
|
-
// For now, returning empty array - to be implemented
|
|
1644
|
-
return [];
|
|
1645
|
-
}
|
|
1646
|
-
/**
|
|
1647
|
-
* Store audit results in database
|
|
1648
|
-
*/
|
|
1649
|
-
async storeAuditResults(auditResult, projectPath) {
|
|
1650
|
-
this.ensureInitialized();
|
|
1651
|
-
// Generate unique audit ID
|
|
1652
|
-
const auditId = `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1653
|
-
// Store the entire result to preserve analyzer structure; auditId must come last so
|
|
1654
|
-
// a stray auditResult.auditId cannot break pagination lookups.
|
|
1655
|
-
const auditRecord = {
|
|
1656
|
-
...auditResult,
|
|
1657
|
-
auditId,
|
|
1658
|
-
timestamp: new Date(),
|
|
1659
|
-
projectPath,
|
|
1660
|
-
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // Expire after 24 hours
|
|
1661
|
-
};
|
|
1662
|
-
this.auditResultsCollection.insert(auditRecord);
|
|
1663
|
-
// Clean up expired results
|
|
1664
|
-
this.cleanupExpiredAudits();
|
|
1665
|
-
// Persist so resultId survives MCP restart (Loki autosave is off; many code paths never save otherwise).
|
|
1666
|
-
this.db.saveDatabase();
|
|
1667
|
-
return auditId;
|
|
1668
|
-
}
|
|
1669
|
-
/**
|
|
1670
|
-
* Retrieve cached audit results
|
|
1671
|
-
*/
|
|
1672
|
-
async getAuditResults(auditId) {
|
|
1673
|
-
this.ensureInitialized();
|
|
1674
|
-
const result = this.auditResultsCollection.findOne({ auditId });
|
|
1675
|
-
if (result && new Date(result.expiresAt) > new Date()) {
|
|
1676
|
-
return result;
|
|
1677
|
-
}
|
|
1678
|
-
// Remove expired result if found
|
|
1679
|
-
if (result) {
|
|
1680
|
-
this.auditResultsCollection.remove(result);
|
|
1681
|
-
}
|
|
1682
|
-
return null;
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Clean up expired audit results
|
|
1686
|
-
*/
|
|
1687
|
-
cleanupExpiredAudits() {
|
|
1688
|
-
const now = new Date();
|
|
1689
|
-
const expired = this.auditResultsCollection.find({
|
|
1690
|
-
expiresAt: { $lt: now }
|
|
1691
|
-
});
|
|
1692
|
-
expired.forEach(doc => {
|
|
1693
|
-
this.auditResultsCollection.remove(doc);
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
// Analyzer Configuration Methods
|
|
1697
|
-
/**
|
|
1698
|
-
* Store analyzer configuration
|
|
1699
|
-
*/
|
|
1700
|
-
async storeAnalyzerConfig(analyzerName, config, options) {
|
|
1701
|
-
this.ensureInitialized();
|
|
1702
|
-
const configDoc = {
|
|
1703
|
-
analyzerName,
|
|
1704
|
-
config,
|
|
1705
|
-
isGlobal: options?.isGlobal ?? true,
|
|
1706
|
-
projectPath: options?.projectPath || null,
|
|
1707
|
-
createdAt: new Date(),
|
|
1708
|
-
updatedAt: new Date(),
|
|
1709
|
-
createdBy: 'user',
|
|
1710
|
-
metadata: options?.metadata
|
|
1711
|
-
};
|
|
1712
|
-
// Check if config already exists
|
|
1713
|
-
const existing = this.analyzerConfigCollection.findOne({
|
|
1714
|
-
analyzerName,
|
|
1715
|
-
projectPath: options?.projectPath || null,
|
|
1716
|
-
isGlobal: options?.isGlobal ?? true
|
|
1717
|
-
});
|
|
1718
|
-
if (existing) {
|
|
1719
|
-
Object.assign(existing, {
|
|
1720
|
-
config,
|
|
1721
|
-
updatedAt: new Date(),
|
|
1722
|
-
metadata: options?.metadata
|
|
1723
|
-
});
|
|
1724
|
-
this.analyzerConfigCollection.update(existing);
|
|
1725
|
-
}
|
|
1726
|
-
else {
|
|
1727
|
-
this.analyzerConfigCollection.insert(configDoc);
|
|
1728
|
-
}
|
|
1729
|
-
this.db.saveDatabase();
|
|
1730
|
-
return analyzerName;
|
|
1731
|
-
}
|
|
1732
|
-
/**
|
|
1733
|
-
* Retrieve analyzer configuration
|
|
1734
|
-
*/
|
|
1735
|
-
async getAnalyzerConfig(analyzerName, projectPath) {
|
|
1736
|
-
this.ensureInitialized();
|
|
1737
|
-
// First try project-specific config
|
|
1738
|
-
if (projectPath) {
|
|
1739
|
-
const projectConfig = this.analyzerConfigCollection.findOne({
|
|
1740
|
-
analyzerName,
|
|
1741
|
-
projectPath,
|
|
1742
|
-
isGlobal: false
|
|
1743
|
-
});
|
|
1744
|
-
if (projectConfig)
|
|
1745
|
-
return projectConfig.config;
|
|
1746
|
-
}
|
|
1747
|
-
// Fall back to global config
|
|
1748
|
-
const globalConfig = this.analyzerConfigCollection.findOne({
|
|
1749
|
-
analyzerName,
|
|
1750
|
-
isGlobal: true
|
|
1751
|
-
});
|
|
1752
|
-
return globalConfig?.config || null;
|
|
1753
|
-
}
|
|
1754
|
-
/**
|
|
1755
|
-
* Get all analyzer configurations
|
|
1756
|
-
*/
|
|
1757
|
-
async getAllAnalyzerConfigs(projectPath) {
|
|
1758
|
-
this.ensureInitialized();
|
|
1759
|
-
const configs = {};
|
|
1760
|
-
// Get global configs first
|
|
1761
|
-
const globalConfigs = this.analyzerConfigCollection.find({ isGlobal: true });
|
|
1762
|
-
for (const config of globalConfigs) {
|
|
1763
|
-
configs[config.analyzerName] = config.config;
|
|
1764
|
-
}
|
|
1765
|
-
// Override with project-specific configs if available
|
|
1766
|
-
if (projectPath) {
|
|
1767
|
-
const projectConfigs = this.analyzerConfigCollection.find({
|
|
1768
|
-
projectPath,
|
|
1769
|
-
isGlobal: false
|
|
1770
|
-
});
|
|
1771
|
-
for (const config of projectConfigs) {
|
|
1772
|
-
configs[config.analyzerName] = config.config;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
return configs;
|
|
1776
|
-
}
|
|
1777
|
-
/**
|
|
1778
|
-
* Delete analyzer configuration
|
|
1779
|
-
*/
|
|
1780
|
-
async deleteAnalyzerConfig(analyzerName, options) {
|
|
1781
|
-
this.ensureInitialized();
|
|
1782
|
-
const config = this.analyzerConfigCollection.findOne({
|
|
1783
|
-
analyzerName,
|
|
1784
|
-
projectPath: options?.projectPath || null,
|
|
1785
|
-
isGlobal: options?.isGlobal ?? !options?.projectPath
|
|
1786
|
-
});
|
|
1787
|
-
if (config) {
|
|
1788
|
-
this.analyzerConfigCollection.remove(config);
|
|
1789
|
-
this.db.saveDatabase();
|
|
1790
|
-
return true;
|
|
1791
|
-
}
|
|
1792
|
-
return false;
|
|
1793
|
-
}
|
|
1794
|
-
/**
|
|
1795
|
-
* Reset all analyzer configurations to defaults
|
|
1796
|
-
*/
|
|
1797
|
-
async resetAnalyzerConfigs(projectPath) {
|
|
1798
|
-
this.ensureInitialized();
|
|
1799
|
-
if (projectPath) {
|
|
1800
|
-
// Remove only project-specific configs
|
|
1801
|
-
const projectConfigs = this.analyzerConfigCollection.find({
|
|
1802
|
-
projectPath,
|
|
1803
|
-
isGlobal: false
|
|
1804
|
-
});
|
|
1805
|
-
projectConfigs.forEach(config => {
|
|
1806
|
-
this.analyzerConfigCollection.remove(config);
|
|
1807
|
-
});
|
|
1808
|
-
}
|
|
1809
|
-
else {
|
|
1810
|
-
// Remove all configs
|
|
1811
|
-
this.analyzerConfigCollection.clear();
|
|
1812
|
-
}
|
|
1813
|
-
this.db.saveDatabase();
|
|
1814
|
-
}
|
|
1815
|
-
/**
|
|
1816
|
-
* Store a code map section in the database
|
|
1817
|
-
*/
|
|
1818
|
-
async storeCodeMapSection(mapId, sectionType, content, metadata) {
|
|
1819
|
-
this.ensureInitialized();
|
|
1820
|
-
// Remove any existing section of this type for this mapId
|
|
1821
|
-
this.codeMapCollection.findAndRemove({
|
|
1822
|
-
mapId,
|
|
1823
|
-
sectionType
|
|
1824
|
-
});
|
|
1825
|
-
this.codeMapCollection.insert({
|
|
1826
|
-
mapId,
|
|
1827
|
-
sectionType,
|
|
1828
|
-
content,
|
|
1829
|
-
metadata: metadata || {},
|
|
1830
|
-
timestamp: new Date(),
|
|
1831
|
-
size: content.length
|
|
1832
|
-
});
|
|
1833
|
-
this.db.saveDatabase();
|
|
1834
|
-
}
|
|
1835
|
-
/**
|
|
1836
|
-
* Retrieve a code map section
|
|
1837
|
-
*/
|
|
1838
|
-
async getCodeMapSection(mapId, sectionType) {
|
|
1839
|
-
this.ensureInitialized();
|
|
1840
|
-
const section = this.codeMapCollection.findOne({
|
|
1841
|
-
mapId,
|
|
1842
|
-
sectionType
|
|
1843
|
-
});
|
|
1844
|
-
return section ? {
|
|
1845
|
-
content: section.content,
|
|
1846
|
-
metadata: section.metadata || {}
|
|
1847
|
-
} : null;
|
|
1848
|
-
}
|
|
1849
|
-
/**
|
|
1850
|
-
* List available code map sections for a mapId
|
|
1851
|
-
*/
|
|
1852
|
-
async listCodeMapSections(mapId) {
|
|
1853
|
-
this.ensureInitialized();
|
|
1854
|
-
const sections = this.codeMapCollection.find({ mapId });
|
|
1855
|
-
return sections.map(section => ({
|
|
1856
|
-
sectionType: section.sectionType,
|
|
1857
|
-
size: section.size || 0,
|
|
1858
|
-
timestamp: section.timestamp
|
|
1859
|
-
}));
|
|
1860
|
-
}
|
|
1861
|
-
/**
|
|
1862
|
-
* Clear code map sections older than specified time
|
|
1863
|
-
*/
|
|
1864
|
-
async clearOldCodeMaps(olderThanHours = 24) {
|
|
1865
|
-
this.ensureInitialized();
|
|
1866
|
-
const cutoffTime = new Date();
|
|
1867
|
-
cutoffTime.setHours(cutoffTime.getHours() - olderThanHours);
|
|
1868
|
-
const oldSections = this.codeMapCollection.find({
|
|
1869
|
-
timestamp: { '$lt': cutoffTime }
|
|
1870
|
-
});
|
|
1871
|
-
const count = oldSections.length;
|
|
1872
|
-
this.codeMapCollection.findAndRemove({
|
|
1873
|
-
timestamp: { '$lt': cutoffTime }
|
|
1874
|
-
});
|
|
1875
|
-
this.db.saveDatabase();
|
|
1876
|
-
return count;
|
|
1877
|
-
}
|
|
1878
|
-
/**
|
|
1879
|
-
* Delete all sections for a specific mapId
|
|
1880
|
-
*/
|
|
1881
|
-
async deleteCodeMap(mapId) {
|
|
1882
|
-
this.ensureInitialized();
|
|
1883
|
-
const sections = this.codeMapCollection.find({ mapId });
|
|
1884
|
-
const count = sections.length;
|
|
1885
|
-
this.codeMapCollection.findAndRemove({ mapId });
|
|
1886
|
-
this.db.saveDatabase();
|
|
1887
|
-
return count;
|
|
1888
|
-
}
|
|
1889
|
-
// Schema Management Methods
|
|
1890
|
-
/**
|
|
1891
|
-
* Store a database schema definition
|
|
1892
|
-
*/
|
|
1893
|
-
async storeSchema(schema) {
|
|
1894
|
-
this.ensureInitialized();
|
|
1895
|
-
const schemaId = `schema_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1896
|
-
const schemaMetadata = {
|
|
1897
|
-
schemaId,
|
|
1898
|
-
schemaName: schema.name,
|
|
1899
|
-
indexedAt: new Date(),
|
|
1900
|
-
tableCount: schema.databases.reduce((acc, db) => acc + db.tables.length, 0),
|
|
1901
|
-
relationshipCount: schema.databases.reduce((acc, db) => acc + (db.relationships?.length || 0), 0),
|
|
1902
|
-
usagePatterns: [],
|
|
1903
|
-
discoveredPatterns: [],
|
|
1904
|
-
violations: []
|
|
1905
|
-
};
|
|
1906
|
-
// Store the schema definition
|
|
1907
|
-
this.schemaCollection.insert({
|
|
1908
|
-
schemaId,
|
|
1909
|
-
schema,
|
|
1910
|
-
metadata: schemaMetadata,
|
|
1911
|
-
indexedAt: new Date()
|
|
1912
|
-
});
|
|
1913
|
-
this.db.saveDatabase();
|
|
1914
|
-
return schemaId;
|
|
1915
|
-
}
|
|
1916
|
-
/**
|
|
1917
|
-
* Retrieve a schema by ID
|
|
1918
|
-
*/
|
|
1919
|
-
async getSchema(schemaId) {
|
|
1920
|
-
this.ensureInitialized();
|
|
1921
|
-
const result = this.schemaCollection.findOne({ schemaId });
|
|
1922
|
-
return result?.schema || null;
|
|
1923
|
-
}
|
|
1924
|
-
/**
|
|
1925
|
-
* Get all schemas
|
|
1926
|
-
*/
|
|
1927
|
-
async getAllSchemas() {
|
|
1928
|
-
this.ensureInitialized();
|
|
1929
|
-
const schemas = this.schemaCollection.find();
|
|
1930
|
-
return schemas.map(doc => ({
|
|
1931
|
-
schemaId: doc.schemaId,
|
|
1932
|
-
metadata: doc.metadata,
|
|
1933
|
-
schema: doc.schema
|
|
1934
|
-
}));
|
|
1935
|
-
}
|
|
1936
|
-
/**
|
|
1937
|
-
* Delete a schema
|
|
1938
|
-
*/
|
|
1939
|
-
async deleteSchema(schemaId) {
|
|
1940
|
-
this.ensureInitialized();
|
|
1941
|
-
const schema = this.schemaCollection.findOne({ schemaId });
|
|
1942
|
-
if (schema) {
|
|
1943
|
-
this.schemaCollection.remove(schema);
|
|
1944
|
-
// Also remove associated usage patterns
|
|
1945
|
-
this.schemaUsageCollection.findAndRemove({ schemaId });
|
|
1946
|
-
this.db.saveDatabase();
|
|
1947
|
-
return true;
|
|
1948
|
-
}
|
|
1949
|
-
return false;
|
|
1950
|
-
}
|
|
1951
|
-
/**
|
|
1952
|
-
* Record schema usage in code
|
|
1953
|
-
*/
|
|
1954
|
-
async recordSchemaUsage(usage, schemaId) {
|
|
1955
|
-
this.ensureInitialized();
|
|
1956
|
-
const usageDoc = {
|
|
1957
|
-
...usage,
|
|
1958
|
-
schemaId: schemaId || 'default',
|
|
1959
|
-
recordedAt: new Date()
|
|
1960
|
-
};
|
|
1961
|
-
// Check if this usage already exists
|
|
1962
|
-
const existing = this.schemaUsageCollection.findOne({
|
|
1963
|
-
tableName: usage.tableName,
|
|
1964
|
-
filePath: usage.filePath,
|
|
1965
|
-
functionName: usage.functionName,
|
|
1966
|
-
line: usage.line
|
|
1967
|
-
});
|
|
1968
|
-
if (existing) {
|
|
1969
|
-
Object.assign(existing, usageDoc);
|
|
1970
|
-
this.schemaUsageCollection.update(existing);
|
|
1971
|
-
}
|
|
1972
|
-
else {
|
|
1973
|
-
this.schemaUsageCollection.insert(usageDoc);
|
|
1974
|
-
}
|
|
1975
|
-
this.db.saveDatabase();
|
|
1976
|
-
}
|
|
1977
|
-
/**
|
|
1978
|
-
* Get schema usage patterns
|
|
1979
|
-
*/
|
|
1980
|
-
async getSchemaUsage(options = {}) {
|
|
1981
|
-
this.ensureInitialized();
|
|
1982
|
-
const query = {};
|
|
1983
|
-
if (options.schemaId)
|
|
1984
|
-
query.schemaId = options.schemaId;
|
|
1985
|
-
if (options.tableName)
|
|
1986
|
-
query.tableName = options.tableName;
|
|
1987
|
-
if (options.filePath)
|
|
1988
|
-
query.filePath = options.filePath;
|
|
1989
|
-
if (options.functionName)
|
|
1990
|
-
query.functionName = options.functionName;
|
|
1991
|
-
if (options.usageType)
|
|
1992
|
-
query.usageType = options.usageType;
|
|
1993
|
-
const results = this.schemaUsageCollection.find(query);
|
|
1994
|
-
return results.map(doc => {
|
|
1995
|
-
const { $loki, meta, schemaId, recordedAt, ...usage } = doc;
|
|
1996
|
-
return usage;
|
|
1997
|
-
});
|
|
1998
|
-
}
|
|
1999
|
-
/**
|
|
2000
|
-
* Find functions that interact with specific tables
|
|
2001
|
-
*/
|
|
2002
|
-
async findFunctionsUsingTable(tableName) {
|
|
2003
|
-
this.ensureInitialized();
|
|
2004
|
-
const usages = this.schemaUsageCollection.find({ tableName });
|
|
2005
|
-
return usages.map(usage => ({
|
|
2006
|
-
functionName: usage.functionName,
|
|
2007
|
-
filePath: usage.filePath,
|
|
2008
|
-
usageType: usage.usageType,
|
|
2009
|
-
line: usage.line
|
|
2010
|
-
}));
|
|
2011
|
-
}
|
|
2012
|
-
/**
|
|
2013
|
-
* Get schema statistics
|
|
2014
|
-
*/
|
|
2015
|
-
async getSchemaStats() {
|
|
2016
|
-
this.ensureInitialized();
|
|
2017
|
-
const schemas = this.schemaCollection.find();
|
|
2018
|
-
const usages = this.schemaUsageCollection.find();
|
|
2019
|
-
const totalTables = schemas.reduce((acc, schema) => acc + schema.schema.databases.reduce((dbAcc, db) => dbAcc + db.tables.length, 0), 0);
|
|
2020
|
-
// Count usage by table
|
|
2021
|
-
const tableUsage = {};
|
|
2022
|
-
const usageByType = {};
|
|
2023
|
-
usages.forEach(usage => {
|
|
2024
|
-
tableUsage[usage.tableName] = (tableUsage[usage.tableName] || 0) + 1;
|
|
2025
|
-
usageByType[usage.usageType] = (usageByType[usage.usageType] || 0) + 1;
|
|
2026
|
-
});
|
|
2027
|
-
const mostUsedTables = Object.entries(tableUsage)
|
|
2028
|
-
.sort((a, b) => b[1] - a[1])
|
|
2029
|
-
.slice(0, 10)
|
|
2030
|
-
.map(([tableName, usageCount]) => ({ tableName, usageCount }));
|
|
2031
|
-
return {
|
|
2032
|
-
totalSchemas: schemas.length,
|
|
2033
|
-
totalTables,
|
|
2034
|
-
totalUsagePatterns: usages.length,
|
|
2035
|
-
mostUsedTables,
|
|
2036
|
-
usageByType
|
|
2037
|
-
};
|
|
2038
|
-
}
|
|
2039
|
-
/**
|
|
2040
|
-
* Update search to include schema context
|
|
2041
|
-
*/
|
|
2042
|
-
async searchWithSchemaContext(query, options = {}) {
|
|
2043
|
-
this.ensureInitialized();
|
|
2044
|
-
// First perform regular function search
|
|
2045
|
-
const searchResult = await this.searchFunctions(options);
|
|
2046
|
-
if (!options.includeSchemaUsage) {
|
|
2047
|
-
return searchResult;
|
|
2048
|
-
}
|
|
2049
|
-
// Add schema context to results
|
|
2050
|
-
const enhancedFunctions = await Promise.all(searchResult.functions.map(async (func) => {
|
|
2051
|
-
const schemaUsage = await this.getSchemaUsage({
|
|
2052
|
-
filePath: func.filePath,
|
|
2053
|
-
functionName: func.name
|
|
2054
|
-
});
|
|
2055
|
-
return {
|
|
2056
|
-
...func,
|
|
2057
|
-
schemaUsage,
|
|
2058
|
-
affectedTables: [...new Set(schemaUsage.map(u => u.tableName))],
|
|
2059
|
-
schemaPatterns: [...new Set(schemaUsage.map(u => u.usageType))]
|
|
2060
|
-
};
|
|
2061
|
-
}));
|
|
2062
|
-
// Extract schema context for summary
|
|
2063
|
-
const allSchemaUsage = enhancedFunctions.flatMap(f => f.schemaUsage || []);
|
|
2064
|
-
const schemaContext = [...new Set(allSchemaUsage.map(u => ({
|
|
2065
|
-
tableName: u.tableName,
|
|
2066
|
-
usageType: u.usageType
|
|
2067
|
-
})))];
|
|
2068
|
-
return {
|
|
2069
|
-
...searchResult,
|
|
2070
|
-
functions: enhancedFunctions,
|
|
2071
|
-
schemaContext
|
|
2072
|
-
};
|
|
2073
|
-
}
|
|
2074
|
-
async createProjectTask(input) {
|
|
2075
|
-
return this.getTaskRepository().create(input);
|
|
2076
|
-
}
|
|
2077
|
-
async getProjectTask(taskId) {
|
|
2078
|
-
return this.getTaskRepository().getById(taskId);
|
|
2079
|
-
}
|
|
2080
|
-
async listProjectTasks(projectPath, options) {
|
|
2081
|
-
return this.getTaskRepository().list(projectPath, options);
|
|
2082
|
-
}
|
|
2083
|
-
async listProjectTasksTree(projectPath, options) {
|
|
2084
|
-
return this.getTaskRepository().listTree(projectPath, options);
|
|
2085
|
-
}
|
|
2086
|
-
async listActionableProjectTasks(projectPath, options) {
|
|
2087
|
-
return this.getTaskRepository().listActionable(projectPath, options);
|
|
2088
|
-
}
|
|
2089
|
-
async completeProjectTask(taskId) {
|
|
2090
|
-
return this.getTaskRepository().complete(taskId);
|
|
2091
|
-
}
|
|
2092
|
-
/**
|
|
2093
|
-
* @param patch Raw JSON-shaped update; unknown keys are ignored (see sanitizeUpdatePatch).
|
|
2094
|
-
*/
|
|
2095
|
-
async updateProjectTask(taskId, patch) {
|
|
2096
|
-
return this.getTaskRepository().update(taskId, patch);
|
|
2097
|
-
}
|
|
2098
|
-
async deleteProjectTask(taskId, mode) {
|
|
2099
|
-
return this.getTaskRepository().delete(taskId, mode);
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
//# sourceMappingURL=codeIndexDB.js.map
|