cli: graph drift semantics — status, staleness, catch-up, hooks, doctor, watch (spec steps 1–6)#35
Merged
Merged
Conversation
kai status gains a Graph: section (human) and a drift block (--json) classifying the semantic graph against git HEAD: synced, graph-behind (with unprocessed count and oldest-commit age), graph-ahead, diverged (both legs), orphaned, or unpinned — each with an action hint. Classification is merge-base/rev-list only, no semantic work on the status path. Pin sites: the post-commit bridge import records each processed commit in .kai/graph-refs.json, and kai import pins every replayed commit (batched, one write), attributing the branch ref only when the branch tip is the last imported commit.
🔍 Kai reviewNote Reviewing this change… The verification result will appear here shortly. |
kai shadow drift (no flags) now shows the git-drift detail: the graph↔git relationship, each unprocessed commit with files touched and import targets, the superseded leg of a divergence, and the manifest location. Explicit --snap/--git-ref keep the legacy snapshot-content comparison. The drift manifest is synced after the bridge-import and kai-import pin sites, so catch-up shrinks it and new drift extends it without per-query git work.
…ness block Query commands (query callers/dependents/impact, test affected, blame) now classify their answer against graph drift before printing: an orphaned graph or intersection above staleness.refuse_after_intersecting refuses with the reason instead of answering; otherwise the answer prints with a one-line stale-valid/stale-suspect annotation on stderr (suppressible with --quiet). fresh stays silent. --strict (or staleness.strict in config) makes stale-suspect exit 75, following the CI tripwire convention. blame --json embeds the full staleness block without changing existing output shapes. kai prime appends a '## Staleness' section to injected context — outside the char budget so truncation can't drop the trust signal — listing intersecting commits and steering the agent to prefer current file contents over stale symbol lists.
…since catchUpDrift processes unprocessed commits oldest-first, one checkpoint per commit: semantic snapshot built from the git ref (the working tree is never touched), git.<sha> ref, graph_refs pin, manifest retirement. The pin is written only after the snapshot lands and snapshots are content-addressed, so interruption at any point — budget, ctrl-C, SIGKILL — leaves the graph at the last completed commit and a re-run resumes there, re-doing at most one commit's work. Query commands catch up inline under staleness.inline_budget_ms (default 2000; explicit 0 disables): small drift vanishes instead of being annotated, and whatever remains past the budget is classified honestly. A time budget rather than a commit count — commit cost varies too much for a count to mean anything. Diverged graphs are never caught up inline; a history rewrite deserves an explicit action. kai import --since <sha> treats <sha> as the last-processed commit and imports everything after it, checkpointed with progress; a re-run resumes from the advanced pin instead of re-importing.
post-rewrite joins the bridge hook set (install, doctor, init-upgrade paths): after a rebase or amend, each rewritten commit is imported and the graph follows the new line instead of sitting diverged on orphaned SHAs until the next commit. Same best-effort discipline as the other bridge hooks — never blocks git. kai hook uninstall now removes every kai-managed hook (the scripts all say 'Remove with: kai hook uninstall'; previously only pre-commit was honored). Foreign hooks are left untouched. kai doctor gains a drift section: relationship with an action hint (orphaned is an error), graph_refs readability, and manifest consistency with the current (graphState, gitHead). --fix rebuilds a stale or corrupt manifest — it's derived cache; graph_refs pins are assertions and are never auto-modified.
The watch daemon gains a second, independent channel: fsnotify on .git's HEAD, ORIG_HEAD, packed-refs and refs tree (index and object churn deliberately excluded — staging is not drift), debounced into a single-flight convergence pass, with a 30s poll backstop for missed events. Each pass runs the checkpointed catch-up when the graph is behind or diverged, and just resyncs the manifest on ref switches. The DB is held only for the duration of a pass; failures print and retry on the next signal or tick. kai watch --once runs a drift pass before the structural pass, so a single invocation leaves both the graph and the verdict current. With the daemon running, queries stop paying the inline catch-up cost: commits converge as they land (verified live with git hooks disabled — fsnotify alone caught the commit within the debounce window).
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.
Steps 1–6 of the graph staleness / git drift spec — the complete local-tier implementation. Companion to kaicontext/kai-engine#23, which this depends on. (Step 7, server webhook + pull fast-forward, is specced as separate work.)
kai status (step 1)
Drift section per relationship (synced / graph-behind / graph-ahead / diverged / orphaned / unpinned) with action hints;
--jsongains thedriftblock. Merge-base/rev-list only on the status path.kai shadow drift (step 2)
No-flag default is the drift detail view: per-commit files, import-target annotations, superseded leg on divergence, manifest state.
--snap/--git-refkeep the legacy snapshot comparison.Per-query staleness (step 3)
kai query callers|dependents|impact,kai test affected,kai blame: neighborhood classified before printing;stale-refusedsuppresses the answer with a reason; one stderr annotation line otherwise (--quiet,freshsilent);--strict/staleness.strict→ exit 75;blame --jsonembeds the block;kai primeappends a## Stalenesssection outside its char budget.Checkpointed catch-up (step 4)
catchUpDrift: oldest-first, one checkpoint per commit (snapshot from git ref — working tree untouched — thengit.<sha>ref, graph_refs pin, manifest retirement). SIGKILL-safe and resumable. Inline catch-up on the query path understaleness.inline_budget_ms(default 2000).kai import --since <sha>for bulk, with progress and pin-forward resume.Hooks + doctor (step 5)
post-rewrite bridge hook (rebase/amend keeps the graph on the new line);
kai hook uninstallremoves every kai-managed hook;kai doctordrift section (relationship, graph_refs readability, manifest consistency;--fixrebuilds derived cache, never touches pins).Watch daemon (step 6)
kai watchgains an independent git-state channel: fsnotify on.gitHEAD/ORIG_HEAD/packed-refs/refs (index and object churn excluded — staging is not drift), debounced single-flight convergence, 30s poll backstop for missed events. Behind/diverged → unbounded checkpointed catch-up; ref switch → manifest resync. DB held only per pass; failures retry on the next tick.--onceruns a drift pass before the structural pass. With the daemon live, queries stop paying inline catch-up: verified with git hooks fully disabled — fsnotify alone converged a landed commit within the 1s debounce.Config slice:
staleness.strict,staleness.refuse_after_intersecting,staleness.inline_budget_ms.Verified end-to-end
Every relationship state driven live; inline catch-up heals drift on query;
import --sinceresumes after SIGKILL mid-run (lands on a completed checkpoint, honest count); doctor detects/installs the post-rewrite hook and an amend stays in sync; the watch daemon converges commits landed with hooks disabled and logs each catch-up. Suites green:cmd/kai,internal/config, engine drift/status, vet, gofmt on all touched files.Note for CI
Builds against local engine via
go.work; proxy-resolved CI needs an engine tag containing kaicontext/kai-engine#23.🤖 Generated with Claude Code