@in-the-loop-labs/pair-review 3.0.5 → 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.
Files changed (79) hide show
  1. package/package.json +2 -1
  2. package/plugin/.claude-plugin/plugin.json +1 -1
  3. package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
  4. package/plugin-code-critic/skills/analyze/references/level1-balanced.md +8 -0
  5. package/plugin-code-critic/skills/analyze/references/level1-fast.md +7 -0
  6. package/plugin-code-critic/skills/analyze/references/level1-thorough.md +8 -0
  7. package/plugin-code-critic/skills/analyze/references/level2-balanced.md +9 -0
  8. package/plugin-code-critic/skills/analyze/references/level2-fast.md +8 -0
  9. package/plugin-code-critic/skills/analyze/references/level2-thorough.md +9 -0
  10. package/plugin-code-critic/skills/analyze/references/level3-balanced.md +9 -0
  11. package/plugin-code-critic/skills/analyze/references/level3-fast.md +8 -0
  12. package/plugin-code-critic/skills/analyze/references/level3-thorough.md +9 -0
  13. package/plugin-code-critic/skills/analyze/references/orchestration-balanced.md +9 -0
  14. package/plugin-code-critic/skills/analyze/references/orchestration-fast.md +5 -0
  15. package/plugin-code-critic/skills/analyze/references/orchestration-thorough.md +9 -0
  16. package/public/css/analysis-config.css +83 -0
  17. package/public/css/pr.css +191 -4
  18. package/public/index.html +20 -0
  19. package/public/js/components/AIPanel.js +1 -1
  20. package/public/js/components/AdvancedConfigTab.js +83 -8
  21. package/public/js/components/AnalysisConfigModal.js +155 -5
  22. package/public/js/components/ChatPanel.js +22 -5
  23. package/public/js/components/CouncilProgressModal.js +239 -22
  24. package/public/js/components/TimeoutSelect.js +2 -0
  25. package/public/js/components/VoiceCentricConfigTab.js +179 -12
  26. package/public/js/index.js +119 -1
  27. package/public/js/local.js +141 -47
  28. package/public/js/modules/suggestion-manager.js +2 -1
  29. package/public/js/pr.js +71 -12
  30. package/public/js/repo-settings.js +2 -2
  31. package/public/local.html +32 -11
  32. package/public/pr.html +2 -0
  33. package/src/ai/analyzer.js +371 -111
  34. package/src/ai/claude-provider.js +2 -0
  35. package/src/ai/codex-provider.js +1 -1
  36. package/src/ai/copilot-provider.js +2 -0
  37. package/src/ai/executable-provider.js +534 -0
  38. package/src/ai/gemini-provider.js +2 -0
  39. package/src/ai/index.js +9 -1
  40. package/src/ai/pi-provider.js +10 -8
  41. package/src/ai/prompts/baseline/consolidation/balanced.js +54 -2
  42. package/src/ai/prompts/baseline/consolidation/fast.js +31 -1
  43. package/src/ai/prompts/baseline/consolidation/thorough.js +46 -3
  44. package/src/ai/prompts/baseline/level1/balanced.js +12 -0
  45. package/src/ai/prompts/baseline/level1/fast.js +11 -0
  46. package/src/ai/prompts/baseline/level1/thorough.js +12 -0
  47. package/src/ai/prompts/baseline/level2/balanced.js +13 -0
  48. package/src/ai/prompts/baseline/level2/fast.js +12 -0
  49. package/src/ai/prompts/baseline/level2/thorough.js +13 -0
  50. package/src/ai/prompts/baseline/level3/balanced.js +13 -0
  51. package/src/ai/prompts/baseline/level3/fast.js +12 -0
  52. package/src/ai/prompts/baseline/level3/thorough.js +13 -0
  53. package/src/ai/prompts/baseline/orchestration/balanced.js +15 -0
  54. package/src/ai/prompts/baseline/orchestration/fast.js +11 -0
  55. package/src/ai/prompts/baseline/orchestration/thorough.js +15 -0
  56. package/src/ai/prompts/render-for-skill.js +3 -0
  57. package/src/ai/prompts/shared/output-schema.js +8 -0
  58. package/src/ai/provider.js +89 -4
  59. package/src/chat/prompt-builder.js +17 -1
  60. package/src/chat/session-manager.js +32 -28
  61. package/src/config.js +15 -2
  62. package/src/database.js +59 -15
  63. package/src/git/base-branch.js +133 -52
  64. package/src/local-review.js +15 -9
  65. package/src/main.js +3 -2
  66. package/src/routes/analyses.js +34 -8
  67. package/src/routes/chat.js +15 -8
  68. package/src/routes/config.js +3 -120
  69. package/src/routes/councils.js +15 -6
  70. package/src/routes/executable-analysis.js +494 -0
  71. package/src/routes/local.js +160 -26
  72. package/src/routes/mcp.js +9 -4
  73. package/src/routes/pr.js +166 -29
  74. package/src/routes/reviews.js +31 -5
  75. package/src/routes/shared.js +72 -5
  76. package/src/routes/worktrees.js +4 -2
  77. package/src/utils/comment-formatter.js +28 -11
  78. package/src/utils/instructions.js +22 -8
  79. package/src/utils/logger.js +20 -10
