@bridge_gpt/mcp-server 0.2.66 → 0.2.68

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 (171) hide show
  1. package/README.md +421 -83
  2. package/build/agent-capabilities/probes.js +15 -5
  3. package/build/agent-launchers/claude-executor-adapter.js +70 -16
  4. package/build/agent-launchers/executor-adapter.js +7 -4
  5. package/build/agents.generated.js +22 -2
  6. package/build/bitbucket-workspace-remote.js +136 -0
  7. package/build/bootstrap-project-identity.js +24 -0
  8. package/build/bounded-wait.js +67 -10
  9. package/build/bridge-permissions.js +829 -0
  10. package/build/claude-settings.js +18 -0
  11. package/build/cli-help.js +21 -0
  12. package/build/command-assets-doctor.js +24 -70
  13. package/build/command-provisioning.js +29 -5
  14. package/build/commands.generated.js +12 -12
  15. package/build/conductor/bridge-api-client.js +857 -1
  16. package/build/conductor/bring-up-facts.js +1 -1
  17. package/build/conductor/command-drift-production.js +92 -0
  18. package/build/conductor/command-drift.js +89 -0
  19. package/build/conductor/doctor.js +72 -29
  20. package/build/conductor/epic-branch-ci-inspection.js +1375 -0
  21. package/build/conductor/epic-implementer-cli.js +98 -28
  22. package/build/conductor/errors.js +4 -3
  23. package/build/conductor/gate-discovery.js +890 -0
  24. package/build/conductor/guided-repair.js +6 -2
  25. package/build/conductor/hook-runtime-qualification.js +1 -1
  26. package/build/conductor/install-doctor.js +15 -3
  27. package/build/conductor/json-output.js +63 -0
  28. package/build/conductor/merge-execution-contract.js +27 -8
  29. package/build/conductor/merge-execution-eligibility.js +3 -2
  30. package/build/conductor/pr-ci-producer.js +1 -1
  31. package/build/conductor/readiness-cli.js +113 -12
  32. package/build/conductor/readiness-sections.js +410 -9
  33. package/build/conductor/readiness.js +388 -33
  34. package/build/conductor/recovery-cli.js +310 -12
  35. package/build/conductor/recovery-operations.js +167 -3
  36. package/build/conductor/repair-contract.js +54 -3
  37. package/build/conductor/repository-workflow-remedy.js +125 -0
  38. package/build/conductor/tools.js +2 -2
  39. package/build/conductor-bundle-artifacts.js +8 -0
  40. package/build/connect-bitbucket-api.js +41 -6
  41. package/build/connect-bitbucket.js +106 -33
  42. package/build/credential-store.js +207 -47
  43. package/build/docs.generated.js +62 -3
  44. package/build/doctor.js +92 -1
  45. package/build/drive-epic-key.js +6 -5
  46. package/build/drive-epic.js +129 -61
  47. package/build/epic-coherence-input.js +2 -1
  48. package/build/epic-coherence-input.test-fixtures.js +4 -3
  49. package/build/epic-implementer-bin.js +1379 -28
  50. package/build/epic-implementer-bundle-cli.js +2 -2
  51. package/build/epic-workarounds-path.js +39 -0
  52. package/build/executor/cli.js +33 -2
  53. package/build/executor/completion-contract.js +66 -10
  54. package/build/executor/completion-supervision.js +4 -0
  55. package/build/executor/deps.js +4 -1
  56. package/build/executor/env.js +19 -0
  57. package/build/executor/gate-qualification-job.js +499 -0
  58. package/build/executor/guarded-fast-forward-push.js +129 -0
  59. package/build/executor/heartbeat.js +1 -1
  60. package/build/executor/http-client.js +20 -0
  61. package/build/executor/integration-job.js +122 -8
  62. package/build/executor/job-errors.js +33 -1
  63. package/build/executor/job-runner.js +813 -87
  64. package/build/executor/likely-tests.js +290 -0
  65. package/build/executor/live-worker-registry.js +1 -1
  66. package/build/executor/observation.js +37 -3
  67. package/build/executor/packaged-recipe.js +106 -0
  68. package/build/executor/permissions.js +115 -28
  69. package/build/executor/platform.js +1 -1
  70. package/build/executor/preflight.js +112 -26
  71. package/build/executor/process-containment.js +36 -12
  72. package/build/executor/process.js +15 -7
  73. package/build/executor/prompt-spec.js +33 -1
  74. package/build/executor/recently-failed-tests-file.js +71 -0
  75. package/build/executor/reconcile-overlap-job.js +41 -23
  76. package/build/executor/required-verification.js +1 -1
  77. package/build/executor/required-work-ledger.js +111 -18
  78. package/build/executor/resume-pre-spawn.js +2 -0
  79. package/build/executor/retirement.js +69 -0
  80. package/build/executor/runner.js +122 -13
  81. package/build/executor/service-unit.js +7 -1
  82. package/build/executor/test-clock.js +15 -0
  83. package/build/executor/types.js +32 -0
  84. package/build/executor/viewer-tabs.js +58 -8
  85. package/build/executor/worker-command.js +23 -7
  86. package/build/executor/worker-diagnostics.js +37 -0
  87. package/build/executor/worker-finalization.js +113 -34
  88. package/build/executor/worker-guard-hook.js +316 -58
  89. package/build/executor/worker-log.js +4 -3
  90. package/build/executor/worker-verification-guidance.js +37 -0
  91. package/build/executor/worktree.js +166 -8
  92. package/build/first-build-advisory.js +97 -0
  93. package/build/generated-assets-advisory.js +14 -0
  94. package/build/generated-assets-catalog.js +66 -0
  95. package/build/generated-assets-context.js +46 -0
  96. package/build/generated-assets-dependencies.js +26 -0
  97. package/build/generated-assets-doctor.js +50 -0
  98. package/build/generated-assets-health.js +65 -0
  99. package/build/generated-assets-host.js +17 -0
  100. package/build/generated-assets-inventory.js +17 -0
  101. package/build/generated-assets-paths.js +60 -0
  102. package/build/generated-assets-publisher.js +133 -0
  103. package/build/generated-assets-remedy.js +25 -0
  104. package/build/generated-assets-repair.js +72 -0
  105. package/build/generated-assets-session.js +71 -0
  106. package/build/generated-assets-test-support.js +35 -0
  107. package/build/index.js +1224 -1054
  108. package/build/init.js +54 -177
  109. package/build/install-bridge-conductor-gate.js +1372 -0
  110. package/build/install-bridge-conductor.js +296 -41
  111. package/build/install-bridge.js +547 -40
  112. package/build/mcp-install-state.js +50 -4
  113. package/build/merge-workflow-install.js +3 -4
  114. package/build/phase-context-git-probe.js +68 -0
  115. package/build/phase-result-artifacts.js +173 -5
  116. package/build/pipeline-utils.js +6 -2
  117. package/build/pipelines.generated.js +34 -160
  118. package/build/plan-execution-ledger.js +2 -32
  119. package/build/plane/alembic-head.js +12 -3
  120. package/build/plane/base-worktree-guard.js +147 -0
  121. package/build/plane/cli.js +217 -17
  122. package/build/plane/code-snapshot.js +468 -0
  123. package/build/plane/defaults.js +48 -3
  124. package/build/plane/endpoint.js +41 -0
  125. package/build/plane/manifest.js +136 -2
  126. package/build/plane/member-roster.js +94 -19
  127. package/build/plane/preflight.js +83 -2
  128. package/build/plane/process-cwd.js +67 -0
  129. package/build/plane/restart.js +542 -0
  130. package/build/plane/shutdown.js +31 -0
  131. package/build/plane/status.js +143 -8
  132. package/build/plane/supervisor.js +586 -179
  133. package/build/plane/types.js +54 -2
  134. package/build/printed-remedy-commands.js +52 -0
  135. package/build/readiness-check.js +1 -1
  136. package/build/readme.generated.js +1 -1
  137. package/build/repair-assets.js +93 -0
  138. package/build/review-generation.js +22 -0
  139. package/build/run-unit-tests-launcher.js +130 -49
  140. package/build/secret-redaction.js +33 -0
  141. package/build/setup-epic.js +152 -22
  142. package/build/sfcc/config.js +9 -0
  143. package/build/sfcc/credentials.js +100 -38
  144. package/build/sfcc/hostname.js +67 -0
  145. package/build/sfcc/permissions.js +17 -13
  146. package/build/sfcc/register.js +1 -1
  147. package/build/sfcc/tool-wrapper.js +39 -43
  148. package/build/sfcc/write-grants.js +17 -11
  149. package/build/sfcc/write-result.js +4 -3
  150. package/build/sfcc/writes-site-preference.js +20 -8
  151. package/build/start-tickets-prereqs.js +70 -16
  152. package/build/start-tickets.js +208 -36
  153. package/build/tool-error-envelope.js +61 -0
  154. package/build/tool-surface-gating.js +36 -0
  155. package/build/upgrade-cli.js +55 -1
  156. package/build/version.generated.js +3 -3
  157. package/build/visual-diff.js +22 -1
  158. package/build/worker-guard-hook-bin.js +4 -6
  159. package/build/worker-guard-hook-main.js +47 -0
  160. package/build/worktree-core.js +145 -6
  161. package/docs/EPIC-IMPLEMENTER-INSTALL.md +867 -0
  162. package/docs/EPIC-IMPLEMENTER.md +728 -0
  163. package/docs/install/mcp-tool-integrations.md +24 -15
  164. package/docs/install/sfcc-integration.md +12 -4
  165. package/package.json +3 -3
  166. package/pipelines/greenfield-setup.json +4 -128
  167. package/pipelines/implement-ticket.json +3 -2
  168. package/pipelines/plan-epic.json +0 -5
  169. package/pipelines/review-ticket.json +1 -1
  170. package/docs/CONDUCTOR.md +0 -605
  171. package/pipelines/idea-to-pr.json +0 -49
