@bridge_gpt/mcp-server 0.2.67 → 0.2.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +178 -15
  2. package/build/agent-launchers/claude-executor-adapter.js +27 -0
  3. package/build/agents.generated.js +22 -2
  4. package/build/bitbucket-workspace-remote.js +136 -0
  5. package/build/bootstrap-project-identity.js +24 -0
  6. package/build/bridge-permissions.js +829 -0
  7. package/build/claude-settings.js +18 -0
  8. package/build/cli-help.js +21 -0
  9. package/build/command-assets-doctor.js +24 -70
  10. package/build/command-provisioning.js +29 -5
  11. package/build/commands.generated.js +8 -8
  12. package/build/conductor/bridge-api-client.js +714 -0
  13. package/build/conductor/command-drift-production.js +92 -0
  14. package/build/conductor/command-drift.js +89 -0
  15. package/build/conductor/doctor.js +69 -26
  16. package/build/conductor/epic-branch-ci-inspection.js +1375 -0
  17. package/build/conductor/epic-implementer-cli.js +76 -22
  18. package/build/conductor/gate-discovery.js +890 -0
  19. package/build/conductor/install-doctor.js +15 -3
  20. package/build/conductor/json-output.js +63 -0
  21. package/build/conductor/readiness-cli.js +95 -10
  22. package/build/conductor/readiness-sections.js +369 -3
  23. package/build/conductor/readiness.js +211 -8
  24. package/build/conductor/recovery-cli.js +110 -4
  25. package/build/conductor/recovery-operations.js +99 -1
  26. package/build/connect-bitbucket-api.js +41 -6
  27. package/build/connect-bitbucket.js +106 -33
  28. package/build/credential-store.js +207 -47
  29. package/build/docs.generated.js +57 -3
  30. package/build/doctor.js +92 -1
  31. package/build/drive-epic.js +45 -8
  32. package/build/epic-coherence-input.test-fixtures.js +4 -3
  33. package/build/epic-implementer-bin.js +1374 -25
  34. package/build/epic-workarounds-path.js +39 -0
  35. package/build/executor/cli.js +30 -0
  36. package/build/executor/completion-contract.js +18 -5
  37. package/build/executor/deps.js +4 -1
  38. package/build/executor/gate-qualification-job.js +499 -0
  39. package/build/executor/guarded-fast-forward-push.js +129 -0
  40. package/build/executor/integration-job.js +122 -8
  41. package/build/executor/job-errors.js +8 -1
  42. package/build/executor/job-runner.js +589 -47
  43. package/build/executor/likely-tests.js +290 -0
  44. package/build/executor/live-worker-registry.js +1 -1
  45. package/build/executor/observation.js +31 -2
  46. package/build/executor/packaged-recipe.js +106 -0
  47. package/build/executor/permissions.js +115 -28
  48. package/build/executor/platform.js +1 -1
  49. package/build/executor/preflight.js +61 -12
  50. package/build/executor/process-containment.js +36 -12
  51. package/build/executor/process.js +15 -7
  52. package/build/executor/prompt-spec.js +33 -1
  53. package/build/executor/recently-failed-tests-file.js +71 -0
  54. package/build/executor/reconcile-overlap-job.js +41 -23
  55. package/build/executor/required-verification.js +1 -1
  56. package/build/executor/required-work-ledger.js +105 -17
  57. package/build/executor/retirement.js +69 -0
  58. package/build/executor/runner.js +114 -11
  59. package/build/executor/test-clock.js +15 -0
  60. package/build/executor/types.js +32 -0
  61. package/build/executor/viewer-tabs.js +58 -8
  62. package/build/executor/worker-command.js +23 -7
  63. package/build/executor/worker-diagnostics.js +37 -0
  64. package/build/executor/worker-finalization.js +31 -5
  65. package/build/executor/worker-guard-hook.js +179 -25
  66. package/build/executor/worker-log.js +4 -3
  67. package/build/executor/worker-verification-guidance.js +37 -0
  68. package/build/executor/worktree.js +88 -11
  69. package/build/first-build-advisory.js +97 -0
  70. package/build/generated-assets-advisory.js +14 -0
  71. package/build/generated-assets-catalog.js +66 -0
  72. package/build/generated-assets-context.js +46 -0
  73. package/build/generated-assets-dependencies.js +26 -0
  74. package/build/generated-assets-doctor.js +50 -0
  75. package/build/generated-assets-health.js +65 -0
  76. package/build/generated-assets-host.js +17 -0
  77. package/build/generated-assets-inventory.js +17 -0
  78. package/build/generated-assets-paths.js +60 -0
  79. package/build/generated-assets-publisher.js +133 -0
  80. package/build/generated-assets-remedy.js +25 -0
  81. package/build/generated-assets-repair.js +72 -0
  82. package/build/generated-assets-session.js +71 -0
  83. package/build/generated-assets-test-support.js +35 -0
  84. package/build/index.js +964 -768
  85. package/build/init.js +54 -177
  86. package/build/install-bridge-conductor-gate.js +1372 -0
  87. package/build/install-bridge-conductor.js +289 -36
  88. package/build/install-bridge.js +544 -37
  89. package/build/mcp-install-state.js +50 -4
  90. package/build/merge-workflow-install.js +3 -4
  91. package/build/pipeline-utils.js +6 -2
  92. package/build/pipelines.generated.js +22 -142
  93. package/build/plan-execution-ledger.js +2 -32
  94. package/build/plane/manifest.js +16 -1
  95. package/build/plane/preflight.js +54 -1
  96. package/build/printed-remedy-commands.js +16 -1
  97. package/build/readme.generated.js +1 -1
  98. package/build/repair-assets.js +93 -0
  99. package/build/run-unit-tests-launcher.js +130 -49
  100. package/build/secret-redaction.js +33 -0
  101. package/build/setup-epic.js +108 -3
  102. package/build/start-tickets-prereqs.js +70 -16
  103. package/build/start-tickets.js +29 -5
  104. package/build/tool-surface-gating.js +36 -0
  105. package/build/upgrade-cli.js +55 -1
  106. package/build/version.generated.js +3 -3
  107. package/build/worker-guard-hook-bin.js +3 -2
  108. package/build/worker-guard-hook-main.js +1 -1
  109. package/build/worktree-core.js +15 -2
  110. package/docs/EPIC-IMPLEMENTER-INSTALL.md +30 -12
  111. package/docs/EPIC-IMPLEMENTER.md +68 -2
  112. package/package.json +3 -3
  113. package/pipelines/greenfield-setup.json +4 -128
  114. package/pipelines/implement-ticket.json +3 -2
@@ -17,6 +17,7 @@ import path from "path";
17
17
  import { resolveBapiCredentials, getPrimaryCredentialStorePath, } from "./credential-store.js";
18
18
  import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
19
19
  import { probeWorktreeMcpRegistration } from "./mcp-registration-doctor.js";
20
+ import { VERSION } from "./version.generated.js";
20
21
  import { probeWorktreeCommandAssets } from "./command-assets-doctor.js";
21
22
  import { MCP_SERVER_NAME, MCP_PACKAGE_NAME } from "./mcp-identity.js";
22
23
  // BAPI-941: both are LEAF modules (the pure classifier and its git-backed
@@ -159,7 +160,7 @@ const WORKTRUNK_INSTALL_HINTS = {
159
160
  linux: "See the Worktrunk documentation for Linux install instructions: https://worktrunk.dev",
160
161
  };
161
162
  const GIT_INSTALL_HINTS = {
162
- darwin: "xcode-select --install (or brew install git)",
163
+ darwin: "xcode-select --install (or brew install git). Installing another Git does not ensure PATH selects it; verify with which git and git --version.",
163
164
  linux: "Install git with your distro package manager, e.g. apt install git",
164
165
  win32: "Install Git for Windows: https://git-scm.com/download/win",
165
166
  };
@@ -444,24 +445,19 @@ export function worktreeMcpReachabilityDescriptor() {
444
445
  },
445
446
  };
446
447
  }
447
- /**
448
- * Secret-free remediation hint shared by the worktree command-assets descriptor
449
- * (BAPI-664). Directs users to rerun start-tickets — the supported provisioning
450
- * workflow — rather than manually copying or committing the generated command
451
- * files, which are fill-only, never refreshed, and Git-excluded per worktree.
452
- */
453
- const WORKTREE_COMMAND_ASSETS_HINT = "Re-run start-tickets to provision the packaged Claude command assets " +
454
- "(.claude/commands/) into this worktree. Do not manually copy or commit the " +
455
- "generated command files — they are bootstrapped per worktree and Git-excluded.";
448
+ /** Version-pinned fill-only repair; permissions and unsafe paths need their own fix. */
449
+ const WORKTREE_COMMAND_ASSETS_HINT = `Run npx -y ${MCP_PACKAGE_NAME}@${VERSION} repair-assets --project-root <established-root> --host claude ` +
450
+ "to fill missing packaged files. Resolve permission/path/platform problems separately; refresh/reconnect and run doctor.";
451
+ const WORKTREE_COMMAND_ASSETS_PROVISION_HINT = "In-place asset repair is not supported on this platform. Re-run start-tickets to provision the packaged Claude command assets (.claude/commands/) into this worktree. Existing files are preserved; refresh/reconnect and run doctor.";
456
452
  const WORKTREE_COMMAND_ASSETS_INSTALL_HINTS = {
457
- darwin: WORKTREE_COMMAND_ASSETS_HINT,
453
+ darwin: WORKTREE_COMMAND_ASSETS_PROVISION_HINT,
458
454
  linux: WORKTREE_COMMAND_ASSETS_HINT,
459
- win32: WORKTREE_COMMAND_ASSETS_HINT,
455
+ win32: WORKTREE_COMMAND_ASSETS_PROVISION_HINT,
460
456
  };
