@bridge_gpt/mcp-server 0.2.9 → 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 +56 -4
- 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 +298 -17
- 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 +186 -10
- package/build/upgrade-cli.js +154 -0
- package/build/version.generated.js +1 -1
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -21,29 +21,26 @@ import { writeFile, mkdir, readFile, stat, rename, chmod, unlink } from "fs/prom
|
|
|
21
21
|
import path from "path";
|
|
22
22
|
import os from "os";
|
|
23
23
|
import { fileURLToPath } from "url";
|
|
24
|
-
import { execSync } from "child_process";
|
|
25
|
-
import { createRequire } from "module";
|
|
26
24
|
import { PIPELINES as BUNDLED_PIPELINES, INSTRUCTIONS as BUNDLED_INSTRUCTIONS, CHAIN_RECIPES } from "./pipelines.generated.js";
|
|
27
|
-
import { COMMANDS } from "./commands.generated.js";
|
|
28
|
-
import { AGENTS } from "./agents.generated.js";
|
|
29
25
|
import { VERSION } from "./version.generated.js";
|
|
30
26
|
import { README } from "./readme.generated.js";
|
|
31
27
|
import { checkForUpdate } from "./update-check.js";
|
|
32
|
-
import { reconstructAgentMarkdown, translateAgentToCopilot } from "./agent-utils.js";
|
|
33
28
|
import { resolveRecipe, loadCustomPipelines } from "./pipeline-utils.js";
|
|
29
|
+
import { runInit } from "./init.js";
|
|
34
30
|
import { runStartTicketsCli } from "./start-tickets.js";
|
|
35
31
|
import { runReviewTicketsCli } from "./review-tickets.js";
|
|
36
32
|
import { runDoctorCli } from "./doctor.js";
|
|
37
33
|
import { runScheduleRunCli } from "./schedule-run.js";
|
|
38
34
|
import { runMcpInvokeCli } from "./mcp-invoke.js";
|
|
39
35
|
import { runAgentCapabilitiesCli } from "./agent-capabilities/cli.js";
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
36
|
+
import { runInstallBridgeCli } from "./install-bridge.js";
|
|
37
|
+
import { runUpgradeCli } from "./upgrade-cli.js";
|
|
42
38
|
import { resolveBapiCredentials, upsertBapiCredential, getPrimaryCredentialStorePath, } from "./credential-store.js";
|
|
43
39
|
import { runCredentialsCli } from "./credentials-cli.js";
|
|
44
40
|
import { registerConductorTools } from "./conductor/tools.js";
|
|
41
|
+
import { resolveProfile, profileIncludes } from "./mcp-profile.js";
|
|
45
42
|
import { runConductorCli } from "./conductor/cli.js";
|
|
46
|
-
import { observePrCiFromPollResponse } from "./conductor/pr-ci-producer.js";
|
|
43
|
+
import { observePrCiFromPollResponse, resolveDispatchRunIdForBinding } from "./conductor/pr-ci-producer.js";
|
|
47
44
|
import { generateDecisionPageHtml } from "./decision-page-template.js";
|
|
48
45
|
import { DecisionPageInputShape } from "./decision-page-schema.js";
|
|
49
46
|
import { slugify, saveBrainstormResultsToDir, } from "./brainstorm-files.js";
|
|
@@ -77,6 +74,10 @@ function parseDefaultOnEnvFlag(value) {
|
|
|
77
74
|
// authoritative for whether advice exists and for its exact wording
|
|
78
75
|
// (compute_upgrade()). Defaults to enabled; fail-open on any unexpected value.
|
|
79
76
|
const UPGRADE_ADVICE_SURFACING_ENABLED = parseDefaultOnEnvFlag(process.env.BAPI_MCP_UPGRADE_ADVICE_ENABLED);
|
|
77
|
+
// BAPI-434 Phase 2a: startup-time profile gating. Resolved once at module load;
|
|
78
|
+
// unknown / blank / undefined values fail safe to "core" (smallest surface).
|
|
79
|
+
// Dynamic mid-session switching via tools/list_changed is intentionally unsupported.
|
|
80
|
+
const BRIDGE_MCP_PROFILE = resolveProfile(process.env.BRIDGE_MCP_PROFILE);
|
|
80
81
|
// ---------------------------------------------------------------------------
|
|
81
82
|
// Resolved-once credential + path accessors (BAPI-338)
|
|
82
83
|
//
|
|
@@ -911,451 +912,6 @@ async function requestTicketReview(args) {
|
|
|
911
912
|
return { content: [{ type: "text", text }] };
|
|
912
913
|
}
|
|
913
914
|
// ---------------------------------------------------------------------------
|
|
914
|
-
// CLI: --init scaffolds slash commands and MCP configs into the current project
|
|
915
|
-
// ---------------------------------------------------------------------------
|
|
916
|
-
function buildBridgeApiEntry(cwd) {
|
|
917
|
-
// Secret-free by design: BAPI_API_KEY is NEVER scaffolded into generated MCP
|
|
918
|
-
// config. The server self-resolves the key from the environment or the
|
|
919
|
-
// home-dir credential store (~/.config/bridge/credentials.json) at runtime.
|
|
920
|
-
return {
|
|
921
|
-
command: "npx",
|
|
922
|
-
args: ["-y", "@bridge_gpt/mcp-server"],
|
|
923
|
-
env: {
|
|
924
|
-
BAPI_BASE_URL: "https://bridgegpt-api.com",
|
|
925
|
-
BAPI_REPO_NAME: "YOUR_REPO_NAME",
|
|
926
|
-
BAPI_DOCS_DIR: "docs/tmp",
|
|
927
|
-
BAPI_PROJECT_ROOT: cwd,
|
|
928
|
-
},
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
/**
|
|
932
|
-
* Choose the `repo_name` written into a scaffolded `.bridge/config`. Uses the
|
|
933
|
-
* cwd basename when it passes manifest repo-name validation; otherwise falls
|
|
934
|
-
* back to the `YOUR_REPO_NAME` placeholder for the user to edit.
|
|
935
|
-
*/
|
|
936
|
-
export function chooseScaffoldRepoName(cwd) {
|
|
937
|
-
const validated = validateRepoName(path.basename(cwd));
|
|
938
|
-
return validated.ok ? validated.value : "YOUR_REPO_NAME";
|
|
939
|
-
}
|
|
940
|
-
/** Build the secret-free `.bridge/config` TOML scaffolded by `--init`. */
|
|
941
|
-
export function buildBridgeConfigManifest(repoName) {
|
|
942
|
-
return [
|
|
943
|
-
"# Bridge API repository MCP manifest (committed, secret-free, machine-agnostic).",
|
|
944
|
-
"#",
|
|
945
|
-
"# Inherited by every git worktree; start-tickets worktree provisioning reads it",
|
|
946
|
-
"# to decide which Bridge API MCP registrations to write. Do not add credentials,",
|
|
947
|
-
"# connection URLs, or machine-specific paths here — those are resolved at runtime.",
|
|
948
|
-
"",
|
|
949
|
-
`repo_name = "${repoName}"`,
|
|
950
|
-
"",
|
|
951
|
-
"[[mcp]]",
|
|
952
|
-
'target = "bapi"',
|
|
953
|
-
"",
|
|
954
|
-
"# Optional: declare additional (Tier-2) MCP targets here. Each non-bapi target",
|
|
955
|
-
"# names the real command/args to launch and the credential-store bundle that",
|
|
956
|
-
"# supplies its secrets — never the secrets themselves. Uncomment and adapt:",
|
|
957
|
-
"#",
|
|
958
|
-
"# [[mcp]]",
|
|
959
|
-
'# target = "sfcc"',
|
|
960
|
-
'# command = "npx"',
|
|
961
|
-
'# args = ["-y", "@salesforce/b2c-dx-mcp"]',
|
|
962
|
-
'# secret_bundle = "sfcc:YOUR_SANDBOX_ID"',
|
|
963
|
-
"",
|
|
964
|
-
].join("\n");
|
|
965
|
-
}
|
|
966
|
-
async function ensureGitignored(cwd, filePath) {
|
|
967
|
-
await ensureGitignoredShared(cwd, filePath, {
|
|
968
|
-
readFile: (p) => readFile(p, "utf-8"),
|
|
969
|
-
writeFile: (p, data) => writeFile(p, data, "utf-8"),
|
|
970
|
-
mkdir: (p, options) => mkdir(p, options),
|
|
971
|
-
});
|
|
972
|
-
}
|
|
973
|
-
/**
|
|
974
|
-
* Core initialization logic shared by --init and --upgrade.
|
|
975
|
-
* Runs all scaffolding phases without calling process.exit().
|
|
976
|
-
*/
|
|
977
|
-
async function runInit(cwd) {
|
|
978
|
-
// ---- Phase 1: IDE Detection ----
|
|
979
|
-
const ideDetection = {
|
|
980
|
-
claude: true,
|
|
981
|
-
vscode: false,
|
|
982
|
-
cursor: false,
|
|
983
|
-
windsurf: false,
|
|
984
|
-
};
|
|
985
|
-
try {
|
|
986
|
-
await stat(path.join(cwd, ".vscode"));
|
|
987
|
-
ideDetection.vscode = true;
|
|
988
|
-
}
|
|
989
|
-
catch { }
|
|
990
|
-
try {
|
|
991
|
-
await stat(path.join(cwd, ".cursor"));
|
|
992
|
-
ideDetection.cursor = true;
|
|
993
|
-
}
|
|
994
|
-
catch { }
|
|
995
|
-
if (!ideDetection.cursor && process.env.CURSOR_TRACE_DIR)
|
|
996
|
-
ideDetection.cursor = true;
|
|
997
|
-
try {
|
|
998
|
-
await stat(path.join(cwd, ".windsurf"));
|
|
999
|
-
ideDetection.windsurf = true;
|
|
1000
|
-
}
|
|
1001
|
-
catch { }
|
|
1002
|
-
if (!ideDetection.windsurf) {
|
|
1003
|
-
try {
|
|
1004
|
-
await stat(path.join(cwd, ".windsurfrules"));
|
|
1005
|
-
ideDetection.windsurf = true;
|
|
1006
|
-
}
|
|
1007
|
-
catch { }
|
|
1008
|
-
}
|
|
1009
|
-
const detectedIDEs = Object.entries(ideDetection)
|
|
1010
|
-
.filter(([, v]) => v)
|
|
1011
|
-
.map(([k]) => k);
|
|
1012
|
-
console.log(`Bridge API --init: detected IDEs: ${detectedIDEs.join(", ")}`);
|
|
1013
|
-
// ---- Phase 2: Windsurf manual instructions ----
|
|
1014
|
-
if (ideDetection.windsurf) {
|
|
1015
|
-
const windsurfSnippet = JSON.stringify({ mcpServers: { "bridge-api": buildBridgeApiEntry(cwd) } }, null, 2);
|
|
1016
|
-
console.log("\n⚠ Windsurf does not support project-local MCP configuration.\n" +
|
|
1017
|
-
"Add the following to ~/.codeium/windsurf/mcp_config.json:\n\n" +
|
|
1018
|
-
windsurfSnippet + "\n");
|
|
1019
|
-
}
|
|
1020
|
-
// ---- Phase 3: Config file handling ----
|
|
1021
|
-
const configTargets = [
|
|
1022
|
-
{ path: ".mcp.json", topLevelKey: "mcpServers", shouldCreate: true },
|
|
1023
|
-
{ path: ".vscode/mcp.json", topLevelKey: "servers", shouldCreate: ideDetection.vscode },
|
|
1024
|
-
{ path: ".cursor/mcp.json", topLevelKey: "mcpServers", shouldCreate: ideDetection.cursor },
|
|
1025
|
-
];
|
|
1026
|
-
const configActions = [];
|
|
1027
|
-
let anyCreatedOrAdded = false;
|
|
1028
|
-
for (const target of configTargets) {
|
|
1029
|
-
const fullPath = path.join(cwd, target.path);
|
|
1030
|
-
let fileExists = false;
|
|
1031
|
-
try {
|
|
1032
|
-
await stat(fullPath);
|
|
1033
|
-
fileExists = true;
|
|
1034
|
-
}
|
|
1035
|
-
catch { }
|
|
1036
|
-
if (!target.shouldCreate && !fileExists) {
|
|
1037
|
-
configActions.push({ path: target.path, action: "skipped — IDE not detected" });
|
|
1038
|
-
continue;
|
|
1039
|
-
}
|
|
1040
|
-
if (fileExists) {
|
|
1041
|
-
// Read and parse existing file
|
|
1042
|
-
const raw = await readFile(fullPath, "utf-8");
|
|
1043
|
-
let parsed;
|
|
1044
|
-
try {
|
|
1045
|
-
parsed = JSON.parse(raw);
|
|
1046
|
-
}
|
|
1047
|
-
catch {
|
|
1048
|
-
console.warn(` ${target.path} skipped — invalid JSON format`);
|
|
1049
|
-
configActions.push({ path: target.path, action: "skipped — invalid JSON" });
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
const topLevel = parsed[target.topLevelKey];
|
|
1053
|
-
if (topLevel && topLevel["bridge-api"]) {
|
|
1054
|
-
// Entry exists — update BAPI_PROJECT_ROOT only
|
|
1055
|
-
if (!topLevel["bridge-api"].env)
|
|
1056
|
-
topLevel["bridge-api"].env = {};
|
|
1057
|
-
topLevel["bridge-api"].env.BAPI_PROJECT_ROOT = cwd;
|
|
1058
|
-
await writeFile(fullPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
1059
|
-
configActions.push({ path: target.path, action: "updated BAPI_PROJECT_ROOT" });
|
|
1060
|
-
}
|
|
1061
|
-
else {
|
|
1062
|
-
// Entry missing — add it, preserving existing content
|
|
1063
|
-
if (!parsed[target.topLevelKey])
|
|
1064
|
-
parsed[target.topLevelKey] = {};
|
|
1065
|
-
parsed[target.topLevelKey]["bridge-api"] = buildBridgeApiEntry(cwd);
|
|
1066
|
-
await writeFile(fullPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
1067
|
-
configActions.push({ path: target.path, action: "added entry" });
|
|
1068
|
-
anyCreatedOrAdded = true;
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
else {
|
|
1072
|
-
// Create new file
|
|
1073
|
-
await mkdir(path.dirname(fullPath), { recursive: true });
|
|
1074
|
-
const content = { [target.topLevelKey]: { "bridge-api": buildBridgeApiEntry(cwd) } };
|
|
1075
|
-
await writeFile(fullPath, JSON.stringify(content, null, 2) + "\n", "utf-8");
|
|
1076
|
-
await ensureGitignored(cwd, target.path);
|
|
1077
|
-
configActions.push({ path: target.path, action: "created" });
|
|
1078
|
-
anyCreatedOrAdded = true;
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
console.log("\nMCP config files:");
|
|
1082
|
-
for (const entry of configActions) {
|
|
1083
|
-
console.log(` ${entry.path}: ${entry.action}`);
|
|
1084
|
-
}
|
|
1085
|
-
// ---- Phase 4: Scaffold slash command directories ----
|
|
1086
|
-
const commandDirs = [path.join(cwd, ".claude", "commands")];
|
|
1087
|
-
if (ideDetection.cursor) {
|
|
1088
|
-
commandDirs.push(path.join(cwd, ".cursor", "commands"));
|
|
1089
|
-
}
|
|
1090
|
-
const writtenFiles = new Set();
|
|
1091
|
-
const skippedFiles = new Set();
|
|
1092
|
-
const overwrittenFiles = new Set();
|
|
1093
|
-
for (const dir of commandDirs) {
|
|
1094
|
-
await mkdir(dir, { recursive: true });
|
|
1095
|
-
for (const [filename, content] of Object.entries(COMMANDS)) {
|
|
1096
|
-
const target = path.join(dir, filename);
|
|
1097
|
-
try {
|
|
1098
|
-
const existing = await readFile(target, "utf-8");
|
|
1099
|
-
if (existing === content) {
|
|
1100
|
-
skippedFiles.add(filename);
|
|
1101
|
-
continue;
|
|
1102
|
-
}
|
|
1103
|
-
overwrittenFiles.add(filename);
|
|
1104
|
-
}
|
|
1105
|
-
catch { /* file doesn't exist */ }
|
|
1106
|
-
await writeFile(target, content, "utf-8");
|
|
1107
|
-
writtenFiles.add(filename);
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
// A file written in any directory takes priority over skipped
|
|
1111
|
-
for (const f of writtenFiles)
|
|
1112
|
-
skippedFiles.delete(f);
|
|
1113
|
-
const total = Object.keys(COMMANDS).length;
|
|
1114
|
-
const dirNames = commandDirs.map((d) => path.relative(cwd, d)).join(" and ");
|
|
1115
|
-
console.log(`\nSlash commands: scaffolded ${total} commands into ${commandDirs.length} director${commandDirs.length === 1 ? "y" : "ies"}`);
|
|
1116
|
-
if (writtenFiles.size > 0)
|
|
1117
|
-
console.log(` Written: ${writtenFiles.size}`);
|
|
1118
|
-
if (overwrittenFiles.size > 0)
|
|
1119
|
-
console.log(` Overwritten (content changed): ${overwrittenFiles.size}`);
|
|
1120
|
-
if (skippedFiles.size > 0)
|
|
1121
|
-
console.log(` Skipped (unchanged): ${skippedFiles.size}`);
|
|
1122
|
-
console.log(` ${dirNames}`);
|
|
1123
|
-
// ---- Phase 5: Scaffold agent directories ----
|
|
1124
|
-
const agentWritten = new Set();
|
|
1125
|
-
const agentSkipped = new Set();
|
|
1126
|
-
const agentOverwritten = new Set();
|
|
1127
|
-
// Always scaffold to .claude/agents/
|
|
1128
|
-
const claudeAgentsDir = path.join(cwd, ".claude", "agents");
|
|
1129
|
-
await mkdir(claudeAgentsDir, { recursive: true });
|
|
1130
|
-
for (const [key, agent] of Object.entries(AGENTS)) {
|
|
1131
|
-
const filename = `${key}.md`;
|
|
1132
|
-
const content = reconstructAgentMarkdown(agent.frontmatter, agent.body);
|
|
1133
|
-
const target = path.join(claudeAgentsDir, filename);
|
|
1134
|
-
try {
|
|
1135
|
-
const existing = await readFile(target, "utf-8");
|
|
1136
|
-
if (existing === content) {
|
|
1137
|
-
agentSkipped.add(filename);
|
|
1138
|
-
continue;
|
|
1139
|
-
}
|
|
1140
|
-
agentOverwritten.add(filename);
|
|
1141
|
-
}
|
|
1142
|
-
catch { /* file doesn't exist */ }
|
|
1143
|
-
await writeFile(target, content, "utf-8");
|
|
1144
|
-
agentWritten.add(filename);
|
|
1145
|
-
}
|
|
1146
|
-
// Scaffold to .github/agents/ for VS Code / Copilot
|
|
1147
|
-
if (ideDetection.vscode) {
|
|
1148
|
-
const copilotAgentsDir = path.join(cwd, ".github", "agents");
|
|
1149
|
-
await mkdir(copilotAgentsDir, { recursive: true });
|
|
1150
|
-
for (const [key, agent] of Object.entries(AGENTS)) {
|
|
1151
|
-
const filename = `${key}.agent.md`;
|
|
1152
|
-
const content = translateAgentToCopilot(agent.frontmatter, agent.body);
|
|
1153
|
-
const target = path.join(copilotAgentsDir, filename);
|
|
1154
|
-
try {
|
|
1155
|
-
const existing = await readFile(target, "utf-8");
|
|
1156
|
-
if (existing === content) {
|
|
1157
|
-
agentSkipped.add(filename);
|
|
1158
|
-
continue;
|
|
1159
|
-
}
|
|
1160
|
-
agentOverwritten.add(filename);
|
|
1161
|
-
}
|
|
1162
|
-
catch { /* file doesn't exist */ }
|
|
1163
|
-
await writeFile(target, content, "utf-8");
|
|
1164
|
-
agentWritten.add(filename);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
// TODO: Add Cursor agent scaffolding when Cursor publishes a formal agent spec
|
|
1168
|
-
// A file written in any directory takes priority over skipped
|
|
1169
|
-
for (const f of agentWritten)
|
|
1170
|
-
agentSkipped.delete(f);
|
|
1171
|
-
const agentTotal = Object.keys(AGENTS).length;
|
|
1172
|
-
console.log(`\nAgents: scaffolded ${agentTotal} agent${agentTotal === 1 ? "" : "s"}`);
|
|
1173
|
-
if (agentWritten.size > 0)
|
|
1174
|
-
console.log(` Written: ${agentWritten.size}`);
|
|
1175
|
-
if (agentOverwritten.size > 0)
|
|
1176
|
-
console.log(` Overwritten (content changed): ${agentOverwritten.size}`);
|
|
1177
|
-
if (agentSkipped.size > 0)
|
|
1178
|
-
console.log(` Skipped (unchanged): ${agentSkipped.size}`);
|
|
1179
|
-
// ---- Phase 6: Scaffold custom pipeline directories ----
|
|
1180
|
-
const pipelinesDir = path.resolve(cwd, process.env.BAPI_PIPELINES_DIR ?? ".bridge/pipelines");
|
|
1181
|
-
const instrDir = path.join(path.dirname(pipelinesDir), "instructions");
|
|
1182
|
-
await mkdir(pipelinesDir, { recursive: true });
|
|
1183
|
-
await mkdir(instrDir, { recursive: true });
|
|
1184
|
-
const readmePath = path.join(pipelinesDir, "README.md");
|
|
1185
|
-
const examplePath = path.join(pipelinesDir, "example-pipeline.json");
|
|
1186
|
-
const readmeContent = `# Custom Pipelines
|
|
1187
|
-
|
|
1188
|
-
Place custom pipeline JSON files in this directory. They will be loaded at
|
|
1189
|
-
server startup and available alongside the bundled pipelines.
|
|
1190
|
-
|
|
1191
|
-
## JSON Schema
|
|
1192
|
-
|
|
1193
|
-
Each pipeline file must be a JSON object with these fields:
|
|
1194
|
-
|
|
1195
|
-
| Field | Type | Required | Description |
|
|
1196
|
-
|---------------|------------|----------|------------------------------------------|
|
|
1197
|
-
| \`name\` | string | yes | Display name of the pipeline |
|
|
1198
|
-
| \`description\` | string | no | Short description shown in list_pipelines |
|
|
1199
|
-
| \`variables\` | string[] | no | Variable names for substitution |
|
|
1200
|
-
| \`steps\` | Step[] | yes | Ordered list of steps to execute |
|
|
1201
|
-
|
|
1202
|
-
## Step Types
|
|
1203
|
-
|
|
1204
|
-
### mcp_call
|
|
1205
|
-
Calls an MCP tool with specific parameters.
|
|
1206
|
-
\`\`\`json
|
|
1207
|
-
{
|
|
1208
|
-
"type": "mcp_call",
|
|
1209
|
-
"tool": "tool_name",
|
|
1210
|
-
"params": { "key": "value" },
|
|
1211
|
-
"description": "What this step does",
|
|
1212
|
-
"on_error": "halt",
|
|
1213
|
-
"requires_approval": false
|
|
1214
|
-
}
|
|
1215
|
-
\`\`\`
|
|
1216
|
-
|
|
1217
|
-
### agent_task
|
|
1218
|
-
Gives the agent a free-form instruction to execute.
|
|
1219
|
-
\`\`\`json
|
|
1220
|
-
{
|
|
1221
|
-
"type": "agent_task",
|
|
1222
|
-
"instruction": "Do something specific",
|
|
1223
|
-
"description": "What this step does",
|
|
1224
|
-
"on_error": "warn_and_continue"
|
|
1225
|
-
}
|
|
1226
|
-
\`\`\`
|
|
1227
|
-
|
|
1228
|
-
Or reference an instruction file from \`.bridge/instructions/\`:
|
|
1229
|
-
\`\`\`json
|
|
1230
|
-
{
|
|
1231
|
-
"type": "agent_task",
|
|
1232
|
-
"instruction_file": "my-instructions.md",
|
|
1233
|
-
"description": "What this step does"
|
|
1234
|
-
}
|
|
1235
|
-
\`\`\`
|
|
1236
|
-
|
|
1237
|
-
## Variables
|
|
1238
|
-
|
|
1239
|
-
Declare variables in the \`variables\` array and reference them with \`{variable_name}\`
|
|
1240
|
-
in step params, instructions, and descriptions. The \`docs_dir\` variable is
|
|
1241
|
-
automatically provided by the server.
|
|
1242
|
-
|
|
1243
|
-
## on_error
|
|
1244
|
-
|
|
1245
|
-
- \`"halt"\` (default) — stop the pipeline immediately on failure
|
|
1246
|
-
- \`"warn_and_continue"\` — log a warning and proceed to the next step
|
|
1247
|
-
|
|
1248
|
-
## Executing Pipelines
|
|
1249
|
-
|
|
1250
|
-
Pipelines defined here can be executed end-to-end via the \`run_pipeline\` MCP
|
|
1251
|
-
tool. \`run_pipeline\` returns a unified envelope keyed on \`status\`:
|
|
1252
|
-
|
|
1253
|
-
- \`completed\` — the pipeline finished and \`results\` holds the per-step output.
|
|
1254
|
-
- \`needs_agent_task\` — the orchestrator paused on an \`agent_task\` step (or an
|
|
1255
|
-
approval-gated \`mcp_call\` when \`auto_approve\` was false). Perform the task
|
|
1256
|
-
described by \`instruction\`, then call \`resume_pipeline\` with the
|
|
1257
|
-
\`pipeline_run_id\` and the resulting string as \`agent_result\`.
|
|
1258
|
-
- \`failed\` — terminal failure; \`error_code\` is one of \`VALIDATION\`,
|
|
1259
|
-
\`NOT_FOUND\`, \`EXPIRED\`, \`REPO_MISMATCH\`, or \`TOOL_ERROR\`.
|
|
1260
|
-
|
|
1261
|
-
Paused runs auto-expire after an idle TTL (default 24 hours, override via
|
|
1262
|
-
\`ttl_seconds\` on \`run_pipeline\`). The TTL is reset on every state transition.
|
|
1263
|
-
Use \`list_pipeline_runs\` to recover a \`pipeline_run_id\` if the prior
|
|
1264
|
-
\`needs_agent_task\` envelope is no longer in scope.
|
|
1265
|
-
|
|
1266
|
-
## \`agent_task\` Instruction Files
|
|
1267
|
-
|
|
1268
|
-
When you reference an instruction file (\`instruction_file: "…"\`), the file
|
|
1269
|
-
markdown MUST end with a terminal \`## Return\` H2 section describing what the
|
|
1270
|
-
agent should pass back as \`resume_pipeline.agent_result\`. The return value
|
|
1271
|
-
is a string — do NOT ask the agent to wrap it in JSON unless the instruction
|
|
1272
|
-
body explicitly says to serialize structured output.
|
|
1273
|
-
`;
|
|
1274
|
-
const exampleContent = JSON.stringify({
|
|
1275
|
-
name: "Example Pipeline",
|
|
1276
|
-
description: "A sample pipeline demonstrating all step types and features.",
|
|
1277
|
-
variables: ["ticket_key"],
|
|
1278
|
-
steps: [
|
|
1279
|
-
{
|
|
1280
|
-
type: "mcp_call",
|
|
1281
|
-
tool: "get_ticket",
|
|
1282
|
-
params: { ticket_number: "{ticket_key}" },
|
|
1283
|
-
description: "Fetch the ticket details from Jira",
|
|
1284
|
-
on_error: "halt",
|
|
1285
|
-
},
|
|
1286
|
-
{
|
|
1287
|
-
type: "agent_task",
|
|
1288
|
-
instruction: "Read the ticket details above and summarize the key requirements in 3 bullet points.",
|
|
1289
|
-
description: "Summarize ticket requirements",
|
|
1290
|
-
on_error: "halt",
|
|
1291
|
-
},
|
|
1292
|
-
{
|
|
1293
|
-
type: "agent_task",
|
|
1294
|
-
instruction: "Search the codebase for files related to the ticket and list the top 5 most relevant files.",
|
|
1295
|
-
description: "Find relevant source files",
|
|
1296
|
-
on_error: "warn_and_continue",
|
|
1297
|
-
},
|
|
1298
|
-
{
|
|
1299
|
-
type: "mcp_call",
|
|
1300
|
-
tool: "add_comment",
|
|
1301
|
-
params: {
|
|
1302
|
-
ticket_number: "{ticket_key}",
|
|
1303
|
-
comment_text: "Pipeline analysis complete. See agent output for details.",
|
|
1304
|
-
},
|
|
1305
|
-
description: "Post a summary comment to the ticket",
|
|
1306
|
-
on_error: "warn_and_continue",
|
|
1307
|
-
requires_approval: true,
|
|
1308
|
-
},
|
|
1309
|
-
],
|
|
1310
|
-
}, null, 2) + "\n";
|
|
1311
|
-
try {
|
|
1312
|
-
await stat(readmePath);
|
|
1313
|
-
console.log(` ${path.relative(cwd, readmePath)} (skipped — already exists)`);
|
|
1314
|
-
}
|
|
1315
|
-
catch {
|
|
1316
|
-
await writeFile(readmePath, readmeContent, "utf-8");
|
|
1317
|
-
console.log(` ${path.relative(cwd, readmePath)} (written)`);
|
|
1318
|
-
}
|
|
1319
|
-
try {
|
|
1320
|
-
await stat(examplePath);
|
|
1321
|
-
console.log(` ${path.relative(cwd, examplePath)} (skipped — already exists)`);
|
|
1322
|
-
}
|
|
1323
|
-
catch {
|
|
1324
|
-
await writeFile(examplePath, exampleContent, "utf-8");
|
|
1325
|
-
console.log(` ${path.relative(cwd, examplePath)} (written)`);
|
|
1326
|
-
}
|
|
1327
|
-
console.log(` ${path.relative(cwd, instrDir)}/ (ensured)`);
|
|
1328
|
-
// ---- Phase 6b: Scaffold the committed secret-free `.bridge/config` ----
|
|
1329
|
-
// This manifest is committed and inherited by every worktree; it is the
|
|
1330
|
-
// input to start-tickets worktree MCP provisioning. It is intentionally
|
|
1331
|
-
// NOT gitignored (unlike the .mcp.json configs above). Credentials are
|
|
1332
|
-
// resolved at runtime, never written here.
|
|
1333
|
-
const bridgeConfigPath = path.join(cwd, ".bridge", "config");
|
|
1334
|
-
let bridgeConfigExists = false;
|
|
1335
|
-
try {
|
|
1336
|
-
await stat(bridgeConfigPath);
|
|
1337
|
-
bridgeConfigExists = true;
|
|
1338
|
-
}
|
|
1339
|
-
catch { }
|
|
1340
|
-
if (bridgeConfigExists) {
|
|
1341
|
-
console.log(`\n.bridge/config: skipped — already exists`);
|
|
1342
|
-
}
|
|
1343
|
-
else {
|
|
1344
|
-
await mkdir(path.dirname(bridgeConfigPath), { recursive: true });
|
|
1345
|
-
await writeFile(bridgeConfigPath, buildBridgeConfigManifest(chooseScaffoldRepoName(cwd)), "utf-8");
|
|
1346
|
-
console.log(`\n.bridge/config: written`);
|
|
1347
|
-
}
|
|
1348
|
-
console.log(" Credentials are resolved at runtime from BAPI_API_KEY or " +
|
|
1349
|
-
"~/.config/bridge/credentials.json (no secrets are written to .bridge/config).");
|
|
1350
|
-
// ---- Phase 7: Final summary ----
|
|
1351
|
-
if (anyCreatedOrAdded) {
|
|
1352
|
-
console.log("\nSet BAPI_REPO_NAME in your config files. Do NOT put BAPI_API_KEY in the " +
|
|
1353
|
-
"generated MCP config — supply it via the BAPI_API_KEY environment variable, " +
|
|
1354
|
-
"or store it under \"bapi:<repo_name>\" in ~/.config/bridge/credentials.json. " +
|
|
1355
|
-
"Get your values from the Bridge API setup UI at https://bridgegpt-api.com");
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
// ---------------------------------------------------------------------------
|
|
1359
915
|
// CLI subcommand dispatch
|
|
1360
916
|
// ---------------------------------------------------------------------------
|
|
1361
917
|
//
|
|
@@ -1396,37 +952,6 @@ async function runInitCli(cwd) {
|
|
|
1396
952
|
return 1;
|
|
1397
953
|
}
|
|
1398
954
|
}
|
|
1399
|
-
/** Run the ``--upgrade`` flow (npm install latest + re-scaffold). */
|
|
1400
|
-
async function runUpgradeCli(cwd) {
|
|
1401
|
-
const guardError = await ensurePackageJsonForCliCommand("--upgrade", cwd);
|
|
1402
|
-
if (guardError) {
|
|
1403
|
-
console.error(guardError);
|
|
1404
|
-
return 1;
|
|
1405
|
-
}
|
|
1406
|
-
try {
|
|
1407
|
-
const oldVersion = VERSION;
|
|
1408
|
-
console.log("Upgrading @bridge_gpt/mcp-server to latest...\n");
|
|
1409
|
-
execSync("npm i @bridge_gpt/mcp-server@latest", { stdio: "inherit" });
|
|
1410
|
-
// Read the newly installed version from node_modules
|
|
1411
|
-
const require = createRequire(cwd + "/");
|
|
1412
|
-
const newPkgPath = require.resolve("@bridge_gpt/mcp-server/package.json");
|
|
1413
|
-
const newPkg = JSON.parse(await readFile(newPkgPath, "utf-8"));
|
|
1414
|
-
const newVersion = newPkg.version;
|
|
1415
|
-
console.log(`\n@bridge_gpt/mcp-server: ${oldVersion} -> ${newVersion}`);
|
|
1416
|
-
if (oldVersion === newVersion) {
|
|
1417
|
-
console.log("Already up-to-date.");
|
|
1418
|
-
}
|
|
1419
|
-
console.log("\nRefreshing scaffolded artifacts...\n");
|
|
1420
|
-
await runInit(cwd);
|
|
1421
|
-
console.log("\nUpgrade complete.");
|
|
1422
|
-
return 0;
|
|
1423
|
-
}
|
|
1424
|
-
catch (err) {
|
|
1425
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1426
|
-
console.error(`Bridge API --upgrade failed: ${msg}`);
|
|
1427
|
-
return 1;
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
955
|
/**
|
|
1431
956
|
* Route CLI subcommands before MCP server startup. Returns an exit code to
|
|
1432
957
|
* exit the process with, or ``null`` to continue to normal MCP server startup.
|
|
@@ -1486,12 +1011,32 @@ async function dispatchCliSubcommand(argv) {
|
|
|
1486
1011
|
if (argv[0] === "conductor") {
|
|
1487
1012
|
return runConductorCli(argv.slice(1));
|
|
1488
1013
|
}
|
|
1014
|
+
// The `install-bridge` subcommand (BAPI-429) is the one-command project
|
|
1015
|
+
// bootstrap: it scaffolds (`runInit`), writes the per-host MCP config with real
|
|
1016
|
+
// values, verifies connectivity, persists the routing credential, then spawns a
|
|
1017
|
+
// fresh agent session for the agentic remainder. Like the others it is a
|
|
1018
|
+
// positional subcommand routed before the --init / --upgrade flag guards and
|
|
1019
|
+
// well before MCP server construction so it never falls through to startup.
|
|
1020
|
+
if (argv[0] === "install-bridge") {
|
|
1021
|
+
return runInstallBridgeCli(argv.slice(1));
|
|
1022
|
+
}
|
|
1023
|
+
// The `upgrade` subcommand (BAPI-430) is the one-command upgrade path: re-exec
|
|
1024
|
+
// from @latest, reconcile stale layers, rewrite the launcher pin, and reconnect.
|
|
1025
|
+
if (argv[0] === "upgrade") {
|
|
1026
|
+
return runUpgradeCli(argv.slice(1));
|
|
1027
|
+
}
|
|
1028
|
+
// --version exits immediately with the current version string (no server start).
|
|
1029
|
+
if (argv.includes("--version")) {
|
|
1030
|
+
console.log(VERSION);
|
|
1031
|
+
return 0;
|
|
1032
|
+
}
|
|
1489
1033
|
// --init takes precedence over --upgrade; both are position-independent flags.
|
|
1490
1034
|
if (argv.includes("--init")) {
|
|
1491
1035
|
return runInitCli(cwd);
|
|
1492
1036
|
}
|
|
1037
|
+
// --upgrade fallback passes all args so flags like --dry-run are forwarded.
|
|
1493
1038
|
if (argv.includes("--upgrade")) {
|
|
1494
|
-
return runUpgradeCli(
|
|
1039
|
+
return runUpgradeCli(argv);
|
|
1495
1040
|
}
|
|
1496
1041
|
if (argv.length > 0 && !argv[0].startsWith("-")) {
|
|
1497
1042
|
console.error(`Error: Unknown subcommand '${argv[0]}'. Run with --help for usage, or omit subcommands to start the MCP server.`);
|
|
@@ -1594,6 +1139,23 @@ const registerTool = ((name, config, handler) => {
|
|
|
1594
1139
|
return toolHandle;
|
|
1595
1140
|
});
|
|
1596
1141
|
// ---------------------------------------------------------------------------
|
|
1142
|
+
// Shared Zod schema fragments (BAPI-433 Phase 1)
|
|
1143
|
+
// Centralizes common parameter definitions to eliminate redundant describe() prose.
|
|
1144
|
+
// The MCP SDK serializes these per-tool, so the savings come from shorter text,
|
|
1145
|
+
// not from object reuse in the wire payload.
|
|
1146
|
+
// ---------------------------------------------------------------------------
|
|
1147
|
+
const commonFields = {
|
|
1148
|
+
ticket_number: z.string(),
|
|
1149
|
+
repo_name: z.string().optional(),
|
|
1150
|
+
save_locally: z.boolean().optional().default(true),
|
|
1151
|
+
wait_for_result: z.boolean().optional().default(false).describe("When true, blocks and polls until ready, returning full content directly. " +
|
|
1152
|
+
"When false (default), returns immediately with confirmation/handle. " +
|
|
1153
|
+
"Use the corresponding get_* tool to retrieve results."),
|
|
1154
|
+
second_opinion: z.string().optional().describe("Provider routing override for THIS request. NOT the standalone second_opinion tool. " +
|
|
1155
|
+
"Takes precedence over provider."),
|
|
1156
|
+
provider: z.string().optional().describe("Use a specific LLM provider (openai, anthropic, gemini) without triggering second-opinion semantics."),
|
|
1157
|
+
};
|
|
1158
|
+
// ---------------------------------------------------------------------------
|
|
1597
1159
|
// Conductor event-ledger tools (BAPI-393)
|
|
1598
1160
|
// ---------------------------------------------------------------------------
|
|
1599
1161
|
//
|
|
@@ -1602,7 +1164,9 @@ const registerTool = ((name, config, handler) => {
|
|
|
1602
1164
|
// ~/.config/bridge/events.db and intentionally do NOT route through the Bridge
|
|
1603
1165
|
// API HTTP helpers. They are registered through the same `registerTool` wrapper
|
|
1604
1166
|
// as every other tool so they share enable/disable + in-process dispatch.
|
|
1605
|
-
|
|
1167
|
+
if (profileIncludes(BRIDGE_MCP_PROFILE, "conductor")) {
|
|
1168
|
+
registerConductorTools(registerTool);
|
|
1169
|
+
}
|
|
1606
1170
|
registerTool("ping", {
|
|
1607
1171
|
annotations: {
|
|
1608
1172
|
readOnlyHint: true,
|
|
@@ -1636,7 +1200,8 @@ registerTool("ping", {
|
|
|
1636
1200
|
{ type: "text", text: JSON.stringify(body, null, 2) },
|
|
1637
1201
|
];
|
|
1638
1202
|
if (typeof body.upgrade_advice === "string" && body.upgrade_advice.length > 0) {
|
|
1639
|
-
|
|
1203
|
+
const actionableAdvice = `Update available: running ${body.mcp_version}, latest ${body.latest_mcp_version} — run npx -y @bridge_gpt/mcp-server@latest --upgrade`;
|
|
1204
|
+
content.push({ type: "text", text: actionableAdvice });
|
|
1640
1205
|
}
|
|
1641
1206
|
return { content };
|
|
1642
1207
|
}
|
|
@@ -1652,15 +1217,9 @@ registerTool("second_opinion", {
|
|
|
1652
1217
|
idempotentHint: false, // each call submits a fresh LLM request
|
|
1653
1218
|
openWorldHint: true,
|
|
1654
1219
|
},
|
|
1655
|
-
description: "
|
|
1656
|
-
"
|
|
1657
|
-
"This
|
|
1658
|
-
"If instead you want a Bridge ARTIFACT (a plan, critique, clarifying questions, architecture doc, reimplementation context, etc.) generated by a different provider, do NOT use this tool — call the matching `request_*` tool and set that tool's own `second_opinion` or `provider` parameter to route its generation to another provider. " +
|
|
1659
|
-
"Pick a provider from a DIFFERENT model family than the one you are running on so the response is genuinely independent (e.g. if you are an OpenAI agent, ask 'anthropic' or 'gemini'). " +
|
|
1660
|
-
"Pick a model tier appropriate for the depth of pushback you want: CHEAP_MODEL for a quick sanity check, BASIC_MODEL for a focused review, PREMIUM_MODEL for serious architectural pushback. " +
|
|
1661
|
-
"The 'prompt' you supply should be a complete, self-contained brief: the full plan, recommendation, or question you want challenged, along with whatever context is needed to evaluate it. The server adds a system prompt that frames the responder as an independent senior engineer and injects lightweight project context. " +
|
|
1662
|
-
"Returns the responding model's reply text plus the resolved provider, tier, and model id. The reply is returned to you as-is; you decide whether to incorporate the feedback. " +
|
|
1663
|
-
"Project authorization uses the standard repo access check; per-project provider gating is intentionally not enforced for this tool.",
|
|
1220
|
+
description: "Use to get an immediate, ad hoc independent critique on a plan or analysis you already have. " +
|
|
1221
|
+
"Returns the responding model's reply text plus the resolved provider. " +
|
|
1222
|
+
"This does NOT create or retrieve a Bridge artifact (use request_* tools for that).",
|
|
1664
1223
|
inputSchema: {
|
|
1665
1224
|
prompt: z
|
|
1666
1225
|
.string()
|
|
@@ -2009,9 +1568,7 @@ registerTool("get_ticket", {
|
|
|
2009
1568
|
"All data is fetched live from Jira. Use get_tickets to search/list multiple tickets. " +
|
|
2010
1569
|
"Use get_comments to fetch comments on the ticket.",
|
|
2011
1570
|
inputSchema: {
|
|
2012
|
-
ticket_number:
|
|
2013
|
-
.string()
|
|
2014
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123)"),
|
|
1571
|
+
ticket_number: commonFields.ticket_number,
|
|
2015
1572
|
},
|
|
2016
1573
|
}, async ({ ticket_number }) => {
|
|
2017
1574
|
const url = buildGetUrl(`/tickets/${encodeURIComponent(ticket_number)}`, { repo_name: REPO_NAME });
|
|
@@ -2038,9 +1595,7 @@ registerTool("get_ticket_model_tier", {
|
|
|
2038
1595
|
"the tier->model mapping is owned by the start-tickets CLI. A null tier (source=\"fallback\") means the " +
|
|
2039
1596
|
"model could not be resolved and callers should use the agent's default model.",
|
|
2040
1597
|
inputSchema: {
|
|
2041
|
-
ticket_number:
|
|
2042
|
-
.string()
|
|
2043
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123)"),
|
|
1598
|
+
ticket_number: commonFields.ticket_number,
|
|
2044
1599
|
},
|
|
2045
1600
|
}, async ({ ticket_number }) => {
|
|
2046
1601
|
const url = buildGetUrl(`/tickets/${encodeURIComponent(ticket_number)}/model-tier`, { repo_name: REPO_NAME });
|
|
@@ -2061,9 +1616,7 @@ registerTool("get_comments", {
|
|
|
2061
1616
|
"Use this to read what a developer or stakeholder has said on a ticket. " +
|
|
2062
1617
|
"Use add_comment to post a new comment.",
|
|
2063
1618
|
inputSchema: {
|
|
2064
|
-
ticket_number:
|
|
2065
|
-
.string()
|
|
2066
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123)"),
|
|
1619
|
+
ticket_number: commonFields.ticket_number,
|
|
2067
1620
|
},
|
|
2068
1621
|
}, async ({ ticket_number }) => {
|
|
2069
1622
|
const url = buildGetUrl(`/tickets/${encodeURIComponent(ticket_number)}/comments`, { repo_name: REPO_NAME });
|
|
@@ -2144,15 +1697,8 @@ registerTool("get_plan", {
|
|
|
2144
1697
|
"Returns a 404 / not-found response when no plan is ready yet — that means generation has not run, not that this tool failed. " +
|
|
2145
1698
|
"Tip: call get_clarifying_questions for the same ticket to get the full context for implementation.",
|
|
2146
1699
|
inputSchema: {
|
|
2147
|
-
ticket_number:
|
|
2148
|
-
|
|
2149
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123, BAPI-42)"),
|
|
2150
|
-
save_locally: z
|
|
2151
|
-
.boolean()
|
|
2152
|
-
.optional()
|
|
2153
|
-
.default(true)
|
|
2154
|
-
.describe("Whether to save the retrieved plan to a local file. Saves to BAPI_DOCS_DIR/plans/{ticket}-plan.md. " +
|
|
2155
|
-
"Defaults to true. Set to false to skip saving."),
|
|
1700
|
+
ticket_number: commonFields.ticket_number,
|
|
1701
|
+
save_locally: commonFields.save_locally,
|
|
2156
1702
|
},
|
|
2157
1703
|
}, async (args) => {
|
|
2158
1704
|
return getTicketArtifact("plan", args);
|
|
@@ -2170,15 +1716,8 @@ registerTool("get_architecture", {
|
|
|
2170
1716
|
"The plan includes high-level architectural decisions, component design, and integration guidance. " +
|
|
2171
1717
|
"Returns a 404 / not-found response when no architecture plan is ready yet — that means generation has not run, not that this tool failed.",
|
|
2172
1718
|
inputSchema: {
|
|
2173
|
-
ticket_number:
|
|
2174
|
-
|
|
2175
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123, BAPI-42)"),
|
|
2176
|
-
save_locally: z
|
|
2177
|
-
.boolean()
|
|
2178
|
-
.optional()
|
|
2179
|
-
.default(true)
|
|
2180
|
-
.describe("Whether to save the architecture plan to a local file in the BAPI_DOCS_DIR/architecture/ directory. " +
|
|
2181
|
-
"Defaults to true. Set to false to skip saving."),
|
|
1719
|
+
ticket_number: commonFields.ticket_number,
|
|
1720
|
+
save_locally: commonFields.save_locally,
|
|
2182
1721
|
},
|
|
2183
1722
|
}, async (args) => {
|
|
2184
1723
|
return getTicketArtifact("architecture", args);
|
|
@@ -2196,15 +1735,8 @@ registerTool("get_prd", {
|
|
|
2196
1735
|
"The PRD is product/stakeholder-facing: problem framing, goals, success metrics, scope, and product requirements. " +
|
|
2197
1736
|
"Returns a 404 / not-found response when no PRD is ready yet — that means generation has not run, not that this tool failed.",
|
|
2198
1737
|
inputSchema: {
|
|
2199
|
-
ticket_number:
|
|
2200
|
-
|
|
2201
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123, BAPI-42)"),
|
|
2202
|
-
save_locally: z
|
|
2203
|
-
.boolean()
|
|
2204
|
-
.optional()
|
|
2205
|
-
.default(true)
|
|
2206
|
-
.describe("Whether to save the PRD to a local file in the BAPI_DOCS_DIR/prd/ directory. " +
|
|
2207
|
-
"Defaults to true. Set to false to skip saving."),
|
|
1738
|
+
ticket_number: commonFields.ticket_number,
|
|
1739
|
+
save_locally: commonFields.save_locally,
|
|
2208
1740
|
},
|
|
2209
1741
|
}, async (args) => {
|
|
2210
1742
|
return getTicketArtifact("prd", args);
|
|
@@ -2222,15 +1754,8 @@ registerTool("get_clarifying_questions", {
|
|
|
2222
1754
|
"Returns a 404 / not-found response when no questions are ready yet — that means generation has not run, not that this tool failed. " +
|
|
2223
1755
|
"Tip: call get_plan for the same ticket to get the implementation plan alongside these questions.",
|
|
2224
1756
|
inputSchema: {
|
|
2225
|
-
ticket_number:
|
|
2226
|
-
|
|
2227
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123, BAPI-42)"),
|
|
2228
|
-
save_locally: z
|
|
2229
|
-
.boolean()
|
|
2230
|
-
.optional()
|
|
2231
|
-
.default(true)
|
|
2232
|
-
.describe("Whether to save the clarifying questions to a local file in the BAPI_DOCS_DIR/clarifying-questions/ directory. " +
|
|
2233
|
-
"Defaults to true. Set to false to skip saving."),
|
|
1757
|
+
ticket_number: commonFields.ticket_number,
|
|
1758
|
+
save_locally: commonFields.save_locally,
|
|
2234
1759
|
},
|
|
2235
1760
|
}, async (args) => {
|
|
2236
1761
|
return getTicketArtifact("clarifying_questions", args);
|
|
@@ -2330,9 +1855,7 @@ registerTool("add_comment", {
|
|
|
2330
1855
|
"Tip: To generate plans, clarifying questions, or ticket critiques, use the dedicated " +
|
|
2331
1856
|
"request_plan_generation, request_clarifying_questions, or request_ticket_critique tools.",
|
|
2332
1857
|
inputSchema: {
|
|
2333
|
-
ticket_number:
|
|
2334
|
-
.string()
|
|
2335
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123, BAPI-42)"),
|
|
1858
|
+
ticket_number: commonFields.ticket_number,
|
|
2336
1859
|
comment: z
|
|
2337
1860
|
.string()
|
|
2338
1861
|
.optional()
|
|
@@ -2388,9 +1911,7 @@ registerTool("update_ticket_description", {
|
|
|
2388
1911
|
"This does NOT create a new ticket. Use create_ticket for that. " +
|
|
2389
1912
|
"Returns a success message with the ticket number, or an error if the update fails.",
|
|
2390
1913
|
inputSchema: {
|
|
2391
|
-
ticket_number:
|
|
2392
|
-
.string()
|
|
2393
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123)"),
|
|
1914
|
+
ticket_number: commonFields.ticket_number,
|
|
2394
1915
|
description: z
|
|
2395
1916
|
.string()
|
|
2396
1917
|
.optional()
|
|
@@ -2431,9 +1952,7 @@ registerTool("upload_attachment", {
|
|
|
2431
1952
|
"Note: link_type is text-only; cannot be used with base64 uploads. " +
|
|
2432
1953
|
"⚠️ SECURITY: Binary bytes cannot be secret-redacted by automated text scrubbers; review sensitive visual content before upload.",
|
|
2433
1954
|
inputSchema: {
|
|
2434
|
-
ticket_number:
|
|
2435
|
-
.string()
|
|
2436
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123, BAPI-42)"),
|
|
1955
|
+
ticket_number: commonFields.ticket_number,
|
|
2437
1956
|
file_path: z
|
|
2438
1957
|
.string()
|
|
2439
1958
|
.optional()
|
|
@@ -2502,9 +2021,7 @@ registerTool("list_attachments", {
|
|
|
2502
2021
|
"By default, AI-generated attachments are excluded. " +
|
|
2503
2022
|
"Use this to discover available attachments before downloading.",
|
|
2504
2023
|
inputSchema: {
|
|
2505
|
-
ticket_number:
|
|
2506
|
-
.string()
|
|
2507
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123)"),
|
|
2024
|
+
ticket_number: commonFields.ticket_number,
|
|
2508
2025
|
include_ai_generated: z
|
|
2509
2026
|
.boolean()
|
|
2510
2027
|
.optional()
|
|
@@ -2539,9 +2056,7 @@ registerTool("download_attachment", {
|
|
|
2539
2056
|
"For binary files, the file is saved to disk and the path is returned. " +
|
|
2540
2057
|
"Default save location: {BAPI_DOCS_DIR}/attachments/{ticket_number}/{filename}.",
|
|
2541
2058
|
inputSchema: {
|
|
2542
|
-
ticket_number:
|
|
2543
|
-
.string()
|
|
2544
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123)"),
|
|
2059
|
+
ticket_number: commonFields.ticket_number,
|
|
2545
2060
|
attachment_id: z
|
|
2546
2061
|
.string()
|
|
2547
2062
|
.optional()
|
|
@@ -2635,35 +2150,11 @@ registerTool("request_plan_generation", {
|
|
|
2635
2150
|
"or 403 if the API key is unauthorized. " +
|
|
2636
2151
|
"Set wait_for_result to true to block until the result is ready (typically 1-5 minutes) instead of returning immediately.",
|
|
2637
2152
|
inputSchema: {
|
|
2638
|
-
ticket_number:
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
.optional()
|
|
2644
|
-
.default(false)
|
|
2645
|
-
.describe("When true, the tool blocks and polls until the plan is ready (typically 1-5 minutes), " +
|
|
2646
|
-
"then returns the full plan content directly. When false (default), returns immediately " +
|
|
2647
|
-
"with a confirmation message — use get_plan later to retrieve results."),
|
|
2648
|
-
save_locally: z
|
|
2649
|
-
.boolean()
|
|
2650
|
-
.optional()
|
|
2651
|
-
.default(true)
|
|
2652
|
-
.describe("When wait_for_result is true, whether to save the plan to a local file in the " +
|
|
2653
|
-
"BAPI_DOCS_DIR/plans/ directory. Defaults to true. Ignored when wait_for_result is false."),
|
|
2654
|
-
second_opinion: z
|
|
2655
|
-
.string()
|
|
2656
|
-
.optional()
|
|
2657
|
-
.describe("Provider routing override for THIS artifact-generation request " +
|
|
2658
|
-
"(e.g. 'anthropic', 'openai', 'gemini'). When set, the artifact is " +
|
|
2659
|
-
"generated by the named provider and, where supported, a cross-provider " +
|
|
2660
|
-
"second-opinion pass is applied to this request only. " +
|
|
2661
|
-
"This is NOT the standalone `second_opinion` tool — it does not return " +
|
|
2662
|
-
"an ad hoc critique; it only changes which provider produces this " +
|
|
2663
|
-
"request's artifact. Takes precedence over `provider` when both are set."),
|
|
2664
|
-
provider: z.string().optional().describe("Pure provider switch — use a specific LLM provider (openai, anthropic, gemini) without " +
|
|
2665
|
-
"triggering second-opinion semantics. If both provider and second_opinion are set, " +
|
|
2666
|
-
"second_opinion takes precedence."),
|
|
2153
|
+
ticket_number: commonFields.ticket_number,
|
|
2154
|
+
wait_for_result: commonFields.wait_for_result,
|
|
2155
|
+
save_locally: commonFields.save_locally,
|
|
2156
|
+
second_opinion: commonFields.second_opinion,
|
|
2157
|
+
provider: commonFields.provider,
|
|
2667
2158
|
},
|
|
2668
2159
|
}, async (args) => {
|
|
2669
2160
|
return requestTicketArtifact("plan", args);
|
|
@@ -2683,35 +2174,11 @@ registerTool("request_architecture", {
|
|
|
2683
2174
|
"or 403 if the API key is unauthorized. " +
|
|
2684
2175
|
"Set wait_for_result to true to block until the result is ready (typically 2-4 minutes) instead of returning immediately.",
|
|
2685
2176
|
inputSchema: {
|
|
2686
|
-
ticket_number:
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
.optional()
|
|
2692
|
-
.default(false)
|
|
2693
|
-
.describe("When true, the tool blocks and polls until the architecture plan is ready (typically 2-4 minutes), " +
|
|
2694
|
-
"then returns the full plan content directly. When false (default), returns immediately " +
|
|
2695
|
-
"with a confirmation message — use get_architecture later to retrieve results."),
|
|
2696
|
-
save_locally: z
|
|
2697
|
-
.boolean()
|
|
2698
|
-
.optional()
|
|
2699
|
-
.default(true)
|
|
2700
|
-
.describe("When wait_for_result is true, whether to save the architecture plan to a local file in the " +
|
|
2701
|
-
"BAPI_DOCS_DIR/architecture/ directory. Defaults to true. Only takes effect when wait_for_result is true."),
|
|
2702
|
-
second_opinion: z
|
|
2703
|
-
.string()
|
|
2704
|
-
.optional()
|
|
2705
|
-
.describe("Provider routing override for THIS artifact-generation request " +
|
|
2706
|
-
"(e.g. 'anthropic', 'openai', 'gemini'). When set, the artifact is " +
|
|
2707
|
-
"generated by the named provider and, where supported, a cross-provider " +
|
|
2708
|
-
"second-opinion pass is applied to this request only. " +
|
|
2709
|
-
"This is NOT the standalone `second_opinion` tool — it does not return " +
|
|
2710
|
-
"an ad hoc critique; it only changes which provider produces this " +
|
|
2711
|
-
"request's artifact. Takes precedence over `provider` when both are set."),
|
|
2712
|
-
provider: z.string().optional().describe("Pure provider switch — use a specific LLM provider (openai, anthropic, gemini) without " +
|
|
2713
|
-
"triggering second-opinion semantics. If both provider and second_opinion are set, " +
|
|
2714
|
-
"second_opinion takes precedence."),
|
|
2177
|
+
ticket_number: commonFields.ticket_number,
|
|
2178
|
+
wait_for_result: commonFields.wait_for_result,
|
|
2179
|
+
save_locally: commonFields.save_locally,
|
|
2180
|
+
second_opinion: commonFields.second_opinion,
|
|
2181
|
+
provider: commonFields.provider,
|
|
2715
2182
|
},
|
|
2716
2183
|
}, async (args) => {
|
|
2717
2184
|
return requestTicketArtifact("architecture", args);
|
|
@@ -2731,35 +2198,11 @@ registerTool("request_prd", {
|
|
|
2731
2198
|
"or 403 if the API key is unauthorized. " +
|
|
2732
2199
|
"Set wait_for_result to true to block until the result is ready (typically 2-4 minutes) instead of returning immediately.",
|
|
2733
2200
|
inputSchema: {
|
|
2734
|
-
ticket_number:
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
.optional()
|
|
2740
|
-
.default(false)
|
|
2741
|
-
.describe("When true, the tool blocks and polls until the PRD is ready (typically 2-4 minutes), " +
|
|
2742
|
-
"then returns the full PRD content directly. When false (default), returns immediately " +
|
|
2743
|
-
"with a confirmation message — use get_prd later to retrieve results."),
|
|
2744
|
-
save_locally: z
|
|
2745
|
-
.boolean()
|
|
2746
|
-
.optional()
|
|
2747
|
-
.default(true)
|
|
2748
|
-
.describe("When wait_for_result is true, whether to save the PRD to a local file in the " +
|
|
2749
|
-
"BAPI_DOCS_DIR/prd/ directory. Defaults to true. Only takes effect when wait_for_result is true."),
|
|
2750
|
-
second_opinion: z
|
|
2751
|
-
.string()
|
|
2752
|
-
.optional()
|
|
2753
|
-
.describe("Provider routing override for THIS artifact-generation request " +
|
|
2754
|
-
"(e.g. 'anthropic', 'openai', 'gemini'). When set, the artifact is " +
|
|
2755
|
-
"generated by the named provider and, where supported, a cross-provider " +
|
|
2756
|
-
"second-opinion pass is applied to this request only. " +
|
|
2757
|
-
"This is NOT the standalone `second_opinion` tool — it does not return " +
|
|
2758
|
-
"an ad hoc critique; it only changes which provider produces this " +
|
|
2759
|
-
"request's artifact. Takes precedence over `provider` when both are set."),
|
|
2760
|
-
provider: z.string().optional().describe("Pure provider switch — use a specific LLM provider (openai, anthropic, gemini) without " +
|
|
2761
|
-
"triggering second-opinion semantics. If both provider and second_opinion are set, " +
|
|
2762
|
-
"second_opinion takes precedence."),
|
|
2201
|
+
ticket_number: commonFields.ticket_number,
|
|
2202
|
+
wait_for_result: commonFields.wait_for_result,
|
|
2203
|
+
save_locally: commonFields.save_locally,
|
|
2204
|
+
second_opinion: commonFields.second_opinion,
|
|
2205
|
+
provider: commonFields.provider,
|
|
2763
2206
|
},
|
|
2764
2207
|
}, async (args) => {
|
|
2765
2208
|
return requestTicketArtifact("prd", args);
|
|
@@ -2771,35 +2214,17 @@ registerTool("create_doc", {
|
|
|
2771
2214
|
idempotentHint: false,
|
|
2772
2215
|
openWorldHint: true,
|
|
2773
2216
|
},
|
|
2774
|
-
description: "
|
|
2775
|
-
"
|
|
2776
|
-
"
|
|
2777
|
-
"Product Requirements Document (product-requirements-focused: problem, goals, and success metrics). " +
|
|
2778
|
-
"This triggers an asynchronous background job — results are NOT immediate. " +
|
|
2779
|
-
"Processing typically takes 2-4 minutes depending on ticket complexity. " +
|
|
2780
|
-
"The matching get_doc tool retrieves the generated document later (call get_doc with the same ticket_number and doc_type) — unless you set wait_for_result, in which case this call blocks and returns it directly. " +
|
|
2781
|
-
"Returns 202 if the request was accepted, 404 if the ticket does not exist in Jira, or 403 if the API key is unauthorized.",
|
|
2217
|
+
description: "Use to start async generation of a design document (tdd, fsd, or prd) for a Jira ticket. " +
|
|
2218
|
+
"Returns confirmation immediately (or the full document if wait_for_result is true). " +
|
|
2219
|
+
"Use get_doc to retrieve. Generates and persists a retrievable artifact.",
|
|
2782
2220
|
inputSchema: {
|
|
2783
|
-
ticket_number:
|
|
2784
|
-
.string()
|
|
2785
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123) to generate a design document for"),
|
|
2221
|
+
ticket_number: commonFields.ticket_number,
|
|
2786
2222
|
doc_type: z.enum(["tdd", "fsd", "prd"])
|
|
2787
2223
|
.describe("Which design document to generate: 'tdd' (Technical Design Document, engineer audience), " +
|
|
2788
2224
|
"'fsd' (Functional Specification Document, product/functional audience), or " +
|
|
2789
2225
|
"'prd' (Product Requirements Document, product-requirements focused: problem, goals, success metrics)."),
|
|
2790
|
-
wait_for_result:
|
|
2791
|
-
|
|
2792
|
-
.optional()
|
|
2793
|
-
.default(false)
|
|
2794
|
-
.describe("When true, the tool blocks and polls until the document is ready (typically 2-4 minutes), " +
|
|
2795
|
-
"then returns the full content directly. When false (default), returns immediately " +
|
|
2796
|
-
"with a confirmation message — use get_doc later to retrieve results."),
|
|
2797
|
-
save_locally: z
|
|
2798
|
-
.boolean()
|
|
2799
|
-
.optional()
|
|
2800
|
-
.default(true)
|
|
2801
|
-
.describe("When wait_for_result is true, whether to save the generated document to a local file under " +
|
|
2802
|
-
"BAPI_DOCS_DIR. Defaults to true. Only takes effect when wait_for_result is true."),
|
|
2226
|
+
wait_for_result: commonFields.wait_for_result,
|
|
2227
|
+
save_locally: commonFields.save_locally,
|
|
2803
2228
|
second_opinion: z
|
|
2804
2229
|
.string()
|
|
2805
2230
|
.optional()
|
|
@@ -2830,18 +2255,11 @@ registerTool("get_doc", {
|
|
|
2830
2255
|
"Returns the full document as markdown text — present it verbatim without summarizing. " +
|
|
2831
2256
|
"Returns a 404 / not-found response when no document is ready yet — that means generation has not run, not that this tool failed.",
|
|
2832
2257
|
inputSchema: {
|
|
2833
|
-
ticket_number:
|
|
2834
|
-
.string()
|
|
2835
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123, BAPI-42)"),
|
|
2258
|
+
ticket_number: commonFields.ticket_number,
|
|
2836
2259
|
doc_type: z.enum(["tdd", "fsd", "prd"])
|
|
2837
2260
|
.describe("Which design document to retrieve: 'tdd' (Technical Design Document), " +
|
|
2838
2261
|
"'fsd' (Functional Specification Document), or 'prd' (Product Requirements Document)."),
|
|
2839
|
-
save_locally:
|
|
2840
|
-
.boolean()
|
|
2841
|
-
.optional()
|
|
2842
|
-
.default(true)
|
|
2843
|
-
.describe("Whether to save the retrieved document to a local file under BAPI_DOCS_DIR. " +
|
|
2844
|
-
"Defaults to true. Set to false to skip saving."),
|
|
2262
|
+
save_locally: commonFields.save_locally,
|
|
2845
2263
|
},
|
|
2846
2264
|
}, async (args) => {
|
|
2847
2265
|
return getTicketArtifact(resolveDesignDocArtifactType(args.doc_type), args);
|
|
@@ -2862,35 +2280,11 @@ registerTool("request_clarifying_questions", {
|
|
|
2862
2280
|
"or 403 if the API key is unauthorized. " +
|
|
2863
2281
|
"Set wait_for_result to true to block until the result is ready (typically 1-5 minutes) instead of returning immediately.",
|
|
2864
2282
|
inputSchema: {
|
|
2865
|
-
ticket_number:
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
.optional()
|
|
2871
|
-
.default(false)
|
|
2872
|
-
.describe("When true, the tool blocks and polls until the clarifying questions are ready (typically 1-5 minutes), " +
|
|
2873
|
-
"then returns the full content directly. When false (default), returns immediately " +
|
|
2874
|
-
"with a confirmation message — use get_clarifying_questions later to retrieve results."),
|
|
2875
|
-
save_locally: z
|
|
2876
|
-
.boolean()
|
|
2877
|
-
.optional()
|
|
2878
|
-
.default(true)
|
|
2879
|
-
.describe("When wait_for_result is true, whether to save the clarifying questions to a local file in the " +
|
|
2880
|
-
"BAPI_DOCS_DIR/clarifying-questions/ directory. Defaults to true. Ignored when wait_for_result is false."),
|
|
2881
|
-
second_opinion: z
|
|
2882
|
-
.string()
|
|
2883
|
-
.optional()
|
|
2884
|
-
.describe("Provider routing override for THIS artifact-generation request " +
|
|
2885
|
-
"(e.g. 'anthropic', 'openai', 'gemini'). When set, the artifact is " +
|
|
2886
|
-
"generated by the named provider and, where supported, a cross-provider " +
|
|
2887
|
-
"second-opinion pass is applied to this request only. " +
|
|
2888
|
-
"This is NOT the standalone `second_opinion` tool — it does not return " +
|
|
2889
|
-
"an ad hoc critique; it only changes which provider produces this " +
|
|
2890
|
-
"request's artifact. Takes precedence over `provider` when both are set."),
|
|
2891
|
-
provider: z.string().optional().describe("Pure provider switch — use a specific LLM provider (openai, anthropic, gemini) without " +
|
|
2892
|
-
"triggering second-opinion semantics. If both provider and second_opinion are set, " +
|
|
2893
|
-
"second_opinion takes precedence."),
|
|
2283
|
+
ticket_number: commonFields.ticket_number,
|
|
2284
|
+
wait_for_result: commonFields.wait_for_result,
|
|
2285
|
+
save_locally: commonFields.save_locally,
|
|
2286
|
+
second_opinion: commonFields.second_opinion,
|
|
2287
|
+
provider: commonFields.provider,
|
|
2894
2288
|
},
|
|
2895
2289
|
}, async (args) => {
|
|
2896
2290
|
return requestTicketArtifact("clarifying_questions", args);
|
|
@@ -2911,15 +2305,8 @@ registerTool("get_ticket_critique", {
|
|
|
2911
2305
|
"Standards Deviations, and Suggested Improvements. " +
|
|
2912
2306
|
"Returns a 404 / not-found response when no critique is ready yet — that means generation has not run, not that this tool failed.",
|
|
2913
2307
|
inputSchema: {
|
|
2914
|
-
ticket_number:
|
|
2915
|
-
|
|
2916
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123, BAPI-42)"),
|
|
2917
|
-
save_locally: z
|
|
2918
|
-
.boolean()
|
|
2919
|
-
.optional()
|
|
2920
|
-
.default(true)
|
|
2921
|
-
.describe("Whether to save the critique to a local file in the BAPI_DOCS_DIR/ticket-critiques/ directory. " +
|
|
2922
|
-
"Defaults to true. Set to false to skip saving."),
|
|
2308
|
+
ticket_number: commonFields.ticket_number,
|
|
2309
|
+
save_locally: commonFields.save_locally,
|
|
2923
2310
|
},
|
|
2924
2311
|
}, async (args) => {
|
|
2925
2312
|
return getTicketArtifact("ticket_critique", args);
|
|
@@ -2939,35 +2326,11 @@ registerTool("request_ticket_critique", {
|
|
|
2939
2326
|
"or 403 if the API key is unauthorized. " +
|
|
2940
2327
|
"Set wait_for_result to true to block until the result is ready (typically 1-5 minutes) instead of returning immediately.",
|
|
2941
2328
|
inputSchema: {
|
|
2942
|
-
ticket_number:
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
.optional()
|
|
2948
|
-
.default(false)
|
|
2949
|
-
.describe("When true, the tool blocks and polls until the ticket critique is ready (typically 1-5 minutes), " +
|
|
2950
|
-
"then returns the full content directly. When false (default), returns immediately " +
|
|
2951
|
-
"with a confirmation message — use get_ticket_critique later to retrieve results."),
|
|
2952
|
-
save_locally: z
|
|
2953
|
-
.boolean()
|
|
2954
|
-
.optional()
|
|
2955
|
-
.default(true)
|
|
2956
|
-
.describe("When wait_for_result is true, whether to save the ticket critique to a local file in the " +
|
|
2957
|
-
"BAPI_DOCS_DIR/ticket-critiques/ directory. Defaults to true. Ignored when wait_for_result is false."),
|
|
2958
|
-
second_opinion: z
|
|
2959
|
-
.string()
|
|
2960
|
-
.optional()
|
|
2961
|
-
.describe("Provider routing override for THIS artifact-generation request " +
|
|
2962
|
-
"(e.g. 'anthropic', 'openai', 'gemini'). When set, the artifact is " +
|
|
2963
|
-
"generated by the named provider and, where supported, a cross-provider " +
|
|
2964
|
-
"second-opinion pass is applied to this request only. " +
|
|
2965
|
-
"This is NOT the standalone `second_opinion` tool — it does not return " +
|
|
2966
|
-
"an ad hoc critique; it only changes which provider produces this " +
|
|
2967
|
-
"request's artifact. Takes precedence over `provider` when both are set."),
|
|
2968
|
-
provider: z.string().optional().describe("Pure provider switch — use a specific LLM provider (openai, anthropic, gemini) without " +
|
|
2969
|
-
"triggering second-opinion semantics. If both provider and second_opinion are set, " +
|
|
2970
|
-
"second_opinion takes precedence."),
|
|
2329
|
+
ticket_number: commonFields.ticket_number,
|
|
2330
|
+
wait_for_result: commonFields.wait_for_result,
|
|
2331
|
+
save_locally: commonFields.save_locally,
|
|
2332
|
+
second_opinion: commonFields.second_opinion,
|
|
2333
|
+
provider: commonFields.provider,
|
|
2971
2334
|
},
|
|
2972
2335
|
}, async (args) => {
|
|
2973
2336
|
return requestTicketArtifact("ticket_critique", args);
|
|
@@ -2990,35 +2353,11 @@ registerTool("request_ticket_review", {
|
|
|
2990
2353
|
"or 403 if the API key is unauthorized. " +
|
|
2991
2354
|
"Set wait_for_result to true to block until both documents are ready and receive them concatenated.",
|
|
2992
2355
|
inputSchema: {
|
|
2993
|
-
ticket_number:
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
.optional()
|
|
2999
|
-
.default(false)
|
|
3000
|
-
.describe("When true, the tool blocks and polls until both documents are ready (typically 2-6 minutes), " +
|
|
3001
|
-
"then returns the concatenated markdown directly. When false (default), returns immediately " +
|
|
3002
|
-
"with a confirmation message — use get_clarifying_questions and get_ticket_critique later to retrieve results."),
|
|
3003
|
-
save_locally: z
|
|
3004
|
-
.boolean()
|
|
3005
|
-
.optional()
|
|
3006
|
-
.default(true)
|
|
3007
|
-
.describe("When wait_for_result is true, whether to save each generated document to its canonical local path under " +
|
|
3008
|
-
"BAPI_DOCS_DIR (clarifying-questions/ or ticket-critiques/). Defaults to true. Ignored when wait_for_result is false."),
|
|
3009
|
-
second_opinion: z
|
|
3010
|
-
.string()
|
|
3011
|
-
.optional()
|
|
3012
|
-
.describe("Provider routing override for THIS artifact-generation request " +
|
|
3013
|
-
"(e.g. 'anthropic', 'openai', 'gemini'). When set, the artifact is " +
|
|
3014
|
-
"generated by the named provider and, where supported, a cross-provider " +
|
|
3015
|
-
"second-opinion pass is applied to this request only. " +
|
|
3016
|
-
"This is NOT the standalone `second_opinion` tool — it does not return " +
|
|
3017
|
-
"an ad hoc critique; it only changes which provider produces this " +
|
|
3018
|
-
"request's artifact. Takes precedence over `provider` when both are set."),
|
|
3019
|
-
provider: z.string().optional().describe("Pure provider switch — use a specific LLM provider (openai, anthropic, gemini) without " +
|
|
3020
|
-
"triggering second-opinion semantics. If both provider and second_opinion are set, " +
|
|
3021
|
-
"second_opinion takes precedence."),
|
|
2356
|
+
ticket_number: commonFields.ticket_number,
|
|
2357
|
+
wait_for_result: commonFields.wait_for_result,
|
|
2358
|
+
save_locally: commonFields.save_locally,
|
|
2359
|
+
second_opinion: commonFields.second_opinion,
|
|
2360
|
+
provider: commonFields.provider,
|
|
3022
2361
|
},
|
|
3023
2362
|
}, async (args) => {
|
|
3024
2363
|
return requestTicketReview(args);
|
|
@@ -3039,34 +2378,11 @@ registerTool("request_reimplement_context", {
|
|
|
3039
2378
|
"The matching get_reimplement_context tool retrieves the assembled context later (call get_reimplement_context with the same ticket_number) — unless you set wait_for_result, in which case this call blocks and returns it directly. " +
|
|
3040
2379
|
"Set wait_for_result to true to block until the context is ready instead of returning immediately.",
|
|
3041
2380
|
inputSchema: {
|
|
3042
|
-
ticket_number:
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
.optional()
|
|
3048
|
-
.default(false)
|
|
3049
|
-
.describe("When true, POST then poll the GET endpoint until the context is ready. " +
|
|
3050
|
-
"When false (default), return immediately with a confirmation — use get_reimplement_context later."),
|
|
3051
|
-
save_locally: z
|
|
3052
|
-
.boolean()
|
|
3053
|
-
.optional()
|
|
3054
|
-
.default(true)
|
|
3055
|
-
.describe("When wait_for_result is true, save the context markdown to " +
|
|
3056
|
-
"BAPI_DOCS_DIR/reimplementations/{ticket_number}-context.md. Defaults to true."),
|
|
3057
|
-
second_opinion: z
|
|
3058
|
-
.string()
|
|
3059
|
-
.optional()
|
|
3060
|
-
.describe("Provider routing override for THIS artifact-generation request " +
|
|
3061
|
-
"(e.g. 'anthropic', 'openai', 'gemini'). When set, the artifact is " +
|
|
3062
|
-
"generated by the named provider and, where supported, a cross-provider " +
|
|
3063
|
-
"second-opinion pass is applied to this request only. " +
|
|
3064
|
-
"This is NOT the standalone `second_opinion` tool — it does not return " +
|
|
3065
|
-
"an ad hoc critique; it only changes which provider produces this " +
|
|
3066
|
-
"request's artifact. Takes precedence over `provider` when both are set."),
|
|
3067
|
-
provider: z.string().optional().describe("Pure provider switch — use a specific LLM provider (openai, anthropic, gemini) without " +
|
|
3068
|
-
"triggering second-opinion semantics. If both provider and second_opinion are set, " +
|
|
3069
|
-
"second_opinion takes precedence."),
|
|
2381
|
+
ticket_number: commonFields.ticket_number,
|
|
2382
|
+
wait_for_result: commonFields.wait_for_result,
|
|
2383
|
+
save_locally: commonFields.save_locally,
|
|
2384
|
+
second_opinion: commonFields.second_opinion,
|
|
2385
|
+
provider: commonFields.provider,
|
|
3070
2386
|
},
|
|
3071
2387
|
}, async (args) => {
|
|
3072
2388
|
return requestTicketArtifact("reimplement_context", args);
|
|
@@ -3084,14 +2400,8 @@ registerTool("get_reimplement_context", {
|
|
|
3084
2400
|
"the original ticket description, and the existing implementation plan. " +
|
|
3085
2401
|
"Returns a 404 / not-found response when processing is not yet complete — that means processing has not finished, not that this tool failed.",
|
|
3086
2402
|
inputSchema: {
|
|
3087
|
-
ticket_number:
|
|
3088
|
-
|
|
3089
|
-
.describe("Jira ticket key in PROJECT-NUMBER format (e.g. PROJ-123)"),
|
|
3090
|
-
save_locally: z
|
|
3091
|
-
.boolean()
|
|
3092
|
-
.optional()
|
|
3093
|
-
.default(true)
|
|
3094
|
-
.describe("Save the context to BAPI_DOCS_DIR/reimplementations/{ticket_number}-context.md. Defaults to true."),
|
|
2403
|
+
ticket_number: commonFields.ticket_number,
|
|
2404
|
+
save_locally: commonFields.save_locally,
|
|
3095
2405
|
},
|
|
3096
2406
|
}, async (args) => {
|
|
3097
2407
|
return getTicketArtifact("reimplement_context", args);
|
|
@@ -3113,7 +2423,7 @@ registerTool("track_ticket", {
|
|
|
3113
2423
|
"For Jira mutations use a different tool instead: `update_ticket_description` to replace the Jira description, `add_comment` to post a Jira comment, and `update_jira_status` to move the Jira workflow status. " +
|
|
3114
2424
|
"The repo_name is automatically injected from the configured environment.",
|
|
3115
2425
|
inputSchema: {
|
|
3116
|
-
ticket_number:
|
|
2426
|
+
ticket_number: commonFields.ticket_number,
|
|
3117
2427
|
description: z.string().optional().describe("Ticket description text. Optional — used to store a local copy of the description for reference."),
|
|
3118
2428
|
},
|
|
3119
2429
|
}, async ({ ticket_number, description }) => {
|
|
@@ -3141,7 +2451,7 @@ registerTool("update_ticket_state", {
|
|
|
3141
2451
|
"Returns 400 if any field name is invalid. " +
|
|
3142
2452
|
"The repo_name is automatically injected from the configured environment.",
|
|
3143
2453
|
inputSchema: {
|
|
3144
|
-
ticket_number:
|
|
2454
|
+
ticket_number: commonFields.ticket_number,
|
|
3145
2455
|
fields: z.array(z.string()).describe("List of state field names to set to the current UTC timestamp. Valid values: 'critique_called', 'critique_answered', 'clarify_called', 'clarify_answered', 'plan_generated', 'implemented', 'reimplement_called'"),
|
|
3146
2456
|
},
|
|
3147
2457
|
}, async ({ ticket_number, fields }) => {
|
|
@@ -3167,7 +2477,7 @@ registerTool("get_ticket_state", {
|
|
|
3167
2477
|
"The ticket must be tracked via track_ticket first, or a 404 is returned. " +
|
|
3168
2478
|
"The repo_name is automatically injected from the configured environment.",
|
|
3169
2479
|
inputSchema: {
|
|
3170
|
-
ticket_number:
|
|
2480
|
+
ticket_number: commonFields.ticket_number,
|
|
3171
2481
|
},
|
|
3172
2482
|
}, async ({ ticket_number }) => {
|
|
3173
2483
|
const url = buildGetUrl(`/ticket/${encodeURIComponent(ticket_number)}/state`, { repo_name: REPO_NAME });
|
|
@@ -3189,7 +2499,7 @@ registerTool("get_jira_transitions", {
|
|
|
3189
2499
|
"Use this to discover what status changes are possible for a given ticket. " +
|
|
3190
2500
|
"The repo_name is automatically injected from the configured environment.",
|
|
3191
2501
|
inputSchema: {
|
|
3192
|
-
ticket_number:
|
|
2502
|
+
ticket_number: commonFields.ticket_number,
|
|
3193
2503
|
},
|
|
3194
2504
|
}, async ({ ticket_number }) => {
|
|
3195
2505
|
const url = buildGetUrl(`/tickets/${encodeURIComponent(ticket_number)}/jira-transitions`, { repo_name: REPO_NAME });
|
|
@@ -3212,7 +2522,7 @@ registerTool("update_jira_status", {
|
|
|
3212
2522
|
"Returns the from/to status on success, or an error listing available transitions if no match is found. " +
|
|
3213
2523
|
"The repo_name is automatically injected from the configured environment.",
|
|
3214
2524
|
inputSchema: {
|
|
3215
|
-
ticket_number:
|
|
2525
|
+
ticket_number: commonFields.ticket_number,
|
|
3216
2526
|
target_status: z.string().optional().describe('Target status name to transition to (case-insensitive match). Pass "auto" to resolve the target status server-side via LLM agent.'),
|
|
3217
2527
|
transition_id: z.string().optional().describe("Specific transition ID to execute (takes precedence over target_status)"),
|
|
3218
2528
|
},
|
|
@@ -3245,7 +2555,7 @@ registerTool("resolve_target_status", {
|
|
|
3245
2555
|
"Requires a ticket_number to fetch available transitions from Jira. " +
|
|
3246
2556
|
"The repo_name is automatically injected from the configured environment.",
|
|
3247
2557
|
inputSchema: {
|
|
3248
|
-
ticket_number:
|
|
2558
|
+
ticket_number: commonFields.ticket_number,
|
|
3249
2559
|
force_rerun: z.boolean().optional().describe("Set to true to bypass the cache and re-resolve the target status via LLM"),
|
|
3250
2560
|
},
|
|
3251
2561
|
}, async ({ ticket_number, force_rerun }) => {
|
|
@@ -3534,7 +2844,7 @@ registerTool("get_install_manifest", {
|
|
|
3534
2844
|
"snapshot_token. Pass that exact snapshot_token to apply_install_manifest. " +
|
|
3535
2845
|
"Prefer this over many individual get_config_field reads during install bootstrap.",
|
|
3536
2846
|
inputSchema: {
|
|
3537
|
-
save_locally:
|
|
2847
|
+
save_locally: commonFields.save_locally,
|
|
3538
2848
|
},
|
|
3539
2849
|
}, async ({ save_locally }) => {
|
|
3540
2850
|
const url = buildGetUrl("/config/install-manifest", { repo_name: REPO_NAME });
|
|
@@ -3733,24 +3043,9 @@ registerTool("request_deep_research", {
|
|
|
3733
3043
|
idempotentHint: false,
|
|
3734
3044
|
openWorldHint: true,
|
|
3735
3045
|
},
|
|
3736
|
-
description: "
|
|
3737
|
-
"Returns a task_id
|
|
3738
|
-
"
|
|
3739
|
-
"WHEN TO USE: Use this tool when you need to investigate unfamiliar technologies, " +
|
|
3740
|
-
"gather implementation guidance across multiple sources, research best practices for a complex topic, " +
|
|
3741
|
-
"or when the depth of information needed exceeds what a single web search can provide. " +
|
|
3742
|
-
"Example: 'What are the best practices for implementing WebSocket connection pooling in Python asyncio, " +
|
|
3743
|
-
"including error handling, reconnection strategies, and load balancing across multiple servers?' " +
|
|
3744
|
-
"\n\n" +
|
|
3745
|
-
"WHEN NOT TO USE: Do NOT use this for quick factual lookups, single-question searches, " +
|
|
3746
|
-
"or anything a standard web search could answer in seconds. " +
|
|
3747
|
-
"Bad example: 'websocket python' — use a web search instead. " +
|
|
3748
|
-
"\n\n" +
|
|
3749
|
-
"BEHAVIOR: By default, returns immediately with a task_id. Use get_deep_research to retrieve the result " +
|
|
3750
|
-
"once processing completes (typically 2-10 minutes). " +
|
|
3751
|
-
"Set wait_for_result to true to block until the report is ready (up to 15 minutes). " +
|
|
3752
|
-
"On failure, do not retry automatically — the underlying issue (provider outage, rate limit) " +
|
|
3753
|
-
"is unlikely to resolve immediately. Fall back to standard web searches to gather the information incrementally.",
|
|
3046
|
+
description: "Use to start async deep research on a technical topic using AI-powered web search. " +
|
|
3047
|
+
"Returns a task_id immediately (or the full report if wait_for_result is true). " +
|
|
3048
|
+
"Use get_deep_research to retrieve. Generates and persists a retrievable artifact.",
|
|
3754
3049
|
inputSchema: {
|
|
3755
3050
|
query: z.string().describe("The research query. Be specific and detailed about what you need to learn. " +
|
|
3756
3051
|
"Good: 'What are the tradeoffs between Redis, Memcached, and DynamoDB DAX for caching in a Python FastAPI " +
|
|
@@ -3759,13 +3054,9 @@ registerTool("request_deep_research", {
|
|
|
3759
3054
|
context: z.string().optional().describe("Optional context to focus the research scope. Describe your current task, tech stack, and constraints. " +
|
|
3760
3055
|
"Example: 'I am building a FastAPI application that uses PostgreSQL and needs to implement real-time " +
|
|
3761
3056
|
"notifications. Focus on Python-specific solutions compatible with async frameworks.'"),
|
|
3762
|
-
ticket_number:
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
"then returns the full report directly. When false (default), returns immediately " +
|
|
3766
|
-
"with a task_id — use get_deep_research later to retrieve results."),
|
|
3767
|
-
save_locally: z.boolean().optional().default(true).describe("Whether to save the research report to the BAPI_DOCS_DIR/deep-research/ directory. " +
|
|
3768
|
-
"Defaults to true. When wait_for_result is false, saving happens when you call get_deep_research instead."),
|
|
3057
|
+
ticket_number: commonFields.ticket_number.optional(),
|
|
3058
|
+
wait_for_result: commonFields.wait_for_result,
|
|
3059
|
+
save_locally: commonFields.save_locally,
|
|
3769
3060
|
},
|
|
3770
3061
|
}, async ({ query, context, ticket_number, wait_for_result, save_locally }) => {
|
|
3771
3062
|
// 1. Submit the deep research request
|
|
@@ -3869,7 +3160,7 @@ registerTool("get_deep_research", {
|
|
|
3869
3160
|
task_id: z.number().describe("The task ID returned by request_deep_research."),
|
|
3870
3161
|
query_slug: z.string().optional().describe("Optional slug derived from the original query, used for the saved filename. " +
|
|
3871
3162
|
"If omitted, the file is saved as 'research-{task_id}.md'."),
|
|
3872
|
-
save_locally:
|
|
3163
|
+
save_locally: commonFields.save_locally,
|
|
3873
3164
|
},
|
|
3874
3165
|
}, async ({ task_id, query_slug, save_locally }) => {
|
|
3875
3166
|
// 1. Check status
|
|
@@ -3988,39 +3279,20 @@ registerTool("request_brainstorm", {
|
|
|
3988
3279
|
idempotentHint: false,
|
|
3989
3280
|
openWorldHint: true,
|
|
3990
3281
|
},
|
|
3991
|
-
description: "
|
|
3992
|
-
"
|
|
3993
|
-
"
|
|
3994
|
-
"\n\n" +
|
|
3995
|
-
"BEHAVIOR: By default, returns immediately with a brainstorm_id. Set wait_for_result=true " +
|
|
3996
|
-
"to poll for terminal status (up to 15 minutes), then retrieve and optionally save the result. " +
|
|
3997
|
-
"When save_locally=true (default), each provider's markdown is written with a semantic " +
|
|
3998
|
-
"filename derived from task_description: " +
|
|
3999
|
-
"BAPI_DOCS_DIR/brainstorm/{slugified-task-description}-{short_brainstorm_id}-{provider}.md. " +
|
|
4000
|
-
"(If task_description is empty or slugifies to nothing, it falls back to {brainstorm_id}-{provider}.md.) " +
|
|
4001
|
-
"\n\n" +
|
|
4002
|
-
"MODES: Set mode to pick the brainstorm style. 'technical' (default) runs the implementation/" +
|
|
4003
|
-
"architecture brainstorm. 'design' runs web-page/UI design ideation focused on visual appeal and " +
|
|
4004
|
-
"conversion. 'discovery' generates stakeholder discovery questions for early/vague tasks, grouped " +
|
|
4005
|
-
"into 'Technical Discovery Questions' and 'Business / Stakeholder Discovery Questions' and tagged " +
|
|
4006
|
-
"[HUMAN], [CODE], or [TICKET]; discovery needs no extra configuration. " +
|
|
4007
|
-
"The legacy boolean design=true still works and maps to mode='design'; prefer the explicit mode " +
|
|
4008
|
-
"selector for new callers.",
|
|
3282
|
+
description: "Use to start an async brainstorm that fans out a task to opinion-provider LLMs. " +
|
|
3283
|
+
"Returns a brainstorm_id immediately (or the full result envelope if wait_for_result is true). " +
|
|
3284
|
+
"Use get_brainstorm to retrieve. Generates and persists a retrievable artifact.",
|
|
4009
3285
|
inputSchema: {
|
|
4010
3286
|
task_description: z.string().describe("Free-form description of the task to brainstorm about. Sent verbatim — " +
|
|
4011
3287
|
"this tool does NOT read task_description from a file."),
|
|
4012
|
-
repo_name:
|
|
4013
|
-
ticket_number:
|
|
4014
|
-
"Ticket 1 only stores this for cross-reference — no Jira writes happen."),
|
|
3288
|
+
repo_name: commonFields.repo_name,
|
|
3289
|
+
ticket_number: commonFields.ticket_number.optional(),
|
|
4015
3290
|
providers: z.array(z.string()).optional().describe("Opinion-provider LLMs. Defaults to ['openai', 'gemini']. " +
|
|
4016
3291
|
"A single-provider request runs one opinion provider and returns that " +
|
|
4017
3292
|
"provider's markdown directly."),
|
|
4018
3293
|
concerns: z.string().optional().describe("Optional caller-supplied concerns to surface to the brainstorm agents."),
|
|
4019
|
-
wait_for_result:
|
|
4020
|
-
|
|
4021
|
-
save_locally: z.boolean().optional().describe("When true (default), writes each provider's markdown to BAPI_DOCS_DIR/brainstorm/ " +
|
|
4022
|
-
"after the result is fetched. Request-time saves use semantic filenames derived from " +
|
|
4023
|
-
"task_description (slugified) plus a short brainstorm-id segment."),
|
|
3294
|
+
wait_for_result: commonFields.wait_for_result,
|
|
3295
|
+
save_locally: commonFields.save_locally,
|
|
4024
3296
|
prior_brainstorm_id: z.string().optional().describe("Optional brainstorm_id from an earlier brainstorm to refine. " +
|
|
4025
3297
|
"When provided, the prior brainstorm's completed opinion-provider " +
|
|
4026
3298
|
"markdowns are concatenated and supplied as prior context."),
|
|
@@ -4115,21 +3387,14 @@ registerTool("get_brainstorm", {
|
|
|
4115
3387
|
idempotentHint: true,
|
|
4116
3388
|
openWorldHint: true,
|
|
4117
3389
|
},
|
|
4118
|
-
description: "
|
|
4119
|
-
"
|
|
4120
|
-
"
|
|
4121
|
-
"
|
|
4122
|
-
"BAPI_DOCS_DIR/brainstorm/{brainstorm_id}-{provider}.md. " +
|
|
4123
|
-
"Retrieval uses this UUID-only filename (not the semantic task-description name that " +
|
|
4124
|
-
"request_brainstorm uses) because the original task description is not available in the " +
|
|
4125
|
-
"result envelope on retrieval. " +
|
|
4126
|
-
"DB-only retrieval — never falls back to Jira attachments.",
|
|
3390
|
+
description: "Use to retrieve the result envelope for a previously submitted brainstorm by brainstorm_id. " +
|
|
3391
|
+
"Returns opinion-provider rows only (including error_kind for each row). " +
|
|
3392
|
+
"Does NOT start a new brainstorm — use request_brainstorm first if none exists. " +
|
|
3393
|
+
"Returns not-found when still processing.",
|
|
4127
3394
|
inputSchema: {
|
|
4128
3395
|
brainstorm_id: z.string().describe("The brainstorm_id (UUID) returned by request_brainstorm."),
|
|
4129
|
-
repo_name:
|
|
4130
|
-
save_locally:
|
|
4131
|
-
"Retrieval-time saves intentionally use the UUID-only fallback filename " +
|
|
4132
|
-
"({brainstorm_id}-{provider}.md), not a task-description-derived semantic name."),
|
|
3396
|
+
repo_name: commonFields.repo_name,
|
|
3397
|
+
save_locally: commonFields.save_locally,
|
|
4133
3398
|
},
|
|
4134
3399
|
}, async ({ brainstorm_id, repo_name, save_locally }) => {
|
|
4135
3400
|
const effectiveRepo = repo_name && repo_name.length > 0 ? repo_name : REPO_NAME;
|
|
@@ -4269,7 +3534,7 @@ const pollCiChecksTool = registerTool("poll_ci_checks", {
|
|
|
4269
3534
|
!("error" in parsed) &&
|
|
4270
3535
|
(Array.isArray(parsed.checks) || typeof parsed.all_complete === "boolean");
|
|
4271
3536
|
if (looksLikePollStatus) {
|
|
4272
|
-
void observePrCiFromPollResponse(commit_ref, parsed).catch(() => { });
|
|
3537
|
+
void observePrCiFromPollResponse(commit_ref, parsed, { resolveRunId: resolveDispatchRunIdForBinding }).catch(() => { });
|
|
4273
3538
|
}
|
|
4274
3539
|
}
|
|
4275
3540
|
catch {
|
|
@@ -4334,28 +3599,54 @@ registerTool("get_docs_dir", {
|
|
|
4334
3599
|
}, async () => {
|
|
4335
3600
|
return { content: [{ type: "text", text: await getDocsDir() }] };
|
|
4336
3601
|
});
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
3602
|
+
// ---------------------------------------------------------------------------
|
|
3603
|
+
// Pipeline Execution Tools (BAPI-275)
|
|
3604
|
+
// ---------------------------------------------------------------------------
|
|
3605
|
+
//
|
|
3606
|
+
// run_pipeline / resume_pipeline / list_pipeline_runs promote pipelines from
|
|
3607
|
+
// declarative JSON recipes into first-class callable operations. They share
|
|
3608
|
+
// a unified response envelope (status: completed | needs_agent_task | failed)
|
|
3609
|
+
// with a strict error_code enum (VALIDATION | NOT_FOUND | EXPIRED |
|
|
3610
|
+
// REPO_MISMATCH | TOOL_ERROR). Pipeline state is persisted server-side; the
|
|
3611
|
+
// idle TTL defaults to 24 hours and is auto-extended on every state
|
|
3612
|
+
// transition.
|
|
3613
|
+
async function buildPipelineOrchestratorDeps() {
|
|
3614
|
+
await ensureCustomPipelinesLoaded();
|
|
3615
|
+
return {
|
|
3616
|
+
baseUrl: BASE_URL,
|
|
3617
|
+
apiKey: await getResolvedApiKey(),
|
|
3618
|
+
repoName: REPO_NAME,
|
|
3619
|
+
docsDir: await getDocsDir(),
|
|
3620
|
+
pipelines: PIPELINES,
|
|
3621
|
+
instructions: INSTRUCTIONS,
|
|
3622
|
+
toolHandlers: TOOL_HANDLERS,
|
|
3623
|
+
includeUpgradeAdviceSurfacing: UPGRADE_ADVICE_SURFACING_ENABLED,
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3626
|
+
// BAPI-326: dependency injection for the full-automation chain orchestrator.
|
|
3627
|
+
// Same shape as the pipeline deps plus the bundled chain-recipe registry. The
|
|
3628
|
+
// chain orchestrator never imports index.ts; everything flows through here.
|
|
3629
|
+
async function buildChainOrchestratorDeps() {
|
|
4348
3630
|
await ensureCustomPipelinesLoaded();
|
|
4349
|
-
const list = Object.entries(PIPELINES).map(([key, pipeline]) => ({
|
|
4350
|
-
name: key,
|
|
4351
|
-
description: pipeline.description ?? "",
|
|
4352
|
-
variables: (pipeline.variables ?? []).filter((v) => v !== "docs_dir" && v !== "idea_hash"),
|
|
4353
|
-
source: userPipelineKeys.has(key) ? "user" : "bundled",
|
|
4354
|
-
}));
|
|
4355
3631
|
return {
|
|
4356
|
-
|
|
3632
|
+
baseUrl: BASE_URL,
|
|
3633
|
+
apiKey: await getResolvedApiKey(),
|
|
3634
|
+
repoName: REPO_NAME,
|
|
3635
|
+
docsDir: await getDocsDir(),
|
|
3636
|
+
pipelines: PIPELINES,
|
|
3637
|
+
chainRecipes: CHAIN_RECIPES,
|
|
3638
|
+
instructions: INSTRUCTIONS,
|
|
3639
|
+
toolHandlers: TOOL_HANDLERS,
|
|
3640
|
+
includeUpgradeAdviceSurfacing: UPGRADE_ADVICE_SURFACING_ENABLED,
|
|
4357
3641
|
};
|
|
4358
|
-
}
|
|
3642
|
+
}
|
|
3643
|
+
// get_pipeline_recipe is the entry point for every recipe-driven slash command
|
|
3644
|
+
// (/review-ticket, /implement-ticket, /idea-to-ticket, /plan-epic,
|
|
3645
|
+
// /learn-repository): each calls it as its first/only direct MCP call and then
|
|
3646
|
+
// follows the returned agent_instructions (which use non-gated core tools). It
|
|
3647
|
+
// must therefore be available in the default "core" profile, so it is registered
|
|
3648
|
+
// unconditionally here — OUTSIDE the pipeline-authoring gate below. The remaining
|
|
3649
|
+
// 5 pipeline-authoring/admin tools stay gated (BAPI-434 Phase 2a token savings).
|
|
4359
3650
|
registerTool("get_pipeline_recipe", {
|
|
4360
3651
|
annotations: {
|
|
4361
3652
|
readOnlyHint: true,
|
|
@@ -4451,179 +3742,167 @@ registerTool("get_pipeline_recipe", {
|
|
|
4451
3742
|
};
|
|
4452
3743
|
}
|
|
4453
3744
|
});
|
|
4454
|
-
//
|
|
4455
|
-
//
|
|
4456
|
-
//
|
|
4457
|
-
//
|
|
4458
|
-
//
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
}
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
},
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
},
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
pipeline_run_id: z
|
|
4616
|
-
.string()
|
|
4617
|
-
.describe("UUID of the pipeline run to delete."),
|
|
4618
|
-
},
|
|
4619
|
-
}, async (input) => {
|
|
4620
|
-
const result = await deletePipelineRun(await buildPipelineOrchestratorDeps(), input);
|
|
4621
|
-
return {
|
|
4622
|
-
content: [
|
|
4623
|
-
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
4624
|
-
],
|
|
4625
|
-
};
|
|
4626
|
-
});
|
|
3745
|
+
// BAPI-434 Phase 2a: the remaining pipeline-authoring/admin tools are excluded
|
|
3746
|
+
// from the default "core" profile. These 5 tools are registered only when the
|
|
3747
|
+
// active profile is "pipeline-authoring" or "full". (get_pipeline_recipe is
|
|
3748
|
+
// intentionally registered above, in core, because the recipe-driven slash
|
|
3749
|
+
// commands depend on it.)
|
|
3750
|
+
if (profileIncludes(BRIDGE_MCP_PROFILE, "pipeline-authoring")) {
|
|
3751
|
+
registerTool("list_pipelines", {
|
|
3752
|
+
annotations: {
|
|
3753
|
+
readOnlyHint: true,
|
|
3754
|
+
destructiveHint: false,
|
|
3755
|
+
idempotentHint: true,
|
|
3756
|
+
openWorldHint: false,
|
|
3757
|
+
},
|
|
3758
|
+
description: "List all available pipeline recipes with their names, descriptions, and required variables. " +
|
|
3759
|
+
"No parameters. Use this to discover available pipelines before calling get_pipeline_recipe.",
|
|
3760
|
+
inputSchema: {},
|
|
3761
|
+
}, async () => {
|
|
3762
|
+
await ensureCustomPipelinesLoaded();
|
|
3763
|
+
const list = Object.entries(PIPELINES).map(([key, pipeline]) => ({
|
|
3764
|
+
name: key,
|
|
3765
|
+
description: pipeline.description ?? "",
|
|
3766
|
+
variables: (pipeline.variables ?? []).filter((v) => v !== "docs_dir" && v !== "idea_hash"),
|
|
3767
|
+
source: userPipelineKeys.has(key) ? "user" : "bundled",
|
|
3768
|
+
}));
|
|
3769
|
+
return {
|
|
3770
|
+
content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
|
|
3771
|
+
};
|
|
3772
|
+
});
|
|
3773
|
+
registerTool("run_pipeline", {
|
|
3774
|
+
annotations: {
|
|
3775
|
+
readOnlyHint: false,
|
|
3776
|
+
destructiveHint: false,
|
|
3777
|
+
idempotentHint: false,
|
|
3778
|
+
openWorldHint: true,
|
|
3779
|
+
},
|
|
3780
|
+
description: "Execute a Bridge API pipeline by name. The orchestrator runs steps sequentially, " +
|
|
3781
|
+
"dispatching mcp_call steps in-process and pausing on agent_task steps with a " +
|
|
3782
|
+
"needs_agent_task envelope. Returns a unified envelope keyed on `status`: " +
|
|
3783
|
+
"`completed` (terminal success with `results`), `needs_agent_task` (pause — read " +
|
|
3784
|
+
"`instruction`, perform the task, then call `resume_pipeline` with the resulting " +
|
|
3785
|
+
"string as `agent_result`), or `failed` (terminal error — check `error_code`: " +
|
|
3786
|
+
"VALIDATION | NOT_FOUND | EXPIRED | REPO_MISMATCH | TOOL_ERROR). " +
|
|
3787
|
+
"Paused runs auto-expire after an idle TTL (default 24 hours; override with " +
|
|
3788
|
+
"`ttl_seconds`). The TTL is reset on every state transition.\n\n" +
|
|
3789
|
+
"Call list_pipelines for the current resolved catalog of available pipeline " +
|
|
3790
|
+
"names (bundled plus any custom user pipelines).",
|
|
3791
|
+
inputSchema: {
|
|
3792
|
+
pipeline: z
|
|
3793
|
+
.string()
|
|
3794
|
+
.describe("Pipeline name (e.g. 'review-ticket', 'implement-ticket')"),
|
|
3795
|
+
variables: z
|
|
3796
|
+
.record(z.string(), z.string())
|
|
3797
|
+
.optional()
|
|
3798
|
+
.describe("Key-value pairs for variable substitution (e.g. { ticket_key: 'BAPI-123' }). " +
|
|
3799
|
+
"Do NOT pass `auto_approve` here — use the top-level parameter."),
|
|
3800
|
+
auto_approve: z
|
|
3801
|
+
.union([z.boolean(), z.literal("true"), z.literal("false")])
|
|
3802
|
+
.optional()
|
|
3803
|
+
.describe("When true, approval-gated mcp_call steps execute directly. When false or " +
|
|
3804
|
+
"omitted, the orchestrator synthesises a needs_agent_task pause so the agent " +
|
|
3805
|
+
"can confirm with the user before resuming. Accepts boolean or 'true'/'false' " +
|
|
3806
|
+
"strings for MCP clients that serialize booleans as strings."),
|
|
3807
|
+
ttl_seconds: z
|
|
3808
|
+
.number()
|
|
3809
|
+
.int()
|
|
3810
|
+
.positive()
|
|
3811
|
+
.optional()
|
|
3812
|
+
.describe("Override the default 24-hour idle TTL for this run. Must be a positive integer."),
|
|
3813
|
+
},
|
|
3814
|
+
}, async (input) => {
|
|
3815
|
+
const result = await runPipeline(await buildPipelineOrchestratorDeps(), input);
|
|
3816
|
+
return {
|
|
3817
|
+
content: [
|
|
3818
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
3819
|
+
],
|
|
3820
|
+
};
|
|
3821
|
+
});
|
|
3822
|
+
registerTool("resume_pipeline", {
|
|
3823
|
+
annotations: {
|
|
3824
|
+
readOnlyHint: false,
|
|
3825
|
+
destructiveHint: false,
|
|
3826
|
+
idempotentHint: false,
|
|
3827
|
+
openWorldHint: true,
|
|
3828
|
+
},
|
|
3829
|
+
description: "Resume a paused pipeline run with the result of the agent_task. Provide the " +
|
|
3830
|
+
"`pipeline_run_id` returned by the prior needs_agent_task envelope, and the string " +
|
|
3831
|
+
"the instruction's `## Return` section asked you to produce as `agent_result`. " +
|
|
3832
|
+
"`agent_result` is always a string — do not wrap it in JSON unless the instruction " +
|
|
3833
|
+
"explicitly asked you to serialize structured output. Returns the same unified " +
|
|
3834
|
+
"envelope shape as `run_pipeline`.",
|
|
3835
|
+
inputSchema: {
|
|
3836
|
+
pipeline_run_id: z
|
|
3837
|
+
.string()
|
|
3838
|
+
.describe("The pipeline_run_id returned by a prior needs_agent_task envelope"),
|
|
3839
|
+
agent_result: z
|
|
3840
|
+
.string()
|
|
3841
|
+
.describe("The string the paused instruction's ## Return section asked you to produce"),
|
|
3842
|
+
},
|
|
3843
|
+
}, async (input) => {
|
|
3844
|
+
const result = await resumePipeline(await buildPipelineOrchestratorDeps(), input);
|
|
3845
|
+
return {
|
|
3846
|
+
content: [
|
|
3847
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
3848
|
+
],
|
|
3849
|
+
};
|
|
3850
|
+
});
|
|
3851
|
+
registerTool("list_pipeline_runs", {
|
|
3852
|
+
annotations: {
|
|
3853
|
+
readOnlyHint: true,
|
|
3854
|
+
destructiveHint: false,
|
|
3855
|
+
idempotentHint: true,
|
|
3856
|
+
openWorldHint: true,
|
|
3857
|
+
},
|
|
3858
|
+
description: "List recent pipeline runs for the configured repository, newest first. Returns " +
|
|
3859
|
+
"metadata only — `resolved_recipe`, resolved params, instruction text, results, " +
|
|
3860
|
+
"and agent outputs are intentionally excluded. Use this to recover a " +
|
|
3861
|
+
"`pipeline_run_id` when an earlier needs_agent_task envelope is no longer in " +
|
|
3862
|
+
"scope (e.g. after compaction or a client restart). " +
|
|
3863
|
+
"Optionally filter by `status`: running | paused | completed | failed | expired.",
|
|
3864
|
+
inputSchema: {
|
|
3865
|
+
status: z
|
|
3866
|
+
.enum(["running", "paused", "completed", "failed", "expired"])
|
|
3867
|
+
.optional()
|
|
3868
|
+
.describe("Optional status filter"),
|
|
3869
|
+
},
|
|
3870
|
+
}, async (input) => {
|
|
3871
|
+
const result = await listPipelineRuns(await buildPipelineOrchestratorDeps(), input);
|
|
3872
|
+
return {
|
|
3873
|
+
content: [
|
|
3874
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
3875
|
+
],
|
|
3876
|
+
};
|
|
3877
|
+
});
|
|
3878
|
+
registerTool("delete_pipeline_run", {
|
|
3879
|
+
annotations: {
|
|
3880
|
+
readOnlyHint: false,
|
|
3881
|
+
destructiveHint: true,
|
|
3882
|
+
idempotentHint: true,
|
|
3883
|
+
openWorldHint: true,
|
|
3884
|
+
},
|
|
3885
|
+
description: "Delete a pipeline run row (any status). Use this to discard orphaned `running` " +
|
|
3886
|
+
"rows from a previous session that can't be resumed (resume_pipeline only accepts " +
|
|
3887
|
+
"`paused`), to clean up after a failed run, or to remove a no-longer-needed paused " +
|
|
3888
|
+
"session. Returns `{ status: 'completed', deleted: true, pipeline_run_id }` on " +
|
|
3889
|
+
"success, or a `failed` envelope with error_code in (VALIDATION | NOT_FOUND | " +
|
|
3890
|
+
"REPO_MISMATCH | TOOL_ERROR). Repo-scoped: the row's stored repo_name must match " +
|
|
3891
|
+
"the caller's repo.",
|
|
3892
|
+
inputSchema: {
|
|
3893
|
+
pipeline_run_id: z
|
|
3894
|
+
.string()
|
|
3895
|
+
.describe("UUID of the pipeline run to delete."),
|
|
3896
|
+
},
|
|
3897
|
+
}, async (input) => {
|
|
3898
|
+
const result = await deletePipelineRun(await buildPipelineOrchestratorDeps(), input);
|
|
3899
|
+
return {
|
|
3900
|
+
content: [
|
|
3901
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
3902
|
+
],
|
|
3903
|
+
};
|
|
3904
|
+
});
|
|
3905
|
+
} // end if (profileIncludes(BRIDGE_MCP_PROFILE, "pipeline-authoring"))
|
|
4627
3906
|
// ---------------------------------------------------------------------------
|
|
4628
3907
|
// Full Automation Chain Tools (BAPI-326)
|
|
4629
3908
|
// ---------------------------------------------------------------------------
|
|
@@ -4830,17 +4109,8 @@ registerTool("generate_decision_page", {
|
|
|
4830
4109
|
idempotentHint: true,
|
|
4831
4110
|
openWorldHint: true,
|
|
4832
4111
|
},
|
|
4833
|
-
description: "
|
|
4834
|
-
"
|
|
4835
|
-
"original-question and closed-by-default codebase-evidence display, and an optional confirmed-" +
|
|
4836
|
-
"improvements list. Presentation labels (title, intro, section/improvements headings) are " +
|
|
4837
|
-
"overridable, and the output location under the docs directory is configurable, so automations " +
|
|
4838
|
-
"beyond ticket review can reuse it. Pass artifact_type=\"pre_ticket_planning\" to additionally " +
|
|
4839
|
-
"render read-only system_goals (business goal, desired end-state, system behavior, classified " +
|
|
4840
|
-
"NFRs) and a read-only implementation_order section for pre-ticket epic/task framing; open NFRs " +
|
|
4841
|
-
"still go in actionable_items so the human can decide them. The default artifact_type " +
|
|
4842
|
-
"\"review_decisions\" is unchanged. The user opens the HTML file in a browser, makes selections, " +
|
|
4843
|
-
"and copies the resulting JSON output back to the agent.",
|
|
4112
|
+
description: "Use to generate a local, review-shaped HTML decision page for capturing user decisions. " +
|
|
4113
|
+
"Returns the local file path and a summary of the rendered items.",
|
|
4844
4114
|
inputSchema: DecisionPageInputShape,
|
|
4845
4115
|
}, async (input) => {
|
|
4846
4116
|
// Returned messages travel through JSON.stringify in the MCP envelope below,
|
|
@@ -4972,7 +4242,7 @@ await server.connect(transport);
|
|
|
4972
4242
|
// Record that the server has connected so project-root resolution may now query
|
|
4973
4243
|
// MCP `roots/list` (it falls through to CLAUDE_PROJECT_DIR / cwd before this).
|
|
4974
4244
|
serverConnected = true;
|
|
4975
|
-
console.error(
|
|
4245
|
+
console.error(`Bridge API MCP server ${VERSION} running on stdio`);
|
|
4976
4246
|
// Fire-and-forget update check — delay to let MCP client attach listeners
|
|
4977
4247
|
(async () => {
|
|
4978
4248
|
await new Promise((r) => setTimeout(r, 2000));
|
|
@@ -4981,7 +4251,7 @@ console.error("Bridge API MCP server running on stdio");
|
|
|
4981
4251
|
server.server.sendLoggingMessage({
|
|
4982
4252
|
level: "notice",
|
|
4983
4253
|
logger: "bridge-api",
|
|
4984
|
-
data: `Update available:
|
|
4254
|
+
data: `Update available: running ${result.currentVersion}, latest ${result.latestVersion} — run npx -y @bridge_gpt/mcp-server@latest --upgrade`,
|
|
4985
4255
|
});
|
|
4986
4256
|
}
|
|
4987
4257
|
})().catch(() => { });
|