@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.
- package/README.md +144 -18
- package/build/base-ref.js +151 -0
- package/build/commands.generated.js +6 -4
- package/build/conductor/bridge-api-client.js +44 -3
- package/build/conductor/doctor.js +33 -22
- package/build/conductor/epic-runtime.js +101 -5
- package/build/conductor/pr-ci-producer.js +21 -2
- package/build/conductor/pr-discovery.js +12 -2
- package/build/conductor-bin.js +50 -20
- package/build/credential-store.js +564 -64
- package/build/decision-page-template.js +9 -4
- package/build/docs.generated.js +5 -0
- package/build/executor/base-branch.js +50 -0
- package/build/executor/env.js +12 -1
- package/build/executor/job-errors.js +1 -0
- package/build/executor/job-runner.js +38 -7
- package/build/executor/test-clock.js +6 -1
- package/build/executor/worker-finalization.js +88 -1
- package/build/executor/worktree.js +21 -1
- package/build/index.js +2741 -702
- package/build/init.js +29 -0
- package/build/install-bridge.js +1076 -114
- package/build/pipelines.generated.js +2 -2
- package/build/pr-base-contract.js +36 -0
- package/build/readme.generated.js +1 -1
- package/build/setup-epic.js +483 -0
- package/build/sfcc/log-gate.js +85 -0
- package/build/sfcc/log-query.js +170 -0
- package/build/sfcc/register.js +10 -0
- package/build/sfcc/setup-status.js +33 -3
- package/build/start-tickets.js +164 -75
- package/build/version.generated.js +1 -1
- package/build/worktree-core.js +62 -10
- package/{CONDUCTOR.md → docs/CONDUCTOR.md} +88 -29
- package/docs/install/github-app.md +189 -0
- package/docs/install/mcp-tool-integrations.md +305 -0
- package/docs/install/sfcc-integration.md +140 -0
- package/package.json +5 -5
- package/public/js/main.min.js +55 -10
- package/public/js/main.min.js.map +1 -1
- 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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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`
|
|
71
|
-
remaining config fields from your codebase
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
|
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,
|
|
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 `
|
|
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
|
+
}
|