@in-the-loop-labs/pair-review 2.3.3 → 2.4.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 (45) hide show
  1. package/.pi/skills/review-model-guidance/SKILL.md +1 -1
  2. package/.pi/skills/review-roulette/SKILL.md +1 -1
  3. package/README.md +15 -1
  4. package/package.json +2 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/skills/review-requests/SKILL.md +1 -1
  7. package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
  8. package/public/css/pr.css +287 -14
  9. package/public/index.html +121 -57
  10. package/public/js/components/AIPanel.js +2 -1
  11. package/public/js/components/AdvancedConfigTab.js +2 -2
  12. package/public/js/components/AnalysisConfigModal.js +2 -2
  13. package/public/js/components/ChatPanel.js +187 -28
  14. package/public/js/components/CouncilProgressModal.js +4 -7
  15. package/public/js/components/SplitButton.js +66 -1
  16. package/public/js/components/VoiceCentricConfigTab.js +2 -2
  17. package/public/js/index.js +274 -21
  18. package/public/js/pr.js +194 -5
  19. package/public/local.html +8 -1
  20. package/public/pr.html +17 -2
  21. package/src/ai/codex-provider.js +14 -2
  22. package/src/ai/copilot-provider.js +1 -10
  23. package/src/ai/cursor-agent-provider.js +1 -10
  24. package/src/ai/gemini-provider.js +8 -17
  25. package/src/chat/acp-bridge.js +442 -0
  26. package/src/chat/api-reference.js +539 -0
  27. package/src/chat/chat-providers.js +290 -0
  28. package/src/chat/claude-code-bridge.js +499 -0
  29. package/src/chat/codex-bridge.js +601 -0
  30. package/src/chat/pi-bridge.js +56 -3
  31. package/src/chat/prompt-builder.js +12 -11
  32. package/src/chat/session-manager.js +110 -29
  33. package/src/config.js +4 -2
  34. package/src/database.js +50 -2
  35. package/src/github/client.js +43 -0
  36. package/src/routes/chat.js +60 -27
  37. package/src/routes/config.js +24 -1
  38. package/src/routes/github-collections.js +126 -0
  39. package/src/routes/mcp.js +2 -1
  40. package/src/routes/pr.js +166 -2
  41. package/src/routes/reviews.js +2 -1
  42. package/src/routes/shared.js +70 -49
  43. package/src/server.js +27 -1
  44. package/src/utils/safe-parse-json.js +19 -0
  45. package/.pi/skills/pair-review-api/SKILL.md +0 -448
