@bridge_gpt/mcp-server 0.2.21 → 0.2.23

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.
@@ -71,6 +71,7 @@ import { WORKTRUNK_BINARY_OVERRIDE_ENV, WINDOWS_TERMINAL_COMMAND, WINDOWS_POWERS
71
71
  import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, resolveModelAlias, isValidModelAlias, isModelTier, } from "./agent-registry.js";
72
72
  import { createStartTicketsConductorContext, provisionConductorHooksForRows, emitStartTicketsRunStarted, injectConductorEnvIntoShellCommand, buildSupervisorTabCommand, isSupervisorLaunchEnabled, supervisorSpawnKey, } from "./start-tickets-conductor.js";
73
73
  import { transitionEpicDispatch, resolveConductorBridgeApiAccess, } from "./conductor/bridge-api-client.js";
74
+ import { PR_BASE_BRANCH_ENV_VAR, buildPrBaseContractLaunchInstruction, } from "./pr-base-contract.js";
74
75
  // Re-export the shared prereq surface (constants, platform helpers, command
75
76
  // probes) so existing import sites that read them from "./start-tickets.js"
76
77
  // keep working unchanged.
@@ -101,6 +102,8 @@ export function getStartTicketsUsage() {
101
102
  "",
102
103
  "Flags:",
103
104
  " --agent claude|cursor-agent Agent command to launch in each worktree (default: claude)",
105
+ " --workflow implement|review-and-implement Slash command each spawned worktree runs (default: implement). review-and-implement runs /review-ticket then, after a per-ticket halt gate, /implement-ticket in the same session; --auto applies to the selected workflow.",
106
+ " --rounds 1|2 Review round count forwarded to the review phase; review-only, valid only with --workflow review-and-implement",
104
107
  " --terminal terminal|iterm Override the macOS terminal app (default: auto-detect via $TERM_PROGRAM); honored on macOS only",
105
108
  " --dry-run Print intended actions; creates no worktrees and opens no tabs, but DOES resolve model routing read-only (may compute+cache a ticket's difficulty) to preview the --model each tab would use",
106
109
  " --branch KEY=BRANCH Use BRANCH instead of feature/KEY for that ticket (repeatable)",
@@ -151,6 +154,8 @@ export function parseStartTicketsArgs(argv) {
151
154
  let agentName = DEFAULT_AGENT_NAME;
152
155
  let baseBranch = "main";
153
156
  let conductorEnabled = false;
157
+ let workflow = "implement";
158
+ let reviewRoundsRaw;
154
159
  const branchEntries = [];
155
160
  const keys = [];
156
161
  for (let i = 0; i < argv.length; i++) {
@@ -183,6 +188,49 @@ export function parseStartTicketsArgs(argv) {
183
188
  agentName = value;
184
189
  continue;
185
190
  }
191
+ if (arg === "--workflow" || arg.startsWith("--workflow=")) {
192
+ let value;
193
+ if (arg.startsWith("--workflow=")) {
194
+ value = arg.slice("--workflow=".length);
195
+ }
196
+ else {
197
+ value = takeValue();
198
+ if (value === undefined) {
199
+ return {
200
+ status: "error",
201
+ message: "--workflow requires a value (allowed values: implement, review-and-implement).",
202
+ };
203
+ }
204
+ }
205
+ if (value !== "implement" && value !== "review-and-implement") {
206
+ return {
207
+ status: "error",
208
+ message: `Invalid --workflow value: '${value}' (allowed values: implement, review-and-implement).`,
209
+ };
210
+ }
211
+ workflow = value;
212
+ continue;
213
+ }
214
+ if (arg === "--rounds" || arg.startsWith("--rounds=")) {
215
+ let value;
216
+ if (arg.startsWith("--rounds=")) {
217
+ value = arg.slice("--rounds=".length);
218
+ }
219
+ else {
220
+ value = takeValue();
221
+ if (value === undefined) {
222
+ return { status: "error", message: "--rounds requires a value (allowed values: 1, 2)." };
223
+ }
224
+ }
225
+ if (value !== "1" && value !== "2") {
226
+ return {
227
+ status: "error",
228
+ message: `Invalid --rounds value: '${value}' (allowed values: 1, 2).`,
229
+ };
230
+ }
231
+ reviewRoundsRaw = value;
232
+ continue;
233
+ }
186
234
  if (arg === "--terminal" || arg.startsWith("--terminal=")) {
187
235
  let value;
188
236
  if (arg.startsWith("--terminal=")) {
@@ -337,29 +385,38 @@ export function parseStartTicketsArgs(argv) {
337
385
  }
338
386
  branchOverrides[overrideKey] = branchName;
339
387
  }
388
+ // --- rounds validation: review-only, checked after the full argument scan
389
+ // so flag order (e.g. --rounds before --workflow) never affects validation.
390
+ let reviewRounds;
391
+ if (reviewRoundsRaw !== undefined) {
392
+ if (workflow !== "review-and-implement") {
393
+ return {
394
+ status: "error",
395
+ message: "--rounds is only valid with --workflow review-and-implement.",
396
+ };
397
+ }
398
+ reviewRounds = reviewRoundsRaw === "1" ? 1 : 2;
399
+ }
340
400
  return {
341
401
  status: "ok",
342
- options: { keys, terminal, dryRun, autoApprove, refreshMain, maxParallel, branchOverrides, agentName, baseBranch, conductorEnabled },
402
+ options: {
403
+ keys,
404
+ terminal,
405
+ dryRun,
406
+ autoApprove,
407
+ refreshMain,
408
+ maxParallel,
409
+ branchOverrides,
410
+ agentName,
411
+ baseBranch,
412
+ conductorEnabled,
413
+ workflow,
414
+ reviewRounds,
415
+ },
343
416
  };
344
417
  }
345
- /** Returns an error string for an unsafe branch name, or null when valid. */
346
- export function validateBranchName(branch) {
347
- if (branch.trim().length === 0)
348
- return "branch name must not be empty.";
349
- if (branch.length > 255)
350
- return "branch name must be 255 characters or fewer.";
351
- if (branch.startsWith("-"))
352
- return "branch name must not start with '-'.";
353
- // Reject ASCII control characters (0x00-0x1F and 0x7F) without embedding
354
- // raw control bytes in source.
355
- for (let i = 0; i < branch.length; i++) {
356
- const code = branch.charCodeAt(i);
357
- if (code <= 0x1f || code === 0x7f) {
358
- return "branch name must not contain control characters.";
359
- }
360
- }
361
- return null;
362
- }
418
+ // `validateBranchName` moved to `base-ref.ts` (BAPI-586); imported + re-exported
419
+ // above. See the import block near the worktree-core re-exports.
363
420
  /**
364
421
  * Determine which macOS terminal to drive. An explicit choice wins; otherwise
365
422
  * auto-detect iTerm from `$TERM_PROGRAM` (case-insensitive), defaulting to
@@ -403,19 +460,27 @@ export function getDefaultSpawnTerminalTabForPlatform(platform) {
403
460
  export function resolveStartTicketsPlatformConfig(deps, agent, autoApprove = false, conductorEnabled = false, repoName = null,
404
461
  // BAPI-494: a conductor remediation re-dispatch. Appends the full-suite finalize
405
462
  // instruction to the resume-mode worker's prompt.
406
- resumeMode = false) {
463
+ resumeMode = false, workflow = "implement", reviewRounds,
464
+ // BAPI-586: the effective run base branch (incl. epic.base_branch override).
465
+ // Injected as BAPI_BASE_BRANCH for conductor workers so their PR targets it,
466
+ // and threaded (BAPI-593) into the spawned workflow command.
467
+ baseBranch) {
407
468
  if (!isSupportedStartTicketsPlatform(deps.platform)) {
408
469
  return { ok: false, error: unsupportedPlatformMessage(deps.platform) };
409
470
  }
410
471
  const platform = deps.platform;
472
+ // Only conductor dispatch uses the PR-base contract; interactive dispatch omits
473
+ // the BAPI_BASE_BRANCH assignment so its worker launch is unchanged.
474
+ const prBaseBranch = conductorEnabled ? baseBranch : null;
411
475
  return {
412
476
  ok: true,
413
477
  config: {
414
478
  platform,
415
479
  worktrunkBinary: resolveWorktrunkBinary(platform, deps.env),
416
480
  // Inject the resolved repo identity so the spawned worktree session never
417
- // falls back to the basename-derived repo name (the 403 root cause).
418
- buildAgentShellCommand: (key, worktreePath, modelAlias) => prependRepoNameEnvAssignment(buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove, modelAlias, conductorEnabled, resumeMode), repoName, platform),
481
+ // falls back to the basename-derived repo name (the 403 root cause), and
482
+ // (BAPI-586) the run base so the conductor worker opens its PR against it.
483
+ buildAgentShellCommand: (key, worktreePath, modelAlias) => prependBaseBranchEnvAssignment(prependRepoNameEnvAssignment(buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch), repoName, platform), prBaseBranch, platform),
419
484
  spawnTerminalTab: deps.spawnTerminalTab,
420
485
  },
421
486
  };
@@ -445,6 +510,24 @@ export function prependRepoNameEnvAssignment(command, repoName, platform = "darw
445
510
  }
446
511
  return `export BAPI_REPO_NAME='${shSquoteInner(repoName)}' && ${command}`;
447
512
  }
513
+ /**
514
+ * BAPI-586: prepend a `BAPI_BASE_BRANCH` environment assignment to a spawned
515
+ * conductor worker's shell command so the worker can open its PR against the run
516
+ * base via `gh pr create --base "$BAPI_BASE_BRANCH"` (paired with the PR-base
517
+ * launch instruction), instead of inferring the repo default branch. Platform
518
+ * correct — `$env:VAR = '…'; …` on PowerShell, `export VAR='…' && …` on POSIX —
519
+ * with the value quoted by the same escaper used for the rest of the command.
520
+ * Fail-open: a null/empty `baseBranch` returns the command unchanged (interactive
521
+ * dispatch does not use the PR-base contract).
522
+ */
523
+ export function prependBaseBranchEnvAssignment(command, baseBranch, platform = "darwin") {
524
+ if (!baseBranch)
525
+ return command;
526
+ if (platform === "win32") {
527
+ return `$env:${PR_BASE_BRANCH_ENV_VAR} = ${powershellSquote(baseBranch)}; ${command}`;
528
+ }
529
+ return `export ${PR_BASE_BRANCH_ENV_VAR}='${shSquoteInner(baseBranch)}' && ${command}`;
530
+ }
448
531
  /**
449
532
  * Escape a string for inclusion inside a single-quoted shell context: each
450
533
  * embedded single-quote becomes the sequence `'\''`. Returns only the inner
@@ -725,42 +808,10 @@ export async function refreshBaseBranch(deps, options) {
725
808
  export async function refreshMainBranch(deps, options) {
726
809
  return refreshBaseBranch(deps, { refreshMain: options.refreshMain, baseBranch: "main" });
727
810
  }
728
- /**
729
- * Non-mutating counterpart to {@link refreshBaseBranch}: fetches
730
- * `origin/<baseBranch>` and resolves the fetched tip to an immutable commit
731
- * SHA, WITHOUT ever fast-forwarding or force-moving any local branch ref (no
732
- * `git merge --ff-only`, no `git branch --force`). Used by review-grounding
733
- * (BAPI-474), which must read a pinned base tree without touching the user's
734
- * working tree, index, stash, or local branch refs.
735
- *
736
- * Validates `baseBranch` with {@link validateBranchName} first so an
737
- * injection-shaped ref name is rejected before any git invocation. Every git
738
- * call uses an argv array (never a shell string), consistent with
739
- * {@link refreshBaseBranch}.
740
- */
741
- export async function fetchAndResolveBaseSha(deps, baseBranch) {
742
- const validationError = validateBranchName(baseBranch);
743
- if (validationError) {
744
- return { ok: false, error: `Invalid base branch '${baseBranch}': ${validationError}` };
745
- }
746
- const fetch = await deps.runCommand("git", ["fetch", "origin", baseBranch], {
747
- cwd: deps.cwd,
748
- });
749
- if (!commandSucceeded(fetch)) {
750
- return {
751
- ok: false,
752
- error: `git fetch origin ${baseBranch} failed. Check your network and 'git remote get-url origin', or pass --no-refresh-base to skip.`,
753
- };
754
- }
755
- const resolve = await deps.runCommand("git", ["rev-parse", "--verify", `origin/${baseBranch}^{commit}`], { cwd: deps.cwd });
756
- if (!commandSucceeded(resolve)) {
757
- return {
758
- ok: false,
759
- error: `Failed to resolve origin/${baseBranch} to a commit SHA after fetch (git rev-parse --verify failed).`,
760
- };
761
- }
762
- return { ok: true, base_sha: resolve.stdout.trim() };
763
- }
811
+ // `fetchAndResolveBaseSha` + `FetchAndResolveBaseShaResult` moved to
812
+ // `base-ref.ts` (BAPI-586) and re-exported above. The shared helper now also
813
+ // serializes the fetch per repository via an async mutex so concurrent executor
814
+ // fresh jobs against one clone cannot collide on git lock files.
764
815
  // ---------------------------------------------------------------------------
765
816
  // Concurrency + worktree creation
766
817
  // ---------------------------------------------------------------------------
@@ -798,6 +849,13 @@ export async function runWithConcurrency(items, limit, worker) {
798
849
  // same function references.
799
850
  import { resolveBranchForTicket, branchExists, buildWtSwitchArgs, pathApiForPlatform, extractWorktreePath, isExistingBranchSafeToReuse, createWorktreeForTicket, } from "./worktree-core.js";
800
851
  export { resolveBranchForTicket, branchExists, buildWtSwitchArgs, pathApiForPlatform, extractWorktreePath, isExistingBranchSafeToReuse, createWorktreeForTicket, };
852
+ // BAPI-586: base-ref resolution now lives in the focused `base-ref.ts` module so
853
+ // the executor can depend on it without pulling in this large CLI module. We
854
+ // import the VALUES here (so internal uses keep working) and re-export the same
855
+ // references so existing importers of `./start-tickets.js` (index.ts,
856
+ // review-tickets.ts) and the pinned start-tickets tests are unaffected.
857
+ import { validateBranchName, fetchAndResolveBaseSha } from "./base-ref.js";
858
+ export { validateBranchName, fetchAndResolveBaseSha };
801
859
  /**
802
860
  * Create / switch worktrees for every ticket, throttled to `maxParallel`, using
803
861
  * the resolved Worktrunk binary. Returns one row per ticket in original key
@@ -811,7 +869,17 @@ export { resolveBranchForTicket, branchExists, buildWtSwitchArgs, pathApiForPlat
811
869
  * historical branch-name behavior unchanged.
812
870
  */
813
871
  export async function createWorktrees(deps, options, worktrunkBinary, baseStartPoint = options.baseBranch) {
814
- return runWithConcurrency(options.keys, options.maxParallel, (key) => createWorktreeForTicket(deps, key, options.branchOverrides, worktrunkBinary, baseStartPoint, options.guardStaleWorktree === true));
872
+ // BAPI-586: for guard-enabled Conductor fresh dispatch the caller has already
873
+ // resolved `baseStartPoint` to an immutable base SHA (`nonMutatingBase`). Align
874
+ // any safe pre-existing branch exactly to that SHA and verify the resulting
875
+ // head against it, so a dependent ticket starts at fresh `origin/<base>` rather
876
+ // than at a stale ancestor or a sibling seed. Interactive dispatch (a moving
877
+ // branch name, guard off) keeps the historical reuse-as-is behavior.
878
+ const exactBase = options.guardStaleWorktree === true && options.nonMutatingBase === true;
879
+ const behavior = exactBase
880
+ ? { alignExistingBranchTo: baseStartPoint, verifyHeadMatches: baseStartPoint }
881
+ : {};
882
+ return runWithConcurrency(options.keys, options.maxParallel, (key) => createWorktreeForTicket(deps, key, options.branchOverrides, worktrunkBinary, baseStartPoint, options.guardStaleWorktree === true, behavior));
815
883
  }
816
884
  /**
817
885
  * Resume-mode worktree resolution (BAPI-441). Instead of creating worktrees,
@@ -931,10 +999,26 @@ export function buildAgentPrompt(key, opts = {}) {
931
999
  // `modelAlias` is accepted for signature consistency only — the model is
932
1000
  // injected as a `--model` flag (see buildAgentInvocationArgv), never embedded
933
1001
  // in the prompt text.
934
- const command = `/implement-ticket ${key}${opts.autoApprove ? " --auto" : ""}`;
1002
+ const workflow = opts.workflow ?? "implement";
1003
+ const head = workflow === "review-and-implement" ? "/review-and-implement" : "/implement-ticket";
1004
+ let command = `${head} ${key}${opts.autoApprove ? " --auto" : ""}`;
1005
+ // Review-only arguments (`--rounds`, non-default `--base-branch`) must never
1006
+ // leak into the legacy `/implement-ticket` prompt.
1007
+ if (workflow === "review-and-implement") {
1008
+ if (opts.reviewRounds !== undefined) {
1009
+ command += ` --rounds=${opts.reviewRounds}`;
1010
+ }
1011
+ if (opts.baseBranch !== undefined && opts.baseBranch !== "main") {
1012
+ command += ` --base-branch='${shSquoteInner(opts.baseBranch)}'`;
1013
+ }
1014
+ }
935
1015
  const parts = [command];
936
- if (opts.conductorEnabled)
1016
+ if (opts.conductorEnabled) {
937
1017
  parts.push(buildConductorMessageRelayLaunchInstruction());
1018
+ // BAPI-586: conductor implementation workers must open their PR against the
1019
+ // run base (injected as BAPI_BASE_BRANCH), never the repo default branch.
1020
+ parts.push(buildPrBaseContractLaunchInstruction());
1021
+ }
938
1022
  if (opts.resumeMode)
939
1023
  parts.push(buildResumeModeRemediationFinalizeInstruction());
940
1024
  return parts.join(" ");
@@ -976,13 +1060,13 @@ export function buildAgentInvocation(agent, prompt, quote, modelAlias) {
976
1060
  }
977
1061
  }
978
1062
  /** POSIX agent shell command: `cd '<path>' && <agent> [--model '<alias>'] '<prompt>'`. */
979
- export function buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false) {
980
- const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode }), (p) => `'${shSquoteInner(p)}'`, modelAlias);
1063
+ export function buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch) {
1064
+ const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch }), (p) => `'${shSquoteInner(p)}'`, modelAlias);
981
1065
  return `cd '${shSquoteInner(worktreePath)}' && ${invocation}`;
