@bridge_gpt/mcp-server 0.2.9 → 0.2.12

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 (43) hide show
  1. package/README.md +59 -7
  2. package/build/commands.generated.js +6 -6
  3. package/build/conductor/bridge-api-client.js +263 -35
  4. package/build/conductor/cli.js +38 -17
  5. package/build/conductor/doctor.js +35 -2
  6. package/build/conductor/done-gate.js +301 -58
  7. package/build/conductor/epic-reconcile.js +318 -4
  8. package/build/conductor/epic-runtime.js +382 -18
  9. package/build/conductor/epic-state.js +188 -15
  10. package/build/conductor/errors.js +12 -0
  11. package/build/conductor/git-ci-types.js +16 -0
  12. package/build/conductor/git-producer.js +4 -4
  13. package/build/conductor/merge-ledger.js +7 -7
  14. package/build/conductor/pr-ci-producer.js +118 -19
  15. package/build/conductor/pr-review-producer.js +116 -0
  16. package/build/conductor/producer-ledger.js +5 -5
  17. package/build/conductor/spec-review-producer.js +88 -0
  18. package/build/conductor/store.js +105 -26
  19. package/build/conductor/supervisor-ledger.js +2 -2
  20. package/build/conductor/supervisor-merge.js +5 -5
  21. package/build/conductor/supervisor-message-relay.js +32 -1
  22. package/build/conductor/supervisor-runtime.js +10 -10
  23. package/build/conductor/taxonomy.js +8 -0
  24. package/build/conductor/tools.js +7 -7
  25. package/build/conductor-bin.js +12350 -19
  26. package/build/conductor-claude-hook-bin.js +167 -17
  27. package/build/decision-page-schema.js +26 -0
  28. package/build/doctor.js +200 -0
  29. package/build/index.js +23696 -4351
  30. package/build/init.js +481 -0
  31. package/build/install-bridge.js +772 -0
  32. package/build/mcp-profile.js +43 -0
  33. package/build/pipelines.generated.js +70 -48
  34. package/build/readme.generated.js +1 -1
  35. package/build/start-tickets-conductor.js +1 -0
  36. package/build/start-tickets.js +186 -10
  37. package/build/upgrade-cli.js +154 -0
  38. package/build/version.generated.js +1 -1
  39. package/package.json +7 -4
  40. package/pipelines/check-ci-ticket.json +2 -2
  41. package/pipelines/implement-ticket.json +2 -2
  42. package/pipelines/learn-repository.json +84 -42
  43. package/smoke-test/SMOKE-TEST.md +11 -17
