@in-the-loop-labs/pair-review 2.6.3 → 3.0.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 (150) hide show
  1. package/.pi/extensions/task/index.ts +1 -1
  2. package/.pi/skills/review-roulette/SKILL.md +1 -1
  3. package/LICENSE +201 -674
  4. package/README.md +2 -2
  5. package/bin/pair-review.js +1 -1
  6. package/package.json +2 -2
  7. package/plugin/.claude-plugin/plugin.json +2 -2
  8. package/plugin-code-critic/.claude-plugin/plugin.json +2 -2
  9. package/plugin-code-critic/skills/analyze/scripts/git-diff-lines +1 -1
  10. package/public/css/ai-summary-modal.css +1 -1
  11. package/public/css/pr.css +194 -0
  12. package/public/index.html +168 -3
  13. package/public/js/components/AIPanel.js +17 -3
  14. package/public/js/components/AISummaryModal.js +1 -1
  15. package/public/js/components/AdvancedConfigTab.js +1 -1
  16. package/public/js/components/AnalysisConfigModal.js +1 -1
  17. package/public/js/components/ChatPanel.js +42 -7
  18. package/public/js/components/ConfirmDialog.js +22 -3
  19. package/public/js/components/CouncilProgressModal.js +14 -1
  20. package/public/js/components/DiffOptionsDropdown.js +411 -24
  21. package/public/js/components/EmojiPicker.js +1 -1
  22. package/public/js/components/KeyboardShortcuts.js +1 -1
  23. package/public/js/components/PanelGroup.js +1 -1
  24. package/public/js/components/PreviewModal.js +1 -1
  25. package/public/js/components/ReviewModal.js +1 -1
  26. package/public/js/components/SplitButton.js +1 -1
  27. package/public/js/components/StatusIndicator.js +1 -1
  28. package/public/js/components/SuggestionNavigator.js +13 -6
  29. package/public/js/components/TabTitle.js +96 -0
  30. package/public/js/components/TextInputDialog.js +1 -1
  31. package/public/js/components/TimeoutSelect.js +1 -1
  32. package/public/js/components/Toast.js +7 -1
  33. package/public/js/components/VoiceCentricConfigTab.js +1 -1
  34. package/public/js/index.js +649 -44
  35. package/public/js/local.js +570 -77
  36. package/public/js/modules/analysis-history.js +4 -3
  37. package/public/js/modules/comment-manager.js +6 -1
  38. package/public/js/modules/comment-minimizer.js +304 -0
  39. package/public/js/modules/diff-context.js +1 -1
  40. package/public/js/modules/diff-renderer.js +1 -1
  41. package/public/js/modules/file-comment-manager.js +1 -1
  42. package/public/js/modules/file-list-merger.js +1 -1
  43. package/public/js/modules/gap-coordinates.js +1 -1
  44. package/public/js/modules/hunk-parser.js +1 -1
  45. package/public/js/modules/line-tracker.js +1 -1
  46. package/public/js/modules/panel-resizer.js +1 -1
  47. package/public/js/modules/storage-cleanup.js +1 -1
  48. package/public/js/modules/suggestion-manager.js +1 -1
  49. package/public/js/pr.js +83 -7
  50. package/public/js/repo-settings.js +1 -1
  51. package/public/js/utils/category-emoji.js +1 -1
  52. package/public/js/utils/file-order.js +1 -1
  53. package/public/js/utils/markdown.js +1 -1
  54. package/public/js/utils/suggestion-ui.js +1 -1
  55. package/public/js/utils/tier-icons.js +1 -1
  56. package/public/js/utils/time.js +1 -1
  57. package/public/js/ws-client.js +1 -1
  58. package/public/local.html +14 -0
  59. package/public/pr.html +3 -0
  60. package/public/setup.html +1 -1
  61. package/src/ai/analyzer.js +18 -12
  62. package/src/ai/claude-cli.js +1 -1
  63. package/src/ai/claude-provider.js +1 -1
  64. package/src/ai/codex-provider.js +1 -1
  65. package/src/ai/copilot-provider.js +1 -1
  66. package/src/ai/cursor-agent-provider.js +1 -1
  67. package/src/ai/gemini-provider.js +1 -1
  68. package/src/ai/index.js +1 -1
  69. package/src/ai/opencode-provider.js +1 -1
  70. package/src/ai/pi-provider.js +1 -1
  71. package/src/ai/prompts/baseline/consolidation/balanced.js +1 -1
  72. package/src/ai/prompts/baseline/consolidation/fast.js +1 -1
  73. package/src/ai/prompts/baseline/consolidation/thorough.js +1 -1
  74. package/src/ai/prompts/baseline/level1/balanced.js +1 -1
  75. package/src/ai/prompts/baseline/level1/fast.js +1 -1
  76. package/src/ai/prompts/baseline/level1/thorough.js +1 -1
  77. package/src/ai/prompts/baseline/level2/balanced.js +1 -1
  78. package/src/ai/prompts/baseline/level2/fast.js +1 -1
  79. package/src/ai/prompts/baseline/level2/thorough.js +1 -1
  80. package/src/ai/prompts/baseline/level3/balanced.js +1 -1
  81. package/src/ai/prompts/baseline/level3/fast.js +1 -1
  82. package/src/ai/prompts/baseline/level3/thorough.js +1 -1
  83. package/src/ai/prompts/baseline/orchestration/balanced.js +1 -1
  84. package/src/ai/prompts/baseline/orchestration/fast.js +1 -1
  85. package/src/ai/prompts/baseline/orchestration/thorough.js +1 -1
  86. package/src/ai/prompts/config.js +1 -1
  87. package/src/ai/prompts/index.js +1 -1
  88. package/src/ai/prompts/line-number-guidance.js +1 -1
  89. package/src/ai/prompts/render-for-skill.js +1 -1
  90. package/src/ai/prompts/shared/diff-instructions.js +1 -1
  91. package/src/ai/prompts/shared/output-schema.js +1 -1
  92. package/src/ai/prompts/shared/valid-files.js +1 -1
  93. package/src/ai/prompts/sparse-checkout-guidance.js +1 -1
  94. package/src/ai/provider-availability.js +1 -1
  95. package/src/ai/provider.js +1 -1
  96. package/src/ai/stream-parser.js +1 -1
  97. package/src/chat/acp-bridge.js +1 -1
  98. package/src/chat/api-reference.js +1 -1
  99. package/src/chat/chat-providers.js +1 -1
  100. package/src/chat/claude-code-bridge.js +1 -1
  101. package/src/chat/codex-bridge.js +1 -1
  102. package/src/chat/pi-bridge.js +1 -1
  103. package/src/chat/prompt-builder.js +1 -1
  104. package/src/chat/session-manager.js +1 -1
  105. package/src/config.js +3 -1
  106. package/src/database.js +591 -40
  107. package/src/events/review-events.js +1 -1
  108. package/src/git/base-branch.js +173 -0
  109. package/src/git/gitattributes.js +1 -1
  110. package/src/git/sha-abbrev.js +35 -0
  111. package/src/git/worktree.js +1 -1
  112. package/src/github/client.js +33 -2
  113. package/src/github/parser.js +1 -1
  114. package/src/hooks/hook-runner.js +100 -0
  115. package/src/hooks/payloads.js +212 -0
  116. package/src/local-review.js +469 -130
  117. package/src/local-scope.js +58 -0
  118. package/src/main.js +56 -5
  119. package/src/mcp-stdio.js +1 -1
  120. package/src/protocol-handler.js +1 -1
  121. package/src/routes/analyses.js +74 -11
  122. package/src/routes/chat.js +34 -1
  123. package/src/routes/config.js +2 -1
  124. package/src/routes/context-files.js +1 -1
  125. package/src/routes/councils.js +1 -1
  126. package/src/routes/github-collections.js +1 -1
  127. package/src/routes/local.js +735 -69
  128. package/src/routes/mcp.js +21 -11
  129. package/src/routes/pr.js +91 -13
  130. package/src/routes/reviews.js +1 -1
  131. package/src/routes/setup.js +2 -1
  132. package/src/routes/shared.js +1 -1
  133. package/src/routes/worktrees.js +213 -149
  134. package/src/server.js +31 -1
  135. package/src/setup/local-setup.js +47 -6
  136. package/src/setup/pr-setup.js +29 -6
  137. package/src/utils/auto-context.js +1 -1
  138. package/src/utils/category-emoji.js +1 -1
  139. package/src/utils/comment-formatter.js +1 -1
  140. package/src/utils/diff-annotator.js +1 -1
  141. package/src/utils/diff-file-list.js +1 -1
  142. package/src/utils/instructions.js +1 -1
  143. package/src/utils/json-extractor.js +1 -1
  144. package/src/utils/line-validation.js +1 -1
  145. package/src/utils/logger.js +1 -1
  146. package/src/utils/paths.js +1 -1
  147. package/src/utils/safe-parse-json.js +1 -1
  148. package/src/utils/stats-calculator.js +1 -1
  149. package/src/ws/index.js +1 -1
  150. package/src/ws/server.js +1 -1
