@in-the-loop-labs/pair-review 1.6.2 → 2.0.1
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/README.md +77 -4
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/review-requests/SKILL.md +4 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/SKILL.md +4 -3
- package/public/css/pr.css +1962 -114
- package/public/js/CONVENTIONS.md +16 -0
- package/public/js/components/AIPanel.js +66 -0
- package/public/js/components/AnalysisConfigModal.js +2 -2
- package/public/js/components/ChatPanel.js +2955 -0
- package/public/js/components/CouncilProgressModal.js +12 -16
- package/public/js/components/KeyboardShortcuts.js +3 -0
- package/public/js/components/PanelGroup.js +723 -0
- package/public/js/components/PreviewModal.js +3 -8
- package/public/js/index.js +8 -0
- package/public/js/local.js +17 -615
- package/public/js/modules/analysis-history.js +19 -68
- package/public/js/modules/comment-manager.js +103 -20
- package/public/js/modules/diff-context.js +176 -0
- package/public/js/modules/diff-renderer.js +30 -0
- package/public/js/modules/file-comment-manager.js +126 -105
- package/public/js/modules/file-list-merger.js +64 -0
- package/public/js/modules/panel-resizer.js +25 -6
- package/public/js/modules/suggestion-manager.js +40 -125
- package/public/js/pr.js +1009 -159
- package/public/js/repo-settings.js +36 -6
- package/public/js/utils/category-emoji.js +44 -0
- package/public/js/utils/time.js +32 -0
- package/public/local.html +107 -70
- package/public/pr.html +107 -70
- package/public/repo-settings.html +32 -0
- package/src/ai/analyzer.js +5 -1
- package/src/ai/copilot-provider.js +39 -9
- package/src/ai/cursor-agent-provider.js +45 -11
- package/src/ai/gemini-provider.js +17 -4
- package/src/ai/prompts/config.js +7 -1
- package/src/ai/provider-availability.js +1 -1
- package/src/ai/provider.js +25 -37
- package/src/chat/CONVENTIONS.md +18 -0
- package/src/chat/pi-bridge.js +491 -0
- package/src/chat/prompt-builder.js +272 -0
- package/src/chat/session-manager.js +619 -0
- package/src/config.js +14 -0
- package/src/database.js +322 -15
- package/src/main.js +4 -17
- package/src/routes/analyses.js +721 -0
- package/src/routes/chat.js +655 -0
- package/src/routes/config.js +29 -8
- package/src/routes/context-files.js +274 -0
- package/src/routes/local.js +225 -1133
- package/src/routes/mcp.js +39 -30
- package/src/routes/pr.js +424 -58
- package/src/routes/reviews.js +1035 -0
- package/src/routes/shared.js +4 -29
- package/src/server.js +34 -12
- package/src/sse/review-events.js +46 -0
- package/src/utils/auto-context.js +88 -0
- package/src/utils/category-emoji.js +33 -0
- package/src/utils/diff-annotator.js +75 -1
- package/src/utils/diff-file-list.js +57 -0
- package/src/routes/analysis.js +0 -1600
- package/src/routes/comments.js +0 -534
package/src/routes/config.js
CHANGED
|
@@ -37,7 +37,10 @@ router.get('/api/config', (req, res) => {
|
|
|
37
37
|
theme: config.theme || 'light',
|
|
38
38
|
comment_button_action: config.comment_button_action || 'submit',
|
|
39
39
|
// Include npx detection for frontend command examples
|
|
40
|
-
is_running_via_npx: isRunningViaNpx()
|
|
40
|
+
is_running_via_npx: isRunningViaNpx(),
|
|
41
|
+
enable_chat: config.enable_chat !== false,
|
|
42
|
+
chat_enable_shortcuts: config.chat?.enable_shortcuts !== false,
|
|
43
|
+
pi_available: getCachedAvailability('pi')?.available || false
|
|
41
44
|
});
|
|
42
45
|
});
|
|
43
46
|
|
|
@@ -47,7 +50,7 @@ router.get('/api/config', (req, res) => {
|
|
|
47
50
|
*/
|
|
48
51
|
router.patch('/api/config', async (req, res) => {
|
|
49
52
|
try {
|
|
50
|
-
const { comment_button_action } = req.body;
|
|
53
|
+
const { comment_button_action, chat_enable_shortcuts } = req.body;
|
|
51
54
|
|
|
52
55
|
// Validate comment_button_action if provided
|
|
53
56
|
if (comment_button_action !== undefined) {
|
|
@@ -58,6 +61,14 @@ router.patch('/api/config', async (req, res) => {
|
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
if (chat_enable_shortcuts !== undefined) {
|
|
65
|
+
if (typeof chat_enable_shortcuts !== 'boolean') {
|
|
66
|
+
return res.status(400).json({
|
|
67
|
+
error: 'Invalid chat_enable_shortcuts. Must be a boolean'
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
// Get current config
|
|
62
73
|
const config = req.app.get('config') || {};
|
|
63
74
|
|
|
@@ -66,6 +77,11 @@ router.patch('/api/config', async (req, res) => {
|
|
|
66
77
|
config.comment_button_action = comment_button_action;
|
|
67
78
|
}
|
|
68
79
|
|
|
80
|
+
if (chat_enable_shortcuts !== undefined) {
|
|
81
|
+
if (!config.chat) config.chat = {};
|
|
82
|
+
config.chat.enable_shortcuts = chat_enable_shortcuts;
|
|
83
|
+
}
|
|
84
|
+
|
|
69
85
|
// Save config to file
|
|
70
86
|
const { saveConfig } = require('../config');
|
|
71
87
|
await saveConfig(config);
|
|
@@ -77,7 +93,8 @@ router.patch('/api/config', async (req, res) => {
|
|
|
77
93
|
success: true,
|
|
78
94
|
config: {
|
|
79
95
|
theme: config.theme || 'light',
|
|
80
|
-
comment_button_action: config.comment_button_action || 'submit'
|
|
96
|
+
comment_button_action: config.comment_button_action || 'submit',
|
|
97
|
+
chat_enable_shortcuts: config.chat?.enable_shortcuts !== false
|
|
81
98
|
}
|
|
82
99
|
});
|
|
83
100
|
|
|
@@ -111,7 +128,8 @@ router.get('/api/repos/:owner/:repo/settings', async (req, res) => {
|
|
|
111
128
|
default_model: null,
|
|
112
129
|
local_path: null,
|
|
113
130
|
default_council_id: null,
|
|
114
|
-
default_tab: null
|
|
131
|
+
default_tab: null,
|
|
132
|
+
default_chat_instructions: null
|
|
115
133
|
});
|
|
116
134
|
}
|
|
117
135
|
|
|
@@ -123,6 +141,7 @@ router.get('/api/repos/:owner/:repo/settings', async (req, res) => {
|
|
|
123
141
|
local_path: settings.local_path,
|
|
124
142
|
default_council_id: settings.default_council_id,
|
|
125
143
|
default_tab: settings.default_tab,
|
|
144
|
+
default_chat_instructions: settings.default_chat_instructions,
|
|
126
145
|
created_at: settings.created_at,
|
|
127
146
|
updated_at: settings.updated_at
|
|
128
147
|
});
|
|
@@ -142,14 +161,14 @@ router.get('/api/repos/:owner/:repo/settings', async (req, res) => {
|
|
|
142
161
|
router.post('/api/repos/:owner/:repo/settings', async (req, res) => {
|
|
143
162
|
try {
|
|
144
163
|
const { owner, repo } = req.params;
|
|
145
|
-
const { default_instructions, default_provider, default_model, local_path, default_council_id, default_tab } = req.body;
|
|
164
|
+
const { default_instructions, default_provider, default_model, local_path, default_council_id, default_tab, default_chat_instructions } = req.body;
|
|
146
165
|
const repository = normalizeRepository(owner, repo);
|
|
147
166
|
const db = req.app.get('db');
|
|
148
167
|
|
|
149
168
|
// Validate that at least one setting is provided
|
|
150
|
-
if (default_instructions === undefined && default_provider === undefined && default_model === undefined && local_path === undefined && default_council_id === undefined && default_tab === undefined) {
|
|
169
|
+
if (default_instructions === undefined && default_provider === undefined && default_model === undefined && local_path === undefined && default_council_id === undefined && default_tab === undefined && default_chat_instructions === undefined) {
|
|
151
170
|
return res.status(400).json({
|
|
152
|
-
error: 'At least one setting (default_instructions, default_provider, default_model, local_path, default_council_id, or
|
|
171
|
+
error: 'At least one setting (default_instructions, default_provider, default_model, local_path, default_council_id, default_tab, or default_chat_instructions) must be provided'
|
|
153
172
|
});
|
|
154
173
|
}
|
|
155
174
|
|
|
@@ -160,7 +179,8 @@ router.post('/api/repos/:owner/:repo/settings', async (req, res) => {
|
|
|
160
179
|
default_model,
|
|
161
180
|
local_path,
|
|
162
181
|
default_council_id,
|
|
163
|
-
default_tab
|
|
182
|
+
default_tab,
|
|
183
|
+
default_chat_instructions
|
|
164
184
|
});
|
|
165
185
|
|
|
166
186
|
logger.info(`Saved repo settings for ${repository}`);
|
|
@@ -175,6 +195,7 @@ router.post('/api/repos/:owner/:repo/settings', async (req, res) => {
|
|
|
175
195
|
local_path: settings.local_path,
|
|
176
196
|
default_council_id: settings.default_council_id,
|
|
177
197
|
default_tab: settings.default_tab,
|
|
198
|
+
default_chat_instructions: settings.default_chat_instructions,
|
|
178
199
|
updated_at: settings.updated_at
|
|
179
200
|
}
|
|
180
201
|
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* Context Files Routes
|
|
4
|
+
*
|
|
5
|
+
* Provides endpoints for managing context file ranges that pin specific
|
|
6
|
+
* line ranges from non-diff files into the diff panel for review.
|
|
7
|
+
*
|
|
8
|
+
* All endpoints live under /api/reviews/:reviewId/context-files
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const express = require('express');
|
|
14
|
+
const { ReviewRepository, ContextFileRepository, WorktreeRepository } = require('../database');
|
|
15
|
+
const logger = require('../utils/logger');
|
|
16
|
+
const { broadcastReviewEvent } = require('../sse/review-events');
|
|
17
|
+
const { getDiffFileList } = require('../utils/diff-file-list');
|
|
18
|
+
|
|
19
|
+
const router = express.Router();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the repository root directory for a review.
|
|
23
|
+
* Local reviews use local_path; PR reviews look up the worktree path.
|
|
24
|
+
* Returns null when the root cannot be determined (e.g. worktree not set up).
|
|
25
|
+
*
|
|
26
|
+
* @param {object} db - SQLite database handle
|
|
27
|
+
* @param {object} review - Review row from the database
|
|
28
|
+
* @returns {Promise<string|null>} Absolute path to the repo root, or null
|
|
29
|
+
*/
|
|
30
|
+
async function resolveRepoRoot(db, review) {
|
|
31
|
+
// Local mode – the path is stored directly on the review record
|
|
32
|
+
if (review.local_path) {
|
|
33
|
+
return review.local_path;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// PR mode – look up the worktree record
|
|
37
|
+
if (review.pr_number && review.repository) {
|
|
38
|
+
const worktreeRepo = new WorktreeRepository(db);
|
|
39
|
+
const worktree = await worktreeRepo.findByPR(review.pr_number, review.repository);
|
|
40
|
+
if (worktree && worktree.path) {
|
|
41
|
+
return worktree.path;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Middleware: validate that :reviewId exists in the reviews table.
|
|
50
|
+
* Attaches the review record to req.review for downstream handlers.
|
|
51
|
+
*/
|
|
52
|
+
async function validateReviewId(req, res, next) {
|
|
53
|
+
try {
|
|
54
|
+
const reviewId = parseInt(req.params.reviewId, 10);
|
|
55
|
+
|
|
56
|
+
if (isNaN(reviewId) || reviewId <= 0) {
|
|
57
|
+
return res.status(400).json({ error: 'Invalid review ID' });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const db = req.app.get('db');
|
|
61
|
+
const reviewRepo = new ReviewRepository(db);
|
|
62
|
+
const review = await reviewRepo.getReview(reviewId);
|
|
63
|
+
|
|
64
|
+
if (!review) {
|
|
65
|
+
return res.status(404).json({ error: `Review #${reviewId} not found` });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
req.review = review;
|
|
69
|
+
req.reviewId = reviewId;
|
|
70
|
+
next();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
next(error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* POST /api/reviews/:reviewId/context-files
|
|
78
|
+
* Add a context file range for a review.
|
|
79
|
+
* Body: { file, line_start, line_end, label? }
|
|
80
|
+
*/
|
|
81
|
+
router.post('/api/reviews/:reviewId/context-files', validateReviewId, async (req, res) => {
|
|
82
|
+
try {
|
|
83
|
+
const { file, line_start, line_end, label } = req.body;
|
|
84
|
+
|
|
85
|
+
// Validate: file is required and non-empty string
|
|
86
|
+
if (!file || typeof file !== 'string' || file.trim().length === 0) {
|
|
87
|
+
return res.status(400).json({ error: 'file is required and must be a non-empty string' });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (file.includes('..') || file.startsWith('/')) {
|
|
91
|
+
return res.status(400).json({ error: 'file must be a relative path without .. segments' });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Validate: line_start and line_end are positive integers
|
|
95
|
+
const lineStart = parseInt(line_start, 10);
|
|
96
|
+
const lineEnd = parseInt(line_end, 10);
|
|
97
|
+
|
|
98
|
+
if (isNaN(lineStart) || lineStart <= 0) {
|
|
99
|
+
return res.status(400).json({ error: 'line_start must be a positive integer' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isNaN(lineEnd) || lineEnd <= 0) {
|
|
103
|
+
return res.status(400).json({ error: 'line_end must be a positive integer' });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Validate: line_end >= line_start
|
|
107
|
+
if (lineEnd < lineStart) {
|
|
108
|
+
return res.status(400).json({ error: 'line_end must be >= line_start' });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Validate: max range of 500 lines
|
|
112
|
+
if (lineEnd - lineStart + 1 > 500) {
|
|
113
|
+
return res.status(400).json({ error: 'Range cannot exceed 500 lines' });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const db = req.app.get('db');
|
|
117
|
+
|
|
118
|
+
// Reject files that are already part of the review's diff
|
|
119
|
+
const diffFiles = await getDiffFileList(db, req.review);
|
|
120
|
+
if (diffFiles.includes(file.trim())) {
|
|
121
|
+
return res.status(400).json({
|
|
122
|
+
error: `Cannot add context file: '${file.trim()}' is already part of the diff`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate that the file exists on disk when we can resolve the repo root
|
|
127
|
+
const repoRoot = await resolveRepoRoot(db, req.review);
|
|
128
|
+
if (repoRoot) {
|
|
129
|
+
const resolved = path.resolve(repoRoot, file.trim());
|
|
130
|
+
// Double-check the resolved path is still within the repo root (belt-and-suspenders with the .. check above)
|
|
131
|
+
if (!resolved.startsWith(repoRoot + path.sep) && resolved !== repoRoot) {
|
|
132
|
+
return res.status(400).json({ error: 'file must be a relative path without .. segments' });
|
|
133
|
+
}
|
|
134
|
+
if (!fs.existsSync(resolved)) {
|
|
135
|
+
return res.status(400).json({ error: 'File not found in repository' });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const contextFileRepo = new ContextFileRepository(db);
|
|
140
|
+
|
|
141
|
+
const record = await contextFileRepo.add(
|
|
142
|
+
req.reviewId,
|
|
143
|
+
file.trim(),
|
|
144
|
+
lineStart,
|
|
145
|
+
lineEnd,
|
|
146
|
+
label || null
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
res.status(201).json({ success: true, contextFile: record });
|
|
150
|
+
broadcastReviewEvent(req.reviewId, { type: 'review:context_files_changed' }, { sourceClientId: req.get('X-Client-Id') });
|
|
151
|
+
} catch (error) {
|
|
152
|
+
logger.error('Error adding context file:', error);
|
|
153
|
+
res.status(500).json({ error: 'Failed to add context file' });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* GET /api/reviews/:reviewId/context-files
|
|
159
|
+
* List all context file ranges for a review.
|
|
160
|
+
*/
|
|
161
|
+
router.get('/api/reviews/:reviewId/context-files', validateReviewId, async (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const db = req.app.get('db');
|
|
164
|
+
const contextFileRepo = new ContextFileRepository(db);
|
|
165
|
+
|
|
166
|
+
const contextFiles = await contextFileRepo.getByReviewId(req.reviewId);
|
|
167
|
+
|
|
168
|
+
res.json({ success: true, contextFiles: contextFiles || [] });
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logger.error('Error fetching context files:', error);
|
|
171
|
+
res.status(500).json({ error: 'Failed to fetch context files' });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* PATCH /api/reviews/:reviewId/context-files/:id
|
|
177
|
+
* Update the line range of an existing context file entry.
|
|
178
|
+
* Body: { line_start, line_end }
|
|
179
|
+
*/
|
|
180
|
+
router.patch('/api/reviews/:reviewId/context-files/:id', validateReviewId, async (req, res) => {
|
|
181
|
+
try {
|
|
182
|
+
const id = parseInt(req.params.id, 10);
|
|
183
|
+
|
|
184
|
+
if (isNaN(id) || id <= 0) {
|
|
185
|
+
return res.status(400).json({ error: 'Invalid context file ID' });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const { line_start, line_end } = req.body;
|
|
189
|
+
|
|
190
|
+
const lineStart = parseInt(line_start, 10);
|
|
191
|
+
const lineEnd = parseInt(line_end, 10);
|
|
192
|
+
|
|
193
|
+
if (isNaN(lineStart) || lineStart <= 0) {
|
|
194
|
+
return res.status(400).json({ error: 'line_start must be a positive integer' });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (isNaN(lineEnd) || lineEnd <= 0) {
|
|
198
|
+
return res.status(400).json({ error: 'line_end must be a positive integer' });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (lineEnd < lineStart) {
|
|
202
|
+
return res.status(400).json({ error: 'line_end must be >= line_start' });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (lineEnd - lineStart + 1 > 500) {
|
|
206
|
+
return res.status(400).json({ error: 'Range cannot exceed 500 lines' });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const db = req.app.get('db');
|
|
210
|
+
const contextFileRepo = new ContextFileRepository(db);
|
|
211
|
+
|
|
212
|
+
const updated = await contextFileRepo.updateRange(id, lineStart, lineEnd);
|
|
213
|
+
|
|
214
|
+
if (!updated) {
|
|
215
|
+
return res.status(404).json({ error: 'Context file not found' });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
res.json({ success: true });
|
|
219
|
+
broadcastReviewEvent(req.reviewId, { type: 'review:context_files_changed' }, { sourceClientId: req.get('X-Client-Id') });
|
|
220
|
+
} catch (error) {
|
|
221
|
+
logger.error('Error updating context file range:', error);
|
|
222
|
+
res.status(500).json({ error: 'Failed to update context file range' });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* DELETE /api/reviews/:reviewId/context-files/:id
|
|
228
|
+
* Remove a single context file range by ID.
|
|
229
|
+
*/
|
|
230
|
+
router.delete('/api/reviews/:reviewId/context-files/:id', validateReviewId, async (req, res) => {
|
|
231
|
+
try {
|
|
232
|
+
const id = parseInt(req.params.id, 10);
|
|
233
|
+
|
|
234
|
+
if (isNaN(id) || id <= 0) {
|
|
235
|
+
return res.status(400).json({ error: 'Invalid context file ID' });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const db = req.app.get('db');
|
|
239
|
+
const contextFileRepo = new ContextFileRepository(db);
|
|
240
|
+
|
|
241
|
+
const deleted = await contextFileRepo.remove(id, req.reviewId);
|
|
242
|
+
|
|
243
|
+
if (!deleted) {
|
|
244
|
+
return res.status(404).json({ error: 'Context file not found' });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
res.json({ success: true, message: 'Context file removed' });
|
|
248
|
+
broadcastReviewEvent(req.reviewId, { type: 'review:context_files_changed' }, { sourceClientId: req.get('X-Client-Id') });
|
|
249
|
+
} catch (error) {
|
|
250
|
+
logger.error('Error removing context file:', error);
|
|
251
|
+
res.status(500).json({ error: 'Failed to remove context file' });
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* DELETE /api/reviews/:reviewId/context-files
|
|
257
|
+
* Remove all context file ranges for a review.
|
|
258
|
+
*/
|
|
259
|
+
router.delete('/api/reviews/:reviewId/context-files', validateReviewId, async (req, res) => {
|
|
260
|
+
try {
|
|
261
|
+
const db = req.app.get('db');
|
|
262
|
+
const contextFileRepo = new ContextFileRepository(db);
|
|
263
|
+
|
|
264
|
+
const deletedCount = await contextFileRepo.removeAll(req.reviewId);
|
|
265
|
+
|
|
266
|
+
res.json({ success: true, deletedCount, message: `Removed ${deletedCount} context file${deletedCount !== 1 ? 's' : ''}` });
|
|
267
|
+
broadcastReviewEvent(req.reviewId, { type: 'review:context_files_changed' }, { sourceClientId: req.get('X-Client-Id') });
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logger.error('Error removing all context files:', error);
|
|
270
|
+
res.status(500).json({ error: 'Failed to remove context files' });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
module.exports = router;
|