package/build/init.js ADDED
@@ -0,0 +1,481 @@
1
+ /**
2
+ * Bridge API project scaffolding (`--init`).
3
+ *
4
+ * Extracted from `index.ts` (BAPI-429) so the scaffolding logic can be reused by
5
+ * the `install-bridge` CLI subcommand without importing `index.ts` — `index.ts`
6
+ * constructs and connects the MCP server at module top level, so importing it
7
+ * from a subcommand module would start the server as a side effect AND create a
8
+ * circular dependency (`index.ts` imports `runInstallBridgeCli`, which would
9
+ * import `runInit` back from `index.ts`). This module has no top-level side
10
+ * effects and depends only on leaf modules.
11
+ */
12
+ import { writeFile, mkdir, readFile, stat } from "fs/promises";
13
+ import path from "path";
14
+ import { COMMANDS } from "./commands.generated.js";
15
+ import { AGENTS } from "./agents.generated.js";
16
+ import { VERSION } from "./version.generated.js";
17
+ import { reconstructAgentMarkdown, translateAgentToCopilot } from "./agent-utils.js";
18
+ import { validateRepoName } from "./bridge-config.js";
19
+ import { ensureGitignored as ensureGitignoredShared } from "./git-ignore-utils.js";
20
+ // ---------------------------------------------------------------------------
21
+ // CLI: --init scaffolds slash commands and MCP configs into the current project
22
+ // ---------------------------------------------------------------------------
23
+ export function buildBridgeApiEntry(cwd) {
24
+ // Secret-free by design: BAPI_API_KEY is NEVER scaffolded into generated MCP
25
+ // config. The server self-resolves the key from the environment or the
26
+ // home-dir credential store (~/.config/bridge/credentials.json) at runtime.
27
+ //
28
+ // R4a (BAPI-429): the launcher is pinned to the EXACT running version
29
+ // (`@bridge_gpt/mcp-server@${VERSION}`, sourced from version.generated.js)
30
+ // rather than the unpinned name. An unpinned `npx -y @bridge_gpt/mcp-server`
31
+ // lets npx silently reuse a stale local copy of the server (a verified bug
32
+ // where 0.2.6 persisted despite an upgrade). Pinning keeps the running
33
+ // configuration predictable and visible in version control. The `install-bridge`
34
+ // config write reuses this same entry so both install paths agree.
35
+ return {
36
+ command: "npx",
37
+ args: ["-y", `@bridge_gpt/mcp-server@${VERSION}`],
38
+ env: {
39
+ BAPI_BASE_URL: "https://bridgegpt-api.com",
40
+ BAPI_REPO_NAME: "YOUR_REPO_NAME",
41
+ BAPI_DOCS_DIR: "docs/tmp",
42
+ BAPI_PROJECT_ROOT: cwd,
43
+ },
44
+ };
45
+ }
46
+ /**
47
+ * Choose the `repo_name` written into a scaffolded `.bridge/config`. Uses the
48
+ * cwd basename when it passes manifest repo-name validation; otherwise falls
49
+ * back to the `YOUR_REPO_NAME` placeholder for the user to edit.
50
+ */
51
+ export function chooseScaffoldRepoName(cwd) {
52
+ const validated = validateRepoName(path.basename(cwd));
53
+ return validated.ok ? validated.value : "YOUR_REPO_NAME";
54
+ }
55
+ /** Build the secret-free `.bridge/config` TOML scaffolded by `--init`. */
56
+ export function buildBridgeConfigManifest(repoName) {
57
+ return [
58
+ "# Bridge API repository MCP manifest (committed, secret-free, machine-agnostic).",
59
+ "#",
60
+ "# Inherited by every git worktree; start-tickets worktree provisioning reads it",
61
+ "# to decide which Bridge API MCP registrations to write. Do not add credentials,",
62
+ "# connection URLs, or machine-specific paths here — those are resolved at runtime.",
63
+ "",
64
+ `repo_name = "${repoName}"`,
65
+ "",
66
+ "[[mcp]]",
67
+ 'target = "bapi"',
68
+ "",
69
+ "# Optional: declare additional (Tier-2) MCP targets here. Each non-bapi target",
70
+ "# names the real command/args to launch and the credential-store bundle that",
71
+ "# supplies its secrets — never the secrets themselves. Uncomment and adapt:",
72
+ "#",
73
+ "# [[mcp]]",
74
+ '# target = "sfcc"',
75
+ '# command = "npx"',
76
+ '# args = ["-y", "@salesforce/b2c-dx-mcp"]',
77
+ '# secret_bundle = "sfcc:YOUR_SANDBOX_ID"',
78
+ "",
79
+ ].join("\n");
80
+ }
81
+ async function ensureGitignored(cwd, filePath) {
82
+ await ensureGitignoredShared(cwd, filePath, {
83
+ readFile: (p) => readFile(p, "utf-8"),
84
+ writeFile: (p, data) => writeFile(p, data, "utf-8"),
85
+ mkdir: (p, options) => mkdir(p, options),
86
+ });
87
+ }
88
+ /**
89
+ * Core initialization logic shared by --init and --upgrade.
90
+ * Runs all scaffolding phases without calling process.exit().
91
+ */
92
+ export async function runInit(cwd) {
93
+ // ---- Phase 1: IDE Detection ----
94
+ const ideDetection = {
95
+ claude: true,
96
+ vscode: false,
97
+ cursor: false,
98
+ windsurf: false,
99
+ };
100
+ try {
101
+ await stat(path.join(cwd, ".vscode"));
102
+ ideDetection.vscode = true;
103
+ }
104
+ catch { }
105
+ try {
106
+ await stat(path.join(cwd, ".cursor"));
107
+ ideDetection.cursor = true;
108
+ }
109
+ catch { }
110
+ if (!ideDetection.cursor && process.env.CURSOR_TRACE_DIR)
111
+ ideDetection.cursor = true;
112
+ try {
113
+ await stat(path.join(cwd, ".windsurf"));
114
+ ideDetection.windsurf = true;
115
+ }
116
+ catch { }
117
+ if (!ideDetection.windsurf) {
118
+ try {
119
+ await stat(path.join(cwd, ".windsurfrules"));
120
+ ideDetection.windsurf = true;
121
+ }
122
+ catch { }
123
+ }
124
+ const detectedIDEs = Object.entries(ideDetection)
125
+ .filter(([, v]) => v)
126
+ .map(([k]) => k);
127
+ console.log(`Bridge API --init: detected IDEs: ${detectedIDEs.join(", ")}`);
128
+ // ---- Phase 2: Windsurf manual instructions ----
129
+ if (ideDetection.windsurf) {
130
+ const windsurfSnippet = JSON.stringify({ mcpServers: { "bridge-api": buildBridgeApiEntry(cwd) } }, null, 2);
131
+ console.log("\n⚠ Windsurf does not support project-local MCP configuration.\n" +
132
+ "Add the following to ~/.codeium/windsurf/mcp_config.json:\n\n" +
133
+ windsurfSnippet + "\n");
134
+ }
135
+ // ---- Phase 3: Config file handling ----
136
+ const configTargets = [
137
+ { path: ".mcp.json", topLevelKey: "mcpServers", shouldCreate: true },
138
+ { path: ".vscode/mcp.json", topLevelKey: "servers", shouldCreate: ideDetection.vscode },
139
+ { path: ".cursor/mcp.json", topLevelKey: "mcpServers", shouldCreate: ideDetection.cursor },
140
+ ];
141
+ const configActions = [];
142
+ let anyCreatedOrAdded = false;
143
+ for (const target of configTargets) {
144
+ const fullPath = path.join(cwd, target.path);
145
+ let fileExists = false;
146
+ try {
147
+ await stat(fullPath);
148
+ fileExists = true;
149
+ }
150
+ catch { }
151
+ if (!target.shouldCreate && !fileExists) {
152
+ configActions.push({ path: target.path, action: "skipped — IDE not detected" });
153
+ continue;
154
+ }
155
+ if (fileExists) {
156
+ // Read and parse existing file
157
+ const raw = await readFile(fullPath, "utf-8");
158
+ let parsed;
159
+ try {
160
+ parsed = JSON.parse(raw);
161
+ }
162
+ catch {
163
+ console.warn(` ${target.path} skipped — invalid JSON format`);
164
+ configActions.push({ path: target.path, action: "skipped — invalid JSON" });
165
+ continue;
166
+ }
167
+ const topLevel = parsed[target.topLevelKey];
168
+ if (topLevel && topLevel["bridge-api"]) {
169
+ // Entry exists — update BAPI_PROJECT_ROOT and launcher args to preserve exact version pin.
170
+ // Note: command is always rewritten to "npx". A custom launcher (e.g. "bun x", wrapper
171
+ // script) will be replaced. This is intentional for the standard npx-based setup.
172
+ const entryTemplate = buildBridgeApiEntry(cwd);
173
+ const prevCommand = topLevel["bridge-api"].command;
174
+ if (prevCommand && prevCommand !== entryTemplate.command) {
175
+ console.warn(`Warning: replacing launcher "${prevCommand}" with "${entryTemplate.command}" in ${target.path}. If you use a custom launcher, re-add it after this upgrade.`);
176
+ }
177
+ topLevel["bridge-api"].command = entryTemplate.command;
178
+ topLevel["bridge-api"].args = entryTemplate.args;
179
+ if (!topLevel["bridge-api"].env)
180
+ topLevel["bridge-api"].env = {};
181
+ topLevel["bridge-api"].env.BAPI_PROJECT_ROOT = cwd;
182
+ await writeFile(fullPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
183
+ configActions.push({ path: target.path, action: "updated entry and pinned version" });
184
+ }
185
+ else {
186
+ // Entry missing — add it, preserving existing content
187
+ if (!parsed[target.topLevelKey])
188
+ parsed[target.topLevelKey] = {};
189
+ parsed[target.topLevelKey]["bridge-api"] = buildBridgeApiEntry(cwd);
190
+ await writeFile(fullPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
191
+ configActions.push({ path: target.path, action: "added entry" });
192
+ anyCreatedOrAdded = true;
193
+ }
194
+ }
195
+ else {
196
+ // Create new file
197
+ await mkdir(path.dirname(fullPath), { recursive: true });
198
+ const content = { [target.topLevelKey]: { "bridge-api": buildBridgeApiEntry(cwd) } };
199
+ await writeFile(fullPath, JSON.stringify(content, null, 2) + "\n", "utf-8");
200
+ await ensureGitignored(cwd, target.path);
201
+ configActions.push({ path: target.path, action: "created" });
202
+ anyCreatedOrAdded = true;
203
+ }
204
+ }
205
+ console.log("\nMCP config files:");
206
+ for (const entry of configActions) {
207
+ console.log(` ${entry.path}: ${entry.action}`);
208
+ }
209
+ // ---- Phase 4: Scaffold slash command directories ----
210
+ const commandDirs = [path.join(cwd, ".claude", "commands")];
211
+ if (ideDetection.cursor) {
212
+ commandDirs.push(path.join(cwd, ".cursor", "commands"));
213
+ }
214
+ const writtenFiles = new Set();
215
+ const skippedFiles = new Set();
216
+ const overwrittenFiles = new Set();
217
+ for (const dir of commandDirs) {
218
+ await mkdir(dir, { recursive: true });
219
+ for (const [filename, content] of Object.entries(COMMANDS)) {
220
+ const target = path.join(dir, filename);
221
+ try {
222
+ const existing = await readFile(target, "utf-8");
223
+ if (existing === content) {
224
+ skippedFiles.add(filename);
225
+ continue;
226
+ }
227
+ overwrittenFiles.add(filename);
228
+ }
229
+ catch { /* file doesn't exist */ }
230
+ await writeFile(target, content, "utf-8");
231
+ writtenFiles.add(filename);
232
+ }
233
+ }
234
+ // A file written in any directory takes priority over skipped
235
+ for (const f of writtenFiles)
236
+ skippedFiles.delete(f);
237
+ const total = Object.keys(COMMANDS).length;
238
+ const dirNames = commandDirs.map((d) => path.relative(cwd, d)).join(" and ");
239
+ console.log(`\nSlash commands: scaffolded ${total} commands into ${commandDirs.length} director${commandDirs.length === 1 ? "y" : "ies"}`);
240
+ if (writtenFiles.size > 0)
241
+ console.log(` Written: ${writtenFiles.size}`);
242
+ if (overwrittenFiles.size > 0)
243
+ console.log(` Overwritten (content changed): ${overwrittenFiles.size}`);
244
+ if (skippedFiles.size > 0)
245
+ console.log(` Skipped (unchanged): ${skippedFiles.size}`);
246
+ console.log(` ${dirNames}`);
247
+ // ---- Phase 5: Scaffold agent directories ----
248
+ const agentWritten = new Set();
249
+ const agentSkipped = new Set();
250
+ const agentOverwritten = new Set();
251
+ // Always scaffold to .claude/agents/
252
+ const claudeAgentsDir = path.join(cwd, ".claude", "agents");
253
+ await mkdir(claudeAgentsDir, { recursive: true });
254
+ for (const [key, agent] of Object.entries(AGENTS)) {
255
+ const filename = `${key}.md`;
256
+ const content = reconstructAgentMarkdown(agent.frontmatter, agent.body);
257
+ const target = path.join(claudeAgentsDir, filename);
258
+ try {
259
+ const existing = await readFile(target, "utf-8");
260
+ if (existing === content) {
261
+ agentSkipped.add(filename);
262
+ continue;
263
+ }
264
+ agentOverwritten.add(filename);
265
+ }
266
+ catch { /* file doesn't exist */ }
267
+ await writeFile(target, content, "utf-8");
268
+ agentWritten.add(filename);
269
+ }
270
+ // Scaffold to .github/agents/ for VS Code / Copilot
271
+ if (ideDetection.vscode) {
272
+ const copilotAgentsDir = path.join(cwd, ".github", "agents");
273
+ await mkdir(copilotAgentsDir, { recursive: true });
274
+ for (const [key, agent] of Object.entries(AGENTS)) {
275
+ const filename = `${key}.agent.md`;
276
+ const content = translateAgentToCopilot(agent.frontmatter, agent.body);
277
+ const target = path.join(copilotAgentsDir, filename);
278
+ try {
279
+ const existing = await readFile(target, "utf-8");
280
+ if (existing === content) {
281
+ agentSkipped.add(filename);
282
+ continue;
283
+ }
284
+ agentOverwritten.add(filename);
285
+ }
286
+ catch { /* file doesn't exist */ }
287
+ await writeFile(target, content, "utf-8");
288
+ agentWritten.add(filename);
289
+ }
290
+ }
291
+ // TODO: Add Cursor agent scaffolding when Cursor publishes a formal agent spec
292
+ // A file written in any directory takes priority over skipped
293
+ for (const f of agentWritten)
294
+ agentSkipped.delete(f);
295
+ const agentTotal = Object.keys(AGENTS).length;
296
+ console.log(`\nAgents: scaffolded ${agentTotal} agent${agentTotal === 1 ? "" : "s"}`);
297
+ if (agentWritten.size > 0)
298
+ console.log(` Written: ${agentWritten.size}`);
299
+ if (agentOverwritten.size > 0)
300
+ console.log(` Overwritten (content changed): ${agentOverwritten.size}`);
301
+ if (agentSkipped.size > 0)
302
+ console.log(` Skipped (unchanged): ${agentSkipped.size}`);
303
+ // ---- Phase 6: Scaffold custom pipeline directories ----
304
+ const pipelinesDir = path.resolve(cwd, process.env.BAPI_PIPELINES_DIR ?? ".bridge/pipelines");
305
+ const instrDir = path.join(path.dirname(pipelinesDir), "instructions");
306
+ await mkdir(pipelinesDir, { recursive: true });
307
+ await mkdir(instrDir, { recursive: true });
308
+ const readmePath = path.join(pipelinesDir, "README.md");
309
+ const examplePath = path.join(pipelinesDir, "example-pipeline.json");
310
+ const readmeContent = `# Custom Pipelines
311
+
312
+ Place custom pipeline JSON files in this directory. They will be loaded at
313
+ server startup and available alongside the bundled pipelines.
314
+
315
+ ## JSON Schema
316
+
317
+ Each pipeline file must be a JSON object with these fields:
318
+
319
+ | Field | Type | Required | Description |
320
+ |---------------|------------|----------|------------------------------------------|
321
+ | \`name\` | string | yes | Display name of the pipeline |
322
+ | \`description\` | string | no | Short description shown in list_pipelines |
323
+ | \`variables\` | string[] | no | Variable names for substitution |
324
+ | \`steps\` | Step[] | yes | Ordered list of steps to execute |
325
+
326
+ ## Step Types
327
+
328
+ ### mcp_call
329
+ Calls an MCP tool with specific parameters.
330
+ \`\`\`json
331
+ {
332
+ "type": "mcp_call",
333
+ "tool": "tool_name",
334
+ "params": { "key": "value" },
335
+ "description": "What this step does",
336
+ "on_error": "halt",
337
+ "requires_approval": false
338
+ }
339
+ \`\`\`
340
+
341
+ ### agent_task
342
+ Gives the agent a free-form instruction to execute.
343
+ \`\`\`json
344
+ {
345
+ "type": "agent_task",
346
+ "instruction": "Do something specific",
347
+ "description": "What this step does",
348
+ "on_error": "warn_and_continue"
349
+ }
350
+ \`\`\`
351
+
352
+ Or reference an instruction file from \`.bridge/instructions/\`:
353
+ \`\`\`json
354
+ {
355
+ "type": "agent_task",
356
+ "instruction_file": "my-instructions.md",
357
+ "description": "What this step does"
358
+ }
359
+ \`\`\`
360
+
361
+ ## Variables
362
+
363
+ Declare variables in the \`variables\` array and reference them with \`{variable_name}\`
364
+ in step params, instructions, and descriptions. The \`docs_dir\` variable is
365
+ automatically provided by the server.
366
+
367
+ ## on_error
368
+
369
+ - \`"halt"\` (default) — stop the pipeline immediately on failure
370
+ - \`"warn_and_continue"\` — log a warning and proceed to the next step
371
+
372
+ ## Executing Pipelines
373
+
374
+ Pipelines defined here can be executed end-to-end via the \`run_pipeline\` MCP
375
+ tool. \`run_pipeline\` returns a unified envelope keyed on \`status\`:
376
+
377
+ - \`completed\` — the pipeline finished and \`results\` holds the per-step output.
378
+ - \`needs_agent_task\` — the orchestrator paused on an \`agent_task\` step (or an
379
+ approval-gated \`mcp_call\` when \`auto_approve\` was false). Perform the task
380
+ described by \`instruction\`, then call \`resume_pipeline\` with the
381
+ \`pipeline_run_id\` and the resulting string as \`agent_result\`.
382
+ - \`failed\` — terminal failure; \`error_code\` is one of \`VALIDATION\`,
383
+ \`NOT_FOUND\`, \`EXPIRED\`, \`REPO_MISMATCH\`, or \`TOOL_ERROR\`.
384
+
385
+ Paused runs auto-expire after an idle TTL (default 24 hours, override via
386
+ \`ttl_seconds\` on \`run_pipeline\`). The TTL is reset on every state transition.
387
+ Use \`list_pipeline_runs\` to recover a \`pipeline_run_id\` if the prior
388
+ \`needs_agent_task\` envelope is no longer in scope.
389
+
390
+ ## \`agent_task\` Instruction Files
391
+
392
+ When you reference an instruction file (\`instruction_file: "…"\`), the file
393
+ markdown MUST end with a terminal \`## Return\` H2 section describing what the
394
+ agent should pass back as \`resume_pipeline.agent_result\`. The return value
395
+ is a string — do NOT ask the agent to wrap it in JSON unless the instruction
396
+ body explicitly says to serialize structured output.
397
+ `;
398
+ const exampleContent = JSON.stringify({
399
+ name: "Example Pipeline",
400
+ description: "A sample pipeline demonstrating all step types and features.",
401
+ variables: ["ticket_key"],
402
+ steps: [
403
+ {
404
+ type: "mcp_call",
405
+ tool: "get_ticket",
406
+ params: { ticket_number: "{ticket_key}" },
407
+ description: "Fetch the ticket details from Jira",
408
+ on_error: "halt",
409
+ },
410
+ {
411
+ type: "agent_task",
412
+ instruction: "Read the ticket details above and summarize the key requirements in 3 bullet points.",
413
+ description: "Summarize ticket requirements",
414
+ on_error: "halt",
415
+ },
416
+ {
417
+ type: "agent_task",
418
+ instruction: "Search the codebase for files related to the ticket and list the top 5 most relevant files.",
419
+ description: "Find relevant source files",
420
+ on_error: "warn_and_continue",
421
+ },
422
+ {
423
+ type: "mcp_call",
424
+ tool: "add_comment",
425
+ params: {
426
+ ticket_number: "{ticket_key}",
427
+ comment_text: "Pipeline analysis complete. See agent output for details.",
428
+ },
429
+ description: "Post a summary comment to the ticket",
430
+ on_error: "warn_and_continue",
431
+ requires_approval: true,
432
+ },
433
+ ],
434
+ }, null, 2) + "\n";
435
+ try {
436
+ await stat(readmePath);
437
+ console.log(` ${path.relative(cwd, readmePath)} (skipped — already exists)`);
438
+ }
439
+ catch {
440
+ await writeFile(readmePath, readmeContent, "utf-8");
441
+ console.log(` ${path.relative(cwd, readmePath)} (written)`);
442
+ }
443
+ try {
444
+ await stat(examplePath);
445
+ console.log(` ${path.relative(cwd, examplePath)} (skipped — already exists)`);
446
+ }
447
+ catch {
448
+ await writeFile(examplePath, exampleContent, "utf-8");
449
+ console.log(` ${path.relative(cwd, examplePath)} (written)`);
450
+ }
451
+ console.log(` ${path.relative(cwd, instrDir)}/ (ensured)`);
452
+ // ---- Phase 6b: Scaffold the committed secret-free `.bridge/config` ----
453
+ // This manifest is committed and inherited by every worktree; it is the
454
+ // input to start-tickets worktree MCP provisioning. It is intentionally
455
+ // NOT gitignored (unlike the .mcp.json configs above). Credentials are
456
+ // resolved at runtime, never written here.
457
+ const bridgeConfigPath = path.join(cwd, ".bridge", "config");
458
+ let bridgeConfigExists = false;
459
+ try {
460
+ await stat(bridgeConfigPath);
461
+ bridgeConfigExists = true;
462
+ }
463
+ catch { }
464
+ if (bridgeConfigExists) {
465
+ console.log(`\n.bridge/config: skipped — already exists`);
466
+ }
467
+ else {
468
+ await mkdir(path.dirname(bridgeConfigPath), { recursive: true });
469
+ await writeFile(bridgeConfigPath, buildBridgeConfigManifest(chooseScaffoldRepoName(cwd)), "utf-8");
470
+ console.log(`\n.bridge/config: written`);
471
+ }
472
+ console.log(" Credentials are resolved at runtime from BAPI_API_KEY or " +
473
+ "~/.config/bridge/credentials.json (no secrets are written to .bridge/config).");
474
+ // ---- Phase 7: Final summary ----
475
+ if (anyCreatedOrAdded) {
476
+ console.log("\nSet BAPI_REPO_NAME in your config files. Do NOT put BAPI_API_KEY in the " +
477
+ "generated MCP config — supply it via the BAPI_API_KEY environment variable, " +
478
+ "or store it under \"bapi:<repo_name>\" in ~/.config/bridge/credentials.json. " +
479
+ "Get your values from the Bridge API setup UI at https://bridgegpt-api.com");
480
+ }
481
+ }