@bridge_gpt/mcp-server 0.2.34 → 0.2.36
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 +456 -370
- package/build/agent-capabilities/probe-context.js +8 -1
- package/build/agent-capabilities/probes.js +7 -1
- package/build/agents.generated.js +1 -1
- package/build/claude-review-workflow.js +264 -0
- package/build/cli-release.js +53 -0
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +215 -0
- package/build/conductor/deny-enforcement-preflight.js +1 -0
- package/build/conductor/done-gate.js +44 -5
- package/build/conductor/epic-reconcile.js +6 -0
- package/build/conductor/install-doctor.js +462 -0
- package/build/conductor-bin.js +3 -3
- package/build/conductor-bundle-artifacts.js +30 -9
- package/build/doctor.js +234 -1
- package/build/executor/cli.js +32 -5
- package/build/executor/credentials.js +45 -11
- package/build/executor/deps.js +14 -0
- package/build/executor/env.js +23 -6
- package/build/executor/index.js +4 -0
- package/build/executor/job-runner.js +119 -9
- package/build/executor/permissions.js +12 -2
- package/build/executor/preflight.js +95 -8
- package/build/executor/prompt-spec.js +51 -0
- package/build/executor/runner.js +15 -2
- package/build/executor/service-unit.js +876 -0
- package/build/executor/test-clock.js +8 -0
- package/build/executor/types.js +0 -17
- package/build/executor/worker-command.js +62 -9
- package/build/index.js +575 -143
- package/build/init.js +153 -51
- package/build/install-bridge-conductor.js +491 -0
- package/build/install-bridge.js +628 -175
- package/build/install-reexec.js +233 -0
- package/build/mcp-host-config.js +11 -1
- package/build/mcp-install-state.js +32 -0
- package/build/mcp-provisioning.js +22 -6
- package/build/pipelines.generated.js +14 -8
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +257 -0
- package/build/setup-epic.js +117 -8
- package/build/upgrade-cli.js +1 -15
- package/build/version.generated.js +1 -1
- package/docs/CONDUCTOR.md +115 -4
- package/docs/install/mcp-tool-integrations.md +29 -21
- package/package.json +8 -5
- package/pipelines/implement-ticket.json +6 -1
- package/build/conductor/supervisor-judgment-python.js +0 -141
- package/build/conductor/supervisor-judgment.js +0 -215
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installer-owned self-re-exec from `@latest` (BAPI-714, Group B).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. The documented setup command dropped `@latest`
|
|
5
|
+
* (`npx -y @bridge_gpt/mcp-server install`), and an UNPINNED npx spec silently
|
|
6
|
+
* reuses a stale cached copy — the verified bug recorded in `init.ts:32-38`,
|
|
7
|
+
* where 0.2.6 persisted across an upgrade. So the install path must do for itself
|
|
8
|
+
* what `upgrade` already does: look up the latest published version and, when the
|
|
9
|
+
* local copy is strictly older, hand off to `@latest`.
|
|
10
|
+
*
|
|
11
|
+
* THREE PROPERTIES ARE LOAD-BEARING.
|
|
12
|
+
*
|
|
13
|
+
* 1. FAIL OPEN, ALWAYS. `upgrade` can afford to fail; `install` is someone's
|
|
14
|
+
* first contact with Bridge. An unreachable, slow, non-2xx, or malformed
|
|
15
|
+
* registry means "run the local copy" — never an error, prompt, or stall.
|
|
16
|
+
* 2. `stdio: "inherit"` IS NOT STYLISTIC. `promptSecret`/`promptLine`/
|
|
17
|
+
* `promptMultiSelect` are wired only when `process.stdin.isTTY`
|
|
18
|
+
* (`install-bridge.ts:1195`, `:1224-1226`); non-inherited stdio silently
|
|
19
|
+
* strips all three and turns an interactive install into a non-interactive
|
|
20
|
+
* one with no error.
|
|
21
|
+
* 3. THE SENTINEL IS STRIPPED BEFORE *ANY* ARGV INTERPRETATION. Two verified
|
|
22
|
+
* reasons: `parseInstallBridgeArgs` rejects unrecognized arguments, so a
|
|
23
|
+
* leftover `--internal-reexec` is an argument error on the child run; and
|
|
24
|
+
* `install-bridge.ts:1690` computes `isBareInvocation = argv.length === 0`,
|
|
25
|
+
* so a leftover sentinel makes the child take a DIFFERENT onboarding branch
|
|
26
|
+
* silently. Its presence is also the terminating condition, so exactly one
|
|
27
|
+
* re-exec is possible per invocation.
|
|
28
|
+
*
|
|
29
|
+
* SECRET DISCIPLINE. Child argv is process-visible (`ps`), so `--api-key <value>`
|
|
30
|
+
* and a VALUED `--invite <token>` are lifted out of argv and forwarded only
|
|
31
|
+
* through the child ENVIRONMENT as `BAPI_API_KEY` / `BAPI_INVITE`. A value-less
|
|
32
|
+
* `--invite` marker is deliberately RETAINED in child argv: it is what selects
|
|
33
|
+
* bootstrap-invite mode, and dropping it would leave `argv.length === 0` and send
|
|
34
|
+
* the child down the bare-onboarding branch instead. The failure diagnostic is a
|
|
35
|
+
* fixed string — it interpolates no argv, no environment value, and no exception
|
|
36
|
+
* text.
|
|
37
|
+
*/
|
|
38
|
+
import { spawn } from "child_process";
|
|
39
|
+
import { VERSION } from "./version.generated.js";
|
|
40
|
+
import { isNewerVersion } from "./update-check.js";
|
|
41
|
+
import { fetchLatestVersion } from "./cli-release.js";
|
|
42
|
+
import { runInstallBridgeCli } from "./install-bridge.js";
|
|
43
|
+
/** The sentinel that marks an already-re-exec'd child. Matches `upgrade-cli.ts`. */
|
|
44
|
+
export const INSTALL_REEXEC_SENTINEL = "--internal-reexec";
|
|
45
|
+
/**
|
|
46
|
+
* The ONLY text emitted when the hand-off itself fails. Fixed by construction —
|
|
47
|
+
* no argv, no environment value, no raw exception message, no credential.
|
|
48
|
+
*/
|
|
49
|
+
export const INSTALL_REEXEC_FAILURE_MESSAGE = "Bridge API install failed: could not run the latest installer.";
|
|
50
|
+
/**
|
|
51
|
+
* Remove EVERY `--internal-reexec` occurrence and report whether any was present.
|
|
52
|
+
*
|
|
53
|
+
* Called before nested-command detection, before the re-exec decision, and before
|
|
54
|
+
* anything is handed to `runInstallBridgeCli` — the sentinel must never reach the
|
|
55
|
+
* installer's strict argument parser or its bare-invocation computation.
|
|
56
|
+
*/
|
|
57
|
+
export function stripInternalReexecSentinels(argv) {
|
|
58
|
+
const args = [];
|
|
59
|
+
let sentinelPresent = false;
|
|
60
|
+
for (const arg of argv) {
|
|
61
|
+
if (arg === INSTALL_REEXEC_SENTINEL) {
|
|
62
|
+
sentinelPresent = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
args.push(arg);
|
|
66
|
+
}
|
|
67
|
+
return { args, sentinelPresent };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Lift secret-valued flags out of argv and into a child-environment overlay.
|
|
71
|
+
*
|
|
72
|
+
* Mirrors `parseInstallBridgeArgs` token-for-token so the two never disagree:
|
|
73
|
+
*
|
|
74
|
+
* - `--api-key=<value>` / `--api-key <value>` → `BAPI_API_KEY`, flag AND value
|
|
75
|
+
* removed. The installer resolves the key from the environment, and
|
|
76
|
+
* `hasEnvApiKey` keeps the bare-interactive chooser suppressed exactly as the
|
|
77
|
+
* flag did.
|
|
78
|
+
* - `--invite=<token>` / `--invite <token>` → `BAPI_INVITE`, with a VALUE-LESS
|
|
79
|
+
* `--invite` left in argv. That marker still selects bootstrap-invite mode and
|
|
80
|
+
* the installer falls back to `BAPI_INVITE` for the token.
|
|
81
|
+
* - MALFORMED forms (`--api-key` at end of argv, `--api-key=`, `--invite=`) are
|
|
82
|
+
* left untouched so the installer's own parser produces the established
|
|
83
|
+
* argument error rather than this layer inventing a new one.
|
|
84
|
+
*
|
|
85
|
+
* Every other argument is preserved byte-for-byte in its original order. This is
|
|
86
|
+
* deliberately NOT `sanitizePrewarmEnv()` from `install-bridge.ts`: that helper
|
|
87
|
+
* STRIPS installer credentials for a `--version` probe and has the opposite
|
|
88
|
+
* contract from this hand-off.
|
|
89
|
+
*/
|
|
90
|
+
export function prepareInstallReexecArguments(argv) {
|
|
91
|
+
const forwardedArgs = [];
|
|
92
|
+
const secretEnv = {};
|
|
93
|
+
for (let i = 0; i < argv.length; i++) {
|
|
94
|
+
const arg = argv[i];
|
|
95
|
+
if (arg === "--api-key" || arg.startsWith("--api-key=")) {
|
|
96
|
+
let value;
|
|
97
|
+
let consumedNext = false;
|
|
98
|
+
if (arg.startsWith("--api-key=")) {
|
|
99
|
+
value = arg.slice("--api-key=".length);
|
|
100
|
+
}
|
|
101
|
+
else if (i + 1 < argv.length) {
|
|
102
|
+
value = argv[i + 1];
|
|
103
|
+
consumedNext = true;
|
|
104
|
+
}
|
|
105
|
+
// Blank / absent value == malformed: hand it to the installer's parser.
|
|
106
|
+
if (value === undefined || value.length === 0) {
|
|
107
|
+
forwardedArgs.push(arg);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
secretEnv.BAPI_API_KEY = value;
|
|
111
|
+
if (consumedNext)
|
|
112
|
+
i += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (arg === "--invite" || arg.startsWith("--invite=")) {
|
|
116
|
+
if (arg.startsWith("--invite=")) {
|
|
117
|
+
const value = arg.slice("--invite=".length);
|
|
118
|
+
if (value.length === 0) {
|
|
119
|
+
// Malformed: leave it for the installer's parser.
|
|
120
|
+
forwardedArgs.push(arg);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
secretEnv.BAPI_INVITE = value;
|
|
124
|
+
forwardedArgs.push("--invite");
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// Bare `--invite`: only a following NON-flag token is its value, exactly as
|
|
128
|
+
// `parseInstallBridgeArgs` reads it.
|
|
129
|
+
const next = argv[i + 1];
|
|
130
|
+
forwardedArgs.push("--invite");
|
|
131
|
+
if (typeof next === "string" && !next.startsWith("-") && next.length > 0) {
|
|
132
|
+
secretEnv.BAPI_INVITE = next;
|
|
133
|
+
i += 1;
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
forwardedArgs.push(arg);
|
|
138
|
+
}
|
|
139
|
+
return { forwardedArgs, secretEnv };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The public `install` / legacy `install-bridge` entry point.
|
|
143
|
+
*
|
|
144
|
+
* Fail-open ordering, in the only order that is safe:
|
|
145
|
+
*
|
|
146
|
+
* 1. Strip sentinels (before anything reads argv).
|
|
147
|
+
* 2. If a sentinel was present, or the first remaining token is `conductor`,
|
|
148
|
+
* delegate straight to the local installer — no registry lookup, no spawn.
|
|
149
|
+
* The nested `install conductor` surface is intercepted inside
|
|
150
|
+
* `runInstallBridgeCli`, so it must reach it as `["conductor", ...]` rather
|
|
151
|
+
* than being collapsed into bare onboarding.
|
|
152
|
+
* 3. Otherwise look up the latest version, defaulting the comparison target to
|
|
153
|
+
* the local `VERSION` so an unusable answer changes nothing.
|
|
154
|
+
* 4. Re-exec only for a STRICTLY newer published version.
|
|
155
|
+
*/
|
|
156
|
+
export async function runInstallBridgeWithLatestCli(argv, deps = {}) {
|
|
157
|
+
const resolveLatest = deps.fetchLatestVersion ?? (() => fetchLatestVersion());
|
|
158
|
+
const runLocal = deps.runInstallBridgeCli ?? runInstallBridgeCli;
|
|
159
|
+
const doSpawn = deps.spawn ?? spawn;
|
|
160
|
+
const env = deps.env ?? process.env;
|
|
161
|
+
const platform = deps.platform ?? process.platform;
|
|
162
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
163
|
+
const errorLog = deps.errorLog ?? ((message) => console.error(message));
|
|
164
|
+
const localVersion = deps.localVersion ?? VERSION;
|
|
165
|
+
const { args: cleanedArgs, sentinelPresent } = stripInternalReexecSentinels(argv);
|
|
166
|
+
// Terminating condition, and the nested-conductor bypass. Both skip the
|
|
167
|
+
// registry entirely.
|
|
168
|
+
if (sentinelPresent || cleanedArgs[0] === "conductor") {
|
|
169
|
+
return runLocal(cleanedArgs);
|
|
170
|
+
}
|
|
171
|
+
// Default the comparison target to the local version: an unusable registry
|
|
172
|
+
// answer must be indistinguishable from "already current".
|
|
173
|
+
let latestVersion = localVersion;
|
|
174
|
+
let fetched = null;
|
|
175
|
+
try {
|
|
176
|
+
fetched = await resolveLatest();
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
fetched = null;
|
|
180
|
+
}
|
|
181
|
+
if (fetched)
|
|
182
|
+
latestVersion = fetched;
|
|
183
|
+
if (!isNewerVersion(localVersion, latestVersion)) {
|
|
184
|
+
return runLocal(cleanedArgs);
|
|
185
|
+
}
|
|
186
|
+
const { forwardedArgs, secretEnv } = prepareInstallReexecArguments(cleanedArgs);
|
|
187
|
+
const npxCmd = platform === "win32" ? "npx.cmd" : "npx";
|
|
188
|
+
const childArgs = [
|
|
189
|
+
"-y",
|
|
190
|
+
"@bridge_gpt/mcp-server@latest",
|
|
191
|
+
"install",
|
|
192
|
+
INSTALL_REEXEC_SENTINEL,
|
|
193
|
+
...forwardedArgs,
|
|
194
|
+
];
|
|
195
|
+
return new Promise((resolve) => {
|
|
196
|
+
let settled = false;
|
|
197
|
+
const settle = (code) => {
|
|
198
|
+
if (settled)
|
|
199
|
+
return;
|
|
200
|
+
settled = true;
|
|
201
|
+
resolve(code);
|
|
202
|
+
};
|
|
203
|
+
const fail = () => {
|
|
204
|
+
errorLog(INSTALL_REEXEC_FAILURE_MESSAGE);
|
|
205
|
+
settle(1);
|
|
206
|
+
};
|
|
207
|
+
let child;
|
|
208
|
+
try {
|
|
209
|
+
child = doSpawn(npxCmd, childArgs, {
|
|
210
|
+
shell: false,
|
|
211
|
+
stdio: "inherit",
|
|
212
|
+
cwd,
|
|
213
|
+
// Explicit CLI values win over an inherited value of the same key.
|
|
214
|
+
env: { ...env, ...secretEnv },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
fail();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
child.on("close", (code) => {
|
|
222
|
+
// A signal termination or an absent numeric code is an abnormal exit.
|
|
223
|
+
if (typeof code === "number") {
|
|
224
|
+
settle(code);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
fail();
|
|
228
|
+
});
|
|
229
|
+
child.on("error", () => {
|
|
230
|
+
fail();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
package/build/mcp-host-config.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* rather than reserialize unrelated content.
|
|
29
29
|
*/
|
|
30
30
|
import { VERSION } from "./version.generated.js";
|
|
31
|
+
import { refreshBridgeApiPackageSpec } from "./init.js";
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
33
|
// Path helpers (POSIX-normalized; module stays free of node:path).
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
@@ -119,7 +120,16 @@ export function mergeJsonHostConfig(existing, target, adaptedEntry) {
|
|
|
119
120
|
const root = rootRaw && typeof rootRaw === "object" && !Array.isArray(rootRaw)
|
|
120
121
|
? { ...rootRaw }
|
|
121
122
|
: {};
|
|
122
|
-
|
|
123
|
+
// BAPI-714 (Group C): an UPDATE to a pre-existing Bridge registration keeps that
|
|
124
|
+
// registration's own launcher args composition — only the package-spec token is
|
|
125
|
+
// refreshed — so a global/manual host config written before `serve` existed is
|
|
126
|
+
// never migrated to it. A registration this path CREATES receives the adapted
|
|
127
|
+
// template args, `serve` included.
|
|
128
|
+
const prior = root["bridge-api"];
|
|
129
|
+
const preservedArgs = prior && typeof prior === "object" && !Array.isArray(prior)
|
|
130
|
+
? refreshBridgeApiPackageSpec(prior.args, currentLauncherSpec())
|
|
131
|
+
: null;
|
|
132
|
+
root["bridge-api"] = preservedArgs ? { ...adaptedEntry, args: preservedArgs } : adaptedEntry;
|
|
123
133
|
merged[target.topLevelKey] = root;
|
|
124
134
|
return merged;
|
|
125
135
|
}
|
|
@@ -152,6 +152,10 @@ export async function writeMcpInstallState(cwd, input, deps) {
|
|
|
152
152
|
projectConfigPaths: normalizeProjectPaths(input.projectConfigPaths),
|
|
153
153
|
};
|
|
154
154
|
const finalPath = installStatePath(cwd);
|
|
155
|
+
return persistMcpInstallState(cwd, state, finalPath, deps);
|
|
156
|
+
}
|
|
157
|
+
/** Shared serialize-and-atomically-persist tail for the two writers below. */
|
|
158
|
+
async function persistMcpInstallState(cwd, state, finalPath, deps) {
|
|
155
159
|
const tempPath = installStateTempPath(cwd);
|
|
156
160
|
const serialized = serializeMcpInstallState(state);
|
|
157
161
|
try {
|
|
@@ -173,3 +177,31 @@ export async function writeMcpInstallState(cwd, input, deps) {
|
|
|
173
177
|
}
|
|
174
178
|
return { ok: true, path: finalPath, state };
|
|
175
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Record a locally installed project artifact WITHOUT clobbering existing state.
|
|
182
|
+
*
|
|
183
|
+
* `writeMcpInstallState` rebuilds the whole document from its `input` — it does
|
|
184
|
+
* not merge. A caller supplying only `projectConfigPaths` would therefore write
|
|
185
|
+
* an EMPTY `selectedPlatforms`, silently dropping the platform roster a later
|
|
186
|
+
* `doctor` run reports from (BAPI-679 review, E-13). This helper reads the
|
|
187
|
+
* current state first and writes back the union, so the additive record stays
|
|
188
|
+
* additive.
|
|
189
|
+
*
|
|
190
|
+
* Only the repo-relative PATH is stored. No OAuth token, API key, GitHub
|
|
191
|
+
* credential, or expanded workflow secret can reach the file: the state schema
|
|
192
|
+
* has no field for one, and the serializer emits only schema-approved keys.
|
|
193
|
+
*/
|
|
194
|
+
export async function recordInstalledProjectArtifact(cwd, relPath, deps) {
|
|
195
|
+
if (typeof relPath !== "string" || relPath.length === 0) {
|
|
196
|
+
return { ok: false, error: "artifact path must be a non-empty relative path" };
|
|
197
|
+
}
|
|
198
|
+
const existing = await readMcpInstallState(cwd, deps);
|
|
199
|
+
const priorPlatforms = existing.status === "valid" ? existing.state.selectedPlatforms : [];
|
|
200
|
+
const priorPaths = existing.status === "valid" ? existing.state.projectConfigPaths : [];
|
|
201
|
+
const state = {
|
|
202
|
+
version: MCP_INSTALL_STATE_VERSION,
|
|
203
|
+
selectedPlatforms: normalizePlatforms(priorPlatforms),
|
|
204
|
+
projectConfigPaths: normalizeProjectPaths([...priorPaths, relPath]),
|
|
205
|
+
};
|
|
206
|
+
return persistMcpInstallState(cwd, state, installStatePath(cwd), deps);
|
|
207
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Worktree MCP registration provisioning.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Shared by `start-tickets` worktrees AND, since BAPI-724, every Conductor
|
|
5
|
+
* executor worker worktree (`mcp_server/src/executor/job-runner.ts`, alongside
|
|
6
|
+
* the executor's deny-layer provisioning) — not start-tickets-only. Both
|
|
7
|
+
* callers write secret-free MCP registrations into both Claude (`.mcp.json`)
|
|
8
|
+
* and Cursor (`.cursor/mcp.json`) so the configured MCP servers are reachable
|
|
9
|
+
* from either editor. Every generated entry contains NO `env` block — it
|
|
10
|
+
* points at the `mcp-invoke` shim with an absolute `--project-root` and the
|
|
11
|
+
* target name; credentials are resolved at runtime by the shim, never written
|
|
12
|
+
* into the worktree.
|
|
10
13
|
*
|
|
11
14
|
* Registrations are driven by `.bridge/config`: the `bapi` target is always
|
|
12
15
|
* provisioned when present, and every supported Tier-2 target (e.g. `sfcc`) is
|
|
@@ -69,6 +72,12 @@ export function serverNameForMcpTarget(target) {
|
|
|
69
72
|
* value. Keeping the shim env-free preserves the credential-safety invariant
|
|
70
73
|
* (secrets are resolved by the `mcp-invoke` shim at launch, not persisted in the
|
|
71
74
|
* worktree) and equally avoids persisting non-secret operational metadata here.
|
|
75
|
+
*
|
|
76
|
+
* BAPI-724: the executor caller's `invocation` is resolved BEFORE spawn from its
|
|
77
|
+
* OWN running process (`resolveMcpShimInvocationForRuntime`, absolute-build-path
|
|
78
|
+
* form preferred) and never depends on a worker-injected `CONDUCTOR_NODE_PATH`
|
|
79
|
+
* or any other worker-shell env — the executor worker environment carries no
|
|
80
|
+
* such key at all (see `executor/env.ts`).
|
|
72
81
|
*/
|
|
73
82
|
export function buildShimMcpServerEntry(target, absoluteWorktreePath, invocation) {
|
|
74
83
|
return buildMcpShimCommand(invocation, target, absoluteWorktreePath);
|
|
@@ -284,6 +293,13 @@ function withWarnings(row, warnings) {
|
|
|
284
293
|
* add a secret-free warning but never abort provisioning.
|
|
285
294
|
* - A required write failure (or a malformed existing registration file) marks
|
|
286
295
|
* only this row `spawn-failed` with a descriptive error; other rows continue.
|
|
296
|
+
* This `spawn-failed` status is the `start-tickets` row contract: that
|
|
297
|
+
* caller propagates it verbatim into its summary report. The executor caller
|
|
298
|
+
* (BAPI-724) deliberately does NOT propagate it the same way — it constructs
|
|
299
|
+
* a synthetic `created` row per job, and its adapter
|
|
300
|
+
* (`provisionMcpForPreparedSpawn` in `job-runner.ts`) converts a
|
|
301
|
+
* `spawn-failed` result into a logged fail-open warning instead, since a
|
|
302
|
+
* provisioning failure must never block an executor worker from spawning.
|
|
287
303
|
* - After the registration files are written, the worktree's
|
|
288
304
|
* `.claude/settings.local.json` is updated to pre-approve those servers via
|
|
289
305
|
* `enabledMcpjsonServers` (suppressing Claude Code's per-project trust
|