Skip to content

feat(git): git-diff aware incremental re-index — closes #14#26

Merged
Wolfvin merged 8 commits into
mainfrom
feat/issue-14-git-diff-watcher
Jun 28, 2026
Merged

feat(git): git-diff aware incremental re-index — closes #14#26
Wolfvin merged 8 commits into
mainfrom
feat/issue-14-git-diff-watcher

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

Implements issue #14 — git-diff aware incremental re-index. Adds optional git-diff awareness so incremental scans target exactly the files git knows changed (tracked + untracked), instead of relying solely on filesystem mtime polling. All features gracefully degrade to None / [] / False / mtime-fallback when git is unavailable or the workspace is not a git repo.

Closes #14.

Files Touched

Created

  • scripts/git_aware.py — new module: SHA tracking, changed-file detection, registry_meta bookmark, branch-switch detection, rescan predicate
  • scripts/commands/git_status.py — new git-status command (auto-registered)
  • tests/test_git_aware.py — 32 test cases across 9 classes

Modified

  • scripts/persistent_registry.py — calls init_registry_meta(conn) during _init_schema (additive — only adds registry_meta table init block)
  • scripts/incremental.pyfind_changed_files tries git-diff path FIRST, falls back to mtime (signature backward-compatible; new optional db_path kwarg)
  • scripts/commands/scan.py — after successful scan, persists last_indexed_sha + last_indexed_branch; scan output now includes git field (only my additions — no other logic touched)
  • scripts/commands/diff.py — new --git-aware flag; default snapshot-diff behavior unchanged (purely additive)
  • scripts/commands/watch.py — new --git-mode + --interval flags; default watchdog behavior preserved (BOS decision: keep watchdog as default, ADD git-awareness as alternative)

