@bridge_gpt/mcp-server 0.2.27 → 0.2.29

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.
@@ -24,8 +24,29 @@
24
24
  */
25
25
  import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
26
26
  import os from "node:os";
27
+ import readline from "node:readline";
27
28
  import { approveEpicPlan, createEpicRun, fetchEpicRunState, resolveConductorBridgeApiAccess, storeEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
29
+ import { validateBranchName } from "./base-ref.js";
28
30
  import { hashPlan } from "./conductor/plan.js";
31
+ /** Echoed single-line prompt on stderr (mirrors connect-github's helper). */
32
+ function defaultPromptLine(promptText) {
33
+ return new Promise((resolve) => {
34
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
35
+ // `rl.close()` emits `close` SYNCHRONOUSLY, so without this guard the close
36
+ // handler would settle the promise empty and discard a real answer. EOF must
37
+ // also resolve rather than deadlock a top-level await.
38
+ let answered = false;
39
+ rl.on("close", () => {
40
+ if (!answered)
41
+ resolve("");
42
+ });
43
+ rl.question(promptText, (answer) => {
44
+ answered = true;
45
+ rl.close();
46
+ resolve(answer.trim());
47
+ });
48
+ });
49
+ }
29
50
  export function createDefaultSetupEpicDeps() {
30
51
  return {
31
52
  env: process.env,
@@ -37,6 +58,8 @@ export function createDefaultSetupEpicDeps() {
37
58
  fetch: globalThis.fetch,
38
59
  log: (m) => console.log(m),
39
60
  errorLog: (m) => console.error(m),
61
+ isTTY: Boolean(process.stdin.isTTY),
62
+ promptLine: defaultPromptLine,
40
63
  };
41
64
  }
42
65
  /** User-facing usage text. */
@@ -54,6 +77,11 @@ export function getSetupEpicUsage() {
54
77
  "Options:",
55
78
  " --repo <name> Repo name (default: BAPI_REPO_NAME or .bridge/config)",
56
79
  " --plan-version <n> Assert the sidecar's plan_version equals <n>",
80
+ " --feature-branch <name> Run the epic on a dedicated feature branch.",
81
+ " The branch is created from the repository base branch",
82
+ " on origin, and every child-ticket PR targets it.",
83
+ " Omit (the default) to continue on the repository base",
84
+ " branch. Interactive runs are offered a proposal.",
57
85
  " --dry-run Validate and preview; make no mutating calls",
58
86
  " --json Emit a single JSON result object on stdout",
59
87
  " -h, --help Show this help",
@@ -69,6 +97,24 @@ function takeValue(argv, i, flag) {
69
97
  return null;
70
98
  return next;
71
99
  }
100
+ /**
101
+ * Normalize + validate a raw `--feature-branch` value.
102
+ *
103
+ * Trims the value; an explicitly blank value normalizes to `undefined` (no
104
+ * option), matching "omit to continue on the base branch". A nonblank value is
105
+ * validated with the shared {@link validateBranchName}; a malformed value yields
106
+ * a flag-specific error. Returns a discriminated result so the caller can report
107
+ * the error without consuming a following flag.
108
+ */
109
+ function parseFeatureBranchValue(raw) {
110
+ const trimmed = raw.trim();
111
+ if (trimmed === "")
112
+ return { ok: true, value: undefined };
113
+ const reason = validateBranchName(trimmed);
114
+ if (reason)
115
+ return { ok: false, error: `Invalid --feature-branch value: ${reason}` };
116
+ return { ok: true, value: trimmed };
117
+ }
72
118
  export function parseSetupEpicArgs(argv) {
73
119
  if (argv.includes("-h") || argv.includes("--help")) {
74
120
  return { status: "help", usage: getSetupEpicUsage() };
@@ -77,11 +123,32 @@ export function parseSetupEpicArgs(argv) {
77
123
  let planFile;
78
124
  let repo;
79
125
  let planVersion;
126
+ let featureBranch;
80
127
  let dryRun = false;
81
128
  let json = false;
82
129
  for (let i = 0; i < argv.length; i++) {
83
130
  const arg = argv[i];
131
+ // `--feature-branch=<name>` inline form (handled before the exact-match switch).
132
+ if (arg.startsWith("--feature-branch=")) {
133
+ const parsedFb = parseFeatureBranchValue(arg.slice("--feature-branch=".length));
134
+ if (!parsedFb.ok)
135
+ return { status: "error", message: parsedFb.error };
136
+ featureBranch = parsedFb.value;
137
+ continue;
138
+ }
84
139
  switch (arg) {
140
+ case "--feature-branch": {
141
+ // Do not consume a following flag as the value (Step 2.5).
142
+ const v = takeValue(argv, i, arg);
143
+ if (v === null)
144
+ return { status: "error", message: "--feature-branch requires a value." };
145
+ const parsedFb = parseFeatureBranchValue(v);
146
+ if (!parsedFb.ok)
147
+ return { status: "error", message: parsedFb.error };
148
+ featureBranch = parsedFb.value;
149
+ i++;
150
+ break;
151
+ }
85
152
  case "--epic-key": {
86
153
  const v = takeValue(argv, i, arg);
87
154
  if (v === null)
@@ -139,7 +206,7 @@ export function parseSetupEpicArgs(argv) {
139
206
  return { status: "error", message: "setup-epic requires --plan-file <path>." };
140
207
  return {
141
208
  status: "ok",
142
- options: { epicKey, planFile, repo, planVersion, dryRun, json },
209
+ options: { epicKey, planFile, repo, planVersion, featureBranch, dryRun, json },
143
210
  };
144
211
  }
145
212
  /**
@@ -258,6 +325,55 @@ function findCycle(keys, adjacency) {
258
325
  }
259
326
  return null;
260
327
  }
328
+ /** Propose a conductor epic feature branch name for an epic key (BAPI-655). */
329
+ function proposeFeatureBranchName(epicKey) {
330
+ return `epic/${epicKey}`;
331
+ }
332
+ /**
333
+ * Resolve the epic feature branch selection for this invocation.
334
+ *
335
+ * Returns a validated branch name, or `undefined` to continue on the repository
336
+ * base branch. Precedence:
337
+ *
338
+ * - An explicit `--feature-branch <name>` is already confirmed + validated at
339
+ * parse time and is returned unchanged (no prompt).
340
+ * - A non-interactive (piped/CI) run or a `--json` run never prompts and never
341
+ * blocks — it retains the absent-branch behavior.
342
+ * - An interactive run is shown the proposal (branch name, "create from the
343
+ * repository base branch" strategy, and the child-PR-targeting consequence)
344
+ * and may accept it, type a custom name, or decline to use the base branch.
345
+ * An edited nonblank value is re-validated; an invalid value is redisplayed
346
+ * for correction rather than silently rewritten.
347
+ */
348
+ async function resolveFeatureBranchSelection(opts, repoName, deps) {
349
+ if (opts.featureBranch !== undefined)
350
+ return opts.featureBranch;
351
+ if (!deps.isTTY || opts.json)
352
+ return undefined;
353
+ const proposed = proposeFeatureBranchName(opts.epicKey);
354
+ deps.errorLog("");
355
+ deps.errorLog(`Feature branch (optional) for epic ${opts.epicKey} on ${repoName}:`);
356
+ deps.errorLog(` Proposed: ${proposed}`);
357
+ deps.errorLog(` Strategy: create a new branch from the repository base branch`);
358
+ deps.errorLog(` Effect: every child-ticket PR will target this branch`);
359
+ for (;;) {
360
+ const answer = (await deps.promptLine(`Use feature branch? 'y' = ${proposed}, a name = custom, Enter = base branch: `)).trim();
361
+ if (answer === "")
362
+ return undefined; // decline → repository base branch
363
+ const lowered = answer.toLowerCase();
364
+ if (lowered === "n" || lowered === "no")
365
+ return undefined;
366
+ if (lowered === "y" || lowered === "yes")
367
+ return proposed;
368
+ // Otherwise the answer is an edited branch name.
369
+ const reason = validateBranchName(answer);
370
+ if (reason) {
371
+ deps.errorLog(` Invalid branch name: ${reason} Try again, or press Enter for the base branch.`);
372
+ continue; // redisplay for correction; never silently rewrite the value
373
+ }
374
+ return answer;
375
+ }
376
+ }
261
377
  function errorDetail(err) {
262
378
  if (err instanceof ConductorBridgeApiError) {
263
379
  const status = err.status !== undefined ? ` (HTTP ${err.status})` : "";
@@ -334,14 +450,35 @@ export async function runSetupEpicCli(argv, overrides = {}) {
334
450
  say(`Local hash: ${localHash}`);
335
451
  for (const w of warnings)
336
452
  say(` [warn] ${w}`);
453
+ // --- Feature branch selection (BAPI-655) --------------------------------
454
+ // Resolved AFTER local/access context is known but BEFORE any run-state read
455
+ // or mutating request, so malformed interactive input fails before network
456
+ // dispatch. Returns undefined to continue on the repository base branch.
457
+ const promptedInteractively = opts.featureBranch === undefined && deps.isTTY && !opts.json;
458
+ const featureBranch = await resolveFeatureBranchSelection(opts, access.repoName, deps);
459
+ if (featureBranch !== undefined) {
460
+ say(`Feature: ${featureBranch} (create from repository base branch on origin)`);
461
+ }
462
+ else if (promptedInteractively) {
463
+ // Ordinary, non-warning notice — only when an interactive operator declined.
464
+ say("Feature: none — continue using the repository base branch");
465
+ }
337
466
  // --- Step 0: pre-check ---------------------------------------------------
338
467
  // Never create on an ambiguous read. A wrong answer here mints a duplicate run.
339
468
  let existingRunId = null;
340
469
  let existingStatus = null;
470
+ let existingBaseBranch = null;
341
471
  try {
342
472
  const state = await fetchEpicRunState(access, opts.epicKey, deps.fetch);
343
473
  existingRunId = state.epic_run?.epic_run_id ?? null;
344
474
  existingStatus = state.epic_run?.status ?? null;
475
+ const existingPolicy = state.epic_run?.policy_json;
476
+ const existingBase = existingPolicy && typeof existingPolicy === "object"
477
+ ? existingPolicy.base_branch
478
+ : undefined;
479
+ existingBaseBranch = typeof existingBase === "string" && existingBase.trim() !== ""
480
+ ? existingBase
481
+ : null;
345
482
  }
346
483
  catch (err) {
347
484
  if (err instanceof ConductorBridgeApiError && err.status === 404) {
@@ -361,6 +498,19 @@ export async function runSetupEpicCli(argv, overrides = {}) {
361
498
  return 1;
362
499
  }
363
500
  }
501
+ // --- Feature-branch conflict guard against an existing live run ----------
502
+ // A selected branch that disagrees with the existing run's stored base branch
503
+ // must fail closed rather than patch or silently change that run. A matching
504
+ // branch is allowed (the rerun re-validates remote provisioning at approval);
505
+ // supplying no branch preserves the existing run's policy untouched.
506
+ if (existingRunId && featureBranch !== undefined && existingBaseBranch !== featureBranch) {
507
+ deps.errorLog(`Epic ${opts.epicKey} already has a live run (${existingRunId}) whose feature ` +
508
+ `branch is ${existingBaseBranch ? `'${existingBaseBranch}'` : "unset (base branch)"}, ` +
509
+ `which conflicts with the requested '${featureBranch}'. setup-epic will not ` +
510
+ `retarget or rebuild an existing run. Re-run without --feature-branch to reuse ` +
511
+ `it unchanged, or abandon the run to start over on a new branch.`);
512
+ return 1;
513
+ }
364
514
  if (opts.dryRun) {
365
515
  say("");
366
516
  say("[dry-run] No changes made. Would:");
@@ -370,6 +520,9 @@ export async function runSetupEpicCli(argv, overrides = {}) {
370
520
  else {
371
521
  say(` - POST /jira/epic-runs/runs (create run for ${opts.epicKey})`);
372
522
  }
523
+ if (featureBranch !== undefined) {
524
+ say(` - feature branch: ${featureBranch} (create from repository base branch; no request made in dry-run)`);
525
+ }
373
526
  say(` - POST /jira/epic-runs/runs/${opts.epicKey}/plan (v${plan.plan_version})`);
374
527
  say(` - POST /jira/epic-runs/runs/${opts.epicKey}/approve-plan (v${plan.plan_version})`);
375
528
  if (opts.json) {
@@ -380,6 +533,8 @@ export async function runSetupEpicCli(argv, overrides = {}) {
380
533
  plan_version: plan.plan_version,
381
534
  local_plan_hash: localHash,
382
535
  existing_run_id: existingRunId,
536
+ // Only present for a feature-branch run — no-feature JSON is unchanged.
537
+ ...(featureBranch !== undefined ? { feature_branch: featureBranch } : {}),
383
538
  warnings,
384
539
  }, null, 2));
385
540
  }
@@ -398,13 +553,20 @@ export async function runSetupEpicCli(argv, overrides = {}) {
398
553
  plan_approved: false,
399
554
  warnings,
400
555
  };
556
+ if (featureBranch !== undefined)
557
+ result.feature_branch = featureBranch;
401
558
  // --- Step 1: create (only when there is no live run) ---------------------
402
559
  if (existingRunId) {
403
560
  say(`Run: reusing ${existingRunId} (status: ${existingStatus})`);
404
561
  }
405
562
  else {
406
563
  try {
407
- const run = await createEpicRun(access, { epicKey: opts.epicKey }, deps.fetch);
564
+ // Persist the confirmed feature branch as policy_json.base_branch ONLY when
565
+ // one was selected; otherwise keep the exact legacy create request shape.
566
+ const createRequest = featureBranch !== undefined
567
+ ? { epicKey: opts.epicKey, policyJson: { base_branch: featureBranch } }
568
+ : { epicKey: opts.epicKey };
569
+ const run = await createEpicRun(access, createRequest, deps.fetch);
408
570
  result.epic_run_id = run.epic_run_id;
409
571
  result.status = run.status;
410
572
  result.run_created = true;
@@ -439,9 +601,29 @@ export async function runSetupEpicCli(argv, overrides = {}) {
439
601
  deps.errorLog(`Failed to store the plan: ${errorDetail(err)}`);
440
602
  return 1;
441
603
  }
442
- // --- Step 4: approve -----------------------------------------------------
604
+ // --- Step 4: approve (also provisions the feature branch server-side) -----
605
+ // For a feature-branch run the backend creates/validates the branch on origin
606
+ // as an approval prerequisite. Provisioning runs and completes BEFORE the
607
+ // approval (ticket-seeding + CAS activation) step server-side, so only a
608
+ // failure the backend tags with the structured FEATURE_BRANCH_PROVISIONING
609
+ // error_code is actually a branch/credentials problem — key the distinct
610
+ // message off that code, not merely off whether --feature-branch was passed,
611
+ // so an unrelated approval failure (e.g. ticket-seeding, a masked 500) is not
612
+ // misattributed to provisioning.
613
+ if (featureBranch !== undefined) {
614
+ say(`Branch: creating or validating ${featureBranch} on origin…`);
615
+ }
443
616
  const approval = await approveEpicPlan(access, { epicKey: opts.epicKey, planVersion: plan.plan_version }, deps.fetch).catch((err) => {
444
- deps.errorLog(`Failed to approve the plan: ${errorDetail(err)}`);
617
+ if (featureBranch !== undefined &&
618
+ err instanceof ConductorBridgeApiError &&
619
+ err.errorCode === "FEATURE_BRANCH_PROVISIONING") {
620
+ deps.errorLog(`Failed to provision the feature branch '${featureBranch}' — child-ticket ` +
621
+ `dispatch has NOT started. Correct repository access or the branch ` +
622
+ `configuration, then re-run setup-epic.\nDetail: ${errorDetail(err)}`);
623
+ }
624
+ else {
625
+ deps.errorLog(`Failed to approve the plan: ${errorDetail(err)}`);
626
+ }
445
627
  return null;
446
628
  });
447
629
  if (approval === null)
@@ -451,6 +633,18 @@ export async function runSetupEpicCli(argv, overrides = {}) {
451
633
  result.plan_hash = approval.plan_hash;
452
634
  result.status = "active";
453
635
  say(`Plan: approved v${plan.plan_version}`);
636
+ const prov = approval.featureBranchProvisioning;
637
+ if (prov) {
638
+ result.feature_branch_provisioning = prov;
639
+ if (prov.status === "created") {
640
+ say(`Branch: ready on origin — created '${prov.feature_branch}' from ` +
641
+ `'${prov.source_branch}' at ${prov.source_sha}`);
642
+ }
643
+ else {
644
+ say(`Branch: '${prov.feature_branch}' already exists — validated, unchanged ` +
645
+ `(the remote ref was not moved or reset); head ${prov.remote_head_sha}`);
646
+ }
647
+ }
454
648
  }
455
649
  else if (approval.reason === "multiple_active_runs") {
456
650
  deps.errorLog(`Epic ${opts.epicKey} has MULTIPLE active runs — the plan could not be approved ` +
@@ -1064,10 +1064,13 @@ export function buildAgentPrompt(key, opts = {}) {
1064
1064
  }
1065
1065
  /**
1066
1066
  * Build the ordered argv for an agent invocation:
1067
- * `[command, (--model, alias)?, prompt]`. The model flag+alias are appended ONLY
1068
- * when the agent supports a model override AND `modelAlias` is a non-empty valid
1069
- * alias; otherwise they are omitted entirely (fail-open). The prompt is always
1070
- * the final argument. Pure and never throws on an invalid alias.
1067
+ * `[command, (--model, alias)?, ...interactiveLaunchArgs, prompt]`. The model
1068
+ * flag+alias are appended ONLY when the agent supports a model override AND
1069
+ * `modelAlias` is a non-empty valid alias; otherwise they are omitted entirely
1070
+ * (fail-open) without suppressing `interactiveLaunchArgs`. Any registry-declared
1071
+ * `agent.interactiveLaunchArgs` (e.g. cursor-agent's `--trust`) are spread in
1072
+ * next, before the prompt. The prompt is always the final argument. Pure and
1073
+ * never throws on an invalid alias.
1071
1074
  */
1072
1075
  export function buildAgentInvocationArgv(agent, prompt, modelAlias) {
1073
1076
  const argv = [agent.command];
@@ -1076,14 +1079,16 @@ export function buildAgentInvocationArgv(agent, prompt, modelAlias) {
1076
1079
  isValidModelAlias(modelAlias)) {
1077
1080
  argv.push(agent.modelFlag, modelAlias);
1078
1081
  }
1082
+ argv.push(...(agent.interactiveLaunchArgs ?? []));
1079
1083
  argv.push(prompt);
1080
1084
  return argv;
1081
1085
  }
1082
1086
  /**
1083
1087
  * Build the agent invocation string for the agent's prompt style, from the
1084
1088
  * validated argv array. The registry-controlled command head stays unquoted
1085
- * (it is never untrusted input); every following argument (the optional
1086
- * `--model <alias>` and the prompt) is run through the platform-correct `quote`.
1089
+ * (it is never untrusted input); every following argument the optional
1090
+ * `--model <alias>` pair, any registry-declared interactive launch arguments,
1091
+ * and the prompt — is run through the platform-correct `quote`.
1087
1092
  */
1088
1093
  export function buildAgentInvocation(agent, prompt, quote, modelAlias) {
1089
1094
  switch (agent.promptArgStyle) {
@@ -1098,12 +1103,12 @@ export function buildAgentInvocation(agent, prompt, quote, modelAlias) {
1098
1103
  }
1099
1104
  }
1100
1105
  }
1101
- /** POSIX agent shell command: `cd '<path>' && <agent> [--model '<alias>'] '<prompt>'`. */
1106
+ /** POSIX agent shell command: `cd '<path>' && <agent> [--model '<alias>'] [...interactiveLaunchArgs] '<prompt>'`. */
1102
1107
  export function buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch) {
1103
1108
  const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch }), (p) => `'${shSquoteInner(p)}'`, modelAlias);
1104
1109
  return `cd '${shSquoteInner(worktreePath)}' && ${invocation}`;
1105
1110
  }
1106
- /** PowerShell agent shell command: `Set-Location -LiteralPath '<path>'; <agent> [--model '<alias>'] '<prompt>'`. */
1111
+ /** PowerShell agent shell command: `Set-Location -LiteralPath '<path>'; <agent> [--model '<alias>'] [...interactiveLaunchArgs] '<prompt>'`. */
1107
1112
  export function buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch) {
1108
1113
  const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch }), powershellSquote, modelAlias);
1109
1114
  return `Set-Location -LiteralPath ${powershellSquote(worktreePath)}; ${invocation}`;
@@ -1130,7 +1135,12 @@ export function buildAgentShellCommand(agent, key, worktreePath, platform = "dar
1130
1135
  * everywhere else. The directory-change and agent invocation are quoted with the
1131
1136
  * platform-correct quoter (`Set-Location -LiteralPath …;` on Windows, `cd '…' &&`
1132
1137
  * on POSIX). An optional validated `modelAlias` is injected as `--model` at the
1133
- * spawn boundary, exactly like `buildAgentShellCommand`.
1138
+ * spawn boundary, exactly like `buildAgentShellCommand`. Any registry-declared
1139
+ * `agent.interactiveLaunchArgs` (e.g. cursor-agent's `--trust`) are included
1140
+ * automatically via the shared `buildAgentInvocation` call below — this covers
1141
+ * both `start-tickets` (via its platform shell-command functions) and
1142
+ * `install-bridge` (which reaches this function through its `buildShellCommand`
1143
+ * dependency) without any caller-specific logic.
1134
1144
  */
1135
1145
  export function buildGenericAgentShellCommand(agent, prompt, cwd, platform = "darwin", modelAlias) {
1136
1146
  if (platform === "win32") {
@@ -1233,9 +1243,13 @@ export function buildITermAppleScript(shellCommand, title, badgeText) {
1233
1243
  * failure for expected spawn errors (never throws).
1234
1244
  */
1235
1245
  export async function spawnMacOSTerminalTab(deps, terminal, shellCommand, context) {
1236
- const title = terminalTitleForTicket(context?.key ?? "");
1246
+ // BAPI-657: an explicit `context.title` overrides the key-derived title; absent it,
1247
+ // real ticket tabs keep `<KEY> Implementation`. The iTerm badge follows the same
1248
+ // rule — the override when supplied, otherwise the existing key-based badge.
1249
+ const title = context?.title ?? terminalTitleForTicket(context?.key ?? "");
1250
+ const badgeText = context?.title ?? (context?.key || undefined);
1237
1251
  const script = terminal === "iterm"
1238
- ? buildITermAppleScript(shellCommand, title, context?.key || undefined)
1252
+ ? buildITermAppleScript(shellCommand, title, badgeText)
1239
1253
  : buildTerminalAppleScript(shellCommand, title);
1240
1254
  const result = await deps.runCommand("osascript", ["-e", script]);
1241
1255
  if (commandSucceeded(result))
@@ -1309,7 +1323,9 @@ export async function spawnWindowsTerminalTab(deps, _terminal, shellCommand, con
1309
1323
  error: "Windows spawner requires a worktreePath context to open a tab.",
1310
1324
  };
1311
1325
  }
1312
- const title = terminalTitleForTicket(context?.key ?? "");
1326
+ // BAPI-657: honor an explicit title override for both the Windows Terminal tab and
1327
+ // the PowerShell fallback window; absent it, keep the key-derived ticket title.
1328
+ const title = context?.title ?? terminalTitleForTicket(context?.key ?? "");
1313
1329
  if (await isCommandOnPath(deps, WINDOWS_TERMINAL_COMMAND)) {
1314
1330
  const args = buildWindowsTerminalArgs(worktreePath, shellCommand, title);
1315
1331
  const result = await deps.runCommand(WINDOWS_TERMINAL_COMMAND, args);
@@ -1365,6 +1381,17 @@ export function sanitizeTmuxName(value) {
1365
1381
  export function tmuxWindowNameForTicket(key) {
1366
1382
  return terminalTitleForTicket(sanitizeTmuxName(key));
1367
1383
  }
1384
+ /**
1385
+ * A tmux WINDOW label derived from an explicit `context.title` override (BAPI-657).
1386
+ * Unlike a session identifier, a tmux window name may contain spaces, so this keeps
1387
+ * them (`"Bridge Install"` stays `"Bridge Install"`) while stripping only the
1388
+ * tmux-target-hostile `.`/`:` and collapsing whitespace runs. Falls back to
1389
+ * `"session"` if the title reduces to empty.
1390
+ */
1391
+ export function tmuxWindowLabelFromTitle(title) {
1392
+ const cleaned = title.replace(/[.:]+/g, " ").replace(/\s+/g, " ").trim();
1393
+ return cleaned.length > 0 ? cleaned : "session";
1394
+ }
1368
1395
  /** Resolve the tmux session-name prefix (env override, else the default). */
1369
1396
  export function tmuxSessionPrefix(deps) {
1370
1397
  const override = deps.env[TMUX_SESSION_OVERRIDE_ENV];
@@ -1418,7 +1445,11 @@ export async function spawnLinuxTmuxTerminalTab(deps, _terminal, shellCommand, c
1418
1445
  };
1419
1446
  }
1420
1447
  const session = tmuxSessionNameForTicket(deps, key);
1421
- const window = tmuxWindowNameForTicket(key);
1448
+ // BAPI-657: an explicit title overrides only the WINDOW label; the SESSION
1449
+ // identifier stays key-derived so `tmux attach -t <session>` is unchanged.
1450
+ const window = context?.title
1451
+ ? tmuxWindowLabelFromTitle(context.title)
1452
+ : tmuxWindowNameForTicket(key);
1422
1453
  const paneCommand = buildTmuxPaneCommand(shellCommand);
1423
1454
  const hasSession = await deps.runCommand(TMUX_COMMAND, ["has-session", "-t", session]);
1424
1455
  const args = commandSucceeded(hasSession)
@@ -36,8 +36,19 @@ import { getMethodLiteral } from "@modelcontextprotocol/sdk/server/zod-json-sche
36
36
  export const RECOGNIZED_TOOL_SURFACE_SCHEMA_VERSIONS = new Set([1]);
37
37
  /** The single recognized schema version (convenience export for tests). */
38
38
  export const TOOL_SURFACE_SCHEMA_VERSION = 1;
39
- /** Absolute end-to-end deadline for one probe (header resolve + fetch + parse). */
40
- export const TOOL_SURFACE_PROBE_DEADLINE_MS = 500;
39
+ /**
40
+ * Absolute end-to-end deadline for one probe (header resolve + fetch + parse).
41
+ *
42
+ * The server handler does a catalog read plus two sequential Postgres round-trips
43
+ * and routinely lands at 600-900ms on Heroku, so a 500ms deadline aborted nearly
44
+ * every probe mid-response — the socket teardown surfaced as a continuous stream
45
+ * of Heroku `H27 "Client Request Interrupted"` router warnings, and the gating
46
+ * feature never took effect because the client always fell open on timeout. The
47
+ * deadline sits comfortably above observed server p99 with headroom, while
48
+ * staying well under the 12-18s poll interval so a slow probe never overlaps the
49
+ * next one.
50
+ */
51
+ export const TOOL_SURFACE_PROBE_DEADLINE_MS = 2_500;
41
52
  /** Inclusive lower bound of the recurring-poll jitter window. */
42
53
  export const TOOL_SURFACE_POLL_MIN_MS = 12_000;
43
54
  /** Inclusive upper bound of the recurring-poll jitter window. */
@@ -1,2 +1,2 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.2.27";
2
+ export const VERSION = "0.2.29";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge_gpt/mcp-server",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
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",
@@ -25,8 +25,8 @@
25
25
  "check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
26
26
  "postbuild": "node scripts/prepend-shebang.cjs",
27
27
  "start": "node build/index.js",
28
- "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/request-brainstorm.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/mcp-host-targets.test.js build/mcp-install-state.test.js build/mcp-host-config.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/install-bridge-tools.test.js build/init.test.js build/init-docs.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/conductor-bundle-artifacts.test.js build/conductor-bundle-cli.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/env-flags.test.js build/bridge-api-urls.test.js build/tool-surface-gating.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 build/sfcc/log-gate.test.js build/sfcc/log-query.test.js && node --experimental-test-module-mocks --test --test-force-exit build/index-heavy-read-truncation.test.js build/index-artifacts.test.js build/automation-progress.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 build/connect-github.test.js",
29
- "test:integration": "node --test build/integration/refresh-main.integration.test.js build/integration/start-tickets.integration.test.js build/integration/start-tickets-tier-handoff.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 build/integration/conductor-bundle-artifacts.integration.test.js build/integration/install-bridge-repo-resolution.integration.test.js build/integration/capability-report-contract.integration.test.js build/integration/request-brainstorm-general.integration.test.js",
28
+ "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/request-brainstorm.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/mcp-host-targets.test.js build/mcp-install-state.test.js build/mcp-host-config.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/install-bridge-tools.test.js build/init.test.js build/init-docs.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/conductor-bundle-artifacts.test.js build/conductor-bundle-cli.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/env-flags.test.js build/bridge-api-urls.test.js build/tool-surface-gating.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 build/sfcc/log-gate.test.js build/sfcc/log-query.test.js && node --experimental-test-module-mocks --test --test-force-exit build/index-heavy-read-truncation.test.js build/index-artifacts.test.js build/automation-progress.test.js build/recovery-formatting.test.js build/wait-for-result.test.js build/ticket-wait-recovery.test.js build/council-wait-recovery.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 build/connect-github.test.js build/connect-github-api.test.js",
29
+ "test:integration": "node --test build/integration/refresh-main.integration.test.js build/integration/start-tickets.integration.test.js build/integration/start-tickets-tier-handoff.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 build/integration/conductor-bundle-artifacts.integration.test.js build/integration/install-bridge-repo-resolution.integration.test.js build/integration/capability-report-contract.integration.test.js build/integration/request-brainstorm-general.integration.test.js build/integration/request-council-trigger-drop.integration.test.js",
30
30
  "test:smoke": "node --test build/integration/packaged-cli-smoke.test.js",
31
31
  "prepublishOnly": "node scripts/bundle-assets.js && npm run build && node scripts/verify-shebang.cjs"
32
32
  },