git-ents.gitmain
⌘K
foforge

The Lens

The editor is the third place a conversation surfaces, after the CLI and the web UI, and it earns no third mechanism: the lens is a language server that projects the same anchored comments (<<model.comment>>, <<anchor.projection>>) into whatever buffer the user is reading, and writes new ones through the same signed mutation path every other frontend uses. "Lens" is meant literally: it is a read-time view over refs/meta/*, owning no state of its own, so a comment left in an editor, on the web, or by an agent at the CLI is one and the same entity everywhere else.

The Lens Is a Frontend of the Local Root

git ents lsp MUST serve the Language Server Protocol over stdio, reusing the local composition root’s wiring exactly as git ents serve does (<<roots.local>>), adding only the LSP frontend. It MUST NOT bind a network socket and MUST NOT add any git-serving transport. The lens crate MUST receive its signing identity by injection from the composition root, mirroring <<roots.web-agnostic>>: nothing in the crate may assume which editor, if any, is attached.

Comments Surface as Code Lenses

Every comment whose anchor projects onto an open document — including replies displayed through their thread root (<<model.comment-thread>>) — MUST surface as a code lens at its projected range, derived at request time via <<anchor.projection>> and never cached across mutations of the comment’s ref. A lens MUST identify the comment, summarize its body, and offer the thread’s operations — view, reply, resolve — as LSP commands that call the same library operations the CLI exposes (<<lens.parity>>). Comments whose state is not open (<<model.comment-state>>) MUST be omitted unless the client asks for them.

Diagnostics Mirror the Lenses

The same projected comments MUST also be published as hint-severity diagnostics, so clients that do not render code lenses still show the conversation inline. These diagnostics carry conversation, not judgment: they MUST NEVER use warning or error severity, and they MUST be suppressible by client configuration without affecting the lenses.

Hover Shows the Thread

Hovering a projected comment’s range MUST return the full thread — bodies, states, and authorship read from each ref’s mutation commit chain (<<meta-ref.identity-binding>>) — rendered as markup, so the complete conversation is readable without leaving the buffer.

Composing Uses the Editor-File Flow

Creating a comment from the editor MUST work through a file, the way git itself takes a commit message: a code action on the selection writes a template file under .git/ naming the anchored path and lines, opens it in the editor via window/showDocument, and creating the comment happens when the user saves a non-empty body — lines starting with # ignored, an empty body aborting. This flow MUST require no client-specific extension: any LSP client that can execute a code action and open a file can compose a comment. A richer input surface (a popup, a webview) MAY be layered on by clients that have one, but it MUST be sugar over the same create operation, never a second mechanism.

The Lens Reads and Writes the Working Tree

Projection targets for open documents MUST be the working tree (<<anchor.working-tree>>), with the client’s in-memory buffer content standing in for on-disk bytes when they differ, re-projected on document change so ranges track unsaved edits. Comments composed from the editor MUST anchor to the working tree’s content when it differs from HEAD, so a remark about uncommitted code anchors to exactly the bytes the author was reading.

One Mechanism for Editors, Agents, and the Web

Every operation the lens offers — list projected onto the working tree, create, reply, resolve — MUST be the same library call the git ents comment porcelain exposes, so an agent addressing comments through the CLI, a human leaving them through an editor, and the web UI rendering them are three callers of one mechanism. The CLI listing MUST offer a machine-readable form covering id, state, projected location, and body, sufficient for an agent to enumerate and resolve every open comment in the working tree without any editor attached.

One Record Grammar for Every Porcelain

Every entity family’s --porcelain listing MUST emit the one record grammar git ents comment list --porcelain established (<<lens.parity>>), never a format of its own: a head line of space-separated tokens leading with the record’s id, one <field> <value> line per remaining field — omitted when the field is declared hide-when-empty and is empty (<<model.presentation>>) — each body line prefixed with one tab, and records separated by exactly one blank line. A blank body line MUST render as a lone tab, so only the record separator is ever a true blank line. Every id in machine-readable output MUST be full, never the abbreviated form human listings use (<<model.issue>>).

Web Forms Derive from the Action Shape

A web form for an entity action MUST derive its controls from the same #[derive(Facet)] action shape whose flags the CLI grammar parses — one field list, declared on the action variant, obeyed by both frontends — and parsing the posted form MUST produce that same action shape, dispatched to the same library operation the CLI calls (<<lens.parity>>). A field the web cannot supply (a value bound into the route’s own path, a local signing-key path) MUST be excluded by what its declaration says, never by a per-route field list; an unposted field MUST take the declaration’s own default, exactly as the CLI defaults an omitted flag. A page MAY override a single field’s control (a state picker, a verdict picker), but the override MUST feed the same parse.