@in-the-loop-labs/pair-review 1.6.2 → 2.0.1

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 (63) hide show
  1. package/README.md +77 -4
  2. package/package.json +1 -1
  3. package/plugin/.claude-plugin/plugin.json +1 -1
  4. package/plugin/skills/review-requests/SKILL.md +4 -1
  5. package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
  6. package/plugin-code-critic/skills/analyze/SKILL.md +4 -3
  7. package/public/css/pr.css +1962 -114
  8. package/public/js/CONVENTIONS.md +16 -0
  9. package/public/js/components/AIPanel.js +66 -0
  10. package/public/js/components/AnalysisConfigModal.js +2 -2
  11. package/public/js/components/ChatPanel.js +2955 -0
  12. package/public/js/components/CouncilProgressModal.js +12 -16
  13. package/public/js/components/KeyboardShortcuts.js +3 -0
  14. package/public/js/components/PanelGroup.js +723 -0
  15. package/public/js/components/PreviewModal.js +3 -8
  16. package/public/js/index.js +8 -0
  17. package/public/js/local.js +17 -615
  18. package/public/js/modules/analysis-history.js +19 -68
  19. package/public/js/modules/comment-manager.js +103 -20
  20. package/public/js/modules/diff-context.js +176 -0
  21. package/public/js/modules/diff-renderer.js +30 -0
  22. package/public/js/modules/file-comment-manager.js +126 -105
  23. package/public/js/modules/file-list-merger.js +64 -0
  24. package/public/js/modules/panel-resizer.js +25 -6
  25. package/public/js/modules/suggestion-manager.js +40 -125
  26. package/public/js/pr.js +1009 -159
  27. package/public/js/repo-settings.js +36 -6
  28. package/public/js/utils/category-emoji.js +44 -0
  29. package/public/js/utils/time.js +32 -0
  30. package/public/local.html +107 -70
  31. package/public/pr.html +107 -70
  32. package/public/repo-settings.html +32 -0
  33. package/src/ai/analyzer.js +5 -1
  34. package/src/ai/copilot-provider.js +39 -9
  35. package/src/ai/cursor-agent-provider.js +45 -11
  36. package/src/ai/gemini-provider.js +17 -4
  37. package/src/ai/prompts/config.js +7 -1
  38. package/src/ai/provider-availability.js +1 -1
  39. package/src/ai/provider.js +25 -37
  40. package/src/chat/CONVENTIONS.md +18 -0
  41. package/src/chat/pi-bridge.js +491 -0
  42. package/src/chat/prompt-builder.js +272 -0
  43. package/src/chat/session-manager.js +619 -0
  44. package/src/config.js +14 -0
  45. package/src/database.js +322 -15
  46. package/src/main.js +4 -17
  47. package/src/routes/analyses.js +721 -0
  48. package/src/routes/chat.js +655 -0
  49. package/src/routes/config.js +29 -8
  50. package/src/routes/context-files.js +274 -0
  51. package/src/routes/local.js +225 -1133
  52. package/src/routes/mcp.js +39 -30
  53. package/src/routes/pr.js +424 -58
  54. package/src/routes/reviews.js +1035 -0
  55. package/src/routes/shared.js +4 -29
  56. package/src/server.js +34 -12
  57. package/src/sse/review-events.js +46 -0
  58. package/src/utils/auto-context.js +88 -0
  59. package/src/utils/category-emoji.js +33 -0
  60. package/src/utils/diff-annotator.js +75 -1
  61. package/src/utils/diff-file-list.js +57 -0
  62. package/src/routes/analysis.js +0 -1600
  63. package/src/routes/comments.js +0 -534
package/public/pr.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" data-theme="light">
2
+ <html lang="en" data-theme="light" data-chat="disabled">
3
3
  <head>
4
4
  <script>
5
5
  // Initialize theme immediately to prevent flash
@@ -7,6 +7,16 @@
7
7
  const savedTheme = localStorage.getItem('theme') || 'light';
8
8
  document.documentElement.setAttribute('data-theme', savedTheme);
9
9
  })();
10
+ // Fetch chat availability early so PanelGroup sees the correct state
11
+ fetch('/api/config').then(r => r.ok ? r.json() : null).then(config => {
12
+ if (!config) return;
13
+ let state = 'disabled';
14
+ if (config.enable_chat) state = config.pi_available ? 'available' : 'unavailable';
15
+ document.documentElement.setAttribute('data-chat', state);
16
+ const shortcutsState = config.chat_enable_shortcuts === false ? 'disabled' : 'enabled';
17
+ document.documentElement.setAttribute('data-chat-shortcuts', shortcutsState);
18
+ window.dispatchEvent(new CustomEvent('chat-state-changed', { detail: { state } }));
19
+ }).catch(() => {});
10
20
  </script>