982
1066
  }
983
1067
  /** PowerShell agent shell command: `Set-Location -LiteralPath '<path>'; <agent> [--model '<alias>'] '<prompt>'`. */
984
- export function buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false) {
985
- const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode }), powershellSquote, modelAlias);
1068
+ export function buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch) {
1069
+ const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch }), powershellSquote, modelAlias);
986
1070
  return `Set-Location -LiteralPath ${powershellSquote(worktreePath)}; ${invocation}`;
987
1071
  }
988
1072
  /**
@@ -993,10 +1077,10 @@ export function buildPowerShellAgentShellCommand(agent, key, worktreePath, autoA
993
1077
  * injected as `--model` at the spawn boundary. `conductorEnabled` appends the
994
1078
  * BAPI-397 message-relay instruction to the prompt (opt-in via `--conductor`).
995
1079
  */
996
- export function buildAgentShellCommand(agent, key, worktreePath, platform = "darwin", autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false) {
1080
+ export function buildAgentShellCommand(agent, key, worktreePath, platform = "darwin", autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch) {
997
1081
  if (platform === "win32")
998
- return buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove, modelAlias, conductorEnabled, resumeMode);
999
- return buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove, modelAlias, conductorEnabled, resumeMode);
1082
+ return buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch);
1083
+ return buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch);
1000
1084
  }