@@ -0,0 +1,93 @@
1
+ import { access, lstat, realpath, readFile } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import os from "node:os";
4
+ import { WORKFLOW_HOSTS, buildPackagedAssetCatalog } from "./generated-assets-catalog.js";
5
+ import { authorizeAssetRepair, assetRootBinding } from "./generated-assets-context.js";
6
+ import { validateAssetRoot } from "./generated-assets-paths.js";
7
+ import { readMcpInstallState } from "./mcp-install-state.js";
8
+ import { repairPackagedAssets } from "./generated-assets-repair.js";
9
+ import { createConfinedAssetPublisher } from "./generated-assets-publisher.js";
10
+ import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
11
+ import { VERSION } from "./version.generated.js";
12
+ export const REPAIR_ASSETS_HELP = `Usage: repair-assets [--project-root <directory>] [--host <claude|cursor|codex|copilot>]
13
+ Fill missing packaged workflow files only; preserve existing customized files.
14
+ Explicit --project-root plus --host authorizes provisioning without inventory.
15
+ Implicit selection requires root-bound inventory at exact cwd; subdirectories must
16
+ select --project-root. Never infer installed hosts from folders or selectedPlatforms.
17
+ Reports restored/preserved/unreadable/failed files; incomplete repair exits 1.
18
+ Linux procfs supports confined exclusive publication. Other platforms refuse writes
19
+ when a confined adapter is unavailable. Existing readable files remain preserved.
20
+ Excludes custom pipelines/instructions, registrations, credentials, generated outputs,
21
+ onboarding, API setup, and Windsurf. Refresh/reconnect after repair, then run doctor.
22
+ Unpinned npx -y ${MCP_PACKAGE_NAME} repair-assets intentionally selects the latest
23
+ package; use @${VERSION} to repair from this running version.\n`;
24
+ export async function runRepairAssetsCli(argv, options = {}) {
25
+ const log = options.log ?? (text => process.stdout.write(`${text}\n`));
26
+ const error = options.error ?? (text => process.stderr.write(`${text}\n`));
27
+ if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
28
+ log(REPAIR_ASSETS_HELP);
29
+ return 0;
30
+ }
31
+ let requestedRoot;
32
+ const hosts = [];
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const flag = argv[i];
35
+ const value = argv[++i];
36
+ if (!value || value.startsWith("--")) {
37
+ error("repair-assets: missing option value; use --help.");
38
+ return 1;
39
+ }
40
+ if (flag === "--project-root" && !requestedRoot)
41
+ requestedRoot = value;
42
+ else if (flag === "--host" && WORKFLOW_HOSTS.includes(value))
43
+ hosts.push(value);
44
+ else {
45
+ error("repair-assets: unknown, duplicate, or invalid option; use --help.");
46
+ return 1;
47
+ }
48
+ }
49
+ const fs = options.fs ?? { lstat, realpath, readFile: (p) => readFile(p, "utf-8"), access: (p) => access(p, constants.R_OK) };
50
+ const root = await validateAssetRoot(requestedRoot ?? options.cwd ?? process.cwd(), options.home ?? os.homedir(), fs);
51
+ if (!root) {
52
+ error("repair-assets: select an existing canonical project directory, excluding home and filesystem roots.");
53
+ return 1;
54
+ }
55
+ const state = await readMcpInstallState(root, fs);
56
+ const inventory = state.status === "valid" ? state.state.packagedAssets : undefined;
57
+ const explicitPair = requestedRoot !== undefined && hosts.length > 0;
58
+ const selected = [...new Set(hosts.length ? hosts : inventory?.assets.map(a => a.host) ?? [])];
59
+ if (!authorizeAssetRepair(root, selected, explicitPair, inventory)) {
60
+ error("repair-assets: installation context is uncertain. Supply both --project-root and --host for explicit provisioning.");
61
+ return 1;
62
+ }
63
+ log(`Selected project root: ${root}`);
64
+ log(`Invoking package: ${VERSION}; ${explicitPair ? "explicit fill-only provisioning" : "inventory-authorized repair"}.`);
65
+ const boundInventory = inventory?.rootBinding === assetRootBinding(root) ? inventory : undefined;
66
+ if (boundInventory)
67
+ log(`Recorded package: ${boundInventory.packageVersion}; invoking bundle may differ.`);
68
+ const catalog = buildPackagedAssetCatalog();
69
+ if (boundInventory) {
70
+ const historical = new Set(boundInventory.assets.map(a => a.id));
71
+ const introduced = catalog.assets.filter(a => a.hosts.some(h => selected.includes(h)) && !historical.has(a.id));
72
+ log(`Newly introduced catalog assets: ${introduced.length} (provisioning, not historical loss).`);
73
+ }
74
+ try {
75
+ const result = await (options.repair ?? repairPackagedAssets)({ root, hosts: selected, explicitPair, inventory: boundInventory, fs,
76
+ catalog, publisher: options.publisher ?? createConfinedAssetPublisher(options.platform) });
77
+ for (const status of ["restored", "preserved", "unreadable", "failed"]) {
78
+ const items = result.findings.filter(f => f.status === status);
79
+ log(`${status}: ${items.length}${items.length ? ` (${items.slice(0, 5).map(f => `${f.destination}: ${f.reason}`).join(", ")}${items.length > 5 ? ", …" : ""})` : ""}`);
80
+ }
81
+ if (result.packageProblems.length)
82
+ error(`Package problems: ${result.packageProblems.join(", ")}`);
83
+ if (result.cleanupFailures.length)
84
+ error("Some owned temporary files could not be removed. Inspect .bridge-repair-*.tmp in the reported asset parents; do not remove other files.");
85
+ if (!result.complete)
86
+ error("repair-assets: requested availability restoration is incomplete. Resolve reported permission/path/platform problems before retrying.");
87
+ return result.complete ? 0 : 1;
88
+ }
89
+ catch {
90
+ error("repair-assets: repair failed; no unsafe fallback was attempted.");
91
+ return 1;
92
+ }
93
+ }
@@ -193,6 +193,28 @@ export class ReviewGenerationRegistry {
193
193
  return null;
194
194
  return { baseline: entry.baseline, expectedLegCount: entry.expectedLegCount };
195
195
  }
