@bridge_gpt/mcp-server 0.2.54 → 0.2.55

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 (81) hide show
  1. package/README.md +38 -54
  2. package/build/agent-guidance.generated.js +3 -0
  3. package/build/agent-launchers/claude-executor-adapter.js +3 -0
  4. package/build/agent-notes.js +178 -0
  5. package/build/agent-registry.js +5 -2
  6. package/build/agent-utils.js +58 -0
  7. package/build/agents.generated.js +1 -1
  8. package/build/codex-skill-adapter.js +55 -0
  9. package/build/commands.generated.js +5 -4
  10. package/build/conductor/bridge-api-client.js +199 -6
  11. package/build/conductor/bring-up-facts.js +187 -0
  12. package/build/conductor/claude-hook.js +7 -5
  13. package/build/conductor/cli.js +28 -0
  14. package/build/conductor/doctor.js +80 -9
  15. package/build/conductor/epic-implementer-cli.js +1298 -0
  16. package/build/conductor/epic-runtime.js +1 -1
  17. package/build/conductor/errors.js +2 -2
  18. package/build/conductor/git-ci-types.js +1 -1
  19. package/build/conductor/git-hooks.js +28 -14
  20. package/build/conductor/install-doctor.js +11 -5
  21. package/build/conductor/readiness-cli.js +10 -10
  22. package/build/conductor/readiness-sections.js +58 -9
  23. package/build/conductor/readiness.js +37 -6
  24. package/build/conductor/recovery-cli.js +289 -10
  25. package/build/conductor/recovery-operations.js +125 -2
  26. package/build/conductor/repair-contract.js +58 -0
  27. package/build/conductor/store.js +2 -2
  28. package/build/conductor/supervisor-runtime.js +1 -1
  29. package/build/conductor-bin.js +2 -139
  30. package/build/conductor-claude-hook-bin.js +2 -2
  31. package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
  32. package/build/conductor-removed-stub-bin.js +30 -0
  33. package/build/docs.generated.js +1 -1
  34. package/build/doctor.js +77 -17
  35. package/build/drive-epic.js +541 -115
  36. package/build/epic-implementer-bin.js +145 -0
  37. package/build/epic-implementer-bundle-cli.js +264 -0
  38. package/build/epic-implementer-claude-hook-bin.js +3 -0
  39. package/build/epic-integration-pr.js +5 -3
  40. package/build/executor/env.js +6 -0
  41. package/build/executor/interrupted-worktree.js +60 -0
  42. package/build/executor/job-errors.js +45 -0
  43. package/build/executor/job-runner.js +274 -9
  44. package/build/executor/job-types.js +25 -9
  45. package/build/executor/merge-tree-classifier.js +171 -0
  46. package/build/executor/reconcile-overlap-governance.js +129 -0
  47. package/build/executor/reconcile-overlap-job.js +989 -0
  48. package/build/executor/reconcile-overlap-types.js +14 -0
  49. package/build/executor/spawn-job-driver.js +1 -0
  50. package/build/executor/types.js +2 -0
  51. package/build/executor/worker-finalization.js +25 -2
  52. package/build/executor/worker-guard-hook.js +15 -7
  53. package/build/implement-epic/bridge-client.js +773 -0
  54. package/build/implement-epic/checkpoint-store.js +542 -0
  55. package/build/implement-epic/cli.js +3158 -0
  56. package/build/implement-epic/cut-protocol.js +392 -0
  57. package/build/implement-epic/lock.js +302 -0
  58. package/build/implement-epic/pr-state.js +286 -0
  59. package/build/implement-epic/spawn.js +113 -0
  60. package/build/index.js +586 -138
  61. package/build/init.js +72 -8
  62. package/build/install-bridge-conductor.js +5 -5
  63. package/build/install-bridge.js +403 -70
  64. package/build/mcp-host-config.js +22 -60
  65. package/build/mcp-host-entry-adapter.js +18 -0
  66. package/build/mcp-host-targets.js +1 -21
  67. package/build/merge-pull-request.js +1 -1
  68. package/build/pipelines.generated.js +7 -7
  69. package/build/plan-epic-conductor-eligibility.js +1 -1
  70. package/build/plane/cli.js +36 -5
  71. package/build/plane/preflight.js +128 -12
  72. package/build/plane/shutdown.js +4 -4
  73. package/build/readiness-check.js +3 -3
  74. package/build/readme.generated.js +1 -1
  75. package/build/run-unit-tests-launcher.js +1 -1
  76. package/build/setup-epic.js +69 -31
  77. package/build/start-tickets-conductor.js +8 -7
  78. package/build/version.generated.js +3 -3
  79. package/build/worker-guard-hook-bin.js +1 -1
  80. package/docs/CONDUCTOR.md +8 -6
  81. package/package.json +5 -3
