@bridge_gpt/mcp-server 0.2.29 → 0.2.31
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 +2 -1
- package/build/agent-registry.js +0 -5
- package/build/command-assets-doctor.js +72 -0
- package/build/command-provisioning.js +157 -0
- package/build/commands.generated.js +2 -2
- package/build/conductor-bin.js +1 -1
- package/build/executor/test-clock.js +3 -1
- package/build/executor/worktree.js +31 -2
- package/build/git-ignore-utils.js +67 -14
- package/build/index.js +2881 -2860
- package/build/install-bridge.js +123 -96
- package/build/readme.generated.js +1 -1
- package/build/start-tickets-prereqs.js +45 -0
- package/build/start-tickets.js +46 -21
- package/build/version.generated.js +1 -1
- package/package.json +3 -3
package/build/install-bridge.js
CHANGED
|
@@ -150,18 +150,27 @@ export function buildPrewarmCommandPreview() {
|
|
|
150
150
|
* (`maybe_auto_parse_if_ready`), with no prompt on any path.
|
|
151
151
|
*/
|
|
152
152
|
export const INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command in the install-spawn context (tell the command it is running " +
|
|
153
|
-
"in the install-spawn context so it SKIPS its Stage 8, Stage 9, and Stage 10 offers
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
153
|
+
"in the install-spawn context so it SKIPS its Stage 8, Stage 9, and Stage 10 offers). " +
|
|
154
|
+
"The command chooses its own mode from the install manifest's `configured` flag — do NOT force a " +
|
|
155
|
+
"mode: when `configured` is false it runs its fresh-configuration flow, and when `configured` is " +
|
|
156
|
+
"true it runs JOIN MODE. In JOIN MODE the command must NOT derive, approve, apply, or offer any " +
|
|
157
|
+
"configuration and applies zero fields for any caller; a joining member's only closing interaction " +
|
|
158
|
+
"is the welcome plus the concise capability report (no /learn-repository prompt for a member). " +
|
|
159
|
+
"Complete the command's concise capability report: the single 'What Bridge " +
|
|
157
160
|
"can help with' section, rendered exactly as the server's concise_tool_capabilities field gives it " +
|
|
158
161
|
"(Regularly useful, then Occasionally useful, each tool's display_name only, plus each tier's " +
|
|
159
162
|
"server-computed '+N more' where non-zero). Do not render the obsolete five-section report, and do " +
|
|
160
163
|
"not locally filter, count, or fall back to the complete tool_capabilities catalog. " +
|
|
164
|
+
"The install-spawn skip set is exactly Stage 8, Stage 9, and Stage 10. The command-owned Stage 11 " +
|
|
165
|
+
"(invite teammates) is NOT in that skip set: it is independently gated and may run only when the " +
|
|
166
|
+
"caller's role is admin AND customer_type is b2b AND an interactive response is available; " +
|
|
167
|
+
"otherwise it is skipped silently. If Stage 11 mints a teammate key, show that plaintext key " +
|
|
168
|
+
"exactly once and never repeat it in any summary, retry, or diagnostic. " +
|
|
161
169
|
"Do NOT ask any indexing question, call parse_repository, mention /parse-repository, or claim " +
|
|
162
170
|
"indexing has started or is pending — indexing is decided entirely by the server-side readiness " +
|
|
163
171
|
"funnel with no visibility from this session, so say nothing about it at all. " +
|
|
164
|
-
"After the capability report, recommend /learn-repository as the
|
|
172
|
+
"After the capability report on the fresh-configuration path, recommend /learn-repository as the " +
|
|
173
|
+
"next step: explain briefly that it " +
|
|
165
174
|
"learns repository-specific architecture, review, testing, correctness, and validation-manual " +
|
|
166
175
|
"configuration by researching the actual codebase. Do NOT run /learn-repository yourself — only " +
|
|
167
176
|
"recommend it; running it is the human's next explicit invocation. " +
|
|
@@ -169,9 +178,11 @@ export const INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command
|
|
|
169
178
|
"integration's own configure_in pointer, verbatim. The pointer is per-integration and is NOT " +
|
|
170
179
|
"always the setup UI: GitHub's is a terminal command (connect-github), while Jira, SFCC, and " +
|
|
171
180
|
"Bitbucket point at the setup UI. Follow whatever the report says rather than assuming. " +
|
|
172
|
-
"
|
|
181
|
+
"On the fresh-configuration path, end with an explicit summary line stating how many config fields " +
|
|
182
|
+
"the apply_install_manifest call " +
|
|
173
183
|
"applied (e.g. 'Applied 8 of 9 derived fields') — if 0 fields were applied, say so loudly and " +
|
|
174
|
-
"explain what is still pending."
|
|
184
|
+
"explain what is still pending. In JOIN MODE there is no apply — state instead that the project " +
|
|
185
|
+
"was already configured and zero changes were made, without a fabricated applied count.";
|
|
175
186
|
/** Default base URL when `BAPI_BASE_URL` is unset (mirrors index.ts). */
|
|
176
187
|
export const DEFAULT_BAPI_BASE_URL = "https://bridgegpt-api.com";
|
|
177
188
|
/** Default docs dir when `BAPI_DOCS_DIR` is unset (mirrors index.ts). */
|
|
@@ -185,10 +196,13 @@ export function getInstallBridgeUsage() {
|
|
|
185
196
|
"One-command Bridge API project bootstrap. Scaffolds the project, writes the",
|
|
186
197
|
"per-host MCP config with your credentials, verifies connectivity, persists the",
|
|
187
198
|
"routing credential, then — on a TTY, only after a Y/N consent prompt — opens a",
|
|
188
|
-
"fresh session in your selected tool
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
"
|
|
199
|
+
"fresh session in your selected tool. If the project is not yet configured it",
|
|
200
|
+
"derives the remaining config, presents a concise capability report, and",
|
|
201
|
+
"recommends /learn-repository. If the project is already configured it instead",
|
|
202
|
+
"joins you to it without proposing or applying any changes and just shows the",
|
|
203
|
+
"concise capability report (a b2b admin is additionally offered a teammate-invite",
|
|
204
|
+
"step). So not every run applies config fields. Indexing is never asked about —",
|
|
205
|
+
"it starts automatically once the repository reaches full parse readiness.",
|
|
192
206
|
"",
|
|
193
207
|
"Run it bare — `install-bridge` with no flags — in a terminal and it asks",
|
|
194
208
|
`\`${INSTALL_BRIDGE_KEY_SELECTOR_PROMPT.trim()}\` first. Answer yes (or press Enter) for the`,
|
|
@@ -261,10 +275,12 @@ export function getInstallBridgeUsage() {
|
|
|
261
275
|
` ${HOST_PLATFORM_ORDER.join(", ")}.`,
|
|
262
276
|
" Both --tools=claude-code,codex and",
|
|
263
277
|
" --tools claude-code,codex are accepted. On an",
|
|
264
|
-
" interactive terminal WITHOUT this flag
|
|
265
|
-
"
|
|
266
|
-
" (
|
|
267
|
-
"
|
|
278
|
+
" interactive terminal WITHOUT this flag you get a",
|
|
279
|
+
" numbered picker: enter one or more tool numbers,",
|
|
280
|
+
" comma-separated (e.g. 1,3), and press Enter once —",
|
|
281
|
+
" no tool is pre-selected (not even Claude Code) and",
|
|
282
|
+
" you must select at least one. A non-interactive",
|
|
283
|
+
" (non-TTY) run without",
|
|
268
284
|
" --tools writes the legacy automatic set (Claude",
|
|
269
285
|
" Code plus any detected Cursor / Copilot VS Code)",
|
|
270
286
|
" and never opens an agent terminal (it prints",
|
|
@@ -284,12 +300,15 @@ export function getInstallBridgeUsage() {
|
|
|
284
300
|
" --agent claude|cursor-agent Explicit launch override for the post-install",
|
|
285
301
|
" session — it always wins, and there is NO Claude",
|
|
286
302
|
" default. Without it the launch tool is derived",
|
|
287
|
-
" from your selection: Claude Code opens `claude
|
|
288
|
-
" Cursor
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
303
|
+
" from your selection: Claude Code opens `claude`.",
|
|
304
|
+
" Cursor is NOT auto-opened (its cursor-agent CLI",
|
|
305
|
+
" first-run workspace-trust prompt collapses a spawned",
|
|
306
|
+
" session); selecting Cursor writes .cursor/mcp.json",
|
|
307
|
+
" and prints how to finish by running /install-bridge",
|
|
308
|
+
" in a new Cursor session. A selection whose tools have",
|
|
309
|
+
" no agentic CLI (e.g. Copilot) likewise opens nothing",
|
|
310
|
+
" and prints how to finish configuring later. Passing",
|
|
311
|
+
" --agent cursor-agent still force-spawns it.",
|
|
293
312
|
" -h, --help Show this help.",
|
|
294
313
|
"",
|
|
295
314
|
"Environment: BAPI_BASE_URL (default https://bridgegpt-api.com) and BAPI_DOCS_DIR",
|
|
@@ -1009,88 +1028,78 @@ export async function resolveRepoName(options, deps, mode = "existing-registrati
|
|
|
1009
1028
|
/** A per-host MCP config target (mirrors runInit's configTargets shape). */
|
|
1010
1029
|
/** Stable wording of the interactive tool-selection prompt (BAPI-635). */
|
|
1011
1030
|
export const INSTALL_BRIDGE_TOOL_SELECTOR_PROMPT = "Which AI coding tools do you use on this project?";
|
|
1031
|
+
/** A one-based selection token is exactly one or more digits, no sign/decimal/leading-zero. */
|
|
1032
|
+
const SELECTION_TOKEN_PATTERN = /^[1-9][0-9]*$/;
|
|
1012
1033
|
/**
|
|
1013
|
-
* Interactive numbered
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1034
|
+
* Interactive single-shot numbered picker on stderr (TTY only). Renders the
|
|
1035
|
+
* heading and each option exactly once, then accepts ONE strict
|
|
1036
|
+
* comma-separated line of one-based numbers and resolves immediately — there
|
|
1037
|
+
* is no preselected/toggle state and no separate confirmation step (BAPI-663).
|
|
1038
|
+
*
|
|
1039
|
+
* A blank line or a line containing any invalid token (non-numeric, zero,
|
|
1040
|
+
* signed, leading-zero, decimal, out-of-range) is rejected atomically — the
|
|
1041
|
+
* whole line is discarded, one error line is printed, and the picker
|
|
1042
|
+
* re-prompts without re-rendering the option list. A valid line is
|
|
1043
|
+
* deduplicated and returned in `options` order regardless of entry order.
|
|
1044
|
+
*
|
|
1045
|
+
* Resolves to `[]` on EOF / synchronous close before any line has been
|
|
1046
|
+
* committed, so a readline regression can never hang. Returns `[]`
|
|
1047
|
+
* immediately, without creating a readline interface, when `options` is
|
|
1048
|
+
* empty.
|
|
1021
1049
|
*/
|
|
1022
|
-
export function promptMultiSelectViaReadline(promptText, options,
|
|
1050
|
+
export function promptMultiSelectViaReadline(promptText, options, input = process.stdin, output = process.stderr) {
|
|
1051
|
+
if (options.length === 0)
|
|
1052
|
+
return Promise.resolve([]);
|
|
1023
1053
|
return new Promise((resolve) => {
|
|
1024
|
-
const selected = new Set(defaults);
|
|
1025
|
-
const render = () => {
|
|
1026
|
-
output.write(`\n${promptText}\n`);
|
|
1027
|
-
output.write("[x] = selected · [ ] = not selected\n");
|
|
1028
|
-
options.forEach((opt, idx) => {
|
|
1029
|
-
const mark = selected.has(opt.id) ? "[x]" : "[ ]";
|
|
1030
|
-
output.write(` ${idx + 1}. ${mark} ${opt.label}\n`);
|
|
1031
|
-
});
|
|
1032
|
-
output.write("Type numbers to toggle, e.g. 1,3 — then Enter.\n");
|
|
1033
|
-
output.write("Press Enter on an empty line to confirm — re-typing a number un-selects it.\n");
|
|
1034
|
-
output.write("Enter numbers to toggle (comma-separated), or press Enter to accept: ");
|
|
1035
|
-
};
|
|
1036
1054
|
const rl = readline.createInterface({ input, output });
|
|
1037
|
-
let
|
|
1038
|
-
const finish = () => {
|
|
1039
|
-
|
|
1055
|
+
let settled = false;
|
|
1056
|
+
const finish = (result) => {
|
|
1057
|
+
if (settled)
|
|
1058
|
+
return;
|
|
1059
|
+
settled = true;
|
|
1040
1060
|
rl.close();
|
|
1041
|
-
resolve(
|
|
1061
|
+
resolve(result);
|
|
1042
1062
|
};
|
|
1043
1063
|
// EOF / synchronous close must resolve rather than deadlock the top-level
|
|
1044
|
-
// await; `
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1064
|
+
// await; the `settled` guard stops a synchronous `close` (fired by `rl.close()`
|
|
1065
|
+
// above) from replacing an already-committed answer with the EOF fallback.
|
|
1066
|
+
rl.on("close", () => finish([]));
|
|
1067
|
+
output.write(`\n${promptText}\n`);
|
|
1068
|
+
options.forEach((opt, idx) => {
|
|
1069
|
+
output.write(` ${idx + 1}. ${opt.label}\n`);
|
|
1048
1070
|
});
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
rl.question("", (answer) => {
|
|
1071
|
+
output.write("Enter the number(s) of the tools you use (e.g. 1,3), then Enter.\n");
|
|
1072
|
+
const prompt = () => {
|
|
1073
|
+
rl.question("> ", (answer) => {
|
|
1052
1074
|
const trimmed = answer.trim();
|
|
1053
1075
|
if (trimmed.length === 0) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
// still resolves the current — possibly empty — set so input can never hang.
|
|
1057
|
-
if (selected.size === 0) {
|
|
1058
|
-
output.write("Select at least one tool.\n");
|
|
1059
|
-
ask();
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
finish();
|
|
1076
|
+
output.write("Select at least one tool.\n");
|
|
1077
|
+
prompt();
|
|
1063
1078
|
return;
|
|
1064
1079
|
}
|
|
1065
1080
|
const tokens = trimmed.split(",").map((t) => t.trim());
|
|
1066
|
-
const
|
|
1067
|
-
let bad = false;
|
|
1081
|
+
const indices = [];
|
|
1068
1082
|
for (const tok of tokens) {
|
|
1083
|
+
if (!SELECTION_TOKEN_PATTERN.test(tok)) {
|
|
1084
|
+
output.write(`Invalid selection. Enter numbers between 1 and ${options.length}.\n`);
|
|
1085
|
+
prompt();
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1069
1088
|
const n = Number(tok);
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1072
|
-
|
|
1089
|
+
if (n < 1 || n > options.length) {
|
|
1090
|
+
output.write(`Invalid selection. Enter numbers between 1 and ${options.length}.\n`);
|
|
1091
|
+
prompt();
|
|
1092
|
+
return;
|
|
1073
1093
|
}
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1076
|
-
if (bad) {
|
|
1077
|
-
output.write(`Invalid selection. Enter numbers between 1 and ${options.length}.\n`);
|
|
1078
|
-
ask();
|
|
1079
|
-
return;
|
|
1094
|
+
indices.push(n);
|
|
1080
1095
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
else
|
|
1086
|
-
selected.add(opt.id);
|
|
1087
|
-
}
|
|
1088
|
-
// Toggling re-renders the updated checklist and re-prompts; acceptance is a
|
|
1089
|
-
// separate, deliberate bare Enter (AC-1/AC-3) rather than an implicit accept.
|
|
1090
|
-
ask();
|
|
1096
|
+
const chosen = new Set(indices);
|
|
1097
|
+
finish(options
|
|
1098
|
+
.filter((_, idx) => chosen.has(idx + 1))
|
|
1099
|
+
.map((o) => o.id));
|
|
1091
1100
|
});
|
|
1092
1101
|
};
|
|
1093
|
-
|
|
1102
|
+
prompt();
|
|
1094
1103
|
});
|
|
1095
1104
|
}
|
|
1096
1105
|
/**
|
|
@@ -1110,13 +1119,11 @@ export async function resolveSelectedHostPlatforms(deps, options) {
|
|
|
1110
1119
|
}
|
|
1111
1120
|
const ctx = await buildDetectionContext(deps);
|
|
1112
1121
|
const detected = new Set(detectDefaultPlatforms(ctx));
|
|
1113
|
-
// 2. Interactive
|
|
1114
|
-
// picker
|
|
1115
|
-
// inclusion and detected-platform seeding) and requires an explicit selection.
|
|
1122
|
+
// 2. Interactive single-shot numbered picker on a TTY. No row is pre-selected
|
|
1123
|
+
// — the picker has no default state and requires an explicit selection.
|
|
1116
1124
|
if (deps.isTTY && deps.promptMultiSelect) {
|
|
1117
1125
|
const optionList = allHostTargets().map((t) => ({ id: t.id, label: t.label }));
|
|
1118
|
-
const
|
|
1119
|
-
const chosen = await deps.promptMultiSelect(INSTALL_BRIDGE_TOOL_SELECTOR_PROMPT, optionList, defaults);
|
|
1126
|
+
const chosen = await deps.promptMultiSelect(INSTALL_BRIDGE_TOOL_SELECTOR_PROMPT, optionList);
|
|
1120
1127
|
return HOST_PLATFORM_ORDER.filter((id) => chosen.includes(id));
|
|
1121
1128
|
}
|
|
1122
1129
|
// 3. Legacy non-TTY automatic set: Claude + detected Cursor / Copilot VS Code.
|
|
@@ -1127,18 +1134,37 @@ export async function resolveSelectedHostPlatforms(deps, options) {
|
|
|
1127
1134
|
legacy.push("copilot-vscode");
|
|
1128
1135
|
return HOST_PLATFORM_ORDER.filter((id) => legacy.includes(id));
|
|
1129
1136
|
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Launch agents install-bridge will NEVER auto-spawn from a host SELECTION, even
|
|
1139
|
+
* though their CLI exists and the host's MCP config is still written normally.
|
|
1140
|
+
*
|
|
1141
|
+
* `cursor-agent` is excluded: its FIRST-RUN workspace-trust handshake collapses
|
|
1142
|
+
* under our osascript-spawned launch — the "Workspace Trust Required" dialog
|
|
1143
|
+
* renders, then the process exits before a selection registers, so configuration
|
|
1144
|
+
* never runs (reproduced on a pristine, never-trusted workspace; the identical
|
|
1145
|
+
* command works only where Cursor already trusts the directory). The trust gate is
|
|
1146
|
+
* cursor-agent's own behavior on an untrusted workspace and is not something our
|
|
1147
|
+
* launch can drive, so we treat Cursor as a no-auto-launch host: selecting it
|
|
1148
|
+
* still writes `.cursor/mcp.json`, then prints the manual `/install-bridge`
|
|
1149
|
+
* continuation (same path as Copilot/Codex/Windsurf, which have no agentic CLI).
|
|
1150
|
+
* An explicit `--agent cursor-agent` override still spawns it — a deliberate,
|
|
1151
|
+
* documented power-user opt-in that bypasses this selection guard.
|
|
1152
|
+
*/
|
|
1153
|
+
const SELECTION_NON_LAUNCHABLE_AGENTS = new Set(["cursor-agent"]);
|
|
1130
1154
|
/**
|
|
1131
1155
|
* Resolve the post-install launch decision from the selected host platforms and an
|
|
1132
1156
|
* OPTIONAL explicit `--agent` override (BAPI-657). Pure and total:
|
|
1133
1157
|
*
|
|
1134
1158
|
* - An explicit `--agent` is an ABSOLUTE override (ratified AC-4): a valid explicit
|
|
1135
1159
|
* agent always returns `spawn(agent)`, regardless of which tools were selected —
|
|
1136
|
-
* the flag is honored even for a host outside the selection
|
|
1160
|
+
* the flag is honored even for a host outside the selection, and it bypasses the
|
|
1161
|
+
* {@link SELECTION_NON_LAUNCHABLE_AGENTS} guard below.
|
|
1137
1162
|
* - Otherwise the selection drives it: map each selected host through
|
|
1138
|
-
* {@link agentForPlatform}, drop non-launchable
|
|
1139
|
-
*
|
|
1140
|
-
* `manual("
|
|
1141
|
-
* `
|
|
1163
|
+
* {@link agentForPlatform}, drop hosts that are non-launchable OR excluded from
|
|
1164
|
+
* selection auto-spawn ({@link SELECTION_NON_LAUNCHABLE_AGENTS}, e.g. Cursor),
|
|
1165
|
+
* dedup in registry order, then return `manual("empty-selection")` for an empty
|
|
1166
|
+
* selection, `manual("no-launchable-agent")` for zero launchable agents, `spawn`
|
|
1167
|
+
* for one, or `choose-one` for more than one.
|
|
1142
1168
|
*
|
|
1143
1169
|
* Never prompts, logs, resolves an {@link AgentSpec}, or touches the filesystem.
|
|
1144
1170
|
*/
|
|
@@ -1154,8 +1180,9 @@ export function resolveInstallBridgeLaunchDecision(selectedPlatforms, explicitAg
|
|
|
1154
1180
|
if (!selectedPlatforms.includes(id))
|
|
1155
1181
|
continue;
|
|
1156
1182
|
const agent = agentForPlatform(id);
|
|
1157
|
-
if (agent && !agents.includes(agent))
|
|
1183
|
+
if (agent && !SELECTION_NON_LAUNCHABLE_AGENTS.has(agent) && !agents.includes(agent)) {
|
|
1158
1184
|
agents.push(agent);
|
|
1185
|
+
}
|
|
1159
1186
|
}
|
|
1160
1187
|
if (agents.length === 0)
|
|
1161
1188
|
return { kind: "manual", reason: "no-launchable-agent" };
|
|
@@ -1242,7 +1269,7 @@ export function buildManualInstallBridgeContinuation(kind, toolLabels) {
|
|
|
1242
1269
|
const toolPhrase = formatToolLabelPhrase(toolLabels);
|
|
1243
1270
|
return [
|
|
1244
1271
|
`To finish configuring this project, open it in ${toolPhrase} that has the`,
|
|
1245
|
-
"Bridge MCP server configured and run /install-bridge.",
|
|
1272
|
+
"Bridge MCP server configured, start a new session, and run /install-bridge.",
|
|
1246
1273
|
"Until the project is configured, your Bridge MCP tools stay limited.",
|
|
1247
1274
|
].join("\n");
|
|
1248
1275
|
}
|