1001
1085
  /**
1002
1086
  * Build the shell command run inside a spawned tab/session for an ARBITRARY
@@ -1518,14 +1602,16 @@ export function buildDryRunResults(keys, overrides) {
1518
1602
  * PowerShell on Windows, `wt` + POSIX on macOS/Linux, and a non-throwing `wt` +
1519
1603
  * POSIX fallback for unsupported platforms.
1520
1604
  */
1521
- export function getDryRunPlatformDetails(agent, platform = process.platform, env = process.env, autoApprove = false, conductorEnabled = false, repoName = null) {
1605
+ export function getDryRunPlatformDetails(agent, platform = process.platform, env = process.env, autoApprove = false, conductorEnabled = false, repoName = null, workflow = "implement", reviewRounds, baseBranch) {
1522
1606
  return {
1523
1607
  worktrunkBinary: resolveWorktrunkBinary(platform, env),
1524
1608
  // The builder accepts an optional resolved modelAlias; the dry-run caller
1525
1609
  // now passes the previewed tier's alias so `--model` shows in the preview.
1526
1610
  // The resolved repo name (when known) is injected as a BAPI_REPO_NAME prefix
1527
- // so the dry-run preview matches the real spawn command exactly.
1528
- buildAgentShellCommand: (key, worktreePath, modelAlias) => prependRepoNameEnvAssignment(buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove, modelAlias, conductorEnabled), repoName, platform),
1611
+ // so the dry-run preview matches the real spawn command exactly. Reuses the
1612
+ // same buildAgentShellCommand/buildAgentPrompt path as a real spawn dry-run
1613
+ // is never special-cased — so the previewed workflow prompt is exact.
1614
+ buildAgentShellCommand: (key, worktreePath, modelAlias) => prependRepoNameEnvAssignment(buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove, modelAlias, conductorEnabled, false, workflow, reviewRounds, baseBranch), repoName, platform),
1529
1615
  };
