@bridge_gpt/mcp-server 0.2.65 → 0.2.66

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 (71) hide show
  1. package/README.md +25 -13
  2. package/build/agent-launchers/claude-executor-adapter.js +12 -18
  3. package/build/agent-launchers/executor-adapter.js +27 -0
  4. package/build/bridge-config.js +104 -0
  5. package/build/codex-bridge-profile.js +10 -1
  6. package/build/commands.generated.js +4 -4
  7. package/build/conductor/bridge-api-client.js +230 -10
  8. package/build/conductor/epic-child-continuation.js +61 -34
  9. package/build/conductor/epic-implementer-cli.js +12 -0
  10. package/build/conductor/guided-repair.js +323 -0
  11. package/build/conductor/readiness-cli.js +30 -9
  12. package/build/conductor/readiness.js +23 -1
  13. package/build/conductor/recovery-cli.js +507 -16
  14. package/build/conductor/recovery-operations.js +325 -2
  15. package/build/conductor/repair-contract.js +61 -3
  16. package/build/credential-store.js +5 -4
  17. package/build/diagnostic-text.js +36 -0
  18. package/build/docs.generated.js +1 -1
  19. package/build/doctor.js +70 -10
  20. package/build/drive-epic.js +395 -78
  21. package/build/epic-implementer-bin.js +24 -22
  22. package/build/epic-launch-diagnostic.js +31 -0
  23. package/build/epic-launch-profile.js +44 -14
  24. package/build/executor/completion-supervision.js +5 -0
  25. package/build/executor/correction-dispatch.js +60 -0
  26. package/build/executor/env.js +6 -0
  27. package/build/executor/http-client.js +15 -0
  28. package/build/executor/integration-job.js +6 -1
  29. package/build/executor/job-errors.js +13 -4
  30. package/build/executor/job-log-registry.js +340 -73
  31. package/build/executor/job-runner.js +260 -33
  32. package/build/executor/job-types.js +8 -13
  33. package/build/executor/preflight.js +91 -2
  34. package/build/executor/prompt-spec.js +113 -9
  35. package/build/executor/runner.js +23 -0
  36. package/build/executor/stale-artifacts.js +5 -4
  37. package/build/executor/types.js +1 -0
  38. package/build/executor/watch-cli.js +35 -8
  39. package/build/executor/worker-command.js +43 -43
  40. package/build/executor/worker-finalization.js +80 -15
  41. package/build/executor/worker-guard-hook.js +46 -10
  42. package/build/executor/worker-log.js +137 -70
  43. package/build/executor/worktree-gc.js +30 -6
  44. package/build/index.js +177 -329
  45. package/build/init.js +107 -4
  46. package/build/install-bridge.js +284 -20
  47. package/build/install-doctor.js +172 -30
  48. package/build/install-reexec.js +69 -7
  49. package/build/macos-terminal-permission-guidance.js +16 -0
  50. package/build/model-thresholds-tool.js +185 -0
  51. package/build/pipelines.generated.js +1 -1
  52. package/build/plane/status.js +4 -0
  53. package/build/plane/supervisor.js +4 -0
  54. package/build/plane/worker-exit-code.js +2 -0
  55. package/build/project-root.js +99 -0
  56. package/build/readme.generated.js +1 -1
  57. package/build/review-tickets.js +156 -13
  58. package/build/setup-epic.js +180 -108
  59. package/build/sfcc/setup-status.js +2 -0
  60. package/build/sfcc/tool-wrapper.js +2 -0
  61. package/build/sfcc/tools-configured.js +96 -0
  62. package/build/start-tickets.js +163 -58
  63. package/build/upgrade-cli.js +109 -24
  64. package/build/version.generated.js +3 -3
  65. package/build/windows-npm-entry.js +103 -0
  66. package/build/worker-guard-hook-bin.js +4 -3
  67. package/build/worktree-core.js +6 -5
  68. package/docs/install/mcp-tool-integrations.md +1 -0
  69. package/docs/install/sfcc-integration.md +12 -7
  70. package/package.json +1 -1
  71. package/pipelines/implement-ticket.json +1 -1
package/README.md CHANGED
@@ -252,9 +252,13 @@ workflows. Bridge-spawned Codex workers then select it automatically, including
252
252
  the fresh implementation session after review. Pass `--codex-profile=off` to
253
253
  decline; the installer leaves any existing profile content intact. On package