@@ -0,0 +1,539 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ /**
3
+ * API Reference Module
4
+ *
5
+ * Provides two views of the pair-review HTTP API:
6
+ * 1. renderApiDocs — full markdown reference with curl examples
7
+ * 2. buildApiCheatSheet — compact endpoint-only cheat sheet (~2KB, budget < 2.5KB)
8
+ *
9
+ * Both substitute real port and reviewId values into the output.
10
+ */
11
+
12
+ /**
13
+ * Full API reference markdown, derived from the SKILL.md specification.
14
+ * Placeholders {{PORT}} and {{REVIEW_ID}} are replaced with real values.
15
+ * @param {Object} options
16
+ * @param {number|string} options.port - Server port
17
+ * @param {number|string} options.reviewId - Current review ID
18
+ * @returns {string} Complete API reference in markdown
19
+ */
20
+ function renderApiDocs({ port, reviewId }) {
21
+ if (reviewId == null) {
22
+ throw new Error('reviewId is required for renderApiDocs');
23
+ }
24
+ if (port == null) {
25
+ throw new Error('port is required for renderApiDocs');
26
+ }
27
+
28
+ return API_DOCS_TEMPLATE
29
+ .replace(/\{\{PORT\}\}/g, String(port))
30
+ .replace(/\{\{REVIEW_ID\}\}/g, String(reviewId));
31
+ }
32
+
33
+ /**
34
+ * Compact cheat sheet with endpoint signatures and key params.
35
+ * Under 2.5KB total, with real port/reviewId baked in.
36
+ * @param {Object} options
37
+ * @param {number|string} options.port - Server port
38
+ * @param {number|string} options.reviewId - Current review ID
39
+ * @returns {string} Cheat sheet in markdown
40
+ */
41
+ function buildApiCheatSheet({ port, reviewId }) {
42
+ if (reviewId == null) {
43
+ throw new Error('reviewId is required for buildApiCheatSheet');
44
+ }
45
+ if (port == null) {
46
+ throw new Error('port is required for buildApiCheatSheet');
47
+ }
48
+
49
+ return CHEAT_SHEET_TEMPLATE
50
+ .replace(/\{\{PORT\}\}/g, String(port))
51
+ .replace(/\{\{REVIEW_ID\}\}/g, String(reviewId));
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Templates
56
+ // ---------------------------------------------------------------------------
57
+
58
+ const API_DOCS_TEMPLATE = `# pair-review API Reference
59
+
60
+ ## Comments
61
+
62
+ All comment operations use a single set of unified endpoints.
63
+
64
+ ### List all comments
65
+
66
+ \`\`\`bash
67
+ curl -s http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/comments
68
+ \`\`\`
69
+
70
+ Optional query param: \`includeDismissed=true\` to include soft-deleted (inactive) comments.
71
+
72
+ **Response:** \`{ "success": true, "comments": [...] }\`
73
+
74
+ ### Create a comment
75
+
76
+ A single endpoint handles both line-level and file-level comments. If \`line_start\` is present, it creates a line-level comment; if omitted, it creates a file-level comment.
77
+
78
+ **Line-level comment:**
79
+ \`\`\`bash
80
+ curl -s -X POST http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/comments \\
81
+ -H 'Content-Type: application/json' \\
82
+ -d '{
83
+ "file": "src/example.js",
84
+ "line_start": 42,
85
+ "line_end": 42,
86
+ "side": "right",
87
+ "body": "This variable should be renamed for clarity.",
88
+ "type": "suggestion",
89
+ "title": "Rename variable"
90
+ }'
91
+ \`\`\`
92
+
93
+ **File-level comment (omit \`line_start\`):**
94
+ \`\`\`bash
95
+ curl -s -X POST http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/comments \\
96
+ -H 'Content-Type: application/json' \\
97
+ -d '{
98
+ "file": "src/example.js",
99
+ "body": "This file needs better error handling throughout.",
100
+ "type": "suggestion",
101
+ "title": "Error handling"
102
+ }'
103
+ \`\`\`
104
+
105
+ **Response:** \`{ "success": true, "commentId": 123, "message": "Comment saved successfully" }\`
106
+
107
+ Required fields: \`file\`, \`body\`.
108
+ Optional fields: \`line_start\`, \`line_end\`, \`side\` ("left" or "right"), \`diff_position\`, \`commit_sha\`, \`parent_id\`, \`type\`, \`title\`.
109
+
110
+ ### Get a single comment
111
+
112
+ \`\`\`bash
113
+ curl -s http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/comments/COMMENT_ID
114
+ \`\`\`
115
+
116
+ ### Update a comment
117
+
118
+ \`\`\`bash
119
+ curl -s -X PUT http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/comments/COMMENT_ID \\
120
+ -H 'Content-Type: application/json' \\
121
+ -d '{ "body": "Updated comment text." }'
122
+ \`\`\`
123
+
124
+ **Response:** \`{ "success": true, "message": "Comment updated successfully" }\`
125
+
126
+ ### Delete a comment
127
+
128
+ > **Terminology note:** The UI refers to this operation as "dismiss." When a user asks to "dismiss a comment," use this DELETE endpoint.
129
+
130
+ Soft-deletes the comment. If the comment was adopted from an AI suggestion, the parent suggestion is automatically transitioned to "dismissed" status.
131
+
132
+ \`\`\`bash
133
+ curl -s -X DELETE http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/comments/COMMENT_ID
134
+ \`\`\`
135
+
136
+ **Response:** \`{ "success": true, "message": "Comment deleted successfully", "dismissedSuggestionId": null }\`
137
+
138
+ ### Restore a deleted comment
139
+
140
+ \`\`\`bash
141
+ curl -s -X PUT http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/comments/COMMENT_ID/restore
142
+ \`\`\`
143
+
144
+ **Response:** \`{ "success": true, "message": "Comment restored successfully", "comment": {...} }\`
145
+
146
+ ### Bulk delete all comments
147
+
148
+ Deletes all user comments for a review. Also dismisses any parent AI suggestions.
149
+
150
+ \`\`\`bash
151
+ curl -s -X DELETE http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/comments
152
+ \`\`\`
153
+
154
+ **Response:** \`{ "success": true, "deletedCount": 5, "dismissedSuggestionIds": [...], "message": "Deleted 5 user comments" }\`
155
+
156
+ ---
157
+
158
+ ## Suggestions
159
+
160
+ All suggestion operations use unified endpoints. These work identically for both PR and local reviews.
161
+
162
+ ### List AI suggestions
163
+
164
+ \`\`\`bash
165
+ curl -s 'http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/suggestions'
166
+ \`\`\`
167
+
168
+ Optional query params:
169
+ - \`levels\` — comma-separated list of levels: \`"final"\`, \`"1"\`, \`"2"\`, \`"3"\`. Default: \`"final"\` (orchestrated suggestions only).
170
+ - \`runId\` — specific analysis run UUID. Default: latest run.
171
+
172
+ **Response:** \`{ "suggestions": [{ "id": 1, "file": "...", "line_start": 10, "type": "bug", "title": "...", "body": "...", "status": "active", ... }] }\`
173
+
174
+ ### Check if suggestions exist
175
+
176
+ \`\`\`bash
177
+ curl -s 'http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/suggestions/check'
178
+ \`\`\`
179
+
180
+ Optional query param: \`runId\` (specific analysis run UUID).
181
+
182
+ **Response:** \`{ "hasSuggestions": true, "analysisHasRun": true, "summary": "...", "stats": { "issues": 2, "suggestions": 3, "praise": 1 } }\`
183
+
184
+ ### Update AI suggestion status
185
+
186
+ \`\`\`bash
187
+ curl -s -X POST http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/suggestions/SUGGESTION_ID/status \\
188
+ -H 'Content-Type: application/json' \\
189
+ -d '{ "status": "dismissed" }'
190
+ \`\`\`
191
+
192
+ Valid statuses: \`"dismissed"\`, \`"active"\` (restore).
193
+
194
+ > **Note:** Setting status to \`"adopted"\` directly is not allowed. Adoption must create a linked user comment via \`parent_id\`, which is why raw status-setting cannot do it. Use \`POST /suggestions/:id/adopt\` for adopt-as-is or \`POST /suggestions/:id/edit\` for adopt-with-edits.
195
+
196
+ **Response:** \`{ "success": true, "status": "dismissed" }\`
197
+
198
+ ### Adopt an AI suggestion as-is
199
+
200
+ Atomically creates a user comment from the suggestion's body/type/title (with category prefix), sets \`parent_id\` linkage, and updates suggestion status to \`adopted\`.
201
+
202
+ \`\`\`bash
203
+ curl -s -X POST http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/suggestions/SUGGESTION_ID/adopt
204
+ \`\`\`
205
+
206
+ No request body required.
207
+
208
+ **Response:** \`{ "success": true, "userCommentId": 124, "message": "Suggestion adopted as user comment" }\`
209
+
210
+ ### Adopt an AI suggestion with edits
211
+
212
+ Edits the suggestion text and adopts it as a new user comment.
213
+
214
+ \`\`\`bash
215
+ curl -s -X POST http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/suggestions/SUGGESTION_ID/edit \\
216
+ -H 'Content-Type: application/json' \\
217
+ -d '{
218
+ "action": "adopt_edited",
219
+ "editedText": "The edited comment body to adopt as a user comment."
220
+ }'
221
+ \`\`\`
222
+
223
+ **Response:** \`{ "success": true, "userCommentId": 125, "message": "Suggestion edited and adopted as user comment" }\`
224
+
225
+ ---
226
+
227
+ ## Analysis Launch
228
+
229
+ Analysis launch endpoints are **mode-specific** because starting an analysis requires mode-specific context (worktree paths for PRs, local directory paths for local reviews). Once launched, all subsequent analysis management uses the shared endpoints below.
230
+
231
+ ### Trigger AI analysis (PR mode)
232
+
233
+ \`\`\`bash
234
+ curl -s -X POST http://localhost:{{PORT}}/api/pr/OWNER/REPO/PR_NUMBER/analyses \\
235
+ -H 'Content-Type: application/json' \\
236
+ -d '{
237
+ "provider": "claude",
238
+ "model": "claude-sonnet-4-5-20250929",
239
+ "tier": "balanced",
240
+ "customInstructions": "Focus on security issues."
241
+ }'
242
+ \`\`\`
243
+
244
+ ### Trigger AI analysis (local mode)
245
+
246
+ \`\`\`bash
247
+ curl -s -X POST http://localhost:{{PORT}}/api/local/{{REVIEW_ID}}/analyses \\
248
+ -H 'Content-Type: application/json' \\
249
+ -d '{
250
+ "provider": "claude",
251
+ "tier": "balanced",
252
+ "customInstructions": "Focus on security issues."
253
+ }'
254
+ \`\`\`
255
+
256
+ **Response (both modes):** \`{ "analysisId": "uuid", "runId": "uuid", "status": "started", "message": "AI analysis started in background" }\`
257
+
258
+ Optional body fields: \`provider\`, \`model\`, \`tier\` ("fast", "balanced", "thorough"), \`customInstructions\`, \`skipLevel3\` (boolean), \`enabledLevels\` (object like \`{"1": true, "2": true, "3": false}\`).
259
+
260
+ ### Trigger council analysis (PR mode)
261
+
262
+ \`\`\`bash
263
+ curl -s -X POST http://localhost:{{PORT}}/api/pr/OWNER/REPO/PR_NUMBER/analyses/council \\
264
+ -H 'Content-Type: application/json' \\
265
+ -d '{
266
+ "councilId": "COUNCIL_UUID",
267
+ "customInstructions": "Focus on security."
268
+ }'
269
+ \`\`\`
270
+
271
+ ### Trigger council analysis (local mode)
272
+
273
+ \`\`\`bash
274
+ curl -s -X POST http://localhost:{{PORT}}/api/local/{{REVIEW_ID}}/analyses/council \\
275
+ -H 'Content-Type: application/json' \\
276
+ -d '{
277
+ "councilId": "COUNCIL_UUID",
278
+ "customInstructions": "Focus on security."
279
+ }'
280
+ \`\`\`
281
+
282
+ **Response (both modes):** \`{ "analysisId": "uuid", "runId": "uuid", "status": "started", "message": "Council analysis started in background", "isCouncil": true }\`
283
+
284
+ Required: either \`councilId\` (UUID of a saved council config) or \`councilConfig\` (inline config object).
285
+ Optional: \`customInstructions\`, \`configType\` ("advanced" or "council").
286
+
287
+ ---
288
+
289
+ ## Analysis Status (Review-Level)
290
+
291
+ Check whether an analysis is currently running for a review. This is a unified endpoint that works for both PR and local reviews.
292
+
293
+ \`\`\`bash
294
+ curl -s http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/analyses/status
295
+ \`\`\`
296
+
297
+ **Response (running):** \`{ "running": true, "analysisId": "uuid", "status": {...} }\`
298
+ **Response (idle):** \`{ "running": false, "analysisId": null, "status": null }\`
299
+
300
+ ---
301
+
302
+ ## Analysis Management
303
+
304
+ These shared endpoints operate on analysis UUIDs (returned when you launch an analysis). They work for both PR and local reviews.
305
+
306
+ ### Get analysis status by ID
307
+
308
+ \`\`\`bash
309
+ curl -s http://localhost:{{PORT}}/api/analyses/ANALYSIS_ID/status
310
+ \`\`\`
311
+
312
+ **Response:** \`{ "id": "...", "status": "running"|"completed"|"failed"|"cancelled", "levels": {...}, "progress": "...", ... }\`
313
+
314
+ ### Cancel an analysis
315
+
316
+ \`\`\`bash
317
+ curl -s -X POST http://localhost:{{PORT}}/api/analyses/ANALYSIS_ID/cancel
318
+ \`\`\`
319
+
320
+ **Response:** \`{ "success": true, "message": "Analysis cancelled", "processesKilled": 2, "status": "cancelled" }\`
321
+
322
+ ### Real-time progress (WebSocket)
323
+
324
+ Analysis progress is delivered via WebSocket, not HTTP polling.
325
+
326
+ 1. Connect to \`ws://localhost:{{PORT}}/ws\`
327
+ 2. Subscribe: \`{ "action": "subscribe", "topic": "analysis:ANALYSIS_ID" }\`
328
+ 3. Receive progress events: \`{ "type": "progress", "topic": "analysis:ANALYSIS_ID", ... }\`
329
+
330
+ For simple polling, use \`GET /api/analyses/ANALYSIS_ID/status\` instead.
331
+
332
+ ### List analysis runs for a review
333
+
334
+ \`\`\`bash
335
+ curl -s 'http://localhost:{{PORT}}/api/analyses/runs?reviewId={{REVIEW_ID}}'
336
+ \`\`\`
337
+
338
+ **Response:** \`{ "runs": [{ "id": "uuid", "review_id": 1, "provider": "claude", "status": "completed", ... }] }\`
339
+
340
+ ### Get latest analysis run for a review
341
+
342
+ \`\`\`bash
343
+ curl -s 'http://localhost:{{PORT}}/api/analyses/runs/latest?reviewId={{REVIEW_ID}}'
344
+ \`\`\`
345
+
346
+ **Response:** \`{ "run": { "id": "uuid", "review_id": 1, "provider": "claude", "status": "completed", ... } }\`
347
+
348
+ ### Get a specific analysis run by ID
349
+
350
+ \`\`\`bash
351
+ curl -s http://localhost:{{PORT}}/api/analyses/runs/RUN_ID
352
+ \`\`\`
353
+
354
+ **Response:** \`{ "run": { "id": "uuid", ... } }\`
355
+
356
+ ### Import external analysis results
357
+
358
+ Submit analysis results produced outside pair-review (e.g., by a coding agent):
359
+
360
+ \`\`\`bash
361
+ curl -s -X POST http://localhost:{{PORT}}/api/analyses/results \\
362
+ -H 'Content-Type: application/json' \\
363
+ -d '{
364
+ "path": "/absolute/path/to/repo",
365
+ "headSha": "abc123",
366
+ "provider": "claude",
367
+ "summary": "Analysis summary text",
368
+ "suggestions": [
369
+ {
370
+ "file": "src/example.js",
371
+ "line_start": 10,
372
+ "line_end": 15,
373
+ "type": "bug",
374
+ "title": "Potential null reference",
375
+ "description": "This could throw if input is null."
376
+ }
377
+ ],
378
+ "fileLevelSuggestions": []
379
+ }'
380
+ \`\`\`
381
+
382
+ Required: either \`path\` + \`headSha\` (local mode) or \`repo\` + \`prNumber\` (PR mode).
383
+ Each suggestion requires: \`file\`, \`type\`, \`title\`, \`description\`.
384
+
385
+ **Response (HTTP 201):** \`{ "runId": "uuid", "reviewId": 1, "totalSuggestions": 5, "status": "completed" }\`
386
+
387
+ ---
388
+
389
+ ## Comment Types
390
+
391
+ The \`type\` field on comments and suggestions can be one of:
392
+ - \`"bug"\` - Bug or defect
393
+ - \`"suggestion"\` - General suggestion
394
+ - \`"improvement"\` - Code improvement
395
+ - \`"security"\` - Security concern
396
+ - \`"performance"\` - Performance issue
397
+ - \`"design"\` - Design/architecture concern
398
+ - \`"praise"\` - Positive feedback
399
+ - \`"style"\` or \`"code-style"\` - Style/formatting
400
+ - \`"nitpick"\` - Minor nitpick
401
+ - \`"question"\` - Question for the author
402
+
403
+ ---
404
+
405
+ ## Context Files
406
+
407
+ Add non-diff files to the review's diff panel for reference during discussion. Each context file shows a specific line range from a file that isn't part of the PR/local changes.
408
+
409
+ ### Add a context file
410
+
411
+ \`\`\`bash
412
+ curl -s -X POST http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/context-files \\
413
+ -H 'Content-Type: application/json' \\
414
+ -d '{
415
+ "file": "src/utils/helpers.js",
416
+ "line_start": 42,
417
+ "line_end": 78,
418
+ "label": "Helper function used by the changed code"
419
+ }'
420
+ \`\`\`
421
+
422
+ **Response (HTTP 201):** \`{ "success": true, "contextFile": { "id": 1, "review_id": 1, "file": "...", "line_start": 42, "line_end": 78, "label": "..." } }\`
423
+
424
+ Required fields: \`file\` (must be a relative path without \`..\` segments), \`line_start\`, \`line_end\`.
425
+ Optional fields: \`label\`.
426
+
427
+ ### List context files
428
+
429
+ \`\`\`bash
430
+ curl -s http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/context-files
431
+ \`\`\`
432
+
433
+ **Response:** \`{ "success": true, "contextFiles": [...] }\`
434
+
435
+ ### Remove a context file
436
+
437
+ \`\`\`bash
438
+ curl -s -X DELETE http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/context-files/CONTEXT_FILE_ID
439
+ \`\`\`
440
+
441
+ **Response:** \`{ "success": true, "message": "Context file removed" }\`
442
+
443
+ ### Remove all context files
444
+
445
+ \`\`\`bash
446
+ curl -s -X DELETE http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/context-files
447
+ \`\`\`
448
+
449
+ **Response:** \`{ "success": true, "deletedCount": 3, "message": "Removed 3 context files" }\`
450
+
451
+ ### Guidelines
452
+
453
+ - Use judiciously -- only add files that are directly relevant to the discussion.
454
+ - Keep ranges focused on specific functions/blocks (max 500 lines).
455
+ - Use the \`label\` field to explain why the file is relevant.
456
+ - Context files appear in the diff panel below the actual changes.
457
+ - Reference context files in chat using backtick notation: \`\` \`src/utils/helpers.js:42-78\` \`\`.
458
+
459
+ ---
460
+
461
+ ## Diff Hunk Expansion
462
+
463
+ Expand collapsed diff gaps to reveal hidden lines. Useful when you need to show the user lines that are inside a collapsed hunk. This is a transient UI command — expansions are not persisted.
464
+
465
+ ### Expand a hunk
466
+
467
+ \`\`\`bash
468
+ curl -X POST http://localhost:{{PORT}}/api/reviews/{{REVIEW_ID}}/expand-hunk \\
469
+ -H "Content-Type: application/json" \\
470
+ -d '{
471
+ "file": "src/app.js",
472
+ "line_start": 10,
473
+ "line_end": 20,
474
+ "side": "right"
475
+ }'
476
+ \`\`\`
477
+
478
+ **Required fields:**
479
+ - \`file\` (string): Path of the changed file in the diff
480
+ - \`line_start\` (integer): First line to reveal (>= 1)
481
+ - \`line_end\` (integer): Last line to reveal (>= \`line_start\`)
482
+
483
+ **Optional fields:**
484
+ - \`side\` (string): \`"left"\` or \`"right"\` (default: \`"right"\`)
485
+
486
+ **Response:** \`{ "success": true }\``;
487
+
488
+ const CHEAT_SHEET_TEMPLATE = `# pair-review API Cheat Sheet
489
+
490
+ Full docs: \`curl http://localhost:{{PORT}}/api.md?reviewId={{REVIEW_ID}}\`
491
+
492
+ Base: \`http://localhost:{{PORT}}\` | Review: \`{{REVIEW_ID}}\`
493
+
494
+ ## Comments — /api/reviews/{{REVIEW_ID}}/comments
495
+ - \`GET\` — List all (?includeDismissed=true)
496
+ - \`POST\` — Create {file, body, ?line_start, ?line_end, ?side, ?type, ?title} (no line_start=file-level)
497
+ - \`GET /:id\` — Get one
498
+ - \`PUT /:id\` — Update {body}
499
+ - \`DELETE /:id\` — Dismiss (soft-delete)
500
+ - \`PUT /:id/restore\` — Restore dismissed
501
+ - \`DELETE\` (no id) — Bulk dismiss all
502
+
503
+ ## Suggestions — /api/reviews/{{REVIEW_ID}}/suggestions
504
+ - \`GET\` — List (?levels=final,1,2,3 &runId=UUID)
505
+ - \`GET /check\` — Exists? (?runId=UUID)
506
+ - \`POST /:id/status\` — Dismiss/restore {status}
507
+ - \`POST /:id/adopt\` — Adopt as-is (no body)
508
+ - \`POST /:id/edit\` — Adopt edited {action:"adopt_edited", editedText:"..."}
509
+
510
+ ## Analysis Launch (mode-specific)
511
+ - \`POST /api/pr/:owner/:repo/:pr/analyses\` — PR analysis
512
+ - \`POST /api/local/{{REVIEW_ID}}/analyses\` — Local analysis
513
+ Body: {?provider, ?model, ?tier, ?customInstructions, ?skipLevel3, ?enabledLevels}
514
+ - \`POST .../analyses/council\` — Council (add /council to above)
515
+ Body: {councilId|councilConfig, ?customInstructions}
516
+
517
+ ## Analysis Status & Management
518
+ - \`GET /api/reviews/{{REVIEW_ID}}/analyses/status\` — Running?
519
+ - \`GET /api/analyses/:id/status\` — Status by analysis ID
520
+ - \`POST /api/analyses/:id/cancel\` — Cancel
521
+ - WebSocket /ws: subscribe "analysis:{id}" for progress
522
+ - \`GET /api/analyses/runs?reviewId={{REVIEW_ID}}\` — List runs
523
+ - \`GET /api/analyses/runs/latest?reviewId={{REVIEW_ID}}\` — Latest run
524
+ - \`GET /api/analyses/runs/:id\` — Specific run
525
+ - \`POST /api/analyses/results\` — Import external results
526
+
527
+ ## Context Files — /api/reviews/{{REVIEW_ID}}/context-files
528
+ - \`POST\` — Add {file, line_start, line_end, ?label}
529
+ - \`GET\` — List all
530
+ - \`DELETE /:id\` — Remove one
531
+ - \`DELETE\` (no id) — Remove all
532
+
533
+ ## Diff Hunk Expansion
534
+ - \`POST /api/reviews/{{REVIEW_ID}}/expand-hunk\` — {file, line_start, line_end, ?side}
535
+
536
+ ## Comment Types
537
+ bug | suggestion | improvement | security | performance | design | praise | style | nitpick | question`;
538
+
539
+ module.exports = { renderApiDocs, buildApiCheatSheet };