11
21
  <meta charset="UTF-8">
12
22
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -116,7 +126,7 @@
116
126
  <!-- File Tree Sidebar (Left) -->
117
127
  <aside class="sidebar files-sidebar" id="files-sidebar">
118
128
  <div class="sidebar-header">
119
- <span class="sidebar-title">Changed Files</span>
129
+ <span class="sidebar-title">File Navigator</span>
120
130
  <div style="display: flex; align-items: center; gap: 8px;">
121
131
  <span class="sidebar-count" id="sidebar-file-count">0</span>
122
132
  <button class="sidebar-collapse-btn" id="sidebar-collapse-btn" title="Close panel">
@@ -187,10 +197,19 @@
187
197
  </svg>
188
198
  <span class="btn-text">Analyze</span>
189
199
  </button>
190
- <button class="btn btn-sm btn-icon" id="ai-panel-toggle" title="Toggle Review panel">
200
+ <button class="btn btn-sm btn-icon" id="chat-toggle-btn" title="Toggle Chat panel">
201
+ <svg class="chat-icon" viewBox="0 0 16 16" fill="currentColor" width="14" height="14">
202
+ <path d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z"/>
203
+ </svg>
204
+ </button>
205
+ <button class="btn btn-sm btn-icon" id="panel-layout-toggle" title="Switch panel layout" style="display: none;">
191
206
  <svg viewBox="0 0 16 16" fill="currentColor" width="14" height="14">
192
- <path d="M6.823 7.823a.25.25 0 0 1 0 .354l-2.396 2.396A.25.25 0 0 1 4 10.396V5.604a.25.25 0 0 1 .427-.177Z"></path>
193
- <path d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25H9.5v-13H1.75a.25.25 0 0 0-.25.25ZM11 14.5h3.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H11Z"></path>
207
+ <path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25ZM1.5 1.75v12.5c0 .138.112.25.25.25H7.5v-13H1.75a.25.25 0 0 0-.25.25ZM8.5 14.5h5.75a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H8.5Z"/>
208
+ </svg>
209
+ </button>
210
+ <button class="btn btn-sm btn-icon" id="ai-panel-toggle" title="Toggle Review panel">
211
+ <svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
212
+ <path d="M2 2h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1Zm4.655 8.595a.75.75 0 0 1 0 1.06L4.03 14.28a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.095-2.095a.75.75 0 0 1 1.06 0ZM9.75 2.5h5.5a.75.75 0 0 1 0 1.5h-5.5a.75.75 0 0 1 0-1.5Zm0 5h5.5a.75.75 0 0 1 0 1.5h-5.5a.75.75 0 0 1 0-1.5Zm0 5h5.5a.75.75 0 0 1 0 1.5h-5.5a.75.75 0 0 1 0-1.5Zm-7.25-9v3h3v-3Z"/>
194
213
  </svg>
195
214
  </button>
196
215
  </div>
@@ -200,86 +219,90 @@
200
219
  </div>
201
220
  </main>
202
221
 
203
- <!-- AI Analysis Panel (Right) -->
204
- <aside class="ai-panel" id="ai-panel">
205
- <div class="resize-handle resize-handle-left" data-panel="ai-panel"></div>
206
- <div class="ai-panel-header">
207
- <div class="ai-panel-title">
208
- <button class="ai-avatar-small ai-summary-btn" id="ai-summary-btn" aria-label="View AI summary" title="View AI Summary">
209
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14">
210
- <path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
211
- <path d="M18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z"/>
222
+ <!-- Right Panel Group (AI Panel + Chat) -->
223
+ <div class="right-panel-group" id="right-panel-group">
224
+ <!-- AI Analysis Panel (Right) -->
225
+ <aside class="ai-panel" id="ai-panel">
226
+ <div class="resize-handle resize-handle-left" data-panel="ai-panel"></div>
227
+ <div class="ai-panel-header">
228
+ <div class="ai-panel-title">
229
+ <button class="ai-avatar-small ai-summary-btn" id="ai-summary-btn" aria-label="View AI summary" title="View AI Summary">
230
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14">
231
+ <path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
232
+ <path d="M18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z"/>
233
+ </svg>
234
+ </button>
235
+ <span>Review</span>
236
+ </div>
237
+ <button class="ai-panel-close" id="ai-panel-close" title="Close panel">
238
+ <svg viewBox="0 0 16 16" fill="currentColor" width="14" height="14">
239
+ <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"/>
212
240
  </svg>
