Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions PLAN-inline-comments.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
## Goal

Findings anchored to a line in the PR diff land as inline review comments on
that line. The main issue comment keeps every finding but renders an inlined
one SHORT: title + one-line rationale + link to the inline thread. The main
that line. The main issue comment does not list an inlined finding again: it
shows each inlined finding's id, linked to its thread, in one row per severity. The main
comment stays the single durable state store (embedded state, dismissals,
feedback records, pins, `/dismiss` target). Inline comments are a derived
presentation layer, converged on every run.
Expand All @@ -16,8 +16,9 @@ presentation layer, converged on every run.
Individual review comments isolate failures and PATCH in place.
- No multi-line comments; single `line` + `side: "RIGHT"` only.
- No GraphQL thread resolution, and no detection of author-resolved/outdated
threads. Accepted risk: the PR author can collapse inline threads; the main
comment therefore keeps a one-line rationale per finding (see Short form).
threads. Accepted risk: the PR author can resolve (collapse) an inline thread.
The thread stays on the PR and anyone can expand it, and the author cannot
edit or delete the bot's comment, so the main comment shows only its id.
- `ecr feedback` retroactive crawl keeps reading issue comments only. The
review-comments fetch in `replyComments` is gated on the reporter having an
ENABLED `inline` option, so the crawl (which passes none) gains no fetches.
Expand Down Expand Up @@ -166,16 +167,15 @@ threads are STUBBED, bare ones deleted — never delete a thread humans wrote in
covers the fp-namespace flip on per-scope↔single switches, symmetric with how
main comments are cleared today.

### Main-comment short form
### Main-comment id rows

- `LinkContext.inlineUrls?: Map<string, string>`. NEVER mutate the shared
routed-flow `link` object — render with a copy `{ ...link, inlineUrls }`.
- `renderFindingLines` with an inline URL: `- **title** —
[file:line](thread-url) _(category)_ · id:… · 💬 [inline](url)` + reply
annotation + ONE-LINE truncated rationale (~160 chars, word boundary) — the
audit trail survives the author collapsing/resolving inline threads.
Sources/suggestion/full rationale live inline only. Missing URL ⇒ full form
(tested).
- `renderSeveritySections` leaves every finding with an inline URL out of the
severity lists. Under `💬 **Inline comments on the diff (n)**` it adds one row
per severity with each finding's `id:` linked to its thread, plus a reply link
when an author answered. Severity headings count only the findings listed under
them. Missing URL ⇒ full form (tested). See LLP 0008 "Inline Threads".
- Embedded state unchanged.

### Inline replies → feedback
Expand Down Expand Up @@ -211,14 +211,14 @@ main comments are cleared today.
+ pure exported planner, replyComments extension, clear(), applyDismissal
additive sync.
- `src/core/render.ts` — inline marker helpers (anchored parse), inline body
renderer, `LinkContext.inlineUrls`, short form + rationale truncation.
renderer, `LinkContext.inlineUrls`, main-comment id rows.
- `src/core/responses.ts` — threadFp, unquoted-marker backstop.
- `src/core/schema.ts` — applyPins same-stream guard.
- `src/commands/ci.ts` — wire config, `couldNotComplete: true` on both failure
literals, aggregate teardown flag. `review.ts`, `post-review.ts`,
`dismiss.ts` — wire config.
- Tests: planner (create/patch/stub/delete, sticky cap, dedupe, additive mode),
marker anchor/forgery, short form + missing-URL fallback, backstop vs
marker anchor/forgery, id rows + missing-URL fallback, backstop vs
quote-reply, threadFp in all match modes, applyPins stream guard,
couldNotComplete literals.
- `templates/config.jsonc`, `README.md`.
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -744,11 +744,13 @@ the defaults are asymmetric: [LLP 0011](./llp/0011-author-feedback.explainer.md)