196
+ /**
197
+ * Consecutive all-`404` wait windows recorded for the live submission
198
+ * (BAPI-1382), or `0` when there is none or no live entry.
199
+ */
200
+ notFoundWindowCount(repoName, ticketNumber) {
201
+ this.evictExpired();
202
+ return this.entries.get(baselineKey(repoName, ticketNumber))?.notFoundWindows ?? 0;
203
+ }
204
+ /** Count one more all-`404` wait window for the live submission. No-op without one. */
205
+ noteNotFoundWindow(repoName, ticketNumber) {
206
+ this.evictExpired();
207
+ const entry = this.entries.get(baselineKey(repoName, ticketNumber));
208
+ if (!entry)
209
+ return;
210
+ entry.notFoundWindows = (entry.notFoundWindows ?? 0) + 1;
211
+ }
212
+ /** A non-`404` observation resets the count. No-op without a live entry. */
213
+ resetNotFoundWindows(repoName, ticketNumber) {
214
+ const entry = this.entries.get(baselineKey(repoName, ticketNumber));
215
+ if (entry)
216
+ entry.notFoundWindows = 0;
217
+ }
196
218
  /**
197
219
  * Consume a baseline. Clearing means "durable persistence has happened",
198
220
  * not "a terminal envelope was received" (BAPI-1129) — the anchor is
@@ -16,7 +16,8 @@
16
16
  * compiled module; the logic lives here (under src/, built by tsc) so it can
17
17
  * be unit-tested with node:test like every other module in this package.
18
18
  */