461
457
  /**
462
458
  * Doctor-only, strictly read-only probe: does the current worktree contain every
463
459
  * packaged Claude slash-command asset under `.claude/commands/`? Delegates to the
464
- * read-only {@link probeWorktreeCommandAssets} using only `deps.readFile`,
460
+ * read-only {@link probeWorktreeCommandAssets} using only `deps.commandAssetMetadata`,
465
461
  * `deps.platform`, and `deps.cwd`. Reports an unavailable diagnostic (never a
466
462
  * false "present") when the optional read-only filesystem boundary is absent. The
467
463
  * probe performs NO writes, Git calls, or process spawns.
@@ -472,15 +468,14 @@ export function worktreeCommandAssetsDescriptor() {
472
468
  label: "Worktree Claude command assets",
473
469
  installHint: WORKTREE_COMMAND_ASSETS_INSTALL_HINTS,
474
470
  probe: async (deps) => {
475
- const { readFile } = deps;
476
- if (!readFile) {
471
+ if (!deps.commandAssetMetadata) {
477
472
  return {
478
473
  found: false,
479
474
  detail: "command-asset probe unavailable (no read-only filesystem access)",
480
475
  };
481
476
  }
482
477
  const result = await probeWorktreeCommandAssets(deps.cwd, {
483
- readFile,
478
+ metadata: deps.commandAssetMetadata,
484
479
  platform: deps.platform,
485
480
  });
486
481
  return { found: result.found, detail: result.detail };
@@ -817,6 +812,7 @@ export function getDoctorOnlyPrereqDescriptors(_platform, _env, agent) {
817
812
  return [
818
813
  uvDescriptor(),
819
814
  agentDescriptor(agent),
815
+ gitCompatibilityDescriptor(),
820
816
  credentialResolutionDescriptor(),
821
817
  worktreeMcpReachabilityDescriptor(),
822
818
  worktreeCommandAssetsDescriptor(),
@@ -863,6 +859,7 @@ export async function probePrerequisite(deps, descriptor) {
863
859
  label: descriptor.label,
864
860
  found: outcome.found,
865
861
  detail: outcome.detail,
862
+ gitCompatibility: outcome.gitCompatibility,
866
863
  installHint: hintForPlatform(descriptor.installHint, deps.platform),
867
864
  authNote: descriptor.authNote,
868
865
  // Deliberately NOT carried through when the probe THREW: the catch above
@@ -872,6 +869,63 @@ export async function probePrerequisite(deps, descriptor) {
872
869
  renderDetailWhenMissing: descriptor.renderDetailWhenMissing === true && !probeThrew ? true : undefined,
873
870
  };
874
871
  }
872
+ /** Release-specific advisory baseline; never a launcher admission requirement.
873
+ * Evidence: https://github.com/max-sixty/worktrunk/blob/v0.80.0/src/git/version.rs
874
+ */
875
+ export const WORKTRUNK_GIT_BASELINE = "Worktrunk v0.80.0 (Git >= 2.43.0)";
876
+ /** Read-only, advisory compatibility with the documented release, not every release. */
877
+ export async function probeGitWorktrunkCompatibility(deps) {
878
+ const basis = WORKTRUNK_GIT_BASELINE;
879
+ let executable;
880
+ let version;
881
+ const unknown = (reason) => ({ status: "unknown", executable, version, basis, reason });
882
+ try {
883
+ const locator = getCommandProbe("git", deps.platform);
884
+ const located = await deps.runCommand(locator.file, locator.args, { timeoutMs: 3000 });
885
+ if (located.unavailable)
886
+ return unknown("locator-unavailable");
887
+ if (!commandSucceeded(located))
888
+ return unknown(located.exitCode === 1 ? "git-missing" : "locator-unavailable");
889
+ const candidate = located.stdout.trim().split(/\r?\n/)[0];
890
+ // Only absolute paths from a successful locator are diagnostic metadata.
891
+ if (!candidate || candidate.length > 512 || /[\x00-\x1f\x7f]/.test(candidate) ||
892
+ !(deps.platform === "win32" ? path.win32.isAbsolute(candidate) : path.posix.isAbsolute(candidate))) {
893
+ return unknown("locator-unavailable");
894
+ }
895
+ executable = candidate;
896
+ const result = await deps.runCommand(executable, ["--version"], { timeoutMs: 3000 });
897
+ if (!commandSucceeded(result))
898
+ return unknown("version-unavailable");
899
+ const match = /^git version (\d{1,5})\.(\d{1,5})(?:\.(\d{1,5}))?(?:\.windows\.\d+| \(Apple Git-[\w.-]+\))?$/.exec(result.stdout.trim());
900
+ if (!match)
901
+ return unknown("version-unparseable");
902
+ const parts = [Number(match[1]), Number(match[2]), Number(match[3] ?? 0)];
903
+ version = parts.join(".");
904
+ if (deps.env[WORKTRUNK_BINARY_OVERRIDE_ENV]?.trim())
905
+ return unknown("override-unconfirmed");
906
+ const minimum = [2, 43, 0];
907
+ const differing = parts.findIndex((n, i) => n !== minimum[i]);
908
+ const compatible = differing < 0 || parts[differing] > minimum[differing];
909
+ return { status: compatible ? "compatible" : "incompatible", executable, version, basis };
910
+ }
911
+ catch {
912
+ return unknown(executable ? "version-unavailable" : "locator-unavailable");
913
+ }
914
+ }
915
+ /** Pure formatter shared by doctor and first-build output. No raw probe output. */
916
+ export function formatGitCompatibilityDiagnostic(result) {
917
+ const metadata = [result.executable, result.version && `Git ${result.version}`].filter(Boolean).join("; ");
918
+ return `Git compatibility advisory: ${result.status}${metadata ? ` — ${metadata}` : ""}; comparison: ${result.basis}` +
919
+ (result.reason ? `; ${result.reason}` : "") +
920
+ (result.status === "incompatible" ? ". Verify the Git executable/version selected on PATH and install a supported Git manually." : "") +
921
+ (result.reason === "override-unconfirmed" ? ". Custom Worktrunk release requirement is unconfirmed." : "");
922
+ }
923
+ export function gitCompatibilityDescriptor() {
924
+ return {
925
+ id: "git-compatibility", label: "Git compatibility advisory", installHint: GIT_INSTALL_HINTS,
926
+ probe: async (deps) => ({ found: true, gitCompatibility: await probeGitWorktrunkCompatibility(deps) }),
927
+ };
928
+ }
875
929
  /**
876
930
  * Enforce the preflight descriptors fail-fast (matching the existing
877
931
  * `runPreflight` behaviour). Iterates the shared preflight descriptors in order
@@ -1,3 +1,4 @@
1
+ import { recordProvisionedAssets } from "./generated-assets-inventory.js";
1
2
  /**
2
3
  * start-tickets — packaged CLI subcommand of the bridge-api-mcp-server bin.
3
4
  *
@@ -836,6 +837,7 @@ export function createDefaultStartTicketsDeps() {
836
837
  encoding: "utf-8",
837
838
  // Optional bounded timeout (e.g. cursor-agent --list-models probes).
838
839
  timeout: options?.timeoutMs,
840
+ signal: options?.signal,
839
841
  }, (error, stdout, stderr) => {
840
842
  const exitCode = error && typeof error.code === "number"
841
843
  ? (error.code)
@@ -846,6 +848,7 @@ export function createDefaultStartTicketsDeps() {
846
848
  stdout: stdout ?? "",
847
849
  stderr: stderr ?? "",
848
850
  exitCode,
851
+ ...(error && typeof error.code === "string" ? { unavailable: true } : {}),
849
852
  });
850
853
  });
851
854
  });
@@ -1643,7 +1646,7 @@ export async function spawnMacOSTerminalTab(deps, terminal, shellCommand, contex
1643
1646
  : buildTerminalAppleScript(shellCommand, title);
1644
1647
  const result = await deps.runCommand("osascript", ["-e", script]);
1645
1648
  if (commandSucceeded(result))
1646
- return { ok: true };
1649
+ return { ok: true, destination: terminal === "iterm" ? "iTerm tab" : "macOS Terminal tab" };
1647
1650
  const reason = (result.stderr || result.stdout || "").trim();
1648
1651
  return {
1649
1652
  ok: false,
@@ -1731,7 +1734,7 @@ export async function spawnWindowsTerminalTab(deps, _terminal, shellCommand, con
1731
1734
  const args = buildWindowsTerminalArgs(worktreePath, shellCommand, title);
1732
1735
  const result = await deps.runCommand(WINDOWS_TERMINAL_COMMAND, args);
1733
1736
  if (commandSucceeded(result))
1734
- return { ok: true };
1737
+ return { ok: true, destination: "Windows Terminal tab" };
1735
1738
  const reason = combineCommandOutput(result);
1736
1739
  return {
1737
1740
  ok: false,
@@ -1754,7 +1757,7 @@ export async function spawnWindowsTerminalTab(deps, _terminal, shellCommand, con
1754
1757
  fallback,
1755
1758
  ]);
1756
1759
  if (commandSucceeded(result))
1757
- return { ok: true };
1760
+ return { ok: true, destination: "PowerShell fallback window" };
1758
1761
  const reason = combineCommandOutput(result);
1759
1762
  return {
1760
1763
  ok: false,
@@ -1858,7 +1861,7 @@ export async function spawnLinuxTmuxTerminalTab(deps, _terminal, shellCommand, c
1858
1861
  : buildTmuxNewSessionArgs(session, window, worktreePath, paneCommand);
1859
1862
  const result = await deps.runCommand(TMUX_COMMAND, args);
1860
1863
  if (commandSucceeded(result))
1861
- return { ok: true };
1864
+ return { ok: true, destination: "detached tmux session" };
1862
1865
  const reason = combineCommandOutput(result);
1863
1866
  return {
1864
1867
  ok: false,
@@ -2118,7 +2121,7 @@ export async function spawnTabsForCreatedWorktrees(deps, rows, terminal, buildSh
2118
2121
  worktreePath: row.path,
2119
2122
  });
2120
2123
  if (result.ok) {
2121
- out.push({ ...row, status: "spawned" });
2124
+ out.push({ ...row, status: "spawned", ...(result.destination ? { sessionDestination: result.destination } : {}) });
2122
2125
  }
2123
2126
  else {
2124
2127
  out.push({ ...row, status: "spawn-failed", error: result.error });
@@ -2220,6 +2223,23 @@ export function buildDryRunDetailLines(agent, key, branch, platform = process.pl
2220
2223
  * ` path=PATH` when available) followed by warning lines for any create/spawn
2221
2224
  * failures.
2222
2225
  */
