SqliteStore backend + annotation, audit, and query result cache tools#169
SqliteStore backend + annotation, audit, and query result cache tools#169data-douser wants to merge 10 commits intomainfrom
Conversation
Dependency ReviewThe following issues were found:
Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. License Issuesserver/package.json
OpenSSF Scorecard
Scanned Files
|
There was a problem hiding this comment.
Pull request overview
This PR migrates the server’s persistence layer from lowdb to a unified sql.js-backed SqliteStore, and introduces opt-in MCP tools for annotations, audit workflows, and query result caching while refactoring CLI-related logic into smaller modules.
Changes:
- Replace lowdb session persistence with
SqliteStore(sessions + annotations + query result cache tables). - Add new opt-in MCP tools:
annotation_*,audit_*, andquery_results_cache_*. - Refactor query/database resolution and query-result processing (interpretation + auto-caching) into dedicated modules.
Reviewed changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| server/test/src/tools/monitoring-tools.test.ts | Updates monitoring-tool tests to account for async store initialization and new config flag. |
| server/test/src/lib/sqlite-store.test.ts | Adds unit tests for SqliteStore sessions, annotations, persistence, and cache behaviors. |
| server/test/src/lib/session-data-manager.test.ts | Updates tests to await async initialization after migrating persistence backend. |
| server/src/types/sql-js.d.ts | Adds minimal typings for sql.js asm.js import + core DB surface. |
| server/src/types/monitoring.ts | Adds enableAnnotationTools config flag (default false). |
| server/src/tools/cache-tools.ts | Introduces query_results_cache_* tools (lookup/retrieve/clear/compare). |
| server/src/tools/audit-tools.ts | Introduces audit_* tools layered on annotations. |
| server/src/tools/annotation-tools.ts | Introduces annotation_* tools for CRUD + search. |
| server/src/lib/sqlite-store.ts | Implements the new unified SQLite persistence backend + cache subset retrieval. |
| server/src/lib/session-data-manager.ts | Migrates session persistence from lowdb to SqliteStore; adds getStore(). |
| server/src/lib/result-processor.ts | Extracts query result interpretation/evaluation and auto-cache pipeline. |
| server/src/lib/query-results-evaluator.ts | Adds query metadata caching with mtime-based invalidation. |
| server/src/lib/query-resolver.ts | Extracts query-path resolution via codeql resolve queries. |
| server/src/lib/database-resolver.ts | Extracts database-path resolution and caches results in-memory. |
| server/src/lib/codeql-version.ts | Adds target/actual CodeQL version tracking and mismatch warning. |
| server/src/lib/cli-tool-registry.ts | Integrates extracted resolvers/result processor and fixes output propagation + predicate handling. |
| server/src/lib/cli-executor.ts | Wires version detection into startup validation; re-exports version helpers. |
| server/src/codeql-development-mcp-server.ts | Registers new annotation/audit/cache tools at startup and initializes session manager. |
| server/package.json | Removes lowdb, adds sql.js. |
| package-lock.json | Updates lockfile to reflect dependency swap (remove lowdb/steno, add sql.js). |
Comments suppressed due to low confidence (2)
server/src/lib/sqlite-store.ts:425
updateAnnotationsetsupdated_at = datetime('now'), which produces a different string format than the ISO timestamps written increateAnnotation. This can cause incorrect ordering when sorting byupdated_at. Consider updating this to use the same format as inserts.
setClauses.push("updated_at = datetime('now')");
const db = this.ensureDb();
db.run(
`UPDATE annotations SET ${setClauses.join(', ')} WHERE id = $id`,
params as Record<string, string | number | null>,
);
server/src/lib/session-data-manager.ts:49
- Docstring says “sql.js WASM init is async”, but this store uses the asm.js build. Please adjust the wording so it’s accurate (async init is still true, but it’s not specifically WASM).
/**
* Initialize the database and ensure it's properly set up.
* Must be awaited before any session operations (sql.js WASM init is async).
*/
- Fix TOCTOU in query-results-evaluator (openSync/fstatSync/readFileSync(fd))
- Use datetime('now') consistently for annotation timestamps
- Debounce flush() with 200ms coalescing via scheduleFlush()
- Fix resultIndices to inclusive [start, end] range with clamping
- Fix WASM→asm.js comments in session-data-manager
- Fix audit-tools header comment (no separate ENABLE_AUDIT_TOOLS flag)
- Add separate maxResults parameter for SARIF in cache-tools
- Use createProjectTempDir() in all test files
- Fix monitoring-tools test init order (mock before initialize)
- Add store.close() in session-data-manager test afterEach
- Bound metadataCache to 256 entries with oldest-first eviction - Make SqliteStore.initialize() idempotent (close existing db first) - Fix TOCTOU in initialize(): try readFileSync directly instead of existsSync - Atomic flush: write to temp file + renameSync to prevent corruption - Clarify annotation_search uses substring LIKE matching, not FTS - Close store in monitoring-tools test afterEach to prevent timer leaks
|
@copilot apply changes based on the comments in this thread |
…fallback, JSON.parse fallback Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/7ff5a5d7-9ac1-4964-8b54-e233d7253301 Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
All changes from that review thread have been applied in commit
|
|
@copilot apply changes based on the comments in this thread |
Applied in commit
|
- Fix TOCTOU in query-results-evaluator (openSync/fstatSync/readFileSync(fd))
- Use datetime('now') consistently for annotation timestamps
- Debounce flush() with 200ms coalescing via scheduleFlush()
- Fix resultIndices to inclusive [start, end] range with clamping
- Fix WASM→asm.js comments in session-data-manager
- Fix audit-tools header comment (no separate ENABLE_AUDIT_TOOLS flag)
- Add separate maxResults parameter for SARIF in cache-tools
- Use createProjectTempDir() in all test files
- Fix monitoring-tools test init order (mock before initialize)
- Add store.close() in session-data-manager test afterEach
|
@copilot apply changes based on the comments in this thread |
Applied all changes from that review thread in commit
|
Replace lowdb with sql.js (asm.js build) for zero-dependency SQLite persistence. Bundle inline with esbuild — no native modules, no external deps at runtime. SqliteStore provides three tables: - sessions: session tracking (migrated from lowdb) - annotations: key-value annotation store with categories and metadata - query_result_cache: BQRS/SARIF result caching with subset retrieval New tools (gated by ENABLE_ANNOTATION_TOOLS env var): - annotation_create, annotation_list, annotation_search, annotation_delete - audit_store_findings, audit_list_findings, audit_add_notes, audit_clear_repo - query_results_cache_lookup, query_results_cache_retrieve, query_results_cache_clear, query_results_cache_compare Code refactoring for maintainability: - Extract database-resolver.ts from cli-tool-registry.ts - Extract query-resolver.ts from cli-tool-registry.ts - Extract result-processor.ts from cli-tool-registry.ts - Extract codeql-version.ts from cli-executor.ts Bug fixes: - Fix params.output not propagated to proce- Fix params.output not propagated to proce- Fix params.output not propagated txternal predicate conditions for direct query paths Closes #165
- Fix TOCTOU in query-results-evaluator (openSync/fstatSync/readFileSync(fd))
- Use datetime('now') consistently for annotation timestamps
- Debounce flush() with 200ms coalescing via scheduleFlush()
- Fix resultIndices to inclusive [start, end] range with clamping
- Fix WASM→asm.js comments in session-data-manager
- Fix audit-tools header comment (no separate ENABLE_AUDIT_TOOLS flag)
- Add separate maxResults parameter for SARIF in cache-tools
- Use createProjectTempDir() in all test files
- Fix monitoring-tools test init order (mock before initialize)
- Add store.close() in session-data-manager test afterEach
- Bound metadataCache to 256 entries with oldest-first eviction - Make SqliteStore.initialize() idempotent (close existing db first) - Fix TOCTOU in initialize(): try readFileSync directly instead of existsSync - Atomic flush: write to temp file + renameSync to prevent corruption - Clarify annotation_search uses substring LIKE matching, not FTS - Close store in monitoring-tools test afterEach to prevent timer leaks
…fallback, JSON.parse fallback Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/7ff5a5d7-9ac1-4964-8b54-e233d7253301 Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
…ics; always apply SARIF path for SARIF format Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/219712ee-4c28-4b51-9da5-961020112e6e Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
7c71547 to
5c4f5b8
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 23 changed files in this pull request and generated 10 comments.
Comments suppressed due to low confidence (1)
server/src/tools/annotation-tools.ts:123
annotation_updateaccepts anynumberforid, including non-integers and negatives, butannotations.idis an integer primary key. Tighten the schema to an integer (and typically positive) to avoid confusing “not found” behavior for invalid inputs.
id: z.number().describe('The annotation ID to update.'),
content: z.string().optional().describe('New content (replaces existing).'),
label: z.string().optional().describe('New label (replaces existing).'),
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 26 changed files in this pull request and generated 10 comments.
Comments suppressed due to low confidence (1)
server/test/src/tools/monitoring-tools.test.ts:227
- This test mocks
sessionDataManager.getConfig()to setstorageLocation: testStorageDir, but the singletonSessionDataManager’s underlyingSqliteStoreis created from its constructor config and won’t follow the mocked storage location. That makes the test directory setup/cleanup ineffective and risks cross-test pollution. Prefer stubbingsessionDataManager.getStore()to a per-testSqliteStore(testStorageDir)instance (initialized in the test), or refactorSessionDataManager.initialize()to build the store from config.
// Mock config BEFORE initializing so the store uses the test directory
vi.spyOn(sessionDataManager, 'getConfig').mockReturnValue({
storageLocation: testStorageDir,
autoTrackSessions: true,
retentionDays: 90,
server/src/tools/audit-tools.ts
Outdated
| { | ||
| owner: z.string().describe('Repository owner.'), | ||
| repo: z.string().describe('Repository name.'), | ||
| limit: z.number().optional().describe('Maximum number of results.'), |
There was a problem hiding this comment.
audit_list_findings accepts limit as an unconstrained z.number(). Negative values can disable SQLite limits and return unbounded rows; non-integers can cause runtime errors. Consider validating this as a positive integer (and optionally enforcing a reasonable maximum).
| limit: z.number().optional().describe('Maximum number of results.'), | |
| limit: z | |
| .number() | |
| .int() | |
| .positive() | |
| .max(1000) | |
| .optional() | |
| .describe('Maximum number of results (1–1000).'), |
- annotation_list/annotation_search limit: z.number().int().positive() - annotation_list offset: z.number().int().nonnegative() - audit_list_findings limit: z.number().int().positive().max(1000) - query_results_cache_lookup: add limit param (default 50, max 500) - listAnnotations: wrap FTS MATCH in try/catch; return [] on syntax error - listAnnotations: emit LIMIT -1 when only offset is provided (SQLite requirement) - listCacheEntries: add limit filter support - SessionDataManager.initialize(): recreate SqliteStore when storageLocation changed since construction (fixes test isolation and runtime config updates) - sqlite-store tests: 3 new cases for FTS error, offset-only, cache limit
Relates to #163.
Resolves #165.
📝 Update Information
Primitive Details
annotation_*,audit_*,query_results_cache_*,SqliteStoreThis PR is for updating an existing MCP server primitive and must ONLY include these file types:
✅ ALLOWED FILES:
server/src/**/*.ts)server/src/tools/*.ts)server/test/**/*.ts)README.md, server docs)server/src/types/*.ts)server/src/lib/*.ts)package.json,tsconfig.json)🚫 FORBIDDEN FILES:
Rationale: This PR should contain only the files necessary to update and test the primitive.
🚨 PRs that include forbidden files will be rejected and must be revised.
🛑 MANDATORY PR VALIDATION CHECKLIST
BEFORE SUBMITTING THIS PR, CONFIRM:
Update Metadata
annotation_searchparameter renamed fromquerytosearch;query_results_cache_retrievegrepparameter removed🎯 Changes Description
Current Behavior
lowdb(JSON file)annotation_searchexposed aqueryparameter wired to aLIKE-based substring scan — no indexing, slow at scalequery_results_cache_retrieveincluded agrepparameter for filtering cached content by text pattern; SARIF format only used result-level retrieval whenresultIndicesorfileFilterwas explicitly suppliedextractQueryMetadataperformed two separatestatSync+readFileSynccalls (TOCTOU race) and leaked the file descriptor on cache hitsgetCacheSarifSubsetfallback usedmaxResultsasmaxLinesand returned line counts astotalResults/returnedResults, mixing result and line conceptsresult-processor.tshad duplicateimportstatements from./cli-executorand no unit testsUpdated Behavior
SqliteStore— a unified SQLite backend (via sql.js asm.js build) with three tables:sessions,annotations,query_result_cacheENABLE_ANNOTATION_TOOLS=true:annotation_create,annotation_list,annotation_search,annotation_deleteaudit_store_findings,audit_list_findings,audit_add_notes,audit_clear_repoquery_results_cache_lookup,query_results_cache_retrieve,query_results_cache_clear,query_results_cache_compareannotation_searchnow uses SQLite FTS4 (annotations_ftsvirtual table,unicode61tokenizer) for case-insensitive, token-based full-text search; parameter renamedquery→search; prefix matching via*suffix supportedquery_results_cache_retrievehas thegrepparameter removed entirely; SARIF format always routes throughgetCacheSarifSubsetsomaxResultsand result-level filters apply consistentlyextractQueryMetadataopens a single file descriptor, reads and fstats through it in atry/finallyblock that always callscloseSync(fd)— no TOCTOU, no FD leakgetCacheSarifSubsetfallback now uses a dedicatedFALLBACK_MAX_LINES = 500constant (independent ofmaxResults) and returnstotalResults: 0, returnedResults: 0to signal non-SARIF content, avoiding the results/lines concept mixresult-processor.tsimport from./cli-executorconsolidated into a single alphabetically-ordered statementSqliteStore.flush()uses write-to-temp + atomic rename on POSIX; on Windows the fallback is an in-place overwrite (documented as not crash-safe)result-processor.tscoveringcomputeQueryCacheKey,getDefaultExtension,interpretBQRSFile, andprocessQueryRunResultsMotivation
Replace lowdb with a proper relational store that supports indexed search, structured result caching, and annotation/audit workflows. Address correctness bugs (TOCTOU, FD leak, timestamp format mismatch, Windows rename failure, FIFO-not-LRU eviction, SARIF fallback semantics) and API inconsistencies (
grepin cache tools,queryvssearchnaming, duplicate imports) surfaced during code review.🔄 Before vs. After Comparison
Functionality Changes
API Changes
Output Format Changes
🧪 Testing & Validation
Test Coverage Updates
sqlite-store.test.ts(32 tests),result-processor.test.ts(23 new tests) coveringcomputeQueryCacheKey,getDefaultExtension,interpretBQRSFile,processQueryRunResultsmaxLines-only subset test replaces removedgreptest;enableAnnotationToolsconsistent in all monitoring-tools mocksresultIndicesrange clamping; Windows rename fallback; FD always closed on cache hit; SARIF fallback returns neutral result fieldsValidation Scenarios
maxResultscap; LRU eviction reorders Map on hitJSON.parsefallback inquery_results_cache_retrievefor non-SARIF content; safecloseSyncinextractQueryMetadata; SARIF fallback returnstotalResults: 0to signal non-SARIFTest Results
📋 Implementation Details
Files Modified
server/src/lib/sqlite-store.ts— SqliteStore with FTS4, true LRU metadata cache, debounced flush, atomic rename, idempotent initialize, accurate Windows fallback comment, fixed SARIF fallback semanticsserver/src/lib/query-results-evaluator.ts— true LRU cache (delete + re-insert on hit), FD-safe metadata extractionserver/src/lib/result-processor.ts— consolidated duplicatecli-executorimports;server/src/lib/database-resolver.ts;server/src/lib/query-resolver.ts;server/src/lib/codeql-version.tsserver/src/types/sql-js.d.tsserver/src/tools/annotation-tools.ts—annotation_searchusessearchparam and FTS description;server/src/tools/audit-tools.ts;server/src/tools/cache-tools.ts—grepremoved, SARIF always usesgetCacheSarifSubsetserver/test/src/lib/sqlite-store.test.ts;server/test/src/lib/result-processor.test.ts(new, 23 tests);server/test/src/tools/monitoring-tools.test.ts—enableAnnotationToolsconsistent across all mocks;server/test/src/lib/session-data-manager.test.tsCode Changes Summary
maxResultsalways applied; SARIF fallback uses separateFALLBACK_MAX_LINESconstantgrepremoved fromgetCacheContentSubsetoptionsannotation_searchusessearch;query_results_cache_retrievedropsgrepDependencies
lowdbremoved,sql.jsadded (asm.js build — zero native/WASM dependency, bundles inline)🔍 Quality Improvements
Bug Fixes
Issue:
extractQueryMetadataleaked file descriptors on cache hit and had a TOCTOU race (stat then read)Root Cause: Early return before
closeSync; separatestatSync/readFileSynccallsSolution: Single
openSyncwithtry/finallyblock containingcloseSync(fd)in all exit pathsPrevention: Code review guideline to avoid stat-then-read; enforced by repo instructions
Issue: Metadata cache described as LRU but implemented as FIFO (Map.get does not update insertion order)
Root Cause: Cache hit returned early without re-inserting the key
Solution: Cache hit now does
metadataCache.delete(queryPath); metadataCache.set(queryPath, cached)to refresh Map orderPrevention: Updated comment and unit test validate true LRU behaviour
Issue:
getCacheSarifSubsetfallback mixed "results" and "lines" concepts — passedmaxResultsasmaxLinesand returned line counts astotalResults/returnedResultsRoot Cause: Single fallback path reused SARIF-specific parameter names for line-based fallback
Solution: Fallback now uses
FALLBACK_MAX_LINES = 500and returnstotalResults: 0, returnedResults: 0to signal non-SARIF contentPrevention: Clearly separate SARIF-concept fields from line-concept fields
Issue:
flush()could corrupt the DB on Windows becauserenameSyncfails when destination exists; comment claimed crash-safe for all platformsRoot Cause:
fs.renameSyncis not atomic-replace on WindowsSolution: Catch rename errors and fall back to
writeFileSyncdirectly on the target path; comment updated to accurately state the Windows fallback is not crash-safePrevention: Documented platform-specific guarantees in JSDoc
Issue:
annotation_searchparameter namedquerybut store expectedsearch; tool description said "LIKE" but FTS4 was usedRoot Cause: API/documentation mismatch introduced when FTS4 replaced LIKE
Solution: Renamed to
search, updated description to FTS semanticsIssue:
query_results_cache_retrieveincludedgrepfor filtering cached content by text pattern; MCP tools must be self-sufficientRoot Cause: grep-based filtering concept leaked into the MCP tool API
Solution: Removed
grepentirely; SARIF format now always uses result-level retrievalIssue:
result-processor.tshad two separateimportstatements from./cli-executorRoot Cause: Import added incrementally without consolidating
Solution: Single alphabetically-ordered import statement
Issue:
monitoring-tools.test.tshad onegetConfigmock missingenableAnnotationTools, causing type inconsistencyRoot Cause: Field added later without updating all mocks
Solution:
enableAnnotationTools: falseadded to all four mocked config objectsPerformance Improvements
Code Quality Enhancements
fsimports inquery-results-evaluator.ts; consolidated duplicatecli-executorimports inresult-processor.ts; removedgrepconcept from cache tool APIcreateProjectTempDir; 23 newresult-processor.test.tstests; all monitoring-tools mocks consistentSqliteStorecleanly separates sessions, annotations, and cache into typed methods🔗 References
Related Issues/PRs
Validation Materials
server/test/src/lib/sqlite-store.test.ts(32 tests),server/test/src/lib/result-processor.test.ts(23 tests)unicode61tokenizer, trigger-based sync, and count-based backfill migration against sql.js asm.js build🚀 Compatibility & Migration
Backward Compatibility
Breaking Changes
Changes Made:
annotation_search: parameter renamed fromquerytosearchquery_results_cache_retrieve:grepparameter removed entirelygetCacheSarifSubsetfallback:totalResults/returnedResultsnow return0for non-SARIF content (was line count)lowdbreplaced bysql.js; existing session data (ephemeral JSON) is not migratedMigration Guide:
annotation_search({ query: "term" })withannotation_search({ search: "term" })grepparameter fromquery_results_cache_retrievecalls; uselineRange/maxLinesfor line-based formats andresultIndices/fileFilter/maxResultsfor SARIFgetCacheSarifSubsetdirectly, check fortotalResults === 0to detect the non-SARIF fallback pathTimeline:
API Evolution
resultIndices,fileFilter,maxResultsnow consistently applied for all SARIF retrievals{ totalResults, returnedResults, truncated, results }envelope; fallback returnstotalResults: 0to signal non-SARIF👥 Review Guidelines
For Reviewers
Please verify:
annotation_search.query→search;grepremoved from cache retrieve; SARIF fallback fields changedgrepremoved from API and store interface; all mocks consistentgrep; flush JSDoc accurate for Windowsgrepremoved fromgetCacheContentSubsetTypeScript options; allgetConfigmocks includeenableAnnotationToolsTesting Instructions
Validation Checklist
maxResultscap validated📊 Impact Assessment
Performance Impact
Server Impact
initialize(); FD always closed; timer cleared onclose()AI Assistant Impact
maxResults; LRU keeps frequently-used query metadata hotgrep; consistent SARIF envelope; unambiguous fallback fields🔄 Deployment Strategy
Rollout Considerations
ENABLE_ANNOTATION_TOOLS=truePost-Deployment Validation
queryparameter; watch for callers passinggrepto cache retrieveUpdate Methodology: This update follows best practices:
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.