@@ -47,7 +47,7 @@
47
47
  * association is immutable server-side and can never be attached later.
48
48
  * c. Cut or validate `origin/<feature>` at EXACTLY the canonical indexed SHA
49
49
  * with the operator's LOCAL `git`, through the shared cut protocol
50
- * `conduct-epic init` also drives, with the run id so the scope is minted
50
+ * `implement-epic init` also drives, with the run id so the scope is minted
51
51
  * owned by the run. An existing branch at a different commit fails closed.
52
52
  * The GitHub App never pushes — it is `contents: read`.
53
53
  * d. Store and approve (unchanged requests). The server now READ-validates the
@@ -81,7 +81,7 @@ import { createProductionEpicIntegrationGhRunner, ensureEpicIntegrationPullReque
81
81
  import { approveEpicPlan, createEpicRunWithDisposition, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
82
82
  import { resolveTrackedPaths } from "./git-reachability.js";
83
83
  import { validateBranchName } from "./base-ref.js";
84
- // Reused from the conduct-epic client on purpose: adding a second wrapper under
84
+ // Reused from the implement-epic client on purpose: adding a second wrapper under
85
85
  // `conductor/` would give the v2 path its own copy to drift from.
86
86
  //
87
87
  // BAPI-850 extends that reuse to the canonical parse read, the repository base
@@ -89,9 +89,10 @@ import { validateBranchName } from "./base-ref.js";
89
89
  // typed clients the pilot already has are the v2 path's clients too, so neither
90
90
  // conductor can drift on a field name or an endpoint shape. No MCP tool, no
91
91
  // input-schema field, and no description changes.
92
- import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./conduct-epic/bridge-client.js";
93
- import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./conduct-epic/cut-protocol.js";
92
+ import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./implement-epic/bridge-client.js";
93
+ import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./implement-epic/cut-protocol.js";
94
94
  import { hashPlan } from "./conductor/plan.js";
95
+ import { BRING_UP_REFUSAL_MESSAGES, firstBringUpRefusal, } from "./conductor/bring-up-facts.js";
95
96
  import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
96
97
  // BAPI-872: local plane-manifest binding, so `plane down` can later stop the
97
98
  // run this setup resolved WITHOUT guessing from repository-wide active-run
@@ -176,7 +177,7 @@ export function getSetupEpicUsage() {
176
177
  "single-node plan) is unchanged and composes no posture at all.",
177
178
  "",
178
179
  "Unattended runs have TWO one-time repository prerequisites, refused by name",
179
- "before any run is created: repository consent (unattended_conductor_allowed) and",
180
+ "before any run is created: repository consent (unattended_epic_implementer_allowed) and",
180
181
  "a verified notify webhook default. `drive-epic` readiness lists both with their",
181
182
  "current state.",
182
183
  "",
@@ -1180,7 +1181,7 @@ export function evaluateSetupEpicUnattendedPrerequisites(readiness, requestedPol
1180
1181
  return {
1181
1182
  reason: "target_not_allowed",
1182
1183
  message: "this repository has not consented to unattended conductor runs. Set " +
1183
- "`unattended_conductor_allowed` for it (see " +
1184
+ "`unattended_epic_implementer_allowed` for it (see " +
1184
1185
  "docs/claude/account-settings-operator-runbook.md), or pass --attended to " +
1185
1186
  "create an attended run. No run was created.",
1186
1187
  };
@@ -1218,6 +1219,31 @@ export function evaluateSetupEpicUnattendedPrerequisites(readiness, requestedPol
1218
1219
  }
1219
1220
  return null;
1220
1221
  }
1222
+ /** The closing sentence every pre-create refusal ends with. */
1223
+ const NO_RUN_WAS_CREATED = "No run was created.";
1224
+ /**
1225
+ * Evaluate the bring-up facts against a readiness report.
1226
+ *
1227
+ * Pure and total: `null` is a readiness read that failed, which has proven
1228
+ * nothing and refuses as `readiness_unreadable`. Otherwise at most ONE refusal,
1229
+ * in the fixed {@link BRING_UP_REFUSAL_REASONS} order — each fact's
1230
+ * "unreadable" before its "bad", so an operator is never told to repair a
1231
+ * condition that could not be read.
1232
+ */
1233
+ export function evaluateSetupEpicBringUpPrerequisites(readiness) {
1234
+ if (readiness === null) {
1235
+ return {
1236
+ reason: "readiness_unreadable",
1237
+ message: "the Bridge API conductor readiness report could not be read, so the database " +
1238
+ "migration, worker presence, and single-reconciler facts could not be confirmed. " +
1239
+ `Retry once the server is reachable. ${NO_RUN_WAS_CREATED}`,
1240
+ };
1241
+ }
1242
+ const reason = firstBringUpRefusal(readiness.bring_up ?? null);
1243
+ if (reason === null)
1244
+ return null;
1245
+ return { reason, message: `${BRING_UP_REFUSAL_MESSAGES[reason]} ${NO_RUN_WAS_CREATED}` };
1246
+ }
1221
1247
  /**
1222
1248
  * Does the policy this invocation will actually send request unattended operation?
1223
1249
  *
@@ -1619,13 +1645,13 @@ function scopeFailureGuidance(reason, featureBranch, epicRunId) {
1619
1645
  if (reason === "canonical_index_advanced") {
1620
1646
  return (`${SETUP_EPIC_SCOPE_REDRIVE_RECOVERY} The canonical index advanced before the ` +
1621
1647
  `scope could be seeded: delete origin/${featureBranch}, abandon this run ` +
1622
- `(conductor abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
1648
+ `(epic-implementer abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
1623
1649
  }
1624
1650
  if (reason === "head_mismatch") {
1625
1651
  return (`${SETUP_EPIC_SCOPE_REDRIVE_RECOVERY} origin/${featureBranch} does not sit at the ` +
1626
1652
  `commit this scope was cut at, and setup-epic never force-updates an existing ` +
1627
1653
  `ref: delete origin/${featureBranch}, abandon this run ` +
1628
- `(conductor abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
1654
+ `(epic-implementer abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
1629
1655
  }
1630
1656
  return `Recovery: ${SETUP_EPIC_SCOPE_RECOVERY_COMMAND}.`;
1631
1657
  }
@@ -1924,7 +1950,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
1924
1950
  else if (err instanceof ConductorBridgeApiError && err.status === 409) {
1925
1951
  deps.errorLog(`Epic ${opts.epicKey} has MULTIPLE active runs — it is wedged, and every ` +
1926
1952
  `plan call will keep failing. Abandon the duplicate before retrying:\n` +
1927
- ` conductor abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
1953
+ ` epic-implementer abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
1928
1954
  `Detail: ${errorDetail(err)}`);
1929
1955
  return 1;
1930
1956
  }
@@ -1982,7 +2008,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
1982
2008
  `'${existingBaseBranch}', not the repository base '${repositoryBase}', and ` +
1983
2009
  `--into-base asks for the repository base branch. setup-epic will not retarget ` +
1984
2010
  `an existing run. Re-run without --into-base to reuse it unchanged, or abandon ` +
1985
- `the run (conductor abandon-run --epic-run-id ${existingRunId}) and recreate ` +
2011
+ `the run (epic-implementer abandon-run --epic-run-id ${existingRunId}) and recreate ` +
1986
2012
  `it on base.`);
1987
2013
  return 1;
1988
2014
  }
@@ -2112,7 +2138,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2112
2138
  `keep failing. This is TERMINAL: it will not clear on retry, and it is ` +
2113
2139
  `NOT a problem with your plan. Adopt the run you want to keep, or ` +
2114
2140
  `abandon the duplicate:\n` +
2115
- ` conductor abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
2141
+ ` epic-implementer abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
2116
2142
  `No run was created and no automation-start charge occurred.\n` +
2117
2143
  `Detail: ${errorDetail(err)}`);
2118
2144
  return 1;
@@ -2186,7 +2212,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2186
2212
  // used for status instead). A no-feature run performs none of this.
2187
2213
  //
2188
2214
  // The scope's BASE is the repository's configured base branch (the same
2189
- // resolution `conduct-epic init` uses), NOT `policy_json.base_branch` — that
2215
+ // resolution `implement-epic init` uses), NOT `policy_json.base_branch` — that
2190
2216
  // key names the FEATURE branch. A feature branch equal to the configured base
2191
2217
  // needs no scope (dispatch already cuts from it) and matches the server's
2192
2218
  // long-standing no-op for that case.
@@ -2371,9 +2397,9 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2371
2397
  // run that is already going.
2372
2398
  //
2373
2399
  // Evaluated against the COMPOSED policy, so it covers the default profile and
2374
- // a `--policy-file` alike; skipped entirely for `--attended` and for any
2375
- // policy that is not unattended, which is what keeps an attended run from
2376
- // paying for a readiness read it does not need.
2400
+ // a `--policy-file` alike; the UNATTENDED evaluation is skipped for
2401
+ // `--attended` and for any policy that is not unattended. (The readiness
2402
+ // read itself is no longer skipped see BAPI-1153 below.)
2377
2403
  //
2378
2404
  // The file branch runs through `applySetupEpicUnattendedDefault` rather than
2379
2405
  // taking the file verbatim: a file bypasses the no-file composer entirely, so
@@ -2381,18 +2407,30 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2381
2407
  // which made the default profile unreachable by any documented invocation.
2382
2408
  // The composed object is what gets gated AND what gets sent, so the create
2383
2409
  // request carries exactly what was validated.
2410
+ //
2411
+ // BAPI-1153: ONE readiness read now serves both gates, and it happens for
2412
+ // EVERY create — attended included — because the bring-up gate below applies
2413
+ // to every run. Before any create request, manifest binding, or lane.
2384
2414
  {
2415
+ let readiness = null;
2416
+ try {
2417
+ readiness = await fetchConductorReadiness(access, deps.fetch);
2418
+ }
2419
+ catch {
2420
+ // Fail CLOSED, and silently as to WHY: the thrown value can carry a URL
2421
+ // or a response excerpt, and this refusal is rendered to a terminal. The
2422
+ // generic `readiness_unreadable` message says what to do about it.
2423
+ readiness = null;
2424
+ }
2425
+ // Bring-up first: a behind database or a missing worker makes the
2426
+ // unattended questions moot, and refusing on the cheaper, universal fact
2427
+ // keeps the first-named blocker the same for attended and unattended runs.
2428
+ const bringUpRefusal = evaluateSetupEpicBringUpPrerequisites(readiness);
2429
+ if (bringUpRefusal !== null) {
2430
+ deps.errorLog(`setup-epic REFUSED (${bringUpRefusal.reason}): ${bringUpRefusal.message}`);
2431
+ return 1;
2432
+ }
2385
2433
  if (requestedPolicyIsUnattended(composedForCreate, opts.attended)) {
2386
- let readiness = null;
2387
- try {
2388
- readiness = await fetchConductorReadiness(access, deps.fetch);
2389
- }
2390
- catch {
2391
- // Fail CLOSED, and silently as to WHY: the thrown value can carry a URL
2392
- // or a response excerpt, and this refusal is rendered to a terminal. The
2393
- // generic `readiness_unreadable` message says what to do about it.
2394
- readiness = null;
2395
- }
2396
2434
  // The COMPOSED policy, not the flags: a --policy-file declaring
2397
2435
  // notify.webhook_url satisfies the escalation-channel prerequisite, and
2398
2436
  // it is the same object the create request carries.
@@ -2456,7 +2494,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2456
2494
  say(`Run: adopting existing active run ${run.epic_run_id} (status: ${run.status})`);
2457
2495
  say(` No second run was created — one epic has at most one active ` +
2458
2496
  `run. Continue on this one, or abandon it first with ` +
2459
- `\`conductor abandon-run --epic-run-id ${run.epic_run_id}\` to start over.`);
2497
+ `\`epic-implementer abandon-run --epic-run-id ${run.epic_run_id}\` to start over.`);
2460
2498
  if (requestedPolicy !== undefined) {
2461
2499
  const outcome = await applyReusedRunPolicy({
2462
2500
  access,
@@ -2522,7 +2560,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2522
2560
  else {
2523
2561
  const msg = `Could not bind the local plane to run ${result.epic_run_id} (${bound.message}). ` +
2524
2562
  "A later `plane down` cannot be guaranteed to stop it automatically — if you need " +
2525
- `to stop this run, run \`conductor stop-run --epic-run-id ${result.epic_run_id}\`.`;
2563
+ `to stop this run, run \`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`.`;
2526
2564
  warnings.push(msg);
2527
2565
  say(`Plane: [warn] ${msg}`);
2528
2566
  }
@@ -2542,7 +2580,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2542
2580
  const msg = `A local plane manifest exists but could not be validated (${manifestRead.error}), so ` +
2543
2581
  `run ${result.epic_run_id} was NOT bound to it. If a plane is running, \`plane down\` ` +
2544
2582
  "will not stop this run automatically — use " +
2545
- `\`conductor stop-run --epic-run-id ${result.epic_run_id}\`. A manifest written by a ` +
2583
+ `\`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`. A manifest written by a ` +
2546
2584
  "NEWER build must be wound down with that build's `plane down`.";
2547
2585
  warnings.push(msg);
2548
2586
  say(`Plane: [warn] ${msg}`);
@@ -2553,7 +2591,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2553
2591
  // an unexpected error here is reported as a warning, never thrown.
2554
2592
  const msg = `Could not check for a local plane to bind run ${result.epic_run_id} to (${errorDetail(err)}). ` +
2555
2593
  "A later `plane down` cannot be guaranteed to stop it automatically — if you need to stop " +
2556
- `this run, run \`conductor stop-run --epic-run-id ${result.epic_run_id}\`.`;
2594
+ `this run, run \`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`.`;
2557
2595
  warnings.push(msg);
2558
2596
  say(`Plane: [warn] ${msg}`);
2559
2597
  }
@@ -2562,7 +2600,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2562
2600
  // AFTER the run exists and BEFORE the plan is stored/approved: the scope's run
2563
2601
  // association is immutable server-side, so the cut is driven with this run's
2564
2602
  // id and the scope is minted owned by it. Everything from `cut/begin` to the
2565
- // hold's release is the SHARED protocol `conduct-epic init` drives — local git
2603
+ // hold's release is the SHARED protocol `implement-epic init` drives — local git
2566
2604
  // only, an exact-SHA create-only push, never the GitHub App, never a force
2567
2605
  // update. A failure leaves the run non-active (never discoverable by the
2568
2606
  // reconciler) and re-running setup-epic reuses it and re-drives the cut.
@@ -2683,7 +2721,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2683
2721
  deps.errorLog(`The plan was NOT approved: this repository is no longer authorized for ` +
2684
2722
  `unattended runs. The run WAS created and is left in 'planning' with no ` +
2685
2723
  `activation and no executor dispatch — nothing was half-applied.\n` +
2686
- `Restore \`unattended_conductor_allowed\` for this repository (see ` +
2724
+ `Restore \`unattended_epic_implementer_allowed\` for this repository (see ` +
2687
2725
  `docs/claude/account-settings-operator-runbook.md) and re-run setup-epic, ` +
2688
2726
  `or deliberately convert this run to an attended posture through the ` +
2689
2727
  `supported operator flow.\nDetail: ${errorDetail(err)}`);
@@ -83,9 +83,10 @@ export const DEFAULT_CONDUCTOR_GATE_NAME = "implement-ticket";
83
83
  * Exported since BAPI-1020 so the executor resolves the worker-guard bin through
84
84
  * the SAME resolver rather than growing a second one. Correct in both build
85
85
  * shapes because this module compiles to the build ROOT: the esbuild bundle puts
86
- * `import.meta.url` at `build/index.js` / `build/conductor-bin.js`, and `tsc` puts
87
- * it at `build/start-tickets-conductor.js`. A resolver living in a subdirectory
88
- * module would resolve to `build/<subdir>/<filename>` under `tsc` and be wrong.
86
+ * `import.meta.url` at `build/index.js` / `build/epic-implementer-bin.js`, and
87
+ * `tsc` puts it at `build/start-tickets-conductor.js`. A resolver living in a
88
+ * subdirectory module would resolve to `build/<subdir>/<filename>` under `tsc`
89
+ * and be wrong.
89
90
  */
90
91
  export function defaultResolveBinPath(filename) {
91
92
  return fileURLToPath(new URL(`./${filename}`, import.meta.url));
@@ -124,12 +125,12 @@ export async function createStartTicketsConductorContext(options, agent, deps) {
124
125
  gateName,
125
126
  supervisorMode,
126
127
  agentName: agent.name,
127
- cliFile: resolveBinPath("conductor-bin.js"),
128
+ cliFile: resolveBinPath("epic-implementer-bin.js"),
128
129
  // Capture the conductor process's Node executable so dispatched workers run
129
- // conductor-bin.js under THIS runtime (which owns the ledger), never the
130
- // worker's own Node (BAPI-527).
130
+ // epic-implementer-bin.js under THIS runtime (which owns the ledger), never
131
+ // the worker's own Node (BAPI-527).
131
132
  conductorNodePath: deps.execPath ?? process.execPath,
132
- hookBinPath: resolveBinPath("conductor-claude-hook-bin.js"),
133
+ hookBinPath: resolveBinPath("epic-implementer-claude-hook-bin.js"),
133
134
  };
134
135
  if (options.epic) {
135
136
  context.epic = options.epic;
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.2.54";
3
- export const BUILD_COMMIT = "7b8210791648";
4
- export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.54", "serve"];
2
+ export const VERSION = "0.2.55";
3
+ export const BUILD_COMMIT = "b96c1451f24e";
4
+ export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.55", "serve"];
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
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`${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)}if(positional.length<=1)return{kind:"allow"};let workerBranch=ctx.workerBranch;return typeof workerBranch!="string"||workerBranch.length===0?deny: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 unsafeSegmentDecision(family){switch(family){case"git":return{kind:"deny",category:"git-push",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)}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)}segment={argv,unsafe:segment.unsafe};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family);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 evaluateWorkerGuardPayload(payload,ctx){let root=asRecord(payload);if(root===void 0)return{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return{kind:"malformed"};if(toolName!=="Bash")return{kind:"allow"};let input=asRecord(root.tool_input??root.toolInput);if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return{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){if(typeof raw!="string")return{kind:"malformed"};if(raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES)return{kind:"malformed"};if(raw.trim().length===0)return{kind:"malformed"};let parsed;try{parsed=JSON.parse(raw)}catch{return{kind:"malformed"}}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return{kind:"malformed"}}}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;return typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim()),ctx}function readStdinSync(){return readFileSync(0,"utf-8")}var exitCode=0;try{let raw=readStdinSync(),ctx=resolveWorkerGuardContext(process.env,process.cwd(),process.argv.slice(2)),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(process.stderr.write(`${renderWorkerGuardReason(decision.category,decision.family)}
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 unsafeSegmentDecision(family){switch(family){case"git":return{kind:"deny",category:"git-push",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)}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)}segment={argv,unsafe:segment.unsafe};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family);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 evaluateWorkerGuardPayload(payload,ctx){let root=asRecord(payload);if(root===void 0)return{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return{kind:"malformed"};if(toolName!=="Bash")return{kind:"allow"};let input=asRecord(root.tool_input??root.toolInput);if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return{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){if(typeof raw!="string")return{kind:"malformed"};if(raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES)return{kind:"malformed"};if(raw.trim().length===0)return{kind:"malformed"};let parsed;try{parsed=JSON.parse(raw)}catch{return{kind:"malformed"}}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return{kind:"malformed"}}}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;return typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim()),ctx}function readStdinSync(){return readFileSync(0,"utf-8")}var exitCode=0;try{let raw=readStdinSync(),ctx=resolveWorkerGuardContext(process.env,process.cwd(),process.argv.slice(2)),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(process.stderr.write(`${renderWorkerGuardReason(decision.category,decision.family)}
4
4
  `),exitCode=2):decision.kind==="malformed"&&(process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
5
5
  `),exitCode=0)}catch{try{process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
6
6
  `)}catch{}exitCode=0}process.exit(exitCode);
package/docs/CONDUCTOR.md CHANGED
@@ -12,11 +12,13 @@ config. For the everyday `start-tickets` flags and cross-platform behavior, see
12
12
 
13
13
  ## Epic Conductor v2 — how an epic is actually driven
14
14
 
15
- **The v1 `conductor epic-tick` command is frozen.** It throws
15
+ **The v1 `epic-implementer epic-tick` command is frozen.** It throws
16
16
  `EPIC_TICK_V1_FROZEN` on every invocation and advances nothing. There is nothing
17
17
  to schedule locally. If you have an epic-tick schedule registered from an earlier
18
18
  release, remove it — it is a dead timer.
19
- `conductor doctor` flags one if it finds it.
19
+ `epic-implementer doctor` flags one if it finds it. (S4/BAPI-1080 renamed the
20
+ event-ledger CLI from `conductor` to `epic-implementer`; the retained
21
+ `conductor` bin name is now a fixed migration pointer.)
20
22
 
21
23
  v2 splits the old local tick into two halves:
22
24
 
@@ -227,7 +229,7 @@ existing hooks) so the spawned session streams local `run.started` / `run.stoppe
227
229
  environment scoped to that one terminal/tab/session — no credentials are ever placed
228
230
  in the env, hook command, or run metadata. Override the gate/supervisor labels with
229
231
  `BAPI_CONDUCTOR_GATE_NAME` / `BAPI_CONDUCTOR_SUPERVISOR_MODE`. Inspect the stream
230
- with `conductor doctor`. Observability is best-effort: a conductor failure never
232
+ with `epic-implementer doctor`. Observability is best-effort: a conductor failure never
231
233
  blocks or aborts a spawn, and `--dry-run` performs no conductor side effects.
232
234
  (Epic dispatch always enables conductor internally, independent of this flag.)
233
235
 
@@ -236,13 +238,13 @@ When `--conductor` is set, the spawn boundary also injects
236
238
  supervisor MCP tools (a plain `start-tickets` run stays on the default `core`
237
239
  profile). See [README → Environment Variables](../README.md#environment-variables).
238
240
 
239
- ## `conductor install-git-hooks` (BAPI-395)
241
+ ## `epic-implementer install-git-hooks` (BAPI-395)
240
242
 
241
243
  Installs local git hooks that opportunistically emit conductor git/PR/CI events into
242
244
  the local ledger:
243
245
 
244
246
  ```