213
241
  </button>
214
- <span>Review</span>
215
242
  </div>
216
- <button class="ai-panel-close" id="ai-panel-close" title="Close panel">
217
- <svg viewBox="0 0 16 16" fill="currentColor" width="14" height="14">
218
- <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"/>
219
- </svg>
220
- </button>
221
- </div>
222
243
 
223
- <!-- Analysis Context Section -->
224
- <div class="analysis-context" id="analysis-context">
225
- <!-- Empty state -->
226
- <div class="analysis-context-empty" id="analysis-context-empty">
227
- <svg class="analysis-context-empty-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
228
- <path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
229
- </svg>
230
- <span>No AI analysis yet</span>
231
- </div>
232
-
233
- <!-- Selector (hidden until analysis exists) -->
234
- <div class="analysis-context-selector" id="analysis-context-selector" style="display: none;">
235
- <button class="analysis-context-btn" id="analysis-context-btn">
236
- <span class="analysis-context-label" id="analysis-context-label">--</span>
237
- <svg class="dropdown-caret" width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
238
- <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
244
+ <!-- Analysis Context Section -->
245
+ <div class="analysis-context" id="analysis-context">
246
+ <!-- Empty state -->
247
+ <div class="analysis-context-empty" id="analysis-context-empty">
248
+ <svg class="analysis-context-empty-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
249
+ <path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
239
250
  </svg>
240
- </button>
241
- </div>
251
+ <span>No AI analysis yet</span>
252
+ </div>
242
253
 
243
- <!-- Split-panel dropdown (hidden by default) -->
244
- <div class="analysis-context-dropdown" id="analysis-context-dropdown">
245
- <div class="analysis-run-list" id="analysis-context-list">
246
- <!-- Populated by JavaScript -->
254
+ <!-- Selector (hidden until analysis exists) -->
255
+ <div class="analysis-context-selector" id="analysis-context-selector" style="display: none;">
256
+ <button class="analysis-context-btn" id="analysis-context-btn">
257
+ <span class="analysis-context-label" id="analysis-context-label">--</span>
258
+ <svg class="dropdown-caret" width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
259
+ <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
260
+ </svg>
261
+ </button>
247
262
  </div>
248
- <div class="analysis-preview-panel" id="analysis-context-preview">
249
- <!-- Populated by JavaScript on hover -->
263
+
264
+ <!-- Split-panel dropdown (hidden by default) -->
265
+ <div class="analysis-context-dropdown" id="analysis-context-dropdown">
266
+ <div class="analysis-run-list" id="analysis-context-list">
267
+ <!-- Populated by JavaScript -->
268
+ </div>
269
+ <div class="analysis-preview-panel" id="analysis-context-preview">
270
+ <!-- Populated by JavaScript on hover -->
271
+ </div>
250
272
  </div>
251
273
  </div>
252
- </div>
253
274
 
254
- <!-- Segment Control -->
255
- <div class="segment-control" id="segment-control">
256
- <div class="segment-control-inner">
257
- <button class="segment-btn active" data-segment="ai">AI <span class="segment-count">(0)</span></button>
258
- <button class="segment-btn" data-segment="comments">User <span class="segment-count">(0)</span></button>
259
- <button class="segment-btn" data-segment="all">All <span class="segment-count">(0)</span></button>
275
+ <!-- Segment Control -->
276
+ <div class="segment-control" id="segment-control">
277
+ <div class="segment-control-inner">
278
+ <button class="segment-btn active" data-segment="ai">AI <span class="segment-count">(0)</span></button>
279
+ <button class="segment-btn" data-segment="comments">User <span class="segment-count">(0)</span></button>
280
+ <button class="segment-btn" data-segment="all">All <span class="segment-count">(0)</span></button>
281
+ </div>
260
282
  </div>
261
- </div>
262
-
263
- <!-- Level Filter -->
264
- <div class="level-filter" id="level-filter">
265
- <button class="level-pill active" data-level="final" title="Curated suggestions from all levels">Overall</button>
266
- <button class="level-pill" data-level="1" title="Line-level analysis">Line</button>
267
- <button class="level-pill" data-level="2" title="File-level analysis">File</button>
268
- <button class="level-pill" data-level="3" title="Codebase-level analysis">Codebase</button>
269
- </div>
270
283
 
