Skip to content

Latest commit

 

History

History
492 lines (420 loc) · 35.4 KB

architecture.md

File metadata and controls

492 lines (420 loc) · 35.4 KB

Architecture

Lighthouse: Local Code Review is a single VS Code extension (TypeScript, compiled to out/extension.js). It presents a three-view "pull-request panel" over a local git branch comparison and persists review comments as JSON on disk. This document describes how the pieces fit together and why.

1. Design goals

  1. PR-style review with zero backend. Everything is local: local branches, local diffs, review comments as files. No server, no auth, no network round-trip to review code.
  2. Copilot is an external collaborator, not an embedded model. The extension writes a well-defined schema; a separate copilot CLI agent reads it, replies, and stamps identity. The extension holds no API keys and never issues a model call.
  3. Never clobber the original. The schema is a backward-compatible superset of Gururagavendra.local-pr-review, but Lighthouse writes to its own storage path (.vscode/lighthouse-reviews/) so both extensions can coexist.
  4. Fast activation. Git waits and shared host stalls can consume seconds during startup. Activation must return before doing Git work; local spans alone cannot identify the OS cause.
  5. Testable core. The data model is vscode-free so schema parsing and migrations can be unit tested without the extension host.

2. Module graph

extension.ts ............ activation, view wiring, command registration, Ask-Copilot bridge
  │
  ├─ activeComparison.ts . ActiveComparison — the single source of truth (base + compare + review)
  │      ├─ baseline.ts ... read-only ancestry resolution and explicit baseline-pin validation
  │      ├─ git.ts ........ no-shell git helpers (changedFiles, logRange, aheadBehind, worktrees)
  │      ├─ gitApi.ts ..... thin wrapper over the built-in vscode.git extension API
  │      └─ reviewStore.ts  VS Code discovery/I/O adapter
  │             ├─ reviewModel.ts .. vscode-free schema types, parse, normalizeSeq, formatTimestamp
  │             ├─ reviewCore.ts ... pure serialization/mutations; preserve unknown fields
  │             ├─ reviewInstructions.ts .. Post/Approve scope hashes and legacy proposal compatibility
  │             └─ reviewPersistence.ts .. shared queue, interprocess lock, atomic replacement
  │
  ├─ comparisonView.ts .... WebviewView: selectors, effective baseline, commit data/actions
  │      └─ commitsWebview.ts .. embedded compact commit-tree CSS + script
  ├─ filesWebview.ts ...... WebviewView: changed files (folder tree, reviewed-file checkboxes)
  ├─ reviewedFiles.ts ..... saved-only fingerprints, checked-path watches and coalesced validation
  │      └─ gitMetadataCache.ts .. lazy branch-local immutable tree-entry cache, bounded atomic writes
  ├─ conversationsWebview.ts .. WebviewView: threads -> comments, jump to file:line
  ├─ conversationDocument.ts . read-only virtual conversation tabs, independent of code files
  ├─ conversationModel.ts .... full transcript formatting and stable conversation references
  ├─ conversationPage.ts ..... formal discussion pages, composers and explicit Copilot actions
  ├─ conversationPageStore.ts  scoped references, draft seeds and guarded review mutations
  ├─ conversationPageModel.ts  lazily loaded safe Markdown rendering and page state
  ├─ conversationPageHtml.ts . themed conversation workspace and draft-preserving client
  ├─ commentController.ts . native vscode.comments threads (inline gutter UI)
  ├─ reviewDiff.ts ........ ReviewDiffContentProvider serving historical blobs for diffs
  ├─ tagCompletion.ts ..... /tag CompletionItemProvider for the comment input
  ├─ statusBar.ts ......... active-review status bar item
  ├─ perf.ts .............. [perf] OUTPUT-channel timing adapter
  ├─ diagnostics.ts ....... monotonic, correlated, bounded local startup trace
  ├─ webviewMetrics.ts .... state delivery / DOM readiness / paint-opportunity protocol
  ├─ usage.ts ............. bounded local feature-action and exposure summaries
  └─ usageContext.ts ...... privacy-preserving editor-context classification

mcpConfiguration.ts ..... lazy, explicit user-level setup/repair/removal command
  └─ mcpInstallation.ts .. stable owned payload and non-destructive CLI configuration
