@in-the-loop-labs/pair-review 3.1.0 → 3.1.2
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/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/public/css/analysis-config.css +20 -0
- package/public/js/components/AdvancedConfigTab.js +4 -1
- package/public/js/components/AnalysisConfigModal.js +52 -1
- package/public/js/components/CouncilProgressModal.js +2 -1
- package/public/js/components/VoiceCentricConfigTab.js +4 -1
- package/public/js/local.js +25 -4
- package/src/ai/executable-provider.js +5 -1
- package/src/ai/provider.js +3 -1
- package/src/chat/prompt-builder.js +22 -3
- package/src/github/parser.js +10 -6
- package/src/local-scope.js +83 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "pair-review app integration — Open PRs and local changes in the pair-review web UI, run server-side AI analysis, and address review feedback. Requires the pair-review MCP server.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "in-the-loop-labs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-critic",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "AI-powered code review analysis — Run three-level AI analysis and implement-review-fix loops directly in your coding agent. Works standalone, no server required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "in-the-loop-labs",
|
|
@@ -81,3 +81,23 @@
|
|
|
81
81
|
.option-hint:last-child {
|
|
82
82
|
margin-bottom: 0;
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
/* Section disabled by provider capability —
|
|
86
|
+
pointer-events on the section blocks interaction; opacity is applied
|
|
87
|
+
only to the options area so the note remains clearly readable and
|
|
88
|
+
the already-disabled GitHub toggle doesn't double-dim. */
|
|
89
|
+
.exclude-previous-section.exclude-previous-disabled {
|
|
90
|
+
pointer-events: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.exclude-previous-section.exclude-previous-disabled > summary {
|
|
94
|
+
opacity: 0.5;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.exclude-previous-section.exclude-previous-disabled .exclude-previous-options {
|
|
98
|
+
opacity: 0.5;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.exclude-previous-section .executable-provider-note {
|
|
102
|
+
margin: 8px 14px;
|
|
103
|
+
}
|
|
@@ -705,10 +705,13 @@ class AdvancedConfigTab {
|
|
|
705
705
|
|
|
706
706
|
_populateProviderDropdown(select) {
|
|
707
707
|
const currentValue = select.value;
|
|
708
|
+
const isConsolidation = select.dataset.target === 'orchestration';
|
|
708
709
|
select.innerHTML = '';
|
|
709
710
|
const providerIds = Object.keys(this.providers).filter(id => {
|
|
710
711
|
const p = this.providers[id];
|
|
711
|
-
|
|
712
|
+
if (p.availability && !p.availability.available) return false;
|
|
713
|
+
if (isConsolidation && p.capabilities?.consolidation === false) return false;
|
|
714
|
+
return true;
|
|
712
715
|
}).sort((a, b) => (this.providers[a].name || a).localeCompare(this.providers[b].name || b));
|
|
713
716
|
|
|
714
717
|
for (const id of providerIds) {
|
|
@@ -616,6 +616,9 @@ class AnalysisConfigModal {
|
|
|
616
616
|
if (this.selectedModel) {
|
|
617
617
|
this.selectModel(this.selectedModel);
|
|
618
618
|
}
|
|
619
|
+
|
|
620
|
+
// Update exclude-previous section based on provider's exclude_previous capability
|
|
621
|
+
this._updateExcludePreviousState();
|
|
619
622
|
}
|
|
620
623
|
|
|
621
624
|
/**
|
|
@@ -827,7 +830,9 @@ class AnalysisConfigModal {
|
|
|
827
830
|
enabledLevels: [...this.enabledLevels],
|
|
828
831
|
skipLevel3: !this.enabledLevels.includes(3),
|
|
829
832
|
noLevels,
|
|
830
|
-
excludePrevious:
|
|
833
|
+
excludePrevious: selectedProvider?.capabilities?.exclude_previous === false
|
|
834
|
+
? undefined
|
|
835
|
+
: this._getAndSaveExcludePrevious()
|
|
831
836
|
};
|
|
832
837
|
|
|
833
838
|
if (this.onSubmit) this.onSubmit(config);
|
|
@@ -1151,6 +1156,9 @@ class AnalysisConfigModal {
|
|
|
1151
1156
|
dirtyHintContainer.style.display = 'none';
|
|
1152
1157
|
}
|
|
1153
1158
|
}
|
|
1159
|
+
|
|
1160
|
+
// Re-evaluate exclude-previous section based on new tab context
|
|
1161
|
+
this._updateExcludePreviousState();
|
|
1154
1162
|
}
|
|
1155
1163
|
|
|
1156
1164
|
/**
|
|
@@ -1362,6 +1370,49 @@ class AnalysisConfigModal {
|
|
|
1362
1370
|
return state;
|
|
1363
1371
|
}
|
|
1364
1372
|
|
|
1373
|
+
/**
|
|
1374
|
+
* Update the exclude-previous section's enabled/disabled state based on
|
|
1375
|
+
* the active tab and the selected provider's capabilities.
|
|
1376
|
+
*
|
|
1377
|
+
* Council/Advanced tabs: always enabled (orchestration handles dedup).
|
|
1378
|
+
* Single Model tab: disabled when provider has exclude_previous === false.
|
|
1379
|
+
*
|
|
1380
|
+
* This only toggles the visual state of the entire section — individual
|
|
1381
|
+
* checkbox checked/disabled state is never modified, so persisted
|
|
1382
|
+
* localStorage values remain untouched.
|
|
1383
|
+
* @private
|
|
1384
|
+
*/
|
|
1385
|
+
_updateExcludePreviousState() {
|
|
1386
|
+
const section = this.modal.querySelector('.exclude-previous-section');
|
|
1387
|
+
if (!section) return;
|
|
1388
|
+
|
|
1389
|
+
const provider = this.providers[this.selectedProvider];
|
|
1390
|
+
const disabledByCapability = this.activeTab === 'single'
|
|
1391
|
+
&& provider?.capabilities?.exclude_previous === false;
|
|
1392
|
+
|
|
1393
|
+
const existingNote = section.querySelector('.executable-provider-exclude-note');
|
|
1394
|
+
|
|
1395
|
+
if (disabledByCapability) {
|
|
1396
|
+
section.classList.add('exclude-previous-disabled');
|
|
1397
|
+
section.setAttribute('open', ''); // Ensure note is visible even if section was collapsed
|
|
1398
|
+
|
|
1399
|
+
if (!existingNote) {
|
|
1400
|
+
const note = document.createElement('div');
|
|
1401
|
+
note.className = 'executable-provider-note executable-provider-exclude-note';
|
|
1402
|
+
note.textContent = 'This provider does not support excluding previous findings.';
|
|
1403
|
+
const options = section.querySelector('.exclude-previous-options');
|
|
1404
|
+
if (options) {
|
|
1405
|
+
section.insertBefore(note, options);
|
|
1406
|
+
} else {
|
|
1407
|
+
section.appendChild(note);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
} else {
|
|
1411
|
+
section.classList.remove('exclude-previous-disabled');
|
|
1412
|
+
if (existingNote) existingNote.remove();
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1365
1416
|
/**
|
|
1366
1417
|
* Cleanup event listeners and pending timeouts
|
|
1367
1418
|
*/
|
|
@@ -1621,7 +1621,8 @@ class CouncilProgressModal {
|
|
|
1621
1621
|
* -> "Claude sonnet-4-5 (Balanced)"
|
|
1622
1622
|
*/
|
|
1623
1623
|
_formatVoiceLabel(voice, { isExecutable = false } = {}) {
|
|
1624
|
-
const
|
|
1624
|
+
const providersMap = window.analysisConfigModal?.providers || {};
|
|
1625
|
+
const provider = providersMap[voice.provider]?.name || this._capitalize(voice.provider || 'unknown');
|
|
1625
1626
|
const model = voice.model || 'default';
|
|
1626
1627
|
if (isExecutable) return `${provider} ${model}`;
|
|
1627
1628
|
const tier = this._capitalize(voice.tier || 'balanced');
|
|
@@ -770,10 +770,13 @@ class VoiceCentricConfigTab {
|
|
|
770
770
|
|
|
771
771
|
_populateProviderDropdown(select) {
|
|
772
772
|
const currentValue = select.value;
|
|
773
|
+
const isConsolidation = select.dataset.target === 'orchestration';
|
|
773
774
|
select.innerHTML = '';
|
|
774
775
|
const providerIds = Object.keys(this.providers).filter(id => {
|
|
775
776
|
const p = this.providers[id];
|
|
776
|
-
|
|
777
|
+
if (p.availability && !p.availability.available) return false;
|
|
778
|
+
if (isConsolidation && p.capabilities?.consolidation === false) return false;
|
|
779
|
+
return true;
|
|
777
780
|
}).sort((a, b) => (this.providers[a].name || a).localeCompare(this.providers[b].name || b));
|
|
778
781
|
|
|
779
782
|
for (const id of providerIds) {
|
package/public/js/local.js
CHANGED
|
@@ -1526,6 +1526,23 @@ class LocalManager {
|
|
|
1526
1526
|
}
|
|
1527
1527
|
}
|
|
1528
1528
|
|
|
1529
|
+
/**
|
|
1530
|
+
* Build a notification string describing a scope change for the chat agent.
|
|
1531
|
+
* @param {string} prefix - Leading message (e.g. "Diff scope changed to X.")
|
|
1532
|
+
* @param {{ description: string, diffCommand: string, excludes: string, includesUntracked: boolean }|null} hints - Scope git hints
|
|
1533
|
+
* @returns {string} Formatted notification text
|
|
1534
|
+
*/
|
|
1535
|
+
_buildScopeNotification(prefix, hints) {
|
|
1536
|
+
const parts = [prefix];
|
|
1537
|
+
if (hints) {
|
|
1538
|
+
parts.push(`Scope: ${hints.description}`);
|
|
1539
|
+
parts.push(`Diff command: \`${hints.diffCommand}\``);
|
|
1540
|
+
if (hints.excludes) parts.push(hints.excludes);
|
|
1541
|
+
if (hints.includesUntracked) parts.push('Untracked files are included. List them with: `git ls-files --others --exclude-standard`');
|
|
1542
|
+
}
|
|
1543
|
+
return parts.join('\n');
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1529
1546
|
/**
|
|
1530
1547
|
* Apply the result of a scope-change POST to local state, UI, and diff.
|
|
1531
1548
|
* Shared by _handleScopeChange and showBranchReviewDialog.handleConfirm.
|
|
@@ -1617,9 +1634,11 @@ class LocalManager {
|
|
|
1617
1634
|
// Notify chat agent about scope change
|
|
1618
1635
|
if (window.chatPanel) {
|
|
1619
1636
|
const label = LS ? LS.scopeLabel(scopeStart, scopeEnd) : `${scopeStart}\u2013${scopeEnd}`;
|
|
1620
|
-
|
|
1621
|
-
|
|
1637
|
+
const hints = LS ? LS.scopeGitHints(scopeStart, scopeEnd, this.localData?.baseBranch) : null;
|
|
1638
|
+
const notification = this._buildScopeNotification(
|
|
1639
|
+
`Diff scope changed to ${label}. The set of reviewed files has changed.`, hints
|
|
1622
1640
|
);
|
|
1641
|
+
window.chatPanel.queueDiffStateNotification(notification);
|
|
1623
1642
|
}
|
|
1624
1643
|
|
|
1625
1644
|
if (window.toast) {
|
|
@@ -1752,9 +1771,11 @@ class LocalManager {
|
|
|
1752
1771
|
|
|
1753
1772
|
if (window.chatPanel) {
|
|
1754
1773
|
const label = LS ? LS.scopeLabel('branch', newEnd) : 'branch';
|
|
1755
|
-
|
|
1756
|
-
|
|
1774
|
+
const hints = LS ? LS.scopeGitHints('branch', newEnd, branchInfo.baseBranch) : null;
|
|
1775
|
+
const notification = self._buildScopeNotification(
|
|
1776
|
+
`Diff scope changed to ${label} via branch review. The set of reviewed files has changed.`, hints
|
|
1757
1777
|
);
|
|
1778
|
+
window.chatPanel.queueDiffStateNotification(notification);
|
|
1758
1779
|
}
|
|
1759
1780
|
|
|
1760
1781
|
if (window.toast) {
|
|
@@ -86,6 +86,8 @@ ${rawOutput}`;
|
|
|
86
86
|
* @param {Object} config.capabilities - Provider capabilities overrides
|
|
87
87
|
* @param {boolean} config.capabilities.review_levels - Whether the tool supports L1/L2/L3 analysis
|
|
88
88
|
* @param {boolean} config.capabilities.custom_instructions - Whether the tool supports custom instructions
|
|
89
|
+
* @param {boolean} config.capabilities.exclude_previous - Whether the tool supports excluding previous findings
|
|
90
|
+
* @param {boolean} config.capabilities.consolidation - Whether the tool can be used for consolidation
|
|
89
91
|
* @param {Object} config.context_args - Maps context keys to CLI flags
|
|
90
92
|
* @param {string} config.output_glob - Glob pattern to find result file
|
|
91
93
|
* @param {string} config.mapping_instructions - Tool-specific mapping instructions for LLM
|
|
@@ -525,7 +527,9 @@ function createExecutableProviderClass(id, config) {
|
|
|
525
527
|
const caps = config.capabilities || {};
|
|
526
528
|
ExecProvider.capabilities = {
|
|
527
529
|
review_levels: caps.review_levels !== undefined ? caps.review_levels : false,
|
|
528
|
-
custom_instructions: caps.custom_instructions !== undefined ? caps.custom_instructions : false
|
|
530
|
+
custom_instructions: caps.custom_instructions !== undefined ? caps.custom_instructions : false,
|
|
531
|
+
exclude_previous: caps.exclude_previous !== undefined ? caps.exclude_previous : false,
|
|
532
|
+
consolidation: caps.consolidation !== undefined ? caps.consolidation : false
|
|
529
533
|
};
|
|
530
534
|
|
|
531
535
|
return ExecProvider;
|
package/src/ai/provider.js
CHANGED
|
@@ -621,7 +621,9 @@ function getAllProvidersInfo() {
|
|
|
621
621
|
// Build capabilities: executable providers define their own, others get defaults
|
|
622
622
|
const capabilities = ProviderClass.capabilities || {
|
|
623
623
|
review_levels: true,
|
|
624
|
-
custom_instructions: true
|
|
624
|
+
custom_instructions: true,
|
|
625
|
+
exclude_previous: true,
|
|
626
|
+
consolidation: true
|
|
625
627
|
};
|
|
626
628
|
|
|
627
629
|
providers.push({
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const logger = require('../utils/logger');
|
|
11
|
+
const { scopeGitHints, DEFAULT_SCOPE } = require('../local-scope');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Build a lean system prompt for chat sessions.
|
|
@@ -140,9 +141,27 @@ function buildReviewContext(review, prData) {
|
|
|
140
141
|
lines.push(`This is a local code review for: ${name}`);
|
|
141
142
|
lines.push('');
|
|
142
143
|
lines.push('## Viewing Code Changes');
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
|
|
145
|
+
const scopeStart = review.local_scope_start || DEFAULT_SCOPE.start;
|
|
146
|
+
const scopeEnd = review.local_scope_end || DEFAULT_SCOPE.end;
|
|
147
|
+
const baseBranch = review.local_base_branch || null;
|
|
148
|
+
const hints = scopeGitHints(scopeStart, scopeEnd, baseBranch);
|
|
149
|
+
|
|
150
|
+
if (hints) {
|
|
151
|
+
lines.push(`The current diff scope is **${hints.label}**: ${hints.description}`);
|
|
152
|
+
lines.push(`To see the diff under review: \`${hints.diffCommand}\``);
|
|
153
|
+
if (hints.includesUntracked) {
|
|
154
|
+
lines.push('Untracked files are included. List them with: `git ls-files --others --exclude-standard`');
|
|
155
|
+
}
|
|
156
|
+
if (hints.excludes) {
|
|
157
|
+
lines.push(hints.excludes);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
lines.push('The changes under review are **unstaged and untracked local changes**. Staged changes (`git diff --no-ext-diff --cached`) are treated as already reviewed.');
|
|
161
|
+
lines.push('To see the diff under review: `git diff --no-ext-diff`');
|
|
162
|
+
lines.push('Do NOT use `git diff HEAD~1` or `git log` — those show committed history, not the changes under review.');
|
|
163
|
+
}
|
|
164
|
+
lines.push('The diff scope can change during this session. If you receive a "[Diff State Update]" notification, follow the updated diff command it provides.');
|
|
146
165
|
} else {
|
|
147
166
|
const parts = [];
|
|
148
167
|
if (review.repository) {
|
package/src/github/parser.js
CHANGED
|
@@ -30,7 +30,7 @@ class PRArgumentParser {
|
|
|
30
30
|
// Check if input is a PR number
|
|
31
31
|
const prNumber = parseInt(input);
|
|
32
32
|
if (isNaN(prNumber) || prNumber <= 0) {
|
|
33
|
-
throw new Error('Invalid input format. Expected: PR number, GitHub URL (https://github.com/owner/repo/pull/number), Graphite URL (https://app.graphite.com/github/pr/owner/repo/number), or pair-review:// URL');
|
|
33
|
+
throw new Error('Invalid input format. Expected: PR number, GitHub URL (https://github.com/owner/repo/pull/number), Graphite URL (https://app.graphite.com/github/pr/owner/repo/number or https://app.graphite.com/github/owner/repo/pull/number), or pair-review:// URL');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// Parse repository from current directory's git remote
|
|
@@ -112,14 +112,18 @@ class PRArgumentParser {
|
|
|
112
112
|
* @returns {Object} Parsed information { owner, repo, number }
|
|
113
113
|
*/
|
|
114
114
|
parseGraphiteURL(url) {
|
|
115
|
-
// Match Graphite PR URL
|
|
116
|
-
|
|
115
|
+
// Match Graphite PR URL patterns:
|
|
116
|
+
// https://app.graphite.{dev|com}/github/pr/owner/repo/number[/optional-title]
|
|
117
|
+
// https://app.graphite.{dev|com}/github/owner/repo/pull/number[/optional-title]
|
|
118
|
+
const match = url.match(/^https:\/\/app\.graphite\.(?:dev|com)\/github\/(?:pr\/([^\/]+)\/([^\/]+)\/(\d+)|([^\/]+)\/([^\/]+)\/pull\/(\d+))(?:\/[^?]*)?(?:\?.*)?$/);
|
|
117
119
|
|
|
118
120
|
if (!match) {
|
|
119
|
-
throw new Error('Invalid Graphite URL format. Expected: https://app.graphite.com/github/pr/owner/repo/number');
|
|
121
|
+
throw new Error('Invalid Graphite URL format. Expected: https://app.graphite.com/github/pr/owner/repo/number or https://app.graphite.com/github/owner/repo/pull/number');
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
const
|
|
124
|
+
const owner = match[1] || match[4];
|
|
125
|
+
const repo = match[2] || match[5];
|
|
126
|
+
const numberStr = match[3] || match[6];
|
|
123
127
|
return this._createPRInfo(owner, repo, numberStr, 'Graphite');
|
|
124
128
|
}
|
|
125
129
|
|
|
@@ -156,7 +160,7 @@ class PRArgumentParser {
|
|
|
156
160
|
? 'https://github.com/owner/repo/pull/number'
|
|
157
161
|
: source === 'pair-review://'
|
|
158
162
|
? 'pair-review://pr/owner/repo/number'
|
|
159
|
-
: 'https://app.graphite.com/github/pr/owner/repo/number';
|
|
163
|
+
: 'https://app.graphite.com/github/pr/owner/repo/number or https://app.graphite.com/github/owner/repo/pull/number';
|
|
160
164
|
throw new Error(`Invalid ${source} URL format. Expected: ${exampleUrl}`);
|
|
161
165
|
}
|
|
162
166
|
|
package/src/local-scope.js
CHANGED
|
@@ -39,6 +39,88 @@ function scopeLabel(start, end) {
|
|
|
39
39
|
return `${label(start)}\u2013${label(end)}`;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Return git command hints for a scope range.
|
|
44
|
+
* @param {string} start - Scope start
|
|
45
|
+
* @param {string} end - Scope end
|
|
46
|
+
* @param {string} [baseBranch] - Base branch name (e.g. 'main'); used in merge-base commands
|
|
47
|
+
* @returns {{ label: string, description: string, diffCommand: string, excludes: string, includesUntracked: boolean }|null}
|
|
48
|
+
*/
|
|
49
|
+
function scopeGitHints(start, end, baseBranch) {
|
|
50
|
+
if (!isValidScope(start, end)) return null;
|
|
51
|
+
|
|
52
|
+
const mb = baseBranch
|
|
53
|
+
? '$(git merge-base ' + baseBranch + ' HEAD)'
|
|
54
|
+
: '<merge-base>';
|
|
55
|
+
const incUntracked = scopeIncludes(start, end, 'untracked');
|
|
56
|
+
const label = scopeLabel(start, end);
|
|
57
|
+
|
|
58
|
+
const key = start + '-' + end;
|
|
59
|
+
const hints = {
|
|
60
|
+
'branch-branch': {
|
|
61
|
+
description: 'Committed changes on this branch since the merge-base.',
|
|
62
|
+
diffCommand: 'git diff --no-ext-diff ' + mb + '..HEAD',
|
|
63
|
+
excludes: 'Staged, unstaged, and untracked changes are NOT included in the review.'
|
|
64
|
+
},
|
|
65
|
+
'branch-staged': {
|
|
66
|
+
description: 'Committed changes plus staged changes, relative to the merge-base.',
|
|
67
|
+
diffCommand: 'git diff --no-ext-diff --cached ' + mb,
|
|
68
|
+
excludes: 'Unstaged and untracked changes are NOT included in the review.'
|
|
69
|
+
},
|
|
70
|
+
'branch-unstaged': {
|
|
71
|
+
description: 'All tracked changes (committed, staged, and unstaged) relative to the merge-base.',
|
|
72
|
+
diffCommand: 'git diff --no-ext-diff ' + mb,
|
|
73
|
+
excludes: 'Untracked files are NOT included in the review.'
|
|
74
|
+
},
|
|
75
|
+
'branch-untracked': {
|
|
76
|
+
description: 'All changes (committed, staged, unstaged, and untracked) relative to the merge-base.',
|
|
77
|
+
diffCommand: 'git diff --no-ext-diff ' + mb,
|
|
78
|
+
excludes: ''
|
|
79
|
+
},
|
|
80
|
+
'staged-staged': {
|
|
81
|
+
description: 'Only staged changes (added to the index but not yet committed).',
|
|
82
|
+
diffCommand: 'git diff --no-ext-diff --cached',
|
|
83
|
+
excludes: 'Unstaged, untracked, and committed branch changes are NOT included in the review.'
|
|
84
|
+
},
|
|
85
|
+
'staged-unstaged': {
|
|
86
|
+
description: 'Staged and unstaged changes relative to HEAD.',
|
|
87
|
+
diffCommand: 'git diff --no-ext-diff HEAD',
|
|
88
|
+
excludes: 'Untracked files are NOT included in the review.'
|
|
89
|
+
},
|
|
90
|
+
'staged-untracked': {
|
|
91
|
+
description: 'Staged, unstaged, and untracked changes relative to HEAD.',
|
|
92
|
+
diffCommand: 'git diff --no-ext-diff HEAD',
|
|
93
|
+
excludes: ''
|
|
94
|
+
},
|
|
95
|
+
'unstaged-unstaged': {
|
|
96
|
+
description: 'Only unstaged working tree changes (not staged, not committed).',
|
|
97
|
+
diffCommand: 'git diff --no-ext-diff',
|
|
98
|
+
excludes: 'Staged changes (`git diff --no-ext-diff --cached`) are treated as already reviewed. Untracked files are NOT included in the review.'
|
|
99
|
+
},
|
|
100
|
+
'unstaged-untracked': {
|
|
101
|
+
description: 'Unstaged and untracked local changes.',
|
|
102
|
+
diffCommand: 'git diff --no-ext-diff',
|
|
103
|
+
excludes: 'Staged changes (`git diff --no-ext-diff --cached`) are treated as already reviewed.'
|
|
104
|
+
},
|
|
105
|
+
'untracked-untracked': {
|
|
106
|
+
description: 'Only untracked files (new files not yet added to git).',
|
|
107
|
+
diffCommand: 'git ls-files --others --exclude-standard',
|
|
108
|
+
excludes: 'Tracked file changes (staged, unstaged, committed) are NOT included in the review.'
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const entry = hints[key];
|
|
113
|
+
if (!entry) return null;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
label: label,
|
|
117
|
+
description: entry.description,
|
|
118
|
+
diffCommand: entry.diffCommand,
|
|
119
|
+
excludes: entry.excludes,
|
|
120
|
+
includesUntracked: incUntracked && key !== 'untracked-untracked'
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
42
124
|
const LocalScope = {
|
|
43
125
|
STOPS,
|
|
44
126
|
DEFAULT_SCOPE,
|
|
@@ -47,6 +129,7 @@ const LocalScope = {
|
|
|
47
129
|
includesBranch,
|
|
48
130
|
fromLegacyMode,
|
|
49
131
|
scopeLabel,
|
|
132
|
+
scopeGitHints,
|
|
50
133
|
};
|
|
51
134
|
|
|
52
135
|
if (typeof window !== 'undefined') {
|