@bridge_gpt/mcp-server 0.2.10 → 0.2.13
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 +66 -4
- package/build/commands.generated.js +6 -6
- package/build/conductor/bridge-api-client.js +2 -1
- package/build/conductor/cli.js +16 -16
- package/build/conductor/doctor.js +79 -2
- package/build/conductor/epic-reconcile.js +213 -16
- package/build/conductor/epic-runtime.js +215 -60
- package/build/conductor/epic-state.js +105 -16
- package/build/conductor/errors.js +12 -0
- package/build/conductor/git-ci-types.js +10 -0
- package/build/conductor/git-producer.js +4 -4
- package/build/conductor/local-merge.js +212 -0
- package/build/conductor/merge-ledger.js +7 -7
- package/build/conductor/pr-ci-producer.js +18 -8
- package/build/conductor/pr-review-producer.js +2 -2
- package/build/conductor/producer-ledger.js +5 -5
- package/build/conductor/spec-review-producer.js +88 -0
- package/build/conductor/store.js +100 -25
- package/build/conductor/supervisor-ledger.js +2 -2
- package/build/conductor/supervisor-merge.js +5 -5
- package/build/conductor/supervisor-message-relay.js +1 -1
- package/build/conductor/supervisor-runtime.js +10 -10
- package/build/conductor/taxonomy.js +9 -0
- package/build/conductor/tools.js +5 -5
- package/build/conductor-bin.js +12689 -19
- package/build/conductor-claude-hook-bin.js +167 -17
- package/build/decision-page-schema.js +26 -0
- package/build/doctor.js +203 -0
- package/build/index.js +25084 -3632
- package/build/init.js +57 -0
- package/build/install-bridge.js +80 -0
- package/build/mcp-profile.js +33 -30
- package/build/pipelines.generated.js +70 -48
- package/build/readme.generated.js +1 -1
- package/build/sfcc/client.js +151 -0
- package/build/sfcc/config.js +39 -0
- package/build/sfcc/credentials.js +136 -0
- package/build/sfcc/ocapi-shape.js +77 -0
- package/build/sfcc/output.js +39 -0
- package/build/sfcc/permissions.js +136 -0
- package/build/sfcc/reads-custom-object-def.js +119 -0
- package/build/sfcc/reads-site-preference.js +158 -0
- package/build/sfcc/reads-system-object.js +162 -0
- package/build/sfcc/register.js +73 -0
- package/build/sfcc/setup-status.js +114 -0
- package/build/sfcc/tool-wrapper.js +70 -0
- package/build/start-tickets-conductor.js +9 -1
- package/build/start-tickets.js +47 -4
- package/build/version.generated.js +1 -1
- package/package.json +7 -4
- package/pipelines/check-ci-ticket.json +2 -2
- package/pipelines/implement-ticket.json +2 -2
- package/pipelines/learn-repository.json +84 -42
- package/smoke-test/SMOKE-TEST.md +11 -17
package/build/init.js
CHANGED
|
@@ -78,6 +78,63 @@ export function buildBridgeConfigManifest(repoName) {
|
|
|
78
78
|
"",
|
|
79
79
|
].join("\n");
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Merge a group token (e.g. "sfcc") into the BRIDGE_MCP_PROFILE env value of the
|
|
83
|
+
* `bridge-api` entry in each present MCP config file (.mcp.json, .vscode/mcp.json,
|
|
84
|
+
* .cursor/mcp.json). Existing tokens are preserved; the merge is idempotent.
|
|
85
|
+
*
|
|
86
|
+
* Only files where a `bridge-api` entry already exists are modified — new files
|
|
87
|
+
* are never created by this helper.
|
|
88
|
+
*
|
|
89
|
+
* Returns the final resolved profile string (comma-joined) after merging, or an
|
|
90
|
+
* empty string if no config file was found or updated.
|
|
91
|
+
*/
|
|
92
|
+
export async function mergeBridgeApiProfileToken(cwd, token) {
|
|
93
|
+
const targets = [
|
|
94
|
+
{ path: ".mcp.json", topLevelKey: "mcpServers" },
|
|
95
|
+
{ path: ".vscode/mcp.json", topLevelKey: "servers" },
|
|
96
|
+
{ path: ".cursor/mcp.json", topLevelKey: "mcpServers" },
|
|
97
|
+
];
|
|
98
|
+
let lastProfile = "";
|
|
99
|
+
for (const target of targets) {
|
|
100
|
+
const fullPath = path.join(cwd, target.path);
|
|
101
|
+
let raw;
|
|
102
|
+
try {
|
|
103
|
+
raw = await readFile(fullPath, "utf-8");
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
continue; // file doesn't exist — skip
|
|
107
|
+
}
|
|
108
|
+
let parsed;
|
|
109
|
+
try {
|
|
110
|
+
parsed = JSON.parse(raw);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
continue; // invalid JSON — skip
|
|
114
|
+
}
|
|
115
|
+
const topLevel = parsed[target.topLevelKey];
|
|
116
|
+
if (!topLevel || !topLevel["bridge-api"]) {
|
|
117
|
+
continue; // no bridge-api entry — skip
|
|
118
|
+
}
|
|
119
|
+
const entry = topLevel["bridge-api"];
|
|
120
|
+
if (!entry.env)
|
|
121
|
+
entry.env = {};
|
|
122
|
+
const existing = entry.env.BRIDGE_MCP_PROFILE;
|
|
123
|
+
// Use raw string splitting (not resolveProfiles) so we don't force-inject
|
|
124
|
+
// "core" into the stored value. The runtime always adds "core" implicitly
|
|
125
|
+
// when it reads the value, so storing it would cause duplication.
|
|
126
|
+
const existingTokens = existing
|
|
127
|
+
? existing.split(",").map((t) => t.trim()).filter((t) => t.length > 0)
|
|
128
|
+
: [];
|
|
129
|
+
const tokenSet = new Set(existingTokens);
|
|
130
|
+
tokenSet.add(token);
|
|
131
|
+
const merged = Array.from(tokenSet).join(",");
|
|
132
|
+
entry.env.BRIDGE_MCP_PROFILE = merged;
|
|
133
|
+
await writeFile(fullPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
134
|
+
lastProfile = merged;
|
|
135
|
+
}
|
|
136
|
+
return lastProfile;
|
|
137
|
+
}
|
|
81
138
|
async function ensureGitignored(cwd, filePath) {
|
|
82
139
|
await ensureGitignoredShared(cwd, filePath, {
|
|
83
140
|
readFile: (p) => readFile(p, "utf-8"),
|
package/build/install-bridge.js
CHANGED
|
@@ -34,10 +34,12 @@
|
|
|
34
34
|
* gitignored) and the user-scoped credential store (Step 4).
|
|
35
35
|
*/
|
|
36
36
|
import { readFile, writeFile, mkdir, stat, rename, chmod, unlink } from "fs/promises";
|
|
37
|
+
import { spawn } from "child_process";
|
|
37
38
|
import os from "os";
|
|
38
39
|
import path from "path";
|
|
39
40
|
import readline from "readline";
|
|
40
41
|
import { runInit, buildBridgeApiEntry } from "./init.js";
|
|
42
|
+
import { VERSION } from "./version.generated.js";
|
|
41
43
|
import { validateRepoName } from "./bridge-config.js";
|
|
42
44
|
import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
43
45
|
import { upsertBapiCredential, getPrimaryCredentialStorePath, } from "./credential-store.js";
|
|
@@ -45,6 +47,23 @@ import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentName
|
|
|
45
47
|
import { buildGenericAgentShellCommand, getDefaultSpawnTerminalTabForPlatform, detectTerminal, createDefaultStartTicketsDeps, } from "./start-tickets.js";
|
|
46
48
|
/** Redaction sentinel — the API-key value is NEVER printed; this stands in. */
|
|
47
49
|
export const REDACTED_API_KEY = "<REDACTED>";
|
|
50
|
+
/**
|
|
51
|
+
* Always-surfaced success-path note (BAPI-451 W3, E-3). The pinned launcher's
|
|
52
|
+
* FIRST cold launch can resolve/download the package inline; if the MCP client's
|
|
53
|
+
* connect deadline is very short it may time out before the handshake. Pre-warming
|
|
54
|
+
* (below) makes this rare, but the note is surfaced unconditionally so the
|
|
55
|
+
* fail-soft backstop is always discoverable.
|
|
56
|
+
*/
|
|
57
|
+
export const MCP_TIMEOUT_GUIDANCE = "Note: if your MCP client has a very short connect deadline, raise MCP_TIMEOUT for the first launch " +
|
|
58
|
+
"(the initial npx package resolution/download can exceed a short connect timeout).";
|
|
59
|
+
/** The exact, version-pinned launcher spec the pre-warm spawns (fast-exiting --version). */
|
|
60
|
+
export function buildPrewarmArgs() {
|
|
61
|
+
return ["-y", `@bridge_gpt/mcp-server@${VERSION}`, "--version"];
|
|
62
|
+
}
|
|
63
|
+
/** Secret-free preview of the pre-warm command (no env, no key). */
|
|
64
|
+
export function buildPrewarmCommandPreview() {
|
|
65
|
+
return `npx ${buildPrewarmArgs().join(" ")}`;
|
|
66
|
+
}
|
|
48
67
|
/** The natural-language sequential prompt handed to the spawned agent session. */
|
|
49
68
|
export const INSTALL_BRIDGE_AGENT_PROMPT = "Please execute the /install-bridge command. Once it completes successfully, execute the /learn-repository command.";
|
|
50
69
|
/** Default base URL when `BAPI_BASE_URL` is unset (mirrors index.ts). */
|
|
@@ -198,6 +217,44 @@ function promptLineViaReadline(promptText) {
|
|
|
198
217
|
});
|
|
199
218
|
});
|
|
200
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Default pre-warm spawner (BAPI-451 W3). Spawns array-based (`shell: false`) so
|
|
222
|
+
* no argument is ever shell-interpreted, ignores all child stdio (node-gyp /
|
|
223
|
+
* download noise stays out of the CLI UX), bounds the run with a 60s timeout, and
|
|
224
|
+
* passes a SANITIZED env clone with `BAPI_API_KEY` deleted — the `--version`
|
|
225
|
+
* pre-warm never needs the key, and it must never reach an npm lifecycle script.
|
|
226
|
+
* Always resolves (never rejects): a spawn error / non-zero exit / timeout becomes
|
|
227
|
+
* `{ ok: false, warning }` with secret-free warning text.
|
|
228
|
+
*/
|
|
229
|
+
function spawnPrewarmDefault(command, args, env) {
|
|
230
|
+
return new Promise((resolve) => {
|
|
231
|
+
const sanitizedEnv = { ...env };
|
|
232
|
+
delete sanitizedEnv.BAPI_API_KEY;
|
|
233
|
+
try {
|
|
234
|
+
const child = spawn(command, args, {
|
|
235
|
+
shell: false,
|
|
236
|
+
stdio: "ignore",
|
|
237
|
+
timeout: 60_000,
|
|
238
|
+
env: sanitizedEnv,
|
|
239
|
+
});
|
|
240
|
+
child.on("error", () => resolve({ ok: false, warning: "the pre-warm process could not be started" }));
|
|
241
|
+
child.on("close", (code, signal) => {
|
|
242
|
+
if (signal) {
|
|
243
|
+
resolve({ ok: false, warning: `the pre-warm process timed out or was terminated (${signal})` });
|
|
244
|
+
}
|
|
245
|
+
else if (code === 0) {
|
|
246
|
+
resolve({ ok: true });
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
resolve({ ok: false, warning: `the pre-warm process exited with code ${code}` });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
resolve({ ok: false, warning: "the pre-warm process could not be started" });
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
201
258
|
/** Build default deps from the live process. */
|
|
202
259
|
export function createDefaultInstallBridgeDeps() {
|
|
203
260
|
const isTTY = Boolean(process.stdin.isTTY);
|
|
@@ -217,6 +274,7 @@ export function createDefaultInstallBridgeDeps() {
|
|
|
217
274
|
promptSecret: isTTY ? promptSecretViaReadline : undefined,
|
|
218
275
|
promptLine: isTTY ? promptLineViaReadline : undefined,
|
|
219
276
|
fetch: (...args) => fetch(...args),
|
|
277
|
+
spawnPrewarm: spawnPrewarmDefault,
|
|
220
278
|
runInit,
|
|
221
279
|
upsertCredential: upsertBapiCredential,
|
|
222
280
|
buildShellCommand: buildGenericAgentShellCommand,
|
|
@@ -472,6 +530,8 @@ export function buildDryRunPreview(plan) {
|
|
|
472
530
|
...(plan.manualEditors.length > 0
|
|
473
531
|
? [` ${plan.manualEditors.join(" + ")}: detected (global config) — manual setup instructions would be printed.`]
|
|
474
532
|
: []),
|
|
533
|
+
`Step 3b — pre-warm the version-pinned launcher bucket (fail-open, env sanitized — BAPI_API_KEY removed): ${plan.prewarmCommand}`,
|
|
534
|
+
MCP_TIMEOUT_GUIDANCE,
|
|
475
535
|
`Step 4 — persist routing credential: target ${plan.credentialTarget} at ${plan.credentialStorePath}`,
|
|
476
536
|
`Step 5 — spawn agent session: ${plan.spawnCommand}`,
|
|
477
537
|
];
|
|
@@ -590,6 +650,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
590
650
|
credentialTarget: `bapi:${repoName}`,
|
|
591
651
|
credentialStorePath,
|
|
592
652
|
pingUrl: buildPingUrl(baseUrl, repoName),
|
|
653
|
+
prewarmCommand: buildPrewarmCommandPreview(),
|
|
593
654
|
spawnCommand,
|
|
594
655
|
};
|
|
595
656
|
// ---- --dry-run: preview every step, strictly no side effects ----
|
|
@@ -639,6 +700,25 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
639
700
|
const manualInstructions = buildManualHostInstructions(entry, manualEditors);
|
|
640
701
|
if (manualInstructions)
|
|
641
702
|
log(manualInstructions);
|
|
703
|
+
// ---- Step 3b — pre-warm the @${VERSION}-pinned _npx bucket (BAPI-451 W3) ----
|
|
704
|
+
// The launcher just written is pinned to @${VERSION}, a DIFFERENT _npx bucket
|
|
705
|
+
// than the @latest bucket this `npx … install-bridge` invocation warmed. Spawn
|
|
706
|
+
// the exact pinned launcher once with the fast-exiting `--version` flag so the
|
|
707
|
+
// first real MCP launch resolves from a warm bucket instead of paying the cold
|
|
708
|
+
// install inline (which can exceed the client's connect deadline). Strictly
|
|
709
|
+
// fail-open: a failure warns (secret-free) but never fails the install.
|
|
710
|
+
log(" pre-warming the version-pinned launcher bucket…");
|
|
711
|
+
const prewarm = await deps.spawnPrewarm("npx", buildPrewarmArgs(), deps.env);
|
|
712
|
+
if (prewarm.ok) {
|
|
713
|
+
log(" launcher bucket warmed (the first MCP launch will not pay a cold install).");
|
|
714
|
+
log(` ${MCP_TIMEOUT_GUIDANCE}`);
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
// Escalate to a stronger, still secret-free warning when pre-warm didn't
|
|
718
|
+
// complete cleanly — but the install itself still succeeds (exit unaffected).
|
|
719
|
+
errorLog(`Warning: could not pre-warm the version-pinned launcher bucket${prewarm.warning ? ` (${prewarm.warning})` : ""}. ` +
|
|
720
|
+
`The first MCP launch may pay a one-time cold install and could be slow. ${MCP_TIMEOUT_GUIDANCE}`);
|
|
721
|
+
}
|
|
642
722
|
// ---- Step 4 — persist routing credential (non-blocking / fail-open) ----
|
|
643
723
|
log("Step 4/5 — persisting routing credential…");
|
|
644
724
|
try {
|
package/build/mcp-profile.js
CHANGED
|
@@ -1,43 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP profile resolver (BAPI-
|
|
2
|
+
* MCP profile resolver (BAPI-453).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Generalizes the startup-time tool registration model from a single-string
|
|
5
|
+
* profile to a multi-valued group set. BRIDGE_MCP_PROFILE accepts a
|
|
6
|
+
* comma-separated list of group tokens; unknown tokens are silently dropped
|
|
7
|
+
* (security sentinel — raw input is never echoed). "core" is always implicitly
|
|
8
|
+
* included. "full" is an input shortcut that expands to every known group.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
* and cannot be changed mid-session via tools/list_changed.
|
|
10
|
+
* Groups are resolved once at process startup and cannot change mid-session.
|
|
10
11
|
*/
|
|
11
|
-
const
|
|
12
|
+
const VALID_GROUPS = new Set([
|
|
12
13
|
"core",
|
|
13
14
|
"conductor",
|
|
14
15
|
"pipeline-authoring",
|
|
15
|
-
"
|
|
16
|
+
"sfcc",
|
|
16
17
|
]);
|
|
17
18
|
/**
|
|
18
|
-
* Resolve the active MCP
|
|
19
|
+
* Resolve the active MCP groups from a raw env-var string.
|
|
19
20
|
*
|
|
20
|
-
* - `undefined` or blank → `"core"` (fail-safe to smallest surface).
|
|
21
|
-
* -
|
|
22
|
-
* -
|
|
21
|
+
* - `undefined` or blank → `{"core"}` (fail-safe to smallest surface).
|
|
22
|
+
* - `"full"` (any case) → all four groups.
|
|
23
|
+
* - Comma-separated list: each token is trimmed, lowercased, and checked
|
|
24
|
+
* against VALID_GROUPS. Unknown tokens are dropped. Known tokens are unioned
|
|
25
|
+
* with the implicit `"core"` baseline.
|
|
26
|
+
* - All-unknown tokens → `{"core"}`.
|
|
27
|
+
* - Raw input is never echoed in the output (security NFR).
|
|
23
28
|
*/
|
|
24
|
-
export function
|
|
29
|
+
export function resolveProfiles(raw) {
|
|
30
|
+
const baseline = new Set(["core"]);
|
|
25
31
|
if (raw === undefined)
|
|
26
|
-
return
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*/
|
|
41
|
-
export function profileIncludes(profile, group) {
|
|
42
|
-
return profile === "full" || profile === group;
|
|
32
|
+
return baseline;
|
|
33
|
+
const trimmed = raw.trim();
|
|
34
|
+
if (trimmed === "")
|
|
35
|
+
return baseline;
|
|
36
|
+
const tokens = trimmed.split(",").map((t) => t.trim().toLowerCase());
|
|
37
|
+
if (tokens.some((t) => t === "full")) {
|
|
38
|
+
return new Set(["core", "conductor", "pipeline-authoring", "sfcc"]);
|
|
39
|
+
}
|
|
40
|
+
for (const token of tokens) {
|
|
41
|
+
if (VALID_GROUPS.has(token)) {
|
|
42
|
+
baseline.add(token);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return baseline;
|
|
43
46
|
}
|