@in-the-loop-labs/pair-review 1.4.3 → 1.5.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 +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/review-requests/SKILL.md +54 -0
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/public/css/pr.css +1081 -54
- package/public/css/repo-settings.css +452 -140
- package/public/js/components/AdvancedConfigTab.js +1364 -0
- package/public/js/components/AnalysisConfigModal.js +488 -112
- package/public/js/components/CouncilProgressModal.js +1416 -0
- package/public/js/components/TextInputDialog.js +231 -0
- package/public/js/components/TimeoutSelect.js +367 -0
- package/public/js/components/VoiceCentricConfigTab.js +1334 -0
- package/public/js/local.js +162 -83
- package/public/js/modules/analysis-history.js +185 -11
- package/public/js/modules/comment-manager.js +13 -0
- package/public/js/modules/file-comment-manager.js +28 -0
- package/public/js/pr.js +233 -115
- package/public/js/repo-settings.js +575 -106
- package/public/local.html +11 -1
- package/public/pr.html +6 -1
- package/public/repo-settings.html +28 -21
- package/public/setup.html +8 -2
- package/src/ai/analyzer.js +1262 -111
- package/src/ai/claude-cli.js +2 -2
- package/src/ai/claude-provider.js +6 -6
- package/src/ai/codex-provider.js +6 -6
- package/src/ai/copilot-provider.js +3 -3
- package/src/ai/cursor-agent-provider.js +6 -6
- package/src/ai/gemini-provider.js +6 -6
- package/src/ai/opencode-provider.js +6 -6
- package/src/ai/pi-provider.js +6 -6
- package/src/ai/prompts/baseline/consolidation/balanced.js +208 -0
- package/src/ai/prompts/baseline/consolidation/fast.js +175 -0
- package/src/ai/prompts/baseline/consolidation/thorough.js +283 -0
- package/src/ai/prompts/config.js +1 -1
- package/src/ai/prompts/index.js +26 -2
- package/src/ai/provider.js +4 -2
- package/src/database.js +417 -14
- package/src/main.js +1 -1
- package/src/routes/analysis.js +495 -10
- package/src/routes/config.js +36 -15
- package/src/routes/councils.js +351 -0
- package/src/routes/local.js +33 -11
- package/src/routes/mcp.js +9 -2
- package/src/routes/setup.js +12 -2
- package/src/routes/shared.js +126 -13
- package/src/server.js +34 -4
- package/src/utils/stats-calculator.js +2 -0
package/src/ai/claude-cli.js
CHANGED
|
@@ -37,9 +37,9 @@ class ClaudeCLI {
|
|
|
37
37
|
*/
|
|
38
38
|
async execute(prompt, options = {}) {
|
|
39
39
|
return new Promise((resolve, reject) => {
|
|
40
|
-
const { cwd = process.cwd(), timeout = 300000, level = 'unknown' } = options; // 5 minute default timeout
|
|
40
|
+
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', logPrefix } = options; // 5 minute default timeout
|
|
41
41
|
|
|
42
|
-
const levelPrefix = `[Level ${level}]`;
|
|
42
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
43
43
|
logger.info(`${levelPrefix} Executing Claude CLI...`);
|
|
44
44
|
logger.info(`${levelPrefix} Writing prompt: ${prompt.length} bytes`);
|
|
45
45
|
|
|
@@ -241,9 +241,9 @@ class ClaudeProvider extends AIProvider {
|
|
|
241
241
|
*/
|
|
242
242
|
async execute(prompt, options = {}) {
|
|
243
243
|
return new Promise((resolve, reject) => {
|
|
244
|
-
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent } = options;
|
|
244
|
+
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent, logPrefix } = options;
|
|
245
245
|
|
|
246
|
-
const levelPrefix = `[Level ${level}]`;
|
|
246
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
247
247
|
logger.info(`${levelPrefix} Executing Claude CLI...`);
|
|
248
248
|
logger.info(`${levelPrefix} Writing prompt: ${prompt.length} bytes`);
|
|
249
249
|
|
|
@@ -359,7 +359,7 @@ class ClaudeProvider extends AIProvider {
|
|
|
359
359
|
logger.info(`${levelPrefix} Claude CLI completed: ${lineCount} JSONL events received`);
|
|
360
360
|
|
|
361
361
|
// Parse the Claude JSONL stream response
|
|
362
|
-
const parsed = this.parseClaudeResponse(stdout, level);
|
|
362
|
+
const parsed = this.parseClaudeResponse(stdout, level, levelPrefix);
|
|
363
363
|
if (parsed.success) {
|
|
364
364
|
logger.success(`${levelPrefix} Successfully parsed JSON response`);
|
|
365
365
|
// Dump the parsed data for debugging
|
|
@@ -384,7 +384,7 @@ class ClaudeProvider extends AIProvider {
|
|
|
384
384
|
// Use async IIFE to handle the async LLM extraction
|
|
385
385
|
(async () => {
|
|
386
386
|
try {
|
|
387
|
-
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess });
|
|
387
|
+
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess, logPrefix: levelPrefix });
|
|
388
388
|
if (llmExtracted.success) {
|
|
389
389
|
logger.success(`${levelPrefix} LLM extraction fallback succeeded`);
|
|
390
390
|
settle(resolve, llmExtracted.data);
|
|
@@ -633,8 +633,8 @@ class ClaudeProvider extends AIProvider {
|
|
|
633
633
|
* @param {string|number} level - Analysis level for logging
|
|
634
634
|
* @returns {{success: boolean, data?: Object, error?: string}}
|
|
635
635
|
*/
|
|
636
|
-
parseClaudeResponse(stdout, level) {
|
|
637
|
-
const levelPrefix = `[Level ${level}]`;
|
|
636
|
+
parseClaudeResponse(stdout, level, logPrefix) {
|
|
637
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
638
638
|
|
|
639
639
|
try {
|
|
640
640
|
// Split by newlines and parse each JSON line
|
package/src/ai/codex-provider.js
CHANGED
|
@@ -132,9 +132,9 @@ class CodexProvider extends AIProvider {
|
|
|
132
132
|
*/
|
|
133
133
|
async execute(prompt, options = {}) {
|
|
134
134
|
return new Promise((resolve, reject) => {
|
|
135
|
-
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent } = options;
|
|
135
|
+
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent, logPrefix } = options;
|
|
136
136
|
|
|
137
|
-
const levelPrefix = `[Level ${level}]`;
|
|
137
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
138
138
|
logger.info(`${levelPrefix} Executing Codex CLI...`);
|
|
139
139
|
logger.info(`${levelPrefix} Writing prompt: ${prompt.length} bytes`);
|
|
140
140
|
|
|
@@ -256,7 +256,7 @@ class CodexProvider extends AIProvider {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
// Parse the Codex JSONL response
|
|
259
|
-
const parsed = this.parseCodexResponse(stdout, level);
|
|
259
|
+
const parsed = this.parseCodexResponse(stdout, level, levelPrefix);
|
|
260
260
|
if (parsed.success) {
|
|
261
261
|
logger.success(`${levelPrefix} Successfully parsed JSON response`);
|
|
262
262
|
// Dump the parsed data for debugging
|
|
@@ -278,7 +278,7 @@ class CodexProvider extends AIProvider {
|
|
|
278
278
|
// Use async IIFE to handle the async LLM extraction
|
|
279
279
|
(async () => {
|
|
280
280
|
try {
|
|
281
|
-
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess });
|
|
281
|
+
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess, logPrefix: levelPrefix });
|
|
282
282
|
if (llmExtracted.success) {
|
|
283
283
|
logger.success(`${levelPrefix} LLM extraction fallback succeeded`);
|
|
284
284
|
settle(resolve, llmExtracted.data);
|
|
@@ -337,8 +337,8 @@ class CodexProvider extends AIProvider {
|
|
|
337
337
|
* @param {string|number} level - Analysis level for logging
|
|
338
338
|
* @returns {{success: boolean, data?: Object, error?: string}}
|
|
339
339
|
*/
|
|
340
|
-
parseCodexResponse(stdout, level) {
|
|
341
|
-
const levelPrefix = `[Level ${level}]`;
|
|
340
|
+
parseCodexResponse(stdout, level, logPrefix) {
|
|
341
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
342
342
|
|
|
343
343
|
try {
|
|
344
344
|
// Split by newlines and parse each JSON line
|
|
@@ -197,9 +197,9 @@ class CopilotProvider extends AIProvider {
|
|
|
197
197
|
return new Promise((resolve, reject) => {
|
|
198
198
|
// Note: Copilot does not support streaming — output is plain text returned on process exit, not JSONL.
|
|
199
199
|
// onStreamEvent is therefore not destructured here (no StreamParser integration).
|
|
200
|
-
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess } = options;
|
|
200
|
+
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, logPrefix } = options;
|
|
201
201
|
|
|
202
|
-
const levelPrefix = `[Level ${level}]`;
|
|
202
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
203
203
|
logger.info(`${levelPrefix} Executing Copilot CLI...`);
|
|
204
204
|
logger.info(`${levelPrefix} Writing prompt: ${prompt.length} bytes`);
|
|
205
205
|
|
|
@@ -310,7 +310,7 @@ class CopilotProvider extends AIProvider {
|
|
|
310
310
|
// Use async IIFE to handle the async LLM extraction
|
|
311
311
|
(async () => {
|
|
312
312
|
try {
|
|
313
|
-
const llmExtracted = await this.extractJSONWithLLM(stdout, { level, analysisId, registerProcess });
|
|
313
|
+
const llmExtracted = await this.extractJSONWithLLM(stdout, { level, analysisId, registerProcess, logPrefix: levelPrefix });
|
|
314
314
|
if (llmExtracted.success) {
|
|
315
315
|
logger.success(`${levelPrefix} LLM extraction fallback succeeded`);
|
|
316
316
|
settle(resolve, llmExtracted.data);
|
|
@@ -211,9 +211,9 @@ class CursorAgentProvider extends AIProvider {
|
|
|
211
211
|
*/
|
|
212
212
|
async execute(prompt, options = {}) {
|
|
213
213
|
return new Promise((resolve, reject) => {
|
|
214
|
-
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent } = options;
|
|
214
|
+
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent, logPrefix } = options;
|
|
215
215
|
|
|
216
|
-
const levelPrefix = `[Level ${level}]`;
|
|
216
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
217
217
|
logger.info(`${levelPrefix} Executing Cursor Agent CLI...`);
|
|
218
218
|
logger.info(`${levelPrefix} Writing prompt: ${prompt.length} chars`);
|
|
219
219
|
|
|
@@ -335,7 +335,7 @@ class CursorAgentProvider extends AIProvider {
|
|
|
335
335
|
logger.info(`${levelPrefix} Cursor Agent CLI completed: ${lineCount} JSONL events received`);
|
|
336
336
|
|
|
337
337
|
// Parse the Cursor Agent JSONL stream response
|
|
338
|
-
const parsed = this.parseCursorAgentResponse(stdout, level);
|
|
338
|
+
const parsed = this.parseCursorAgentResponse(stdout, level, levelPrefix);
|
|
339
339
|
if (parsed.success) {
|
|
340
340
|
logger.success(`${levelPrefix} Successfully parsed JSON response`);
|
|
341
341
|
// Dump the parsed data for debugging
|
|
@@ -361,7 +361,7 @@ class CursorAgentProvider extends AIProvider {
|
|
|
361
361
|
// orphan processes if timeout fired between close-handler entry
|
|
362
362
|
// and reaching this point.
|
|
363
363
|
if (settled) return;
|
|
364
|
-
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess });
|
|
364
|
+
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess, logPrefix: levelPrefix });
|
|
365
365
|
if (llmExtracted.success) {
|
|
366
366
|
logger.success(`${levelPrefix} LLM extraction fallback succeeded`);
|
|
367
367
|
settle(resolve, llmExtracted.data);
|
|
@@ -425,8 +425,8 @@ class CursorAgentProvider extends AIProvider {
|
|
|
425
425
|
* @param {string|number} level - Analysis level for logging
|
|
426
426
|
* @returns {{success: boolean, data?: Object, error?: string}}
|
|
427
427
|
*/
|
|
428
|
-
parseCursorAgentResponse(stdout, level) {
|
|
429
|
-
const levelPrefix = `[Level ${level}]`;
|
|
428
|
+
parseCursorAgentResponse(stdout, level, logPrefix) {
|
|
429
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
430
430
|
|
|
431
431
|
try {
|
|
432
432
|
// Split by newlines and parse each JSON line
|
|
@@ -174,9 +174,9 @@ class GeminiProvider extends AIProvider {
|
|
|
174
174
|
*/
|
|
175
175
|
async execute(prompt, options = {}) {
|
|
176
176
|
return new Promise((resolve, reject) => {
|
|
177
|
-
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent } = options;
|
|
177
|
+
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent, logPrefix } = options;
|
|
178
178
|
|
|
179
|
-
const levelPrefix = `[Level ${level}]`;
|
|
179
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
180
180
|
logger.info(`${levelPrefix} Executing Gemini CLI...`);
|
|
181
181
|
logger.info(`${levelPrefix} Writing prompt: ${prompt.length} bytes`);
|
|
182
182
|
|
|
@@ -298,7 +298,7 @@ class GeminiProvider extends AIProvider {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
// Parse the Gemini JSONL stream response
|
|
301
|
-
const parsed = this.parseGeminiResponse(stdout, level);
|
|
301
|
+
const parsed = this.parseGeminiResponse(stdout, level, levelPrefix);
|
|
302
302
|
if (parsed.success) {
|
|
303
303
|
logger.success(`${levelPrefix} Successfully parsed JSON response`);
|
|
304
304
|
// Dump the parsed data for debugging
|
|
@@ -320,7 +320,7 @@ class GeminiProvider extends AIProvider {
|
|
|
320
320
|
// Use async IIFE to handle the async LLM extraction
|
|
321
321
|
(async () => {
|
|
322
322
|
try {
|
|
323
|
-
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess });
|
|
323
|
+
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess, logPrefix: levelPrefix });
|
|
324
324
|
if (llmExtracted.success) {
|
|
325
325
|
logger.success(`${levelPrefix} LLM extraction fallback succeeded`);
|
|
326
326
|
settle(resolve, llmExtracted.data);
|
|
@@ -382,8 +382,8 @@ class GeminiProvider extends AIProvider {
|
|
|
382
382
|
* @param {string|number} level - Analysis level for logging
|
|
383
383
|
* @returns {{success: boolean, data?: Object, error?: string}}
|
|
384
384
|
*/
|
|
385
|
-
parseGeminiResponse(stdout, level) {
|
|
386
|
-
const levelPrefix = `[Level ${level}]`;
|
|
385
|
+
parseGeminiResponse(stdout, level, logPrefix) {
|
|
386
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
387
387
|
|
|
388
388
|
try {
|
|
389
389
|
// Split by newlines and parse each JSON line
|
|
@@ -102,9 +102,9 @@ class OpenCodeProvider extends AIProvider {
|
|
|
102
102
|
*/
|
|
103
103
|
async execute(prompt, options = {}) {
|
|
104
104
|
return new Promise((resolve, reject) => {
|
|
105
|
-
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent } = options;
|
|
105
|
+
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent, logPrefix } = options;
|
|
106
106
|
|
|
107
|
-
const levelPrefix = `[Level ${level}]`;
|
|
107
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
108
108
|
logger.info(`${levelPrefix} Executing OpenCode CLI...`);
|
|
109
109
|
logger.info(`${levelPrefix} Writing prompt via stdin: ${prompt.length} bytes`);
|
|
110
110
|
|
|
@@ -238,7 +238,7 @@ class OpenCodeProvider extends AIProvider {
|
|
|
238
238
|
logger.info(`${levelPrefix} OpenCode CLI completed - received ${lineCount} JSONL events`);
|
|
239
239
|
|
|
240
240
|
// Parse the OpenCode JSONL response
|
|
241
|
-
const parsed = this.parseOpenCodeResponse(stdout, level);
|
|
241
|
+
const parsed = this.parseOpenCodeResponse(stdout, level, levelPrefix);
|
|
242
242
|
if (parsed.success) {
|
|
243
243
|
logger.success(`${levelPrefix} Successfully parsed JSON response`);
|
|
244
244
|
|
|
@@ -262,7 +262,7 @@ class OpenCodeProvider extends AIProvider {
|
|
|
262
262
|
// Use async IIFE to handle the async LLM extraction
|
|
263
263
|
(async () => {
|
|
264
264
|
try {
|
|
265
|
-
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess });
|
|
265
|
+
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess, logPrefix: levelPrefix });
|
|
266
266
|
if (llmExtracted.success) {
|
|
267
267
|
logger.success(`${levelPrefix} LLM extraction fallback succeeded`);
|
|
268
268
|
settle(resolve, llmExtracted.data);
|
|
@@ -442,8 +442,8 @@ class OpenCodeProvider extends AIProvider {
|
|
|
442
442
|
* @param {string|number} level - Analysis level for logging
|
|
443
443
|
* @returns {{success: boolean, data?: Object, error?: string}}
|
|
444
444
|
*/
|
|
445
|
-
parseOpenCodeResponse(stdout, level) {
|
|
446
|
-
const levelPrefix = `[Level ${level}]`;
|
|
445
|
+
parseOpenCodeResponse(stdout, level, logPrefix) {
|
|
446
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
447
447
|
|
|
448
448
|
try {
|
|
449
449
|
// Split by newlines and parse each JSON line
|
package/src/ai/pi-provider.js
CHANGED
|
@@ -241,9 +241,9 @@ class PiProvider extends AIProvider {
|
|
|
241
241
|
*/
|
|
242
242
|
async execute(prompt, options = {}) {
|
|
243
243
|
return new Promise((resolve, reject) => {
|
|
244
|
-
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent } = options;
|
|
244
|
+
const { cwd = process.cwd(), timeout = 300000, level = 'unknown', analysisId, registerProcess, onStreamEvent, logPrefix } = options;
|
|
245
245
|
|
|
246
|
-
const levelPrefix = `[Level ${level}]`;
|
|
246
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
247
247
|
logger.info(`${levelPrefix} Executing Pi CLI...`);
|
|
248
248
|
logger.info(`${levelPrefix} Writing prompt via stdin: ${prompt.length} bytes`);
|
|
249
249
|
|
|
@@ -388,7 +388,7 @@ class PiProvider extends AIProvider {
|
|
|
388
388
|
logger.info(`${levelPrefix} Pi CLI completed - received ${lineCount} JSONL events`);
|
|
389
389
|
|
|
390
390
|
// Parse the Pi JSONL response
|
|
391
|
-
const parsed = this.parsePiResponse(stdout, level);
|
|
391
|
+
const parsed = this.parsePiResponse(stdout, level, levelPrefix);
|
|
392
392
|
if (parsed.success) {
|
|
393
393
|
logger.success(`${levelPrefix} Successfully parsed JSON response`);
|
|
394
394
|
|
|
@@ -419,7 +419,7 @@ class PiProvider extends AIProvider {
|
|
|
419
419
|
if (settled) return;
|
|
420
420
|
|
|
421
421
|
try {
|
|
422
|
-
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess });
|
|
422
|
+
const llmExtracted = await this.extractJSONWithLLM(llmFallbackInput, { level, analysisId, registerProcess, logPrefix: levelPrefix });
|
|
423
423
|
if (llmExtracted.success) {
|
|
424
424
|
logger.success(`${levelPrefix} LLM extraction fallback succeeded`);
|
|
425
425
|
settle(resolve, llmExtracted.data);
|
|
@@ -612,8 +612,8 @@ class PiProvider extends AIProvider {
|
|
|
612
612
|
* @param {string|number} level - Analysis level for logging
|
|
613
613
|
* @returns {{success: boolean, data?: Object, error?: string}}
|
|
614
614
|
*/
|
|
615
|
-
parsePiResponse(stdout, level) {
|
|
616
|
-
const levelPrefix = `[Level ${level}]`;
|
|
615
|
+
parsePiResponse(stdout, level, logPrefix) {
|
|
616
|
+
const levelPrefix = logPrefix || `[Level ${level}]`;
|
|
617
617
|
|
|
618
618
|
try {
|
|
619
619
|
// Split by newlines and parse each JSON line
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* Consolidation Balanced Prompt - Cross-Reviewer Suggestion Merging
|
|
4
|
+
*
|
|
5
|
+
* This is the canonical baseline prompt for Consolidation analysis.
|
|
6
|
+
* It merges suggestions from multiple independent AI reviewers who
|
|
7
|
+
* analyzed the same code changes, deduplicating and resolving conflicts.
|
|
8
|
+
*
|
|
9
|
+
* Unlike Orchestration (which merges across analysis levels 1/2/3),
|
|
10
|
+
* Consolidation merges across reviewers within the same scope.
|
|
11
|
+
*
|
|
12
|
+
* Section categories:
|
|
13
|
+
* - locked: Cannot be modified by variants (data integrity)
|
|
14
|
+
* - required: Must be present, content can be rephrased
|
|
15
|
+
* - optional: Can be removed entirely if unhelpful
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tagged prompt template for Consolidation Balanced analysis
|
|
20
|
+
*
|
|
21
|
+
* Placeholders:
|
|
22
|
+
* - {{reviewIntro}} - Review introduction line
|
|
23
|
+
* - {{lineNumberGuidance}} - Line number guidance section
|
|
24
|
+
* - {{customInstructions}} - Custom instructions section (optional)
|
|
25
|
+
* - {{reviewerSuggestions}} - Formatted reviewer suggestions input
|
|
26
|
+
* - {{suggestionCount}} - Total number of input suggestions
|
|
27
|
+
* - {{reviewerCount}} - Number of reviewers being consolidated
|
|
28
|
+
*/
|
|
29
|
+
const taggedPrompt = `<section name="role" required="true">
|
|
30
|
+
{{reviewIntro}}
|
|
31
|
+
</section>
|
|
32
|
+
|
|
33
|
+
<section name="task-header" required="true">
|
|
34
|
+
# Cross-Reviewer Consolidation Task
|
|
35
|
+
</section>
|
|
36
|
+
|
|
37
|
+
<section name="line-number-guidance" required="true">
|
|
38
|
+
{{lineNumberGuidance}}
|
|
39
|
+
</section>
|
|
40
|
+
|
|
41
|
+
<section name="critical-output" locked="true">
|
|
42
|
+
**>>> CRITICAL: Output ONLY valid JSON. No markdown, no \`\`\`json blocks. Start with { end with }. <<<**
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<section name="role-description" required="true">
|
|
46
|
+
## Your Role
|
|
47
|
+
Multiple independent AI reviewers have analyzed the same code changes. Your job is to merge their findings into a single, high-quality set of suggestions by deduplicating, resolving conflicts, and preserving unique insights.
|
|
48
|
+
</section>
|
|
49
|
+
|
|
50
|
+
<section name="custom-instructions" optional="true">
|
|
51
|
+
{{customInstructions}}
|
|
52
|
+
</section>
|
|
53
|
+
|
|
54
|
+
<section name="input-suggestions" locked="true">
|
|
55
|
+
## Input: {{reviewerCount}} Reviewer(s), {{suggestionCount}} Total Suggestions
|
|
56
|
+
|
|
57
|
+
{{reviewerSuggestions}}
|
|
58
|
+
</section>
|
|
59
|
+
|
|
60
|
+
<section name="consolidation-rules" required="true">
|
|
61
|
+
## Consolidation Guidelines
|
|
62
|
+
|
|
63
|
+
### 1. Deduplication
|
|
64
|
+
- **Merge suggestions** that identify the same issue at the same location
|
|
65
|
+
- When merging, combine the best elements from each reviewer's description
|
|
66
|
+
- Use the most specific and actionable framing
|
|
67
|
+
|
|
68
|
+
### 2. Conflict Resolution
|
|
69
|
+
- When reviewers **disagree**, prefer the analysis with stronger evidence
|
|
70
|
+
- If genuinely uncertain, keep the suggestion with reduced confidence
|
|
71
|
+
- Consider whether one reviewer had context the other missed
|
|
72
|
+
|
|
73
|
+
### 3. Unique Insights
|
|
74
|
+
- **Preserve suggestions** that only one reviewer noticed
|
|
75
|
+
- A unique finding from one reviewer can be the most valuable insight
|
|
76
|
+
- Don't discard something just because only one reviewer flagged it
|
|
77
|
+
|
|
78
|
+
### 4. Quality Filter
|
|
79
|
+
- Drop suggestions with very low confidence (< 0.3) unless multiple reviewers agree
|
|
80
|
+
- Boost confidence when multiple reviewers independently identify the same issue
|
|
81
|
+
</section>
|
|
82
|
+
|
|
83
|
+
<section name="consensus-handling" required="true">
|
|
84
|
+
### 5. Consensus Handling
|
|
85
|
+
- **Agreement**: When multiple reviewers flag the same issue, increase confidence by 0.1-0.2 (cap at 1.0)
|
|
86
|
+
- **Partial overlap**: Merge related but distinct observations into a richer suggestion
|
|
87
|
+
- **Contradiction**: Use your judgment; prefer the more actionable analysis
|
|
88
|
+
</section>
|
|
89
|
+
|
|
90
|
+
<section name="output-schema" locked="true">
|
|
91
|
+
## Output Format
|
|
92
|
+
|
|
93
|
+
**>>> CRITICAL: Output ONLY valid JSON. No markdown, no \`\`\`json blocks. Start with { end with }. <<<**
|
|
94
|
+
|
|
95
|
+
Output JSON with this structure:
|
|
96
|
+
{
|
|
97
|
+
"suggestions": [
|
|
98
|
+
{
|
|
99
|
+
"file": "path/to/file",
|
|
100
|
+
"line": 42,
|
|
101
|
+
"old_or_new": "NEW",
|
|
102
|
+
"type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
|
|
103
|
+
"title": "Brief title",
|
|
104
|
+
"description": "Detailed explanation",
|
|
105
|
+
"suggestion": "How to fix/improve (omit for praise)",
|
|
106
|
+
"confidence": 0.0-1.0
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"fileLevelSuggestions": [{
|
|
110
|
+
"file": "path/to/file",
|
|
111
|
+
"type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
|
|
112
|
+
"title": "Brief title describing file-level concern",
|
|
113
|
+
"description": "Explanation of the file-level observation",
|
|
114
|
+
"suggestion": "How to address the file-level concern (omit for praise items)",
|
|
115
|
+
"confidence": 0.0-1.0
|
|
116
|
+
}],
|
|
117
|
+
"summary": "Brief consolidation summary. Write as if a single reviewer produced this analysis — do NOT mention 'consolidation', 'merging', or 'multiple reviewers'."
|
|
118
|
+
}
|
|
119
|
+
</section>
|
|
120
|
+
|
|
121
|
+
<section name="diff-instructions" required="true">
|
|
122
|
+
## Line Number Reference (old_or_new field)
|
|
123
|
+
- **"NEW"** (default): For added [+] and context lines
|
|
124
|
+
- **"OLD"**: ONLY for deleted [-] lines
|
|
125
|
+
Preserve the old_or_new value from input suggestions when merging.
|
|
126
|
+
</section>
|
|
127
|
+
|
|
128
|
+
<section name="guidelines" required="true">
|
|
129
|
+
## Important Notes
|
|
130
|
+
- **Quality over quantity** — better to have fewer excellent suggestions than many mediocre ones
|
|
131
|
+
- **Cross-reviewer agreement** increases confidence significantly
|
|
132
|
+
- **Preserve actionability** — every suggestion should give clear next steps
|
|
133
|
+
- **Only include modified files** — discard suggestions for unmodified files
|
|
134
|
+
</section>`;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Section definitions with metadata
|
|
138
|
+
* Used for parsing and validation
|
|
139
|
+
*/
|
|
140
|
+
const sections = [
|
|
141
|
+
{ name: 'role', required: true },
|
|
142
|
+
{ name: 'task-header', required: true },
|
|
143
|
+
{ name: 'line-number-guidance', required: true },
|
|
144
|
+
{ name: 'critical-output', locked: true },
|
|
145
|
+
{ name: 'role-description', required: true },
|
|
146
|
+
{ name: 'custom-instructions', optional: true },
|
|
147
|
+
{ name: 'input-suggestions', locked: true },
|
|
148
|
+
{ name: 'consolidation-rules', required: true },
|
|
149
|
+
{ name: 'consensus-handling', required: true },
|
|
150
|
+
{ name: 'output-schema', locked: true },
|
|
151
|
+
{ name: 'diff-instructions', required: true },
|
|
152
|
+
{ name: 'guidelines', required: true }
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Default section order for Consolidation Balanced
|
|
157
|
+
*/
|
|
158
|
+
const defaultOrder = [
|
|
159
|
+
'role',
|
|
160
|
+
'task-header',
|
|
161
|
+
'line-number-guidance',
|
|
162
|
+
'critical-output',
|
|
163
|
+
'role-description',
|
|
164
|
+
'custom-instructions',
|
|
165
|
+
'input-suggestions',
|
|
166
|
+
'consolidation-rules',
|
|
167
|
+
'consensus-handling',
|
|
168
|
+
'output-schema',
|
|
169
|
+
'diff-instructions',
|
|
170
|
+
'guidelines'
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Parse the tagged prompt into section objects
|
|
175
|
+
* @returns {Array<Object>} Array of section objects with name, attributes, and content
|
|
176
|
+
*/
|
|
177
|
+
function parseSections() {
|
|
178
|
+
const sectionRegex = /<section\s+name="([^"]+)"([^>]*)>([\s\S]*?)<\/section>/g;
|
|
179
|
+
const parsed = [];
|
|
180
|
+
let match;
|
|
181
|
+
|
|
182
|
+
while ((match = sectionRegex.exec(taggedPrompt)) !== null) {
|
|
183
|
+
const [, name, attrs, content] = match;
|
|
184
|
+
const section = {
|
|
185
|
+
name,
|
|
186
|
+
content: content.trim(),
|
|
187
|
+
locked: attrs.includes('locked="true"'),
|
|
188
|
+
required: attrs.includes('required="true"'),
|
|
189
|
+
optional: attrs.includes('optional="true"')
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const tierMatch = attrs.match(/tier="([^"]+)"/);
|
|
193
|
+
if (tierMatch) {
|
|
194
|
+
section.tier = tierMatch[1].split(',').map(t => t.trim());
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
parsed.push(section);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return parsed;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
taggedPrompt,
|
|
205
|
+
sections,
|
|
206
|
+
defaultOrder,
|
|
207
|
+
parseSections
|
|
208
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* Consolidation Fast Prompt - Quick Cross-Reviewer Suggestion Merging
|
|
4
|
+
*
|
|
5
|
+
* This is the fast tier variant of Consolidation analysis. It is optimized
|
|
6
|
+
* for speed with shorter, more directive prompts.
|
|
7
|
+
*
|
|
8
|
+
* Tier-specific optimizations applied:
|
|
9
|
+
* - Simplified: consolidation-rules to essential directives
|
|
10
|
+
* - Removed: consensus-handling section (folded into rules)
|
|
11
|
+
* - Shortened: guidelines to essentials
|
|
12
|
+
*
|
|
13
|
+
* Section categories:
|
|
14
|
+
* - locked: Cannot be modified by variants (data integrity)
|
|
15
|
+
* - required: Must be present, content can be rephrased
|
|
16
|
+
* - optional: Can be removed entirely if unhelpful
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tagged prompt template for Consolidation Fast analysis
|
|
21
|
+
*
|
|
22
|
+
* Placeholders:
|
|
23
|
+
* - {{reviewIntro}} - Review introduction line
|
|
24
|
+
* - {{lineNumberGuidance}} - Line number guidance section
|
|
25
|
+
* - {{customInstructions}} - Custom instructions section (optional)
|
|
26
|
+
* - {{reviewerSuggestions}} - Formatted reviewer suggestions input
|
|
27
|
+
* - {{suggestionCount}} - Total number of input suggestions
|
|
28
|
+
* - {{reviewerCount}} - Number of reviewers being consolidated
|
|
29
|
+
*/
|
|
30
|
+
const taggedPrompt = `<section name="role" required="true" tier="fast">
|
|
31
|
+
{{reviewIntro}}
|
|
32
|
+
</section>
|
|
33
|
+
|
|
34
|
+
<section name="task-header" required="true" tier="fast">
|
|
35
|
+
# Cross-Reviewer Consolidation
|
|
36
|
+
</section>
|
|
37
|
+
|
|
38
|
+
<section name="line-number-guidance" required="true">
|
|
39
|
+
{{lineNumberGuidance}}
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
<section name="critical-output" locked="true">
|
|
43
|
+
**>>> CRITICAL: Output ONLY valid JSON. No markdown, no \`\`\`json blocks. Start with { end with }. <<<**
|
|
44
|
+
</section>
|
|
45
|
+
|
|
46
|
+
<section name="role-description" required="true" tier="fast">
|
|
47
|
+
## Task
|
|
48
|
+
Merge suggestions from multiple AI reviewers. Deduplicate. Resolve conflicts. Keep high-value items only.
|
|
49
|
+
</section>
|
|
50
|
+
|
|
51
|
+
<section name="custom-instructions" optional="true" tier="fast,balanced,thorough">
|
|
52
|
+
{{customInstructions}}
|
|
53
|
+
</section>
|
|
54
|
+
|
|
55
|
+
<section name="input-suggestions" locked="true">
|
|
56
|
+
## Input: {{reviewerCount}} Reviewer(s), {{suggestionCount}} Total Suggestions
|
|
57
|
+
|
|
58
|
+
{{reviewerSuggestions}}
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<section name="consolidation-rules" required="true" tier="fast">
|
|
62
|
+
## Rules
|
|
63
|
+
- Merge duplicate suggestions (same file/line/issue). Boost confidence for consensus.
|
|
64
|
+
- When reviewers disagree, keep the more specific analysis.
|
|
65
|
+
- Preserve unique insights from individual reviewers.
|
|
66
|
+
- Drop very low confidence (< 0.3) items unless multiple reviewers agree.
|
|
67
|
+
</section>
|
|
68
|
+
|
|
69
|
+
<section name="output-schema" locked="true">
|
|
70
|
+
## JSON Schema
|
|
71
|
+
{
|
|
72
|
+
"suggestions": [{
|
|
73
|
+
"file": "path/to/file",
|
|
74
|
+
"line": 42,
|
|
75
|
+
"old_or_new": "NEW",
|
|
76
|
+
"type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
|
|
77
|
+
"title": "Brief title",
|
|
78
|
+
"description": "Detailed explanation",
|
|
79
|
+
"suggestion": "How to fix/improve (omit for praise)",
|
|
80
|
+
"confidence": 0.0-1.0
|
|
81
|
+
}],
|
|
82
|
+
"fileLevelSuggestions": [{
|
|
83
|
+
"file": "path/to/file",
|
|
84
|
+
"type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
|
|
85
|
+
"title": "Brief title describing file-level concern",
|
|
86
|
+
"description": "Explanation of the file-level observation",
|
|
87
|
+
"suggestion": "How to address the file-level concern (omit for praise items)",
|
|
88
|
+
"confidence": 0.0-1.0
|
|
89
|
+
}],
|
|
90
|
+
"summary": "Key findings as if from single reviewer (no mention of consolidation/merging)"
|
|
91
|
+
}
|
|
92
|
+
</section>
|
|
93
|
+
|
|
94
|
+
<section name="diff-instructions" required="true" tier="fast">
|
|
95
|
+
## old_or_new
|
|
96
|
+
"NEW" (default): added [+] and context lines. "OLD": deleted [-] only. Preserve from input.
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<section name="guidelines" required="true" tier="fast">
|
|
100
|
+
## Notes
|
|
101
|
+
Quality over quantity. Higher confidence for multi-reviewer agreement. Only modified files. Omit uncertain suggestions.
|
|
102
|
+
</section>`;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Section definitions with metadata
|
|
106
|
+
* Used for parsing and validation
|
|
107
|
+
*/
|
|
108
|
+
const sections = [
|
|
109
|
+
{ name: 'role', required: true, tier: ['fast'] },
|
|
110
|
+
{ name: 'task-header', required: true, tier: ['fast'] },
|
|
111
|
+
{ name: 'line-number-guidance', required: true },
|
|
112
|
+
{ name: 'critical-output', locked: true },
|
|
113
|
+
{ name: 'role-description', required: true, tier: ['fast'] },
|
|
114
|
+
{ name: 'custom-instructions', optional: true, tier: ['fast', 'balanced', 'thorough'] },
|
|
115
|
+
{ name: 'input-suggestions', locked: true },
|
|
116
|
+
{ name: 'consolidation-rules', required: true, tier: ['fast'] },
|
|
117
|
+
{ name: 'output-schema', locked: true },
|
|
118
|
+
{ name: 'diff-instructions', required: true, tier: ['fast'] },
|
|
119
|
+
{ name: 'guidelines', required: true, tier: ['fast'] }
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Default section order for Consolidation Fast
|
|
124
|
+
* Note: Removed consensus-handling section
|
|
125
|
+
*/
|
|
126
|
+
const defaultOrder = [
|
|
127
|
+
'role',
|
|
128
|
+
'task-header',
|
|
129
|
+
'line-number-guidance',
|
|
130
|
+
'critical-output',
|
|
131
|
+
'role-description',
|
|
132
|
+
'custom-instructions',
|
|
133
|
+
'input-suggestions',
|
|
134
|
+
'consolidation-rules',
|
|
135
|
+
'output-schema',
|
|
136
|
+
'diff-instructions',
|
|
137
|
+
'guidelines'
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse the tagged prompt into section objects
|
|
142
|
+
* @returns {Array<Object>} Array of section objects with name, attributes, and content
|
|
143
|
+
*/
|
|
144
|
+
function parseSections() {
|
|
145
|
+
const sectionRegex = /<section\s+name="([^"]+)"([^>]*)>([\s\S]*?)<\/section>/g;
|
|
146
|
+
const parsed = [];
|
|
147
|
+
let match;
|
|
148
|
+
|
|
149
|
+
while ((match = sectionRegex.exec(taggedPrompt)) !== null) {
|
|
150
|
+
const [, name, attrs, content] = match;
|
|
151
|
+
const section = {
|
|
152
|
+
name,
|
|
153
|
+
content: content.trim(),
|
|
154
|
+
locked: attrs.includes('locked="true"'),
|
|
155
|
+
required: attrs.includes('required="true"'),
|
|
156
|
+
optional: attrs.includes('optional="true"')
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const tierMatch = attrs.match(/tier="([^"]+)"/);
|
|
160
|
+
if (tierMatch) {
|
|
161
|
+
section.tier = tierMatch[1].split(',').map(t => t.trim());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
parsed.push(section);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = {
|
|
171
|
+
taggedPrompt,
|
|
172
|
+
sections,
|
|
173
|
+
defaultOrder,
|
|
174
|
+
parseSections
|
|
175
|
+
};
|