@bridge_gpt/mcp-server 0.1.16 → 0.2.0
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 +333 -162
- package/build/agent-capabilities/cli.js +152 -0
- package/build/agent-capabilities/default-deps.js +45 -0
- package/build/agent-capabilities/probe-context.js +111 -0
- package/build/agent-capabilities/probes.js +278 -0
- package/build/agent-capabilities/reporter.js +50 -0
- package/build/agent-capabilities/runner.js +56 -0
- package/build/agent-capabilities/types.js +10 -0
- package/build/agent-launchers/claude.js +85 -0
- package/build/agent-launchers/index.js +17 -0
- package/build/agent-launchers/types.js +1 -0
- package/build/agents.generated.js +1 -1
- package/build/brainstorm-files.js +89 -0
- package/build/bridge-config.js +404 -0
- package/build/chain-orchestrator.js +1364 -0
- package/build/chain-utils.js +68 -0
- package/build/commands.generated.js +5 -3
- package/build/credential-materialization.js +128 -0
- package/build/credential-store.js +232 -0
- package/build/decision-page-schema.js +39 -6
- package/build/decision-page-template.js +54 -18
- package/build/doctor.js +18 -2
- package/build/fetch-stub.js +139 -0
- package/build/git-ignore-utils.js +63 -0
- package/build/index.js +1623 -546
- package/build/mcp-invoke.js +417 -0
- package/build/mcp-provisioning.js +249 -0
- package/build/mcp-registration-doctor.js +96 -0
- package/build/pipeline-orchestrator.js +66 -1
- package/build/pipeline-utils.js +33 -0
- package/build/pipelines.generated.js +165 -5
- package/build/schedule-run.js +951 -0
- package/build/schedule-store.js +132 -0
- package/build/scheduler-backends/at-fallback.js +144 -0
- package/build/scheduler-backends/escaping.js +113 -0
- package/build/scheduler-backends/index.js +72 -0
- package/build/scheduler-backends/launchd.js +216 -0
- package/build/scheduler-backends/systemd-user.js +237 -0
- package/build/scheduler-backends/task-scheduler.js +219 -0
- package/build/scheduler-backends/types.js +23 -0
- package/build/start-tickets-prereqs.js +90 -1
- package/build/start-tickets.js +222 -70
- package/build/third-party-mcp-targets.js +75 -0
- package/build/version.generated.js +1 -1
- package/package.json +8 -8
- package/pipelines/full-automation.json +49 -0
- package/pipelines/idea-to-ticket.json +71 -0
- package/pipelines/implement-ticket.json +28 -2
- package/smoke-test/SMOKE-TEST.md +511 -0
- package/smoke-test/smoke-test-mcp.md +23 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only worktree MCP registration inspection (doctor-only).
|
|
3
|
+
*
|
|
4
|
+
* Confirms that a worktree's generated `.mcp.json` / `.cursor/mcp.json` contains
|
|
5
|
+
* a valid Bridge API `mcp-invoke` shim entry pointing at THAT worktree — without
|
|
6
|
+
* launching `mcp-invoke`, `npx`, Node, or the MCP server. Pure read/parse only;
|
|
7
|
+
* the sole injected dependency is `readFile`.
|
|
8
|
+
*/
|
|
9
|
+
import path from "path";
|
|
10
|
+
/**
|
|
11
|
+
* Read and parse a JSON file. A read failure (e.g. ENOENT) is `missing`; invalid
|
|
12
|
+
* JSON is `malformed` (carrying only the path, never the raw content); otherwise
|
|
13
|
+
* `present` with the parsed value.
|
|
14
|
+
*/
|
|
15
|
+
export async function readJsonIfPresent(filePath, deps) {
|
|
16
|
+
let raw;
|
|
17
|
+
try {
|
|
18
|
+
raw = await deps.readFile(filePath);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return { state: "missing" };
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return { state: "present", value: JSON.parse(raw) };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { state: "malformed", path: filePath };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Return the token immediately following `flag` in `args`, or undefined. */
|
|
31
|
+
function flagValue(args, flag) {
|
|
32
|
+
const index = args.indexOf(flag);
|
|
33
|
+
if (index >= 0 && index + 1 < args.length)
|
|
34
|
+
return args[index + 1];
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* True when `entry` is a secret-free Bridge API shim registration that launches
|
|
39
|
+
* `npx -y @bridge_gpt/mcp-server@<version> mcp-invoke --target bapi
|
|
40
|
+
* --project-root <worktreeRoot>`. The `--target` must be `bapi` and the
|
|
41
|
+
* `--project-root` must match the given worktree exactly.
|
|
42
|
+
*/
|
|
43
|
+
export function isBridgeApiShimEntry(entry, worktreeRoot) {
|
|
44
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
45
|
+
return false;
|
|
46
|
+
const candidate = entry;
|
|
47
|
+
if (candidate.command !== "npx")
|
|
48
|
+
return false;
|
|
49
|
+
if (!Array.isArray(candidate.args))
|
|
50
|
+
return false;
|
|
51
|
+
const args = candidate.args.filter((a) => typeof a === "string");
|
|
52
|
+
if (!args.some((a) => a.startsWith("@bridge_gpt/mcp-server")))
|
|
53
|
+
return false;
|
|
54
|
+
if (!args.includes("mcp-invoke"))
|
|
55
|
+
return false;
|
|
56
|
+
if (flagValue(args, "--target") !== "bapi")
|
|
57
|
+
return false;
|
|
58
|
+
if (flagValue(args, "--project-root") !== worktreeRoot)
|
|
59
|
+
return false;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Inspect both `<worktreeRoot>/.mcp.json` and `<worktreeRoot>/.cursor/mcp.json`.
|
|
64
|
+
* Reports `found` when at least one registration file contains a valid
|
|
65
|
+
* `bridge-api` shim entry pointing at this worktree; otherwise `found: false`
|
|
66
|
+
* with an actionable, secret-free hint. Never spawns anything.
|
|
67
|
+
*/
|
|
68
|
+
export async function probeWorktreeMcpRegistration(worktreeRoot, deps) {
|
|
69
|
+
const targets = [
|
|
70
|
+
path.join(worktreeRoot, ".mcp.json"),
|
|
71
|
+
path.join(worktreeRoot, ".cursor", "mcp.json"),
|
|
72
|
+
];
|
|
73
|
+
for (const filePath of targets) {
|
|
74
|
+
const read = await readJsonIfPresent(filePath, deps);
|
|
75
|
+
if (read.state !== "present")
|
|
76
|
+
continue;
|
|
77
|
+
const doc = read.value;
|
|
78
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc))
|
|
79
|
+
continue;
|
|
80
|
+
const servers = doc.mcpServers;
|
|
81
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers))
|
|
82
|
+
continue;
|
|
83
|
+
const entry = servers["bridge-api"];
|
|
84
|
+
if (isBridgeApiShimEntry(entry, worktreeRoot)) {
|
|
85
|
+
return {
|
|
86
|
+
found: true,
|
|
87
|
+
detail: `bridge-api shim registered in ${path.basename(path.dirname(filePath)) === ".cursor" ? ".cursor/mcp.json" : ".mcp.json"}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
found: false,
|
|
93
|
+
detail: "No worktree .mcp.json or .cursor/mcp.json points at the bridge-api mcp-invoke shim. " +
|
|
94
|
+
"Re-run start-tickets to provision the worktree MCP registration.",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -16,7 +16,21 @@
|
|
|
16
16
|
* are inlined into the instruction string so the agent can review and
|
|
17
17
|
* confirm before resuming. (BAPI-275 decisions E-4 / E-36.)
|
|
18
18
|
*/
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
19
20
|
import { resolveRecipe } from "./pipeline-utils.js";
|
|
21
|
+
/**
|
|
22
|
+
* Derive a STABLE, content-addressed hash of an idea — the basis for the
|
|
23
|
+
* cross-run idempotency label ``bapi-idea-hash-{idea_hash}``. Normalizes
|
|
24
|
+
* (trim + lowercase + collapse internal whitespace) so cosmetic edits don't
|
|
25
|
+
* change the hash, then takes the first 12 hex chars of the SHA-256. Unlike the
|
|
26
|
+
* per-run ``run_id`` label, this is identical every time the same idea is run,
|
|
27
|
+
* so a re-run of the same idea is reliably caught by label (not just fuzzy
|
|
28
|
+
* keyword search). Auto-injected as the ``idea_hash`` pipeline variable.
|
|
29
|
+
*/
|
|
30
|
+
export function deriveIdeaHash(idea) {
|
|
31
|
+
const normalized = String(idea ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
32
|
+
return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
|
|
33
|
+
}
|
|
20
34
|
class PipelinePersistenceError extends Error {
|
|
21
35
|
code;
|
|
22
36
|
constructor(code, message) {
|
|
@@ -175,6 +189,10 @@ const ORCHESTRATION_TOOLS = new Set([
|
|
|
175
189
|
"resume_pipeline",
|
|
176
190
|
"list_pipeline_runs",
|
|
177
191
|
"delete_pipeline_run",
|
|
192
|
+
// BAPI-326: chain orchestration tools must never be invoked from within a
|
|
193
|
+
// declarative pipeline step (avoids accidental orchestration recursion).
|
|
194
|
+
"run_full_automation",
|
|
195
|
+
"resume_full_automation",
|
|
178
196
|
]);
|
|
179
197
|
// ---------------------------------------------------------------------------
|
|
180
198
|
// runPipeline
|
|
@@ -196,6 +214,12 @@ export async function runPipeline(deps, input) {
|
|
|
196
214
|
auto_approve: autoApprove ? "true" : "",
|
|
197
215
|
...(input.variables ?? {}),
|
|
198
216
|
};
|
|
217
|
+
// Auto-inject the stable idea-hash (like docs_dir) whenever the pipeline was
|
|
218
|
+
// given an `idea`, so both the chain and standalone callers get the cross-run
|
|
219
|
+
// idempotency label without computing it themselves.
|
|
220
|
+
if ("idea" in mergedVariables) {
|
|
221
|
+
mergedVariables.idea_hash = deriveIdeaHash(mergedVariables.idea);
|
|
222
|
+
}
|
|
199
223
|
let recipe;
|
|
200
224
|
try {
|
|
201
225
|
recipe = resolveRecipe(pipelineDef, deps.instructions, mergedVariables, undefined, autoApprove);
|
|
@@ -227,6 +251,39 @@ export async function runPipeline(deps, input) {
|
|
|
227
251
|
}
|
|
228
252
|
return continuePipelineExecution(deps, persistence, row, recipe, autoApprove);
|
|
229
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Read-only snapshot of a pipeline run's current persisted state — performs no
|
|
256
|
+
* execution and no state transition. Used by the chain orchestrator to decide,
|
|
257
|
+
* at a stage boundary, whether the inner pipeline still needs resuming
|
|
258
|
+
* (``paused``), has already reached a terminal state (``completed`` / ``failed``)
|
|
259
|
+
* so the stage can be advanced/failed idempotently, or is in an indeterminate
|
|
260
|
+
* state that cannot be safely resumed or recovered.
|
|
261
|
+
*/
|
|
262
|
+
export async function peekPipelineRun(deps, pipeline_run_id) {
|
|
263
|
+
const persistence = createPipelinePersistenceClient({
|
|
264
|
+
baseUrl: deps.baseUrl,
|
|
265
|
+
apiKey: deps.apiKey,
|
|
266
|
+
repoName: deps.repoName,
|
|
267
|
+
});
|
|
268
|
+
let row;
|
|
269
|
+
try {
|
|
270
|
+
row = await persistence.getRun(pipeline_run_id);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
if (err instanceof PipelinePersistenceError) {
|
|
274
|
+
return failedEnvelope(err.code, err.message, { pipeline_run_id });
|
|
275
|
+
}
|
|
276
|
+
return failedEnvelope("TOOL_ERROR", err instanceof Error ? err.message : String(err), { pipeline_run_id });
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
status: row.status,
|
|
280
|
+
pipeline_run_id: row.pipeline_run_id,
|
|
281
|
+
pipeline: row.pipeline_name,
|
|
282
|
+
total_steps: row.resolved_recipe.total_steps,
|
|
283
|
+
current_step_index: row.current_step_index,
|
|
284
|
+
results: row.results,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
230
287
|
// ---------------------------------------------------------------------------
|
|
231
288
|
// resumePipeline
|
|
232
289
|
// ---------------------------------------------------------------------------
|
|
@@ -291,7 +348,13 @@ export async function resumePipeline(deps, input) {
|
|
|
291
348
|
updated = await persistence.patchRun(row.pipeline_run_id, {
|
|
292
349
|
current_step_index: nextIndex,
|
|
293
350
|
results: accumulatedResults,
|
|
294
|
-
status
|
|
351
|
+
// Derive the persisted status from the resumed position. When the resumed
|
|
352
|
+
// step is the final ``agent_task``, ``nextIndex`` already equals
|
|
353
|
+
// ``recipe.total_steps``, so ``continuePipelineExecution``'s loop body never
|
|
354
|
+
// runs and it falls through to the completed envelope without issuing
|
|
355
|
+
// another patch. We must persist ``completed`` on THIS transition or the run
|
|
356
|
+
// is orphaned in ``running`` forever. Non-final resumes stay ``running``.
|
|
357
|
+
status: nextIndex >= recipe.total_steps ? "completed" : "running",
|
|
295
358
|
expected_status: "paused",
|
|
296
359
|
expected_current_step_index: stepIndex,
|
|
297
360
|
});
|
|
@@ -583,6 +646,8 @@ async function executeMcpCallStep(deps, step) {
|
|
|
583
646
|
error: err instanceof Error ? err.message : String(err),
|
|
584
647
|
};
|
|
585
648
|
}
|
|
649
|
+
// Non-text tool output (e.g. image content) intentionally degrades to "" for
|
|
650
|
+
// pipeline dispatch in v1 — pipeline steps only act on text envelopes.
|
|
586
651
|
const text = toolResult?.content?.[0]?.type === "text"
|
|
587
652
|
? toolResult.content[0].text
|
|
588
653
|
: "";
|
package/build/pipeline-utils.js
CHANGED
|
@@ -6,6 +6,35 @@
|
|
|
6
6
|
import { readdir, readFile } from "fs/promises";
|
|
7
7
|
import path from "path";
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
|
+
// Tiered section-graph contract metadata (BAPI-345, Ticket 1)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
//
|
|
12
|
+
// Static, server-owned metadata advertised on every resolved recipe envelope so
|
|
13
|
+
// MCP consumers can detect that the section-graph contract exists. Ticket 1 is
|
|
14
|
+
// contract-only: every execution-related capability is disabled, and the
|
|
15
|
+
// section graph itself is NEVER embedded in the recipe envelope (it lives on the
|
|
16
|
+
// plan-generation response, not here). This descriptor is NOT derived from the
|
|
17
|
+
// repo's `tiered_execution` config value.
|
|
18
|
+
export const PIPELINE_CONTRACT_VERSION = "2";
|
|
19
|
+
export const TIERED_SECTION_CAPABILITY_DESCRIPTOR = {
|
|
20
|
+
section_graph: {
|
|
21
|
+
supported: true,
|
|
22
|
+
execution_enabled: false,
|
|
23
|
+
payload_location: "plan_generation_response.sections",
|
|
24
|
+
},
|
|
25
|
+
tiers: ["cheap", "basic", "premium"],
|
|
26
|
+
risk_levels: ["low", "medium", "high"],
|
|
27
|
+
activities: ["implement", "test", "docs", "debug", "config"],
|
|
28
|
+
tiered_execution_modes: ["off", "claude_code_only", "all_capable"],
|
|
29
|
+
capabilities: {
|
|
30
|
+
// All Ticket 2 execution features are disabled / not implemented in Ticket 1.
|
|
31
|
+
escalation: false,
|
|
32
|
+
checkpoint: false,
|
|
33
|
+
rollback: false,
|
|
34
|
+
subagent_dispatch: false,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
9
38
|
// Schema Validation
|
|
10
39
|
// ---------------------------------------------------------------------------
|
|
11
40
|
export function validatePipelineSchema(json) {
|
|
@@ -212,6 +241,10 @@ export function resolveRecipe(pipeline, instructions, variables, skipSteps, auto
|
|
|
212
241
|
agent_instructions: baseInstructions + autoApproveSuffix,
|
|
213
242
|
auto_approve: !!autoApprove,
|
|
214
243
|
steps: resolvedSteps,
|
|
244
|
+
// BAPI-345 (Ticket 1): additive static contract metadata. The section graph
|
|
245
|
+
// is NEVER embedded here — it is delivered on the plan-generation response.
|
|
246
|
+
contract_version: PIPELINE_CONTRACT_VERSION,
|
|
247
|
+
capability_descriptor: TIERED_SECTION_CAPABILITY_DESCRIPTOR,
|
|
215
248
|
};
|
|
216
249
|
}
|
|
217
250
|
// ---------------------------------------------------------------------------
|