@bridge_gpt/mcp-server 0.2.68 → 0.2.69

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/build/doctor.js CHANGED
@@ -36,6 +36,9 @@ import { modelRoutingPreferencesPath, readModelRoutingPreference } from "./model
36
36
  import { inspectExecutorAgentAdapter, inspectionForFailedResolution, } from "./agent-launchers/executor-adapter-inspection.js";
37
37
  import { resolveExecutorAgentAdapter } from "./agent-launchers/executor-adapter-registry.js";
38
38
  import { parseDefaultOnEnvFlag } from "./env-flags.js";
39
+ import { checkForUpdate, isPlainReleaseVersion } from "./update-check.js";
40
+ import { getNoticeDecision, isUnattendedSession, UPDATE_CHECK_ENABLED_ENV, } from "./session-upgrade-notice.js";
41
+ import { formatUpdateAdvice } from "./upgrade-advice.js";
39
42
  import { createBridgeApiUrls } from "./bridge-api-urls.js";
40
43
  import { probeToolSurface } from "./tool-surface-gating.js";
41
44
  import { readStoredBindingState, resolveBapiCredentials } from "./credential-store.js";
@@ -45,7 +48,7 @@ import { validateHttpBaseUrl } from "./base-url.js";
45
48
  import { getLiveRepositoryConductors, getParseDispatcherHealth, } from "./epic-shared/bridge-client.js";
46
49
  import { resolveRequiredStartTicketsRepoName } from "./start-tickets-repo.js";
47
50
  import { collectWorkerContainmentDiagnostic, formatWorkerContainmentDiagnosticReport, } from "./worker-containment-diagnostic.js";
48
- import { BRIDGE_PACKAGE_NAME, describeLauncherReason, DUPLICATE_REGISTRATION_GUIDANCE, inspectLauncherConfigs, parseLauncherPin as parseSharedLauncherPin, } from "./launcher-config-inspection.js";
51
+ import { BRIDGE_PACKAGE_NAME, describeLauncherReason, DUPLICATE_REGISTRATION_GUIDANCE, inspectLauncherConfigs, isExactVersion, isPrerequisiteFailure, parseLauncherPin as parseSharedLauncherPin, } from "./launcher-config-inspection.js";
49
52
  import { MCP_SERVER_NAME, MCP_PACKAGE_NAME, } from "./mcp-identity.js";
50
53
  import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, } from "./agent-registry.js";
51
54
  import { getDoctorPrereqDescriptors, probePrerequisite, } from "./start-tickets-prereqs.js";
@@ -166,7 +169,8 @@ export function getDoctorUsage() {
166
169
  "",
167
170
  "It also includes an advisory '/drive-epic-run permission rules' section",
168
171
  "(BAPI-1398): whether the project permissions.allow rules and the optional user",
169
- "autoMode entries that install writes are PRESENT, MISSING, or DRIFTED (for",
172
+ "autoMode entries (added through /drive-epic-run or the bridge-permissions",
173
+ "subcommand, never by install) are PRESENT, MISSING, or DRIFTED (for",
170
174
  "example after the BAPI_MCP_CLI launcher changed). It reads .claude/settings.local.json,",
171
175
  "~/.claude/settings.json, and .bridge/bridge-permissions.json, writes nothing,",
172
176
  "and never changes the exit code.",
@@ -386,6 +390,82 @@ export async function collectDoctorResults(deps, agentName) {
386
390
  }
387
391
  return { ok: true, results };
388
392
  }
