@bridge_gpt/mcp-server 0.2.50 → 0.2.52

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 (76) hide show
  1. package/README.md +24 -8
  2. package/build/agent-capabilities/probe-context.js +15 -7
  3. package/build/agent-capabilities/probes.js +42 -6
  4. package/build/agent-launchers/claude-executor-adapter.js +98 -14
  5. package/build/commands.generated.js +1 -1
  6. package/build/conduct-epic/bridge-client.js +115 -1
  7. package/build/conduct-epic/cli.js +351 -33
  8. package/build/conduct-epic/cut-protocol.js +65 -0
  9. package/build/conductor/bridge-api-client.js +171 -5
  10. package/build/conductor/deny-enforcement-preflight.js +107 -10
  11. package/build/conductor/local-merge.js +170 -11
  12. package/build/conductor-bin.js +2 -2
  13. package/build/connect-bitbucket-api.js +370 -0
  14. package/build/connect-bitbucket.js +437 -0
  15. package/build/docs.generated.js +1 -1
  16. package/build/doctor.js +230 -1
  17. package/build/drive-epic.js +423 -11
  18. package/build/env-file-link.js +164 -0
  19. package/build/epic-integration-pr.js +290 -0
  20. package/build/executor/cli.js +41 -6
  21. package/build/executor/deps.js +5 -1
  22. package/build/executor/env-file-guard.js +113 -0
  23. package/build/executor/env.js +78 -1
  24. package/build/executor/heartbeat.js +9 -0
  25. package/build/executor/http-client.js +90 -22
  26. package/build/executor/job-errors.js +43 -2
  27. package/build/executor/job-runner.js +137 -29
  28. package/build/executor/merge-job.js +102 -6
  29. package/build/executor/permissions.js +106 -0
  30. package/build/executor/preflight.js +38 -13
  31. package/build/executor/resume-pre-spawn.js +2 -1
  32. package/build/executor/runner.js +175 -4
  33. package/build/executor/service-unit.js +15 -0
  34. package/build/executor/terminal-mutation.js +22 -1
  35. package/build/executor/types.js +86 -0
  36. package/build/executor/worker-command.js +21 -5
  37. package/build/executor/worker-guard-hook.js +939 -0
  38. package/build/executor/worker-log.js +56 -0
  39. package/build/executor/worktree.js +11 -0
  40. package/build/git-reachability.js +147 -0
  41. package/build/index.js +535 -95
  42. package/build/install-bridge.js +95 -0
  43. package/build/pipelines.generated.js +10 -2
  44. package/build/plan-epic-conductor-eligibility.js +213 -0
  45. package/build/plane/cli.js +78 -15
  46. package/build/plane/defaults.js +165 -0
  47. package/build/plane/manifest.js +63 -8
  48. package/build/plane/member-logs.js +6 -0
  49. package/build/plane/member-roster.js +195 -11
  50. package/build/plane/preflight.js +43 -0
  51. package/build/plane/shutdown.js +25 -3
  52. package/build/plane/status.js +11 -0
  53. package/build/plane/supervisor.js +343 -14
  54. package/build/plane/test-fakes.js +43 -0
  55. package/build/plane/types.js +82 -11
  56. package/build/pr-base-contract.js +20 -0
  57. package/build/readme.generated.js +1 -1
  58. package/build/review-synthesis-config.js +60 -0
  59. package/build/scripts/executor-protocol-contract-driver.js +311 -0
  60. package/build/setup-epic.js +592 -139
  61. package/build/sfcc/log-query.js +2 -1
  62. package/build/sfcc/reads-custom-object-def.js +10 -13
  63. package/build/sfcc/reads-site-preference.js +5 -5
  64. package/build/sfcc/reads-system-object.js +4 -4
  65. package/build/sfcc/writes-custom-object-def.js +7 -7
  66. package/build/sfcc/writes-site-preference.js +4 -3
  67. package/build/sfcc/writes-system-object.js +7 -6
  68. package/build/start-tickets-conductor.js +11 -2
  69. package/build/start-tickets.js +69 -2
  70. package/build/version.generated.js +3 -3
  71. package/build/worker-containment-diagnostic.js +97 -0
  72. package/build/worker-guard-hook-bin.js +6 -0
  73. package/docs/CONDUCTOR.md +27 -0
  74. package/docs/install/mcp-tool-integrations.md +3 -2
  75. package/package.json +5 -3
  76. package/pipelines/plan-epic.json +5 -0
