@bridge_gpt/mcp-server 0.2.18 → 0.2.19

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 (81) hide show
  1. package/CONDUCTOR.md +75 -0
  2. package/README.md +2 -2
  3. package/build/agent-capabilities/probe-context.js +13 -3
  4. package/build/agent-capabilities/probes.js +262 -11
  5. package/build/agent-capabilities/reporter.js +1 -0
  6. package/build/agents.generated.js +1 -1
  7. package/build/backend-warnings.js +44 -0
  8. package/build/claude-settings.js +129 -0
  9. package/build/commands.generated.js +1 -0
  10. package/build/conductor/bridge-api-client.js +7 -7
  11. package/build/conductor/cli.js +65 -12
  12. package/build/conductor/deny-enforcement-preflight.js +96 -0
  13. package/build/conductor/doctor.js +183 -2
  14. package/build/conductor/epic-reconcile.js +9 -1
  15. package/build/conductor/epic-runtime.js +403 -43
  16. package/build/conductor/epic-state.js +7 -0
  17. package/build/conductor/errors.js +115 -3
  18. package/build/conductor/event-accessors.js +28 -10
  19. package/build/conductor/merge-ledger.js +6 -4
  20. package/build/conductor/pr-ci-producer.js +17 -2
  21. package/build/conductor/producer-ledger.js +1 -1
  22. package/build/conductor/store.js +161 -18
  23. package/build/conductor/supervisor-merge.js +32 -5
  24. package/build/conductor/taxonomy.js +8 -0
  25. package/build/conductor/tools.js +28 -6
  26. package/build/conductor/worker-ledger-cli.js +244 -0
  27. package/build/conductor-bin.js +1884 -6917
  28. package/build/doctor.js +8 -0
  29. package/build/executor/cli.js +229 -0
  30. package/build/executor/credentials.js +65 -0
  31. package/build/executor/deps.js +117 -0
  32. package/build/executor/env.js +79 -0
  33. package/build/executor/heartbeat.js +59 -0
  34. package/build/executor/http-client.js +131 -0
  35. package/build/executor/index.js +10 -0
  36. package/build/executor/job-errors.js +55 -0
  37. package/build/executor/job-log-registry.js +110 -0
  38. package/build/executor/job-runner.js +688 -0
  39. package/build/executor/job-types.js +60 -0
  40. package/build/executor/merge-job.js +155 -0
  41. package/build/executor/observation.js +123 -0
  42. package/build/executor/permissions.js +79 -0
  43. package/build/executor/preflight.js +144 -0
  44. package/build/executor/process.js +81 -0
  45. package/build/executor/prompt-spec.js +235 -0
  46. package/build/executor/results.js +134 -0
  47. package/build/executor/resume-pre-spawn.js +179 -0
  48. package/build/executor/runner.js +98 -0
  49. package/build/executor/terminal-mutation.js +34 -0
  50. package/build/executor/test-clock.js +109 -0
  51. package/build/executor/types.js +18 -0
  52. package/build/executor/verdict-artifact.js +53 -0
  53. package/build/executor/viewer-tabs.js +78 -0
  54. package/build/executor/watch-cli.js +113 -0
  55. package/build/executor/worker-command.js +106 -0
  56. package/build/executor/worker-finalization.js +97 -0
  57. package/build/executor/worker-log.js +92 -0
  58. package/build/executor/worktree-gc.js +134 -0
  59. package/build/executor/worktree-inspection.js +86 -0
  60. package/build/executor/worktree.js +103 -0
  61. package/build/index.js +11222 -8544
  62. package/build/mcp-invoke.js +19 -3
  63. package/build/mcp-provisioning.js +31 -25
  64. package/build/mcp-registration-doctor.js +27 -7
  65. package/build/mcp-server-invocation.js +152 -0
  66. package/build/pipelines.generated.js +1 -1
  67. package/build/readme.generated.js +1 -1
  68. package/build/sfcc/reads-site-preference.js +52 -19
  69. package/build/start-tickets-conductor.js +25 -93
  70. package/build/start-tickets-prereqs.js +152 -1
  71. package/build/start-tickets.js +96 -158
  72. package/build/version.generated.js +1 -1
  73. package/build/visual-diff-worker.js +313 -0
  74. package/build/visual-diff.js +632 -0
  75. package/build/worktree-core.js +202 -0
  76. package/package.json +8 -4
  77. package/public/css/main.min.css +39 -0
  78. package/public/css/main.min.css.map +1 -1
  79. package/public/js/main.min.js +7924 -1
  80. package/public/js/main.min.js.map +1 -1
  81. package/smoke-test/SMOKE-TEST.md +2 -1