2226
+ /** Explain only actual successful sessions; summary serialization stays stable. */
2227
+ export function formatSpawnedSessionExplanation(rows, context) {
2228
+ const spawned = rows.filter((row) => row.status === "spawned");
2229
+ if (!spawned.length)
2230
+ return "";
2231
+ const lines = ["Spawned sessions:"];
2232
+ for (const row of spawned) {
2233
+ lines.push(` ${row.key}: ${context.agent} runs in ${row.path ?? "a worktree whose path was not reported"} on branch ${row.branch}; ${row.sessionDestination ?? "terminal destination not reported"}.`);
2234
+ }
2235
+ lines.push("Work on each branch from its reported folder: it is already checked out there and normally cannot also be checked out in the original folder.");
2236
+ lines.push("Monitor the reported terminal session and its agent output; use the attach commands below on Linux. Session startup is not review or implementation completion.");
2237
+ if (context.workflow === "review-and-implement")
2238
+ lines.push("Each session reviews independently, then opens a fresh implementation session only after its review halt gate permits it.");
2239
+ if (spawned.length > 1)
2240
+ lines.push("Coordinate ownership across these independent sessions; pause affected work if overlapping changes or conflicts appear.");
2241
+ return lines.join("\n");
2242
+ }
2223
2243
  export function formatSummaryReport(rows) {
2224
2244
  const lines = ["Summary:"];
2225
2245
  // Surface the single conductor run id near the top (first line stays
@@ -2323,6 +2343,7 @@ export function buildCommandProvisioningDeps(deps) {
2323
2343
  runCommand: deps.runCommand,
2324
2344
  platform: deps.platform,
2325
2345
  cwd: deps.cwd,
2346
+ ...(!deps.readFile && !deps.writeFile && !deps.mkdir ? { recordAssets: recordProvisionedAssets } : {}),
2326
2347
  };
2327
2348
  }
2328
2349
  /**
@@ -4213,6 +4234,9 @@ export async function runStartTicketsCli(argv, overrides = {}) {
4213
4234
  return 1;
4214
4235
  }
4215
4236
  log(formatSummaryReport(result.rows));
4237
+ const explanation = formatSpawnedSessionExplanation(result.rows, { agent: options.agentName, workflow: options.workflow ?? "implement" });
4238
+ if (explanation)
4239
+ log(explanation);
4216
4240
  // Linux: each spawned ticket runs in a detached tmux session — tell the user
4217
4241
  // how to attach.
4218
4242
  const spawned = result.rows.filter((r) => r.status === "spawned");
@@ -449,6 +449,42 @@ export function createUpdateAdvisoryListHandler(inner, getAdvisory, onListServed
449
449
  }
450
450
  };
451
451
  }
452
+ /** Compose once over undecorated SDK results, preserving both metadata keys.
453
+ * Weak keys prevent retained results from accumulating session-wide state. */
454
+ const undecoratedResults = new WeakMap();
455
+ export function composeListAdvisories(original, update, recovery) {
456
+ const base = undecoratedResults.get(original) ?? original;
457
+ const updated = decorateListResultWithUpdateAdvisory(base, update);
458
+ if (!recovery)
459
+ return updated;
460
+ try {
461
+ const anchor = base.tools.find(t => t.name === "ping") ?? base.tools[0];
462
+ const result = { ...updated,
463
+ tools: updated.tools.map(tool => tool.name === anchor?.name
464
+ ? { ...tool, description: `${tool.name === "ping" && update ? `${update}\n\n` : ""}${recovery.text}\n\n${anchor?.description ?? ""}`.trimEnd() } : tool),
465
+ _meta: { ...(updated._meta ?? {}), "bridge-api/asset-recovery": { host: recovery.host,
466
+ missingCount: recovery.missingCount, paths: recovery.paths, capabilities: recovery.capabilities, command: recovery.command } },
467
+ };
468
+ undecoratedResults.set(result, base);
469
+ return result;
470
+ }
471
+ catch {
472
+ return updated;
473
+ }
474
+ }
475
+ export function createComposedAdvisoryListHandler(inner, getUpdate, getRecovery, onListServed) {
476
+ return async (request, extra) => {
477
+ const original = await inner(request, extra);
478
+ try {
479
+ const recovery = await getRecovery();
480
+ onListServed?.();
481
+ return composeListAdvisories(original, getUpdate(), recovery);
482
+ }
483
+ catch {
484
+ return decorateListResultWithUpdateAdvisory(original, getUpdate());
485
+ }
486
+ };
487
+ }
452
488
  /** Fixed, sanitized boot error (never includes map contents or config). */
453
489
  const COMPAT_ERROR = "tool-surface gating: incompatible MCP SDK — the tools/list handler could not be resolved for override.";
454
490
  /**
@@ -32,7 +32,7 @@
32
32
  * The command never reports a transition it did not confirm on disk.
33
33
  */
34
34
  import { spawn } from "child_process";
35
- import { readFile, stat } from "fs/promises";
35
+ import { mkdir, readFile, stat, writeFile } from "fs/promises";
36
36
  import path from "path";
37
37
  import { VERSION } from "./version.generated.js";
38
38
  import { runInit } from "./init.js";
@@ -42,9 +42,28 @@ import { fetchLatestVersion } from "./cli-release.js";
42
42
  import { CANONICAL_UPGRADE_COMMAND } from "./upgrade-advice.js";
43
43
  import { describeLauncherReason, inspectLauncherConfigs, isApplicableStandardLauncher, isPrerequisiteFailure, LAUNCHER_CONFIG_TARGETS, } from "./launcher-config-inspection.js";
44
44
  import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
45
+ import { applyProjectPermissions, BRIDGE_PERMISSIONS_MANIFEST_RELPATH, BRIDGE_PROJECT_SETTINGS_RELPATH, } from "./bridge-permissions.js";
46
+ import { ensureGitignored } from "./git-ignore-utils.js";
45
47
  import { defaultNpmEntryFileExists, resolveNpmEntry } from "./windows-npm-entry.js";
46
48
  /** The sentinel this CLI passes to the child it re-execs from `@latest`. */
47
49
  const INTERNAL_REEXEC_FLAG = "--internal-reexec";