package/build/doctor.js CHANGED
@@ -28,9 +28,11 @@ import { createBridgeApiUrls } from "./bridge-api-urls.js";
28
28
  import { probeToolSurface } from "./tool-surface-gating.js";
29
29
  import { resolveBapiCredentials } from "./credential-store.js";
30
30
  import { resolveConductorBridgeApiAccess } from "./conductor/bridge-api-client.js";
31
+ import { getLiveRepositoryConductors, getParseDispatcherHealth, } from "./conduct-epic/bridge-client.js";
31
32
  import { resolveConductEpicStateDirectory } from "./conduct-epic/checkpoint-store.js";
32
33
  import { isConductEpicLockOwnerAlive, parseConductEpicLock, } from "./conduct-epic/lock.js";
33
34
  import { resolveRequiredStartTicketsRepoName } from "./start-tickets-repo.js";
35
+ import { collectWorkerContainmentDiagnostic, formatWorkerContainmentDiagnosticReport, } from "./worker-containment-diagnostic.js";
34
36
  import { BRIDGE_PACKAGE_NAME, describeLauncherReason, DUPLICATE_REGISTRATION_GUIDANCE, inspectLauncherConfigs, parseLauncherPin as parseSharedLauncherPin, } from "./launcher-config-inspection.js";
35
37
  import { MCP_SERVER_NAME, MCP_PACKAGE_NAME, } from "./mcp-identity.js";
36
38
  import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, } from "./agent-registry.js";