mcpIdentity.ts .......... lighthouse-codereview identity and shared platform user-data location
mcp/server.ts ........... standalone stdio entry point (never imports vscode)
  ├─ mcp/access.ts ...... launch-worktree confinement and bounded saved-review reads
  ├─ mcp/service.ts ..... seven MCP tools, paginated reads/inbox, guarded replies/claims/results
  ├─ mcp/usageLog.ts .... content-free, bounded, coordinated local JSONL call diagnostics
  └─ reviewModel / reviewCore / reviewPersistence .. shared with the UI
mcp/setup.ts ............ bundled headless setup helper and read-only protocol verification
scripts/setup-mcp.ps1 ... source/VSIX/installed-extension onboarding; manual refresh is left to the user

3. The three views

The activity-bar container lighthouse hosts three views, all reading from one ActiveComparison supplied via a () => active getter:

Order View Default Role
1 Comparison (lighthouse.comparison) Collapsed; embedded Commits collapsed too Base/Compare selectors, History/Review commit tree and native Set as Base
2 Changed Files (lighthouse.files) All folders initially expanded Changed files and reviewed-file checkboxes; no all-changes toolbar button
3 Threads (lighthouse.conversations) Resolved threads hidden Inline-code navigation, Read thread, resolve/reopen controls

All three are WebviewViews. The Comparison view originally used a two-row TreeView whose rows fired showQuickPick(). The resulting top-center popup was routinely mistaken for the Command Palette / search bar. Replacing it with a WebviewViewProvider that renders two <input> + filterable-dropdown fields in place removed the popup entirely.

Manifest collapse/order defaults do not override layouts VS Code already remembers for a workspace. Changed Files applies initial expansion once per webview, preserving subsequent folder choices across refreshes; its Expand All command remains available. Conversations retains an explicit Show Resolved choice across reloads. lighthouse.commitsViewAllChanges remains a Command Palette action even though its Changed Files toolbar button was removed.

4. The active comparison (single source of truth)

ActiveComparison (activeComparison.ts) holds base/compare branch names, their resolved commit shas, a separate effective baselineCommit, the current HEAD, the resolved reviewDir + sourceFile path, and the in-memory Review. All three views and the CommentController read from it, so a single refreshAll() keeps everything consistent.

  • base = TARGET branch or full pinned commit SHA → maps to review.targetBranch.
  • compare = SOURCE branch under review → maps to review.sourceBranch.
  • changedFiles / logRange results are memoized keyed by the effective baseline/compare commit pair, so re-renders don't re-shell git.
  • The Comparison pane shares an in-flight branch-catalog request and retains its completed result across selection/review/worktree-only refreshes. Ref changes, explicit refresh and Git operations invalidate it. HEAD and staleness remain live; overtaken state builds never post an older selection. CLI enumeration reads full refs once and performs ambiguity checks in memory. Only collisions need Git's short-ref disambiguation, in bounded batches. Empty/failure results are not retained.
  • The review file is created lazily: only a mutation (a reviewed-file checkbox toggle or a comment add/reply) persists comments.json.

Target branch vs. effective baseline