19
- import { readdirSync } from "fs";
19
+ import { readdirSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync, renameSync, rmSync, existsSync } from "fs";
20
+ import { tmpdir } from "os";
20
21
  import { spawnSync } from "child_process";
21
22
  import path from "path";
22
23
  const DEFAULT_BUILD_DIR = "build";
@@ -76,6 +77,7 @@ export const MODULE_MOCK_MANIFEST = [
76
77
  "build/connect-github.test.js",
77
78
  "build/council-submission.test.js",
78
79
  "build/council-wait-recovery.test.js",
80
+ "build/generated-assets-publisher.test.js",
79
81
  "build/index-artifacts.test.js",
80
82
  "build/index-council-filenames.test.js",
81
83
  "build/index-generate-decision-page.integration.test.js",
@@ -314,27 +316,31 @@ export function selectTestGroup(mode, discovered) {
314
316
  */
315
317
  export function partitionTestBatches(files, options = {}) {
316
318
  const { execPath = process.execPath, nodeOptions = [], maxLength = MAX_COMMAND_LENGTH } = options;
317
- const prefixLength = [execPath, ...nodeOptions].join(" ").length;
319
+ const size = (batch, index) => {
320
+ const args = options.argumentBuilder?.(batch, index) ?? [...nodeOptions, ...batch];
321
+ // Quoted serialization conservatively includes spaces/backslashes/quotes on
322
+ // Windows; legacy callers without an argument builder keep their old budget.
323
+ return options.argumentBuilder
324
+ ? [execPath, ...args].map((arg) => JSON.stringify(arg)).join(" ").length
325
+ : [execPath, ...args].join(" ").length;
326
+ };
318
327
  const batches = [];
319
328
  let current = [];
320
- let currentLength = prefixLength;
321
329
  for (const file of files) {
322
- const addition = 1 + file.length; // separating space + path
323
- if (prefixLength + addition > maxLength) {
324
- throw new Error(`run-unit-tests: "${file}" alone exceeds the command-length budget ` +
325
- `(${prefixLength + addition} > ${maxLength} chars) — cannot batch it safely`);
330
+ if (size([file], batches.length) > maxLength) {
331
+ throw new Error(`run-unit-tests: single file alone exceeds the command-length budget`);
326
332
  }
327
- if (current.length > 0 && currentLength + addition > maxLength) {
333
+ if (current.length && size([...current, file], batches.length) > maxLength) {
328
334
  batches.push(current);
329
335
  current = [];
330
- currentLength = prefixLength;
336
+ if (size([file], batches.length) > maxLength) {
337
+ throw new Error(`run-unit-tests: single file alone exceeds the command-length budget`);
338
+ }
331
339
  }
332
340
  current.push(file);
333
- currentLength += addition;
334
341
  }
335
- if (current.length > 0) {
342
+ if (current.length)
336
343
  batches.push(current);
337
- }
338
344
  return batches;
339
345
  }
340
346
  /** Discovers, selects, batches, and spawns the requested test group; returns an aggregate exit code. */
@@ -355,30 +361,7 @@ export function runSelectedGroup(mode, options) {
355
361
  const batchable = selected.filter((file) => !quarantineSet.has(file));
356
362
  const quarantined = selected.filter((file) => quarantineSet.has(file));
357
363
  const extraFlags = mode === "module-mocks" ? ["--experimental-test-module-mocks"] : [];
358
- const nodeOptions = [...extraFlags, "--test"];
359
- const batches = partitionTestBatches(batchable, { execPath, nodeOptions });
360
- log(`run-unit-tests: group="${mode}" files=${selected.length} batches=${batches.length}` +
361
- (quarantined.length > 0 ? ` quarantined=${quarantined.length}` : ""));
362
- let aggregateExit = 0;
363
- const spawn = (args) => {
364
- const result = spawnFn(execPath, args, {
365
- cwd: root,
366
- stdio: "inherit",
367
- shell: false,
368
- });
369
- if ((result.status ?? 1) !== 0) {
370
- aggregateExit = 1;
371
- }
372
- };
373
- batches.forEach((batch, index) => {
374
- log(`run-unit-tests: batch ${index + 1}/${batches.length} (${batch.length} file(s))`);
375
- spawn([...nodeOptions, ...batch]);
376
- });
377
- quarantined.forEach((file, index) => {
378
- log(`run-unit-tests: quarantined ${index + 1}/${quarantined.length} ${file} (single-file, --test-force-exit)`);
379
- spawn([...nodeOptions, "--test-force-exit", file]);
380
- });
381
- return aggregateExit;
364
+ return runWithReports(mode, options, batchable, quarantined, [...extraFlags, "--test"], log);
382
365
  }
383
366
  /**
384
367
  * Returns the integration lane's file list: every compiled `*.test.js` under
@@ -420,20 +403,118 @@ export function runIntegrationLane(options) {
420
403
  throw new Error("run-integration-tests: discovered zero integration tests — refusing to run " +
421
404
  "(did you forget 'npm run build'?)");
422
405
  }
423
- const nodeOptions = ["--test"];
424
- const batches = partitionTestBatches(selected, { execPath, nodeOptions });
425
- log(`run-integration-tests: files=${selected.length} batches=${batches.length}`);
406
+ return runWithReports("integration", options, selected, [], ["--test"], log);
407
+ }
408
+ /** Shared reporter argument builder: sizing and execution cannot diverge. */
409
+ export function reporterArguments(nodeOptions, files, destination, forceExit = false) {
410
+ return [...nodeOptions, ...(forceExit ? ["--test-force-exit"] : []),
411
+ "--test-reporter=spec", "--test-reporter-destination=stdout",
412
+ "--test-reporter=junit", `--test-reporter-destination=${destination}`, ...files];
413
+ }
414
+ function runWithReports(group, options, ordinary, quarantined, nodeOptions, log) {
415
+ const { root, execPath = process.execPath, spawnFn = spawnSync } = options;
416
+ const retained = options.reportOutputDirectory !== undefined;
417
+ let directory;
426
418
  let aggregateExit = 0;
427
- batches.forEach((batch, index) => {
428
- log(`run-integration-tests: batch ${index + 1}/${batches.length} (${batch.length} file(s))`);
429
- const result = spawnFn(execPath, [...nodeOptions, ...batch], {
430
- cwd: root,
431
- stdio: "inherit",
432
- shell: false,
433
- });
434
- if ((result.status ?? 1) !== 0) {
435
- aggregateExit = 1;
419
+ try {
420
+ if (retained) {
421
+ if (!options.reportOutputDirectory?.trim())
422
+ throw new Error("empty report directory");
423
+ directory = path.resolve(options.reportOutputDirectory);
424
+ // Exclusive allocation refuses stale reports and concurrent invocations.
425
+ mkdirSync(directory, { recursive: false });
436
426
  }
437
- });
427
+ else {
428
+ directory = mkdtempSync(path.join(tmpdir(), "bridge-junit-"));
429
+ }
430
+ const outputDirectory = directory;
431
+ const reportName = (index) => `report-${index}.xml`;
432
+ const args = (files, index, forceExit = false) => reporterArguments(nodeOptions, files, path.join(outputDirectory, reportName(index)), forceExit);
433
+ // Source mapping comes from the build's actual rootDir/outDir, including
434
+ // authored scripts (which do not pass through tsc).
435
+ const config = JSON.parse(readFileSync(new URL("../tsconfig.json", import.meta.url), "utf8"));
436
+ const { rootDir, outDir } = config.compilerOptions;
437
+ if (rootDir !== "src" || outDir !== "build")
438
+ throw new Error("unsupported build source mapping");
439
+ const sourceFor = (file) => {
440
+ const posix = file.replaceAll("\\", "/");
441
+ if (posix.startsWith(`${outDir}/`) && posix.endsWith(".js")) {
442
+ return `mcp_server/${rootDir}/${posix.slice(outDir.length + 1, -3)}.ts`;
443
+ }
444
+ if (posix.startsWith("scripts/") && posix.endsWith(".js"))
445
+ return `mcp_server/${posix}`;
446
+ throw new Error("unmapped test source");
447
+ };
448
+ // Node 24.16 discovery confirmed explicit testcase file attribution.
449
+ // Older local/scheduled runtimes use one source per invocation, so the
450
+ // inventory provides an unambiguous fallback without adding concurrency.
451
+ const [major, minor] = process.versions.node.split(".").map(Number);
452
+ const explicitAttribution = major > 24 || (major === 24 && minor >= 16);
453
+ const batches = explicitAttribution
454
+ ? partitionTestBatches(ordinary, { execPath, nodeOptions, argumentBuilder: args })
455
+ : ordinary.map((file, index) => {
456
+ partitionTestBatches([file], { execPath, argumentBuilder: (files) => args(files, index) });
457
+ return [file];
458
+ });
459
+ const plans = [];
460
+ const addPlan = (files, forceExit) => {
461
+ const index = plans.length;
462
+ partitionTestBatches(files, { execPath, argumentBuilder: (values) => args(values, index, forceExit) });
463
+ const sources = Object.fromEntries(files.map((file) => [`mcp_server/${file}`, sourceFor(file)]));
464
+ // integrationSuite declares skipped suites inside the shared harness;
465
+ // Node attributes those cases to the wrapper, rather than its caller.
466
+ // Map that real declaration source using the same verified build config.
467
+ // Older runtimes still need exactly one entry for invocation attribution.
468
+ const harness = `${outDir}/integration/harness.js`;
469
+ if (group === "integration" && explicitAttribution &&
470
+ existsSync(path.join(root, harness)) &&
471
+ existsSync(path.join(root, rootDir, "integration/harness.ts"))) {
472
+ sources[`mcp_server/${harness}`] = sourceFor(harness);
473
+ }
474
+ plans.push({ report: reportName(index), files, forceExit, state: "planned", status: null,
475
+ sources });
476
+ };
477
+ batches.forEach((batch) => addPlan(batch, false));
478
+ quarantined.forEach((file) => addPlan([file], true));
479
+ const inventory = { version: 1, lane: group === "integration" ? "integration" : "unit", group,
480
+ nodeVersion: process.version, attribution: explicitAttribution ? "reporter-file" : "single-source", spawns: plans };
481
+ const publish = () => {
482
+ const temporary = path.join(outputDirectory, "inventory.json.tmp");
483
+ writeFileSync(temporary, JSON.stringify(inventory, null, 2));
484
+ renameSync(temporary, path.join(outputDirectory, "inventory.json"));
485
+ };
486
+ publish(); // Complete expected inventory is durable before the first spawn.
487
+ log(`run-unit-tests: group="${group}" files=${ordinary.length + quarantined.length} batches=${plans.length}`);
488
+ for (const [index, plan] of plans.entries()) {
489
+ plan.state = "started";
490
+ publish();
491
+ try {
492
+ const result = spawnFn(execPath, args(plan.files, index, plan.forceExit), { cwd: root, stdio: "inherit", shell: false });
493
+ plan.status = result.status;
494
+ plan.state = result.status === null || !existsSync(path.join(outputDirectory, plan.report)) ? "failed" : "completed";
495
+ }
496
+ catch {
497
+ plan.state = "failed";
498
+ }
499
+ if (plan.state !== "completed" || plan.status !== 0)
500
+ aggregateExit = 1;
501
+ publish();
502
+ }
503
+ }
504
+ catch {
505
+ // No exception/environment dump reaches CI logs or exported evidence.
506
+ log("run-unit-tests: report allocation, inventory publication, or execution failed");
507
+ aggregateExit = 1;
508
+ }
509
+ finally {
510
+ if (!retained && directory) {
511
+ try {
512
+ rmSync(directory, { recursive: true, force: true });
513
+ }
514
+ catch {
515
+ aggregateExit = 1;
516
+ }
517
+ }
518
+ }
438
519
  return aggregateExit;
439
520
  }
@@ -0,0 +1,33 @@
1
+ /** Shared leaf scrubber for durable untrusted text. */
2
+ const REDACTED = "[REDACTED]";
3
+ /**
4
+ * Patterns for values that must never reach durable state or a log line.
5
+ *
6
+ * Each rewrites only the secret itself and keeps the surrounding key or scheme, so a
7
+ * redacted record still shows *that* a credential was involved — useful for audit,
8
+ * and materially different from dropping the field.
9
+ */
10
+ const SECRET_PATTERNS = [
11
+ // Bearer / token schemes in an Authorization header or bare.
12
+ [/\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{8,}/gi, `$1 ${REDACTED}`],
13
+ // authorization: <anything>
14
+ [/\b(authorization)\s*[:=]\s*\S+/gi, `$1: ${REDACTED}`],
15
+ // Well-known provider key shapes.
16
+ [/\b(sk|pk|rk)-[A-Za-z0-9_-]{16,}/g, REDACTED],
17
+ [/\bgh[pousr]_[A-Za-z0-9]{16,}/g, REDACTED],
18
+ // key/secret/password/token assignments in prose, env, JSON, or CLI form.
19
+ [
20
+ /\b([A-Za-z0-9_-]*(?:api[_-]?key|secret|password|passwd|token|credential)[A-Za-z0-9_-]*)\s*[:=]\s*("[^"]*"|'[^']*'|\S+)/gi,
21
+ `$1=${REDACTED}`,
22
+ ],
23
+ // Credentials embedded in a URL.
24
+ [/\b([a-z][a-z0-9+.-]*:\/\/)[^/\s:@]+:[^/\s@]+@/gi, `$1${REDACTED}@`],
25
+ ];
26
+ /** Redact secret-bearing substrings while leaving safe technical text intact. */
27
+ export function redactSecrets(value) {
28
+ let output = value;
29
+ for (const [pattern, replacement] of SECRET_PATTERNS) {
30
+ output = output.replace(pattern, replacement);
31
+ }
32
+ return output;
33
+ }