@@ -96,7 +98,17 @@ export function getDoctorUsage() {
96
98
  // scoping, and BAPI-791 deleted that doctor section together with the whole
97
99
  // worker-auth strategy vocabulary. The section it described exists in no
98
100
  // doctor output, so the claim is removed rather than reworded.
99
- "Worker containment is no longer reported here. A conductor worker is scoped",
101
+ "It includes an advisory 'Worker containment' section (BAPI-1019) reporting the",
102
+ "DECLARED posture a conductor worker runs under: that the executor strips this",
103
+ "checkout's .env* files from a worker's worktree as the last step before spawn",
104
+ "(refusing to spawn with ContractError.WorkerEnvFilePresent when it cannot",
105
+ "confirm they are gone), that a worker's database environment names a target",
106
+ "that does not exist, and the run policy's default permission posture. The",
107
+ "section issues ZERO probes — no command, no file read, no network call — and",
108
+ "reports constants rather than measurements, so it never claims a live check",
109
+ "ran and never changes the exit code.",
110
+ "",
111
+ "MCP containment is still not reported here. A conductor worker is scoped",
100
112
  "at spawn time with `--strict-mcp-config --mcp-config <file>`, and the",
101
113
  "executor asserts that scope per spawn from the worker's own system/init",
102
114
  "event — failing the job with ContractError.McpSurfaceMismatch on a confirmed",
@@ -1401,6 +1413,151 @@ export function formatConductEpicDiagnosticReport(diagnostic) {
1401
1413
  lines.push(" lock, rewrites no checkpoint, and never changes the exit code.");
1402
1414
  return lines.join("\n");
1403
1415
  }
1416
+ /**
1417
+ * Collect the operational advisories, read-only.
1418
+ *
1419
+ * Local pilot conductors are identified from the SAME checkpoint-directory and
1420
+ * lock inspection `conduct-epic readiness` uses — `parseConductEpicLock` plus the
1421
+ * injected liveness seam — never from the process table. Only a lock proven live
1422
+ * on THIS host counts; malformed, unreadable, dead, and remote-host locks are
1423
+ * excluded from the live set, with the indeterminate ones counted separately.
1424
+ */
1425
+ export async function collectOperationalAdvisories(deps) {
1426
+ const isAlive = deps.isProcessAlive ?? isConductEpicLockOwnerAlive;
1427
+ const now = deps.now ?? (() => new Date());
1428
+ const resolveAccess = deps.resolveAccess ?? resolveConductorBridgeApiAccess;
1429
+ let dispatcher = null;
1430
+ let conductorObservation = "unavailable";
1431
+ const conductors = [];
1432
+ let repo = null;
1433
+ let access = null;
1434
+ try {
1435
+ access = await resolveAccess({
1436
+ env: deps.env,
1437
+ cwd: deps.cwd,
1438
+ homedir: deps.homedir,
1439
+ platform: deps.platform,
1440
+ readFile: deps.readFile,
1441
+ stat: deps.stat,
1442
+ });
1443
+ }
1444
+ catch {
1445
+ access = null;
1446
+ }
1447
+ if (access !== null && access.ok) {
1448
+ repo = access.access.repoName;
1449
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
1450
+ const health = await getParseDispatcherHealth(access.access, fetchImpl);
1451
+ dispatcher = health.ok
1452
+ ? health.value
1453
+ : { observation: "unavailable", heartbeatState: null, respondingSchedulerRunning: false };
1454
+ const live = await getLiveRepositoryConductors(access.access, now(), fetchImpl);
1455
+ if (live.ok) {
1456
+ conductorObservation = live.value.observation;
1457
+ conductors.push(...live.value.conductors);
1458
+ }
1459
+ }
1460
+ // Local pilot locks. Read through the same path the readiness section uses; a
1461
+ // failure to read the directory is silence, not a claim that none are held.
1462
+ let indeterminateLocks = 0;
1463
+ if (repo !== null) {
1464
+ const stateDirectory = resolveConductEpicStateDirectory(repo, {
1465
+ env: deps.env,
1466
+ homedir: deps.homedir,
1467
+ });
1468
+ let entries = [];
1469
+ try {
1470
+ entries = await deps.readdir(stateDirectory);
1471
+ }
1472
+ catch {
1473
+ entries = [];
1474
+ }
1475
+ for (const entry of entries) {
1476
+ if (!entry.endsWith(".lock"))
1477
+ continue;
1478
+ const epic = entry.slice(0, -".lock".length);
1479
+ let raw;
1480
+ try {
1481
+ raw = await deps.readFile(path.join(stateDirectory, entry));
1482
+ }
1483
+ catch {
1484
+ indeterminateLocks += 1;
1485
+ continue;
1486
+ }
1487
+ const owner = parseConductEpicLock(raw);
1488
+ if (owner === null) {
1489
+ indeterminateLocks += 1;
1490
+ continue;
1491
+ }
1492
+ const local = owner.host === deps.env.HOSTNAME || owner.host === os.hostname();
1493
+ if (!local) {
1494
+ indeterminateLocks += 1;
1495
+ continue;
1496
+ }
1497
+ if (!isAlive(owner.owner_pid))
1498
+ continue;
1499
+ // A pilot lock and a v2 run are two DISTINCT conductor authorities. Even
1500
+ // when both name the same epic they are not deduplicated — that pairing is
1501
+ // precisely the overlap an operator most needs to see.
1502
+ //
1503
+ // The key comes from a FILENAME, so it is bounded against the epic-key shape
1504
+ // before it is rendered; an unrecognized name reports `unknown` rather than
1505
+ // putting arbitrary filesystem text into a diagnostic line.
1506
+ conductors.push({
1507
+ kind: "pilot",
1508
+ epicKey: validateTicketKey(epic, "epic key").ok ? epic : null,
1509
+ state: "lock held",
1510
+ livenessSource: "lock",
1511
+ });
1512
+ }
1513
+ if (conductors.length > 0 && conductorObservation !== "observed") {
1514
+ conductorObservation = "observed";
1515
+ }
1516
+ }
1517
+ return { dispatcher, conductorObservation, conductors, indeterminateLocks };
1518
+ }
1519
+ /** Render the operational-advisories section. Advisory: never changes the exit code. */
1520
+ export function formatOperationalAdvisoryReport(diagnostic) {
1521
+ const lines = ["", "operational advisories (advisory)"];
1522
+ const dispatcher = diagnostic.dispatcher;
1523
+ if (dispatcher === null) {
1524
+ lines.push(" SKIP Parse dispatcher: not checked (no Bridge credential resolved).");
1525
+ }
1526
+ else if (dispatcher.observation === "observed") {
1527
+ lines.push(" OK Parse dispatcher: observable — queued repository parses are being swept.");
1528
+ }
1529
+ else if (dispatcher.observation === "absent") {
1530
+ lines.push(" WARN Parse dispatcher: NOT observed. Queued repository parses wait for the\n" +
1531
+ " `worker:` dyno's parse-queue sweep; nothing else dispatches them.");
1532
+ }
1533
+ else {
1534
+ lines.push(" SKIP Parse dispatcher: unavailable — the liveness read produced no verdict.\n" +
1535
+ " This is not evidence that no dispatcher is running.");
1536
+ }
1537
+ if (diagnostic.conductorObservation === "unavailable") {
1538
+ lines.push(" SKIP Concurrent conductors: unavailable — the repository-scoped read produced\n" +
1539
+ " no verdict.");
1540
+ }
1541
+ else if (diagnostic.conductors.length === 0) {
1542
+ lines.push(" OK Concurrent conductors: none live on this repository.");
1543
+ }
1544
+ else {
1545
+ lines.push(` WARN Concurrent conductors: ${diagnostic.conductors.length} live on this repository.`);
1546
+ for (const conductor of diagnostic.conductors) {
1547
+ lines.push(` ${conductor.kind} epic=${conductor.epicKey ?? "unknown"} ` +
1548
+ `state=${conductor.state} liveness=${conductor.livenessSource}`);
1549
+ }
1550
+ lines.push(" A distinct-epic overlap is informational and may be legitimate: separately\n" +
1551
+ " scoped epics do not share transition authority.");
1552
+ }
1553
+ if (diagnostic.indeterminateLocks > 0) {
1554
+ lines.push(` ${diagnostic.indeterminateLocks} local lock(s) could not be judged (remote host,\n` +
1555
+ " malformed, or unreadable) and are excluded from the live count.");
1556
+ }
1557
+ lines.push(" Read-only: this section issues two Bridge GETs and reads local lock state. It");
1558
+ lines.push(" runs NO command probe, repairs no scheduler, and never changes the exit code.");
1559
+ return lines.join("\n");
1560
+ }
1404
1561
  /**
1405
1562
  * CLI entry for the read-only `doctor` subcommand. Returns a process exit code.
1406
1563
  * Help returns 0; parser errors return 1; otherwise it prints the report and
@@ -1550,6 +1707,34 @@ export async function runDoctorCli(argv, overrides = {}) {
1550
1707
  /* local-launcher diagnostics are advisory; never block the doctor report */
1551
1708
  }