The Base target identifies the intended branch or fixed commit and continues to identify the review. The Effective baseline is the exact commit used on the left of branch-review diffs:

  • Auto: for a local target, consider that ref and its configured upstream. When no upstream is configured, also consider an existing matching origin/<target> ref. Choose the most advanced shared ancestor with the compare commit by ancestry, never by timestamp. Equal ancestors retain the selected target as the explanation source. An explicitly selected remote-tracking target uses only that ref.
  • Existing pin preferences: legacy baseline pins remain scoped/revalidated for compatibility. There are no separate Pin/Auto controls. Selecting a Base branch clears an older pin for that pair; use Set as Base on a History commit for an exact fixed boundary.
  • Set as Base: right-click a commit to use its full SHA as a fixed target. The same ancestor validation applies; its baseline is exact, never substituted with another shared ancestor. A fixed-target review uses a SHA-named folder, separate from a branch-target review.
  • Unpin: a conditional action beside Base removes the fixed target/legacy pin for the current source and reruns automatic target discovery plus shared-ancestor resolution. It does not force main, leave the SHA target selected, or clear other sources' preferences. Reloads remain automatic. The redundant pinned-commit caption is omitted; the hash is already in the input and commit list.
  • main shortcut: the always-visible ↶ main button beside Base explicitly selects the named main branch, clears that pair's legacy baseline pin, and selects its existing review. It does not fetch, check out a branch, or rerun automatic parent selection. Normal shared-ancestor resolution still considers the locally known main/upstream refs. The shortcut is disabled until the branch catalogue contains main; remote-only refs are not silently substituted.
  • Pending Base feedback: pin previews the chosen SHA immediately. Warm Unpin previews the last successful automatic target from memory, then validates source/target IDs before skipping target rediscovery. Ref events/manual refresh invalidate reuse; the preview is not a diff boundary. Baseline and review data are always re-resolved. Cache hints are scoped to the repo and source SHA, are not persisted across reloads, and fall back to discovery when unavailable/changed. A themed horizontal progress bar stays directly below the native Comparison title, outside the scrolling selectors and Commits, during the action and any open Review reload. Like VS Code's native progress bar, this essential activity cue continues animating under reduced motion; it does not change OS/editor settings. Its narrow bit uses the native four-second translate/scale sweep. The 2px slot remains reserved when idle; only its visibility changes, so commit rows do not shift during loading. Collapsed commit sections are not forced to load. Failed actions restore authoritative input state and surface the error; stale source/action responses cannot overwrite a newer choice.
  • Explicit Refresh feedback: the Refresh commands and all three pane title-bar buttons share one awaited refresh and activate all three panes' progress indicators to represent that shared work. Comparison's indicator remains directly below its native title, outside scrolling content. Thread-page Refresh has the same feedback locally and refreshes only that thread page. Even an immediate no-change result runs one complete four-second CSS sweep; data and result toasts are not delayed to finish the animation. Repeated clicks during the same refresh join that work. Completion toasts summarize the result; failures are reported instead of success. Automatic background refreshes do not emit these toasts or minimum-duration animations.
  • Multiple valid merge-bases use Git's normal default selection, matching the approved automatic workflow. This does not claim that the chosen ancestor is a unique original fork point.
  • Missing refs, no shared ancestry, and incomparable candidate baselines are visible errors, not empty successful comparisons or fallbacks to the target tip.

The Base input tooltip includes the baseline SHA and reason, without an extra baseline row. Target selections are remembered per source branch in workspace state; review folder names and comments.json target identity do not change when Auto chooses an upstream's shared ancestor. Successful Suggested/Default target captions are omitted; their details remain in the Base tooltip. Discovery or resolution failures still appear explicitly in the status area. Unselected or temporarily resolving comparisons remain quiet instead of showing an instructional prompt. Commit rows and Changed Files folders use right-facing disclosure arrows when collapsed and down-facing arrows when expanded, matching the pane and Commits-section headers. Both lists' disclosure SVGs inherit the themed icon foreground rather than the SVG default black fill.

This handles both an advancing main branch (the shared ancestor stays put) and a rebased source with stale local main (a newer upstream ancestor can win). It does not guess a feature branch's parent from unrelated branch names or fetch automatically. If all available refs are stale, fetch explicitly or pin the intended ancestor.

Committed file lists and commit ranges use the resolved baseline/compare SHAs. Cumulative lists use baseline vs. working tree plus untracked files. Single-file and View All Changes diffs use the same baseline; the latter includes cumulative/untracked changes when cumulative mode is on. Uncommitted SCM-group diffs and individual-commit diffs retain their index/HEAD/parent semantics.

Resolution refreshes on Git API state events, manual Refresh, and filesystem events for the actual worktree HEAD and shared loose/packed refs. This includes ref changes made from another worktree. Overtaken resolutions/results are discarded so an older query cannot replace a newer selection.

Suggested targets and the embedded commit tree

With no remembered/explicit target, defaults use validated creation/rebase reflog evidence when the parent tip has advanced: the recorded fork must still be a source ancestor, and a unique parent candidate must contain it without containing the source head. A named creation target or single local alias is preferred when unambiguous. Otherwise, scan up to 200 first-parent source commits for the nearest unambiguous ancestor branch tip. Source/tracking aliases are excluded. Ambiguous/missing evidence falls back to the repository default. This is a suggestion, not proof of a branch's original parent. Saved targets are restored on startup/automatic HEAD changes; explicit current choices remain stable across manual source changes.