393
+ /** The production checker, bound so it can neither read nor populate the cache. */
394
+ export function createDoctorVersionChecker(env) {
395
+ return () => checkForUpdate({ env, readCache: false, writeCache: false });
396
+ }
397
+ /**
398
+ * Collect the version diagnostic. Never throws. The lookup is skipped when update
399
+ * checks are disabled or the session is unattended; a null, rejected, or malformed
400
+ * result is `unavailable`. Only a reliable fresh (or override) result reaches the
401
+ * shared notice decision, and only then are project pins evaluated.
402
+ */
403
+ export async function collectVersionDiagnostic(deps) {
404
+ try {
405
+ if (!parseDefaultOnEnvFlag(deps.env[UPDATE_CHECK_ENABLED_ENV]) || isUnattendedSession(deps.env)) {
406
+ return { status: "not-checked" };
407
+ }
408
+ let result = null;
409
+ try {
410
+ result = await deps.checkLatest();
411
+ }
412
+ catch {
413
+ result = null;
414
+ }
415
+ if (!result || typeof result !== "object" || !isPlainReleaseVersion(result.latestVersion)) {
416
+ return { status: "unavailable" };
417
+ }
418
+ const latest = result.latestVersion;
419
+ const authoritative = { known: true, latest };
420
+ const upgrade = getNoticeDecision(deps.env, VERSION, authoritative).show;
421
+ const stalePins = [];
422
+ try {
423
+ const shared = await inspectLauncherConfigs({
424
+ cwd: deps.cwd,
425
+ targetVersion: VERSION,
426
+ readFile: deps.readFile,
427
+ });
428
+ for (const found of shared) {
429
+ if (!found.filePresent || !found.bridgeEntryPresent)
430
+ continue;
431
+ if (found.shape !== "standard" || isPrerequisiteFailure(found))
432
+ continue;
433
+ if (!isExactVersion(found.currentVersion))
434
+ continue;
435
+ const pin = found.currentVersion;
436
+ if (getNoticeDecision(deps.env, pin, authoritative).show) {
437
+ stalePins.push({ relPath: found.relPath, pin });
438
+ }
439
+ }
440
+ }
441
+ catch {
442
+ // Pin diagnostics are advisory; the installed-runtime line still renders.
443
+ }
444
+ return { status: "known", latest, upgrade, stalePins };
445
+ }
446
+ catch {
447
+ return { status: "unavailable" };
448
+ }
449
+ }
450
+ /** The `Bridge MCP version` lines, or none when no diagnostic was collected. */
451
+ export function formatDoctorVersionLines(diagnostic) {
452
+ if (!diagnostic)
453
+ return [];
454
+ if (diagnostic.status === "not-checked") {
455
+ return [`Bridge MCP version: installed ${VERSION}, latest: not checked`];
456
+ }
457
+ if (diagnostic.status === "unavailable") {
458
+ return [`Bridge MCP version: installed ${VERSION}, latest: unavailable`];
459
+ }
460
+ const lines = [`Bridge MCP version: installed ${VERSION}, latest ${diagnostic.latest}`];
461
+ if (diagnostic.upgrade)
462
+ lines.push(` ${formatUpdateAdvice(VERSION, diagnostic.latest)}`);
463
+ // Stale project pins are subordinate to the runtime line above.
464
+ for (const { relPath, pin } of diagnostic.stalePins) {
465
+ lines.push(` ${relPath} pin: ${formatUpdateAdvice(pin, diagnostic.latest)}`);
466
+ }
467
+ return lines;
468
+ }
389
469
  /**
390
470
  * The BROAD report header: title plus the run's platform / agent / profile
391
471
  * metadata (BAPI-669, U9b). Split out from the prerequisite body so `runDoctorCli`
@@ -393,7 +473,10 @@ export async function collectDoctorResults(deps, agentName) {
393
473
  * ordering change is the whole point, and it must not require rewriting the
394
474
  * prerequisite formatting below.
395
475
  */