254
254
  upgrade, the next Bridge-spawned worker refreshes an older, unmodified managed
255
- profile before launch. If a profile
256
- already contains user-authored or conflicting content, the installer leaves it
257
- untouched and reports that workers cannot use it. Run `doctor --agent codex` to
255
+ profile before launch. The profile preapproves review-decision capture, the
256
+ fixed reviewed-document publisher, and the implementation phase-result record,
257
+ so those required Bridge writes do not enter Codex Auto-review. The general
258
+ `attachment` tool stays on automatic review because it also supports deletion
259
+ and replacement. If a profile already contains user-authored or conflicting
260
+ content, the installer leaves it untouched and reports that workers cannot use
261
+ it. Run `doctor --agent codex` to
258
262
  inspect the current status. To use the profile in a Codex session you open
259
263
  yourself, run `codex --profile bridge`. Bridge `--auto` controls recipe gates;
260
264
  Codex MCP tool approvals are a separate setting. The profile does not change
@@ -369,7 +373,7 @@ later boot.
369
373
  "mcpServers": {
370
374
  "bridge": {
371
375
  "command": "npx",
372
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.65", "serve"],
376
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"],
373
377
  "env": {
374
378
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
375
379
  "BAPI_REPO_NAME": "your-repo",
@@ -391,7 +395,7 @@ later boot.
391
395
  "bridge": {
392
396
  "type": "stdio",
393
397
  "command": "npx",
394
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.65", "serve"],
398
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"],
395
399
  "env": {
396
400
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
397
401
  "BAPI_REPO_NAME": "your-repo",
@@ -413,7 +417,7 @@ later boot.
413
417
  "bridge": {
414
418
  "type": "stdio",
415
419
  "command": "npx",
416
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.65", "serve"],
420
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"],
417
421
  "env": {
418
422
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
419
423
  "BAPI_REPO_NAME": "your-repo",
@@ -436,7 +440,7 @@ Windsurf only supports global MCP configuration.
436
440
  "mcpServers": {
437
441
  "bridge": {
438
442
  "command": "npx",
439
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.65", "serve"],
443
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"],
440
444
  "env": {
441
445
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
442
446
  "BAPI_REPO_NAME": "your-repo",
@@ -455,7 +459,7 @@ Windsurf only supports global MCP configuration.
455
459
  ```toml
456
460
  [mcp_servers.bridge]
457
461
  command = "npx"
458
- args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.65", "serve"]
462
+ args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"]
459
463
 
460
464
  [mcp_servers.bridge.env]
461
465
  BAPI_BASE_URL = "https://bridgegpt-api.com"
@@ -893,7 +897,7 @@ Because tier 2 outranks tier 3, a stale `SFCC_HOSTNAME` left in the environment
893
897
  "env": { "BRIDGE_MCP_PROFILE": "sfcc" }
894
898
  ```
895
899
 
896
- `sfcc` gives the eight read tools plus `sfcc_log_query`. For the nine destructive write tools as well, use `"sfcc,sfcc-write"`; `full` expands to every group and is therefore write-capable. Without any of these, only the diagnostic tools are registered.
900
+ `sfcc` gives the two diagnostics, the eight read tools, and `sfcc_log_query`. For the nine destructive write tools as well, use `"sfcc,sfcc-write"`; `full` expands to every group and is therefore write-capable. Without any of these, no SFCC tool is registered — not even the diagnostics.
897
901
 
898
902
  **4. Verify and grant OCAPI access.** With `sfcc` selected in step 3 (both diagnostics live in that profile), ask your agent to run `sfcc_setup_status` (expect all checks ✓), then `check_permissions`. If it reports HTTP 401/403, it prints the exact OCAPI Settings JSON to paste in Business Manager (**Administration → Site Development → Open Commerce API Settings → Data API** tab); save it there and re-run. Restart the MCP client after any credential or env change — a running session does not pick them up.
899
903
 
@@ -1163,7 +1167,15 @@ requests a lane, or starts or signals an executor. With `--json` (valid only wit
1163
1167
  `readiness`, `live_run`, `plan`, `summary`, `decision`, and `refusal`. The
1164
1168
  readiness `reason_id` and `remedy` are the strings the real refusal prints. The
1165
1169
  webhook URL, credentials, and policy contents are never included. The preview does
1166
- not probe ports, so a local plane that is not listening reads as unknown readiness.
1170
+ probes only a selected loopback plane after a network readiness failure and an
1171
+ available plan. A refused port plus passing local preflight yields a conditional
1172
+ `create` with optional `cold_start` (`plane_endpoint`, `will_start_plane: true`,
1173
+ `live_run_checked: false`), `live_run: null`, and partial offline setup validation.
1174
+ The real launch attempts guarded startup, then rechecks run existence and server
1175
+ validation; the preview does not guarantee creation. Remote targets, non-refused
1176
+ port probes, and ordinary production previews keep their existing refusal or
1177
+ create behavior. `target.source` is optional and appears only for selected
1178
+ profiles (`profile` or `profile-env`).
1167
1179
 
1168
1180
  **The composed bring-up is two-phase (BAPI-1102).** With `--plan-file` on a
1169
1181
  repository whose readiness is missing only the runtime facts, one invocation runs,
@@ -1456,7 +1468,7 @@ If a custom pipeline has the same key as a built-in pipeline, the custom version
1456
1468
  | `BAPI_BASE_URL` (`executor` subcommand) | **Yes** | _(none)_ | The `executor` deliberately has **no** production fallback — it refuses to start rather than guess a target |
1457
1469
  | `BAPI_REPO_NAME` | Yes | _(none)_ | Jira project/repository identifier configured in Bridge API |
1458
1470
  | `BAPI_API_KEY` | No | _(none)_ | A Bridge credential **is** required; this environment variable is only the first place the server looks for it. When it is unset the server resolves the credential from the user-scoped store (`~/.config/bridge/credentials.json`, target `bapi:<repo>`), which is why generated MCP registrations are secret-free |
1459
- | `BAPI_PROJECT_ROOT` | No | _(see fallback order)_ | Absolute path to project root. Anchors `BAPI_DOCS_DIR` and `BAPI_PIPELINES_DIR` resolution. Resolved once, in order: `BAPI_PROJECT_ROOT` → the connected client's MCP `roots/list` → `CLAUDE_PROJECT_DIR` → `process.cwd()`. Several paths *write* it into a generated registration (`--init`, host-config provisioning, the worktree `mcp-invoke` shim) — that is provenance, not a runtime default |
1471
+ | `BAPI_PROJECT_ROOT` | No | _(see fallback order)_ | Absolute path to project root. Anchors `BAPI_DOCS_DIR` and `BAPI_PIPELINES_DIR` resolution. Resolved once, in order: `BAPI_PROJECT_ROOT` → the connected client's MCP `roots/list` → `CLAUDE_PROJECT_DIR` → `process.cwd()`. The explicit value is validated: it is used only when it exists, is a folder, and contains `.git` (a folder, or a worktree's `.git` file) or `.bridge/config`. Otherwise it is ignored and the next source is used, so a path committed from another machine is harmless. The later sources are not validated. The server logs the chosen root and its source to stderr once (for example `Bridge MCP: project root /home/b/app (from roots/list); BAPI_PROJECT_ROOT /Users/a/app ignored: does not exist`), and `doctor` reports whether each configured value is valid. The check proves the path looks like a project, not that it is *this* project. Several paths *write* it into a generated registration (`--init`, `install`, `upgrade`, host-config provisioning, the worktree `mcp-invoke` shim) — that is provenance, not a runtime default; a git-tracked config keeps its existing value. What each host passes at start: see the per-host table in `docs/claude/cli-install-architecture.md` (§5a) |
1460
1472
  | `SFCC_HOSTNAME` | No | _(none)_ | SFCC sandbox hostname. Part of the environment credential tier — `SFCC_HOSTNAME`, `SFCC_CLIENT_ID`, and `SFCC_CLIENT_SECRET` must **all three** be set for that tier to apply, and a complete tier takes precedence over `dw.json` |
1461
1473
  | `SFCC_CLIENT_ID` | No | _(none)_ | Account Manager API client id. See `SFCC_HOSTNAME` — all three are needed together. Also required on its own when a tool is called with an explicit dotted `instance` |
1462
1474
  | `SFCC_CLIENT_SECRET` | No | _(none)_ | Account Manager API client secret. See `SFCC_HOSTNAME` — all three are needed together. Never sent to Bridge; it goes only to the Account Manager token endpoint |
@@ -1730,7 +1742,7 @@ The full surface, for when you need the complete enumeration. Day-to-day, use [U
1730
1742
 
1731
1743
  ### MCP tools
1732
1744
 
1733
- The authoritative tool catalog covers **77 tools** (enumerated below). What's actually registered in a session depends on `BRIDGE_MCP_PROFILE`: the default `core` profile loads a trimmed subset, and the conductor/pipeline-authoring/SFCC tools are added only under their respective profiles (see [Environment Variables](#environment-variables)). Async AI tools follow a request/get pattern: call the `request_*` tool to kick off generation, then the matching `get_*` tool to retrieve the result (or pass `wait_for_result: true` to poll automatically).
1745
+ The authoritative tool catalog covers **78 tools** (enumerated below). What's actually registered in a session depends on `BRIDGE_MCP_PROFILE`: the default `core` profile loads a trimmed subset, and the conductor/pipeline-authoring/SFCC tools are added only under their respective profiles (see [Environment Variables](#environment-variables)). Async AI tools follow a request/get pattern: call the `request_*` tool to kick off generation, then the matching `get_*` tool to retrieve the result (or pass `wait_for_result: true` to poll automatically).
1734
1746
 
1735
1747
  - **Connectivity & identity** — `ping` (its JSON also carries `docs_dir`, `role`, and `customer_type`)
1736
1748
  - **Team & access** — `invite_member` (admin-only; mints a scoped access key for a teammate on an already-configured project — the plaintext key is shown exactly once)
@@ -1743,7 +1755,7 @@ The authoritative tool catalog covers **77 tools** (enumerated below). What's ac
1743
1755
  - **Jira status** — `update_jira_status` (pass `status: "auto"` to resolve the configured post-PR status server-side)
1744
1756
  - **Repository & CI** — `parse_repository` (`action`: `start`, `status`), `regenerate_directory_map`, `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`, `wait_for_ci_checks` (the last two are hidden until `ci_check_config` resolves, and share one visibility gate)
1745
1757
  - **Pipelines & automation** — `get_pipeline_recipe` (returns a fully resolved recipe the agent executes step-by-step)
1746
- - **Config** — `get_project_standards`, `config_field` (operations: `get`, `update`, `list`)
1758
+ - **Config** — `get_project_standards`, `config_field` (operations: `get`, `update`, `list`), `model_thresholds` (actions: `get`, `set`, `clear`; the only way to set personal thresholds)
1747
1759
 
1748
1760
  ### Bundled pipelines
1749
1761
 
@@ -1,4 +1,3 @@
1
- import { isModelTier, isValidModelAlias, resolveModelAlias } from "../agent-registry.js";
2
1
  import { evaluateClaudeMcpShadowingPolicy, inspectClaudeUserConfigForMcpShadowing, resolveClaudeUserConfigPath, } from "../claude-user-config-doctor.js";
3
2
  import { runDenyEnforcementPreflight } from "../conductor/deny-enforcement-preflight.js";
4
3
  import { buildExecutorBaseWorkerEnv } from "../executor/env.js";
@@ -7,7 +6,7 @@ import { provisionExecutorDenyLayer } from "../executor/permissions.js";
7
6
  import { WORKER_GUARD_ROOT_FLAG } from "../executor/worker-guard-hook.js";
8
7
  import { SUPPORTED_EXECUTOR_PLATFORMS, evaluateExecutorPlatform } from "../executor/platform.js";
9
8
  import { normalizeMcpServerNames } from "../executor/observation.js";
10
- import { COMPLETION_EVIDENCE_TRANSPORT, supported } from "./executor-adapter.js";
9
+ import { COMPLETION_EVIDENCE_TRANSPORT, resolveExecutorModelAlias, supported, } from "./executor-adapter.js";
11
10
  import { SUPPORTED_COMPLETION_RUNTIME, boundedIdentifier, } from "../executor/completion-contract.js";
12
11
  import { MCP_SERVER_NAME } from "../mcp-identity.js";
13
12
  /** Stable identity of this reference implementation. */
@@ -159,21 +158,12 @@ export function classifyClaudeAuthFailure(stdoutExcerpt) {
159
158
  * when it passes both registry alias validation AND the spec's static allowlist;
160
159
  * anything else falls through to tier resolution, and an unusable result is null
161
160
  * (meaning "omit `--model`") rather than an unvalidated passthrough.
161
+ *
162
+ * BAPI-1343: the rule now lives in the agent-neutral
163
+ * {@link resolveExecutorModelAlias}; this export delegates with no behavior change.
162
164
  */
163
165
  export function resolveClaudeModelAlias(spec, payload) {
164
- const p = payload && typeof payload === "object" ? payload : {};
165
- const directRaw = p.model_alias;
166
- if (typeof directRaw === "string" && directRaw.trim().length > 0) {
167
- const direct = directRaw.trim();
168
- const allowed = !spec.staticModelAliasAllowlist || spec.staticModelAliasAllowlist.includes(direct);
169
- if (isValidModelAlias(direct) && allowed) {
170
- return direct;
171
- }
172
- // An invalid/disallowed direct alias falls through to the tier path — never
173
- // returned as-is.
174
- }
175
- const tier = isModelTier(p.model_tier) ? p.model_tier : null;
176
- return resolveModelAlias(spec, tier, null);
166
+ return resolveExecutorModelAlias(spec, payload);
177
167
  }
178
168
  // ---------------------------------------------------------------------------
179
169
  // Spawn shape
@@ -411,9 +401,10 @@ function shellQuoteHookPath(value) {
411
401
  * where the executor's env deny-list can guarantee an ambient value never
412
402
  * substitutes for the job's own.
413
403
  */
414
- export function buildWorkerGuardHookCommand(binPath, execPath, worktreePath) {
404
+ export function buildWorkerGuardHookCommand(binPath, execPath, worktreePath, correctionIntentVersion) {
415
405
  return (`${shellQuoteHookPath(execPath)} ${shellQuoteHookPath(binPath)} ` +
416
- `${WORKER_GUARD_ROOT_FLAG} ${shellQuoteHookPath(worktreePath)}`);
406
+ `${WORKER_GUARD_ROOT_FLAG} ${shellQuoteHookPath(worktreePath)}` +
407
+ (correctionIntentVersion === 1 ? " --correction-intent-version 1" : ""));
417
408
  }
418
409
  // ---------------------------------------------------------------------------
419
410
  // The adapter factory
@@ -450,6 +441,9 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
450
441
  if (input.workerBranch !== undefined) {
451
442
  envOptions.workerBranch = input.workerBranch;
452
443
  }
444
+ if (input.correctionIntentVersion !== undefined) {
445
+ envOptions.correctionIntentVersion = input.correctionIntentVersion;
446
+ }
453
447
  if (input.declaredTouchedFiles !== undefined) {
454
448
  envOptions.declaredTouchedFiles = input.declaredTouchedFiles;
455
449
  }
@@ -522,7 +516,7 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
522
516
  }
523
517
  const options = {
524
518
  baseBranch: input.baseBranch,
525
- workerGuardHookCommand: buildWorkerGuardHookCommand(input.workerGuardHookBinPath, deps.execPath ?? process.execPath, input.worktreePath),
519
+ workerGuardHookCommand: buildWorkerGuardHookCommand(input.workerGuardHookBinPath, deps.execPath ?? process.execPath, input.worktreePath, input.correctionIntentVersion),
526
520
  };
527
521
  if (input.workerBranch !== undefined) {
528
522
  options.workerBranch = input.workerBranch;
@@ -1,3 +1,4 @@
1
+ import { isModelTier, isValidModelAlias, resolveModelAlias } from "../agent-registry.js";
1
2
  /** Construct a supported capability. */
2
3
  export function supported(value) {
3
4
  return { supported: true, value };
@@ -8,6 +9,32 @@ export function unsupported(reason, strategyId) {
8
9
  ? { supported: false, reason }
9
10
  : { supported: false, reason, strategyId };
10
11
  }
12
+ /**
13
+ * Agent-neutral payload -> model alias rule shared by every executor adapter
14
+ * (BAPI-1343).
15
+ *
16
+ * A direct `model_alias` is accepted only when it passes registry alias
17
+ * validation AND the spec's static allowlist. Otherwise a stamped `model_tier`
18
+ * maps through {@link resolveModelAlias} with no repository override. Anything
19
+ * unusable returns null, meaning "omit the model flag". There is deliberately no
20
+ * job-type branch: the server decides whether to stamp a tier, and every stamped
21
+ * payload takes the same path.
22
+ */
23
+ export function resolveExecutorModelAlias(spec, payload) {
24
+ const p = payload && typeof payload === "object" ? payload : {};
25
+ const directRaw = p.model_alias;
26
+ if (typeof directRaw === "string" && directRaw.trim().length > 0) {
27
+ const direct = directRaw.trim();
28
+ const allowed = !spec.staticModelAliasAllowlist || spec.staticModelAliasAllowlist.includes(direct);
29
+ if (isValidModelAlias(direct) && allowed) {
30
+ return direct;
31
+ }
32
+ // An invalid/disallowed direct alias falls through to the tier path — never
33
+ // returned as-is.
34
+ }
35
+ const tier = isModelTier(p.model_tier) ? p.model_tier : null;
36
+ return resolveModelAlias(spec, tier, null);
37
+ }
11
38
  // ---------------------------------------------------------------------------
12
39
  // Completion evidence (job-specific, non-required) — BAPI-1301 (D4a)
13
40
  // ---------------------------------------------------------------------------
@@ -11,6 +11,7 @@
11
11
  * touching the filesystem or invoking real `git`.
12
12
  */
13
13
  import path from "path";
14
+ import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
14
15
  /**
15
16
  * True if a string contains any ASCII control character (0x00-0x1f or 0x7f).
16
17
  * Keeps identifiers path-safe and free of anything that could smuggle into a
@@ -307,6 +308,109 @@ export function parseBridgeConfigToml(text) {
307
308
  return { ok: true, manifest: { repoName, mcp: cleaned } };
308
309
  }
309
310
  // ---------------------------------------------------------------------------
311
+ // Teammate notice, representability, and lossless repo_name rewrite (BAPI-1318)
312
+ // ---------------------------------------------------------------------------
313
+ /**
314
+ * The teammate notice carried in the header of every scaffolded `.bridge/config`,
315
+ * and inserted by a consented repair when an older file lacks it. A teammate who
316
+ * clones a committed project gets this file but no MCP config (`.mcp.json` is
317
+ * gitignored), so this is the one place that tells them to run install.
318
+ *
319
+ * ONE source for both writers ({@link rewriteBridgeConfigRepoName} and
320
+ * `buildBridgeConfigManifest` in `init.ts`), and the exact-text detector for "this
321
+ * file already carries the notice", so the two can never drift.
322
+ */
323
+ export const BRIDGE_CONFIG_TEAMMATE_NOTICE_LINES = [
324
+ "# Teammates: this file does not connect your editor. Run",
325
+ `# "npx -y ${MCP_PACKAGE_NAME} install" once in this folder.`,
326
+ ];
327
+ /**
328
+ * Whether `name` can be written as `repo_name = "<name>"` and read back unchanged
329
+ * by {@link parseBridgeConfigToml}. `validateRepoName()` is deliberately left as
330
+ * it is (it gates every install name input); this is the stricter,
331
+ * manifest-only check the scaffold, post-rename, and repair writers all apply.
332
+ * The quoted-string subset has no escapes, so an embedded `"` or `\` is refused,
333
+ * and surrounding whitespace is refused because the parser would trim it away.
334
+ */
335
+ export function validateBridgeConfigRepoNameRepresentability(name) {
336
+ const validated = validateRepoName(name);
337
+ if (!validated.ok)
338
+ return validated;
339
+ if (validated.value !== name) {
340
+ return { ok: false, error: "repo_name must not have leading or trailing whitespace" };
341
+ }
342
+ if (validated.value.includes('"')) {
343
+ return { ok: false, error: "repo_name cannot contain a double quote in .bridge/config" };
344
+ }
345
+ if (validated.value.includes("\\")) {
346
+ return { ok: false, error: "repo_name cannot contain a backslash in .bridge/config" };
347
+ }
348
+ return validated;
349
+ }
350
+ /**
351
+ * Rewrite ONLY the quoted value of the single top-level `repo_name` line, and add
352
+ * the teammate notice at the top when its exact lines are absent. Every other
353
+ * byte — comments, blank lines, each `[[mcp]]` block, the `repo_name` line's own
354
+ * indentation and spacing around `=`, and the line endings — is preserved.
355
+ *
356
+ * Pure. The input is parsed first (a malformed file is refused, never rewritten)
357
+ * and the output is parsed again: it must carry the new name and the same ordered
358
+ * `mcp` list, or the rewrite is refused. Refusal messages never echo file content.
359
+ */
360
+ export function rewriteBridgeConfigRepoName(text, newName) {
361
+ const representable = validateBridgeConfigRepoNameRepresentability(newName);
362
+ if (!representable.ok) {
363
+ return { ok: false, kind: "unrepresentable-name", error: representable.error };
364
+ }
365
+ const before = parseBridgeConfigToml(text);
366
+ if (!before.ok) {
367
+ return { ok: false, kind: "parse-error", error: before.error };
368
+ }
369
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
370
+ const lines = text.split("\n");
371
+ // Locate the one top-level repo_name line: the parse above proved there is
372
+ // exactly one, and that it sits before the first [[mcp]] header.
373
+ let repoLine = -1;
374
+ for (let i = 0; i < lines.length; i++) {
375
+ const trimmed = lines[i].trim();
376
+ if (trimmed === "[[mcp]]")
377
+ break;
378
+ if (trimmed.startsWith("#") || trimmed.length === 0)
379
+ continue;
380
+ const eq = trimmed.indexOf("=");
381
+ if (eq !== -1 && trimmed.slice(0, eq).trim() === "repo_name") {
382
+ repoLine = i;
383
+ break;
384
+ }
385
+ }
386
+ if (repoLine === -1) {
387
+ return { ok: false, kind: "verification-failed", error: "Could not locate the repo_name line" };
388
+ }
389
+ const original = lines[repoLine];
390
+ const open = original.indexOf('"', original.indexOf("=") + 1);
391
+ const close = original.lastIndexOf('"');
392
+ if (open === -1 || close <= open) {
393
+ return { ok: false, kind: "verification-failed", error: "Could not locate the repo_name value" };
394
+ }
395
+ lines[repoLine] = `${original.slice(0, open + 1)}${representable.value}${original.slice(close)}`;
396
+ let next = lines.join("\n");
397
+ const noticePresent = BRIDGE_CONFIG_TEAMMATE_NOTICE_LINES.every((line) => text.split("\n").some((existing) => existing.replace(/\r$/, "") === line));
398
+ if (!noticePresent) {
399
+ next = BRIDGE_CONFIG_TEAMMATE_NOTICE_LINES.map((line) => `${line}${eol}`).join("") + next;
400
+ }
401
+ const after = parseBridgeConfigToml(next);
402
+ if (!after.ok ||
403
+ after.manifest.repoName !== representable.value ||
404
+ JSON.stringify(after.manifest.mcp) !== JSON.stringify(before.manifest.mcp)) {
405
+ return {
406
+ ok: false,
407
+ kind: "verification-failed",
408
+ error: "The rewritten .bridge/config did not parse back to the same manifest",
409
+ };
410
+ }
411
+ return { ok: true, text: next, changed: next !== text, noticeAdded: !noticePresent };
412
+ }
413
+ // ---------------------------------------------------------------------------
310
414
  // Reading + identity resolution
311
415
  // ---------------------------------------------------------------------------
312
416
  /** Absolute path to a project root's committed manifest. */
@@ -18,6 +18,15 @@ export const BRIDGE_WORKFLOW_TOOLS = [
18
18
  "update_jira_status", "config_field", "resolve_ci_checks", "poll_ci_checks",
19
19
  "wait_for_ci_checks",
20
20
  ];
21
+ // These bounded workflow writes are required to complete review and implementation.
22
+ // `auto` can route their repository-derived payloads to Auto-review, which can
23
+ // reject the Jira publish or phase ledger write despite the opted-in profile.
24
+ // Keep the general `attachment` tool on `auto`: it also supports delete/replace.
25
+ const PREAPPROVED_WORKFLOW_TOOLS = new Set([
26
+ "capture_review_decisions",
27
+ "publish_reviewed_attachment",
28
+ "record_phase_result",
29
+ ]);
21
30
  export function codexProfilePaths(home) {
22
31
  return {
23
32
  profile: path.join(home, PROFILE_FILE),
@@ -31,7 +40,7 @@ export function renderBridgeCodexProfile(key) {
31
40
  `# Applies only when selected with codex --profile ${BRIDGE_CODEX_PROFILE}.`,
32
41
  ...BRIDGE_WORKFLOW_TOOLS.flatMap((tool) => [
33
42
  `[mcp_servers.${key}.tools.${tool}]`,
34
- 'approval_mode = "auto"',
43
+ `approval_mode = "${PREAPPROVED_WORKFLOW_TOOLS.has(tool) ? "approve" : "auto"}"`,
35
44
  "",
36
45
  ]),
37
46
  ].join("\n") + "\n";