Commits starts collapsed. No list requests or row rendering occur until expansion. Loaded pages, including empty results and requests finishing while closed, are cached for reopening. Source/mode changes invalidate the cache; Review baseline changes do too. Replacement queries stay deferred while closed. The target-discovery scan is separate and still needed when defaults are requested.

History pages 50 first-parent ancestors at a time, including commits before the baseline. Review uses the effective baseline/source range, including merged side commits and its 200-commit cap. Rows retain file expansion and per-file diffs; native right-click actions provide Set as Base, Copy Commit SHA and Open Commit Diff. The full row SHA is passed through data-vscode-context, scoped to commit rows, not files. Source/Base role badges precede the SHA/subject so ellipsis cannot hide them. BASE marks the effective baseline, not a potentially stale target branch tip. Comparison role tags use a 1px icon-foreground outline matching toolbar strokes; ordinary branch-name tags keep their subtle panel-border outline. The role badge's outer height stays unchanged.

History remains usable to repair a baseline error; Review cannot load stale/invalid baseline data. Pending responses are tied to the source and (for Review) effective baseline, so pin/ref changes cannot replace a newer selection. Collapse-all now belongs to Comparison's title bar.

5. Activation flow (fast-return pattern)

activate() (extension.ts) deliberately does no awaited git work:

  1. Create the Lighthouse OUTPUT channel, initPerf, and the ReviewStatusBar.
  2. Construct ActiveComparison(wsFolder, wsFolder) with a placeholder repo root.
  3. Construct LighthouseCommentController with a () => active getter (so a first-ever thread can target the active comparison before any comments.json exists).
  4. Register the three providers, all reading () => active.
  5. Register ReviewDiffContentProvider on the diff scheme, registerTagCompletion(), the file watcher, and ~40 commands.
  6. Leave heavy comparison initialization deferred until a pane, comparison command, or new inline discussion needs it. Concurrent requests share initialization of the repo root, metadata, subscriptions, defaults, comparison and first refresh.

Initialization captures the repository root, worktree HEAD path and shared Git directory in one Git read, then installs HEAD/ref/configuration watches before reading comparison metadata. One generation-scoped HEAD/ref snapshot feeds default-target discovery and baseline resolution. Changing HEAD, refs or local/worktree Git configuration invalidates reuse; changes during resolution retain a trailing fresh pass. Failed initialization disposes its early subscriptions before retrying. The snapshot is not a persistent ref cache, and manual/later resolutions read live metadata.

The CLI HEAD fallback captures branch and commit in one shell-free process, sharing only simultaneous same-repository reads. A ready Git API HEAD takes precedence. Initial API discovery is not itself an on-disk mutation. Worktree-only Git notifications refresh files, checklist validation and threads without repeating baseline ancestry or Comparison branch/staleness queries.

Comparison's HTML shell is immediate, but branch/HEAD/staleness requests wait for shared repository readiness, not a completed baseline. This avoids querying the placeholder nested workspace and reloading its catalogue after root detection, without serializing branch choices behind target discovery. Superseded/disposed view requests stop before starting Git work. Ordinary Comparison refreshes use the model's resolved HEAD; explicit branch/manual refreshes still read live HEAD. Initial API hydration cannot invalidate a disk catalogue protected by the early filesystem watches.

onStartupFinished activation makes inline discussions available in ordinary and Source Control editors without opening Lighthouse. Existing threads/replies do not require comparison Git work. Usage observation starts here too, independently of performance logging; it records fixed feature labels and editor categories, never user content. See Local feature usage.

This ordering exists because subprocess waits and shared extension-host stalls have measured tens of seconds during startup. Extension-local timings do not establish antivirus causality. Returning first and reducing the critical comparison work keeps the panel responsive.

refreshAll() refreshes all three providers, re-renders the CommentController from disk, and updates the status bar.

