@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.
Files changed (91) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +371 -0
  3. package/bin/git-diff-lines +146 -0
  4. package/bin/pair-review.js +49 -0
  5. package/package.json +71 -0
  6. package/public/css/ai-summary-modal.css +183 -0
  7. package/public/css/pr.css +8698 -0
  8. package/public/css/repo-settings.css +891 -0
  9. package/public/css/styles.css +479 -0
  10. package/public/favicon.png +0 -0
  11. package/public/index.html +1104 -0
  12. package/public/js/components/AIPanel.js +1639 -0
  13. package/public/js/components/AISummaryModal.js +278 -0
  14. package/public/js/components/AnalysisConfigModal.js +684 -0
  15. package/public/js/components/ConfirmDialog.js +227 -0
  16. package/public/js/components/PreviewModal.js +344 -0
  17. package/public/js/components/ProgressModal.js +678 -0
  18. package/public/js/components/ReviewModal.js +531 -0
  19. package/public/js/components/SplitButton.js +382 -0
  20. package/public/js/components/StatusIndicator.js +265 -0
  21. package/public/js/components/SuggestionNavigator.js +489 -0
  22. package/public/js/components/Toast.js +166 -0
  23. package/public/js/local.js +1580 -0
  24. package/public/js/modules/analysis-history.js +940 -0
  25. package/public/js/modules/comment-manager.js +643 -0
  26. package/public/js/modules/diff-renderer.js +585 -0
  27. package/public/js/modules/file-comment-manager.js +1242 -0
  28. package/public/js/modules/gap-coordinates.js +190 -0
  29. package/public/js/modules/hunk-parser.js +358 -0
  30. package/public/js/modules/line-tracker.js +386 -0
  31. package/public/js/modules/panel-resizer.js +228 -0
  32. package/public/js/modules/storage-cleanup.js +36 -0
  33. package/public/js/modules/suggestion-manager.js +692 -0
  34. package/public/js/pr.js +3503 -0
  35. package/public/js/repo-settings.js +691 -0
  36. package/public/js/utils/file-order.js +87 -0
  37. package/public/js/utils/markdown.js +97 -0
  38. package/public/js/utils/suggestion-ui.js +55 -0
  39. package/public/js/utils/tier-icons.js +25 -0
  40. package/public/local.html +460 -0
  41. package/public/pr.html +329 -0
  42. package/public/repo-settings.html +243 -0
  43. package/src/ai/analyzer.js +2592 -0
  44. package/src/ai/claude-cli.js +153 -0
  45. package/src/ai/claude-provider.js +261 -0
  46. package/src/ai/codex-provider.js +361 -0
  47. package/src/ai/copilot-provider.js +345 -0
  48. package/src/ai/gemini-provider.js +375 -0
  49. package/src/ai/index.js +47 -0
  50. package/src/ai/prompts/baseline/_meta.json +14 -0
  51. package/src/ai/prompts/baseline/level1/balanced.js +239 -0
  52. package/src/ai/prompts/baseline/level1/fast.js +194 -0
  53. package/src/ai/prompts/baseline/level1/thorough.js +319 -0
  54. package/src/ai/prompts/baseline/level2/balanced.js +248 -0
  55. package/src/ai/prompts/baseline/level2/fast.js +201 -0
  56. package/src/ai/prompts/baseline/level2/thorough.js +367 -0
  57. package/src/ai/prompts/baseline/level3/balanced.js +280 -0
  58. package/src/ai/prompts/baseline/level3/fast.js +220 -0
  59. package/src/ai/prompts/baseline/level3/thorough.js +459 -0
  60. package/src/ai/prompts/baseline/orchestration/balanced.js +259 -0
  61. package/src/ai/prompts/baseline/orchestration/fast.js +213 -0
  62. package/src/ai/prompts/baseline/orchestration/thorough.js +446 -0
  63. package/src/ai/prompts/config.js +52 -0
  64. package/src/ai/prompts/index.js +267 -0
  65. package/src/ai/prompts/shared/diff-instructions.js +50 -0
  66. package/src/ai/prompts/shared/output-schema.js +179 -0
  67. package/src/ai/prompts/shared/valid-files.js +37 -0
  68. package/src/ai/provider.js +260 -0
  69. package/src/config.js +139 -0
  70. package/src/database.js +2284 -0
  71. package/src/git/gitattributes.js +207 -0
  72. package/src/git/worktree.js +688 -0
  73. package/src/github/client.js +893 -0
  74. package/src/github/parser.js +247 -0
  75. package/src/local-review.js +691 -0
  76. package/src/main.js +987 -0
  77. package/src/routes/analysis.js +897 -0
  78. package/src/routes/comments.js +534 -0
  79. package/src/routes/config.js +250 -0
  80. package/src/routes/local.js +1728 -0
  81. package/src/routes/pr.js +1164 -0
  82. package/src/routes/shared.js +218 -0
  83. package/src/routes/worktrees.js +500 -0
  84. package/src/server.js +295 -0
  85. package/src/utils/diff-annotator.js +414 -0
  86. package/src/utils/instructions.js +33 -0
  87. package/src/utils/json-extractor.js +107 -0
  88. package/src/utils/line-validation.js +183 -0
  89. package/src/utils/logger.js +142 -0
  90. package/src/utils/paths.js +161 -0
  91. 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;