@@ -0,0 +1,313 @@
1
+ /**
2
+ * visual-diff-worker.ts — deterministic PNG pixel-diff engine (BAPI-563).
3
+ *
4
+ * This module runs the CPU-heavy part of the `visual_diff` MCP tool: decode two
5
+ * PNG buffers with pngjs, apply mask boxes, diff them with pixelmatch (with an
6
+ * anti-aliasing-tolerant threshold so OS font rendering never inflates the
7
+ * mismatch), and derive coarse diff regions + a heatmap. Keeping this off the
8
+ * MCP server's event loop is why it is spawned as a worker thread from
9
+ * `visual-diff.ts` (see computeVisualDiffInWorker), but the actual logic lives
10
+ * in the exported pure function `computePngVisualDiff` so it can be unit-tested
11
+ * directly and reused inline as a fallback when worker spawning is unavailable.
12
+ *
13
+ * Dimensions are NEVER rescaled: a comp/render size mismatch is a hard signal
14
+ * (`dimension_match: false`) surfaced via a union canvas, not silently resized
15
+ * away.
16
+ */
17
+ import { PNG } from "pngjs";
18
+ import pixelmatch from "pixelmatch";
19
+ import { isMainThread, parentPort, workerData } from "worker_threads";
20
+ /** Anti-aliasing-tolerant pixelmatch color-distance threshold (0..1). */
21
+ export const PIXELMATCH_COLOR_THRESHOLD = 0.1;
22
+ /** Default pass budget (percent) — never 0%, since AA/font rendering makes a
23
+ * true 0% mismatch impossible. Callers override via the `threshold` param. */
24
+ export const DEFAULT_PASS_MISMATCH_PCT = 2;
25
+ /** Maximum number of coarse diff regions returned (deterministic cap). */
26
+ export const MAX_DIFF_REGIONS = 10;
27
+ function decodePng(buffer, label) {
28
+ try {
29
+ const png = PNG.sync.read(Buffer.from(buffer));
30
+ if (!Number.isInteger(png.width) ||
31
+ !Number.isInteger(png.height) ||
32
+ png.width <= 0 ||
33
+ png.height <= 0) {
34
+ return {
35
+ ok: false,
36
+ error: "IMAGE_DECODE_FAILED",
37
+ status: 422,
38
+ message: `Decoded ${label} PNG has invalid dimensions.`,
39
+ };
40
+ }
41
+ return { width: png.width, height: png.height, data: png.data };
42
+ }
43
+ catch {
44
+ // Never surface a raw pngjs stack trace to the caller.
45
+ return {
46
+ ok: false,
47
+ error: "IMAGE_DECODE_FAILED",
48
+ status: 422,
49
+ message: `Failed to decode ${label} image as PNG.`,
50
+ };
51
+ }
52
+ }
53
+ /**
54
+ * Extract the top-left overlapW×overlapH region of a decoded RGBA image into a
55
+ * fresh contiguous RGBA buffer (no scaling).
56
+ */
57
+ function extractOverlap(src, srcW, overlapW, overlapH) {
58
+ const out = new Uint8Array(overlapW * overlapH * 4);
59
+ for (let y = 0; y < overlapH; y++) {
60
+ const srcRow = y * srcW * 4;
61
+ const dstRow = y * overlapW * 4;
62
+ out.set(src.subarray(srcRow, srcRow + overlapW * 4), dstRow);
63
+ }
64
+ return out;
65
+ }
66
+ /** Build a union-sized boolean grid marking pixels covered by any mask box. */
67
+ function buildMaskGrid(boxes, unionW, unionH) {
68
+ const grid = new Uint8Array(unionW * unionH);
69
+ for (const box of boxes) {
70
+ const x0 = Math.max(0, Math.floor(box.x));
71
+ const y0 = Math.max(0, Math.floor(box.y));
72
+ const x1 = Math.min(unionW, Math.floor(box.x + box.width));
73
+ const y1 = Math.min(unionH, Math.floor(box.y + box.height));
74
+ for (let y = y0; y < y1; y++) {
75
+ for (let x = x0; x < x1; x++) {
76
+ grid[y * unionW + x] = 1;
77
+ }
78
+ }
79
+ }
80
+ return grid;
81
+ }
82
+ /** Fill masked cells (within the overlap region) with opaque black in-place. */
83
+ function applyMaskToOverlap(buf, overlapW, overlapH, maskGrid, unionW) {
84
+ for (let y = 0; y < overlapH; y++) {
85
+ for (let x = 0; x < overlapW; x++) {
86
+ if (maskGrid[y * unionW + x] === 1) {
87
+ const off = (y * overlapW + x) * 4;
88
+ buf[off] = 0;
89
+ buf[off + 1] = 0;
90
+ buf[off + 2] = 0;
91
+ buf[off + 3] = 255;
92
+ }
93
+ }
94
+ }
95
+ }
96
+ /**
97
+ * Group contiguous differing pixels into coarse bounding boxes via an iterative
98
+ * 4-connectivity flood fill. Deterministic: regions are sorted by pixel count
99
+ * descending, then y, then x, and capped at `maxRegions`.
100
+ */
101
+ function extractDiffRegions(mask, width, height, maxRegions) {
102
+ const visited = new Uint8Array(width * height);
103
+ const regions = [];
104
+ const stack = [];
105
+ for (let start = 0; start < mask.length; start++) {
106
+ if (mask[start] === 0 || visited[start] === 1)
107
+ continue;
108
+ let minX = width;
109
+ let minY = height;
110
+ let maxX = -1;
111
+ let maxY = -1;
112
+ let pixels = 0;
113
+ stack.length = 0;
114
+ stack.push(start);
115
+ visited[start] = 1;
116
+ while (stack.length > 0) {
117
+ const idx = stack.pop();
118
+ const x = idx % width;
119
+ const y = (idx - x) / width;
120
+ pixels++;
121
+ if (x < minX)
122
+ minX = x;
123
+ if (y < minY)
124
+ minY = y;
125
+ if (x > maxX)
126
+ maxX = x;
127
+ if (y > maxY)
128
+ maxY = y;
129
+ // 4-connected neighbors.
130
+ if (x > 0) {
131
+ const n = idx - 1;
132
+ if (mask[n] === 1 && visited[n] === 0) {
133
+ visited[n] = 1;
134
+ stack.push(n);
135
+ }
136
+ }
137
+ if (x < width - 1) {
138
+ const n = idx + 1;
139
+ if (mask[n] === 1 && visited[n] === 0) {
140
+ visited[n] = 1;
141
+ stack.push(n);
142
+ }
143
+ }
144
+ if (y > 0) {
145
+ const n = idx - width;
146
+ if (mask[n] === 1 && visited[n] === 0) {
147
+ visited[n] = 1;
148
+ stack.push(n);
149
+ }
150
+ }
151
+ if (y < height - 1) {
152
+ const n = idx + width;
153
+ if (mask[n] === 1 && visited[n] === 0) {
154
+ visited[n] = 1;
155
+ stack.push(n);
156
+ }
157
+ }
158
+ }
159
+ regions.push({
160
+ x: minX,
161
+ y: minY,
162
+ width: maxX - minX + 1,
163
+ height: maxY - minY + 1,
164
+ pixels,
165
+ });
166
+ }
167
+ regions.sort((a, b) => {
168
+ if (b.pixels !== a.pixels)
169
+ return b.pixels - a.pixels;
170
+ if (a.y !== b.y)
171
+ return a.y - b.y;
172
+ return a.x - b.x;
173
+ });
174
+ return regions.slice(0, Math.max(0, maxRegions));
175
+ }
176
+ /**
177
+ * Pure deterministic PNG visual diff. Given two PNG buffers plus mask boxes and
178
+ * thresholds, returns mismatch percent, dimension-match signal, coarse diff
179
+ * regions, and a base64 heatmap — or a structured decode error. Never throws a
180
+ * raw stack to the caller.
181
+ */
182
+ export function computePngVisualDiff(input) {
183
+ const comp = decodePng(input.compPngBuffer, "comp");
184
+ if ("ok" in comp && comp.ok === false)
185
+ return comp;
186
+ const render = decodePng(input.renderPngBuffer, "render");
187
+ if ("ok" in render && render.ok === false)
188
+ return render;
189
+ const compImg = comp;
190
+ const renderImg = render;
191
+ const dimensionMatch = compImg.width === renderImg.width && compImg.height === renderImg.height;
192
+ const unionW = Math.max(compImg.width, renderImg.width);
193
+ const unionH = Math.max(compImg.height, renderImg.height);
194
+ const overlapW = Math.min(compImg.width, renderImg.width);
195
+ const overlapH = Math.min(compImg.height, renderImg.height);
196
+ const maskGrid = buildMaskGrid(input.maskBoxes ?? [], unionW, unionH);
197
+ // Diff the OVERLAP region with pixelmatch (both images have real, opaque
198
+ // pixels there, so alpha-blending never distorts the comparison). Dimensions
199
+ // are NEVER rescaled: the non-overlap region is handled explicitly below.
200
+ const output = new Uint8Array(unionW * unionH * 4);
201
+ const diffMask = new Uint8Array(unionW * unionH);
202
+ let differingPixels = 0;
203
+ if (overlapW > 0 && overlapH > 0) {
204
+ const compOverlap = extractOverlap(compImg.data, compImg.width, overlapW, overlapH);
205
+ const renderOverlap = extractOverlap(renderImg.data, renderImg.width, overlapW, overlapH);
206
+ applyMaskToOverlap(compOverlap, overlapW, overlapH, maskGrid, unionW);
207
+ applyMaskToOverlap(renderOverlap, overlapW, overlapH, maskGrid, unionW);
208
+ const overlapOut = new Uint8Array(overlapW * overlapH * 4);
209
+ try {
210
+ differingPixels = pixelmatch(compOverlap, renderOverlap, overlapOut, overlapW, overlapH, {
211
+ threshold: input.pixelmatchColorThreshold,
212
+ // includeAA: false => anti-aliased pixels are detected and NOT counted
213
+ // as diffs, so OS font rendering does not inflate the mismatch.
214
+ includeAA: false,
215
+ diffColor: [255, 0, 0],
216
+ diffColorAlt: [255, 0, 0],
217
+ aaColor: [255, 255, 0],
218
+ });
219
+ }
220
+ catch {
221
+ return {
222
+ ok: false,
223
+ error: "DIFF_FAILED",
224
+ status: 500,
225
+ message: "Pixel comparison failed.",
226
+ };
227
+ }
228
+ // Copy the overlap heatmap into the union output's top-left; differing
229
+ // pixels are drawn pure red ([255,0,0]).
230
+ for (let y = 0; y < overlapH; y++) {
231
+ for (let x = 0; x < overlapW; x++) {
232
+ const so = (y * overlapW + x) * 4;
233
+ const uo = (y * unionW + x) * 4;
234
+ output[uo] = overlapOut[so];
235
+ output[uo + 1] = overlapOut[so + 1];
236
+ output[uo + 2] = overlapOut[so + 2];
237
+ output[uo + 3] = 255;
238
+ if (overlapOut[so] === 255 && overlapOut[so + 1] === 0 && overlapOut[so + 2] === 0) {
239
+ diffMask[y * unionW + x] = 1;
240
+ }
241
+ }
242
+ }
243
+ }
244
+ // Non-overlap region: a union cell in-bounds for exactly ONE image is a hard
245
+ // dimension-mismatch difference (the render has area the comp does not account
246
+ // for, or vice versa) and is forced-counted — never rescaled away. Cells
247
+ // covered by a mask box are ignored.
248
+ for (let y = 0; y < unionH; y++) {
249
+ for (let x = 0; x < unionW; x++) {
250
+ const inComp = x < compImg.width && y < compImg.height;
251
+ const inRender = x < renderImg.width && y < renderImg.height;
252
+ if (inComp === inRender)
253
+ continue; // overlap (handled) or both-out (empty)
254
+ if (maskGrid[y * unionW + x] === 1)
255
+ continue;
256
+ const off = (y * unionW + x) * 4;
257
+ output[off] = 255;
258
+ output[off + 1] = 0;
259
+ output[off + 2] = 0;
260
+ output[off + 3] = 255;
261
+ diffMask[y * unionW + x] = 1;
262
+ differingPixels++;
263
+ }
264
+ }
265
+ const diffRegions = extractDiffRegions(diffMask, unionW, unionH, input.maxRegions ?? MAX_DIFF_REGIONS);
266
+ const totalPixels = unionW * unionH;
267
+ const mismatchPct = totalPixels > 0 ? (differingPixels / totalPixels) * 100 : 0;
268
+ const passed = dimensionMatch && mismatchPct <= input.passMismatchPct;
269
+ let heatmapBase64;
270
+ try {
271
+ const png = new PNG({ width: unionW, height: unionH });
272
+ png.data = Buffer.from(output);
273
+ heatmapBase64 = PNG.sync.write(png).toString("base64");
274
+ }
275
+ catch {
276
+ return {
277
+ ok: false,
278
+ error: "DIFF_FAILED",
279
+ status: 500,
280
+ message: "Failed to encode diff heatmap.",
281
+ };
282
+ }
283
+ return {
284
+ ok: true,
285
+ mismatch_pct: mismatchPct,
286
+ dimension_match: dimensionMatch,
287
+ passed,
288
+ diff_regions: diffRegions,
289
+ comp_dimensions: { width: compImg.width, height: compImg.height },
290
+ render_dimensions: { width: renderImg.width, height: renderImg.height },
291
+ differing_pixels: differingPixels,
292
+ total_pixels: totalPixels,
293
+ heatmap_base64: heatmapBase64,
294
+ };
295
+ }
296
+ // Worker-thread entrypoint: when spawned via new Worker(...), read the input
297
+ // from workerData, run the pure diff, and post the structured result back. Any
298
+ // unexpected failure is returned as a structured DIFF_FAILED envelope rather
299
+ // than crashing the worker.
300
+ if (!isMainThread && parentPort) {
301
+ try {
302
+ const result = computePngVisualDiff(workerData);
303
+ parentPort.postMessage(result);
304
+ }
305
+ catch (err) {
306
+ parentPort.postMessage({
307
+ ok: false,
308
+ error: "DIFF_FAILED",
309
+ status: 500,
310
+ message: `Diff worker failed: ${err instanceof Error ? err.message : "unknown error"}`,
311
+ });
312
+ }
313
+ }