@in-the-loop-labs/pair-review 2.3.2 → 2.4.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 (47) hide show
  1. package/.pi/skills/review-model-guidance/SKILL.md +1 -1
  2. package/.pi/skills/review-roulette/SKILL.md +1 -1
  3. package/README.md +15 -1
  4. package/package.json +2 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/skills/review-requests/SKILL.md +1 -1
  7. package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
  8. package/public/css/pr.css +296 -15
  9. package/public/index.html +121 -57
  10. package/public/js/components/AIPanel.js +2 -1
  11. package/public/js/components/AdvancedConfigTab.js +2 -2
  12. package/public/js/components/AnalysisConfigModal.js +2 -2
  13. package/public/js/components/ChatPanel.js +187 -28
  14. package/public/js/components/CouncilProgressModal.js +4 -7
  15. package/public/js/components/SplitButton.js +66 -1
  16. package/public/js/components/VoiceCentricConfigTab.js +2 -2
  17. package/public/js/index.js +274 -21
  18. package/public/js/modules/comment-manager.js +16 -12
  19. package/public/js/modules/file-comment-manager.js +8 -6
  20. package/public/js/pr.js +194 -5
  21. package/public/local.html +8 -1
  22. package/public/pr.html +17 -2
  23. package/src/ai/codex-provider.js +14 -2
  24. package/src/ai/copilot-provider.js +1 -10
  25. package/src/ai/cursor-agent-provider.js +1 -10
  26. package/src/ai/gemini-provider.js +8 -17
  27. package/src/chat/acp-bridge.js +442 -0
  28. package/src/chat/api-reference.js +539 -0
  29. package/src/chat/chat-providers.js +290 -0
  30. package/src/chat/claude-code-bridge.js +499 -0
  31. package/src/chat/codex-bridge.js +601 -0
  32. package/src/chat/pi-bridge.js +56 -3
  33. package/src/chat/prompt-builder.js +12 -11
  34. package/src/chat/session-manager.js +110 -29
  35. package/src/config.js +4 -2
  36. package/src/database.js +50 -2
  37. package/src/github/client.js +43 -0
  38. package/src/routes/chat.js +60 -27
  39. package/src/routes/config.js +24 -1
  40. package/src/routes/github-collections.js +126 -0
  41. package/src/routes/mcp.js +2 -1
  42. package/src/routes/pr.js +166 -2
  43. package/src/routes/reviews.js +2 -1
  44. package/src/routes/shared.js +70 -49
  45. package/src/server.js +27 -1
  46. package/src/utils/safe-parse-json.js +19 -0
  47. package/.pi/skills/pair-review-api/SKILL.md +0 -448
@@ -78,7 +78,7 @@ Here are the higher-end models and their strengths for code review:
78
78
  files or functions.
79
79
 
80
80
  **Google**
81
- - **google/gemini-3-pro-preview**: Google's latest and most capable model (1M context).
81
+ - **google/gemini-3.1-pro-preview**: Google's latest and most capable model (1M context).
82
82
  Strong at cross-file analysis and understanding large codebases holistically.
83
83
  - **google/gemini-2.5-pro with thinking**: Excellent at large-context analysis — can
84
84
  reason over many files simultaneously with its 1M token context window. Good for
@@ -30,7 +30,7 @@ Example reasoning models you might see (provider/model format):
30
30
  - `openai/gpt-5.2-pro`
31
31
  - `google/gemini-2.5-pro` (with thinking)
32
32
  - `google/gemini-2.5-flash` (with thinking)
33
- - `google/gemini-3-pro-preview`
33
+ - `google/gemini-3.1-pro-preview`
34
34
  - `xai/grok-4`
35
35
 
36
36
  The exact list depends on which API keys are configured. Always check — do not
package/README.md CHANGED
@@ -248,7 +248,7 @@ pair-review loads configuration from multiple files, merged in order of increasi
248
248
  | 4 | `.pair-review/config.json` | Project-specific configuration (can be checked in) |
