code-auditor-mcp 4.1.0 → 5.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +374 -0
- package/README.md +4 -4
- package/configs/hhra-compat.json +0 -8
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js +7 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts +2 -8
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js +4 -4
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts +2 -2
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js.map +1 -1
- package/dist/analyzers/applicability.d.ts +42 -0
- package/dist/analyzers/applicability.d.ts.map +1 -1
- package/dist/analyzers/applicability.js +74 -0
- package/dist/analyzers/applicability.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +18 -24
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +30 -23
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +113 -76
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +2 -0
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +8 -3
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +210 -148
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +23 -28
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/handleIdentification.d.ts +356 -0
- package/dist/analyzers/handleIdentification.d.ts.map +1 -0
- package/dist/analyzers/handleIdentification.js +424 -0
- package/dist/analyzers/handleIdentification.js.map +1 -0
- package/dist/analyzers/manifestStaleness.d.ts +64 -0
- package/dist/analyzers/manifestStaleness.d.ts.map +1 -0
- package/dist/analyzers/manifestStaleness.js +115 -0
- package/dist/analyzers/manifestStaleness.js.map +1 -0
- package/dist/analyzers/orgFilterTiers.d.ts +73 -1
- package/dist/analyzers/orgFilterTiers.d.ts.map +1 -1
- package/dist/analyzers/orgFilterTiers.js +166 -1
- package/dist/analyzers/orgFilterTiers.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +251 -57
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +1109 -429
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +2 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +2 -0
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/receiverResolution.d.ts +183 -0
- package/dist/analyzers/receiverResolution.d.ts.map +1 -0
- package/dist/analyzers/receiverResolution.js +540 -0
- package/dist/analyzers/receiverResolution.js.map +1 -0
- package/dist/analyzers/receiverRoot.d.ts +328 -0
- package/dist/analyzers/receiverRoot.d.ts.map +1 -0
- package/dist/analyzers/receiverRoot.js +990 -0
- package/dist/analyzers/receiverRoot.js.map +1 -0
- package/dist/analyzers/ruleRegistry.d.ts +12 -68
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +32 -342
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +10 -0
- package/dist/analyzers/ruleTiming.d.ts.map +1 -1
- package/dist/analyzers/ruleTiming.js +20 -0
- package/dist/analyzers/ruleTiming.js.map +1 -1
- package/dist/analyzers/sqlLiteral.d.ts +76 -0
- package/dist/analyzers/sqlLiteral.d.ts.map +1 -0
- package/dist/analyzers/sqlLiteral.js +419 -0
- package/dist/analyzers/sqlLiteral.js.map +1 -0
- package/dist/analyzers/tsEcosystem.d.ts +115 -0
- package/dist/analyzers/tsEcosystem.d.ts.map +1 -0
- package/dist/analyzers/tsEcosystem.js +231 -0
- package/dist/analyzers/tsEcosystem.js.map +1 -0
- package/dist/analyzers/tsExpressionDescriptor.d.ts +156 -0
- package/dist/analyzers/tsExpressionDescriptor.d.ts.map +1 -0
- package/dist/analyzers/tsExpressionDescriptor.js +374 -0
- package/dist/analyzers/tsExpressionDescriptor.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +6 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +107 -44
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +1 -12
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +24 -101
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +287 -49
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +2130 -512
- 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 +2 -18
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -33
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +20 -143
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +4 -3
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +36 -13
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js +6 -0
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +15 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +25 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +21 -32
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +166 -196
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/functionConcerns.d.ts +3 -0
- package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
- package/dist/analyzers/universal/functionConcerns.js +4 -1
- package/dist/analyzers/universal/functionConcerns.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +310 -69
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +709 -383
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/config.d.ts +3 -14
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/config.js +3 -16
- package/dist/analyzers/universal/schema/config.js.map +1 -1
- package/dist/analyzers/universal/schema/discovery.d.ts +22 -1
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/discovery.js +36 -20
- package/dist/analyzers/universal/schema/discovery.js.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js +13 -3
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
- package/dist/analyzers/universal/schema/migrations.d.ts +187 -27
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/migrations.js +443 -186
- package/dist/analyzers/universal/schema/migrations.js.map +1 -1
- package/dist/analyzers/universal/schema/types.d.ts +17 -3
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/violations.js +1 -1
- package/dist/analyzers/universal/schema/violations.js.map +1 -1
- package/dist/analyzers/universal/styleScale.d.ts +52 -0
- package/dist/analyzers/universal/styleScale.d.ts.map +1 -0
- package/dist/analyzers/universal/styleScale.js +95 -0
- package/dist/analyzers/universal/styleScale.js.map +1 -0
- package/dist/applyDataDirEnv.js +1 -1
- package/dist/applyDataDirEnv.js.map +1 -1
- package/dist/auditRouter.d.ts +26 -30
- package/dist/auditRouter.d.ts.map +1 -1
- package/dist/auditRouter.js +373 -143
- package/dist/auditRouter.js.map +1 -1
- package/dist/auditRunner.d.ts +64 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +1537 -1051
- package/dist/auditRunner.js.map +1 -1
- package/dist/baseline.d.ts +30 -3
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +70 -9
- package/dist/baseline.js.map +1 -1
- package/dist/cli.js +997 -812
- package/dist/cli.js.map +1 -1
- package/dist/codeIndex/analyzerConfig.d.ts +60 -0
- package/dist/codeIndex/analyzerConfig.d.ts.map +1 -0
- package/dist/codeIndex/analyzerConfig.js +98 -0
- package/dist/codeIndex/analyzerConfig.js.map +1 -0
- package/dist/codeIndex/auditResults.d.ts +39 -0
- package/dist/codeIndex/auditResults.d.ts.map +1 -0
- package/dist/codeIndex/auditResults.js +99 -0
- package/dist/codeIndex/auditResults.js.map +1 -0
- package/dist/codeIndex/codeMap.d.ts +59 -0
- package/dist/codeIndex/codeMap.d.ts.map +1 -0
- package/dist/codeIndex/codeMap.js +72 -0
- package/dist/codeIndex/codeMap.js.map +1 -0
- package/dist/codeIndex/conventions.d.ts +33 -0
- package/dist/codeIndex/conventions.d.ts.map +1 -0
- package/dist/codeIndex/conventions.js +84 -0
- package/dist/codeIndex/conventions.js.map +1 -0
- package/dist/codeIndex/coverage.d.ts +78 -0
- package/dist/codeIndex/coverage.d.ts.map +1 -0
- package/dist/codeIndex/coverage.js +164 -0
- package/dist/codeIndex/coverage.js.map +1 -0
- package/dist/codeIndex/dependencyGraph.d.ts +97 -0
- package/dist/codeIndex/dependencyGraph.d.ts.map +1 -0
- package/dist/codeIndex/dependencyGraph.js +241 -0
- package/dist/codeIndex/dependencyGraph.js.map +1 -0
- package/dist/codeIndex/functionIndex.d.ts +111 -0
- package/dist/codeIndex/functionIndex.d.ts.map +1 -0
- package/dist/codeIndex/functionIndex.js +306 -0
- package/dist/codeIndex/functionIndex.js.map +1 -0
- package/dist/codeIndex/inferredReceivers.d.ts +32 -0
- package/dist/codeIndex/inferredReceivers.d.ts.map +1 -0
- package/dist/codeIndex/inferredReceivers.js +41 -0
- package/dist/codeIndex/inferredReceivers.js.map +1 -0
- package/dist/codeIndex/meta.d.ts +30 -0
- package/dist/codeIndex/meta.d.ts.map +1 -0
- package/dist/codeIndex/meta.js +37 -0
- package/dist/codeIndex/meta.js.map +1 -0
- package/dist/codeIndex/migrations.d.ts +96 -0
- package/dist/codeIndex/migrations.d.ts.map +1 -0
- package/dist/codeIndex/migrations.js +1302 -0
- package/dist/codeIndex/migrations.js.map +1 -0
- package/dist/codeIndex/projectTasks.d.ts +54 -0
- package/dist/codeIndex/projectTasks.d.ts.map +1 -0
- package/dist/codeIndex/projectTasks.js +81 -0
- package/dist/codeIndex/projectTasks.js.map +1 -0
- package/dist/codeIndex/provenance.d.ts +47 -0
- package/dist/codeIndex/provenance.d.ts.map +1 -0
- package/dist/codeIndex/provenance.js +48 -0
- package/dist/codeIndex/provenance.js.map +1 -0
- package/dist/codeIndex/rawSql.d.ts +54 -0
- package/dist/codeIndex/rawSql.d.ts.map +1 -0
- package/dist/codeIndex/rawSql.js +71 -0
- package/dist/codeIndex/rawSql.js.map +1 -0
- package/dist/codeIndex/schema.d.ts +99 -0
- package/dist/codeIndex/schema.d.ts.map +1 -0
- package/dist/codeIndex/schema.js +174 -0
- package/dist/codeIndex/schema.js.map +1 -0
- package/dist/codeIndex/search.d.ts +40 -0
- package/dist/codeIndex/search.d.ts.map +1 -0
- package/dist/codeIndex/search.js +316 -0
- package/dist/codeIndex/search.js.map +1 -0
- package/dist/codeIndex/shared.d.ts +27 -0
- package/dist/codeIndex/shared.d.ts.map +1 -0
- package/dist/codeIndex/shared.js +46 -0
- package/dist/codeIndex/shared.js.map +1 -0
- package/dist/codeIndex/sqliteCollection.d.ts +81 -0
- package/dist/codeIndex/sqliteCollection.d.ts.map +1 -0
- package/dist/codeIndex/sqliteCollection.js +250 -0
- package/dist/codeIndex/sqliteCollection.js.map +1 -0
- package/dist/codeIndex/whitelist.d.ts +59 -0
- package/dist/codeIndex/whitelist.d.ts.map +1 -0
- package/dist/codeIndex/whitelist.js +188 -0
- package/dist/codeIndex/whitelist.js.map +1 -0
- package/dist/codeIndexDB.d.ts +130 -277
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +364 -2748
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/codeIndexService.d.ts +87 -12
- package/dist/codeIndexService.d.ts.map +1 -1
- package/dist/codeIndexService.js +110 -34
- package/dist/codeIndexService.js.map +1 -1
- package/dist/componentScanner.d.ts +35 -0
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +114 -80
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/configGeneratePath.d.ts +4 -0
- package/dist/config/configGeneratePath.d.ts.map +1 -1
- package/dist/config/configGeneratePath.js +4 -0
- package/dist/config/configGeneratePath.js.map +1 -1
- package/dist/config/configLoader.d.ts +16 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +26 -23
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +11 -7
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +21 -20
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +17 -2
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +32 -25
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/config/lintConfigReader.d.ts +6 -0
- package/dist/config/lintConfigReader.d.ts.map +1 -1
- package/dist/config/lintConfigReader.js +6 -0
- package/dist/config/lintConfigReader.js.map +1 -1
- package/dist/config/staticObjectExtract.d.ts +4 -0
- package/dist/config/staticObjectExtract.d.ts.map +1 -1
- package/dist/config/staticObjectExtract.js +7 -3
- package/dist/config/staticObjectExtract.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +2 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -1
- package/dist/config/thresholdRationales.js +5 -2
- package/dist/config/thresholdRationales.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -5
- package/dist/constants.js.map +1 -1
- package/dist/conventions/conventionMiner.d.ts +105 -2
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +334 -259
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/coverage/coverageService.js +4 -4
- package/dist/coverage/coverageService.js.map +1 -1
- package/dist/daemon/args.d.ts +3 -0
- package/dist/daemon/args.d.ts.map +1 -1
- package/dist/daemon/args.js +3 -0
- package/dist/daemon/args.js.map +1 -1
- package/dist/daemon/core.d.ts +33 -27
- package/dist/daemon/core.d.ts.map +1 -1
- package/dist/daemon/core.js +198 -162
- package/dist/daemon/core.js.map +1 -1
- package/dist/daemon/lspServer.d.ts +8 -0
- package/dist/daemon/lspServer.d.ts.map +1 -1
- package/dist/daemon/lspServer.js +8 -0
- package/dist/daemon/lspServer.js.map +1 -1
- package/dist/daemon/main.d.ts +2 -0
- package/dist/daemon/main.d.ts.map +1 -1
- package/dist/daemon/main.js +2 -0
- package/dist/daemon/main.js.map +1 -1
- package/dist/daemon/resolve.d.ts +22 -7
- package/dist/daemon/resolve.d.ts.map +1 -1
- package/dist/daemon/resolve.js +29 -8
- package/dist/daemon/resolve.js.map +1 -1
- package/dist/daemon/socketClient.d.ts +10 -1
- package/dist/daemon/socketClient.d.ts.map +1 -1
- package/dist/daemon/socketClient.js +10 -1
- package/dist/daemon/socketClient.js.map +1 -1
- package/dist/daemon/socketServer.d.ts +13 -0
- package/dist/daemon/socketServer.d.ts.map +1 -1
- package/dist/daemon/socketServer.js +13 -0
- package/dist/daemon/socketServer.js.map +1 -1
- package/dist/dataPaths.d.ts +27 -8
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +28 -44
- package/dist/dataPaths.js.map +1 -1
- package/dist/dismissals.d.ts +22 -1
- package/dist/dismissals.d.ts.map +1 -1
- package/dist/dismissals.js +22 -1
- package/dist/dismissals.js.map +1 -1
- package/dist/enforcement/gate.d.ts +1 -0
- package/dist/enforcement/gate.d.ts.map +1 -1
- package/dist/enforcement/gate.js +1 -0
- package/dist/enforcement/gate.js.map +1 -1
- package/dist/fingerprint.d.ts +6 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +46 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/functionScanner.d.ts +30 -1
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +258 -256
- package/dist/functionScanner.js.map +1 -1
- package/dist/generators/AWSQConfigGenerator.d.ts +23 -0
- package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
- package/dist/generators/AWSQConfigGenerator.js +23 -0
- package/dist/generators/AWSQConfigGenerator.js.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts +7 -0
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js +7 -0
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.d.ts +23 -0
- package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.js +23 -0
- package/dist/generators/ClaudeConfigGenerator.js.map +1 -1
- package/dist/generators/ClineConfigGenerator.d.ts +23 -0
- package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClineConfigGenerator.js +23 -0
- package/dist/generators/ClineConfigGenerator.js.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.d.ts +23 -0
- package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.js +23 -0
- package/dist/generators/CodeiumConfigGenerator.js.map +1 -1
- package/dist/generators/CodexConfigGenerator.d.ts +23 -0
- package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodexConfigGenerator.js +23 -0
- package/dist/generators/CodexConfigGenerator.js.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.d.ts +9 -0
- package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.js +9 -0
- package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
- package/dist/generators/ContinueConfigGenerator.d.ts +23 -0
- package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ContinueConfigGenerator.js +23 -0
- package/dist/generators/ContinueConfigGenerator.js.map +1 -1
- package/dist/generators/CopilotConfigGenerator.d.ts +23 -0
- package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CopilotConfigGenerator.js +23 -0
- package/dist/generators/CopilotConfigGenerator.js.map +1 -1
- package/dist/generators/CursorConfigGenerator.d.ts +23 -0
- package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CursorConfigGenerator.js +23 -0
- package/dist/generators/CursorConfigGenerator.js.map +1 -1
- package/dist/generators/GeminiConfigGenerator.d.ts +23 -0
- package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
- package/dist/generators/GeminiConfigGenerator.js +23 -0
- package/dist/generators/GeminiConfigGenerator.js.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.d.ts +23 -0
- package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.js +23 -0
- package/dist/generators/VSCodeConfigGenerator.js.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.d.ts +23 -0
- package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.js +23 -0
- package/dist/generators/ZCodeConfigGenerator.js.map +1 -1
- package/dist/graph/blastRadius.d.ts.map +1 -1
- package/dist/graph/blastRadius.js +12 -7
- package/dist/graph/blastRadius.js.map +1 -1
- package/dist/graph/callGraph.d.ts +31 -0
- package/dist/graph/callGraph.d.ts.map +1 -1
- package/dist/graph/callGraph.js +62 -32
- package/dist/graph/callGraph.js.map +1 -1
- package/dist/graph/importClassification.d.ts +28 -0
- package/dist/graph/importClassification.d.ts.map +1 -1
- package/dist/graph/importClassification.js +57 -0
- package/dist/graph/importClassification.js.map +1 -1
- package/dist/graph/importGraph.d.ts +38 -0
- package/dist/graph/importGraph.d.ts.map +1 -1
- package/dist/graph/importGraph.js +38 -0
- package/dist/graph/importGraph.js.map +1 -1
- package/dist/graph/outputFormatter.d.ts +12 -4
- package/dist/graph/outputFormatter.d.ts.map +1 -1
- package/dist/graph/outputFormatter.js +12 -62
- package/dist/graph/outputFormatter.js.map +1 -1
- package/dist/hooks/codex.d.ts +8 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +7 -0
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hooks/core.d.ts +6 -0
- package/dist/hooks/core.d.ts.map +1 -1
- package/dist/hooks/core.js +6 -1
- package/dist/hooks/core.js.map +1 -1
- package/dist/hooks/cursor.d.ts +11 -1
- package/dist/hooks/cursor.d.ts.map +1 -1
- package/dist/hooks/cursor.js +10 -0
- package/dist/hooks/cursor.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/installConfig.d.ts +23 -5
- package/dist/installConfig.d.ts.map +1 -1
- package/dist/installConfig.js +23 -5
- package/dist/installConfig.js.map +1 -1
- package/dist/installer.d.ts +6 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +6 -0
- package/dist/installer.js.map +1 -1
- package/dist/invariants/ruleEngine.d.ts +3 -0
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +156 -119
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.d.ts +3 -0
- package/dist/invariants/ruleValidator.d.ts.map +1 -1
- package/dist/invariants/ruleValidator.js +185 -178
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +4 -1
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +8 -19
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +8 -0
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +55 -5
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +1 -1
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +4 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +37 -3
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +11 -14
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/go/analyzer-darwin-amd64 +0 -0
- package/dist/languages/go/analyzer-darwin-arm64 +0 -0
- package/dist/languages/go/analyzer-linux-amd64 +0 -0
- package/dist/languages/go/analyzer-linux-arm64 +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +2 -0
- package/dist/languages/go/analyzer-src/dataaccess.go +388 -0
- package/dist/languages/go/analyzer-src/types.go +8 -0
- package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
- package/dist/languages/go/database-packages.json +8 -0
- package/dist/languages/go/goResolution.d.ts +229 -0
- package/dist/languages/go/goResolution.d.ts.map +1 -0
- package/dist/languages/go/goResolution.js +799 -0
- package/dist/languages/go/goResolution.js.map +1 -0
- package/dist/languages/go/stdlib-packages.json +353 -0
- package/dist/languages/go/tenantInputs.d.ts +37 -0
- package/dist/languages/go/tenantInputs.d.ts.map +1 -0
- package/dist/languages/go/tenantInputs.js +121 -0
- package/dist/languages/go/tenantInputs.js.map +1 -0
- package/dist/languages/index.d.ts +1 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +5 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/json/JsonAdapter.d.ts +177 -0
- package/dist/languages/json/JsonAdapter.d.ts.map +1 -0
- package/dist/languages/json/JsonAdapter.js +531 -0
- package/dist/languages/json/JsonAdapter.js.map +1 -0
- package/dist/languages/locationIndex.d.ts +41 -0
- package/dist/languages/locationIndex.d.ts.map +1 -0
- package/dist/languages/locationIndex.js +42 -0
- package/dist/languages/locationIndex.js.map +1 -0
- package/dist/languages/sql/dialectDetection.d.ts +57 -0
- package/dist/languages/sql/dialectDetection.d.ts.map +1 -0
- package/dist/languages/sql/dialectDetection.js +135 -0
- package/dist/languages/sql/dialectDetection.js.map +1 -0
- package/dist/languages/sql/sqlAst.d.ts +494 -0
- package/dist/languages/sql/sqlAst.d.ts.map +1 -0
- package/dist/languages/sql/sqlAst.js +1817 -0
- package/dist/languages/sql/sqlAst.js.map +1 -0
- package/dist/languages/testConventions.d.ts +12 -1
- package/dist/languages/testConventions.d.ts.map +1 -1
- package/dist/languages/testConventions.js +13 -1
- package/dist/languages/testConventions.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.js +2 -2
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +3 -4
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +10 -4
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/tree-sitter/rawNode.d.ts +3 -0
- package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -1
- package/dist/languages/tree-sitter/rawNode.js +3 -0
- package/dist/languages/tree-sitter/rawNode.js.map +1 -1
- package/dist/languages/types.d.ts +7 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +36 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +168 -45
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/languages/typescript/database-packages.json +19 -0
- package/dist/ledger.d.ts +118 -2
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +167 -45
- package/dist/ledger.js.map +1 -1
- package/dist/legacyIndexNotice.d.ts +32 -0
- package/dist/legacyIndexNotice.d.ts.map +1 -0
- package/dist/legacyIndexNotice.js +89 -0
- package/dist/legacyIndexNotice.js.map +1 -0
- package/dist/mcp-tools/discoveryQueries.d.ts +55 -0
- package/dist/mcp-tools/discoveryQueries.d.ts.map +1 -0
- package/dist/mcp-tools/discoveryQueries.js +157 -0
- package/dist/mcp-tools/discoveryQueries.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts +9 -123
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +180 -170
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +20 -0
- package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
- package/dist/mcp-tools/whitelistTools.js +20 -0
- package/dist/mcp-tools/whitelistTools.js.map +1 -1
- package/dist/mcp-tools/workflowGuide.d.ts +9 -0
- package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.js +13 -5
- package/dist/mcp-tools/workflowGuide.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts +78 -0
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +203 -340
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.d.ts +2 -0
- package/dist/mcp-ui-simple.d.ts.map +1 -1
- package/dist/mcp-ui-simple.js +12 -5
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +832 -851
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts +29 -33
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +102 -791
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/mcpAutoIndex.d.ts +5 -0
- package/dist/mcpAutoIndex.d.ts.map +1 -1
- package/dist/mcpAutoIndex.js +7 -2
- package/dist/mcpAutoIndex.js.map +1 -1
- package/dist/mcpDiagnostics.d.ts +7 -0
- package/dist/mcpDiagnostics.d.ts.map +1 -1
- package/dist/mcpDiagnostics.js +34 -9
- package/dist/mcpDiagnostics.js.map +1 -1
- package/dist/mcpToolErrors.d.ts +16 -0
- package/dist/mcpToolErrors.d.ts.map +1 -1
- package/dist/mcpToolErrors.js +16 -0
- package/dist/mcpToolErrors.js.map +1 -1
- package/dist/nextFile.d.ts +6 -0
- package/dist/nextFile.d.ts.map +1 -1
- package/dist/nextFile.js +6 -0
- package/dist/nextFile.js.map +1 -1
- package/dist/nextFileIncremental.d.ts +44 -4
- package/dist/nextFileIncremental.d.ts.map +1 -1
- package/dist/nextFileIncremental.js +47 -7
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/phase/batchFunctions.d.ts +38 -0
- package/dist/phase/batchFunctions.d.ts.map +1 -0
- package/dist/phase/batchFunctions.js +64 -0
- package/dist/phase/batchFunctions.js.map +1 -0
- package/dist/phase/checks.d.ts +39 -0
- package/dist/phase/checks.d.ts.map +1 -0
- package/dist/phase/checks.js +48 -0
- package/dist/phase/checks.js.map +1 -0
- package/dist/phase/codeBlocks.d.ts +25 -0
- package/dist/phase/codeBlocks.d.ts.map +1 -0
- package/dist/phase/codeBlocks.js +268 -0
- package/dist/phase/codeBlocks.js.map +1 -0
- package/dist/phase/colorMath.d.ts +41 -0
- package/dist/phase/colorMath.d.ts.map +1 -0
- package/dist/phase/colorMath.js +109 -0
- package/dist/phase/colorMath.js.map +1 -0
- package/dist/phase/config.d.ts +29 -0
- package/dist/phase/config.d.ts.map +1 -0
- package/dist/phase/config.js +54 -0
- package/dist/phase/config.js.map +1 -0
- package/dist/phase/consumed.d.ts +47 -0
- package/dist/phase/consumed.d.ts.map +1 -0
- package/dist/phase/consumed.js +22 -0
- package/dist/phase/consumed.js.map +1 -0
- package/dist/phase/conventionMining.d.ts +59 -0
- package/dist/phase/conventionMining.d.ts.map +1 -0
- package/dist/phase/conventionMining.js +152 -0
- package/dist/phase/conventionMining.js.map +1 -0
- package/dist/phase/coverage.d.ts +65 -0
- package/dist/phase/coverage.d.ts.map +1 -0
- package/dist/phase/coverage.js +121 -0
- package/dist/phase/coverage.js.map +1 -0
- package/dist/phase/dag.d.ts +49 -0
- package/dist/phase/dag.d.ts.map +1 -0
- package/dist/phase/dag.js +120 -0
- package/dist/phase/dag.js.map +1 -0
- package/dist/phase/dataAccessCalls.d.ts +33 -0
- package/dist/phase/dataAccessCalls.d.ts.map +1 -0
- package/dist/phase/dataAccessCalls.js +42 -0
- package/dist/phase/dataAccessCalls.js.map +1 -0
- package/dist/phase/dataAccessCallsCandidates.d.ts +25 -0
- package/dist/phase/dataAccessCallsCandidates.d.ts.map +1 -0
- package/dist/phase/dataAccessCallsCandidates.js +27 -0
- package/dist/phase/dataAccessCallsCandidates.js.map +1 -0
- package/dist/phase/dynamicSql.d.ts +18 -0
- package/dist/phase/dynamicSql.d.ts.map +1 -0
- package/dist/phase/dynamicSql.js +20 -0
- package/dist/phase/dynamicSql.js.map +1 -0
- package/dist/phase/exportForm.d.ts +23 -0
- package/dist/phase/exportForm.d.ts.map +1 -0
- package/dist/phase/exportForm.js +29 -0
- package/dist/phase/exportForm.js.map +1 -0
- package/dist/phase/exportSymbols.d.ts +21 -0
- package/dist/phase/exportSymbols.d.ts.map +1 -0
- package/dist/phase/exportSymbols.js +28 -0
- package/dist/phase/exportSymbols.js.map +1 -0
- package/dist/phase/fileHeader.d.ts +15 -0
- package/dist/phase/fileHeader.d.ts.map +1 -0
- package/dist/phase/fileHeader.js +33 -0
- package/dist/phase/fileHeader.js.map +1 -0
- package/dist/phase/fileImports.d.ts +39 -0
- package/dist/phase/fileImports.d.ts.map +1 -0
- package/dist/phase/fileImports.js +138 -0
- package/dist/phase/fileImports.js.map +1 -0
- package/dist/phase/fileSymbols.d.ts +25 -0
- package/dist/phase/fileSymbols.d.ts.map +1 -0
- package/dist/phase/fileSymbols.js +302 -0
- package/dist/phase/fileSymbols.js.map +1 -0
- package/dist/phase/functionBodies.d.ts +28 -0
- package/dist/phase/functionBodies.d.ts.map +1 -0
- package/dist/phase/functionBodies.js +67 -0
- package/dist/phase/functionBodies.js.map +1 -0
- package/dist/phase/functionIndex.d.ts +28 -0
- package/dist/phase/functionIndex.d.ts.map +1 -0
- package/dist/phase/functionIndex.js +152 -0
- package/dist/phase/functionIndex.js.map +1 -0
- package/dist/phase/goFunctionAnalysis.d.ts +47 -0
- package/dist/phase/goFunctionAnalysis.d.ts.map +1 -0
- package/dist/phase/goFunctionAnalysis.js +291 -0
- package/dist/phase/goFunctionAnalysis.js.map +1 -0
- package/dist/phase/goFunctions.d.ts +38 -0
- package/dist/phase/goFunctions.d.ts.map +1 -0
- package/dist/phase/goFunctions.js +154 -0
- package/dist/phase/goFunctions.js.map +1 -0
- package/dist/phase/goImports.d.ts +29 -0
- package/dist/phase/goImports.d.ts.map +1 -0
- package/dist/phase/goImports.js +68 -0
- package/dist/phase/goImports.js.map +1 -0
- package/dist/phase/goPackageBindings.d.ts +22 -0
- package/dist/phase/goPackageBindings.d.ts.map +1 -0
- package/dist/phase/goPackageBindings.js +30 -0
- package/dist/phase/goPackageBindings.js.map +1 -0
- package/dist/phase/goSwitches.d.ts +28 -0
- package/dist/phase/goSwitches.d.ts.map +1 -0
- package/dist/phase/goSwitches.js +52 -0
- package/dist/phase/goSwitches.js.map +1 -0
- package/dist/phase/importForm.d.ts +20 -0
- package/dist/phase/importForm.d.ts.map +1 -0
- package/dist/phase/importForm.js +27 -0
- package/dist/phase/importForm.js.map +1 -0
- package/dist/phase/importSpecifiers.d.ts +20 -0
- package/dist/phase/importSpecifiers.d.ts.map +1 -0
- package/dist/phase/importSpecifiers.js +30 -0
- package/dist/phase/importSpecifiers.js.map +1 -0
- package/dist/phase/imports.d.ts +18 -0
- package/dist/phase/imports.d.ts.map +1 -0
- package/dist/phase/imports.js +25 -0
- package/dist/phase/imports.js.map +1 -0
- package/dist/phase/jsonDocument.d.ts +26 -0
- package/dist/phase/jsonDocument.d.ts.map +1 -0
- package/dist/phase/jsonDocument.js +36 -0
- package/dist/phase/jsonDocument.js.map +1 -0
- package/dist/phase/localBinding.d.ts +33 -0
- package/dist/phase/localBinding.d.ts.map +1 -0
- package/dist/phase/localBinding.js +208 -0
- package/dist/phase/localBinding.js.map +1 -0
- package/dist/phase/loopQueries.d.ts +31 -0
- package/dist/phase/loopQueries.d.ts.map +1 -0
- package/dist/phase/loopQueries.js +44 -0
- package/dist/phase/loopQueries.js.map +1 -0
- package/dist/phase/loopQueryCandidates.d.ts +26 -0
- package/dist/phase/loopQueryCandidates.d.ts.map +1 -0
- package/dist/phase/loopQueryCandidates.js +28 -0
- package/dist/phase/loopQueryCandidates.js.map +1 -0
- package/dist/phase/oracles.d.ts +216 -0
- package/dist/phase/oracles.d.ts.map +1 -0
- package/dist/phase/oracles.js +484 -0
- package/dist/phase/oracles.js.map +1 -0
- package/dist/phase/phaseModel.d.ts +190 -0
- package/dist/phase/phaseModel.d.ts.map +1 -0
- package/dist/phase/phaseModel.js +468 -0
- package/dist/phase/phaseModel.js.map +1 -0
- package/dist/phase/producers.d.ts +450 -0
- package/dist/phase/producers.d.ts.map +1 -0
- package/dist/phase/producers.js +1027 -0
- package/dist/phase/producers.js.map +1 -0
- package/dist/phase/querySiteCandidates.d.ts +27 -0
- package/dist/phase/querySiteCandidates.d.ts.map +1 -0
- package/dist/phase/querySiteCandidates.js +66 -0
- package/dist/phase/querySiteCandidates.js.map +1 -0
- package/dist/phase/querySites.d.ts +30 -0
- package/dist/phase/querySites.d.ts.map +1 -0
- package/dist/phase/querySites.js +93 -0
- package/dist/phase/querySites.js.map +1 -0
- package/dist/phase/reachability.d.ts +61 -0
- package/dist/phase/reachability.d.ts.map +1 -0
- package/dist/phase/reachability.js +104 -0
- package/dist/phase/reachability.js.map +1 -0
- package/dist/phase/reactComponents.d.ts +33 -0
- package/dist/phase/reactComponents.d.ts.map +1 -0
- package/dist/phase/reactComponents.js +47 -0
- package/dist/phase/reactComponents.js.map +1 -0
- package/dist/phase/receiverActivity.d.ts +35 -0
- package/dist/phase/receiverActivity.d.ts.map +1 -0
- package/dist/phase/receiverActivity.js +66 -0
- package/dist/phase/receiverActivity.js.map +1 -0
- package/dist/phase/receiverConsumers.d.ts +200 -0
- package/dist/phase/receiverConsumers.d.ts.map +1 -0
- package/dist/phase/receiverConsumers.js +1025 -0
- package/dist/phase/receiverConsumers.js.map +1 -0
- package/dist/phase/receiverProvenance.d.ts +97 -0
- package/dist/phase/receiverProvenance.d.ts.map +1 -0
- package/dist/phase/receiverProvenance.js +337 -0
- package/dist/phase/receiverProvenance.js.map +1 -0
- package/dist/phase/routing.d.ts +47 -0
- package/dist/phase/routing.d.ts.map +1 -0
- package/dist/phase/routing.js +62 -0
- package/dist/phase/routing.js.map +1 -0
- package/dist/phase/rules/conventions.d.ts +61 -0
- package/dist/phase/rules/conventions.d.ts.map +1 -0
- package/dist/phase/rules/conventions.js +403 -0
- package/dist/phase/rules/conventions.js.map +1 -0
- package/dist/phase/rules/crossDomain.d.ts +102 -0
- package/dist/phase/rules/crossDomain.d.ts.map +1 -0
- package/dist/phase/rules/crossDomain.js +918 -0
- package/dist/phase/rules/crossDomain.js.map +1 -0
- package/dist/phase/rules/dataAccess.d.ts +62 -0
- package/dist/phase/rules/dataAccess.d.ts.map +1 -0
- package/dist/phase/rules/dataAccess.js +480 -0
- package/dist/phase/rules/dataAccess.js.map +1 -0
- package/dist/phase/rules/dependencyGraph.d.ts +44 -0
- package/dist/phase/rules/dependencyGraph.d.ts.map +1 -0
- package/dist/phase/rules/dependencyGraph.js +260 -0
- package/dist/phase/rules/dependencyGraph.js.map +1 -0
- package/dist/phase/rules/documentation.d.ts +45 -0
- package/dist/phase/rules/documentation.d.ts.map +1 -0
- package/dist/phase/rules/documentation.js +340 -0
- package/dist/phase/rules/documentation.js.map +1 -0
- package/dist/phase/rules/dry.d.ts +109 -0
- package/dist/phase/rules/dry.d.ts.map +1 -0
- package/dist/phase/rules/dry.js +803 -0
- package/dist/phase/rules/dry.js.map +1 -0
- package/dist/phase/rules/fileDocumentation.d.ts +36 -0
- package/dist/phase/rules/fileDocumentation.d.ts.map +1 -0
- package/dist/phase/rules/fileDocumentation.js +134 -0
- package/dist/phase/rules/fileDocumentation.js.map +1 -0
- package/dist/phase/rules/functionBodies.d.ts +33 -0
- package/dist/phase/rules/functionBodies.d.ts.map +1 -0
- package/dist/phase/rules/functionBodies.js +71 -0
- package/dist/phase/rules/functionBodies.js.map +1 -0
- package/dist/phase/rules/goRules.d.ts +49 -0
- package/dist/phase/rules/goRules.d.ts.map +1 -0
- package/dist/phase/rules/goRules.js +209 -0
- package/dist/phase/rules/goRules.js.map +1 -0
- package/dist/phase/rules/querySites.d.ts +29 -0
- package/dist/phase/rules/querySites.d.ts.map +1 -0
- package/dist/phase/rules/querySites.js +94 -0
- package/dist/phase/rules/querySites.js.map +1 -0
- package/dist/phase/rules/react.d.ts +38 -0
- package/dist/phase/rules/react.d.ts.map +1 -0
- package/dist/phase/rules/react.js +202 -0
- package/dist/phase/rules/react.js.map +1 -0
- package/dist/phase/rules/registry.d.ts +170 -0
- package/dist/phase/rules/registry.d.ts.map +1 -0
- package/dist/phase/rules/registry.js +103 -0
- package/dist/phase/rules/registry.js.map +1 -0
- package/dist/phase/rules/schema.d.ts +79 -0
- package/dist/phase/rules/schema.d.ts.map +1 -0
- package/dist/phase/rules/schema.js +273 -0
- package/dist/phase/rules/schema.js.map +1 -0
- package/dist/phase/rules/schemaJson.d.ts +32 -0
- package/dist/phase/rules/schemaJson.d.ts.map +1 -0
- package/dist/phase/rules/schemaJson.js +81 -0
- package/dist/phase/rules/schemaJson.js.map +1 -0
- package/dist/phase/rules/schemaValidator.d.ts +38 -0
- package/dist/phase/rules/schemaValidator.d.ts.map +1 -0
- package/dist/phase/rules/schemaValidator.js +107 -0
- package/dist/phase/rules/schemaValidator.js.map +1 -0
- package/dist/phase/rules/secrets.d.ts +34 -0
- package/dist/phase/rules/secrets.d.ts.map +1 -0
- package/dist/phase/rules/secrets.js +208 -0
- package/dist/phase/rules/secrets.js.map +1 -0
- package/dist/phase/rules/security.d.ts +30 -0
- package/dist/phase/rules/security.d.ts.map +1 -0
- package/dist/phase/rules/security.js +85 -0
- package/dist/phase/rules/security.js.map +1 -0
- package/dist/phase/rules/securityDefects.d.ts +37 -0
- package/dist/phase/rules/securityDefects.d.ts.map +1 -0
- package/dist/phase/rules/securityDefects.js +167 -0
- package/dist/phase/rules/securityDefects.js.map +1 -0
- package/dist/phase/rules/solid.d.ts +61 -0
- package/dist/phase/rules/solid.d.ts.map +1 -0
- package/dist/phase/rules/solid.js +562 -0
- package/dist/phase/rules/solid.js.map +1 -0
- package/dist/phase/rules/styles.d.ts +77 -0
- package/dist/phase/rules/styles.d.ts.map +1 -0
- package/dist/phase/rules/styles.js +868 -0
- package/dist/phase/rules/styles.js.map +1 -0
- package/dist/phase/rules/unreferencedModule.d.ts +26 -0
- package/dist/phase/rules/unreferencedModule.d.ts.map +1 -0
- package/dist/phase/rules/unreferencedModule.js +61 -0
- package/dist/phase/rules/unreferencedModule.js.map +1 -0
- package/dist/phase/runner.d.ts +549 -0
- package/dist/phase/runner.d.ts.map +1 -0
- package/dist/phase/runner.js +1098 -0
- package/dist/phase/runner.js.map +1 -0
- package/dist/phase/schemaCode.d.ts +34 -0
- package/dist/phase/schemaCode.d.ts.map +1 -0
- package/dist/phase/schemaCode.js +53 -0
- package/dist/phase/schemaCode.js.map +1 -0
- package/dist/phase/schemaObjects.d.ts +37 -0
- package/dist/phase/schemaObjects.d.ts.map +1 -0
- package/dist/phase/schemaObjects.js +139 -0
- package/dist/phase/schemaObjects.js.map +1 -0
- package/dist/phase/schemaUsage.d.ts +30 -0
- package/dist/phase/schemaUsage.d.ts.map +1 -0
- package/dist/phase/schemaUsage.js +99 -0
- package/dist/phase/schemaUsage.js.map +1 -0
- package/dist/phase/schemaUsageCandidates.d.ts +32 -0
- package/dist/phase/schemaUsageCandidates.d.ts.map +1 -0
- package/dist/phase/schemaUsageCandidates.js +165 -0
- package/dist/phase/schemaUsageCandidates.js.map +1 -0
- package/dist/phase/schemaValidations.d.ts +34 -0
- package/dist/phase/schemaValidations.d.ts.map +1 -0
- package/dist/phase/schemaValidations.js +53 -0
- package/dist/phase/schemaValidations.js.map +1 -0
- package/dist/phase/secretCandidates.d.ts +28 -0
- package/dist/phase/secretCandidates.d.ts.map +1 -0
- package/dist/phase/secretCandidates.js +132 -0
- package/dist/phase/secretCandidates.js.map +1 -0
- package/dist/phase/securityCandidates.d.ts +29 -0
- package/dist/phase/securityCandidates.d.ts.map +1 -0
- package/dist/phase/securityCandidates.js +469 -0
- package/dist/phase/securityCandidates.js.map +1 -0
- package/dist/phase/seeded-defects.d.ts +18 -0
- package/dist/phase/seeded-defects.d.ts.map +1 -0
- package/dist/phase/seeded-defects.js +61 -0
- package/dist/phase/seeded-defects.js.map +1 -0
- package/dist/phase/stringLiterals.d.ts +24 -0
- package/dist/phase/stringLiterals.d.ts.map +1 -0
- package/dist/phase/stringLiterals.js +35 -0
- package/dist/phase/stringLiterals.js.map +1 -0
- package/dist/phase/stylesCss.d.ts +25 -0
- package/dist/phase/stylesCss.d.ts.map +1 -0
- package/dist/phase/stylesCss.js +34 -0
- package/dist/phase/stylesCss.js.map +1 -0
- package/dist/phase/stylesMarkup.d.ts +31 -0
- package/dist/phase/stylesMarkup.d.ts.map +1 -0
- package/dist/phase/stylesMarkup.js +48 -0
- package/dist/phase/stylesMarkup.js.map +1 -0
- package/dist/phase/stylesSource.d.ts +36 -0
- package/dist/phase/stylesSource.d.ts.map +1 -0
- package/dist/phase/stylesSource.js +50 -0
- package/dist/phase/stylesSource.js.map +1 -0
- package/dist/phase/typeDeclarations.d.ts +33 -0
- package/dist/phase/typeDeclarations.d.ts.map +1 -0
- package/dist/phase/typeDeclarations.js +76 -0
- package/dist/phase/typeDeclarations.js.map +1 -0
- package/dist/phase/types.d.ts +2191 -0
- package/dist/phase/types.d.ts.map +1 -0
- package/dist/phase/types.js +27 -0
- package/dist/phase/types.js.map +1 -0
- package/dist/phase/withinFileProvenance.d.ts +50 -0
- package/dist/phase/withinFileProvenance.d.ts.map +1 -0
- package/dist/phase/withinFileProvenance.js +112 -0
- package/dist/phase/withinFileProvenance.js.map +1 -0
- package/dist/pipeline.d.ts +37 -30
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +131 -326
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +104 -21
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +1145 -988
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/pipelineTypes.d.ts +21 -0
- package/dist/pipelineTypes.d.ts.map +1 -1
- package/dist/pipelineTypes.js +21 -10
- package/dist/pipelineTypes.js.map +1 -1
- package/dist/presets/presets.d.ts +9 -2
- package/dist/presets/presets.d.ts.map +1 -1
- package/dist/presets/presets.js +15 -26
- package/dist/presets/presets.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts +14 -3
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +14 -3
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts +5 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +5 -1
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts +9 -2
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +19 -5
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/reportGenerator.d.ts +9 -2
- package/dist/reporting/reportGenerator.d.ts.map +1 -1
- package/dist/reporting/reportGenerator.js +9 -2
- package/dist/reporting/reportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts +14 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +14 -0
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/reporting/sizeDistribution.d.ts +19 -2
- package/dist/reporting/sizeDistribution.d.ts.map +1 -1
- package/dist/reporting/sizeDistribution.js +23 -5
- package/dist/reporting/sizeDistribution.js.map +1 -1
- package/dist/ruleAliases.d.ts +6 -8
- package/dist/ruleAliases.d.ts.map +1 -1
- package/dist/ruleAliases.js +29 -4
- package/dist/ruleAliases.js.map +1 -1
- package/dist/search/QueryParser.d.ts +1 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +32 -91
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts +19 -20
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +214 -194
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/services/ProjectTaskRepository.d.ts +65 -3
- package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
- package/dist/services/ProjectTaskRepository.js +94 -25
- package/dist/services/ProjectTaskRepository.js.map +1 -1
- package/dist/services/auditJobService.d.ts +17 -1
- package/dist/services/auditJobService.d.ts.map +1 -1
- package/dist/services/auditJobService.js +16 -0
- package/dist/services/auditJobService.js.map +1 -1
- package/dist/services/fileAccounting.d.ts +43 -5
- package/dist/services/fileAccounting.d.ts.map +1 -1
- package/dist/services/fileAccounting.js +48 -9
- package/dist/services/fileAccounting.js.map +1 -1
- package/dist/services/projectTaskHelpers.d.ts +45 -0
- package/dist/services/projectTaskHelpers.d.ts.map +1 -1
- package/dist/services/projectTaskHelpers.js +45 -0
- package/dist/services/projectTaskHelpers.js.map +1 -1
- package/dist/services/whitelistService.d.ts +40 -5
- package/dist/services/whitelistService.d.ts.map +1 -1
- package/dist/services/whitelistService.js +87 -46
- package/dist/services/whitelistService.js.map +1 -1
- package/dist/sqlite/betterSqlite3.d.ts +35 -1
- package/dist/sqlite/betterSqlite3.d.ts.map +1 -1
- package/dist/sqlite/betterSqlite3.js +35 -1
- package/dist/sqlite/betterSqlite3.js.map +1 -1
- package/dist/sqlite/driver.d.ts +23 -0
- package/dist/sqlite/driver.d.ts.map +1 -1
- package/dist/sqlite/driver.js +23 -0
- package/dist/sqlite/driver.js.map +1 -1
- package/dist/sqlite/nodeSqlite.d.ts +35 -1
- package/dist/sqlite/nodeSqlite.d.ts.map +1 -1
- package/dist/sqlite/nodeSqlite.js +35 -1
- package/dist/sqlite/nodeSqlite.js.map +1 -1
- package/dist/structuralSignature.d.ts +9 -0
- package/dist/structuralSignature.d.ts.map +1 -1
- package/dist/structuralSignature.js +9 -0
- package/dist/structuralSignature.js.map +1 -1
- package/dist/styles/cssAstExtractor.d.ts +15 -0
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +15 -0
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/normalizer.d.ts +6 -0
- package/dist/styles/normalizer.d.ts.map +1 -1
- package/dist/styles/normalizer.js +6 -0
- package/dist/styles/normalizer.js.map +1 -1
- package/dist/styles/styleExtractor.d.ts +29 -2
- package/dist/styles/styleExtractor.d.ts.map +1 -1
- package/dist/styles/styleExtractor.js +67 -48
- package/dist/styles/styleExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.d.ts +8 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +135 -138
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +12 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +12 -0
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindExpander.d.ts +3 -0
- package/dist/styles/tailwindExpander.d.ts.map +1 -1
- package/dist/styles/tailwindExpander.js +8 -4
- package/dist/styles/tailwindExpander.js.map +1 -1
- package/dist/styles/tailwindProbe.d.ts +9 -0
- package/dist/styles/tailwindProbe.d.ts.map +1 -1
- package/dist/styles/tailwindProbe.js +181 -166
- package/dist/styles/tailwindProbe.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts +13 -0
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -0
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/symbols.d.ts +13 -11
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +14 -21
- package/dist/symbols.js.map +1 -1
- package/dist/telemetry.d.ts +31 -3
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +31 -3
- package/dist/telemetry.js.map +1 -1
- package/dist/tool-registry.d.ts +37 -1
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js +37 -1
- package/dist/tool-registry.js.map +1 -1
- package/dist/types.d.ts +106 -92
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -16
- package/dist/types.js.map +1 -1
- package/dist/ui/InteractivePrompts.d.ts +12 -0
- package/dist/ui/InteractivePrompts.d.ts.map +1 -1
- package/dist/ui/InteractivePrompts.js +12 -0
- package/dist/ui/InteractivePrompts.js.map +1 -1
- package/dist/utils/astParser.d.ts +15 -0
- package/dist/utils/astParser.d.ts.map +1 -1
- package/dist/utils/astParser.js +15 -0
- package/dist/utils/astParser.js.map +1 -1
- package/dist/utils/astUtils.d.ts +60 -0
- package/dist/utils/astUtils.d.ts.map +1 -1
- package/dist/utils/astUtils.js +350 -362
- package/dist/utils/astUtils.js.map +1 -1
- package/dist/utils/contentHash.d.ts +22 -0
- package/dist/utils/contentHash.d.ts.map +1 -0
- package/dist/utils/contentHash.js +26 -0
- package/dist/utils/contentHash.js.map +1 -0
- package/dist/utils/dependencyExtractor.d.ts +28 -0
- package/dist/utils/dependencyExtractor.d.ts.map +1 -1
- package/dist/utils/dependencyExtractor.js +28 -1
- package/dist/utils/dependencyExtractor.js.map +1 -1
- package/dist/utils/errorMessage.d.ts +10 -0
- package/dist/utils/errorMessage.d.ts.map +1 -0
- package/dist/utils/errorMessage.js +12 -0
- package/dist/utils/errorMessage.js.map +1 -0
- package/dist/utils/fileDiscovery.d.ts +61 -2
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js +131 -23
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/dist/utils/reactDetection.d.ts +34 -5
- package/dist/utils/reactDetection.d.ts.map +1 -1
- package/dist/utils/reactDetection.js +73 -52
- package/dist/utils/reactDetection.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workers/auditWorker.js +7 -3
- package/dist/workers/auditWorker.js.map +1 -1
- package/examples/.auditrc.example.json +0 -8
- package/examples/.auditrc.json +0 -11
- package/examples/.code-auditor.json +0 -2
- package/examples/nextjs.auditrc.json +0 -8
- package/examples/node-api.auditrc.json +0 -7
- package/examples/react.auditrc.json +0 -6
- package/examples/tolerant-solid.auditrc.json +0 -5
- package/package.json +14 -8
- package/plugin/.claude-plugin/manifest.json +16 -0
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +6 -2
- package/plugin/scripts/hook-audit.sh +41 -14
- package/plugin/scripts/hook-common.sh +50 -5
- package/plugin/scripts/hook-drift-check.sh +72 -0
- package/plugin/scripts/hook-self-audit.sh +17 -12
- package/plugin/skills/code-auditor/SKILL.md +1 -1
- package/server.json +2 -2
package/dist/codeIndexDB.js
CHANGED
|
@@ -1,247 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Code Index Database using SQLite (better-sqlite3) + FTS5
|
|
3
3
|
* Replaces LokiJS + FlexSearch with durable, transactional storage.
|
|
4
|
+
*
|
|
5
|
+
* `CodeIndexDB` is the connection owner and transaction boundary, plus the
|
|
6
|
+
* cross-concern orchestrators (sync, diff detection, churn/hotspot/convention
|
|
7
|
+
* mining). Each storage concern — functions, search, coverage, dependency
|
|
8
|
+
* graph, schema usage, whitelist, audit results, analyzer configs, code maps,
|
|
9
|
+
* project tasks, meta, provenance, inferred receivers, conventions, raw SQL —
|
|
10
|
+
* lives in its own module under `codeIndex/` and is exposed as a public field.
|
|
4
11
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { openSqlite } from './sqlite/driver.js';
|
|
12
|
+
import { openSqlite, DB_BUSY_TIMEOUT_MS } from './sqlite/driver.js';
|
|
7
13
|
import { promises as fs } from 'fs';
|
|
8
14
|
import path from 'path';
|
|
9
15
|
import { discoverFiles, ALL_EXTENSIONS } from './utils/fileDiscovery.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
16
|
+
import { computeContentHash } from './utils/contentHash.js';
|
|
17
|
+
import { errorMessage } from './utils/errorMessage.js';
|
|
18
|
+
import { getPersistedStorageRoot, resolvePersistedIndexPath } from './dataPaths.js';
|
|
19
|
+
import { ContextualError, getErrnoCode } from './mcpToolErrors.js';
|
|
12
20
|
import { extractChurn } from './churn/churnExtractor.js';
|
|
13
21
|
import { computeHotspots } from './hotspots/hotspotScorer.js';
|
|
14
|
-
import { populateCallGraphCache
|
|
22
|
+
import { populateCallGraphCache } from './graph/callGraph.js';
|
|
15
23
|
import { populateImportGraphCache } from './graph/importGraph.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
import { CoverageIndex } from './codeIndex/coverage.js';
|
|
25
|
+
import { DependencyGraphIndex } from './codeIndex/dependencyGraph.js';
|
|
26
|
+
import { FunctionIndex } from './codeIndex/functionIndex.js';
|
|
27
|
+
import { SearchIndex } from './codeIndex/search.js';
|
|
28
|
+
import { SchemaMigrations, migrateFromLokiJS } from './codeIndex/migrations.js';
|
|
29
|
+
import { MetaStore } from './codeIndex/meta.js';
|
|
30
|
+
import { ProvenanceStore } from './codeIndex/provenance.js';
|
|
31
|
+
import { InferredReceiversStore } from './codeIndex/inferredReceivers.js';
|
|
32
|
+
import { CodeMapIndex } from './codeIndex/codeMap.js';
|
|
33
|
+
import { AnalyzerConfigIndex } from './codeIndex/analyzerConfig.js';
|
|
34
|
+
import { AuditResultsIndex } from './codeIndex/auditResults.js';
|
|
35
|
+
import { WhitelistIndex } from './codeIndex/whitelist.js';
|
|
36
|
+
import { SchemaIndex } from './codeIndex/schema.js';
|
|
37
|
+
import { ProjectTasksIndex } from './codeIndex/projectTasks.js';
|
|
38
|
+
import { ConventionsIndex } from './codeIndex/conventions.js';
|
|
39
|
+
import { RawSqlIndex } from './codeIndex/rawSql.js';
|
|
40
|
+
// Re-exported for the security conformance test (`codeIndexDB-security.spec.ts`);
|
|
41
|
+
// the implementations live in `codeIndex/shared.ts` so every extracted module and
|
|
42
|
+
// the facade share one copy instead of drifting.
|
|
43
|
+
export { assertSqlIdentifier, escapeRegExpLiteral } from './codeIndex/shared.js';
|
|
44
|
+
export { DB_BUSY_TIMEOUT_MS };
|
|
33
45
|
// ── Path containment ─────────────────────────────────────────────────────
|
|
34
46
|
/** True when `filePath` is `root` itself or a descendant of `root` (not outside it). */
|
|
35
47
|
function isPathUnderRoot(root, filePath) {
|
|
36
48
|
const rel = path.relative(root, filePath);
|
|
37
49
|
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
38
50
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!SQL_IDENTIFIER_RE.test(name)) {
|
|
45
|
-
throw new Error(`invalid SQL identifier in ${context}: ${JSON.stringify(name)}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/** Escape regex metacharacters so a string matches itself literally. */
|
|
49
|
-
export function escapeRegExpLiteral(s) {
|
|
50
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
51
|
-
}
|
|
52
|
-
class SqliteCollectionAdapter {
|
|
53
|
-
db;
|
|
54
|
-
tableName;
|
|
55
|
-
constructor(db, tableName) {
|
|
56
|
-
this.db = db;
|
|
57
|
-
this.tableName = tableName;
|
|
58
|
-
assertSqlIdentifier(this.tableName, 'table name');
|
|
59
|
-
}
|
|
60
|
-
/** Return all rows, or rows matching the query. */
|
|
61
|
-
find(query) {
|
|
62
|
-
if (!query) {
|
|
63
|
-
return this.db.prepare(`SELECT *, rowid as "$loki" FROM "${this.tableName}"`).all()
|
|
64
|
-
.map(r => this.unbindRow(r));
|
|
65
|
-
}
|
|
66
|
-
const clauses = [];
|
|
67
|
-
const params = {};
|
|
68
|
-
for (const [key, value] of Object.entries(query)) {
|
|
69
|
-
if (key !== '$loki')
|
|
70
|
-
assertSqlIdentifier(key, 'column name');
|
|
71
|
-
if (value === null || value === undefined) {
|
|
72
|
-
clauses.push(`"${key}" IS NULL`);
|
|
73
|
-
}
|
|
74
|
-
else if (key === '$loki' && typeof value === 'object' && value.$in) {
|
|
75
|
-
// Handle $loki: { $in: [...] }
|
|
76
|
-
const placeholders = value.$in.map((_, i) => `@in_${i}`);
|
|
77
|
-
clauses.push(`rowid IN (${placeholders.join(', ')})`);
|
|
78
|
-
value.$in.forEach((v, i) => { params[`in_${i}`] = this.bindable(v); });
|
|
79
|
-
}
|
|
80
|
-
else if (key === 'expiresAt' && typeof value === 'object' && value.$lt) {
|
|
81
|
-
clauses.push(`"expiresAt" < @expiresAt`);
|
|
82
|
-
params['expiresAt'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
|
|
83
|
-
}
|
|
84
|
-
else if (key === 'timestamp' && typeof value === 'object' && value.$lt) {
|
|
85
|
-
clauses.push(`"timestamp" < @timestamp`);
|
|
86
|
-
params['timestamp'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
clauses.push(`"${key}" = @${key}`);
|
|
90
|
-
params[key] = this.bindable(value);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const sql = `SELECT *, rowid as "$loki" FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''}`;
|
|
94
|
-
return this.db.prepare(sql).all(params).map(r => this.unbindRow(r));
|
|
95
|
-
}
|
|
96
|
-
findOne(query) {
|
|
97
|
-
const clauses = [];
|
|
98
|
-
const params = {};
|
|
99
|
-
for (const [key, value] of Object.entries(query)) {
|
|
100
|
-
if (key !== '$loki')
|
|
101
|
-
assertSqlIdentifier(key, 'column name');
|
|
102
|
-
if (value === null || value === undefined) {
|
|
103
|
-
clauses.push(`"${key}" IS NULL`);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
clauses.push(`"${key}" = @${key}`);
|
|
107
|
-
params[key] = this.bindable(value);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const sql = `SELECT *, rowid as "$loki" FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''} LIMIT 1`;
|
|
111
|
-
const row = this.db.prepare(sql).get(params) ?? null;
|
|
112
|
-
return row ? this.unbindRow(row) : null;
|
|
113
|
-
}
|
|
114
|
-
/** Convert a doc value to something SQLite can bind (primitives + Buffer, null). */
|
|
115
|
-
bindable(v) {
|
|
116
|
-
if (v === null || v === undefined)
|
|
117
|
-
return null;
|
|
118
|
-
if (typeof v === 'object' && !Buffer.isBuffer(v))
|
|
119
|
-
return JSON.stringify(v);
|
|
120
|
-
return v;
|
|
121
|
-
}
|
|
122
|
-
/** Try to parse a value that looks like serialized JSON back to its native form. */
|
|
123
|
-
unbindable(v) {
|
|
124
|
-
if (typeof v === 'string' && (v.startsWith('[') || v.startsWith('{'))) {
|
|
125
|
-
try {
|
|
126
|
-
return JSON.parse(v);
|
|
127
|
-
}
|
|
128
|
-
catch { /* not JSON, leave as string */ }
|
|
129
|
-
}
|
|
130
|
-
return v;
|
|
131
|
-
}
|
|
132
|
-
unbindRow(row) {
|
|
133
|
-
if (!row)
|
|
134
|
-
return row;
|
|
135
|
-
const out = {};
|
|
136
|
-
for (const [k, v] of Object.entries(row)) {
|
|
137
|
-
out[k] = this.unbindable(v);
|
|
138
|
-
}
|
|
139
|
-
return out;
|
|
140
|
-
}
|
|
141
|
-
insert(doc) {
|
|
142
|
-
const keys = Object.keys(doc);
|
|
143
|
-
for (const k of keys)
|
|
144
|
-
assertSqlIdentifier(k, 'column name');
|
|
145
|
-
const vals = keys.map(k => `@${k}`);
|
|
146
|
-
const sql = `INSERT INTO "${this.tableName}" ("${keys.join('", "')}") VALUES (${vals.join(', ')})`;
|
|
147
|
-
const params = {};
|
|
148
|
-
for (const k of keys)
|
|
149
|
-
params[k] = this.bindable(doc[k]);
|
|
150
|
-
const info = this.db.prepare(sql).run(params);
|
|
151
|
-
return { ...doc, $loki: Number(info.lastInsertRowid) };
|
|
152
|
-
}
|
|
153
|
-
update(doc) {
|
|
154
|
-
const keys = Object.keys(doc).filter(k => k !== '$loki' && k !== 'meta');
|
|
155
|
-
for (const k of keys)
|
|
156
|
-
assertSqlIdentifier(k, 'column name');
|
|
157
|
-
const sets = keys.map(k => `"${k}" = @${k}`);
|
|
158
|
-
const params = {};
|
|
159
|
-
for (const k of keys)
|
|
160
|
-
params[k] = this.bindable(doc[k]);
|
|
161
|
-
params['_rowid'] = doc.$loki;
|
|
162
|
-
this.db.prepare(`UPDATE "${this.tableName}" SET ${sets.join(', ')} WHERE rowid = @_rowid`).run(params);
|
|
163
|
-
}
|
|
164
|
-
remove(doc) {
|
|
165
|
-
if (doc.$loki !== undefined) {
|
|
166
|
-
this.db.prepare(`DELETE FROM "${this.tableName}" WHERE rowid = @_rowid`).run({ _rowid: doc.$loki });
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/** Remove all rows matching a query. */
|
|
170
|
-
findAndRemove(query) {
|
|
171
|
-
const clauses = [];
|
|
172
|
-
const params = {};
|
|
173
|
-
for (const [key, value] of Object.entries(query)) {
|
|
174
|
-
if (key !== '$loki')
|
|
175
|
-
assertSqlIdentifier(key, 'column name');
|
|
176
|
-
if (value === null || value === undefined) {
|
|
177
|
-
clauses.push(`"${key}" IS NULL`);
|
|
178
|
-
}
|
|
179
|
-
else if (key === 'timestamp' && typeof value === 'object' && value.$lt) {
|
|
180
|
-
clauses.push(`"timestamp" < @timestamp`);
|
|
181
|
-
params['timestamp'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
clauses.push(`"${key}" = @${key}`);
|
|
185
|
-
params[key] = value;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
this.db.prepare(`DELETE FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''}`).run(params);
|
|
189
|
-
}
|
|
190
|
-
clear() {
|
|
191
|
-
this.db.prepare(`DELETE FROM "${this.tableName}"`).run();
|
|
192
|
-
}
|
|
193
|
-
chain() {
|
|
194
|
-
return {
|
|
195
|
-
_table: this.tableName,
|
|
196
|
-
_db: this.db,
|
|
197
|
-
_query: null,
|
|
198
|
-
_result: null,
|
|
199
|
-
find(query) {
|
|
200
|
-
this._query = query ?? null;
|
|
201
|
-
return this;
|
|
202
|
-
},
|
|
203
|
-
where(fn) {
|
|
204
|
-
if (this._result === null) {
|
|
205
|
-
this._result = this._query !== null
|
|
206
|
-
? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
|
|
207
|
-
: new SqliteCollectionAdapter(this._db, this._table).find();
|
|
208
|
-
}
|
|
209
|
-
this._result = this._result.filter(fn);
|
|
210
|
-
return this;
|
|
211
|
-
},
|
|
212
|
-
simplesort(field, opts = {}) {
|
|
213
|
-
if (this._result === null) {
|
|
214
|
-
this._result = this._query !== null
|
|
215
|
-
? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
|
|
216
|
-
: new SqliteCollectionAdapter(this._db, this._table).find();
|
|
217
|
-
}
|
|
218
|
-
this._result.sort((a, b) => {
|
|
219
|
-
const av = a[field] ?? '';
|
|
220
|
-
const bv = b[field] ?? '';
|
|
221
|
-
if (av < bv)
|
|
222
|
-
return opts.desc ? 1 : -1;
|
|
223
|
-
if (av > bv)
|
|
224
|
-
return opts.desc ? -1 : 1;
|
|
225
|
-
return 0;
|
|
226
|
-
});
|
|
227
|
-
return this;
|
|
228
|
-
},
|
|
229
|
-
limit(n) {
|
|
230
|
-
if (this._result === null) {
|
|
231
|
-
this._result = this._query !== null
|
|
232
|
-
? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
|
|
233
|
-
: new SqliteCollectionAdapter(this._db, this._table).find();
|
|
234
|
-
}
|
|
235
|
-
this._result = this._result.slice(0, n);
|
|
236
|
-
return this;
|
|
237
|
-
},
|
|
238
|
-
data() {
|
|
239
|
-
return this._result ?? [];
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
}
|
|
51
|
+
// ── Hoisted SQL / identifier constants ─────────────────────────────────
|
|
52
|
+
const PRAGMA_JOURNAL_WAL = 'journal_mode = WAL';
|
|
53
|
+
const PRAGMA_FOREIGN_KEYS_ON = 'foreign_keys = ON';
|
|
54
|
+
const SQL_DISTINCT_FILE_PATHS = 'SELECT DISTINCT file_path FROM functions';
|
|
55
|
+
const SQL_DELETE_FUNCTIONS_IN_FILEPATHS = 'DELETE FROM functions WHERE file_path IN (SELECT value FROM json_each(?))';
|
|
244
56
|
// ── Main class ──────────────────────────────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* SQLite-backed code index for a single project. Connection owner and
|
|
59
|
+
* transaction boundary; storage concerns live on the public module fields
|
|
60
|
+
* (`functionIndex`, `search`, `coverage`, `graph`, `whitelist`, `schema`,
|
|
61
|
+
* `projectTasks`, `meta`, …) and cross-concern orchestration stays here.
|
|
62
|
+
*/
|
|
245
63
|
export class CodeIndexDB {
|
|
246
64
|
static instance;
|
|
247
65
|
/** Subclasses (e.g. EnhancedCodeIndexDB) need access for extra tables without `as any`. */
|
|
@@ -257,31 +75,51 @@ export class CodeIndexDB {
|
|
|
257
75
|
* *cause* (e.g. missing better-sqlite3 binding) instead of the generic
|
|
258
76
|
* "Database not initialized". Cleared on success and on close(). */
|
|
259
77
|
initFailure = null;
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
78
|
+
// ── Concern modules (public fields — assigned in initializeInternal) ──
|
|
79
|
+
migrations;
|
|
80
|
+
functionIndex;
|
|
81
|
+
search;
|
|
82
|
+
coverage;
|
|
83
|
+
graph;
|
|
84
|
+
meta;
|
|
85
|
+
provenance;
|
|
86
|
+
inferredReceivers;
|
|
87
|
+
codeMap;
|
|
88
|
+
analyzerConfig;
|
|
89
|
+
auditResults;
|
|
90
|
+
whitelist;
|
|
91
|
+
schema;
|
|
92
|
+
projectTasks;
|
|
93
|
+
conventions;
|
|
94
|
+
rawSql;
|
|
95
|
+
/**
|
|
96
|
+
* Read-only facade analyzers consume — the raw-SQL, meta, and coverage
|
|
97
|
+
* concerns composed into the `IndexHandle` contract. Built once at
|
|
98
|
+
* initialization; a plain object field (not delegator methods), so it adds
|
|
99
|
+
* nothing to `solid/class-size`.
|
|
100
|
+
*/
|
|
101
|
+
indexHandle;
|
|
277
102
|
// ── Schema version ──────────────────────────────────────────────────
|
|
278
|
-
static SCHEMA_VERSION =
|
|
103
|
+
static SCHEMA_VERSION = 19;
|
|
104
|
+
/**
|
|
105
|
+
* Create an index handle for the given SQLite file (in-memory by default).
|
|
106
|
+
*
|
|
107
|
+
* @param dbPath - Path to the SQLite database file, or ':memory:'.
|
|
108
|
+
*/
|
|
279
109
|
constructor(dbPath = ':memory:') {
|
|
280
110
|
this.dbPath = dbPath === ':memory:' ? dbPath : path.resolve(dbPath);
|
|
281
111
|
}
|
|
282
112
|
// ── Singleton ───────────────────────────────────────────────────────
|
|
283
113
|
/** The project root this singleton was opened for (used for mismatch detection). */
|
|
284
114
|
static currentProjectRoot;
|
|
115
|
+
/**
|
|
116
|
+
* Return the process-wide singleton, reopening the store when the requested
|
|
117
|
+
* path differs from the one currently open.
|
|
118
|
+
*
|
|
119
|
+
* @param dbPath - Optional explicit database file path.
|
|
120
|
+
* @param projectRoot - The project root the store is scoped to.
|
|
121
|
+
* @returns The singleton index instance.
|
|
122
|
+
*/
|
|
285
123
|
static getInstance(dbPath, projectRoot) {
|
|
286
124
|
let resolved;
|
|
287
125
|
if (dbPath !== undefined && dbPath !== '') {
|
|
@@ -330,6 +168,12 @@ export class CodeIndexDB {
|
|
|
330
168
|
return CodeIndexDB.currentProjectRoot;
|
|
331
169
|
}
|
|
332
170
|
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Initialize the database schema and adapters once, memoized via a shared
|
|
173
|
+
* promise so concurrent callers await the same initialization.
|
|
174
|
+
*
|
|
175
|
+
* @returns A promise that resolves once initialization completes.
|
|
176
|
+
*/
|
|
333
177
|
async initialize() {
|
|
334
178
|
if (this.isInitialized)
|
|
335
179
|
return;
|
|
@@ -381,17 +225,17 @@ export class CodeIndexDB {
|
|
|
381
225
|
throw e;
|
|
382
226
|
const code = getErrnoCode(e);
|
|
383
227
|
throw new ContextualError(`Failed to prepare code index storage: ${e instanceof Error ? e.message : String(e)}`, { ...(code && { errnoCode: code }), storageRoot, dbPath: this.dbPath,
|
|
384
|
-
hint: 'Ensure the storage directory is writable (default:
|
|
228
|
+
hint: 'Ensure the storage directory is writable (default: a user-level OS cache dir when CODE_AUDITOR_DATA_DIR is unset).' }, e instanceof Error ? e : undefined);
|
|
385
229
|
}
|
|
386
230
|
}
|
|
387
231
|
// Check for LokiJS migration
|
|
388
|
-
const migrationResult = this.
|
|
232
|
+
const migrationResult = migrateFromLokiJS(this.dbPath);
|
|
389
233
|
// Open SQLite database (with auto-recovery for corrupted files)
|
|
390
234
|
let retried = false;
|
|
391
235
|
try {
|
|
392
236
|
this.db = openSqlite(this.dbPath, { timeoutMs: DB_BUSY_TIMEOUT_MS });
|
|
393
|
-
this.db.pragma(
|
|
394
|
-
this.db.pragma(
|
|
237
|
+
this.db.pragma(PRAGMA_JOURNAL_WAL);
|
|
238
|
+
this.db.pragma(PRAGMA_FOREIGN_KEYS_ON);
|
|
395
239
|
}
|
|
396
240
|
catch (e) {
|
|
397
241
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -410,8 +254,8 @@ export class CodeIndexDB {
|
|
|
410
254
|
}
|
|
411
255
|
catch { /* ignore */ }
|
|
412
256
|
this.db = openSqlite(this.dbPath, { timeoutMs: DB_BUSY_TIMEOUT_MS });
|
|
413
|
-
this.db.pragma(
|
|
414
|
-
this.db.pragma(
|
|
257
|
+
this.db.pragma(PRAGMA_JOURNAL_WAL);
|
|
258
|
+
this.db.pragma(PRAGMA_FOREIGN_KEYS_ON);
|
|
415
259
|
}
|
|
416
260
|
else {
|
|
417
261
|
const code = getErrnoCode(e);
|
|
@@ -420,26 +264,41 @@ export class CodeIndexDB {
|
|
|
420
264
|
}
|
|
421
265
|
}
|
|
422
266
|
// Create schema
|
|
423
|
-
this.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
this.
|
|
427
|
-
this.
|
|
428
|
-
this.
|
|
429
|
-
this.
|
|
430
|
-
this.
|
|
431
|
-
this.
|
|
432
|
-
this.
|
|
433
|
-
this.
|
|
434
|
-
this.
|
|
435
|
-
this.
|
|
436
|
-
this.
|
|
437
|
-
this.
|
|
438
|
-
this.
|
|
267
|
+
this.migrations = new SchemaMigrations(this.db);
|
|
268
|
+
this.migrations.createSchema(CodeIndexDB.SCHEMA_VERSION);
|
|
269
|
+
// Construct concern modules against the shared handle.
|
|
270
|
+
this.functionIndex = new FunctionIndex(this.db);
|
|
271
|
+
this.search = new SearchIndex(this.db, this.functionIndex);
|
|
272
|
+
this.coverage = new CoverageIndex(this.db);
|
|
273
|
+
this.graph = new DependencyGraphIndex(this.db);
|
|
274
|
+
this.meta = new MetaStore(this.db);
|
|
275
|
+
this.provenance = new ProvenanceStore(this.db);
|
|
276
|
+
this.inferredReceivers = new InferredReceiversStore(this.db);
|
|
277
|
+
this.codeMap = new CodeMapIndex(this.db);
|
|
278
|
+
this.analyzerConfig = new AnalyzerConfigIndex(this.db);
|
|
279
|
+
this.auditResults = new AuditResultsIndex(this.db);
|
|
280
|
+
this.whitelist = new WhitelistIndex(this.db);
|
|
281
|
+
this.schema = new SchemaIndex(this.db);
|
|
282
|
+
this.projectTasks = new ProjectTasksIndex(this.db);
|
|
283
|
+
this.conventions = new ConventionsIndex(this.db);
|
|
284
|
+
this.rawSql = new RawSqlIndex(this.db);
|
|
285
|
+
// Compose the read-only analyzer facade from the raw-SQL, meta, and
|
|
286
|
+
// coverage modules. This is the same shape the detached-audit runner builds
|
|
287
|
+
// inline; keeping it here gives every consumer one source of truth.
|
|
288
|
+
this.indexHandle = {
|
|
289
|
+
query: (sql, params) => this.rawSql.query(sql, params),
|
|
290
|
+
count: (table) => this.rawSql.count(table),
|
|
291
|
+
tableHasRows: (table) => this.rawSql.tableHasRows(table),
|
|
292
|
+
run: (sql, params) => this.rawSql.run(sql, params),
|
|
293
|
+
exec: (sql) => this.rawSql.exec(sql),
|
|
294
|
+
getMeta: (key) => this.meta.getMeta(key),
|
|
295
|
+
getUntestedTopDecile: (decile) => this.coverage.getUntestedTopDecile(decile),
|
|
296
|
+
rawDb: this.db,
|
|
297
|
+
};
|
|
439
298
|
// Init defaults
|
|
440
299
|
const whitelistCount = this.db.prepare('SELECT COUNT(*) as cnt FROM whitelist').get().cnt;
|
|
441
300
|
if (whitelistCount === 0) {
|
|
442
|
-
await this.initializeDefaultWhitelists();
|
|
301
|
+
await this.whitelist.initializeDefaultWhitelists();
|
|
443
302
|
}
|
|
444
303
|
// Auto-sync if we just migrated
|
|
445
304
|
if (migrationResult.migrated) {
|
|
@@ -456,1028 +315,6 @@ export class CodeIndexDB {
|
|
|
456
315
|
this.isInitialized = true;
|
|
457
316
|
this.initFailure = null;
|
|
458
317
|
}
|
|
459
|
-
// ── Schema migrations ────────────────────────────────────────────────
|
|
460
|
-
runMigrations() {
|
|
461
|
-
// Read the currently stored schema version (if any)
|
|
462
|
-
const row = this.db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
|
|
463
|
-
const currentVersion = row ? parseInt(row.value, 10) : 0;
|
|
464
|
-
// Migration 1 → 2: Unique index on (name, file_path, line_number)
|
|
465
|
-
// Previously the unique index was on (name, file_path) only, which caused
|
|
466
|
-
// same-named functions at different lines in the same file to collide.
|
|
467
|
-
if (currentVersion < 2) {
|
|
468
|
-
this.db.exec(`
|
|
469
|
-
DROP INDEX IF EXISTS idx_functions_name_file;
|
|
470
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_functions_name_file_line ON functions(name, file_path, line_number);
|
|
471
|
-
`);
|
|
472
|
-
}
|
|
473
|
-
// Migration 2 → 3: Style intelligence tables (Spec 10)
|
|
474
|
-
if (currentVersion < 3) {
|
|
475
|
-
this.db.exec(`
|
|
476
|
-
CREATE TABLE IF NOT EXISTS style_declarations (
|
|
477
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
478
|
-
property TEXT NOT NULL,
|
|
479
|
-
raw_value TEXT NOT NULL,
|
|
480
|
-
normalized_value TEXT,
|
|
481
|
-
mechanism TEXT NOT NULL,
|
|
482
|
-
file_path TEXT NOT NULL,
|
|
483
|
-
line INTEGER NOT NULL,
|
|
484
|
-
context TEXT,
|
|
485
|
-
variant_context TEXT,
|
|
486
|
-
token_ref TEXT,
|
|
487
|
-
content_hash TEXT,
|
|
488
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
489
|
-
);
|
|
490
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_property ON style_declarations(property);
|
|
491
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_mechanism ON style_declarations(mechanism);
|
|
492
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_file_path ON style_declarations(file_path);
|
|
493
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_token_ref ON style_declarations(token_ref);
|
|
494
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_content_hash ON style_declarations(content_hash);
|
|
495
|
-
|
|
496
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS style_declarations_fts USING fts5(
|
|
497
|
-
property, raw_value, normalized_value,
|
|
498
|
-
content='style_declarations', content_rowid='id',
|
|
499
|
-
tokenize='porter unicode61'
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
CREATE TABLE IF NOT EXISTS style_tokens (
|
|
503
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
504
|
-
name TEXT NOT NULL,
|
|
505
|
-
value TEXT NOT NULL,
|
|
506
|
-
file_path TEXT NOT NULL,
|
|
507
|
-
mechanism TEXT NOT NULL,
|
|
508
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
509
|
-
);
|
|
510
|
-
CREATE INDEX IF NOT EXISTS idx_style_tokens_name ON style_tokens(name);
|
|
511
|
-
|
|
512
|
-
CREATE TABLE IF NOT EXISTS style_class_usage (
|
|
513
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
514
|
-
class_name TEXT NOT NULL,
|
|
515
|
-
file_path TEXT NOT NULL,
|
|
516
|
-
line INTEGER NOT NULL,
|
|
517
|
-
mechanism TEXT NOT NULL,
|
|
518
|
-
unresolvable INTEGER DEFAULT 0,
|
|
519
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
520
|
-
);
|
|
521
|
-
CREATE INDEX IF NOT EXISTS idx_style_class_usage_name ON style_class_usage(class_name);
|
|
522
|
-
CREATE INDEX IF NOT EXISTS idx_style_class_usage_file ON style_class_usage(file_path);
|
|
523
|
-
CREATE INDEX IF NOT EXISTS idx_style_class_usage_unres ON style_class_usage(unresolvable);
|
|
524
|
-
`);
|
|
525
|
-
}
|
|
526
|
-
// Migration 3 → 4: Convention mining tables (Spec 12)
|
|
527
|
-
if (currentVersion < 4) {
|
|
528
|
-
this.db.exec(`
|
|
529
|
-
CREATE TABLE IF NOT EXISTS conventions (
|
|
530
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
531
|
-
domain TEXT NOT NULL,
|
|
532
|
-
rule_id TEXT NOT NULL,
|
|
533
|
-
antecedent TEXT,
|
|
534
|
-
consequent TEXT,
|
|
535
|
-
pattern TEXT,
|
|
536
|
-
directory TEXT,
|
|
537
|
-
file_path TEXT,
|
|
538
|
-
line INTEGER,
|
|
539
|
-
support INTEGER DEFAULT 0,
|
|
540
|
-
total_cases INTEGER DEFAULT 0,
|
|
541
|
-
confidence REAL DEFAULT 0,
|
|
542
|
-
exemplar_file TEXT,
|
|
543
|
-
exemplar_line INTEGER,
|
|
544
|
-
hash TEXT,
|
|
545
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
546
|
-
);
|
|
547
|
-
CREATE INDEX IF NOT EXISTS idx_conv_domain ON conventions(domain);
|
|
548
|
-
CREATE INDEX IF NOT EXISTS idx_conv_rule_id ON conventions(rule_id);
|
|
549
|
-
CREATE INDEX IF NOT EXISTS idx_conv_directory ON conventions(directory);
|
|
550
|
-
CREATE INDEX IF NOT EXISTS idx_conv_hash ON conventions(hash);
|
|
551
|
-
`);
|
|
552
|
-
}
|
|
553
|
-
// Migration 4 → 5: Hotspots & temporal analysis (Spec 13)
|
|
554
|
-
if (currentVersion < 5) {
|
|
555
|
-
this.db.exec(`
|
|
556
|
-
CREATE TABLE IF NOT EXISTS file_churn (
|
|
557
|
-
file_path TEXT PRIMARY KEY,
|
|
558
|
-
commit_count INTEGER NOT NULL DEFAULT 0,
|
|
559
|
-
lines_added INTEGER NOT NULL DEFAULT 0,
|
|
560
|
-
lines_deleted INTEGER NOT NULL DEFAULT 0,
|
|
561
|
-
distinct_authors INTEGER NOT NULL DEFAULT 0,
|
|
562
|
-
dominant_author TEXT,
|
|
563
|
-
dominant_author_share REAL DEFAULT 0,
|
|
564
|
-
last_touched TEXT
|
|
565
|
-
);
|
|
566
|
-
CREATE INDEX IF NOT EXISTS idx_file_churn_cc ON file_churn(commit_count);
|
|
567
|
-
|
|
568
|
-
CREATE TABLE IF NOT EXISTS function_churn (
|
|
569
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
570
|
-
function_id INTEGER REFERENCES functions(id) ON DELETE CASCADE,
|
|
571
|
-
function_name TEXT NOT NULL,
|
|
572
|
-
file_path TEXT NOT NULL,
|
|
573
|
-
commit_count INTEGER NOT NULL DEFAULT 0,
|
|
574
|
-
distinct_authors INTEGER NOT NULL DEFAULT 0,
|
|
575
|
-
dominant_author TEXT,
|
|
576
|
-
dominant_author_share REAL DEFAULT 0,
|
|
577
|
-
renamed INTEGER DEFAULT 0,
|
|
578
|
-
confidence REAL DEFAULT 1.0
|
|
579
|
-
);
|
|
580
|
-
CREATE INDEX IF NOT EXISTS idx_func_churn_fid ON function_churn(function_id);
|
|
581
|
-
|
|
582
|
-
CREATE TABLE IF NOT EXISTS hotspot_scores (
|
|
583
|
-
target TEXT PRIMARY KEY,
|
|
584
|
-
type TEXT NOT NULL,
|
|
585
|
-
score REAL NOT NULL DEFAULT 0,
|
|
586
|
-
churn_pct REAL NOT NULL DEFAULT 0,
|
|
587
|
-
complexity_pct REAL NOT NULL DEFAULT 0,
|
|
588
|
-
commit_count INTEGER NOT NULL DEFAULT 0,
|
|
589
|
-
distinct_authors INTEGER NOT NULL DEFAULT 0,
|
|
590
|
-
dominant_author TEXT,
|
|
591
|
-
dominant_author_share REAL DEFAULT 0,
|
|
592
|
-
bus_factor_risk INTEGER DEFAULT 0,
|
|
593
|
-
complexity INTEGER NOT NULL DEFAULT 0,
|
|
594
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
595
|
-
);
|
|
596
|
-
CREATE INDEX IF NOT EXISTS idx_hotspot_scores_score ON hotspot_scores(score DESC);
|
|
597
|
-
|
|
598
|
-
CREATE TABLE IF NOT EXISTS dry_pair_history (
|
|
599
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
600
|
-
pair_fingerprint TEXT NOT NULL,
|
|
601
|
-
file1 TEXT NOT NULL,
|
|
602
|
-
symbol1 TEXT,
|
|
603
|
-
line1 INTEGER NOT NULL,
|
|
604
|
-
content_hash1 TEXT NOT NULL,
|
|
605
|
-
file2 TEXT NOT NULL,
|
|
606
|
-
symbol2 TEXT,
|
|
607
|
-
line2 INTEGER NOT NULL,
|
|
608
|
-
content_hash2 TEXT NOT NULL,
|
|
609
|
-
similarity REAL NOT NULL,
|
|
610
|
-
timestamp TEXT DEFAULT (datetime('now')),
|
|
611
|
-
run_id TEXT NOT NULL
|
|
612
|
-
);
|
|
613
|
-
CREATE INDEX IF NOT EXISTS idx_dph_fingerprint ON dry_pair_history(pair_fingerprint);
|
|
614
|
-
CREATE INDEX IF NOT EXISTS idx_dph_run ON dry_pair_history(run_id);
|
|
615
|
-
`);
|
|
616
|
-
}
|
|
617
|
-
// Migration 5 → 6: Graph cache for call/import graph construction (Spec 14)
|
|
618
|
-
if (currentVersion < 6) {
|
|
619
|
-
this.db.exec(`
|
|
620
|
-
CREATE TABLE IF NOT EXISTS graph_cache (
|
|
621
|
-
graph_type TEXT NOT NULL,
|
|
622
|
-
node_key TEXT NOT NULL,
|
|
623
|
-
neighbor_key TEXT NOT NULL,
|
|
624
|
-
weight REAL NOT NULL,
|
|
625
|
-
PRIMARY KEY (graph_type, node_key, neighbor_key)
|
|
626
|
-
);
|
|
627
|
-
CREATE INDEX IF NOT EXISTS idx_gc_type_node ON graph_cache(graph_type, node_key);
|
|
628
|
-
CREATE INDEX IF NOT EXISTS idx_gc_type_neighbor ON graph_cache(graph_type, neighbor_key);
|
|
629
|
-
`);
|
|
630
|
-
}
|
|
631
|
-
// Migration 6 → 7: Coverage data for cross-domain analysis (Spec 15)
|
|
632
|
-
if (currentVersion < 7) {
|
|
633
|
-
this.db.exec(`
|
|
634
|
-
CREATE TABLE IF NOT EXISTS coverage_data (
|
|
635
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
636
|
-
function_name TEXT NOT NULL,
|
|
637
|
-
file_path TEXT NOT NULL,
|
|
638
|
-
line_number INTEGER,
|
|
639
|
-
basis TEXT NOT NULL DEFAULT 'static-reach',
|
|
640
|
-
covered INTEGER NOT NULL DEFAULT 0,
|
|
641
|
-
source TEXT,
|
|
642
|
-
imported_at TEXT DEFAULT (datetime('now')),
|
|
643
|
-
UNIQUE(function_name, file_path, line_number)
|
|
644
|
-
);
|
|
645
|
-
CREATE INDEX IF NOT EXISTS idx_cov_basis ON coverage_data(basis);
|
|
646
|
-
CREATE INDEX IF NOT EXISTS idx_cov_covered ON coverage_data(covered);
|
|
647
|
-
CREATE INDEX IF NOT EXISTS idx_cov_function ON coverage_data(function_name);
|
|
648
|
-
`);
|
|
649
|
-
}
|
|
650
|
-
// Migration 7 → 8: export_kind on conventions (Spec 22 R5.1)
|
|
651
|
-
if (currentVersion < 8) {
|
|
652
|
-
const cols = this.db
|
|
653
|
-
.prepare(`PRAGMA table_info('conventions')`)
|
|
654
|
-
.all();
|
|
655
|
-
if (!cols.some((c) => c.name === 'export_kind')) {
|
|
656
|
-
this.db.exec(`ALTER TABLE conventions ADD COLUMN export_kind TEXT`);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
// Migration 8 → 9: Detached-run lifecycle + queryable coverage (Spec 41).
|
|
660
|
-
// `findings_ledger_runs` gains the job-lifecycle columns (status, timing,
|
|
661
|
-
// provenance, lease heartbeat, progress, stderr log); the one genuinely new
|
|
662
|
-
// table is `findings_ledger_coverage` (per-rule state + reason rows that
|
|
663
|
-
// `writeAuditToLedger` previously dropped).
|
|
664
|
-
if (currentVersion < 9) {
|
|
665
|
-
const runCols = this.db
|
|
666
|
-
.prepare(`PRAGMA table_info('findings_ledger_runs')`)
|
|
667
|
-
.all();
|
|
668
|
-
const hasRunCol = (name) => runCols.some((c) => c.name === name);
|
|
669
|
-
const newRunCols = [
|
|
670
|
-
['status', "TEXT NOT NULL DEFAULT 'completed'"],
|
|
671
|
-
['project_root', 'TEXT'],
|
|
672
|
-
['started_at', 'TEXT'],
|
|
673
|
-
['heartbeat_at', 'TEXT'],
|
|
674
|
-
['finished_at', 'TEXT'],
|
|
675
|
-
['error', 'TEXT'],
|
|
676
|
-
['progress_json', 'TEXT'],
|
|
677
|
-
['stderr_log', 'TEXT'],
|
|
678
|
-
['content_hash', 'TEXT'],
|
|
679
|
-
['files_count', 'INTEGER'],
|
|
680
|
-
['file_manifest_json', 'TEXT'],
|
|
681
|
-
];
|
|
682
|
-
for (const [name, decl] of newRunCols) {
|
|
683
|
-
if (!hasRunCol(name)) {
|
|
684
|
-
this.db.exec(`ALTER TABLE findings_ledger_runs ADD COLUMN ${name} ${decl}`);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
this.db.exec(`
|
|
688
|
-
CREATE TABLE IF NOT EXISTS findings_ledger_coverage (
|
|
689
|
-
run_id TEXT NOT NULL REFERENCES findings_ledger_runs(run_id) ON DELETE CASCADE,
|
|
690
|
-
analyzer TEXT NOT NULL,
|
|
691
|
-
rule_id TEXT NOT NULL,
|
|
692
|
-
state TEXT NOT NULL,
|
|
693
|
-
count INTEGER NOT NULL DEFAULT 0,
|
|
694
|
-
reason TEXT,
|
|
695
|
-
PRIMARY KEY (run_id, analyzer, rule_id)
|
|
696
|
-
);
|
|
697
|
-
CREATE INDEX IF NOT EXISTS idx_ledger_coverage_run ON findings_ledger_coverage(run_id);
|
|
698
|
-
`);
|
|
699
|
-
}
|
|
700
|
-
// Migration 9 → 10: PID-based lease liveness (Spec 41 Amendment B).
|
|
701
|
-
// The heartbeat is no longer the primary liveness signal for the common
|
|
702
|
-
// case (a healthy child whose synchronous `syncFileIndex` phase starves the
|
|
703
|
-
// event loop, so its heartbeat interval cannot fire). The child records its
|
|
704
|
-
// PID + process start time + hostname at lease claim; `reclaimStaleRunning`
|
|
705
|
-
// then skips any `running` row whose PID is still the same live process, and
|
|
706
|
-
// only reclaims when the process is genuinely gone (or on a foreign host,
|
|
707
|
-
// where the PID means nothing and the heartbeat stays the fallback).
|
|
708
|
-
if (currentVersion < 10) {
|
|
709
|
-
const runCols = this.db
|
|
710
|
-
.prepare(`PRAGMA table_info('findings_ledger_runs')`)
|
|
711
|
-
.all();
|
|
712
|
-
const hasRunCol = (name) => runCols.some((c) => c.name === name);
|
|
713
|
-
const newRunCols = [
|
|
714
|
-
['runner_pid', 'INTEGER'],
|
|
715
|
-
['runner_pid_started_at', 'TEXT'],
|
|
716
|
-
['runner_host', 'TEXT'],
|
|
717
|
-
];
|
|
718
|
-
for (const [name, decl] of newRunCols) {
|
|
719
|
-
if (!hasRunCol(name)) {
|
|
720
|
-
this.db.exec(`ALTER TABLE findings_ledger_runs ADD COLUMN ${name} ${decl}`);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
// Migration 10 → 11: unread stylesheet sources (Spec 45 R5).
|
|
725
|
-
// Records stylesheets whose dialect the style indexer cannot read, so
|
|
726
|
-
// styles/undefined-class findings can carry them as incomplete-definition
|
|
727
|
-
// context instead of asserting a class is undefined against the whole project.
|
|
728
|
-
if (currentVersion < 11) {
|
|
729
|
-
this.db.exec(`
|
|
730
|
-
CREATE TABLE IF NOT EXISTS style_unread_sources (
|
|
731
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
732
|
-
file_path TEXT NOT NULL UNIQUE,
|
|
733
|
-
reason TEXT NOT NULL,
|
|
734
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
735
|
-
);
|
|
736
|
-
`);
|
|
737
|
-
}
|
|
738
|
-
// Migration 11 → 12: defined-class catalog (Spec 45 — styles/undefined-class).
|
|
739
|
-
// A dedicated (class_name, file_path) table so the undefined-class detector
|
|
740
|
-
// can resolve a class name with an indexed `class_name IN (...)` lookup
|
|
741
|
-
// instead of loading every style_declarations row and regex-extracting
|
|
742
|
-
// selectors from `context` in JS. Populated by the style indexer on insert;
|
|
743
|
-
// backfilled here once so an in-place upgrade does not leave the catalog
|
|
744
|
-
// empty (unchanged-file hashing would otherwise skip the re-extract).
|
|
745
|
-
if (currentVersion < 12) {
|
|
746
|
-
this.db.exec(`
|
|
747
|
-
CREATE TABLE IF NOT EXISTS style_defined_classes (
|
|
748
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
749
|
-
class_name TEXT NOT NULL,
|
|
750
|
-
file_path TEXT NOT NULL,
|
|
751
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
752
|
-
UNIQUE(class_name, file_path)
|
|
753
|
-
);
|
|
754
|
-
CREATE INDEX IF NOT EXISTS idx_style_defined_class_name ON style_defined_classes(class_name);
|
|
755
|
-
`);
|
|
756
|
-
const definedRows = this.db
|
|
757
|
-
.prepare('SELECT DISTINCT context, file_path FROM style_declarations WHERE context IS NOT NULL')
|
|
758
|
-
.all();
|
|
759
|
-
const insertDefined = this.db.prepare('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)');
|
|
760
|
-
const backfill = this.db.transaction(() => {
|
|
761
|
-
for (const r of definedRows) {
|
|
762
|
-
for (const m of r.context.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
|
|
763
|
-
insertDefined.run(m[1], r.file_path);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
backfill();
|
|
768
|
-
}
|
|
769
|
-
// Migration 12 → 13: import_specifiers (Spec 60 — import classification at
|
|
770
|
-
// emission). One row per static import specifier occurrence, classified
|
|
771
|
-
// package / unresolved-alias / internal-resolved / internal-broken /
|
|
772
|
-
// unresolved-virtual (Spec 60.1 widened the three-class scheme to five;
|
|
773
|
-
// `classification` is free TEXT, no constraint, so no further migration).
|
|
774
|
-
// Nothing consumes it yet (Spec 61 persists edges; Spec 62 derives coverage).
|
|
775
|
-
// Flat — resolved_path inline, no lookup table, no join.
|
|
776
|
-
if (currentVersion < 13) {
|
|
777
|
-
this.db.exec(`
|
|
778
|
-
CREATE TABLE IF NOT EXISTS import_specifiers (
|
|
779
|
-
file_path TEXT NOT NULL,
|
|
780
|
-
specifier TEXT NOT NULL,
|
|
781
|
-
classification TEXT NOT NULL,
|
|
782
|
-
resolved_path TEXT,
|
|
783
|
-
line INTEGER,
|
|
784
|
-
PRIMARY KEY (file_path, specifier, line)
|
|
785
|
-
);
|
|
786
|
-
CREATE INDEX IF NOT EXISTS idx_import_specifiers_class ON import_specifiers(classification);
|
|
787
|
-
CREATE INDEX IF NOT EXISTS idx_import_specifiers_resolved ON import_specifiers(resolved_path);
|
|
788
|
-
`);
|
|
789
|
-
}
|
|
790
|
-
// Migration 13 → 14: schema_usage identity becomes a coordinate (Spec 61
|
|
791
|
-
// Amendment A). function_name stops holding source text and becomes the
|
|
792
|
-
// nullable display name (null for anonymous handlers); identity is the
|
|
793
|
-
// inline (file_path, function_start_line, function_start_column) coordinate.
|
|
794
|
-
// function_name drops its NOT NULL so anonymous handlers store NULL, distinct
|
|
795
|
-
// from 'top-level' (function_start_line IS NULL). Old rows keep their
|
|
796
|
-
// (now-legacy) names but are regenerated on the next per-file sync.
|
|
797
|
-
if (currentVersion < 14) {
|
|
798
|
-
const suCols = this.db
|
|
799
|
-
.prepare(`PRAGMA table_info('schema_usage')`)
|
|
800
|
-
.all();
|
|
801
|
-
if (!suCols.some((c) => c.name === 'function_start_line')) {
|
|
802
|
-
this.db.exec(`
|
|
803
|
-
ALTER TABLE schema_usage RENAME TO schema_usage_old;
|
|
804
|
-
CREATE TABLE schema_usage (
|
|
805
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
806
|
-
schema_id TEXT,
|
|
807
|
-
table_name TEXT NOT NULL,
|
|
808
|
-
file_path TEXT NOT NULL,
|
|
809
|
-
function_name TEXT,
|
|
810
|
-
function_start_line INTEGER,
|
|
811
|
-
function_start_column INTEGER,
|
|
812
|
-
usage_type TEXT NOT NULL,
|
|
813
|
-
line INTEGER,
|
|
814
|
-
"column" INTEGER,
|
|
815
|
-
raw_query TEXT,
|
|
816
|
-
parameters TEXT,
|
|
817
|
-
origin TEXT,
|
|
818
|
-
recorded_at TEXT DEFAULT (datetime('now'))
|
|
819
|
-
);
|
|
820
|
-
INSERT INTO schema_usage (id, schema_id, table_name, file_path, function_name, usage_type, line, "column", raw_query, parameters, recorded_at)
|
|
821
|
-
SELECT id, schema_id, table_name, file_path, function_name, usage_type, line, "column", raw_query, parameters, recorded_at FROM schema_usage_old;
|
|
822
|
-
DROP TABLE schema_usage_old;
|
|
823
|
-
CREATE INDEX IF NOT EXISTS idx_schema_usage_table ON schema_usage(table_name);
|
|
824
|
-
CREATE INDEX IF NOT EXISTS idx_schema_usage_file ON schema_usage(file_path);
|
|
825
|
-
CREATE INDEX IF NOT EXISTS idx_schema_usage_function ON schema_usage(function_name);
|
|
826
|
-
CREATE INDEX IF NOT EXISTS idx_schema_usage_usage_type ON schema_usage(usage_type);
|
|
827
|
-
`);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
// Migration 14 → 15: tool's own git sha on the run (the second axis next to
|
|
831
|
-
// tool_version). `git_sha` already records the *audited project* commit; this
|
|
832
|
-
// records the *code-auditor* commit so a count change is attributable to a
|
|
833
|
-
// tool commit, not just a version. NULL when running from a published
|
|
834
|
-
// install (no .git) — best-effort, and tool_version stays always-present.
|
|
835
|
-
if (currentVersion < 15) {
|
|
836
|
-
const runCols = this.db
|
|
837
|
-
.prepare(`PRAGMA table_info('findings_ledger_runs')`)
|
|
838
|
-
.all();
|
|
839
|
-
if (!runCols.some((c) => c.name === 'tool_git_sha')) {
|
|
840
|
-
this.db.exec(`ALTER TABLE findings_ledger_runs ADD COLUMN tool_git_sha TEXT`);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
// Migration 15 → 16: schema_usage gains an `origin` column. Rows produced by
|
|
844
|
-
// the knex-style fluent-builder read extractor carry `origin = 'query-builder'`;
|
|
845
|
-
// every other extractor (raw SQL, tagged template, .sql, ORM) leaves it NULL.
|
|
846
|
-
// The cross-domain lifecycle rules use it to exempt tables whose usage is
|
|
847
|
-
// entirely query-builder — scratch/test tables built and consumed through the
|
|
848
|
-
// fluent builder are not one-sided lifecycle defects.
|
|
849
|
-
if (currentVersion < 16) {
|
|
850
|
-
const suCols = this.db
|
|
851
|
-
.prepare(`PRAGMA table_info('schema_usage')`)
|
|
852
|
-
.all();
|
|
853
|
-
if (!suCols.some((c) => c.name === 'origin')) {
|
|
854
|
-
this.db.exec(`ALTER TABLE schema_usage ADD COLUMN origin TEXT`);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
// ── SQLite schema ───────────────────────────────────────────────────
|
|
859
|
-
createSchema() {
|
|
860
|
-
this.db.exec(`
|
|
861
|
-
CREATE TABLE IF NOT EXISTS meta (
|
|
862
|
-
key TEXT PRIMARY KEY,
|
|
863
|
-
value TEXT NOT NULL
|
|
864
|
-
);
|
|
865
|
-
|
|
866
|
-
CREATE TABLE IF NOT EXISTS functions (
|
|
867
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
868
|
-
name TEXT NOT NULL,
|
|
869
|
-
file_path TEXT NOT NULL,
|
|
870
|
-
line_number INTEGER,
|
|
871
|
-
start_line INTEGER,
|
|
872
|
-
end_line INTEGER,
|
|
873
|
-
language TEXT DEFAULT 'typescript',
|
|
874
|
-
entity_type TEXT DEFAULT 'function',
|
|
875
|
-
component_type TEXT,
|
|
876
|
-
signature TEXT,
|
|
877
|
-
return_type TEXT,
|
|
878
|
-
complexity INTEGER DEFAULT 0,
|
|
879
|
-
is_exported INTEGER DEFAULT 0,
|
|
880
|
-
has_jsdoc INTEGER DEFAULT 0,
|
|
881
|
-
jsdoc_description TEXT,
|
|
882
|
-
jsdoc_tags TEXT,
|
|
883
|
-
parameters TEXT,
|
|
884
|
-
type_info TEXT,
|
|
885
|
-
hooks TEXT,
|
|
886
|
-
props TEXT,
|
|
887
|
-
used_imports TEXT,
|
|
888
|
-
unused_imports TEXT,
|
|
889
|
-
import_usage TEXT,
|
|
890
|
-
has_unused_imports INTEGER DEFAULT 0,
|
|
891
|
-
dependency_depth INTEGER DEFAULT 0,
|
|
892
|
-
purpose TEXT DEFAULT '',
|
|
893
|
-
context TEXT DEFAULT '',
|
|
894
|
-
body TEXT,
|
|
895
|
-
content_hash TEXT,
|
|
896
|
-
last_modified TEXT,
|
|
897
|
-
metadata_json TEXT,
|
|
898
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
899
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
900
|
-
);
|
|
901
|
-
|
|
902
|
-
CREATE INDEX IF NOT EXISTS idx_functions_name ON functions(name);
|
|
903
|
-
CREATE INDEX IF NOT EXISTS idx_functions_file_path ON functions(file_path);
|
|
904
|
-
CREATE INDEX IF NOT EXISTS idx_functions_language ON functions(language);
|
|
905
|
-
CREATE INDEX IF NOT EXISTS idx_functions_entity_type ON functions(entity_type);
|
|
906
|
-
CREATE INDEX IF NOT EXISTS idx_functions_complexity ON functions(complexity);
|
|
907
|
-
CREATE INDEX IF NOT EXISTS idx_functions_content_hash ON functions(content_hash);
|
|
908
|
-
CREATE INDEX IF NOT EXISTS idx_functions_is_exported ON functions(is_exported);
|
|
909
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_functions_name_file_line ON functions(name, file_path, line_number);
|
|
910
|
-
|
|
911
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS functions_fts USING fts5(
|
|
912
|
-
name, signature, jsdoc_description, purpose, context, body,
|
|
913
|
-
content='functions', content_rowid='id',
|
|
914
|
-
tokenize='porter unicode61'
|
|
915
|
-
);
|
|
916
|
-
|
|
917
|
-
CREATE TRIGGER IF NOT EXISTS functions_ai AFTER INSERT ON functions BEGIN
|
|
918
|
-
INSERT INTO functions_fts(rowid, name, signature, jsdoc_description, purpose, context, body)
|
|
919
|
-
VALUES (new.id, new.name, new.signature, new.jsdoc_description, new.purpose, new.context, new.body);
|
|
920
|
-
END;
|
|
921
|
-
CREATE TRIGGER IF NOT EXISTS functions_ad AFTER DELETE ON functions BEGIN
|
|
922
|
-
INSERT INTO functions_fts(functions_fts, rowid, name, signature, jsdoc_description, purpose, context, body)
|
|
923
|
-
VALUES ('delete', old.id, old.name, old.signature, old.jsdoc_description, old.purpose, old.context, old.body);
|
|
924
|
-
END;
|
|
925
|
-
CREATE TRIGGER IF NOT EXISTS functions_au AFTER UPDATE ON functions BEGIN
|
|
926
|
-
INSERT INTO functions_fts(functions_fts, rowid, name, signature, jsdoc_description, purpose, context, body)
|
|
927
|
-
VALUES ('delete', old.id, old.name, old.signature, old.jsdoc_description, old.purpose, old.context, old.body);
|
|
928
|
-
INSERT INTO functions_fts(rowid, name, signature, jsdoc_description, purpose, context, body)
|
|
929
|
-
VALUES (new.id, new.name, new.signature, new.jsdoc_description, new.purpose, new.context, new.body);
|
|
930
|
-
END;
|
|
931
|
-
|
|
932
|
-
CREATE TABLE IF NOT EXISTS function_calls (
|
|
933
|
-
caller_id INTEGER NOT NULL REFERENCES functions(id) ON DELETE CASCADE,
|
|
934
|
-
callee_name TEXT NOT NULL,
|
|
935
|
-
PRIMARY KEY (caller_id, callee_name)
|
|
936
|
-
);
|
|
937
|
-
CREATE INDEX IF NOT EXISTS idx_function_calls_callee ON function_calls(callee_name);
|
|
938
|
-
|
|
939
|
-
CREATE TABLE IF NOT EXISTS function_dependencies (
|
|
940
|
-
function_id INTEGER NOT NULL REFERENCES functions(id) ON DELETE CASCADE,
|
|
941
|
-
dependency TEXT NOT NULL,
|
|
942
|
-
PRIMARY KEY (function_id, dependency)
|
|
943
|
-
);
|
|
944
|
-
CREATE INDEX IF NOT EXISTS idx_function_dependencies_dep ON function_dependencies(dependency);
|
|
945
|
-
|
|
946
|
-
CREATE TABLE IF NOT EXISTS whitelist (
|
|
947
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
948
|
-
name TEXT NOT NULL,
|
|
949
|
-
type TEXT NOT NULL,
|
|
950
|
-
status TEXT DEFAULT 'Active',
|
|
951
|
-
category TEXT,
|
|
952
|
-
description TEXT,
|
|
953
|
-
patterns TEXT,
|
|
954
|
-
added_by TEXT DEFAULT 'system',
|
|
955
|
-
added_at TEXT DEFAULT (datetime('now')),
|
|
956
|
-
updated_at TEXT,
|
|
957
|
-
metadata_json TEXT
|
|
958
|
-
);
|
|
959
|
-
CREATE INDEX IF NOT EXISTS idx_whitelist_name ON whitelist(name);
|
|
960
|
-
CREATE INDEX IF NOT EXISTS idx_whitelist_type ON whitelist(type);
|
|
961
|
-
CREATE INDEX IF NOT EXISTS idx_whitelist_status ON whitelist(status);
|
|
962
|
-
|
|
963
|
-
CREATE TABLE IF NOT EXISTS audit_results (
|
|
964
|
-
audit_id TEXT PRIMARY KEY,
|
|
965
|
-
timestamp TEXT NOT NULL,
|
|
966
|
-
project_path TEXT NOT NULL,
|
|
967
|
-
summary_json TEXT NOT NULL,
|
|
968
|
-
analyzer_results_json TEXT NOT NULL,
|
|
969
|
-
violations_json TEXT,
|
|
970
|
-
recommendations_json TEXT,
|
|
971
|
-
metadata_json TEXT,
|
|
972
|
-
expires_at TEXT NOT NULL
|
|
973
|
-
);
|
|
974
|
-
CREATE INDEX IF NOT EXISTS idx_audit_results_timestamp ON audit_results(timestamp);
|
|
975
|
-
CREATE INDEX IF NOT EXISTS idx_audit_results_project_path ON audit_results(project_path);
|
|
976
|
-
|
|
977
|
-
CREATE TABLE IF NOT EXISTS analyzer_configs (
|
|
978
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
979
|
-
analyzer_name TEXT NOT NULL,
|
|
980
|
-
project_path TEXT,
|
|
981
|
-
is_global INTEGER DEFAULT 0,
|
|
982
|
-
config_json TEXT NOT NULL DEFAULT '{}',
|
|
983
|
-
version TEXT,
|
|
984
|
-
created_by TEXT DEFAULT 'system',
|
|
985
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
986
|
-
updated_at TEXT DEFAULT (datetime('now')),
|
|
987
|
-
metadata_json TEXT
|
|
988
|
-
);
|
|
989
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_analyzer_configs_key
|
|
990
|
-
ON analyzer_configs(analyzer_name, COALESCE(project_path, '__global__'), is_global);
|
|
991
|
-
|
|
992
|
-
CREATE TABLE IF NOT EXISTS code_maps (
|
|
993
|
-
map_id TEXT NOT NULL,
|
|
994
|
-
section_type TEXT NOT NULL,
|
|
995
|
-
content TEXT NOT NULL,
|
|
996
|
-
metadata_json TEXT DEFAULT '{}',
|
|
997
|
-
timestamp TEXT DEFAULT (datetime('now')),
|
|
998
|
-
size INTEGER DEFAULT 0,
|
|
999
|
-
PRIMARY KEY (map_id, section_type)
|
|
1000
|
-
);
|
|
1001
|
-
CREATE INDEX IF NOT EXISTS idx_code_maps_timestamp ON code_maps(timestamp);
|
|
1002
|
-
|
|
1003
|
-
CREATE TABLE IF NOT EXISTS schema_definitions (
|
|
1004
|
-
schema_id TEXT PRIMARY KEY,
|
|
1005
|
-
schema_name TEXT,
|
|
1006
|
-
schema_json TEXT NOT NULL,
|
|
1007
|
-
metadata_json TEXT,
|
|
1008
|
-
indexed_at TEXT DEFAULT (datetime('now'))
|
|
1009
|
-
);
|
|
1010
|
-
|
|
1011
|
-
CREATE TABLE IF NOT EXISTS schema_usage (
|
|
1012
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1013
|
-
schema_id TEXT,
|
|
1014
|
-
table_name TEXT NOT NULL,
|
|
1015
|
-
file_path TEXT NOT NULL,
|
|
1016
|
-
function_name TEXT,
|
|
1017
|
-
function_start_line INTEGER,
|
|
1018
|
-
function_start_column INTEGER,
|
|
1019
|
-
usage_type TEXT NOT NULL,
|
|
1020
|
-
line INTEGER,
|
|
1021
|
-
"column" INTEGER,
|
|
1022
|
-
raw_query TEXT,
|
|
1023
|
-
parameters TEXT,
|
|
1024
|
-
origin TEXT,
|
|
1025
|
-
recorded_at TEXT DEFAULT (datetime('now'))
|
|
1026
|
-
);
|
|
1027
|
-
CREATE INDEX IF NOT EXISTS idx_schema_usage_table ON schema_usage(table_name);
|
|
1028
|
-
CREATE INDEX IF NOT EXISTS idx_schema_usage_file ON schema_usage(file_path);
|
|
1029
|
-
CREATE INDEX IF NOT EXISTS idx_schema_usage_function ON schema_usage(function_name);
|
|
1030
|
-
CREATE INDEX IF NOT EXISTS idx_schema_usage_usage_type ON schema_usage(usage_type);
|
|
1031
|
-
|
|
1032
|
-
CREATE TABLE IF NOT EXISTS coverage_data (
|
|
1033
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1034
|
-
function_name TEXT NOT NULL,
|
|
1035
|
-
file_path TEXT NOT NULL,
|
|
1036
|
-
line_number INTEGER,
|
|
1037
|
-
basis TEXT NOT NULL DEFAULT 'static-reach',
|
|
1038
|
-
covered INTEGER NOT NULL DEFAULT 0,
|
|
1039
|
-
source TEXT,
|
|
1040
|
-
imported_at TEXT DEFAULT (datetime('now')),
|
|
1041
|
-
UNIQUE(function_name, file_path, line_number)
|
|
1042
|
-
);
|
|
1043
|
-
CREATE INDEX IF NOT EXISTS idx_cov_basis ON coverage_data(basis);
|
|
1044
|
-
CREATE INDEX IF NOT EXISTS idx_cov_covered ON coverage_data(covered);
|
|
1045
|
-
CREATE INDEX IF NOT EXISTS idx_cov_function ON coverage_data(function_name);
|
|
1046
|
-
|
|
1047
|
-
CREATE TABLE IF NOT EXISTS project_tasks (
|
|
1048
|
-
taskId TEXT PRIMARY KEY,
|
|
1049
|
-
projectPath TEXT NOT NULL,
|
|
1050
|
-
title TEXT NOT NULL,
|
|
1051
|
-
description TEXT DEFAULT '',
|
|
1052
|
-
status TEXT DEFAULT 'pending',
|
|
1053
|
-
priority TEXT DEFAULT 'medium',
|
|
1054
|
-
labels TEXT DEFAULT '[]',
|
|
1055
|
-
source TEXT DEFAULT 'manual',
|
|
1056
|
-
parentTaskId TEXT,
|
|
1057
|
-
blockedBy TEXT DEFAULT '[]',
|
|
1058
|
-
dueAt TEXT,
|
|
1059
|
-
sortOrder INTEGER DEFAULT 0,
|
|
1060
|
-
relatedFiles TEXT DEFAULT '[]',
|
|
1061
|
-
relatedSymbols TEXT DEFAULT '[]',
|
|
1062
|
-
fingerprint TEXT,
|
|
1063
|
-
metadata TEXT DEFAULT '{}',
|
|
1064
|
-
createdAt TEXT DEFAULT (datetime('now')),
|
|
1065
|
-
updatedAt TEXT DEFAULT (datetime('now')),
|
|
1066
|
-
completedAt TEXT
|
|
1067
|
-
);
|
|
1068
|
-
CREATE INDEX IF NOT EXISTS idx_project_tasks_project ON project_tasks(projectPath);
|
|
1069
|
-
CREATE INDEX IF NOT EXISTS idx_project_tasks_status ON project_tasks(status);
|
|
1070
|
-
CREATE INDEX IF NOT EXISTS idx_project_tasks_source ON project_tasks(source);
|
|
1071
|
-
CREATE INDEX IF NOT EXISTS idx_project_tasks_fingerprint ON project_tasks(fingerprint);
|
|
1072
|
-
CREATE INDEX IF NOT EXISTS idx_project_tasks_sort ON project_tasks(sortOrder);
|
|
1073
|
-
|
|
1074
|
-
-- Spec 11 R1 — Findings Ledger: append-only audit history
|
|
1075
|
-
CREATE TABLE IF NOT EXISTS findings_ledger_runs (
|
|
1076
|
-
run_id TEXT PRIMARY KEY,
|
|
1077
|
-
timestamp TEXT NOT NULL,
|
|
1078
|
-
git_sha TEXT,
|
|
1079
|
-
git_dirty INTEGER NOT NULL DEFAULT 0,
|
|
1080
|
-
tool_version TEXT NOT NULL,
|
|
1081
|
-
tool_git_sha TEXT,
|
|
1082
|
-
command TEXT NOT NULL,
|
|
1083
|
-
surface TEXT NOT NULL,
|
|
1084
|
-
scope TEXT NOT NULL,
|
|
1085
|
-
target TEXT NOT NULL,
|
|
1086
|
-
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
1087
|
-
exit_status INTEGER NOT NULL DEFAULT 0,
|
|
1088
|
-
metadata_json TEXT DEFAULT '{}',
|
|
1089
|
-
status TEXT NOT NULL DEFAULT 'completed',
|
|
1090
|
-
project_root TEXT,
|
|
1091
|
-
started_at TEXT,
|
|
1092
|
-
heartbeat_at TEXT,
|
|
1093
|
-
finished_at TEXT,
|
|
1094
|
-
error TEXT,
|
|
1095
|
-
progress_json TEXT,
|
|
1096
|
-
stderr_log TEXT,
|
|
1097
|
-
content_hash TEXT,
|
|
1098
|
-
files_count INTEGER,
|
|
1099
|
-
file_manifest_json TEXT,
|
|
1100
|
-
runner_pid INTEGER,
|
|
1101
|
-
runner_pid_started_at TEXT,
|
|
1102
|
-
runner_host TEXT
|
|
1103
|
-
);
|
|
1104
|
-
CREATE TABLE IF NOT EXISTS findings_ledger_findings (
|
|
1105
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1106
|
-
run_id TEXT NOT NULL REFERENCES findings_ledger_runs(run_id) ON DELETE CASCADE,
|
|
1107
|
-
analyzer TEXT NOT NULL,
|
|
1108
|
-
rule TEXT NOT NULL,
|
|
1109
|
-
severity TEXT NOT NULL,
|
|
1110
|
-
message TEXT NOT NULL,
|
|
1111
|
-
file TEXT NOT NULL,
|
|
1112
|
-
line INTEGER,
|
|
1113
|
-
symbol TEXT DEFAULT '',
|
|
1114
|
-
fingerprint TEXT NOT NULL
|
|
1115
|
-
);
|
|
1116
|
-
CREATE TABLE IF NOT EXISTS findings_ledger_coverage (
|
|
1117
|
-
run_id TEXT NOT NULL REFERENCES findings_ledger_runs(run_id) ON DELETE CASCADE,
|
|
1118
|
-
analyzer TEXT NOT NULL,
|
|
1119
|
-
rule_id TEXT NOT NULL,
|
|
1120
|
-
state TEXT NOT NULL,
|
|
1121
|
-
count INTEGER NOT NULL DEFAULT 0,
|
|
1122
|
-
reason TEXT,
|
|
1123
|
-
PRIMARY KEY (run_id, analyzer, rule_id)
|
|
1124
|
-
);
|
|
1125
|
-
CREATE INDEX IF NOT EXISTS idx_ledger_runs_surface ON findings_ledger_runs(surface);
|
|
1126
|
-
CREATE INDEX IF NOT EXISTS idx_ledger_runs_timestamp ON findings_ledger_runs(timestamp);
|
|
1127
|
-
CREATE INDEX IF NOT EXISTS idx_ledger_coverage_run ON findings_ledger_coverage(run_id);
|
|
1128
|
-
CREATE INDEX IF NOT EXISTS idx_ledger_findings_run ON findings_ledger_findings(run_id);
|
|
1129
|
-
CREATE INDEX IF NOT EXISTS idx_ledger_findings_fp ON findings_ledger_findings(fingerprint);
|
|
1130
|
-
CREATE INDEX IF NOT EXISTS idx_ledger_findings_rule ON findings_ledger_findings(analyzer, rule);
|
|
1131
|
-
|
|
1132
|
-
-- Spec 10: Style intelligence tables
|
|
1133
|
-
CREATE TABLE IF NOT EXISTS style_declarations (
|
|
1134
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1135
|
-
property TEXT NOT NULL,
|
|
1136
|
-
raw_value TEXT NOT NULL,
|
|
1137
|
-
normalized_value TEXT,
|
|
1138
|
-
mechanism TEXT NOT NULL,
|
|
1139
|
-
file_path TEXT NOT NULL,
|
|
1140
|
-
line INTEGER NOT NULL,
|
|
1141
|
-
context TEXT,
|
|
1142
|
-
variant_context TEXT,
|
|
1143
|
-
token_ref TEXT,
|
|
1144
|
-
content_hash TEXT,
|
|
1145
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1146
|
-
);
|
|
1147
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_property ON style_declarations(property);
|
|
1148
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_mechanism ON style_declarations(mechanism);
|
|
1149
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_file_path ON style_declarations(file_path);
|
|
1150
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_token_ref ON style_declarations(token_ref);
|
|
1151
|
-
CREATE INDEX IF NOT EXISTS idx_style_decls_content_hash ON style_declarations(content_hash);
|
|
1152
|
-
|
|
1153
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS style_declarations_fts USING fts5(
|
|
1154
|
-
property, raw_value, normalized_value,
|
|
1155
|
-
content='style_declarations', content_rowid='id',
|
|
1156
|
-
tokenize='porter unicode61'
|
|
1157
|
-
);
|
|
1158
|
-
|
|
1159
|
-
CREATE TABLE IF NOT EXISTS style_tokens (
|
|
1160
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1161
|
-
name TEXT NOT NULL,
|
|
1162
|
-
value TEXT NOT NULL,
|
|
1163
|
-
file_path TEXT NOT NULL,
|
|
1164
|
-
mechanism TEXT NOT NULL,
|
|
1165
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1166
|
-
);
|
|
1167
|
-
CREATE INDEX IF NOT EXISTS idx_style_tokens_name ON style_tokens(name);
|
|
1168
|
-
|
|
1169
|
-
CREATE TABLE IF NOT EXISTS style_class_usage (
|
|
1170
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1171
|
-
class_name TEXT NOT NULL,
|
|
1172
|
-
file_path TEXT NOT NULL,
|
|
1173
|
-
line INTEGER NOT NULL,
|
|
1174
|
-
mechanism TEXT NOT NULL,
|
|
1175
|
-
unresolvable INTEGER DEFAULT 0,
|
|
1176
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1177
|
-
);
|
|
1178
|
-
CREATE INDEX IF NOT EXISTS idx_style_class_usage_name ON style_class_usage(class_name);
|
|
1179
|
-
CREATE INDEX IF NOT EXISTS idx_style_class_usage_file ON style_class_usage(file_path);
|
|
1180
|
-
CREATE INDEX IF NOT EXISTS idx_style_class_usage_unres ON style_class_usage(unresolvable);
|
|
1181
|
-
|
|
1182
|
-
-- Spec 45 R5: Stylesheet sources the style indexer could not read.
|
|
1183
|
-
-- When any rows exist, styles/undefined-class findings carry them as
|
|
1184
|
-
-- incomplete-definition context instead of going silent.
|
|
1185
|
-
CREATE TABLE IF NOT EXISTS style_unread_sources (
|
|
1186
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1187
|
-
file_path TEXT NOT NULL UNIQUE,
|
|
1188
|
-
reason TEXT NOT NULL,
|
|
1189
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1190
|
-
);
|
|
1191
|
-
|
|
1192
|
-
-- Spec 12: Convention mining
|
|
1193
|
-
CREATE TABLE IF NOT EXISTS conventions (
|
|
1194
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1195
|
-
domain TEXT NOT NULL,
|
|
1196
|
-
rule_id TEXT NOT NULL,
|
|
1197
|
-
antecedent TEXT,
|
|
1198
|
-
consequent TEXT,
|
|
1199
|
-
pattern TEXT,
|
|
1200
|
-
directory TEXT,
|
|
1201
|
-
file_path TEXT,
|
|
1202
|
-
line INTEGER,
|
|
1203
|
-
support INTEGER DEFAULT 0,
|
|
1204
|
-
total_cases INTEGER DEFAULT 0,
|
|
1205
|
-
confidence REAL DEFAULT 0,
|
|
1206
|
-
exemplar_file TEXT,
|
|
1207
|
-
exemplar_line INTEGER,
|
|
1208
|
-
export_kind TEXT,
|
|
1209
|
-
hash TEXT,
|
|
1210
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
1211
|
-
);
|
|
1212
|
-
CREATE INDEX IF NOT EXISTS idx_conv_domain ON conventions(domain);
|
|
1213
|
-
CREATE INDEX IF NOT EXISTS idx_conv_rule_id ON conventions(rule_id);
|
|
1214
|
-
CREATE INDEX IF NOT EXISTS idx_conv_directory ON conventions(directory);
|
|
1215
|
-
CREATE INDEX IF NOT EXISTS idx_conv_file_path ON conventions(file_path);
|
|
1216
|
-
CREATE INDEX IF NOT EXISTS idx_conv_hash ON conventions(hash);
|
|
1217
|
-
|
|
1218
|
-
-- Spec 13: Hotspots & temporal analysis
|
|
1219
|
-
CREATE TABLE IF NOT EXISTS file_churn (
|
|
1220
|
-
file_path TEXT PRIMARY KEY,
|
|
1221
|
-
commit_count INTEGER NOT NULL DEFAULT 0,
|
|
1222
|
-
lines_added INTEGER NOT NULL DEFAULT 0,
|
|
1223
|
-
lines_deleted INTEGER NOT NULL DEFAULT 0,
|
|
1224
|
-
distinct_authors INTEGER NOT NULL DEFAULT 0,
|
|
1225
|
-
dominant_author TEXT,
|
|
1226
|
-
dominant_author_share REAL DEFAULT 0,
|
|
1227
|
-
last_touched TEXT
|
|
1228
|
-
);
|
|
1229
|
-
CREATE INDEX IF NOT EXISTS idx_file_churn_cc ON file_churn(commit_count);
|
|
1230
|
-
|
|
1231
|
-
CREATE TABLE IF NOT EXISTS function_churn (
|
|
1232
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1233
|
-
function_id INTEGER REFERENCES functions(id) ON DELETE CASCADE,
|
|
1234
|
-
function_name TEXT NOT NULL,
|
|
1235
|
-
file_path TEXT NOT NULL,
|
|
1236
|
-
commit_count INTEGER NOT NULL DEFAULT 0,
|
|
1237
|
-
distinct_authors INTEGER NOT NULL DEFAULT 0,
|
|
1238
|
-
dominant_author TEXT,
|
|
1239
|
-
dominant_author_share REAL DEFAULT 0,
|
|
1240
|
-
renamed INTEGER DEFAULT 0,
|
|
1241
|
-
confidence REAL DEFAULT 1.0
|
|
1242
|
-
);
|
|
1243
|
-
CREATE INDEX IF NOT EXISTS idx_func_churn_fid ON function_churn(function_id);
|
|
1244
|
-
|
|
1245
|
-
CREATE TABLE IF NOT EXISTS hotspot_scores (
|
|
1246
|
-
target TEXT PRIMARY KEY,
|
|
1247
|
-
type TEXT NOT NULL,
|
|
1248
|
-
score REAL NOT NULL DEFAULT 0,
|
|
1249
|
-
churn_pct REAL NOT NULL DEFAULT 0,
|
|
1250
|
-
complexity_pct REAL NOT NULL DEFAULT 0,
|
|
1251
|
-
commit_count INTEGER NOT NULL DEFAULT 0,
|
|
1252
|
-
distinct_authors INTEGER NOT NULL DEFAULT 0,
|
|
1253
|
-
dominant_author TEXT,
|
|
1254
|
-
dominant_author_share REAL DEFAULT 0,
|
|
1255
|
-
bus_factor_risk INTEGER DEFAULT 0,
|
|
1256
|
-
complexity INTEGER NOT NULL DEFAULT 0,
|
|
1257
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
1258
|
-
);
|
|
1259
|
-
CREATE INDEX IF NOT EXISTS idx_hotspot_scores_score ON hotspot_scores(score DESC);
|
|
1260
|
-
|
|
1261
|
-
CREATE TABLE IF NOT EXISTS dry_pair_history (
|
|
1262
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1263
|
-
pair_fingerprint TEXT NOT NULL,
|
|
1264
|
-
file1 TEXT NOT NULL,
|
|
1265
|
-
symbol1 TEXT,
|
|
1266
|
-
line1 INTEGER NOT NULL,
|
|
1267
|
-
content_hash1 TEXT NOT NULL,
|
|
1268
|
-
file2 TEXT NOT NULL,
|
|
1269
|
-
symbol2 TEXT,
|
|
1270
|
-
line2 INTEGER NOT NULL,
|
|
1271
|
-
content_hash2 TEXT NOT NULL,
|
|
1272
|
-
similarity REAL NOT NULL,
|
|
1273
|
-
timestamp TEXT DEFAULT (datetime('now')),
|
|
1274
|
-
run_id TEXT NOT NULL
|
|
1275
|
-
);
|
|
1276
|
-
CREATE INDEX IF NOT EXISTS idx_dph_fingerprint ON dry_pair_history(pair_fingerprint);
|
|
1277
|
-
CREATE INDEX IF NOT EXISTS idx_dph_run ON dry_pair_history(run_id);
|
|
1278
|
-
`);
|
|
1279
|
-
// Run schema migrations
|
|
1280
|
-
this.runMigrations();
|
|
1281
|
-
// Record schema version
|
|
1282
|
-
this.db.prepare(`INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)`).run(String(CodeIndexDB.SCHEMA_VERSION));
|
|
1283
|
-
}
|
|
1284
|
-
// ── LokiJS migration ────────────────────────────────────────────────
|
|
1285
|
-
maybeMigrateFromLokiJS() {
|
|
1286
|
-
if (this.dbPath === ':memory:')
|
|
1287
|
-
return { migrated: false };
|
|
1288
|
-
const bakPath = this.dbPath + '.loki.bak';
|
|
1289
|
-
// If backup already exists alongside a valid SQLite db, skip
|
|
1290
|
-
try {
|
|
1291
|
-
const st = require('fs').statSync(bakPath);
|
|
1292
|
-
if (st.isFile()) {
|
|
1293
|
-
// Check if SQLite db exists
|
|
1294
|
-
try {
|
|
1295
|
-
require('fs').statSync(this.dbPath);
|
|
1296
|
-
return { migrated: false };
|
|
1297
|
-
}
|
|
1298
|
-
catch { /* fall through */ }
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
catch { /* no backup */ }
|
|
1302
|
-
// Check if dbPath is a LokiJS file
|
|
1303
|
-
try {
|
|
1304
|
-
const raw = require('fs').readFileSync(this.dbPath, 'utf-8');
|
|
1305
|
-
if (!raw.startsWith('{"filename":') && !raw.includes('"collections":')) {
|
|
1306
|
-
return { migrated: false };
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
catch {
|
|
1310
|
-
return { migrated: false };
|
|
1311
|
-
}
|
|
1312
|
-
// It's LokiJS — migrate
|
|
1313
|
-
try {
|
|
1314
|
-
const content = require('fs').readFileSync(this.dbPath, 'utf-8');
|
|
1315
|
-
const data = JSON.parse(content);
|
|
1316
|
-
// LokiJS stores collections as an array of {name, data, ...}
|
|
1317
|
-
const collArray = Array.isArray(data.collections)
|
|
1318
|
-
? data.collections
|
|
1319
|
-
: [];
|
|
1320
|
-
const collections = {};
|
|
1321
|
-
for (const c of collArray) {
|
|
1322
|
-
collections[c.name] = c.data ?? [];
|
|
1323
|
-
}
|
|
1324
|
-
// Rename old LokiJS file FIRST, then create fresh SQLite DB
|
|
1325
|
-
require('fs').renameSync(this.dbPath, bakPath);
|
|
1326
|
-
const migDb = openSqlite(this.dbPath, { timeoutMs: DB_BUSY_TIMEOUT_MS });
|
|
1327
|
-
migDb.pragma('journal_mode = WAL');
|
|
1328
|
-
migDb.pragma('foreign_keys = ON');
|
|
1329
|
-
// User-authored tables only (function index gets rebuilt by sync).
|
|
1330
|
-
// whitelist + analyzer_configs use snake_case (matching raw SQL queries in the main code).
|
|
1331
|
-
// project_tasks uses camelCase (matching ProjectTaskRepository document fields).
|
|
1332
|
-
migDb.exec(`
|
|
1333
|
-
CREATE TABLE IF NOT EXISTS whitelist (
|
|
1334
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1335
|
-
name TEXT NOT NULL,
|
|
1336
|
-
type TEXT NOT NULL,
|
|
1337
|
-
status TEXT DEFAULT 'Active',
|
|
1338
|
-
category TEXT,
|
|
1339
|
-
description TEXT,
|
|
1340
|
-
patterns TEXT,
|
|
1341
|
-
added_by TEXT DEFAULT 'system',
|
|
1342
|
-
added_at TEXT DEFAULT (datetime('now')),
|
|
1343
|
-
updated_at TEXT,
|
|
1344
|
-
metadata_json TEXT
|
|
1345
|
-
);
|
|
1346
|
-
|
|
1347
|
-
CREATE TABLE IF NOT EXISTS analyzer_configs (
|
|
1348
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1349
|
-
analyzer_name TEXT NOT NULL,
|
|
1350
|
-
project_path TEXT,
|
|
1351
|
-
is_global INTEGER DEFAULT 0,
|
|
1352
|
-
config_json TEXT NOT NULL DEFAULT '{}',
|
|
1353
|
-
version TEXT,
|
|
1354
|
-
created_by TEXT DEFAULT 'system',
|
|
1355
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
1356
|
-
updated_at TEXT DEFAULT (datetime('now')),
|
|
1357
|
-
metadata_json TEXT
|
|
1358
|
-
);
|
|
1359
|
-
|
|
1360
|
-
CREATE TABLE IF NOT EXISTS project_tasks (
|
|
1361
|
-
taskId TEXT PRIMARY KEY,
|
|
1362
|
-
projectPath TEXT NOT NULL,
|
|
1363
|
-
title TEXT NOT NULL,
|
|
1364
|
-
description TEXT DEFAULT '',
|
|
1365
|
-
status TEXT DEFAULT 'pending',
|
|
1366
|
-
priority TEXT DEFAULT 'medium',
|
|
1367
|
-
labels TEXT DEFAULT '[]',
|
|
1368
|
-
source TEXT DEFAULT 'manual',
|
|
1369
|
-
parentTaskId TEXT,
|
|
1370
|
-
blockedBy TEXT DEFAULT '[]',
|
|
1371
|
-
dueAt TEXT,
|
|
1372
|
-
sortOrder INTEGER DEFAULT 0,
|
|
1373
|
-
relatedFiles TEXT DEFAULT '[]',
|
|
1374
|
-
relatedSymbols TEXT DEFAULT '[]',
|
|
1375
|
-
fingerprint TEXT,
|
|
1376
|
-
metadata TEXT DEFAULT '{}',
|
|
1377
|
-
createdAt TEXT DEFAULT (datetime('now')),
|
|
1378
|
-
updatedAt TEXT DEFAULT (datetime('now')),
|
|
1379
|
-
completedAt TEXT
|
|
1380
|
-
);
|
|
1381
|
-
CREATE INDEX IF NOT EXISTS idx_project_tasks_fingerprint ON project_tasks(fingerprint);
|
|
1382
|
-
`);
|
|
1383
|
-
let taskCount = 0;
|
|
1384
|
-
let configCount = 0;
|
|
1385
|
-
let whitelistCount = 0;
|
|
1386
|
-
// Migrate tasks
|
|
1387
|
-
const tasksData = collections['projectTasks'] ?? [];
|
|
1388
|
-
const insertTask = migDb.prepare(`INSERT OR IGNORE INTO project_tasks
|
|
1389
|
-
(taskId, projectPath, title, description, status, priority, labels, source,
|
|
1390
|
-
parentTaskId, blockedBy, dueAt, sortOrder, relatedFiles, relatedSymbols,
|
|
1391
|
-
fingerprint, metadata, createdAt, updatedAt, completedAt)
|
|
1392
|
-
VALUES (@taskId, @projectPath, @title, @description, @status, @priority, @labels, @source,
|
|
1393
|
-
@parentTaskId, @blockedBy, @dueAt, @sortOrder, @relatedFiles, @relatedSymbols,
|
|
1394
|
-
@fingerprint, @metadata, @createdAt, @updatedAt, @completedAt)`);
|
|
1395
|
-
for (const t of tasksData) {
|
|
1396
|
-
insertTask.run({
|
|
1397
|
-
taskId: t.taskId ?? '',
|
|
1398
|
-
projectPath: t.projectPath ?? '',
|
|
1399
|
-
title: t.title ?? '',
|
|
1400
|
-
description: t.description ?? '',
|
|
1401
|
-
status: t.status ?? 'pending',
|
|
1402
|
-
priority: t.priority ?? 'medium',
|
|
1403
|
-
labels: JSON.stringify(t.labels ?? []),
|
|
1404
|
-
source: t.source ?? 'manual',
|
|
1405
|
-
parentTaskId: t.parentTaskId ?? null,
|
|
1406
|
-
blockedBy: JSON.stringify(t.blockedBy ?? []),
|
|
1407
|
-
dueAt: t.dueAt ?? null,
|
|
1408
|
-
sortOrder: t.sortOrder ?? 0,
|
|
1409
|
-
relatedFiles: JSON.stringify(t.relatedFiles ?? []),
|
|
1410
|
-
relatedSymbols: JSON.stringify(t.relatedSymbols ?? []),
|
|
1411
|
-
fingerprint: t.fingerprint ?? null,
|
|
1412
|
-
metadata: JSON.stringify(t.metadata ?? t.metadata_json ?? {}),
|
|
1413
|
-
createdAt: t.createdAt ?? t.created_at ?? new Date().toISOString(),
|
|
1414
|
-
updatedAt: t.updatedAt ?? t.updated_at ?? new Date().toISOString(),
|
|
1415
|
-
completedAt: t.completedAt ?? t.completed_at ?? null,
|
|
1416
|
-
});
|
|
1417
|
-
taskCount++;
|
|
1418
|
-
}
|
|
1419
|
-
// Migrate analyzer configs (snake_case columns matching createSchema)
|
|
1420
|
-
const configsData = collections['analyzerConfigs'] ?? [];
|
|
1421
|
-
const insertConfig = migDb.prepare(`INSERT OR IGNORE INTO analyzer_configs
|
|
1422
|
-
(analyzer_name, project_path, is_global, config_json, version, created_by, created_at, updated_at, metadata_json)
|
|
1423
|
-
VALUES (@analyzerName, @projectPath, @isGlobal, @configJson, @version, @createdBy, @createdAt, @updatedAt, @metadataJson)`);
|
|
1424
|
-
for (const c of configsData) {
|
|
1425
|
-
insertConfig.run({
|
|
1426
|
-
analyzerName: c.analyzerName ?? '',
|
|
1427
|
-
projectPath: c.projectPath ?? null,
|
|
1428
|
-
isGlobal: c.isGlobal ? 1 : 0,
|
|
1429
|
-
configJson: typeof c.config_json === 'string'
|
|
1430
|
-
? c.config_json
|
|
1431
|
-
: JSON.stringify(c.config ?? c.config_json ?? {}),
|
|
1432
|
-
version: c.version ?? null,
|
|
1433
|
-
createdBy: c.createdBy ?? c.created_by ?? 'system',
|
|
1434
|
-
createdAt: c.createdAt ?? c.created_at ?? new Date().toISOString(),
|
|
1435
|
-
updatedAt: c.updatedAt ?? c.updated_at ?? new Date().toISOString(),
|
|
1436
|
-
metadataJson: JSON.stringify(c.metadata ?? c.metadata_json ?? {}),
|
|
1437
|
-
});
|
|
1438
|
-
configCount++;
|
|
1439
|
-
}
|
|
1440
|
-
// Migrate whitelist (snake_case columns matching createSchema)
|
|
1441
|
-
const whitelistData = collections['whitelist'] ?? [];
|
|
1442
|
-
const insertWl = migDb.prepare(`INSERT OR IGNORE INTO whitelist
|
|
1443
|
-
(name, type, status, category, description, patterns, added_by, added_at, updated_at, metadata_json)
|
|
1444
|
-
VALUES (@name, @type, @status, @category, @description, @patterns, @addedBy, @addedAt, @updatedAt, @metadataJson)`);
|
|
1445
|
-
for (const w of whitelistData) {
|
|
1446
|
-
insertWl.run({
|
|
1447
|
-
name: w.name ?? '',
|
|
1448
|
-
type: w.type ?? 'PlatformAPI',
|
|
1449
|
-
status: w.status ?? 'Active',
|
|
1450
|
-
category: w.category ?? null,
|
|
1451
|
-
description: w.description ?? null,
|
|
1452
|
-
patterns: JSON.stringify(w.patterns ?? []),
|
|
1453
|
-
addedBy: w.addedBy ?? w.added_by ?? 'system',
|
|
1454
|
-
addedAt: w.addedAt ?? w.added_at ?? new Date().toISOString(),
|
|
1455
|
-
updatedAt: w.updatedAt ?? w.updated_at ?? null,
|
|
1456
|
-
metadataJson: JSON.stringify(w.metadata ?? w.metadata_json ?? {}),
|
|
1457
|
-
});
|
|
1458
|
-
whitelistCount++;
|
|
1459
|
-
}
|
|
1460
|
-
migDb.close();
|
|
1461
|
-
const countLine = [
|
|
1462
|
-
taskCount && `${taskCount} tasks`,
|
|
1463
|
-
configCount && `${configCount} analyzer configs`,
|
|
1464
|
-
whitelistCount && `${whitelistCount} whitelist entries`,
|
|
1465
|
-
].filter(Boolean).join(', ') || '0 entries';
|
|
1466
|
-
console.error(`[code-auditor] Migrated ${countLine} from LokiJS to SQLite. Old file saved as ${path.basename(bakPath)}`);
|
|
1467
|
-
return { migrated: true, counts: { tasks: taskCount, configs: configCount, whitelist: whitelistCount } };
|
|
1468
|
-
}
|
|
1469
|
-
catch (err) {
|
|
1470
|
-
// Migration failed — try to restore the backup
|
|
1471
|
-
console.error('[code-auditor] LokiJS migration failed:', err instanceof Error ? err.message : String(err));
|
|
1472
|
-
try {
|
|
1473
|
-
if (require('fs').existsSync(bakPath) && !require('fs').existsSync(this.dbPath)) {
|
|
1474
|
-
require('fs').renameSync(bakPath, this.dbPath);
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
catch { /* best effort */ }
|
|
1478
|
-
return { migrated: false };
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
318
|
// ── Init guard ──────────────────────────────────────────────────────
|
|
1482
319
|
ensureInitialized() {
|
|
1483
320
|
if (!this.isInitialized) {
|
|
@@ -1489,174 +326,18 @@ export class CodeIndexDB {
|
|
|
1489
326
|
throw new Error('Database not initialized. Call initialize() first.');
|
|
1490
327
|
}
|
|
1491
328
|
}
|
|
1492
|
-
// ──
|
|
1493
|
-
rowToFunction(row) {
|
|
1494
|
-
return {
|
|
1495
|
-
name: row.name,
|
|
1496
|
-
filePath: row.file_path,
|
|
1497
|
-
lineNumber: row.line_number,
|
|
1498
|
-
startLine: row.start_line,
|
|
1499
|
-
endLine: row.end_line,
|
|
1500
|
-
language: row.language,
|
|
1501
|
-
dependencies: [],
|
|
1502
|
-
purpose: row.purpose ?? '',
|
|
1503
|
-
context: row.context ?? '',
|
|
1504
|
-
signature: row.signature ?? '',
|
|
1505
|
-
parameters: tryParseJson(row.parameters) ?? [],
|
|
1506
|
-
jsDoc: { description: row.jsdoc_description ?? '' },
|
|
1507
|
-
imports: [],
|
|
1508
|
-
body: row.body ?? '',
|
|
1509
|
-
comments: [],
|
|
1510
|
-
isAsync: false,
|
|
1511
|
-
isGenerator: false,
|
|
1512
|
-
returnType: row.return_type,
|
|
1513
|
-
visibility: 'public',
|
|
1514
|
-
complexity: row.complexity ?? 0,
|
|
1515
|
-
content_hash: row.content_hash,
|
|
1516
|
-
metadata: tryParseJson(row.metadata_json) ?? {},
|
|
1517
|
-
hooks: tryParseJson(row.hooks),
|
|
1518
|
-
props: tryParseJson(row.props),
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
functionToRow(func, lastModified) {
|
|
1522
|
-
const enhanced = func;
|
|
1523
|
-
const jsDoc = func.jsDoc;
|
|
1524
|
-
return {
|
|
1525
|
-
name: func.name,
|
|
1526
|
-
file_path: func.filePath,
|
|
1527
|
-
line_number: func.lineNumber ?? 0,
|
|
1528
|
-
start_line: func.startLine ?? null,
|
|
1529
|
-
end_line: func.endLine ?? null,
|
|
1530
|
-
language: func.language ?? 'typescript',
|
|
1531
|
-
entity_type: func.metadata?.entityType ?? 'function',
|
|
1532
|
-
component_type: func.metadata?.componentType ?? null,
|
|
1533
|
-
signature: enhanced.signature ?? '',
|
|
1534
|
-
return_type: enhanced.returnType ?? null,
|
|
1535
|
-
complexity: enhanced.complexity ?? func.metadata?.complexity ?? 0,
|
|
1536
|
-
is_exported: func.metadata?.isExported ? 1 : 0,
|
|
1537
|
-
has_jsdoc: (jsDoc && jsDoc.description) ? 1 : 0,
|
|
1538
|
-
jsdoc_description: typeof jsDoc === 'string' ? jsDoc : (jsDoc?.description ?? ''),
|
|
1539
|
-
jsdoc_tags: jsDoc?.tags ? JSON.stringify(jsDoc.tags) : null,
|
|
1540
|
-
parameters: enhanced.parameters ? JSON.stringify(enhanced.parameters) : null,
|
|
1541
|
-
type_info: func.metadata?.typeInfo ? JSON.stringify(func.metadata.typeInfo) : null,
|
|
1542
|
-
hooks: enhanced.hooks ? JSON.stringify(enhanced.hooks) : null,
|
|
1543
|
-
props: enhanced.props ? JSON.stringify(enhanced.props) : null,
|
|
1544
|
-
used_imports: func.metadata?.usedImports ? JSON.stringify(func.metadata.usedImports) : null,
|
|
1545
|
-
unused_imports: func.metadata?.unusedImports ? JSON.stringify(func.metadata.unusedImports) : null,
|
|
1546
|
-
import_usage: func.metadata?.importUsage ? JSON.stringify(func.metadata.importUsage) : null,
|
|
1547
|
-
has_unused_imports: func.metadata?.unusedImports?.length > 0 ? 1 : 0,
|
|
1548
|
-
dependency_depth: func.metadata?.dependencyDepth ?? 0,
|
|
1549
|
-
purpose: func.purpose ?? '',
|
|
1550
|
-
context: func.context ?? '',
|
|
1551
|
-
body: func.body ?? null,
|
|
1552
|
-
content_hash: enhanced.content_hash ?? computeContentHash(func.body, enhanced.signature),
|
|
1553
|
-
last_modified: lastModified ?? new Date().toISOString(),
|
|
1554
|
-
// body lives only in the dedicated `body` column, never in metadata_json
|
|
1555
|
-
// (previously triple-stored). JSON.stringify omits the undefined value, so
|
|
1556
|
-
// this strips any stray `body` key at the write boundary.
|
|
1557
|
-
metadata_json: func.metadata ? JSON.stringify({ ...func.metadata, body: undefined }) : '{}',
|
|
1558
|
-
};
|
|
1559
|
-
}
|
|
1560
|
-
// ── Function CRUD ───────────────────────────────────────────────────
|
|
1561
|
-
async registerFunction(func) {
|
|
1562
|
-
this.ensureInitialized();
|
|
1563
|
-
const row = this.functionToRow(func);
|
|
1564
|
-
try {
|
|
1565
|
-
this.db.prepare(`
|
|
1566
|
-
INSERT INTO functions (name, file_path, line_number, start_line, end_line, language,
|
|
1567
|
-
entity_type, component_type, signature, return_type, complexity, is_exported, has_jsdoc,
|
|
1568
|
-
jsdoc_description, jsdoc_tags, parameters, type_info, hooks, props,
|
|
1569
|
-
used_imports, unused_imports, import_usage, has_unused_imports, dependency_depth,
|
|
1570
|
-
purpose, context, body, content_hash, last_modified, metadata_json)
|
|
1571
|
-
VALUES (@name, @file_path, @line_number, @start_line, @end_line, @language,
|
|
1572
|
-
@entity_type, @component_type, @signature, @return_type, @complexity, @is_exported, @has_jsdoc,
|
|
1573
|
-
@jsdoc_description, @jsdoc_tags, @parameters, @type_info, @hooks, @props,
|
|
1574
|
-
@used_imports, @unused_imports, @import_usage, @has_unused_imports, @dependency_depth,
|
|
1575
|
-
@purpose, @context, @body, @content_hash, @last_modified, @metadata_json)
|
|
1576
|
-
ON CONFLICT(name, file_path, line_number) DO UPDATE SET
|
|
1577
|
-
line_number=excluded.line_number, start_line=excluded.start_line, end_line=excluded.end_line,
|
|
1578
|
-
language=excluded.language, entity_type=excluded.entity_type, component_type=excluded.component_type,
|
|
1579
|
-
signature=excluded.signature, return_type=excluded.return_type, complexity=excluded.complexity,
|
|
1580
|
-
is_exported=excluded.is_exported, has_jsdoc=excluded.has_jsdoc,
|
|
1581
|
-
jsdoc_description=excluded.jsdoc_description, jsdoc_tags=excluded.jsdoc_tags,
|
|
1582
|
-
parameters=excluded.parameters, type_info=excluded.type_info, hooks=excluded.hooks, props=excluded.props,
|
|
1583
|
-
used_imports=excluded.used_imports, unused_imports=excluded.unused_imports,
|
|
1584
|
-
import_usage=excluded.import_usage, has_unused_imports=excluded.has_unused_imports,
|
|
1585
|
-
dependency_depth=excluded.dependency_depth,
|
|
1586
|
-
purpose=excluded.purpose, context=excluded.context, body=excluded.body,
|
|
1587
|
-
content_hash=excluded.content_hash, last_modified=excluded.last_modified,
|
|
1588
|
-
metadata_json=excluded.metadata_json
|
|
1589
|
-
`).run(row);
|
|
1590
|
-
}
|
|
1591
|
-
catch (error) {
|
|
1592
|
-
throw new Error(`Failed to register function: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
async registerFunctions(functions) {
|
|
1596
|
-
this.ensureInitialized();
|
|
1597
|
-
let registered = 0;
|
|
1598
|
-
let failed = 0;
|
|
1599
|
-
const errors = [];
|
|
1600
|
-
const insertAll = this.db.transaction((funcs) => {
|
|
1601
|
-
for (const func of funcs) {
|
|
1602
|
-
try {
|
|
1603
|
-
this.registerFunction(func);
|
|
1604
|
-
registered++;
|
|
1605
|
-
}
|
|
1606
|
-
catch (error) {
|
|
1607
|
-
failed++;
|
|
1608
|
-
errors.push({
|
|
1609
|
-
function: func.name || 'unknown',
|
|
1610
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
});
|
|
1615
|
-
insertAll(functions);
|
|
1616
|
-
return {
|
|
1617
|
-
success: failed === 0,
|
|
1618
|
-
registered,
|
|
1619
|
-
failed,
|
|
1620
|
-
errors: errors
|
|
1621
|
-
};
|
|
1622
|
-
}
|
|
329
|
+
// ── Function sync (orchestration) ────────────────────────────────────
|
|
1623
330
|
/**
|
|
1624
|
-
* Upsert
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
331
|
+
* Upsert one file's functions and remove rows that no longer exist, then
|
|
332
|
+
* rebuild the dependency graph for that file.
|
|
333
|
+
*
|
|
334
|
+
* @param filePath - The file whose functions are being synced.
|
|
335
|
+
* @param currentFunctions - The functions currently present in the file.
|
|
336
|
+
* @param fileHash - Optional file-level content hash stamped onto each written
|
|
337
|
+
* row for diff detection.
|
|
338
|
+
* @returns Counts of added, updated, and removed rows.
|
|
1627
339
|
*/
|
|
1628
|
-
|
|
1629
|
-
const existing = this.db.prepare('SELECT id, name, file_path, line_number FROM functions WHERE file_path = ?').all(filePath);
|
|
1630
|
-
const createKey = (f) => `${f.name ?? f.name}:${f.filePath ?? f.file_path}:${f.lineNumber ?? f.line_number}`;
|
|
1631
|
-
const currentMap = new Map(currentFunctions.map(f => [createKey(f), f]));
|
|
1632
|
-
// Insert/update current functions
|
|
1633
|
-
for (const func of currentFunctions) {
|
|
1634
|
-
const exists = existing.find(e => e.name === func.name && e.line_number === func.lineNumber);
|
|
1635
|
-
if (exists) {
|
|
1636
|
-
const row = this.functionToRow(func, lastModified);
|
|
1637
|
-
const keys = Object.keys(row);
|
|
1638
|
-
const sets = keys.filter(k => k !== 'name' && k !== 'file_path').map(k => `"${k}" = @${k}`);
|
|
1639
|
-
const params = { ...row, _id: exists.id };
|
|
1640
|
-
this.db.prepare(`UPDATE functions SET ${sets.join(', ')} WHERE id = @_id`).run(params);
|
|
1641
|
-
stats.updated++;
|
|
1642
|
-
}
|
|
1643
|
-
else {
|
|
1644
|
-
const row = this.functionToRow(func, lastModified);
|
|
1645
|
-
const keys = Object.keys(row);
|
|
1646
|
-
const sql = `INSERT INTO functions ("${keys.join('", "')}") VALUES (${keys.map(k => '@' + k).join(', ')})`;
|
|
1647
|
-
this.db.prepare(sql).run(row);
|
|
1648
|
-
stats.added++;
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
// Remove stale functions
|
|
1652
|
-
for (const e of existing) {
|
|
1653
|
-
if (!currentMap.has(createKey(e))) {
|
|
1654
|
-
this.db.prepare('DELETE FROM functions WHERE id = ?').run(e.id);
|
|
1655
|
-
stats.removed++;
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
async syncFileIndex(filePath, currentFunctions) {
|
|
340
|
+
async syncFileIndex(filePath, currentFunctions, fileHash) {
|
|
1660
341
|
this.ensureInitialized();
|
|
1661
342
|
const stats = { added: 0, updated: 0, removed: 0 };
|
|
1662
343
|
// Record the file's actual mtime (not wall clock) so detectModifiedFiles can
|
|
@@ -1670,8 +351,8 @@ export class CodeIndexDB {
|
|
|
1670
351
|
// stat failed — fall back to wall clock; detectModifiedFiles will then treat
|
|
1671
352
|
// an unreadable file as modified, which is the safe direction.
|
|
1672
353
|
}
|
|
1673
|
-
this.db.transaction(() => this.syncFileIndexRow(filePath, currentFunctions, stats, lastModified)).immediate();
|
|
1674
|
-
await this.updateDependencyGraph(filePath);
|
|
354
|
+
this.db.transaction(() => this.functionIndex.syncFileIndexRow(filePath, currentFunctions, stats, lastModified, fileHash)).immediate();
|
|
355
|
+
await this.graph.updateDependencyGraph(filePath);
|
|
1675
356
|
return stats;
|
|
1676
357
|
}
|
|
1677
358
|
/**
|
|
@@ -1681,6 +362,9 @@ export class CodeIndexDB {
|
|
|
1681
362
|
* one transaction plus one dependency-graph rebuild per file, and the
|
|
1682
363
|
* rebuild's un-scoped `SELECT` made that O(files × functions). Collapsing it
|
|
1683
364
|
* to one transaction is Amendment B2.
|
|
365
|
+
*
|
|
366
|
+
* @param entries - The per-file sync entries (path plus current functions).
|
|
367
|
+
* @returns Counts of added, updated, and removed rows across all files.
|
|
1684
368
|
*/
|
|
1685
369
|
async syncFileIndexBatch(entries) {
|
|
1686
370
|
this.ensureInitialized();
|
|
@@ -1697,380 +381,69 @@ export class CodeIndexDB {
|
|
|
1697
381
|
}
|
|
1698
382
|
}));
|
|
1699
383
|
this.db.transaction(() => {
|
|
1700
|
-
for (const { filePath, currentFunctions } of entries) {
|
|
1701
|
-
this.syncFileIndexRow(filePath, currentFunctions, stats, mtimes.get(filePath));
|
|
384
|
+
for (const { filePath, currentFunctions, fileHash } of entries) {
|
|
385
|
+
this.functionIndex.syncFileIndexRow(filePath, currentFunctions, stats, mtimes.get(filePath), fileHash);
|
|
1702
386
|
}
|
|
1703
387
|
}).immediate();
|
|
1704
|
-
await this.updateDependencyGraph();
|
|
388
|
+
await this.graph.updateDependencyGraph();
|
|
1705
389
|
return stats;
|
|
1706
390
|
}
|
|
1707
|
-
// ──
|
|
1708
|
-
async updateDependencyGraph(filePath) {
|
|
1709
|
-
this.ensureInitialized();
|
|
1710
|
-
const txn = this.db.transaction(() => {
|
|
1711
|
-
// Clear existing call edges for scoped functions
|
|
1712
|
-
if (filePath) {
|
|
1713
|
-
this.db.prepare(`DELETE FROM function_calls WHERE caller_id IN (SELECT id FROM functions WHERE file_path = ?)`).run(filePath);
|
|
1714
|
-
}
|
|
1715
|
-
else {
|
|
1716
|
-
this.db.prepare('DELETE FROM function_calls').run();
|
|
1717
|
-
}
|
|
1718
|
-
// Clear existing dependency edges for scoped functions
|
|
1719
|
-
if (filePath) {
|
|
1720
|
-
this.db.prepare(`DELETE FROM function_dependencies WHERE function_id IN (SELECT id FROM functions WHERE file_path = ?)`).run(filePath);
|
|
1721
|
-
}
|
|
1722
|
-
else {
|
|
1723
|
-
this.db.prepare('DELETE FROM function_dependencies').run();
|
|
1724
|
-
}
|
|
1725
|
-
// Rebuild edges from metadata. When scoped to one file, read only that
|
|
1726
|
-
// file's functions — a per-file call previously re-read every function
|
|
1727
|
-
// in the index, making the detached-audit loop O(files × functions).
|
|
1728
|
-
const allFns = filePath
|
|
1729
|
-
? this.db.prepare('SELECT id, name, file_path, metadata_json FROM functions WHERE file_path = ?').all(filePath)
|
|
1730
|
-
: this.db.prepare('SELECT id, name, file_path, metadata_json FROM functions').all();
|
|
1731
|
-
const insertCall = this.db.prepare('INSERT OR IGNORE INTO function_calls (caller_id, callee_name) VALUES (?, ?)');
|
|
1732
|
-
const insertFnDep = this.db.prepare('INSERT OR IGNORE INTO function_dependencies (function_id, dependency) VALUES (?, ?)');
|
|
1733
|
-
for (const fn of allFns) {
|
|
1734
|
-
const meta = tryParseJson(fn.metadata_json) ?? {};
|
|
1735
|
-
if (meta.functionCalls) {
|
|
1736
|
-
for (const callee of meta.functionCalls) {
|
|
1737
|
-
insertCall.run(fn.id, callee);
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
// Add specifier-level dependencies (e.g., useState, useEffect)
|
|
1741
|
-
const usedImports = meta.usedImports ?? [];
|
|
1742
|
-
for (const imp of usedImports) {
|
|
1743
|
-
insertFnDep.run(fn.id, imp);
|
|
1744
|
-
}
|
|
1745
|
-
// Add module-level dependencies (e.g., react, express) — stored in
|
|
1746
|
-
// metadata.dependencies since v3.0.4 to power the dep: operator
|
|
1747
|
-
const moduleDeps = meta.dependencies ?? [];
|
|
1748
|
-
for (const dep of moduleDeps) {
|
|
1749
|
-
insertFnDep.run(fn.id, dep);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
});
|
|
1753
|
-
txn.immediate();
|
|
1754
|
-
}
|
|
1755
|
-
async getTransitiveDependencies(functionName, maxDepth = 10) {
|
|
1756
|
-
this.ensureInitialized();
|
|
1757
|
-
const result = [];
|
|
1758
|
-
const visited = new Set();
|
|
1759
|
-
// Find starting function(s)
|
|
1760
|
-
const startFns = this.db.prepare('SELECT id FROM functions WHERE name = ?').all(functionName);
|
|
1761
|
-
if (startFns.length === 0)
|
|
1762
|
-
return result;
|
|
1763
|
-
// Use recursive CTE for transitive closure
|
|
1764
|
-
const rows = this.db.prepare(`
|
|
1765
|
-
WITH RECURSIVE deps(id, callee_name, depth) AS (
|
|
1766
|
-
SELECT fc.caller_id, fc.callee_name, 1
|
|
1767
|
-
FROM function_calls fc
|
|
1768
|
-
WHERE fc.caller_id IN (SELECT id FROM functions WHERE name = ?)
|
|
1769
|
-
UNION
|
|
1770
|
-
SELECT fc.caller_id, fc.callee_name, deps.depth + 1
|
|
1771
|
-
FROM function_calls fc
|
|
1772
|
-
JOIN deps ON fc.caller_id IN (SELECT id FROM functions WHERE name = deps.callee_name)
|
|
1773
|
-
WHERE deps.depth < ?
|
|
1774
|
-
)
|
|
1775
|
-
SELECT DISTINCT callee_name as name, depth FROM deps ORDER BY depth, name
|
|
1776
|
-
`).all(functionName, maxDepth);
|
|
1777
|
-
return rows;
|
|
1778
|
-
}
|
|
1779
|
-
async getTransitiveCallers(functionName, maxDepth = 10) {
|
|
1780
|
-
this.ensureInitialized();
|
|
1781
|
-
const rows = this.db.prepare(`
|
|
1782
|
-
WITH RECURSIVE callers(id, caller_name, depth) AS (
|
|
1783
|
-
SELECT fc.caller_id, f.name, 1
|
|
1784
|
-
FROM function_calls fc
|
|
1785
|
-
JOIN functions f ON f.id = fc.caller_id
|
|
1786
|
-
WHERE fc.callee_name = ?
|
|
1787
|
-
UNION
|
|
1788
|
-
SELECT fc.caller_id, f2.name, callers.depth + 1
|
|
1789
|
-
FROM function_calls fc
|
|
1790
|
-
JOIN functions f2 ON f2.id = fc.caller_id
|
|
1791
|
-
JOIN functions f3 ON f3.name = callers.caller_name AND fc.callee_name = f3.name
|
|
1792
|
-
WHERE callers.depth < ?
|
|
1793
|
-
)
|
|
1794
|
-
SELECT DISTINCT caller_name as name, depth FROM callers ORDER BY depth, name
|
|
1795
|
-
`).all(functionName, maxDepth);
|
|
1796
|
-
return rows;
|
|
1797
|
-
}
|
|
1798
|
-
async detectCircularDependencies() {
|
|
1799
|
-
this.ensureInitialized();
|
|
1800
|
-
// SQLite recursive CTE for cycle detection
|
|
1801
|
-
const rows = this.db.prepare(`
|
|
1802
|
-
WITH RECURSIVE paths(start_name, path, current_name, depth) AS (
|
|
1803
|
-
SELECT f.name, f.name, fc.callee_name, 1
|
|
1804
|
-
FROM functions f
|
|
1805
|
-
JOIN function_calls fc ON fc.caller_id = f.id
|
|
1806
|
-
UNION
|
|
1807
|
-
SELECT paths.start_name,
|
|
1808
|
-
paths.path || '→' || fc.callee_name,
|
|
1809
|
-
fc.callee_name,
|
|
1810
|
-
paths.depth + 1
|
|
1811
|
-
FROM paths
|
|
1812
|
-
JOIN functions f ON f.name = paths.current_name
|
|
1813
|
-
JOIN function_calls fc ON fc.caller_id = f.id
|
|
1814
|
-
WHERE paths.depth < 20
|
|
1815
|
-
AND instr(paths.path, fc.callee_name) = 0
|
|
1816
|
-
)
|
|
1817
|
-
SELECT DISTINCT path FROM paths
|
|
1818
|
-
WHERE current_name = start_name AND depth > 1
|
|
1819
|
-
`).all();
|
|
1820
|
-
return rows.map(r => r.path.split('→'));
|
|
1821
|
-
}
|
|
1822
|
-
async calculateDependencyDepths() {
|
|
1823
|
-
this.ensureInitialized();
|
|
1824
|
-
const functions = this.db.prepare('SELECT id, name FROM functions').all();
|
|
1825
|
-
const update = this.db.prepare('UPDATE functions SET dependency_depth = ? WHERE id = ?');
|
|
1826
|
-
// Calculate depths outside transaction since getTransitiveDependencies is async
|
|
1827
|
-
const depths = [];
|
|
1828
|
-
for (const fn of functions) {
|
|
1829
|
-
const deps = await this.getTransitiveDependencies(fn.name);
|
|
1830
|
-
const maxDepth = deps.length > 0 ? Math.max(...deps.map(d => d.depth)) : 0;
|
|
1831
|
-
depths.push({ id: fn.id, maxDepth });
|
|
1832
|
-
}
|
|
1833
|
-
const txn = this.db.transaction(() => {
|
|
1834
|
-
for (const { id, maxDepth } of depths) {
|
|
1835
|
-
update.run(maxDepth, id);
|
|
1836
|
-
}
|
|
1837
|
-
});
|
|
1838
|
-
txn();
|
|
1839
|
-
}
|
|
1840
|
-
// ── Search ──────────────────────────────────────────────────────────
|
|
1841
|
-
async searchFunctions(options) {
|
|
1842
|
-
this.ensureInitialized();
|
|
1843
|
-
const startTime = Date.now();
|
|
1844
|
-
const queryParser = new QueryParser();
|
|
1845
|
-
let parsedQuery;
|
|
1846
|
-
if (options.query) {
|
|
1847
|
-
parsedQuery = options.parsedQuery || queryParser.parse(options.query);
|
|
1848
|
-
}
|
|
1849
|
-
else if (options.parsedQuery) {
|
|
1850
|
-
parsedQuery = options.parsedQuery;
|
|
1851
|
-
}
|
|
1852
|
-
// If there's a parsed query, use the SQL path (handles both FTS5 and operator-only queries)
|
|
1853
|
-
if (parsedQuery) {
|
|
1854
|
-
const compiled = compileToSQL(parsedQuery, { defaultLimit: options.limit || 50, offset: options.offset || 0 });
|
|
1855
|
-
return this.executeCompiledSearch(compiled, parsedQuery, options, startTime);
|
|
1856
|
-
}
|
|
1857
|
-
// Otherwise, get all functions and apply filters in-memory (backward compatible)
|
|
1858
|
-
const rows = this.db.prepare('SELECT *, rowid as "$loki" FROM functions').all();
|
|
1859
|
-
let results = rows.map((r) => this.rowToFunctionDoc(r));
|
|
1860
|
-
// Apply filters (parsedQuery is undefined here — the if-parsedQuery branch returned early)
|
|
1861
|
-
const combinedFilters = this.mergeFilters(options.filters, undefined);
|
|
1862
|
-
results = this.applyFilters(results, combinedFilters);
|
|
1863
|
-
const totalCount = results.length;
|
|
1864
|
-
const limit = options.limit || 50;
|
|
1865
|
-
const offset = options.offset || 0;
|
|
1866
|
-
results = results.slice(offset, offset + limit);
|
|
1867
|
-
const functions = results.map(doc => ({
|
|
1868
|
-
...doc,
|
|
1869
|
-
score: 50
|
|
1870
|
-
}));
|
|
1871
|
-
return {
|
|
1872
|
-
functions,
|
|
1873
|
-
totalCount,
|
|
1874
|
-
query: options.query,
|
|
1875
|
-
parsedQuery,
|
|
1876
|
-
executionTime: Date.now() - startTime
|
|
1877
|
-
};
|
|
1878
|
-
}
|
|
1879
|
-
executeCompiledSearch(compiled, parsedQuery, options, startTime) {
|
|
1880
|
-
let sql;
|
|
1881
|
-
const params = { ...compiled.params };
|
|
1882
|
-
let afterFrom = '';
|
|
1883
|
-
// Collect JOINs first — they must precede WHERE in SQL
|
|
1884
|
-
for (const join of compiled.joinClauses) {
|
|
1885
|
-
afterFrom += ` ${join}`;
|
|
1886
|
-
}
|
|
1887
|
-
if (compiled.ftsMatch) {
|
|
1888
|
-
// FTS5 path
|
|
1889
|
-
sql = `SELECT f.*, f.rowid as "$loki", bm25(functions_fts) as score
|
|
1890
|
-
FROM functions f
|
|
1891
|
-
JOIN functions_fts ON functions_fts.rowid = f.id${afterFrom}
|
|
1892
|
-
WHERE functions_fts MATCH @_ftsMatch`;
|
|
1893
|
-
params['_ftsMatch'] = compiled.ftsMatch;
|
|
1894
|
-
}
|
|
1895
|
-
else {
|
|
1896
|
-
sql = `SELECT f.*, f.rowid as "$loki", 0 as score FROM functions f${afterFrom} WHERE 1=1`;
|
|
1897
|
-
}
|
|
1898
|
-
// Add WHERE clauses
|
|
1899
|
-
for (const clause of compiled.whereClauses) {
|
|
1900
|
-
sql += ` AND (${clause})`;
|
|
1901
|
-
}
|
|
1902
|
-
// Ordering
|
|
1903
|
-
sql += ` ORDER BY ${compiled.orderBy}`;
|
|
1904
|
-
// Limit/offset
|
|
1905
|
-
sql += ` LIMIT ${compiled.limit} OFFSET ${compiled.offset}`;
|
|
1906
|
-
const rows = this.db.prepare(sql).all(params);
|
|
1907
|
-
// Get total count (without limit)
|
|
1908
|
-
let totalCount = rows.length;
|
|
1909
|
-
try {
|
|
1910
|
-
let countSql;
|
|
1911
|
-
let countAfterFrom = '';
|
|
1912
|
-
for (const join of compiled.joinClauses) {
|
|
1913
|
-
countAfterFrom += ` ${join}`;
|
|
1914
|
-
}
|
|
1915
|
-
if (compiled.ftsMatch) {
|
|
1916
|
-
countSql = `SELECT COUNT(*) as cnt FROM functions f
|
|
1917
|
-
JOIN functions_fts ON functions_fts.rowid = f.id${countAfterFrom}
|
|
1918
|
-
WHERE functions_fts MATCH @_ftsMatch`;
|
|
1919
|
-
}
|
|
1920
|
-
else {
|
|
1921
|
-
countSql = `SELECT COUNT(*) as cnt FROM functions f${countAfterFrom} WHERE 1=1`;
|
|
1922
|
-
}
|
|
1923
|
-
for (const clause of compiled.whereClauses) {
|
|
1924
|
-
countSql += ` AND (${clause})`;
|
|
1925
|
-
}
|
|
1926
|
-
const countParams = { ...params };
|
|
1927
|
-
delete countParams['_ftsMatch'];
|
|
1928
|
-
if (compiled.ftsMatch) {
|
|
1929
|
-
countParams['_ftsMatch'] = compiled.ftsMatch;
|
|
1930
|
-
}
|
|
1931
|
-
const countResult = this.db.prepare(countSql).get(countParams);
|
|
1932
|
-
totalCount = countResult?.cnt ?? rows.length;
|
|
1933
|
-
}
|
|
1934
|
-
catch {
|
|
1935
|
-
// Fall back to results length
|
|
1936
|
-
}
|
|
1937
|
-
// Convert rows to FunctionDocument, applying excluded terms
|
|
1938
|
-
let docs = rows.map((r) => this.rowToFunctionDoc(r));
|
|
1939
|
-
if (parsedQuery.excludedTerms.length > 0) {
|
|
1940
|
-
docs = this.excludeTermsFromResults(docs, parsedQuery.excludedTerms);
|
|
1941
|
-
}
|
|
1942
|
-
// Apply in-memory filters (for filters not handled by compileToSQL)
|
|
1943
|
-
const combinedFilters = this.mergeFilters(options.filters, parsedQuery.filters);
|
|
1944
|
-
docs = this.applyFilters(docs, combinedFilters);
|
|
1945
|
-
const functions = docs.map(doc => ({
|
|
1946
|
-
...doc,
|
|
1947
|
-
score: doc.score ?? 0
|
|
1948
|
-
}));
|
|
1949
|
-
return {
|
|
1950
|
-
functions,
|
|
1951
|
-
totalCount,
|
|
1952
|
-
query: options.query,
|
|
1953
|
-
parsedQuery,
|
|
1954
|
-
executionTime: Date.now() - startTime
|
|
1955
|
-
};
|
|
1956
|
-
}
|
|
1957
|
-
rowToFunctionDoc(row) {
|
|
1958
|
-
const func = this.rowToFunction(row);
|
|
1959
|
-
const doc = {
|
|
1960
|
-
...func,
|
|
1961
|
-
$loki: row['$loki'] ?? row.id,
|
|
1962
|
-
dependencies: [],
|
|
1963
|
-
};
|
|
1964
|
-
// Populate dependencies from metadata
|
|
1965
|
-
const meta = tryParseJson(row.metadata_json);
|
|
1966
|
-
if (meta) {
|
|
1967
|
-
// Include both specifier-level (usedImports) and module-level (dependencies)
|
|
1968
|
-
const usedImports = meta.usedImports ?? [];
|
|
1969
|
-
const moduleDeps = meta.dependencies ?? [];
|
|
1970
|
-
doc.dependencies = [...new Set([...usedImports, ...moduleDeps])];
|
|
1971
|
-
}
|
|
1972
|
-
return doc;
|
|
1973
|
-
}
|
|
1974
|
-
async findDefinition(name, filePath) {
|
|
1975
|
-
this.ensureInitialized();
|
|
1976
|
-
let row;
|
|
1977
|
-
if (filePath) {
|
|
1978
|
-
row = this.db.prepare('SELECT * FROM functions WHERE name = ? AND file_path = ?').get(name, filePath);
|
|
1979
|
-
}
|
|
1980
|
-
else {
|
|
1981
|
-
row = this.db.prepare('SELECT * FROM functions WHERE name = ?').get(name);
|
|
1982
|
-
}
|
|
1983
|
-
if (!row)
|
|
1984
|
-
return null;
|
|
1985
|
-
const func = this.rowToFunction(row);
|
|
1986
|
-
return func;
|
|
1987
|
-
}
|
|
1988
|
-
// ── Stats ───────────────────────────────────────────────────────────
|
|
1989
|
-
async getAllFunctions() {
|
|
1990
|
-
this.ensureInitialized();
|
|
1991
|
-
const rows = this.db.prepare('SELECT * FROM functions').all();
|
|
1992
|
-
return rows.map((r) => this.rowToFunction(r));
|
|
1993
|
-
}
|
|
391
|
+
// ── Lifecycle: clear & close ────────────────────────────────────────
|
|
1994
392
|
/**
|
|
1995
|
-
*
|
|
1996
|
-
*
|
|
1997
|
-
*
|
|
1998
|
-
*
|
|
1999
|
-
* `content_hash`) and their per-row `JSON.parse` cost.
|
|
393
|
+
* Delete all index data while preserving project tasks, configs, whitelist,
|
|
394
|
+
* and ledger rows.
|
|
395
|
+
*
|
|
396
|
+
* @returns A promise that resolves once the index is cleared.
|
|
2000
397
|
*/
|
|
2001
|
-
async getAllFunctionsForDry() {
|
|
2002
|
-
this.ensureInitialized();
|
|
2003
|
-
const rows = this.db
|
|
2004
|
-
.prepare('SELECT name, file_path, start_line, line_number, body FROM functions')
|
|
2005
|
-
.all();
|
|
2006
|
-
return rows.map((r) => ({
|
|
2007
|
-
name: r.name,
|
|
2008
|
-
filePath: r.file_path,
|
|
2009
|
-
startLine: r.start_line ?? undefined,
|
|
2010
|
-
lineNumber: r.line_number ?? undefined,
|
|
2011
|
-
body: r.body ?? '',
|
|
2012
|
-
}));
|
|
2013
|
-
}
|
|
2014
|
-
/** Row count of the `functions` table — for callers that only need `.length`. */
|
|
2015
|
-
async getFunctionCount() {
|
|
2016
|
-
this.ensureInitialized();
|
|
2017
|
-
const row = this.db.prepare('SELECT COUNT(*) AS n FROM functions').get();
|
|
2018
|
-
return row?.n ?? 0;
|
|
2019
|
-
}
|
|
2020
|
-
async getStats() {
|
|
2021
|
-
this.ensureInitialized();
|
|
2022
|
-
const totalFunctions = this.db.prepare('SELECT COUNT(*) as cnt FROM functions').get().cnt;
|
|
2023
|
-
const languages = {};
|
|
2024
|
-
const langRows = this.db.prepare('SELECT language, COUNT(*) as cnt FROM functions GROUP BY language').all();
|
|
2025
|
-
for (const r of langRows) {
|
|
2026
|
-
languages[r.language] = r.cnt;
|
|
2027
|
-
}
|
|
2028
|
-
const depRows = this.db.prepare('SELECT dependency as name, COUNT(*) as cnt FROM function_dependencies GROUP BY dependency ORDER BY cnt DESC LIMIT 10').all();
|
|
2029
|
-
const topDependencies = depRows.map((r) => ({ name: r.name, count: r.cnt }));
|
|
2030
|
-
const filesIndexed = this.db.prepare('SELECT COUNT(DISTINCT file_path) as cnt FROM functions').get().cnt;
|
|
2031
|
-
return {
|
|
2032
|
-
totalFunctions,
|
|
2033
|
-
languages,
|
|
2034
|
-
topDependencies,
|
|
2035
|
-
filesIndexed,
|
|
2036
|
-
lastUpdated: new Date()
|
|
2037
|
-
};
|
|
2038
|
-
}
|
|
2039
|
-
/** Get graph statistics — delegates to graph/callGraph.ts (Spec 14 R1). */
|
|
2040
|
-
getGraphStats() {
|
|
2041
|
-
this.ensureInitialized();
|
|
2042
|
-
return getGs(this.db);
|
|
2043
|
-
}
|
|
2044
|
-
// ── Lifecycle: clear & close ────────────────────────────────────────
|
|
2045
398
|
async clearIndex() {
|
|
2046
399
|
this.ensureInitialized();
|
|
400
|
+
// Delete every indexed table inside a single transaction. Each DELETE is a
|
|
401
|
+
// literal statement (no interpolation → no sql-injection), one table per
|
|
402
|
+
// statement (no multi-table `exec` → no complex-query), prepared once outside
|
|
403
|
+
// the loop and only `.run()` inside it (no prepare-in-loop → no loop-query),
|
|
404
|
+
// and executed through one shared loop (one query site → no too-many-queries).
|
|
405
|
+
// `meta` keeps the keyed subset below; the FTS5 triggers handle functions_fts
|
|
406
|
+
// cleanup on the functions delete. Preserved by design: project_tasks,
|
|
407
|
+
// analyzer_configs, whitelist, findings_ledger_runs, findings_ledger_findings.
|
|
2047
408
|
this.db.transaction(() => {
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
409
|
+
const deletes = [
|
|
410
|
+
this.db.prepare('DELETE FROM functions'),
|
|
411
|
+
this.db.prepare('DELETE FROM audit_results'),
|
|
412
|
+
this.db.prepare('DELETE FROM code_maps'),
|
|
413
|
+
this.db.prepare('DELETE FROM schema_definitions'),
|
|
414
|
+
this.db.prepare('DELETE FROM schema_usage'),
|
|
415
|
+
this.db.prepare('DELETE FROM conventions'),
|
|
416
|
+
this.db.prepare('DELETE FROM file_churn'),
|
|
417
|
+
this.db.prepare('DELETE FROM function_churn'),
|
|
418
|
+
this.db.prepare('DELETE FROM hotspot_scores'),
|
|
419
|
+
this.db.prepare('DELETE FROM dry_pair_history'),
|
|
420
|
+
this.db.prepare('DELETE FROM graph_cache'),
|
|
421
|
+
this.db.prepare("DELETE FROM meta WHERE key IN ('churn_hash', 'conventions_hash', 'style_last_sync')"),
|
|
422
|
+
];
|
|
423
|
+
for (const del of deletes)
|
|
424
|
+
del.run();
|
|
2062
425
|
})();
|
|
2063
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Close the database handle and release resources, if initialized.
|
|
429
|
+
*
|
|
430
|
+
* @returns A promise that resolves once the handle is closed.
|
|
431
|
+
*/
|
|
2064
432
|
async close() {
|
|
2065
433
|
if (this.isInitialized) {
|
|
2066
|
-
this.taskRepository = null;
|
|
2067
|
-
this.stmts.clear();
|
|
2068
434
|
this.db.close();
|
|
2069
435
|
this.isInitialized = false;
|
|
2070
436
|
}
|
|
2071
437
|
this.initFailure = null;
|
|
2072
438
|
}
|
|
2073
439
|
// ── File sync & bulk cleanup ────────────────────────────────────────
|
|
440
|
+
/**
|
|
441
|
+
* Re-scan a single file and reconcile its indexed functions, removing rows
|
|
442
|
+
* when the file no longer exists.
|
|
443
|
+
*
|
|
444
|
+
* @param filePath - The file to synchronize.
|
|
445
|
+
* @returns Counts of added, updated, and removed rows (null on scan error).
|
|
446
|
+
*/
|
|
2074
447
|
async synchronizeFile(filePath) {
|
|
2075
448
|
this.ensureInitialized();
|
|
2076
449
|
try {
|
|
@@ -2085,17 +458,23 @@ export class CodeIndexDB {
|
|
|
2085
458
|
const { FunctionScanner } = await import('./functionScanner.js');
|
|
2086
459
|
const scanner = new FunctionScanner();
|
|
2087
460
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
2088
|
-
const
|
|
2089
|
-
const parsedFunctions = await scanner.scanFunctions(fileContent, filePath, language);
|
|
461
|
+
const parsedFunctions = await scanner.scanFunctions(fileContent, filePath);
|
|
2090
462
|
return await this.syncFileIndex(filePath, parsedFunctions);
|
|
2091
463
|
}
|
|
2092
464
|
catch (error) {
|
|
2093
|
-
throw new Error(`Failed to sync file: ${error
|
|
465
|
+
throw new Error(`Failed to sync file: ${errorMessage(error)}`);
|
|
2094
466
|
}
|
|
2095
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Remove stale function rows whose files are deleted or no longer part of
|
|
470
|
+
* the discovery set.
|
|
471
|
+
*
|
|
472
|
+
* @param projectRoot - Optional project root to reconcile against.
|
|
473
|
+
* @returns Scan counts, removed rows, and per-file errors.
|
|
474
|
+
*/
|
|
2096
475
|
async bulkCleanup(projectRoot) {
|
|
2097
476
|
this.ensureInitialized();
|
|
2098
|
-
const files = this.db.prepare(
|
|
477
|
+
const files = this.db.prepare(SQL_DISTINCT_FILE_PATHS).all();
|
|
2099
478
|
const removedFiles = [];
|
|
2100
479
|
const errors = [];
|
|
2101
480
|
let removedCount = 0;
|
|
@@ -2118,6 +497,7 @@ export class CodeIndexDB {
|
|
|
2118
497
|
discovered = null;
|
|
2119
498
|
}
|
|
2120
499
|
}
|
|
500
|
+
const stalePaths = [];
|
|
2121
501
|
for (const { file_path: fp } of files) {
|
|
2122
502
|
scannedCount++;
|
|
2123
503
|
let stale = false;
|
|
@@ -2135,13 +515,30 @@ export class CodeIndexDB {
|
|
|
2135
515
|
}
|
|
2136
516
|
}
|
|
2137
517
|
if (stale) {
|
|
2138
|
-
|
|
2139
|
-
removedCount += result.changes;
|
|
518
|
+
stalePaths.push(fp);
|
|
2140
519
|
removedFiles.push(fp);
|
|
2141
520
|
}
|
|
2142
521
|
}
|
|
522
|
+
// Batch the deletes — one `IN (SELECT value FROM json_each(?))` DELETE for the
|
|
523
|
+
// whole stale set, instead of one DELETE per stale file (loop-query / N+1).
|
|
524
|
+
// A single JSON-array bind expands through json_each, so a large stale set
|
|
525
|
+
// can't exceed the SQLite bind-parameter ceiling.
|
|
526
|
+
if (stalePaths.length > 0) {
|
|
527
|
+
const result = this.db
|
|
528
|
+
.prepare(SQL_DELETE_FUNCTIONS_IN_FILEPATHS)
|
|
529
|
+
.run(JSON.stringify(stalePaths));
|
|
530
|
+
removedCount += result.changes;
|
|
531
|
+
}
|
|
2143
532
|
return { scannedCount, removedCount, removedFiles, errors };
|
|
2144
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Re-scan the whole project (or the already-indexed files) and reconcile the
|
|
536
|
+
* index, reporting aggregate counts.
|
|
537
|
+
*
|
|
538
|
+
* @param projectRoot - Optional project root to discover files from.
|
|
539
|
+
* @param progressCallback - Optional per-file progress callback.
|
|
540
|
+
* @returns Aggregate sync counts and errors.
|
|
541
|
+
*/
|
|
2145
542
|
async deepSync(projectRoot, progressCallback) {
|
|
2146
543
|
this.ensureInitialized();
|
|
2147
544
|
let syncedFiles = 0;
|
|
@@ -2159,31 +556,68 @@ export class CodeIndexDB {
|
|
|
2159
556
|
}
|
|
2160
557
|
else {
|
|
2161
558
|
// Fallback: sync files already in the index
|
|
2162
|
-
const rows = this.db.prepare(
|
|
559
|
+
const rows = this.db.prepare(SQL_DISTINCT_FILE_PATHS).all();
|
|
2163
560
|
files = rows.map(r => r.file_path);
|
|
2164
561
|
}
|
|
2165
562
|
const total = files.length;
|
|
563
|
+
// Parse each file and defer its index write to one batched transaction plus
|
|
564
|
+
// one dependency-graph rebuild after the loop. The previous per-file
|
|
565
|
+
// `synchronizeFile` → `syncFileIndex` path was one transaction plus one
|
|
566
|
+
// per-file dependency-graph rebuild per file — the same O(files × functions)
|
|
567
|
+
// query-in-loop N+1 Amendment B2 collapsed in the detached-audit path.
|
|
568
|
+
// Tree-sitter parsing stays per-file (it is the inherent cost); only the DB
|
|
569
|
+
// write is batched. Progress and per-file error attribution are unchanged.
|
|
570
|
+
const syncEntries = [];
|
|
571
|
+
const inaccessiblePaths = [];
|
|
2166
572
|
for (let i = 0; i < files.length; i++) {
|
|
2167
573
|
const fp = files[i];
|
|
2168
574
|
if (progressCallback) {
|
|
2169
575
|
progressCallback({ current: i + 1, total, file: fp });
|
|
2170
576
|
}
|
|
577
|
+
let accessible = true;
|
|
2171
578
|
try {
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
579
|
+
await fs.access(fp);
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
accessible = false;
|
|
583
|
+
}
|
|
584
|
+
if (!accessible) {
|
|
585
|
+
// File doesn't exist — defer its delete to one batched DELETE after the
|
|
586
|
+
// loop (the same json_each batch the stale cleanup below uses), instead
|
|
587
|
+
// of one `DELETE … WHERE file_path = ?` per missing file (loop-query N+1).
|
|
588
|
+
inaccessiblePaths.push(fp);
|
|
589
|
+
syncedFiles++;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
const { FunctionScanner } = await import('./functionScanner.js');
|
|
594
|
+
const scanner = new FunctionScanner();
|
|
595
|
+
const fileContent = await fs.readFile(fp, 'utf-8');
|
|
596
|
+
const parsedFunctions = await scanner.scanFunctions(fileContent, fp);
|
|
597
|
+
syncEntries.push({ filePath: fp, currentFunctions: parsedFunctions });
|
|
598
|
+
syncedFiles++;
|
|
2179
599
|
}
|
|
2180
600
|
catch (error) {
|
|
2181
601
|
errors.push({
|
|
2182
602
|
file: fp,
|
|
2183
|
-
error:
|
|
603
|
+
error: `Failed to sync file: ${errorMessage(error)}`
|
|
2184
604
|
});
|
|
2185
605
|
}
|
|
2186
606
|
}
|
|
607
|
+
if (syncEntries.length > 0) {
|
|
608
|
+
const batchStats = await this.syncFileIndexBatch(syncEntries);
|
|
609
|
+
totalAdded += batchStats.added;
|
|
610
|
+
totalUpdated += batchStats.updated;
|
|
611
|
+
totalRemoved += batchStats.removed;
|
|
612
|
+
}
|
|
613
|
+
// Batch-delete the functions of every file that vanished between discovery
|
|
614
|
+
// and the scan (one json_each DELETE, not one per file).
|
|
615
|
+
if (inaccessiblePaths.length > 0) {
|
|
616
|
+
const removed = this.db
|
|
617
|
+
.prepare(SQL_DELETE_FUNCTIONS_IN_FILEPATHS)
|
|
618
|
+
.run(JSON.stringify(inaccessiblePaths));
|
|
619
|
+
totalRemoved += removed.changes;
|
|
620
|
+
}
|
|
2187
621
|
// Clean up stale entries: a file is stale when it is no longer in the
|
|
2188
622
|
// discovery set (deleted OR gitignored since the last sync). Comparing
|
|
2189
623
|
// against discovery — not `fs.access` — makes a gitignored-but-present file
|
|
@@ -2191,7 +625,8 @@ export class CodeIndexDB {
|
|
|
2191
625
|
// project root (files came from the index itself), fall back to the on-disk
|
|
2192
626
|
// existence check.
|
|
2193
627
|
const discoveredSet = projectRoot ? new Set(files) : null;
|
|
2194
|
-
const allIndexed = this.db.prepare(
|
|
628
|
+
const allIndexed = this.db.prepare(SQL_DISTINCT_FILE_PATHS).all();
|
|
629
|
+
const stalePaths = [];
|
|
2195
630
|
for (const { file_path: fp } of allIndexed) {
|
|
2196
631
|
let stale = false;
|
|
2197
632
|
if (discoveredSet) {
|
|
@@ -2205,12 +640,18 @@ export class CodeIndexDB {
|
|
|
2205
640
|
stale = true;
|
|
2206
641
|
}
|
|
2207
642
|
}
|
|
2208
|
-
if (stale)
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
643
|
+
if (stale)
|
|
644
|
+
stalePaths.push(fp);
|
|
645
|
+
}
|
|
646
|
+
// Batch the deletes — one `IN (SELECT value FROM json_each(?))` DELETE for the
|
|
647
|
+
// whole stale set, instead of one DELETE per stale file (loop-query / N+1). A
|
|
648
|
+
// single JSON-array bind expands through json_each, so a large stale set can't
|
|
649
|
+
// exceed the SQLite bind-parameter ceiling.
|
|
650
|
+
if (stalePaths.length > 0) {
|
|
651
|
+
const result = this.db
|
|
652
|
+
.prepare(SQL_DELETE_FUNCTIONS_IN_FILEPATHS)
|
|
653
|
+
.run(JSON.stringify(stalePaths));
|
|
654
|
+
totalRemoved += result.changes;
|
|
2214
655
|
}
|
|
2215
656
|
// Spec 13 — Extract git churn data after index is built and stale entries
|
|
2216
657
|
// are cleaned up. Runs before conventions mining so hotspot data is
|
|
@@ -2230,7 +671,7 @@ export class CodeIndexDB {
|
|
|
2230
671
|
}
|
|
2231
672
|
// Spec 12 — Mine codebase conventions after sync when the functions table
|
|
2232
673
|
// has changed. Content-hash-based skip: stored hash avoids re-mining.
|
|
2233
|
-
this.mineAllConventions(projectRoot);
|
|
674
|
+
this.conventions.mineAllConventions(projectRoot);
|
|
2234
675
|
// Spec 14 — Populate graph caches after index is built
|
|
2235
676
|
try {
|
|
2236
677
|
populateCallGraphCache(this.db);
|
|
@@ -2255,6 +696,9 @@ export class CodeIndexDB {
|
|
|
2255
696
|
*
|
|
2256
697
|
* Returns the list of changed/new function metadata for scoped analysis,
|
|
2257
698
|
* and the set of file paths that were actually touched.
|
|
699
|
+
*
|
|
700
|
+
* @param filePaths - The file paths to re-parse and diff.
|
|
701
|
+
* @returns Changed and deleted functions, touched paths, and per-file errors.
|
|
2258
702
|
*/
|
|
2259
703
|
async detectChangedFunctions(filePaths) {
|
|
2260
704
|
this.ensureInitialized();
|
|
@@ -2263,43 +707,87 @@ export class CodeIndexDB {
|
|
|
2263
707
|
const changedFilePaths = [];
|
|
2264
708
|
const errors = [];
|
|
2265
709
|
const { FunctionScanner } = await import('./functionScanner.js');
|
|
710
|
+
// First pass: split the changed set into files that no longer exist (deleted)
|
|
711
|
+
// and files that are still on disk. The deleted set is handled with one batched
|
|
712
|
+
// SELECT + DELETE below — a per-file query here was a query-in-loop N+1.
|
|
713
|
+
const missingFiles = [];
|
|
2266
714
|
for (const filePath of filePaths) {
|
|
2267
715
|
try {
|
|
2268
716
|
await fs.access(filePath);
|
|
2269
717
|
}
|
|
2270
718
|
catch {
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
719
|
+
missingFiles.push(filePath);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (missingFiles.length > 0) {
|
|
723
|
+
const removed = this.db.prepare(`SELECT * FROM functions WHERE file_path IN (SELECT value FROM json_each(?))`).all(JSON.stringify(missingFiles));
|
|
724
|
+
if (removed.length > 0) {
|
|
725
|
+
deletedFunctions.push(...removed.map((r) => this.functionIndex.rowToFunction(r)));
|
|
726
|
+
this.db.prepare(SQL_DELETE_FUNCTIONS_IN_FILEPATHS).run(JSON.stringify(missingFiles));
|
|
727
|
+
const touched = new Set(removed.map((r) => r.file_path));
|
|
728
|
+
changedFilePaths.push(...missingFiles.filter((fp) => touched.has(fp)));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const missingSet = new Set(missingFiles);
|
|
732
|
+
// Batch-fetch the existing functions for every still-present file in one
|
|
733
|
+
// query, grouped in memory — a per-file `SELECT … WHERE file_path = ?` inside
|
|
734
|
+
// the diff loop below was a query-in-loop N+1.
|
|
735
|
+
const presentFiles = filePaths.filter((fp) => !missingSet.has(fp));
|
|
736
|
+
const existingByFile = new Map();
|
|
737
|
+
if (presentFiles.length > 0) {
|
|
738
|
+
const rows = this.db.prepare(`SELECT * FROM functions WHERE file_path IN (SELECT value FROM json_each(?))`).all(JSON.stringify(presentFiles));
|
|
739
|
+
for (const row of rows) {
|
|
740
|
+
const list = existingByFile.get(row.file_path) ?? [];
|
|
741
|
+
list.push(row);
|
|
742
|
+
existingByFile.set(row.file_path, list);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// Collect per-file sync entries here and batch them after the loop — a
|
|
746
|
+
// `syncFileIndex` call inside the loop was a per-file transaction plus a
|
|
747
|
+
// per-file dependency-graph rebuild (query-in-loop N+1). `syncFileIndexBatch`
|
|
748
|
+
// collapses that to one transaction and one unscoped rebuild (Amendment B2),
|
|
749
|
+
// and carries each file's content hash so the warm-path short-circuit keeps
|
|
750
|
+
// its stored `file_hash` baseline.
|
|
751
|
+
const syncEntries = [];
|
|
752
|
+
for (const filePath of filePaths) {
|
|
753
|
+
if (missingSet.has(filePath))
|
|
2279
754
|
continue;
|
|
2280
|
-
}
|
|
2281
755
|
try {
|
|
2282
|
-
const scanner = new FunctionScanner();
|
|
2283
756
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
2284
|
-
const
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
//
|
|
2288
|
-
|
|
2289
|
-
//
|
|
757
|
+
const fileHash = computeContentHash(fileContent);
|
|
758
|
+
const existing = existingByFile.get(filePath) ?? [];
|
|
759
|
+
// Short-circuit: when the file's whole-content hash round-trips to the
|
|
760
|
+
// stored `file_hash`, no function can be new, changed, or deleted — skip
|
|
761
|
+
// the tree-sitter re-scan and the re-sync. This is the warm path for
|
|
762
|
+
// `changed` on an already-indexed, unchanged file. The hash is content-
|
|
763
|
+
// based (not mtime), so it preserves the precise change-detection
|
|
764
|
+
// guarantee; a NULL stored hash (first sync after upgrade) never
|
|
765
|
+
// short-circuits, so the index self-heals.
|
|
766
|
+
if (existing.length > 0 && existing[0].file_hash && existing[0].file_hash === fileHash) {
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
const scanner = new FunctionScanner();
|
|
770
|
+
const currentFunctions = await scanner.scanFunctions(fileContent, filePath);
|
|
771
|
+
// Key identity on (name, line_number) — the table's unique index —
|
|
772
|
+
// not name alone. Two same-named functions in one file (overloads, a
|
|
773
|
+
// method and a free function, the same name in two scopes) otherwise
|
|
774
|
+
// collapse to one map entry, and an edit to the shadowed one is
|
|
775
|
+
// reported against the wrong row or not at all. file_path is constant
|
|
776
|
+
// across the loop, so (name, line_number) is the distinguishing pair.
|
|
777
|
+
const keyOf = (name, lineNumber) => `${name}:${lineNumber}`;
|
|
2290
778
|
const existingByName = new Map();
|
|
2291
779
|
for (const e of existing) {
|
|
2292
|
-
existingByName.set(e.name, e);
|
|
780
|
+
existingByName.set(keyOf(e.name, e.line_number), e);
|
|
2293
781
|
}
|
|
2294
|
-
const
|
|
782
|
+
const currentKeys = new Set(currentFunctions.map((f) => keyOf(f.name, f.lineNumber)));
|
|
2295
783
|
let fileChanged = false;
|
|
2296
784
|
for (const func of currentFunctions) {
|
|
2297
|
-
const existingRow = existingByName.get(func.name);
|
|
785
|
+
const existingRow = existingByName.get(keyOf(func.name, func.lineNumber));
|
|
2298
786
|
// Convert scanner output to EnhancedFunctionMetadata shape
|
|
2299
787
|
const funcMeta = {
|
|
2300
788
|
...func,
|
|
2301
789
|
complexity: func.complexity,
|
|
2302
|
-
content_hash: computeContentHash(func.body
|
|
790
|
+
content_hash: computeContentHash(func.body),
|
|
2303
791
|
};
|
|
2304
792
|
const newHash = funcMeta.content_hash;
|
|
2305
793
|
if (!existingRow) {
|
|
@@ -2315,16 +803,17 @@ export class CodeIndexDB {
|
|
|
2315
803
|
}
|
|
2316
804
|
// Detect deleted functions
|
|
2317
805
|
for (const e of existing) {
|
|
2318
|
-
if (!
|
|
2319
|
-
deletedFunctions.push(this.rowToFunction(e));
|
|
806
|
+
if (!currentKeys.has(keyOf(e.name, e.line_number))) {
|
|
807
|
+
deletedFunctions.push(this.functionIndex.rowToFunction(e));
|
|
2320
808
|
fileChanged = true;
|
|
2321
809
|
}
|
|
2322
810
|
}
|
|
2323
811
|
if (fileChanged) {
|
|
2324
812
|
changedFilePaths.push(filePath);
|
|
2325
813
|
}
|
|
2326
|
-
//
|
|
2327
|
-
|
|
814
|
+
// Defer the index write: collected above and flushed in one batch after
|
|
815
|
+
// the loop (see `syncEntries`).
|
|
816
|
+
syncEntries.push({ filePath, currentFunctions: currentFunctions, fileHash });
|
|
2328
817
|
}
|
|
2329
818
|
catch (error) {
|
|
2330
819
|
errors.push({
|
|
@@ -2333,11 +822,18 @@ export class CodeIndexDB {
|
|
|
2333
822
|
});
|
|
2334
823
|
}
|
|
2335
824
|
}
|
|
825
|
+
// One batched write + one dependency-graph rebuild for all changed files.
|
|
826
|
+
if (syncEntries.length > 0) {
|
|
827
|
+
await this.syncFileIndexBatch(syncEntries);
|
|
828
|
+
}
|
|
2336
829
|
return { changedFunctions, deletedFunctions, changedFilePaths, errors };
|
|
2337
830
|
}
|
|
2338
831
|
/**
|
|
2339
832
|
* Given a project root, detect all indexed files whose mtime is newer than
|
|
2340
833
|
* the stored last_modified timestamp. Returns file paths for further processing.
|
|
834
|
+
*
|
|
835
|
+
* @param projectRoot - The project root to scan.
|
|
836
|
+
* @returns The file paths whose mtime is newer than the stored timestamp.
|
|
2341
837
|
*/
|
|
2342
838
|
async detectModifiedFiles(projectRoot) {
|
|
2343
839
|
this.ensureInitialized();
|
|
@@ -2365,582 +861,19 @@ export class CodeIndexDB {
|
|
|
2365
861
|
}
|
|
2366
862
|
return modifiedFiles;
|
|
2367
863
|
}
|
|
864
|
+
// ── Cross-concern search (search + schema usage) ─────────────────────
|
|
2368
865
|
/**
|
|
2369
|
-
*
|
|
2370
|
-
*
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
this.ensureInitialized();
|
|
2374
|
-
const result = new Map();
|
|
2375
|
-
if (filePaths.length === 0)
|
|
2376
|
-
return result;
|
|
2377
|
-
const placeholders = filePaths.map(() => '?').join(', ');
|
|
2378
|
-
const rows = this.db.prepare(`SELECT file_path, name, content_hash FROM functions WHERE file_path IN (${placeholders})`).all(...filePaths);
|
|
2379
|
-
for (const row of rows) {
|
|
2380
|
-
if (!result.has(row.file_path)) {
|
|
2381
|
-
result.set(row.file_path, new Map());
|
|
2382
|
-
}
|
|
2383
|
-
result.get(row.file_path).set(row.name, row.content_hash ?? '');
|
|
2384
|
-
}
|
|
2385
|
-
return result;
|
|
2386
|
-
}
|
|
2387
|
-
// ── Legacy search helpers (for in-memory filtering) ─────────────────
|
|
2388
|
-
mergeFilters(optionsFilters, queryFilters) {
|
|
2389
|
-
const merged = {};
|
|
2390
|
-
if (optionsFilters)
|
|
2391
|
-
Object.assign(merged, optionsFilters);
|
|
2392
|
-
if (queryFilters) {
|
|
2393
|
-
if (queryFilters.language)
|
|
2394
|
-
merged.language = queryFilters.language;
|
|
2395
|
-
if (queryFilters.filePath)
|
|
2396
|
-
merged.filePath = queryFilters.filePath;
|
|
2397
|
-
if (queryFilters.fileType)
|
|
2398
|
-
merged.fileType = queryFilters.fileType;
|
|
2399
|
-
if (queryFilters.hasJsDoc !== undefined)
|
|
2400
|
-
merged.hasJsDoc = queryFilters.hasJsDoc;
|
|
2401
|
-
if (queryFilters.complexity)
|
|
2402
|
-
merged.complexity = queryFilters.complexity;
|
|
2403
|
-
if (queryFilters.dateRange)
|
|
2404
|
-
merged.dateRange = queryFilters.dateRange;
|
|
2405
|
-
if (queryFilters.metadata)
|
|
2406
|
-
merged.metadata = queryFilters.metadata;
|
|
2407
|
-
}
|
|
2408
|
-
return merged;
|
|
2409
|
-
}
|
|
2410
|
-
applyFilters(results, filters) {
|
|
2411
|
-
if (!filters)
|
|
2412
|
-
return results;
|
|
2413
|
-
let filtered = results;
|
|
2414
|
-
if (filters.language) {
|
|
2415
|
-
filtered = filtered.filter(doc => doc.language === filters.language);
|
|
2416
|
-
}
|
|
2417
|
-
if (filters.filePath) {
|
|
2418
|
-
if (filters.filePath.includes('*') || filters.filePath.includes('?')) {
|
|
2419
|
-
// `*` and `?` are the only glob wildcards; every other character is
|
|
2420
|
-
// escaped so it matches literally. A path like `foo[0-9].ts` no longer
|
|
2421
|
-
// becomes an accidental regex character class.
|
|
2422
|
-
const pattern = filters.filePath
|
|
2423
|
-
.split(/([*?])/)
|
|
2424
|
-
.map((seg) => (seg === '*' ? '.*' : seg === '?' ? '.' : escapeRegExpLiteral(seg)))
|
|
2425
|
-
.join('');
|
|
2426
|
-
const regex = new RegExp(pattern);
|
|
2427
|
-
filtered = filtered.filter(doc => regex.test(doc.filePath));
|
|
2428
|
-
}
|
|
2429
|
-
else if (filters.filePath.endsWith('.ts') || filters.filePath.endsWith('.tsx') ||
|
|
2430
|
-
filters.filePath.endsWith('.js') || filters.filePath.endsWith('.jsx')) {
|
|
2431
|
-
filtered = filtered.filter(doc => doc.filePath.endsWith(filters.filePath));
|
|
2432
|
-
}
|
|
2433
|
-
else {
|
|
2434
|
-
filtered = filtered.filter(doc => doc.filePath.includes(filters.filePath));
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
if (filters.fileType) {
|
|
2438
|
-
filtered = filtered.filter(doc => doc.filePath.endsWith(filters.fileType));
|
|
2439
|
-
}
|
|
2440
|
-
if (filters.hasJsDoc !== undefined) {
|
|
2441
|
-
filtered = filtered.filter(doc => {
|
|
2442
|
-
const hasJsDoc = doc.jsDoc && doc.jsDoc.description && doc.jsDoc.description.length > 0;
|
|
2443
|
-
return filters.hasJsDoc ? hasJsDoc : !hasJsDoc;
|
|
2444
|
-
});
|
|
2445
|
-
}
|
|
2446
|
-
if (filters.complexity) {
|
|
2447
|
-
filtered = filtered.filter(doc => {
|
|
2448
|
-
if (!doc.complexity)
|
|
2449
|
-
return false;
|
|
2450
|
-
const min = filters.complexity.min || 0;
|
|
2451
|
-
const max = filters.complexity.max || Infinity;
|
|
2452
|
-
return doc.complexity >= min && doc.complexity <= max;
|
|
2453
|
-
});
|
|
2454
|
-
}
|
|
2455
|
-
if (filters.hasAnyDependency && filters.hasAnyDependency.length > 0) {
|
|
2456
|
-
filtered = filtered.filter(doc => filters.hasAnyDependency.some(dep => doc.dependencies.includes(dep)));
|
|
2457
|
-
}
|
|
2458
|
-
if (filters.metadata) {
|
|
2459
|
-
filtered = filtered.filter(doc => {
|
|
2460
|
-
if (!doc.metadata)
|
|
2461
|
-
return false;
|
|
2462
|
-
const m = filters.metadata;
|
|
2463
|
-
if (m.entityType && doc.metadata.entityType !== m.entityType)
|
|
2464
|
-
return false;
|
|
2465
|
-
if (m.componentType && doc.metadata.componentType !== m.componentType)
|
|
2466
|
-
return false;
|
|
2467
|
-
if (m.hasHook) {
|
|
2468
|
-
if (!doc.metadata.hooks)
|
|
2469
|
-
return false;
|
|
2470
|
-
const found = doc.metadata.hooks.some((h) => h.name?.toLowerCase().includes(m.hasHook.toLowerCase()));
|
|
2471
|
-
if (!found)
|
|
2472
|
-
return false;
|
|
2473
|
-
}
|
|
2474
|
-
if (m.hasProp) {
|
|
2475
|
-
if (!doc.metadata.props)
|
|
2476
|
-
return false;
|
|
2477
|
-
const found = doc.metadata.props.some((p) => p.name?.toLowerCase().includes(m.hasProp.toLowerCase()));
|
|
2478
|
-
if (!found)
|
|
2479
|
-
return false;
|
|
2480
|
-
}
|
|
2481
|
-
if (m.usesDependency) {
|
|
2482
|
-
const dep = m.usesDependency.toLowerCase();
|
|
2483
|
-
const inFile = doc.dependencies.some(d => d.toLowerCase().includes(dep));
|
|
2484
|
-
const inFunc = doc.metadata.usedImports?.some(i => i.toLowerCase().includes(dep)) ?? false;
|
|
2485
|
-
if (!inFile && !inFunc)
|
|
2486
|
-
return false;
|
|
2487
|
-
}
|
|
2488
|
-
if (m.callsFunction) {
|
|
2489
|
-
const target = m.callsFunction.toLowerCase();
|
|
2490
|
-
const calls = doc.metadata.functionCalls;
|
|
2491
|
-
if (!calls || !calls.some(c => c.toLowerCase().includes(target)))
|
|
2492
|
-
return false;
|
|
2493
|
-
}
|
|
2494
|
-
if (m.calledByFunction) {
|
|
2495
|
-
const caller = m.calledByFunction.toLowerCase();
|
|
2496
|
-
const calledBy = doc.metadata.calledBy;
|
|
2497
|
-
if (!calledBy || !calledBy.some(c => c.toLowerCase().includes(caller)))
|
|
2498
|
-
return false;
|
|
2499
|
-
}
|
|
2500
|
-
if (m.dependsOnModule) {
|
|
2501
|
-
const mod = m.dependsOnModule.toLowerCase();
|
|
2502
|
-
const inFile2 = doc.filePath.toLowerCase().includes(mod);
|
|
2503
|
-
const inDep = doc.dependencies.some(d => d.toLowerCase().includes(mod));
|
|
2504
|
-
const inCall = doc.metadata.functionCalls?.some(c => c.toLowerCase().includes(mod)) ?? false;
|
|
2505
|
-
if (!inFile2 && !inDep && !inCall)
|
|
2506
|
-
return false;
|
|
2507
|
-
}
|
|
2508
|
-
if (m.hasUnusedImports) {
|
|
2509
|
-
const unused = doc.metadata.unusedImports;
|
|
2510
|
-
if (!unused || unused.length === 0)
|
|
2511
|
-
return false;
|
|
2512
|
-
}
|
|
2513
|
-
return true;
|
|
2514
|
-
});
|
|
2515
|
-
}
|
|
2516
|
-
return filtered;
|
|
2517
|
-
}
|
|
2518
|
-
excludeTermsFromResults(results, excludedTerms) {
|
|
2519
|
-
return results.filter(doc => {
|
|
2520
|
-
const searchText = [
|
|
2521
|
-
doc.name, doc.signature, doc.purpose, doc.context,
|
|
2522
|
-
doc.jsDoc?.description, doc.returnType,
|
|
2523
|
-
...(doc.parameters || []).map((p) => `${p.name} ${p.description || ''}`),
|
|
2524
|
-
...doc.dependencies
|
|
2525
|
-
].filter(Boolean).join(' ').toLowerCase();
|
|
2526
|
-
return !excludedTerms.some(term => searchText.includes(term.toLowerCase()));
|
|
2527
|
-
});
|
|
2528
|
-
}
|
|
2529
|
-
// ── Whitelist ───────────────────────────────────────────────────────
|
|
2530
|
-
async initializeDefaultWhitelists() {
|
|
2531
|
-
const defaults = [
|
|
2532
|
-
{ name: 'Date', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
2533
|
-
{ name: 'Error', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
2534
|
-
{ name: 'Array', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
2535
|
-
{ name: 'Map', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
2536
|
-
{ name: 'Set', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
2537
|
-
{ name: 'Promise', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
2538
|
-
{ name: 'RegExp', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
|
|
2539
|
-
{ name: 'URL', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
|
|
2540
|
-
{ name: 'URLSearchParams', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
|
|
2541
|
-
{ name: 'FormData', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
|
|
2542
|
-
{ name: 'Headers', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
|
|
2543
|
-
{ name: 'fs', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['fs', 'node:fs', 'fs/promises'], addedBy: 'system', addedAt: new Date() },
|
|
2544
|
-
{ name: 'path', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['path', 'node:path'], addedBy: 'system', addedAt: new Date() },
|
|
2545
|
-
{ name: 'crypto', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['crypto', 'node:crypto'], addedBy: 'system', addedAt: new Date() },
|
|
2546
|
-
{ name: 'NextResponse', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
|
|
2547
|
-
{ name: 'NextRequest', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
|
|
2548
|
-
{ name: 'Response', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
|
|
2549
|
-
{ name: 'Request', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
|
|
2550
|
-
{ name: 'Component', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
2551
|
-
{ name: 'PureComponent', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
2552
|
-
{ name: 'Fragment', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
2553
|
-
{ name: 'StrictMode', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
2554
|
-
{ name: 'Suspense', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
|
|
2555
|
-
{ name: 'Pool', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
|
|
2556
|
-
{ name: 'Client', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
|
|
2557
|
-
{ name: 'MongoClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['mongodb'], addedBy: 'system', addedAt: new Date() },
|
|
2558
|
-
{ name: 'PrismaClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['@prisma/client'], addedBy: 'system', addedAt: new Date() },
|
|
2559
|
-
{ name: 'StackServerApp', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
|
|
2560
|
-
{ name: 'StackClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
|
|
2561
|
-
{ name: 'ClerkProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@clerk/nextjs'], addedBy: 'system', addedAt: new Date() },
|
|
2562
|
-
{ name: 'Auth0Provider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@auth0/nextjs-auth0'], addedBy: 'system', addedAt: new Date() },
|
|
2563
|
-
{ name: 'Axios', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', patterns: ['axios'], addedBy: 'system', addedAt: new Date() },
|
|
2564
|
-
{ name: 'HttpClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', addedBy: 'system', addedAt: new Date() },
|
|
2565
|
-
{ name: 'Router', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
|
|
2566
|
-
{ name: 'Application', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
|
|
2567
|
-
{ name: 'TestingModule', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@nestjs/testing'], addedBy: 'system', addedAt: new Date() },
|
|
2568
|
-
{ name: 'MockedProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@apollo/client/testing'], addedBy: 'system', addedAt: new Date() },
|
|
2569
|
-
{ name: 'EventEmitter', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['events', 'node:events'], addedBy: 'system', addedAt: new Date() },
|
|
2570
|
-
{ name: 'Readable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
|
|
2571
|
-
{ name: 'Writable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
|
|
2572
|
-
{ name: 'Transform', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
|
|
2573
|
-
{ name: 'Buffer', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', addedBy: 'system', addedAt: new Date() },
|
|
2574
|
-
];
|
|
2575
|
-
const insert = this.db.prepare(`INSERT INTO whitelist (name, type, status, category, description, patterns, added_by, added_at, metadata_json)
|
|
2576
|
-
VALUES (@name, @type, @status, @category, @description, @patterns, @added_by, @added_at, @metadata_json)`);
|
|
2577
|
-
for (const entry of defaults) {
|
|
2578
|
-
insert.run({
|
|
2579
|
-
name: entry.name,
|
|
2580
|
-
type: entry.type,
|
|
2581
|
-
status: entry.status,
|
|
2582
|
-
category: entry.category ?? null,
|
|
2583
|
-
description: entry.description ?? null,
|
|
2584
|
-
patterns: JSON.stringify(entry.patterns ?? []),
|
|
2585
|
-
added_by: entry.addedBy ?? 'system',
|
|
2586
|
-
added_at: (entry.addedAt ?? new Date()).toISOString(),
|
|
2587
|
-
metadata_json: '{}',
|
|
2588
|
-
});
|
|
2589
|
-
}
|
|
2590
|
-
}
|
|
2591
|
-
async getWhitelist(type, status) {
|
|
2592
|
-
this.ensureInitialized();
|
|
2593
|
-
const rows = this.whitelistAdapter.find({
|
|
2594
|
-
...(type ? { type } : {}),
|
|
2595
|
-
...(status ? { status } : {}),
|
|
2596
|
-
});
|
|
2597
|
-
return rows.map((r) => ({
|
|
2598
|
-
name: r.name,
|
|
2599
|
-
type: r.type,
|
|
2600
|
-
status: r.status,
|
|
2601
|
-
category: r.category,
|
|
2602
|
-
description: r.description,
|
|
2603
|
-
patterns: tryParseJson(r.patterns),
|
|
2604
|
-
addedBy: r.added_by ?? r.addedBy,
|
|
2605
|
-
addedAt: new Date(r.added_at ?? r.addedAt),
|
|
2606
|
-
updatedAt: r.updated_at ? new Date(r.updated_at) : undefined,
|
|
2607
|
-
metadata: tryParseJson(r.metadata_json),
|
|
2608
|
-
}));
|
|
2609
|
-
}
|
|
2610
|
-
async addWhitelistEntry(entry) {
|
|
2611
|
-
this.ensureInitialized();
|
|
2612
|
-
const row = this.whitelistAdapter.insert({
|
|
2613
|
-
name: entry.name,
|
|
2614
|
-
type: entry.type,
|
|
2615
|
-
status: entry.status ?? WhitelistStatus.Active,
|
|
2616
|
-
category: entry.category ?? null,
|
|
2617
|
-
description: entry.description ?? null,
|
|
2618
|
-
patterns: JSON.stringify(entry.patterns ?? []),
|
|
2619
|
-
added_by: entry.addedBy ?? 'user',
|
|
2620
|
-
added_at: new Date().toISOString(),
|
|
2621
|
-
metadata_json: '{}',
|
|
2622
|
-
});
|
|
2623
|
-
return {
|
|
2624
|
-
name: row.name,
|
|
2625
|
-
type: row.type,
|
|
2626
|
-
status: row.status,
|
|
2627
|
-
category: row.category,
|
|
2628
|
-
description: row.description,
|
|
2629
|
-
patterns: tryParseJson(row.patterns),
|
|
2630
|
-
addedBy: row.added_by,
|
|
2631
|
-
addedAt: new Date(row.added_at),
|
|
2632
|
-
};
|
|
2633
|
-
}
|
|
2634
|
-
async updateWhitelistStatus(name, status) {
|
|
2635
|
-
this.ensureInitialized();
|
|
2636
|
-
this.db.prepare('UPDATE whitelist SET status = ?, updated_at = ? WHERE name = ?')
|
|
2637
|
-
.run(status, new Date().toISOString(), name);
|
|
2638
|
-
}
|
|
2639
|
-
isWhitelisted(name, type) {
|
|
2640
|
-
if (!this.isInitialized)
|
|
2641
|
-
return false;
|
|
2642
|
-
const rows = this.db.prepare('SELECT name, patterns FROM whitelist WHERE type = ? AND status = ?').all(type, WhitelistStatus.Active);
|
|
2643
|
-
return rows.some(entry => {
|
|
2644
|
-
if (entry.name === name)
|
|
2645
|
-
return true;
|
|
2646
|
-
const patterns = tryParseJson(entry.patterns);
|
|
2647
|
-
if (patterns && Array.isArray(patterns)) {
|
|
2648
|
-
return patterns.some((p) => {
|
|
2649
|
-
if (p.includes('*')) {
|
|
2650
|
-
const regex = new RegExp(p.replace(/\*/g, '.*'));
|
|
2651
|
-
return regex.test(name);
|
|
2652
|
-
}
|
|
2653
|
-
return p === name;
|
|
2654
|
-
});
|
|
2655
|
-
}
|
|
2656
|
-
return false;
|
|
2657
|
-
});
|
|
2658
|
-
}
|
|
2659
|
-
async detectWhitelistCandidates() {
|
|
2660
|
-
return [];
|
|
2661
|
-
}
|
|
2662
|
-
// ── Audit results ───────────────────────────────────────────────────
|
|
2663
|
-
async storeAuditResults(auditResult, projectPath) {
|
|
2664
|
-
this.ensureInitialized();
|
|
2665
|
-
const auditId = `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2666
|
-
this.db.prepare(`INSERT INTO audit_results (audit_id, timestamp, project_path, summary_json, analyzer_results_json, violations_json, recommendations_json, metadata_json, expires_at)
|
|
2667
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(auditId, new Date().toISOString(), projectPath, JSON.stringify(auditResult.summary ?? {}), JSON.stringify(auditResult.analyzerResults ?? auditResult.results ?? {}), JSON.stringify(auditResult.violations ?? null), JSON.stringify(auditResult.recommendations ?? null), JSON.stringify(auditResult.metadata ?? {}), new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString());
|
|
2668
|
-
this.cleanupExpiredAudits();
|
|
2669
|
-
return auditId;
|
|
2670
|
-
}
|
|
2671
|
-
async getAuditResults(auditId) {
|
|
2672
|
-
this.ensureInitialized();
|
|
2673
|
-
const row = this.db.prepare('SELECT * FROM audit_results WHERE audit_id = ?').get(auditId);
|
|
2674
|
-
if (!row)
|
|
2675
|
-
return null;
|
|
2676
|
-
if (new Date(row.expires_at) <= new Date()) {
|
|
2677
|
-
this.db.prepare('DELETE FROM audit_results WHERE audit_id = ?').run(auditId);
|
|
2678
|
-
return null;
|
|
2679
|
-
}
|
|
2680
|
-
return {
|
|
2681
|
-
auditId: row.audit_id,
|
|
2682
|
-
timestamp: new Date(row.timestamp),
|
|
2683
|
-
projectPath: row.project_path,
|
|
2684
|
-
summary: tryParseJson(row.summary_json),
|
|
2685
|
-
analyzerResults: tryParseJson(row.analyzer_results_json),
|
|
2686
|
-
violations: tryParseJson(row.violations_json),
|
|
2687
|
-
recommendations: tryParseJson(row.recommendations_json),
|
|
2688
|
-
metadata: tryParseJson(row.metadata_json),
|
|
2689
|
-
expiresAt: new Date(row.expires_at),
|
|
2690
|
-
};
|
|
2691
|
-
}
|
|
2692
|
-
async getMostRecentAuditResults(projectPath, resultScope) {
|
|
2693
|
-
this.ensureInitialized();
|
|
2694
|
-
const now = new Date().toISOString();
|
|
2695
|
-
let row;
|
|
2696
|
-
const scopeFilter = resultScope
|
|
2697
|
-
? "AND json_extract(metadata_json, '$.scope') = ?"
|
|
2698
|
-
: '';
|
|
2699
|
-
if (projectPath) {
|
|
2700
|
-
const params = [projectPath, now];
|
|
2701
|
-
if (resultScope)
|
|
2702
|
-
params.push(resultScope);
|
|
2703
|
-
row = this.db.prepare(`SELECT * FROM audit_results WHERE project_path = ? AND expires_at > ? ${scopeFilter} ORDER BY timestamp DESC, rowid DESC LIMIT 1`).get(...params);
|
|
2704
|
-
}
|
|
2705
|
-
else {
|
|
2706
|
-
const params = [now];
|
|
2707
|
-
if (resultScope)
|
|
2708
|
-
params.push(resultScope);
|
|
2709
|
-
row = this.db.prepare(`SELECT * FROM audit_results WHERE expires_at > ? ${scopeFilter} ORDER BY timestamp DESC, rowid DESC LIMIT 1`).get(...params);
|
|
2710
|
-
}
|
|
2711
|
-
if (!row)
|
|
2712
|
-
return null;
|
|
2713
|
-
return {
|
|
2714
|
-
auditId: row.audit_id,
|
|
2715
|
-
timestamp: new Date(row.timestamp),
|
|
2716
|
-
projectPath: row.project_path,
|
|
2717
|
-
summary: tryParseJson(row.summary_json),
|
|
2718
|
-
analyzerResults: tryParseJson(row.analyzer_results_json),
|
|
2719
|
-
violations: tryParseJson(row.violations_json),
|
|
2720
|
-
recommendations: tryParseJson(row.recommendations_json),
|
|
2721
|
-
metadata: tryParseJson(row.metadata_json),
|
|
2722
|
-
expiresAt: new Date(row.expires_at),
|
|
2723
|
-
};
|
|
2724
|
-
}
|
|
2725
|
-
hasOpenTaskByFingerprint(fingerprint) {
|
|
2726
|
-
if (!fingerprint)
|
|
2727
|
-
return false;
|
|
2728
|
-
return this.getTaskRepository().findOpenByFingerprint(fingerprint).length > 0;
|
|
2729
|
-
}
|
|
2730
|
-
cleanupExpiredAudits() {
|
|
2731
|
-
this.db.prepare('DELETE FROM audit_results WHERE expires_at < ?').run(new Date().toISOString());
|
|
2732
|
-
}
|
|
2733
|
-
// ── Analyzer configs ────────────────────────────────────────────────
|
|
2734
|
-
async storeAnalyzerConfig(analyzerName, config, options) {
|
|
2735
|
-
this.ensureInitialized();
|
|
2736
|
-
const projectPath = options?.projectPath ?? null;
|
|
2737
|
-
const isGlobal = options?.isGlobal ?? true;
|
|
2738
|
-
const existing = this.db.prepare('SELECT id FROM analyzer_configs WHERE analyzer_name = ? AND COALESCE(project_path, \'__global__\') = ? AND is_global = ?').get(analyzerName, projectPath ?? '__global__', isGlobal ? 1 : 0);
|
|
2739
|
-
if (existing) {
|
|
2740
|
-
this.db.prepare('UPDATE analyzer_configs SET config_json = ?, updated_at = ?, metadata_json = ? WHERE id = ?').run(JSON.stringify(config), new Date().toISOString(), JSON.stringify(options?.metadata ?? {}), existing.id);
|
|
2741
|
-
}
|
|
2742
|
-
else {
|
|
2743
|
-
this.db.prepare(`INSERT INTO analyzer_configs (analyzer_name, project_path, is_global, config_json, created_by, created_at, updated_at, metadata_json)
|
|
2744
|
-
VALUES (?, ?, ?, ?, 'user', ?, ?, ?)`).run(analyzerName, projectPath, isGlobal ? 1 : 0, JSON.stringify(config), new Date().toISOString(), new Date().toISOString(), JSON.stringify(options?.metadata ?? {}));
|
|
2745
|
-
}
|
|
2746
|
-
return analyzerName;
|
|
2747
|
-
}
|
|
2748
|
-
async getAnalyzerConfig(analyzerName, projectPath) {
|
|
2749
|
-
this.ensureInitialized();
|
|
2750
|
-
if (projectPath) {
|
|
2751
|
-
const row = this.db.prepare('SELECT config_json FROM analyzer_configs WHERE analyzer_name = ? AND project_path = ? AND is_global = 0').get(analyzerName, projectPath);
|
|
2752
|
-
if (row)
|
|
2753
|
-
return tryParseJson(row.config_json);
|
|
2754
|
-
}
|
|
2755
|
-
const globalRow = this.db.prepare('SELECT config_json FROM analyzer_configs WHERE analyzer_name = ? AND is_global = 1').get(analyzerName);
|
|
2756
|
-
return globalRow ? tryParseJson(globalRow.config_json) : null;
|
|
2757
|
-
}
|
|
2758
|
-
async getAllAnalyzerConfigs(projectPath) {
|
|
2759
|
-
this.ensureInitialized();
|
|
2760
|
-
const configs = {};
|
|
2761
|
-
const globals = this.db.prepare('SELECT analyzer_name, config_json FROM analyzer_configs WHERE is_global = 1').all();
|
|
2762
|
-
for (const c of globals) {
|
|
2763
|
-
configs[c.analyzer_name] = tryParseJson(c.config_json);
|
|
2764
|
-
}
|
|
2765
|
-
if (projectPath) {
|
|
2766
|
-
const locals = this.db.prepare('SELECT analyzer_name, config_json FROM analyzer_configs WHERE project_path = ? AND is_global = 0').all(projectPath);
|
|
2767
|
-
for (const c of locals) {
|
|
2768
|
-
configs[c.analyzer_name] = tryParseJson(c.config_json);
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
return configs;
|
|
2772
|
-
}
|
|
2773
|
-
async deleteAnalyzerConfig(analyzerName, options) {
|
|
2774
|
-
this.ensureInitialized();
|
|
2775
|
-
const result = this.db.prepare('DELETE FROM analyzer_configs WHERE analyzer_name = ? AND COALESCE(project_path, \'__global__\') = ? AND is_global = ?').run(analyzerName, options?.projectPath ?? '__global__', (options?.isGlobal ?? !options?.projectPath) ? 1 : 0);
|
|
2776
|
-
return result.changes > 0;
|
|
2777
|
-
}
|
|
2778
|
-
async resetAnalyzerConfigs(projectPath) {
|
|
2779
|
-
this.ensureInitialized();
|
|
2780
|
-
if (projectPath) {
|
|
2781
|
-
this.db.prepare('DELETE FROM analyzer_configs WHERE project_path = ? AND is_global = 0').run(projectPath);
|
|
2782
|
-
}
|
|
2783
|
-
else {
|
|
2784
|
-
this.db.prepare('DELETE FROM analyzer_configs').run();
|
|
2785
|
-
}
|
|
2786
|
-
}
|
|
2787
|
-
// ── Code maps ───────────────────────────────────────────────────────
|
|
2788
|
-
async storeCodeMapSection(mapId, sectionType, content, metadata) {
|
|
2789
|
-
this.ensureInitialized();
|
|
2790
|
-
this.db.prepare(`INSERT OR REPLACE INTO code_maps (map_id, section_type, content, metadata_json, timestamp, size)
|
|
2791
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(mapId, sectionType, content, JSON.stringify(metadata ?? {}), new Date().toISOString(), content.length);
|
|
2792
|
-
}
|
|
2793
|
-
async getCodeMapSection(mapId, sectionType) {
|
|
2794
|
-
this.ensureInitialized();
|
|
2795
|
-
const row = this.db.prepare('SELECT content, metadata_json FROM code_maps WHERE map_id = ? AND section_type = ?')
|
|
2796
|
-
.get(mapId, sectionType);
|
|
2797
|
-
return row ? { content: row.content, metadata: tryParseJson(row.metadata_json) ?? {} } : null;
|
|
2798
|
-
}
|
|
2799
|
-
async listCodeMapSections(mapId) {
|
|
2800
|
-
this.ensureInitialized();
|
|
2801
|
-
const rows = this.db.prepare('SELECT section_type, size, timestamp FROM code_maps WHERE map_id = ?')
|
|
2802
|
-
.all(mapId);
|
|
2803
|
-
return rows.map((r) => ({ sectionType: r.section_type, size: r.size ?? 0, timestamp: new Date(r.timestamp) }));
|
|
2804
|
-
}
|
|
2805
|
-
async clearOldCodeMaps(olderThanHours = 24) {
|
|
2806
|
-
this.ensureInitialized();
|
|
2807
|
-
const cutoff = new Date(Date.now() - olderThanHours * 60 * 60 * 1000).toISOString();
|
|
2808
|
-
const result = this.db.prepare('DELETE FROM code_maps WHERE timestamp < ?').run(cutoff);
|
|
2809
|
-
return result.changes;
|
|
2810
|
-
}
|
|
2811
|
-
async deleteCodeMap(mapId) {
|
|
2812
|
-
this.ensureInitialized();
|
|
2813
|
-
const result = this.db.prepare('DELETE FROM code_maps WHERE map_id = ?').run(mapId);
|
|
2814
|
-
return result.changes;
|
|
2815
|
-
}
|
|
2816
|
-
// ── Schema management ───────────────────────────────────────────────
|
|
2817
|
-
async storeSchema(schema) {
|
|
2818
|
-
this.ensureInitialized();
|
|
2819
|
-
const schemaId = `schema_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2820
|
-
const metadata = {
|
|
2821
|
-
schemaId,
|
|
2822
|
-
schemaName: schema.name,
|
|
2823
|
-
indexedAt: new Date(),
|
|
2824
|
-
tableCount: schema.databases.reduce((acc, db) => acc + db.tables.length, 0),
|
|
2825
|
-
relationshipCount: schema.databases.reduce((acc, db) => acc + (db.relationships?.length || 0), 0),
|
|
2826
|
-
usagePatterns: [],
|
|
2827
|
-
discoveredPatterns: [],
|
|
2828
|
-
violations: []
|
|
2829
|
-
};
|
|
2830
|
-
this.db.prepare('INSERT INTO schema_definitions (schema_id, schema_name, schema_json, metadata_json, indexed_at) VALUES (?, ?, ?, ?, ?)').run(schemaId, schema.name, JSON.stringify(schema), JSON.stringify(metadata), new Date().toISOString());
|
|
2831
|
-
return schemaId;
|
|
2832
|
-
}
|
|
2833
|
-
async getSchema(schemaId) {
|
|
2834
|
-
this.ensureInitialized();
|
|
2835
|
-
const row = this.db.prepare('SELECT schema_json FROM schema_definitions WHERE schema_id = ?').get(schemaId);
|
|
2836
|
-
return row ? tryParseJson(row.schema_json) : null;
|
|
2837
|
-
}
|
|
2838
|
-
async getAllSchemas() {
|
|
2839
|
-
this.ensureInitialized();
|
|
2840
|
-
const rows = this.db.prepare('SELECT schema_id, schema_json, metadata_json FROM schema_definitions').all();
|
|
2841
|
-
return rows.map((r) => ({
|
|
2842
|
-
schemaId: r.schema_id,
|
|
2843
|
-
metadata: tryParseJson(r.metadata_json) ?? {},
|
|
2844
|
-
schema: tryParseJson(r.schema_json),
|
|
2845
|
-
}));
|
|
2846
|
-
}
|
|
2847
|
-
async deleteSchema(schemaId) {
|
|
2848
|
-
this.ensureInitialized();
|
|
2849
|
-
this.db.prepare('DELETE FROM schema_usage WHERE schema_id = ?').run(schemaId);
|
|
2850
|
-
const result = this.db.prepare('DELETE FROM schema_definitions WHERE schema_id = ?').run(schemaId);
|
|
2851
|
-
return result.changes > 0;
|
|
2852
|
-
}
|
|
2853
|
-
async recordSchemaUsage(usage, schemaId) {
|
|
2854
|
-
this.ensureInitialized();
|
|
2855
|
-
const existing = this.db.prepare('SELECT id FROM schema_usage WHERE table_name = ? AND file_path = ? AND function_start_line IS ? AND function_start_column IS ? AND line = ?').get(usage.tableName, usage.filePath, usage.functionStartLine ?? null, usage.functionStartColumn ?? null, usage.line);
|
|
2856
|
-
if (existing) {
|
|
2857
|
-
this.db.prepare('UPDATE schema_usage SET schema_id = ?, function_name = ?, usage_type = ?, "column" = ?, raw_query = ?, parameters = ?, origin = ?, recorded_at = ? WHERE id = ?').run(schemaId ?? 'default', usage.functionName ?? null, usage.usageType, usage.column ?? null, usage.rawQuery ?? null, JSON.stringify(usage.parameters ?? []), usage.origin ?? null, new Date().toISOString(), existing.id);
|
|
2858
|
-
}
|
|
2859
|
-
else {
|
|
2860
|
-
this.db.prepare('INSERT INTO schema_usage (schema_id, table_name, file_path, function_name, function_start_line, function_start_column, usage_type, line, "column", raw_query, parameters, origin, recorded_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(schemaId ?? 'default', usage.tableName, usage.filePath, usage.functionName ?? null, usage.functionStartLine ?? null, usage.functionStartColumn ?? null, usage.usageType, usage.line ?? null, usage.column ?? null, usage.rawQuery ?? null, JSON.stringify(usage.parameters ?? []), usage.origin ?? null, new Date().toISOString());
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
/**
|
|
2864
|
-
* Clear all schema_usage entries for a given file path.
|
|
2865
|
-
* Used for idempotent per-file writes during indexing — stale entries
|
|
2866
|
-
* from a previous scan are removed before fresh references are inserted.
|
|
866
|
+
* Search functions and optionally annotate results with schema usage context.
|
|
867
|
+
*
|
|
868
|
+
* @param options - Search options, including includeSchemaUsage.
|
|
869
|
+
* @returns The search result, with schema context when requested.
|
|
2867
870
|
*/
|
|
2868
|
-
|
|
2869
|
-
this.
|
|
2870
|
-
this.db.prepare('DELETE FROM schema_usage WHERE file_path = ?').run(filePath);
|
|
2871
|
-
}
|
|
2872
|
-
async getSchemaUsage(options = {}) {
|
|
2873
|
-
this.ensureInitialized();
|
|
2874
|
-
const clauses = [];
|
|
2875
|
-
const params = [];
|
|
2876
|
-
if (options.schemaId) {
|
|
2877
|
-
clauses.push('schema_id = ?');
|
|
2878
|
-
params.push(options.schemaId);
|
|
2879
|
-
}
|
|
2880
|
-
if (options.tableName) {
|
|
2881
|
-
clauses.push('table_name = ?');
|
|
2882
|
-
params.push(options.tableName);
|
|
2883
|
-
}
|
|
2884
|
-
if (options.filePath) {
|
|
2885
|
-
clauses.push('file_path = ?');
|
|
2886
|
-
params.push(options.filePath);
|
|
2887
|
-
}
|
|
2888
|
-
if (options.functionName) {
|
|
2889
|
-
clauses.push('function_name = ?');
|
|
2890
|
-
params.push(options.functionName);
|
|
2891
|
-
}
|
|
2892
|
-
if (options.usageType) {
|
|
2893
|
-
clauses.push('usage_type = ?');
|
|
2894
|
-
params.push(options.usageType);
|
|
2895
|
-
}
|
|
2896
|
-
const sql = 'SELECT * FROM schema_usage' + (clauses.length ? ' WHERE ' + clauses.join(' AND ') : '');
|
|
2897
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
2898
|
-
return rows.map((r) => ({
|
|
2899
|
-
tableName: r.table_name,
|
|
2900
|
-
filePath: r.file_path,
|
|
2901
|
-
functionName: r.function_name,
|
|
2902
|
-
functionStartLine: r.function_start_line,
|
|
2903
|
-
functionStartColumn: r.function_start_column,
|
|
2904
|
-
usageType: r.usage_type,
|
|
2905
|
-
line: r.line,
|
|
2906
|
-
column: r.column,
|
|
2907
|
-
rawQuery: r.raw_query,
|
|
2908
|
-
parameters: tryParseJson(r.parameters) ?? [],
|
|
2909
|
-
}));
|
|
2910
|
-
}
|
|
2911
|
-
async findFunctionsUsingTable(tableName) {
|
|
2912
|
-
this.ensureInitialized();
|
|
2913
|
-
const rows = this.db.prepare('SELECT function_name, file_path, usage_type, line FROM schema_usage WHERE table_name = ?').all(tableName);
|
|
2914
|
-
return rows.map((r) => ({
|
|
2915
|
-
functionName: r.function_name,
|
|
2916
|
-
filePath: r.file_path,
|
|
2917
|
-
usageType: r.usage_type,
|
|
2918
|
-
line: r.line,
|
|
2919
|
-
}));
|
|
2920
|
-
}
|
|
2921
|
-
async getSchemaStats() {
|
|
2922
|
-
this.ensureInitialized();
|
|
2923
|
-
const totalSchemas = this.db.prepare('SELECT COUNT(*) as cnt FROM schema_definitions').get().cnt;
|
|
2924
|
-
const totalUsagePatterns = this.db.prepare('SELECT COUNT(*) as cnt FROM schema_usage').get().cnt;
|
|
2925
|
-
const schemas = this.db.prepare('SELECT schema_json FROM schema_definitions').all();
|
|
2926
|
-
const totalTables = schemas.reduce((acc, s) => {
|
|
2927
|
-
const schema = tryParseJson(s.schema_json);
|
|
2928
|
-
return acc + (schema?.databases?.reduce((dbAcc, db) => dbAcc + (db.tables?.length ?? 0), 0) ?? 0);
|
|
2929
|
-
}, 0);
|
|
2930
|
-
const mostUsedTables = this.db.prepare('SELECT table_name as tableName, COUNT(*) as usageCount FROM schema_usage GROUP BY table_name ORDER BY usageCount DESC LIMIT 10').all();
|
|
2931
|
-
const usageByType = {};
|
|
2932
|
-
const typeRows = this.db.prepare('SELECT usage_type, COUNT(*) as cnt FROM schema_usage GROUP BY usage_type').all();
|
|
2933
|
-
for (const r of typeRows) {
|
|
2934
|
-
usageByType[r.usage_type] = r.cnt;
|
|
2935
|
-
}
|
|
2936
|
-
return { totalSchemas, totalTables, totalUsagePatterns, mostUsedTables, usageByType };
|
|
2937
|
-
}
|
|
2938
|
-
async searchWithSchemaContext(query, options = {}) {
|
|
2939
|
-
const searchResult = await this.searchFunctions(options);
|
|
871
|
+
async searchWithSchemaContext(options = {}) {
|
|
872
|
+
const searchResult = await this.search.searchFunctions(options);
|
|
2940
873
|
if (!options.includeSchemaUsage)
|
|
2941
874
|
return searchResult;
|
|
2942
875
|
const enhancedFunctions = await Promise.all(searchResult.functions.map(async (func) => {
|
|
2943
|
-
const schemaUsage = await this.getSchemaUsage({
|
|
876
|
+
const schemaUsage = await this.schema.getSchemaUsage({
|
|
2944
877
|
filePath: func.filePath,
|
|
2945
878
|
functionName: func.name
|
|
2946
879
|
});
|
|
@@ -2957,322 +890,5 @@ export class CodeIndexDB {
|
|
|
2957
890
|
})))];
|
|
2958
891
|
return { ...searchResult, functions: enhancedFunctions, schemaContext };
|
|
2959
892
|
}
|
|
2960
|
-
// ── Project tasks ───────────────────────────────────────────────────
|
|
2961
|
-
getTaskRepository() {
|
|
2962
|
-
this.ensureInitialized();
|
|
2963
|
-
if (!this.taskRepository) {
|
|
2964
|
-
this.taskRepository = new ProjectTaskRepository(() => this.tasksAdapter, () => { });
|
|
2965
|
-
}
|
|
2966
|
-
return this.taskRepository;
|
|
2967
|
-
}
|
|
2968
|
-
async createProjectTask(input) {
|
|
2969
|
-
return this.getTaskRepository().create(input);
|
|
2970
|
-
}
|
|
2971
|
-
async getProjectTask(taskId) {
|
|
2972
|
-
return this.getTaskRepository().getById(taskId);
|
|
2973
|
-
}
|
|
2974
|
-
async listProjectTasks(projectPath, options) {
|
|
2975
|
-
return this.getTaskRepository().list(projectPath, options);
|
|
2976
|
-
}
|
|
2977
|
-
async listProjectTasksTree(projectPath, options) {
|
|
2978
|
-
return this.getTaskRepository().listTree(projectPath, options);
|
|
2979
|
-
}
|
|
2980
|
-
async listActionableProjectTasks(projectPath, options) {
|
|
2981
|
-
return this.getTaskRepository().listActionable(projectPath, options);
|
|
2982
|
-
}
|
|
2983
|
-
async completeProjectTask(taskId) {
|
|
2984
|
-
return this.getTaskRepository().complete(taskId);
|
|
2985
|
-
}
|
|
2986
|
-
async updateProjectTask(taskId, patch) {
|
|
2987
|
-
return this.getTaskRepository().update(taskId, patch);
|
|
2988
|
-
}
|
|
2989
|
-
async deleteProjectTask(taskId, mode) {
|
|
2990
|
-
return this.getTaskRepository().delete(taskId, mode);
|
|
2991
|
-
}
|
|
2992
|
-
// ── Meta key-value store ─────────────────────────────────────────────
|
|
2993
|
-
/** Upsert a key-value pair in the meta table. */
|
|
2994
|
-
setMeta(key, value) {
|
|
2995
|
-
this.ensureInitialized();
|
|
2996
|
-
this.db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)
|
|
2997
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(key, value);
|
|
2998
|
-
}
|
|
2999
|
-
/** Retrieve a value from the meta table, or null if absent. */
|
|
3000
|
-
getMeta(key) {
|
|
3001
|
-
this.ensureInitialized();
|
|
3002
|
-
const row = this.db.prepare('SELECT value FROM meta WHERE key = ?').get(key);
|
|
3003
|
-
return row?.value ?? null;
|
|
3004
|
-
}
|
|
3005
|
-
// ── Provenance storage (Spec-21 R2 cross-file) ─────────────────────
|
|
3006
|
-
/** Store per-file provenance context in the meta table. */
|
|
3007
|
-
storeFileProvenance(filePath, provenanceData) {
|
|
3008
|
-
const key = `provenance:${filePath}`;
|
|
3009
|
-
this.setMeta(key, JSON.stringify(provenanceData));
|
|
3010
|
-
}
|
|
3011
|
-
/** Retrieve per-file provenance context, or null if not stored. */
|
|
3012
|
-
getFileProvenance(filePath) {
|
|
3013
|
-
const raw = this.getMeta(`provenance:${filePath}`);
|
|
3014
|
-
if (!raw)
|
|
3015
|
-
return null;
|
|
3016
|
-
try {
|
|
3017
|
-
return JSON.parse(raw);
|
|
3018
|
-
}
|
|
3019
|
-
catch {
|
|
3020
|
-
return null;
|
|
3021
|
-
}
|
|
3022
|
-
}
|
|
3023
|
-
/** Store the inferred receiver set as a meta record. */
|
|
3024
|
-
storeInferredReceivers(inferred) {
|
|
3025
|
-
this.setMeta('inferred_receivers', JSON.stringify(inferred));
|
|
3026
|
-
}
|
|
3027
|
-
/** Retrieve the inferred receiver set, or null if not stored. */
|
|
3028
|
-
getInferredReceivers() {
|
|
3029
|
-
const raw = this.getMeta('inferred_receivers');
|
|
3030
|
-
if (!raw)
|
|
3031
|
-
return null;
|
|
3032
|
-
try {
|
|
3033
|
-
return JSON.parse(raw);
|
|
3034
|
-
}
|
|
3035
|
-
catch {
|
|
3036
|
-
return null;
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
// ── Coverage data (Spec 15 R4) ──────────────────────────────────────
|
|
3040
|
-
/** Import coverage entries, replacing any existing data for the given basis. */
|
|
3041
|
-
importCoverageData(entries) {
|
|
3042
|
-
this.ensureInitialized();
|
|
3043
|
-
// Group by basis so we only clear one basis at a time
|
|
3044
|
-
const bases = new Set(entries.map(e => e.basis));
|
|
3045
|
-
const clearStmt = this.db.prepare('DELETE FROM coverage_data WHERE basis = ?');
|
|
3046
|
-
for (const basis of bases) {
|
|
3047
|
-
clearStmt.run(basis);
|
|
3048
|
-
}
|
|
3049
|
-
const insert = this.db.prepare(`INSERT OR REPLACE INTO coverage_data (function_name, file_path, line_number, basis, covered, source, imported_at)
|
|
3050
|
-
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`);
|
|
3051
|
-
const tx = this.db.transaction((items) => {
|
|
3052
|
-
for (const e of items) {
|
|
3053
|
-
insert.run(e.functionName, e.filePath, e.lineNumber, e.basis, e.covered ? 1 : 0, e.source ?? null);
|
|
3054
|
-
}
|
|
3055
|
-
});
|
|
3056
|
-
tx(entries);
|
|
3057
|
-
}
|
|
3058
|
-
/** Get all coverage entries for a given basis. */
|
|
3059
|
-
getCoverageByBasis(basis) {
|
|
3060
|
-
this.ensureInitialized();
|
|
3061
|
-
const rows = this.db.prepare('SELECT * FROM coverage_data WHERE basis = ?').all(basis);
|
|
3062
|
-
return rows.map((r) => ({
|
|
3063
|
-
functionName: r.function_name,
|
|
3064
|
-
filePath: r.file_path,
|
|
3065
|
-
lineNumber: r.line_number,
|
|
3066
|
-
basis: r.basis,
|
|
3067
|
-
covered: r.covered === 1,
|
|
3068
|
-
source: r.source,
|
|
3069
|
-
importedAt: r.imported_at,
|
|
3070
|
-
}));
|
|
3071
|
-
}
|
|
3072
|
-
/** Clear coverage data, optionally scoped to a single basis. */
|
|
3073
|
-
clearCoverageData(basis) {
|
|
3074
|
-
this.ensureInitialized();
|
|
3075
|
-
if (basis) {
|
|
3076
|
-
this.db.prepare('DELETE FROM coverage_data WHERE basis = ?').run(basis);
|
|
3077
|
-
}
|
|
3078
|
-
else {
|
|
3079
|
-
this.db.prepare('DELETE FROM coverage_data').run();
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
/** Check if measured coverage data is stale (older than the last full sync). */
|
|
3083
|
-
isCoverageStale() {
|
|
3084
|
-
this.ensureInitialized();
|
|
3085
|
-
const lastSync = this.getMeta('last_full_sync_timestamp');
|
|
3086
|
-
if (!lastSync)
|
|
3087
|
-
return false;
|
|
3088
|
-
const staleRow = this.db.prepare(`SELECT COUNT(*) as cnt FROM coverage_data
|
|
3089
|
-
WHERE basis = 'measured' AND imported_at < ?`).get(lastSync);
|
|
3090
|
-
return (staleRow?.cnt ?? 0) > 0;
|
|
3091
|
-
}
|
|
3092
|
-
/** Get coverage rate by risk decile for the coverage report. */
|
|
3093
|
-
getCoverageByRiskDecile(decileCount = 10) {
|
|
3094
|
-
this.ensureInitialized();
|
|
3095
|
-
// Rank functions by risk score from hotspot_scores, join with coverage_data
|
|
3096
|
-
const rows = this.db.prepare(`
|
|
3097
|
-
WITH ranked AS (
|
|
3098
|
-
SELECT
|
|
3099
|
-
f.name,
|
|
3100
|
-
f.file_path,
|
|
3101
|
-
f.line_number,
|
|
3102
|
-
COALESCE(hs.score, 0.0) as risk_score,
|
|
3103
|
-
NTILE(?) OVER (ORDER BY COALESCE(hs.score, 0.0) DESC) as decile
|
|
3104
|
-
FROM functions f
|
|
3105
|
-
LEFT JOIN hotspot_scores hs ON hs.target = (f.file_path || ':' || f.name)
|
|
3106
|
-
AND hs.type = 'function'
|
|
3107
|
-
WHERE f.is_exported = 1
|
|
3108
|
-
),
|
|
3109
|
-
coverage AS (
|
|
3110
|
-
SELECT DISTINCT function_name, file_path FROM coverage_data
|
|
3111
|
-
WHERE covered = 1
|
|
3112
|
-
)
|
|
3113
|
-
SELECT
|
|
3114
|
-
r.decile,
|
|
3115
|
-
COUNT(*) as total,
|
|
3116
|
-
SUM(CASE WHEN c.function_name IS NOT NULL THEN 1 ELSE 0 END) as covered
|
|
3117
|
-
FROM ranked r
|
|
3118
|
-
LEFT JOIN coverage c ON c.function_name = r.name AND c.file_path = r.file_path
|
|
3119
|
-
GROUP BY r.decile
|
|
3120
|
-
ORDER BY r.decile
|
|
3121
|
-
`).all(decileCount);
|
|
3122
|
-
return rows.map((r) => ({
|
|
3123
|
-
decile: r.decile,
|
|
3124
|
-
covered: r.covered,
|
|
3125
|
-
total: r.total,
|
|
3126
|
-
rate: r.total > 0 ? r.covered / r.total : 0,
|
|
3127
|
-
}));
|
|
3128
|
-
}
|
|
3129
|
-
/** Get untested functions in the top risk decile. */
|
|
3130
|
-
getUntestedTopDecile(topDecile = 0.1) {
|
|
3131
|
-
this.ensureInitialized();
|
|
3132
|
-
const rows = this.db.prepare(`
|
|
3133
|
-
WITH ranked AS (
|
|
3134
|
-
SELECT
|
|
3135
|
-
f.name,
|
|
3136
|
-
f.file_path,
|
|
3137
|
-
f.line_number,
|
|
3138
|
-
COALESCE(hs.score, 0.0) as risk_score,
|
|
3139
|
-
PERCENT_RANK() OVER (ORDER BY COALESCE(hs.score, 0.0) DESC) as pct
|
|
3140
|
-
FROM functions f
|
|
3141
|
-
LEFT JOIN hotspot_scores hs ON hs.target = (f.file_path || ':' || f.name)
|
|
3142
|
-
AND hs.type = 'function'
|
|
3143
|
-
WHERE f.is_exported = 1
|
|
3144
|
-
),
|
|
3145
|
-
best_coverage AS (
|
|
3146
|
-
SELECT DISTINCT function_name, file_path, basis FROM coverage_data
|
|
3147
|
-
WHERE covered = 1
|
|
3148
|
-
)
|
|
3149
|
-
SELECT
|
|
3150
|
-
r.name,
|
|
3151
|
-
r.file_path,
|
|
3152
|
-
r.line_number,
|
|
3153
|
-
r.risk_score,
|
|
3154
|
-
COALESCE(bc.basis, 'static-reach') as basis
|
|
3155
|
-
FROM ranked r
|
|
3156
|
-
LEFT JOIN best_coverage bc ON bc.function_name = r.name AND bc.file_path = r.file_path
|
|
3157
|
-
WHERE r.pct <= ?
|
|
3158
|
-
AND bc.basis IS NULL
|
|
3159
|
-
ORDER BY r.risk_score DESC
|
|
3160
|
-
`).all(topDecile);
|
|
3161
|
-
return rows.map((r) => ({
|
|
3162
|
-
functionName: r.name,
|
|
3163
|
-
filePath: r.file_path,
|
|
3164
|
-
lineNumber: r.line_number ?? 1,
|
|
3165
|
-
riskScore: r.risk_score,
|
|
3166
|
-
basis: r.basis,
|
|
3167
|
-
}));
|
|
3168
|
-
}
|
|
3169
|
-
/** Execute a parameterized query and return all rows. */
|
|
3170
|
-
query(sql, params) {
|
|
3171
|
-
this.ensureInitialized();
|
|
3172
|
-
return params?.length
|
|
3173
|
-
? this.db.prepare(sql).all(...params)
|
|
3174
|
-
: this.db.prepare(sql).all();
|
|
3175
|
-
}
|
|
3176
|
-
/** Count rows in a table, with optional WHERE clause. */
|
|
3177
|
-
count(table, where, params) {
|
|
3178
|
-
this.ensureInitialized();
|
|
3179
|
-
assertSqlIdentifier(table, 'table name');
|
|
3180
|
-
const whereClause = where ? ` WHERE ${where}` : '';
|
|
3181
|
-
const row = this.db.prepare(`SELECT COUNT(*) as cnt FROM ${table}${whereClause}`).get(...(params ?? []));
|
|
3182
|
-
return row.cnt;
|
|
3183
|
-
}
|
|
3184
|
-
/** Check if a table has any rows. */
|
|
3185
|
-
tableHasRows(table) {
|
|
3186
|
-
this.ensureInitialized();
|
|
3187
|
-
assertSqlIdentifier(table, 'table name');
|
|
3188
|
-
const row = this.db.prepare(`SELECT 1 FROM ${table} LIMIT 1`).get();
|
|
3189
|
-
return row !== undefined;
|
|
3190
|
-
}
|
|
3191
|
-
/**
|
|
3192
|
-
* Mine codebase conventions from the functions table and upsert into the
|
|
3193
|
-
* conventions table. Content-hash-based skip: stored hash avoids re-mining
|
|
3194
|
-
* when the function corpus hasn't changed since the last mine.
|
|
3195
|
-
*
|
|
3196
|
-
* Called both by deepSync() (after indexing) and by audit runs (so the
|
|
3197
|
-
* conventions analyzer has data to query even without an explicit sync).
|
|
3198
|
-
*/
|
|
3199
|
-
mineAllConventions(projectRoot, getSource) {
|
|
3200
|
-
this.ensureInitialized();
|
|
3201
|
-
const miningConfig = {
|
|
3202
|
-
minCorpus: 20,
|
|
3203
|
-
pairConfidence: 0.9,
|
|
3204
|
-
modeShare: 0.8,
|
|
3205
|
-
maxConventionsPerDomain: 200,
|
|
3206
|
-
};
|
|
3207
|
-
const newHash = computeMinerInputHash(this.db, miningConfig);
|
|
3208
|
-
const oldHashRow = this.db.prepare("SELECT value FROM meta WHERE key = 'conventions_hash'").get();
|
|
3209
|
-
if (!oldHashRow || oldHashRow.value !== newHash) {
|
|
3210
|
-
const conventions = mineConventions(this.db, miningConfig, projectRoot, getSource);
|
|
3211
|
-
const deleteStmt = this.db.prepare('DELETE FROM conventions');
|
|
3212
|
-
const insertStmt = this.db.prepare(`INSERT INTO conventions
|
|
3213
|
-
(domain, rule_id, antecedent, consequent, pattern, directory,
|
|
3214
|
-
file_path, line, support, total_cases, confidence,
|
|
3215
|
-
exemplar_file, exemplar_line, export_kind, hash)
|
|
3216
|
-
VALUES (@domain, @rule_id, @antecedent, @consequent, @pattern,
|
|
3217
|
-
@directory, @file_path, @line, @support, @total_cases,
|
|
3218
|
-
@confidence, @exemplar_file, @exemplar_line, @export_kind, @hash)`);
|
|
3219
|
-
const upsertAll = this.db.transaction(() => {
|
|
3220
|
-
deleteStmt.run();
|
|
3221
|
-
for (const c of conventions) {
|
|
3222
|
-
insertStmt.run({
|
|
3223
|
-
domain: c.domain,
|
|
3224
|
-
rule_id: c.rule_id,
|
|
3225
|
-
antecedent: c.antecedent ?? null,
|
|
3226
|
-
consequent: c.consequent ?? null,
|
|
3227
|
-
pattern: c.pattern ?? null,
|
|
3228
|
-
directory: c.directory ?? null,
|
|
3229
|
-
file_path: c.file_path ?? null,
|
|
3230
|
-
line: c.line ?? null,
|
|
3231
|
-
support: c.support,
|
|
3232
|
-
total_cases: c.total_cases,
|
|
3233
|
-
confidence: c.confidence,
|
|
3234
|
-
exemplar_file: c.exemplar_file ?? null,
|
|
3235
|
-
exemplar_line: c.exemplar_line ?? null,
|
|
3236
|
-
export_kind: c.export_kind ?? null,
|
|
3237
|
-
hash: c.hash ?? null,
|
|
3238
|
-
});
|
|
3239
|
-
}
|
|
3240
|
-
});
|
|
3241
|
-
upsertAll();
|
|
3242
|
-
// Only store the hash when conventions were actually produced.
|
|
3243
|
-
// Storing it on an empty mine would be a poison pill — the hash
|
|
3244
|
-
// would match on every subsequent run, skipping mining forever.
|
|
3245
|
-
if (conventions.length > 0) {
|
|
3246
|
-
this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('conventions_hash', ?)").run(newHash);
|
|
3247
|
-
}
|
|
3248
|
-
}
|
|
3249
|
-
}
|
|
3250
|
-
/** Execute a DML statement (INSERT/UPDATE/DELETE) and return its result. */
|
|
3251
|
-
run(sql, params) {
|
|
3252
|
-
this.ensureInitialized();
|
|
3253
|
-
return params?.length
|
|
3254
|
-
? this.db.prepare(sql).run(...params)
|
|
3255
|
-
: this.db.prepare(sql).run();
|
|
3256
|
-
}
|
|
3257
|
-
/** Execute raw SQL (multi-statement) — for schema setup / bulk operations. */
|
|
3258
|
-
exec(sql) {
|
|
3259
|
-
this.ensureInitialized();
|
|
3260
|
-
this.db.exec(sql);
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
// ── Helpers ────────────────────────────────────────────────────────────
|
|
3264
|
-
function tryParseJson(val) {
|
|
3265
|
-
if (val === null || val === undefined)
|
|
3266
|
-
return null;
|
|
3267
|
-
if (typeof val === 'object')
|
|
3268
|
-
return val;
|
|
3269
|
-
if (typeof val !== 'string')
|
|
3270
|
-
return val;
|
|
3271
|
-
try {
|
|
3272
|
-
return JSON.parse(val);
|
|
3273
|
-
}
|
|
3274
|
-
catch {
|
|
3275
|
-
return val;
|
|
3276
|
-
}
|
|
3277
893
|
}
|
|
3278
894
|
//# sourceMappingURL=codeIndexDB.js.map
|