@bridge_gpt/mcp-server 0.2.66 → 0.2.67
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 +252 -77
- package/build/agent-capabilities/probes.js +15 -5
- package/build/agent-launchers/claude-executor-adapter.js +43 -16
- package/build/agent-launchers/executor-adapter.js +7 -4
- package/build/agents.generated.js +1 -1
- package/build/bounded-wait.js +67 -10
- package/build/commands.generated.js +11 -11
- package/build/conductor/bridge-api-client.js +143 -1
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/doctor.js +3 -3
- package/build/conductor/epic-implementer-cli.js +22 -6
- package/build/conductor/errors.js +4 -3
- package/build/conductor/guided-repair.js +6 -2
- package/build/conductor/hook-runtime-qualification.js +1 -1
- package/build/conductor/merge-execution-contract.js +27 -8
- package/build/conductor/merge-execution-eligibility.js +3 -2
- package/build/conductor/pr-ci-producer.js +1 -1
- package/build/conductor/readiness-cli.js +18 -2
- package/build/conductor/readiness-sections.js +41 -6
- package/build/conductor/readiness.js +181 -29
- package/build/conductor/recovery-cli.js +200 -8
- package/build/conductor/recovery-operations.js +69 -3
- package/build/conductor/repair-contract.js +54 -3
- package/build/conductor/repository-workflow-remedy.js +125 -0
- package/build/conductor/tools.js +2 -2
- package/build/conductor-bundle-artifacts.js +8 -0
- package/build/docs.generated.js +8 -3
- package/build/drive-epic-key.js +6 -5
- package/build/drive-epic.js +85 -54
- package/build/epic-coherence-input.js +2 -1
- package/build/epic-implementer-bin.js +22 -20
- package/build/epic-implementer-bundle-cli.js +2 -2
- package/build/executor/cli.js +3 -2
- package/build/executor/completion-contract.js +48 -5
- package/build/executor/completion-supervision.js +4 -0
- package/build/executor/env.js +19 -0
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/http-client.js +20 -0
- package/build/executor/job-errors.js +25 -0
- package/build/executor/job-runner.js +225 -41
- package/build/executor/observation.js +6 -1
- package/build/executor/preflight.js +51 -14
- package/build/executor/required-work-ledger.js +6 -1
- package/build/executor/resume-pre-spawn.js +2 -0
- package/build/executor/runner.js +8 -2
- package/build/executor/service-unit.js +7 -1
- package/build/executor/worker-finalization.js +82 -29
- package/build/executor/worker-guard-hook.js +137 -33
- package/build/executor/worktree.js +82 -1
- package/build/index.js +403 -429
- package/build/install-bridge-conductor.js +8 -6
- package/build/install-bridge.js +3 -3
- package/build/phase-context-git-probe.js +68 -0
- package/build/phase-result-artifacts.js +173 -5
- package/build/pipelines.generated.js +18 -24
- package/build/plane/alembic-head.js +12 -3
- package/build/plane/base-worktree-guard.js +147 -0
- package/build/plane/cli.js +217 -17
- package/build/plane/code-snapshot.js +468 -0
- package/build/plane/defaults.js +48 -3
- package/build/plane/endpoint.js +41 -0
- package/build/plane/manifest.js +120 -1
- package/build/plane/member-roster.js +94 -19
- package/build/plane/preflight.js +30 -2
- package/build/plane/process-cwd.js +67 -0
- package/build/plane/restart.js +542 -0
- package/build/plane/shutdown.js +31 -0
- package/build/plane/status.js +143 -8
- package/build/plane/supervisor.js +586 -179
- package/build/plane/types.js +54 -2
- package/build/printed-remedy-commands.js +37 -0
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +22 -0
- package/build/setup-epic.js +44 -19
- package/build/sfcc/config.js +9 -0
- package/build/sfcc/credentials.js +100 -38
- package/build/sfcc/hostname.js +67 -0
- package/build/sfcc/permissions.js +17 -13
- package/build/sfcc/register.js +1 -1
- package/build/sfcc/tool-wrapper.js +39 -43
- package/build/sfcc/write-grants.js +17 -11
- package/build/sfcc/write-result.js +4 -3
- package/build/sfcc/writes-site-preference.js +20 -8
- package/build/start-tickets.js +179 -31
- package/build/tool-error-envelope.js +61 -0
- package/build/version.generated.js +3 -3
- package/build/visual-diff.js +22 -1
- package/build/worker-guard-hook-bin.js +3 -6
- package/build/worker-guard-hook-main.js +47 -0
- package/build/worktree-core.js +130 -4
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +849 -0
- package/docs/EPIC-IMPLEMENTER.md +662 -0
- package/docs/install/mcp-tool-integrations.md +24 -15
- package/docs/install/sfcc-integration.md +12 -4
- package/package.json +1 -1
- package/pipelines/plan-epic.json +0 -5
- package/pipelines/review-ticket.json +1 -1
- package/docs/CONDUCTOR.md +0 -605
- package/pipelines/idea-to-pr.json +0 -49
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
# Epic Implementer — driving a multi-ticket epic
|
|
2
|
+
|
|
3
|
+
The **Epic Implementer** drives a multi-ticket epic from an approved plan to
|
|
4
|
+
`done`. It dispatches each child ticket to an agent, watches CI and review, merges
|
|
5
|
+
each child PR, and re-indexes the repository after each merge.
|
|
6
|
+
|
|
7
|
+
> **Naming.** The Epic Implementer used to be called the "Conductor". It was
|
|
8
|
+
> renamed in BAPI-1077 to BAPI-1083, BAPI-1190, BAPI-1232, and BAPI-1233. A few
|
|
9
|
+
> real identifiers still carry the old name and are spelled as-is below: the
|
|
10
|
+
> `--conductor` flag on `start-tickets`, the `install conductor` subcommand, the
|
|
11
|
+
> `mcp_server/src/conductor/` source directory, and two retired `conductor` bins.
|
|
12
|
+
|
|
13
|
+
It has two parts. Keep them apart when you read this guide:
|
|
14
|
+
|
|
15
|
+
| Part | What it is | Opt-in? |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| **The v2 engine** | A server-side **reconciler** plus a local **executor**. This is what drives an epic. | No. It runs for every epic run. |
|
|
18
|
+
| **The observability layer** | A local event ledger, Claude Code lifecycle hooks, optional git hooks, and a supervisor tab for `start-tickets --conductor` runs. | Yes, per run, with `--conductor`. |
|
|
19
|
+
|
|
20
|
+
For the everyday `start-tickets` flags, see
|
|
21
|
+
[README → CLI Subcommands](../README.md#cli-subcommands).
|
|
22
|
+
|
|
23
|
+
## How the v2 engine works
|
|
24
|
+
|
|
25
|
+
| Half | Where it runs | What it does |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| **Reconciler** | Server-side, on the Bridge API worker, about every 30s | Picks up every run whose status is `active`, evaluates each ticket's gates, and queues executor jobs. It is the **only** component that decides a transition. Nothing to install or schedule. |
|
|
28
|
+
| **Executor** | Locally, on your machine | Polls for jobs, claims them, spawns worker agents, and heartbeats. Workers report facts; the reconciler decides. |
|
|
29
|
+
|
|
30
|
+
**Run statuses** are `planning`, `pending_approval`, `active`, `blocked`,
|
|
31
|
+
`abandoned`, and `done`. A paused, quarantined, or stopped run is `blocked` with a
|
|
32
|
+
typed run-control record that says why and what clears it.
|
|
33
|
+
|
|
34
|
+
**Ticket statuses** are `planned`, `spec_review`, `implementing`, `ci`,
|
|
35
|
+
`code_review`, `merging`, `parsing`, `smoke`, `needs_human`, `done`, and
|
|
36
|
+
`abandoned`. A ticket that needs a person **parks** as `needs_human`. See
|
|
37
|
+
[Recovering a parked run](#recovering-a-stuck-or-parked-run).
|
|
38
|
+
|
|
39
|
+
**The v1 `epic-implementer epic-tick` command is frozen.** It throws
|
|
40
|
+
`EPIC_TICK_V1_FROZEN` and advances nothing. There is nothing to schedule
|
|
41
|
+
locally. If you registered an epic-tick schedule with an older release, remove
|
|
42
|
+
it; it is a dead timer.
|
|
43
|
+
|
|
44
|
+
## Drive an epic: `drive-epic`
|
|
45
|
+
|
|
46
|
+
`drive-epic` is the **one entry point** for running an epic. It reads the
|
|
47
|
+
server's readiness report and routes to exactly one path. You never choose
|
|
48
|
+
between engines.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
npx -y @bridge_gpt/mcp-server drive-epic BAPI-405 \
|
|
52
|
+
--plan-file docs/tmp/epic-plans/<slug>/epic-plan.dag.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Run it from a Git checkout of the target repository. In one command it:
|
|
56
|
+
|
|
57
|
+
1. Checks readiness and, for an unattended run, the unattended prerequisites.
|
|
58
|
+
A check that fails here creates nothing.
|
|
59
|
+
2. Validates the plan with the server's real validator, then creates and approves
|
|
60
|
+
the run (it delegates this to `setup-epic`).
|
|
61
|
+
3. Starts a **run-scoped executor** for the run in the background (BAPI-1279). It
|
|
62
|
+
claims only that run's jobs, logs to `.bridge/executors/<run>.log`, and exits by
|
|
63
|
+
itself when the run is `done` or `abandoned`. Re-running `drive-epic` for the
|
|
64
|
+
same live run never starts a second one.
|
|
65
|
+
|
|
66
|
+
**Finding the plan.** `--plan-file` is optional. Without it, `drive-epic` looks
|
|
67
|
+
for `{docs_dir}/epic-plans/*/epic-plan.meta.json` naming this epic, where
|
|
68
|
+
`docs_dir` is `BAPI_DOCS_DIR` or `docs/tmp`. It uses exactly one match. No match,
|
|
69
|
+
or more than one, stops with the paths it found. The plan files come from the
|
|
70
|
+
epic planning flow (`/define-tickets` for an epic, or `/plan-epic`).
|
|
71
|
+
|
|
72
|
+
**A live run with no plan.** If the epic already has a live run and you pass no
|
|
73
|
+
plan, `drive-epic` creates nothing. It makes sure the run has an executor and
|
|
74
|
+
reports it.
|
|
75
|
+
|
|
76
|
+
**Preview first.** `--dry-run` resolves the target, reads readiness and any live
|
|
77
|
+
run, finds the plan, and runs the server's plan validation. It changes nothing.
|
|
78
|
+
Add `--json` for one machine-readable document. It exits `0` for create or
|
|
79
|
+
attach, and `1` for refuse or stop.
|
|
80
|
+
|
|
81
|
+
### Main flags
|
|
82
|
+
|
|
83
|
+
| Flag | Meaning |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `--plan-file <path>` | The plan DAG. Optional (see above). |
|
|
86
|
+
| `--repo <name>` | Repo name. Default: `BAPI_REPO_NAME` or `.bridge/config`. |
|
|
87
|
+
| `--feature-branch <name>` | Use your own epic branch name. |
|
|
88
|
+
| `--into-base` | Opt out of the epic branch; child PRs target the base branch. |
|
|
89
|
+
| `--attended` | Opt out of the unattended default (see below). |
|
|
90
|
+
| `--policy-file <path>` | The **complete** run policy, applied when the run is created. |
|
|
91
|
+
| `--review-policy <src>` | The review policy for this run only. |
|
|
92
|
+
| `--replace-policy` | Allow replacing a live run's stored policy with `--policy-file`. |
|
|
93
|
+
| `--launch-profile <name>` | Use a host-local launch profile approved by `install conductor --launch-profile <name>`. `BAPI_EPIC_LAUNCH_PROFILE` selects one too; the flag wins. |
|
|
94
|
+
| `--base-url <url>` | Explicit Bridge API target. It must agree with a profile. |
|
|
95
|
+
| `--dry-run`, `--json` | Read-only preview (see above). |
|
|
96
|
+
|
|
97
|
+
**Target.** With no profile, `--base-url`, or `BAPI_BASE_URL`, `drive-epic`
|
|
98
|
+
targets production.
|
|
99
|
+
|
|
100
|
+
### Unattended by default
|
|
101
|
+
|
|
102
|
+
A run on an epic branch is created **unattended**. Child PRs merge automatically
|
|
103
|
+
into the epic branch. The one **integration PR** from the epic branch into the
|
|
104
|
+
base branch stays human-gated: a person merges it. Pass `--attended` to opt out;
|
|
105
|
+
the run then has no auto-merge authorization.
|
|
106
|
+
|
|
107
|
+
An unattended run needs three things. Each is refused **by name** before any run
|
|
108
|
+
is created, and `drive-epic --dry-run` lists them:
|
|
109
|
+
|
|
110
|
+
| Prerequisite | How to satisfy it |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| Repository consent | On by default. The repository owner can switch "Allow unattended Epic Implementer runs" off and on in the Bridge admin panel's Setup page. |
|
|
113
|
+
| An escalation channel | A `--policy-file` that declares `notify.webhook_url`, **or** an email address on the account. `notify.local_sink` does not count. |
|
|
114
|
+
| Repository readiness | `.github/workflows/claude-review.yml` and `.github/workflows/epic-implementer-ci.yml` confirmed on the default branch. The refusal names what is missing. |
|
|
115
|
+
|
|
116
|
+
**Merge-execution eligibility.** If the repository cannot perform a merge without
|
|
117
|
+
a standing write credential, `setup-epic` refuses with
|
|
118
|
+
`merge_execution_ineligible`, names the remedy
|
|
119
|
+
`npx -y @bridge_gpt/mcp-server install conductor --merge-workflow --codeowner <@org/team|@user>`
|
|
120
|
+
(replace the placeholder with the one `@user` or `@org/team` who should review the
|
|
121
|
+
workflow file; Bridge cannot infer it), and ends with `No run was created.`
|
|
122
|
+
This applies to attended and unattended runs alike.
|
|
123
|
+
|
|
124
|
+
### Branch strategy
|
|
125
|
+
|
|
126
|
+
A plan with **two or more** nodes runs on a dedicated `epic/<KEY>` branch. Every
|
|
127
|
+
child PR targets that branch, and one **draft** integration PR targets the base
|
|
128
|
+
branch.
|
|
129
|
+
|
|
130
|
+
| You want | Pass |
|
|
131
|
+
|---|---|
|
|
132
|
+
| The epic branch on a plan with 2 or more nodes | *nothing* — it is the default |
|
|
133
|
+
| Your own branch name, or a branch for a single-node plan | `--feature-branch <name>` |
|
|
134
|
+
| Direct-to-base (the pre-BAPI-1009 behavior) | `--into-base` |
|
|
135
|
+
|
|
136
|
+
A single-node plan keeps the base branch by default. `--feature-branch` and
|
|
137
|
+
`--into-base` together are a parse error.
|
|
138
|
+
|
|
139
|
+
On an epic-branch run, a shadow-index check that cannot be proven right away
|
|
140
|
+
**holds, then parks**: independent tickets go on, a dependent ticket waits, and
|
|
141
|
+
past the deadline it parks as `needs_human`. The run's gate snapshot carries a
|
|
142
|
+
`shadow_hold` object naming the reason and the recovery.
|
|
143
|
+
|
|
144
|
+
### `setup-epic` (what `drive-epic` delegates to)
|
|
145
|
+
|
|
146
|
+
`setup-epic --epic-key <KEY> --plan-file <path>` creates the run, stores the
|
|
147
|
+
plan, and approves it. `drive-epic` calls it for you; call it yourself only when
|
|
148
|
+
you need its lower-level flags (`--plan-version`, `--local-only`).
|
|
149
|
+
|
|
150
|
+
- It validates the plan locally (unique keys, resolvable `depends_on`, no cycles),
|
|
151
|
+
then with the server's real validator, **before** creating anything. A bad plan
|
|
152
|
+
creates no run and incurs no automation-start charge.
|
|
153
|
+
- It is **idempotent**: re-running on an epic with a live run reuses that run.
|
|
154
|
+
- `--dry-run` runs the server's validation and stops. `--dry-run --local-only`
|
|
155
|
+
skips the server and is labeled `partial (local checks only)`.
|
|
156
|
+
|
|
157
|
+
Unlike `drive-epic`, `setup-epic` does not start an executor.
|
|
158
|
+
|
|
159
|
+
### One command for the whole session: `/drive-epic-run`
|
|
160
|
+
|
|
161
|
+
The `/drive-epic-run <EPIC> [<plan path>]` slash command wraps all of this for an
|
|
162
|
+
agent session. It previews with `drive-epic --dry-run`, asks you once, creates the
|
|
163
|
+
run through `drive-epic`, and then watches the run until it is terminal. It may
|
|
164
|
+
apply one sanctioned recovery verb at a time, within a fixed scope and budget, and
|
|
165
|
+
records each one. It never edits gate state or code.
|
|
166
|
+
|
|
167
|
+
## The executor
|
|
168
|
+
|
|
169
|
+
`drive-epic` starts the executor for you. Start one by hand only for a durable
|
|
170
|
+
host or a manual recovery:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
npx -y @bridge_gpt/mcp-server executor --repo <name> --epic-run-id <id>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- **A claim scope is required** (BAPI-1026). Pass exactly one of
|
|
177
|
+
`--epic-run-id <id>` (repeatable) or `--repo-wide`. With neither, the executor
|
|
178
|
+
refuses to start.
|
|
179
|
+
- **A base URL is required.** Pass `--base-url` or set `BAPI_BASE_URL`. The
|
|
180
|
+
executor never falls back to production.
|
|
181
|
+
- `--exit-when-run-terminal` exits once the one `--epic-run-id` run is finished.
|
|
182
|
+
- `--once` runs a single claim cycle. `--max-concurrent` defaults to 1.
|
|
183
|
+
- `executor watch <job-id>` attaches read-only to a job's worker log.
|
|
184
|
+
- `executor install-service --executor-id <id> ...` writes one launchd (macOS) or
|
|
185
|
+
systemd (Linux) user unit so an executor survives a reboot. For the guided,
|
|
186
|
+
consented setup, use `install conductor` instead.
|
|
187
|
+
|
|
188
|
+
> **Running against a local Bridge API server? Do not use `npx`.** The published
|
|
189
|
+
> package often lags the server's source. A newer server can send a `prompt_spec`
|
|
190
|
+
> the older executor rejects (`ContractError.Prompt ... unsupported placeholder`).
|
|
191
|
+
> Build and run the local tree so both come from the same commit:
|
|
192
|
+
>
|
|
193
|
+
> ```
|
|
194
|
+
> cd mcp_server && npm run build
|
|
195
|
+
> node mcp_server/build/index.js executor --repo <name> --epic-run-id <id>
|
|
196
|
+
> ```
|
|
197
|
+
|
|
198
|
+
**The plane.** `plane up|status|down` brings up a whole local stack in one
|
|
199
|
+
command: the Bridge API server, the dead-man observer, the reconciler worker, and
|
|
200
|
+
scoped executor lanes. It is for operators who run the Bridge API server
|
|
201
|
+
themselves, and it runs executors from that checkout's `mcp_server/build/`.
|
|
202
|
+
`drive-epic` starts or attaches to a plane on its own when `BAPI_BASE_URL` names
|
|
203
|
+
the local plane endpoint.
|
|
204
|
+
|
|
205
|
+
## The CI gate
|
|
206
|
+
|
|
207
|
+
Each ticket's `ci` gate reads `policy_json.required_checks` and waits for those
|
|
208
|
+
checks at the ticket PR's **current head SHA**.
|
|
209
|
+
|
|
210
|
+
**The server stamps the CI gate at first approval** when your policy declares
|
|
211
|
+
neither `required_checks` nor `no_ci_gate`:
|
|
212
|
+
|
|
213
|
+
1. If the repository's default done gate (`done_gate_config`, see
|
|
214
|
+
[below](#supervisor-config)) lists required checks, those become the run's
|
|
215
|
+
`required_checks`.
|
|
216
|
+
2. Otherwise, on an epic-branch run that auto-merges, and whose plan declares no
|
|
217
|
+
node-level checks, the server records `no_ci_gate: true`. That run's `ci` gate
|
|
218
|
+
passes without CI.
|
|
219
|
+
3. Otherwise nothing is stamped.
|
|
220
|
+
|
|
221
|
+
A run left with **no** effective check and **no** `no_ci_gate` declaration does
|
|
222
|
+
not pass trivially. Its tickets **park** (`required_checks_empty_no_mode`). An
|
|
223
|
+
auto-merge run on the base branch with no check parks as `required_checks_empty`.
|
|
224
|
+
|
|
225
|
+
To set the checks yourself, put them in the policy file:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{ "required_checks": ["epic-implementer-ci / gate"] }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
To change the policy of a live run, re-run with `--policy-file`. An identical
|
|
232
|
+
policy is a no-op. A different one is refused with a redacted diff unless you add
|
|
233
|
+
`--replace-policy`. For a targeted change, use `epic-implementer patch-policy`.
|
|
234
|
+
|
|
235
|
+
**Once gated:**
|
|
236
|
+
|
|
237
|
+
- A complete **red** check enters the bounded `ci_fix` repair path. A missing or
|
|
238
|
+
still-running check stays pending.
|
|
239
|
+
- A green result from an **older head** never satisfies the gate after a rebase
|
|
240
|
+
or force-push.
|
|
241
|
+
- In Bridge's own `epic-implementer-ci` workflow, PRs from forks, bots, and
|
|
242
|
+
branches other than same-repository `feature/BAPI-*` and `epic/**` get an
|
|
243
|
+
immediate green. That is cost control, not proof the suites ran.
|
|
244
|
+
|
|
245
|
+
## Plan metadata: `touched_files`
|
|
246
|
+
|
|
247
|
+
**Plan-time file-overlap serialization is on by default**
|
|
248
|
+
(`planner_file_overlap_serialization_enabled`). Before the plan is stored, two
|
|
249
|
+
sibling tickets that predict overlapping files gain a dependency edge. Siblings
|
|
250
|
+
with disjoint files stay parallel.
|
|
251
|
+
|
|
252
|
+
`touched_files` is that prediction. It is optional and never checked against the
|
|
253
|
+
real diff:
|
|
254
|
+
|
|
255
|
+
| In the node | Meaning | Result |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| Field **absent** | The node opts out of serialization | Accepted. No edge is added for it. |
|
|
258
|
+
| `"touched_files": []` | "This ticket predicts no overlap" | Accepted as declared and empty. |
|
|
259
|
+
| Present but **malformed** | Not a list, or a blank, absolute, or non-string path | **400** (`VALIDATION`) |
|
|
260
|
+
|
|
261
|
+
Paths are relative to the repository root.
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"plan_version": 1,
|
|
266
|
+
"nodes": [
|
|
267
|
+
{ "ticket_key": "BAPI-405", "status": "planned", "depends_on": [],
|
|
268
|
+
"touched_files": ["api/routes/jira_api.py"] }
|
|
269
|
+
],
|
|
270
|
+
"edges": []
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Declare it anyway. `setup-epic --dry-run` warns when it is missing; that is a
|
|
275
|
+
planning-quality signal, not a predicted failure. Serialization only **adds**
|
|
276
|
+
edges, so your own `depends_on` and `touched_files` combine safely.
|
|
277
|
+
|
|
278
|
+
Serialization is a scheduling aid, not a safety net. If a sibling's merge makes a
|
|
279
|
+
PR conflict, the reconciler sees it and routes the ticket to a rebase job, whether
|
|
280
|
+
or not the node declared `touched_files`.
|
|
281
|
+
|
|
282
|
+
## Recovering a stuck or parked run
|
|
283
|
+
|
|
284
|
+
**Diagnose first:**
|
|
285
|
+
|
|
286
|
+
- The `explain_run` MCP tool explains why a run, or one ticket, is stuck: current
|
|
287
|
+
gate, latest observation, outstanding jobs, lease, blockers, and head SHA.
|
|
288
|
+
- The `get_epic_snapshot` MCP tool returns the full run state.
|
|
289
|
+
- `notifications --repo <repo> --run <epic-key|run-id> [--follow]` prints the
|
|
290
|
+
run's notification feed.
|
|
291
|
+
- `epic-implementer epic-status --epic-key <KEY>` prints a read-only health summary.
|
|
292
|
+
|
|
293
|
+
Both MCP tools need the `epic_implementer` profile group (see
|
|
294
|
+
[MCP profile](#mcp-profile)).
|
|
295
|
+
|
|
296
|
+
**Then apply one verb.** These are CLI-only (never MCP tools) and run as
|
|
297
|
+
`npx -y @bridge_gpt/mcp-server epic-implementer <verb> ...` or through the
|
|
298
|
+
`epic-implementer` bin. Each takes `--epic-run-id <id>`.
|
|
299
|
+
|
|
300
|
+
| Verb | What it does |
|
|
301
|
+
| --- | --- |
|
|
302
|
+
| `stop-run` | Stop the run: block dispatch and cancel queued work. The record stays. |
|
|
303
|
+
| `abandon-run` | Abandon the run. Terminal and irreversible. |
|
|
304
|
+
| `resume-run [--clear-quarantine]` | Resume a paused run (a quarantined one only with the flag). |
|
|
305
|
+
| `unpark --ticket-key <key>` | Move a `needs_human` ticket back into its gates. |
|
|
306
|
+
| `adopt-current-head-and-unpark --ticket-key <key>` | Recover a ticket parked by a PR-head change: adopt the current head and unpark. |
|
|
307
|
+
| `continue-child --ticket-key <key> [--reason <text>]` | Continue a parked child's current attempt without a reset. |
|
|
308
|
+
| `continuation-receipt --ticket-key <key> [--request-id <id>]` | Read-only: whether a continuation request committed. |
|
|
309
|
+
| `resume-ci --ticket-key <key>` | Resume a child parked on a CI stop once CI is green. |
|
|
310
|
+
| `dispatch-remediation --ticket-key <key> --context-file <path>` | Queue a repair job carrying your notes (at most 16384 bytes). |
|
|
311
|
+
| `merge-child --pr <number>` | Merge the run's child PR through the shared merge service. |
|
|
312
|
+
| `scope-reparse --scope-id <scope>` | Re-drive the run's own index scope and schedule its parse. |
|
|
313
|
+
| `scope-catch-up --scope-id <scope>` | Replay merges the run's scope did not observe. |
|
|
314
|
+
| `patch-policy --patch-file <path>` | Merge-patch (RFC 7396) the run's policy. Protected paths are refused. |
|
|
315
|
+
|
|
316
|
+
Most verbs accept `--json`. A replay that changes nothing prints
|
|
317
|
+
`committed: false` and exits `0`. A refusal carries the server's closed code, such
|
|
318
|
+
as `TICKET_NOT_IN_RUN`.
|
|
319
|
+
|
|
320
|
+
**Check prerequisites.** `epic-implementer readiness` is a read-only advisory
|
|
321
|
+
report of every prerequisite (install, doctor, plane preflight, and server), each
|
|
322
|
+
with one named fix. It never runs a fix and always exits `0`.
|
|
323
|
+
`epic-implementer doctor` reports local health read-only.
|
|
324
|
+
|
|
325
|
+
## Setting up a repository: `install conductor`
|
|
326
|
+
|
|
327
|
+
`npx -y @bridge_gpt/mcp-server install conductor` bootstraps a repository for the
|
|
328
|
+
Epic Implementer. It runs a read-only doctor first, then asks for consent before
|
|
329
|
+
each write: the supervisor project defaults, an optional `claude-review` workflow,
|
|
330
|
+
adding `epic_implementer` to your MCP config, and the local git hooks.
|
|
331
|
+
|
|
332
|
+
`npx -y @bridge_gpt/mcp-server install conductor --merge-workflow --codeowner <@org/team|@user>`
|
|
333
|
+
generates the merge workflow and its CODEOWNERS fragment, pushes a branch with your own git, and
|
|
334
|
+
opens a PR with your own `gh`. It never merges that PR. Replace
|
|
335
|
+
`<@org/team|@user>` with the one reviewer of the workflow file before running;
|
|
336
|
+
Bridge cannot infer it.
|
|
337
|
+
|
|
338
|
+
## MCP profile
|
|
339
|
+
|
|
340
|
+
The Epic Implementer MCP tools are `get_epic_snapshot` and `explain_run`. They
|
|
341
|
+
register only when `BRIDGE_MCP_PROFILE` includes the `epic_implementer` group (or
|
|
342
|
+
`full`). The default profile is `core`, which does not include them.
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
BRIDGE_MCP_PROFILE=core,epic_implementer
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The old `conductor` profile token is retired and ignored. `epic-implementer
|
|
349
|
+
doctor` flags it.
|
|
350
|
+
|
|
351
|
+
## Binaries
|
|
352
|
+
|
|
353
|
+
| Bin | Purpose |
|
|
354
|
+
| --- | --- |
|
|
355
|
+
| `epic-implementer` | The CLI: the recovery verbs above, `readiness`, `doctor`, `epic-status`, `approve-plan`, `install-git-hooks`, and the observability-layer ledger commands (`emit-event`, `supervise`, `send-message`, `check-messages`, `purge`, `git-hook`, `file-scope-guard`). |
|
|
356
|
+
| `epic-implementer-claude-hook` | The Claude Code lifecycle hook for `--conductor` runs. |
|
|
357
|
+
| `conductor`, `conductor-claude-hook` | Retired names (renamed by BAPI-1080). They print a fixed migration message and exit `1`. |
|
|
358
|
+
|
|
359
|
+
The same CLI is available as the `epic-implementer` subcommand:
|
|
360
|
+
`npx -y @bridge_gpt/mcp-server epic-implementer <command>`.
|
|
361
|
+
|
|
362
|
+
## Environment-variable names and the legacy-name refusal (BAPI-1190)
|
|
363
|
+
|
|
364
|
+
Every Epic Implementer environment variable uses the `BAPI_EPIC_IMPLEMENTER_`
|
|
365
|
+
prefix. The worker Node path is `EPIC_IMPLEMENTER_NODE_PATH`. These replaced the
|
|
366
|
+
old names by a plain token swap: `CONDUCTOR` became `EPIC_IMPLEMENTER` and the
|
|
367
|
+
rest stayed the same. For example, the run-id variable is now
|
|
368
|
+
`BAPI_EPIC_IMPLEMENTER_RUN_ID`. There are no aliases and no dual reads.
|
|
369
|
+
|
|
370
|
+
The three dead-man variables that the Python worker reads were renamed the same
|
|
371
|
+
way: `EPIC_IMPLEMENTER_DEAD_MAN_ONLY`, `EPIC_IMPLEMENTER_DEADMAN_CHANNEL_TYPE`, and
|
|
372
|
+
`EPIC_IMPLEMENTER_DEADMAN_DESTINATION_REF`.
|
|
373
|
+
|
|
374
|
+
**Old names are refused, not ignored.** If any `BAPI_CONDUCTOR_*` variable,
|
|
375
|
+
`CONDUCTOR_NODE_PATH`, or an old `CONDUCTOR_DEAD_MAN_ONLY` /
|
|
376
|
+
`CONDUCTOR_DEADMAN_*` name is set, these entry points refuse:
|
|
377
|
+
|
|
378
|
+
| Entry point | On an old name |
|
|
379
|
+
|---|---|
|
|
380
|
+
| MCP server (bare startup) and every `npx @bridge_gpt/mcp-server <subcommand>` | stderr diagnostic, exit `1`, before any spawn, network call, ledger open, or file write |
|
|
381
|
+
| `epic-implementer` bin | stderr diagnostic, exit `1`, before argv reaches the CLI |
|
|
382
|
+
| `plane up` preflight | refused as the first check, before anything starts |
|
|
383
|
+
| `epic-implementer-claude-hook` bin | same stderr diagnostic, **exit `0`**, no ledger event |
|
|
384
|
+
| Bridge API `worker.py` (dead-man names only) | refuses at boot |
|
|
385
|
+
|
|
386
|
+
The hook exits `0` because a hook must never block the Claude tool loop. The two
|
|
387
|
+
retired `conductor` bins are not guarded; they already fail with a fixed message.
|
|
388
|
+
|
|
389
|
+
The diagnostic lists **every** offending key, sorted, in one message, with its
|
|
390
|
+
successor, a "retired — remove it" note, or generic advice for an unknown name. It
|
|
391
|
+
prints **key names only, never values**, because these variables can hold secrets.
|
|
392
|
+
Stale names usually live in shell profiles, hand-edited executor service units, or
|
|
393
|
+
CI settings.
|
|
394
|
+
|
|
395
|
+
**Version skew.** A worker environment injected by an **older** package still
|
|
396
|
+
carries old names, so a **newer** process started inside it refuses to start. This
|
|
397
|
+
is intended. Re-spawn the worker so the launcher and the process it starts use the
|
|
398
|
+
same package version. If that does not fix it, send the MCP package owner both
|
|
399
|
+
versions and the offending **key names only**.
|
|
400
|
+
|
|
401
|
+
The executor's worker-environment filter also strips both prefixes and both
|
|
402
|
+
node-path spellings, so an old name never reaches a worker it spawns.
|
|
403
|
+
|
|
404
|
+
## Observability layer (`--conductor`, opt-in, BAPI-394)
|
|
405
|
+
|
|
406
|
+
This layer is **off by default**. It is separate from the v2 engine and never
|
|
407
|
+
decides a transition. Without `--conductor`, `start-tickets` produces no
|
|
408
|
+
`BAPI_EPIC_IMPLEMENTER_*` env, supervisor tab, or message-relay prompt.
|
|
409
|
+
|
|
410
|
+
With `start-tickets --conductor`, a run mints one `run_id`, writes a `run.started`
|
|
411
|
+
event to the local ledger (`~/.config/bridge/events.db`), attributes each worker by
|
|
412
|
+
`worker_id`, ticket key, and worktree path, and opens a supervisor tab. With
|
|
413
|
+
**Claude Code**, it also adds a lifecycle hook to each worktree's
|
|
414
|
+
`.claude/settings.local.json` (keeping existing hooks). The hook streams
|
|
415
|
+
`run.started`, `run.stopped`, and `agent.notification` events, plus `tool.intent`
|
|
416
|
+
with `BAPI_EPIC_IMPLEMENTER_ENABLE_PRE_TOOL_USE=1`.
|
|
417
|
+
|
|
418
|
+
- Worker identity passes only through secret-free env scoped to one tab. No
|
|
419
|
+
credential is ever placed in env, the hook command, or run metadata.
|
|
420
|
+
- Override labels with `BAPI_EPIC_IMPLEMENTER_GATE_NAME` and
|
|
421
|
+
`BAPI_EPIC_IMPLEMENTER_SUPERVISOR_MODE`.
|
|
422
|
+
- Observability is best-effort. A failure never blocks a spawn, and `--dry-run`
|
|
423
|
+
has no side effects.
|
|
424
|
+
- Each worker's `BRIDGE_MCP_PROFILE` becomes the parent's resolved groups **plus**
|
|
425
|
+
`epic_implementer`, so the worker gets the two Epic Implementer tools. A plain
|
|
426
|
+
`start-tickets` run keeps the parent profile.
|
|
427
|
+
|
|
428
|
+
Inspect the stream with `epic-implementer doctor`. See also
|
|
429
|
+
[README → Environment Variables](../README.md#environment-variables).
|
|
430
|
+
|
|
431
|
+
## `epic-implementer install-git-hooks` (BAPI-395)
|
|
432
|
+
|
|
433
|
+
Installs local git hooks that emit git events into the local ledger:
|
|
434
|
+
|
|
435
|
+
```
|
|
436
|
+
epic-implementer install-git-hooks [--json]
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The hooks are **local, unversioned, opportunistic, and bypassable**. They live in
|
|
440
|
+
the worktree's git hooks directory (from `git rev-parse --git-common-dir`), add
|
|
441
|
+
only a clearly delimited managed block (keeping your own hook content), run the
|
|
442
|
+
producer **detached in the background** so a commit is never blocked, and end in
|
|
443
|
+
`|| true`. A directory that is not a git worktree, or an existing hook that looks
|
|
444
|
+
binary or unsafe, is left alone and reported as a **degraded optional
|
|
445
|
+
capability**, never a fatal error. The hooks are `post-commit` (emits
|
|
446
|
+
`git.commit_created`) and `reference-transaction` (emits `worktree.changed` for
|
|
447
|
+
committed ref updates).
|
|
448
|
+
|
|
449
|
+
Missing hooks do **not** block anything. `epic-implementer doctor` reports hook
|
|
450
|
+
state read-only, and the reconciler drives CI polling and gates either way.
|
|
451
|
+
|
|
452
|
+
### Durable hook installation (BAPI-1290)
|
|
453
|
+
|
|
454
|
+
A managed hook embeds one absolute entry path and runs it on every commit for as
|
|
455
|
+
long as the hook exists. So the installer embeds only a **qualified persistent
|
|
456
|
+
runtime** and refuses anything that can disappear under it.
|
|
457
|
+
|
|
458
|
+
**Supported installation.** Install the package persistently, then install the
|
|
459
|
+
hooks from that install:
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
npm i -g @bridge_gpt/mcp-server
|
|
463
|
+
epic-implementer install-git-hooks
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
A user-owned persistent prefix (`npm i -g --prefix <dir>`, with `<dir>/bin` on
|
|
467
|
+
PATH) works the same way. The hook embeds the fixed entry
|
|
468
|
+
`<prefix>/lib/node_modules/@bridge_gpt/mcp-server/build/epic-implementer-bin.js`
|
|
469
|
+
(POSIX single-quoted) and launches it with `node` from PATH. It never embeds npm's
|
|
470
|
+
`.bin` shim and never pins `process.execPath`.
|
|
471
|
+
|
|
472
|
+
Running the installer through `npx` works only as a front door: it looks up an
|
|
473
|
+
**already installed** global package with a bounded, offline `npm root -g` and
|
|
474
|
+
embeds that. With no qualified persistent install, it refuses.
|
|
475
|
+
|
|
476
|
+
**What "qualified" means.** Before touching any hook, the installer checks,
|
|
477
|
+
read-only:
|
|
478
|
+
|
|
479
|
+
| Check | Refusal classification |
|
|
480
|
+
|---|---|
|
|
481
|
+
| The entry is not inside the prunable `_npx` cache | `ephemeral-cache` |
|
|
482
|
+
| The canonical entry is a regular `build/epic-implementer-bin.js` | `missing` |
|
|
483
|
+
| It is not the retired `conductor-bin.js` name | `legacy-name` |
|
|
484
|
+
| It sits at `…/node_modules/@bridge_gpt/mcp-server/build/`, and the adjacent `package.json` has that `name`, a compatible `version` (`>=0.2.0 <1.0.0`), and `bin.epic-implementer` → `./build/epic-implementer-bin.js` | `unqualified-runtime` |
|
|
485
|
+
| The platform is macOS or Linux | `unqualified-runtime` |
|
|
486
|
+
| `node` from PATH launches the entry's private probe within 15 seconds | `node-unavailable` / `unknown` |
|
|
487
|
+
| That Node is an even LTS line ≥ 20 | `unqualified-runtime` |
|
|
488
|
+
| `better-sqlite3` loads for that Node | `native-unavailable` |
|
|
489
|
+
|
|
490
|
+
A refusal exits 1, prints only the classification and a fixed action (never a
|
|
491
|
+
path or error text), and leaves both hooks unchanged. Hooks are optional: a
|
|
492
|
+
refusal never blocks the engine.
|
|
493
|
+
|
|
494
|
+
**Qualified matrix.** Recorded evidence, not inference:
|
|
495
|
+
|
|
496
|
+
| OS | Install | Node | Native | Status |
|
|
497
|
+
|---|---|---|---|---|
|
|
498
|
+
| macOS 26 (Darwin 25.6, arm64) | persistent `npm i -g --prefix` (npm 11.13.0) | v24.16.0 (ABI 137) | better-sqlite3 13.0.3 | qualified by `packaged-git-hooks-lifetime.test.ts` |
|
|
499
|
+
| Linux | persistent `npm i -g` | even LTS ≥ 20 | better-sqlite3 13.x | accepted by policy; **not yet locally qualified** |
|
|
500
|
+
| Windows | any | any | any | **unqualified** — always refused |
|
|
501
|
+
| Any | `npx` cache only, version-manager switches (nvm/fnm/volta), other layouts | — | — | **unqualified** |
|
|
502
|
+
|
|
503
|
+
**Publication.** The installer plans both hooks before writing either. It refuses
|
|
504
|
+
a symlinked, non-regular, unreadable, or binary hook and changes nothing. Each
|
|
505
|
+
hook is written to a temporary file in the same directory, re-checked, and renamed
|
|
506
|
+
atomically over the target. A hook edited in the meantime is left alone
|
|
507
|
+
(`intervening-edit`). If one hook publishes and the other does not, the result is
|
|
508
|
+
`PARTIAL` (exit 1); re-running converges to one managed block per hook.
|
|
509
|
+
|
|
510
|
+
**Diagnosis.** `epic-implementer doctor` (and `install doctor`) report each hook's
|
|
511
|
+
`state` and `usable` verdict read-only. A hook that points into the npx cache
|
|
512
|
+
(`ephemeral-cache`), at a removed install (`missing`), at the retired name
|
|
513
|
+
(`legacy-name`), or at a runtime whose native binding no longer loads
|
|
514
|
+
(`native-unavailable`) is **unavailable**. The raw embedded path appears only in
|
|
515
|
+
`--json` output (`embedded_bin_path`).
|
|
516
|
+
|
|
517
|
+
**Repairing old cache-backed hooks.** Hooks installed from `npx` before BAPI-1290
|
|
518
|
+
point into the npx cache. Install persistently (`npm i -g @bridge_gpt/mcp-server`),
|
|
519
|
+
then re-run `epic-implementer install-git-hooks` (or `install conductor` and
|
|
520
|
+
accept the local-observability consent). The managed block is replaced in place.
|
|
521
|
+
|
|
522
|
+
**Upgrades and rollback.** `npm i -g @bridge_gpt/mcp-server@<version>` replaces
|
|
523
|
+
the package in place, so hooks keep working without a rewrite. To roll back,
|
|
524
|
+
install the previous version the same way, then run `epic-implementer doctor`.
|
|
525
|
+
|
|
526
|
+
**What is not promised.** Events that happen while hook delivery is unavailable
|
|
527
|
+
are **lost and not replayed**. Commits are never blocked or slowed, and the engine
|
|
528
|
+
never depends on hooks.
|
|
529
|
+
|
|
530
|
+
## Supervisor config
|
|
531
|
+
|
|
532
|
+
> **These are not config fields.** They used to be `conductor_done_gate` and
|
|
533
|
+
> `conductor_auto_merge_enabled` on the generic config-field route. BAPI-438 moved
|
|
534
|
+
> them to dedicated supervisor endpoints. Setting them through
|
|
535
|
+
> `PUT /jira/config-field/...` returns **HTTP 400 "Invalid config field"**, which
|
|
536
|
+
> is correct.
|
|
537
|
+
|
|
538
|
+
Each setting has a per-repo **project default** and a per-epic override:
|
|
539
|
+
|
|
540
|
+
| Setting | Endpoint (project default) |
|
|
541
|
+
| --- | --- |
|
|
542
|
+
| `done_gate_config` | `PUT /jira/epic-runs/supervisor-setup/defaults/?repo_name=<repo>` |
|
|
543
|
+
| `auto_merge_enabled`, `merge_approval_required` | `PUT /jira/epic-runs/supervisor-config/defaults/?repo_name=<repo>` |
|
|
544
|
+
|
|
545
|
+
Swap `…/defaults/` for `…/runs/{epic_key}/…` to scope a setting to one epic. The
|
|
546
|
+
matching `GET` returns the effective value with a `source` of `epic`,
|
|
547
|
+
`project_default`, or `none` (nothing configured; fails closed).
|
|
548
|
+
|
|
549
|
+
Since BAPI-1288 every project-default write is protected. Removing a configured
|
|
550
|
+
requirement needs a preview (`POST …/defaults/preview/`) and the exact approval it
|
|
551
|
+
returns. Turning `auto_merge_enabled` **on**, or dropping a required
|
|
552
|
+
`merge_approval_required`, on the project default is refused
|
|
553
|
+
(`409 NON_RELAXABLE`); ask Bridge support.
|
|
554
|
+
|
|
555
|
+
### `done_gate_config`
|
|
556
|
+
|
|
557
|
+
Defines the done gate. It supports one condition, `required_ci_checks_green`:
|
|
558
|
+
|
|
559
|
+
```json
|
|
560
|
+
{
|
|
561
|
+
"enabled": true,
|
|
562
|
+
"conditions": [
|
|
563
|
+
{ "type": "required_ci_checks_green", "required_checks": ["build", "test"] }
|
|
564
|
+
]
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
It serves two readers:
|
|
569
|
+
|
|
570
|
+
- **The v2 engine** takes its first-approval `required_checks` stamp from the
|
|
571
|
+
project default's checks (see [The CI gate](#the-ci-gate)).
|
|
572
|
+
- **The `merge_pull_request` tool and the observability-layer supervisor** emit
|
|
573
|
+
`gate.met` (once per repo, PR, head SHA, and config) only when every listed
|
|
574
|
+
check is present, complete, and green for the PR head. An unset, disabled,
|
|
575
|
+
malformed, or empty config emits nothing.
|
|
576
|
+
|
|
577
|
+
### `auto_merge_enabled`
|
|
578
|
+
|
|
579
|
+
This switch governs the `merge_pull_request` tool and the observability-layer
|
|
580
|
+
supervisor. It does **not** govern v2 epic merges: those read only the run's
|
|
581
|
+
`policy_json.v2_auto_merge_enabled`, which an unattended epic-branch run sets on.
|
|
582
|
+
|
|
583
|
+
**For the switch's own consumers, auto-merge is off by default.**
|
|
584
|
+
|
|
585
|
+
- **Off, unset, or unconfigured → dry-run.** The supervisor records a
|
|
586
|
+
`merge.dry_run` event and merges nothing.
|
|
587
|
+
- **On → autonomous merge** when the gate is met and the guards pass.
|
|
588
|
+
- **Kill-switch.** Set it to `false` to stop autonomous merges at once. The
|
|
589
|
+
protected merge endpoint re-checks the flag itself.
|
|
590
|
+
|
|
591
|
+
Merge authority is **deterministic code, never an LLM**. Every guard is bound to
|
|
592
|
+
**PR number + expected head SHA** (never a branch name): the enablement flag, the
|
|
593
|
+
PR still open, **no head-SHA drift** between gate and merge, and required checks
|
|
594
|
+
**re-validated green right before the merge**.
|
|
595
|
+
|
|
596
|
+
A TTL lease keyed by `merge:{repo}:{pr}:{head_sha}:{gate}` makes merging
|
|
597
|
+
crash-safe and race-safe; an existing `merge.succeeded` for that key is terminal.
|
|
598
|
+
The caller never holds VCS write credentials: it calls
|
|
599
|
+
`POST /vcs/pull-requests/{pr_number}/merge`, which owns the privileged merge. The
|
|
600
|
+
results are `merge.dry_run`, `merge.attempted`, `merge.succeeded`,
|
|
601
|
+
`merge.failed` (retryable), and `merge.pending_approval` (waits for a human).
|
|
602
|
+
**`merge.succeeded` is the only terminal merge event.**
|
|
603
|
+
|
|
604
|
+
**Who performs the write (BAPI-832).** GitHub's merge endpoint needs
|
|
605
|
+
`contents: write`, and the Bridge GPT App is `contents: read`. So two paths merge
|
|
606
|
+
with the operator's local `gh` session instead of the App token:
|
|
607
|
+
|
|
608
|
+
- the executor's `merge` job; and
|
|
609
|
+
- the `merge_pull_request` MCP tool, which receives `approved_for_local_execution`,
|
|
610
|
+
merges locally, and reports to `POST /vcs/pull-requests/{pr_number}/merge/complete`.
|
|
611
|
+
|
|
612
|
+
The server still owns every decision: the guards, the lease, and the final record.
|
|
613
|
+
The mode is `merge_execution` on the supervisor config (`local` by default,
|
|
614
|
+
`server` when the App holds `contents: write`). A caller that dies mid-merge is
|
|
615
|
+
recovered by lease expiry; a retry reports `already_merged` if the merge landed.
|
|
616
|
+
|
|
617
|
+
## Worker ledger hardening (BAPI-527)
|
|
618
|
+
|
|
619
|
+
This section applies to the observability layer. It keeps a worker's own ledger
|
|
620
|
+
calls reliable. It is not a completion backstop.
|
|
621
|
+
|
|
622
|
+
**The invariant: the worker Node never loads `better-sqlite3`.** Worker ledger
|
|
623
|
+
operations run through `epic-implementer-bin.js` under the Node that owns the
|
|
624
|
+
ledger. Dispatch passes that Node to each worker as `EPIC_IMPLEMENTER_NODE_PATH`,
|
|
625
|
+
and the packaged `epic-implementer-bin.js` path as
|
|
626
|
+
`BAPI_EPIC_IMPLEMENTER_CLI_FILE`. The worker side shells out with a fixed list
|
|
627
|
+
argv through `execFile` (never a shell string) and sends event JSON over stdin, so
|
|
628
|
+
payloads never enter the argument list. Processes that own the ledger (the CLI and
|
|
629
|
+
supervisor) call the store directly. The local ledger store is at schema version 8.
|
|
630
|
+
|
|
631
|
+
**Fail loud, never fall back.** A missing or invalid `EPIC_IMPLEMENTER_NODE_PATH`,
|
|
632
|
+
or a missing `BAPI_EPIC_IMPLEMENTER_CLI_FILE`, returns the
|
|
633
|
+
`LEDGER_SUBPROCESS_RUNTIME_UNAVAILABLE` (503) envelope with a path-free
|
|
634
|
+
`details: { env_var, reason }`. It never falls back to the worker's own Node.
|
|
635
|
+
`EPIC_IMPLEMENTER_NODE_PATH` is non-secret and is never written to `.mcp.json`.
|
|
636
|
+
|
|
637
|
+
**Rejected alternatives.**
|
|
638
|
+
|
|
639
|
+
- **Local HTTP sidecar** (a ledger daemon on a port): port conflicts, zombie
|
|
640
|
+
processes, and token handling, all too much for a file-backed ledger.
|
|
641
|
+
- **WASM / pure-JS SQLite in the worker**: it gives up `better-sqlite3`'s proven
|
|
642
|
+
WAL behavior and fits poorly with a database file shared by several processes.
|
|
643
|
+
|
|
644
|
+
### Three protective guarantees
|
|
645
|
+
|
|
646
|
+
1. **Ledger operations are isolated from the worker.** A Node ABI mismatch
|
|
647
|
+
degrades a worker convenience, never the whole session.
|
|
648
|
+
|
|
649
|
+
2. **Non-mutating dispatch.** Dispatch resolves the base with a fetch-only
|
|
650
|
+
`origin/<base>` lookup and cuts new worktrees from that SHA. It never checks
|
|
651
|
+
out, fast-forwards, force-moves, or stashes your live checkout. A live-source
|
|
652
|
+
guard also refuses dispatch when the base repo path equals your live dev-server
|
|
653
|
+
checkout:
|
|
654
|
+
- `BAPI_EPIC_IMPLEMENTER_LIVE_SOURCE_PATH` — the checkout to protect.
|
|
655
|
+
- `BAPI_EPIC_IMPLEMENTER_ALLOW_LIVE_SOURCE_DISPATCH` — a truthy override that
|
|
656
|
+
turns a collision into a loud warning.
|
|
657
|
+
`doctor` reports the guard state (`not-configured`, `safe`, or `collision`).
|
|
658
|
+
|
|
659
|
+
3. **Parse runs in a separate process.** The post-merge re-index runs outside the
|
|
660
|
+
API server's event loop, so the server stays responsive during a parse. A
|
|
661
|
+
merged ticket enters `parsing` and moves on only after the parse succeeds. A failed parse is retried within a budget and then parks the ticket
|
|
662
|
+
as `needs_human`; it never silently wedges the epic.
|