@bridge_gpt/mcp-server 0.2.59 → 0.2.62
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 +181 -51
- package/build/agent-guidance.generated.js +1 -1
- package/build/agent-notes.js +7 -1
- package/build/commands.generated.js +6 -6
- package/build/conductor/bridge-api-client.js +406 -4
- 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 +86 -0
- package/build/conductor/merge-execution-eligibility.js +130 -0
- package/build/conductor/readiness-sections.js +256 -16
- package/build/conductor/readiness.js +203 -29
- 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 +172 -0
- package/build/docs.generated.js +2 -2
- package/build/doctor.js +277 -15
- package/build/drive-epic.js +147 -12
- package/build/epic-implementer-bin.js +15 -15
- package/build/epic-implementer-claude-hook-bin.js +1 -1
- package/build/epic-implementer-env.js +43 -14
- package/build/epic-integration-pr.js +61 -15
- package/build/epic-launch-profile.js +455 -0
- package/build/executor/ci-rerun-job.js +269 -0
- package/build/executor/cli.js +40 -0
- package/build/executor/credentials.js +28 -1
- package/build/executor/env.js +60 -0
- package/build/executor/http-client.js +45 -1
- package/build/executor/index.js +21 -4
- package/build/executor/integration-job.js +759 -0
- package/build/executor/job-runner.js +218 -9
- package/build/executor/job-types.js +12 -2
- package/build/executor/merge-job.js +11 -2
- package/build/executor/observation.js +114 -7
- package/build/executor/runner.js +101 -1
- package/build/executor/service-lifecycle.js +136 -0
- package/build/executor/service-unit.js +427 -9
- package/build/executor/types.js +48 -0
- package/build/index.js +479 -240
- package/build/install-bridge-conductor.js +599 -14
- package/build/install-bridge.js +448 -46
- 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 +187 -0
- package/build/merge-workflow-drift.js +97 -0
- package/build/merge-workflow-install.js +638 -0
- package/build/merge-workflow.js +956 -0
- package/build/pipelines.generated.js +3 -3
- package/build/plane/cli.js +24 -2
- package/build/plane/preflight.js +68 -0
- package/build/plane/supervisor.js +6 -0
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +263 -38
- 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
|
@@ -65,6 +65,11 @@ export const MODULE_MOCK_MANIFEST = [
|
|
|
65
65
|
"build/conductor/store-lifecycle.test.js",
|
|
66
66
|
"build/conductor/store-queries.test.js",
|
|
67
67
|
"build/conductor/tools.test.js",
|
|
68
|
+
// BAPI-1247: drives offerBitbucketConnection to a chosen structured outcome by
|
|
69
|
+
// mocking the shared connect flow at install-bridge.ts's import site, which needs
|
|
70
|
+
// --experimental-test-module-mocks. Kept apart from install-bridge.test.js so that
|
|
71
|
+
// 346-test file stays in the NORMAL lane.
|
|
72
|
+
"build/connect-bitbucket-handoff.test.js",
|
|
68
73
|
"build/connect-github-api.test.js",
|
|
69
74
|
"build/connect-github-dispatch.static.test.js",
|
|
70
75
|
"build/connect-github-handoff.test.js",
|
package/build/setup-epic.js
CHANGED
|
@@ -78,7 +78,14 @@
|
|
|
78
78
|
import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
|
|
79
79
|
import os from "node:os";
|
|
80
80
|
import { createProductionEpicIntegrationGhRunner, ensureEpicIntegrationPullRequest, formatEpicIntegrationPullRequestOutcome, } from "./epic-integration-pr.js";
|
|
81
|
-
import { approveEpicPlan, createEpicRunWithDisposition, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
81
|
+
import { approveEpicPlan, buildConductorJiraUrl, conductorGetHeaders, createEpicRunWithDisposition, decodeCanonicalProvenance, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, resolveEpicLaunchBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
82
|
+
// BAPI-1133 (A2) — the single legacy-to-typed merge-execution-mode mapping and
|
|
83
|
+
// its total legacy resolver live in `merge-pull-request.ts` (no import cycle:
|
|
84
|
+
// that module never imports from `setup-epic.ts`). Reused here rather than
|
|
85
|
+
// reimplemented so the mapping stays single-defined.
|
|
86
|
+
import { mapLegacyMergeExecutionModeToPolicyMode, resolveMergeExecutionMode, } from "./merge-pull-request.js";
|
|
87
|
+
import { MERGE_EXECUTION_INELIGIBLE, MERGE_EXECUTION_MODE_GITHUB_ACTIONS, MERGE_WORKFLOW_REMEDY_COMMAND, } from "./conductor/merge-execution-contract.js";
|
|
88
|
+
import { classifyMergeExecutionEligibility } from "./conductor/merge-execution-eligibility.js";
|
|
82
89
|
import { resolveTrackedPaths } from "./git-reachability.js";
|
|
83
90
|
import { validateBranchName } from "./base-ref.js";
|
|
84
91
|
// Reused from the implement-epic client on purpose: adding a second wrapper under
|
|
@@ -90,7 +97,7 @@ import { validateBranchName } from "./base-ref.js";
|
|
|
90
97
|
// conductor can drift on a field name or an endpoint shape. No MCP tool, no
|
|
91
98
|
// input-schema field, and no description changes.
|
|
92
99
|
import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./epic-shared/bridge-client.js";
|
|
93
|
-
import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally,
|
|
100
|
+
import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./epic-shared/cut-protocol.js";
|
|
94
101
|
import { hashPlan } from "./conductor/plan.js";
|
|
95
102
|
import { BRING_UP_REFUSAL_MESSAGES, firstBringUpRefusal, } from "./conductor/bring-up-facts.js";
|
|
96
103
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
@@ -128,6 +135,29 @@ export const SETUP_EPIC_UNATTENDED_POSTURE_REFUSED_CODE = "UNATTENDED_POSTURE_RE
|
|
|
128
135
|
* which is what the observed BAPI-1085 run did.
|
|
129
136
|
*/
|
|
130
137
|
export const SETUP_EPIC_AUTO_MERGE_POLICY_KEY = "v2_auto_merge_enabled";
|
|
138
|
+
/**
|
|
139
|
+
* The `policy_json` key declaring the typed merge-execution mode (BAPI-1133, A2).
|
|
140
|
+
*
|
|
141
|
+
* Composed into the unattended default alongside `posture` and
|
|
142
|
+
* {@link SETUP_EPIC_AUTO_MERGE_POLICY_KEY}, in the SAME fill-if-absent branch,
|
|
143
|
+
* because the server's `RunPolicy` refuses an unattended auto-merge policy that
|
|
144
|
+
* omits it (`merge_execution_mode_unconfigured`). `setup-epic` is the only
|
|
145
|
+
* IMPLICIT writer of this key — any other client may still submit it explicitly
|
|
146
|
+
* as operator policy, and an explicitly supplied value here is never overwritten.
|
|
147
|
+
* Declaring `github_actions` or `machine_token` is intent only in this slice:
|
|
148
|
+
* neither changes merge execution until a later A2 child.
|
|
149
|
+
*/
|
|
150
|
+
export const SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY = "merge_execution_mode";
|
|
151
|
+
/**
|
|
152
|
+
* BAPI-1259 — the opt-in for automatic integration-PR preparation. Written into
|
|
153
|
+
* the UNATTENDED epic-branch default only (fill-if-absent), so a run the
|
|
154
|
+
* operator starts unattended is prepared and handed off without a manual base
|
|
155
|
+
* merge or a manual un-draft. Attended runs, `--into-base` runs, and runs whose
|
|
156
|
+
* `--policy-file` already declares the key keep exactly what they had; a run
|
|
157
|
+
* approved before this key existed is never changed retroactively.
|
|
158
|
+
*/
|
|
159
|
+
export const SETUP_EPIC_INTEGRATION_PREPARATION_POLICY_KEY = "integration_preparation";
|
|
160
|
+
const SETUP_EPIC_INTEGRATION_PREPARATION_DEFAULT = { enabled: true };
|
|
131
161
|
/** Accepted `policy_json.review_policy.source` values (the `ReviewPolicy` surface). */
|
|
132
162
|
export const SETUP_EPIC_REVIEW_POLICY_SOURCES = [
|
|
133
163
|
"verdict_protocol",
|
|
@@ -169,12 +199,15 @@ export function getSetupEpicUsage() {
|
|
|
169
199
|
"repository base branch, matching plain start-tickets semantics.",
|
|
170
200
|
"",
|
|
171
201
|
"Posture (default): a run that selects an epic branch is created UNATTENDED —",
|
|
172
|
-
"posture: unattended
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
"
|
|
202
|
+
"posture: unattended, v2_auto_merge_enabled: true, and merge_execution_mode",
|
|
203
|
+
"(declared automatically from the repository's configured merge execution; a",
|
|
204
|
+
"policy declaration only — it does not change merge execution in this release)",
|
|
205
|
+
"— and the server stamps its CI gate at first approval from the repository",
|
|
206
|
+
"default, or records an explicit no-CI-gate mode when the repository declares",
|
|
207
|
+
"none. Child PRs merge into the epic branch; the INTEGRATION PR into the",
|
|
208
|
+
"repository base branch stays human-gated. Pass --attended to opt out. A run",
|
|
209
|
+
"that selects no epic branch (--into-base, or a single-node plan) is unchanged",
|
|
210
|
+
"and composes no posture at all.",
|
|
178
211
|
"",
|
|
179
212
|
"Unattended runs have TWO one-time repository prerequisites, refused by name",
|
|
180
213
|
"before any run is created: repository consent (unattended_epic_implementer_allowed) and",
|
|
@@ -226,6 +259,10 @@ export function getSetupEpicUsage() {
|
|
|
226
259
|
" sets no review policy of its own, so a value supplied",
|
|
227
260
|
" here always wins.",
|
|
228
261
|
" --json Emit a single JSON result object on stdout",
|
|
262
|
+
" --launch-profile <name> (BAPI-1260) Resolve the target, repository, and bound",
|
|
263
|
+
" credential from this host-local launch profile. Passed",
|
|
264
|
+
" by drive-epic; there is no production default with it.",
|
|
265
|
+
" --base-url <url> (BAPI-1260) Explicit target; must agree with the profile.",
|
|
229
266
|
" -h, --help Show this help",
|
|
230
267
|
"",
|
|
231
268
|
"Policy controls:",
|
|
@@ -326,6 +363,8 @@ export function parseSetupEpicArgs(argv) {
|
|
|
326
363
|
let localOnly = false;
|
|
327
364
|
let dryRun = false;
|
|
328
365
|
let json = false;
|
|
366
|
+
let launchProfile;
|
|
367
|
+
let baseUrl;
|
|
329
368
|
for (let i = 0; i < argv.length; i++) {
|
|
330
369
|
const arg = argv[i];
|
|
331
370
|
// `--policy-file=<path>` inline form (BAPI-754).
|
|
@@ -427,6 +466,19 @@ export function parseSetupEpicArgs(argv) {
|
|
|
427
466
|
i++;
|
|
428
467
|
break;
|
|
429
468
|
}
|
|
469
|
+
// BAPI-1260 — the launch selection forwarded by drive-epic.
|
|
470
|
+
case "--launch-profile":
|
|
471
|
+
case "--base-url": {
|
|
472
|
+
const v = takeValue(argv, i, arg);
|
|
473
|
+
if (v === null || v.trim() === "")
|
|
474
|
+
return { status: "error", message: `${arg} requires a value.` };
|
|
475
|
+
if (arg === "--launch-profile")
|
|
476
|
+
launchProfile = v.trim();
|
|
477
|
+
else
|
|
478
|
+
baseUrl = v.trim();
|
|
479
|
+
i++;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
430
482
|
case "--into-base":
|
|
431
483
|
intoBase = true;
|
|
432
484
|
break;
|
|
@@ -508,6 +560,9 @@ export function parseSetupEpicArgs(argv) {
|
|
|
508
560
|
localOnly,
|
|
509
561
|
dryRun,
|
|
510
562
|
json,
|
|
563
|
+
// BAPI-1260 — only when supplied, so a legacy options object is unchanged.
|
|
564
|
+
...(launchProfile ? { launchProfile } : {}),
|
|
565
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
511
566
|
},
|
|
512
567
|
};
|
|
513
568
|
}
|
|
@@ -1244,6 +1299,66 @@ export function evaluateSetupEpicBringUpPrerequisites(readiness) {
|
|
|
1244
1299
|
return null;
|
|
1245
1300
|
return { reason, message: `${BRING_UP_REFUSAL_MESSAGES[reason]} ${NO_RUN_WAS_CREATED}` };
|
|
1246
1301
|
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Read the composed policy's typed `merge_execution_mode`, if it declares one.
|
|
1304
|
+
*
|
|
1305
|
+
* Strict equality against the mirrored constant, matching the server's own guard
|
|
1306
|
+
* (`preflight._strict_github_actions_mode`). A missing key, a non-string, and
|
|
1307
|
+
* any other mode all answer `false`, so nothing but an explicit declaration
|
|
1308
|
+
* opens the gate.
|
|
1309
|
+
*/
|
|
1310
|
+
export function policyDeclaresGitHubActionsMerge(policy) {
|
|
1311
|
+
if (policy === undefined || policy === null)
|
|
1312
|
+
return false;
|
|
1313
|
+
return policy.merge_execution_mode === MERGE_EXECUTION_MODE_GITHUB_ACTIONS;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Evaluate merge-execution eligibility against a readiness report.
|
|
1317
|
+
*
|
|
1318
|
+
* Pure and total. Returns `null` — no refusal — for every policy that does not
|
|
1319
|
+
* declare `github_actions` mode, without reading the report at all.
|
|
1320
|
+
*
|
|
1321
|
+
* Uses {@link classifyMergeExecutionEligibility}, the same classifier readiness
|
|
1322
|
+
* and `drive-epic` read, so all three agree on the status AND on the first
|
|
1323
|
+
* blocker. A client that classified the block itself here would eventually
|
|
1324
|
+
* disagree with the row an operator had just been shown.
|
|
1325
|
+
*
|
|
1326
|
+
* BOTH `not_eligible` and `could_not_verify` return
|
|
1327
|
+
* `merge_execution_ineligible`. One code, because the operator's next action is
|
|
1328
|
+
* the same and splitting it would put the burden of telling "not proven" from
|
|
1329
|
+
* "proven absent" on the person least able to check. The MESSAGE keeps the
|
|
1330
|
+
* capability category explicit, so the distinction survives where it is
|
|
1331
|
+
* actionable.
|
|
1332
|
+
*
|
|
1333
|
+
* The message is built from the shared remedy constant, ends with the exact
|
|
1334
|
+
* standalone sentence `No run was created.`, and carries no GitHub payload, URL,
|
|
1335
|
+
* branch name, workflow source, credential, or exception text.
|
|
1336
|
+
*/
|
|
1337
|
+
export function evaluateSetupEpicMergeExecutionPrerequisites(readiness, requestedPolicy) {
|
|
1338
|
+
if (!policyDeclaresGitHubActionsMerge(requestedPolicy))
|
|
1339
|
+
return null;
|
|
1340
|
+
if (readiness === null) {
|
|
1341
|
+
return {
|
|
1342
|
+
reason: MERGE_EXECUTION_INELIGIBLE,
|
|
1343
|
+
message: "this run declares merge_execution_mode: github_actions, and the Bridge API " +
|
|
1344
|
+
"conductor readiness report could not be read, so the repository's merge " +
|
|
1345
|
+
"capability could not be confirmed. Retry once the server is reachable, or " +
|
|
1346
|
+
`resolve it with \`${MERGE_WORKFLOW_REMEDY_COMMAND}\`. ${NO_RUN_WAS_CREATED}`,
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
const verdict = classifyMergeExecutionEligibility(readiness);
|
|
1350
|
+
if (verdict.status === "eligible")
|
|
1351
|
+
return null;
|
|
1352
|
+
const category = verdict.status === "could_not_verify"
|
|
1353
|
+
? "could not be confirmed"
|
|
1354
|
+
: "is not sufficient";
|
|
1355
|
+
return {
|
|
1356
|
+
reason: MERGE_EXECUTION_INELIGIBLE,
|
|
1357
|
+
message: "this run declares merge_execution_mode: github_actions, and this repository's " +
|
|
1358
|
+
`merge capability ${category}: ${verdict.detail}. Resolve it with ` +
|
|
1359
|
+
`\`${MERGE_WORKFLOW_REMEDY_COMMAND}\`, then re-run. ${NO_RUN_WAS_CREATED}`,
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1247
1362
|
/**
|
|
1248
1363
|
* Does the policy this invocation will actually send request unattended operation?
|
|
1249
1364
|
*
|
|
@@ -1283,8 +1398,11 @@ export function requestedPolicyIsUnattended(policy, attended) {
|
|
|
1283
1398
|
* * **Feature branch, `--attended`** → `base_branch` plus an explicitly
|
|
1284
1399
|
* requested `review_policy`, and nothing else. Byte-identical to the
|
|
1285
1400
|
* pre-BAPI-1102 composition, which is precisely what the opt-out promises.
|
|
1286
|
-
* * **Feature branch, default** → adds `posture: "unattended"
|
|
1287
|
-
* `v2_auto_merge_enabled: true
|
|
1401
|
+
* * **Feature branch, default** → adds `posture: "unattended"`,
|
|
1402
|
+
* `v2_auto_merge_enabled: true`, (BAPI-1133, A2) `merge_execution_mode`
|
|
1403
|
+
* when the caller resolved one — the server refuses an unattended auto-merge
|
|
1404
|
+
* policy that omits it — and (BAPI-1259) `integration_preparation:
|
|
1405
|
+
* { enabled: true }`, so the run prepares its own integration PR.
|
|
1288
1406
|
*
|
|
1289
1407
|
* Deliberately absent from every shape:
|
|
1290
1408
|
*
|
|
@@ -1306,6 +1424,12 @@ export function composeDefaultSetupEpicPolicy(args) {
|
|
|
1306
1424
|
if (!args.attended) {
|
|
1307
1425
|
policy.posture = SETUP_EPIC_UNATTENDED_POSTURE;
|
|
1308
1426
|
policy[SETUP_EPIC_AUTO_MERGE_POLICY_KEY] = true;
|
|
1427
|
+
if (args.mergeExecutionMode !== undefined) {
|
|
1428
|
+
policy[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] = args.mergeExecutionMode;
|
|
1429
|
+
}
|
|
1430
|
+
policy[SETUP_EPIC_INTEGRATION_PREPARATION_POLICY_KEY] = {
|
|
1431
|
+
...SETUP_EPIC_INTEGRATION_PREPARATION_DEFAULT,
|
|
1432
|
+
};
|
|
1309
1433
|
}
|
|
1310
1434
|
}
|
|
1311
1435
|
if (args.reviewPolicy !== undefined) {
|
|
@@ -1346,6 +1470,14 @@ export function composeDefaultSetupEpicPolicy(args) {
|
|
|
1346
1470
|
* * **`v2_auto_merge_enabled` already present** → left exactly as the file set
|
|
1347
1471
|
* it, including `false`. Filling the posture does not overwrite an explicit
|
|
1348
1472
|
* merge decision.
|
|
1473
|
+
* * **`merge_execution_mode` already present** (BAPI-1133, A2) → left exactly
|
|
1474
|
+
* as the file set it, valid or not. An explicitly present but invalid mode is
|
|
1475
|
+
* NOT silently repaired here; it is forwarded unchanged so the server's
|
|
1476
|
+
* secret-safe typed validation rejects it. Absent, it is filled from the
|
|
1477
|
+
* caller's already-resolved {@link TypedMergeExecutionMode} in the same
|
|
1478
|
+
* fill-if-absent branch that stamps `posture` and
|
|
1479
|
+
* {@link SETUP_EPIC_AUTO_MERGE_POLICY_KEY} — the server refuses an
|
|
1480
|
+
* unattended auto-merge policy that omits it.
|
|
1349
1481
|
*
|
|
1350
1482
|
* Pure: it returns a new object and never mutates the input. It is applied on the
|
|
1351
1483
|
* CREATE path only. A reused live run reconciles the file against its STORED
|
|
@@ -1365,6 +1497,15 @@ export function applySetupEpicUnattendedDefault(policy, args) {
|
|
|
1365
1497
|
if (!(SETUP_EPIC_AUTO_MERGE_POLICY_KEY in composed)) {
|
|
1366
1498
|
composed[SETUP_EPIC_AUTO_MERGE_POLICY_KEY] = true;
|
|
1367
1499
|
}
|
|
1500
|
+
if (!(SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY in composed) &&
|
|
1501
|
+
args.mergeExecutionMode !== undefined) {
|
|
1502
|
+
composed[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] = args.mergeExecutionMode;
|
|
1503
|
+
}
|
|
1504
|
+
if (!(SETUP_EPIC_INTEGRATION_PREPARATION_POLICY_KEY in composed)) {
|
|
1505
|
+
composed[SETUP_EPIC_INTEGRATION_PREPARATION_POLICY_KEY] = {
|
|
1506
|
+
...SETUP_EPIC_INTEGRATION_PREPARATION_DEFAULT,
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1368
1509
|
return composed;
|
|
1369
1510
|
}
|
|
1370
1511
|
/**
|
|
@@ -1554,14 +1695,51 @@ export function resolveEffectiveFeatureBranch(featureBranch, requestedPolicy) {
|
|
|
1554
1695
|
return trimmed;
|
|
1555
1696
|
}
|
|
1556
1697
|
/**
|
|
1557
|
-
*
|
|
1558
|
-
*
|
|
1698
|
+
* BAPI-1252: the operator sentence for each canonical refusal. Every one names
|
|
1699
|
+
* the situation and what to do; none names a SHA, generation, or server error.
|
|
1700
|
+
*/
|
|
1701
|
+
export function canonicalRefusalMessage(repoName, reason) {
|
|
1702
|
+
switch (reason) {
|
|
1703
|
+
case "canonical_capability_unsupported":
|
|
1704
|
+
return (`The Bridge server for ${repoName} did not report verified canonical index provenance ` +
|
|
1705
|
+
`(it predates this contract, or sent a value this CLI cannot verify). Upgrade the ` +
|
|
1706
|
+
`server; a cut is never authorized from the legacy indexed commit.`);
|
|
1707
|
+
case "canonical_index_unverified":
|
|
1708
|
+
return (`${SETUP_EPIC_PARSE_FIRST_MESSAGE}: the canonical index for ${repoName} has no verified ` +
|
|
1709
|
+
`generation (legacy, interrupted, or still-writing content is never trusted), so there ` +
|
|
1710
|
+
`is no commit to cut the feature branch at.`);
|
|
1711
|
+
case "canonical_source_missing":
|
|
1712
|
+
return (`${SETUP_EPIC_PARSE_FIRST_MESSAGE}: the last canonical parse of ${repoName} observed no ` +
|
|
1713
|
+
`source commit (for example it parsed a supplied directory), so its content cannot be ` +
|
|
1714
|
+
`verified. Re-parse it from its remote.`);
|
|
1715
|
+
case "canonical_manifest_mismatch":
|
|
1716
|
+
return (`The canonical index content for ${repoName} no longer matches its verified manifest ` +
|
|
1717
|
+
`and has been marked unverified. Re-parse the repository before cutting.`);
|
|
1718
|
+
case "canonical_generation_mismatch":
|
|
1719
|
+
case "canonical_index_advanced":
|
|
1720
|
+
return (`The canonical index for ${repoName} advanced while it was being checked. Retry once ` +
|
|
1721
|
+
`the current parse finishes.`);
|
|
1722
|
+
case "canonical_validity_unavailable":
|
|
1723
|
+
default:
|
|
1724
|
+
return (`The canonical validity for ${repoName} could not be read. Retry — this is not ` +
|
|
1725
|
+
`evidence the repository is unindexed.`);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Read canonical parse truth and require AFFIRMATIVE verified provenance — the
|
|
1730
|
+
* commit `origin/<feature>` will be cut at (BAPI-1252).
|
|
1731
|
+
*
|
|
1732
|
+
* The cut commit is the server's VERIFIED canonical source SHA, decoded through
|
|
1733
|
+
* `decodeCanonicalProvenance`. Lifecycle fields (`status`, `indexed_commit_sha`)
|
|
1734
|
+
* are display history and are never consulted: a `succeeded` row can describe a
|
|
1735
|
+
* newer-created parse whose content the index does not hold. An absent field
|
|
1736
|
+
* (an older server), an unknown state, a read failure, or an unverified
|
|
1737
|
+
* generation all refuse before any branch or run mutation.
|
|
1559
1738
|
*
|
|
1560
1739
|
* Fails CLOSED (ratified R-2). The first line of every refusal is the exact
|
|
1561
|
-
* operator instruction; the closing
|
|
1562
|
-
* UNREADABLE status is kept distinct from an
|
|
1563
|
-
* is not evidence the repository is unindexed
|
|
1564
|
-
* re-index would send them to re-parse a repository that may be fine.
|
|
1740
|
+
* operator instruction; the closing line states what did NOT happen. An
|
|
1741
|
+
* UNREADABLE status is kept distinct from an unverified one — "could not read"
|
|
1742
|
+
* is not evidence the repository is unindexed.
|
|
1565
1743
|
*/
|
|
1566
1744
|
export async function readCanonicalParseTruth(access, fetchImpl) {
|
|
1567
1745
|
const notCreated = [
|
|
@@ -1578,30 +1756,14 @@ export async function readCanonicalParseTruth(access, fetchImpl) {
|
|
|
1578
1756
|
],
|
|
1579
1757
|
};
|
|
1580
1758
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
ok: false,
|
|
1584
|
-
lines: [
|
|
1585
|
-
`${SETUP_EPIC_PARSE_FIRST_MESSAGE}: the canonical index for ${access.repoName} has no ` +
|
|
1586
|
-
`successful parse (status: ${String(parse.value.status)}), so there is no commit to ` +
|
|
1587
|
-
`cut the feature branch at.`,
|
|
1588
|
-
...notCreated,
|
|
1589
|
-
],
|
|
1590
|
-
};
|
|
1591
|
-
}
|
|
1592
|
-
const indexed = normalizeCommitSha(parse.value.indexed_commit_sha);
|
|
1593
|
-
if (indexed === null) {
|
|
1759
|
+
const evidence = decodeCanonicalProvenance(parse.value);
|
|
1760
|
+
if (!evidence.verified) {
|
|
1594
1761
|
return {
|
|
1595
1762
|
ok: false,
|
|
1596
|
-
lines: [
|
|
1597
|
-
`${SETUP_EPIC_PARSE_FIRST_MESSAGE}: the canonical index for ${access.repoName} published ` +
|
|
1598
|
-
`no commit for its last successful parse, so there is no commit to cut the feature ` +
|
|
1599
|
-
`branch at.`,
|
|
1600
|
-
...notCreated,
|
|
1601
|
-
],
|
|
1763
|
+
lines: [canonicalRefusalMessage(access.repoName, evidence.reason), ...notCreated],
|
|
1602
1764
|
};
|
|
1603
1765
|
}
|
|
1604
|
-
return { ok: true, cutCommitSha:
|
|
1766
|
+
return { ok: true, cutCommitSha: evidence.sourceCommitSha };
|
|
1605
1767
|
}
|
|
1606
1768
|
/** The strict subset of `SetupEpicDeps` the shared cut protocol consumes. */
|
|
1607
1769
|
function cutProtocolDeps(deps, runCommand) {
|
|
@@ -1836,7 +1998,10 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1836
1998
|
return 0;
|
|
1837
1999
|
}
|
|
1838
2000
|
// --- Resolve access ------------------------------------------------------
|
|
1839
|
-
|
|
2001
|
+
// BAPI-1260: a caller that selected the epic-launch contract (drive-epic
|
|
2002
|
+
// forwards --launch-profile / --base-url) gets the target-first resolver and
|
|
2003
|
+
// the bound credential; the legacy resolver is kept byte-for-byte otherwise.
|
|
2004
|
+
const accessDeps = {
|
|
1840
2005
|
env: deps.env,
|
|
1841
2006
|
cwd: deps.cwd,
|
|
1842
2007
|
homedir: deps.homedir,
|
|
@@ -1844,7 +2009,13 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1844
2009
|
readFile: deps.readFile,
|
|
1845
2010
|
stat: deps.stat,
|
|
1846
2011
|
repoName: opts.repo,
|
|
1847
|
-
}
|
|
2012
|
+
};
|
|
2013
|
+
const accessResult = opts.launchProfile !== undefined || opts.baseUrl !== undefined
|
|
2014
|
+
? await resolveEpicLaunchBridgeApiAccess({
|
|
2015
|
+
...(opts.launchProfile ? { profileName: opts.launchProfile } : {}),
|
|
2016
|
+
...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
|
|
2017
|
+
}, accessDeps)
|
|
2018
|
+
: await resolveConductorBridgeApiAccess(accessDeps);
|
|
1848
2019
|
if (!accessResult.ok) {
|
|
1849
2020
|
deps.errorLog(`Cannot reach the Bridge API: ${accessResult.error}`);
|
|
1850
2021
|
return 1;
|
|
@@ -1873,6 +2044,27 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1873
2044
|
// `base_branch` is honored exactly as `--feature-branch` would be. A malformed
|
|
1874
2045
|
// or unknown policy field is left for the server's strict validation.
|
|
1875
2046
|
const effectiveFeatureBranch = resolveEffectiveFeatureBranch(featureBranch, requestedPolicy);
|
|
2047
|
+
// --- Typed merge-execution-mode declaration (BAPI-1133, A2) ---------------
|
|
2048
|
+
// Resolved ONCE, here, before either composer runs, so the composer functions
|
|
2049
|
+
// stay pure and synchronous. Gated on the exact predicate both composers'
|
|
2050
|
+
// OWN fill-if-absent branches use — `!attended`, a feature branch selected,
|
|
2051
|
+
// and (for the file path) no `posture` already declared — so this never
|
|
2052
|
+
// performs the new configuration read for `--attended` or `--into-base` runs,
|
|
2053
|
+
// and never for a file that already answered the posture question itself.
|
|
2054
|
+
const unattendedDefaultEligible = !opts.attended &&
|
|
2055
|
+
effectiveFeatureBranch !== undefined &&
|
|
2056
|
+
(requestedPolicy === undefined || !("posture" in requestedPolicy));
|
|
2057
|
+
let declaredMergeExecutionMode;
|
|
2058
|
+
if (unattendedDefaultEligible) {
|
|
2059
|
+
const mergeExecutionModeResolverDeps = {
|
|
2060
|
+
repoName: access.repoName,
|
|
2061
|
+
buildGetUrl: (path, params) => buildConductorJiraUrl(access.baseUrl, path, params),
|
|
2062
|
+
getHeaders: async () => conductorGetHeaders(access),
|
|
2063
|
+
fetchImpl: deps.fetch,
|
|
2064
|
+
};
|
|
2065
|
+
const effectiveLegacyMode = await resolveMergeExecutionMode(mergeExecutionModeResolverDeps);
|
|
2066
|
+
declaredMergeExecutionMode = mapLegacyMergeExecutionModeToPolicyMode(effectiveLegacyMode);
|
|
2067
|
+
}
|
|
1876
2068
|
// --- The policy the CREATE path will send (BAPI-1102) ---------------------
|
|
1877
2069
|
// Composed HERE, as soon as both of its inputs exist, so the one object that
|
|
1878
2070
|
// gets reported, gated, and sent is the same object throughout. It is consumed
|
|
@@ -1883,12 +2075,30 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1883
2075
|
? applySetupEpicUnattendedDefault(requestedPolicy, {
|
|
1884
2076
|
featureBranch: effectiveFeatureBranch,
|
|
1885
2077
|
attended: opts.attended,
|
|
2078
|
+
mergeExecutionMode: declaredMergeExecutionMode,
|
|
1886
2079
|
})
|
|
1887
2080
|
: composeDefaultSetupEpicPolicy({
|
|
1888
2081
|
featureBranch,
|
|
1889
2082
|
attended: opts.attended,
|
|
1890
2083
|
reviewPolicy: opts.reviewPolicy,
|
|
2084
|
+
mergeExecutionMode: declaredMergeExecutionMode,
|
|
1891
2085
|
});
|
|
2086
|
+
// BAPI-1133 (A2). Reported only when THIS command stamped the key — i.e. it
|
|
2087
|
+
// is present in the composed policy and was absent from whatever the
|
|
2088
|
+
// operator supplied — as low-emphasis policy metadata, mirroring the
|
|
2089
|
+
// existing posture/auto-merge reporting rather than adding a new prompt,
|
|
2090
|
+
// wizard, or runtime status. An explicitly declared mode (CLI-composed
|
|
2091
|
+
// policy already carrying the key) is never reported here as if it were
|
|
2092
|
+
// this command's decision.
|
|
2093
|
+
const mergeExecutionModeStamped = declaredMergeExecutionMode !== undefined &&
|
|
2094
|
+
composedForCreate[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] === declaredMergeExecutionMode &&
|
|
2095
|
+
(requestedPolicy === undefined ||
|
|
2096
|
+
!(SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY in requestedPolicy));
|
|
2097
|
+
if (mergeExecutionModeStamped) {
|
|
2098
|
+
say(`Policy: merge_execution_mode: ${declaredMergeExecutionMode} (declared automatically ` +
|
|
2099
|
+
`for the unattended default; policy metadata only — no dispatch, actuation, eligibility, ` +
|
|
2100
|
+
`containment, or escalation behavior until a later A2 child)`);
|
|
2101
|
+
}
|
|
1892
2102
|
if (requestedPolicy !== undefined) {
|
|
1893
2103
|
// The posture is stated for the FILE path too, and read off the composed
|
|
1894
2104
|
// object rather than re-derived. The `--policy-file` invocation is the one
|
|
@@ -2430,6 +2640,21 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2430
2640
|
deps.errorLog(`setup-epic REFUSED (${bringUpRefusal.reason}): ${bringUpRefusal.message}`);
|
|
2431
2641
|
return 1;
|
|
2432
2642
|
}
|
|
2643
|
+
// BAPI-1135 — merge-execution eligibility, AFTER the universal bring-up
|
|
2644
|
+
// blockers (an unreadable readiness report and a behind database make this
|
|
2645
|
+
// question moot) and BEFORE the unattended prerequisites and every
|
|
2646
|
+
// approval check below. That position is what keeps readiness, this gate,
|
|
2647
|
+
// and the approval refusal naming the SAME first blocker.
|
|
2648
|
+
//
|
|
2649
|
+
// Outside the `requestedPolicyIsUnattended` branch on purpose: an
|
|
2650
|
+
// `--attended` run that declares `github_actions` mode is gated too. The
|
|
2651
|
+
// evaluator itself returns null for every policy that does not declare the
|
|
2652
|
+
// mode, so no other run's behavior changes.
|
|
2653
|
+
const mergeExecutionRefusal = evaluateSetupEpicMergeExecutionPrerequisites(readiness, composedForCreate);
|
|
2654
|
+
if (mergeExecutionRefusal !== null) {
|
|
2655
|
+
deps.errorLog(`setup-epic REFUSED (${mergeExecutionRefusal.reason}): ${mergeExecutionRefusal.message}`);
|
|
2656
|
+
return 1;
|
|
2657
|
+
}
|
|
2433
2658
|
if (requestedPolicyIsUnattended(composedForCreate, opts.attended)) {
|
|
2434
2659
|
// The COMPOSED policy, not the flags: a --policy-file declaring
|
|
2435
2660
|
// notify.webhook_url satisfies the escalation-channel prerequisite, and
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
|
|
2
|
-
export const VERSION = "0.2.
|
|
3
|
-
export const BUILD_COMMIT = "
|
|
4
|
-
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
2
|
+
export const VERSION = "0.2.62";
|
|
3
|
+
export const BUILD_COMMIT = "9351fac413bf";
|
|
4
|
+
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.62", "serve"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge_gpt/mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.62",
|
|
4
4
|
"description": "Bridge API MCP server — exposes Jira endpoints as MCP tools for Claude Code agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"//test": "The `test` script delegates to the bounded launcher (scripts/run-unit-tests.js → src/run-unit-tests-launcher.ts, BAPI-683), which discovers build/*.test.js at runtime and spawns them in size-bounded batches so the argv never exceeds Windows' ~8191-char command-line limit. Two determinism rules from the pre-launcher script are preserved INSIDE the launcher: (1) multi-file batches run WITHOUT --test-force-exit, because that flag makes node:test exit before aggregating per-subprocess summaries and the tally becomes nondeterministic (failures still surface and still set exit 1, but the count cannot be trusted as a completeness signal); (2) build/secret-safety.test.js leaks a handle that holds the event loop ~60s after its tests finish (they run in ~147ms), so the launcher quarantines it into its own single-file --test-force-exit invocation (FORCE_EXIT_QUARANTINE in src/run-unit-tests-launcher.ts) where the tally stays exact. Do NOT add --test-force-exit to batched invocations, and do NOT remove the quarantine, until the underlying handle leak is fixed.",
|
|
27
27
|
"//version": "BAPI-930: fired by `npm version <level>` AFTER npm rewrites package.json's version field, so the bumped version and the repinned README land in the SAME commit. A bump performed by hand-editing package.json instead of running `npm version` bypasses this hook entirely and must repin manually via `node scripts/sync-readme-host-examples.js` — the `--check` wired into `build` below is the backstop that catches that case.",
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && node scripts/bundle-agent-guidance.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
|
|
29
|
+
"build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && node scripts/bundle-agent-guidance.js && tsc && node scripts/sync-readme-merge-workflow.js --check && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
|
|
30
30
|
"version": "node scripts/sync-readme-host-examples.js --write && git add -- README.md package.json package-lock.json && git commit -m \"chore(mcp-server): release $npm_package_version\" && git tag \"mcp-server/v$npm_package_version\"",
|
|
31
31
|
"check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
|
|
32
32
|
"assert:release-identity": "node scripts/assert-release-identity.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"playwright": "^1.51.1"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@types/better-sqlite3": "^
|
|
54
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
55
55
|
"@types/node": "^26.0.1",
|
|
56
56
|
"@types/pngjs": "^6.0.5",
|
|
57
57
|
"esbuild": "^0.28.1",
|