@bridge_gpt/mcp-server 0.2.59 → 0.2.60
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 +173 -51
- package/build/agent-guidance.generated.js +1 -1
- package/build/agent-notes.js +7 -1
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +53 -2
- package/build/conductor/doctor.js +255 -53
- package/build/conductor/epic-runtime.js +11 -2
- package/build/conductor/install-doctor.js +24 -2
- package/build/conductor/local-merge.js +319 -26
- package/build/conductor/merge-execution-contract.js +61 -0
- package/build/conductor/merge-execution-eligibility.js +129 -0
- package/build/conductor/readiness-sections.js +159 -2
- package/build/conductor/readiness.js +95 -14
- package/build/conductor/recovery-operations.js +8 -2
- package/build/connect-bitbucket-api.js +6 -1
- package/build/connect-bitbucket.js +398 -88
- package/build/credential-store.js +16 -0
- package/build/docs.generated.js +2 -2
- package/build/doctor.js +183 -9
- package/build/drive-epic.js +23 -0
- package/build/epic-implementer-bin.js +10 -10
- package/build/epic-implementer-claude-hook-bin.js +1 -1
- package/build/epic-implementer-env.js +43 -14
- package/build/executor/ci-rerun-job.js +269 -0
- package/build/executor/cli.js +31 -0
- package/build/executor/credentials.js +11 -0
- package/build/executor/env.js +60 -0
- package/build/executor/index.js +21 -4
- package/build/executor/job-runner.js +126 -0
- package/build/executor/job-types.js +12 -2
- package/build/executor/merge-job.js +11 -2
- package/build/executor/service-lifecycle.js +136 -0
- package/build/executor/service-unit.js +427 -9
- package/build/executor/types.js +5 -0
- package/build/index.js +194 -179
- package/build/install-bridge-conductor.js +201 -8
- package/build/install-bridge.js +356 -30
- package/build/mcp-install-state.js +42 -4
- package/build/merge-pull-request.js +103 -10
- package/build/merge-workflow-codeowners.js +224 -0
- package/build/merge-workflow-config.js +133 -0
- package/build/merge-workflow-drift.js +66 -0
- package/build/merge-workflow-install.js +638 -0
- package/build/merge-workflow.js +572 -0
- package/build/pipelines.generated.js +2 -2
- package/build/plane/cli.js +24 -2
- package/build/plane/preflight.js +68 -0
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +163 -9
- package/build/version.generated.js +3 -3
- package/package.json +3 -3
- package/build/conduct-epic/bridge-client.js +0 -773
- package/build/conduct-epic/checkpoint-store.js +0 -542
- package/build/conduct-epic/cli.js +0 -3145
- package/build/conduct-epic/cut-protocol.js +0 -392
- package/build/conduct-epic/lock.js +0 -302
- package/build/conduct-epic/pr-state.js +0 -286
- package/build/conduct-epic/spawn.js +0 -113
- package/build/conductor/cli.js +0 -1278
- package/build/conductor-bundle-cli.js +0 -256
- package/build/direct-ticket-tools.js +0 -70
- package/build/executor/reconcile-overlap-governance.js +0 -129
- package/build/implement-epic/bridge-client.js +0 -773
- package/build/implement-epic/checkpoint-store.js +0 -542
- package/build/implement-epic/cli.js +0 -3158
- package/build/implement-epic/cut-protocol.js +0 -392
- package/build/implement-epic/lock.js +0 -302
- package/build/implement-epic/pr-state.js +0 -286
- package/build/implement-epic/spawn.js +0 -113
- package/build/plan-epic-conductor-eligibility.js +0 -213
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Secret-free agent-tab spawning for `implement-epic spawn` (BAPI-803).
|
|
3
|
-
*
|
|
4
|
-
* `spawn` opens exactly ONE terminal tab in a ticket's worktree running the
|
|
5
|
-
* agent against a prompt the CALLER wrote to a file. The prompt is arbitrary
|
|
6
|
-
* operator/loop-authored text — it can contain apostrophes, `$(…)`, backticks,
|
|
7
|
-
* and newlines — so it is placed inside a single-quoted shell literal using the
|
|
8
|
-
* repository's existing quoting helpers rather than any local escaping. Reusing
|
|
9
|
-
* `shSquoteInner` / `powershellSquote` is the point: a second escaping
|
|
10
|
-
* implementation is a second place for a quoting bug to produce shell execution.
|
|
11
|
-
*
|
|
12
|
-
* Two invariants this module is responsible for:
|
|
13
|
-
*
|
|
14
|
-
* - **Exactly one tab, and only after every check passes.** The spawn seam is
|
|
15
|
-
* invoked once, at the end, after the agent is validated and the command is
|
|
16
|
-
* built. A partial failure must leave no tab, because the caller increments
|
|
17
|
-
* `sessions_spawned` on success and a phantom tab would desynchronize it.
|
|
18
|
-
* - **No credential reaches the command.** Nothing here resolves, receives, or
|
|
19
|
-
* formats Bridge access data. The spawned agent resolves its own credentials
|
|
20
|
-
* from the user-scoped store, exactly as `start-tickets` workers do; a key in
|
|
21
|
-
* argv would be visible to every process on the machine via `ps`.
|
|
22
|
-
*/
|
|
23
|
-
import { powershellSquote, shSquoteInner, } from "../start-tickets.js";
|
|
24
|
-
import { INDEX_SCOPE_ENV_VAR } from "../index-scope-contract.js";
|
|
25
|
-
/** The agents `implement-epic spawn` may launch. */
|
|
26
|
-
export const CONDUCT_EPIC_AGENTS = ["claude", "cursor-agent"];
|
|
27
|
-
/** The repository's established default agent. */
|
|
28
|
-
export const CONDUCT_EPIC_DEFAULT_AGENT = "claude";
|
|
29
|
-
/**
|
|
30
|
-
* Validate an agent value, defaulting to {@link CONDUCT_EPIC_DEFAULT_AGENT}.
|
|
31
|
-
*
|
|
32
|
-
* Rejects before any command is built. The agent name becomes the executable in
|
|
33
|
-
* a shell command, so an unvalidated value is a command-injection surface even
|
|
34
|
-
* though the surrounding operands are quoted.
|
|
35
|
-
*/
|
|
36
|
-
export function resolveConductEpicAgent(agent) {
|
|
37
|
-
if (agent === undefined || agent.trim().length === 0) {
|
|
38
|
-
return { ok: true, agent: CONDUCT_EPIC_DEFAULT_AGENT };
|
|
39
|
-
}
|
|
40
|
-
const trimmed = agent.trim();
|
|
41
|
-
if (!CONDUCT_EPIC_AGENTS.includes(trimmed)) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
error: `Unsupported agent '${trimmed}'. Expected one of: ${CONDUCT_EPIC_AGENTS.join(", ")}`,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return { ok: true, agent: trimmed };
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Build `cd '<worktree>' && <agent> '<prompt>'` (POSIX) or the PowerShell
|
|
51
|
-
* equivalent, with both operands quoted by the shared helpers.
|
|
52
|
-
*
|
|
53
|
-
* Returns the command as a string rather than an argv array because that is what
|
|
54
|
-
* every terminal spawner in this repository consumes — a tab is opened by handing
|
|
55
|
-
* a shell a command line, not by `exec`ing a process.
|
|
56
|
-
*
|
|
57
|
-
* BAPI-844: a validated `indexScope` is prefixed as an environment assignment
|
|
58
|
-
* using the SAME quoting helpers as the rest of the command — the shell's own
|
|
59
|
-
* environment mechanism, not an argument the agent can see. An absent scope emits
|
|
60
|
-
* no prefix, so the unscoped command is unchanged byte-for-byte.
|
|
61
|
-
*/
|
|
62
|
-
export function buildConductEpicAgentCommand(input) {
|
|
63
|
-
const resolved = resolveConductEpicAgent(input.agent);
|
|
64
|
-
if (!resolved.ok)
|
|
65
|
-
return resolved;
|
|
66
|
-
if (input.worktreePath.trim().length === 0) {
|
|
67
|
-
return { ok: false, error: "A worktree path is required to build the agent command." };
|
|
68
|
-
}
|
|
69
|
-
if (input.platform === "win32") {
|
|
70
|
-
const scopePrefix = input.indexScope
|
|
71
|
-
? `$env:${INDEX_SCOPE_ENV_VAR} = ${powershellSquote(input.indexScope)}; `
|
|
72
|
-
: "";
|
|
73
|
-
return {
|
|
74
|
-
ok: true,
|
|
75
|
-
command: `${scopePrefix}Set-Location -LiteralPath ${powershellSquote(input.worktreePath)}; ` +
|
|
76
|
-
`${resolved.agent} ${powershellSquote(input.prompt)}`,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
const scopePrefix = input.indexScope
|
|
80
|
-
? `export ${INDEX_SCOPE_ENV_VAR}='${shSquoteInner(input.indexScope)}' && `
|
|
81
|
-
: "";
|
|
82
|
-
return {
|
|
83
|
-
ok: true,
|
|
84
|
-
command: `${scopePrefix}cd '${shSquoteInner(input.worktreePath)}' && ` +
|
|
85
|
-
`${resolved.agent} '${shSquoteInner(input.prompt)}'`,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Build the command and open exactly one tab through `spawnTab`.
|
|
90
|
-
*
|
|
91
|
-
* The seam is invoked once and only after construction succeeds. A spawner
|
|
92
|
-
* failure is returned as `{ ok: false }` with the spawner's own sanitized
|
|
93
|
-
* message — never the command, which embeds the full prompt text.
|
|
94
|
-
*/
|
|
95
|
-
export async function spawnConductEpicAgentTab(input, spawnTab) {
|
|
96
|
-
const built = buildConductEpicAgentCommand(input);
|
|
97
|
-
if (!built.ok)
|
|
98
|
-
return built;
|
|
99
|
-
let result;
|
|
100
|
-
try {
|
|
101
|
-
result = await spawnTab(built.command, {
|
|
102
|
-
key: input.ticketKey,
|
|
103
|
-
worktreePath: input.worktreePath,
|
|
104
|
-
title: `implement-epic ${input.ticketKey}`,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return { ok: false, error: "The terminal tab could not be opened." };
|
|
109
|
-
}
|
|
110
|
-
if (!result.ok)
|
|
111
|
-
return { ok: false, error: result.error };
|
|
112
|
-
return { ok: true, command: built.command };
|
|
113
|
-
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Combined merge/review conductor-eligibility assessment for `plan-epic` (BAPI-964).
|
|
3
|
-
*
|
|
4
|
-
* Runs once the epic decomposition's child manifest is frozen and every
|
|
5
|
-
* sub-task's exploration text is written, so this module can predict — before
|
|
6
|
-
* any conductor worker is spawned — which children are likely to trip the
|
|
7
|
-
* runtime workflow-file merge guard (`api/library/vcs/epic_implementer_merge_service.py`)
|
|
8
|
-
* and which of those additionally touch `claude-review.yml` itself, where
|
|
9
|
-
* automated review cannot run at all (BAPI-941's supply-chain preflight refuses
|
|
10
|
-
* to review a workflow that differs from the base branch's copy).
|
|
11
|
-
*
|
|
12
|
-
* The BROADER merge classification is delegated entirely to the Python
|
|
13
|
-
* classifier via one Bridge API call for the complete child set — this module
|
|
14
|
-
* never reimplements "what counts as a workflow file". The NARROWER review
|
|
15
|
-
* subclass is classified locally by checking each child's combined text for the
|
|
16
|
-
* full `CLAUDE_REVIEW_WORKFLOW_RELPATH`, imported (not re-declared) from
|
|
17
|
-
* `claude-review-workflow-drift.js`.
|
|
18
|
-
*
|
|
19
|
-
* Fails open, deliberately: any validation failure, classification failure, or
|
|
20
|
-
* internal contradiction (a review-subset child the backend did not also mark
|
|
21
|
-
* merge-blocked) becomes the explicit `unavailable` result — never a fabricated
|
|
22
|
-
* zero count. A caller that cannot get a real answer must say so, not guess.
|
|
23
|
-
*/
|
|
24
|
-
import { CLAUDE_REVIEW_WORKFLOW_RELPATH } from "./claude-review-workflow-drift.js";
|
|
25
|
-
/**
|
|
26
|
-
* Build the real network-backed classification client that POSTs the complete
|
|
27
|
-
* child set to `/jira/planning/epic-workflow-eligibility` exactly once.
|
|
28
|
-
*/
|
|
29
|
-
export function createBridgeEpicConductorEligibilityClient(deps) {
|
|
30
|
-
return {
|
|
31
|
-
async classifyChildren(children) {
|
|
32
|
-
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
33
|
-
const resp = await fetchImpl(deps.buildUrl("/planning/epic-workflow-eligibility"), {
|
|
34
|
-
method: "POST",
|
|
35
|
-
headers: await deps.getPostHeaders(),
|
|
36
|
-
body: JSON.stringify({ repo_name: deps.repoName, children }),
|
|
37
|
-
});
|
|
38
|
-
if (!resp.ok) {
|
|
39
|
-
throw new Error(`epic-workflow-eligibility classification request failed: ${resp.status}`);
|
|
40
|
-
}
|
|
41
|
-
return (await resp.json());
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const UNAVAILABLE = { status: "unavailable" };
|
|
46
|
-
function isNonBlankString(value) {
|
|
47
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
48
|
-
}
|
|
49
|
-
/** Shape-validate an evidence array: every element must be a string. */
|
|
50
|
-
function isStringArray(value) {
|
|
51
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
52
|
-
}
|
|
53
|
-
/** Treat a missing/undefined optional field as empty text; reject a non-string value. */
|
|
54
|
-
function optionalTextOrNull(value) {
|
|
55
|
-
if (value === undefined || value === null)
|
|
56
|
-
return "";
|
|
57
|
-
return typeof value === "string" ? value : null;
|
|
58
|
-
}
|
|
59
|
-
/** Deterministically assemble one child's scan text from its authoritative fields. */
|
|
60
|
-
function buildCombinedText(child) {
|
|
61
|
-
return [child.title, child.scope, child.description, child.requirements]
|
|
62
|
-
.filter((part) => typeof part === "string" && part.length > 0)
|
|
63
|
-
.join("\n\n");
|
|
64
|
-
}
|
|
65
|
-
/** Validate the child collection at the module boundary. `null` means malformed. */
|
|
66
|
-
function validateChildren(children) {
|
|
67
|
-
if (!Array.isArray(children) || children.length === 0)
|
|
68
|
-
return null;
|
|
69
|
-
const validated = [];
|
|
70
|
-
for (const child of children) {
|
|
71
|
-
if (child === null || typeof child !== "object")
|
|
72
|
-
return null;
|
|
73
|
-
if (!isNonBlankString(child.id) || !isNonBlankString(child.title))
|
|
74
|
-
return null;
|
|
75
|
-
const scope = optionalTextOrNull(child.scope);
|
|
76
|
-
const description = optionalTextOrNull(child.description);
|
|
77
|
-
const requirements = optionalTextOrNull(child.requirements);
|
|
78
|
-
if (scope === null || description === null || requirements === null)
|
|
79
|
-
return null;
|
|
80
|
-
validated.push({
|
|
81
|
-
id: child.id,
|
|
82
|
-
title: child.title,
|
|
83
|
-
combinedText: buildCombinedText({
|
|
84
|
-
id: child.id,
|
|
85
|
-
title: child.title,
|
|
86
|
-
scope,
|
|
87
|
-
description,
|
|
88
|
-
requirements,
|
|
89
|
-
}),
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
return validated;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Assess conductor merge/review eligibility for one frozen epic child set.
|
|
96
|
-
*
|
|
97
|
-
* Sends the complete ordered child set to the injected Bridge client exactly
|
|
98
|
-
* once. Never throws — every failure path (malformed input, client rejection,
|
|
99
|
-
* malformed backend response, an internal merge/review contradiction) resolves
|
|
100
|
-
* to the explicit `unavailable` result, diagnosed with `console.error` (never
|
|
101
|
-
* `console.log`, which is reserved for the MCP stdio transport).
|
|
102
|
-
*/
|
|
103
|
-
export async function assessEpicConductorEligibility(children, deps) {
|
|
104
|
-
const validated = validateChildren(children);
|
|
105
|
-
if (validated === null) {
|
|
106
|
-
console.error("assessEpicConductorEligibility: malformed child collection; unavailable.");
|
|
107
|
-
return UNAVAILABLE;
|
|
108
|
-
}
|
|
109
|
-
let response;
|
|
110
|
-
try {
|
|
111
|
-
response = await deps.client.classifyChildren(validated.map((child) => ({ child_id: child.id, combined_text: child.combinedText })));
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
console.error("assessEpicConductorEligibility: Bridge classification call failed:", err);
|
|
115
|
-
return UNAVAILABLE;
|
|
116
|
-
}
|
|
117
|
-
if (response === null ||
|
|
118
|
-
typeof response !== "object" ||
|
|
119
|
-
!Array.isArray(response.predictions) ||
|
|
120
|
-
!Number.isInteger(response.total_children) ||
|
|
121
|
-
response.total_children < 0 ||
|
|
122
|
-
!Number.isInteger(response.predicted_workflow_children) ||
|
|
123
|
-
response.predicted_workflow_children < 0 ||
|
|
124
|
-
!Number.isInteger(response.containment_children) ||
|
|
125
|
-
response.containment_children < 0 ||
|
|
126
|
-
typeof response.reason !== "string") {
|
|
127
|
-
console.error("assessEpicConductorEligibility: malformed backend response shape; unavailable.");
|
|
128
|
-
return UNAVAILABLE;
|
|
129
|
-
}
|
|
130
|
-
if (response.predictions.length !== validated.length) {
|
|
131
|
-
console.error("assessEpicConductorEligibility: backend prediction count does not match child count; unavailable.");
|
|
132
|
-
return UNAVAILABLE;
|
|
133
|
-
}
|
|
134
|
-
// The backend is contractually order-preserving; still verify identity and
|
|
135
|
-
// order rather than trusting it, so a reordered/duplicate/unknown response
|
|
136
|
-
// never silently mislabels a child's prediction.
|
|
137
|
-
const predictionById = new Map();
|
|
138
|
-
for (const prediction of response.predictions) {
|
|
139
|
-
if (prediction === null ||
|
|
140
|
-
typeof prediction !== "object" ||
|
|
141
|
-
!isNonBlankString(prediction.child_id) ||
|
|
142
|
-
typeof prediction.predicted_workflow_modified !== "boolean" ||
|
|
143
|
-
!isStringArray(prediction.matched_paths) ||
|
|
144
|
-
typeof prediction.predicted_containment_hazard !== "boolean" ||
|
|
145
|
-
!isStringArray(prediction.matched_hazards)) {
|
|
146
|
-
console.error("assessEpicConductorEligibility: malformed per-child prediction; unavailable.");
|
|
147
|
-
return UNAVAILABLE;
|
|
148
|
-
}
|
|
149
|
-
if (prediction.predicted_containment_hazard !== (prediction.matched_hazards.length > 0)) {
|
|
150
|
-
console.error(`assessEpicConductorEligibility: child ${prediction.child_id} has a containment flag that ` +
|
|
151
|
-
"disagrees with whether matched hazard evidence is present; unavailable.");
|
|
152
|
-
return UNAVAILABLE;
|
|
153
|
-
}
|
|
154
|
-
if (predictionById.has(prediction.child_id)) {
|
|
155
|
-
console.error("assessEpicConductorEligibility: duplicate child_id in backend response; unavailable.");
|
|
156
|
-
return UNAVAILABLE;
|
|
157
|
-
}
|
|
158
|
-
predictionById.set(prediction.child_id, prediction);
|
|
159
|
-
}
|
|
160
|
-
const affectedChildren = [];
|
|
161
|
-
let reviewSubsetChildren = 0;
|
|
162
|
-
let predictedWorkflowChildren = 0;
|
|
163
|
-
let containmentChildren = 0;
|
|
164
|
-
for (const child of validated) {
|
|
165
|
-
const prediction = predictionById.get(child.id);
|
|
166
|
-
if (!prediction) {
|
|
167
|
-
console.error(`assessEpicConductorEligibility: backend response is missing child ${child.id}; unavailable.`);
|
|
168
|
-
return UNAVAILABLE;
|
|
169
|
-
}
|
|
170
|
-
const reviewPathReferenced = child.combinedText.includes(CLAUDE_REVIEW_WORKFLOW_RELPATH);
|
|
171
|
-
if (reviewPathReferenced && !prediction.predicted_workflow_modified) {
|
|
172
|
-
// Subset invariant violated: a child naming claude-review.yml itself must
|
|
173
|
-
// also be in the broader merge-blocked set. Publishing this as a
|
|
174
|
-
// "review-only" child would contradict the merge count, so refuse instead.
|
|
175
|
-
console.error(`assessEpicConductorEligibility: child ${child.id} references the review workflow ` +
|
|
176
|
-
"but the backend did not mark it merge-blocked; refusing contradictory result.");
|
|
177
|
-
return UNAVAILABLE;
|
|
178
|
-
}
|
|
179
|
-
if (prediction.predicted_workflow_modified) {
|
|
180
|
-
predictedWorkflowChildren += 1;
|
|
181
|
-
if (reviewPathReferenced)
|
|
182
|
-
reviewSubsetChildren += 1;
|
|
183
|
-
}
|
|
184
|
-
if (prediction.predicted_containment_hazard)
|
|
185
|
-
containmentChildren += 1;
|
|
186
|
-
// A child is affected — and therefore included — when it is workflow-modifying,
|
|
187
|
-
// containment-flagged, or both. Skip only a child that is neither.
|
|
188
|
-
if (!prediction.predicted_workflow_modified && !prediction.predicted_containment_hazard)
|
|
189
|
-
continue;
|
|
190
|
-
affectedChildren.push({
|
|
191
|
-
id: child.id,
|
|
192
|
-
title: child.title,
|
|
193
|
-
matchedPaths: [...prediction.matched_paths],
|
|
194
|
-
requiresHandReview: reviewPathReferenced,
|
|
195
|
-
requiresContainmentReview: prediction.predicted_containment_hazard,
|
|
196
|
-
matchedHazards: [...prediction.matched_hazards],
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
if (containmentChildren !== response.containment_children) {
|
|
200
|
-
console.error("assessEpicConductorEligibility: containment_children count disagrees with the validated " +
|
|
201
|
-
"per-child containment flags; unavailable.");
|
|
202
|
-
return UNAVAILABLE;
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
status: "assessed",
|
|
206
|
-
totalChildren: validated.length,
|
|
207
|
-
predictedWorkflowChildren,
|
|
208
|
-
reason: response.reason,
|
|
209
|
-
reviewSubsetChildren,
|
|
210
|
-
containmentChildren,
|
|
211
|
-
affectedChildren,
|
|
212
|
-
};
|
|
213
|
-
}
|