249
249
  | 5 (highest) | `.pair-review/config.local.json` | Personal project overrides (gitignored) |
250
250
 
251
- Nested objects (like `chat`, `providers`, `monorepos`) are deep-merged across layers — you only need to specify the keys you want to override.
251
+ Nested objects (like `chat`, `providers`, `chat_providers`, `monorepos`) are deep-merged across layers — you only need to specify the keys you want to override.
252
252
 
253
253
  **`config.local.json`** files are intended for personal overrides that should not be committed to version control. Add `config.local.json` to your `.gitignore`.
254
254
 
@@ -471,6 +471,20 @@ npm install -g @mariozechner/pi-coding-agent
471
471
 
472
472
  Configure your preferred models in `providers.pi.models` — see [AI Provider Configuration](#ai-provider-configuration) for details. Everything else in pair-review works without Pi installed.
473
473
 
474
+ **Chat provider command overrides:** To customize CLI commands for chat providers (e.g., to use a wrapper script), use the `chat_providers` config key:
475
+
476
+ ```json
477
+ {
478
+ "chat_providers": {
479
+ "claude": { "command": "devx", "args": ["claude", "--"] },
480
+ "codex": { "command": "devx", "args": ["codex", "--", "app-server"] },
481
+ "opencode-acp": { "command": "devx", "args": ["opencode", "acp"] }
482
+ }
483
+ }
484
+ ```
485
+
486
+ Available chat provider IDs: `pi`, `claude`, `codex`, `copilot-acp`, `gemini-acp`, `opencode-acp`, `cursor-acp`. Each supports `command`, `args` (replaces defaults), `extra_args` (appends), and `env` overrides.
487
+
474
488
  **Keyboard shortcut:** Press `p` then `c` to toggle the chat panel.
475
489
 
476
490
  ### Customization
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@in-the-loop-labs/pair-review",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
4
4
  "description": "Your AI-powered code review partner - Close the feedback loop with AI coding agents",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -59,6 +59,7 @@
59
59
  },
60
60
  "homepage": "https://github.com/in-the-loop-labs/pair-review#readme",