@@ -1,4 +1,4 @@
1
- // SPDX-License-Identifier: GPL-3.0-or-later
1
+ // Copyright 2026 Tim Perkins (tjwp) | SPDX-License-Identifier: Apache-2.0
2
2
  /**
3
3
  * ChatPanel - AI chat sidebar component
4
4
  * Provides a sliding chat panel for conversing with AI about the current review.
@@ -10,6 +10,13 @@ const DISMISS_ICON = `<svg viewBox="0 0 16 16" fill="currentColor" width="12" he
10
10
  /** Pixel threshold for considering the user "near the bottom" of the messages container. */
11
11
  const NEAR_BOTTOM_THRESHOLD = 80;
12
12
 
13
+ const LOOP_SPINNER_HTML = `<span class="chat-panel__loop-spinner"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" width="20" height="20"><path transform="rotate(-50 12 12)" d="M18.178 8c5.096 0 5.096 8 0 8-5.095 0-7.133-8-12.356-8-5.096 0-5.096 8 0 8 5.223 0 7.26-8 12.356-8z"/></svg></span>`;
14
+ const DOTS_SPINNER_HTML = '<span class="chat-panel__typing-indicator"><span></span><span></span><span></span></span>';
15
+
16
+ function getChatSpinnerHTML() {
17
+ return window.__pairReview?.chatSpinner === 'loop' ? LOOP_SPINNER_HTML : DOTS_SPINNER_HTML;
18
+ }
19
+
13
20
  class ChatPanel {
14
21
  constructor(containerId) {
15
22
  this.containerId = containerId;
@@ -24,6 +31,7 @@ class ChatPanel {
24
31
  this._streamingContent = '';
25
32
  this._pendingContext = [];
26
33
  this._pendingContextData = [];
34
+ this._pendingDiffStateNotifications = [];
27
35
  this._contextSource = null; // 'suggestion' or 'user' — set when opened with context
28
36
  this._contextItemId = null; // suggestion ID or comment ID from context
29
37
  this._contextLineMeta = null; // { file, line_start, line_end } — set when opened with line context
@@ -641,6 +649,7 @@ class ChatPanel {
641
649
  this._streamingContent = '';
642
650
  this._pendingContext = [];
643
651
  this._pendingContextData = [];
652
+ this._pendingDiffStateNotifications = [];
644
653
  this._contextSource = null;
645
654
  this._contextItemId = null;
646
655
  this._contextLineMeta = null;
@@ -1003,6 +1012,7 @@ class ChatPanel {
1003
1012
  this._streamingContent = '';
1004
1013
  this._pendingContext = [];
1005
1014
  this._pendingContextData = [];
1015
+ this._pendingDiffStateNotifications = [];
1006
1016
  this._contextSource = null;
1007
1017
  this._contextItemId = null;
1008
1018
  this._contextLineMeta = null;
@@ -1233,10 +1243,22 @@ class ChatPanel {
1233
1243
 
1234
1244
  // Build the API payload — may include pending context from "Ask about this"
1235
1245
  const payload = { content };
1246
+
1247
+ // Snapshot diff-state queue for error recovery (invisible to user, no UI cards)
1248
+ const savedDiffState = this._pendingDiffStateNotifications.slice();
1249
+ let diffStatePrefix = '';
1250
+ if (this._pendingDiffStateNotifications.length > 0) {
1251
+ diffStatePrefix = '[Diff State Update]\n' + this._pendingDiffStateNotifications.join('\n');
1252
+ this._pendingDiffStateNotifications = [];
1253
+ }
1254
+
1236
1255
  const savedContext = this._pendingContext;
1237
1256
  const savedContextData = this._pendingContextData;
1238
1257
  if (this._pendingContext.length > 0) {
1239
- payload.context = this._pendingContext.join('\n\n');
1258
+ const userContext = this._pendingContext.join('\n\n');
1259
+ payload.context = diffStatePrefix
1260
+ ? diffStatePrefix + '\n\n' + userContext
1261
+ : userContext;
1240
1262
  payload.contextData = this._pendingContextData;
1241
1263
  this._pendingContext = [];
1242
1264
  this._pendingContextData = [];
@@ -1248,6 +1270,8 @@ class ChatPanel {
1248
1270
  if (btn) btn.remove();
1249
1271
  delete card.dataset.contextIndex;
1250
1272
  });
1273
+ } else if (diffStatePrefix) {
1274
+ payload.context = diffStatePrefix;
1251
1275
  }
1252
1276
 
1253
1277
  // Lock analysis context card (not indexed, handled separately from pending context)
@@ -1311,6 +1335,7 @@ class ChatPanel {
1311
1335
  // Restore pending context so it's not lost
1312
1336
  this._pendingContext = savedContext;
1313
1337
  this._pendingContextData = savedContextData;
1338
+ this._pendingDiffStateNotifications = [...savedDiffState, ...this._pendingDiffStateNotifications];
1314
1339
  // Restore removability on context cards that were locked before the failed send
1315
1340
  this._restoreRemovableCards();
1316
1341
  console.error('[ChatPanel] Error sending message:', error);
@@ -1319,6 +1344,16 @@ class ChatPanel {
1319
1344
  }
1320
1345
  }
1321
1346
 
1347
+ /**
1348
+ * Queue an invisible diff-state notification for the chat agent.
1349
+ * Unlike _pendingContext, these do NOT render UI cards and survive panel close.
1350
+ * Drained into the context parameter on the next sendMessage() call.
1351
+ * @param {string} message - Description of the diff state change
1352
+ */
1353
+ queueDiffStateNotification(message) {
1354
+ this._pendingDiffStateNotifications.push(message);
1355
+ }
1356
+
1322
1357
  /**
1323
1358
  * Store pending context and render a compact context card in the UI.
1324
1359
  * Called when the user clicks "Ask about this" on a suggestion.
@@ -2409,7 +2444,7 @@ class ChatPanel {
2409
2444
 
2410
2445
  const bubble = document.createElement('div');
2411
2446
  bubble.className = 'chat-panel__bubble';
2412
- bubble.innerHTML = '<span class="chat-panel__typing-indicator"><span></span><span></span><span></span></span>';
2447
+ bubble.innerHTML = getChatSpinnerHTML();
2413
2448
 
2414
2449
  msgEl.appendChild(bubble);
2415
2450
  this.messagesEl.appendChild(msgEl);
@@ -2641,10 +2676,10 @@ class ChatPanel {
2641
2676
  // Don't add duplicate
2642
2677
  if (streamingMsg.querySelector('.chat-panel__thinking')) return;
2643
2678
 
2644
- // Don't add if the bubble still has its initial typing indicator (no content yet).
2645
- // The bubble's own dots are sufficient — adding a second set would show two pulsing indicators.
2679
+ // Don't add if the bubble still has its initial spinner (no content yet).
2680
+ // The bubble's own indicator is sufficient — adding a second would show two.
2646
2681
  const bubble = streamingMsg.querySelector('.chat-panel__bubble');
2647
- if (bubble && bubble.querySelector('.chat-panel__typing-indicator')) return;
2682
+ if (bubble && (bubble.querySelector('.chat-panel__typing-indicator') || bubble.querySelector('.chat-panel__loop-spinner'))) return;
2648
2683
 
2649
2684
  // Remove the cursor — the thinking indicator replaces it as the "working" signal.
2650
2685
  // When new text arrives, updateStreamingMessage() will re-add the cursor naturally.
@@ -2653,7 +2688,7 @@ class ChatPanel {
2653
2688
 
2654
2689
  const indicator = document.createElement('div');
2655
2690
  indicator.className = 'chat-panel__thinking';
2656
- indicator.innerHTML = '<span class="chat-panel__typing-indicator"><span></span><span></span><span></span></span>';
2691
+ indicator.innerHTML = getChatSpinnerHTML();
2657
2692
  streamingMsg.appendChild(indicator);
2658
2693
  this.scrollToBottom();
2659
2694
  }
@@ -1,4 +1,4 @@
1
- // SPDX-License-Identifier: GPL-3.0-or-later
1
+ // Copyright 2026 Tim Perkins (tjwp) | SPDX-License-Identifier: Apache-2.0
2
2
  /**
3
3
  * Generic Confirmation Dialog Component
4
4
  * Displays a confirmation dialog with customizable message and actions
@@ -133,10 +133,20 @@ class ConfirmDialog {
133
133
  messageElement.textContent = options.message || 'Are you sure?';
134
134
  }
135
135
 
136
+ // Helper: set button label + optional description subtitle
137
+ const setBtnContent = (btn, label, description) => {
138
+ if (!btn) return;
139
+ if (description) {
140
+ btn.innerHTML = `<span class="btn-label">${label}</span><span class="btn-desc">${description}</span>`;
141
+ } else {
142
+ btn.textContent = label;
143
+ }
144
+ };
145
+
136
146
  // Set confirm button text and style
137
147
  const confirmBtn = this.modal.querySelector('#confirm-dialog-btn');
138
148
  if (confirmBtn) {
139
- confirmBtn.textContent = options.confirmText || 'Confirm';
149
+ setBtnContent(confirmBtn, options.confirmText || 'Confirm', options.confirmDesc);
140
150
  // Remove previous style classes and add new one
141
151
  confirmBtn.classList.remove('btn-primary', 'btn-secondary', 'btn-danger', 'btn-warning');
142
152
  const confirmClass = options.confirmClass || 'btn-danger';
@@ -145,19 +155,28 @@ class ConfirmDialog {
145
155
 
146
156
  // Set secondary button (optional 3rd button)
147
157
  const secondaryBtn = this.modal.querySelector('#confirm-dialog-secondary-btn');
158
+ const container = this.modal.querySelector('.confirm-dialog-container');
148
159
  if (secondaryBtn) {
149
160
  if (options.secondaryText) {
150
- secondaryBtn.textContent = options.secondaryText;
161
+ setBtnContent(secondaryBtn, options.secondaryText, options.secondaryDesc);
151
162
  secondaryBtn.style.display = '';
152
163
  // Remove previous style classes and add new one
153
164
  secondaryBtn.classList.remove('btn-primary', 'btn-secondary', 'btn-danger', 'btn-warning');
154
165
  const secondaryClass = options.secondaryClass || 'btn-secondary';
155
166
  secondaryBtn.classList.add(secondaryClass);
167
+ if (container) container.classList.add('has-secondary');
156
168
  } else {
157
169
  secondaryBtn.style.display = 'none';
170
+ if (container) container.classList.remove('has-secondary');
158
171
  }
159
172
  }
160
173
 
174
+ // Set cancel button text (optional)
175
+ const cancelBtn = this.modal.querySelector('.modal-footer [data-action="cancel"]');
176
+ if (cancelBtn) {
177
+ setBtnContent(cancelBtn, options.cancelText || 'Cancel', options.cancelDesc);
178
+ }
179
+
161
180
  // Store callbacks with promise resolution
162
181
  this.onConfirm = () => {
163
182
  if (options.onConfirm) {
@@ -1,4 +1,4 @@
1
- // SPDX-License-Identifier: GPL-3.0-or-later
1
+ // Copyright 2026 Tim Perkins (tjwp) | SPDX-License-Identifier: Apache-2.0
2
2
  /**
3
3
  * Council AI Analysis Progress Modal Component
4
4
  *
@@ -806,6 +806,11 @@ class CouncilProgressModal {
806
806
  window.prManager.setButtonComplete();
807
807
  }
808
808
 
809
+ // Flash tab title
810
+ if (window.tabTitle) {
811
+ window.tabTitle.flashComplete();
812
+ }
813
+
809
814
  // Reload suggestions
810
815
  const manager = window.prManager || window.localManager;
811
816
  if (manager && typeof manager.loadAISuggestions === 'function') {
@@ -884,6 +889,11 @@ class CouncilProgressModal {
884
889
  if (window.prManager) {
885
890
  window.prManager.resetButton();
886
891
  }
892
+
893
+ // Flash tab title
894
+ if (window.tabTitle) {
895
+ window.tabTitle.flashFailed();
896
+ }
887
897
  }
888
898
 
889
899
  _handleCancellation(_status) {
@@ -924,6 +934,9 @@ class CouncilProgressModal {
924
934
  if (window.prManager) {
925
935
  window.prManager.resetButton();
926
936
  }
937
+
938
+ // No tab-title flash for cancellation — the user initiated it, so no notification needed.
939
+
927
940
  if (window.aiPanel?.setAnalysisState) {
928
941
  window.aiPanel.setAnalysisState('unknown');
929
942
  }