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.
- package/README.md +135 -103
- package/package.json +2 -2
- package/src/analyzer/pipeline.ts +1 -4
- package/src/cli/args.ts +1 -1
- package/src/cli/index.ts +2 -1
- package/src/github/fetch-pr.ts +1 -0
- package/src/history/store.ts +25 -1
- package/src/llm/prompts.ts +82 -27
- package/src/llm/slides.ts +381 -0
- package/src/types/config.ts +1 -1
- package/src/types/github.ts +1 -0
- package/src/types/output.ts +26 -0
- package/src/version.ts +23 -0
- package/src/web/client/App.tsx +51 -1
- package/src/web/client/components/AppShell.tsx +173 -45
- package/src/web/client/components/ChatSection.tsx +76 -185
- package/src/web/client/components/DetailPane.tsx +1 -0
- package/src/web/client/components/DiffViewer.tsx +200 -4
- package/src/web/client/components/InputScreen.tsx +3 -0
- package/src/web/client/components/Markdown.tsx +66 -16
- package/src/web/client/components/ResultsScreen.tsx +32 -2
- package/src/web/client/components/SettingsPanel.tsx +1 -1
- package/src/web/client/hooks/useBackgroundAnalyses.ts +152 -0
- package/src/web/client/hooks/useChatStore.ts +247 -0
- package/src/web/client/hooks/useFeatures.ts +2 -1
- package/src/web/client/hooks/useOutdatedCheck.ts +41 -0
- package/src/web/client/lib/notify.ts +21 -0
- package/src/web/client/panels/SlidesPanel.tsx +316 -0
- package/src/web/index.html +1 -0
- package/src/web/server/routes.ts +226 -4
- package/src/web/server/session-manager.ts +34 -0
- package/src/web/server.ts +20 -1
- package/src/web/styles/built.css +1 -1
- package/src/workspace/explore.ts +39 -6
- 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
|
|
3
|
+
AI-powered PR review tool that turns large pull requests into readable, navigable stories.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Quick Install
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
11
|
+
Or install globally:
|
|
19
12
|
|
|
20
13
|
```bash
|
|
21
|
-
bun
|
|
14
|
+
bun add -g newpr
|
|
15
|
+
newpr --web
|
|
22
16
|
```
|
|
23
17
|
|
|
24
|
-
|
|
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
|
|
31
|
+
newpr --web
|
|
32
|
+
|
|
33
|
+
# Option B: Claude Code (no API key needed)
|
|
34
|
+
newpr --web
|
|
29
35
|
```
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
## What it Does
|
|
32
38
|
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
###
|
|
48
|
+
### Anchors & Navigation
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
newpr --web --port 3000
|
|
43
|
-
```
|
|
50
|
+
Every analysis is densely linked. The narrative contains three types of clickable references:
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
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
|
|
56
|
-
newpr <pr-url>
|
|
57
|
-
newpr --web [--port 3000]
|
|
58
|
-
newpr
|
|
59
|
-
newpr
|
|
60
|
-
newpr
|
|
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
|
-
###
|
|
70
|
+
### Options
|
|
64
71
|
|
|
65
72
|
```
|
|
66
|
-
--
|
|
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
|
-
--
|
|
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
|
|
79
|
+
--stream-json Stream progress as NDJSON
|
|
72
80
|
--verbose Show progress on stderr
|
|
73
81
|
```
|
|
74
82
|
|
|
75
|
-
### PR
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
+
The chat in the Story tab supports agentic tool execution:
|
|
119
111
|
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
+
## Analysis Pipeline
|
|
128
126
|
|
|
129
|
-
|
|
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
|
-
|
|
147
|
+
## Exploration Agents
|
|
132
148
|
|
|
133
|
-
| Agent |
|
|
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
|
-
|
|
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.
|
|
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
|
|
168
|
+
## Config
|
|
156
169
|
|
|
157
|
-
Persistent settings
|
|
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.
|
|
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 #
|
|
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
|
-
##
|
|
195
|
+
## Architecture
|
|
182
196
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
221
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newpr",
|
|
3
|
-
"version": "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",
|
package/src/analyzer/pipeline.ts
CHANGED
|
@@ -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.
|
|
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 =
|
|
16
|
+
const VERSION = getVersion();
|
|
16
17
|
|
|
17
18
|
async function main(): Promise<void> {
|
|
18
19
|
const args = parseArgs(process.argv);
|
package/src/github/fetch-pr.ts
CHANGED
|
@@ -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",
|
package/src/history/store.ts
CHANGED
|
@@ -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
|
}
|
package/src/llm/prompts.ts
CHANGED
|
@@ -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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
- Use
|
|
171
|
-
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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
|
}
|