newpr 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +135 -103
  2. package/package.json +2 -2
  3. package/src/analyzer/pipeline.ts +1 -4
  4. package/src/cli/args.ts +1 -1
  5. package/src/cli/index.ts +2 -1
  6. package/src/github/fetch-pr.ts +1 -0
  7. package/src/history/store.ts +25 -1
  8. package/src/llm/prompts.ts +82 -27
  9. package/src/llm/slides.ts +381 -0
  10. package/src/types/config.ts +1 -1
  11. package/src/types/github.ts +1 -0
  12. package/src/types/output.ts +26 -0
  13. package/src/version.ts +23 -0
  14. package/src/web/client/App.tsx +51 -1
  15. package/src/web/client/components/AppShell.tsx +173 -45
  16. package/src/web/client/components/ChatSection.tsx +76 -185
  17. package/src/web/client/components/DetailPane.tsx +1 -0
  18. package/src/web/client/components/DiffViewer.tsx +200 -4
  19. package/src/web/client/components/InputScreen.tsx +3 -0
  20. package/src/web/client/components/Markdown.tsx +66 -16
  21. package/src/web/client/components/ResultsScreen.tsx +32 -2
  22. package/src/web/client/components/SettingsPanel.tsx +1 -1
  23. package/src/web/client/hooks/useBackgroundAnalyses.ts +152 -0
  24. package/src/web/client/hooks/useChatStore.ts +247 -0
  25. package/src/web/client/hooks/useFeatures.ts +2 -1
  26. package/src/web/client/hooks/useOutdatedCheck.ts +41 -0
  27. package/src/web/client/lib/notify.ts +21 -0
  28. package/src/web/client/panels/SlidesPanel.tsx +316 -0
  29. package/src/web/index.html +1 -0
  30. package/src/web/server/routes.ts +226 -4
  31. package/src/web/server/session-manager.ts +34 -0
  32. package/src/web/server.ts +20 -1
  33. package/src/web/styles/built.css +1 -1
  34. package/src/workspace/explore.ts +39 -6
  35. package/src/workspace/types.ts +1 -0
package/README.md CHANGED
@@ -1,78 +1,86 @@
1
1
  # newpr
2
2
 
3
- AI-powered PR review tool for understanding large pull requests with 1000+ lines of changes.
3
+ AI-powered PR review tool that turns large pull requests into readable, navigable stories.
4
4
 
5
- newpr fetches a GitHub PR, optionally clones the repo for deep codebase exploration using an agentic coding tool, then uses an LLM to produce a structured analysis: file summaries, logical groupings, an overall summary, and a narrative walkthrough with clickable cross-references.
5
+ ## Quick Install
6
6
 
7
- ## Features
8
-
9
- - **Narrative walkthrough** — reads like an article, with `[[group:...]]` and `[[file:...]]` cross-references
10
- - **Logical grouping** — clusters changed files by purpose (feature, refactor, bugfix, etc.)
11
- - **Codebase exploration** — uses Claude Code / OpenCode / Codex to analyze the actual repository, not just the diff
12
- - **Interactive TUI** — Ink-based terminal UI with tabbed panels, slash commands, ASCII logo
13
- - **Web UI** — browser-based interface with sidebar, resizable panels, markdown rendering, dark/light mode
14
- - **Streaming progress** — real-time SSE streaming of analysis steps
15
- - **Session history** — saves past analyses for instant recall
16
- - **Multi-language** — output in any language (auto-detected or configured)
7
+ ```bash
8
+ bunx newpr --web
9
+ ```
17
10
 
18
- ## Quick Start
11
+ Or install globally:
19
12
 
20
13
  ```bash
21
- bun install
14
+ bun add -g newpr
15
+ newpr --web
22
16
  ```
23
17
 
