@bridge_gpt/mcp-server 0.2.21 → 0.2.24

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 (41) hide show
  1. package/README.md +144 -18
  2. package/build/base-ref.js +151 -0
  3. package/build/commands.generated.js +6 -4
  4. package/build/conductor/bridge-api-client.js +44 -3
  5. package/build/conductor/doctor.js +33 -22
  6. package/build/conductor/epic-runtime.js +101 -5
  7. package/build/conductor/pr-ci-producer.js +21 -2
  8. package/build/conductor/pr-discovery.js +12 -2
  9. package/build/conductor-bin.js +50 -20
  10. package/build/credential-store.js +564 -64
  11. package/build/decision-page-template.js +9 -4
  12. package/build/docs.generated.js +5 -0
  13. package/build/executor/base-branch.js +50 -0
  14. package/build/executor/env.js +12 -1
  15. package/build/executor/job-errors.js +1 -0
  16. package/build/executor/job-runner.js +38 -7
  17. package/build/executor/test-clock.js +6 -1
  18. package/build/executor/worker-finalization.js +88 -1
  19. package/build/executor/worktree.js +21 -1
  20. package/build/index.js +2741 -702
  21. package/build/init.js +29 -0
  22. package/build/install-bridge.js +1076 -114
  23. package/build/pipelines.generated.js +2 -2
  24. package/build/pr-base-contract.js +36 -0
  25. package/build/readme.generated.js +1 -1
  26. package/build/setup-epic.js +483 -0
  27. package/build/sfcc/log-gate.js +85 -0
  28. package/build/sfcc/log-query.js +170 -0
  29. package/build/sfcc/register.js +10 -0
  30. package/build/sfcc/setup-status.js +33 -3
  31. package/build/start-tickets.js +164 -75
  32. package/build/version.generated.js +1 -1
  33. package/build/worktree-core.js +62 -10
  34. package/{CONDUCTOR.md → docs/CONDUCTOR.md} +88 -29
  35. package/docs/install/github-app.md +189 -0
  36. package/docs/install/mcp-tool-integrations.md +305 -0
  37. package/docs/install/sfcc-integration.md +140 -0
  38. package/package.json +5 -5
  39. package/public/js/main.min.js +55 -10
  40. package/public/js/main.min.js.map +1 -1
  41. package/smoke-test/SMOKE-TEST.md +3 -2