50
+ /** Production binding of {@link UpgradeCliDeps.applyBridgePermissions}. */
51
+ export async function defaultApplyBridgePermissions(cwd, env) {
52
+ const fsDeps = {
53
+ readFile: (filePath) => readFile(filePath, "utf-8"),
54
+ writeFile: (filePath, data) => writeFile(filePath, data, "utf-8"),
55
+ mkdir: (dirPath, options) => mkdir(dirPath, options),
56
+ };
57
+ for (const relPath of [BRIDGE_PERMISSIONS_MANIFEST_RELPATH, BRIDGE_PROJECT_SETTINGS_RELPATH]) {
58
+ try {
59
+ await ensureGitignored(cwd, relPath, fsDeps);
60
+ }
61
+ catch {
62
+ // Advisory: the files are still written, and doctor reads them either way.
63
+ }
64
+ }
65
+ return applyProjectPermissions({ cwd, env }, fsDeps);
66
+ }
48
67
  /**
49
68
  * The fixed, secret-free message printed when, on Windows, the latest CLI could
50
69
  * not be STARTED. Upgrade stops here rather than continuing with the local copy.
@@ -186,6 +205,7 @@ export function createDefaultUpgradeCliDeps(cwd) {
186
205
  stdout: (m) => console.log(m),
187
206
  stderr: (m) => console.error(m),
188
207
  env: process.env,
208
+ applyBridgePermissions: defaultApplyBridgePermissions,
189
209
  };
190
210
  }
191
211
  // ---------------------------------------------------------------------------
@@ -495,6 +515,17 @@ export async function runUpgradeCli(argv, injected) {
495
515
  stderr("\nBridge API upgrade finished with an UNCONFIGURED MCP entry (see above).");
496
516
  return 1;
497
517
  }
518
+ // ---- Phase 8 (BAPI-1398): the project's /drive-epic-run permission rules ----
519
+ // Only after every gate above passed. Advisory: a malformed settings file or a
520
+ // refused launcher is reported and never changes the exit code.
521
+ if (deps.applyBridgePermissions) {
522
+ try {
523
+ reportUpgradePermissions(await deps.applyBridgePermissions(deps.cwd, deps.env ?? {}), stdout, stderr);
524
+ }
525
+ catch {
526
+ stderr("Warning: could not write Bridge's permission rules (non-fatal).");
527
+ }
528
+ }
498
529
  const applicableStandard = verified.filter(isApplicableStandardLauncher);
499
530
  if (isEveryLauncherAlreadyCurrent(initResult.configActions)) {
500
531
  // The exact string `/upgrade-bridge` is contracted to relay verbatim.
@@ -514,6 +545,29 @@ export async function runUpgradeCli(argv, injected) {
514
545
  }
515
546
  return 0;
516
547
  }
548
+ /** Render the project permission result in upgrade's own report style. */
549
+ export function reportUpgradePermissions(result, stdout, stderr) {
550
+ if (!result.launcher.ok) {
551
+ stderr(`Warning: Bridge permission rules were not written: ${result.launcher.reason}.`);
552
+ return;
553
+ }
554
+ const scope = result.project;
555
+ if (scope.status === "written" || scope.status === "unchanged") {
556
+ stdout(`Bridge permission rules (${scope.path}, launcher ${result.launcher.prefix}):`);
557
+ if (scope.inserted.length === 0 && scope.removedStale.length === 0) {
558
+ stdout(` all ${scope.alreadyPresent.length} rules already present.`);
559
+ }
560
+ for (const rule of scope.inserted)
561
+ stdout(` wrote ${rule}`);
562
+ for (const rule of scope.removedStale)
563
+ stdout(` removed stale ${rule}`);
564
+ return;
565
+ }
566
+ if (scope.status === "malformed" || scope.status === "io-error") {
567
+ stderr(`Warning: ${scope.path} ${scope.status === "malformed" ? "is not valid JSON settings and was left unchanged" : "could not be written"}; ` +
568
+ `${scope.skipped.length} Bridge permission rule(s) were not written.`);
569
+ }
570
+ }
517
571
  /**
518
572
  * Project pre-mutation inspections onto the action shape `isEveryLauncherAlreadyCurrent`
519
573
  * consumes, so the dry run answers the currentness question exactly the way the
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.2.67";
3
- export const BUILD_COMMIT = "8eb6ba563e5f";
4
- export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.67", "serve"];
2
+ export const VERSION = "0.2.68";
3
+ export const BUILD_COMMIT = "2aef1c5656bc";
4
+ export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.68", "serve"];
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync}from"node:fs";import path from"node:path";var WORKER_GUARD_REASON_PREFIX="bridge worker guard",WORKER_GUARD_MALFORMED_LINE=`${WORKER_GUARD_REASON_PREFIX}: payload could not be read; continuing fail-open`;function renderWorkerGuardReason(category,family){return category==="git-ambiguous"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). This shell form cannot be safely resolved: substitution, redirection, or stdin-fed arguments hide what git will run. Use literal arguments instead; for a commit message, write it to a file and run git commit -F <file>.`:category==="workflow-entry"||category==="workflow-launch-skill"||category==="workflow-launch-shell"||category==="workflow-planner-tool"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). A correction worker must not start a full workflow. Reading or editing files that mention a workflow is allowed; make the focused correction directly.`:category==="correction-context-invalid"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). The correction context for this worker is not valid, so no tool call can be checked. Stop and report the launch problem.`:category==="correction-payload-invalid"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). The tool call could not be read as a tool name with a tool input, so it cannot be checked. Retry with a well-formed call.`:`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). This command family is restricted to the worker's own branch, worktree, and read-only network access. Use a different approach and continue.`}var UNSAFE_UNQUOTED=new Set(["$","`",">","<"]),SEPARATORS=new Set([";","|","&"]);function tokenizeShellCommand(command){let commands=[],argv=[],current="",hasCurrent=!1,unsafe=!1,endToken=()=>{hasCurrent&&(argv.push(current),current="",hasCurrent=!1)},endCommand=()=>{endToken(),(argv.length>0||unsafe)&&commands.push({argv,unsafe}),argv=[],unsafe=!1};for(let i=0;i<command.length;i+=1){let ch=command[i];if(ch==="\\"){let next=command[i+1];next!==void 0&&(current+=next,hasCurrent=!0,i+=1);continue}if(ch==="'"){let close=command.indexOf("'",i+1);if(close===-1){unsafe=!0,current+=command.slice(i+1),hasCurrent=!0;break}current+=command.slice(i+1,close),hasCurrent=!0,i=close;continue}if(ch==='"'){let j=i+1,body="",closed=!1;for(;j<command.length;){let c=command[j];if(c==="\\"&&j+1<command.length){body+=command[j+1],j+=2;continue}if(c==='"'){closed=!0;break}(c==="$"||c==="`")&&(unsafe=!0),body+=c,j+=1}closed||(unsafe=!0),current+=body,hasCurrent=!0,i=j;continue}if(UNSAFE_UNQUOTED.has(ch)){unsafe=!0,current+=ch,hasCurrent=!0;continue}if(SEPARATORS.has(ch)){endCommand();continue}if(ch===`
3
- `){endCommand();continue}if(ch===" "||ch===" "||ch==="\r"){endToken();continue}current+=ch,hasCurrent=!0}return endCommand(),commands}var WORKER_GUARD_MAX_PAYLOAD_BYTES=1e6;function asRecord(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)?value:void 0}function isInside(root,candidate){let rel=path.relative(root,candidate);return rel===""?!0:rel.startsWith("..")?!1:!path.isAbsolute(rel)}function resolveEffectiveCwd(ctx,payloadCwd){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root))return;let normalizedRoot=path.resolve(root);if(payloadCwd===void 0)return normalizedRoot;if(typeof payloadCwd!="string"||payloadCwd.length===0)return;let resolved=path.isAbsolute(payloadCwd)?path.resolve(payloadCwd):path.resolve(normalizedRoot,payloadCwd);return isInside(normalizedRoot,resolved)?resolved:void 0}function allTargetsContained(ctx,cwd,targets){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root)||cwd===void 0)return!1;let normalizedRoot=path.resolve(root);return targets.every(target=>{if(target.length===0||target==="~"||target.startsWith("~/"))return!1;let resolved=path.isAbsolute(target)?path.resolve(target):path.resolve(cwd,target);return isInside(normalizedRoot,resolved)})}function parseGitInvocation(argv,baseCwd){let cwd=baseCwd,unsafe=!1,i=1;for(;i<argv.length;){let token=argv[i];if(token==="-C"){let dir=argv[i+1];dir===void 0||cwd===void 0?(unsafe=!0,cwd=void 0):cwd=path.isAbsolute(dir)?path.resolve(dir):path.resolve(cwd,dir),i+=2;continue}if(token==="-c"||token==="--namespace"||token==="--git-dir"||token==="--work-tree"){unsafe=!0,i+=2;continue}if(token.startsWith("-")){i+=1;continue}break}return{subcommand:argv[i]??"",args:argv.slice(i+1),cwd,unsafe}}function isOwnBranchDestination(destination,workerBranch){let colon=destination.indexOf(":"),dst=colon===-1?destination:destination.slice(colon+1);if(dst.length===0)return!1;let bare=dst.startsWith("+")?dst.slice(1):dst;return bare===workerBranch||bare===`refs/heads/${workerBranch}`}function evaluateGitPush(args,ctx){let deny={kind:"deny",category:"git-push",family:"git"},positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}if(token.startsWith("-")){if(token==="--tags"||token==="--delete"||token==="-d"||token==="--mirror"||token==="--all"||token==="--follow-tags"||token.startsWith("--repo"))return deny;(token==="-o"||token==="--push-option"||token==="--receive-pack"||token==="--exec")&&(i+=1);continue}positional.push(token)}let workerBranch=ctx.workerBranch;return typeof workerBranch!="string"||workerBranch.length===0?deny:positional.length<=1?{kind:"allow"}:positional.slice(1).every(d=>isOwnBranchDestination(d,workerBranch))?{kind:"allow"}:deny}function evaluateGitReset(args,ctx){let deny={kind:"deny",category:"git-reset-hard",family:"git"};if(!args.includes("--hard"))return{kind:"allow"};let positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}token.startsWith("-")||positional.push(token)}if(positional.length===0)return{kind:"allow"};let workerBranch=ctx.workerBranch;if(typeof workerBranch!="string"||workerBranch.length===0)return deny;let allowed=new Set([workerBranch,`origin/${workerBranch}`,`refs/heads/${workerBranch}`,`refs/remotes/origin/${workerBranch}`]);return positional.every(ref=>allowed.has(ref))?{kind:"allow"}:deny}function evaluateGitClean(args,ctx,cwd){let deny={kind:"deny",category:"filesystem-clean",family:"git"};if(cwd===void 0||!allTargetsContained(ctx,cwd,[cwd]))return deny;let pathspecs=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){pathspecs.push(...args.slice(i+1));break}if(token==="-e"||token==="--exclude"){i+=1;continue}token.startsWith("-")||pathspecs.push(token)}return pathspecs.length===0?{kind:"allow"}:allTargetsContained(ctx,cwd,pathspecs)?{kind:"allow"}:deny}function isRecursiveForcedRemove(args){let recursive=!1,force=!1;for(let token of args){if(token==="--")break;if(token==="--recursive")recursive=!0;else if(token==="--force")force=!0;else{if(token.startsWith("--"))continue;if(token.startsWith("-")&&token.length>1)for(let flag of token.slice(1))(flag==="r"||flag==="R")&&(recursive=!0),flag==="f"&&(force=!0)}}return recursive&&force}function evaluateRemove(argv,ctx,cwd){let deny={kind:"deny",category:"filesystem-remove",family:"rm"},args=argv.slice(1);if(!isRecursiveForcedRemove(args))return{kind:"allow"};let targets=[],sawDoubleDash=!1;for(let token of args){if(!sawDoubleDash&&token==="--"){sawDoubleDash=!0;continue}!sawDoubleDash&&token.startsWith("-")&&token.length>1||targets.push(token)}return targets.length===0?deny:allTargetsContained(ctx,cwd,targets)?{kind:"allow"}:deny}function isDatabaseCommand(executable){let base=path.basename(executable);return base==="psql"||base.startsWith("pg_")}var DESTRUCTIVE_SQL=/\b(?:DROP|TRUNCATE)\b/i;function evaluateDatabase(argv){let deny={kind:"deny",category:"database-destructive",family:"psql"};return argv.slice(1).some(arg=>DESTRUCTIVE_SQL.test(arg))?deny:{kind:"allow"}}var MUTATING_METHODS=new Set(["POST","PUT","PATCH","DELETE"]);function evaluateNetwork(argv){let deny={kind:"deny",category:"network-mutating",family:"curl"},base=path.basename(argv[0]??""),args=argv.slice(1);for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="-X"||token==="--request"||token==="--method"){let value=args[i+1];if(value===void 0||MUTATING_METHODS.has(value.toUpperCase()))return deny;i+=1;continue}if(token.startsWith("-X")&&token.length>2){if(MUTATING_METHODS.has(token.slice(2).toUpperCase()))return deny;continue}if(token.startsWith("--request=")||token.startsWith("--method=")){let value=token.slice(token.indexOf("=")+1);if(MUTATING_METHODS.has(value.toUpperCase()))return deny;continue}if(token==="--data"||token.startsWith("--data=")||token.startsWith("--data-")||token==="--form"||token==="-F"||token==="--upload-file"||token==="-T"||token==="--post-data"||token.startsWith("--post-data=")||token==="--post-file"||token.startsWith("--post-file=")||token==="--body-data"||token.startsWith("--body-data=")||token==="--body-file"||token.startsWith("--body-file=")||base==="curl"&&token==="-d")return deny}return{kind:"allow"}}function classify(executable){let base=path.basename(executable);return base==="git"?"git":base==="rm"?"rm":isDatabaseCommand(base)?"database":base==="curl"||base==="wget"?"network":null}function unsafeGitCategory(argv){let{subcommand}=parseGitInvocation(argv,void 0);return subcommand==="push"?"git-push":subcommand==="reset"?"git-reset-hard":subcommand==="clean"?"filesystem-clean":"git-ambiguous"}function unsafeSegmentDecision(family,argv){switch(family){case"git":return{kind:"deny",category:unsafeGitCategory(argv),family:"git"};case"rm":return{kind:"deny",category:"filesystem-remove",family:"rm"};case"database":return{kind:"deny",category:"database-destructive",family:"psql"};case"network":return{kind:"deny",category:"network-mutating",family:"curl"}}}var TRANSPARENT_PREFIXES=new Set(["sudo","env","command","nohup","time","nice","exec"]),STDIN_ARGUMENT_PREFIXES=new Set(["xargs"]),SHELL_EXECUTABLES=new Set(["sh","bash","zsh","dash","ksh"]),EVAL_BUILTINS=new Set(["eval"]);function unwrapTransparentPrefixes(argv){let rest=argv;for(let depth=0;depth<8;depth+=1){let head=rest[0];if(head===void 0||!TRANSPARENT_PREFIXES.has(path.basename(head)))return rest;let i=1;for(;i<rest.length;){let token=rest[i];if(path.basename(head)==="env"&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)){i+=1;continue}if(token.startsWith("-")){i+=1;continue}break}rest=rest.slice(i)}return rest}function stripStdinPrefixOptions(argv){let VALUE_OPTS=new Set(["-I","-i","-L","-n","-P","-s","-d","-E","-e","-a","--replace","--max-lines","--max-args","--max-procs","--max-chars","--delimiter","--eof","--arg-file"]),i=1;for(;i<argv.length;){let token=argv[i];if(token==="--"){i+=1;break}if(!token.startsWith("-"))break;if(token.includes("=")){i+=1;continue}i+=VALUE_OPTS.has(token)?2:1}return argv.slice(i)}var WORKFLOW_NAMES="implement-ticket|plan-ticket|idea-to-pr|review-and-implement",WORKFLOW_SLASH_LAUNCH=new RegExp(`(?:^|\\s)/(?:bridge-)?(?:${WORKFLOW_NAMES})(?=\\s|$)`,"i"),WORKFLOW_NAME_EXACT=new RegExp(`^/?(?:[A-Za-z0-9_-]+:)?(?:bridge-)?(?:${WORKFLOW_NAMES})$`,"i"),CLAUDE_PACKAGE_TOKEN=/^(?:@anthropic-ai\/)?claude(?:-code)?(?:@\S+)?$/;function claudeLaunchArgs(argv){let base=path.basename(argv[0]??"");if(base==="claude")return argv.slice(1);if(base==="npx"){let index=argv.findIndex((token,i)=>i>0&&CLAUDE_PACKAGE_TOKEN.test(token));return index===-1?void 0:argv.slice(index+1)}}function isClaudeWorkflowPrintLaunch(segment){let args=claudeLaunchArgs(segment.argv);return args===void 0||!args.some(token=>token==="-p"||token==="--print"||token.startsWith("--print="))?!1:segment.unsafe?!0:args.some(token=>{let value=token.startsWith("--print=")?token.slice(8):token;return WORKFLOW_SLASH_LAUNCH.test(value)})}function evaluateSimpleCommand(segment,ctx,payloadCwd,depth=0,stdinFed=!1){let argv=unwrapTransparentPrefixes(segment.argv),executable=argv[0];if(executable===void 0)return{kind:"allow"};if(SHELL_EXECUTABLES.has(path.basename(executable))&&depth<4){let cIndex=argv.findIndex(t=>t==="-c"),inner=cIndex===-1?void 0:argv[cIndex+1];if(inner!==void 0){for(let nested of tokenizeShellCommand(inner)){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed);if(decision.kind==="deny")return decision}return{kind:"allow"}}}if(EVAL_BUILTINS.has(path.basename(executable))&&depth<4){for(let nested of tokenizeShellCommand(argv.slice(1).join(" "))){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed);if(decision.kind==="deny")return decision}return{kind:"allow"}}if(STDIN_ARGUMENT_PREFIXES.has(path.basename(executable))&&depth<4){let rest=stripStdinPrefixOptions(argv);return rest.length===0?{kind:"allow"}:evaluateSimpleCommand({argv:rest,unsafe:segment.unsafe},ctx,payloadCwd,depth+1,!0)}if(segment={argv,unsafe:segment.unsafe},ctx.correctionEnforced&&isClaudeWorkflowPrintLaunch(segment))return{kind:"deny",category:"workflow-launch-shell",family:"workflow"};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family,segment.argv);let cwd=resolveEffectiveCwd(ctx,payloadCwd);if(family==="rm")return evaluateRemove(segment.argv,ctx,cwd);if(family==="database")return evaluateDatabase(segment.argv);if(family==="network")return evaluateNetwork(segment.argv);let git=parseGitInvocation(segment.argv,cwd);return git.subcommand==="push"?git.unsafe?{kind:"deny",category:"git-push",family:"git"}:evaluateGitPush(git.args,ctx):git.subcommand==="reset"?git.unsafe?{kind:"deny",category:"git-reset-hard",family:"git"}:evaluateGitReset(git.args,ctx):git.subcommand==="clean"?git.unsafe?{kind:"deny",category:"filesystem-clean",family:"git"}:evaluateGitClean(git.args,ctx,git.cwd):{kind:"allow"}}function workflowDeny(category){return{kind:"deny",category,family:"workflow"}}var FILE_TOOLS=new Set(["Read","Edit","Write","MultiEdit","NotebookEdit","Grep","Glob"]),INVOCATION_FIELDS={Skill:["skill","args"],Task:["prompt","description"],Agent:["prompt","description"],SlashCommand:["command"]},PLANNER_TOOL_NAME=/(?:request_plan_generation|get_pipeline_recipe|plan_ticket|implement_ticket)/i;function evaluateCorrectionToolRole(toolName,input){if(FILE_TOOLS.has(toolName))return{kind:"allow"};if(toolName==="Bash")return;let fields=INVOCATION_FIELDS[toolName];if(fields!==void 0){for(let field of fields){let value=input[field];if(value===void 0)continue;if(typeof value!="string")return workflowDeny("workflow-launch-skill");let nameField=toolName==="Skill"?field==="skill":toolName==="SlashCommand";if(WORKFLOW_SLASH_LAUNCH.test(value)||nameField&&WORKFLOW_NAME_EXACT.test(value.trim().split(/\s+/)[0]??""))return workflowDeny("workflow-launch-skill")}return{kind:"allow"}}if(/^mcp__/i.test(toolName)&&PLANNER_TOOL_NAME.test(toolName))return workflowDeny("workflow-planner-tool");let args=Object.values(input).filter(v=>typeof v=="string").join(" ");return WORKFLOW_SLASH_LAUNCH.test(args)?workflowDeny("workflow-entry"):void 0}function evaluateWorkerGuardPayload(payload,ctx){if(ctx.correctionContextInvalid)return workflowDeny("correction-context-invalid");let root=asRecord(payload);if(root===void 0)return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let input=asRecord(root.tool_input??root.toolInput);if(ctx.correctionEnforced){if(input===void 0)return workflowDeny("correction-payload-invalid");let decision=evaluateCorrectionToolRole(toolName,input);if(decision!==void 0)return decision}if(toolName!=="Bash")return{kind:"allow"};if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let rawCwd=root.cwd??input.cwd,payloadCwd=typeof rawCwd=="string"?rawCwd:void 0;for(let segment of tokenizeShellCommand(command)){let decision=evaluateSimpleCommand(segment,ctx,payloadCwd);if(decision.kind==="deny")return decision}return{kind:"allow"}}function evaluateWorkerGuardInput(raw,ctx){let unreadable=()=>ctx.correctionContextInvalid?workflowDeny("correction-context-invalid"):ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};if(typeof raw!="string"||raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES||raw.trim().length===0)return unreadable();let parsed;try{parsed=JSON.parse(raw)}catch{return unreadable()}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return unreadable()}}var WORKER_GUARD_ROOT_FLAG="--worktree-root";function resolveWorkerGuardContext(env,cwd,argv=[]){let ctx={},root;for(let i=0;i<argv.length;i+=1){if(argv[i]===WORKER_GUARD_ROOT_FLAG){root=argv[i+1];break}if(argv[i].startsWith(`${WORKER_GUARD_ROOT_FLAG}=`)){root=argv[i].slice(WORKER_GUARD_ROOT_FLAG.length+1);break}}root===void 0&&typeof cwd=="string"&&(root=cwd),typeof root=="string"&&path.isAbsolute(root)&&(ctx.worktreePath=path.resolve(root));let branch=env.BAPI_WORKER_BRANCH;typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim());let flagIndex=argv.indexOf("--correction-intent-version"),flagValue=flagIndex>=0?argv[flagIndex+1]:void 0,envValue=env.BAPI_CORRECTION_INTENT_VERSION;return ctx.correctionEnforced=flagValue==="1"||envValue==="1",ctx.correctionContextInvalid=flagIndex>=0&&flagValue!=="1"||envValue!==void 0&&envValue!=="1"||flagIndex>=0&&envValue!==void 0&&envValue!==flagValue,ctx}function runWorkerGuardHook(io){let exitCode=0,correctionRequested=io.argv.includes("--correction-intent-version")||io.env.BAPI_CORRECTION_INTENT_VERSION!==void 0;try{let raw=io.readStdin(),ctx=resolveWorkerGuardContext(io.env,io.cwd,io.argv),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(io.writeStderr(renderWorkerGuardReason(decision.category,decision.family)),exitCode=2):decision.kind==="malformed"&&(io.writeStderr(WORKER_GUARD_MALFORMED_LINE),exitCode=0)}catch{try{io.writeStderr(correctionRequested?renderWorkerGuardReason("correction-payload-invalid","workflow"):WORKER_GUARD_MALFORMED_LINE)}catch{}exitCode=correctionRequested?2:0}return exitCode}function readStdinSync(){return readFileSync(0,"utf-8")}process.exit(runWorkerGuardHook({readStdin:readStdinSync,env:process.env,cwd:process.cwd(),argv:process.argv.slice(2),writeStderr:text=>{process.stderr.write(`${text}
2
+ import{readFileSync}from"node:fs";import path from"node:path";var WORKER_GUARD_REASON_PREFIX="bridge worker guard",WORKER_GUARD_MALFORMED_LINE=`${WORKER_GUARD_REASON_PREFIX}: payload could not be read; continuing fail-open`;function renderWorkerGuardReason(category,family,form){if(category==="git-ambiguous")return`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). This shell form cannot be safely resolved: substitution, redirection, or stdin-fed arguments hide what git will run. Use literal arguments instead; for a commit message, write it to a file and run git commit -F <file>.`;if(form==="shell_form"){let pattern=category==="git-push"?"If the destination is your own branch and no force is used, run git push origin HEAD:<your work branch> alone.":category==="git-reset-hard"?"If resetting your own branch, run git reset --hard origin/<your work branch> alone.":category==="filesystem-clean"?"If all pathspecs are inside your worktree, run git clean with literal worktree-contained pathspecs alone.":category==="filesystem-remove"?"If all paths are inside your worktree, run rm with literal worktree-contained paths alone.":"If arguments are read-only, run literal read-only arguments alone, without redirects or $VAR.";return`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). Shell form cannot be checked: redirects, pipes, substitution, or stdin-fed arguments. `+pattern}let restriction={"git-push":"Push only to your own branch, without force. Use HEAD:<your work branch> as the destination.","git-reset-hard":"Hard reset only against your own branch ref.","filesystem-clean":"Clean only literal paths inside your worktree.","filesystem-remove":"Remove only literal paths inside your worktree.","database-destructive":"Destructive SQL is prohibited. Use read-only database arguments.","network-mutating":"Mutating requests are prohibited. Use read-only network arguments."};return restriction[category]?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). ${restriction[category]}`:category==="workflow-entry"||category==="workflow-launch-skill"||category==="workflow-launch-shell"||category==="workflow-planner-tool"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). A correction worker must not start a full workflow. Reading or editing files that mention a workflow is allowed; make the focused correction directly.`:category==="correction-context-invalid"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). The correction context for this worker is not valid, so no tool call can be checked. Stop and report the launch problem.`:category==="correction-payload-invalid"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). The tool call could not be read as a tool name with a tool input, so it cannot be checked. Retry with a well-formed call.`:`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). This command family is restricted to the worker's own branch, worktree, and read-only network access. Use a different approach and continue.`}var UNSAFE_UNQUOTED=new Set(["$","`",">","<"]),SEPARATORS=new Set([";","|","&"]);function tokenizeShellCommand(command){let commands=[],argv=[],current="",hasCurrent=!1,unsafe=!1,endToken=()=>{hasCurrent&&(argv.push(current),current="",hasCurrent=!1)},endCommand=()=>{endToken(),(argv.length>0||unsafe)&&commands.push({argv,unsafe}),argv=[],unsafe=!1};for(let i=0;i<command.length;i+=1){let ch=command[i];if(ch==="\\"){let next=command[i+1];next!==void 0&&(current+=next,hasCurrent=!0,i+=1);continue}if(ch==="'"){let close=command.indexOf("'",i+1);if(close===-1){unsafe=!0,current+=command.slice(i+1),hasCurrent=!0;break}current+=command.slice(i+1,close),hasCurrent=!0,i=close;continue}if(ch==='"'){let j=i+1,body="",closed=!1;for(;j<command.length;){let c=command[j];if(c==="\\"&&j+1<command.length){body+=command[j+1],j+=2;continue}if(c==='"'){closed=!0;break}(c==="$"||c==="`")&&(unsafe=!0),body+=c,j+=1}closed||(unsafe=!0),current+=body,hasCurrent=!0,i=j;continue}if(UNSAFE_UNQUOTED.has(ch)){unsafe=!0,current+=ch,hasCurrent=!0;continue}if(SEPARATORS.has(ch)){endCommand();continue}if(ch===`
3
+ `){endCommand();continue}if(ch===" "||ch===" "||ch==="\r"){endToken();continue}current+=ch,hasCurrent=!0}return endCommand(),commands}function diagnosticProjections(command){if(command.length>WORKER_GUARD_MAX_PAYLOAD_BYTES)return[];let result=[],start=0,masked="",quote="",uncertain=!1,finish=end=>{let original=tokenizeShellCommand(command.slice(start,end)),projected=tokenizeShellCommand(masked),value=!uncertain&&projected.length===1?{argv:projected[0].argv,complete:!projected[0].unsafe}:void 0;result.push(...original.map((_,i)=>i===0?value:void 0)),masked="",uncertain=!1,start=end+1};for(let i=0;i<command.length;i+=1){let ch=command[i];if(ch==="\\"&&quote!=="'"){masked+=command.slice(i,i+2),i+=1;continue}if(quote){masked+=ch,ch===quote&&(quote="");continue}if(ch==="'"||ch==='"'){quote=ch,masked+=ch;continue}if(ch===">"||ch==="<"){masked=masked.replace(/(^|\s)\d+$/,"$1");let j=i+1;for(command[j]===ch&&(j+=1),command[j]==="&"&&(j+=1);/[ \t]/.test(command[j]??"")&&j<command.length;)j+=1;let operandStart=j,operandQuote="";for(;j<command.length;){let c=command[j];if(c==="\\"&&operandQuote!=="'"){j+=2;continue}if(operandQuote){c===operandQuote&&(operandQuote=""),j+=1;continue}if(c==="'"||c==='"'){operandQuote=c,j+=1;continue}if(/[\s;|&<>]/.test(c))break;j+=1}(j===operandStart||operandQuote||command.slice(i,j).startsWith("<<"))&&(uncertain=!0),masked+=" ",i=j-1;continue}if(SEPARATORS.has(ch)||ch===`
4
+ `){finish(i);continue}masked+=ch}return quote&&(uncertain=!0),finish(command.length),result}var WORKER_GUARD_MAX_PAYLOAD_BYTES=1e6;function asRecord(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)?value:void 0}function isInside(root,candidate){let rel=path.relative(root,candidate);return rel===""?!0:rel.startsWith("..")?!1:!path.isAbsolute(rel)}function resolveEffectiveCwd(ctx,payloadCwd){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root))return;let normalizedRoot=path.resolve(root);if(payloadCwd===void 0)return normalizedRoot;if(typeof payloadCwd!="string"||payloadCwd.length===0)return;let resolved=path.isAbsolute(payloadCwd)?path.resolve(payloadCwd):path.resolve(normalizedRoot,payloadCwd);return isInside(normalizedRoot,resolved)?resolved:void 0}function allTargetsContained(ctx,cwd,targets){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root)||cwd===void 0)return!1;let normalizedRoot=path.resolve(root);return targets.every(target=>{if(target.length===0||target==="~"||target.startsWith("~/"))return!1;let resolved=path.isAbsolute(target)?path.resolve(target):path.resolve(cwd,target);return isInside(normalizedRoot,resolved)})}function parseGitInvocation(argv,baseCwd){let cwd=baseCwd,unsafe=!1,i=1;for(;i<argv.length;){let token=argv[i];if(token==="-C"){let dir=argv[i+1];dir===void 0||cwd===void 0?(unsafe=!0,cwd=void 0):cwd=path.isAbsolute(dir)?path.resolve(dir):path.resolve(cwd,dir),i+=2;continue}if(token==="-c"||token==="--namespace"||token==="--git-dir"||token==="--work-tree"){unsafe=!0,i+=2;continue}if(token.startsWith("-")){i+=1;continue}break}return{subcommand:argv[i]??"",args:argv.slice(i+1),cwd,unsafe}}function isOwnBranchDestination(destination,workerBranch){let colon=destination.indexOf(":"),dst=colon===-1?destination:destination.slice(colon+1);if(dst.length===0)return!1;let bare=dst.startsWith("+")?dst.slice(1):dst;return bare===workerBranch||bare===`refs/heads/${workerBranch}`}function evaluateGitPush(args,ctx){let deny={kind:"deny",category:"git-push",family:"git"},positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}if(token.startsWith("-")){if(token==="--tags"||token==="--delete"||token==="-d"||token==="--mirror"||token==="--all"||token==="--follow-tags"||token.startsWith("--repo"))return deny;(token==="-o"||token==="--push-option"||token==="--receive-pack"||token==="--exec")&&(i+=1);continue}positional.push(token)}let workerBranch=ctx.workerBranch;return typeof workerBranch!="string"||workerBranch.length===0?deny:positional.length<=1?{kind:"allow"}:positional.slice(1).every(d=>isOwnBranchDestination(d,workerBranch))?{kind:"allow"}:deny}function evaluateGitReset(args,ctx){let deny={kind:"deny",category:"git-reset-hard",family:"git"};if(!args.includes("--hard"))return{kind:"allow"};let positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}token.startsWith("-")||positional.push(token)}if(positional.length===0)return{kind:"allow"};let workerBranch=ctx.workerBranch;if(typeof workerBranch!="string"||workerBranch.length===0)return deny;let allowed=new Set([workerBranch,`origin/${workerBranch}`,`refs/heads/${workerBranch}`,`refs/remotes/origin/${workerBranch}`]);return positional.every(ref=>allowed.has(ref))?{kind:"allow"}:deny}function evaluateGitClean(args,ctx,cwd){let deny={kind:"deny",category:"filesystem-clean",family:"git"};if(cwd===void 0||!allTargetsContained(ctx,cwd,[cwd]))return deny;let pathspecs=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){pathspecs.push(...args.slice(i+1));break}if(token==="-e"||token==="--exclude"){i+=1;continue}token.startsWith("-")||pathspecs.push(token)}return pathspecs.length===0?{kind:"allow"}:allTargetsContained(ctx,cwd,pathspecs)?{kind:"allow"}:deny}function isRecursiveForcedRemove(args){let recursive=!1,force=!1;for(let token of args){if(token==="--")break;if(token==="--recursive")recursive=!0;else if(token==="--force")force=!0;else{if(token.startsWith("--"))continue;if(token.startsWith("-")&&token.length>1)for(let flag of token.slice(1))(flag==="r"||flag==="R")&&(recursive=!0),flag==="f"&&(force=!0)}}return recursive&&force}function evaluateRemove(argv,ctx,cwd){let deny={kind:"deny",category:"filesystem-remove",family:"rm"},args=argv.slice(1);if(!isRecursiveForcedRemove(args))return{kind:"allow"};let targets=[],sawDoubleDash=!1;for(let token of args){if(!sawDoubleDash&&token==="--"){sawDoubleDash=!0;continue}!sawDoubleDash&&token.startsWith("-")&&token.length>1||targets.push(token)}return targets.length===0?deny:allTargetsContained(ctx,cwd,targets)?{kind:"allow"}:deny}function isDatabaseCommand(executable){let base=path.basename(executable);return base==="psql"||base.startsWith("pg_")}var DESTRUCTIVE_SQL=/\b(?:DROP|TRUNCATE)\b/i;function evaluateDatabase(argv){let deny={kind:"deny",category:"database-destructive",family:"psql"};return argv.slice(1).some(arg=>DESTRUCTIVE_SQL.test(arg))?deny:{kind:"allow"}}var MUTATING_METHODS=new Set(["POST","PUT","PATCH","DELETE"]);function evaluateNetwork(argv){let deny={kind:"deny",category:"network-mutating",family:"curl"},base=path.basename(argv[0]??""),args=argv.slice(1);for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="-X"||token==="--request"||token==="--method"){let value=args[i+1];if(value===void 0||MUTATING_METHODS.has(value.toUpperCase()))return deny;i+=1;continue}if(token.startsWith("-X")&&token.length>2){if(MUTATING_METHODS.has(token.slice(2).toUpperCase()))return deny;continue}if(token.startsWith("--request=")||token.startsWith("--method=")){let value=token.slice(token.indexOf("=")+1);if(MUTATING_METHODS.has(value.toUpperCase()))return deny;continue}if(token==="--data"||token.startsWith("--data=")||token.startsWith("--data-")||token==="--form"||token==="-F"||token==="--upload-file"||token==="-T"||token==="--post-data"||token.startsWith("--post-data=")||token==="--post-file"||token.startsWith("--post-file=")||token==="--body-data"||token.startsWith("--body-data=")||token==="--body-file"||token.startsWith("--body-file=")||base==="curl"&&token==="-d")return deny}return{kind:"allow"}}function classify(executable){let base=path.basename(executable);return base==="git"?"git":base==="rm"?"rm":isDatabaseCommand(base)?"database":base==="curl"||base==="wget"?"network":null}function unsafeGitCategory(argv){let{subcommand}=parseGitInvocation(argv,void 0);return subcommand==="push"?"git-push":subcommand==="reset"?"git-reset-hard":subcommand==="clean"?"filesystem-clean":"git-ambiguous"}function diagnosticPushForce(args){return args.some(t=>t==="--force"||t==="-f"||t==="--force-with-lease"||t.startsWith("--force-with-lease=")||t.startsWith("+"))}function diagnosticViolation(family,diagnostic,ctx,cwd){let full=unwrapTransparentPrefixes(diagnostic.argv),unknown=diagnostic.complete?-1:full.findIndex(t=>/[$`]/.test(t)),argv=unknown<0?full:full.slice(0,unknown);if(family==="database")return evaluateDatabase(argv).kind==="deny";if(family==="network")return evaluateNetwork(argv).kind==="deny";let git=parseGitInvocation(argv,cwd);return family==="git"&&git.subcommand==="push"&&diagnosticPushForce(git.args)?!0:diagnostic.complete?family==="rm"?evaluateRemove(argv,ctx,cwd).kind==="deny":git.unsafe?!0:git.subcommand==="push"?evaluateGitPush(git.args,ctx).kind==="deny":git.subcommand==="reset"?evaluateGitReset(git.args,ctx).kind==="deny":git.subcommand==="clean"?evaluateGitClean(git.args,ctx,git.cwd).kind==="deny":!1:family==="rm"?argv.slice(1).some(t=>!t.startsWith("-"))&&evaluateRemove(argv,ctx,cwd).kind==="deny":git.subcommand==="push"?git.args.filter(t=>!t.startsWith("-")).length>=2&&evaluateGitPush(git.args,ctx).kind==="deny":git.subcommand==="reset"?git.args.some(t=>!t.startsWith("-"))&&evaluateGitReset(git.args,ctx).kind==="deny":git.subcommand==="clean"?evaluateGitClean(git.args,ctx,git.cwd).kind==="deny":!1}function unsafeSegmentDecision(family,argv,ctx,cwd,diagnostic){let category=family==="git"?unsafeGitCategory(argv):family==="rm"?"filesystem-remove":family==="database"?"database-destructive":"network-mutating",decision={kind:"deny",category,family:family==="database"?"psql":family==="network"?"curl":family};return diagnostic&&category!=="git-ambiguous"&&!diagnosticViolation(family,diagnostic,ctx,cwd)?{...decision,form:"shell_form"}:decision}var TRANSPARENT_PREFIXES=new Set(["sudo","env","command","nohup","time","nice","exec"]),STDIN_ARGUMENT_PREFIXES=new Set(["xargs"]),SHELL_EXECUTABLES=new Set(["sh","bash","zsh","dash","ksh"]),EVAL_BUILTINS=new Set(["eval"]);function unwrapTransparentPrefixes(argv){let rest=argv;for(let depth=0;depth<8;depth+=1){let head=rest[0];if(head===void 0||!TRANSPARENT_PREFIXES.has(path.basename(head)))return rest;let i=1;for(;i<rest.length;){let token=rest[i];if(path.basename(head)==="env"&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)){i+=1;continue}if(token.startsWith("-")){i+=1;continue}break}rest=rest.slice(i)}return rest}function stripStdinPrefixOptions(argv){let VALUE_OPTS=new Set(["-I","-i","-L","-n","-P","-s","-d","-E","-e","-a","--replace","--max-lines","--max-args","--max-procs","--max-chars","--delimiter","--eof","--arg-file"]),i=1;for(;i<argv.length;){let token=argv[i];if(token==="--"){i+=1;break}if(!token.startsWith("-"))break;if(token.includes("=")){i+=1;continue}i+=VALUE_OPTS.has(token)?2:1}return argv.slice(i)}var WORKFLOW_NAMES="implement-ticket|plan-ticket|idea-to-pr|review-and-implement",WORKFLOW_SLASH_LAUNCH=new RegExp(`(?:^|\\s)/(?:bridge-)?(?:${WORKFLOW_NAMES})(?=\\s|$)`,"i"),WORKFLOW_NAME_EXACT=new RegExp(`^/?(?:[A-Za-z0-9_-]+:)?(?:bridge-)?(?:${WORKFLOW_NAMES})$`,"i"),CLAUDE_PACKAGE_TOKEN=/^(?:@anthropic-ai\/)?claude(?:-code)?(?:@\S+)?$/;function claudeLaunchArgs(argv){let base=path.basename(argv[0]??"");if(base==="claude")return argv.slice(1);if(base==="npx"){let index=argv.findIndex((token,i)=>i>0&&CLAUDE_PACKAGE_TOKEN.test(token));return index===-1?void 0:argv.slice(index+1)}}function isClaudeWorkflowPrintLaunch(segment){let args=claudeLaunchArgs(segment.argv);return args===void 0||!args.some(token=>token==="-p"||token==="--print"||token.startsWith("--print="))?!1:segment.unsafe?!0:args.some(token=>{let value=token.startsWith("--print=")?token.slice(8):token;return WORKFLOW_SLASH_LAUNCH.test(value)})}function evaluateSimpleCommand(segment,ctx,payloadCwd,depth=0,stdinFed=!1,diagnostic){let argv=unwrapTransparentPrefixes(segment.argv),executable=argv[0];if(executable===void 0)return{kind:"allow"};if(SHELL_EXECUTABLES.has(path.basename(executable))&&depth<4){let cIndex=argv.findIndex(t=>t==="-c"),inner=cIndex===-1?void 0:argv[cIndex+1];if(inner!==void 0){let projections=diagnostic?.complete?diagnosticProjections(inner):[];for(let[i,nested]of tokenizeShellCommand(inner).entries()){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed,projections[i]);if(decision.kind==="deny")return decision}return{kind:"allow"}}}if(EVAL_BUILTINS.has(path.basename(executable))&&depth<4){let inner=argv.slice(1).join(" "),projections=diagnostic?.complete?diagnosticProjections(inner):[];for(let[i,nested]of tokenizeShellCommand(inner).entries()){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed,projections[i]);if(decision.kind==="deny")return decision}return{kind:"allow"}}if(STDIN_ARGUMENT_PREFIXES.has(path.basename(executable))&&depth<4){let rest=stripStdinPrefixOptions(argv);return rest.length===0?{kind:"allow"}:evaluateSimpleCommand({argv:rest,unsafe:segment.unsafe},ctx,payloadCwd,depth+1,!0,diagnostic?{argv:stripStdinPrefixOptions(unwrapTransparentPrefixes(diagnostic.argv)),complete:!1}:void 0)}if(segment={argv,unsafe:segment.unsafe},ctx.correctionEnforced&&isClaudeWorkflowPrintLaunch(segment))return{kind:"deny",category:"workflow-launch-shell",family:"workflow"};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family,segment.argv,ctx,resolveEffectiveCwd(ctx,payloadCwd),diagnostic);let cwd=resolveEffectiveCwd(ctx,payloadCwd);if(family==="rm")return evaluateRemove(segment.argv,ctx,cwd);if(family==="database")return evaluateDatabase(segment.argv);if(family==="network")return evaluateNetwork(segment.argv);let git=parseGitInvocation(segment.argv,cwd);return git.subcommand==="push"?git.unsafe?unsafeSegmentDecision("git",segment.argv,ctx,cwd,diagnostic):evaluateGitPush(git.args,ctx):git.subcommand==="reset"?git.unsafe?unsafeSegmentDecision("git",segment.argv,ctx,cwd,diagnostic):evaluateGitReset(git.args,ctx):git.subcommand==="clean"?git.unsafe?unsafeSegmentDecision("git",segment.argv,ctx,cwd,diagnostic):evaluateGitClean(git.args,ctx,git.cwd):{kind:"allow"}}function workflowDeny(category){return{kind:"deny",category,family:"workflow"}}var FILE_TOOLS=new Set(["Read","Edit","Write","MultiEdit","NotebookEdit","Grep","Glob"]),INVOCATION_FIELDS={Skill:["skill","args"],Task:["prompt","description"],Agent:["prompt","description"],SlashCommand:["command"]},PLANNER_TOOL_NAME=/(?:request_plan_generation|get_pipeline_recipe|plan_ticket|implement_ticket)/i;function evaluateCorrectionToolRole(toolName,input){if(FILE_TOOLS.has(toolName))return{kind:"allow"};if(toolName==="Bash")return;let fields=INVOCATION_FIELDS[toolName];if(fields!==void 0){for(let field of fields){let value=input[field];if(value===void 0)continue;if(typeof value!="string")return workflowDeny("workflow-launch-skill");let nameField=toolName==="Skill"?field==="skill":toolName==="SlashCommand";if(WORKFLOW_SLASH_LAUNCH.test(value)||nameField&&WORKFLOW_NAME_EXACT.test(value.trim().split(/\s+/)[0]??""))return workflowDeny("workflow-launch-skill")}return{kind:"allow"}}if(/^mcp__/i.test(toolName)&&PLANNER_TOOL_NAME.test(toolName))return workflowDeny("workflow-planner-tool");let args=Object.values(input).filter(v=>typeof v=="string").join(" ");return WORKFLOW_SLASH_LAUNCH.test(args)?workflowDeny("workflow-entry"):void 0}function evaluateWorkerGuardPayload(payload,ctx){if(ctx.correctionContextInvalid)return workflowDeny("correction-context-invalid");let root=asRecord(payload);if(root===void 0)return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let input=asRecord(root.tool_input??root.toolInput);if(ctx.correctionEnforced){if(input===void 0)return workflowDeny("correction-payload-invalid");let decision=evaluateCorrectionToolRole(toolName,input);if(decision!==void 0)return decision}if(toolName!=="Bash")return{kind:"allow"};if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let rawCwd=root.cwd??input.cwd,payloadCwd=typeof rawCwd=="string"?rawCwd:void 0,projections=diagnosticProjections(command);for(let[i,segment]of tokenizeShellCommand(command).entries()){let decision=evaluateSimpleCommand(segment,ctx,payloadCwd,0,!1,projections[i]);if(decision.kind==="deny")return decision}return{kind:"allow"}}function evaluateWorkerGuardInput(raw,ctx){let unreadable=()=>ctx.correctionContextInvalid?workflowDeny("correction-context-invalid"):ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};if(typeof raw!="string"||raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES||raw.trim().length===0)return unreadable();let parsed;try{parsed=JSON.parse(raw)}catch{return unreadable()}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return unreadable()}}var WORKER_GUARD_ROOT_FLAG="--worktree-root";function resolveWorkerGuardContext(env,cwd,argv=[]){let ctx={},root;for(let i=0;i<argv.length;i+=1){if(argv[i]===WORKER_GUARD_ROOT_FLAG){root=argv[i+1];break}if(argv[i].startsWith(`${WORKER_GUARD_ROOT_FLAG}=`)){root=argv[i].slice(WORKER_GUARD_ROOT_FLAG.length+1);break}}root===void 0&&typeof cwd=="string"&&(root=cwd),typeof root=="string"&&path.isAbsolute(root)&&(ctx.worktreePath=path.resolve(root));let branch=env.BAPI_WORKER_BRANCH;typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim());let flagIndex=argv.indexOf("--correction-intent-version"),flagValue=flagIndex>=0?argv[flagIndex+1]:void 0,envValue=env.BAPI_CORRECTION_INTENT_VERSION;return ctx.correctionEnforced=flagValue==="1"||envValue==="1",ctx.correctionContextInvalid=flagIndex>=0&&flagValue!=="1"||envValue!==void 0&&envValue!=="1"||flagIndex>=0&&envValue!==void 0&&envValue!==flagValue,ctx}function runWorkerGuardHook(io){let exitCode=0,correctionRequested=io.argv.includes("--correction-intent-version")||io.env.BAPI_CORRECTION_INTENT_VERSION!==void 0;try{let raw=io.readStdin(),ctx=resolveWorkerGuardContext(io.env,io.cwd,io.argv),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(io.writeStderr(renderWorkerGuardReason(decision.category,decision.family,decision.form)),exitCode=2):decision.kind==="malformed"&&(io.writeStderr(WORKER_GUARD_MALFORMED_LINE),exitCode=0)}catch{try{io.writeStderr(correctionRequested?renderWorkerGuardReason("correction-payload-invalid","workflow"):WORKER_GUARD_MALFORMED_LINE)}catch{}exitCode=correctionRequested?2:0}return exitCode}function readStdinSync(){return readFileSync(0,"utf-8")}process.exit(runWorkerGuardHook({readStdin:readStdinSync,env:process.env,cwd:process.cwd(),argv:process.argv.slice(2),writeStderr:text=>{process.stderr.write(`${text}
4
5
  `)}}));
@@ -20,7 +20,7 @@ export function runWorkerGuardHook(io) {
20
20
  const ctx = resolveWorkerGuardContext(io.env, io.cwd, io.argv);
21
21
  const decision = evaluateWorkerGuardInput(raw, ctx);
22
22
  if (decision.kind === "deny") {
23
- io.writeStderr(renderWorkerGuardReason(decision.category, decision.family));
23
+ io.writeStderr(renderWorkerGuardReason(decision.category, decision.family, decision.form));
24
24
  exitCode = 2;
25
25
  }
26
26
  else if (decision.kind === "malformed") {
@@ -420,6 +420,13 @@ export async function createWorktreeForTicket(deps, key, branchOverrides, worktr
420
420
  };
421
421
  }
422
422
  const worktreePath = extractWorktreePath(result.stdout, deps.cwd, deps.platform);
423
+ if (behavior.postSwitch) {
424
+ const error = await behavior.postSwitch(worktreePath, branch);
425
+ if (error)
426
+ return { key, branch, status: "create-failed", error };
427
+ }
428
+ const resetTarget = async (target) => behavior.preReset
429
+ ? behavior.preReset(worktreePath, branch, target) : { ok: true, target };
423
430
  // BAPI-1367: a lineage-approved resume stops here. Retained commits and
424
431
  // uncommitted files ARE the work, so none of the destructive steps below
425
432
  // (reset, clean, base verification) may run.
@@ -431,7 +438,10 @@ export async function createWorktreeForTicket(deps, key, branchOverrides, worktr
431
438
  // `origin/<branch>`), so this only applies when the local branch pre-existed.
432
439
  // Run inside the switched worktree (the branch is checked out there).
433
440
  if (exists && behavior.freshenFromOrigin) {
434
- const resetError = await hardResetWorktree(deps, worktreePath, behavior.freshenFromOrigin);
441
+ const selected = await resetTarget(behavior.freshenFromOrigin);
442
+ if (!selected.ok)
443
+ return { key, branch, status: "create-failed", error: selected.error };
444
+ const resetError = await hardResetWorktree(deps, worktreePath, selected.target);
435
445
  if (resetError) {
436
446
  return { key, branch, status: "create-failed", error: resetError };
437
447
  }
@@ -447,7 +457,10 @@ export async function createWorktreeForTicket(deps, key, branchOverrides, worktr
447
457
  // branches) before this destructive reset. Absent branches were created
448
458
  // straight from `baseStartPoint`, so no alignment is needed there.
449
459
  if (exists && behavior.alignExistingBranchTo) {
450
- const resetError = await hardResetWorktree(deps, worktreePath, behavior.alignExistingBranchTo);
460
+ const selected = await resetTarget(behavior.alignExistingBranchTo);
461
+ if (!selected.ok)
462
+ return { key, branch, status: "create-failed", error: selected.error };
463
+ const resetError = await hardResetWorktree(deps, worktreePath, selected.target);
451
464
  if (resetError) {
452
465
  return { key, branch, status: "create-failed", error: resetError };
453
466
  }