@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.
Files changed (103) hide show
  1. package/README.md +116 -56
  2. package/build/agent-guidance.generated.js +3 -0
  3. package/build/agent-launchers/claude-executor-adapter.js +3 -0
  4. package/build/agent-launchers/claude.js +3 -3
  5. package/build/agent-launchers/prompt.js +8 -11
  6. package/build/agent-notes.js +178 -0
  7. package/build/agent-registry.js +5 -2
  8. package/build/agent-utils.js +58 -0
  9. package/build/agents.generated.js +1 -1
  10. package/build/base-ref.js +33 -9
  11. package/build/bounded-wait.js +174 -0
  12. package/build/codex-skill-adapter.js +55 -0
  13. package/build/commands.generated.js +6 -5
  14. package/build/conductor/bridge-api-client.js +235 -14
  15. package/build/conductor/bring-up-facts.js +187 -0
  16. package/build/conductor/claude-hook.js +7 -5
  17. package/build/conductor/cli.js +28 -0
  18. package/build/conductor/doctor.js +80 -9
  19. package/build/conductor/epic-implementer-cli.js +1298 -0
  20. package/build/conductor/epic-runtime.js +134 -98
  21. package/build/conductor/errors.js +2 -2
  22. package/build/conductor/git-ci-types.js +1 -1
  23. package/build/conductor/git-hooks.js +28 -14
  24. package/build/conductor/install-doctor.js +11 -5
  25. package/build/conductor/readiness-cli.js +10 -10
  26. package/build/conductor/readiness-sections.js +58 -9
  27. package/build/conductor/readiness.js +120 -4
  28. package/build/conductor/recovery-cli.js +289 -10
  29. package/build/conductor/recovery-operations.js +125 -2
  30. package/build/conductor/repair-contract.js +58 -0
  31. package/build/conductor/run-branch.js +137 -0
  32. package/build/conductor/store.js +2 -2
  33. package/build/conductor/supervisor-runtime.js +1 -1
  34. package/build/conductor/test-run-branch-vectors.js +165 -0
  35. package/build/conductor-bin.js +2 -139
  36. package/build/conductor-claude-hook-bin.js +2 -2
  37. package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
  38. package/build/conductor-removed-stub-bin.js +30 -0
  39. package/build/docs.generated.js +1 -1
  40. package/build/doctor.js +145 -18
  41. package/build/drive-epic.js +752 -90
  42. package/build/epic-implementer-bin.js +145 -0
  43. package/build/epic-implementer-bundle-cli.js +264 -0
  44. package/build/epic-implementer-claude-hook-bin.js +3 -0
  45. package/build/epic-integration-pr.js +5 -3
  46. package/build/executor/claim-scope.js +104 -0
  47. package/build/executor/cli.js +14 -25
  48. package/build/executor/env-file-guard.js +82 -3
  49. package/build/executor/env.js +6 -0
  50. package/build/executor/interrupted-worktree.js +60 -0
  51. package/build/executor/job-errors.js +45 -0
  52. package/build/executor/job-runner.js +334 -9
  53. package/build/executor/job-types.js +25 -9
  54. package/build/executor/merge-tree-classifier.js +171 -0
  55. package/build/executor/reconcile-overlap-governance.js +129 -0
  56. package/build/executor/reconcile-overlap-job.js +989 -0
  57. package/build/executor/reconcile-overlap-types.js +14 -0
  58. package/build/executor/spawn-job-driver.js +1 -0
  59. package/build/executor/types.js +2 -0
  60. package/build/executor/worker-finalization.js +25 -2
  61. package/build/executor/worker-guard-hook.js +15 -7
  62. package/build/implement-epic/bridge-client.js +773 -0
  63. package/build/implement-epic/checkpoint-store.js +542 -0
  64. package/build/implement-epic/cli.js +3158 -0
  65. package/build/implement-epic/cut-protocol.js +392 -0
  66. package/build/implement-epic/lock.js +302 -0
  67. package/build/implement-epic/pr-state.js +286 -0
  68. package/build/implement-epic/spawn.js +113 -0
  69. package/build/index.js +657 -481
  70. package/build/init.js +72 -8
  71. package/build/install-bridge-conductor.js +5 -5
  72. package/build/install-bridge.js +403 -70
  73. package/build/local-artifact-storage.js +130 -0
  74. package/build/mcp-host-config.js +22 -60
  75. package/build/mcp-host-entry-adapter.js +18 -0
  76. package/build/mcp-host-targets.js +1 -21
  77. package/build/merge-pull-request.js +1 -1
  78. package/build/pipelines.generated.js +23 -16
  79. package/build/plan-epic-conductor-eligibility.js +1 -1
  80. package/build/plane/cli.js +321 -41
  81. package/build/plane/manifest.js +209 -1
  82. package/build/plane/member-roster.js +70 -0
  83. package/build/plane/preflight.js +128 -12
  84. package/build/plane/shutdown.js +18 -5
  85. package/build/plane/status.js +35 -1
  86. package/build/plane/supervisor.js +546 -164
  87. package/build/plane/types.js +25 -2
  88. package/build/polling-policy.js +72 -0
  89. package/build/readiness-check.js +3 -3
  90. package/build/readme.generated.js +1 -1
  91. package/build/review-generation.js +219 -0
  92. package/build/run-unit-tests-launcher.js +6 -1
  93. package/build/setup-epic.js +567 -38
  94. package/build/start-tickets-conductor.js +8 -7
  95. package/build/ticket-key-utils.js +4 -3
  96. package/build/ticket-review-artifact-gate.js +461 -0
  97. package/build/upgrade-cli.js +5 -26
  98. package/build/version.generated.js +3 -3
  99. package/build/worker-guard-hook-bin.js +1 -1
  100. package/docs/CONDUCTOR.md +8 -6
  101. package/docs/install/mcp-tool-integrations.md +23 -1
  102. package/package.json +5 -3
  103. 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",