1530
1616
  }
1531
1617
  /**
@@ -1563,8 +1649,8 @@ export function buildDryRunMcpProvisioningLines(worktreePath, platform = process
1563
1649
  * the secret-free MCP provisioning preview. Pure platform formatting only — no
1564
1650
  * preflight, no routing failures.
1565
1651
  */
1566
- export function buildDryRunDetailLines(agent, key, branch, platform = process.platform, env = process.env, baseBranch = "main", autoApprove = false, modelAlias = null, conductorEnabled = false, repoName = null, mcpServerInvocation) {
1567
- const { worktrunkBinary, buildAgentShellCommand: build } = getDryRunPlatformDetails(agent, platform, env, autoApprove, conductorEnabled, repoName);
1652
+ export function buildDryRunDetailLines(agent, key, branch, platform = process.platform, env = process.env, baseBranch = "main", autoApprove = false, modelAlias = null, conductorEnabled = false, repoName = null, mcpServerInvocation, workflow = "implement", reviewRounds) {
1653
+ const { worktrunkBinary, buildAgentShellCommand: build } = getDryRunPlatformDetails(agent, platform, env, autoApprove, conductorEnabled, repoName, workflow, reviewRounds, baseBranch);
1568
1654
  const wtArgs = buildWtSwitchArgs(branch, false, baseBranch);
1569
1655
  const agentInvocation = build(key, "<worktree-path>", modelAlias);
1570
1656
  return [
@@ -2504,7 +2590,10 @@ export async function orchestrateStartTickets(deps, options, overrides = {}) {
2504
2590
  }
2505
2591
  const platformConfig = resolveStartTicketsPlatformConfig(deps, agent, options.autoApprove, options.conductorEnabled ?? false, resolvedRepoName,
2506
2592
  // BAPI-494: resume-mode dispatches get the full-suite remediation finalize prompt.
2507
- options.resumeMode ?? false);
2593
+ options.resumeMode ?? false, options.workflow, options.reviewRounds,
2594
+ // BAPI-586: the effective run base (already carries any epic.base_branch
2595
+ // override applied above) so conductor workers get BAPI_BASE_BRANCH.
2596
+ options.baseBranch);
2508
2597
  if (!platformConfig.ok)
2509
2598
  return { ok: false, error: platformConfig.error };
2510
2599
  // BAPI-527: resolve the base start point new worktrees are cut from BEFORE any
@@ -2707,7 +2796,7 @@ export async function runStartTicketsCli(argv, overrides = {}) {
2707
2796
  const branch = resolveBranchForTicket(key, options.branchOverrides);
2708
2797
  const routedRow = routedByKey.get(key);
2709
2798
  const modelAlias = routedRow?.modelAlias ?? null;
2710
- for (const line of buildDryRunDetailLines(agent, key, branch, deps.platform, deps.env, options.baseBranch, options.autoApprove, modelAlias, options.conductorEnabled ?? false, dryRunRepoName, dryRunMcpInvocation)) {
2799
+ for (const line of buildDryRunDetailLines(agent, key, branch, deps.platform, deps.env, options.baseBranch, options.autoApprove, modelAlias, options.conductorEnabled ?? false, dryRunRepoName, dryRunMcpInvocation, options.workflow, options.reviewRounds)) {
2711
2800
  log(line);
2712
2801
  }
2713
2802
  log(`DRY-RUN: model routing: ${formatModelRoutingLine(routedRow ?? { key, branch, status: "dry-run" }, agent)}`);
@@ -1,2 +1,2 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.2.21";
2
+ export const VERSION = "0.2.23";
@@ -145,6 +145,44 @@ export async function isExistingBranchSafeToReuse(deps, branch, baseStartPoint)
145
145
  * job-type logic lives here — the caller decides the start point, the stale
146
146
  * guard, and the freshen behavior.
147
147
  */
148
+ /**
149
+ * Hard-reset the branch checked out in `worktreePath` onto `ref`. Returns null on
150
+ * success or a bounded, secret-free `create-failed` error string on failure.
151
+ * Shared by the recovery freshen path and the BAPI-586 fresh-dispatch alignment.
152
+ */
153
+ async function hardResetWorktree(deps, worktreePath, ref) {
154
+ const resetArgs = ["reset", "--hard", ref];
155
+ const reset = await deps.runCommand("git", resetArgs, { cwd: worktreePath });
156
+ if (!commandSucceeded(reset)) {
157
+ const reason = (reset.stderr || reset.stdout || "").trim();
158
+ return `git ${resetArgs.join(" ")} failed${reason ? `: ${reason}` : ""}`;
159
+ }
160
+ return null;
161
+ }
162
+ /**
163
+ * BAPI-586: resolve the worktree's `HEAD^{commit}` and the `expected` ref/commit
164
+ * and confirm they are equal. Returns null when they match, or a bounded,
165
+ * secret-free `create-failed` error string otherwise. Short-SHA fragments in the
166
+ * message are safe (they are public commit identifiers, not secrets).
167
+ */
168
+ async function verifyWorktreeHead(deps, worktreePath, expected) {
169
+ const headRes = await deps.runCommand("git", ["rev-parse", "--verify", "HEAD^{commit}"], { cwd: worktreePath });
170
+ if (!commandSucceeded(headRes)) {
171
+ return "failed to resolve worktree HEAD after creation (git rev-parse HEAD^{commit} failed).";
172
+ }
173
+ const expectedRes = await deps.runCommand("git", ["rev-parse", "--verify", `${expected}^{commit}`], { cwd: worktreePath });
174
+ if (!commandSucceeded(expectedRes)) {
175
+ return "failed to resolve the expected base commit after creation (git rev-parse --verify failed).";
176
+ }
177
+ const head = headRes.stdout.trim();
178
+ const want = expectedRes.stdout.trim();
179
+ if (head !== want) {
180
+ return (`worktree head ${head.slice(0, 12)} does not match the pinned base ${want.slice(0, 12)}; ` +
181
+ `Worktrunk seeded the worktree from an unexpected start point. Refusing to hand a mis-seeded ` +
182
+ `worktree to a worker.`);
183
+ }
184
+ return null;
185
+ }
148
186
  export async function createWorktreeForTicket(deps, key, branchOverrides, worktrunkBinary, baseStartPoint = "main", guardStaleWorktree = false, behavior = {}) {
149
187
  const branch = resolveBranchForTicket(key, branchOverrides);
150
188
  try {
@@ -181,16 +219,30 @@ export async function createWorktreeForTicket(deps, key, branchOverrides, worktr
181
219
  // `origin/<branch>`), so this only applies when the local branch pre-existed.
182
220
  // Run inside the switched worktree (the branch is checked out there).
183
221
  if (exists && behavior.freshenFromOrigin) {
184
- const resetArgs = ["reset", "--hard", behavior.freshenFromOrigin];
185
- const reset = await deps.runCommand("git", resetArgs, { cwd: worktreePath });
186
- if (!commandSucceeded(reset)) {
187
- const reason = (reset.stderr || reset.stdout || "").trim();
188
- return {
189
- key,
190
- branch,
191
- status: "create-failed",
192
- error: `git ${resetArgs.join(" ")} failed${reason ? `: ${reason}` : ""}`,
193
- };
222
+ const resetError = await hardResetWorktree(deps, worktreePath, behavior.freshenFromOrigin);
223
+ if (resetError) {
224
+ return { key, branch, status: "create-failed", error: resetError };
225
+ }
226
+ }
227
+ // BAPI-586 (fresh dispatch): a guard-approved pre-existing branch is an
228
+ // ancestor of base but may sit at an OLDER tip. Align it EXACTLY to the
229
+ // pinned base SHA so a fresh implementation starts at base, not at a stale
230
+ // ancestor. The stale-branch guard above already ran (and refused unsafe
231
+ // branches) before this destructive reset. Absent branches were created
232
+ // straight from `baseStartPoint`, so no alignment is needed there.
233
+ if (exists && behavior.alignExistingBranchTo) {
234
+ const resetError = await hardResetWorktree(deps, worktreePath, behavior.alignExistingBranchTo);
235
+ if (resetError) {
236
+ return { key, branch, status: "create-failed", error: resetError };
237
+ }
238
+ }
239
+ // BAPI-586 (fresh dispatch): verify the worktree actually starts at the
240
+ // pinned base. If Worktrunk seeded from an unexpected sibling despite
241
+ // `-b <sha>`, fail CLOSED before the worktree is handed to a worker.
242
+ if (behavior.verifyHeadMatches) {
243
+ const verifyError = await verifyWorktreeHead(deps, worktreePath, behavior.verifyHeadMatches);
244
+ if (verifyError) {
245
+ return { key, branch, status: "create-failed", error: verifyError };
194
246
  }
195
247
  }
196
248
  return { key, branch, status: "created", path: worktreePath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge_gpt/mcp-server",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "Bridge API MCP server — exposes Jira endpoints as MCP tools for Claude Code agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,8 +27,8 @@
27
27
  "check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
28
28
  "postbuild": "node scripts/prepend-shebang.cjs",
29
29
  "start": "node build/index.js",
30
- "test": "node --test --test-force-exit build/pipeline-utils.test.js build/backend-warnings.test.js build/update-check.test.js build/cli-upgrade.test.js build/decision-page-schema.test.js build/decision-page-template.test.js build/bundle-pipelines.test.js build/instructions-contract.test.js build/pipeline-orchestrator-persistence.test.js build/pipeline-orchestrator-execution.test.js build/pipeline-orchestrator-integration.test.js build/index-static.test.js build/config-fields.static.test.js build/index-resolvers.test.js build/index-project-root.test.js build/index-pipelines.test.js build/index.test.js build/bridge-config.test.js build/credential-store.test.js build/agent-config-credential-migration.test.js build/mcp-invoke.test.js build/mcp-provisioning.test.js build/mcp-server-invocation.test.js build/third-party-mcp-targets.test.js build/git-ignore-utils.test.js build/credential-materialization.test.js build/mcp-registration-doctor.test.js build/secret-safety.test.js build/start-tickets.test.js build/review-tickets.test.js build/start-tickets-base-branch.test.js build/agent-registry.test.js build/agent-registry.model-routing.test.js build/start-tickets.shell-model-routing.test.js build/start-tickets.bridge-api-model-routing.test.js build/start-tickets.tier-fetch-model-routing.test.js build/start-tickets.resolve-model-routing.test.js build/start-tickets.orchestrate-model-routing.test.js build/start-tickets.routing-diagnostics.test.js build/start-tickets-repo.test.js build/start-tickets-credential-invariants.static.test.js build/credentials-cli.test.js build/start-tickets-prereqs.test.js build/regression-check.test.js build/doctor.test.js build/install-doctor.test.js build/install-bridge.test.js build/init.test.js build/resolveUploadAttachment.test.js build/package-static.test.js build/chain-utils.test.js build/chain-orchestrator.test.js build/scheduler-backends/types.test.js build/scheduler-backends/escaping.test.js build/scheduler-backends/launchd.test.js build/scheduler-backends/task-scheduler.test.js build/scheduler-backends/systemd-user.test.js build/scheduler-backends/at-fallback.test.js build/scheduler-backends/index.test.js build/command-catalog.test.js build/scheduled-prompt.test.js build/agent-launchers/claude.test.js build/agent-launchers/cursor.test.js build/agent-launchers/index.test.js build/schedule-store.test.js build/schedule-run.test.js build/agent-capabilities/cli.test.js build/agent-capabilities/runner.test.js build/agent-capabilities/probes.test.js build/agent-capabilities/probe-context.test.js build/agent-capabilities/reporter.test.js build/conductor/taxonomy-and-errors.test.js build/conductor/github-mergeability.test.js build/conductor/merge-conflict-routing.test.js build/conductor/redaction-normalization.test.js build/conductor/claude-hook.test.js build/conductor/git-ci-types.test.js build/conductor/done-gate.test.js build/conductor/git-ci-taxonomy-payload.test.js build/conductor/bridge-api-client.test.js build/conductor/plan.test.js build/conductor/producer-ledger.test.js build/conductor/spec-review-producer.test.js build/conductor/git-producer.test.js build/conductor/git-hooks.test.js build/conductor/store-migration.test.js build/conductor/pr-discovery.test.js build/conductor/pr-ci-producer.test.js build/conductor/pr-review-producer.test.js build/conductor/doctor.test.js build/conductor/deny-enforcement-preflight.test.js build/conductor/errors.test.js build/conductor/store.test.js build/conductor/index-poll-ci-producer.test.js build/start-tickets-conductor.test.js build/start-tickets-conductor.spawn.test.js build/start-tickets-conductor-node-propagation.test.js build/start-tickets.non-mutating-base.test.js build/start-tickets-live-source-guard.test.js build/conductor/worker-ledger-cli.test.js build/conductor/supervisor-config.test.js build/conductor/supervisor-ledger.test.js build/conductor/supervisor-state-reducer.test.js build/conductor/supervisor-housekeeping-projection.test.js build/conductor/supervisor-escalation.test.js build/conductor/supervisor-retired-judgment.static.test.js build/conductor/supervisor-runtime.test.js build/conductor/supervisor-store-projection.test.js build/conductor/supervisor-cli.test.js build/conductor/supervisor-start-tickets.test.js build/conductor/supervisor-message-relay.test.js build/conductor/supervisor-state-message-events.test.js build/conductor/store-message-relay.test.js build/start-tickets-message-relay.test.js build/conductor/event-accessors.test.js build/conductor/merge-ledger.test.js build/conductor/deterministic-completion.static.test.js build/conductor/deterministic-completion.integration.test.js build/conductor/local-merge.test.js build/conductor/local-merge.static.test.js build/conductor/supervisor-merge.test.js build/conductor/bridge-api-merge-client.test.js build/conductor/bridge-api-epic-client.test.js build/conductor/supervisor-merge-runtime-state.test.js build/conductor/epic-state.test.js build/executor/claude-settings.test.js build/executor/cli.test.js build/executor/credentials.test.js build/executor/echo-acceptance.test.js build/executor/env.test.js build/executor/heartbeat.test.js build/executor/http-client.test.js build/executor/job-runner.test.js build/executor/job-runner.payload-timeout.test.js build/executor/worker-finalization.test.js build/executor/job-runner.static.test.js build/executor/prompt-spec.test.js build/executor/job-types.test.js build/executor/recovery-job.static.test.js build/executor/observation.test.js build/executor/permissions.test.js build/executor/preflight.test.js build/executor/process.test.js build/executor/results.test.js build/executor/runner.test.js build/executor/terminal-mutation.test.js build/executor/worker-command.test.js build/executor/worktree-core.test.js build/executor/worktree.test.js build/executor/job-errors.test.js build/executor/worktree-inspection.test.js build/executor/resume-pre-spawn.test.js build/executor/verdict-artifact.test.js build/executor/worker-log.test.js build/executor/job-log-registry.test.js build/executor/viewer-tabs.test.js build/executor/watch-cli.test.js build/executor/merge-job.test.js build/executor/job-runner.merge.test.js build/executor/worktree-gc.test.js build/executor/runner.job-behaviors.test.js build/conductor/cli-freeze.test.js build/conductor/epic-reconcile.test.js build/conductor/epic-runtime.test.js build/conductor/epic-tick-sequence.test.js build/conductor/epic-runtime-post-action.test.js build/conductor/file-scope-guard.test.js build/conductor/file-scope-guard.integration.test.js build/mcp-profile.test.js build/mcp-profile-registration.test.js build/tools-budget.test.js build/visual-diff-worker.test.js build/visual-diff.test.js build/estimate-epic.test.js build/integration/measure-tools.test.js build/sfcc/config.test.js build/sfcc/ocapi-shape.test.js build/sfcc/output.test.js build/sfcc/credentials.test.js build/sfcc/ocapi-write-faults.test.js build/sfcc/write-guard.test.js build/sfcc/write-grants.test.js build/sfcc/write-result.test.js build/sfcc/writes.test.js build/sfcc/writes-system-object-payloads.test.js build/sfcc/writes-payloads.test.js && node --experimental-test-module-mocks --test --test-force-exit build/index-heavy-read-truncation.test.js build/index-artifacts.test.js build/index.review-rounds.test.js build/index-brainstorm-filenames.test.js build/index-output-path.test.js build/index-generate-decision-page.test.js build/index-generate-decision-page.integration.test.js build/visual-diff.registration.test.js build/visual-diff.attachment-adapter.test.js build/attachment-download.test.js build/attachment-upload.test.js build/conductor/paths.test.js build/conductor/store-lifecycle.test.js build/conductor/store-queries.test.js build/conductor/tools.test.js build/conductor/cli.test.js build/conductor/security-regressions.test.js build/conductor/git-inspection.test.js build/conductor/tools-done-gate.test.js build/conductor/pr-ci-producer-emit-seam.test.js build/conductor/cli-git-hooks.test.js build/sfcc/client.test.js build/sfcc/tool-wrapper.test.js build/sfcc/setup-status.test.js build/sfcc/permissions.test.js build/sfcc/register.test.js build/sfcc/reads-system-object.test.js build/sfcc/reads-custom-object-def.test.js build/sfcc/reads-site-preference.test.js build/sfcc/writes-system-object.test.js build/sfcc/writes-custom-object-def.test.js build/sfcc/writes-site-preference.test.js",
31
- "test:integration": "node --test build/integration/refresh-main.integration.test.js build/integration/start-tickets.integration.test.js build/integration/doctor.integration.test.js build/integration/agent-capabilities.integration.test.js build/integration/conductor-producer.integration.test.js build/integration/conductor-message-relay.integration.test.js build/integration/executor-http-runner.integration.test.js build/integration/executor-job-behaviors.integration.test.js build/integration/executor-recovery-jobs.integration.test.js build/integration/executor-spec-review-prompt.integration.test.js build/integration/resume-pre-spawn.git.integration.test.js build/integration/worker-finalization-origin.integration.test.js build/integration/post-remediation-merge-ci-wait.integration.test.js build/integration/executor-merge-supervision.integration.test.js build/integration/attachment-binary-roundtrip.integration.test.js",
30
+ "test": "node --test --test-force-exit build/pipeline-utils.test.js build/backend-warnings.test.js build/update-check.test.js build/cli-upgrade.test.js build/decision-page-schema.test.js build/decision-page-template.test.js build/bundle-pipelines.test.js build/instructions-contract.test.js build/pipeline-orchestrator-persistence.test.js build/pipeline-orchestrator-execution.test.js build/pipeline-orchestrator-integration.test.js build/index-static.test.js build/config-fields.static.test.js build/execute-plan-instructions.static.test.js build/index-resolvers.test.js build/index-project-root.test.js build/index-pipelines.test.js build/index.test.js build/bridge-config.test.js build/credential-store.test.js build/agent-config-credential-migration.test.js build/mcp-invoke.test.js build/mcp-provisioning.test.js build/mcp-server-invocation.test.js build/third-party-mcp-targets.test.js build/git-ignore-utils.test.js build/credential-materialization.test.js build/mcp-registration-doctor.test.js build/secret-safety.test.js build/base-ref.test.js build/pr-base-contract.test.js build/start-tickets.test.js build/review-tickets.test.js build/start-tickets-base-branch.test.js build/agent-registry.test.js build/agent-registry.model-routing.test.js build/start-tickets.shell-model-routing.test.js build/start-tickets.bridge-api-model-routing.test.js build/start-tickets.tier-fetch-model-routing.test.js build/start-tickets.resolve-model-routing.test.js build/start-tickets.orchestrate-model-routing.test.js build/start-tickets.routing-diagnostics.test.js build/start-tickets-repo.test.js build/start-tickets-credential-invariants.static.test.js build/credentials-cli.test.js build/start-tickets-prereqs.test.js build/regression-check.test.js build/doctor.test.js build/install-doctor.test.js build/install-bridge.test.js build/install-bridge-invite.test.js build/install-bridge-prompt.test.js build/init.test.js build/resolveUploadAttachment.test.js build/package-static.test.js build/chain-utils.test.js build/chain-orchestrator.test.js build/scheduler-backends/types.test.js build/scheduler-backends/escaping.test.js build/scheduler-backends/launchd.test.js build/scheduler-backends/task-scheduler.test.js build/scheduler-backends/systemd-user.test.js build/scheduler-backends/at-fallback.test.js build/scheduler-backends/index.test.js build/command-catalog.test.js build/scheduled-prompt.test.js build/agent-launchers/claude.test.js build/agent-launchers/cursor.test.js build/agent-launchers/index.test.js build/schedule-store.test.js build/schedule-run.test.js build/setup-epic.test.js build/agent-capabilities/cli.test.js build/agent-capabilities/runner.test.js build/agent-capabilities/probes.test.js build/agent-capabilities/probe-context.test.js build/agent-capabilities/reporter.test.js build/conductor/taxonomy-and-errors.test.js build/conductor/github-mergeability.test.js build/conductor/merge-conflict-routing.test.js build/conductor/redaction-normalization.test.js build/conductor/claude-hook.test.js build/conductor/git-ci-types.test.js build/conductor/done-gate.test.js build/conductor/git-ci-taxonomy-payload.test.js build/conductor/bridge-api-client.test.js build/conductor/plan.test.js build/conductor/producer-ledger.test.js build/conductor/spec-review-producer.test.js build/conductor/git-producer.test.js build/conductor/git-hooks.test.js build/conductor/store-migration.test.js build/conductor/pr-discovery.test.js build/conductor/pr-ci-producer.test.js build/conductor/pr-review-producer.test.js build/conductor/doctor.test.js build/conductor/deny-enforcement-preflight.test.js build/conductor/errors.test.js build/conductor/store.test.js build/conductor/index-poll-ci-producer.test.js build/start-tickets-conductor.test.js build/start-tickets-conductor.spawn.test.js build/start-tickets-conductor-node-propagation.test.js build/start-tickets.non-mutating-base.test.js build/start-tickets-live-source-guard.test.js build/conductor/worker-ledger-cli.test.js build/conductor/supervisor-config.test.js build/conductor/supervisor-ledger.test.js build/conductor/supervisor-state-reducer.test.js build/conductor/supervisor-housekeeping-projection.test.js build/conductor/supervisor-escalation.test.js build/conductor/supervisor-retired-judgment.static.test.js build/conductor/supervisor-runtime.test.js build/conductor/supervisor-store-projection.test.js build/conductor/supervisor-cli.test.js build/conductor/supervisor-start-tickets.test.js build/conductor/supervisor-message-relay.test.js build/conductor/supervisor-state-message-events.test.js build/conductor/store-message-relay.test.js build/start-tickets-message-relay.test.js build/conductor/event-accessors.test.js build/conductor/merge-ledger.test.js build/conductor/deterministic-completion.static.test.js build/conductor/deterministic-completion.integration.test.js build/conductor/local-merge.test.js build/conductor/local-merge.static.test.js build/conductor/supervisor-merge.test.js build/conductor/bridge-api-merge-client.test.js build/conductor/bridge-api-epic-client.test.js build/conductor/supervisor-merge-runtime-state.test.js build/conductor/epic-state.test.js build/executor/claude-settings.test.js build/executor/cli.test.js build/executor/credentials.test.js build/executor/echo-acceptance.test.js build/executor/env.test.js build/executor/base-branch.test.js build/executor/heartbeat.test.js build/executor/http-client.test.js build/executor/job-runner.test.js build/executor/job-runner.payload-timeout.test.js build/executor/worker-finalization.test.js build/executor/job-runner.static.test.js build/executor/prompt-spec.test.js build/executor/job-types.test.js build/executor/recovery-job.static.test.js build/executor/observation.test.js build/executor/permissions.test.js build/executor/preflight.test.js build/executor/process.test.js build/executor/results.test.js build/executor/runner.test.js build/executor/terminal-mutation.test.js build/executor/worker-command.test.js build/executor/worktree-core.test.js build/executor/worktree.test.js build/executor/job-errors.test.js build/executor/worktree-inspection.test.js build/executor/resume-pre-spawn.test.js build/executor/verdict-artifact.test.js build/executor/worker-log.test.js build/executor/job-log-registry.test.js build/executor/viewer-tabs.test.js build/executor/watch-cli.test.js build/executor/merge-job.test.js build/executor/job-runner.merge.test.js build/executor/worktree-gc.test.js build/executor/runner.job-behaviors.test.js build/conductor/cli-freeze.test.js build/conductor/epic-reconcile.test.js build/conductor/epic-runtime.test.js build/conductor/epic-tick-sequence.test.js build/conductor/epic-runtime-post-action.test.js build/conductor/file-scope-guard.test.js build/conductor/file-scope-guard.integration.test.js build/mcp-profile.test.js build/mcp-profile-registration.test.js build/tools-budget.test.js build/visual-diff-worker.test.js build/visual-diff.test.js build/estimate-epic.test.js build/integration/measure-tools.test.js build/sfcc/config.test.js build/sfcc/ocapi-shape.test.js build/sfcc/output.test.js build/sfcc/credentials.test.js build/sfcc/ocapi-write-faults.test.js build/sfcc/write-guard.test.js build/sfcc/write-grants.test.js build/sfcc/write-result.test.js build/sfcc/writes.test.js build/sfcc/writes-system-object-payloads.test.js build/sfcc/writes-payloads.test.js && node --experimental-test-module-mocks --test --test-force-exit build/index-heavy-read-truncation.test.js build/index-artifacts.test.js build/index.review-rounds.test.js build/index-brainstorm-filenames.test.js build/index-output-path.test.js build/index-generate-decision-page.test.js build/index-generate-decision-page.integration.test.js build/visual-diff.registration.test.js build/visual-diff.attachment-adapter.test.js build/attachment-download.test.js build/attachment-upload.test.js build/conductor/paths.test.js build/conductor/store-lifecycle.test.js build/conductor/store-queries.test.js build/conductor/tools.test.js build/conductor/cli.test.js build/conductor/security-regressions.test.js build/conductor/git-inspection.test.js build/conductor/tools-done-gate.test.js build/conductor/pr-ci-producer-emit-seam.test.js build/conductor/cli-git-hooks.test.js build/sfcc/client.test.js build/sfcc/tool-wrapper.test.js build/sfcc/setup-status.test.js build/sfcc/permissions.test.js build/sfcc/register.test.js build/sfcc/reads-system-object.test.js build/sfcc/reads-custom-object-def.test.js build/sfcc/reads-site-preference.test.js build/sfcc/writes-system-object.test.js build/sfcc/writes-custom-object-def.test.js build/sfcc/writes-site-preference.test.js",
31
+ "test:integration": "node --test build/integration/refresh-main.integration.test.js build/integration/start-tickets.integration.test.js build/integration/doctor.integration.test.js build/integration/agent-capabilities.integration.test.js build/integration/conductor-producer.integration.test.js build/integration/conductor-message-relay.integration.test.js build/integration/executor-http-runner.integration.test.js build/integration/executor-job-behaviors.integration.test.js build/integration/executor-recovery-jobs.integration.test.js build/integration/executor-spec-review-prompt.integration.test.js build/integration/resume-pre-spawn.git.integration.test.js build/integration/worker-finalization-origin.integration.test.js build/integration/post-remediation-merge-ci-wait.integration.test.js build/integration/executor-merge-supervision.integration.test.js build/integration/attachment-binary-roundtrip.integration.test.js build/integration/dependent-ticket-fresh-base.integration.test.js build/integration/execute-plan-instructions.integration.test.js",
32
32
  "test:smoke": "node --test build/integration/packaged-cli-smoke.test.js",
33
33
  "prepublishOnly": "node scripts/bundle-assets.js && npm run build && node scripts/verify-shebang.cjs"
34
34
  },
@@ -5321,12 +5321,12 @@ function populateCiFollowupConfig(rawValue) {
5321
5321
  // ladder below are kept byte-identical to the backend
5322
5322
  // (api/library/review_ticket_policy.py) so a stored override round-trips exactly.
5323
5323
  const REVIEW_POLICY_BUCKETS = Object.freeze([
5324
- { name: 'Very Easy', slug: 'very-easy', range: '1' },
5325
- { name: 'Easy', slug: 'easy', range: '2' },
5326
- { name: 'Easy-Moderate', slug: 'easy-moderate', range: '3-4' },
5327
- { name: 'Moderate', slug: 'moderate', range: '5' },
5328
- { name: 'Moderate-Hard', slug: 'moderate-hard', range: '6' },
5329
- { name: 'Hard', slug: 'hard', range: '7-10' },
5324
+ { name: 'Very Easy', slug: 'very-easy', range: '1-2' },
5325
+ { name: 'Easy', slug: 'easy', range: '3-4' },
5326
+ { name: 'Easy-Moderate', slug: 'easy-moderate', range: '5' },
5327
+ { name: 'Moderate', slug: 'moderate', range: '6' },
5328
+ { name: 'Moderate-Hard', slug: 'moderate-hard', range: '7' },
5329
+ { name: 'Hard', slug: 'hard', range: '8-10' },
5330
5330
  ]);
5331
5331
 
5332
5332
  // The two per-bucket automations and their coarse per-round tiers. `clarify` must
@@ -5345,9 +5345,9 @@ const REVIEW_POLICY_DEFAULT_NEW_TIER = 'basic';
5345
5345
  const DEFAULT_REVIEW_POLICY_LADDER = Object.freeze({
5346
5346
  'Very Easy': { clarify: ['basic'], critique: [] },
5347
5347
  'Easy': { clarify: ['basic', 'basic'], critique: [] },
5348
- 'Easy-Moderate': { clarify: ['premium', 'basic'], critique: [] },
5349
- 'Moderate': { clarify: ['premium', 'premium'], critique: ['basic'] },
5350
- 'Moderate-Hard': { clarify: ['premium'], critique: ['premium'] },
5348
+ 'Easy-Moderate': { clarify: ['basic', 'basic'], critique: ['basic'] },
5349
+ 'Moderate': { clarify: ['premium', 'basic'], critique: ['premium'] },
5350
+ 'Moderate-Hard': { clarify: ['premium', 'basic'], critique: ['premium', 'basic'] },
5351
5351
  'Hard': { clarify: ['premium', 'premium'], critique: ['premium', 'premium'] },
5352
5352
  });
5353
5353