@bridge_gpt/mcp-server 0.2.6 → 0.2.10
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 +58 -5
- package/build/commands.generated.js +1 -1
- package/build/conductor/bridge-api-client.js +262 -35
- package/build/conductor/cli.js +22 -1
- package/build/conductor/doctor.js +34 -1
- package/build/conductor/done-gate.js +301 -58
- package/build/conductor/epic-reconcile.js +121 -4
- package/build/conductor/epic-runtime.js +299 -13
- package/build/conductor/epic-state.js +108 -9
- package/build/conductor/git-ci-types.js +6 -0
- package/build/conductor/pr-ci-producer.js +114 -15
- package/build/conductor/pr-review-producer.js +116 -0
- package/build/conductor/store.js +8 -1
- package/build/conductor/supervisor-message-relay.js +31 -0
- package/build/conductor/taxonomy.js +3 -0
- package/build/conductor/tools.js +2 -2
- package/build/index.js +356 -1086
- package/build/init.js +481 -0
- package/build/install-bridge.js +692 -0
- package/build/mcp-profile.js +43 -0
- package/build/readme.generated.js +1 -1
- package/build/start-tickets-conductor.js +1 -0
- package/build/start-tickets.js +328 -36
- package/build/upgrade-cli.js +154 -0
- package/build/version.generated.js +1 -1
- package/package.json +3 -2
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
|
+
}
|