271
- <!-- Findings Summary -->
272
- <div class="findings-summary" id="findings-summary">
273
- <div class="findings-header">
274
- <span class="findings-count" id="findings-count">0 items</span>
284
+ <!-- Level Filter -->
285
+ <div class="level-filter" id="level-filter">
286
+ <button class="level-pill active" data-level="final" title="Curated suggestions from all levels">Overall</button>
287
+ <button class="level-pill" data-level="1" title="Line-level analysis">Line</button>
288
+ <button class="level-pill" data-level="2" title="File-level analysis">File</button>
289
+ <button class="level-pill" data-level="3" title="Codebase-level analysis">Codebase</button>
275
290
  </div>
276
- <div class="findings-list" id="findings-list">
277
- <div class="findings-empty">
278
- <p>No AI analysis yet. Click "Analyze" to get started.</p>
291
+
292
+ <!-- Findings Summary -->
293
+ <div class="findings-summary" id="findings-summary">
294
+ <div class="findings-header">
295
+ <span class="findings-count" id="findings-count">0 items</span>
296
+ </div>
297
+ <div class="findings-list" id="findings-list">
298
+ <div class="findings-empty">
299
+ <p>No AI analysis yet. Click "Analyze" to get started.</p>
300
+ </div>
279
301
  </div>
280
302
  </div>
281
- </div>
282
- </aside>
303
+ </aside>
304
+ <div id="chat-panel-container"></div>
305
+ </div>
283
306
  </div>
284
307
  </div>
285
308
 
@@ -299,12 +322,18 @@
299
322
  <!-- Tier icons utility -->
300
323
  <script src="/js/utils/tier-icons.js"></script>
301
324
 
325
+ <!-- Category emoji mapping -->
326
+ <script src="/js/utils/category-emoji.js"></script>
327
+
302
328
  <!-- Suggestion UI utility -->
303
329
  <script src="/js/utils/suggestion-ui.js"></script>
304
330
 
305
331
  <!-- File order utility -->
306
332
  <script src="/js/utils/file-order.js"></script>
307
333
 
334
+ <!-- Timestamp parsing utility -->
335
+ <script src="/js/utils/time.js"></script>
336
+
308
337
  <!-- Components -->
309
338
  <script src="/js/components/Toast.js"></script>
310
339
  <script src="/js/components/ConfirmDialog.js"></script>
@@ -335,6 +364,14 @@
335
364
  <script src="/js/modules/file-comment-manager.js"></script>
336
365
  <script src="/js/modules/panel-resizer.js"></script>
337
366
  <script src="/js/modules/analysis-history.js"></script>
367
+ <script src="/js/modules/diff-context.js"></script>
368
+ <script src="/js/modules/file-list-merger.js"></script>
369
+
370
+ <!-- Chat Panel Component -->
371
+ <script src="/js/components/ChatPanel.js"></script>
372
+
373
+ <!-- Panel Group Component -->
374
+ <script src="/js/components/PanelGroup.js"></script>
338
375
 
339
376
  <!-- PR JavaScript -->
340
377
  <script src="/js/pr.js"></script>
@@ -166,6 +166,38 @@ Examples:
166
166
  </div>
167
167
  </section>
168
168
 
169
+ <!-- Chat Instructions Section -->
170
+ <section class="settings-section">
171
+ <div class="section-header">
172
+ <h2>
173
+ Chat Instructions
174
+ </h2>
175
+ <p class="section-description">
176
+ These instructions will be appended to the system prompt when starting chat sessions for this repository.
177
+ Use this for guiding the chat assistant's behavior, tone, or focus areas.
178
+ </p>
179
+ </div>
180
+ <div class="instructions-wrapper">
181
+ <textarea
182
+ id="chat-instructions"
183
+ name="default_chat_instructions"
184
+ class="instructions-textarea"
185
+ placeholder="Enter instructions for chat sessions...
186
+
187
+ Examples:
188
+ • Always reference the project's coding standards when answering
189
+ • Focus on explaining the reasoning behind suggestions
190
+ • When discussing tests, prefer integration tests over unit tests
191
+ • Use concise responses unless asked for detail"
192
+ rows="6"
193
+ ></textarea>
194
+ <div class="textarea-footer">
195
+ <span class="char-count"><span id="chat-char-count">0</span> characters</span>
196
+ <span class="textarea-hint">Markdown supported</span>
197
+ </div>
198
+ </div>
199
+ </section>
200
+
169
201
  <!-- Repository Location Section -->
170
202
  <section class="settings-section" id="local-path-section">
171
203
  <div class="section-header">
