@bridge_gpt/mcp-server 0.2.65 → 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.
Files changed (132) hide show
  1. package/README.md +269 -82
  2. package/build/agent-capabilities/probes.js +15 -5
  3. package/build/agent-launchers/claude-executor-adapter.js +55 -34
  4. package/build/agent-launchers/executor-adapter.js +34 -4
  5. package/build/agents.generated.js +1 -1
  6. package/build/bounded-wait.js +67 -10
  7. package/build/bridge-config.js +104 -0
  8. package/build/codex-bridge-profile.js +10 -1
  9. package/build/commands.generated.js +12 -12
  10. package/build/conductor/bridge-api-client.js +372 -10
  11. package/build/conductor/bring-up-facts.js +1 -1
  12. package/build/conductor/doctor.js +3 -3
  13. package/build/conductor/epic-child-continuation.js +61 -34
  14. package/build/conductor/epic-implementer-cli.js +34 -6
  15. package/build/conductor/errors.js +4 -3
  16. package/build/conductor/guided-repair.js +327 -0
  17. package/build/conductor/hook-runtime-qualification.js +1 -1
  18. package/build/conductor/merge-execution-contract.js +27 -8
  19. package/build/conductor/merge-execution-eligibility.js +3 -2
  20. package/build/conductor/pr-ci-producer.js +1 -1
  21. package/build/conductor/readiness-cli.js +46 -9
  22. package/build/conductor/readiness-sections.js +41 -6
  23. package/build/conductor/readiness.js +204 -30
  24. package/build/conductor/recovery-cli.js +703 -20
  25. package/build/conductor/recovery-operations.js +392 -3
  26. package/build/conductor/repair-contract.js +112 -3
  27. package/build/conductor/repository-workflow-remedy.js +125 -0
  28. package/build/conductor/tools.js +2 -2
  29. package/build/conductor-bundle-artifacts.js +8 -0
  30. package/build/credential-store.js +5 -4
  31. package/build/diagnostic-text.js +36 -0
  32. package/build/docs.generated.js +8 -3
  33. package/build/doctor.js +70 -10
  34. package/build/drive-epic-key.js +6 -5
  35. package/build/drive-epic.js +479 -131
  36. package/build/epic-coherence-input.js +2 -1
  37. package/build/epic-implementer-bin.js +30 -26
  38. package/build/epic-implementer-bundle-cli.js +2 -2
  39. package/build/epic-launch-diagnostic.js +31 -0
  40. package/build/epic-launch-profile.js +44 -14
  41. package/build/executor/cli.js +3 -2
  42. package/build/executor/completion-contract.js +48 -5
  43. package/build/executor/completion-supervision.js +9 -0
  44. package/build/executor/correction-dispatch.js +60 -0
  45. package/build/executor/env.js +25 -0
  46. package/build/executor/heartbeat.js +1 -1
  47. package/build/executor/http-client.js +35 -0
  48. package/build/executor/integration-job.js +6 -1
  49. package/build/executor/job-errors.js +38 -4
  50. package/build/executor/job-log-registry.js +340 -73
  51. package/build/executor/job-runner.js +485 -74
  52. package/build/executor/job-types.js +8 -13
  53. package/build/executor/observation.js +6 -1
  54. package/build/executor/preflight.js +136 -10
  55. package/build/executor/prompt-spec.js +113 -9
  56. package/build/executor/required-work-ledger.js +6 -1
  57. package/build/executor/resume-pre-spawn.js +2 -0
  58. package/build/executor/runner.js +31 -2
  59. package/build/executor/service-unit.js +7 -1
  60. package/build/executor/stale-artifacts.js +5 -4
  61. package/build/executor/types.js +1 -0
  62. package/build/executor/watch-cli.js +35 -8
  63. package/build/executor/worker-command.js +43 -43
  64. package/build/executor/worker-finalization.js +161 -43
  65. package/build/executor/worker-guard-hook.js +152 -12
  66. package/build/executor/worker-log.js +137 -70
  67. package/build/executor/worktree-gc.js +30 -6
  68. package/build/executor/worktree.js +82 -1
  69. package/build/index.js +482 -660
  70. package/build/init.js +107 -4
  71. package/build/install-bridge-conductor.js +8 -6
  72. package/build/install-bridge.js +287 -23
  73. package/build/install-doctor.js +172 -30
  74. package/build/install-reexec.js +69 -7
  75. package/build/macos-terminal-permission-guidance.js +16 -0
  76. package/build/model-thresholds-tool.js +185 -0
  77. package/build/phase-context-git-probe.js +68 -0
  78. package/build/phase-result-artifacts.js +173 -5
  79. package/build/pipelines.generated.js +19 -25
  80. package/build/plane/alembic-head.js +12 -3
  81. package/build/plane/base-worktree-guard.js +147 -0
  82. package/build/plane/cli.js +217 -17
  83. package/build/plane/code-snapshot.js +468 -0
  84. package/build/plane/defaults.js +48 -3
  85. package/build/plane/endpoint.js +41 -0
  86. package/build/plane/manifest.js +120 -1
  87. package/build/plane/member-roster.js +94 -19
  88. package/build/plane/preflight.js +30 -2
  89. package/build/plane/process-cwd.js +67 -0
  90. package/build/plane/restart.js +542 -0
  91. package/build/plane/shutdown.js +31 -0
  92. package/build/plane/status.js +145 -6
  93. package/build/plane/supervisor.js +588 -177
  94. package/build/plane/types.js +54 -2
  95. package/build/plane/worker-exit-code.js +2 -0
  96. package/build/printed-remedy-commands.js +37 -0
  97. package/build/project-root.js +99 -0
  98. package/build/readiness-check.js +1 -1
  99. package/build/readme.generated.js +1 -1
  100. package/build/review-generation.js +22 -0
  101. package/build/review-tickets.js +156 -13
  102. package/build/setup-epic.js +224 -127
  103. package/build/sfcc/config.js +9 -0
  104. package/build/sfcc/credentials.js +100 -38
  105. package/build/sfcc/hostname.js +67 -0
  106. package/build/sfcc/permissions.js +17 -13
  107. package/build/sfcc/register.js +1 -1
  108. package/build/sfcc/setup-status.js +2 -0
  109. package/build/sfcc/tool-wrapper.js +41 -43
  110. package/build/sfcc/tools-configured.js +96 -0
  111. package/build/sfcc/write-grants.js +17 -11
  112. package/build/sfcc/write-result.js +4 -3
  113. package/build/sfcc/writes-site-preference.js +20 -8
  114. package/build/start-tickets.js +332 -79
  115. package/build/tool-error-envelope.js +61 -0
  116. package/build/upgrade-cli.js +109 -24
  117. package/build/version.generated.js +3 -3
  118. package/build/visual-diff.js +22 -1
  119. package/build/windows-npm-entry.js +103 -0
  120. package/build/worker-guard-hook-bin.js +3 -5
  121. package/build/worker-guard-hook-main.js +47 -0
  122. package/build/worktree-core.js +136 -9
  123. package/docs/EPIC-IMPLEMENTER-INSTALL.md +849 -0
  124. package/docs/EPIC-IMPLEMENTER.md +662 -0
  125. package/docs/install/mcp-tool-integrations.md +25 -15
  126. package/docs/install/sfcc-integration.md +21 -8
  127. package/package.json +1 -1
  128. package/pipelines/implement-ticket.json +1 -1
  129. package/pipelines/plan-epic.json +0 -5
  130. package/pipelines/review-ticket.json +1 -1
  131. package/docs/CONDUCTOR.md +0 -605
  132. package/pipelines/idea-to-pr.json +0 -49
