@in-the-loop-labs/pair-review 1.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/LICENSE +674 -0
- package/README.md +371 -0
- package/bin/git-diff-lines +146 -0
- package/bin/pair-review.js +49 -0
- package/package.json +71 -0
- package/public/css/ai-summary-modal.css +183 -0
- package/public/css/pr.css +8698 -0
- package/public/css/repo-settings.css +891 -0
- package/public/css/styles.css +479 -0
- package/public/favicon.png +0 -0
- package/public/index.html +1104 -0
- package/public/js/components/AIPanel.js +1639 -0
- package/public/js/components/AISummaryModal.js +278 -0
- package/public/js/components/AnalysisConfigModal.js +684 -0
- package/public/js/components/ConfirmDialog.js +227 -0
- package/public/js/components/PreviewModal.js +344 -0
- package/public/js/components/ProgressModal.js +678 -0
- package/public/js/components/ReviewModal.js +531 -0
- package/public/js/components/SplitButton.js +382 -0
- package/public/js/components/StatusIndicator.js +265 -0
- package/public/js/components/SuggestionNavigator.js +489 -0
- package/public/js/components/Toast.js +166 -0
- package/public/js/local.js +1580 -0
- package/public/js/modules/analysis-history.js +940 -0
- package/public/js/modules/comment-manager.js +643 -0
- package/public/js/modules/diff-renderer.js +585 -0
- package/public/js/modules/file-comment-manager.js +1242 -0
- package/public/js/modules/gap-coordinates.js +190 -0
- package/public/js/modules/hunk-parser.js +358 -0
- package/public/js/modules/line-tracker.js +386 -0
- package/public/js/modules/panel-resizer.js +228 -0
- package/public/js/modules/storage-cleanup.js +36 -0
- package/public/js/modules/suggestion-manager.js +692 -0
- package/public/js/pr.js +3503 -0
- package/public/js/repo-settings.js +691 -0
- package/public/js/utils/file-order.js +87 -0
- package/public/js/utils/markdown.js +97 -0
- package/public/js/utils/suggestion-ui.js +55 -0
- package/public/js/utils/tier-icons.js +25 -0
- package/public/local.html +460 -0
- package/public/pr.html +329 -0
- package/public/repo-settings.html +243 -0
- package/src/ai/analyzer.js +2592 -0
- package/src/ai/claude-cli.js +153 -0
- package/src/ai/claude-provider.js +261 -0
- package/src/ai/codex-provider.js +361 -0
- package/src/ai/copilot-provider.js +345 -0
- package/src/ai/gemini-provider.js +375 -0
- package/src/ai/index.js +47 -0
- package/src/ai/prompts/baseline/_meta.json +14 -0
- package/src/ai/prompts/baseline/level1/balanced.js +239 -0
- package/src/ai/prompts/baseline/level1/fast.js +194 -0
- package/src/ai/prompts/baseline/level1/thorough.js +319 -0
- package/src/ai/prompts/baseline/level2/balanced.js +248 -0
- package/src/ai/prompts/baseline/level2/fast.js +201 -0
- package/src/ai/prompts/baseline/level2/thorough.js +367 -0
- package/src/ai/prompts/baseline/level3/balanced.js +280 -0
- package/src/ai/prompts/baseline/level3/fast.js +220 -0
- package/src/ai/prompts/baseline/level3/thorough.js +459 -0
- package/src/ai/prompts/baseline/orchestration/balanced.js +259 -0
- package/src/ai/prompts/baseline/orchestration/fast.js +213 -0
- package/src/ai/prompts/baseline/orchestration/thorough.js +446 -0
- package/src/ai/prompts/config.js +52 -0
- package/src/ai/prompts/index.js +267 -0
- package/src/ai/prompts/shared/diff-instructions.js +50 -0
- package/src/ai/prompts/shared/output-schema.js +179 -0
- package/src/ai/prompts/shared/valid-files.js +37 -0
- package/src/ai/provider.js +260 -0
- package/src/config.js +139 -0
- package/src/database.js +2284 -0
- package/src/git/gitattributes.js +207 -0
- package/src/git/worktree.js +688 -0
- package/src/github/client.js +893 -0
- package/src/github/parser.js +247 -0
- package/src/local-review.js +691 -0
- package/src/main.js +987 -0
- package/src/routes/analysis.js +897 -0
- package/src/routes/comments.js +534 -0
- package/src/routes/config.js +250 -0
- package/src/routes/local.js +1728 -0
- package/src/routes/pr.js +1164 -0
- package/src/routes/shared.js +218 -0
- package/src/routes/worktrees.js +500 -0
- package/src/server.js +295 -0
- package/src/utils/diff-annotator.js +414 -0
- package/src/utils/instructions.js +33 -0
- package/src/utils/json-extractor.js +107 -0
- package/src/utils/line-validation.js +183 -0
- package/src/utils/logger.js +142 -0
- package/src/utils/paths.js +161 -0
- package/src/utils/stats-calculator.js +86 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* Configuration Routes
|
|
4
|
+
*
|
|
5
|
+
* Handles all configuration-related endpoints:
|
|
6
|
+
* - User configuration (theme, comment button action)
|
|
7
|
+
* - Repository-specific settings (default instructions, default model, default provider)
|
|
8
|
+
* - Review settings for PRs
|
|
9
|
+
* - AI provider information
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const express = require('express');
|
|
13
|
+
const { RepoSettingsRepository, ReviewRepository } = require('../database');
|
|
14
|
+
const { getAllProvidersInfo, testProviderAvailability } = require('../ai');
|
|
15
|
+
const { normalizeRepository } = require('../utils/paths');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
|
+
|
|
18
|
+
const router = express.Router();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get user configuration (for frontend use)
|
|
22
|
+
* Returns safe-to-expose configuration values
|
|
23
|
+
*/
|
|
24
|
+
router.get('/api/config', (req, res) => {
|
|
25
|
+
const config = req.app.get('config') || {};
|
|
26
|
+
|
|
27
|
+
// Only return safe configuration values (not secrets like github_token)
|
|
28
|
+
res.json({
|
|
29
|
+
theme: config.theme || 'light',
|
|
30
|
+
comment_button_action: config.comment_button_action || 'submit'
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Update user configuration
|
|
36
|
+
* Updates safe configuration values
|
|
37
|
+
*/
|
|
38
|
+
router.patch('/api/config', async (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { comment_button_action } = req.body;
|
|
41
|
+
|
|
42
|
+
// Validate comment_button_action if provided
|
|
43
|
+
if (comment_button_action !== undefined) {
|
|
44
|
+
if (!['submit', 'preview'].includes(comment_button_action)) {
|
|
45
|
+
return res.status(400).json({
|
|
46
|
+
error: 'Invalid comment_button_action. Must be "submit" or "preview"'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get current config
|
|
52
|
+
const config = req.app.get('config') || {};
|
|
53
|
+
|
|
54
|
+
// Update allowed fields
|
|
55
|
+
if (comment_button_action !== undefined) {
|
|
56
|
+
config.comment_button_action = comment_button_action;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Save config to file
|
|
60
|
+
const { saveConfig } = require('../config');
|
|
61
|
+
await saveConfig(config);
|
|
62
|
+
|
|
63
|
+
// Update app config
|
|
64
|
+
req.app.set('config', config);
|
|
65
|
+
|
|
66
|
+
res.json({
|
|
67
|
+
success: true,
|
|
68
|
+
config: {
|
|
69
|
+
theme: config.theme || 'light',
|
|
70
|
+
comment_button_action: config.comment_button_action || 'submit'
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Error updating config:', error);
|
|
76
|
+
res.status(500).json({
|
|
77
|
+
error: 'Failed to update configuration'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get repository-specific settings
|
|
84
|
+
* Returns default_instructions, default_provider, and default_model for the repository
|
|
85
|
+
*/
|
|
86
|
+
router.get('/api/repos/:owner/:repo/settings', async (req, res) => {
|
|
87
|
+
try {
|
|
88
|
+
const { owner, repo } = req.params;
|
|
89
|
+
const repository = normalizeRepository(owner, repo);
|
|
90
|
+
const db = req.app.get('db');
|
|
91
|
+
|
|
92
|
+
const repoSettingsRepo = new RepoSettingsRepository(db);
|
|
93
|
+
const settings = await repoSettingsRepo.getRepoSettings(repository);
|
|
94
|
+
|
|
95
|
+
if (!settings) {
|
|
96
|
+
// Return empty object if no settings exist
|
|
97
|
+
return res.json({
|
|
98
|
+
repository,
|
|
99
|
+
default_instructions: null,
|
|
100
|
+
default_provider: null,
|
|
101
|
+
default_model: null,
|
|
102
|
+
local_path: null
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
res.json({
|
|
107
|
+
repository: settings.repository,
|
|
108
|
+
default_instructions: settings.default_instructions,
|
|
109
|
+
default_provider: settings.default_provider,
|
|
110
|
+
default_model: settings.default_model,
|
|
111
|
+
local_path: settings.local_path,
|
|
112
|
+
created_at: settings.created_at,
|
|
113
|
+
updated_at: settings.updated_at
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Error fetching repo settings:', error);
|
|
118
|
+
res.status(500).json({
|
|
119
|
+
error: 'Failed to fetch repository settings'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Save repository-specific settings
|
|
126
|
+
* Saves default_instructions, default_provider, and/or default_model for the repository
|
|
127
|
+
*/
|
|
128
|
+
router.post('/api/repos/:owner/:repo/settings', async (req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
const { owner, repo } = req.params;
|
|
131
|
+
const { default_instructions, default_provider, default_model, local_path } = req.body;
|
|
132
|
+
const repository = normalizeRepository(owner, repo);
|
|
133
|
+
const db = req.app.get('db');
|
|
134
|
+
|
|
135
|
+
// Validate that at least one setting is provided
|
|
136
|
+
if (default_instructions === undefined && default_provider === undefined && default_model === undefined && local_path === undefined) {
|
|
137
|
+
return res.status(400).json({
|
|
138
|
+
error: 'At least one setting (default_instructions, default_provider, default_model, or local_path) must be provided'
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const repoSettingsRepo = new RepoSettingsRepository(db);
|
|
143
|
+
const settings = await repoSettingsRepo.saveRepoSettings(repository, {
|
|
144
|
+
default_instructions,
|
|
145
|
+
default_provider,
|
|
146
|
+
default_model,
|
|
147
|
+
local_path
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
logger.info(`Saved repo settings for ${repository}`);
|
|
151
|
+
|
|
152
|
+
res.json({
|
|
153
|
+
success: true,
|
|
154
|
+
settings: {
|
|
155
|
+
repository: settings.repository,
|
|
156
|
+
default_instructions: settings.default_instructions,
|
|
157
|
+
default_provider: settings.default_provider,
|
|
158
|
+
default_model: settings.default_model,
|
|
159
|
+
local_path: settings.local_path,
|
|
160
|
+
updated_at: settings.updated_at
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('Error saving repo settings:', error);
|
|
166
|
+
res.status(500).json({
|
|
167
|
+
error: 'Failed to save repository settings'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get review settings for a PR
|
|
174
|
+
* Returns the custom_instructions from the most recent review
|
|
175
|
+
*/
|
|
176
|
+
router.get('/api/pr/:owner/:repo/:number/review-settings', async (req, res) => {
|
|
177
|
+
try {
|
|
178
|
+
const { owner, repo, number } = req.params;
|
|
179
|
+
const prNumber = parseInt(number);
|
|
180
|
+
|
|
181
|
+
if (isNaN(prNumber) || prNumber <= 0) {
|
|
182
|
+
return res.status(400).json({
|
|
183
|
+
error: 'Invalid pull request number'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const repository = normalizeRepository(owner, repo);
|
|
188
|
+
const db = req.app.get('db');
|
|
189
|
+
|
|
190
|
+
const reviewRepo = new ReviewRepository(db);
|
|
191
|
+
const review = await reviewRepo.getReviewByPR(prNumber, repository);
|
|
192
|
+
|
|
193
|
+
if (!review) {
|
|
194
|
+
return res.json({
|
|
195
|
+
custom_instructions: null
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
res.json({
|
|
200
|
+
custom_instructions: review.custom_instructions || null
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('Error fetching review settings:', error);
|
|
205
|
+
res.status(500).json({
|
|
206
|
+
error: 'Failed to fetch review settings'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get available AI providers and their models
|
|
213
|
+
* Returns provider info including available models
|
|
214
|
+
*/
|
|
215
|
+
router.get('/api/providers', (req, res) => {
|
|
216
|
+
try {
|
|
217
|
+
const providers = getAllProvidersInfo();
|
|
218
|
+
res.json({ providers });
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error('Error fetching providers:', error);
|
|
221
|
+
res.status(500).json({
|
|
222
|
+
error: 'Failed to fetch AI providers'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Test if a specific AI provider is available
|
|
229
|
+
* Checks if the provider's CLI is installed and accessible
|
|
230
|
+
*/
|
|
231
|
+
router.get('/api/providers/:providerId/test', async (req, res) => {
|
|
232
|
+
try {
|
|
233
|
+
const { providerId } = req.params;
|
|
234
|
+
const result = await testProviderAvailability(providerId);
|
|
235
|
+
|
|
236
|
+
res.json({
|
|
237
|
+
provider: providerId,
|
|
238
|
+
available: result.available,
|
|
239
|
+
error: result.error || null,
|
|
240
|
+
installInstructions: result.installInstructions || null
|
|
241
|
+
});
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error('Error testing provider:', error);
|
|
244
|
+
res.status(500).json({
|
|
245
|
+
error: 'Failed to test provider availability'
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
module.exports = router;
|