@@ -17,6 +17,7 @@ const {
17
17
  buildAnalysisLineNumberGuidance,
18
18
  buildOrchestrationLineNumberGuidance: buildOrchestrationGuidance,
19
19
  } = require('./prompts/line-number-guidance');
20
+ const { resolveTier } = require('./prompts/config');
20
21
  const { registerProcess, isAnalysisCancelled, CancellationError } = require('../routes/shared');
21
22
  const { AnalysisRunRepository, CommentRepository } = require('../database');
22
23
  const { mergeInstructions } = require('../utils/instructions');
@@ -175,6 +176,7 @@ class Analyzer {
175
176
  reviewId: prId, // prId is actually review.id (works for both PR and local modes)
176
177
  provider: this.provider,
177
178
  model: this.model,
179
+ tier: options.tier ? resolveTier(options.tier) : 'balanced',
178
180
  customInstructions: mergedInstructions, // Keep for backward compat
179
181
  repoInstructions,
180
182
  requestInstructions,
@@ -212,7 +214,7 @@ class Analyzer {
212
214
  if (mergedInstructions) {
213
215
  logger.info(`${logPrefix}Custom instructions provided: ${mergedInstructions.length} chars`);
214
216
  }
215
- const tier = options.tier || 'balanced';
217
+ const tier = options.tier ? resolveTier(options.tier) : 'balanced';
216
218
 
217
219
  // Build the promises array for each level based on enabledLevels
218
220
  const levelAnalyzers = [
@@ -2664,6 +2666,7 @@ File-level suggestions should NOT have a line number. They apply to the entire f
2664
2666
  reviewId,
2665
2667
  provider: 'council',
2666
2668
  model: 'voice-centric',
2669
+ tier: null,
2667
2670
  customInstructions: mergedInstructions,
2668
2671
  repoInstructions: instructions?.repoInstructions || null,
2669
2672
  requestInstructions: instructions?.requestInstructions || null,
@@ -2760,6 +2763,7 @@ File-level suggestions should NOT have a line number. They apply to the entire f
2760
2763
  reviewId,
2761
2764
  provider: voice.provider,
2762
2765
  model: voice.model,
2766
+ tier: voiceTier,
2763
2767
  customInstructions: mergedInstructions,
2764
2768
  repoInstructions: instructions?.repoInstructions || null,
2765
2769
  requestInstructions: instructions?.requestInstructions || null,
@@ -21,9 +21,10 @@ const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
21
21
  *
22
22
  * GitHub Copilot CLI supports multiple AI models including OpenAI,
23
23
  * Anthropic, and Google models via the --model flag.
24
- * Available models (as of Feb 2026): claude-haiku-4.5, claude-sonnet-4.5,
25
- * gemini-3-pro-preview, gpt-5.2-codex, claude-opus-4.5,
26
- * claude-opus-4.6. Default is claude-sonnet-4.5.
24
+ * Available models (as of Feb 2026): claude-haiku-4.5, claude-sonnet-4.6,
25
+ * claude-sonnet-4.5, gemini-3-pro-preview, gpt-5.2-codex, gpt-5.3-codex,
26
+ * claude-opus-4.5, claude-opus-4.6, claude-opus-4.6-fast.
27
+ * Default is claude-sonnet-4.6.
27
28
  */
28
29
  const COPILOT_MODELS = [
29
30
  {
@@ -36,15 +37,24 @@ const COPILOT_MODELS = [
36
37
  badgeClass: 'badge-speed'
37
38
  },
38
39
  {
39
- id: 'claude-sonnet-4.5',
40
- name: 'Claude Sonnet 4.5',
40
+ id: 'claude-sonnet-4.6',
41
+ name: 'Claude Sonnet 4.6',
41
42
  tier: 'balanced',
42
- tagline: 'Reliable Review',
43
- description: 'Copilot defaultstrong code understanding with excellent quality-to-cost ratio',
43
+ tagline: 'Best Balance',
44
+ description: 'Latest Sonnetexcellent code understanding with fast turnaround',
44
45
  badge: 'Recommended',
45
46
  badgeClass: 'badge-recommended',
46
47
  default: true
47
48
  },
49
+ {
50
+ id: 'claude-sonnet-4.5',
51
+ name: 'Claude Sonnet 4.5',
52
+ tier: 'balanced',
53
+ tagline: 'Previous Gen',
54
+ description: 'Previous generation Sonnet—strong code understanding with excellent quality-to-cost ratio',
55
+ badge: 'Previous Gen',
56
+ badgeClass: 'badge-balanced'
57
+ },
48
58
  {
49
59
  id: 'gemini-3-pro-preview',
50
60
  name: 'Gemini 3 Pro',
@@ -63,6 +73,15 @@ const COPILOT_MODELS = [
63
73
  badge: 'Balanced',
64
74
  badgeClass: 'badge-balanced'
65
75
  },
76
+ {
77
+ id: 'gpt-5.3-codex',
78
+ name: 'GPT-5.3 Codex',
79
+ tier: 'thorough',
80
+ tagline: 'Deep Code Analysis',
81
+ description: 'Most capable OpenAI coding model—frontier performance for complex multi-file reviews',
82
+ badge: 'Thorough',
83
+ badgeClass: 'badge-power'
84
+ },
66
85
  {
67
86
  id: 'claude-opus-4.5',
68
87
  name: 'Claude Opus 4.5',
@@ -80,9 +99,20 @@ const COPILOT_MODELS = [
80
99
  description: 'Most capable model for critical code reviews—deep reasoning for security and architecture',
81
100
  badge: 'Premium',
82
101
  badgeClass: 'badge-premium'
102
+ },
103
+ {
104
+ id: 'claude-opus-4.6-fast',
105
+ name: 'Claude Opus 4.6 Fast',
106
+ tier: 'thorough',
107
+ tagline: 'Fast Opus',
108
+ description: 'Opus-level analysis at reduced latency—preview mode for faster deep reviews',
109
+ badge: 'Preview',
110
+ badgeClass: 'badge-premium'
83
111
  }
84
112
  ];
85
113
 
114
+ const DEFAULT_COPILOT_MODEL = 'claude-sonnet-4.6';
115
+
86
116
  class CopilotProvider extends AIProvider {
87
117
  /**
88
118
  * @param {string} model - Model identifier
@@ -92,7 +122,7 @@ class CopilotProvider extends AIProvider {
92
122
  * @param {Object} configOverrides.env - Additional environment variables
93
123
  * @param {Object[]} configOverrides.models - Custom model definitions
94
124
  */
95
- constructor(model = 'claude-sonnet-4.5', configOverrides = {}) {
125
+ constructor(model = DEFAULT_COPILOT_MODEL, configOverrides = {}) {
96
126
  super(model);
97
127
 
98
128
  // Command precedence: ENV > config > default
@@ -462,7 +492,7 @@ class CopilotProvider extends AIProvider {
462
492
  }
463
493
 
464
494
  static getDefaultModel() {
465
- return 'claude-sonnet-4.5';
495
+ return DEFAULT_COPILOT_MODEL;
466
496
  }
467
497
 
468
498
  static getInstallInstructions() {
@@ -31,8 +31,8 @@ const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
31
31
  * Tier structure:
32
32
  * - free (auto): Cursor's default auto-routing model
33
33
  * - fast (composer-1, gpt-5.3-codex-fast, gemini-3-flash): Quick analysis
34
- * - balanced (composer-1.5, sonnet-4.5-thinking, gemini-3-pro): Recommended for most reviews
35
- * - thorough (gpt-5.3-codex-high, opus-4.5-thinking, opus-4.6-thinking): Deep analysis for complex code
34
+ * - balanced (composer-1.5, sonnet-4.6-thinking, sonnet-4.5-thinking, gemini-3-pro, gemini-3.1-pro): Recommended for most reviews
35
+ * - thorough (gpt-5.3-codex-high, gpt-5.3-codex-xhigh, opus-4.5-thinking, opus-4.6-thinking): Deep analysis for complex code
36
36
  */
37
37
  const CURSOR_AGENT_MODELS = [
38
38
  {
@@ -81,15 +81,24 @@ const CURSOR_AGENT_MODELS = [
81
81
  badgeClass: 'badge-speed'
82
82
  },
83
83
  {
84
- id: 'sonnet-4.5-thinking',
85
- name: 'Claude 4.5 Sonnet (Thinking)',
84
+ id: 'sonnet-4.6-thinking',
85
+ name: 'Claude 4.6 Sonnet (Thinking)',
86
86
  tier: 'balanced',
87
87
  tagline: 'Best Balance',
88
- description: 'Extended thinking for thorough analysis with excellent quality-to-cost ratio',
88
+ description: 'Latest Sonnet with extended thinking—excellent quality-to-cost ratio for thorough reviews',
89
89
  badge: 'Recommended',
90
90
  badgeClass: 'badge-recommended',
91
91
  default: true
92
92
  },
93
+ {
94
+ id: 'sonnet-4.5-thinking',
95
+ name: 'Claude 4.5 Sonnet (Thinking)',
96
+ tier: 'balanced',
97
+ tagline: 'Previous Gen',
98
+ description: 'Previous generation extended thinking—still strong for thorough analysis',
99
+ badge: 'Previous Gen',
100
+ badgeClass: 'badge-balanced'
101
+ },
93
102
  {
94
103
  id: 'gemini-3-pro',
95
104
  name: 'Gemini 3 Pro',
@@ -99,6 +108,15 @@ const CURSOR_AGENT_MODELS = [
99
108
  badge: 'Balanced',
100
109
  badgeClass: 'badge-balanced'
101
110
  },
111
+ {
112
+ id: 'gemini-3.1-pro',
113
+ name: 'Gemini 3.1 Pro',
114
+ tier: 'balanced',
115
+ tagline: 'Latest Gemini',
116
+ description: 'Newest Gemini model—cutting-edge reasoning for complex multi-file reviews',
117
+ badge: 'Latest',
118
+ badgeClass: 'badge-balanced'
119
+ },
102
120
  {
103
121
  id: 'gpt-5.3-codex-high',
104
122
  name: 'GPT-5.3 Codex High',
@@ -108,6 +126,15 @@ const CURSOR_AGENT_MODELS = [
108
126
  badge: 'Thorough',
109
127
  badgeClass: 'badge-power'
110
128
  },
129
+ {
130
+ id: 'gpt-5.3-codex-xhigh',
131
+ name: 'GPT-5.3 Codex Extra High',
132
+ tier: 'thorough',
133
+ tagline: 'Maximum Reasoning',
134
+ description: 'Highest reasoning effort for the most complex architectural reviews',
135
+ badge: 'Maximum',
136
+ badgeClass: 'badge-power'
137
+ },
111
138
  {
112
139
  id: 'opus-4.5-thinking',
113
140
  name: 'Claude 4.5 Opus (Thinking)',
@@ -128,6 +155,8 @@ const CURSOR_AGENT_MODELS = [
128
155
  }
129
156
  ];
130
157
 
158
+ const DEFAULT_CURSOR_AGENT_MODEL = 'sonnet-4.6-thinking';
159
+
131
160
  class CursorAgentProvider extends AIProvider {
132
161
  /**
133
162
  * @param {string} model - Model identifier
@@ -136,8 +165,9 @@ class CursorAgentProvider extends AIProvider {
136
165
  * @param {string[]} configOverrides.extra_args - Additional CLI arguments
137
166
  * @param {Object} configOverrides.env - Additional environment variables
138
167
  * @param {Object[]} configOverrides.models - Custom model definitions
168
+ * @param {boolean} configOverrides.yolo - When true, use --yolo instead of --trust/--sandbox
139
169
  */
140
- constructor(model = 'sonnet-4.5-thinking', configOverrides = {}) {
170
+ constructor(model = DEFAULT_CURSOR_AGENT_MODEL, configOverrides = {}) {
141
171
  super(model);
142
172
 
143
173
  // Command precedence: ENV > config > default
@@ -180,9 +210,12 @@ class CursorAgentProvider extends AIProvider {
180
210
  // in parseCursorAgentResponse (isStreamingDelta / timestamp_ms check). If this
181
211
  // flag is removed, the parser will treat all assistant events as complete messages,
182
212
  // which is fine but will change accumulation behavior. Keep these in sync.
183
- // Use --sandbox enabled for security (when not in yolo mode)
184
- const sandboxArgs = configOverrides.yolo ? ['--sandbox', 'disabled'] : ['--sandbox', 'enabled'];
185
- const baseArgs = ['-p', '--output-format', 'stream-json', '--stream-partial-output', '--model', model, ...sandboxArgs];
213
+ // Normal mode: --trust to auto-approve the working directory, --sandbox enabled for safety
214
+ // Yolo mode: --yolo to skip all permissions (implies trust + sandbox disabled)
215
+ const permissionArgs = configOverrides.yolo
216
+ ? ['--yolo']
217
+ : ['--trust', '--sandbox', 'enabled'];
218
+ const baseArgs = ['-p', '--output-format', 'stream-json', '--stream-partial-output', '--model', model, ...permissionArgs];
186
219
  const providerArgs = configOverrides.extra_args || [];
187
220
  const modelConfig = configOverrides.models?.find(m => m.id === model);
188
221
  const modelArgs = modelConfig?.extra_args || [];
@@ -671,7 +704,8 @@ class CursorAgentProvider extends AIProvider {
671
704
  */
672
705
  buildArgsForModel(model) {
673
706
  // Base args for extraction (text output, no tools needed)
674
- const baseArgs = ['-p', '--output-format', 'text', '--model', model];
707
+ // --trust needed to auto-approve the working directory without interactive prompt
708
+ const baseArgs = ['-p', '--output-format', 'text', '--trust', '--model', model];
675
709
  // Provider-level extra_args (from configOverrides)
676
710
  const providerArgs = this.configOverrides?.extra_args || [];
677
711
  // Model-specific extra_args (from the model config for the given model)
@@ -790,7 +824,7 @@ class CursorAgentProvider extends AIProvider {
790
824
  }
791
825
 
792
826
  static getDefaultModel() {
793
- return 'sonnet-4.5-thinking';
827
+ return DEFAULT_CURSOR_AGENT_MODEL;
794
828
  }
795
829
 
796
830
  static getInstallInstructions() {
@@ -21,7 +21,8 @@ const BIN_DIR = path.join(__dirname, '..', '..', 'bin');
21
21
  */
22
22
  const GEMINI_MODELS = [
23
23
  {
24
- id: 'gemini-3-flash-preview',
24
+ id: 'gemini-3-flash',
25
+ aliases: ['gemini-3-flash-preview'],
25
26
  name: '3.0 Flash',
26
27
  tier: 'fast',
27
28
  tagline: 'Rapid Sanity Check',
@@ -40,16 +41,28 @@ const GEMINI_MODELS = [
40
41
  default: true
41
42
  },
42
43
  {
43
- id: 'gemini-3-pro-preview',
44
+ id: 'gemini-3-pro',
45
+ aliases: ['gemini-3-pro-preview'],
44
46
  name: '3.0 Pro',
45
47
  tier: 'thorough',
46
48
  tagline: 'Architectural Audit',
47
49
  description: 'Most intelligent Gemini model—advanced reasoning for deep architectural analysis',
48
50
  badge: 'Deep Dive',
49
51
  badgeClass: 'badge-power'
52
+ },
53
+ {
54
+ id: 'gemini-3.1-pro',
55
+ name: '3.1 Pro',
56
+ tier: 'thorough',
57
+ tagline: 'Latest & Greatest',
58
+ description: 'Newest Gemini model—cutting-edge reasoning for complex architectural reviews',
59
+ badge: 'Latest',
60
+ badgeClass: 'badge-power'
50
61
  }
51
62
  ];
52
63
 
64
+ const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro';
65
+
53
66
  class GeminiProvider extends AIProvider {
54
67
  /**
55
68
  * @param {string} model - Model identifier
@@ -59,7 +72,7 @@ class GeminiProvider extends AIProvider {
59
72
  * @param {Object} configOverrides.env - Additional environment variables
60
73
  * @param {Object[]} configOverrides.models - Custom model definitions
61
74
  */
62
- constructor(model = 'gemini-2.5-pro', configOverrides = {}) {
75
+ constructor(model = DEFAULT_GEMINI_MODEL, configOverrides = {}) {
63
76
  super(model);
64
77
 
65
78
  // Command precedence: ENV > config > default
@@ -690,7 +703,7 @@ class GeminiProvider extends AIProvider {
690
703
  }
691
704
 
692
705
  static getDefaultModel() {
693
- return 'gemini-2.5-pro';
706
+ return DEFAULT_GEMINI_MODEL;
694
707
  }
695
708
 
696
709
  static getInstallInstructions() {
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Defines tier mappings for the prompt system.
6
6
  * Note: Provider-specific model-to-tier mappings are defined in each provider's
7
- * getModels() method. Use getTierForModel() from src/ai/provider.js to query them.
7
+ * getModels() method. The tier is persisted on each analysis_runs record at creation time.
8
8
  */
9
9
 
10
10
  /**
@@ -21,6 +21,11 @@ const TIER_ALIASES = {
21
21
  */
22
22
  const TIERS = ['fast', 'balanced', 'thorough'];
23
23
 
24
+ /**
25
+ * All accepted tier values (internal tiers + user-facing aliases)
26
+ */
27
+ const VALID_TIERS = [...TIERS, ...Object.keys(TIER_ALIASES)];
28
+
24
29
  /**
25
30
  * Prompt types (analysis levels)
26
31
  */
@@ -44,6 +49,7 @@ function resolveTier(tierOrAlias) {
44
49
  module.exports = {
45
50
  TIER_ALIASES,
46
51
  TIERS,
52
+ VALID_TIERS,
47
53
  PROMPT_TYPES,
48
54
  resolveTier
49
55
  };