Repository navigation
[AI-2176] CLI: evidence-based repo association for sessions started outside a repo - #648
Conversation
…ack via final-drain batch Spec review findings on the watcher-integration work: - Scanner latched Done the moment FindRoot found a .git dir, before checking whether the root resolved to a usable owner/repo. A no-remote local repo edited first permanently blocked a later real GitHub repo from attributing. RepoEvidenceScanner is now generic over TRepo with an injected async resolver + completeness predicate; a root is only latched once resolved AND complete, and each distinct root is resolved at most once (cached). - The read-fallback promotion for a clean session end (StopWatcher, no parent-exit) was applied after the final drain's batch had already been sent, so a correctly-computed fallback was never transmitted. Promotion now happens inside DrainNewLines's isFinalDrain branch, before repoToSend is computed, so it rides the same live-hub batch that goes out on every exit that runs a final drain.
Part A whole-branch review, item 1: the final-drain promote-and-deliver path (Finding 1's fix) was verified only by inspection. Extract it into WatchCommand.ApplyEvidenceScanAsync — a small seam over WatchState + the generic scanner, no HubConnection needed — and unit-test with a fake scanner that a read-only line's fallback lands on state.Repository when isFinalDrain is true, and stays untouched when it's false.
…ce scan Part A whole-branch review, item 2: File.ReadLines(session.FilePath) was passed as a call argument to TryBuildEvidenceRepositoryNodeAsync, i.e. evaluated outside that helper's own try/catch. File.ReadLines validates its path argument eagerly (confirmed: an empty path throws ArgumentException synchronously, before the lazy file read), so an invalid path would mark an otherwise-importable session Errored instead of importing without evidence. Add a path-based overload that reads inside its own try and delegates to the existing line-based one; the call site now passes session.FilePath directly instead of pre-evaluating File.ReadLines.
PR Summary by QodoCLI: infer repository from tool-use path evidence for outside-repo sessions
AI Description
Diagram
High-Level Assessment
Files changed (7)
|
Code Review by Qodo
1.
|
… too Windows CI + qodo review on PR #648: - qodo #3 / Windows CI failure: SafeDirectory used Path.GetDirectoryName, which rewrites/misreads the OTHER OS's separator style (a Unix path run through Windows' Path becomes garbage, and vice versa) instead of the transcript-producing OS's own convention. Replaced with a lexical last-separator split. ExtractClaudePaths' path.StartsWith('/') gate likewise dropped every Windows-absolute path; replaced with IsLexicallyAbsolute, mirroring RepoAttributionMatcher.IsLexicallyAbsolute from the server repo (reimplemented locally, no dependency taken) — Unix-rooted, Windows drive-rooted, or UNC, purely lexical either way. - qodo #2: a tool_use block can sit at an assistant event's top-level content, not only nested under message.content; ExtractClaudePaths now checks both.
qodo #1 (High) on PR #648: both new transcript reads (the watcher's one-shot prefix scan, and ImportCommand's path-based evidence overload) used File.ReadLines, which opens FileShare.Read — mandatory-exclusive on Windows, so it can deny the write handle the agent itself still holds on that same transcript mid-flush. Added WatchCommand.ReadLinesShared: a line-yielding sibling of the existing ReadAllTextShared/ReadAllTextSharedAsync helpers (same FileShare.ReadWrite, same rationale, doc-commented in place already), streamed rather than materialized so a scan that attributes early doesn't pay for the whole file. Both call sites now use it; ImportCommand reuses it directly since it's in the same project/namespace.
Part of AI-2176.
What & why
Coding-agent sessions launched outside any git repo (e.g. from a parent dir like
~/devthat holds sibling checkouts) currently never get associated with the repo they actually work in — repo identity is derived once from the launch cwd, andkcap watchre-probes that same cwd forever. This is the live-detection half of the fix: when the launch cwd is not in a git tree, derive the repo once from evidence — the absolute file paths in the session's Claude tool-use inputs — and deliver it over the existing per-batchrepositorypayload, so the server's existing first-wins freeze fills the NULL primary. No server change is needed for this path.(The companion server PR adds the admin backfill for already-recorded sessions. Full design: spec
docs/superpowers/specs/2026-08-21-ai2176-repo-association-evidence-design.md, rev 7, in the kcap-server repo.)Changes
RepoEvidenceScanner<TRepo>(Capacitor.Cli.Core/RepoEvidence/) — pure, generic scanner. From each Claudetool_useinput it extracts absolute paths (mutation toolsEdit/MultiEdit/Write/NotebookEditfirst, read toolsRead/Glob/Grepas fallback), resolves each candidate root via injectedfindRoot+ asyncresolve, and attributes the first mutation-derived root that resolves to a detectable owner/repo (read-derived root is the fallback, promoted only if no mutation attributed). Resolution is part of the win condition, so a no-remote/unresolvable root never latches; per-root resolve caching bounds the git cost. Path extraction lives in the non-genericRepoEvidencePaths.WatchCommand.cs) — creates the scanner only for a Claude session watcher whose launch cwd is not in a repo (in-repo sessions are completely unaffected — D1). One-shot prefix scan on startup (restart/crash recovery), per-drained-line feeding, and final-drain promotion of the read fallback before the batch is sent over the live hub (so read-only sessions deliver on a clean session end, not only abnormal exits). The 60s launch-cwd refresh can never clear an evidence-derived payload.ImportCommand.cs) —kcap importapplies the same scanner to newly-imported transcripts whose launch cwd is not in a repo, attaching arepositorynode to the session-start payload (fail-open on an unreadable transcript).Tests
RepoEvidenceScannerTests(10) — two-slot rule, mutation-over-read priority, incomplete-root-doesn't-latch-then-later-complete-root-wins, stop-after-attribution, relative/temp/non-repo paths ignored, fail-open, evidence-is-input (a failed tool result still counts), tool classification.WatchRepoEvidenceTests(3) — the 60s-refresh guard never clears an evidence payload; final-drain delivers the read fallback tostate.Repository; non-final drain does not promote.ImportRepoEvidenceTests(5) — outside-repo transcript gains arepositorynode; read-only promotes the fallback; no evidence → null; invalid path degrades to null.Scope notes
C:\/C://UNC forms).repo_hash IS NULLrows.🤖 Generated with Claude Code