@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.
- package/.pi/extensions/task/index.ts +1 -1
- package/.pi/skills/review-roulette/SKILL.md +1 -1
- package/LICENSE +201 -674
- package/README.md +2 -2
- package/bin/pair-review.js +1 -1
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin-code-critic/.claude-plugin/plugin.json +2 -2
- package/plugin-code-critic/skills/analyze/scripts/git-diff-lines +1 -1
- package/public/css/ai-summary-modal.css +1 -1
- package/public/css/pr.css +194 -0
- package/public/index.html +168 -3
- package/public/js/components/AIPanel.js +17 -3
- package/public/js/components/AISummaryModal.js +1 -1
- package/public/js/components/AdvancedConfigTab.js +1 -1
- package/public/js/components/AnalysisConfigModal.js +1 -1
- package/public/js/components/ChatPanel.js +42 -7
- package/public/js/components/ConfirmDialog.js +22 -3
- package/public/js/components/CouncilProgressModal.js +14 -1
- package/public/js/components/DiffOptionsDropdown.js +411 -24
- package/public/js/components/EmojiPicker.js +1 -1
- package/public/js/components/KeyboardShortcuts.js +1 -1
- package/public/js/components/PanelGroup.js +1 -1
- package/public/js/components/PreviewModal.js +1 -1
- package/public/js/components/ReviewModal.js +1 -1
- package/public/js/components/SplitButton.js +1 -1
- package/public/js/components/StatusIndicator.js +1 -1
- package/public/js/components/SuggestionNavigator.js +13 -6
- package/public/js/components/TabTitle.js +96 -0
- package/public/js/components/TextInputDialog.js +1 -1
- package/public/js/components/TimeoutSelect.js +1 -1
- package/public/js/components/Toast.js +7 -1
- package/public/js/components/VoiceCentricConfigTab.js +1 -1
- package/public/js/index.js +649 -44
- package/public/js/local.js +570 -77
- package/public/js/modules/analysis-history.js +4 -3
- package/public/js/modules/comment-manager.js +6 -1
- package/public/js/modules/comment-minimizer.js +304 -0
- package/public/js/modules/diff-context.js +1 -1
- package/public/js/modules/diff-renderer.js +1 -1
- package/public/js/modules/file-comment-manager.js +1 -1
- package/public/js/modules/file-list-merger.js +1 -1
- package/public/js/modules/gap-coordinates.js +1 -1
- package/public/js/modules/hunk-parser.js +1 -1
- package/public/js/modules/line-tracker.js +1 -1
- package/public/js/modules/panel-resizer.js +1 -1
- package/public/js/modules/storage-cleanup.js +1 -1
- package/public/js/modules/suggestion-manager.js +1 -1
- package/public/js/pr.js +83 -7
- package/public/js/repo-settings.js +1 -1
- package/public/js/utils/category-emoji.js +1 -1
- package/public/js/utils/file-order.js +1 -1
- package/public/js/utils/markdown.js +1 -1
- package/public/js/utils/suggestion-ui.js +1 -1
- package/public/js/utils/tier-icons.js +1 -1
- package/public/js/utils/time.js +1 -1
- package/public/js/ws-client.js +1 -1
- package/public/local.html +14 -0
- package/public/pr.html +3 -0
- package/public/setup.html +1 -1
- package/src/ai/analyzer.js +18 -12
- package/src/ai/claude-cli.js +1 -1
- package/src/ai/claude-provider.js +1 -1
- package/src/ai/codex-provider.js +1 -1
- package/src/ai/copilot-provider.js +1 -1
- package/src/ai/cursor-agent-provider.js +1 -1
- package/src/ai/gemini-provider.js +1 -1
- package/src/ai/index.js +1 -1
- package/src/ai/opencode-provider.js +1 -1
- package/src/ai/pi-provider.js +1 -1
- package/src/ai/prompts/baseline/consolidation/balanced.js +1 -1
- package/src/ai/prompts/baseline/consolidation/fast.js +1 -1
- package/src/ai/prompts/baseline/consolidation/thorough.js +1 -1
- package/src/ai/prompts/baseline/level1/balanced.js +1 -1
- package/src/ai/prompts/baseline/level1/fast.js +1 -1
- package/src/ai/prompts/baseline/level1/thorough.js +1 -1
- package/src/ai/prompts/baseline/level2/balanced.js +1 -1
- package/src/ai/prompts/baseline/level2/fast.js +1 -1
- package/src/ai/prompts/baseline/level2/thorough.js +1 -1
- package/src/ai/prompts/baseline/level3/balanced.js +1 -1
- package/src/ai/prompts/baseline/level3/fast.js +1 -1
- package/src/ai/prompts/baseline/level3/thorough.js +1 -1
- package/src/ai/prompts/baseline/orchestration/balanced.js +1 -1
- package/src/ai/prompts/baseline/orchestration/fast.js +1 -1
- package/src/ai/prompts/baseline/orchestration/thorough.js +1 -1
- package/src/ai/prompts/config.js +1 -1
- package/src/ai/prompts/index.js +1 -1
- package/src/ai/prompts/line-number-guidance.js +1 -1
- package/src/ai/prompts/render-for-skill.js +1 -1
- package/src/ai/prompts/shared/diff-instructions.js +1 -1
- package/src/ai/prompts/shared/output-schema.js +1 -1
- package/src/ai/prompts/shared/valid-files.js +1 -1
- package/src/ai/prompts/sparse-checkout-guidance.js +1 -1
- package/src/ai/provider-availability.js +1 -1
- package/src/ai/provider.js +1 -1
- package/src/ai/stream-parser.js +1 -1
- package/src/chat/acp-bridge.js +1 -1
- package/src/chat/api-reference.js +1 -1
- package/src/chat/chat-providers.js +1 -1
- package/src/chat/claude-code-bridge.js +1 -1
- package/src/chat/codex-bridge.js +1 -1
- package/src/chat/pi-bridge.js +1 -1
- package/src/chat/prompt-builder.js +1 -1
- package/src/chat/session-manager.js +1 -1
- package/src/config.js +3 -1
- package/src/database.js +591 -40
- package/src/events/review-events.js +1 -1
- package/src/git/base-branch.js +173 -0
- package/src/git/gitattributes.js +1 -1
- package/src/git/sha-abbrev.js +35 -0
- package/src/git/worktree.js +1 -1
- package/src/github/client.js +33 -2
- package/src/github/parser.js +1 -1
- package/src/hooks/hook-runner.js +100 -0
- package/src/hooks/payloads.js +212 -0
- package/src/local-review.js +469 -130
- package/src/local-scope.js +58 -0
- package/src/main.js +56 -5
- package/src/mcp-stdio.js +1 -1
- package/src/protocol-handler.js +1 -1
- package/src/routes/analyses.js +74 -11
- package/src/routes/chat.js +34 -1
- package/src/routes/config.js +2 -1
- package/src/routes/context-files.js +1 -1
- package/src/routes/councils.js +1 -1
- package/src/routes/github-collections.js +1 -1
- package/src/routes/local.js +735 -69
- package/src/routes/mcp.js +21 -11
- package/src/routes/pr.js +91 -13
- package/src/routes/reviews.js +1 -1
- package/src/routes/setup.js +2 -1
- package/src/routes/shared.js +1 -1
- package/src/routes/worktrees.js +213 -149
- package/src/server.js +31 -1
- package/src/setup/local-setup.js +47 -6
- package/src/setup/pr-setup.js +29 -6
- package/src/utils/auto-context.js +1 -1
- package/src/utils/category-emoji.js +1 -1
- package/src/utils/comment-formatter.js +1 -1
- package/src/utils/diff-annotator.js +1 -1
- package/src/utils/diff-file-list.js +1 -1
- package/src/utils/instructions.js +1 -1
- package/src/utils/json-extractor.js +1 -1
- package/src/utils/line-validation.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/paths.js +1 -1
- package/src/utils/safe-parse-json.js +1 -1
- package/src/utils/stats-calculator.js +1 -1
- package/src/ws/index.js +1 -1
- package/src/ws/server.js +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SPDX-License-Identifier:
|
|
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
|
-
|
|
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 =
|
|
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
|
|
2645
|
-
// The bubble's own
|
|
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 =
|
|
2691
|
+
indicator.innerHTML = getChatSpinnerHTML();
|
|
2657
2692
|
streamingMsg.appendChild(indicator);
|
|
2658
2693
|
this.scrollToBottom();
|
|
2659
2694
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SPDX-License-Identifier:
|
|
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
|
|
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.
|
|
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:
|
|
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
|
}
|