@in-the-loop-labs/pair-review 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -7
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +3 -4
- package/plugin-pair-loop/.claude-plugin/plugin.json +19 -0
- package/plugin-pair-loop/skills/loop/SKILL.md +233 -0
- package/public/js/index.js +87 -10
- package/public/js/local.js +19 -9
- package/public/js/pr.js +64 -36
- package/public/js/repo-links.js +11 -3
- package/public/js/utils/analyze-params.js +69 -0
- package/public/js/utils/provider-model.js +66 -1
- package/public/js/vendor/pierre-diffs-worker.js +16158 -0
- package/public/js/vendor/pierre-diffs.js +1880 -0
- package/public/local.html +1 -0
- package/public/pr.html +1 -0
- package/public/setup.html +35 -16
- package/src/config.js +150 -28
- package/src/database.js +127 -3
- package/src/external/github-adapter.js +18 -3
- package/src/github/client.js +37 -0
- package/src/github/parser.js +41 -7
- package/src/interactive-analysis-config.js +2 -2
- package/src/links/repo-links.js +66 -28
- package/src/local-review.js +134 -5
- package/src/local-scope.js +38 -0
- package/src/main.js +199 -33
- package/src/routes/config.js +47 -12
- package/src/routes/external-comments.js +13 -1
- package/src/routes/github-collections.js +175 -13
- package/src/routes/local.js +11 -20
- package/src/routes/pr.js +63 -36
- package/src/routes/setup.js +63 -8
- package/src/routes/shared.js +85 -0
- package/src/routes/stack-analysis.js +39 -3
- package/src/server.js +74 -3
- package/src/setup/local-setup.js +23 -7
- package/src/setup/pr-setup.js +237 -39
- package/src/setup/stack-setup.js +7 -2
- package/src/single-port.js +73 -18
- package/src/utils/host-resolution.js +157 -0
- package/plugin-code-critic/skills/loop/SKILL.md +0 -373
package/src/routes/shared.js
CHANGED
|
@@ -68,6 +68,89 @@ function getModel(req) {
|
|
|
68
68
|
return 'opus';
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Get the provider to use for AI analysis
|
|
73
|
+
* Priority: CLI flag (PAIR_REVIEW_PROVIDER env var) > config.default_provider
|
|
74
|
+
* > config.provider (legacy) > 'claude' default
|
|
75
|
+
* Mirrors getModel() so the --provider CLI flag / PAIR_REVIEW_PROVIDER env var
|
|
76
|
+
* is honored by the web/UI analysis paths the same way --model is.
|
|
77
|
+
* @param {Object} req - Express request object
|
|
78
|
+
* @returns {string} Provider id to use
|
|
79
|
+
*/
|
|
80
|
+
function getProvider(req) {
|
|
81
|
+
// CLI flag takes priority (passed via environment variable)
|
|
82
|
+
if (process.env.PAIR_REVIEW_PROVIDER) {
|
|
83
|
+
return process.env.PAIR_REVIEW_PROVIDER;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Config file setting (default_provider preferred, provider for backwards compatibility)
|
|
87
|
+
const config = req.app.get('config');
|
|
88
|
+
if (config) {
|
|
89
|
+
if (config.default_provider) {
|
|
90
|
+
return config.default_provider;
|
|
91
|
+
}
|
|
92
|
+
// Backwards compatibility with old config key
|
|
93
|
+
if (config.provider) {
|
|
94
|
+
return config.provider;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Default fallback
|
|
99
|
+
return 'claude';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the provider/model pair for a web/UI-triggered analysis.
|
|
104
|
+
*
|
|
105
|
+
* Precedence (highest first):
|
|
106
|
+
* 1. request body (requestProvider / requestModel)
|
|
107
|
+
* 2. env/CLI override (PAIR_REVIEW_PROVIDER / PAIR_REVIEW_MODEL, via getProvider/getModel)
|
|
108
|
+
* 3. saved repo settings (default_provider / default_model)
|
|
109
|
+
* 4. config/legacy defaults (via getProvider/getModel, ending at 'claude'/'opus')
|
|
110
|
+
*
|
|
111
|
+
* The env/CLI override intentionally outranks saved repo settings to match the
|
|
112
|
+
* documented `CLI/env > repo settings` contract already honored by the
|
|
113
|
+
* headless/stack/MCP paths. Because getProvider()/getModel() read the env var
|
|
114
|
+
* first, calling them in the env branch returns the override; the final branch
|
|
115
|
+
* (reached only when the env var is unset) returns config/legacy/default.
|
|
116
|
+
*
|
|
117
|
+
* Both the PR route (src/routes/pr.js) and the local route (src/routes/local.js)
|
|
118
|
+
* MUST use this helper so the two paths resolve the same pair — keeping the
|
|
119
|
+
* resolution logic in one place prevents the paths from silently diverging.
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} req - Express request (source of config + env-backed helpers)
|
|
122
|
+
* @param {Object} [opts]
|
|
123
|
+
* @param {string} [opts.requestProvider] - provider from the request body
|
|
124
|
+
* @param {string} [opts.requestModel] - model from the request body
|
|
125
|
+
* @param {Object} [opts.repoSettings] - saved repo settings row (may be null)
|
|
126
|
+
* @returns {{ provider: string, model: string }}
|
|
127
|
+
*/
|
|
128
|
+
function resolveProviderModel(req, { requestProvider, requestModel, repoSettings } = {}) {
|
|
129
|
+
let provider;
|
|
130
|
+
if (requestProvider) {
|
|
131
|
+
provider = requestProvider;
|
|
132
|
+
} else if (process.env.PAIR_REVIEW_PROVIDER) {
|
|
133
|
+
provider = getProvider(req); // env/CLI override
|
|
134
|
+
} else if (repoSettings && repoSettings.default_provider) {
|
|
135
|
+
provider = repoSettings.default_provider;
|
|
136
|
+
} else {
|
|
137
|
+
provider = getProvider(req); // config/legacy/'claude'
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let model;
|
|
141
|
+
if (requestModel) {
|
|
142
|
+
model = requestModel;
|
|
143
|
+
} else if (process.env.PAIR_REVIEW_MODEL) {
|
|
144
|
+
model = getModel(req); // env/CLI override
|
|
145
|
+
} else if (repoSettings && repoSettings.default_model) {
|
|
146
|
+
model = repoSettings.default_model;
|
|
147
|
+
} else {
|
|
148
|
+
model = getModel(req); // config/legacy/'opus'
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { provider, model };
|
|
152
|
+
}
|
|
153
|
+
|
|
71
154
|
/**
|
|
72
155
|
* Determine completion level and suggestion counts from analysis result
|
|
73
156
|
* @param {Object} result - Analysis result object
|
|
@@ -474,6 +557,8 @@ module.exports = {
|
|
|
474
557
|
activeProcesses,
|
|
475
558
|
activeSetups,
|
|
476
559
|
getModel,
|
|
560
|
+
getProvider,
|
|
561
|
+
resolveProviderModel,
|
|
477
562
|
determineCompletionInfo,
|
|
478
563
|
broadcastProgress,
|
|
479
564
|
broadcastIndexAnalysisEvent,
|
|
@@ -20,6 +20,7 @@ const { mergeInstructions } = require('../utils/instructions');
|
|
|
20
20
|
const { GitWorktreeManager } = require('../git/worktree');
|
|
21
21
|
const { GitHubClient } = require('../github/client');
|
|
22
22
|
const { getGitHubToken, resolveHostBinding, resolveBindingRepositoryFromPR, resolveRepoOptions, resolveLoadSkills, buildCouncilProviderOverrides } = require('../config');
|
|
23
|
+
const { storedHostToOption } = require('../utils/host-resolution');
|
|
23
24
|
const { setupStackPR } = require('../setup/stack-setup');
|
|
24
25
|
const Analyzer = require('../ai/analyzer');
|
|
25
26
|
const { getProviderClass, createProvider } = require('../ai/provider');
|
|
@@ -253,7 +254,14 @@ async function executeStackAnalysis(params) {
|
|
|
253
254
|
// monorepo-style `url_pattern` configs (one `repos[...]` entry serves many
|
|
254
255
|
// captured owner/repo pairs). The PR identity is still used for DB rows and
|
|
255
256
|
// worktree identity.
|
|
256
|
-
|
|
257
|
+
// Resolve the stack binding from the MAIN (trigger) PR's stored host so every
|
|
258
|
+
// sibling is fetched from — and later stamped with — the same host. Stacks
|
|
259
|
+
// don't span systems, so a dual repo's alt-hosted stack must not fall back to
|
|
260
|
+
// github via the two-arg ambiguity rule. `storedHostToOption` applies the
|
|
261
|
+
// legacy-NULL convention; `undefined` (no row) preserves the ambiguity rule.
|
|
262
|
+
const mainStoredHost = await new PRMetadataRepository(db).getPRHost(repository, triggerPRNumber);
|
|
263
|
+
const stackHostOption = storedHostToOption(config, bindingRepository, mainStoredHost);
|
|
264
|
+
const stackBinding = deps.resolveHostBinding(bindingRepository, config, stackHostOption || {});
|
|
257
265
|
const githubToken = stackBinding.token;
|
|
258
266
|
const prDataMap = new Map();
|
|
259
267
|
if (githubToken) {
|
|
@@ -365,6 +373,32 @@ async function executeStackAnalysis(params) {
|
|
|
365
373
|
}
|
|
366
374
|
}
|
|
367
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Resolve the provider/model pair for a single/executable stack analysis.
|
|
378
|
+
*
|
|
379
|
+
* Precedence (highest first): request body > env/CLI override
|
|
380
|
+
* (PAIR_REVIEW_PROVIDER / PAIR_REVIEW_MODEL) > saved repo settings >
|
|
381
|
+
* config default > legacy config key > hard default ('claude' / 'opus').
|
|
382
|
+
*
|
|
383
|
+
* Both env vars are read together so a CLI invocation like
|
|
384
|
+
* `pair-review 123 --provider codex --model gpt-5.5` (which mirrors BOTH flags
|
|
385
|
+
* into the environment) resolves the requested pair, rather than pairing a
|
|
386
|
+
* non-default provider with a default Claude model.
|
|
387
|
+
*
|
|
388
|
+
* @param {Object} [args]
|
|
389
|
+
* @param {string} [args.reqProvider] - provider from the analysis request body
|
|
390
|
+
* @param {string} [args.reqModel] - model from the analysis request body
|
|
391
|
+
* @param {Object} [args.repoSettings] - saved repo settings row (may be null)
|
|
392
|
+
* @param {Object} [args.config] - loaded app config
|
|
393
|
+
* @returns {{ provider: string, model: string }}
|
|
394
|
+
*/
|
|
395
|
+
function _resolveStackProviderModel({ reqProvider, reqModel, repoSettings, config } = {}) {
|
|
396
|
+
const cfg = config || {};
|
|
397
|
+
const provider = reqProvider || process.env.PAIR_REVIEW_PROVIDER || repoSettings?.default_provider || cfg.default_provider || cfg.provider || 'claude';
|
|
398
|
+
const model = reqModel || process.env.PAIR_REVIEW_MODEL || repoSettings?.default_model || cfg.default_model || cfg.model || 'opus';
|
|
399
|
+
return { provider, model };
|
|
400
|
+
}
|
|
401
|
+
|
|
368
402
|
/**
|
|
369
403
|
* Run setup + analysis for a single PR in the stack.
|
|
370
404
|
* Called in parallel for all PRs.
|
|
@@ -437,8 +471,9 @@ async function analyzeStackPR(deps, db, config, {
|
|
|
437
471
|
providerOverridesMap: councilProviderOverridesMap
|
|
438
472
|
});
|
|
439
473
|
} else {
|
|
440
|
-
|
|
441
|
-
|
|
474
|
+
const { provider: selectedProvider, model: selectedModel } = _resolveStackProviderModel({
|
|
475
|
+
reqProvider, reqModel, repoSettings, config
|
|
476
|
+
});
|
|
442
477
|
|
|
443
478
|
// Resolve load_skills across all config tiers
|
|
444
479
|
const providerLoadSkills = config.providers?.[selectedProvider]?.load_skills;
|
|
@@ -959,3 +994,4 @@ module.exports.activeStackAnalyses = activeStackAnalyses;
|
|
|
959
994
|
module.exports.executeStackAnalysis = executeStackAnalysis;
|
|
960
995
|
module.exports.waitForAnalysisCompletion = waitForAnalysisCompletion;
|
|
961
996
|
module.exports.estimateCouncilTimeout = estimateCouncilTimeout;
|
|
997
|
+
module.exports._resolveStackProviderModel = _resolveStackProviderModel;
|
package/src/server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Copyright 2026 Tim Perkins (tjwp) | SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
const express = require('express');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const { loadConfig, getGitHubToken, resolveDbName, warnIfDevModeWithoutDbName } = require('./config');
|
|
5
|
-
const { initializeDatabase, getDatabaseStatus, queryOne, run } = require('./database');
|
|
4
|
+
const { loadConfig, getGitHubToken, resolveDbName, warnIfDevModeWithoutDbName, getRepoConfig, resolveBindingRepositoryFromPR, isExclusiveAltHost } = require('./config');
|
|
5
|
+
const { initializeDatabase, getDatabaseStatus, queryOne, run, PRMetadataRepository } = require('./database');
|
|
6
6
|
const { normalizeRepository } = require('./utils/paths');
|
|
7
7
|
const { applyConfigOverrides, checkAllProviders } = require('./ai');
|
|
8
8
|
const { checkAllChatProviders } = require('./chat/chat-providers');
|
|
@@ -27,6 +27,69 @@ function applyEnvOverrides(config) {
|
|
|
27
27
|
return config;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Apply an explicit `?host` correction from the /pr fast path.
|
|
32
|
+
*
|
|
33
|
+
* Dashboard clicks and pasted URLs can carry `?host` to say which system a PR
|
|
34
|
+
* lives on. When the PR already has metadata + a worktree, the route serves
|
|
35
|
+
* pr.html directly (no setup), so this is the only place that consumes the
|
|
36
|
+
* hint on the fast path. It is a LABEL correction only — the no-collision
|
|
37
|
+
* assumption means a same-numbered PR is the same logical PR, so no re-fetch.
|
|
38
|
+
*
|
|
39
|
+
* Sentinel mapping mirrors setup.html: `'github'` → null (github.com), any
|
|
40
|
+
* other non-empty string → an alt `api_host` URL, absent/empty → no-op. The
|
|
41
|
+
* target is validated against config so a stale link can't stamp a host the
|
|
42
|
+
* repo isn't configured for (which `resolveHostBinding` would later reject);
|
|
43
|
+
* on any mismatch we warn and ignore rather than break the page load.
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} db - Database handle
|
|
46
|
+
* @param {Object} config - Loaded config
|
|
47
|
+
* @param {string} owner - URL owner segment
|
|
48
|
+
* @param {string} repo - URL repo segment
|
|
49
|
+
* @param {string} repository - Normalized `owner/repo` used as the pr_metadata key
|
|
50
|
+
* @param {number} prNumber - PR number
|
|
51
|
+
* @param {*} rawHost - `req.query.host` (already URL-decoded by Express)
|
|
52
|
+
* @returns {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
async function applyHostQueryCorrection(db, config, owner, repo, repository, prNumber, rawHost) {
|
|
55
|
+
if (rawHost === undefined || rawHost === null || rawHost === '') return;
|
|
56
|
+
if (typeof rawHost !== 'string') return;
|
|
57
|
+
|
|
58
|
+
const targetHost = rawHost === 'github' ? null : rawHost;
|
|
59
|
+
|
|
60
|
+
// Resolve the repo's config entry (handles monorepo-style keys matched via
|
|
61
|
+
// url_pattern) to learn its configured api_host.
|
|
62
|
+
const bindingRepo = resolveBindingRepositoryFromPR(owner, repo, config);
|
|
63
|
+
const repoConfig = getRepoConfig(config, bindingRepo);
|
|
64
|
+
const configuredApiHost = (repoConfig && typeof repoConfig.api_host === 'string' && repoConfig.api_host)
|
|
65
|
+
? repoConfig.api_host
|
|
66
|
+
: null;
|
|
67
|
+
|
|
68
|
+
if (targetHost !== null) {
|
|
69
|
+
// A string host must match this repo's configured api_host exactly.
|
|
70
|
+
if (targetHost !== configuredApiHost) {
|
|
71
|
+
logger.warn(`Ignoring ?host correction for ${repository} #${prNumber}: "${targetHost}" does not match configured api_host (${configuredApiHost || 'none'})`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
} else if (isExclusiveAltHost(repoConfig)) {
|
|
75
|
+
// The github sentinel is meaningless for an exclusive alt-host repo (it has
|
|
76
|
+
// no github.com presence); stamping null would break binding resolution.
|
|
77
|
+
logger.warn(`Ignoring ?host=github correction for ${repository} #${prNumber}: repo is an exclusive alt-host repo with no github.com presence`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const prMetadataRepo = new PRMetadataRepository(db);
|
|
82
|
+
const stored = await prMetadataRepo.getPRHost(repository, prNumber);
|
|
83
|
+
// Fast path only runs when a row exists, so `stored` is null or a string.
|
|
84
|
+
// Skip the write when it already matches (avoids a needless updated_at bump).
|
|
85
|
+
if (stored === targetHost) return;
|
|
86
|
+
|
|
87
|
+
const changed = await prMetadataRepo.updatePRHost(repository, prNumber, targetHost);
|
|
88
|
+
if (changed) {
|
|
89
|
+
logger.info(`Corrected stored host for ${repository} #${prNumber} to ${targetHost === null ? 'github.com' : targetHost} via ?host`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
30
93
|
/**
|
|
31
94
|
* Request logging middleware (disabled for cleaner output)
|
|
32
95
|
*/
|
|
@@ -276,6 +339,14 @@ async function startServer(sharedDb = null, sharedPoolLifecycle = null) {
|
|
|
276
339
|
if (worktree) {
|
|
277
340
|
// Update last_accessed_at so the recent reviews list reflects actual access
|
|
278
341
|
run(db, 'UPDATE pr_metadata SET last_accessed_at = ? WHERE id = ?', [new Date().toISOString(), existing.id]).catch(err => logger.warn(`Failed to update last_accessed_at: ${err.message}`));
|
|
342
|
+
// Persist an explicit ?host correction BEFORE serving so pr.html's
|
|
343
|
+
// first data fetch resolves the binding against the corrected host.
|
|
344
|
+
// Never let a bad hint break the page — the helper swallows and warns.
|
|
345
|
+
try {
|
|
346
|
+
await applyHostQueryCorrection(db, config, owner, repo, repository, prNumber, req.query.host);
|
|
347
|
+
} catch (err) {
|
|
348
|
+
logger.warn(`Failed to apply ?host correction for ${repository} #${prNumber}: ${err.message}`);
|
|
349
|
+
}
|
|
279
350
|
res.sendFile(path.join(__dirname, '..', 'public', 'pr.html'));
|
|
280
351
|
} else {
|
|
281
352
|
logger.info(`PR metadata exists but no worktree for ${repository} #${prNumber}, serving setup page`);
|
|
@@ -521,4 +592,4 @@ if (require.main === module) {
|
|
|
521
592
|
startServer();
|
|
522
593
|
}
|
|
523
594
|
|
|
524
|
-
module.exports = { startServer, applyEnvOverrides };
|
|
595
|
+
module.exports = { startServer, applyEnvOverrides, applyHostQueryCorrection };
|
package/src/setup/local-setup.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Copyright 2026 Tim Perkins (tjwp) | SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
const { findGitRoot, getHeadSha, getCurrentBranch, getRepositoryName, generateScopedDiff, generateLocalReviewId, computeScopedDigest, findMainGitRoot } = require('../local-review');
|
|
2
|
+
const { findGitRoot, getHeadSha, getCurrentBranch, getRepositoryName, generateScopedDiff, generateLocalReviewId, computeScopedDigest, findMainGitRoot, resolveScopeAndBase, persistScopeSelection } = require('../local-review');
|
|
3
3
|
const { ReviewRepository, RepoSettingsRepository } = require('../database');
|
|
4
4
|
const { localReviewDiffs } = require('../routes/shared');
|
|
5
5
|
const { fireHooks, hasHooks } = require('../hooks/hook-runner');
|
|
6
6
|
const { buildReviewStartedPayload, buildReviewLoadedPayload, getCachedUser } = require('../hooks/payloads');
|
|
7
|
-
const { STOPS
|
|
7
|
+
const { STOPS } = require('../local-scope');
|
|
8
8
|
const logger = require('../utils/logger');
|
|
9
9
|
const { LOCAL_REVIEW_PATH_URL_ERROR, rejectUrlLikeLocalReviewPath } = require('../utils/local-path-input');
|
|
10
10
|
const path = require('path');
|
|
@@ -21,9 +21,12 @@ const fs = require('fs').promises;
|
|
|
21
21
|
* @param {string} options.targetPath - Path to review (file or directory)
|
|
22
22
|
* @param {Function} [options.onProgress] - Progress callback ({ step, status, message })
|
|
23
23
|
* @param {Object} [options.config] - App config (for hooks)
|
|
24
|
+
* @param {Object} [options.flags] - Scope override { scope?, base? } forwarded from
|
|
25
|
+
* a delegated `--scope`/`--base` launch. The route MUST have validated these
|
|
26
|
+
* first. Absent flags leave scope resolution at persisted/default (unchanged).
|
|
24
27
|
* @returns {Promise<Object>} Review session info
|
|
25
28
|
*/
|
|
26
|
-
async function setupLocalReview({ db, targetPath, onProgress, config }) {
|
|
29
|
+
async function setupLocalReview({ db, targetPath, onProgress, config, flags = {} }) {
|
|
27
30
|
const progress = typeof onProgress === 'function'
|
|
28
31
|
? onProgress
|
|
29
32
|
: () => {};
|
|
@@ -104,10 +107,14 @@ async function setupLocalReview({ db, targetPath, onProgress, config }) {
|
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
// ── Step: diff ──────────────────────────────────────────────────────
|
|
107
|
-
|
|
110
|
+
// Resolve scope + base via the shared helper so a delegated --scope/--base
|
|
111
|
+
// launch is honored identically to the cold-start CLI path. With no override
|
|
112
|
+
// (flags absent) this yields the persisted/default scope — unchanged behavior.
|
|
113
|
+
let diff, stats, digest, scopeStart, scopeEnd, baseBranch, scopeOverridden;
|
|
108
114
|
try {
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
({ scopeStart, scopeEnd, baseBranch, scopeOverridden } = await resolveScopeAndBase({
|
|
116
|
+
existingReview, flags, repoPath, branch, repository, config
|
|
117
|
+
}));
|
|
111
118
|
|
|
112
119
|
progress({ step: 'diff', status: 'running', message: 'Generating diff for local changes...' });
|
|
113
120
|
|
|
@@ -146,6 +153,15 @@ async function setupLocalReview({ db, targetPath, onProgress, config }) {
|
|
|
146
153
|
});
|
|
147
154
|
}
|
|
148
155
|
|
|
156
|
+
// Persist an explicitly-overridden scope (and its resolved base) so the web
|
|
157
|
+
// UI opens this delegated session at the requested scope — only after the
|
|
158
|
+
// diff above succeeded. No-op when no override was supplied.
|
|
159
|
+
if (scopeOverridden) {
|
|
160
|
+
await persistScopeSelection({
|
|
161
|
+
reviewRepo: storeRepo, sessionId, existingReview, scopeStart, scopeEnd, baseBranch, branch, repoPath
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
149
165
|
localReviewDiffs.set(sessionId, { diff, stats, digest });
|
|
150
166
|
|
|
151
167
|
progress({ step: 'store', status: 'completed', message: `Review session ${sessionId} stored` });
|
|
@@ -159,7 +175,7 @@ async function setupLocalReview({ db, targetPath, onProgress, config }) {
|
|
|
159
175
|
if (config && hasHooks(hookEvent, config)) {
|
|
160
176
|
getCachedUser(config).then(user => {
|
|
161
177
|
const builder = existingReview ? buildReviewLoadedPayload : buildReviewStartedPayload;
|
|
162
|
-
|
|
178
|
+
// Reflect the resolved (possibly overridden) scope, not just the persisted one.
|
|
163
179
|
const si = STOPS.indexOf(scopeStart);
|
|
164
180
|
const ei = STOPS.indexOf(scopeEnd);
|
|
165
181
|
const scope = STOPS.slice(si, ei + 1);
|