Docs (additive only — no existing sections rewritten)

  • README.md — new Features bullet + new command table rows + git_aware.py in architecture tree
  • SKILL-QUICK.mdgit-status in Setup category + new trigger map entries
  • CHANGELOG.md — new Git-Aware Incremental Re-Index (issue #14) subsection under [8.2.0]
  • references/agent-integration.md — new 9.4 Git-Aware Workflow subsection with 5-step flow

Design Decisions (per BOS spec)

  1. New scripts/git_aware.py module with: get_current_sha, get_current_branch, get_changed_files(workspace, since_sha=None), get_untracked_files, get_last_indexed_sha, set_last_indexed_sha, get_last_indexed_branch, detect_branch_switch, rescan_recommended, init_registry_meta.
  2. registry_meta(key TEXT PRIMARY KEY, value TEXT) table for the SHA + branch bookmark — created by init_registry_meta(conn), called by PersistentRegistry._init_schema.
  3. incremental.find_changed_files tries git FIRST — if last_indexed_sha is set, uses git diff <sha> --name-only + git ls-files --others (catches tracked changes AND untracked new files). Falls back to existing mtime path when git unavailable, no bookmark yet, or any error. Mtime fallback stays.
  4. scan.py stores the bookmark after every successful scan — both full and incremental. The bookmark is the diff base for the next incremental.
  5. diff --git-aware returns changed_files + symbols + impact in one call. Impact uses graph_model.query_callers (empty when graph tables aren't populated — see Known Gaps).
  6. New git-status command — single-call 'do I need to re-scan?' check.
  7. watch --git-mode — polls git diff --name-only instead of watchdog. Default stays watchdog (BOS decision).
  8. No new external deps — uses subprocess to call git, no GitPython.
  9. Python 3.8+ compatible — uses from __future__ import annotations for forward refs.

Test Results

  • tests/test_git_aware.py: 32/32 pass in 1.65s
  • Broader suite (pytest tests/ --ignore=tests/test_integration.py): 568 pass, 4 fail (pre-existing), 12 skipped in 10.20s
  • The 4 failures are in test_hybrid_engine.py (confidence-field assertions) — pre-existing on main, NOT caused by this PR. Documented in worklog from worker-1.
  • test_integration.py is excluded (pre-existing OOM scanning /home/z).

Graceful Degradation

Every git-aware feature degrades cleanly when git is unavailable or the workspace is not a git repo:

Function Returns when git unavailable
get_current_sha / get_current_branch None
get_changed_files / get_untracked_files []
get_last_indexed_sha None (db missing)
set_last_indexed_sha no-op (logged at debug)
detect_branch_switch False
rescan_recommended False
git-status command status=ok, git_available=False
diff --git-aware status=ok, git_available=False, changed_files=[], symbols=[], impact=[]
watch --git-mode falls back to mtime polling with a printed notice
find_changed_files (incremental) falls back to mtime path
scan (bookmark write) bookmark left as None, scan still succeeds

Tests skip (not fail) when git is missing: pytest.mark.skipif(not _git_available(), reason='git not available').

Verified on a Temp Git Repo Fixture

The test suite creates a fresh git init temp directory per test and verifies end-to-end:

  • git-status returns status=ok with all fields populated on a real git repo, git_available=False outside git.
  • diff --git-aware returns the changed file (module.py), the symbol defined in it (hello from app.py), and an empty impact array (graph not populated by the test's incremental scan).
  • find_changed_files uses the git path when a bookmark is set, falls back to mtime when no bookmark.
  • scan writes the bookmark that matches the current HEAD SHA.
  • detect_branch_switch returns True after git checkout -b feature/x + commit, False on same-branch commit, False when back on the original branch.

Known Gaps (NOT made worse by this change)

Coordination with Parallel Workers

Two other workers are running in parallel on separate branches:

  • 2-a on feat/issue-17-compact-output (compact format + pagination + graph-schema command) — file ownership respected: I did NOT touch scripts/formatters/ or add --limit/--offset to commands. I did NOT create graph-schema.
  • 2-b on feat/issue-19-get-architecture (architecture command + architecture_engine.py) — file ownership respected: I did NOT create architecture command or architecture_engine.py.

Shared docs (README, SKILL-QUICK, CHANGELOG, agent-integration.md) were edited additively — I only ADDED my own sections; existing content was not rewritten. Other workers can add their sections in parallel without conflict.

Commits

8 logical commits following the BOS workflow:

  1. feat(git): add git_aware.py with SHA tracking + changed file detection
  2. feat(scan): store last_indexed_sha after scan
  3. feat(incremental): use git-diff for changed file detection (mtime fallback)
  4. feat(cmd): add git-status command
  5. feat(diff): add --git-aware flag for changed symbols + impact
  6. feat(watch): add --git-mode flag for git-diff polling
  7. test(git): add git_aware + git-status + diff --git-aware tests
  8. docs(git): update README, SKILL-QUICK, CHANGELOG, agent-integration

Wolfvin and others added 8 commits June 28, 2026 05:53
Adds scripts/git_aware.py with:
- get_current_sha(workspace) / get_current_branch(workspace) -> HEAD SHA / branch
- get_changed_files(workspace, since_sha) -> git diff --name-only (HEAD or <sha>)
- get_last_indexed_sha / set_last_indexed_sha -> registry_meta key/value
- detect_branch_switch(workspace, db_path) -> True when HEAD moved AND
  branch name changed (catches git checkout, not same-branch commits)
- rescan_recommended(workspace, db_path) -> True when branch switch OR
  changed files since last index (used by git-status command)
- init_registry_meta(conn) -> creates registry_meta(key TEXT PK, value TEXT)

All functions gracefully degrade to None / [] / False when git is
unavailable or the workspace is not a git repo (no exceptions raised).

persistent_registry.py: calls init_registry_meta(conn) during _init_schema
so the table always exists by the time any git-aware function tries to
read or write a bookmark. Additive — no existing table or column modified.

Refs #14.
After a successful scan (full or incremental), if git is available and
the workspace is a git repo, record the current HEAD SHA + branch in the
registry_meta table via set_last_indexed_sha(). This is the 'bookmark'
that the next 'scan --incremental' will diff against.

Additive and fail-soft: if git_aware import fails, git is unavailable,
or the workspace is not a git repo, the scan still succeeds — the
bookmark is simply left as None. Scan output now includes a 'git' field
with last_indexed_sha + last_indexed_branch so agents can verify the
bookmark was recorded.

Refs #14.
…lback)

incremental.find_changed_files now tries the git-aware path FIRST:
if a last_indexed_sha bookmark exists in registry_meta, uses
'git diff <sha> --name-only' + 'git ls-files --others' to enumerate
exactly the files git knows changed (tracked changes + untracked new
files). Deleted files (in diff but not on disk) are returned in the
deleted slot so scan.py's existing deletion cleanup path runs.

Fallback to the existing mtime-based detection is preserved for:
- non-git workspaces
- git unavailable
- first scan (no bookmark yet)
- any unexpected error in the git path

Signature is backward-compatible: db_path is a new optional kwarg.
scan.py passes db_path explicitly so the bookmark lookup is cheap.

Also adds git_aware.get_untracked_files(workspace) helper used by the
git path to catch newly-created files that haven't been git-added.

Does NOT fix issue #25 (incremental graph population) — incremental
scans still don't populate graph tables, only full scans do. Existing
behavior preserved.

Refs #14.
New 'git-status' command (auto-registered) shows the git-aware scan
state in one call:
- current HEAD SHA + branch
- last-indexed SHA + branch (from registry_meta)
- changed files count since last index
- whether a branch switch was detected
- whether a re-scan is recommended

Designed for AI agents that need a 'do I need to re-scan?' check
without running a full diff themselves. All fields degrade gracefully
to None / 0 / False when git is unavailable or the workspace is not a
git repo — status is always 'ok' in those cases (git-unavailable is
reported via git_available=False, not an error).

Refs #14.
Diff command now supports '--git-aware' which produces a single-call
'what changed + what's affected' view for AI agents:

- changed_files: paths git knows changed since last_indexed_sha
  (tracked) + untracked new files
- symbols: backend flat-registry nodes whose 'file' is in changed_files
- impact: for each changed symbol, the direct callers from the graph
  tables (graph_model.query_callers max_depth=1). Empty when graph
  tables aren't populated (e.g. incremental-only scan — issue #25);
  that's a known gap, not a bug in this command.

Default snapshot-diff behavior is unchanged — --git-aware is purely
additive. Falls back to git_available=False when git is unavailable
or the workspace is not a git repo (status stays 'ok').

Refs #14.
Watch command now supports --git-mode (default off) which switches
from watchdog file events to git-diff polling. Every --interval
seconds (default 2.0), runs 'git diff --name-only' + 'git ls-files
--others' and re-indexes only the files git knows changed.

Behavior matches the BOS decision: keep watchdog as the default
real-time path (the issue title says 'replace file-watcher polling'
but ripping out watchdog is risky), ADD git-awareness as an
alternative/complement. Falls back to mtime polling when git is
unavailable or the workspace is not a git repo.

The new --interval flag is shared between git-mode and any future
polling modes; --debounce remains for coalescing rapid changes.

Refs #14.
Adds tests/test_git_aware.py with 32 test cases across 9 classes:
- TestCurrentSha: get_current_sha / get_current_branch in & out of git
- TestChangedFiles: get_changed_files / get_untracked_files (modified,
  untracked, since-sha diff)
- TestRegistryMeta: set/get_last_indexed_sha round-trip, branch
  persistence, None handling, clear-via-None
- TestBranchSwitch: false on no-bookmark, false on same-branch commit,
  true after checkout, false when back on original, false outside git
- TestRescanRecommended: true when bookmark unset + in git, false when
  bookmark matches HEAD, true when working tree dirty
- TestGitStatusCommand: status=ok with all fields, ok outside git,
  changed_files_count after edit
- TestDiffGitAware: ok outside git, changed files in git repo, symbols
  extracted from changed files (real scan), impact empty without graph
- TestIncrementalGitPath: uses git when bookmark set, mtime fallback
  when no bookmark, mtime fallback outside git
- TestScanStoresBookmark: scan writes bookmark in git repo, no bookmark
  outside git

All git operations use a temp directory + 'git init' so tests don't
depend on the CodeLens repo's git state. If git isn't installed, every
test is skipped via pytest.mark.skipif('git not available') (NOT failed).

Test results:
- test_git_aware.py: 32/32 pass in 1.75s
- broader suite: 568 pass, 4 pre-existing failures in test_hybrid_engine.py
  (confidence-field assertions, NOT caused by this PR), 12 skipped

Refs #14.
README.md:
- Add 'Git-Aware Re-Index (v8.2)' to Features section
- Add 'git-status' and 'watch --git-mode/--interval' to Setup & Lifecycle
  command table
- Add git_aware.py to the scripts/ architecture tree

SKILL-QUICK.md:
- Add 'git-status' to Setup & Lifecycle category (now 8+ commands)
- Add 'watch [--git-mode] [--interval SECS]' to watch entry
- Add 'what changed?' -> 'diff --git-aware' to trigger map
- Add 'do I need to re-scan?' -> 'git-status' to trigger map

CHANGELOG.md ([8.2.0] section):
- Add 'Git-Aware Incremental Re-Index (issue #14)' subsection with
  Added / Changed / Non-Breaking / Known Gaps entries
- Note that issue #25 (incremental graph population) is NOT made worse

references/agent-integration.md (Section 9):
- Add '9.4 Git-Aware Workflow (v8.2+)' with 5-step flow diagram covering
  git-status -> scan --incremental -> diff --git-aware -> impact review
  -> branch-switch full-scan
- Document branch-switch detection semantics
- Document watch --git-mode behavior
- Note issue #25 known gap

All doc changes are ADDITIVE — no existing sections rewritten. Other
parallel workers (2-a, 2-b) may also add their own sections to the
same files.

Refs #14.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Wolfvin Wolfvin merged commit f2dd4f2 into main Jun 28, 2026
6 of 11 checks passed
@sonarqubecloud

Copy link
Copy Markdown

Wolfvin pushed a commit that referenced this pull request Jun 28, 2026
… (architecture)

Navigation section: 10 → 11 commands (added architecture from #28)
Pagination flags [--limit N] [--offset N] from #27 preserved on trace/search/symbols/outline/list
diff gains [--git-aware] flag from #26
Total commands: 57 → 58 (graph-schema #17 + architecture #19)
MCP tools: 55 → 56 (51 static + 5 dynamic, added codelens_architecture)
@Wolfvin Wolfvin deleted the feat/issue-14-git-diff-watcher branch June 28, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Git-diff aware incremental re-index (replace file-watcher polling)

1 participant