package/README.md CHANGED
@@ -18,7 +18,7 @@ The Bridge MCP is an end-to-end accelerator for shipping code within SFCC, power
18
18
  - [Worktree credentials and the `mcp-invoke` shim](#worktree-credentials-and-the-mcp-invoke-shim)
19
19
  - [Reference](#reference)
20
20
 
21
- For advanced epic/multi-agent orchestration, see [CONDUCTOR.md](./CONDUCTOR.md).
21
+ For advanced epic/multi-agent orchestration, see [CONDUCTOR.md](./docs/CONDUCTOR.md).
22
22
 
23
23
  ## Getting Started
24
24
 
@@ -32,10 +32,25 @@ npx -y @bridge_gpt/mcp-server@latest install-bridge
32
32
 
33
33
  `install-bridge` scaffolds the project, writes your editor's MCP config with real
34
34
  values, verifies connectivity, persists your API key to the user-scoped credential
35
- store, and opens a fresh agent session to finish setup (`/install-bridge` then
36
- `/learn-repository`). The only inputs are an **API key** (generate one on the Bridge
37
- API web UI **Security** page) and a **repo name** everything else is derived. Add
38
- `--dry-run` to preview every step without writing, pinging, or spawning anything.
35
+ store, and opens a fresh agent session that runs `/install-bridge` to derive and
36
+ apply the remaining config, presents a **capability report** (what you can use now
37
+ and what you'll unlock), and closes by asking whether to index the repository. It
38
+ does **not** automatically run `/learn-repository` or index without your consent
39
+ both remain available as separate steps. The only inputs are an **API key**
40
+ (generate one on the Bridge API web UI **Security** page) and a **repo name** —
41
+ everything else is derived. Add `--dry-run` to preview every step without writing,
42
+ pinging, or spawning anything.
43
+
44
+ **Were you sent a bootstrap invite?** Then you don't need an API key or the web UI
45
+ at all — run the command your operator gave you:
46
+
47
+ ```bash
48
+ npx -y @bridge_gpt/mcp-server@latest install-bridge --invite
49
+ ```
50
+
51
+ That one-liner is deliberately **secret-free**: the CLI prompts for the bootstrap
52
+ invite token with **echo suppressed**, and sends it only in the request body. It
53
+ creates your project and mints your own admin API key in a single command.
39
54
 
40
55
  To upgrade later, run:
41
56
 
@@ -67,8 +82,13 @@ works as a fallback.)
67
82
  4. **Persists your key** to the user-scoped credential store
68
83
  (`~/.config/bridge/credentials.json`, target `bapi:<repo>`) so shell-spawned
69
84
  tooling (e.g. `start-tickets`) can resolve it.
70
- 5. **Opens a fresh agent session** that runs `/install-bridge` (to derive the
71
- remaining config fields from your codebase) and then `/learn-repository`.
85
+ 5. **Opens a fresh agent session** that runs `/install-bridge` to derive and apply
86
+ the remaining config fields from your codebase, presents the capability report
87
+ (Connected / Not yet connected / Tools you can use now / Tools you'll unlock /
88
+ Recommended next step), and closes with one optional `[Y/n] Index repository
89
+ now?` question. It does not chain `/learn-repository` and never indexes without
90
+ consent; run `/learn-repository` and `/parse-repository` yourself when you want
91
+ them.
72
92
 
73
93
  The only inputs are an **API key** and a **repo name** (everything else is
74
94
  derived). Resolution order:
@@ -76,19 +96,88 @@ derived). Resolution order:
76
96
  - **API key:** `--api-key <key>` → `BAPI_API_KEY` env → an interactive (no-echo)
77
97
  prompt. Generate one first on the Bridge API web UI **Security** page (see
78
98
  [Generate an API Key](#2-generate-an-api-key)); the command consumes a key, it
79
- never mints one. The key is **never printed or logged**.
80
- - **Repo name:** `--repo <name>` → `BAPI_REPO_NAME` env → an inferred default you
81
- confirm interactively. It MUST match the server-side repository registration.
99
+ never mints one **`--invite` is the one exception** (below). The key is
100
+ **never printed or logged**.
101
+ - **Repo name:** `--repo <name>` and `BAPI_REPO_NAME` remain the deterministic
102
+ short-circuits and compatibility fallbacks — when either is set it is used
103
+ directly, with no network round-trip. When **neither** is set, a compatible
104
+ server resolves the unique repository from your existing API key automatically
105
+ (a read-only lookup), so you don't have to supply it. If the server is older,
106
+ the key can't be uniquely resolved, or the lookup fails, `install-bridge` falls
107
+ back to an inferred default you confirm interactively (and requires `--repo`
108
+ when stdin is non-interactive). Whatever name is used MUST match the
109
+ server-side repository registration.
110
+
111
+ #### Self-serve email onboarding (`--email`) — no account, no key, no invite
112
+
113
+ The primary path for a **first-time user with nothing yet** — no Bridge account,
114
+ no API key, and no pre-issued invite. Run:
115
+
116
+ ```bash
117
+ npx -y @bridge_gpt/mcp-server@latest install-bridge --email you@example.com
118
+ ```
119
+
120
+ `install-bridge` requests a brand-new Bridge workspace for that email, receives an
121
+ invite token, and then feeds it into the **exact same** persist-before-exchange
122
+ bootstrap protocol as `--invite` below — so the project is created and your first
123
+ admin key is minted in one command. The minted token is used internally and **never
124
+ shown**.
125
+
126
+ The email may instead come from the `BAPI_SIGNUP_EMAIL` environment variable or a
127
+ **visible** interactive prompt (email is not a secret, so it is echoed as you type —
128
+ unlike the API key and the invite token, which use a hidden prompt). It is still
129
+ **never written to a log line**. No email verification is performed and no message
130
+ is sent to the address — it only labels your new workspace. `--email` is mutually
131
+ exclusive with `--api-key` and `--invite`.
132
+
133
+ #### Bootstrap-invite onboarding (`--invite`)
134
+
135
+ With a **bootstrap invite** you were already given, there is no pre-existing key and
136
+ no web UI: like `--email` above, this mode **creates** the project and its first
137
+ admin key instead of consuming one. Run `install-bridge --invite` and it:
138
+
139
+ 1. **Prompts for the bootstrap invite token** with echo suppressed (the default —
140
+ see below).
141
+ 2. **Generates your `key_secret`** (32 CSPRNG bytes) and **fsyncs it locally**
142
+ *before* contacting the server. If that write fails the run aborts and the
143
+ invite is **not** spent.
144
+ 3. **Redeems the invite** — `POST /setup/bootstrap` with the token, repo name, and
145
+ `key_secret` in the **body** — which creates the project and mints your admin
146
+ key. (This replaces the connectivity ping: there is no key to ping with yet.)
147
+ 4. **Verifies the newly-minted key**, then writes the per-host MCP config.
148
+ 5. **Promotes the credential** to `bapi:<repo>` and **opens the agent session** —
149
+ the same Steps 3–5 as the normal flow.
150
+
151
+ Because the locally-saved `key_secret` is the only proof that can replay a
152
+ redemption, a re-run after a network failure is safe: it re-sends the *same* secret
153
+ and gets back the *same* project and key. If the repo name you chose is already
154
+ taken (names are globally unique) the server rolls back — your invite is untouched —
155
+ and the CLI asks for a different name and retries with the same token.
156
+
157
+ **The delivered one-liner is secret-free, by design.** "A copy/paste one-liner" and
158
+ "the token never touches shell history" are contradictory, so the token is *not* in
159
+ the command: the CLI asks for it, and it travels only in the request body.
160
+ `--invite <token>`, `--invite=<token>`, and `BAPI_INVITE` still work for
161
+ **scripting only** — and both forms **expose the token to your shell history and to
162
+ the process list**. Prefer the prompt.
82
163
 
83
164
  Useful flags:
84
165
 
85
166
  - `--dry-run` — preview every step (scaffold targets, config files and keys with
86
167
  the key value **redacted**, the ping target, the credential store target, and
87
168
  the exact agent spawn command) without writing, pinging, or spawning anything.
88
- - `--force` overwrite an existing real `BAPI_API_KEY` in a host config without
89
- prompting (re-running is otherwise non-destructive).
169
+ With `--invite` it also never calls the exchange endpoint and never generates or
170
+ stores a secret.
171
+ - `--force` — overwrite an existing real `BAPI_API_KEY` in a host config, or in the
172
+ credential store, without prompting (re-running is otherwise non-destructive).
90
173
  - `--agent claude|cursor-agent` — which agent to launch for the agentic remainder
91
174
  (default `claude`).
175
+ - `--email <addr>` — self-serve signup: create a new workspace from just an email
176
+ (mutually exclusive with `--api-key` and `--invite`). Falls back to
177
+ `BAPI_SIGNUP_EMAIL`, then a visible prompt. Visible input, not a secret; still
178
+ never logged.
179
+ - `--invite [token]` — redeem a bootstrap invite (mutually exclusive with
180
+ `--api-key` and `--email`). Omit the value to get the hidden prompt.
92
181
 
93
182
  That's it — once `install-bridge` finishes you're connected. If you prefer to do
94
183
  it by hand (or just want to understand each step), the manual flow below does the
@@ -276,7 +365,14 @@ These features are useful for most tickets.
276
365
  - **What it does:** Creates one git worktree per ticket and spawns an agent session in each to implement them in parallel.
277
366
  - **When it's useful:** (Implementation | Automation) When you're ready to start building one or more refined tickets concurrently.
278
367
  - **How to use it:** `/start-tickets BAPI-248 BAPI-250` (see [CLI Subcommands](#cli-subcommands) for the full flag table and cross-platform behavior).
279
- - **Flags:** `--auto` skip the approval gates · `--base-branch <branch>` branch off something other than the default.
368
+ - **Flags:** `--auto` skip the approval gates · `--base-branch <branch>` branch off something other than the default · `--workflow implement|review-and-implement` selects which slash command each spawned worktree runs (default `implement`, byte-identical to today; `review-and-implement` runs `/review-ticket` then, after a per-ticket halt gate, `/implement-ticket` in the same session) · `--rounds=1|2` review-only, forwarded to the review phase, valid only with `--workflow review-and-implement`.
369
+
370
+ **2b. Review and Start**
371
+ - **What it does:** Spawns one worktree per ticket, each running review then (after a per-ticket human proceed/halt gate) implementation — the chained `review → gate → implement` composition.
372
+ - **When it's useful:** (Refinement | Implementation | Automation) The **recommended front door** for "review these tickets, then implement the ones that pass," starting from existing ticket keys (unlike `/full-automation`, which only accepts an idea).
373
+ - **How to use it:** `/review-and-start BAPI-248 BAPI-250` (single or multiple keys flow through the identical code path).
374
+ - **Flags:** `--auto` a single chain-level flag that auto-approves both the review and the implementation phase of every spawned session · `--rounds=1|2` forwarded to the review phase · `--agent`, `--base-branch`, `--max-parallel`, `--dry-run` mirror `/start-tickets`.
375
+ - Under the hood, this is a thin shim over `start-tickets --workflow review-and-implement <KEYS>` — the lower-level launcher seam documented above; the halt-gate decision logic lives in the spawned `/review-and-implement` session, never in this command or the CLI.
280
376
 
281
377
  **3. Brainstorm**
282
378
  - **What it does:** Fans your problem out to two different LLMs and returns their approaches directly. Runs in one of three modes, selected via `mode`: **`technical`** (default — implementation/architecture approaches), **`design`** (UI/UX and visual direction), or **`discovery`** (stakeholder discovery questions for early/vague tasks, grouped into `Technical Discovery Questions` and `Business / Stakeholder Discovery Questions` and tagged `[HUMAN]`/`[CODE]`/`[TICKET]`). Discovery needs no extra configuration. The legacy boolean `design=true` still works and maps to `mode: "design"`.
@@ -448,10 +544,12 @@ Salesforce's official MCP server, `@salesforce/b2c-dx-mcp`, covers developer-exp
448
544
 
449
545
  **v1 is read-only and developer-sandbox-only** — no writes, and non-sandbox instances are rejected. Credentials stay local (in `dw.json` or `SFCC_*` env vars) and are never sent to Bridge.
450
546
 
547
+ For a step-by-step OCAPI client setup guide (including the Business Manager permissions grant), see [docs/install/sfcc-integration.md](./docs/install/sfcc-integration.md).
548
+
451
549
  <details>
452
550
  <summary><strong>Setup</strong></summary>
453
551
 
454
- The two diagnostic tools (`sfcc_setup_status`, `check_permissions`) are always available. The seven read tools must be enabled with a profile (step 3).
552
+ The two diagnostic tools (`sfcc_setup_status`, `check_permissions`) are always available. The read tools, the write tools, and `sfcc_log_query` must be enabled with a profile (step 3). Changing `BRIDGE_MCP_PROFILE` requires an MCP client restart.
455
553
 
456
554
  **Prerequisites:** a running SFCC **sandbox** and its hostname, plus an Account Manager API client (`client-id` + `client-secret`).
457
555
 
@@ -486,7 +584,7 @@ Without this, only the diagnostic tools are registered.
486
584
  All SFCC tools are read-only and target a developer sandbox. Oversized responses are automatically saved to `BAPI_DOCS_DIR/sfcc/` and previewed inline.
487
585
 
488
586
  **Diagnostics** (always available, no profile needed)
489
- - `sfcc_setup_status` — report on every prerequisite: Bridge API key, repo name, `version` config, `dw.json` presence/uniqueness, and AM token acquisition.
587
+ - `sfcc_setup_status` — report on every prerequisite: Bridge API key, repo name, `version` config, `dw.json` presence/uniqueness, AM (OCAPI) token acquisition, and the independent **SFCC Log Query (WebDAV)** capability that gates `sfcc_log_query`.
490
588
  - `check_permissions` — probe OCAPI access via `GET /system_object_definitions`; on 401/403, print the exact OCAPI Settings JSON to paste in Business Manager (split read-only vs. write/import grants).
491
589
 
492
590
  **System object model** (needs the `sfcc` profile)
@@ -505,13 +603,21 @@ All SFCC tools are read-only and target a developer sandbox. Oversized responses
505
603
  - `site_preference_search` — search/filter preferences within a group.
506
604
  - `site_preference_values_set` — **write** (sandbox only): set custom preference values via `PATCH /site_preferences/preference_groups/{group}/sandbox` with a flat map of `c_`-prefixed ids to string/number/boolean/string[] values. A bad group returns 404 `CustomPreferenceGroupNotFoundException`; echoes grant JSON on 403.
507
605
 
606
+ **On-demand log query** (needs the `sfcc` profile)
607
+ - `sfcc_log_query` — query redacted, filtered SFCC logs on demand. `environment` and `time_range` (`start`/`end`) are **required** — production, "all environments", and an open-ended period are never inferred. The tool calls a Bridge backend endpoint that runs the pull → redaction → filter pipeline server-side and returns scoped, redacted findings; **WebDAV credentials, retrieval, redaction, and filtering all stay server-side and single-sourced.** It holds no credentials of its own.
608
+ - **Guardrails.** Selection is bounded by log-file `prefixes` (max 5), the time range, a scanned-entry cap (`max_entries`, ≤ 2000), a finding cap, and a per-snippet length cap. High-volume prefix classes (`info`, `jobs`, `debug`, `customdebug`) impose a **stricter 6-hour** max range (vs. 24h for the error class) because `info-*` runs ~1 MB/day versus `error-*` at ~13 KB median — a wide window over a high-volume prefix is **rejected**, never silently narrowed.
609
+ - **Response order.** Resolved scope (`environment`, `time_range`, `applied_prefixes`) and cap `status` first, redacted `findings` second, retrieval/truncation `metadata` last.
610
+ - **Statuses & errors.** `ready`, `no_matching_findings`, `results_truncated`; plus `VALIDATION_ERROR` (bad/oversized scope, caught before any network call), `NOT_CONFIGURED` (503 — the log capability isn't set up; run `sfcc_setup_status`, whose step 6 reports it), and `BAD_GATEWAY`/`SERVICE_UNAVAILABLE` on a retrieval/backend failure.
611
+ - **Auth is separate from OCAPI.** Log retrieval uses **HTTP Basic auth** — a Business Manager username + a **40-character WebDAV access key** — *not* the OCAPI Account Manager OAuth token the other SFCC tools use. A valid AM bearer token 401s on `/Logs`. `sfcc_setup_status` step 5 (AM/OCAPI token) and step 6 (WebDAV log access) are independent: one can be green while the other is not.
612
+ - **Local / air-gapped fallback.** The primary path above is the only path this tool takes. For air-gapped development, the documented fallback is Salesforce's own **`@salesforce/b2c-dx-mcp`** (`logs_list_files`, configured from `dw.json`) — it is vendor-maintained and reads log files over WebDAV, so it is strictly less work than shelling the B2C CLI (`b2c logs get --since <window> --search <q> --json`). It is **not** the primary path because its credentials live client-side and its output has **not** passed Bridge's redaction/filter. If you use it, its output must be treated as raw: route it back through the same server-side Python `LogSource` composition and `RedactionPort`/T3 filter workflow — never paste or relay unredacted `b2c-dx-mcp` or CLI output to an LLM.
613
+
508
614
  ## CLI Subcommands
509
615
 
510
616
  Beyond `--init` / `--upgrade`, the package ships operational subcommands of the **single `bridge-api-mcp-server` bin** (not separate binaries) — so they travel with the package to every consumer. See [Usage Documentation → Start Tickets](#tier-1--regularly-useful) for *when* to use `start-tickets`; this section is the full CLI reference.
511
617
 
512
618
  ### `start-tickets`
513
619
 
514
- Spawns one Worktrunk worktree + selected-agent session per Jira ticket and backs the `/start-tickets` slash command. The agent defaults to **Claude Code** (`claude`) and is configurable via `--agent`.
620
+ Spawns one Worktrunk worktree + selected-agent session per Jira ticket and backs the `/start-tickets` slash command. The agent defaults to **Claude Code** (`claude`) and is configurable via `--agent`. For existing ticket keys, `/review-and-start` (see [Usage Documentation → Review and Start](#tier-1--regularly-useful)) is the recommended enriched front door over this CLI's `--workflow review-and-implement` seam; using the CLI directly (below) remains the advanced/lower-level path.
515
621
 
516
622
  ```
517
623
  npx -y @bridge_gpt/mcp-server start-tickets [flags] KEY [KEY ...]
@@ -520,6 +626,8 @@ npx -y @bridge_gpt/mcp-server start-tickets [flags] KEY [KEY ...]
520
626
  | Flag | Default | Meaning |
521
627
  |---|---|---|
522
628
  | `--agent claude\|cursor-agent` | `claude` | Agent command to launch in each worktree |
629
+ | `--workflow implement\|review-and-implement` | `implement` | Slash command each spawned worktree runs. `implement` is byte-identical to today's `/implement-ticket <KEY> [--auto]`. `review-and-implement` spawns `/review-and-implement <KEY> [--auto] [--rounds=<n>]`, which runs `/review-ticket` then, after a per-ticket halt gate, `/implement-ticket` in the same session. `--auto` applies to the selected workflow as a whole. |
630
+ | `--rounds 1\|2` | unset | Review round count forwarded to the review phase. Review-only — valid only with `--workflow review-and-implement`. |
523
631
  | `--terminal terminal\|iterm` | auto-detect via `$TERM_PROGRAM` | Override the macOS terminal app (honored on macOS only) |
524
632
  | `--dry-run` | off | Print intended actions; create no worktrees, open no tabs (any OS) |
525
633
  | `--branch KEY=BRANCH` | `feature/<KEY>` | Use a custom branch for that ticket (repeatable) |
@@ -537,7 +645,7 @@ npx -y @bridge_gpt/mcp-server start-tickets --agent cursor-agent BAPI-248
537
645
 
538
646
  **Difficulty-based model routing.** Before launching each agent, the CLI selects an implementation **model tier** from the ticket's `difficulty` (1-2 → cheap, 3-5 → basic, 6+ → premium) and injects it as a `--model` flag at the spawn boundary. The Python backend returns only the coarse tier (`GET /jira/tickets/{KEY}/model-tier`, computing + caching difficulty on demand); this CLI alone maps a tier to the agent-specific alias (`claude`: `haiku`/`sonnet`/`opus`; `cursor-agent`: version-suffixed strings validated against `cursor-agent --list-models`). It is gated per repo by `difficulty_model_routing_enabled` (default **ON**) with an optional `difficulty_model_tier_overrides` JSON map (tier → alias). Routing is **fail-open**: missing credentials, an evaluation failure/timeout, a backend `fallback`, an invalid/unavailable alias, an unadvertised Cursor model, or an agent without `--model` support all omit `--model` (the agent uses its default) and surface a per-ticket warning rather than failing the spawn. `--dry-run` does **not** fetch tiers or inject `--model`.
539
647
 
540
- **Conductor observability (opt-in via `--conductor`, BAPI-394).** Conductor is **off by default**. With `--conductor`, a run mints a conductor `run_id`, emits events into the local ledger (`~/.config/bridge/events.db`), opens a supervisor peer tab, and (for Claude Code) injects a secret-free lifecycle hook into each worktree; it also sets `BRIDGE_MCP_PROFILE=conductor` so workers get the conductor MCP tools. Observability is best-effort — a conductor failure never blocks a spawn, and `--dry-run` performs no conductor side effects. (Epic-tick dispatch always enables conductor internally.) See **[CONDUCTOR.md](./CONDUCTOR.md)** for the full observability, git-hook, done-gate, and auto-merge reference.
648
+ **Conductor observability (opt-in via `--conductor`, BAPI-394).** Conductor is **off by default**. With `--conductor`, a run mints a conductor `run_id`, emits events into the local ledger (`~/.config/bridge/events.db`), opens a supervisor peer tab, and (for Claude Code) injects a secret-free lifecycle hook into each worktree; it also sets `BRIDGE_MCP_PROFILE=conductor` so workers get the conductor MCP tools. Observability is best-effort — a conductor failure never blocks a spawn, and `--dry-run` performs no conductor side effects. (Epic-tick dispatch always enables conductor internally.) See **[CONDUCTOR.md](./docs/CONDUCTOR.md)** for the full observability, git-hook, done-gate, and auto-merge reference.
541
649
 
542
650
  **Cross-platform spawning.** The CLI routes spawning per platform; `--dry-run` previews the platform-correct command form on any OS. An unsupported `process.platform` (not `darwin`/`win32`/`linux`) fails fast with a clear "unsupported platform" message.
543
651
 
@@ -557,9 +665,27 @@ npx -y @bridge_gpt/mcp-server doctor [--agent <name>]
557
665
 
558
666
  It is **read-only**: it never installs anything, modifies your system, adds an npm `postinstall`, spawns a terminal, or starts the MCP server, and there is no `--fix`. For each prerequisite it prints found/missing and, when missing, the exact per-OS install command **as a manual instruction you run yourself**. The checked set is the `start-tickets` preflight prerequisites **plus `uv`** **plus the selected agent's command** (`claude` by default, or `cursor-agent` with `--agent cursor-agent`). The Worktrunk binary is probed via the resolved name (honoring `BAPI_WORKTRUNK_BIN`), not a hard-coded one. **Exit code:** `0` when all required prerequisites are present, non-zero when any is missing or the platform is unsupported. A failing `start-tickets` preflight now hints you to run `doctor` for an actionable diagnostics report.
559
667
 
668
+ ### `setup-epic`
669
+
670
+ Bootstraps an Epic Conductor v2 run in one command — creates the epic run, stores the plan DAG, and approves it:
671
+
672
+ ```
673
+ npx -y @bridge_gpt/mcp-server setup-epic --epic-key <KEY> --plan-file <path-to-epic-plan.dag.json>
674
+ ```
675
+
676
+ The plan sidecar is produced by the `decompose-epic` pipeline step. `setup-epic` validates it locally (unique ticket keys, resolvable dependency references, acyclicity) before sending anything, so a malformed plan fails legibly instead of as a bare HTTP 400. It is **idempotent**: re-running it on an epic that already has a live run reuses that run rather than minting a second one. `--dry-run` validates and previews the calls without mutating anything; `--json` emits a single machine-readable result object.
677
+
678
+ Once the plan is approved, the **server-side reconciler** picks the run up within ~30s. To execute claimed jobs on your machine, run `executor`:
679
+
680
+ ```
681
+ npx -y @bridge_gpt/mcp-server executor --repo <name>
682
+ ```
683
+
560
684
  ### Conductor (epic & multi-agent orchestration)
561
685
 
562
- Conductor is an **opt-in, off-by-default** layer for epic supervision, inter-agent messaging, done-gate evaluation, local git-hook event producers, and conditional auto-merge. Its full reference — `conductor install-git-hooks`, the `conductor_done_gate` and `conductor_auto_merge_enabled` config fields, and the observability stream — lives in **[CONDUCTOR.md](./CONDUCTOR.md)**.
686
+ Conductor is an **opt-in, off-by-default** layer for epic supervision, inter-agent messaging, done-gate evaluation, local git-hook event producers, and conditional auto-merge. Its full reference — the v2 architecture (server-side reconciler + local executor), `setup-epic`, `conductor install-git-hooks`, the supervisor `done_gate_config` / `auto_merge_enabled` settings, and the observability stream — lives in **[CONDUCTOR.md](./docs/CONDUCTOR.md)**.
687
+
688
+ > The v1 `conductor epic-tick` command is **frozen** — it throws on every invocation. There is nothing to schedule locally, and `conductor doctor` flags any epic-tick schedule left over from an earlier release so you can cancel it.
563
689
 
564
690
  ## Custom Pipelines
565
691
 
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Shared remote-base resolution (BAPI-586).
3
+ *
4
+ * A focused, executor-agnostic helper that BOTH Conductor dispatch paths use to
5
+ * validate, fetch, and pin the configured remote base commit before creating a
6
+ * fresh worktree:
7
+ *
8
+ * - the interactive `start-tickets` non-mutating dispatch path
9
+ * (`orchestrateStartTickets`), and
10
+ * - the Epic Conductor v2 executor fresh worktree path
11
+ * (`executor/worktree.ts#ensureExecutorWorktree`).
12
+ *
13
+ * Extracted out of the large `start-tickets.ts` CLI module so executor code can
14
+ * depend on a small, focused module instead of pulling in the whole CLI. The
15
+ * operation is FETCH-ONLY: it never fast-forwards, resets, or force-moves any
16
+ * local branch ref — it fetches `origin/<base>` and resolves the fetched tip to
17
+ * an immutable commit SHA, which becomes Worktrunk's `-b <start-point>`.
18
+ *
19
+ * Import direction: this module imports the shared `RunCommand` type from
20
+ * `start-tickets.js` as a TYPE-ONLY import (erased at runtime) plus the
21
+ * `commandSucceeded` value from `start-tickets-prereqs.js`. `start-tickets.ts`
22
+ * imports the VALUES here and re-exports them, so the runtime graph stays
23
+ * acyclic (mirrors the existing `worktree-core.ts` / prereqs pattern).
24
+ */
25
+ import path from "path";
26
+ import { commandSucceeded } from "./start-tickets-prereqs.js";
27
+ /**
28
+ * Returns an error string for an unsafe branch name, or null when valid.
29
+ *
30
+ * Rejects names that Git itself refuses (`git check-ref-format`) as well as
31
+ * injection-shaped inputs, WITHOUT invoking Git: an empty/whitespace-only name,
32
+ * a leading `-` (which git would parse as a flag, e.g. `--upload-pack=evil`),
33
+ * ASCII control characters, a `..` sequence, and a `.lock` suffix. Every failure
34
+ * is a short, secret-free reason string that names only the rule that failed.
35
+ */
36
+ export function validateBranchName(branch) {
37
+ if (branch.trim().length === 0)
38
+ return "branch name must not be empty.";
39
+ if (branch.length > 255)
40
+ return "branch name must be 255 characters or fewer.";
41
+ if (branch.startsWith("-"))
42
+ return "branch name must not start with '-'.";
43
+ if (branch.includes(".."))
44
+ return "branch name must not contain '..'.";
45
+ if (branch.endsWith(".lock"))
46
+ return "branch name must not end with '.lock'.";
47
+ // Reject ASCII control characters (0x00-0x1F and 0x7F) without embedding
48
+ // raw control bytes in source.
49
+ for (let i = 0; i < branch.length; i++) {
50
+ const code = branch.charCodeAt(i);
51
+ if (code <= 0x1f || code === 0x7f) {
52
+ return "branch name must not contain control characters.";
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Per-repository fetch serialization
59
+ // ---------------------------------------------------------------------------
60
+ /**
61
+ * A per-repository async mutex chain. The executor runner fills up to
62
+ * `maxConcurrent` slots against ONE shared clone, so once a `git fetch` is
63
+ * introduced on the fresh path, simultaneous fresh jobs would race on
64
+ * `FETCH_HEAD.lock` / `index.lock`. Serializing fetch-and-resolve per repository
65
+ * removes that collision while still letting independent repositories proceed
66
+ * concurrently (the map is keyed per normalized repo path, not one global lock).
67
+ */
68
+ const repoFetchLocks = new Map();
69
+ /** Normalize a repo working directory into a stable, spelling-independent key. */
70
+ function normalizeRepoKey(cwd) {
71
+ // `path.resolve` collapses `.`/`..` segments and trailing separators so
72
+ // different spellings of the same directory share one lock entry.
73
+ return path.resolve(cwd);
74
+ }
75
+ /**
76
+ * Run `fn` while holding the per-repository lock. Concurrent calls for the same
77
+ * normalized repo run strictly one at a time; calls for different repos overlap.
78
+ * The lock is released and its map entry removed in `finally` — including on a
79
+ * thrown/failed operation — so a failed fetch never deadlocks later dispatches
80
+ * or leaks repository keys.
81
+ */
82
+ async function withRepoFetchLock(repoKey, fn) {
83
+ const previous = repoFetchLocks.get(repoKey) ?? Promise.resolve();
84
+ let releaseCurrent;
85
+ const current = new Promise((resolve) => {
86
+ releaseCurrent = resolve;
87
+ });
88
+ // Later waiters chain on `previous` THEN on our gate, so they cannot start
89
+ // until we release. `current` only ever resolves, so this chain never rejects.
90
+ const chained = previous.then(() => current);
91
+ repoFetchLocks.set(repoKey, chained);
92
+ await previous.catch(() => { });
93
+ try {
94
+ return await fn();
95
+ }
96
+ finally {
97
+ releaseCurrent();
98
+ // Drop the entry only if no later waiter chained on after us (we are still
99
+ // the tail). Otherwise the newest waiter owns the key.
100
+ if (repoFetchLocks.get(repoKey) === chained) {
101
+ repoFetchLocks.delete(repoKey);
102
+ }
103
+ }
104
+ }
105
+ /** Test-only: clear all per-repository fetch locks between tests. */
106
+ export function __resetBaseRefFetchLocksForTests() {
107
+ repoFetchLocks.clear();
108
+ }
109
+ /**
110
+ * Validate `baseBranch`, then (serialized per repository) fetch
111
+ * `origin/<baseBranch>` and resolve the fetched tip to an immutable commit SHA,
112
+ * WITHOUT ever fast-forwarding or force-moving any local branch ref (no
113
+ * `git merge --ff-only`, no `git branch --force`, no `git reset`). Pinning the
114
+ * SHA removes ambiguity and prevents Worktrunk or stale local branch state from
115
+ * selecting a sibling feature branch when the resulting SHA is passed as
116
+ * Worktrunk's `-b` start point.
117
+ *
118
+ * Validation runs BEFORE the lock is acquired so an injection-shaped ref name is
119
+ * rejected without any git invocation and without touching the mutex. Both the
120
+ * `git fetch` and the `git rev-parse` run inside the same per-repository lock so
121
+ * concurrent fresh jobs cannot collide on git lock files or resolve an unrelated
122
+ * fetch result. Failures name only the configured branch and the failed
123
+ * operation — never remote URLs, credentials, raw command arguments, or
124
+ * unbounded command output.
125
+ */
126
+ export async function fetchAndResolveBaseSha(deps, baseBranch) {
127
+ const validationError = validateBranchName(baseBranch);
128
+ if (validationError) {
129
+ return { ok: false, error: `Invalid base branch '${baseBranch}': ${validationError}` };
130
+ }
131
+ const repoKey = normalizeRepoKey(deps.cwd);
132
+ return withRepoFetchLock(repoKey, async () => {
133
+ const fetch = await deps.runCommand("git", ["fetch", "origin", baseBranch], {
134
+ cwd: deps.cwd,
135
+ });
136
+ if (!commandSucceeded(fetch)) {
137
+ return {
138
+ ok: false,
139
+ error: `git fetch origin ${baseBranch} failed. Check your network and 'git remote get-url origin', or pass --no-refresh-base to skip.`,
140
+ };
141
+ }
142
+ const resolve = await deps.runCommand("git", ["rev-parse", "--verify", `origin/${baseBranch}^{commit}`], { cwd: deps.cwd });
143
+ if (!commandSucceeded(resolve)) {
144
+ return {
145
+ ok: false,
146
+ error: `Failed to resolve origin/${baseBranch} to a commit SHA after fetch (git rev-parse --verify failed).`,
147
+ };
148
+ }
149
+ return { ok: true, base_sha: resolve.stdout.trim() };
150
+ });
151
+ }