feat(mcp): get_node_history — per-symbol git timeline, lazy + sparse#1
Open
RiceTooCold wants to merge 4 commits into
Open
feat(mcp): get_node_history — per-symbol git timeline, lazy + sparse#1RiceTooCold wants to merge 4 commits into
RiceTooCold wants to merge 4 commits into
Conversation
New MCP/CLI tool: the evolution timeline of any line-scoped graph node (function/class/method) — every commit that touched its line range, newest first, with author/date/subject and per-commit line stats. Design: sparse versioning with on-demand query power. - Metadata only in SQLite (node_revisions + node_revision_meta, created lazily on first use so query-path opens and pre-existing DBs upgrade in place). Patch text is never stored: git is already a content-addressed store of every version, so diffs are regenerated on demand via include_patch. - Lazy compute + cache: first query runs git log -L (~50ms) and caches until HEAD moves; warm queries serve from SQLite (~25ms). - Working-tree drift mapping: indexed line numbers are remapped to HEAD coordinates by parsing git diff -U0 hunk headers, so uncommitted edits above a node no longer shift its history onto the wrong lines. Dirty files bypass the cache and are flagged computed_dirty. - co_changed option surfaces temporal coupling: files that repeatedly changed in the same commits as the symbol, which static edges miss. Benchmarked on this repo (6 provenance/coupling/volatility questions, A/B vs a raw-git baseline agent): answer parity after the drift fix, with single get_node_history calls replacing git-log + awk pipelines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019w17HvR6LS8wk1DPDDdUHH
…15th tool Upstream-readiness pass for get_node_history: - Extract the working-tree→HEAD line-range mapper into exported pure functions (cbm_range_map_init/hunk/finish) so the drift math is unit- testable without git, mirroring the pass_gitdiff parser precedent. - Add tests/test_node_history.c (15 tests): node_revisions CRUD roundtrip/overwrite/isolation on a memory store incl. lazy schema, and range-mapper cases (insert/delete above, overlap, below-range, multi- hunk accumulation, count-omitted single-line hunks, clamping). - Register the suite in test_main.c and Makefile.cbm TEST_STORE_SRCS. - Update tool counts (14 -> 15) in mcp.h/mcp.c/README/CONTRIBUTING and document get_node_history in the README feature list and tool table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019w17HvR6LS8wk1DPDDdUHH
There was a problem hiding this comment.
Pull request overview
This PR introduces a new MCP/CLI capability (get_node_history) that provides per-symbol git history (timeline of commits touching a symbol’s line range), with lazy computation and caching in the project SQLite store to work even in shallow-clone agent environments.
Changes:
- Added a node-revision cache to the SQLite store (lazy schema + CRUD APIs) for per-symbol history metadata.
- Implemented the
get_node_historyMCP tool, including a working-tree→HEAD line-range drift mapper and optional patch/co-change output. - Added a dedicated test suite for node revision storage and range mapping; updated docs/tool counts and wired tests into the build.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_node_history.c | New unit tests for node revision store CRUD and range mapping logic. |
| tests/test_main.c | Registers the new node_history test suite. |
| src/store/store.h | Adds public types/APIs for cached node revision timelines. |
| src/store/store.c | Implements lazy schema creation and node revision cache read/write paths. |
| src/mcp/mcp.h | Exposes range-mapping API for unit tests and updates tool count comment. |
| src/mcp/mcp.c | Adds get_node_history tool implementation (git parsing, caching, coupling, drift mapping). |
| README.md | Documents the new tool and updates tool-count references. |
| Makefile.cbm | Includes the new test source in the test build. |
| CONTRIBUTING.md | Updates repo structure docs for the new tool count. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1787
to
+1790
| sqlite3_stmt *stmt = | ||
| prepare_cached(s, &s->stmt_get_node_revs, | ||
| "SELECT sha, ts, author, subject, added, deleted FROM node_revisions " | ||
| "WHERE project = ?1 AND qualified_name = ?2 ORDER BY ts DESC;"); |
Comment on lines
+4744
to
+4749
| if (include_patch && fresh && i < fresh_count && i < patch_limit && fresh[i].patch) { | ||
| yyjson_mut_obj_add_strcpy(doc, item, "patch", fresh[i].patch); | ||
| if (fresh[i].patch_truncated) { | ||
| yyjson_mut_obj_add_bool(doc, item, "patch_truncated", true); | ||
| } | ||
| } |
Comment on lines
+4564
to
+4567
| int limit = cbm_mcp_get_int_arg(args, "limit", NH_DEFAULT_LIMIT); | ||
| bool include_patch = cbm_mcp_get_bool_arg(args, "include_patch"); | ||
| int patch_limit = cbm_mcp_get_int_arg(args, "patch_limit", NH_DEFAULT_PATCH_LIMIT); | ||
| bool co_changed = cbm_mcp_get_bool_arg(args, "co_changed"); |
The audit is file:function pair-based so src/mcp/mcp.c:cbm_popen already passes, but CONTRIBUTING requires each new popen call site to carry an explicit justification entry for human review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019w17HvR6LS8wk1DPDDdUHH
…idity, staleness guard Review findings from an 8-angle adversarial pass, all verified against live repros before fixing: - Patch misattribution: emit computed results straight from git's output (miss path) and sha-match patches on cache hits — the write-then-read- back roundtrip paired `git log` order with `ORDER BY ts DESC` by index, attaching diffs to the wrong commit after rebases/cherry-picks. - Silent-empty timelines: persist is now best-effort on the miss path (response no longer depends on the store write), and a failed cache read on a hit returns an explicit error instead of total_revisions: 0. - Staleness guard: compare the file's mtime+size against its file_hashes row (new cbm_store_get_file_hash) before trusting indexed line numbers; stale files get an actionable re-index error instead of a double-shifted range whose wrong history was then cached under HEAD. - Cache poisoning: overlap-approximated (dirty) computations are no longer persisted, so a later clean tree at the same HEAD can't serve them as authoritative hits. - Uncommitted symbols: a pure-insertion hunk swallowing the whole range now short-circuits to total_revisions: 0 with an explanatory note — previously git log died past HEAD's EOF and the error blamed a stale index that reindexing could never fix. - include_patch no longer defeats the cache: warm hits re-run git with --max-count=patch_limit (all calls now bounded by NH_MAX_REVS) and skip the redundant cache rewrite; edits entirely below a symbol (zero shift, no overlap) no longer void its cache. - added/deleted counters: hunk-boundary state machine instead of a bare +++/--- prefix test, which miscounted column-0 content like "--i;" or SQL "-- comment" lines (reproduced against real git output). - Schema: node_revisions/node_revision_meta gain the same REFERENCES projects(name) ON DELETE CASCADE as every other per-project table (delete_project no longer orphans timeline rows); dropped the unqueried idx_noderev_sha; deterministic ORDER BY tiebreaker. - nh_append_patch: O(n) via tracked length; co_changed command buffer offset clamped; three duplicate stat_mtime_ns copies consolidated into platform.h cbm_stat_mtime_ns. - Docs/tests: installer template, README, npm README now list all 15 tools incl. get_node_history; tools/list and skill-content assertions updated; new mapper tests for the uncommitted-detection cases. Full suite: 4971 passed; the 88 reds are main's intentional probe reproduction suites, byte-identical to baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019c3No8anx3TLVa2n5uBAnC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Per-symbol git history timeline (
get_node_history)Extends the temporal layer from upstream DeusData#257 (file-level
change_count/last_modifiedscalars) down to symbol granularity: a new MCP/CLI tool that returns the evolution timeline of any line-scoped node (Function/Method/Class) — every commit that touched its line range, newest first, with author/date/subject and per-commit line stats.Motivation
Agents can answer what/where with the graph, but not why/when:
This matters doubly for agent environments: CI and web agents usually run on shallow clones, where
git log -L/--follow/ pickaxe return nothing. The timeline is cached in the graph DB keyed by HEAD, so a carried DB/artifact answers with a full timeline in exactly the environments where git archaeology is impossible (verified:--depth 1clone → git sees 1 commit, tool serves the full 21-revision history as a cache hit).Design (sparse versioning, on-demand queryability)
git log -L<start>,<end>:<file>(~58 ms cold), persists metadata into two new tables (node_revisions,node_revision_meta) keyed by repo HEAD; warm queries (~24 ms) never touch git. No libgit2 — aligns with upstream chore: drop optional libgit2 dependency (keep git log fallback) DeusData/codebase-memory-mcp#865.include_patch: trueregenerates diffs on demand. Full warm of all 8,725 symbols in this repo: 61 s, 16,594 revisions, 5.4 MiB.cbm_store_open_path_query) and pre-existing DBs upgrade in place; projects that never use the feature never grow the tables.git log -Lanchors at HEAD — uncommitted edits above a symbol would silently shift its history onto the wrong lines. The handler maps the range back to HEAD coordinates viagit diff -U0 HEADhunk headers; dirty files bypass the cache (cache: "computed_dirty", mapped range reported).co_changed: true: files that most often changed in the same commits as this symbol — temporal coupling at symbol granularity, generalizingFILE_CHANGES_WITH.Changes
src/store/store.{c,h}:cbm_node_revision_t+ transactional replace/get/head CRUD (prepared-statement style), lazy schema guardsrc/mcp/mcp.{c,h}:get_node_historyhandler (resolution tiers identical toget_code_snippet, quoting/validation perdetect_changes), exported purecbm_range_map_*functions for the drift mappertests/test_node_history.c: 15 tests — store CRUD roundtrip/overwrite/isolation incl. lazy schema on a memory store; range mapper: insert/delete above, overlap, below-range, multi-hunk accumulation, count-omitted single-line hunks, clamping (all passing)tests/test_main.c,Makefile.cbm: suite registrationREADME.md,CONTRIBUTING.md,mcp.{c,h}headers: tool count 14 → 15, feature/tool-table docsNo changes to existing schema paths, passes, or tools. Windows command variants follow the existing
detect_changespattern (not yet exercised on a Windows runner).Verification
include_patchregenerates exact diffs;co_changedsurfaces real coupling (e.g.handle_detect_changes↔store.cin 7 of 11 revisions)extraction_importsLSan leak on main (byte-identical to baseline, unrelated paths)Follow-ups (separate PRs): opt-in eager warm pass in BEST mode,
find_temporal_coupling/risk_hotspotsquery tools over the warmed table, commit→symbols reverse index for regression localization, rename/extract lineage stitching.🤖 Generated with Claude Code
https://claude.ai/code/session_019w17HvR6LS8wk1DPDDdUHH
Generated by Claude Code