1552
1709
  }
1710
+ // Worker containment advisory section (BAPI-1019/A.7). Placed beside the other
1711
+ // advisory sections and, uniquely, issuing NO probe of any kind — no command,
1712
+ // no file read, no network call. That is what keeps doctor's "an unsupported
1713
+ // platform issues zero command probes" guarantee intact: a section that cannot
1714
+ // probe cannot break it on any platform.
1715
+ //
1716
+ // Wrapped in the same swallowing `try` as its neighbours. It reports declared
1717
+ // constants, so it should not be able to throw at all — which is exactly why an
1718
+ // unexpected throw here must not take the whole report (or the exit code) with
1719
+ // it.
1720
+ if (overrides.workerContainment !== false) {
1721
+ try {
1722
+ log(formatWorkerContainmentDiagnosticReport(collectWorkerContainmentDiagnostic()));
1723
+ }
1724
+ catch {
1725
+ // Degrade VISIBLY rather than dropping a section the help text promises:
1726
+ // the formatter's own unusable-input path renders the heading with
1727
+ // "Not confirmed" rows, which is the honest answer and is exactly what a
1728
+ // reader needs to see instead of silence. Passing `undefined` reuses that
1729
+ // one fallback rather than inventing a second wording here.
1730
+ try {
1731
+ log(formatWorkerContainmentDiagnosticReport(undefined));
1732
+ }
1733
+ catch {
1734
+ /* worker-containment posture is advisory; never block the doctor report */
1735
+ }
1736
+ }
1737
+ }
1553
1738
  // Advisory MCP tool-surface capability section (BAPI-641). Strictly read-only:
1554
1739
  // one 500 ms GET (or none, under the kill switch). Any timeout, malformed
1555
1740
  // response, or unexpected throw degrades to a "fail-open to full surface" line
@@ -1701,6 +1886,50 @@ export async function runDoctorCli(argv, overrides = {}) {
1701
1886
  }));
1702
1887
  }
1703
1888
  }
1889
+ // Advisory operational-advisories section (BAPI-963), placed immediately after
1890
+ // `conduct-epic readiness` because both are read by the same operator at the
1891
+ // same moment — "can I conduct?" then "what else is already running?".
1892
+ //
1893
+ // Gated on `collection.ok` for the same reason the section above is: an
1894
+ // unsupported platform reports nothing. That gate is belt-and-braces here,
1895
+ // because this section issues NO command probe on any path — the two facts it
1896
+ // reports come from read-only Bridge GETs and from local lock state. The
1897
+ // "unsupported platform runs zero probes" pin is therefore untouched by it.
1898
+ //
1899
+ // Advisory with respect to the EXIT CODE: an absent dispatcher, a concurrent
1900
+ // conductor, and an unavailable read all leave doctor's pass/fail exactly as
1901
+ // the required prerequisites computed it.
1902
+ if (overrides.operationalAdvisories !== false && collection.ok) {
1903
+ try {
1904
+ const injectedFs = deps;
1905
+ const advisoryDeps = {
1906
+ env: overrides.operationalAdvisories?.env ?? deps.env,
1907
+ cwd: overrides.operationalAdvisories?.cwd ?? deps.cwd,
1908
+ platform: overrides.operationalAdvisories?.platform ?? deps.platform,
1909
+ homedir: overrides.operationalAdvisories?.homedir ?? injectedFs.homedir ?? os.homedir,
1910
+ readFile: overrides.operationalAdvisories?.readFile ??
1911
+ injectedFs.readFile ??
1912
+ ((p) => readFile(p, "utf-8")),
1913
+ stat: overrides.operationalAdvisories?.stat ?? injectedFs.stat ?? ((p) => stat(p)),
1914
+ readdir: overrides.operationalAdvisories?.readdir ?? ((p) => readdir(p)),
1915
+ resolveAccess: overrides.operationalAdvisories?.resolveAccess,
1916
+ isProcessAlive: overrides.operationalAdvisories?.isProcessAlive,
1917
+ fetchImpl: overrides.operationalAdvisories?.fetchImpl,
1918
+ now: overrides.operationalAdvisories?.now,
1919
+ };
1920
+ log(formatOperationalAdvisoryReport(await collectOperationalAdvisories(advisoryDeps)));
1921
+ }
1922
+ catch {
1923
+ // Any unexpected failure still renders a sanitized advisory section rather
1924
+ // than dropping it or leaking exception text.
1925
+ log(formatOperationalAdvisoryReport({
1926
+ dispatcher: null,
1927
+ conductorObservation: "unavailable",
1928
+ conductors: [],
1929
+ indeterminateLocks: 0,
1930
+ }));
1931
+ }
1932
+ }
1704
1933
  // Opt-in stale-ticket-branch section (BAPI-948). Gated on argv, not on platform
1705
1934
  // support: it runs ONLY when the operator named at least one `--stale-branch`
1706
1935
  // ticket, so an ordinary `doctor` run issues zero extra command probes and