refactor(git): kolu-git owns the resolve+watch compose loop#613
Conversation
Move the combined "resolve, watch .git/HEAD, re-resolve on change, dedup
via gitInfoEqual" loop out of `server/src/meta/git.ts` and into
`kolu-git` as a new `subscribeGitInfo(cwd, onChange) => {setCwd, stop}`
export. The server file is now a thin adapter wiring the `cwd:` channel
to `watcher.setCwd` and forwarding `onChange` to `trackRecentRepo` +
`updateServerMetadata` + `publishForTerminal("git", …)`.
Closes item 4 (remainder) of #601. Companions the #603 agent-integration
unification — kolu-git was the last integration with its compose loop
leaked out into the server. `AgentProvider` stays unchanged; git has no
session-identity axis, so a narrower cwd-driven shape is the honest
contract (per focused hickey+lowy review).
hasGitDir-based `git init` detection moves inside `subscribeGitInfo`;
server no longer imports it. `meta/github.ts` is untouched — its
lastBranch/lastRepoRoot dedup uses a narrower equality than gitInfoEqual
(branch+repoRoot vs branch+repoRoot+worktreePath) so the cache still
earns its keep.
No behavior change. Zero public-API removal (resolveGitInfo,
watchGitHead, gitInfoEqual, hasGitDir all stay exported for tests and
future consumers).
Hickey/Lowy AnalysisTwo parallel reviews were run during the planning phase (on the concrete sketch), with both landing on ship as planned. Hickey: the extraction is a net simplification — one compose loop, one module owns it. Notes two follow-up candidates (not in scope here): (F1) dissolve the Lowy: Both "Fix in this PR" actions were verified in the diff: |
|
| Step | Status | Duration | Verification |
|---|---|---|---|
| sync | ✓ | 1s | git fetch ok; forge=github; noGit=false |
| research | ✓ | 2m 2s | confirmed Option B plan; meta/git.ts consumers: resolveGitInfo/watchGitHead/gitInfoEqual/hasGitDir; meta/github.ts dedup stays (branch+repoRoot is narrower than gitInfoEqual's branch+repoRoot+worktreePath) |
| hickey+lowy | ✓ | 2m 20s | both reviewers: ship as planned. Hickey flagged F1/F2 (dissolve null→non-null restart rule via self-polling watchGitHead) as deferrable follow-ups. Lowy misread — hasGitDir already planned inside subscribeGitInfo. |
| branch | ✓ | 3s | on branch refactor/git-subscribe-extraction |
| implement | ✓ | 40s | added subscribeGitInfo to kolu-git (~60 LOC); rewrote meta/git.ts as thin adapter (~50 LOC, down from ~100) |
| check | ✓ | 10s | pnpm typecheck clean across 10 projects |
| docs | ✓ | 19s | kolu-git README updated with subscribeGitInfo in modules table and refreshed server-integration section |
| police | ✓ | 1m 38s | all 3 passes clean; hickey/lowy in-scope findings addressed (hasGitDir moved inside subscribeGitInfo) |
| fmt | ✓ | 7s | fmt applied table realignment in kolu-git README; no other changes |
| commit | ✓ | 24s | commit c5b420b pushed; branch refactor/git-subscribe-extraction tracks origin |
| test | ✓ | 47s | 18 scenarios / 214 steps passed (git-context, cwd, recent-repos, worktree) |
| create-pr | ✓ | 59s | PR #613 opened as draft; hickey/lowy analysis posted as comment |
| ci | ✓ | 2m 22s | all 10 contexts green on c5b420b |
| Total | 14m 36s |
Slowest step: ci (2m 22s)
Optimization suggestions
ciis dominated by the two e2e jobs (67s linux + 81s darwin, running in parallel). Both are at steady-state; no CI-side speedup available. For retry-only cases,/do --from ci-onlyskips everything up to ci.hickey+lowyran 2m 20s in parallel — that's the floor for two full structural reviews. A second round on the implementation diff would be cheap to skip if the planning-phase findings have already been addressed (as they were here).policetook 1m 38s for a ~170-LOC diff with zero findings. For pure-refactor PRs with tight scope, consider trusting the planning-phase review and running police only after implement (not as a separate skill invocation).research(2m 2s) spent most of its time re-reading files already read in the earlier talk-mode turn. Pre-loading the talk-mode findings into the research prompt would trim this.
Workflow completed at 2026-04-17.
The git integration now owns its resolve +
.git/HEADwatch + re-resolve loop as a singlesubscribeGitInfo(cwd, onChange) => {setCwd, stop}export fromkolu-git. The server'smeta/git.tsshrinks to a thin adapter that wires thecwd:channel intowatcher.setCwdand forwardsonChangeto the metadata publish +git:channel fan-out.This closes the last remaining slice of item 4 in #601 — the companion piece to #603's
AgentProviderunification. Before this,kolu-gitexposed pure primitives (resolveGitInfo,watchGitHead,hasGitDir,gitInfoEqual) that the server composed by hand into a stateful reconcile loop, while the agent integrations had already been standardized behind a single contract. The compose loop was integration-domain logic leaking into a server adapter.AgentProviderdeliberately stays different fromsubscribeGitInfo— agents have a first-class session-identity axis (replace the watcher whensessionKeychanges) that git doesn't have, so a unifiedValueWatcher<Deps, Output>would be false reuse. A focused Hickey+Lowy review (re-run on the concrete sketch before implementation) both landed on this conclusion.hasGitDir-basedgit initdetection moves insidesubscribeGitInfo— the server no longer imports it. All four existing primitives stay exported for tests and future consumers; the extraction is purely additive at the public-API layer.Try it locally