@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.
- package/README.md +25 -13
- package/build/agent-launchers/claude-executor-adapter.js +12 -18
- package/build/agent-launchers/executor-adapter.js +27 -0
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +230 -10
- package/build/conductor/epic-child-continuation.js +61 -34
- package/build/conductor/epic-implementer-cli.js +12 -0
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness.js +23 -1
- package/build/conductor/recovery-cli.js +507 -16
- package/build/conductor/recovery-operations.js +325 -2
- package/build/conductor/repair-contract.js +61 -3
- package/build/credential-store.js +5 -4
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +70 -10
- package/build/drive-epic.js +395 -78
- package/build/epic-implementer-bin.js +24 -22
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +44 -14
- package/build/executor/completion-supervision.js +5 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/env.js +6 -0
- package/build/executor/http-client.js +15 -0
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +13 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +260 -33
- package/build/executor/job-types.js +8 -13
- package/build/executor/preflight.js +91 -2
- package/build/executor/prompt-spec.js +113 -9
- package/build/executor/runner.js +23 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/types.js +1 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +43 -43
- package/build/executor/worker-finalization.js +80 -15
- package/build/executor/worker-guard-hook.js +46 -10
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/index.js +177 -329
- package/build/init.js +107 -4
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +1 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +4 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/setup-epic.js +180 -108
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +4 -3
- package/build/worktree-core.js +6 -5
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +1 -1
- 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.
|
|
256
|
-
|
|
257
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 **
|
|
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
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|
package/build/bridge-config.js
CHANGED
|
@@ -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
|
-
|
|
43
|
+
`approval_mode = "${PREAPPROVED_WORKFLOW_TOOLS.has(tool) ? "approve" : "auto"}"`,
|
|
35
44
|
"",
|
|
36
45
|
]),
|
|
37
46
|
].join("\n") + "\n";
|