Skip to content

cli: graph drift semantics — status, staleness, catch-up, hooks, doctor, watch (spec steps 1–6)#35

Merged
jschatz1 merged 7 commits into
mainfrom
drift/01-graph-refs-status
Jul 11, 2026
Merged

cli: graph drift semantics — status, staleness, catch-up, hooks, doctor, watch (spec steps 1–6)#35
jschatz1 merged 7 commits into
mainfrom
drift/01-graph-refs-status

Conversation

@jschatz1

@jschatz1 jschatz1 commented Jul 10, 2026

Copy link
Copy Markdown
Member

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; --json gains the drift block. 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-ref keep the legacy snapshot comparison.

Per-query staleness (step 3)

kai query callers|dependents|impact, kai test affected, kai blame: neighborhood classified before printing; stale-refused suppresses the answer with a reason; one stderr annotation line otherwise (--quiet, fresh silent); --strict/staleness.strict → exit 75; blame --json embeds the block; kai prime appends a ## Staleness section outside its char budget.

Checkpointed catch-up (step 4)

catchUpDrift: oldest-first, one checkpoint per commit (snapshot from git ref — working tree untouched — then git.<sha> ref, graph_refs pin, manifest retirement). SIGKILL-safe and resumable. Inline catch-up on the query path under staleness.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 uninstall removes every kai-managed hook; kai doctor drift section (relationship, graph_refs readability, manifest consistency; --fix rebuilds derived cache, never touches pins).

Watch daemon (step 6)

kai watch gains an independent git-state channel: fsnotify on .git HEAD/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. --once runs 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 --since resumes 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

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.
@kaicontext

kaicontext Bot commented Jul 10, 2026

Copy link
Copy Markdown

🔍 Kai review

Note

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.
@jschatz1 jschatz1 changed the title cli: report graph↔git drift in kai status; pin graph_refs on import cli: kai status drift reporting + kai shadow drift detail view (spec steps 1–2) Jul 10, 2026
…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.
@jschatz1 jschatz1 changed the title cli: kai status drift reporting + kai shadow drift detail view (spec steps 1–2) cli: drift status, shadow drift detail, per-query staleness + prime block (spec steps 1–3) Jul 10, 2026
…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.
@jschatz1 jschatz1 changed the title cli: drift status, shadow drift detail, per-query staleness + prime block (spec steps 1–3) cli: drift status, staleness classes, checkpointed catch-up (spec steps 1–4) Jul 11, 2026
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.
@jschatz1 jschatz1 changed the title cli: drift status, staleness classes, checkpointed catch-up (spec steps 1–4) cli: graph drift semantics — status, staleness, catch-up, hooks, doctor (spec steps 1–5) Jul 11, 2026
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).
@jschatz1 jschatz1 changed the title cli: graph drift semantics — status, staleness, catch-up, hooks, doctor (spec steps 1–5) cli: graph drift semantics — status, staleness, catch-up, hooks, doctor, watch (spec steps 1–6) Jul 11, 2026
@jschatz1
jschatz1 merged commit 62a2b09 into main Jul 11, 2026
5 checks passed
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.

1 participant