package/docs/CONDUCTOR.md DELETED
@@ -1,605 +0,0 @@
1
- # Conductor — epic & multi-agent orchestration
2
-
3
- Conductor is the **opt-in, off-by-default** coordination layer for running many
4
- agent sessions together (epic supervision, inter-agent messaging, done-gate
5
- evaluation, and conditional auto-merge). A normal `start-tickets` run does **not**
6
- involve Conductor — you opt in per run with `--conductor`.
7
-
8
- This document is the reference for Conductor's architecture, epic setup,
9
- observability stream, local git hooks, and the per-repo done-gate / auto-merge
10
- config. For the everyday `start-tickets` flags and cross-platform behavior, see
11
- [README → CLI Subcommands](../README.md#cli-subcommands).
12
-
13
- ## Epic Conductor v2 — how an epic is actually driven
14
-
15
- **The v1 `epic-implementer epic-tick` command is frozen.** It throws
16
- `EPIC_TICK_V1_FROZEN` on every invocation and advances nothing. There is nothing
17
- to schedule locally. If you have an epic-tick schedule registered from an earlier
18
- release, remove it — it is a dead timer.
19
- `epic-implementer doctor` flags one if it finds it. (S4/BAPI-1080 renamed the
20
- event-ledger CLI from `conductor` to `epic-implementer`; the retained
21
- `conductor` bin name is now a fixed migration pointer.)
22
-
23
- v2 splits the old local tick into two halves:
24
-
25
- | Half | Where it runs | What it does |
26
- | --- | --- | --- |
27
- | **Reconciler** | **Server-side**, on the Bridge API worker dyno, every 30s | Selects every epic run whose status is `active`, evaluates gates, and enqueues executor jobs. Nothing to install or schedule. |
28
- | **Executor** | **Locally**, on your machine | Polls for jobs, claims them, spawns worker agents, heartbeats. This is the only piece you run. |
29
-
30
- > **⚠️ For a run against a LOCAL server, do NOT use `npx`.** The npm-published
31
- > package routinely lags `main` — that is the normal steady state right after any
32
- > conductor ticket merges. When the server is built from a newer `main` it mints
33
- > `prompt_spec` placeholders the older published executor rejects, and the job dies
34
- > instantly with `ContractError.Prompt` (`prompt_spec for job N declares unsupported
35
- > placeholder '…'`). Confirmed on the BAPI-716 run: published `0.2.34` vs. local
36
- > `0.2.35`, over BAPI-699's `RELATED_CONTEXT`. Build and run the local tree so the
37
- > executor and server come from the same commit:
38
- >
39
- > ```
40
- > cd mcp_server && npm run build
41
- > node mcp_server/build/index.js executor --repo <name>
42
- > ```
43
-
44
- For a run against a deployed server whose version you match, the published CLI is
45
- fine:
46
-
47
- ```
48
- npx -y @bridge_gpt/mcp-server executor --repo <name>
49
- ```
50
-
51
- The reconciler drives a run purely off `epic_runs.status = 'active'` — there is no
52
- local schedule, and no per-repo "enable the tick" flag. (An `epic_tick_enabled`
53
- config field existed briefly and was inert; it has been removed.)
54
-
55
- ## Setting up an epic
56
-
57
- One command creates the run, stores the plan, and approves it:
58
-
59
- ```
60
- npx -y @bridge_gpt/mcp-server setup-epic --epic-key BAPI-405 \
61
- --plan-file docs/tmp/epic-plans/<slug>/epic-plan.dag.json
62
- ```
63
-
64
- The plan sidecar (`epic-plan.dag.json`) is produced by the `decompose-epic`
65
- pipeline step. `setup-epic` validates it locally (unique ticket keys, resolvable
66
- `depends_on`/edge references, acyclicity) before sending anything, so a malformed
67
- plan fails legibly instead of as a bare HTTP 400.
68
-
69
- ### Branch strategy: the epic branch is the default (BAPI-1009)
70
-
71
- The command above — no branch flag — now runs a **multi-ticket** epic on a
72
- dedicated `epic/<KEY>` branch. Every child-ticket PR targets that branch, and one
73
- **draft** integration PR targets the repository base branch. This applies on every
74
- path, CI and `--json` included. `drive-epic` forwards both flags below to
75
- `setup-epic`, so the universal entry point can select either strategy.
76
-
77
- | You want | Pass |
78
- |---|---|
79
- | The epic branch on a plan with **2 or more** nodes | *nothing* — it is the default |
80
- | Your own branch name, or a branch for a **single-node** plan | `--feature-branch <name>` |
81
- | The old direct-to-base behavior | `--into-base` |
82
-
83
- > **Behavior change.** Before BAPI-1009 a run with no branch flag went straight to
84
- > the repository base branch. Automation that relied on that must now pass
85
- > `--into-base`. A single-node plan still keeps the base branch by default, matching
86
- > plain `start-tickets`. `--feature-branch` and `--into-base` together are a parse
87
- > error, refused before any file read, credential resolution, or network call.
88
-
89
- On an epic-branch run, a shadow-index verification that cannot be proven
90
- immediately resolves as bounded **hold-then-park** rather than blocking the run:
91
- independent tickets proceed, a dependent holds, and past the deadline it parks
92
- through the ordinary `needs_human` path. The epic run's gate snapshot carries a
93
- run-level `shadow_hold` object naming the reason and the recovery while that is
94
- live. This addresses BAPI-993 run issue A1.
95
-
96
- Since BAPI-754 it then calls the server's **real** validator
97
- (`POST /jira/epic-runs/plan/validate`) before creating anything, so a plan the
98
- server would reject — a node missing `status`, say, which the local checks do not
99
- cover — no longer creates a run and incurs an automation-start charge on its way
100
- to failing.
101
-
102
- It is **idempotent**: re-running it on an epic that already has a live run reuses
103
- that run rather than minting a second one. Use `--dry-run` to run that
104
- authoritative validation and preview the calls without mutating anything; add
105
- `--local-only` to skip the server entirely, which reports
106
- `partial (local checks only)` and is never a substitute for the server's verdict.
107
-
108
- Once the plan is approved the run becomes `active`, the server-side reconciler
109
- picks it up within ~30s, and your local `executor` starts claiming jobs.
110
-
111
- ### `touched_files` — optional, but declare it anyway
112
-
113
- **Plan-time file-overlap serialization is live and on by default.**
114
- `planner_file_overlap_serialization_enabled` (BAPI-495, defaulted on by BAPI-677 /
115
- migration 0126, on `config_code_repositories`) makes overlapping siblings gain a
116
- `depends_on`/edge on each other *before* the DAG is validated, hashed, and stored,
117
- while siblings with disjoint files stay parallel.
118
-
119
- `touched_files` is the plan-time metadata that pass reads. It is a **best-effort
120
- prediction, never reconciled against the real diff**, so BAPI-722 made it optional.
121
- Three outcomes, and the difference between the first two is the point
122
- (`_extract_predicted_touched_files`, `api/library/db/epic_runs.py:3195`):
123
-
124
- | In the node | Meaning | Result |
125
- |---|---|---|
126
- | Field **absent** | The node **opts out** of preemptive serialization | **Accepted.** Every pair involving it is skipped (`undeclared_pairs_skipped`); no edge is added in either direction |
127
- | `"touched_files": []` | A deliberate "this ticket predicts no file overlap" | Accepted as a *declared* node with an empty set |
128
- | Field present but **malformed** | A non-list, or a list holding a blank, absolute, or non-string path | **400** — `EpicRunConflictError(code="VALIDATION")` |
129
-
130
- Only omission is relaxed. Data you *did* supply must be well-formed. Paths are
131
- repo-root-relative.
132
-
133
- ```json
134
- {
135
- "plan_version": 1,
136
- "nodes": [
137
- { "ticket_key": "BAPI-405", "status": "planned", "depends_on": [],
138
- "touched_files": ["api/routes/jira_api.py"] }
139
- ],
140
- "edges": []
141
- }
142
- ```
143
-
144
- > **`setup-epic --dry-run` warns on a missing `touched_files`, and that warning is
145
- > a planning-quality signal, not a predicted failure.** The server accepts the
146
- > plan. What you lose is scheduling protection: an undeclared node is never
147
- > pre-serialized against its siblings.
148
-
149
- Declare it anyway. Serialization only ever **adds** edges — it never removes an
150
- authored one — so hand-authored `depends_on` and `touched_files` compose safely.
151
- Author both: the edges as your reviewable statement of intent, `touched_files` so
152
- the scheduler can catch overlaps you did not anticipate.
153
-
154
- > **Serialization is a scheduling optimization, not a safety mechanism.** It avoids
155
- > *dispatching* file-overlapping siblings in parallel; it does not resolve a
156
- > conflict once one exists. The safety net is reactive and independent of any
157
- > prediction: a PR that a sibling's merge turns `CONFLICTING` is observed live and
158
- > routed to `JOB_TYPE_REBASE` (`_merge_entry_failure_requires_rebase` in the
159
- > reconciler). That backstop covers declared and undeclared nodes alike — which is
160
- > exactly why omitting `touched_files` is safe to accept.
161
-
162
- ## The conductor CI gate — `epic-implementer-ci / gate` (BAPI-695)
163
-
164
- **Before driving a run you want CI-gated, wire its policy.** The reconciler's
165
- per-ticket `ci` gate reads **`policy_json.required_checks`** (a declared
166
- `RunPolicy` key) and observes those check-run conclusions at the ticket PR's
167
- **current head SHA**. If that list is empty, the observer short-circuits to
168
- `PASSED` with `{"no_required_checks": true}` and **runs no CI at all** — the loop
169
- merges on hand-verification only. `.github/workflows/epic-implementer-ci.yml` publishes
170
- the real, always-reporting required context **`epic-implementer-ci / gate`**; set
171
-
172
- ```json
173
- "policy_json": { "required_checks": ["epic-implementer-ci / gate"] }
174
- ```
175
-
176
- on every run you want gated. The workflow existing is not enough — the context has
177
- to be in the run policy.
178
-
179
- > **Wire it at creation with `--policy-file` (BAPI-754).** There is still no
180
- > `--required-checks` option, but `setup-epic --policy-file <path>` sends the
181
- > **complete** run policy on the create that mints the run, so the run is born with
182
- > `required_checks` already in force. A run created *without* a policy still starts
183
- > with an **empty** `required_checks` and a `ci` gate that short-circuits to
184
- > `PASSED` — and because `setup-epic` also approves the plan, making the run
185
- > `active` and visible to the reconciler within ~30s, there is no comfortable window
186
- > to fix that afterwards. Supply the policy file up front.
187
- >
188
- > To change the policy on a run that is already live, re-run with `--policy-file`:
189
- > an identical policy is a no-op, and a divergent one is **refused** with a redacted
190
- > diff unless you also pass `--replace-policy` (which applies it as a complete
191
- > replacement).
192
-
193
- > **This is NOT `done_gate_config`.** The per-run `policy_json.required_checks`
194
- > read by the reconciler `ci` gate is a *different* surface from the supervisor
195
- > `done_gate_config.required_ci_checks_green` documented under "Supervisor config"
196
- > below. BAPI-695 wires only the former, per run; it does **not** change
197
- > `done_gate_config` defaults, repo-default policy, or GitHub branch protection.
198
-
199
- **Once gated, expect this behavior:**
200
-
201
- - The `ci` gate now waits for `epic-implementer-ci / gate` on the ticket PR's current head.
202
- A complete **red** aggregator enters the bounded `ci_fix` remediation path; a
203
- **missing or still-running** current-head context stays pending (never a hard
204
- fail).
205
- - A **green from an older head** can never satisfy the gate after a rebase or
206
- force-push — the poll is bound to the current head SHA, and the workflow's per-PR
207
- concurrency cancels the superseded run so the new head publishes a fresh context.
208
- - Out-of-scope PRs (forks, `dependabot/*`, non-`feature/BAPI-*`) get an intentional
209
- fast green — cost control, **not** proof the suites ran.
210
-
211
- For the exact new-run/PATCH procedure (and the **whole-object `policy_json`
212
- replacement** caution — GET, merge `required_checks` locally, PATCH the whole
213
- object), and the immutable case-sensitive matching of the context string, see the
214
- operator runbook, **§4a "Wire the conductor CI required check"**
215
- ([`docs/claude/epic-implementer-v2-operator-runbook.md`](../../docs/claude/epic-implementer-v2-operator-runbook.md)).
216
-
217
- ## Conductor observability (opt-in via `--conductor`, BAPI-394)
218
-
219
- Conductor is **off by default** — without `--conductor` no `BAPI_EPIC_IMPLEMENTER_*` env,
220
- supervisor tab, or message-relay prompt is produced. With `--conductor`, a run mints
221
- a single conductor `run_id` and emits one canonical `run.started` event into the
222
- local conductor ledger (`~/.config/bridge/events.db`), attributing each worker by
223
- `worker_id`, ticket key, and worktree path, and opens a supervisor peer tab. When
224
- the selected agent is **Claude Code**, the CLI also injects a conductor lifecycle
225
- hook into each created worktree's `.claude/settings.local.json` (preserving any
226
- existing hooks) so the spawned session streams local `run.started` / `run.stopped` /
227
- `agent.notification` (and, with `BAPI_EPIC_IMPLEMENTER_ENABLE_PRE_TOOL_USE=1`,
228
- `tool.intent`) events. Per-worker conductor identity is passed only via secret-free
229
- environment scoped to that one terminal/tab/session — no credentials are ever placed
230
- in the env, hook command, or run metadata. Override the gate/supervisor labels with
231
- `BAPI_EPIC_IMPLEMENTER_GATE_NAME` / `BAPI_EPIC_IMPLEMENTER_SUPERVISOR_MODE`. Inspect the stream
232
- with `epic-implementer doctor`. Observability is best-effort: a conductor failure never
233
- blocks or aborts a spawn, and `--dry-run` performs no conductor side effects.
234
- (Epic dispatch always enables conductor internally, independent of this flag.)
235
-
236
- When `--conductor` is set, the spawn boundary also injects
237
- `BRIDGE_MCP_PROFILE=conductor` so each worker registers the 8 conductor/event/
238
- supervisor MCP tools (a plain `start-tickets` run stays on the default `core`
239
- profile). See [README → Environment Variables](../README.md#environment-variables).
240
-
241
- ### Environment-variable names and the legacy-name refusal (BAPI-1190)
242
-
243
- Every Epic Implementer environment variable uses the `BAPI_EPIC_IMPLEMENTER_`
244
- prefix, and the worker Node path is `EPIC_IMPLEMENTER_NODE_PATH`. These replaced
245
- the old Conductor names by a plain token swap: `CONDUCTOR` became
246
- `EPIC_IMPLEMENTER` and the rest of the name stayed the same. For example, the old
247
- run-id variable is now `BAPI_EPIC_IMPLEMENTER_RUN_ID`. There are no aliases and no
248
- dual reads.
249
-
250
- **Old names are refused, not ignored.** If any variable with the old
251
- `BAPI_CONDUCTOR_` prefix, or the old `CONDUCTOR_NODE_PATH`, is set, these entry
252
- points refuse to start:
253
-
254
- | Entry point | On an old name |
255
- |---|---|
256
- | 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 |
257
- | `epic-implementer` bin | stderr diagnostic, exit `1`, before argv reaches the CLI |
258
- | `epic-implementer-claude-hook` bin | same stderr diagnostic, **exit `0`**, no ledger event |
259
-
260
- The hook is the one exception to the non-zero exit. A hook must never block the
261
- Claude tool loop, so it stays loud but exits `0`. The two removed `conductor`
262
- stub bins are not guarded; they already fail with a fixed migration message.
263
-
264
- The diagnostic lists **every** offending key, sorted, in one message. Each line
265
- gives the exact successor, a "retired — remove it" note (the old MCP shadowing
266
- override), or, for an unknown old name, the generic advice: rename the prefix if
267
- it is still a current setting, otherwise remove it. It prints **key names only,
268
- never values**, because these variables can hold secrets. Old names copied from
269
- frozen records (`docs/BAPI-*.md`, run notes) are refused the same way. Stale
270
- names usually live in shell profiles, hand-edited executor service units, or CI
271
- environment configuration.
272
-
273
- **Version skew.** A worker environment injected by an **older** published package
274
- still carries the old names. A **newer** TypeScript process started inside it (for
275
- example the MCP server launched through `mcp-invoke`) will refuse to start. This
276
- is intended: it is the loud outcome, not a silent half-configured worker.
277
-
278
- - **Remedy:** re-spawn the worker so the launcher and the process it starts run
279
- the same package version.
280
- - **If that does not fix it:** send the MCP package owner the launcher version,
281
- the process version, and the offending **key names only** — never their values.
282
-
283
- The executor's worker-environment filter rejects both whole prefixes and both
284
- node-path spellings, so an old ambient name never reaches a worker it spawns.
285
-
286
- **No Python guard.** No production Python reads these variables, so they are
287
- inert there, and no broad Python `BAPI_*` guard is added. They are refused when
288
- inherited by a TypeScript child or MCP process. The Python dead-man variables
289
- (`CONDUCTOR_DEAD_MAN_ONLY`, `CONDUCTOR_DEADMAN_*`) are a separate exact-name
290
- contract owned by BAPI-1231.
291
-
292
- ## `epic-implementer install-git-hooks` (BAPI-395)
293
-
294
- Installs local git hooks that opportunistically emit conductor git/PR/CI events into
295
- the local ledger:
296
-
297
- ```
298
- epic-implementer install-git-hooks [--json]
299
- ```
300
-
301
- The installed hooks are **local, unversioned, opportunistic, and bypassable**: they
302
- live in the worktree's git hooks directory (resolved via
303
- `git rev-parse --git-common-dir`), insert only a clearly-delimited managed block
304
- (preserving any existing user hook content), launch the producer **detached in the
305
- background** so a commit or ref update is never blocked, and tolerate every failure
306
- (`|| true`). A directory that is not a git worktree, or an existing hook that looks
307
- binary/unsafe, is left untouched and reported as a **degraded optional capability** —
308
- never a fatal error. The hooks installed are `post-commit` (emits
309
- `git.commit_created`) and `reference-transaction` (emits `worktree.changed` for
310
- committed ref updates).
311
-
312
- Missing hooks do **not** prevent PR/CI gate evaluation — `epic-implementer doctor` reads
313
- hook presence and managed-snippet status **read-only** (a new `git hooks` section /
314
- `git_hooks` JSON object alongside the ledger report), and the reconciler drives CI
315
- polling and gate evaluation regardless of whether hooks are installed.
316
-
317
- ### Durable hook installation (BAPI-1290)
318
-
319
- A managed hook embeds one absolute entry path and runs it on every commit for as
320
- long as the hook file lives. So the installer embeds only a **qualified persistent
321
- runtime** and refuses anything that can disappear under it.
322
-
323
- **Supported installation.** Install the package persistently, then install the hooks
324
- from that install:
325
-
326
- ```bash
327
- npm i -g @bridge_gpt/mcp-server
328
- epic-implementer install-git-hooks
329
- ```
330
-
331
- A user-owned persistent prefix (`npm i -g --prefix <dir>`, with `<dir>/bin` on PATH)
332
- works the same way. The hook embeds the fixed package entry
333
- `<prefix>/lib/node_modules/@bridge_gpt/mcp-server/build/epic-implementer-bin.js`
334
- (POSIX single-quoted, so spaces and shell metacharacters stay literal) and launches it
335
- with `node` from PATH. It never embeds npm's `.bin` shim and never pins
336
- `process.execPath`.
337
-
338
- Running the installer through `npx` is allowed only as a front door: it looks up an
339
- **already installed** persistent global package with a bounded, offline
340
- `npm root -g` (at install time only) and embeds that package's entry. When no
341
- qualified persistent install exists, it refuses.
342
-
343
- **What "qualified" means.** Before touching any hook, the installer checks, read-only:
344
-
345
- | Check | Refusal classification |
346
- |---|---|
347
- | The entry is not inside the prunable `_npx` cache | `ephemeral-cache` |
348
- | The canonical entry is a regular `build/epic-implementer-bin.js` | `missing` |
349
- | It is not the retired `conductor-bin.js` name | `legacy-name` |
350
- | 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` |
351
- | The platform is macOS or Linux | `unqualified-runtime` |
352
- | `node` from PATH launches the entry's private probe within 15 seconds | `node-unavailable` / `unknown` |
353
- | That Node is an even LTS line ≥ 20 | `unqualified-runtime` |
354
- | `better-sqlite3` loads for that Node | `native-unavailable` |
355
-
356
- A refusal exits 1, prints only the classification and a fixed action (never a path or
357
- error text), and leaves both existing hooks byte-for-byte and mode-for-mode unchanged.
358
- Hooks are optional observability: a refusal is **not** an engine-admission failure and
359
- not a setup-permission stop.
360
-
361
- **Qualified matrix.** Recorded evidence, not inference:
362
-
363
- | OS | Install | Node | Native | Status |
364
- |---|---|---|---|---|
365
- | 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` |
366
- | Linux | persistent `npm i -g` | even LTS ≥ 20 | better-sqlite3 13.x | accepted by policy; **not yet locally qualified** |
367
- | Windows | any | any | any | **unqualified** — always refused |
368
- | Any | `npx` cache only, version-manager switches (nvm/fnm/volta), other layouts | — | — | **unqualified** |
369
-
370
- A package-layout change or a Node/native ABI change must be requalified by re-running
371
- `npm run test:hooks-lifetime` before this table changes.
372
-
373
- **Publication.** The installer plans both hooks before writing either. It refuses a
374
- symlinked, non-regular, unreadable, or binary hook with a per-hook reason and changes
375
- nothing. Each hook is then written to a same-directory temporary file with its original
376
- mode plus only the missing execute bits, re-checked against its planned snapshot, and
377
- renamed atomically over the target. A hook edited in the meantime is left untouched
378
- (`intervening-edit`). Two renames are not a transaction: if one hook publishes and the
379
- other does not, the result is `PARTIAL` (exit 1), the published hook is not rolled
380
- back, and re-running the installer converges to exactly one managed block per hook.
381
- User content before and after the managed block is preserved.
382
-
383
- **Behavior changes to note.** A symlinked hook is refused rather than written
384
- through; one unsafe hook (symlinked, unreadable, binary, or not a regular file)
385
- blocks installing both; a source-checkout build is refused as
386
- `unqualified-runtime`; and on Windows `doctor` always reports `unqualified-runtime`.
387
- A persistent install older than this change cannot answer the runtime probe and is
388
- reported `unknown` — upgrade it with `npm i -g @bridge_gpt/mcp-server@latest`.
389
-
390
- **Diagnosis.** `epic-implementer doctor` (and `install doctor`) report each hook's
391
- `state` and `usable` verdict read-only: no npm lookup, no download, no hook rewrite,
392
- no service start, no ledger write. Its only subprocess is the bounded runtime probe.
393
- A hook that exists but points into the npx cache (`ephemeral-cache`), at a removed
394
- install (`missing`), at the retired name (`legacy-name`), or at a runtime whose native
395
- binding no longer loads (`native-unavailable`) is **unavailable**, never healthy. The
396
- raw embedded path appears only in `--json` output (`embedded_bin_path`).
397
-
398
- **Repairing old cache-backed hooks.** Hooks installed from `npx` before BAPI-1290
399
- point into the npx cache. Doctor reports them as `ephemeral-cache`. To repair:
400
- install persistently (`npm i -g @bridge_gpt/mcp-server`), then re-run
401
- `epic-implementer install-git-hooks` (or `install conductor` and accept the
402
- local-observability consent). The managed block is replaced in place; user content,
403
- modes, and the local ledger are preserved.
404
-
405
- **Upgrades and rollback.** `npm i -g @bridge_gpt/mcp-server@<version>` replaces the
406
- package in place, so the embedded path is unchanged and hooks keep working **without
407
- being rewritten**. While the replacement is in progress, or if it is interrupted,
408
- optional delivery may be temporarily unavailable and doctor reports it as degraded;
409
- completing the install (or re-running it) restores delivery. To roll back, install the
410
- previous compatible version the same way, then run `epic-implementer doctor` and, if
411
- it reports degraded, `epic-implementer install-git-hooks`. Removing the global package
412
- deliberately is outside the availability guarantee but stays diagnosable (`missing`).
413
-
414
- **What is not promised.** Events that occur while hook delivery is unavailable are
415
- **lost and not replayed**; the ledger is not complete for those intervals, and anyone
416
- relying on a missed observation must reconcile it from Git itself. Git commits and ref
417
- updates are never blocked or slowed by an unavailable runtime (the hook is detached and
418
- ends in `|| true`), and engine admission never depends on hooks. No run-speed
419
- improvement is claimed. Activating a hook build against a live or shared control plane
420
- remains a separately authorized operation.
421
-
422
- ## Supervisor config — where the done gate and auto-merge live
423
-
424
- > **These are not config fields.** They used to be `conductor_done_gate` and
425
- > `conductor_auto_merge_enabled` on the generic config-field route; BAPI-438 moved
426
- > them onto dedicated supervisor endpoints. Setting them via
427
- > `PUT /jira/config-field/...` returns **HTTP 400 "Invalid config field"** — that
428
- > rejection is correct, not a bug. Use the endpoints below.
429
-
430
- Two scopes exist for each: a per-repo **project default**, and a per-epic override.
431
-
432
- | Setting | Endpoint (project default) |
433
- | --- | --- |
434
- | `done_gate_config` | `PUT /jira/epic-runs/supervisor-setup/defaults/?repo_name=<repo>` |
435
- | `auto_merge_enabled`, `merge_approval_required` | `PUT /jira/epic-runs/supervisor-config/defaults/?repo_name=<repo>` (see note below) |
436
-
437
- Since BAPI-1288 every project-default write is protected. A write that removes a
438
- configured requirement needs a preview (`POST …/defaults/preview/`) and the exact
439
- approval it returns. Turning `auto_merge_enabled` **on**, or dropping a required
440
- `merge_approval_required`, on the project default is refused (`409 NON_RELAXABLE`)
441
- whatever the approval. That is an operator decision; see the account-settings
442
- operator runbook (`docs/claude/account-settings-operator-runbook.md`).
443
-
444
- Swap `…/defaults/` for `…/runs/{epic_key}/…` to scope a setting to one epic. The
445
- matching `GET` on each returns the effective value, with a `source` of `epic`,
446
- `project_default`, or `none` (nothing configured — fails closed).
447
-
448
- ### `done_gate_config`
449
-
450
- Defines the done gate. It supports exactly one condition,
451
- `required_ci_checks_green`:
452
-
453
- ```json
454
- {
455
- "enabled": true,
456
- "conditions": [
457
- { "type": "required_ci_checks_green", "required_checks": ["build", "test"] }
458
- ]
459
- }
460
- ```
461
-
462
- `gate.met` is emitted (exactly once per `repo + pr_number + head_sha + effective
463
- config`) only when every listed required check is present, complete, and green for
464
- the bound PR head SHA. The gate **fails closed**: an unset, disabled (`enabled` not
465
- strictly `true`), malformed, empty, or unsupported config emits no `gate.met`.
466
-
467
- ### `auto_merge_enabled` (C6 conditional auto-merge)
468
-
469
- When a worker's PR meets the done gate (`gate.met`), the supervisor can autonomously
470
- merge it — but **only** when the repo has explicitly opted in.
471
- `auto_merge_enabled` on the supervisor-config endpoint is the opt-in switch. On the
472
- project default it can no longer be turned on through the API (BAPI-1288); an
473
- operator sets it, as the note above describes. A per-epic override is unchanged.
474
-
475
- **Auto-merge is disabled by default.** Behavior:
476
-
477
- - **Disabled / unset / unconfigured → dry-run.** Anything other than an explicit
478
- `true` — including a repo with no supervisor config at all (`source: "none"`) —
479
- fails **closed**: the supervisor records a `merge.dry_run` event and **no PR is
480
- ever merged**.
481
- - **Enabled → autonomous merge** when the gate is met and the deterministic guards
482
- pass.
483
- - **Kill-switch.** Set `auto_merge_enabled` to `false` to immediately stop
484
- autonomous merges. The protected merge endpoint **independently re-enforces** the
485
- flag, so even a conductor that calls it cannot merge while the flag is off.
486
-
487
- Merge authority is **deterministic code, never an LLM**. The deterministic guards,
488
- all bound to **PR number + expected head SHA (never a branch name)**:
489
-
490
- - the per-repo enablement flag (off → dry-run),
491
- - the PR is still open,
492
- - the merge is bound to the PR number plus the expected head SHA — **head-SHA drift
493
- between gate evaluation and merge aborts the merge**,
494
- - required CI checks are **revalidated green immediately before merge**.
495
-
496
- Idempotency is crash-safe and race-safe: a TTL lease keyed by the deterministic
497
- action key `merge:{repo}:{pr}:{head_sha}:{gate}` is claimed before acting, and an
498
- existing `merge.succeeded` for that key is terminal — the supervisor never
499
- double-merges across a crash/restart or two racing instances. The conductor never
500
- holds VCS write credentials: it calls the protected Bridge API endpoint
501
- `POST /vcs/pull-requests/{pr_number}/merge`, which owns the privileged merge, and
502
- records the returned `merge.dry_run` / `merge.attempted` / `merge.succeeded` /
503
- `merge.failed` / `merge.pending_approval` events into the local ledger.
504
- `merge.failed` is **retryable** (a drifted head SHA produces a new action key);
505
- `merge.pending_approval` is **nonterminal** — the worker remains active until a human
506
- redeems the approval token and the server returns `merge.succeeded`.
507
- **`merge.succeeded` is the only terminal merge event.** The local SQLite conductor
508
- store uses schema version 5 (BAPI-413) to accommodate the `merge.pending_approval`
509
- type in the `events.type` CHECK constraint.
510
-
511
- **Who performs the write (BAPI-832).** "Never holds VCS write credentials" above
512
- describes the *credential*, not the *typing*, and it remains true everywhere. Two
513
- paths now perform the provider write with the operator's local `gh` rather than
514
- with the GitHub App installation token, because GitHub's merge endpoint requires
515
- `contents: write` and the Bridge GPT App installation is `contents: read`:
516
-
517
- - the executor's `merge` job, via `makeLocalMergeExecutor` — the arrangement
518
- BAPI-572 locked in; and
519
- - the `merge_pull_request` MCP tool, which since BAPI-832 sends
520
- `execution: "local"`, receives a non-terminal `approved_for_local_execution`
521
- from the route above, merges through that same `conductor/local-merge.ts` seam,
522
- and reports the outcome to
523
- `POST /vcs/pull-requests/{pr_number}/merge/complete`.
524
-
525
- In both, the server still owns every decision — the guards, the lease, and the
526
- terminal ledger row — and neither path holds a credential of its own; they borrow
527
- the operator's `gh` session. Mode is `merge_execution` on `epic_supervisor_config`
528
- (`local` by default, `server` for installations whose App holds
529
- `contents: write`). A caller that dies mid-merge is recovered by lease expiry, not
530
- by a new mechanism: the next call re-runs the whole tail, and the provider re-read
531
- reports `already_merged` if the merge landed.
532
-
533
- ## Durable execution environment hardening (BAPI-527)
534
-
535
- Worker ledger operations are made **reliable again** — this is a robustness fix for
536
- the worker-facing path, **not** a new primary completion backstop. The done gate and
537
- supervisor remain the authority; this change only stops the worker's own ledger calls
538
- from failing.
539
-
540
- **The invariant.** Worker-facing ledger operations run through `conductor-bin.js`
541
- executed **under the captured conductor Node** (`EPIC_IMPLEMENTER_NODE_PATH`). **The worker
542
- Node must never load `better-sqlite3`.** Dispatch captures the conductor process's
543
- own Node executable and threads it to each worker as `EPIC_IMPLEMENTER_NODE_PATH`, paired
544
- with `BAPI_EPIC_IMPLEMENTER_CLI_FILE` (the packaged `conductor-bin.js` path). The
545
- worker-side boundary (`src/conductor/worker-ledger-cli.ts`) shells out with a fixed
546
- **list argv** via `execFile` (never `shell: true`, never a command string) and
547
- passes event JSON over **stdin** (`--data-json-stdin`), so raw payloads/secrets
548
- never enter the process argument list. Conductor-**owned** processes (the CLI,
549
- supervisor, and epic runtimes) keep calling `store.ts` directly — they legitimately
550
- own the native load.
551
-
552
- **Fail loud, never fall back.** A missing/invalid `EPIC_IMPLEMENTER_NODE_PATH` or absent
553
- `BAPI_EPIC_IMPLEMENTER_CLI_FILE` raises the typed `ConductorLedgerSubprocessRuntimeError`,
554
- surfaced to callers as the `LEDGER_SUBPROCESS_RUNTIME_UNAVAILABLE` (503) envelope
555
- with a fixed, path-free, secret-free `details: { env_var, reason }` bag
556
- (`reason ∈ missing | invalid | cli_missing | spawn_failed | malformed_stdout`). The
557
- boundary **never** falls back to the worker's `process.execPath` — doing so would
558
- re-introduce the native load into the worker Node, the exact failure this removes.
559
- `EPIC_IMPLEMENTER_NODE_PATH` is **non-secret operational metadata**: it is inherited from
560
- the worker shell env injected at the spawn boundary and is **not** persisted into
561
- `.mcp.json`, which stays intentionally env-free.
562
-
563
- **Rejected alternatives.**
564
-
565
- - **Local HTTP sidecar** (a long-lived ledger daemon the worker talks to over a
566
- port) — rejected: port conflicts, zombie-process lifecycle management, capability-
567
- token handling, and a large failure surface, all disproportionate for a
568
- file-backed local ledger.
569
- - **WASM / pure-JS SQLite in the worker** — rejected: it replaces the proven
570
- `better-sqlite3` WAL / synchronous-performance behavior and is a poor fit for a
571
- database file shared by multiple processes (the conductor Node and any workers).
572
-
573
- ### Three protective guarantees (operator view)
574
-
575
- BAPI-527 adds three independent reliability guarantees. Each is a protective
576
- default; none changes the interactive (non-conductor) `start-tickets` contract.
577
-
578
- 1. **Ledger operations are isolated from the worker path.** Worker-facing ledger
579
- operations run in a subprocess under `EPIC_IMPLEMENTER_NODE_PATH` (details above), so a
580
- Node ABI mismatch degrades a worker convenience, never the whole session.
581
-
582
- 2. **Dispatch does not alter your branch.** Unattended/epic dispatch is
583
- **non-mutating**: it resolves the base with a fetch-only
584
- `origin/<base>` lookup and cuts new worktrees from that fetched SHA. It never
585
- checks out, fast-forwards (`git merge --ff-only`), force-moves
586
- (`git branch --force`), or stashes the operator's live checkout. A **live-source
587
- guard** additionally refuses dispatch (or, with an explicit override, warns
588
- loudly) when the conductor's base repo path equals the operator's live
589
- dev-server checkout:
590
- - `BAPI_EPIC_IMPLEMENTER_LIVE_SOURCE_PATH` — the operator's live dev-server checkout
591
- path to protect.
592
- - `BAPI_EPIC_IMPLEMENTER_ALLOW_LIVE_SOURCE_DISPATCH` — truthy (`1`/`true`/`yes`/`on`)
593
- override that downgrades a collision from fatal to a loud warning.
594
- The read-only `doctor` command surfaces the guard state
595
- (`not-configured` / `safe` / `collision`) without mutating anything.
596
-
597
- 3. **Parse runs in a separate process.** The post-merge repository re-index runs
598
- out-of-process via `asyncio.create_subprocess_exec`, so the FastAPI event loop
599
- stays responsive to concurrent dispatch calls while a CPU-bound parse runs. The
600
- ticket enters **`parse_pending`** after merge and folds to **`done`** only when
601
- `/parse-status` reports terminal success; a permanent parse failure (or the
602
- `max_wait_ms` budget being exhausted) folds it to **`blocked`** with a sanitized
603
- reason and never silently wedges the epic. Parse status values are `idle`,
604
- `queued`, `in_progress`, `succeeded`, and `failed`; terminal failure reasons are
605
- bounded and sanitized (no stderr, tracebacks, argv, or secrets).
@@ -1,49 +0,0 @@
1
- {
2
- "name": "idea-to-pr",
3
- "description": "Drive an idea end-to-end: create ticket(s) (idea-to-ticket), review each ticket (review-ticket fan-out), then hand off to the /start-tickets CLI seam to spawn worktrees.",
4
- "variables": [
5
- "idea",
6
- "auto_approve",
7
- "scheduled_at",
8
- "max_children",
9
- "allow_duplicate",
10
- "agent"
11
- ],
12
- "stages": [
13
- {
14
- "pipeline_name": "idea-to-ticket",
15
- "description": "Convert the idea into one or more Jira tickets.",
16
- "variables": {
17
- "idea": "{idea}",
18
- "max_children": "{max_children}",
19
- "allow_duplicate": "{allow_duplicate}",
20
- "auto_approve_external": "{auto_approve}"
21
- },
22
- "outputs": {
23
- "child_ticket_keys": "child_ticket_keys",
24
- "epic_parent_key": "epic_parent_key",
25
- "created_ticket_keys": "created_ticket_keys"
26
- }
27
- },
28
- {
29
- "pipeline_name": "review-ticket",
30
- "description": "Review each created ticket (one child pipeline run per ticket).",
31
- "fan_out_input": "child_ticket_keys",
32
- "fan_out_variable": "ticket_key",
33
- "variables": {
34
- "ticket_key": "{ticket_key}"
35
- },
36
- "outputs": {
37
- "reviewed_ticket_keys": "reviewed_ticket_keys"
38
- }
39
- },
40
- {
41
- "pipeline_name": "start-tickets",
42
- "description": "Hand off the reviewed tickets to the /start-tickets CLI seam (agent-task; not a server pipeline).",
43
- "fan_out_input": "reviewed_ticket_keys",
44
- "outputs": {
45
- "started_ticket_keys": "started_ticket_keys"
46
- }
47
- }
48
- ]
49
- }