@bridge_gpt/mcp-server 0.2.16 → 0.2.18
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/build/agents.generated.js +2 -2
- package/build/commands.generated.js +6 -6
- package/build/conductor/bridge-api-client.js +191 -11
- package/build/conductor/claude-hook.js +22 -4
- package/build/conductor/cli.js +11 -13
- package/build/conductor/done-gate.js +5 -0
- package/build/conductor/epic-reconcile.js +62 -13
- package/build/conductor/epic-runtime.js +447 -35
- package/build/conductor/epic-state.js +517 -63
- package/build/conductor/errors.js +41 -0
- package/build/conductor/event-accessors.js +234 -0
- package/build/conductor/file-scope-guard.js +201 -0
- package/build/conductor/github-mergeability.js +85 -0
- package/build/conductor/local-merge.js +47 -1
- package/build/conductor/merge-identity.js +41 -0
- package/build/conductor/merge-ledger.js +13 -68
- package/build/conductor/plan.js +12 -2
- package/build/conductor/pr-discovery.js +11 -1
- package/build/conductor/supervisor-config.js +4 -39
- package/build/conductor/supervisor-escalation.js +10 -26
- package/build/conductor/supervisor-ledger.js +5 -12
- package/build/conductor/supervisor-message-relay.js +2 -5
- package/build/conductor/supervisor-notification.js +1 -1
- package/build/conductor/supervisor-runtime.js +12 -54
- package/build/conductor/supervisor-state.js +4 -18
- package/build/conductor/supervisor-types.js +2 -2
- package/build/conductor/taxonomy.js +4 -0
- package/build/conductor-bin.js +2333 -666
- package/build/conductor-claude-hook-bin.js +4 -2
- package/build/doctor.js +32 -0
- package/build/index.js +10125 -8522
- package/build/install-bridge.js +25 -8
- package/build/install-doctor.js +387 -0
- package/build/pipelines.generated.js +30 -5
- package/build/regression-check.js +53 -1
- package/build/review-tickets.js +175 -21
- package/build/start-tickets-conductor.js +22 -6
- package/build/start-tickets-prereqs.js +33 -3
- package/build/start-tickets.js +122 -22
- package/build/version.generated.js +1 -1
- package/package.json +5 -5
- package/pipelines/review-ticket.json +24 -2
- package/public/css/main.min.css +3272 -1
- package/public/css/main.min.css.map +1 -1
- package/smoke-test/SMOKE-TEST.md +4 -2
package/build/review-tickets.js
CHANGED
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
* This path is worktree-free and base-branch-free; the human CLI path leaves
|
|
16
16
|
* `epic` unset and retains byte-for-byte identical behavior.
|
|
17
17
|
*/
|
|
18
|
-
import { createDefaultStartTicketsDeps, detectTerminal, runWithConcurrency, buildAgentInvocation, shSquoteInner, powershellSquote, isCommandOnPath, resolveFirstCommandOnPath, tmuxSessionNameForTicket, TICKET_KEY_PATTERN, DEFAULT_MAX_PARALLEL, } from "./start-tickets.js";
|
|
18
|
+
import { createDefaultStartTicketsDeps, detectTerminal, runWithConcurrency, buildAgentInvocation, shSquoteInner, powershellSquote, isCommandOnPath, resolveFirstCommandOnPath, tmuxSessionNameForTicket, TICKET_KEY_PATTERN, DEFAULT_MAX_PARALLEL, validateBranchName, fetchAndResolveBaseSha, fetchStartTicketsConfigField, resolveStartTicketsBridgeApiAccess, } from "./start-tickets.js";
|
|
19
19
|
import { WINDOWS_TERMINAL_COMMAND, WINDOWS_POWERSHELL_CANDIDATES, TMUX_COMMAND, isSupportedStartTicketsPlatform, unsupportedPlatformMessage, } from "./start-tickets-prereqs.js";
|
|
20
20
|
import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, isValidModelAlias, } from "./agent-registry.js";
|
|
21
21
|
import { injectConductorEnvIntoShellCommand, buildEpicIdentityEnv, mintStartTicketsRunId, } from "./start-tickets-conductor.js";
|
|
22
|
+
/** Default base context: no flags threaded through (matches pre-BAPI-474 behavior). */
|
|
23
|
+
const NO_BASE_CONTEXT = { noRefreshBase: false };
|
|
22
24
|
// ---------------------------------------------------------------------------
|
|
23
25
|
// Usage
|
|
24
26
|
// ---------------------------------------------------------------------------
|
|
@@ -35,6 +37,8 @@ export function getReviewTicketsUsage() {
|
|
|
35
37
|
" --model VALUE Model alias to pass as --model to the agent (optional passthrough)",
|
|
36
38
|
" --max-parallel N Max tabs to spawn concurrently (default: 3)",
|
|
37
39
|
" --dry-run Print intended commands; creates no tabs and opens no sessions",
|
|
40
|
+
" --base-branch BRANCH Base branch to fetch+pin for grounding (default: config base_branch, else \"main\")",
|
|
41
|
+
" --no-refresh-base Skip the parent-fetch-once base pin; restores git-free, in-place-grounded behavior",
|
|
38
42
|
" -h, --help Show this help",
|
|
39
43
|
"",
|
|
40
44
|
"Examples:",
|
|
@@ -42,19 +46,107 @@ export function getReviewTicketsUsage() {
|
|
|
42
46
|
" npx -y @bridge_gpt/mcp-server review-tickets --auto --rounds=1 BAPI-1 BAPI-2",
|
|
43
47
|
" npx -y @bridge_gpt/mcp-server review-tickets --review BAPI-1=auto,rounds=1 --review BAPI-2=rounds=2 BAPI-1 BAPI-2",
|
|
44
48
|
" npx -y @bridge_gpt/mcp-server review-tickets --dry-run BAPI-1 BAPI-2",
|
|
49
|
+
" npx -y @bridge_gpt/mcp-server review-tickets --base-branch=develop BAPI-1 BAPI-2",
|
|
50
|
+
" npx -y @bridge_gpt/mcp-server review-tickets --no-refresh-base BAPI-1 BAPI-2",
|
|
45
51
|
"",
|
|
46
52
|
"Each KEY must match [A-Z]+-[0-9]+ (e.g., BAPI-248).",
|
|
47
53
|
"",
|
|
48
|
-
"Prerequisites (terminal launcher
|
|
49
|
-
" macOS osascript",
|
|
50
|
-
" Windows wt.exe or PowerShell",
|
|
51
|
-
" Linux tmux",
|
|
54
|
+
"Prerequisites (terminal launcher, plus git — no wt, git-wt, or Worktrunk required):",
|
|
55
|
+
" macOS osascript, git",
|
|
56
|
+
" Windows wt.exe or PowerShell, git",
|
|
57
|
+
" Linux tmux, git",
|
|
52
58
|
"",
|
|
53
|
-
"review-tickets runs all tabs in the current repository cwd and creates no Worktrunk worktrees.",
|
|
54
|
-
"
|
|
59
|
+
"review-tickets runs all tabs in the current repository cwd and creates no Worktrunk worktrees. It now",
|
|
60
|
+
"requires git on PATH (BAPI-474): before spawning tabs it fetches origin/<base_branch> once and pins a",
|
|
61
|
+
"single base_sha for the whole batch, so every spawned review grounds against the same base tree even if",
|
|
62
|
+
"origin advances mid-batch. Pass --no-refresh-base to skip this and restore the prior git-free behavior.",
|
|
63
|
+
"--dry-run works on any platform, including unsupported ones, and never performs the base-branch fetch.",
|
|
55
64
|
].join("\n");
|
|
56
65
|
}
|
|
57
66
|
// ---------------------------------------------------------------------------
|
|
67
|
+
// Fail-loud base-fetch diagnostic (BAPI-474 D-4)
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
const ANSI_BOLD = "\x1b[1m";
|
|
70
|
+
// Warm Coral — a high-contrast 256-color red/coral used exclusively for this
|
|
71
|
+
// fail-loud alert box so a base-branch fetch failure is unmistakable in a scan
|
|
72
|
+
// of scrollback, without hijacking the terminal's general error-red semantics.
|
|
73
|
+
const ANSI_WARM_CORAL = "\x1b[38;5;203m";
|
|
74
|
+
const ANSI_RESET = "\x1b[0m";
|
|
75
|
+
/**
|
|
76
|
+
* Render a bordered, high-contrast fail-loud alert box for a parent-fetch-once
|
|
77
|
+
* failure (BAPI-474 D-4 "Design Direction Fail-Loud"): an operation-failed
|
|
78
|
+
* header, the target base branch, and an actionable remediation line. Pure
|
|
79
|
+
* formatting (no I/O) so it stays unit-testable; the caller is responsible for
|
|
80
|
+
* writing it to stderr.
|
|
81
|
+
*/
|
|
82
|
+
export function formatBaseFetchFailureBox(baseBranch, error) {
|
|
83
|
+
const title = "review-tickets: base-branch fetch failed";
|
|
84
|
+
// The box always shows a dedicated Remediation line below. fetchAndResolveBaseSha's
|
|
85
|
+
// own error text embeds the same remediation clause because it also has to stand
|
|
86
|
+
// alone when surfaced outside a box (e.g. raw JSON from materialize_fresh_base) —
|
|
87
|
+
// strip it here so the boxed rendering doesn't say it twice.
|
|
88
|
+
const reason = error
|
|
89
|
+
.replace(/\s*Check your network and 'git remote get-url origin', or pass --no-refresh-base to skip\.?\s*$/i, "")
|
|
90
|
+
.trim();
|
|
91
|
+
const bodyLines = [
|
|
92
|
+
`Target base branch: origin/${baseBranch}`,
|
|
93
|
+
`Reason: ${reason}`,
|
|
94
|
+
"Remediation: check your network and 'git remote get-url origin', or rerun with --no-refresh-base to evaluate in-place.",
|
|
95
|
+
];
|
|
96
|
+
const contentWidth = Math.max(title.length + 2, ...bodyLines.map((l) => l.length)) + 2;
|
|
97
|
+
const horizontal = "─".repeat(contentWidth);
|
|
98
|
+
const pad = (s) => `│ ${s}${" ".repeat(contentWidth - s.length - 1)}│`;
|
|
99
|
+
const lines = [
|
|
100
|
+
`╭${horizontal}╮`,
|
|
101
|
+
pad(`✖ ${title}`),
|
|
102
|
+
`├${horizontal}┤`,
|
|
103
|
+
...bodyLines.map((l) => pad(l)),
|
|
104
|
+
`╰${horizontal}╯`,
|
|
105
|
+
];
|
|
106
|
+
return `${ANSI_BOLD}${ANSI_WARM_CORAL}${lines.join("\n")}${ANSI_RESET}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve ONE base_branch/base_sha pin for the whole review-tickets batch
|
|
110
|
+
* BEFORE any tab is spawned (D-3), so a mid-batch `origin` advance can never
|
|
111
|
+
* mix bases within one run. Skipped entirely (never fetches) when
|
|
112
|
+
* `--no-refresh-base` was passed. base_branch precedence: `--base-branch` flag
|
|
113
|
+
* > config field `base_branch` (BAPI-313) > `"main"`. The config-field lookup
|
|
114
|
+
* is best-effort — any failure there falls through to `"main"`; only the git
|
|
115
|
+
* fetch/resolve itself is a hard failure (the caller applies the D-4 fail-loud
|
|
116
|
+
* response to that).
|
|
117
|
+
*/
|
|
118
|
+
export async function resolveBatchBaseContext(deps, options) {
|
|
119
|
+
if (options.noRefreshBase) {
|
|
120
|
+
return { ok: true, context: { noRefreshBase: true } };
|
|
121
|
+
}
|
|
122
|
+
let effectiveBaseBranch = (options.baseBranch ?? "").trim();
|
|
123
|
+
if (effectiveBaseBranch.length === 0) {
|
|
124
|
+
try {
|
|
125
|
+
const accessResult = await resolveStartTicketsBridgeApiAccess(deps);
|
|
126
|
+
if (accessResult.ok) {
|
|
127
|
+
const configValue = await fetchStartTicketsConfigField(accessResult.access, "base_branch");
|
|
128
|
+
if (typeof configValue === "string" && configValue.trim().length > 0) {
|
|
129
|
+
effectiveBaseBranch = configValue.trim();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
/* config-field lookup is best-effort; fall through to the "main" default */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (effectiveBaseBranch.length === 0) {
|
|
138
|
+
effectiveBaseBranch = "main";
|
|
139
|
+
}
|
|
140
|
+
const fetchResult = await fetchAndResolveBaseSha(deps, effectiveBaseBranch);
|
|
141
|
+
if (!fetchResult.ok) {
|
|
142
|
+
return { ok: false, baseBranch: effectiveBaseBranch, error: fetchResult.error };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
context: { baseBranch: effectiveBaseBranch, baseSha: fetchResult.base_sha, noRefreshBase: false },
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
58
150
|
// Argument parsing
|
|
59
151
|
// ---------------------------------------------------------------------------
|
|
60
152
|
export function parseReviewOverrideEntry(entry, requestedKeys) {
|
|
@@ -105,6 +197,8 @@ export function parseReviewTicketsArgs(argv) {
|
|
|
105
197
|
let maxParallelRaw;
|
|
106
198
|
let agentName = DEFAULT_AGENT_NAME;
|
|
107
199
|
let modelAlias;
|
|
200
|
+
let noRefreshBase = false;
|
|
201
|
+
let baseBranch;
|
|
108
202
|
const keys = [];
|
|
109
203
|
const rawReviewEntries = [];
|
|
110
204
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -123,6 +217,30 @@ export function parseReviewTicketsArgs(argv) {
|
|
|
123
217
|
globalAuto = true;
|
|
124
218
|
continue;
|
|
125
219
|
}
|
|
220
|
+
if (arg === "--no-refresh-base") {
|
|
221
|
+
noRefreshBase = true;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (arg === "--base-branch" || arg.startsWith("--base-branch=")) {
|
|
225
|
+
let value;
|
|
226
|
+
if (arg.startsWith("--base-branch=")) {
|
|
227
|
+
value = arg.slice("--base-branch=".length);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const next = i + 1 < argv.length ? argv[i + 1] : undefined;
|
|
231
|
+
if (next === undefined || next.startsWith("-")) {
|
|
232
|
+
return { status: "error", message: "--base-branch requires a value (a branch name)." };
|
|
233
|
+
}
|
|
234
|
+
value = takeValue();
|
|
235
|
+
}
|
|
236
|
+
const trimmed = (value ?? "").trim();
|
|
237
|
+
const error = validateBranchName(trimmed);
|
|
238
|
+
if (error) {
|
|
239
|
+
return { status: "error", message: `Invalid --base-branch value: ${error}` };
|
|
240
|
+
}
|
|
241
|
+
baseBranch = trimmed;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
126
244
|
if (arg === "--rounds" || arg.startsWith("--rounds=")) {
|
|
127
245
|
let value;
|
|
128
246
|
if (arg.startsWith("--rounds=")) {
|
|
@@ -267,6 +385,8 @@ export function parseReviewTicketsArgs(argv) {
|
|
|
267
385
|
auto: globalAuto,
|
|
268
386
|
rounds: globalRounds,
|
|
269
387
|
reviewOverrides,
|
|
388
|
+
noRefreshBase,
|
|
389
|
+
baseBranch,
|
|
270
390
|
},
|
|
271
391
|
};
|
|
272
392
|
}
|
|
@@ -289,24 +409,27 @@ export function resolveEffectiveReviewMode(key, options) {
|
|
|
289
409
|
// hard-wired to `/implement-ticket`. Review-aware builders are used instead so
|
|
290
410
|
// the prompt uses `/review-ticket` and no directory change is prepended.
|
|
291
411
|
// ---------------------------------------------------------------------------
|
|
292
|
-
export function buildReviewTicketPrompt(key, mode) {
|
|
412
|
+
export function buildReviewTicketPrompt(key, mode, base = NO_BASE_CONTEXT) {
|
|
293
413
|
const autoFlag = mode.auto ? " --auto" : "";
|
|
294
|
-
|
|
414
|
+
const baseBranchFlag = base.baseBranch ? ` --base-branch=${base.baseBranch}` : "";
|
|
415
|
+
const baseShaFlag = base.baseSha ? ` --base-sha=${base.baseSha}` : "";
|
|
416
|
+
const noRefreshFlag = base.noRefreshBase ? " --no-refresh-base" : "";
|
|
417
|
+
return `/review-ticket ${key}${autoFlag} --rounds=${mode.rounds}${baseBranchFlag}${baseShaFlag}${noRefreshFlag}`;
|
|
295
418
|
}
|
|
296
|
-
export function buildPosixReviewAgentShellCommand(agent, key, mode, cwd, modelAlias) {
|
|
297
|
-
const prompt = buildReviewTicketPrompt(key, mode);
|
|
419
|
+
export function buildPosixReviewAgentShellCommand(agent, key, mode, cwd, modelAlias, base = NO_BASE_CONTEXT) {
|
|
420
|
+
const prompt = buildReviewTicketPrompt(key, mode, base);
|
|
298
421
|
const invocation = buildAgentInvocation(agent, prompt, (p) => `'${shSquoteInner(p)}'`, modelAlias);
|
|
299
422
|
return `cd '${shSquoteInner(cwd)}' && ${invocation}`;
|
|
300
423
|
}
|
|
301
|
-
export function buildPowerShellReviewAgentShellCommand(agent, key, mode, modelAlias) {
|
|
302
|
-
const prompt = buildReviewTicketPrompt(key, mode);
|
|
424
|
+
export function buildPowerShellReviewAgentShellCommand(agent, key, mode, modelAlias, base = NO_BASE_CONTEXT) {
|
|
425
|
+
const prompt = buildReviewTicketPrompt(key, mode, base);
|
|
303
426
|
return buildAgentInvocation(agent, prompt, powershellSquote, modelAlias);
|
|
304
427
|
}
|
|
305
|
-
export function buildReviewAgentShellCommand(agent, key, mode, platform, cwd, modelAlias) {
|
|
428
|
+
export function buildReviewAgentShellCommand(agent, key, mode, platform, cwd, modelAlias, base = NO_BASE_CONTEXT) {
|
|
306
429
|
if (platform === "win32") {
|
|
307
|
-
return buildPowerShellReviewAgentShellCommand(agent, key, mode, modelAlias);
|
|
430
|
+
return buildPowerShellReviewAgentShellCommand(agent, key, mode, modelAlias, base);
|
|
308
431
|
}
|
|
309
|
-
return buildPosixReviewAgentShellCommand(agent, key, mode, cwd, modelAlias);
|
|
432
|
+
return buildPosixReviewAgentShellCommand(agent, key, mode, cwd, modelAlias, base);
|
|
310
433
|
}
|
|
311
434
|
// ---------------------------------------------------------------------------
|
|
312
435
|
// Unsupported platform message for review-tickets
|
|
@@ -325,6 +448,16 @@ export async function runReviewTicketsPreflight(deps, options) {
|
|
|
325
448
|
if (!isSupportedStartTicketsPlatform(deps.platform)) {
|
|
326
449
|
return { ok: false, error: unsupportedReviewTicketsPlatformMessage(deps.platform) };
|
|
327
450
|
}
|
|
451
|
+
// BAPI-474: the parent-fetch-once base pin adds a git prerequisite unless the
|
|
452
|
+
// caller opted out via --no-refresh-base. Checked here (before any tab is
|
|
453
|
+
// spawned) so a missing git fails loud with a clear, actionable message.
|
|
454
|
+
if (!options.noRefreshBase && !(await isCommandOnPath(deps, "git"))) {
|
|
455
|
+
return {
|
|
456
|
+
ok: false,
|
|
457
|
+
error: "git is required for review-tickets' parent-fetch-once base-branch pin (BAPI-474) but was not " +
|
|
458
|
+
"found on PATH. Install git, or rerun with --no-refresh-base to skip the fetch and evaluate in-place.",
|
|
459
|
+
};
|
|
460
|
+
}
|
|
328
461
|
if (deps.platform === "darwin") {
|
|
329
462
|
if (await isCommandOnPath(deps, "osascript"))
|
|
330
463
|
return { ok: true };
|
|
@@ -355,10 +488,10 @@ export async function runReviewTicketsPreflight(deps, options) {
|
|
|
355
488
|
// ---------------------------------------------------------------------------
|
|
356
489
|
// Plan rows + orchestration
|
|
357
490
|
// ---------------------------------------------------------------------------
|
|
358
|
-
export function buildReviewPlanRows(deps, options, agent, status, overrides = {}) {
|
|
491
|
+
export function buildReviewPlanRows(deps, options, agent, status, overrides = {}, baseContext = NO_BASE_CONTEXT) {
|
|
359
492
|
return options.keys.map((key) => {
|
|
360
493
|
const mode = resolveEffectiveReviewMode(key, options);
|
|
361
|
-
const baseCommand = buildReviewAgentShellCommand(agent, key, mode, deps.platform, deps.cwd, options.modelAlias);
|
|
494
|
+
const baseCommand = buildReviewAgentShellCommand(agent, key, mode, deps.platform, deps.cwd, options.modelAlias, baseContext);
|
|
362
495
|
let command = baseCommand;
|
|
363
496
|
let runId;
|
|
364
497
|
if (options.epic) {
|
|
@@ -379,6 +512,14 @@ export function buildReviewPlanRows(deps, options, agent, status, overrides = {}
|
|
|
379
512
|
};
|
|
380
513
|
});
|
|
381
514
|
}
|
|
515
|
+
/** Build a dry-run preview base context from parsed CLI options only — never fetches. */
|
|
516
|
+
function previewBaseContextFromOptions(options) {
|
|
517
|
+
if (options.noRefreshBase)
|
|
518
|
+
return { noRefreshBase: true };
|
|
519
|
+
if (options.baseBranch)
|
|
520
|
+
return { baseBranch: options.baseBranch, noRefreshBase: false };
|
|
521
|
+
return NO_BASE_CONTEXT;
|
|
522
|
+
}
|
|
382
523
|
export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
383
524
|
const agent = resolveAgentSpec(options.agentName);
|
|
384
525
|
if (!agent) {
|
|
@@ -395,7 +536,9 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
395
536
|
"and would be re-claimed for each key in the batch. Call orchestrateReviewTickets once per ticket instead.",
|
|
396
537
|
};
|
|
397
538
|
}
|
|
398
|
-
|
|
539
|
+
// --dry-run never performs the base-branch fetch (side-effect-free preview);
|
|
540
|
+
// it reflects only what the user explicitly passed on the CLI.
|
|
541
|
+
const rows = buildReviewPlanRows(deps, options, agent, "dry-run", overrides, previewBaseContextFromOptions(options));
|
|
399
542
|
return { ok: true, rows };
|
|
400
543
|
}
|
|
401
544
|
const preflight = await runReviewTicketsPreflight(deps, options);
|
|
@@ -411,10 +554,21 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
411
554
|
"and would be re-claimed for each key in the batch. Call orchestrateReviewTickets once per ticket instead.",
|
|
412
555
|
};
|
|
413
556
|
}
|
|
557
|
+
// BAPI-474 D-3: parent-fetch-once — resolve ONE base_branch/base_sha pin for
|
|
558
|
+
// the whole batch before any tab is spawned. D-4: on failure (and no
|
|
559
|
+
// --no-refresh-base), fail loud with a bordered diagnostic and halt the
|
|
560
|
+
// fan-out entirely rather than silently grounding in place.
|
|
561
|
+
const resolveContext = overrides.resolveBaseContext ?? resolveBatchBaseContext;
|
|
562
|
+
const baseContextResult = await resolveContext(deps, options);
|
|
563
|
+
if (!baseContextResult.ok) {
|
|
564
|
+
console.error(formatBaseFetchFailureBox(baseContextResult.baseBranch, baseContextResult.error));
|
|
565
|
+
return { ok: false, error: baseContextResult.error };
|
|
566
|
+
}
|
|
567
|
+
const baseContext = baseContextResult.context;
|
|
414
568
|
const terminal = detectTerminal(undefined, deps.env);
|
|
415
569
|
const rows = await runWithConcurrency(options.keys, options.maxParallel, async (key) => {
|
|
416
570
|
const mode = resolveEffectiveReviewMode(key, options);
|
|
417
|
-
const baseShellCommand = buildReviewAgentShellCommand(agent, key, mode, deps.platform, deps.cwd, options.modelAlias);
|
|
571
|
+
const baseShellCommand = buildReviewAgentShellCommand(agent, key, mode, deps.platform, deps.cwd, options.modelAlias, baseContext);
|
|
418
572
|
let shellCommand = baseShellCommand;
|
|
419
573
|
let runId;
|
|
420
574
|
if (options.epic) {
|
|
@@ -565,7 +719,7 @@ export async function runReviewTicketsCli(argv, overrides = {}) {
|
|
|
565
719
|
errorLog(`Error: Unknown agent: '${options.agentName}'. Valid agents: ${formatValidAgentNames()}.`);
|
|
566
720
|
return 1;
|
|
567
721
|
}
|
|
568
|
-
const rows = buildReviewPlanRows(deps, options, agent, "dry-run", overrides.orchestrateOverrides);
|
|
722
|
+
const rows = buildReviewPlanRows(deps, options, agent, "dry-run", overrides.orchestrateOverrides, previewBaseContextFromOptions(options));
|
|
569
723
|
for (const row of rows) {
|
|
570
724
|
log(`DRY-RUN: ${row.key} -> ${row.command}`);
|
|
571
725
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { randomBytes } from "node:crypto";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { normalizeDeclaredTouchedFiles } from "./conductor/file-scope-guard.js";
|
|
25
26
|
import { resolveProfiles } from "./mcp-profile.js";
|
|
26
27
|
import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
@@ -54,15 +55,22 @@ export function mintStartTicketsWorkerId(ticketKey, agentName, fragment = random
|
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
56
57
|
* Build the secret-free epic identity env keys from an {@link EpicDispatchIdentity}.
|
|
57
|
-
* Strict allowlist — constructs a FRESH object with
|
|
58
|
-
* never copies arbitrary parent env so credentials cannot leak.
|
|
58
|
+
* Strict allowlist — constructs a FRESH object with only named conductor keys,
|
|
59
|
+
* never copies arbitrary parent env so credentials cannot leak. The declared
|
|
60
|
+
* touched-file set (BAPI-507 N-2) is included as compact JSON ONLY when it
|
|
61
|
+
* normalizes to a non-empty repo-relative set.
|
|
59
62
|
*/
|
|
60
63
|
export function buildEpicIdentityEnv(epic) {
|
|
61
|
-
|
|
64
|
+
const env = {
|
|
62
65
|
BAPI_CONDUCTOR_EPIC_KEY: epic.epic_key,
|
|
63
66
|
BAPI_CONDUCTOR_EPIC_RUN_ID: epic.epic_run_id,
|
|
64
67
|
BAPI_CONDUCTOR_PLAN_VERSION: String(epic.plan_version),
|
|
65
68
|
};
|
|
69
|
+
const declared = normalizeDeclaredTouchedFiles(epic.declared_touched_files);
|
|
70
|
+
if (declared.length > 0) {
|
|
71
|
+
env.BAPI_CONDUCTOR_DECLARED_TOUCHED_FILES_JSON = JSON.stringify(declared);
|
|
72
|
+
}
|
|
73
|
+
return env;
|
|
66
74
|
}
|
|
67
75
|
// ---------------------------------------------------------------------------
|
|
68
76
|
// Conductor context
|
|
@@ -182,11 +190,19 @@ export function buildConductorWorkerEnv(context, worker, parentEnv) {
|
|
|
182
190
|
// ---------------------------------------------------------------------------
|
|
183
191
|
// Claude hook settings merge / provisioning
|
|
184
192
|
// ---------------------------------------------------------------------------
|
|
185
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* Claude lifecycle events always registered for the conductor hook.
|
|
195
|
+
*
|
|
196
|
+
* BAPI-507 (N-1): `Stop` and `SubagentStop` are deliberately NOT registered.
|
|
197
|
+
* They are per-turn / per-subagent events (verified against the Claude Code
|
|
198
|
+
* lifecycle-hook contract), so registering them emitted a `run.stopped` on every
|
|
199
|
+
* turn and folded the ticket to `ready_for_review` while it was still being
|
|
200
|
+
* implemented. `SessionEnd` is the one true session-terminal event and is the
|
|
201
|
+
* only session-end signal registered here.
|
|
202
|
+
*/
|
|
186
203
|
export const CONDUCTOR_HOOK_LIFECYCLE_EVENTS = [
|
|
187
204
|
"SessionStart",
|
|
188
|
-
"
|
|
189
|
-
"SubagentStop",
|
|
205
|
+
"SessionEnd",
|
|
190
206
|
"Notification",
|
|
191
207
|
];
|
|
192
208
|
/** Broad matcher used for `PreToolUse` when no existing matcher is present. */
|
|
@@ -254,6 +254,34 @@ function uvDescriptor() {
|
|
|
254
254
|
return commandDescriptor("uv", "uv", UV_INSTALL_HINTS);
|
|
255
255
|
}
|
|
256
256
|
// ---------------------------------------------------------------------------
|
|
257
|
+
// review-tickets git prerequisite (BAPI-474). review-tickets has its own
|
|
258
|
+
// terminal-launcher-only live preflight (runReviewTicketsPreflight in
|
|
259
|
+
// review-tickets.ts) — separate from getPreflightPrereqDescriptors above,
|
|
260
|
+
// which is start-tickets-specific. Since the parent-fetch-once base pin now
|
|
261
|
+
// requires git for review-tickets too (unless --no-refresh-base is passed),
|
|
262
|
+
// this doctor-only descriptor gives it its own distinctly-labeled,
|
|
263
|
+
// read-only-diagnosable entry rather than relying on the generic start-tickets
|
|
264
|
+
// "git" descriptor, so a user running review-tickets alone can self-diagnose.
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
const REVIEW_TICKETS_GIT_INSTALL_HINTS = {
|
|
267
|
+
darwin: `${GIT_INSTALL_HINTS.darwin} (or rerun review-tickets with --no-refresh-base to skip the fetch)`,
|
|
268
|
+
linux: `${GIT_INSTALL_HINTS.linux} (or rerun review-tickets with --no-refresh-base to skip the fetch)`,
|
|
269
|
+
win32: `${GIT_INSTALL_HINTS.win32} (or rerun review-tickets with --no-refresh-base to skip the fetch)`,
|
|
270
|
+
};
|
|
271
|
+
function reviewTicketsGitDescriptor() {
|
|
272
|
+
return {
|
|
273
|
+
id: "review-tickets-git",
|
|
274
|
+
label: "git (required by review-tickets base-branch fetch)",
|
|
275
|
+
installHint: REVIEW_TICKETS_GIT_INSTALL_HINTS,
|
|
276
|
+
probe: async (deps) => {
|
|
277
|
+
const found = await isCommandOnPath(deps, "git");
|
|
278
|
+
return found
|
|
279
|
+
? { found: true, detail: "found on PATH" }
|
|
280
|
+
: { found: false, detail: "review-tickets' parent-fetch-once base pin needs git unless --no-refresh-base is passed" };
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
257
285
|
// regression-check (BAPI-460) doctor-only tool descriptors. These probe the
|
|
258
286
|
// deterministic tooling regression-check relies on (ast-grep, ripgrep,
|
|
259
287
|
// lizard). They are doctor-diagnosable but NOT preflight blockers: a missing
|
|
@@ -270,10 +298,11 @@ const LIZARD_INSTALL_HINTS = {
|
|
|
270
298
|
linux: "uv tool install lizard",
|
|
271
299
|
win32: "uv tool install lizard",
|
|
272
300
|
};
|
|
301
|
+
const RIPGREP_SHELL_FUNCTION_CAVEAT = "a shell function/alias will not satisfy this check — ripgrep must be installed as a real PATH binary.";
|
|
273
302
|
const RIPGREP_INSTALL_HINTS = {
|
|
274
|
-
darwin:
|
|
275
|
-
linux:
|
|
276
|
-
win32:
|
|
303
|
+
darwin: `brew install ripgrep (${RIPGREP_SHELL_FUNCTION_CAVEAT})`,
|
|
304
|
+
linux: `Install ripgrep with your distro package manager, e.g. apt install ripgrep (${RIPGREP_SHELL_FUNCTION_CAVEAT})`,
|
|
305
|
+
win32: `winget install BurntSushi.ripgrep.MSVC (${RIPGREP_SHELL_FUNCTION_CAVEAT})`,
|
|
277
306
|
};
|
|
278
307
|
/** ast-grep ships two equivalent binary names (`ast-grep` and `sg`); either satisfies the probe. */
|
|
279
308
|
function astGrepDescriptor() {
|
|
@@ -427,6 +456,7 @@ export function getDoctorOnlyPrereqDescriptors(_platform, _env, agent) {
|
|
|
427
456
|
astGrepDescriptor(),
|
|
428
457
|
lizardDescriptor(),
|
|
429
458
|
ripgrepDescriptor(),
|
|
459
|
+
reviewTicketsGitDescriptor(),
|
|
430
460
|
];
|
|
431
461
|
}
|
|
432
462
|
/**
|