24
- ### Option A: OpenRouter API key
18
+ The web UI opens automatically at `http://localhost:3000`. Paste any GitHub PR URL to start.
19
+
20
+ ### Prerequisites
21
+
22
+ - [Bun](https://bun.sh) — `curl -fsSL https://bun.sh/install | bash`
23
+ - [GitHub CLI](https://cli.github.com) — `brew install gh && gh auth login`
24
+ - One of:
25
+ - `OPENROUTER_API_KEY` — for model selection (Claude, GPT-4, Gemini, etc.)
26
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) — zero-config fallback
25
27
 
26
28
  ```bash
29
+ # Option A: OpenRouter
27
30
  export OPENROUTER_API_KEY=sk-or-...
28
- newpr https://github.com/owner/repo/pull/123
31
+ newpr --web
32
+
33
+ # Option B: Claude Code (no API key needed)
34
+ newpr --web
29
35
  ```
30
36
 
31
- ### Option B: Claude Code (no API key needed)
37
+ ## What it Does
32
38
 
33
- If you have [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed, newpr uses it as a fallback when no OpenRouter API key is set for both LLM analysis and codebase exploration.
39
+ newpr fetches a GitHub PR, clones the repo for deep codebase exploration using an agentic coding tool (Claude Code / OpenCode / Codex), then produces:
34
40
 
35
- ```bash
36
- newpr https://github.com/owner/repo/pull/123
37
- ```
41
+ - **Narrative walkthrough** — prose-first story with clickable code references that open diffs at the exact line
42
+ - **Logical grouping** — clusters files by purpose (feature, refactor, bugfix) with key changes, risk assessment, and dependency mapping
43
+ - **Interactive chat** — ask follow-up questions with agentic tool execution (file diffs, GitHub API, web search)
44
+ - **Inline diff comments** — create/edit/delete review comments synced to GitHub
45
+ - **PR actions** — approve, request changes, or comment directly from the UI
46
+ - **React Doctor** — auto-runs [react-doctor](https://github.com/millionco/react-doctor) on React projects for code quality scoring
38
47
 
39
- ### Web UI
48
+ ### Anchors & Navigation
40
49
 
41
- ```bash
42
- newpr --web --port 3000
43
- ```
50
+ Every analysis is densely linked. The narrative contains three types of clickable references:
44
51
 
45
- Opens a browser-based UI at `http://localhost:3000` with:
46
- - Left sidebar with session history
47
- - Resizable detail panel on the right
48
- - Clickable group/file anchors in the narrative
49
- - Settings modal for model, agent, language configuration
50
- - GitHub profile integration
52
+ | Anchor | Appearance | Action |
53
+ |--------|------------|--------|
54
+ | `[[group:Name]]` | Blue chip | Opens group detail in sidebar |
55
+ | `[[file:path]]` | Blue chip | Opens file diff in sidebar |
56
+ | `[[line:path#L-L]](text)` | Subtle underline | Opens diff scrolled to line, highlights range |
51
57
 
52
58
  ## Usage
53
59
 
54
- ```
55
- newpr # launch interactive shell
56
- newpr <pr-url> # shell with PR pre-loaded
57
- newpr --web [--port 3000] # launch web UI
58
- newpr review <pr-url> --json # non-interactive JSON output
59
- newpr history # list past review sessions
60
- newpr auth [--key <api-key>] # configure API key
60
+ ```bash
61
+ newpr # interactive shell (TUI)
62
+ newpr <pr-url> # shell with PR pre-loaded
63
+ newpr --web [--port 3000] # web UI (default)
64
+ newpr --web --cartoon # web UI with comic strip generation
65
+ newpr review <pr-url> --json # non-interactive JSON output
66
+ newpr history # list past sessions
67
+ newpr auth [--key <api-key>] # configure API key
61
68
  ```
62
69
 
63
- ### Review mode options
70
+ ### Options
64
71
 
65
72
  ```
66
- --repo <owner/repo> Repository (when using PR number only)
67
- --model <model> Override LLM model (default: anthropic/claude-sonnet-4.5)
73
+ --model <model> LLM model (default: anthropic/claude-sonnet-4.6)
68
74
  --agent <tool> Preferred agent: claude | opencode | codex (default: auto)
69
- --no-clone Skip git clone, diff-only analysis (faster, less context)
75
+ --port <port> Web UI port (default: 3000)
76
+ --cartoon Enable comic strip generation
77
+ --no-clone Skip git clone, diff-only analysis
70
78
  --json Output raw JSON
71
- --stream-json Stream progress as NDJSON, then emit result
79
+ --stream-json Stream progress as NDJSON
72
80
  --verbose Show progress on stderr
73
81
  ```
74
82
 
75
- ### PR input formats
83
+ ### PR Input Formats
76
84
 
77
85
  ```bash
78
86
  newpr https://github.com/owner/repo/pull/123
@@ -80,66 +88,71 @@ newpr owner/repo#123
80
88
  newpr review 123 --repo owner/repo
81
89
  ```
82
90
 
83
- ## Architecture
91
+ ## Web UI
84
92
 
85
- ```
86
- src/
87
- ├── cli/ # CLI entry, arg parsing, auth, history commands
88
- ├── config/ # Config loading (~/.newpr/config.json)
89
- ├── github/ # GitHub API (fetch PR data, diff, parse URL)
90
- ├── diff/ # Unified diff parser + chunker
91
- ├── llm/ # LLM clients (OpenRouter + Claude Code fallback), prompts, response parser
92
- ├── analyzer/ # Pipeline orchestrator + progress events
93
- ├── workspace/ # Agent system (claude/opencode/codex), git operations, codebase exploration
94
- ├── types/ # Shared TypeScript types
95
- ├── history/ # Session persistence (~/.newpr/history/)
96
- ├── tui/ # Ink TUI (shell, panels, theme, slash commands)
97
- └── web/ # Web UI
98
- ├── server.ts # Bun.serve() with Tailwind CSS build
99
- ├── server/ # REST API + SSE endpoints, session manager
100
- ├── client/ # React frontend
101
- │ ├── components/ # AppShell, ResultsScreen, Markdown, DetailPane, etc.
102
- │ ├── panels/ # Story, Summary, Groups, Files, Narrative
103
- │ └── hooks/ # useAnalysis, useSessions, useTheme, useGithubUser
104
- └── styles/ # Tailwind v4 + Pretendard font
105
- ```
93
+ The web interface provides:
106
94
 
107
- ## Analysis Pipeline
95
+ - **Sidebar** — sessions grouped by repository, background analysis tracking
96
+ - **Story tab** — narrative with inline line anchors + chat input at bottom
97
+ - **Discussion tab** — PR description + GitHub comments
98
+ - **Groups tab** — collapsible change groups with key changes and risk
99
+ - **Files tab** — tree/group/changes view modes with inline summaries
100
+ - **Comic tab** — AI-generated 4-panel comic strip (with `--cartoon`)
101
+ - **Right sidebar** — file diffs with syntax highlighting, inline comments, line highlighting
102
+ - **TipTap editor** — `@` to reference files/groups, `/` for commands
103
+ - **KaTeX** — LaTeX math rendering in chat and narrative
104
+ - **Review modal** — approve, request changes, or comment via GitHub API
105
+ - **Settings** — model, agent, language, API keys
106
+ - **Preflight checks** — system health (gh, agents, API key) on startup
108
107
 
109
- 1. **Fetch** — PR metadata, commits, and diff from GitHub API
110
- 2. **Parse** — unified diff into per-file chunks
111
- 3. **Clone** — bare repo clone with worktree checkout (cached)
112
- 4. **Explore** — 3-phase codebase exploration via agentic tool (structure → related code → issues)
113
- 5. **Analyze** — LLM summarizes each file chunk in parallel batches
114
- 6. **Group** — LLM clusters files into logical groups with types
115
- 7. **Summarize** — LLM generates purpose, scope, impact, risk level
116
- 8. **Narrate** — LLM writes a walkthrough article with cross-references
108
+ ## Chat
117
109
 
118
- ## LLM Backend
110
+ The chat in the Story tab supports agentic tool execution:
119
111
 
120
- newpr supports two LLM backends:
112
+ | Tool | Description |
113
+ |------|-------------|
114
+ | `get_file_diff` | Fetch unified diff for a specific file |
115
+ | `list_files` | List all changed files with summaries |
116
+ | `get_pr_comments` | Fetch PR discussion comments |
117
+ | `get_review_comments` | Fetch inline review comments |
118
+ | `get_pr_details` | PR metadata, labels, reviewers |
119
+ | `web_search` | Search the web (delegated to agent) |
120
+ | `web_fetch` | Fetch URL content (delegated to agent) |
121
+ | `run_react_doctor` | Run react-doctor analysis |
121
122
 
122
- | Backend | Setup | Use case |
123
- |---------|-------|----------|
124
- | **OpenRouter** | Set `OPENROUTER_API_KEY` | Full model selection (Claude, GPT-4, Gemini, etc.) |
125
- | **Claude Code** | Install `claude` CLI | Zero-config fallback, uses your existing Claude subscription |
123
+ Type `/undo` to remove the last exchange.
126
124
 
127
- When no OpenRouter API key is configured, newpr automatically falls back to Claude Code for all LLM calls.
125
+ ## Analysis Pipeline
128
126
 
129
- ## Codebase Exploration Agents
127
+ 1. **Fetch** PR metadata, commits, diff, and discussion from GitHub API
128
+ 2. **Parse** — unified diff into per-file chunks
129
+ 3. **Clone** — bare repo with worktree checkout (cached in `~/.newpr/repos/`)
130
+ 4. **Explore** — 3-4 phase codebase exploration via agent:
131
+ - Structure — project type, architecture
132
+ - Related code — imports, usages, tests
133
+ - Issues — breaking changes, inconsistencies
134
+ - React Doctor — code quality score (React projects only)
135
+ 5. **Analyze** — LLM summarizes each file in parallel batches
136
+ 6. **Group** — LLM clusters files with key changes, risk, dependencies
137
+ 7. **Summarize** — purpose, scope, impact, risk level
138
+ 8. **Narrate** — prose walkthrough with line-level code references
139
+
140
+ ## LLM Backends
141
+
142
+ | Backend | Setup | Use case |
143
+ |---------|-------|----------|
144
+ | **OpenRouter** | `OPENROUTER_API_KEY` | Full model selection |
145
+ | **Claude Code** | `claude` CLI installed | Zero-config fallback |
130
146
 
131
- For deep analysis beyond the diff, newpr uses an agentic coding tool to explore the actual repository:
147
+ ## Exploration Agents
132
148
 
133
- | Agent | Command | Detection |
149
+ | Agent | Install | Detection |
134
150
  |-------|---------|-----------|
135
- | Claude Code | `claude` | `which claude` |
136
- | OpenCode | `opencode` | `which opencode` |
137
- | Codex | `codex` | `which codex` |
151
+ | Claude Code | `npm i -g @anthropic-ai/claude-code` | `which claude` |
152
+ | OpenCode | `npm i -g opencode` | `which opencode` |
153
+ | Codex | `npm i -g @openai/codex` | `which codex` |
138
154
 
139
- The agent runs 3 exploration phases:
140
- 1. **Structure** — project type, key directories, architecture pattern
141
- 2. **Related code** — imports, usages, test coverage for changed files
142
- 3. **Issues** — breaking changes, missing error handling, inconsistencies
155
+ Agents run with read-only tools (Read, Glob, Grep, Bash, WebSearch, WebFetch). No write operations.
143
156
 
144
157
  ## Environment Variables
145
158
 
@@ -147,19 +160,19 @@ The agent runs 3 exploration phases:
147
160
  |----------|----------|-------------|
148
161
  | `OPENROUTER_API_KEY` | No* | OpenRouter API key (*falls back to Claude Code) |
149
162
  | `GITHUB_TOKEN` | No | GitHub token (falls back to `gh` CLI) |
150
- | `NEWPR_MODEL` | No | Default model (default: `anthropic/claude-sonnet-4.5`) |
163
+ | `NEWPR_MODEL` | No | Default model (default: `anthropic/claude-sonnet-4.6`) |
151
164
  | `NEWPR_MAX_FILES` | No | Max files to analyze (default: 100) |
152
165
  | `NEWPR_TIMEOUT` | No | Timeout per LLM call in seconds (default: 120) |
153
166
  | `NEWPR_CONCURRENCY` | No | Parallel LLM calls (default: 5) |
154
167
 
155
- ## Config File
168
+ ## Config
156
169
 
157
- Persistent settings are stored in `~/.newpr/config.json`:
170
+ Persistent settings in `~/.newpr/config.json`:
158
171
 
159
172
  ```json
160
173
  {
161
174
  "openrouter_api_key": "sk-or-...",
162
- "model": "anthropic/claude-sonnet-4.5",
175
+ "model": "anthropic/claude-sonnet-4.6",
163
176
  "language": "auto",
164
177
  "agent": "claude",
165
178
  "max_files": 100,
@@ -171,19 +184,38 @@ Persistent settings are stored in `~/.newpr/config.json`:
171
184
  ## Development
172
185
 
173
186
  ```bash
187
+ git clone https://github.com/jiwonMe/newpr
188
+ cd newpr
174
189
  bun install
175
- bun test # run tests (91 tests)
190
+ bun test # 91 tests
176
191
  bun run typecheck # tsc --noEmit
177
- bun run lint # biome check
178
192
  bun run start # launch CLI
179
193
  ```
180
194
 
181
- ## Requirements
195
+ ## Architecture
182
196
 
183
- - [Bun](https://bun.sh) ≥ 1.3
184
- - GitHub CLI (`gh`) for authentication, or `GITHUB_TOKEN`
185
- - One of: `OPENROUTER_API_KEY` or Claude Code (`claude` CLI)
197
+ ```
198
+ src/
199
+ ├── cli/ # CLI entry, args, auth, preflight, update-check
200
+ ├── config/ # Config loading (~/.newpr/config.json)
201
+ ├── github/ # GitHub API (PR data, diff, comments)
202
+ ├── diff/ # Unified diff parser + chunker
203
+ ├── llm/ # LLM clients (OpenRouter + Claude Code), prompts, parser
204
+ ├── analyzer/ # Pipeline orchestrator + progress events
205
+ ├── workspace/ # Agent system, git operations, codebase exploration
206
+ ├── types/ # Shared TypeScript types
207
+ ├── history/ # Session persistence + sidecar files
208
+ ├── tui/ # Ink TUI (shell, panels, theme)
209
+ └── web/ # Web UI
210
+ ├── server.ts # Bun.serve()
211
+ ├── server/ # REST/SSE API, session manager
212
+ ├── client/ # React frontend
213
+ │ ├── components/ # AppShell, ChatSection, Markdown, TipTapEditor, etc.
214
+ │ ├── panels/ # Story, Discussion, Groups, Files, Cartoon
215
+ │ └── hooks/ # useAnalysis, useBackgroundAnalyses, useChatState, etc.
216
+ └── styles/ # Tailwind v4 + Pretendard + Tab0 Mono K
217
+ ```
186
218
 
187
219
  ## License
188
220
 
189
- Private
221
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newpr",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "AI-powered large PR review tool - understand PRs with 1000+ lines of changes",
5
5
  "module": "src/cli/index.ts",
6
6
  "type": "module",
@@ -53,7 +53,7 @@
53
53
  "@types/react-dom": "^19.2.3"
54
54
  },
55
55
  "peerDependencies": {
56
- "typescript": "^5"
56
+ "typescript": "^5.9.3"
57
57
  },
58
58
  "dependencies": {
59
59
  "@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -85,18 +85,14 @@ async function runExploration(
85
85
  ): Promise<ExplorationResult> {
86
86
  const agent = await requireAgent(preferredAgent);
87
87
 
88
- onProgress?.({ stage: "cloning", message: `Cloning ${pr.owner}/${pr.repo}...` });
89
88
  const bareRepoPath = await ensureRepo(pr.owner, pr.repo, token, (msg) => {
90
89
  onProgress?.({ stage: "cloning", message: `📦 ${msg}` });
91
90
  });
92
- onProgress?.({ stage: "cloning", message: `📦 ${pr.owner}/${pr.repo} cached` });
93
91
 
94
- onProgress?.({ stage: "checkout", message: `🌿 Preparing worktrees: ${baseBranch} ← PR #${pr.number}` });
95
92
  const worktrees = await createWorktrees(
96
93
  bareRepoPath, baseBranch, pr.number, pr.owner, pr.repo,
97
94
  (msg) => onProgress?.({ stage: "checkout", message: `🌿 ${msg}` }),
98
95
  );
99
- onProgress?.({ stage: "checkout", message: `🌿 Worktrees ready: ${baseBranch} ← PR #${pr.number}` });
100
96
 
101
97
  onProgress?.({ stage: "exploring", message: `🤖 ${agent.name}: exploring ${changedFiles.length} changed files...` });
102
98
  const exploration = await exploreCodebase(
@@ -302,6 +298,7 @@ export async function analyzePr(options: PipelineOptions): Promise<NewprOutput>
302
298
  pr_body: prData.body || undefined,
303
299
  pr_url: prData.url,
304
300
  pr_state: prData.state,
301
+ pr_updated_at: prData.updated_at,
305
302
  base_branch: prData.base_branch,
306
303
  head_branch: prData.head_branch,
307
304
  author: prData.author,
package/src/cli/args.ts CHANGED
@@ -46,7 +46,7 @@ Options:
46
46
 
47
47
  Options (review mode):
48
48
  --repo <owner/repo> Repository (required when using PR number only)
49
- --model <model> Override LLM model (default: anthropic/claude-sonnet-4.5)
49
+ --model <model> Override LLM model (default: anthropic/claude-sonnet-4.6)
50
50
  --agent <tool> Preferred agent: claude | opencode | codex (default: auto)
51
51
  --no-clone Skip git clone, diff-only analysis (faster, less context)
52
52
  --json Output raw JSON (for piping/scripting)
package/src/cli/index.ts CHANGED
@@ -11,8 +11,9 @@ import { createStderrProgress, createSilentProgress, createStreamJsonProgress }
11
11
  import { renderLoading, renderShell } from "../tui/render.tsx";
12
12
  import { checkForUpdate, printUpdateNotice } from "./update-check.ts";
13
13
  import { runPreflight, printPreflight } from "./preflight.ts";
14
+ import { getVersion } from "../version.ts";
14
15
 
15
- const VERSION = "0.3.0";
16
+ const VERSION = getVersion();
16
17
 
17
18
  async function main(): Promise<void> {
18
19
  const args = parseArgs(process.argv);
@@ -20,6 +20,7 @@ export function mapPrResponse(json: Record<string, unknown>): Omit<GithubPrData,
20
20
  body: (json.body as string) ?? "",
21
21
  url: json.html_url as string,
22
22
  state,
23
+ updated_at: (json.updated_at as string) ?? new Date().toISOString(),
23
24
  base_branch: (base?.ref as string) ?? "unknown",
24
25
  head_branch: (head?.ref as string) ?? "unknown",
25
26
  author: (user?.login as string) ?? "unknown",
@@ -2,7 +2,7 @@ import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
3
  import { mkdirSync, rmSync, existsSync } from "node:fs";
4
4
  import { randomBytes } from "node:crypto";
5
- import type { NewprOutput, DiffComment, ChatMessage, CartoonImage } from "../types/output.ts";
5
+ import type { NewprOutput, DiffComment, ChatMessage, CartoonImage, SlideDeck } from "../types/output.ts";
6
6
  import type { SessionRecord } from "./types.ts";
7
7
 
8
8
  const HISTORY_DIR = join(homedir(), ".newpr", "history");
@@ -197,6 +197,30 @@ export async function loadCartoonSidecar(
197
197
  }
198
198
  }
199
199
 
200
+ export async function saveSlidesSidecar(
201
+ id: string,
202
+ deck: SlideDeck,
203
+ ): Promise<void> {
204
+ ensureDirs();
205
+ await Bun.write(
206
+ join(SESSIONS_DIR, `${id}.slides.json`),
207
+ JSON.stringify(deck),
208
+ );
209
+ }
210
+
211
+ export async function loadSlidesSidecar(
212
+ id: string,
213
+ ): Promise<SlideDeck | null> {
214
+ try {
215
+ const filePath = join(SESSIONS_DIR, `${id}.slides.json`);
216
+ const file = Bun.file(filePath);
217
+ if (!(await file.exists())) return null;
218
+ return JSON.parse(await file.text()) as SlideDeck;
219
+ } catch {
220
+ return null;
221
+ }
222
+ }
223
+
200
224
  export function getHistoryPath(): string {
201
225
  return HISTORY_DIR;
202
226
  }
@@ -155,30 +155,83 @@ export function buildNarrativePrompt(
155
155
 
156
156
  return {
157
157
  system: `You are an expert code reviewer writing a review walkthrough for other developers.
158
- Write a clear, concise narrative that tells the "story" of this PR — what changes were made and in what logical order.
159
- Use the commit history and PR discussion to understand the development progression: which changes came first, how the PR evolved, and the intent behind each step. The PR description often explains the author's motivation and approach.
160
- Use markdown formatting. Write 2-5 paragraphs. Do NOT use JSON. Write natural prose.
161
- ${lang ? `CRITICAL: Write the ENTIRE narrative in ${lang}. Every sentence must be in ${lang}. Do NOT use English except for code identifiers, file paths, and [[group:...]]/[[file:...]] tokens.` : "If the PR title is in a non-English language, write the narrative in that same language."}
162
-
163
- IMPORTANT: Use these anchor formats they become clickable links in the UI:
164
-
165
- 1. Group: [[group:Group Name]] renders as a clickable chip.
166
- 2. File: [[file:path/to/file.ts]] renders as a clickable chip.
167
- 3. Line reference: [[line:path/to/file.ts#L42-L50]](descriptive text here) the "descriptive text" becomes an underlined clickable link that opens the diff at that line. The line info itself is NOT shown to the user — only the descriptive text is visible.
168
-
169
- RULES:
170
- - Use EXACT group names and file paths from the context above.
171
- - Every group MUST be referenced at least once with [[group:...]].
172
- - For line references, ALWAYS use the form [[line:path#Lstart-Lend]](text). NEVER use bare [[line:...]] without (text).
173
- - The (text) should be a natural description of what the code does, NOT the file name or line numbers. The reader should not see any line numbers — they just see underlined text they can click.
174
- - Do NOT place [[file:...]] and [[line:...]] next to each other for the same file. Use [[line:...]] with descriptive text instead — it already opens the file.
175
- - Aim for most sentences about code to have at least one [[line:...]](...) reference.
176
-
177
- GOOD example:
178
- "The [[group:Auth Flow]] group introduces session management. [[line:src/auth/session.ts#L15-L30]](The new validateToken function) handles JWT parsing, and [[line:src/auth/middleware.ts#L8-L12]](the auth middleware) invokes it on every request."
179
-
180
- BAD example (DO NOT do this):
181
- "The new validateToken function [[line:src/auth/session.ts#L15-L30]] in [[file:src/auth/session.ts]] handles JWT parsing."`,
158
+
159
+ ## Goal
160
+ Write a narrative that tells the "story" of this PR — what was changed, why, and how the pieces connect. The reader should finish with a clear mental model of the PR.
161
+
162
+ ## Writing Style
163
+ - Write in flowing prose paragraphs. This is a narrative, not a changelog.
164
+ - Do NOT use horizontal rules (---), dividers, or excessive headers. Let the prose flow naturally.
165
+ - Use ### headers ONLY for major conceptual sections (2-4 max for the whole narrative). Prefer topic sentences over headers.
166
+ - Default to prose. Use bullet lists only for 3+ parallel items that are genuinely list-like (e.g., list of API endpoints, config options). Never list things that would read better as a sentence.
167
+ - Use tables only when comparing structured data (e.g., before/after schemas, flag mappings).
168
+ - Keep paragraphs short (3-5 sentences). Human working memory holds ~4 chunks — each paragraph should be one coherent idea.
169
+ - Lead each paragraph with a topic sentence that states the key point. Supporting details follow.
170
+ - Use transition phrases to connect paragraphs naturally. If writing in a non-English language, use idiomatic transitions in THAT language — never insert English phrases like "Building on this" into non-English text.
171
+ - Use commit history and PR discussion to understand the development progression.
172
+
173
+ ## Anchor Syntax (CRITICAL this is how readers navigate from your text to the actual code)
174
+
175
+ There are THREE anchor types. You MUST use ALL of them.
176
+
177
+ ### Group Anchors (MANDATORY)
178
+ - Format: [[group:Exact Group Name]]
179
+ - Renders as a clickable blue chip.
180
+ - You MUST reference EVERY group from the Change Groups list at least once. No exceptions.
181
+ - Use group anchors when introducing a topic area or explaining what a set of changes accomplishes together.
182
+ - Example: "The [[group:Auth Flow]] group introduces session management."
183
+
184
+ ### File Anchors
185
+ - Format: [[file:exact/path/to/file.ts]]
186
+ - Renders as a clickable blue chip that opens the file diff.
187
+ - Use when referencing a file generally (not a specific line), or when you don't have exact line numbers.
188
+ - Use EXACT file paths from the Change Groups context.
189
+ - Example: "Configuration is defined in [[file:src/config/auth.ts]]."
190
+
191
+ ### Line Anchors
192
+ - Format: [[line:path/to/file.ts#L42-L50]](descriptive text)
193
+ - The "descriptive text" becomes a subtle underlined link. Line numbers are NOT visible.
194
+ - Use for specific code changes — functions, types, config fields, imports.
195
+
196
+ ### Usage Rules:
197
+ - ALWAYS use [[line:path#Lstart-Lend]](text) with BOTH start and end lines. Single lines: [[line:path#L42-L42]](text).
198
+ - The (text) must describe WHAT the code does, not WHERE it is. Bad: "lines 42-50". Good: "the new rate limiter middleware".
199
+ - Wrap EVERY specific code mention in a line anchor. If you mention a function, class, type, constant, config change, or import — anchor it.
200
+ - Interleave anchors naturally within sentences. They should feel like hyperlinks in a wiki article.
201
+ - Do NOT pair [[file:...]] with [[line:...]] for the same file. The line anchor already opens the file.
202
+ - Use the diff context provided to find accurate line numbers. If unsure of exact lines, use [[file:...]] instead.
203
+
204
+ ### Anchor Density — TWO levels:
205
+ When describing a function or class, use anchors at TWO granularity levels:
206
+
207
+ **Level 1 — Declaration**: Anchor the function/class name itself to its full definition range.
208
+ **Level 2 — Implementation details**: When explaining what the code does, anchor EACH distinct piece of logic to its specific lines within the function.
209
+
210
+ Example with two levels:
211
+ "[[line:src/auth/session.ts#L15-L50]](The validateToken function) handles the full JWT lifecycle. It [[line:src/auth/session.ts#L18-L22]](extracts the token from the Authorization header), [[line:src/auth/session.ts#L24-L30]](verifies the signature against the configured secret), and [[line:src/auth/session.ts#L32-L40]](checks the expiration timestamp). If validation fails, [[line:src/auth/session.ts#L42-L48]](it throws a typed AuthError with a specific error code)."
212
+
213
+ Key principles:
214
+ - The first anchor covers the entire function (L15-L50). Subsequent anchors zoom into specific parts within it.
215
+ - Each sub-anchor should cover 2-10 lines — one logical step.
216
+ - Descriptive text for sub-anchors should explain the step, not name the function again.
217
+
218
+ ### Line Anchor Granularity:
219
+ - Anchor individual functions, not entire files: [[line:auth.ts#L15-L30]](validateToken) not [[line:auth.ts#L1-L200]](auth module)
220
+ - Anchor key type definitions: [[line:types.ts#L5-L12]](the new UserSession interface)
221
+ - Anchor config/schema changes: [[line:schema.ts#L42-L45]](the added rate_limit field)
222
+ - Anchor imports and exports that wire things together: [[line:index.ts#L3-L3]](re-exported from the barrel file)
223
+ - For multi-part changes, anchor each part separately
224
+
225
+ GOOD example (uses all 3 anchor types + two-level density):
226
+ "The [[group:Auth Flow]] group introduces session management. [[line:src/auth/session.ts#L15-L50]](The new validateToken function) handles JWT parsing: [[line:src/auth/session.ts#L18-L22]](it extracts the token from the header), then [[line:src/auth/session.ts#L24-L35]](verifies the signature and checks expiration). [[line:src/auth/middleware.ts#L8-L20]](The auth middleware) invokes it on every request, [[line:src/auth/middleware.ts#L15-L18]](rejecting invalid tokens with a 401). Supporting configuration lives in [[file:src/auth/constants.ts]]."
227
+
228
+ BAD examples:
229
+ - No group anchors: "The auth changes introduce session management." (MUST use [[group:Auth Flow]])
230
+ - No anchors in implementation details: "The validateToken function extracts the token, verifies the signature, and checks expiration." (MUST anchor each step separately)
231
+ - One big anchor for everything: "[[line:session.ts#L15-L50]](The function extracts tokens, verifies signatures, and checks expiration)" (MUST split into sub-anchors)
232
+ - Bare line anchor: "[[line:src/auth/session.ts#L15-L30]]" (MUST have (text) after it)
233
+
234
+ ${lang ? `CRITICAL: Write the ENTIRE narrative in ${lang}. Every sentence must be in ${lang}. Do NOT use English except for code identifiers, file paths, and anchor tokens.` : "If the PR title is in a non-English language, write the narrative in that same language."}`,
182
235
  user: `PR Title: ${prTitle}\n\nSummary:\n- Purpose: ${summary.purpose}\n- Scope: ${summary.scope}\n- Impact: ${summary.impact}\n- Risk: ${summary.risk_level}\n\nChange Groups:\n${groupDetails}${commitCtx}${discussionCtx}${diffContext}`,
183
236
  };
184
237
  }
@@ -194,6 +247,9 @@ function formatCodebaseContext(exploration: ExplorationResult): string {
194
247
  if (exploration.potential_issues) {
195
248
  sections.push(`=== Potential Issues (from codebase analysis) ===\n${exploration.potential_issues}`);
196
249
  }
250
+ if (exploration.react_doctor) {
251
+ sections.push(`=== React Doctor Analysis (react-doctor) ===\n${exploration.react_doctor}`);
252
+ }
197
253
  return sections.join("\n\n");
198
254
  }
199
255
 
@@ -229,9 +285,8 @@ export function buildEnrichedNarrativePrompt(
229
285
 
230
286
  return {
231
287
  system: `${base.system}
232
- You have access to full codebase analysis. Use it to explain HOW the changes relate to existing code, not just WHAT changed.
233
- Mention specific existing functions, modules, or patterns that are affected.
234
- Use [[group:Name]], [[file:path]], and [[line:path#L42-L50]](descriptive text) as instructed above.`,
288
+
289
+ You have full codebase analysis below. Use it to explain HOW the changes relate to existing code — mention specific existing functions, patterns, and callers that are affected. This context should enrich your line anchor usage with cross-references to existing code.`,
235
290
  user: `${base.user}\n\n--- CODEBASE CONTEXT (from agentic exploration) ---\n${context}`,
236
291
  };
237
292
  }