396
- export function buildDoctorReportHeader(platform, agent) {
476
+ export function buildDoctorReportHeader(platform, agent,
477
+ // A DEFAULT value, not a plain optional marker, so the function's declared arity
478
+ // stays two: the header takes no dependency seam and does no I/O.
479
+ versionDiagnostic = undefined) {
397
480
  const activeGroups = Array.from(resolveProfiles(process.env.BRIDGE_MCP_PROFILE)).join(", ");
398
481
  return [
399
482
  DOCTOR_REPORT_TITLE,
@@ -404,6 +487,7 @@ export function buildDoctorReportHeader(platform, agent) {
404
487
  // never blank and never omitted. Neither line feeds launcher-pin drift,
405
488
  // which stays a `VERSION`-only comparison.
406
489
  `Package version: ${VERSION}`,
490
+ ...formatDoctorVersionLines(versionDiagnostic),
407
491
  `Build commit: ${BUILD_COMMIT}`,
408
492
  `Platform: ${platform}`,
409
493
  `Selected agent: ${agent.name} (command: ${agent.command})`,
@@ -1736,10 +1820,10 @@ export function formatBridgePermissionsDiagnosticReport(inspection) {
1736
1820
  else {
1737
1821
  lines.push(...formatPermissionScope("project permissions.allow", inspection.project));
1738
1822
  }
1739
- lines.push(...formatPermissionScope("user autoMode entries", inspection.user, "Optional: install adds them only after you say yes."));
1823
+ lines.push(...formatPermissionScope("user autoMode entries", inspection.user, `Optional: /drive-epic-run offers to add them, or run \`npx -y ${MCP_PACKAGE_NAME} bridge-permissions\`.`));
1740
1824
  const healthy = inspection.project.state === "present" && inspection.user.state === "present";
1741
1825
  if (!healthy) {
1742
- lines.push(` To repair: re-run \`npx -y ${MCP_PACKAGE_NAME} install\` in a terminal (it lists every rule it writes).`);
1826
+ lines.push(` To repair the project rules: re-run \`npx -y ${MCP_PACKAGE_NAME} install\` in a terminal (it lists every rule it writes).`);
1743
1827
  lines.push(` To remove Bridge's entries: \`npx -y ${MCP_PACKAGE_NAME} install --remove-bridge-permissions\`.`);
1744
1828
  }
1745
1829
  lines.push(` Bridge's ownership record: ${BRIDGE_PERMISSIONS_MANIFEST_RELPATH}. Advisory only; never changes the exit code.`);
@@ -1885,7 +1969,26 @@ export async function runDoctorCli(argv, overrides = {}) {
1885
1969
  // always-first is both simpler and equivalent. The `installStatus: false` override
1886
1970
  // remains a dependency-injection-only seam for hermetic tests; a normal CLI
1887
1971
  // invocation never takes it.
1888
- log(buildDoctorReportHeader(deps.platform, agent).join("\n"));
1972
+ // BAPI-1446: the version diagnostic is advisory and read-only (fresh lookup, no
1973
+ // cache read or write, no spawn). A failure leaves the header exactly as it was.
1974
+ let versionDiagnostic;
1975
+ if (overrides.version !== false && (overrides.deps === undefined || overrides.version?.checkLatest)) {
1976
+ try {
1977
+ const versionEnv = overrides.version?.env ?? deps.env;
1978
+ versionDiagnostic = await collectVersionDiagnostic({
1979
+ env: versionEnv,
1980
+ cwd: overrides.version?.cwd ?? overrides.launcherProbe?.cwd ?? deps.cwd,
1981
+ readFile: overrides.version?.readFile ??
1982
+ overrides.launcherProbe?.readFile ??
1983
+ ((p) => readFile(p, "utf-8")),
1984
+ checkLatest: overrides.version?.checkLatest ?? createDoctorVersionChecker(versionEnv),
1985
+ });
1986
+ }
1987
+ catch {
1988
+ versionDiagnostic = undefined;
1989
+ }
1990
+ }
1991
+ log(buildDoctorReportHeader(deps.platform, agent, versionDiagnostic).join("\n"));
1889
1992
  // Advisory install-status section (easy-install done criteria). Read-only GETs
1890
1993
  // only; any failure degrades to WARN/SKIP lines inside the section, and any
1891
1994
  // unexpected throw renders the sanitized fallback rather than dropping the