@mycodemap/mycodemap 0.4.2 → 0.5.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/CHANGELOG.md +105 -3
- package/README.md +192 -53
- package/dist/ai/claude.d.ts +38 -0
- package/dist/ai/claude.d.ts.map +1 -0
- package/dist/ai/claude.js +169 -0
- package/dist/ai/claude.js.map +1 -0
- package/dist/ai/codex.d.ts +38 -0
- package/dist/ai/codex.d.ts.map +1 -0
- package/dist/ai/codex.js +169 -0
- package/dist/ai/codex.js.map +1 -0
- package/dist/ai/factory.d.ts +48 -0
- package/dist/ai/factory.d.ts.map +1 -0
- package/dist/ai/factory.js +95 -0
- package/dist/ai/factory.js.map +1 -0
- package/dist/ai/index.d.ts +12 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +29 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/provider.d.ts +70 -0
- package/dist/ai/provider.d.ts.map +1 -0
- package/dist/ai/provider.js +31 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/ai/subagent-caller.d.ts +90 -0
- package/dist/ai/subagent-caller.d.ts.map +1 -0
- package/dist/ai/subagent-caller.js +280 -0
- package/dist/ai/subagent-caller.js.map +1 -0
- package/dist/ai/types.d.ts +70 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +5 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +18 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +239 -6
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/check.d.ts +22 -0
- package/dist/cli/commands/check.d.ts.map +1 -0
- package/dist/cli/commands/check.js +168 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +25 -0
- package/dist/cli/commands/ci.d.ts.map +1 -1
- package/dist/cli/commands/ci.js +139 -36
- package/dist/cli/commands/ci.js.map +1 -1
- package/dist/cli/commands/complexity.d.ts.map +1 -1
- package/dist/cli/commands/complexity.js +6 -0
- package/dist/cli/commands/complexity.js.map +1 -1
- package/dist/cli/commands/design.d.ts +52 -0
- package/dist/cli/commands/design.d.ts.map +1 -0
- package/dist/cli/commands/design.js +274 -0
- package/dist/cli/commands/design.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +121 -8
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/history.d.ts +26 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +92 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +13 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +108 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/server.d.ts +9 -0
- package/dist/cli/commands/server.d.ts.map +1 -0
- package/dist/cli/commands/server.js +65 -0
- package/dist/cli/commands/server.js.map +1 -0
- package/dist/cli/commands/ship/pipeline.d.ts.map +1 -1
- package/dist/cli/commands/ship/pipeline.js +8 -1
- package/dist/cli/commands/ship/pipeline.js.map +1 -1
- package/dist/cli/commands/ship/publisher.d.ts +9 -1
- package/dist/cli/commands/ship/publisher.d.ts.map +1 -1
- package/dist/cli/commands/ship/publisher.js +149 -6
- package/dist/cli/commands/ship/publisher.js.map +1 -1
- package/dist/cli/commands/workflow.d.ts.map +1 -1
- package/dist/cli/commands/workflow.js +22 -2
- package/dist/cli/commands/workflow.js.map +1 -1
- package/dist/cli/config-loader.d.ts.map +1 -1
- package/dist/cli/config-loader.js +3 -2
- package/dist/cli/config-loader.js.map +1 -1
- package/dist/cli/contract-checker.d.ts +33 -0
- package/dist/cli/contract-checker.d.ts.map +1 -0
- package/dist/cli/contract-checker.js +719 -0
- package/dist/cli/contract-checker.js.map +1 -0
- package/dist/cli/contract-diff-scope.d.ts +14 -0
- package/dist/cli/contract-diff-scope.d.ts.map +1 -0
- package/dist/cli/contract-diff-scope.js +127 -0
- package/dist/cli/contract-diff-scope.js.map +1 -0
- package/dist/cli/contract-gate-thresholds.d.ts +14 -0
- package/dist/cli/contract-gate-thresholds.d.ts.map +1 -0
- package/dist/cli/contract-gate-thresholds.js +19 -0
- package/dist/cli/contract-gate-thresholds.js.map +1 -0
- package/dist/cli/design-contract-loader.d.ts +15 -0
- package/dist/cli/design-contract-loader.d.ts.map +1 -0
- package/dist/cli/design-contract-loader.js +527 -0
- package/dist/cli/design-contract-loader.js.map +1 -0
- package/dist/cli/design-contract-schema.d.ts +11 -0
- package/dist/cli/design-contract-schema.d.ts.map +1 -0
- package/dist/cli/design-contract-schema.js +75 -0
- package/dist/cli/design-contract-schema.js.map +1 -0
- package/dist/cli/design-handoff-builder.d.ts +15 -0
- package/dist/cli/design-handoff-builder.d.ts.map +1 -0
- package/dist/cli/design-handoff-builder.js +345 -0
- package/dist/cli/design-handoff-builder.js.map +1 -0
- package/dist/cli/design-scope-resolver.d.ts +8 -0
- package/dist/cli/design-scope-resolver.d.ts.map +1 -0
- package/dist/cli/design-scope-resolver.js +760 -0
- package/dist/cli/design-scope-resolver.js.map +1 -0
- package/dist/cli/design-verification-builder.d.ts +8 -0
- package/dist/cli/design-verification-builder.d.ts.map +1 -0
- package/dist/cli/design-verification-builder.js +369 -0
- package/dist/cli/design-verification-builder.js.map +1 -0
- package/dist/cli/index.js +20 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/paths.d.ts.map +1 -1
- package/dist/cli/paths.js +30 -7
- package/dist/cli/paths.js.map +1 -1
- package/dist/cli-new/commands/server.d.ts +13 -0
- package/dist/cli-new/commands/server.d.ts.map +1 -0
- package/dist/cli-new/commands/server.js +90 -0
- package/dist/cli-new/commands/server.js.map +1 -0
- package/dist/core/analyzer.d.ts.map +1 -1
- package/dist/core/analyzer.js +16 -0
- package/dist/core/analyzer.js.map +1 -1
- package/dist/domain/entities/CodeGraph.d.ts +5 -1
- package/dist/domain/entities/CodeGraph.d.ts.map +1 -1
- package/dist/domain/entities/CodeGraph.js +29 -12
- package/dist/domain/entities/CodeGraph.js.map +1 -1
- package/dist/domain/entities/Dependency.d.ts +8 -1
- package/dist/domain/entities/Dependency.d.ts.map +1 -1
- package/dist/domain/entities/Dependency.js +19 -4
- package/dist/domain/entities/Dependency.js.map +1 -1
- package/dist/domain/entities/Symbol.d.ts +2 -1
- package/dist/domain/entities/Symbol.d.ts.map +1 -1
- package/dist/domain/entities/Symbol.js +6 -3
- package/dist/domain/entities/Symbol.js.map +1 -1
- package/dist/generator/ai-overview.d.ts +51 -0
- package/dist/generator/ai-overview.d.ts.map +1 -0
- package/dist/generator/ai-overview.js +160 -0
- package/dist/generator/ai-overview.js.map +1 -0
- package/dist/infrastructure/storage/StorageFactory.d.ts +13 -5
- package/dist/infrastructure/storage/StorageFactory.d.ts.map +1 -1
- package/dist/infrastructure/storage/StorageFactory.js +62 -16
- package/dist/infrastructure/storage/StorageFactory.js.map +1 -1
- package/dist/infrastructure/storage/adapters/FileSystemStorage.d.ts +3 -1
- package/dist/infrastructure/storage/adapters/FileSystemStorage.d.ts.map +1 -1
- package/dist/infrastructure/storage/adapters/FileSystemStorage.js +10 -2
- package/dist/infrastructure/storage/adapters/FileSystemStorage.js.map +1 -1
- package/dist/infrastructure/storage/adapters/KuzuDBStorage.d.ts +3 -1
- package/dist/infrastructure/storage/adapters/KuzuDBStorage.d.ts.map +1 -1
- package/dist/infrastructure/storage/adapters/KuzuDBStorage.js +9 -1
- package/dist/infrastructure/storage/adapters/KuzuDBStorage.js.map +1 -1
- package/dist/infrastructure/storage/adapters/MemoryStorage.d.ts +3 -1
- package/dist/infrastructure/storage/adapters/MemoryStorage.d.ts.map +1 -1
- package/dist/infrastructure/storage/adapters/MemoryStorage.js +9 -1
- package/dist/infrastructure/storage/adapters/MemoryStorage.js.map +1 -1
- package/dist/infrastructure/storage/adapters/Neo4jStorage.d.ts +41 -0
- package/dist/infrastructure/storage/adapters/Neo4jStorage.d.ts.map +1 -0
- package/dist/infrastructure/storage/adapters/Neo4jStorage.js +162 -0
- package/dist/infrastructure/storage/adapters/Neo4jStorage.js.map +1 -0
- package/dist/infrastructure/storage/adapters/SQLiteStorage.d.ts +53 -0
- package/dist/infrastructure/storage/adapters/SQLiteStorage.d.ts.map +1 -0
- package/dist/infrastructure/storage/adapters/SQLiteStorage.js +879 -0
- package/dist/infrastructure/storage/adapters/SQLiteStorage.js.map +1 -0
- package/dist/infrastructure/storage/graph-helpers.d.ts +3 -1
- package/dist/infrastructure/storage/graph-helpers.d.ts.map +1 -1
- package/dist/infrastructure/storage/graph-helpers.js +90 -0
- package/dist/infrastructure/storage/graph-helpers.js.map +1 -1
- package/dist/infrastructure/storage/index.d.ts +1 -1
- package/dist/infrastructure/storage/index.d.ts.map +1 -1
- package/dist/infrastructure/storage/interfaces/StorageBase.d.ts +3 -1
- package/dist/infrastructure/storage/interfaces/StorageBase.d.ts.map +1 -1
- package/dist/infrastructure/storage/interfaces/StorageBase.js.map +1 -1
- package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.d.ts +27 -0
- package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.d.ts.map +1 -0
- package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.js +246 -0
- package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.js.map +1 -0
- package/dist/infrastructure/storage/sqlite/perf-thresholds.d.ts +25 -0
- package/dist/infrastructure/storage/sqlite/perf-thresholds.d.ts.map +1 -0
- package/dist/infrastructure/storage/sqlite/perf-thresholds.js +25 -0
- package/dist/infrastructure/storage/sqlite/perf-thresholds.js.map +1 -0
- package/dist/infrastructure/storage/sqlite/schema.d.ts +4 -0
- package/dist/infrastructure/storage/sqlite/schema.d.ts.map +1 -0
- package/dist/infrastructure/storage/sqlite/schema.js +111 -0
- package/dist/infrastructure/storage/sqlite/schema.js.map +1 -0
- package/dist/interface/types/design-check.d.ts +73 -0
- package/dist/interface/types/design-check.d.ts.map +1 -0
- package/dist/interface/types/design-check.js +4 -0
- package/dist/interface/types/design-check.js.map +1 -0
- package/dist/interface/types/design-contract.d.ts +123 -0
- package/dist/interface/types/design-contract.d.ts.map +1 -0
- package/dist/interface/types/design-contract.js +7 -0
- package/dist/interface/types/design-contract.js.map +1 -0
- package/dist/interface/types/design-handoff.d.ts +68 -0
- package/dist/interface/types/design-handoff.d.ts.map +1 -0
- package/dist/interface/types/design-handoff.js +4 -0
- package/dist/interface/types/design-handoff.js.map +1 -0
- package/dist/interface/types/design-mapping.d.ts +51 -0
- package/dist/interface/types/design-mapping.d.ts.map +1 -0
- package/dist/interface/types/design-mapping.js +4 -0
- package/dist/interface/types/design-mapping.js.map +1 -0
- package/dist/interface/types/design-verification.d.ts +49 -0
- package/dist/interface/types/design-verification.d.ts.map +1 -0
- package/dist/interface/types/design-verification.js +4 -0
- package/dist/interface/types/design-verification.js.map +1 -0
- package/dist/interface/types/history-risk.d.ts +90 -0
- package/dist/interface/types/history-risk.d.ts.map +1 -0
- package/dist/interface/types/history-risk.js +4 -0
- package/dist/interface/types/history-risk.js.map +1 -0
- package/dist/interface/types/index.d.ts +20 -1
- package/dist/interface/types/index.d.ts.map +1 -1
- package/dist/interface/types/storage.d.ts +28 -1
- package/dist/interface/types/storage.d.ts.map +1 -1
- package/dist/orchestrator/adapters/ast-grep-adapter.d.ts +10 -0
- package/dist/orchestrator/adapters/ast-grep-adapter.d.ts.map +1 -1
- package/dist/orchestrator/adapters/ast-grep-adapter.js +46 -17
- package/dist/orchestrator/adapters/ast-grep-adapter.js.map +1 -1
- package/dist/orchestrator/adapters/codemap-adapter.d.ts.map +1 -1
- package/dist/orchestrator/adapters/codemap-adapter.js +2 -22
- package/dist/orchestrator/adapters/codemap-adapter.js.map +1 -1
- package/dist/orchestrator/ai-feed-generator.d.ts +210 -0
- package/dist/orchestrator/ai-feed-generator.d.ts.map +1 -0
- package/dist/orchestrator/ai-feed-generator.js +377 -0
- package/dist/orchestrator/ai-feed-generator.js.map +1 -0
- package/dist/orchestrator/history-risk-service.d.ts +55 -0
- package/dist/orchestrator/history-risk-service.d.ts.map +1 -0
- package/dist/orchestrator/history-risk-service.js +680 -0
- package/dist/orchestrator/history-risk-service.js.map +1 -0
- package/dist/orchestrator/types.d.ts +19 -1
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/orchestrator/types.js +19 -0
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/server/mcp/index.d.ts +4 -0
- package/dist/server/mcp/index.d.ts.map +1 -0
- package/dist/server/mcp/index.js +5 -0
- package/dist/server/mcp/index.js.map +1 -0
- package/dist/server/mcp/server.d.ts +17 -0
- package/dist/server/mcp/server.d.ts.map +1 -0
- package/dist/server/mcp/server.js +84 -0
- package/dist/server/mcp/server.js.map +1 -0
- package/dist/server/mcp/service.d.ts +22 -0
- package/dist/server/mcp/service.d.ts.map +1 -0
- package/dist/server/mcp/service.js +177 -0
- package/dist/server/mcp/service.js.map +1 -0
- package/dist/server/mcp/types.d.ts +56 -0
- package/dist/server/mcp/types.d.ts.map +1 -0
- package/dist/server/mcp/types.js +4 -0
- package/dist/server/mcp/types.js.map +1 -0
- package/docs/AI_ASSISTANT_SETUP.md +1 -1
- package/docs/SETUP_GUIDE.md +6 -6
- package/docs/ai-guide/COMMANDS.md +171 -4
- package/docs/ai-guide/INTEGRATION.md +137 -433
- package/docs/ai-guide/OUTPUT.md +890 -5
- package/docs/ai-guide/PATTERNS.md +54 -14
- package/docs/ai-guide/PROMPTS.md +17 -6
- package/docs/archive/test-report-symbol-search.md +384 -0
- package/docs/archive/test-scenario-4-complexity-analysis.md +460 -0
- package/docs/archive/test_report_scenario5.md +615 -0
- package/docs/archive/test_scenario_3_impact_analysis_report.md +520 -0
- package/docs/backlog.md +177 -0
- package/docs/eatdogfood-reports/2026-04-17-eatdogfood-agent-experience.md +231 -0
- package/docs/exec-plans/completed/2026-04-17-eatdogfood-codemap-cli.md +103 -0
- package/docs/ideation/2026-04-15-executable-architecture-constitution-ideation.md +102 -0
- package/docs/product-specs/DESIGN_CONTRACT_TEMPLATE.md +126 -0
- package/docs/product-specs/MVP3-ARCHITECTURE-COMPARISON.md +11 -10
- package/docs/product-specs/MVP3-ARCHITECTURE-REDESIGN-PRD.md +10 -10
- package/docs/product-specs/MVP3-ARCHITECTURE-REDESIGN-TECH-PRD.md +17 -12
- package/docs/product-specs/README.md +2 -1
- package/docs/rules/README.md +16 -11
- package/docs/rules/architecture-guardrails.md +24 -336
- package/docs/rules/code-quality-redlines.md +25 -311
- package/docs/rules/engineering-with-codex-openai.md +20 -3
- package/docs/rules/validation.md +90 -37
- package/mycodemap.config.schema.json +3 -3
- package/package.json +7 -2
- package/scripts/benchmark-governance-graph.mjs +132 -0
- package/scripts/calibrate-contract-gate.mjs +221 -0
- package/scripts/capability-report.py +255 -0
- package/scripts/experiments/arcadedb-http-smoke.mjs +90 -0
- package/scripts/qa-rule-control.sh +254 -0
- package/scripts/report-high-risk-files.mjs +395 -0
- package/scripts/rule-context.mjs +155 -0
- package/scripts/smoke-sqlite-impact.mjs +85 -0
- package/scripts/sync-analyze-docs.js +1 -0
- package/scripts/tests/test_capability_report.py +89 -0
- package/scripts/tests/test_rule_control_workflow.py +51 -0
- package/scripts/tests/test_validate_rules.py +81 -0
- package/scripts/validate-ai-docs.js +283 -1
- package/scripts/validate-docs.js +479 -25
- package/scripts/validate-rules.py +254 -0
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
// [META] since:2026-04 | owner:architecture-team | stable:false
|
|
2
|
+
// [WHY] SQLite storage adapter - normalized governance truth source for Phase 26
|
|
3
|
+
// ============================================
|
|
4
|
+
// SQLite 存储适配器 - Phase 26 的规范化治理真相源
|
|
5
|
+
// ============================================
|
|
6
|
+
import { mkdir } from 'node:fs/promises';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { StorageBase, StorageError } from '../interfaces/StorageBase.js';
|
|
9
|
+
import { calculateImpactInGraph, cloneCodeGraph, createEmptyCodeGraph, deserializeCodeGraphSnapshot, detectCyclesInGraph, getProjectStatisticsFromGraph, serializeCodeGraphSnapshot, upsertModuleInGraph, } from '../graph-helpers.js';
|
|
10
|
+
import { CURRENT_SQLITE_SCHEMA_VERSION, SQLITE_GOVERNANCE_SCHEMA_SQL, SQLITE_SCHEMA_VERSION_UPSERT_SQL, } from '../sqlite/schema.js';
|
|
11
|
+
import { GovernanceGraphCache, readGovernanceGraphFromSQLite, } from '../sqlite/GovernanceGraphCache.js';
|
|
12
|
+
import { DEFAULT_GOVERNANCE_GRAPH_PERF_THRESHOLDS, } from '../sqlite/perf-thresholds.js';
|
|
13
|
+
const GRAPH_STATUS_METADATA_KEY = 'graph_status';
|
|
14
|
+
const FAILED_FILE_COUNT_METADATA_KEY = 'failed_file_count';
|
|
15
|
+
const PARSE_FAILURE_FILES_METADATA_KEY = 'parse_failure_files_json';
|
|
16
|
+
const LAST_GRAPH_SYNC_AT_METADATA_KEY = 'last_graph_sync_at';
|
|
17
|
+
async function loadSQLiteModule() {
|
|
18
|
+
const sqliteModule = await import('better-sqlite3');
|
|
19
|
+
return sqliteModule.default;
|
|
20
|
+
}
|
|
21
|
+
function toStringValue(value, fallback = '') {
|
|
22
|
+
return typeof value === 'string' ? value : fallback;
|
|
23
|
+
}
|
|
24
|
+
function toNumberValue(value, fallback = 0) {
|
|
25
|
+
return typeof value === 'number' ? value : Number(value ?? fallback);
|
|
26
|
+
}
|
|
27
|
+
function toOptionalNumberValue(value) {
|
|
28
|
+
if (typeof value === 'number') {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
if (value === null || value === undefined) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const parsed = Number(value);
|
|
35
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
36
|
+
}
|
|
37
|
+
function parseJsonValue(value) {
|
|
38
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(value);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function toGraphStatus(value) {
|
|
49
|
+
return toStringValue(value) === 'partial' ? 'partial' : 'complete';
|
|
50
|
+
}
|
|
51
|
+
function normalizeParseFailureFiles(value) {
|
|
52
|
+
const parsedValue = parseJsonValue(value);
|
|
53
|
+
if (!Array.isArray(parsedValue)) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
return parsedValue.filter((item) => typeof item === 'string');
|
|
57
|
+
}
|
|
58
|
+
function readGraphIntegrityMetadata(database) {
|
|
59
|
+
const metadataRows = database.prepare(`
|
|
60
|
+
SELECT key, value
|
|
61
|
+
FROM metadata
|
|
62
|
+
WHERE key IN (?, ?, ?)
|
|
63
|
+
`).all(GRAPH_STATUS_METADATA_KEY, FAILED_FILE_COUNT_METADATA_KEY, PARSE_FAILURE_FILES_METADATA_KEY);
|
|
64
|
+
const metadataMap = new Map(metadataRows.map(row => [toStringValue(row.key), row.value]));
|
|
65
|
+
const parseFailureFiles = normalizeParseFailureFiles(metadataMap.get(PARSE_FAILURE_FILES_METADATA_KEY));
|
|
66
|
+
return {
|
|
67
|
+
graphStatus: toGraphStatus(metadataMap.get(GRAPH_STATUS_METADATA_KEY)),
|
|
68
|
+
failedFileCount: toNumberValue(metadataMap.get(FAILED_FILE_COUNT_METADATA_KEY), parseFailureFiles.length),
|
|
69
|
+
parseFailureFiles,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function readGraphMetadata(database) {
|
|
73
|
+
const countsRow = database
|
|
74
|
+
.prepare(`
|
|
75
|
+
SELECT
|
|
76
|
+
(SELECT COUNT(*) FROM modules) AS module_count,
|
|
77
|
+
(SELECT COUNT(*) FROM symbols) AS symbol_count
|
|
78
|
+
`)
|
|
79
|
+
.get();
|
|
80
|
+
const metadataRows = database
|
|
81
|
+
.prepare(`
|
|
82
|
+
SELECT key, value
|
|
83
|
+
FROM metadata
|
|
84
|
+
WHERE key IN (?, ?, ?, ?)
|
|
85
|
+
`)
|
|
86
|
+
.all(GRAPH_STATUS_METADATA_KEY, FAILED_FILE_COUNT_METADATA_KEY, PARSE_FAILURE_FILES_METADATA_KEY, LAST_GRAPH_SYNC_AT_METADATA_KEY);
|
|
87
|
+
const metadataMap = new Map(metadataRows.map(row => [toStringValue(row.key), row.value]));
|
|
88
|
+
const parseFailureFiles = normalizeParseFailureFiles(metadataMap.get(PARSE_FAILURE_FILES_METADATA_KEY));
|
|
89
|
+
const moduleCount = toNumberValue(countsRow?.module_count);
|
|
90
|
+
const symbolCount = toNumberValue(countsRow?.symbol_count);
|
|
91
|
+
const hasMaterializedGraph = moduleCount > 0 || symbolCount > 0;
|
|
92
|
+
return {
|
|
93
|
+
generatedAt: hasMaterializedGraph
|
|
94
|
+
? toStringValue(metadataMap.get(LAST_GRAPH_SYNC_AT_METADATA_KEY)) || null
|
|
95
|
+
: null,
|
|
96
|
+
graphStatus: toGraphStatus(metadataMap.get(GRAPH_STATUS_METADATA_KEY)),
|
|
97
|
+
failedFileCount: toNumberValue(metadataMap.get(FAILED_FILE_COUNT_METADATA_KEY), parseFailureFiles.length),
|
|
98
|
+
parseFailureFiles,
|
|
99
|
+
moduleCount,
|
|
100
|
+
symbolCount,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function createHistorySnapshotId(recordedAt) {
|
|
104
|
+
const compactTimestamp = recordedAt.replace(/[^0-9]/g, '');
|
|
105
|
+
const entropy = Math.random().toString(16).slice(2, 10);
|
|
106
|
+
return `history-${compactTimestamp}-${entropy}`;
|
|
107
|
+
}
|
|
108
|
+
function inferEntityType(entityId, symbolIds) {
|
|
109
|
+
return symbolIds.has(entityId) ? 'symbol' : 'module';
|
|
110
|
+
}
|
|
111
|
+
function resolveEntityType(entityType, entityId, symbolIds) {
|
|
112
|
+
return entityType ?? inferEntityType(entityId, symbolIds);
|
|
113
|
+
}
|
|
114
|
+
function hasColumn(database, tableName, columnName) {
|
|
115
|
+
const rows = database.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
116
|
+
return rows.some((row) => toStringValue(row.name) === columnName);
|
|
117
|
+
}
|
|
118
|
+
function addColumnIfMissing(database, tableName, columnName, definition) {
|
|
119
|
+
if (!hasColumn(database, tableName, columnName)) {
|
|
120
|
+
database.exec(`ALTER TABLE ${tableName} ADD COLUMN ${definition}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function createUnavailableHistoryDiagnostics(reason) {
|
|
124
|
+
return {
|
|
125
|
+
status: 'unavailable',
|
|
126
|
+
confidence: 'unavailable',
|
|
127
|
+
freshness: 'unknown',
|
|
128
|
+
source: 'unavailable',
|
|
129
|
+
reasons: [reason],
|
|
130
|
+
analyzedAt: null,
|
|
131
|
+
scopeMode: 'partial',
|
|
132
|
+
requestedFiles: 0,
|
|
133
|
+
analyzedFiles: 0,
|
|
134
|
+
requiresPrecompute: false,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function createUnavailableSymbolHistoryResult(query, reason) {
|
|
138
|
+
return {
|
|
139
|
+
query,
|
|
140
|
+
candidates: [],
|
|
141
|
+
symbol: null,
|
|
142
|
+
files: [],
|
|
143
|
+
timeline: [],
|
|
144
|
+
risk: {
|
|
145
|
+
level: 'unavailable',
|
|
146
|
+
score: null,
|
|
147
|
+
gravity: null,
|
|
148
|
+
heat: null,
|
|
149
|
+
impact: null,
|
|
150
|
+
riskFactors: [reason],
|
|
151
|
+
},
|
|
152
|
+
diagnostics: createUnavailableHistoryDiagnostics(reason),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function removeModuleAndOwnedRelations(graph, moduleId) {
|
|
156
|
+
const nextGraph = cloneCodeGraph(graph);
|
|
157
|
+
const removedSymbolIds = new Set(nextGraph.symbols
|
|
158
|
+
.filter(symbol => symbol.moduleId === moduleId)
|
|
159
|
+
.map(symbol => symbol.id));
|
|
160
|
+
nextGraph.modules = nextGraph.modules.filter(module => module.id !== moduleId);
|
|
161
|
+
nextGraph.symbols = nextGraph.symbols.filter(symbol => symbol.moduleId !== moduleId);
|
|
162
|
+
nextGraph.dependencies = nextGraph.dependencies.filter(dependency => dependency.sourceId !== moduleId
|
|
163
|
+
&& dependency.targetId !== moduleId
|
|
164
|
+
&& !removedSymbolIds.has(dependency.sourceId)
|
|
165
|
+
&& !removedSymbolIds.has(dependency.targetId));
|
|
166
|
+
return nextGraph;
|
|
167
|
+
}
|
|
168
|
+
export class SQLiteStorage extends StorageBase {
|
|
169
|
+
type = 'sqlite';
|
|
170
|
+
database = null;
|
|
171
|
+
storageConfig;
|
|
172
|
+
runtimeOptions;
|
|
173
|
+
governanceGraphCache = null;
|
|
174
|
+
constructor(config, runtimeOptions = {}) {
|
|
175
|
+
super();
|
|
176
|
+
this.storageConfig = config;
|
|
177
|
+
this.runtimeOptions = runtimeOptions;
|
|
178
|
+
}
|
|
179
|
+
async doInitialize() {
|
|
180
|
+
if (!this.projectPath) {
|
|
181
|
+
throw new StorageError('Project path not set', 'PROJECT_PATH_NOT_SET');
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const databasePath = this.storageConfig.databasePath
|
|
185
|
+
? join(this.projectPath, this.storageConfig.databasePath)
|
|
186
|
+
: join(this.projectPath, '.codemap', 'governance.sqlite');
|
|
187
|
+
await mkdir(dirname(databasePath), { recursive: true });
|
|
188
|
+
const Database = await loadSQLiteModule();
|
|
189
|
+
this.database = new Database(databasePath);
|
|
190
|
+
this.database.pragma?.('journal_mode = WAL');
|
|
191
|
+
this.database.pragma?.('foreign_keys = ON');
|
|
192
|
+
this.database.exec(SQLITE_GOVERNANCE_SCHEMA_SQL);
|
|
193
|
+
this.ensureSchemaColumns(this.database);
|
|
194
|
+
this.database
|
|
195
|
+
.prepare(SQLITE_SCHEMA_VERSION_UPSERT_SQL)
|
|
196
|
+
.run('schema_version', CURRENT_SQLITE_SCHEMA_VERSION);
|
|
197
|
+
this.backfillLegacySnapshotIfNeeded(this.database);
|
|
198
|
+
this.refreshGovernanceGraphCache(this.database);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
throw new StorageError('Failed to initialize SQLite storage', 'SQLITE_INIT_FAILED', error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async doClose() {
|
|
205
|
+
if (this.database) {
|
|
206
|
+
this.database.close();
|
|
207
|
+
this.database = null;
|
|
208
|
+
}
|
|
209
|
+
this.governanceGraphCache = null;
|
|
210
|
+
}
|
|
211
|
+
async saveCodeGraph(graph) {
|
|
212
|
+
const database = this.getDatabase();
|
|
213
|
+
this.replaceCurrentGraph(database, graph, 'save-code-graph');
|
|
214
|
+
}
|
|
215
|
+
async loadCodeGraph() {
|
|
216
|
+
return this.readCodeGraph(this.getDatabase());
|
|
217
|
+
}
|
|
218
|
+
async loadGraphMetadata() {
|
|
219
|
+
return readGraphMetadata(this.getDatabase());
|
|
220
|
+
}
|
|
221
|
+
async deleteProject() {
|
|
222
|
+
const database = this.getDatabase();
|
|
223
|
+
this.runInTransaction(database, () => {
|
|
224
|
+
database.prepare('DELETE FROM history_relations').run();
|
|
225
|
+
database.prepare('DELETE FROM history_snapshots').run();
|
|
226
|
+
database.prepare('DELETE FROM dependencies').run();
|
|
227
|
+
database.prepare('DELETE FROM symbols').run();
|
|
228
|
+
database.prepare('DELETE FROM modules').run();
|
|
229
|
+
database.prepare('DELETE FROM projects').run();
|
|
230
|
+
database.prepare('DELETE FROM snapshots').run();
|
|
231
|
+
});
|
|
232
|
+
this.refreshGovernanceGraphCache(database);
|
|
233
|
+
}
|
|
234
|
+
async updateModule(module) {
|
|
235
|
+
const currentGraph = await this.loadCodeGraph();
|
|
236
|
+
await this.saveCodeGraph(upsertModuleInGraph(currentGraph, module));
|
|
237
|
+
}
|
|
238
|
+
async deleteModule(moduleId) {
|
|
239
|
+
const currentGraph = await this.loadCodeGraph();
|
|
240
|
+
await this.saveCodeGraph(removeModuleAndOwnedRelations(currentGraph, moduleId));
|
|
241
|
+
}
|
|
242
|
+
async findModuleById(id) {
|
|
243
|
+
const row = this.getDatabase()
|
|
244
|
+
.prepare(`
|
|
245
|
+
SELECT id, project_id, path, language, lines, code_lines, comment_lines, blank_lines
|
|
246
|
+
FROM modules
|
|
247
|
+
WHERE id = ?
|
|
248
|
+
LIMIT 1
|
|
249
|
+
`)
|
|
250
|
+
.get(id);
|
|
251
|
+
return row ? this.mapModuleRow(row) : null;
|
|
252
|
+
}
|
|
253
|
+
async findModulesByPath(path) {
|
|
254
|
+
const rows = this.getDatabase()
|
|
255
|
+
.prepare(`
|
|
256
|
+
SELECT id, project_id, path, language, lines, code_lines, comment_lines, blank_lines
|
|
257
|
+
FROM modules
|
|
258
|
+
WHERE instr(path, ?) > 0
|
|
259
|
+
ORDER BY path, id
|
|
260
|
+
`)
|
|
261
|
+
.all(path);
|
|
262
|
+
return rows.map(row => this.mapModuleRow(row));
|
|
263
|
+
}
|
|
264
|
+
async findSymbolByName(name) {
|
|
265
|
+
const rows = this.getDatabase()
|
|
266
|
+
.prepare(`
|
|
267
|
+
SELECT id, module_id, name, kind, signature, file_path, line, column_number, end_line, end_column, visibility
|
|
268
|
+
FROM symbols
|
|
269
|
+
WHERE instr(name, ?) > 0
|
|
270
|
+
ORDER BY name, id
|
|
271
|
+
`)
|
|
272
|
+
.all(name);
|
|
273
|
+
return rows.map(row => this.mapSymbolRow(row));
|
|
274
|
+
}
|
|
275
|
+
async findSymbolById(id) {
|
|
276
|
+
const row = this.getDatabase()
|
|
277
|
+
.prepare(`
|
|
278
|
+
SELECT id, module_id, name, kind, signature, file_path, line, column_number, end_line, end_column, visibility
|
|
279
|
+
FROM symbols
|
|
280
|
+
WHERE id = ?
|
|
281
|
+
LIMIT 1
|
|
282
|
+
`)
|
|
283
|
+
.get(id);
|
|
284
|
+
return row ? this.mapSymbolRow(row) : null;
|
|
285
|
+
}
|
|
286
|
+
async findDependencies(moduleId) {
|
|
287
|
+
const cachedDependencies = this.governanceGraphCache?.findDependencies(moduleId);
|
|
288
|
+
if (cachedDependencies) {
|
|
289
|
+
return cachedDependencies;
|
|
290
|
+
}
|
|
291
|
+
const rows = this.getDatabase()
|
|
292
|
+
.prepare(`
|
|
293
|
+
SELECT id, source_id, source_entity_type, target_id, target_entity_type, dependency_type, file_path, line, confidence
|
|
294
|
+
FROM dependencies
|
|
295
|
+
WHERE source_id = ?
|
|
296
|
+
ORDER BY id
|
|
297
|
+
`)
|
|
298
|
+
.all(moduleId);
|
|
299
|
+
return rows.map(row => this.mapDependencyRow(row));
|
|
300
|
+
}
|
|
301
|
+
async findDependents(moduleId) {
|
|
302
|
+
const cachedDependents = this.governanceGraphCache?.findDependents(moduleId);
|
|
303
|
+
if (cachedDependents) {
|
|
304
|
+
return cachedDependents;
|
|
305
|
+
}
|
|
306
|
+
const rows = this.getDatabase()
|
|
307
|
+
.prepare(`
|
|
308
|
+
SELECT id, source_id, source_entity_type, target_id, target_entity_type, dependency_type, file_path, line, confidence
|
|
309
|
+
FROM dependencies
|
|
310
|
+
WHERE target_id = ?
|
|
311
|
+
ORDER BY id
|
|
312
|
+
`)
|
|
313
|
+
.all(moduleId);
|
|
314
|
+
return rows.map(row => this.mapDependencyRow(row));
|
|
315
|
+
}
|
|
316
|
+
async findCallers(functionId) {
|
|
317
|
+
const rows = this.getDatabase()
|
|
318
|
+
.prepare(`
|
|
319
|
+
SELECT s.id, s.module_id, s.name, s.kind, s.signature, s.file_path, s.line, s.column_number, s.end_line, s.end_column, s.visibility
|
|
320
|
+
FROM dependencies d
|
|
321
|
+
INNER JOIN symbols s ON s.id = d.source_id
|
|
322
|
+
WHERE d.target_id = ?
|
|
323
|
+
AND d.dependency_type = 'call'
|
|
324
|
+
ORDER BY s.id
|
|
325
|
+
`)
|
|
326
|
+
.all(functionId);
|
|
327
|
+
return rows.map(row => this.mapSymbolRow(row));
|
|
328
|
+
}
|
|
329
|
+
async findCallees(functionId) {
|
|
330
|
+
const rows = this.getDatabase()
|
|
331
|
+
.prepare(`
|
|
332
|
+
SELECT s.id, s.module_id, s.name, s.kind, s.signature, s.file_path, s.line, s.column_number, s.end_line, s.end_column, s.visibility
|
|
333
|
+
FROM dependencies d
|
|
334
|
+
INNER JOIN symbols s ON s.id = d.target_id
|
|
335
|
+
WHERE d.source_id = ?
|
|
336
|
+
AND d.dependency_type = 'call'
|
|
337
|
+
ORDER BY s.id
|
|
338
|
+
`)
|
|
339
|
+
.all(functionId);
|
|
340
|
+
return rows.map(row => this.mapSymbolRow(row));
|
|
341
|
+
}
|
|
342
|
+
async detectCycles() {
|
|
343
|
+
const cachedCycles = this.governanceGraphCache?.detectCycles();
|
|
344
|
+
if (cachedCycles) {
|
|
345
|
+
return cachedCycles;
|
|
346
|
+
}
|
|
347
|
+
return detectCyclesInGraph(this.readGovernanceGraphForAnalysis(this.getDatabase()));
|
|
348
|
+
}
|
|
349
|
+
async calculateImpact(moduleId, depth) {
|
|
350
|
+
const cachedImpact = this.governanceGraphCache?.calculateImpact(moduleId, depth);
|
|
351
|
+
if (cachedImpact) {
|
|
352
|
+
return cachedImpact;
|
|
353
|
+
}
|
|
354
|
+
return calculateImpactInGraph(this.readGovernanceGraphForAnalysis(this.getDatabase()), moduleId, depth);
|
|
355
|
+
}
|
|
356
|
+
async calculateSymbolImpact(symbolId, depth, limit) {
|
|
357
|
+
const database = this.getDatabase();
|
|
358
|
+
const rootSymbol = await this.findSymbolById(symbolId);
|
|
359
|
+
if (!rootSymbol) {
|
|
360
|
+
throw new StorageError(`Symbol ${symbolId} not found`, 'SYMBOL_NOT_FOUND');
|
|
361
|
+
}
|
|
362
|
+
const selectCallers = database.prepare(`
|
|
363
|
+
SELECT s.id, s.module_id, s.name, s.kind, s.signature, s.file_path, s.line, s.column_number, s.end_line, s.end_column, s.visibility
|
|
364
|
+
FROM dependencies d
|
|
365
|
+
INNER JOIN symbols s ON s.id = d.source_id
|
|
366
|
+
WHERE d.target_id = ?
|
|
367
|
+
AND d.dependency_type = 'call'
|
|
368
|
+
ORDER BY d.id, s.id
|
|
369
|
+
`);
|
|
370
|
+
const affectedSymbols = [];
|
|
371
|
+
const visited = new Set([symbolId]);
|
|
372
|
+
const queue = [{
|
|
373
|
+
id: symbolId,
|
|
374
|
+
level: 0,
|
|
375
|
+
path: [symbolId],
|
|
376
|
+
}];
|
|
377
|
+
let truncated = false;
|
|
378
|
+
while (queue.length > 0) {
|
|
379
|
+
const current = queue.shift();
|
|
380
|
+
if (!current) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (current.level >= depth) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const callerRows = selectCallers.all(current.id);
|
|
387
|
+
for (const row of callerRows) {
|
|
388
|
+
if (affectedSymbols.length >= limit) {
|
|
389
|
+
truncated = true;
|
|
390
|
+
queue.length = 0;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
const callerId = toStringValue(row.id);
|
|
394
|
+
if (visited.has(callerId)) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
visited.add(callerId);
|
|
398
|
+
const callerSymbol = this.mapSymbolRow(row);
|
|
399
|
+
const nextLevel = current.level + 1;
|
|
400
|
+
const nextPath = [...current.path, callerId];
|
|
401
|
+
affectedSymbols.push({
|
|
402
|
+
symbol: callerSymbol,
|
|
403
|
+
depth: nextLevel,
|
|
404
|
+
path: nextPath,
|
|
405
|
+
});
|
|
406
|
+
queue.push({
|
|
407
|
+
id: callerId,
|
|
408
|
+
level: nextLevel,
|
|
409
|
+
path: nextPath,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
rootSymbol,
|
|
415
|
+
affectedSymbols,
|
|
416
|
+
depth,
|
|
417
|
+
limit,
|
|
418
|
+
truncated,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
async getStatistics() {
|
|
422
|
+
const database = this.getDatabase();
|
|
423
|
+
const row = database
|
|
424
|
+
.prepare(`
|
|
425
|
+
SELECT
|
|
426
|
+
(SELECT COUNT(*) FROM modules) AS total_modules,
|
|
427
|
+
(SELECT COUNT(*) FROM symbols) AS total_symbols,
|
|
428
|
+
(SELECT COUNT(*) FROM dependencies) AS total_dependencies,
|
|
429
|
+
(SELECT COALESCE(SUM(lines), 0) FROM modules) AS total_lines
|
|
430
|
+
`)
|
|
431
|
+
.get();
|
|
432
|
+
if (!row) {
|
|
433
|
+
return getProjectStatisticsFromGraph(createEmptyCodeGraph(this.projectPath ?? ''));
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
totalModules: toNumberValue(row.total_modules),
|
|
437
|
+
totalSymbols: toNumberValue(row.total_symbols),
|
|
438
|
+
totalDependencies: toNumberValue(row.total_dependencies),
|
|
439
|
+
totalLines: toNumberValue(row.total_lines),
|
|
440
|
+
averageComplexity: 0,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
async saveHistoryRiskSnapshot(payload) {
|
|
444
|
+
const database = this.getDatabase();
|
|
445
|
+
const recordedAt = payload.recordedAt ?? new Date().toISOString();
|
|
446
|
+
const snapshotId = createHistorySnapshotId(recordedAt);
|
|
447
|
+
const projectId = toStringValue(database.prepare('SELECT id FROM projects LIMIT 1').get()?.id, 'history-risk-project');
|
|
448
|
+
const fileSignals = payload.fileSignals ?? [];
|
|
449
|
+
const symbolSignals = payload.symbolSignals ?? [];
|
|
450
|
+
const relationCount = fileSignals.reduce((sum, signal) => sum + signal.timeline.length + 1, 0)
|
|
451
|
+
+ symbolSignals.reduce((sum, signal) => sum + signal.timeline.length + 1, 0);
|
|
452
|
+
const insertSnapshot = database.prepare(`
|
|
453
|
+
INSERT INTO history_snapshots (
|
|
454
|
+
id, project_id, recorded_at, snapshot_source, module_count, symbol_count, dependency_count
|
|
455
|
+
)
|
|
456
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
457
|
+
`);
|
|
458
|
+
const insertRelation = database.prepare(`
|
|
459
|
+
INSERT INTO history_relations (
|
|
460
|
+
id, snapshot_id, relation_type, source_id, source_entity_type, target_id, target_entity_type, observed_at, metadata_json
|
|
461
|
+
)
|
|
462
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
463
|
+
`);
|
|
464
|
+
this.runInTransaction(database, () => {
|
|
465
|
+
insertSnapshot.run(snapshotId, projectId, recordedAt, payload.source, fileSignals.length, symbolSignals.length, relationCount);
|
|
466
|
+
for (const signal of fileSignals) {
|
|
467
|
+
insertRelation.run(`${snapshotId}:file-signal:${signal.file}`, snapshotId, 'file_history_signal', signal.file, 'module', null, null, recordedAt, JSON.stringify({
|
|
468
|
+
file: signal.file,
|
|
469
|
+
risk: signal.risk,
|
|
470
|
+
diagnostics: signal.diagnostics,
|
|
471
|
+
}));
|
|
472
|
+
for (const entry of signal.timeline) {
|
|
473
|
+
insertRelation.run(`${snapshotId}:file-commit:${signal.file}:${entry.hash}`, snapshotId, 'file_history_commit', signal.file, 'module', entry.hash, 'commit', entry.date, JSON.stringify(entry));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
for (const signal of symbolSignals) {
|
|
477
|
+
const symbolId = signal.symbol?.symbolId;
|
|
478
|
+
if (!symbolId) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
insertRelation.run(`${snapshotId}:symbol-signal:${symbolId}`, snapshotId, 'symbol_history_signal', symbolId, 'symbol', signal.symbol?.moduleId ?? null, signal.symbol ? 'module' : null, recordedAt, JSON.stringify({
|
|
482
|
+
query: signal.query,
|
|
483
|
+
candidates: signal.candidates,
|
|
484
|
+
symbol: signal.symbol,
|
|
485
|
+
files: signal.files,
|
|
486
|
+
risk: signal.risk,
|
|
487
|
+
diagnostics: signal.diagnostics,
|
|
488
|
+
}));
|
|
489
|
+
for (const entry of signal.timeline) {
|
|
490
|
+
insertRelation.run(`${snapshotId}:symbol-commit:${symbolId}:${entry.hash}`, snapshotId, 'symbol_history_commit', symbolId, 'symbol', entry.hash, 'commit', entry.date, JSON.stringify(entry));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
database
|
|
494
|
+
.prepare(SQLITE_SCHEMA_VERSION_UPSERT_SQL)
|
|
495
|
+
.run('last_history_risk_sync_at', recordedAt);
|
|
496
|
+
});
|
|
497
|
+
return {
|
|
498
|
+
snapshotId,
|
|
499
|
+
recordedAt,
|
|
500
|
+
source: payload.source,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
async loadLatestFileHistorySignal(file) {
|
|
504
|
+
const database = this.getDatabase();
|
|
505
|
+
const row = database
|
|
506
|
+
.prepare(`
|
|
507
|
+
SELECT snapshot_id, metadata_json
|
|
508
|
+
FROM history_relations
|
|
509
|
+
WHERE relation_type = 'file_history_signal'
|
|
510
|
+
AND source_id = ?
|
|
511
|
+
ORDER BY observed_at DESC
|
|
512
|
+
LIMIT 1
|
|
513
|
+
`)
|
|
514
|
+
.get(file);
|
|
515
|
+
if (!row) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
const metadata = parseJsonValue(row.metadata_json);
|
|
519
|
+
if (!metadata) {
|
|
520
|
+
return {
|
|
521
|
+
file,
|
|
522
|
+
risk: {
|
|
523
|
+
level: 'unavailable',
|
|
524
|
+
score: null,
|
|
525
|
+
gravity: null,
|
|
526
|
+
heat: null,
|
|
527
|
+
impact: null,
|
|
528
|
+
riskFactors: ['stored file history metadata is invalid'],
|
|
529
|
+
},
|
|
530
|
+
timeline: [],
|
|
531
|
+
diagnostics: createUnavailableHistoryDiagnostics('stored file history metadata is invalid'),
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const timelineRows = database
|
|
535
|
+
.prepare(`
|
|
536
|
+
SELECT metadata_json
|
|
537
|
+
FROM history_relations
|
|
538
|
+
WHERE relation_type = 'file_history_commit'
|
|
539
|
+
AND source_id = ?
|
|
540
|
+
AND snapshot_id = ?
|
|
541
|
+
ORDER BY observed_at DESC, target_id DESC
|
|
542
|
+
`)
|
|
543
|
+
.all(file, toStringValue(row.snapshot_id));
|
|
544
|
+
const timeline = timelineRows
|
|
545
|
+
.map((timelineRow) => parseJsonValue(timelineRow.metadata_json))
|
|
546
|
+
.filter((entry) => entry !== null)
|
|
547
|
+
.sort((left, right) => right.date.localeCompare(left.date));
|
|
548
|
+
return {
|
|
549
|
+
file: metadata.file,
|
|
550
|
+
risk: metadata.risk,
|
|
551
|
+
timeline,
|
|
552
|
+
diagnostics: metadata.diagnostics,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
async loadLatestSymbolHistoryResult(symbolId, query = symbolId) {
|
|
556
|
+
const database = this.getDatabase();
|
|
557
|
+
const row = database
|
|
558
|
+
.prepare(`
|
|
559
|
+
SELECT snapshot_id, metadata_json
|
|
560
|
+
FROM history_relations
|
|
561
|
+
WHERE relation_type = 'symbol_history_signal'
|
|
562
|
+
AND source_id = ?
|
|
563
|
+
ORDER BY observed_at DESC
|
|
564
|
+
LIMIT 1
|
|
565
|
+
`)
|
|
566
|
+
.get(symbolId);
|
|
567
|
+
if (!row) {
|
|
568
|
+
return createUnavailableSymbolHistoryResult(query, 'no materialized history snapshot found for symbol');
|
|
569
|
+
}
|
|
570
|
+
const metadata = parseJsonValue(row.metadata_json);
|
|
571
|
+
if (!metadata) {
|
|
572
|
+
return createUnavailableSymbolHistoryResult(query, 'stored symbol history metadata is invalid');
|
|
573
|
+
}
|
|
574
|
+
const timelineRows = database
|
|
575
|
+
.prepare(`
|
|
576
|
+
SELECT metadata_json
|
|
577
|
+
FROM history_relations
|
|
578
|
+
WHERE relation_type = 'symbol_history_commit'
|
|
579
|
+
AND source_id = ?
|
|
580
|
+
AND snapshot_id = ?
|
|
581
|
+
ORDER BY observed_at DESC, target_id DESC
|
|
582
|
+
`)
|
|
583
|
+
.all(symbolId, toStringValue(row.snapshot_id));
|
|
584
|
+
const timeline = timelineRows
|
|
585
|
+
.map((timelineRow) => parseJsonValue(timelineRow.metadata_json))
|
|
586
|
+
.filter((entry) => entry !== null)
|
|
587
|
+
.sort((left, right) => right.date.localeCompare(left.date));
|
|
588
|
+
return {
|
|
589
|
+
query: metadata.query || query,
|
|
590
|
+
candidates: metadata.candidates,
|
|
591
|
+
symbol: metadata.symbol,
|
|
592
|
+
files: metadata.files,
|
|
593
|
+
timeline,
|
|
594
|
+
risk: metadata.risk,
|
|
595
|
+
diagnostics: metadata.diagnostics,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
getGovernanceGraphRuntimeStats() {
|
|
599
|
+
return this.governanceGraphCache?.getStats() ?? {
|
|
600
|
+
cacheMode: 'sqlite-direct',
|
|
601
|
+
thresholds: this.runtimeOptions.governanceGraphThresholds ?? DEFAULT_GOVERNANCE_GRAPH_PERF_THRESHOLDS,
|
|
602
|
+
moduleCount: 0,
|
|
603
|
+
dependencyCount: 0,
|
|
604
|
+
loadMs: 0,
|
|
605
|
+
rssDeltaMb: 0,
|
|
606
|
+
warning: 'governance graph cache not initialized',
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
ensureSchemaColumns(database) {
|
|
610
|
+
addColumnIfMissing(database, 'symbols', 'signature', 'signature TEXT');
|
|
611
|
+
addColumnIfMissing(database, 'dependencies', 'file_path', 'file_path TEXT');
|
|
612
|
+
addColumnIfMissing(database, 'dependencies', 'line', 'line INTEGER');
|
|
613
|
+
addColumnIfMissing(database, 'dependencies', 'confidence', 'confidence TEXT');
|
|
614
|
+
}
|
|
615
|
+
backfillLegacySnapshotIfNeeded(database) {
|
|
616
|
+
const hasCurrentProject = toNumberValue(database.prepare('SELECT COUNT(*) AS count FROM projects').get()?.count) > 0;
|
|
617
|
+
if (hasCurrentProject) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const legacySnapshot = database
|
|
621
|
+
.prepare(`
|
|
622
|
+
SELECT graph_json, updated_at
|
|
623
|
+
FROM snapshots
|
|
624
|
+
ORDER BY updated_at DESC
|
|
625
|
+
LIMIT 1
|
|
626
|
+
`)
|
|
627
|
+
.get();
|
|
628
|
+
const snapshotJson = toStringValue(legacySnapshot?.graph_json);
|
|
629
|
+
if (!snapshotJson) {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const recordedAt = toStringValue(legacySnapshot?.updated_at, new Date().toISOString());
|
|
633
|
+
const graph = deserializeCodeGraphSnapshot(snapshotJson, this.projectPath ?? '');
|
|
634
|
+
this.replaceCurrentGraph(database, graph, 'legacy-snapshot-backfill', recordedAt);
|
|
635
|
+
database
|
|
636
|
+
.prepare(SQLITE_SCHEMA_VERSION_UPSERT_SQL)
|
|
637
|
+
.run('legacy_snapshot_backfilled_at', recordedAt);
|
|
638
|
+
}
|
|
639
|
+
replaceCurrentGraph(database, graph, historySource, recordedAt = new Date().toISOString()) {
|
|
640
|
+
const normalizedGraph = this.normalizeGraph(graph);
|
|
641
|
+
const symbolIds = new Set(normalizedGraph.symbols.map(symbol => symbol.id));
|
|
642
|
+
const upsertMetadata = database.prepare(SQLITE_SCHEMA_VERSION_UPSERT_SQL);
|
|
643
|
+
const insertProject = database.prepare(`
|
|
644
|
+
INSERT INTO projects (id, name, root_path, created_at, updated_at)
|
|
645
|
+
VALUES (?, ?, ?, ?, ?)
|
|
646
|
+
`);
|
|
647
|
+
const insertModule = database.prepare(`
|
|
648
|
+
INSERT INTO modules (
|
|
649
|
+
id, project_id, path, language, lines, code_lines, comment_lines, blank_lines
|
|
650
|
+
)
|
|
651
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
652
|
+
`);
|
|
653
|
+
const insertSymbol = database.prepare(`
|
|
654
|
+
INSERT INTO symbols (
|
|
655
|
+
id, module_id, name, kind, signature, file_path, line, column_number, end_line, end_column, visibility
|
|
656
|
+
)
|
|
657
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
658
|
+
`);
|
|
659
|
+
const insertDependency = database.prepare(`
|
|
660
|
+
INSERT INTO dependencies (
|
|
661
|
+
id, source_id, source_entity_type, target_id, target_entity_type, dependency_type, file_path, line, confidence
|
|
662
|
+
)
|
|
663
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
664
|
+
`);
|
|
665
|
+
const insertSnapshotMirror = database.prepare(`
|
|
666
|
+
INSERT INTO snapshots (id, project_id, graph_json, updated_at)
|
|
667
|
+
VALUES (?, ?, ?, ?)
|
|
668
|
+
`);
|
|
669
|
+
const insertHistorySnapshot = database.prepare(`
|
|
670
|
+
INSERT INTO history_snapshots (
|
|
671
|
+
id, project_id, recorded_at, snapshot_source, module_count, symbol_count, dependency_count
|
|
672
|
+
)
|
|
673
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
674
|
+
`);
|
|
675
|
+
const insertHistoryRelation = database.prepare(`
|
|
676
|
+
INSERT INTO history_relations (
|
|
677
|
+
id, snapshot_id, relation_type, source_id, source_entity_type, target_id, target_entity_type, observed_at, metadata_json
|
|
678
|
+
)
|
|
679
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
680
|
+
`);
|
|
681
|
+
const snapshotId = createHistorySnapshotId(recordedAt);
|
|
682
|
+
this.runInTransaction(database, () => {
|
|
683
|
+
database.prepare('DELETE FROM dependencies').run();
|
|
684
|
+
database.prepare('DELETE FROM symbols').run();
|
|
685
|
+
database.prepare('DELETE FROM modules').run();
|
|
686
|
+
database.prepare('DELETE FROM projects').run();
|
|
687
|
+
database.prepare('DELETE FROM snapshots').run();
|
|
688
|
+
insertProject.run(normalizedGraph.project.id, normalizedGraph.project.name, normalizedGraph.project.rootPath, normalizedGraph.project.createdAt.toISOString(), normalizedGraph.project.updatedAt.toISOString());
|
|
689
|
+
for (const module of normalizedGraph.modules) {
|
|
690
|
+
insertModule.run(module.id, module.projectId, module.path, module.language, module.stats.lines, module.stats.codeLines, module.stats.commentLines, module.stats.blankLines);
|
|
691
|
+
}
|
|
692
|
+
for (const symbol of normalizedGraph.symbols) {
|
|
693
|
+
insertSymbol.run(symbol.id, symbol.moduleId, symbol.name, symbol.kind, symbol.signature ?? null, symbol.location.file, symbol.location.line, symbol.location.column, symbol.location.endLine ?? null, symbol.location.endColumn ?? null, symbol.visibility);
|
|
694
|
+
}
|
|
695
|
+
for (const dependency of normalizedGraph.dependencies) {
|
|
696
|
+
const sourceEntityType = resolveEntityType(dependency.sourceEntityType, dependency.sourceId, symbolIds);
|
|
697
|
+
const targetEntityType = resolveEntityType(dependency.targetEntityType, dependency.targetId, symbolIds);
|
|
698
|
+
insertDependency.run(dependency.id, dependency.sourceId, sourceEntityType, dependency.targetId, targetEntityType, dependency.type, dependency.filePath ?? null, dependency.line ?? null, dependency.confidence ?? null);
|
|
699
|
+
}
|
|
700
|
+
insertSnapshotMirror.run('codemap-snapshot', normalizedGraph.project.id, serializeCodeGraphSnapshot(normalizedGraph), recordedAt);
|
|
701
|
+
insertHistorySnapshot.run(snapshotId, normalizedGraph.project.id, recordedAt, historySource, normalizedGraph.modules.length, normalizedGraph.symbols.length, normalizedGraph.dependencies.length);
|
|
702
|
+
for (const module of normalizedGraph.modules) {
|
|
703
|
+
insertHistoryRelation.run(`${snapshotId}:module:${module.id}`, snapshotId, 'module_snapshot', module.id, 'module', null, null, recordedAt, JSON.stringify({
|
|
704
|
+
path: module.path,
|
|
705
|
+
language: module.language,
|
|
706
|
+
}));
|
|
707
|
+
}
|
|
708
|
+
for (const symbol of normalizedGraph.symbols) {
|
|
709
|
+
insertHistoryRelation.run(`${snapshotId}:symbol:${symbol.id}`, snapshotId, 'symbol_snapshot', symbol.id, 'symbol', symbol.moduleId, 'module', recordedAt, JSON.stringify({
|
|
710
|
+
name: symbol.name,
|
|
711
|
+
kind: symbol.kind,
|
|
712
|
+
signature: symbol.signature ?? null,
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
for (const dependency of normalizedGraph.dependencies) {
|
|
716
|
+
const sourceEntityType = resolveEntityType(dependency.sourceEntityType, dependency.sourceId, symbolIds);
|
|
717
|
+
const targetEntityType = resolveEntityType(dependency.targetEntityType, dependency.targetId, symbolIds);
|
|
718
|
+
insertHistoryRelation.run(`${snapshotId}:dependency:${dependency.id}`, snapshotId, 'dependency_snapshot', dependency.sourceId, sourceEntityType, dependency.targetId, targetEntityType, recordedAt, JSON.stringify({
|
|
719
|
+
dependencyId: dependency.id,
|
|
720
|
+
dependencyType: dependency.type,
|
|
721
|
+
confidence: dependency.confidence ?? null,
|
|
722
|
+
filePath: dependency.filePath ?? null,
|
|
723
|
+
line: dependency.line ?? null,
|
|
724
|
+
}));
|
|
725
|
+
}
|
|
726
|
+
upsertMetadata.run(GRAPH_STATUS_METADATA_KEY, normalizedGraph.graphStatus ?? 'complete');
|
|
727
|
+
upsertMetadata.run(FAILED_FILE_COUNT_METADATA_KEY, String(normalizedGraph.failedFileCount ?? 0));
|
|
728
|
+
upsertMetadata.run(PARSE_FAILURE_FILES_METADATA_KEY, JSON.stringify(normalizedGraph.parseFailureFiles ?? []));
|
|
729
|
+
upsertMetadata.run(LAST_GRAPH_SYNC_AT_METADATA_KEY, recordedAt);
|
|
730
|
+
});
|
|
731
|
+
this.refreshGovernanceGraphCache(database);
|
|
732
|
+
}
|
|
733
|
+
normalizeGraph(graph) {
|
|
734
|
+
const projectRootPath = graph.project.rootPath || this.projectPath || '';
|
|
735
|
+
return {
|
|
736
|
+
project: {
|
|
737
|
+
...graph.project,
|
|
738
|
+
rootPath: projectRootPath,
|
|
739
|
+
createdAt: new Date(graph.project.createdAt),
|
|
740
|
+
updatedAt: new Date(graph.project.updatedAt),
|
|
741
|
+
},
|
|
742
|
+
modules: graph.modules.map(module => ({
|
|
743
|
+
...module,
|
|
744
|
+
stats: { ...module.stats },
|
|
745
|
+
})),
|
|
746
|
+
symbols: graph.symbols.map(symbol => ({
|
|
747
|
+
...symbol,
|
|
748
|
+
location: { ...symbol.location },
|
|
749
|
+
})),
|
|
750
|
+
dependencies: graph.dependencies.map(dependency => ({ ...dependency })),
|
|
751
|
+
graphStatus: graph.graphStatus ?? 'complete',
|
|
752
|
+
failedFileCount: graph.failedFileCount ?? 0,
|
|
753
|
+
parseFailureFiles: [...(graph.parseFailureFiles ?? [])],
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
readCodeGraph(database) {
|
|
757
|
+
const projectRow = database
|
|
758
|
+
.prepare(`
|
|
759
|
+
SELECT id, name, root_path, created_at, updated_at
|
|
760
|
+
FROM projects
|
|
761
|
+
LIMIT 1
|
|
762
|
+
`)
|
|
763
|
+
.get();
|
|
764
|
+
if (!projectRow) {
|
|
765
|
+
return createEmptyCodeGraph(this.projectPath ?? '');
|
|
766
|
+
}
|
|
767
|
+
const modules = database
|
|
768
|
+
.prepare(`
|
|
769
|
+
SELECT id, project_id, path, language, lines, code_lines, comment_lines, blank_lines
|
|
770
|
+
FROM modules
|
|
771
|
+
ORDER BY path, id
|
|
772
|
+
`)
|
|
773
|
+
.all()
|
|
774
|
+
.map(row => this.mapModuleRow(row));
|
|
775
|
+
const symbols = database
|
|
776
|
+
.prepare(`
|
|
777
|
+
SELECT id, module_id, name, kind, signature, file_path, line, column_number, end_line, end_column, visibility
|
|
778
|
+
FROM symbols
|
|
779
|
+
ORDER BY file_path, line, column_number, id
|
|
780
|
+
`)
|
|
781
|
+
.all()
|
|
782
|
+
.map(row => this.mapSymbolRow(row));
|
|
783
|
+
const dependencies = database
|
|
784
|
+
.prepare(`
|
|
785
|
+
SELECT id, source_id, source_entity_type, target_id, target_entity_type, dependency_type, file_path, line, confidence
|
|
786
|
+
FROM dependencies
|
|
787
|
+
ORDER BY id
|
|
788
|
+
`)
|
|
789
|
+
.all()
|
|
790
|
+
.map(row => this.mapDependencyRow(row));
|
|
791
|
+
const graphIntegrity = readGraphIntegrityMetadata(database);
|
|
792
|
+
return {
|
|
793
|
+
project: {
|
|
794
|
+
id: toStringValue(projectRow.id),
|
|
795
|
+
name: toStringValue(projectRow.name),
|
|
796
|
+
rootPath: toStringValue(projectRow.root_path, this.projectPath ?? ''),
|
|
797
|
+
createdAt: new Date(toStringValue(projectRow.created_at)),
|
|
798
|
+
updatedAt: new Date(toStringValue(projectRow.updated_at)),
|
|
799
|
+
},
|
|
800
|
+
modules,
|
|
801
|
+
symbols,
|
|
802
|
+
dependencies,
|
|
803
|
+
graphStatus: graphIntegrity.graphStatus,
|
|
804
|
+
failedFileCount: graphIntegrity.failedFileCount,
|
|
805
|
+
parseFailureFiles: graphIntegrity.parseFailureFiles,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
mapModuleRow(row) {
|
|
809
|
+
return {
|
|
810
|
+
id: toStringValue(row.id),
|
|
811
|
+
projectId: toStringValue(row.project_id),
|
|
812
|
+
path: toStringValue(row.path),
|
|
813
|
+
language: toStringValue(row.language),
|
|
814
|
+
stats: {
|
|
815
|
+
lines: toNumberValue(row.lines),
|
|
816
|
+
codeLines: toNumberValue(row.code_lines),
|
|
817
|
+
commentLines: toNumberValue(row.comment_lines),
|
|
818
|
+
blankLines: toNumberValue(row.blank_lines),
|
|
819
|
+
},
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
mapSymbolRow(row) {
|
|
823
|
+
return {
|
|
824
|
+
id: toStringValue(row.id),
|
|
825
|
+
moduleId: toStringValue(row.module_id),
|
|
826
|
+
name: toStringValue(row.name),
|
|
827
|
+
kind: toStringValue(row.kind),
|
|
828
|
+
signature: toStringValue(row.signature) || undefined,
|
|
829
|
+
location: {
|
|
830
|
+
file: toStringValue(row.file_path),
|
|
831
|
+
line: toNumberValue(row.line),
|
|
832
|
+
column: toNumberValue(row.column_number),
|
|
833
|
+
endLine: toOptionalNumberValue(row.end_line),
|
|
834
|
+
endColumn: toOptionalNumberValue(row.end_column),
|
|
835
|
+
},
|
|
836
|
+
visibility: toStringValue(row.visibility),
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
mapDependencyRow(row) {
|
|
840
|
+
return {
|
|
841
|
+
id: toStringValue(row.id),
|
|
842
|
+
sourceId: toStringValue(row.source_id),
|
|
843
|
+
sourceEntityType: toStringValue(row.source_entity_type, 'module'),
|
|
844
|
+
targetId: toStringValue(row.target_id),
|
|
845
|
+
targetEntityType: toStringValue(row.target_entity_type, 'module'),
|
|
846
|
+
type: toStringValue(row.dependency_type),
|
|
847
|
+
filePath: toStringValue(row.file_path) || undefined,
|
|
848
|
+
line: toOptionalNumberValue(row.line),
|
|
849
|
+
confidence: toStringValue(row.confidence) === '' ? undefined : toStringValue(row.confidence),
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
runInTransaction(database, callback) {
|
|
853
|
+
database.exec('BEGIN');
|
|
854
|
+
try {
|
|
855
|
+
callback();
|
|
856
|
+
database.exec('COMMIT');
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
database.exec('ROLLBACK');
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
getDatabase() {
|
|
864
|
+
this.ensureInitialized();
|
|
865
|
+
if (!this.database) {
|
|
866
|
+
throw new StorageError('SQLite connection not initialized', 'SQLITE_CONNECTION_NOT_READY');
|
|
867
|
+
}
|
|
868
|
+
return this.database;
|
|
869
|
+
}
|
|
870
|
+
readGovernanceGraphForAnalysis(database) {
|
|
871
|
+
return readGovernanceGraphFromSQLite(database, this.projectPath ?? '');
|
|
872
|
+
}
|
|
873
|
+
refreshGovernanceGraphCache(database) {
|
|
874
|
+
const cache = new GovernanceGraphCache(database, this.projectPath ?? '', this.runtimeOptions.governanceGraphThresholds ?? DEFAULT_GOVERNANCE_GRAPH_PERF_THRESHOLDS);
|
|
875
|
+
cache.hydrate();
|
|
876
|
+
this.governanceGraphCache = cache;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
//# sourceMappingURL=SQLiteStorage.js.map
|