61
61
  "dependencies": {
62
+ "@agentclientprotocol/sdk": "^0.14.1",
62
63
  "@modelcontextprotocol/sdk": "^1.25.3",
63
64
  "@octokit/rest": "^19.0.11",
64
65
  "better-sqlite3": "^11.8.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pair-review",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
4
4
  "description": "pair-review app integration — Open PRs and local changes in the pair-review web UI, run server-side AI analysis, and address review feedback. Requires the pair-review MCP server.",
5
5
  "author": {
6
6
  "name": "in-the-loop-labs",
@@ -35,7 +35,7 @@ Each result provides `number`, `title`, `html_url`, and `repo` (as `owner/repo`)
35
35
 
36
36
  ### Phase 3: Open each PR in pair-review with auto-analysis
37
37
 
38
- **Limit**: Open at most **5** PRs. If more than 5 are found, open only the first 5 (most
38
+ **Limit**: Open at most **10** PRs. If more than 10 are found, open only the first 10 (most
39
39
  recently updated) and report how many were skipped.
40
40
 
41
41
  For each PR found (up to the limit), open it in the browser with the `?analyze=true` query parameter, which
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-critic",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
4
4
  "description": "AI-powered code review analysis — Run three-level AI analysis and implement-review-fix loops directly in your coding agent. Works standalone, no server required.",
5
5
  "author": {
6
6
  "name": "in-the-loop-labs",
package/public/css/pr.css CHANGED
@@ -2106,6 +2106,7 @@ tr.newly-expanded .d2h-code-line-ctn {
2106
2106
  position: relative;
2107
2107
  display: flex;
2108
2108
  align-items: center;
2109
+ flex-shrink: 0;
2109
2110
  }
2110
2111
 
2111
2112
  .btn-reasoning-toggle {
@@ -2439,7 +2440,7 @@ tr.newly-expanded .d2h-code-line-ctn {
2439
2440
  text-overflow: ellipsis;
2440
2441
  white-space: nowrap;
2441
2442
  color: var(--color-text-secondary);
2442
- max-width: 400px;
2443
+ min-width: 0;
2443
2444
  font-size: 13px;
2444
2445
  }
2445
2446
 
@@ -4006,12 +4007,15 @@ tr.line-range-start .d2h-code-line-ctn {
4006
4007
  margin-bottom: 4px;
4007
4008
  padding-bottom: 4px;
4008
4009
  border-bottom: 1px solid rgba(139, 92, 246, 0.1);
4010
+ min-width: 0;
4009
4011
  }
4010
4012
 
4011
4013
  .user-comment-header-left {
4012
4014
  display: flex;
4013
4015
  align-items: center;
4014
4016
  gap: 8px;
4017
+ min-width: 0;
4018
+ overflow: hidden;
4015
4019
  }
4016
4020
 
4017
4021
  .user-comment-line-info {
@@ -4088,6 +4092,9 @@ tr.line-range-start .d2h-code-line-ctn {
4088
4092
  font-size: 12px;
4089
4093
  font-style: italic;
4090
4094
  margin-left: 6px;
4095
+ overflow: hidden;
4096
+ text-overflow: ellipsis;
4097
+ white-space: nowrap;
4091
4098
  }
4092
4099
 
4093
4100
  /* Eye icon toggle button */
@@ -4156,6 +4163,7 @@ tr.line-range-start .d2h-code-line-ctn {
4156
4163
  align-items: center;
4157
4164
  gap: 4px;
4158
4165
  margin-left: auto;
4166
+ flex-shrink: 0;
4159
4167
  }
4160
4168
 
4161
4169
  /* Hide edit/delete buttons when in editing mode */
@@ -5366,6 +5374,16 @@ tr.line-range-start .d2h-code-line-ctn {
5366
5374
  color: #1f883d;
5367
5375
  }
5368
5376
 
5377
+ .menu-item-icon {
5378
+ display: flex;
5379
+ align-items: center;
5380
+ margin-left: 8px;
5381
+ }
5382
+
5383
+ .menu-item-icon svg {
5384
+ /* vertical alignment handled by flex parent */
5385
+ }
5386
+
5369
5387
  .menu-item-text {
5370
5388
  flex: 1;
5371
5389
  }
@@ -5858,6 +5876,145 @@ body::before {
5858
5876
  text-align: center;
5859
5877
  }
5860
5878
 
5879
+ .pr-title-wrapper {
5880
+ display: flex;
5881
+ align-items: center;
5882
+ justify-content: center;
5883
+ gap: 2px;
5884
+ position: relative;
5885
+ }
5886
+
5887
+ .header .pr-description-toggle,
5888
+ [data-theme="dark"] .header .pr-description-toggle {
5889
+ flex-shrink: 0;
5890
+ background: transparent;
5891
+ border: none;
5892
+ color: var(--color-text-tertiary, #6e7681);
5893
+ opacity: 0.7;
5894
+ transition: opacity 0.15s ease, color 0.15s ease;
5895
+ }
5896
+
5897
+ .pr-description-toggle:hover {
5898
+ opacity: 1;
5899
+ color: var(--color-text-secondary, #8b949e);
5900
+ }
5901
+
5902
+ .pr-description-toggle.active {
5903
+ opacity: 1;
5904
+ color: var(--color-accent, #2f81f7);
5905
+ }
5906
+
5907
+ /* PR description popover */
5908
+ .pr-description-popover {
5909
+ position: absolute;
5910
+ top: calc(100% + 8px);
5911
+ left: 50%;
5912
+ transform: translateX(-50%);
5913
+ z-index: 1000;
5914
+ width: min(500px, 90vw);
5915
+ background: var(--color-bg-primary, #0d1117);
5916
+ border: 1px solid var(--color-border-primary, rgba(255, 255, 255, 0.1));
5917
+ border-radius: 8px;
5918
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.2);
5919
+ text-align: left;
5920
+ }
5921
+
5922
+ .pr-description-popover-arrow {
5923
+ position: absolute;
5924
+ top: -6px;
5925
+ left: 50%;
5926
+ margin-left: -6px;
5927
+ width: 12px;
5928
+ height: 12px;
5929
+ background: var(--color-bg-primary, #0d1117);
5930
+ border-top: 1px solid var(--color-border-primary, rgba(255, 255, 255, 0.1));
5931
+ border-left: 1px solid var(--color-border-primary, rgba(255, 255, 255, 0.1));
5932
+ transform: rotate(45deg);
5933
+ }
5934
+
5935
+ .pr-description-popover-header {
5936
+ display: flex;
5937
+ align-items: center;
5938
+ justify-content: space-between;
5939
+ padding: 8px 12px 8px 16px;
5940
+ border-bottom: 1px solid var(--color-border-secondary, rgba(255, 255, 255, 0.06));
5941
+ }
5942
+
5943
+ .pr-description-popover-title {
5944
+ font-size: 0.75rem;
5945
+ font-weight: 600;
5946
+ color: var(--color-text-primary, #e6edf3);
5947
+ text-transform: uppercase;
5948
+ letter-spacing: 0.05em;
5949
+ }
5950
+
5951
+ .pr-description-popover-close {
5952
+ display: flex;
5953
+ align-items: center;
5954
+ justify-content: center;
5955
+ width: 24px;
5956
+ height: 24px;
5957
+ padding: 0;
5958
+ border: none;
5959
+ border-radius: 4px;
5960
+ background: transparent;
5961
+ color: var(--color-text-tertiary, #6e7681);
5962
+ cursor: pointer;
5963
+ transition: all 0.15s ease;
5964
+ }
5965
+
5966
+ .pr-description-popover-close:hover {
5967
+ color: var(--color-text-secondary, #8b949e);
5968
+ background: var(--color-bg-tertiary, rgba(255, 255, 255, 0.04));
5969
+ }
5970
+
5971
+ .pr-description-popover-content {
5972
+ padding: 12px 16px;
5973
+ font-size: 0.8125rem;
5974
+ line-height: 1.6;
5975
+ color: var(--color-text-secondary, #8b949e);
5976
+ max-height: calc(60vh - 40px);
5977
+ overflow-y: auto;
5978
+ }
5979
+
5980
+ .pr-description-popover-content p {
5981
+ margin: 0 0 8px;
5982
+ }
5983
+
5984
+ .pr-description-popover-content p:last-child {
5985
+ margin-bottom: 0;
5986
+ }
5987
+
5988
+ .pr-description-popover-content ul,
5989
+ .pr-description-popover-content ol {
5990
+ margin: 4px 0 8px;
5991
+ padding-left: 20px;
5992
+ }
5993
+
5994
+ .pr-description-popover-content li {
5995
+ margin: 2px 0;
5996
+ }
5997
+
5998
+ .pr-description-popover-content code {
5999
+ padding: 2px 6px;
6000
+ background: var(--color-bg-tertiary, rgba(255, 255, 255, 0.04));
6001
+ border-radius: 3px;
6002
+ font-size: 0.75rem;
6003
+ }
6004
+
6005
+ .pr-description-popover-content pre {
6006
+ margin: 8px 0;
6007
+ padding: 8px 12px;
6008
+ background: var(--color-bg-tertiary, rgba(255, 255, 255, 0.04));
6009
+ border-radius: 6px;
6010
+ overflow-x: auto;
6011
+ }
6012
+
6013
+ .pr-description-popover-content pre code {
6014
+ padding: 0;
6015
+ background: none;
6016
+ }
6017
+
5861
6018
  .header-center .pr-title {
5862
6019
  font-size: 1rem;
5863
6020
  font-weight: 500;
@@ -10898,17 +11055,17 @@ body.resizing * {
10898
11055
  background: linear-gradient(to bottom, var(--chat-subtle), transparent);
10899
11056
  flex-shrink: 0;
10900
11057
  }
10901
- /* Session picker wrapper */
10902
- .chat-panel__session-picker {
11058
+ /* Provider picker */
11059
+ .chat-panel__provider-picker {
10903
11060
  position: relative;
10904
11061
  flex: 1;
10905
11062
  min-width: 0;
10906
11063
  }
10907
11064
 
10908
- .chat-panel__session-picker-btn {
11065
+ .chat-panel__provider-picker-btn {
10909
11066
  display: flex;
10910
11067
  align-items: center;
10911
- gap: 8px;
11068
+ gap: 6px;
10912
11069
  font-size: 13px;
10913
11070
  font-weight: 600;
10914
11071
  color: var(--chat-text);
@@ -10921,11 +11078,11 @@ body.resizing * {
10921
11078
  max-width: 100%;
10922
11079
  }
10923
11080
 
10924
- .chat-panel__session-picker-btn:hover {
11081
+ .chat-panel__provider-picker-btn:hover {
10925
11082
  background: var(--color-bg-tertiary, rgba(128, 128, 128, 0.1));
10926
11083
  }
10927
11084
 
10928
- .chat-panel__session-picker-btn svg:first-child {
11085
+ .chat-panel__provider-picker-btn svg:first-child {
10929
11086
  color: var(--chat-primary);
10930
11087
  flex-shrink: 0;
10931
11088
  }
@@ -10936,18 +11093,18 @@ body.resizing * {
10936
11093
  white-space: nowrap;
10937
11094
  }
10938
11095
 
10939
- .chat-panel__chevron-sep {
10940
- opacity: 0.4;
10941
- font-size: 13px;
10942
- }
10943
-
10944
- .chat-panel__chevron {
11096
+ .chat-panel__provider-chevron {
10945
11097
  flex-shrink: 0;
10946
11098
  opacity: 0.5;
11099
+ transition: transform 0.15s ease;
10947
11100
  }
10948
11101
 
10949
- /* Session dropdown flyout */
10950
- .chat-panel__session-dropdown {
11102
+ .chat-panel__provider-picker-btn--open .chat-panel__provider-chevron {
11103
+ transform: rotate(180deg);
11104
+ }
11105
+
11106
+ /* Provider dropdown */
11107
+ .chat-panel__provider-dropdown {
10951
11108
  position: absolute;
10952
11109
  top: calc(100% + 4px);
10953
11110
  left: 0;
@@ -10962,6 +11119,72 @@ body.resizing * {
10962
11119
  padding: 4px;
10963
11120
  }
10964
11121
 
11122
+ .chat-panel__provider-empty {
11123
+ padding: 16px;
11124
+ text-align: center;
11125
+ color: var(--color-text-tertiary);
11126
+ font-size: 12px;
11127
+ }
11128
+
11129
+ .chat-panel__provider-item {
11130
+ display: flex;
11131
+ align-items: center;
11132
+ justify-content: space-between;
11133
+ width: 100%;
11134
+ padding: 8px 12px;
11135
+ border: none;
11136
+ background: transparent;
11137
+ text-align: left;
11138
+ border-radius: 6px;
11139
+ cursor: pointer;
11140
+ transition: background 0.15s ease;
11141
+ font-size: 13px;
11142
+ color: var(--color-text-primary, #1f2328);
11143
+ }
11144
+
11145
+ .chat-panel__provider-item:hover:not([disabled]) {
11146
+ background: var(--color-bg-tertiary, rgba(128, 128, 128, 0.08));
11147
+ }
11148
+
11149
+ .chat-panel__provider-item--active {
11150
+ background: var(--color-bg-secondary, #f6f8fa);
11151
+ }
11152
+
11153
+ .chat-panel__provider-item--unavailable {
11154
+ opacity: 0.5;
11155
+ cursor: default;
11156
+ }
11157
+
11158
+ .chat-panel__provider-name {
11159
+ overflow: hidden;
11160
+ text-overflow: ellipsis;
11161
+ white-space: nowrap;
11162
+ }
11163
+
11164
+ .chat-panel__provider-check {
11165
+ flex-shrink: 0;
11166
+ color: var(--chat-primary, #0969da);
11167
+ }
11168
+
11169
+ /* Session picker wrapper (dropdown container) */
11170
+ .chat-panel__session-picker {
11171
+ position: relative;
11172
+ }
11173
+
11174
+ /* Session dropdown flyout — uses position:fixed to escape chat-panel overflow:hidden */
11175
+ .chat-panel__session-dropdown {
11176
+ position: fixed;
11177
+ width: 320px;
11178
+ background: var(--color-bg-primary, #fff);
11179
+ border: 1px solid var(--color-border-primary, #d0d7de);
11180
+ border-radius: 8px;
11181
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
11182
+ max-height: 300px;
11183
+ overflow-y: auto;
11184
+ z-index: 1000;
11185
+ padding: 4px;
11186
+ }
11187
+
10965
11188
  .chat-panel__session-empty {
10966
11189
  padding: 16px;
10967
11190
  text-align: center;
@@ -11017,6 +11240,24 @@ body.resizing * {
11017
11240
  color: var(--color-text-tertiary, #656d76);
11018
11241
  }
11019
11242
 
11243
+ .chat-panel__session-provider {
11244
+ display: inline-block;
11245
+ font-size: 10px;
11246
+ font-weight: 500;
11247
+ color: var(--color-text-secondary, #59636e);
11248
+ background: var(--color-bg-tertiary, rgba(128, 128, 128, 0.08));
11249
+ border-radius: 3px;
11250
+ padding: 0 4px;
11251
+ margin-right: 6px;
11252
+ line-height: 16px;
11253
+ vertical-align: baseline;
11254
+ }
11255
+
11256
+ [data-theme="dark"] .chat-panel__session-provider {
11257
+ color: var(--color-text-secondary, #8b949e);
11258
+ background: rgba(128, 128, 128, 0.15);
11259
+ }
11260
+
11020
11261
  /* Dark theme overrides */
11021
11262
  [data-theme="dark"] .chat-panel__session-dropdown {
11022
11263
  background: var(--color-bg-primary, #0d1117);
@@ -11032,6 +11273,46 @@ body.resizing * {
11032
11273
  color: var(--color-text-primary, #e6edf3);
11033
11274
  }
11034
11275
 
11276
+ /* Dark theme: provider dropdown */
11277
+ [data-theme="dark"] .chat-panel__provider-dropdown {
11278
+ background: var(--color-bg-primary, #0d1117);
11279
+ border-color: var(--color-border-primary, #30363d);
11280
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
11281
+ }
11282
+
11283
+ [data-theme="dark"] .chat-panel__provider-item {
11284
+ color: var(--color-text-primary, #e6edf3);
11285
+ }
11286
+
11287
+ [data-theme="dark"] .chat-panel__provider-item--active {
11288
+ background: var(--color-bg-secondary, #161b22);
11289
+ }
11290
+
11291
+ /* History button (icon-only, in actions) */
11292
+ .chat-panel__history-btn {
11293
+ display: flex;
11294
+ align-items: center;
11295
+ justify-content: center;
11296
+ width: 28px;
11297
+ height: 28px;
11298
+ border: none;
11299
+ background: transparent;
11300
+ color: var(--color-text-tertiary);
11301
+ border-radius: 6px;
11302
+ cursor: pointer;
11303
+ transition: background 0.15s ease, color 0.15s ease;
11304
+ }
11305
+
11306
+ .chat-panel__history-btn:hover {
11307
+ background: var(--color-bg-tertiary);
11308
+ color: var(--color-text-primary);
11309
+ }
11310
+
11311
+ .chat-panel__history-btn--open {
11312
+ background: var(--color-bg-tertiary);
11313
+ color: var(--color-text-primary);
11314
+ }
11315
+
11035
11316
  .chat-panel__actions {
11036
11317
  display: flex;
11037
11318
  align-items: center;