Reviewed-file validation is separate from pane rendering. ReviewedFiles records the effective baseline's per-file Git identity and the final saved source fingerprint when a file is checked. Both cumulative and split views use this whole-file contract. Saves schedule affected files immediately; filesystem events use per-file 100 ms coalescing capped at 250 ms without postponing runnable saves or unrelated files. Changes during an in-flight validation retain a trailing pass. Git notifications use cheap metadata hints, and selection/reload/ manual Refresh reconcile the stored checklist. Unchanged immutable endpoints and saved metadata reuse cached results; explicit Refresh and source-change events force saved-byte validation. There is no polling or hashing on unsaved typing, comment-only refreshes, or ordinary UI redraws.

Immutable Git entries are cached by resolved commit and repo-relative path in the comparison's metadata-cache.json, beside comments.json. Cache instances are scoped by repository and comparison folder; their repository key does not expose the absolute root path. Constructor/empty requests do no cache I/O. The first necessary lookup loads disk entries lazily and fetches only missing metadata from Git. Four commit trees and a 4 MiB disk limit bound the cache; eviction is a cold miss, never a review-state change. Resetting working-file state does not discard immutable entries. Successful lookups schedule coalesced atomic cache writes without delaying checkbox completion. Cache errors are explicit but nonfatal; live-Git errors remain failures. Concurrent metadata misses may duplicate queries. This adds neither speculative preloading nor startup file-query coalescing, and leaves the broader Git-state/comparison refresh path unchanged.

Dirty-editor transitions send availability-only messages, not full Files data reloads. The entire checkbox is disabled with a visible Unsaved explanation until save/discard. Changed fingerprints clear checks persistently, never automatically rechecking on undo. Legacy path-only checks are cleared lazily on first review use without discarding discussion data. Failed validation remains explicitly unverified instead of treating errors as deletions.

The store serializes read-modify-write operations through a per-file queue and cooperative interprocess lock, including inline comments, formal discussion pages, checklist invalidation, and MCP replies. It reloads latest content, preserves unrelated fields, and atomically replaces the file after checking for observed external writes. Comparison identities and file-event generations prevent delayed checkbox work from mutating another review. Non-cooperating writers and older extension versions are outside the lock guarantee.

Review discovery walks only each workspace's .vscode/lighthouse-reviews subtree, not the workspace search index. Simultaneous discovery requests share the in-flight directory walk; completed results are not cached, and each caller still parses independent review objects. Missing stores are empty; other filesystem errors propagate. Linked directories are not traversed (to avoid escaping the store or following cycles), and their count is recorded in diagnostics.

Activation return, background completion and visible content readiness are separate milestones. Every pane (including Comparison) reports DOM completion and a subsequent paint opportunity, distinguishing placeholders from resolved content, empty states and errors. Git/API/storage spans and refresh causes are correlated under the initiating operation. The export command snapshots the local trace without waiting for pending work. See Startup diagnostics.

6. CommentController (inline threads)

