@in-the-loop-labs/pair-review 3.0.6 → 3.1.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/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/references/level1-balanced.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level1-fast.md +7 -0
- package/plugin-code-critic/skills/analyze/references/level1-thorough.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level2-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level2-fast.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level2-thorough.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level3-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/level3-fast.md +8 -0
- package/plugin-code-critic/skills/analyze/references/level3-thorough.md +9 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-balanced.md +9 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-fast.md +5 -0
- package/plugin-code-critic/skills/analyze/references/orchestration-thorough.md +9 -0
- package/public/css/analysis-config.css +83 -0
- package/public/css/pr.css +191 -4
- package/public/index.html +20 -0
- package/public/js/components/AIPanel.js +1 -1
- package/public/js/components/AdvancedConfigTab.js +83 -8
- package/public/js/components/AnalysisConfigModal.js +155 -5
- package/public/js/components/ChatPanel.js +22 -5
- package/public/js/components/CouncilProgressModal.js +239 -22
- package/public/js/components/TimeoutSelect.js +2 -0
- package/public/js/components/VoiceCentricConfigTab.js +179 -12
- package/public/js/index.js +119 -1
- package/public/js/local.js +141 -47
- package/public/js/modules/suggestion-manager.js +2 -1
- package/public/js/pr.js +71 -12
- package/public/js/repo-settings.js +2 -2
- package/public/local.html +32 -11
- package/public/pr.html +2 -0
- package/src/ai/analyzer.js +371 -111
- package/src/ai/claude-provider.js +2 -0
- package/src/ai/codex-provider.js +1 -1
- package/src/ai/copilot-provider.js +2 -0
- package/src/ai/executable-provider.js +534 -0
- package/src/ai/gemini-provider.js +2 -0
- package/src/ai/index.js +9 -1
- package/src/ai/pi-provider.js +10 -8
- package/src/ai/prompts/baseline/consolidation/balanced.js +54 -2
- package/src/ai/prompts/baseline/consolidation/fast.js +31 -1
- package/src/ai/prompts/baseline/consolidation/thorough.js +46 -3
- package/src/ai/prompts/baseline/level1/balanced.js +12 -0
- package/src/ai/prompts/baseline/level1/fast.js +11 -0
- package/src/ai/prompts/baseline/level1/thorough.js +12 -0
- package/src/ai/prompts/baseline/level2/balanced.js +13 -0
- package/src/ai/prompts/baseline/level2/fast.js +12 -0
- package/src/ai/prompts/baseline/level2/thorough.js +13 -0
- package/src/ai/prompts/baseline/level3/balanced.js +13 -0
- package/src/ai/prompts/baseline/level3/fast.js +12 -0
- package/src/ai/prompts/baseline/level3/thorough.js +13 -0
- package/src/ai/prompts/baseline/orchestration/balanced.js +15 -0
- package/src/ai/prompts/baseline/orchestration/fast.js +11 -0
- package/src/ai/prompts/baseline/orchestration/thorough.js +15 -0
- package/src/ai/prompts/render-for-skill.js +3 -0
- package/src/ai/prompts/shared/output-schema.js +8 -0
- package/src/ai/provider.js +89 -4
- package/src/chat/prompt-builder.js +17 -1
- package/src/chat/session-manager.js +32 -28
- package/src/config.js +15 -2
- package/src/database.js +59 -15
- package/src/git/base-branch.js +113 -29
- package/src/local-review.js +15 -9
- package/src/main.js +3 -2
- package/src/routes/analyses.js +34 -8
- package/src/routes/chat.js +15 -8
- package/src/routes/config.js +3 -120
- package/src/routes/councils.js +15 -6
- package/src/routes/executable-analysis.js +494 -0
- package/src/routes/local.js +152 -15
- package/src/routes/mcp.js +9 -4
- package/src/routes/pr.js +166 -29
- package/src/routes/reviews.js +31 -5
- package/src/routes/shared.js +72 -5
- package/src/routes/worktrees.js +4 -2
- package/src/utils/comment-formatter.js +28 -11
- package/src/utils/instructions.js +22 -8
- package/src/utils/logger.js +20 -10
package/src/routes/chat.js
CHANGED
|
@@ -20,6 +20,7 @@ const logger = require('../utils/logger');
|
|
|
20
20
|
const ws = require('../ws');
|
|
21
21
|
const { fireHooks, hasHooks } = require('../hooks/hook-runner');
|
|
22
22
|
const { buildChatStartedPayload, buildChatResumedPayload, buildChatHookContext, getCachedUser } = require('../hooks/payloads');
|
|
23
|
+
const { resolveFormat } = require('../utils/comment-formatter');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Fire a chat hook event (non-blocking). Skips async work when no hooks are configured.
|
|
@@ -259,8 +260,10 @@ router.post('/api/chat/session', async (req, res) => {
|
|
|
259
260
|
|
|
260
261
|
const chatInstructions = await getChatInstructions(db, review);
|
|
261
262
|
const prData = await fetchPrData(db, review);
|
|
263
|
+
const config = req.app.get('config') || {};
|
|
264
|
+
const formatConfig = resolveFormat(config.comment_format);
|
|
262
265
|
|
|
263
|
-
finalSystemPrompt = buildChatPrompt({ review, prData, chatInstructions });
|
|
266
|
+
finalSystemPrompt = buildChatPrompt({ review, prData, chatInstructions, commentFormatTemplate: formatConfig.template });
|
|
264
267
|
|
|
265
268
|
if (!skipAnalysisContext) {
|
|
266
269
|
// Fetch all AI suggestions from the latest analysis run
|
|
@@ -268,7 +271,7 @@ router.post('/api/chat/session', async (req, res) => {
|
|
|
268
271
|
SELECT
|
|
269
272
|
id, ai_run_id, ai_level, ai_confidence,
|
|
270
273
|
file, line_start, line_end, type, title, body,
|
|
271
|
-
reasoning, status, is_file_level
|
|
274
|
+
reasoning, status, is_file_level, severity
|
|
272
275
|
FROM comments
|
|
273
276
|
WHERE review_id = ?
|
|
274
277
|
AND source = 'ai'
|
|
@@ -323,7 +326,7 @@ router.post('/api/chat/session', async (req, res) => {
|
|
|
323
326
|
initialContext: initialContextWithPort
|
|
324
327
|
});
|
|
325
328
|
|
|
326
|
-
logger.info(`Chat session created: ${session.id} (provider=${provider}
|
|
329
|
+
logger.info(`Chat session created: ${session.id} (provider=${provider})`);
|
|
327
330
|
|
|
328
331
|
// Register broadcast listeners so events reach all connected clients
|
|
329
332
|
registerChatBroadcast(chatSessionManager, session.id, serverPort);
|
|
@@ -390,8 +393,10 @@ router.post('/api/chat/session/:id/message', async (req, res) => {
|
|
|
390
393
|
}
|
|
391
394
|
const chatInstructions = await getChatInstructions(db, review);
|
|
392
395
|
const prData = await fetchPrData(db, review);
|
|
396
|
+
const config = req.app.get('config') || {};
|
|
397
|
+
const fmtConfig = resolveFormat(config.comment_format);
|
|
393
398
|
|
|
394
|
-
const systemPrompt = buildChatPrompt({ review, prData, chatInstructions });
|
|
399
|
+
const systemPrompt = buildChatPrompt({ review, prData, chatInstructions, commentFormatTemplate: fmtConfig.template });
|
|
395
400
|
const cwd = await resolveReviewCwd(db, review);
|
|
396
401
|
|
|
397
402
|
try {
|
|
@@ -405,7 +410,7 @@ router.post('/api/chat/session/:id/message', async (req, res) => {
|
|
|
405
410
|
// Inject port correction so the agent knows the current server address,
|
|
406
411
|
// even if the conversational history has a stale port from session creation.
|
|
407
412
|
const serverPort = req.socket.localPort;
|
|
408
|
-
portCorrectionContext = `[Server port: ${serverPort}] The pair-review API is at http://localhost:${serverPort}`;
|
|
413
|
+
portCorrectionContext = `[Server port: ${serverPort}] The pair-review API is at http://localhost:${serverPort}\n\n[Comment format template: ${fmtConfig.template}]`;
|
|
409
414
|
// Note: we intentionally do NOT re-inject the API cheat sheet on resume.
|
|
410
415
|
// The agent already has the endpoint shapes from the original session context —
|
|
411
416
|
// it only needs the updated port to adjust its curl calls.
|
|
@@ -530,8 +535,10 @@ router.post('/api/chat/session/:id/resume', async (req, res) => {
|
|
|
530
535
|
// initial session's context.
|
|
531
536
|
const chatInstructions = await getChatInstructions(db, review);
|
|
532
537
|
const prData = await fetchPrData(db, review);
|
|
538
|
+
const config = req.app.get('config') || {};
|
|
539
|
+
const fmtConfig = resolveFormat(config.comment_format);
|
|
533
540
|
|
|
534
|
-
const systemPrompt = buildChatPrompt({ review, prData, chatInstructions });
|
|
541
|
+
const systemPrompt = buildChatPrompt({ review, prData, chatInstructions, commentFormatTemplate: fmtConfig.template });
|
|
535
542
|
const cwd = await resolveReviewCwd(db, review);
|
|
536
543
|
|
|
537
544
|
await chatSessionManager.resumeSession(sessionId, { systemPrompt, cwd });
|
|
@@ -544,7 +551,7 @@ router.post('/api/chat/session/:id/resume', async (req, res) => {
|
|
|
544
551
|
// Uses resumeContext (consumed on next sendMessage) instead of saveContextMessage
|
|
545
552
|
// (which only writes to DB and never reaches the agent process).
|
|
546
553
|
chatSessionManager.setResumeContext(sessionId,
|
|
547
|
-
`[Server port: ${serverPort}] The pair-review API is at http://localhost:${serverPort}`
|
|
554
|
+
`[Server port: ${serverPort}] The pair-review API is at http://localhost:${serverPort}\n\n[Comment format template: ${fmtConfig.template}]`
|
|
548
555
|
);
|
|
549
556
|
|
|
550
557
|
logger.info(`[ChatRoute] Explicitly resumed session ${sessionId}`);
|
|
@@ -622,7 +629,7 @@ router.get('/api/chat/analysis-context/:runId', async (req, res) => {
|
|
|
622
629
|
SELECT
|
|
623
630
|
id, ai_run_id, ai_level, ai_confidence,
|
|
624
631
|
file, line_start, line_end, type, title, body,
|
|
625
|
-
reasoning, status, is_file_level
|
|
632
|
+
reasoning, status, is_file_level, severity
|
|
626
633
|
FROM comments
|
|
627
634
|
WHERE review_id = ?
|
|
628
635
|
AND source = 'ai'
|
package/src/routes/config.js
CHANGED
|
@@ -20,10 +20,9 @@ const {
|
|
|
20
20
|
isCheckInProgress
|
|
21
21
|
} = require('../ai');
|
|
22
22
|
const { normalizeRepository } = require('../utils/paths');
|
|
23
|
-
const { isRunningViaNpx,
|
|
23
|
+
const { isRunningViaNpx, getGitHubToken } = require('../config');
|
|
24
24
|
const { version } = require('../../package.json');
|
|
25
25
|
const { getAllChatProviders, getAllCachedChatAvailability } = require('../chat/chat-providers');
|
|
26
|
-
const { PRESETS } = require('../utils/comment-formatter');
|
|
27
26
|
const logger = require('../utils/logger');
|
|
28
27
|
|
|
29
28
|
const router = express.Router();
|
|
@@ -45,6 +44,7 @@ router.get('/api/config', (req, res) => {
|
|
|
45
44
|
res.json({
|
|
46
45
|
version,
|
|
47
46
|
theme: config.theme || 'light',
|
|
47
|
+
has_github_token: Boolean(getGitHubToken(config)),
|
|
48
48
|
comment_button_action: config.comment_button_action || 'submit',
|
|
49
49
|
comment_format: config.comment_format || 'legacy',
|
|
50
50
|
// Include npx detection for frontend command examples
|
|
@@ -53,6 +53,7 @@ router.get('/api/config', (req, res) => {
|
|
|
53
53
|
chat_provider: config.chat_provider || 'pi',
|
|
54
54
|
chat_providers: chatProviders,
|
|
55
55
|
chat_enable_shortcuts: config.chat?.enable_shortcuts !== false,
|
|
56
|
+
chat_enter_to_send: config.chat?.enter_to_send !== false,
|
|
56
57
|
pi_available: getCachedAvailability('pi')?.available || false,
|
|
57
58
|
assisted_by_url: config.assisted_by_url || 'https://github.com/in-the-loop-labs/pair-review',
|
|
58
59
|
enable_graphite: config.enable_graphite === true,
|
|
@@ -74,124 +75,6 @@ router.get('/api/config', (req, res) => {
|
|
|
74
75
|
});
|
|
75
76
|
});
|
|
76
77
|
|
|
77
|
-
/**
|
|
78
|
-
* Update user configuration
|
|
79
|
-
* Updates safe configuration values
|
|
80
|
-
*/
|
|
81
|
-
router.patch('/api/config', async (req, res) => {
|
|
82
|
-
try {
|
|
83
|
-
const { comment_button_action, chat_enable_shortcuts, comment_format } = req.body;
|
|
84
|
-
|
|
85
|
-
// Validate comment_button_action if provided
|
|
86
|
-
if (comment_button_action !== undefined) {
|
|
87
|
-
if (!['submit', 'preview'].includes(comment_button_action)) {
|
|
88
|
-
return res.status(400).json({
|
|
89
|
-
error: 'Invalid comment_button_action. Must be "submit" or "preview"'
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (chat_enable_shortcuts !== undefined) {
|
|
95
|
-
if (typeof chat_enable_shortcuts !== 'boolean') {
|
|
96
|
-
return res.status(400).json({
|
|
97
|
-
error: 'Invalid chat_enable_shortcuts. Must be a boolean'
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Validate comment_format if provided
|
|
103
|
-
if (comment_format !== undefined) {
|
|
104
|
-
const validPresets = Object.keys(PRESETS);
|
|
105
|
-
if (typeof comment_format === 'string') {
|
|
106
|
-
if (!validPresets.includes(comment_format)) {
|
|
107
|
-
return res.status(400).json({
|
|
108
|
-
error: `Invalid comment_format preset. Must be one of: ${validPresets.join(', ')}`
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
} else if (typeof comment_format === 'object' && comment_format !== null) {
|
|
112
|
-
if (!comment_format.template || typeof comment_format.template !== 'string') {
|
|
113
|
-
return res.status(400).json({
|
|
114
|
-
error: 'Custom comment_format must have a "template" string'
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
// Validate categoryOverrides if provided (must be a string->string mapping)
|
|
118
|
-
if (comment_format.categoryOverrides !== undefined) {
|
|
119
|
-
if (typeof comment_format.categoryOverrides !== 'object' || comment_format.categoryOverrides === null || Array.isArray(comment_format.categoryOverrides)) {
|
|
120
|
-
return res.status(400).json({
|
|
121
|
-
error: 'categoryOverrides must be an object mapping category names to replacement strings'
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
for (const [, value] of Object.entries(comment_format.categoryOverrides)) {
|
|
125
|
-
if (typeof value !== 'string') {
|
|
126
|
-
return res.status(400).json({
|
|
127
|
-
error: 'categoryOverrides must be a string-to-string mapping'
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// Validate emojiOverrides if provided (must be a string->string mapping)
|
|
133
|
-
if (comment_format.emojiOverrides !== undefined) {
|
|
134
|
-
if (typeof comment_format.emojiOverrides !== 'object' || comment_format.emojiOverrides === null || Array.isArray(comment_format.emojiOverrides)) {
|
|
135
|
-
return res.status(400).json({
|
|
136
|
-
error: 'emojiOverrides must be an object mapping category names to emoji strings'
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
for (const [, value] of Object.entries(comment_format.emojiOverrides)) {
|
|
140
|
-
if (typeof value !== 'string') {
|
|
141
|
-
return res.status(400).json({
|
|
142
|
-
error: 'emojiOverrides must be a string-to-string mapping'
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
return res.status(400).json({
|
|
149
|
-
error: 'comment_format must be a preset name string or a custom template object'
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Get current config
|
|
155
|
-
const config = req.app.get('config') || {};
|
|
156
|
-
|
|
157
|
-
// Update allowed fields
|
|
158
|
-
if (comment_button_action !== undefined) {
|
|
159
|
-
config.comment_button_action = comment_button_action;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (chat_enable_shortcuts !== undefined) {
|
|
163
|
-
if (!config.chat) config.chat = {};
|
|
164
|
-
config.chat.enable_shortcuts = chat_enable_shortcuts;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (comment_format !== undefined) {
|
|
168
|
-
config.comment_format = comment_format;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Save config to file
|
|
172
|
-
await saveConfig(config);
|
|
173
|
-
|
|
174
|
-
// Update app config
|
|
175
|
-
req.app.set('config', config);
|
|
176
|
-
|
|
177
|
-
res.json({
|
|
178
|
-
success: true,
|
|
179
|
-
config: {
|
|
180
|
-
theme: config.theme || 'light',
|
|
181
|
-
comment_button_action: config.comment_button_action || 'submit',
|
|
182
|
-
chat_enable_shortcuts: config.chat?.enable_shortcuts !== false,
|
|
183
|
-
comment_format: config.comment_format || 'legacy'
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
} catch (error) {
|
|
188
|
-
logger.error('Error updating config:', error);
|
|
189
|
-
res.status(500).json({
|
|
190
|
-
error: 'Failed to update configuration'
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
78
|
/**
|
|
196
79
|
* Get repository-specific settings
|
|
197
80
|
* Returns default_instructions, default_provider, and default_model for the repository
|
package/src/routes/councils.js
CHANGED
|
@@ -11,6 +11,7 @@ const express = require('express');
|
|
|
11
11
|
const { v4: uuidv4 } = require('uuid');
|
|
12
12
|
const logger = require('../utils/logger');
|
|
13
13
|
const { CouncilRepository } = require('../database');
|
|
14
|
+
const { getProviderClass } = require('../ai/provider');
|
|
14
15
|
|
|
15
16
|
const router = express.Router();
|
|
16
17
|
|
|
@@ -129,12 +130,20 @@ function validateCouncilFormat(config) {
|
|
|
129
130
|
return 'config.levels is required and must be an object';
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
// Skip level requirement when all voices are executable providers
|
|
134
|
+
const allExecutable = config.voices.every(v => {
|
|
135
|
+
const ProviderClass = getProviderClass(v.provider);
|
|
136
|
+
return ProviderClass?.isExecutable;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!allExecutable) {
|
|
140
|
+
const validLevels = ['1', '2', '3'];
|
|
141
|
+
const hasEnabled = Object.entries(config.levels).some(([key, val]) =>
|
|
142
|
+
validLevels.includes(key) && val === true
|
|
143
|
+
);
|
|
144
|
+
if (!hasEnabled) {
|
|
145
|
+
return 'At least one level (1, 2, or 3) must be enabled for non-executable providers';
|
|
146
|
+
}
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
// Validate consolidation (optional)
|