245
- conductor install-git-hooks [--json]
247
+ epic-implementer install-git-hooks [--json]
246
248
  ```
247
249
 
248
250
  The installed hooks are **local, unversioned, opportunistic, and bypassable**: they
@@ -256,7 +258,7 @@ never a fatal error. The hooks installed are `post-commit` (emits
256
258
  `git.commit_created`) and `reference-transaction` (emits `worktree.changed` for
257
259
  committed ref updates).
258
260
 
259
- Missing hooks do **not** prevent PR/CI gate evaluation — `conductor doctor` reads
261
+ Missing hooks do **not** prevent PR/CI gate evaluation — `epic-implementer doctor` reads
260
262
  hook presence and managed-snippet status **read-only** (a new `git hooks` section /
261
263
  `git_hooks` JSON object alongside the ledger report), and the reconciler drives CI
262
264
  polling and gate evaluation regardless of whether hooks are installed.
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@bridge_gpt/mcp-server",
3
- "version": "0.2.54",
3
+ "version": "0.2.55",
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",
7
7
  "bin": {
8
8
  "mcp-server": "./build/index.js",
9
9
  "bridge-api-mcp-server": "./build/index.js",
10
+ "epic-implementer": "./build/epic-implementer-bin.js",
11
+ "epic-implementer-claude-hook": "./build/epic-implementer-claude-hook-bin.js",
10
12
  "conductor": "./build/conductor-bin.js",
11
13
  "conductor-claude-hook": "./build/conductor-claude-hook-bin.js",
12
14
  "bridge-worker-guard": "./build/worker-guard-hook-bin.js"
@@ -24,7 +26,7 @@
24
26
  "//test": "The `test` script delegates to the bounded launcher (scripts/run-unit-tests.js → src/run-unit-tests-launcher.ts, BAPI-683), which discovers build/*.test.js at runtime and spawns them in size-bounded batches so the argv never exceeds Windows' ~8191-char command-line limit. Two determinism rules from the pre-launcher script are preserved INSIDE the launcher: (1) multi-file batches run WITHOUT --test-force-exit, because that flag makes node:test exit before aggregating per-subprocess summaries and the tally becomes nondeterministic (failures still surface and still set exit 1, but the count cannot be trusted as a completeness signal); (2) build/secret-safety.test.js leaks a handle that holds the event loop ~60s after its tests finish (they run in ~147ms), so the launcher quarantines it into its own single-file --test-force-exit invocation (FORCE_EXIT_QUARANTINE in src/run-unit-tests-launcher.ts) where the tally stays exact. Do NOT add --test-force-exit to batched invocations, and do NOT remove the quarantine, until the underlying handle leak is fixed.",
25
27
  "//version": "BAPI-930: fired by `npm version <level>` AFTER npm rewrites package.json's version field, so the bumped version and the repinned README land in the SAME commit. A bump performed by hand-editing package.json instead of running `npm version` bypasses this hook entirely and must repin manually via `node scripts/sync-readme-host-examples.js` — the `--check` wired into `build` below is the backstop that catches that case.",
26
28
  "scripts": {
27
- "build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
29
+ "build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && node scripts/bundle-agent-guidance.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
28
30
  "version": "node scripts/sync-readme-host-examples.js --write && git add -- README.md package.json package-lock.json && git commit -m \"chore(mcp-server): release $npm_package_version\" && git tag \"mcp-server/v$npm_package_version\"",
29
31
  "check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
30
32
  "assert:release-identity": "node scripts/assert-release-identity.js",
@@ -60,7 +62,7 @@
60
62
  },
61
63
  "overrides": {
62
64
  "fast-uri": ">=4.1.3",
63
- "hono": ">=4.13.1",
65
+ "hono": ">=4.13.7",
64
66
  "@hono/node-server": "^1.19.13",
65
67
  "ip-address": ">=10.1.1"
66
68
  },