@@ -96,6 +96,14 @@ Do NOT modify files.
96
96
  {{sparseCheckoutGuidance}}
97
97
  </section>
98
98
 
99
+ <section name="severity-classification" required="true" tier="fast">
100
+ ### Severity
101
+ Assign severity to each suggestion (omit for praise):
102
+ - **critical**: Production incidents, crashes, security vulnerabilities, data corruption/loss, race conditions, breaking changes, test failures
103
+ - **medium**: Degraded functionality, missing error handling, missing validation, missing/poor test coverage
104
+ - **minor**: Code quality, docs gaps, minor optimizations
105
+ </section>
106
+
99
107
  <section name="output-schema" locked="true">
100
108
  ## Output Format
101
109
 
@@ -109,6 +117,7 @@ Output JSON with this structure:
109
117
  "line": 42,
110
118
  "old_or_new": "NEW",
111
119
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
120
+ "severity": "critical|medium|minor (omit for praise)",
112
121
  "title": "Brief title",
113
122
  "description": "Detailed explanation mentioning why codebase context was needed",
114
123
  "suggestion": "How to fix/improve based on codebase context (omit for praise items)",
@@ -117,6 +126,7 @@ Output JSON with this structure:
117
126
  "fileLevelSuggestions": [{
118
127
  "file": "path/to/file",
119
128
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
129
+ "severity": "critical|medium|minor (omit for praise)",
120
130
  "title": "Brief title describing file-level concern",
121
131
  "description": "Explanation of the file-level observation from codebase perspective",
122
132
  "suggestion": "How to address the file-level concern (omit for praise items)",
@@ -169,6 +179,7 @@ const sections = [
169
179
  { name: 'focus-areas', required: true, tier: ['fast'] },
170
180
  { name: 'available-commands', required: true, tier: ['fast'] },
171
181
  { name: 'sparse-checkout', optional: true, tier: ['fast', 'balanced', 'thorough'] },
182
+ { name: 'severity-classification', required: true, tier: ['fast'] },
172
183
  { name: 'output-schema', locked: true },
173
184
  { name: 'diff-instructions', required: true, tier: ['fast'] },
174
185
  { name: 'guidelines', required: true, tier: ['fast'] }
@@ -191,6 +202,7 @@ const defaultOrder = [
191
202
  'focus-areas',
192
203
  'available-commands',
193
204
  'sparse-checkout',
205
+ 'severity-classification',
194
206
  'output-schema',
195
207
  'diff-instructions',
196
208
  'guidelines'
@@ -238,6 +238,15 @@ Note: You may optionally use parallel read-only Tasks to explore different areas
238
238
  {{sparseCheckoutGuidance}}
239
239
  </section>
240
240
 
241
+ <section name="severity-classification" required="true">
242
+ ### Severity Classification
243
+ Assign a severity to each suggestion (except praise):
244
+ - **critical**: Production incidents, system failures, or security vulnerabilities — runtime crashes, data corruption or loss, race conditions, deadlocks, breaking changes, changes that will cause existing tests to fail
245
+ - **medium**: Degraded functionality or reliability — missing error handling, N+1 queries, missing validation, missing or poor test coverage for new functionality
246
+ - **minor**: Code quality concerns — documentation gaps, minor optimizations, style inconsistencies
247
+ Omit severity for praise items.
248
+ </section>
249
+
241
250
  <section name="output-schema" locked="true">
242
251
  ## Output Format
243
252
 
@@ -251,6 +260,7 @@ Output JSON with this structure:
251
260
  "line": 42,
252
261
  "old_or_new": "NEW",
253
262
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
263
+ "severity": "critical|medium|minor (omit for praise)",
254
264
  "title": "Brief title",
255
265
  "description": "Detailed explanation mentioning why codebase context was needed",
256
266
  "suggestion": "How to fix/improve based on codebase context (omit for praise items)",
@@ -260,6 +270,7 @@ Output JSON with this structure:
260
270
  "fileLevelSuggestions": [{
261
271
  "file": "path/to/file",
262
272
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
273
+ "severity": "critical|medium|minor (omit for praise)",
263
274
  "title": "Brief title describing file-level concern",
264
275
  "description": "Explanation of the file-level observation from codebase perspective",
265
276
  "suggestion": "How to address the file-level concern (omit for praise items)",
@@ -403,6 +414,7 @@ const sections = [
403
414
  { name: 'focus-areas', required: true, tier: ['thorough'] },
404
415
  { name: 'available-commands', required: true, tier: ['thorough'] },
405
416
  { name: 'sparse-checkout', optional: true, tier: ['fast', 'balanced', 'thorough'] },
417
+ { name: 'severity-classification', required: true },
406
418
  { name: 'output-schema', locked: true },
407
419
  { name: 'diff-instructions', required: true, tier: ['thorough'] },
408
420
  { name: 'confidence-guidance', required: true, tier: ['thorough'] },
@@ -429,6 +441,7 @@ const defaultOrder = [
429
441
  'focus-areas',
430
442
  'available-commands',
431
443
  'sparse-checkout',
444
+ 'severity-classification',
432
445
  'output-schema',
433
446
  'diff-instructions',
434
447
  'confidence-guidance',
@@ -25,6 +25,7 @@ const { ORCHESTRATION_INPUT_SCHEMA_DOCS } = require('../../shared/output-schema'
25
25
  * - {{reviewIntro}} - Review introduction line
26
26
  * - {{prContext}} - PR context section (optional, may be empty)
27
27
  * - {{customInstructions}} - Custom instructions section (optional)
28
+ * - {{dedupInstructions}} - Dedup instructions section (optional)
28
29
  * - {{lineNumberGuidance}} - Line number guidance section
29
30
  * - {{level1Count}} - Number of Level 1 suggestions
30
31
  * - {{level2Count}} - Number of Level 2 suggestions
@@ -58,6 +59,10 @@ You are helping a human reviewer by intelligently curating and merging suggestio
58
59
  {{customInstructions}}
59
60
  </section>
60
61
 
62
+ <section name="dedup-instructions" optional="true">
63
+ {{dedupInstructions}}
64
+ </section>
65
+
61
66
  <section name="input-suggestions" locked="true">
62
67
  ## Input: Multi-Level Analysis Results
63
68
 
@@ -82,6 +87,12 @@ ${ORCHESTRATION_INPUT_SCHEMA_DOCS}
82
87
  - **Preserve unique insights** that only one level discovered
83
88
  - **Prefer preserving line-level suggestions** over file-level suggestions when curating
84
89
  - **Do NOT mention which level found the issue** - focus on the insight itself
90
+ - **Assess severity** based on the evidence and reasoning across input levels. When levels assign different severities, evaluate the supporting evidence rather than defaulting to the highest. When truly uncertain, preserve the highest severity. Omit severity for praise items.
91
+
92
+ **Severity Definitions:**
93
+ - **critical**: Production incidents, system failures, or security vulnerabilities — runtime crashes, data corruption or loss, race conditions, deadlocks, breaking changes, changes that will cause existing tests to fail
94
+ - **medium**: Degraded functionality or reliability — missing error handling, N+1 queries, missing validation, missing or poor test coverage for new functionality
95
+ - **minor**: Code quality concerns — documentation gaps, minor optimizations, style inconsistencies
85
96
  </section>
86
97
 
87
98
  <section name="priority-curation" required="true">
@@ -123,6 +134,7 @@ Output JSON with this structure:
123
134
  "line": 42,
124
135
  "old_or_new": "NEW",
125
136
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
137
+ "severity": "critical|medium|minor (omit for praise)",
126
138
  "title": "Brief title describing the curated insight",
127
139
  "description": "Clear explanation of the issue and why this guidance matters to the human reviewer",
128
140
  "suggestion": "Specific, actionable guidance for the reviewer. For praise items this can be omitted. For other types always include specific, actionable suggestions.",
@@ -132,6 +144,7 @@ Output JSON with this structure:
132
144
  "fileLevelSuggestions": [{
133
145
  "file": "path/to/file",
134
146
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
147
+ "severity": "critical|medium|minor (omit for praise)",
135
148
  "title": "Brief title describing file-level concern",
136
149
  "description": "Explanation of the file-level observation",
137
150
  "suggestion": "How to address the file-level concern (omit for praise items)",
@@ -199,6 +212,7 @@ const sections = [
199
212
  { name: 'critical-output', locked: true },
200
213
  { name: 'role-description', required: true },
201
214
  { name: 'custom-instructions', optional: true },
215
+ { name: 'dedup-instructions', optional: true },
202
216
  { name: 'input-suggestions', locked: true },
203
217
  { name: 'intelligent-merging', required: true },
204
218
  { name: 'priority-curation', required: true },
@@ -220,6 +234,7 @@ const defaultOrder = [
220
234
  'critical-output',
221
235
  'role-description',
222
236
  'custom-instructions',
237
+ 'dedup-instructions',
223
238
  'input-suggestions',
224
239
  'intelligent-merging',
225
240
  'priority-curation',
@@ -28,6 +28,7 @@ const { ORCHESTRATION_INPUT_SCHEMA_DOCS } = require('../../shared/output-schema'
28
28
  * - {{reviewIntro}} - Review introduction line
29
29
  * - {{prContext}} - PR context section (optional, may be empty)
30
30
  * - {{customInstructions}} - Custom instructions section (optional)
31
+ * - {{dedupInstructions}} - Dedup instructions section (optional)
31
32
  * - {{lineNumberGuidance}} - Line number guidance section
32
33
  * - {{level1Count}} - Number of Level 1 suggestions
33
34
  * - {{level2Count}} - Number of Level 2 suggestions
@@ -61,6 +62,10 @@ Curate and merge 3-level suggestions. Remove duplicates. Keep high-value items o
61
62
  {{customInstructions}}
62
63
  </section>
63
64
 
65
+ <section name="dedup-instructions" optional="true">
66
+ {{dedupInstructions}}
67
+ </section>
68
+
64
69
  <section name="input-suggestions" locked="true">
65
70
  ## Input: Multi-Level Analysis Results
66
71
 
@@ -79,6 +84,8 @@ ${ORCHESTRATION_INPUT_SCHEMA_DOCS}
79
84
  <section name="intelligent-merging" required="true" tier="fast">
80
85
  ## Rules
81
86
  Combine related suggestions. Merge overlaps. Preserve unique insights. Never mention levels.
87
+ Assess severity using evidence across levels; preserve highest when uncertain. Omit for praise.
88
+ Severity: critical (crashes, security, data loss, race conditions, breaking changes, test failures), medium (degraded functionality, missing error handling/validation/test coverage), minor (code quality, docs, optimizations).
82
89
  </section>
83
90
 
84
91
  <section name="priority-curation" required="true" tier="fast">
@@ -105,6 +112,7 @@ Use "Consider...", "Worth noting..." - guidance not mandates.
105
112
  "line": 42,
106
113
  "old_or_new": "NEW",
107
114
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
115
+ "severity": "critical|medium|minor (omit for praise)",
108
116
  "title": "Brief title",
109
117
  "description": "Why it matters",
110
118
  "suggestion": "What to do (omit for praise)",
@@ -113,6 +121,7 @@ Use "Consider...", "Worth noting..." - guidance not mandates.
113
121
  "fileLevelSuggestions": [{
114
122
  "file": "path/to/file",
115
123
  "type": "...",
124
+ "severity": "critical|medium|minor (omit for praise)",
116
125
  "title": "Brief title",
117
126
  "description": "File-level observation",
118
127
  "suggestion": "How to fix (omit for praise)",
@@ -152,6 +161,7 @@ const sections = [
152
161
  { name: 'critical-output', locked: true },
153
162
  { name: 'role-description', required: true, tier: ['fast'] },
154
163
  { name: 'custom-instructions', optional: true, tier: ['fast', 'balanced', 'thorough'] },
164
+ { name: 'dedup-instructions', optional: true },
155
165
  { name: 'input-suggestions', locked: true },
156
166
  { name: 'intelligent-merging', required: true, tier: ['fast'] },
157
167
  { name: 'priority-curation', required: true, tier: ['fast'] },
@@ -173,6 +183,7 @@ const defaultOrder = [
173
183
  'critical-output',
174
184
  'role-description',
175
185
  'custom-instructions',
186
+ 'dedup-instructions',
176
187
  'input-suggestions',
177
188
  'intelligent-merging',
178
189
  'priority-curation',
@@ -32,6 +32,7 @@ const { ORCHESTRATION_INPUT_SCHEMA_DOCS } = require('../../shared/output-schema'
32
32
  * - {{reviewIntro}} - Review introduction line
33
33
  * - {{prContext}} - PR context section (optional, may be empty)
34
34
  * - {{customInstructions}} - Custom instructions section (optional)
35
+ * - {{dedupInstructions}} - Dedup instructions section (optional)
35
36
  * - {{lineNumberGuidance}} - Line number guidance section
36
37
  * - {{level1Count}} - Number of Level 1 suggestions
37
38
  * - {{level2Count}} - Number of Level 2 suggestions
@@ -85,6 +86,10 @@ Quality matters more than speed for this orchestration level. It's better to sur
85
86
  {{customInstructions}}
86
87
  </section>
87
88
 
89
+ <section name="dedup-instructions" optional="true">
90
+ {{dedupInstructions}}
91
+ </section>
92
+
88
93
  <section name="input-suggestions" locked="true">
89
94
  ## Input: Multi-Level Analysis Results
90
95
 
@@ -134,6 +139,12 @@ When levels disagree (e.g., Level 1 flags an issue that Level 3 says follows cod
134
139
  - Use the clearest framing, regardless of which level provided it
135
140
  - Do NOT mention which level found the issue - focus on the insight itself
136
141
  - When merging would lose important nuance, keep suggestions distinct
142
+ - **Assess severity** based on the evidence and reasoning across input levels. When levels assign different severities, evaluate the supporting evidence rather than defaulting to the highest. When truly uncertain, preserve the highest severity. Omit severity for praise items.
143
+
144
+ **Severity Definitions:**
145
+ - **critical**: Production incidents, system failures, or security vulnerabilities — runtime crashes, data corruption or loss, race conditions, deadlocks, breaking changes, changes that will cause existing tests to fail
146
+ - **medium**: Degraded functionality or reliability — missing error handling, N+1 queries, missing validation, missing or poor test coverage for new functionality
147
+ - **minor**: Code quality concerns — documentation gaps, minor optimizations, style inconsistencies
137
148
  </section>
138
149
 
139
150
  <section name="priority-curation" required="true" tier="thorough">
@@ -266,6 +277,7 @@ Output JSON with this structure:
266
277
  "line": 42,
267
278
  "old_or_new": "NEW",
268
279
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
280
+ "severity": "critical|medium|minor (omit for praise)",
269
281
  "title": "Brief title describing the curated insight",
270
282
  "description": "Clear explanation of the issue and why this guidance matters to the human reviewer",
271
283
  "suggestion": "Specific, actionable guidance for the reviewer (omit for praise items)",
@@ -275,6 +287,7 @@ Output JSON with this structure:
275
287
  "fileLevelSuggestions": [{
276
288
  "file": "path/to/file",
277
289
  "type": "bug|improvement|praise|suggestion|design|performance|security|code-style",
290
+ "severity": "critical|medium|minor (omit for praise)",
278
291
  "title": "Brief title describing file-level concern",
279
292
  "description": "Explanation of the file-level observation",
280
293
  "suggestion": "How to address the file-level concern (omit for praise items)",
@@ -380,6 +393,7 @@ const sections = [
380
393
  { name: 'role-description', required: true, tier: ['thorough'] },
381
394
  { name: 'reasoning-encouragement', required: true, tier: ['thorough'] },
382
395
  { name: 'custom-instructions', optional: true, tier: ['balanced', 'thorough'] },
396
+ { name: 'dedup-instructions', optional: true },
383
397
  { name: 'input-suggestions', locked: true },
384
398
  { name: 'intelligent-merging', required: true, tier: ['thorough'] },
385
399
  { name: 'priority-curation', required: true, tier: ['thorough'] },
@@ -405,6 +419,7 @@ const defaultOrder = [
405
419
  'role-description',
406
420
  'reasoning-encouragement',
407
421
  'custom-instructions',
422
+ 'dedup-instructions',
408
423
  'input-suggestions',
409
424
  'intelligent-merging',
410
425
  'priority-curation',
@@ -39,6 +39,9 @@ const SKILL_DEFAULTS = {
39
39
  changedFiles:
40
40
  '[Changed files list provided by the orchestrating agent]',
41
41
 
42
+ // Dedup instructions — empty by default (section collapses)
43
+ dedupInstructions: '',
44
+
42
45
  // Orchestration data markers
43
46
  level1Count: '[N]',
44
47
  level2Count: '[N]',
@@ -22,6 +22,7 @@ const ORCHESTRATION_INPUT_SCHEMA_DOCS = `Each level provides suggestions as a JS
22
22
  - title: brief title
23
23
  - description: full explanation
24
24
  - suggestion: remediation advice
25
+ - severity: "critical", "medium", or "minor" (omit for praise items)
25
26
  - confidence: 0.0-1.0 score
26
27
  - reasoning: (optional) array of strings with step-by-step reasoning
27
28
  - is_file_level: true if this is a file-level suggestion (no line numbers)`;
@@ -37,6 +38,7 @@ const LEVEL1_OUTPUT_SCHEMA = {
37
38
  line_end: 42,
38
39
  old_or_new: 'NEW',
39
40
  type: 'bug|improvement|praise|suggestion|design|performance|security|code-style',
41
+ severity: 'critical|medium|minor (omit for praise)',
40
42
  title: 'Brief title',
41
43
  description: 'Detailed explanation',
42
44
  suggestion: 'How to fix/improve (omit this field for praise items - no action needed)',
@@ -57,6 +59,7 @@ const LEVEL2_OUTPUT_SCHEMA = {
57
59
  line_end: 42,
58
60
  old_or_new: 'NEW',
59
61
  type: 'bug|improvement|praise|suggestion|design|performance|security|code-style',
62
+ severity: 'critical|medium|minor (omit for praise)',
60
63
  title: 'Brief title',
61
64
  description: 'Detailed explanation mentioning why full file context was needed',
62
65
  suggestion: 'How to fix/improve based on file context (omit for praise items)',
@@ -66,6 +69,7 @@ const LEVEL2_OUTPUT_SCHEMA = {
66
69
  fileLevelSuggestions: [{
67
70
  file: 'path/to/file',
68
71
  type: 'bug|improvement|praise|suggestion|design|performance|security|code-style',
72
+ severity: 'critical|medium|minor (omit for praise)',
69
73
  title: 'Brief title describing file-level concern',
70
74
  description: 'Explanation of the file-level observation (architecture, organization, naming, etc.)',
71
75
  suggestion: 'How to address the file-level concern (omit for praise items)',
@@ -86,6 +90,7 @@ const LEVEL3_OUTPUT_SCHEMA = {
86
90
  line_end: 42,
87
91
  old_or_new: 'NEW',
88
92
  type: 'bug|improvement|praise|suggestion|design|performance|security|code-style',
93
+ severity: 'critical|medium|minor (omit for praise)',
89
94
  title: 'Brief title',
90
95
  description: 'Detailed explanation mentioning why codebase context was needed',
91
96
  suggestion: 'How to fix/improve based on codebase context (omit for praise items)',
@@ -95,6 +100,7 @@ const LEVEL3_OUTPUT_SCHEMA = {
95
100
  fileLevelSuggestions: [{
96
101
  file: 'path/to/file',
97
102
  type: 'bug|improvement|praise|suggestion|design|performance|security|code-style',
103
+ severity: 'critical|medium|minor (omit for praise)',
98
104
  title: 'Brief title describing file-level concern',
99
105
  description: 'Explanation of the file-level observation from codebase perspective',
100
106
  suggestion: 'How to address the file-level concern (omit for praise items)',
@@ -115,6 +121,7 @@ const ORCHESTRATION_OUTPUT_SCHEMA = {
115
121
  line_end: 42,
116
122
  old_or_new: 'NEW',
117
123
  type: 'bug|improvement|praise|suggestion|design|performance|security|code-style',
124
+ severity: 'critical|medium|minor (omit for praise)',
118
125
  title: 'Brief title describing the curated insight',
119
126
  description: 'Clear explanation of the issue and why this guidance matters to the human reviewer',
120
127
  suggestion: 'Specific, actionable guidance for the reviewer (omit for praise items)',
@@ -124,6 +131,7 @@ const ORCHESTRATION_OUTPUT_SCHEMA = {
124
131
  fileLevelSuggestions: [{
125
132
  file: 'path/to/file',
126
133
  type: 'bug|improvement|praise|suggestion|design|performance|security|code-style',
134
+ severity: 'critical|medium|minor (omit for praise)',
127
135
  title: 'Brief title describing file-level concern',
128
136
  description: 'Explanation of the file-level observation',
129
137
  suggestion: 'How to address the file-level concern (omit for praise items)',
@@ -424,9 +424,43 @@ function resolveDefaultModel(models) {
424
424
  }
425
425
 
426
426
  /**
427
- * Apply configuration overrides for all providers
428
- * Call this after all providers have registered and config is loaded
429
- * Clears any existing overrides before applying new ones.
427
+ * Create an aliased provider class that reuses an existing provider's implementation
428
+ * but with a different ID, name, and config overrides.
429
+ *
430
+ * @param {string} aliasId - New provider ID (e.g., 'pi-reskin')
431
+ * @param {typeof AIProvider} BaseClass - The base provider class to alias
432
+ * @param {Object} aliasConfig - Config for the alias (name, models, etc.)
433
+ * @returns {typeof AIProvider} A subclass with overridden static metadata
434
+ */
435
+ function createAliasedProviderClass(aliasId, BaseClass, aliasConfig) {
436
+ const processedModels = Array.isArray(aliasConfig.models) && aliasConfig.models.length > 0
437
+ ? aliasConfig.models.map(inferModelDefaults)
438
+ : null;
439
+
440
+ class AliasedProvider extends BaseClass {}
441
+
442
+ // Override static metadata so the alias has its own identity
443
+ AliasedProvider.getProviderName = () => aliasConfig.name || aliasId;
444
+ AliasedProvider.getProviderId = () => aliasId;
445
+ if (processedModels) {
446
+ AliasedProvider.getModels = () => processedModels;
447
+ AliasedProvider.getDefaultModel = () => resolveDefaultModel(processedModels);
448
+ }
449
+ if (aliasConfig.installInstructions) {
450
+ AliasedProvider.getInstallInstructions = () => aliasConfig.installInstructions;
451
+ }
452
+ if (aliasConfig.defaultTimeout != null) {
453
+ AliasedProvider.defaultTimeout = aliasConfig.defaultTimeout;
454
+ }
455
+
456
+ return AliasedProvider;
457
+ }
458
+
459
+ /**
460
+ * Apply configuration overrides for all providers.
461
+ * Called once at startup after all providers have self-registered.
462
+ * Does not support re-application — the provider registry is intentionally
463
+ * not cleaned between calls (aliased/executable classes persist).
430
464
  * @param {Object} config - Configuration object from loadConfig()
431
465
  */
432
466
  function applyConfigOverrides(config) {
@@ -446,6 +480,47 @@ function applyConfigOverrides(config) {
446
480
  for (const [providerId, providerConfig] of Object.entries(providersConfig)) {
447
481
  logger.debug(`Applying config overrides for provider: ${providerId}`);
448
482
 
483
+ // Executable providers: dynamically create and register a provider class
484
+ if (providerConfig.type === 'executable') {
485
+ if (!providerConfig.command) {
486
+ logger.warn(`Executable provider "${providerId}" missing required "command" field`);
487
+ continue;
488
+ }
489
+ // Lazy-require to avoid circular dependency
490
+ const { createExecutableProviderClass } = require('./executable-provider');
491
+ const ExecClass = createExecutableProviderClass(providerId, providerConfig);
492
+ registerProvider(providerId, ExecClass);
493
+ providerConfigOverrides.set(providerId, { ...providerConfig, models: ExecClass.getModels() });
494
+ logger.debug(`Registered executable provider: ${providerId}`);
495
+ continue;
496
+ }
497
+
498
+ // Type matching a registered provider ID creates an alias of that provider
499
+ if (providerConfig.type && providerConfig.type !== providerId && providerRegistry.has(providerConfig.type)) {
500
+ const BaseClass = providerRegistry.get(providerConfig.type);
501
+ const AliasClass = createAliasedProviderClass(providerId, BaseClass, providerConfig);
502
+ registerProvider(providerId, AliasClass);
503
+
504
+ // Aliases reuse the base provider's implementation class, not its config.
505
+ // Only universal override fields are forwarded; provider-specific fields
506
+ // (e.g. codex args) must be explicitly set in the alias config.
507
+ providerConfigOverrides.set(providerId, {
508
+ command: providerConfig.command,
509
+ installInstructions: providerConfig.installInstructions,
510
+ extra_args: providerConfig.extra_args,
511
+ env: providerConfig.env,
512
+ models: AliasClass.getModels() !== BaseClass.getModels() ? AliasClass.getModels() : null
513
+ });
514
+ logger.debug(`Registered aliased provider: ${providerId} (base: ${providerConfig.type})`);
515
+ continue;
516
+ }
517
+
518
+ // Unknown type: warn and skip (self-referential type falls through to standard override path)
519
+ if (providerConfig.type && providerConfig.type !== providerId) {
520
+ logger.warn(`Provider "${providerId}" has unknown type "${providerConfig.type}" — no matching registered provider`);
521
+ continue;
522
+ }
523
+
449
524
  // Process models if specified - infer defaults for each
450
525
  let processedModels = null;
451
526
  if (Array.isArray(providerConfig.models) && providerConfig.models.length > 0) {
@@ -543,12 +618,21 @@ function getAllProvidersInfo() {
543
618
  // Use overridden install instructions if available
544
619
  const installInstructions = overrides?.installInstructions || ProviderClass.getInstallInstructions();
545
620
 
621
+ // Build capabilities: executable providers define their own, others get defaults
622
+ const capabilities = ProviderClass.capabilities || {
623
+ review_levels: true,
624
+ custom_instructions: true
625
+ };
626
+
546
627
  providers.push({
547
628
  id,
548
629
  name: ProviderClass.getProviderName(),
549
630
  models,
550
631
  defaultModel,
551
- installInstructions
632
+ installInstructions,
633
+ capabilities,
634
+ isExecutable: ProviderClass.isExecutable || false,
635
+ ...(ProviderClass.defaultTimeout != null ? { defaultTimeout: ProviderClass.defaultTimeout } : {})
552
636
  });
553
637
  }
554
638
  return providers;
@@ -656,6 +740,7 @@ module.exports = {
656
740
  testProviderAvailability,
657
741
  // Config override support
658
742
  applyConfigOverrides,
743
+ createAliasedProviderClass,
659
744
  getProviderConfigOverrides,
660
745
  inferModelDefaults,
661
746
  resolveDefaultModel,
@@ -18,9 +18,10 @@ const logger = require('../utils/logger');
18
18
  * @param {Object} options.review - Review metadata {id, pr_number, repository, review_type, local_path, name}
19
19
  * @param {Object} [options.prData] - PR data with base_sha/head_sha (for PR reviews)
20
20
  * @param {string} [options.chatInstructions] - Custom instructions from repo settings to append to system prompt
21
+ * @param {string} [options.commentFormatTemplate] - Resolved comment format template for consistent comment formatting
21
22
  * @returns {string} System prompt for the chat agent
22
23
  */
23
- function buildChatPrompt({ review, prData, chatInstructions }) {
24
+ function buildChatPrompt({ review, prData, chatInstructions, commentFormatTemplate }) {
24
25
  const sections = [];
25
26
 
26
27
  // Role
@@ -53,6 +54,21 @@ function buildChatPrompt({ review, prData, chatInstructions }) {
53
54
  }
54
55
  sections.push(domainLines.join('\n'));
55
56
 
57
+ // Comment format template — injected when the user has a configured format
58
+ if (commentFormatTemplate) {
59
+ sections.push(
60
+ '## Comment format\n\n' +
61
+ 'When creating or editing review comments, use this template:\n' +
62
+ '<template>\n' +
63
+ commentFormatTemplate + '\n' +
64
+ '</template>\n\n' +
65
+ 'Template placeholders: {emoji}, {category}, {title}, {severity}, {SEVERITY}, {description}, {suggestion}.\n' +
66
+ '{severity} renders as Title Case (e.g. "Critical"), {SEVERITY} renders as UPPERCASE (e.g. "CRITICAL").\n' +
67
+ 'Conditional sections: {?field}...{/field} — include content only when the field has a value.\n' +
68
+ 'Always follow this format for consistency with the reviewer\'s preferences.'
69
+ );
70
+ }
71
+
56
72
  // API Access — cheat-sheet is injected into initial context; full docs available via GET /api.md
57
73
  sections.push(
58
74
  '## API Access\n\n' +
@@ -44,6 +44,12 @@ class ChatSessionManager {
44
44
  * @returns {Promise<{id: number, status: string}>}
45
45
  */
46
46
  async createSession({ provider, model, reviewId, contextCommentId, systemPrompt, cwd, initialContext }) {
47
+ // Resolve provider definition once — used for model fallback and bridge construction
48
+ const providerDef = getChatProvider(provider);
49
+
50
+ // Resolve model: explicit request value > provider config default
51
+ const resolvedModel = model || providerDef?.model || null;
52
+
47
53
  // Insert session record into DB
48
54
  const stmt = this._db.prepare(`
49
55
  INSERT INTO chat_sessions (review_id, context_comment_id, provider, model, status)
@@ -53,20 +59,20 @@ class ChatSessionManager {
53
59
  reviewId,
54
60
  contextCommentId || null,
55
61
  provider,
56
- model || null
62
+ resolvedModel
57
63
  );
58
64
  const sessionId = Number(result.lastInsertRowid);
59
65
 
60
- logger.info(`[ChatSession] Creating session ${sessionId} (provider=${provider}, review=${reviewId})`);
66
+ logger.info(`[ChatSession] Creating session ${sessionId} (provider=${provider}, model=${resolvedModel}, review=${reviewId})`);
61
67
 
62
68
  // Create and start the bridge
63
69
  // Chat sessions get bash for git commands; review analysis uses the safe default
64
70
  const bridge = this._createBridge(provider, {
65
71
  provider,
66
- model,
72
+ model: resolvedModel,
67
73
  cwd,
68
74
  systemPrompt,
69
- });
75
+ }, providerDef);
70
76
 
71
77
  const listeners = {
72
78
  delta: new Set(),
@@ -530,39 +536,38 @@ class ChatSessionManager {
530
536
  * ACP providers get an AcpBridge; everything else gets a PiBridge with tools/skills.
531
537
  * @param {string} provider
532
538
  * @param {Object} options - Bridge constructor options
539
+ * @param {Object} [providerDef] - Pre-resolved provider definition (avoids redundant getChatProvider calls)
533
540
  * @returns {PiBridge|AcpBridge}
534
541
  */
535
- _createBridge(provider, options) {
542
+ _createBridge(provider, options, providerDef) {
543
+ const def = providerDef || getChatProvider(provider);
536
544
  if (isAcpProvider(provider)) {
537
- const providerDef = getChatProvider(provider);
538
545
  return new AcpBridge({
539
546
  ...options,
540
- model: options.model || providerDef?.model,
541
- acpCommand: providerDef?.command,
542
- acpArgs: providerDef?.args,
543
- env: providerDef?.env,
544
- useShell: providerDef?.useShell,
547
+ model: options.model || def?.model,
548
+ acpCommand: def?.command,
549
+ acpArgs: def?.args,
550
+ env: def?.env,
551
+ useShell: def?.useShell,
545
552
  });
546
553
  }
547
554
  if (isClaudeCodeProvider(provider)) {
548
- const providerDef = getChatProvider(provider);
549
555
  return new ClaudeCodeBridge({
550
556
  ...options,
551
- model: options.model || providerDef?.model,
552
- claudeCommand: providerDef?.command,
553
- env: providerDef?.env,
554
- useShell: providerDef?.useShell,
557
+ model: options.model || def?.model,
558
+ claudeCommand: def?.command,
559
+ env: def?.env,
560
+ useShell: def?.useShell,
555
561
  });
556
562
  }
557
563
  if (isCodexProvider(provider)) {
558
- const providerDef = getChatProvider(provider);
559
564
  return new CodexBridge({
560
565
  ...options,
561
- model: options.model || providerDef?.model,
562
- codexCommand: providerDef?.command,
563
- codexArgs: providerDef?.args,
564
- env: providerDef?.env,
565
- useShell: providerDef?.useShell,
566
+ model: options.model || def?.model,
567
+ codexCommand: def?.command,
568
+ codexArgs: def?.args,
569
+ env: def?.env,
570
+ useShell: def?.useShell,
566
571
  });
567
572
  }
568
573
  // Pi provider — resolve config overrides (command, model, env) from provider def.
@@ -570,14 +575,13 @@ class ChatSessionManager {
570
575
  // which would forward it as `--provider pi` to the Pi CLI. The CLI's --provider flag
571
576
  // expects a model provider ("google", "anthropic", etc.) and should only come from
572
577
  // explicit user configuration (providerDef.provider).
573
- const providerDef = getChatProvider(provider);
574
578
  return new PiBridge({
575
579
  ...options,
576
- provider: providerDef?.provider || null,
577
- model: options.model || providerDef?.model,
578
- piCommand: providerDef?.command,
579
- env: providerDef?.env,
580
- useShell: providerDef?.useShell,
580
+ provider: def?.provider || null,
581
+ model: options.model || def?.model,
582
+ piCommand: def?.command,
583
+ env: def?.env,
584
+ useShell: def?.useShell,
581
585
  tools: CHAT_TOOLS,
582
586
  extensions: [taskExtensionDir],
583
587
  });