Off by default. When enabled, each finding anchored to a line that is actually in
the PR's diff is posted as an inline review comment on that line, carrying the
full rationale, sources, and suggestion. The main comment still lists every
finding, but an inlined one renders short — title, links, category, id, and a
one-line rationale — plus a `💬 inline comment` link to the thread. The main
comment remains the single durable state store: `/dismiss`, embedded state, and
reply adjudication are unchanged.
full rationale, sources, and suggestion. The main comment does not list an
inlined finding again: under `💬 Inline comments on the diff` it shows one row per
severity with each finding's `id:`, linked to its thread, so `/dismiss` still has
the id. Findings that are not
inlined (outside the diff, over `maxComments`, or with no thread link) are listed
in full as before. The main comment remains the single durable state store:
`/dismiss`, embedded state, and reply adjudication are unchanged.

```jsonc
"inline": {
Expand Down
2 changes: 2 additions & 0 deletions llp/0008-sources-and-reporters.explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ With `inline.enabled`, `syncInline` converges one inline thread per in-diff find

**Links come from a second call.** The review response does not list its comments, so the executor reads `GET /pulls/{n}/reviews/{id}/comments` a page at a time, up to the same 30-page bound as every comment listing, and maps each comment to its finding by the anchored marker in its body (`inlineUrlsByFp`). When a page fails, the pages before it stay linked, the threads exist, and the main comment renders the rest in full for this run; the next run finds the threads through `ownInlineComments` and links them. A rate limit on that read aborts the stubs and deletes still to come [observed] (`syncInline`, `inlineReviewCommentPages`).

**The main comment does not repeat an inlined finding.** A finding with a live thread is read in that thread, so the main comment shows only its `id:`, linked to the thread, in one row per severity under `💬 Inline comments on the diff`, with a link to a reply when an author answered it. The id stays because `/dismiss` takes it and `/dismiss` is an issue comment, not a thread reply. A finding without a live thread (outside the diff, over `maxComments`, a failed link, or a stubbed thread) is listed in full. Every render passes the thread links: a review run gets them from `syncInline`, and `/dismiss` and `/undismiss` read the threads before they render, so a dismissal does not bring the full list back. A `/undismiss` restores a stubbed thread before it renders, and links it only when the restore succeeded. When the threads can't be listed, every finding renders in full [observed] (`renderSeveritySections`, `splitInlined`, `inlinedLines` in [render.ts](../src/core/render.ts); `applyDismissal` in [github.ts](../src/reporters/github.ts)). The embedded state keeps every finding the comment shows, so dismissals and reply matching do not change. In the aggregate comment, the size cap trims inlined and listed findings on separate limits, inlined first, and both down to 3 per scope: a trimmed inlined finding keeps its thread on the PR, while a listed one appears nowhere else. Inlined findings must still be trimmable, because each renders as one link but carries its full text in the embedded state. The floor of 3 keeps a scope with findings from showing none. A trimmed finding leaves the state too, so `/dismiss` does not accept its id until a review run that fits (`renderAggregateMarkdown`). An earlier version kept a short entry with a one-line rationale as an audit trail in case the author resolved the thread. It was dropped because the author cannot edit or delete the bot's comment, and a resolved thread stays on the PR.

## Terminal Reporter

The terminal reporter writes the primary report to **stdout** (progress and logging go to stderr) specifically so `ecr review > out.txt` captures a clean report and shell redirection works as expected [observed] ([terminal.ts:45-47](../src/reporters/terminal.ts)). It maps `review.decision` to a process exit code via `decisionExitCode`, which is what makes it usable as a pre-push/pre-commit gate; `--json` disables color and `noFail` forces exit 0 [observed] ([terminal.ts:28-32, 38, 49](../src/reporters/terminal.ts)). Color is enabled only on a TTY and only when not `--json` [observed] ([terminal.ts:38](../src/reporters/terminal.ts): `!options.json && Boolean(process.stdout.isTTY)`) — naively coloring whenever output is not JSON, ignoring `isTTY`, would corrupt piped or redirected output with ANSI codes.
Expand Down
Loading
Loading