commentController.ts renders every thread from every comments.json as a native vscode.comments thread anchored at filePath:startLine. It supports:

  • Reply → writes a v2 comment with replyTo.
  • New thread on any line → writes a v2 thread (falls back to the active comparison so the very first comment works before any file exists on disk).
  • Resolve / unresolve → in-place update of thread.state (tracked separately so it's an update, not a dispose+recreate).
  • /tag tokens in ordinary Post replies → merged into the thread's tags[] on submit.

The native reply editor has two stable-API submit buttons, in one ordered menu group: Post and Approve. Post remains the first/default action used by Ctrl/Cmd+Enter; VS Code derives its tooltip from that native submit binding, showing Post (Ctrl+Enter) on Windows. Post creates ordinary discussion for new code-linked threads and replies without launching an agent. Questions, comments and decisions all use Post; older typed messages remain readable. Tags remain classification, even /question when submitted with Post. Native author labels show message type/work state, and thread labels count queued/in-progress/ blocked instructions. Work-state changes participate in reconciliation even without a new reply.

Approve writes a pending instruction directly from the exact native text, without a dialog or proposal selection. It works for existing discussions and as a new code-linked thread's first message. Approval text bypasses slash-tag extraction so paths, slash tokens and formatting cannot silently change its scope. It shares Post's guarded persistence and duplicate-submission handling. The source/code anchor remains attached to the thread; no agent or model prompt is launched.

It re-renders from disk on demand, so the file watcher can call it after any external change — including this extension's own writes and the copilot CLI shell-out.

Thread workspace and review-wide topics

The Threads title bar and empty state offer New Thread. It opens a draft attached to the current branch review, not a code location. Nothing is written until the first message is posted. These topics have an optional subject and omit filePath, line ranges and anchors entirely; they are not fake comments on line zero. Existing inline comments remain code-linked.

User-facing entity names are consistently Thread/Threads. Existing internal conversation IDs are retained so saved layouts, commands, references and telemetry remain compatible. Buttons show an icon followed by their visible label; decorative SVGs are hidden from assistive technology.

Clicking a code-linked thread/reply still navigates to code. Read, or clicking a review-wide topic, now opens a dedicated WebviewPanel rather than Markdown source. The page shows formatted messages/code blocks, human/agent attribution, a reply composer, Resolve/Reopen and explicit Ask Copilot controls. Saved code context is optional; missing/deleted/uncommitted files never prevent reading or replying. The saved anchor is not presented as a reconstructed historical file.

Post and Approve save locally; the separate Ask Copilot thread action explicitly launches the existing external CLI for posted messages only. No model runs automatically and no streaming response is fabricated. External saved replies update the open page without replacing the draft. Resolved threads are not silently reopened by replies. Draft text is kept in the panel's local VS Code webview state; restored pages retain their original review identity even if a different branch is now active.

The Read-page composer matches the native editor: Post and Approve are its only posting actions. The text box serves questions, comments, decisions and approval descriptions. There is no message type selector, proposal picker, confirmation checkbox, combined Post/Run action or preview button. Clicking Approve is the explicit approval action and queues only what that message describes. Changing approved text invalidates its content hash; unrelated discussion changes do not. Draft text survives refresh/reload, including older isolated approval tabs, without restoring an implicit approval mode or replacing another page's draft. Ctrl/Cmd+Enter always invokes Post, never Approve; Enter/Shift+Enter remain newlines, Tab/Shift+Tab navigate controls, and IME composition never submits. Native inline comments use VS Code's Ctrl+Enter and Ctrl+M Tab-focus behavior.

Queued work is separate from resolved/unresolved discussion state. Thread pages display pending, claimed, completed (agent-reported), blocked and cancelled states, and allow explicit cancellation. Cancellation cannot interrupt an already-running editor operation or undo changes. A stalled or blocked claim is not stolen automatically: cancel it and post a fresh instruction to retry.

Page writes use the same coordinated transaction as inline/checklist/MCP changes, reload current data, and check for observed external edits before committing. Conflicts retain the draft. The cooperative lock does not protect against arbitrary writers that ignore it. Invalid/missing existing review data is never replaced with an empty review. First messages create missing review directories lazily.

Markdown rendering is loaded only when messages need formatting. Raw HTML is disabled, remote images are omitted, and link clicks are routed through an HTTP/HTTPS-only host handler under a nonce-based CSP. The old read-only lighthouse-conversation document provider remains registered for existing/restored transcript tabs. Legacy threads without stable IDs remain readable but cannot be mutated through the new page.

7. The Ask-Copilot bridge (schema → agent contract)

This explicit launch path is a shell-out, not a model call (askCopilot in extension.ts):

reads config: copilotPath (default "copilot"), copilotArgs (default ["-p"])
builds a prompt: "Respond to local review thread <id> in <reviewDir>. Read comments.json,
                  reply in-thread per the schema (v2: author object, tags[], replyTo),
                  stamp your identity as ~Written by 🤖 Copilot, and set thread state appropriately."
opens an integrated terminal "Lighthouse · Copilot" with cwd = reviewDir
terminal.sendText(<cliPath> <args> <prompt>)

The current implementation assumes the agent understands the schema via separately supplied guidance (the author's setup uses a personal Copilot instruction file). The complete format is documented in docs/data-model.md, but docs and contributor .github instructions are excluded from the VSIX. The extension does not bundle a complete agent instruction document, register an instruction provider, or install instructions into reviewed repositories.

The agent edits comments.json; the file watcher then reloads and re-renders the thread with the identity-stamped reply. No LM API, no keys, no in-extension model call. A self-contained, packaged agent contract remains publication-readiness work.

Approved work is picked up through the MCP inbox by an explicitly authorized agent session. Posting or approving does not wake an agent. Approval does not grant Git, deployment or other additional permissions; the approved message defines scope, not the entire surrounding discussion.

Optional MCP path (LH-CLI-007)

The existing CLI session can instead launch the bundled Node stdio server and call list_reviews, get_review, get_thread, reply_to_thread, list_work_items, claim_work_item, and complete_work_item. It neither launches a conversation nor calls a model. There is no HTTP listener or background feedback-processing loop.

The lazy Configure Copilot CLI MCP command installs an owned persistent payload/launcher and, after confirmation, merges one user-level lighthouse-codereview CLI entry. The PowerShell setup script uses the same installation implementation through the bundled headless helper. Both share platform-user-data storage, without writing VS Code's private state database. Verified matching owned lighthouse entries may migrate; unrelated and disabled legacy entries are not silently replaced/re-enabled. Setup never pins a repository. Each process inherits its CLI launch cwd, resolves that Git worktree (not its shared Git directory), and rejects store links/path escapes. The real CLI context probe verified two repositories and linked-worktree metadata without model calls; its client advertised no MCP roots capability. Switching /cwd inside an existing session requires restarting its MCP connection/session.

The server reads only saved review JSON. Cursors are revision-bound, thread indexes contain 64-code-point initial-comment previews, and replies use revision/idempotency guards plus the shared transaction. Existing file watchers refresh open discussions without replacing drafts. The inbox includes work in resolved discussions. Claims are revision-guarded and cooperative; they are not exactly-once source execution or authenticated identity. Completion atomically appends an agent result/blocker and updates work state, without resolving the thread. Ordinary MCP replies cannot create human approvals. Processing requires prior user authorization; neither review prose, tags nor caller-supplied author metadata override session permissions. There is no automatic wake-up, expiry takeover, persistent agent or model API in the extension. Local MCP call metadata is separate from extension usage logs; see usage diagnostics. Payloads are independent of versioned extension folders; only previously enabled installations are refreshed, and older VS Code profiles do not downgrade a newer shared payload. CLI policy/disabled settings are never overridden. The setup script probes identity, tool discovery, and review metadata over stdio without a model call; it does not prove a running editor or CLI has refreshed. See Local MCP setup. An existing four-tool CLI allowlist is not expanded by payload refresh: explicit setup/repair is required to opt into the additional work tools.

8. Data flow

 branch pick (webview)          reviewed-file checkbox / comment add/reply
        │                                     │
        ▼                                     ▼
  ActiveComparison.resolve()          reviewStore.saveReview()  ──writes──► comments.json
        │  (memoized git)                     │                                   │
        ▼                                     ▼                                   │ file watcher
   refreshAll() ──────────────► 3 providers + CommentController.render() ◄────────┘
                                                     ▲
                                     Ask Copilot ────┘ (external CLI edits comments.json)

9. Key design decisions & rationale

Decision Rationale
External agent, not embedded model No keys/telemetry in the extension; the schema is the whole contract; the agent side already exists
Own storage path .vscode/lighthouse-reviews/ Coexist with the original local-pr-review without clobbering its .vscode/local-reviews/
vscode-free reviewModel.ts Unit-test the schema + normalizeSeq migration without the extension host
Fast-return activation + on-demand Git Multi-second dependency waits must not block extension activation
Durable seqCounter for thread #NN ids A transiently-empty in-memory review must not reset/reuse a display id; monotonic counter fixes it
Combined selectors + compact commit tree Avoid the prose ancestry wall and duplicate Commits pane; choose bases through native commit context menus
No-shell git helpers (listWorktreesCli) Avoid flashing shell windows and reduce spawn cost on the hot path
Lazy, diff-friendly writes Don't create files until there's content; 2-space indent + trailing newline keeps git diffs clean

10. Known constraints

  • Locally available refs only. Local and remote-tracking branches are supported, but there is no remote PR integration (GitHub/ADO) or automatic fetch during baseline resolution.
  • Per-row Pull/Update is FF-only. It fetches and fast-forwards a stale local branch to its upstream; if not fast-forwardable it warns and does nothing (never merges or rebases). No push.
  • Single workspace folder assumed for the review store location.
  • The agent round-trip assumes separately supplied schema guidance for .vscode/lighthouse-reviews/ (a superset of the original .vscode/local-reviews/ guidance); the short launch prompt is not a complete, bundled instruction document.