@bridge_gpt/mcp-server 0.2.53 → 0.2.55
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 +116 -56
- package/build/agent-guidance.generated.js +3 -0
- package/build/agent-launchers/claude-executor-adapter.js +3 -0
- package/build/agent-launchers/claude.js +3 -3
- package/build/agent-launchers/prompt.js +8 -11
- package/build/agent-notes.js +178 -0
- package/build/agent-registry.js +5 -2
- package/build/agent-utils.js +58 -0
- package/build/agents.generated.js +1 -1
- package/build/base-ref.js +33 -9
- package/build/bounded-wait.js +174 -0
- package/build/codex-skill-adapter.js +55 -0
- package/build/commands.generated.js +6 -5
- package/build/conductor/bridge-api-client.js +235 -14
- package/build/conductor/bring-up-facts.js +187 -0
- package/build/conductor/claude-hook.js +7 -5
- package/build/conductor/cli.js +28 -0
- package/build/conductor/doctor.js +80 -9
- package/build/conductor/epic-implementer-cli.js +1298 -0
- package/build/conductor/epic-runtime.js +134 -98
- package/build/conductor/errors.js +2 -2
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-hooks.js +28 -14
- package/build/conductor/install-doctor.js +11 -5
- package/build/conductor/readiness-cli.js +10 -10
- package/build/conductor/readiness-sections.js +58 -9
- package/build/conductor/readiness.js +120 -4
- package/build/conductor/recovery-cli.js +289 -10
- package/build/conductor/recovery-operations.js +125 -2
- package/build/conductor/repair-contract.js +58 -0
- package/build/conductor/run-branch.js +137 -0
- package/build/conductor/store.js +2 -2
- package/build/conductor/supervisor-runtime.js +1 -1
- package/build/conductor/test-run-branch-vectors.js +165 -0
- package/build/conductor-bin.js +2 -139
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
- package/build/conductor-removed-stub-bin.js +30 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +145 -18
- package/build/drive-epic.js +752 -90
- package/build/epic-implementer-bin.js +145 -0
- package/build/epic-implementer-bundle-cli.js +264 -0
- package/build/epic-implementer-claude-hook-bin.js +3 -0
- package/build/epic-integration-pr.js +5 -3
- package/build/executor/claim-scope.js +104 -0
- package/build/executor/cli.js +14 -25
- package/build/executor/env-file-guard.js +82 -3
- package/build/executor/env.js +6 -0
- package/build/executor/interrupted-worktree.js +60 -0
- package/build/executor/job-errors.js +45 -0
- package/build/executor/job-runner.js +334 -9
- package/build/executor/job-types.js +25 -9
- package/build/executor/merge-tree-classifier.js +171 -0
- package/build/executor/reconcile-overlap-governance.js +129 -0
- package/build/executor/reconcile-overlap-job.js +989 -0
- package/build/executor/reconcile-overlap-types.js +14 -0
- package/build/executor/spawn-job-driver.js +1 -0
- package/build/executor/types.js +2 -0
- package/build/executor/worker-finalization.js +25 -2
- package/build/executor/worker-guard-hook.js +15 -7
- package/build/implement-epic/bridge-client.js +773 -0
- package/build/implement-epic/checkpoint-store.js +542 -0
- package/build/implement-epic/cli.js +3158 -0
- package/build/implement-epic/cut-protocol.js +392 -0
- package/build/implement-epic/lock.js +302 -0
- package/build/implement-epic/pr-state.js +286 -0
- package/build/implement-epic/spawn.js +113 -0
- package/build/index.js +657 -481
- package/build/init.js +72 -8
- package/build/install-bridge-conductor.js +5 -5
- package/build/install-bridge.js +403 -70
- package/build/local-artifact-storage.js +130 -0
- package/build/mcp-host-config.js +22 -60
- package/build/mcp-host-entry-adapter.js +18 -0
- package/build/mcp-host-targets.js +1 -21
- package/build/merge-pull-request.js +1 -1
- package/build/pipelines.generated.js +23 -16
- package/build/plan-epic-conductor-eligibility.js +1 -1
- package/build/plane/cli.js +321 -41
- package/build/plane/manifest.js +209 -1
- package/build/plane/member-roster.js +70 -0
- package/build/plane/preflight.js +128 -12
- package/build/plane/shutdown.js +18 -5
- package/build/plane/status.js +35 -1
- package/build/plane/supervisor.js +546 -164
- package/build/plane/types.js +25 -2
- package/build/polling-policy.js +72 -0
- package/build/readiness-check.js +3 -3
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +219 -0
- package/build/run-unit-tests-launcher.js +6 -1
- package/build/setup-epic.js +567 -38
- package/build/start-tickets-conductor.js +8 -7
- package/build/ticket-key-utils.js +4 -3
- package/build/ticket-review-artifact-gate.js +461 -0
- package/build/upgrade-cli.js +5 -26
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +1 -1
- package/docs/CONDUCTOR.md +8 -6
- package/docs/install/mcp-tool-integrations.md +23 -1
- package/package.json +5 -3
- package/pipelines/review-ticket.json +17 -4
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-generation correlation for the resumable ticket-review wait (BAPI-1104, R77).
|
|
3
|
+
*
|
|
4
|
+
* THE GAP THIS CLOSES. `wait_for_ticket_review` holds no claim token, so on its
|
|
5
|
+
* own it cannot tell "the review I just submitted finished" from "a review from
|
|
6
|
+
* last week is still attached". The backend's `202`-while-generating branch looks
|
|
7
|
+
* like it settles that, but it does not: the pending branch is gated on
|
|
8
|
+
* `generating_doc_type_set_at` being inside `_GENERATING_TTL_SECONDS`
|
|
9
|
+
* (`api/routes/jira_api.py:2977-2996`), and a review that outlives the TTL falls
|
|
10
|
+
* straight through to the attachment lookup and returns the PRIOR document as a
|
|
11
|
+
* `200`. A worker that resolved against that would write a verdict for a review
|
|
12
|
+
* that never happened — the exact class of failure this ticket exists to remove.
|
|
13
|
+
*
|
|
14
|
+
* WHAT MAKES IT CLOSEABLE WITHOUT A ROUTE CHANGE. Every delivered review
|
|
15
|
+
* document already carries a terminal `bridge-review-decision-identity` footer
|
|
16
|
+
* with a monotonic `generation` integer (stamped by
|
|
17
|
+
* `api/library/review_decision_integrity.py`). So the client can record what was
|
|
18
|
+
* attached at submission time and refuse a `200` whose generation has not
|
|
19
|
+
* advanced past it. The ticket forbids touching the route; this needs nothing
|
|
20
|
+
* from it.
|
|
21
|
+
*
|
|
22
|
+
* WHAT IS STORED. Normalized repository and ticket identity plus two small
|
|
23
|
+
* integers. Never a credential, a header, a URL carrying a query secret, a
|
|
24
|
+
* response body, or exception text — the registry is in-process, bounded, and
|
|
25
|
+
* deliberately holds nothing worth leaking.
|
|
26
|
+
*/
|
|
27
|
+
/** The identity footer's marker, mirroring `review_decision_integrity.py:44`. */
|
|
28
|
+
const REVIEW_DECISION_IDENTITY_MARKER = "bridge-review-decision-identity";
|
|
29
|
+
/**
|
|
30
|
+
* Matches the footer at the TERMINAL end of a document — the one position the
|
|
31
|
+
* stamper writes it. The payload is "any character that does not begin a `-->`"
|
|
32
|
+
* rather than a lazy `.*?` for the same reason the Python side spells it that
|
|
33
|
+
* way: a lazy payload backtracks across an intervening `-->` and swallows two
|
|
34
|
+
* footers as one.
|
|
35
|
+
*/
|
|
36
|
+
const IDENTITY_TERMINAL_RE = new RegExp(`<!--\\s*${REVIEW_DECISION_IDENTITY_MARKER}\\s*:((?:(?!-->)[\\s\\S])*)-->\\s*$`);
|
|
37
|
+
/**
|
|
38
|
+
* The generation integer from a delivered review document, or `null`.
|
|
39
|
+
*
|
|
40
|
+
* `null` means "this document carries no readable identity" — an unstamped
|
|
41
|
+
* legacy attachment, or a body that is not the document at all. The caller
|
|
42
|
+
* treats that as NOT-advanced rather than as advanced, because an unreadable
|
|
43
|
+
* identity is not evidence that a new review landed.
|
|
44
|
+
*/
|
|
45
|
+
export function parseReviewGeneration(markdown) {
|
|
46
|
+
if (typeof markdown !== "string")
|
|
47
|
+
return null;
|
|
48
|
+
const match = IDENTITY_TERMINAL_RE.exec(markdown);
|
|
49
|
+
if (!match)
|
|
50
|
+
return null;
|
|
51
|
+
let payload;
|
|
52
|
+
try {
|
|
53
|
+
payload = JSON.parse(match[1]);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (payload === null || typeof payload !== "object")
|
|
59
|
+
return null;
|
|
60
|
+
const generation = payload.generation;
|
|
61
|
+
if (typeof generation !== "number" || !Number.isInteger(generation))
|
|
62
|
+
return null;
|
|
63
|
+
return generation;
|
|
64
|
+
}
|
|
65
|
+
/** Valid domain for `expectedLegCount`: a review has at most two legs. */
|
|
66
|
+
function isValidExpectedLegCount(value) {
|
|
67
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 2;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* True when `delivered` is a strictly newer generation than `baseline`.
|
|
71
|
+
*
|
|
72
|
+
* An absent baseline (`null`) means nothing was attached at submission, so any
|
|
73
|
+
* readable generation is an advance. An unreadable delivered generation is never
|
|
74
|
+
* an advance — see {@link parseReviewGeneration}.
|
|
75
|
+
*/
|
|
76
|
+
export function generationAdvanced(baseline, delivered) {
|
|
77
|
+
if (delivered === null)
|
|
78
|
+
return false;
|
|
79
|
+
if (baseline === null)
|
|
80
|
+
return true;
|
|
81
|
+
return delivered > baseline;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Normalized registry key. Case-folded so two spellings cannot split an entry.
|
|
85
|
+
*
|
|
86
|
+
* The separator is NUL because it cannot occur in a repository name or a ticket
|
|
87
|
+
* key, so no pair of inputs can collide by straddling it. It is written as an
|
|
88
|
+
* ESCAPE rather than as a literal byte deliberately: a raw NUL makes git treat
|
|
89
|
+
* the whole file as binary, which renders it as `+0/-0` in a pull request and
|
|
90
|
+
* makes the generation-correlation logic in this file unreviewable. The runtime
|
|
91
|
+
* string is identical either way.
|
|
92
|
+
*/
|
|
93
|
+
const KEY_SEPARATOR = "\u0000";
|
|
94
|
+
function baselineKey(repoName, ticketNumber) {
|
|
95
|
+
return `${repoName.trim().toLowerCase()}${KEY_SEPARATOR}${ticketNumber.trim().toUpperCase()}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* How long one submission baseline stays usable.
|
|
99
|
+
*
|
|
100
|
+
* Sized against the consumer, not the producer: a spec-review worker waits in
|
|
101
|
+
* repeated 240-second windows under a 120-minute job timeout
|
|
102
|
+
* (`job_registry.py`), so a baseline must outlive that whole span or the wait it
|
|
103
|
+
* exists to correlate would lose its anchor partway through and start accepting
|
|
104
|
+
* whatever is attached. Two hours covers it with margin.
|
|
105
|
+
*/
|
|
106
|
+
const BASELINE_TTL_MS = 2 * 60 * 60 * 1000;
|
|
107
|
+
/**
|
|
108
|
+
* Cap on live baselines. One MCP server process reviews a handful of tickets at
|
|
109
|
+
* a time; the bound is what keeps a long-lived process from accumulating an
|
|
110
|
+
* entry per ticket it ever touched.
|
|
111
|
+
*/
|
|
112
|
+
const MAX_BASELINES = 64;
|
|
113
|
+
/**
|
|
114
|
+
* In-process, bounded submission-generation registry.
|
|
115
|
+
*
|
|
116
|
+
* In-process is the right scope and not a compromise: the baseline correlates
|
|
117
|
+
* ONE submission with the waits that follow it in the same MCP server, which is
|
|
118
|
+
* exactly the lifetime a recipe run has. Persisting it would make a stale row
|
|
119
|
+
* from a crashed run able to block a fresh review — strictly worse than losing
|
|
120
|
+
* the anchor, which merely degrades to today's behavior.
|
|
121
|
+
*
|
|
122
|
+
* BAPI-1121: an entry's `expectedLegCount` diagnostic is cleared only once the
|
|
123
|
+
* artifact gate has persisted its accepted artifacts and handled any
|
|
124
|
+
* withheld canonical file — see `clear()`. A rejected or indeterminate
|
|
125
|
+
* submission still drops its entry immediately (`clear()` from the trigger
|
|
126
|
+
* failure/non-OK paths), so an unaccepted submission can never wedge a later
|
|
127
|
+
* wait on stale diagnostic metadata.
|
|
128
|
+
*
|
|
129
|
+
* BAPI-1129: the submission context serves BOTH consumers of the gate — the
|
|
130
|
+
* blocking `request_ticket_review(wait_for_result: true)` call that reaches
|
|
131
|
+
* it inline, and any subsequent explicit `wait_for_ticket_review` in the same
|
|
132
|
+
* process. That is why a blocking call which wrote nothing (a zero-advance
|
|
133
|
+
* `200`, a classification-only `save_locally: false` call, a parse or
|
|
134
|
+
* persistence failure) must leave the entry alone: the review it anchored may
|
|
135
|
+
* still be completing, and the follow-up wait needs this anchor to prove it.
|
|
136
|
+
*/
|
|
137
|
+
export class ReviewGenerationRegistry {
|
|
138
|
+
now;
|
|
139
|
+
entries = new Map();
|
|
140
|
+
constructor(now = () => Date.now()) {
|
|
141
|
+
this.now = now;
|
|
142
|
+
}
|
|
143
|
+
/** Record what was attached when a review was submitted. */
|
|
144
|
+
record(repoName, ticketNumber, baseline) {
|
|
145
|
+
this.evictExpired();
|
|
146
|
+
const key = baselineKey(repoName, ticketNumber);
|
|
147
|
+
// Delete-then-set so the insertion order Map iteration gives us is also
|
|
148
|
+
// recency order, which is what makes the eviction below drop the oldest.
|
|
149
|
+
this.entries.delete(key);
|
|
150
|
+
this.entries.set(key, { baseline, recordedAtMs: this.now() });
|
|
151
|
+
while (this.entries.size > MAX_BASELINES) {
|
|
152
|
+
const oldest = this.entries.keys().next();
|
|
153
|
+
if (oldest.done)
|
|
154
|
+
break;
|
|
155
|
+
this.entries.delete(oldest.value);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** The recorded baseline, or `null` when none is live for this ticket. */
|
|
159
|
+
get(repoName, ticketNumber) {
|
|
160
|
+
this.evictExpired();
|
|
161
|
+
return this.entries.get(baselineKey(repoName, ticketNumber))?.baseline ?? null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Attach the accepted submission's `expected_leg_count` diagnostic to the
|
|
165
|
+
* live entry (BAPI-1121). Called only AFTER the generate POST is accepted —
|
|
166
|
+
* the pre-POST baseline recording order above is unchanged.
|
|
167
|
+
*
|
|
168
|
+
* Silently a no-op (never poisons state) when there is no live entry to
|
|
169
|
+
* attach to, or when `expectedLegCount` fails validation (non-integer,
|
|
170
|
+
* negative, or outside the 0-2 domain a two-leg review allows).
|
|
171
|
+
*/
|
|
172
|
+
attachExpectedLegCount(repoName, ticketNumber, expectedLegCount) {
|
|
173
|
+
this.evictExpired();
|
|
174
|
+
if (!isValidExpectedLegCount(expectedLegCount))
|
|
175
|
+
return;
|
|
176
|
+
const key = baselineKey(repoName, ticketNumber);
|
|
177
|
+
const entry = this.entries.get(key);
|
|
178
|
+
if (!entry)
|
|
179
|
+
return;
|
|
180
|
+
entry.expectedLegCount = expectedLegCount;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The full submission context for the wait: the original baseline plus any
|
|
184
|
+
* attached diagnostic. Returns `null` when no baseline is live — same
|
|
185
|
+
* absence contract as `get()`. `expectedLegCount` is `undefined` in the
|
|
186
|
+
* returned context when none was attached, so callers can distinguish "not
|
|
187
|
+
* supplied" from "supplied as zero".
|
|
188
|
+
*/
|
|
189
|
+
getContext(repoName, ticketNumber) {
|
|
190
|
+
this.evictExpired();
|
|
191
|
+
const entry = this.entries.get(baselineKey(repoName, ticketNumber));
|
|
192
|
+
if (!entry)
|
|
193
|
+
return null;
|
|
194
|
+
return { baseline: entry.baseline, expectedLegCount: entry.expectedLegCount };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Consume a baseline. Clearing means "durable persistence has happened",
|
|
198
|
+
* not "a terminal envelope was received" (BAPI-1129) — the anchor is
|
|
199
|
+
* dropped only after the artifact gate has actually written at least one
|
|
200
|
+
* accepted leg. Receiving a terminal `200` proves nothing landed on disk,
|
|
201
|
+
* and a baseline dropped at that moment would downgrade the next
|
|
202
|
+
* `wait_for_ticket_review` in this process to `terminality_only`, which is
|
|
203
|
+
* the degraded mode the anchor exists to avoid.
|
|
204
|
+
*
|
|
205
|
+
* The one exception is the pre-acceptance case: a submission POST that
|
|
206
|
+
* failed or was rejected started no review at all, so its anchor is dropped
|
|
207
|
+
* immediately (see `requestTicketReview`'s trigger-failure paths).
|
|
208
|
+
*/
|
|
209
|
+
clear(repoName, ticketNumber) {
|
|
210
|
+
this.entries.delete(baselineKey(repoName, ticketNumber));
|
|
211
|
+
}
|
|
212
|
+
evictExpired() {
|
|
213
|
+
const cutoff = this.now() - BASELINE_TTL_MS;
|
|
214
|
+
for (const [key, entry] of this.entries) {
|
|
215
|
+
if (entry.recordedAtMs <= cutoff)
|
|
216
|
+
this.entries.delete(key);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -49,8 +49,13 @@ export const MODULE_MOCK_MANIFEST = [
|
|
|
49
49
|
"build/attachment-download.test.js",
|
|
50
50
|
"build/attachment-upload.test.js",
|
|
51
51
|
"build/automation-progress.test.js",
|
|
52
|
+
// BAPI-1104: wait_for_ci_checks drives the real registered handler through the
|
|
53
|
+
// SDK-capture seam, so it needs --experimental-test-module-mocks. Without an
|
|
54
|
+
// entry here it would run in the NORMAL lane, where `mock.module` does not
|
|
55
|
+
// exist — the failure mode this manifest is for.
|
|
56
|
+
"build/ci-check-wait.test.js",
|
|
52
57
|
"build/conductor/cli-git-hooks.test.js",
|
|
53
|
-
"build/conductor/cli.test.js",
|
|
58
|
+
"build/conductor/epic-implementer-cli.test.js",
|
|
54
59
|
"build/conductor/git-inspection.test.js",
|
|
55
60
|
"build/conductor/paths.test.js",
|
|
56
61
|
"build/conductor/pr-ci-producer-emit-seam.test.js",
|