@bridge_gpt/mcp-server 0.2.68 → 0.2.70

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 (166) hide show
  1. package/README.md +226 -320
  2. package/build/agent-launchers/claude-executor-adapter.js +17 -4
  3. package/build/agents.generated.js +4 -4
  4. package/build/artifact-diff.js +110 -0
  5. package/build/bin-entrypoint.js +36 -0
  6. package/build/bridge-permissions.js +650 -98
  7. package/build/check-update-cli.js +74 -0
  8. package/build/claude-review-workflow-drift.js +3 -3
  9. package/build/claude-settings.js +84 -2
  10. package/build/cli-help.js +7 -1
  11. package/build/codex-bridge-profile.js +1 -1
  12. package/build/codex-home.js +9 -1
  13. package/build/codex-launcher-pin.js +598 -0
  14. package/build/commands.generated.js +8 -8
  15. package/build/conductor/bridge-api-client.js +598 -19
  16. package/build/conductor/bring-up-facts.js +9 -9
  17. package/build/conductor/doctor.js +466 -18
  18. package/build/conductor/epic-child-continuation.js +54 -3
  19. package/build/conductor/epic-implementer-cli.js +132 -34
  20. package/build/conductor/epic-runtime.js +2 -2
  21. package/build/conductor/epic-state.js +1 -1
  22. package/build/conductor/hook-runtime-qualification.js +1 -1
  23. package/build/conductor/install-doctor.js +4 -2
  24. package/build/conductor/launch-need-probes.js +337 -0
  25. package/build/conductor/local-merge.js +241 -56
  26. package/build/conductor/merge-credential.js +172 -0
  27. package/build/conductor/merge-execution-eligibility.js +1 -1
  28. package/build/conductor/merged-child-check.js +95 -0
  29. package/build/conductor/plan-trim.js +30 -0
  30. package/build/conductor/pr-ci-producer.js +1 -1
  31. package/build/conductor/readiness-cli.js +11 -6
  32. package/build/conductor/readiness-prerequisites.js +41 -0
  33. package/build/conductor/readiness-sections.js +345 -55
  34. package/build/conductor/readiness.js +99 -87
  35. package/build/conductor/recovery-cli.js +606 -21
  36. package/build/conductor/recovery-operations.js +306 -7
  37. package/build/conductor/repair-contract.js +47 -2
  38. package/build/conductor/repository-workflow-remedy.js +8 -6
  39. package/build/conductor/stored-merge-credential.js +76 -0
  40. package/build/conductor/stored-merge-token-probe.js +28 -0
  41. package/build/conductor/temporary-plan.js +50 -0
  42. package/build/conductor/wait-run-cli.js +759 -0
  43. package/build/conductor/worker-database-doctor-inspector.js +49 -0
  44. package/build/conductor-bin.js +2 -2
  45. package/build/conductor-claude-hook-bin.js +2 -2
  46. package/build/conductor-claude-hook-removed-stub-bin.js +2 -5
  47. package/build/conductor-removed-stub-bin.js +2 -5
  48. package/build/connect-github-api.js +33 -14
  49. package/build/credential-store.js +356 -13
  50. package/build/docs.generated.js +27 -14
  51. package/build/doctor.js +203 -38
  52. package/build/drive-epic-help.js +212 -0
  53. package/build/drive-epic.js +1840 -373
  54. package/build/epic-ci-workflow-drift.js +77 -0
  55. package/build/epic-ci-workflow-install.js +183 -0
  56. package/build/epic-ci-workflow.js +157 -0
  57. package/build/epic-contract-context.js +326 -0
  58. package/build/epic-implementer-bin.js +131 -120
  59. package/build/epic-implementer-claude-hook-bin.js +2 -2
  60. package/build/escalation-reachability.js +134 -0
  61. package/build/executor/cleanup-report.js +99 -0
  62. package/build/executor/cli.js +12 -1
  63. package/build/executor/completion-contract.js +68 -11
  64. package/build/executor/completion-supervision.js +99 -0
  65. package/build/executor/delivery-outcome.js +179 -0
  66. package/build/executor/deps.js +3 -0
  67. package/build/executor/env.js +71 -0
  68. package/build/executor/failure-reason-codes.js +126 -0
  69. package/build/executor/heartbeat.js +38 -0
  70. package/build/executor/http-client.js +115 -4
  71. package/build/executor/integration-job.js +298 -5
  72. package/build/executor/job-errors.js +36 -6
  73. package/build/executor/job-runner.js +720 -36
  74. package/build/executor/likely-tests.js +36 -2
  75. package/build/executor/merge-job.js +145 -2
  76. package/build/executor/observation.js +73 -4
  77. package/build/executor/permissions.js +193 -97
  78. package/build/executor/preflight.js +14 -3
  79. package/build/executor/process-containment.js +28 -2
  80. package/build/executor/prompt-spec.js +50 -5
  81. package/build/executor/rebase-finalization.js +38 -3
  82. package/build/executor/reconcile-overlap-job.js +35 -7
  83. package/build/executor/required-work-ledger.js +236 -81
  84. package/build/executor/resume-pre-spawn.js +38 -1
  85. package/build/executor/runner.js +91 -3
  86. package/build/executor/test-clock.js +8 -0
  87. package/build/executor/types.js +37 -2
  88. package/build/executor/worker-cli-launcher.js +259 -0
  89. package/build/executor/worker-command.js +41 -4
  90. package/build/executor/worker-database-target.js +91 -0
  91. package/build/executor/worker-database.js +908 -0
  92. package/build/executor/worker-finalization.js +106 -0
  93. package/build/executor/worker-guard-hook.js +203 -0
  94. package/build/executor/worker-resources.js +47 -0
  95. package/build/executor/worker-verification-guidance.js +15 -2
  96. package/build/executor/worktree.js +303 -15
  97. package/build/executor-launch.js +11 -1
  98. package/build/generated-assets-doctor.js +9 -1
  99. package/build/generated-assets-health.js +13 -4
  100. package/build/generated-assets-legacy.js +39 -0
  101. package/build/generated-assets-paths.js +12 -1
  102. package/build/generated-assets-repair.js +4 -1
  103. package/build/generated-assets-test-support.js +12 -0
  104. package/build/github-remote.js +33 -0
  105. package/build/github-sign-in.js +62 -0
  106. package/build/index.js +1214 -465
  107. package/build/init.js +39 -1
  108. package/build/install-bridge-conductor-gate.js +78 -0
  109. package/build/install-bridge-conductor-setup.js +413 -0
  110. package/build/install-bridge-conductor.js +1226 -267
  111. package/build/install-bridge.js +629 -350
  112. package/build/launcher-config-inspection.js +184 -19
  113. package/build/mcp-host-targets.js +4 -0
  114. package/build/mcp-identity.js +7 -0
  115. package/build/mcp-install-state.js +8 -2
  116. package/build/merge-pull-request.js +6 -0
  117. package/build/merge-workflow-install.js +763 -345
  118. package/build/phase-result-artifacts.js +75 -20
  119. package/build/pipeline-orchestrator.js +247 -4
  120. package/build/pipelines.generated.js +35 -35
  121. package/build/plan-discovery.js +15 -2
  122. package/build/plan-execution-ledger.js +306 -17
  123. package/build/plan-phase-routing.js +48 -13
  124. package/build/plane/cli.js +105 -51
  125. package/build/plane/defaults.js +3 -0
  126. package/build/plane/plane-freshness.js +294 -0
  127. package/build/plane/schema-compatibility.js +137 -0
  128. package/build/plane/shutdown-deps.js +38 -0
  129. package/build/plane/shutdown.js +83 -8
  130. package/build/plane/terminal-run-cleanup.js +303 -0
  131. package/build/printed-remedy-commands.js +26 -4
  132. package/build/readiness-check.js +6 -2
  133. package/build/readiness-prerequisites.generated.js +532 -0
  134. package/build/readme.generated.js +1 -1
  135. package/build/session-upgrade-notice.js +86 -0
  136. package/build/setup-epic.js +400 -30
  137. package/build/shared-resources.generated.js +5 -0
  138. package/build/start-tickets.js +43 -15
  139. package/build/test-command-detection.js +247 -0
  140. package/build/test-command-validation.js +73 -0
  141. package/build/testing-config-client.js +150 -0
  142. package/build/ticket-delivery-artifacts.js +258 -0
  143. package/build/ticket-delivery-enrollment.js +306 -0
  144. package/build/ticket-delivery.js +205 -0
  145. package/build/ticket-review-summary.js +14 -0
  146. package/build/tool-call-advisory.js +162 -0
  147. package/build/tool-surface-gating.js +28 -89
  148. package/build/update-check.js +97 -16
  149. package/build/update-status.js +61 -71
  150. package/build/upgrade-advice.js +15 -11
  151. package/build/upgrade-cli.js +360 -20
  152. package/build/v2-readiness-requirements.js +552 -0
  153. package/build/version.generated.js +3 -3
  154. package/build/worker-guard-hook-bin.js +2 -2
  155. package/build/worktree-core.js +22 -7
  156. package/docs/BRIDGE-DIRECTORY-README.md +48 -0
  157. package/docs/EPIC-IMPLEMENTER-INSTALL.md +12 -867
  158. package/docs/EPIC-IMPLEMENTER.md +104 -63
  159. package/docs/epic-implementer/getting-started.md +131 -0
  160. package/docs/epic-implementer/self-hosting.md +133 -0
  161. package/package.json +3 -3
  162. package/pipelines/idea-to-ticket.json +1 -1
  163. package/pipelines/implement-ticket.json +8 -8
  164. package/pipelines/review-ticket.json +2 -2
  165. package/design-assets/favicon/favicon-32x32.png +0 -0
  166. package/design-assets/just-logo-rough-draft.png +0 -0
@@ -30,9 +30,41 @@
30
30
  *
31
31
  * Every launcher claim is verified by RE-READING the configs after the write.
32
32
  * The command never reports a transition it did not confirm on disk.
33
+ *
34
+ * ---------------------------------------------------------------------------
35
+ * BAPI-1445 — Codex's global config
36
+ * ---------------------------------------------------------------------------
37
+ * The three project JSON configs are not the only place a Bridge pin lives:
38
+ * Codex keeps its registration in one per-user TOML file
39
+ * (`~/.codex/config.toml`, or `$CODEX_HOME/config.toml` for an absolute
40
+ * override). A repin that skipped it left Codex on an old server for every
41
+ * project on the machine. Upgrade now inspects, repins, and verifies that file
42
+ * too, beside the JSON targets, with these boundaries:
43
+ *
44
+ * - Preflight refusal guarantees NO writes. An unsafe Codex file (a range, tag,
45
+ * or ahead pin, a duplicate or unlocatable registration, an unreadable file)
46
+ * stops the run in Phase 2, before local-install removal, `runInit`, any
47
+ * config write, or the permission write.
48
+ * - A failure AFTER `runInit` can leave the JSON configs upgraded. It exits
49
+ * through `did NOT converge` with exit code 1 — no rollback, no automatic
50
+ * retry, no completion line, and no reload guidance.
51
+ * - The Codex write is one surgical splice of the package specifier, written in
52
+ * place only if the file is byte-identical to what Phase 2 read. That compare
53
+ * is optimistic protection, not a lock.
54
+ * - Install is unchanged: it keeps its existing-table detection, its
55
+ * `manual-required` outcome, and its current guidance. Upgrade adds no install
56
+ * handoff and makes no repair calls. `LAUNCHER_CONFIG_TARGETS`, `runInit`,
57
+ * doctor, and Phases 1, 4, and 8 are untouched.
58
+ * - Windsurf is excluded by its `manual-instructions` write strategy: upgrade
59
+ * never inspects or writes its file, never prints its reload guidance, and
60
+ * leaves its manual setup with the operator.
61
+ * - Upgrade-notice delivery is a separate concern. The one thing this file does
62
+ * for it (BAPI-1446) is Phase 8's advisory SessionStart hook request, for a
63
+ * Claude Code installation only, pinned to the converged target version.
33
64
  */
34
65
  import { spawn } from "child_process";
35
66
  import { mkdir, readFile, stat, writeFile } from "fs/promises";
67
+ import os from "os";
36
68
  import path from "path";
37
69
  import { VERSION } from "./version.generated.js";
38
70
  import { runInit } from "./init.js";
@@ -40,15 +72,18 @@ import { InitPreflightError } from "./init.js";
40
72
  import { isNewerVersion } from "./update-check.js";
41
73
  import { fetchLatestVersion } from "./cli-release.js";
42
74
  import { CANONICAL_UPGRADE_COMMAND } from "./upgrade-advice.js";
43
- import { describeLauncherReason, inspectLauncherConfigs, isApplicableStandardLauncher, isPrerequisiteFailure, LAUNCHER_CONFIG_TARGETS, } from "./launcher-config-inspection.js";
75
+ import { applyCodexRepin, inspectCodexLauncher } from "./codex-launcher-pin.js";
76
+ import { describeLauncherReason, inspectGlobalLauncherConfigs, inspectLauncherConfigs, isApplicableStandardLauncher, isPrerequisiteFailure, LAUNCHER_CONFIG_TARGETS, resolveCodexConfigLocation, } from "./launcher-config-inspection.js";
77
+ import { allHostTargets, HOST_PLATFORM_ORDER, MCP_HOST_TARGETS, } from "./mcp-host-targets.js";
44
78
  import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
45
- import { applyProjectPermissions, BRIDGE_PERMISSIONS_MANIFEST_RELPATH, BRIDGE_PROJECT_SETTINGS_RELPATH, } from "./bridge-permissions.js";
79
+ import { applyProjectPermissions, BRIDGE_PERMISSIONS_MANIFEST_RELPATH, BRIDGE_PROJECT_SETTINGS_RELPATH, describeSessionStartHookOutcome, } from "./bridge-permissions.js";
80
+ import { readMcpInstallState } from "./mcp-install-state.js";
46
81
  import { ensureGitignored } from "./git-ignore-utils.js";
47
82
  import { defaultNpmEntryFileExists, resolveNpmEntry } from "./windows-npm-entry.js";
48
83
  /** The sentinel this CLI passes to the child it re-execs from `@latest`. */
49
84
  const INTERNAL_REEXEC_FLAG = "--internal-reexec";
50
85
  /** Production binding of {@link UpgradeCliDeps.applyBridgePermissions}. */
51
- export async function defaultApplyBridgePermissions(cwd, env) {
86
+ export async function defaultApplyBridgePermissions(cwd, env, sessionStartHook) {
52
87
  const fsDeps = {
53
88
  readFile: (filePath) => readFile(filePath, "utf-8"),
54
89
  writeFile: (filePath, data) => writeFile(filePath, data, "utf-8"),
@@ -62,7 +97,22 @@ export async function defaultApplyBridgePermissions(cwd, env) {
62
97
  // Advisory: the files are still written, and doctor reads them either way.
63
98
  }
64
99
  }
65
- return applyProjectPermissions({ cwd, env }, fsDeps);
100
+ return applyProjectPermissions({ cwd, env, sessionStartHook }, fsDeps);
101
+ }
102
+ /**
103
+ * Is this a Claude Code installation? Answered from the install state `install`
104
+ * recorded, not from a bare `.mcp.json`: that file is also written for other hosts
105
+ * and by hand, so its presence proves nothing about Claude Code. A missing,
106
+ * invalid, or unreadable state is "no", so the hook is never written on a guess.
107
+ */
108
+ async function isClaudeCodeInstallation(deps) {
109
+ try {
110
+ const state = await readMcpInstallState(deps.cwd, { readFile: deps.readFile });
111
+ return state.status === "valid" && state.state.selectedPlatforms.includes("claude-code");
112
+ }
113
+ catch {
114
+ return false;
115
+ }
66
116
  }
67
117
  /**
68
118
  * The fixed, secret-free message printed when, on Windows, the latest CLI could
@@ -198,6 +248,10 @@ export function createDefaultUpgradeCliDeps(cwd) {
198
248
  cwd,
199
249
  fetchLatestVersion,
200
250
  readFile: (filePath) => readFile(filePath, "utf-8"),
251
+ // In place on purpose: writing through the existing file keeps its mode, owner,
252
+ // and symlink behavior, none of which a rename-based write would preserve.
253
+ writeFile: (filePath, content) => writeFile(filePath, content, "utf-8"),
254
+ homedir: () => os.homedir(),
201
255
  hasCompetingLocalInstall: defaultHasCompetingLocalInstall,
202
256
  removeCompetingLocalInstall: (installCwd) => defaultRemoveCompetingLocalInstall(installCwd),
203
257
  runInit,
@@ -316,6 +370,177 @@ export function isEveryLauncherAlreadyCurrent(actions) {
316
370
  return applicable.every((a) => a.kind === "already-current");
317
371
  }
318
372
  // ---------------------------------------------------------------------------
373
+ // Global (Codex) config — BAPI-1445
374
+ // ---------------------------------------------------------------------------
375
+ /** The label that tells a reader this config is machine-wide, not per-project. */
376
+ const GLOBAL_SCOPE_LABEL = "global — applies to every project on this machine";
377
+ /** The path-plus-scope label a global config line carries before its colon. */
378
+ function globalConfigLabel(displayPath) {
379
+ return `${displayPath} (${GLOBAL_SCOPE_LABEL})`;
380
+ }
381
+ /**
382
+ * Project a global inspection onto the project-inspection shape so the report
383
+ * helpers reuse the existing transition, current, normalization, and shim wording
384
+ * rather than growing a second set that could drift.
385
+ */
386
+ function asProjectInspection(found) {
387
+ return { ...found, relPath: globalConfigLabel(found.displayPath), topLevelKey: "mcp_servers" };
388
+ }
389
+ /** Render the dry-run preview line for the global Codex config, or null when inactive. */
390
+ export function formatGlobalDryRunLine(found) {
391
+ return formatDryRunLine(asProjectInspection(found));
392
+ }
393
+ /**
394
+ * The real-run action for the global Codex config, in the form
395
+ * {@link formatResultLine} already renders. Called only after verification
396
+ * passed: a failed write is never advertised as a completed repin.
397
+ */
398
+ function globalResultAction(initial, targetVersion) {
399
+ const relPath = globalConfigLabel(initial.displayPath);
400
+ const registrationKey = initial.registrationKey ?? undefined;
401
+ switch (initial.action) {
402
+ case "repin":
403
+ return {
404
+ relPath,
405
+ kind: "repinned",
406
+ registrationKey,
407
+ fromVersion: initial.currentVersion ?? undefined,
408
+ toVersion: targetVersion,
409
+ };
410
+ case "normalize-unpinned":
411
+ return {
412
+ relPath,
413
+ kind: "normalized",
414
+ registrationKey,
415
+ fromSpec: initial.spec ?? undefined,
416
+ toVersion: targetVersion,
417
+ };
418
+ case "already-current":
419
+ return { relPath, kind: "already-current", registrationKey, toVersion: targetVersion };
420
+ case "skip-worktree-shim":
421
+ return { relPath, kind: "skipped-worktree-shim", registrationKey };
422
+ default:
423
+ return null;
424
+ }
425
+ }
426
+ async function snapshotCodexConfig(deps, env, targetVersion) {
427
+ const home = deps.homedir();
428
+ const location = resolveCodexConfigLocation(env, home);
429
+ const captured = { source: null };
430
+ const inspection = await inspectGlobalLauncherConfigs({
431
+ env,
432
+ homedir: home,
433
+ targetVersion,
434
+ readFile: async (filePath) => {
435
+ const text = await deps.readFile(filePath);
436
+ captured.source = text;
437
+ return text;
438
+ },
439
+ });
440
+ return { ...location, inspection, source: captured.source };
441
+ }
442
+ /**
443
+ * The guarded Phase 5 Codex write. Computes the one-token splice from the Phase 2
444
+ * snapshot, re-reads the file, and writes ONLY if it is still byte-identical to
445
+ * that snapshot. The comparison is optimistic protection against a concurrent
446
+ * edit, not a lock. Every failure is a fixed, path-specific sentence — never the
447
+ * TOML, an argument, an environment value, or exception text.
448
+ */
449
+ async function applyGuardedCodexWrite(deps, snapshot, targetVersion) {
450
+ const where = snapshot.displayPath;
451
+ let expected;
452
+ try {
453
+ if (snapshot.source === null)
454
+ throw new Error("no snapshot");
455
+ expected = applyCodexRepin(snapshot.source, inspectCodexLauncher(snapshot.source), targetVersion);
456
+ }
457
+ catch {
458
+ return { failure: `${where}: the Codex configuration could not be updated safely; it was left unchanged` };
459
+ }
460
+ let current;
461
+ try {
462
+ current = await deps.readFile(snapshot.filePath);
463
+ }
464
+ catch {
465
+ return { failure: `${where}: the Codex configuration could not be re-read before writing; it was left unchanged` };
466
+ }
467
+ if (current !== snapshot.source) {
468
+ return { failure: `${where}: the Codex configuration changed after it was inspected; it was left unchanged` };
469
+ }
470
+ try {
471
+ await deps.writeFile(snapshot.filePath, expected);
472
+ }
473
+ catch {
474
+ return { failure: `${where}: the Codex configuration could not be written` };
475
+ }
476
+ return { expected };
477
+ }
478
+ /**
479
+ * Phase 6 for Codex: re-inspect and prove the claim. A written config must be
480
+ * current at the exact target under its ORIGINAL registration name AND equal the
481
+ * expected spliced text exactly — a correct pin beside any other difference is
482
+ * still a failure. Initially inactive configs are nonparticipants and are never
483
+ * verified, created, or repaired.
484
+ */
485
+ async function verifyCodexConfig(deps, env, snapshot, targetVersion, expected) {
486
+ const initial = snapshot.inspection;
487
+ if (initial.action === "skip-inactive")
488
+ return { inspection: null, failures: [] };
489
+ const where = snapshot.displayPath;
490
+ let after;
491
+ try {
492
+ after = await snapshotCodexConfig(deps, env, targetVersion);
493
+ }
494
+ catch {
495
+ return {
496
+ inspection: null,
497
+ failures: [`${where}: could not verify the Codex configuration after writing`],
498
+ };
499
+ }
500
+ const found = after.inspection;
501
+ const fail = (message) => ({ inspection: found, failures: [`${where}: ${message}`] });
502
+ if (!found.filePresent)
503
+ return fail("the Codex configuration disappeared after the write");
504
+ if (found.reason === "file-unreadable") {
505
+ return fail("the Codex configuration could not be read back after the write");
506
+ }
507
+ if (found.action === "duplicate-registration") {
508
+ return fail("the Codex configuration now carries two Bridge registrations");
509
+ }
510
+ if (found.reason === "unlocatable" || found.action === "skip-inactive") {
511
+ return fail("the Codex Bridge launcher can no longer be located after the write");
512
+ }
513
+ if (found.registrationKey !== initial.registrationKey) {
514
+ return fail("the Codex Bridge entry no longer lives under its original registration name");
515
+ }
516
+ if (initial.action === "skip-worktree-shim") {
517
+ return found.action === "skip-worktree-shim"
518
+ ? { inspection: found, failures: [] }
519
+ : fail("a worktree shim was expected to be preserved but no longer looks like one");
520
+ }
521
+ if (found.action !== "already-current" || found.currentVersion !== targetVersion) {
522
+ return fail(`expected ${MCP_PACKAGE_NAME}@${targetVersion} after the write`);
523
+ }
524
+ if (expected !== null && after.source !== expected) {
525
+ return fail("the Codex configuration differs from the expected upgraded text");
526
+ }
527
+ return { inspection: found, failures: [] };
528
+ }
529
+ /** The host a project config path belongs to, derived from the registry. */
530
+ function hostIdForRelPath(relPath) {
531
+ return allHostTargets().find((t) => t.scope === "project" && t.relPath === relPath)?.id;
532
+ }
533
+ /**
534
+ * One `Reload <host>: <hint>.` line per changed, automatically managed host, in
535
+ * registry order. Hint text lives on the registry entry; nothing here names a
536
+ * host or a path.
537
+ */
538
+ export function reloadGuidanceLines(changed) {
539
+ return HOST_PLATFORM_ORDER.map((id) => MCP_HOST_TARGETS[id])
540
+ .filter((t) => changed.has(t.id) && t.writeStrategy !== "manual-instructions" && t.reloadHint)
541
+ .map((t) => `Reload ${t.label}: ${t.reloadHint}.`);
542
+ }
543
+ // ---------------------------------------------------------------------------
319
544
  // The command
320
545
  // ---------------------------------------------------------------------------
321
546
  export async function runUpgradeCli(argv, injected) {
@@ -361,13 +586,32 @@ export async function runUpgradeCli(argv, injected) {
361
586
  stderr(`Bridge API upgrade failed: could not inspect launcher configs: ${msg}`);
362
587
  return 1;
363
588
  }
589
+ // BAPI-1445: Codex's global config, inspected beside the project targets in its
590
+ // OWN error boundary so an unexpected failure is reported with fixed text and
591
+ // the safe display path, never with exception text that could carry a path.
592
+ const env = deps.env ?? {};
593
+ let codexSnapshot;
594
+ try {
595
+ codexSnapshot = await snapshotCodexConfig(deps, env, target.version);
596
+ }
597
+ catch {
598
+ const where = resolveCodexConfigLocation(env, "").displayPath;
599
+ stderr(`Bridge API upgrade failed: could not inspect the Codex configuration at ${where}.`);
600
+ stderr(" Nothing was changed.");
601
+ return 1;
602
+ }
603
+ const codex = codexSnapshot.inspection;
364
604
  const blocking = inspections.filter(isPrerequisiteFailure);
365
605
  const ahead = inspections.filter((i) => i.action === "ahead");
366
- if (blocking.length > 0 || ahead.length > 0) {
606
+ const codexRefused = isPrerequisiteFailure(codex) || codex.action === "ahead";
607
+ if (blocking.length > 0 || ahead.length > 0 || codexRefused) {
367
608
  stderr("Bridge API upgrade aborted — these configs cannot be safely reconciled:");
368
609
  for (const found of [...blocking, ...ahead]) {
369
610
  stderr(` ${found.relPath}: ${describeLauncherReason(found.reason)}`);
370
611
  }
612
+ if (codexRefused) {
613
+ stderr(` ${codex.displayPath}: ${describeLauncherReason(codex.reason)}`);
614
+ }
371
615
  stderr(" Nothing was changed. Resolve these by hand, then re-run the upgrade.");
372
616
  return 1;
373
617
  }
@@ -381,9 +625,7 @@ export async function runUpgradeCli(argv, injected) {
381
625
  }
382
626
  stdout(`- Would detect and remove competing local install in node_modules/${MCP_PACKAGE_NAME} if present.`);
383
627
  stdout(`- Launcher pins (target ${MCP_PACKAGE_NAME}@${target.version}):`);
384
- const lines = inspections
385
- .map(formatDryRunLine)
386
- .filter((l) => l !== null);
628
+ const lines = [...inspections.map(formatDryRunLine), formatGlobalDryRunLine(codex)].filter((l) => l !== null);
387
629
  if (lines.length === 0) {
388
630
  stdout(" (no applicable launcher configs found)");
389
631
  }
@@ -391,11 +633,24 @@ export async function runUpgradeCli(argv, injected) {
391
633
  for (const line of lines)
392
634
  stdout(line);
393
635
  }
394
- if (isEveryLauncherAlreadyCurrent(toPlannedActions(inspections))) {
636
+ const plannedInspections = [...inspections, asProjectInspection(codex)];
637
+ if (isEveryLauncherAlreadyCurrent(toPlannedActions(plannedInspections))) {
395
638
  stdout(`- Would print: Already up-to-date.`);
396
639
  }
397
640
  stdout(`- Would refresh scaffolded artifacts (commands, agents, pipelines).`);
398
- stdout(`- Would print a reminder to reload/restart your MCP.`);
641
+ // Reload guidance only for hosts whose configuration this run would change.
642
+ const plannedHosts = new Set();
643
+ for (const found of inspections) {
644
+ if (PLANNED_CHANGE_ACTIONS.has(found.action)) {
645
+ const id = hostIdForRelPath(found.relPath);
646
+ if (id)
647
+ plannedHosts.add(id);
648
+ }
649
+ }
650
+ if (PLANNED_CHANGE_ACTIONS.has(codex.action))
651
+ plannedHosts.add(codex.hostId);
652
+ for (const line of reloadGuidanceLines(plannedHosts))
653
+ stdout(`- Would print: ${line}`);
399
654
  return 0;
400
655
  }
401
656
  // ---- Phase 4: competing local install — a reconciliation, not a transition ----
@@ -434,6 +689,21 @@ export async function runUpgradeCli(argv, injected) {
434
689
  stderr(`Bridge API upgrade failed: ${msg}`);
435
690
  return 1;
436
691
  }
692
+ // ---- Phase 5b (BAPI-1445): the guarded Codex write ----
693
+ // Only after `runInit` succeeded, and only for a pin that has to move. A failure
694
+ // here is recorded as a convergence failure, never thrown: the JSON configs
695
+ // `runInit` already upgraded stay upgraded (no rollback) and nothing retries.
696
+ const codexFailures = [];
697
+ let codexExpected = null;
698
+ if (codex.action === "repin" || codex.action === "normalize-unpinned") {
699
+ const written = await applyGuardedCodexWrite(deps, codexSnapshot, target.version);
700
+ if ("failure" in written) {
701
+ codexFailures.push(written.failure);
702
+ }
703
+ else {
704
+ codexExpected = written.expected;
705
+ }
706
+ }
437
707
  // ---- Phase 6: verify every claim by re-reading the configs ----
438
708
  let verified;
439
709
  try {
@@ -491,16 +761,41 @@ export async function runUpgradeCli(argv, injected) {
491
761
  `after the write, found ${after.registrationKey ? `\`${after.registrationKey}\`` : "no recognized registration"}`);
492
762
  }
493
763
  }
764
+ // Codex is verified independently — and only when its write did not already fail.
765
+ let codexVerified = null;
766
+ if (codexFailures.length === 0) {
767
+ const codexCheck = await verifyCodexConfig(deps, env, codexSnapshot, target.version, codexExpected);
768
+ codexVerified = codexCheck.inspection;
769
+ codexFailures.push(...codexCheck.failures);
770
+ }
771
+ verificationFailures.push(...codexFailures);
772
+ // A Codex transition is reported only once it verified.
773
+ const codexAction = codexFailures.length === 0 ? globalResultAction(codex, target.version) : null;
494
774
  // ---- Phase 7: report what actually happened ----
495
- const resultLines = initResult.configActions
496
- .map(formatResultLine)
497
- .filter((l) => l !== null);
775
+ const codexLine = codexAction ? formatResultLine(codexAction) : null;
776
+ const resultLines = [
777
+ ...initResult.configActions.map(formatResultLine),
778
+ codexLine,
779
+ ].filter((l) => l !== null);
498
780
  if (resultLines.length > 0) {
499
781
  stdout("Launcher pins:");
500
782
  for (const line of resultLines)
501
783
  stdout(line);
502
784
  }
503
785
  stdout("Refreshed scaffolded artifacts (commands, agents, pipelines).");
786
+ // BAPI-1470: `runInit` already printed the notice and any probe warnings in
787
+ // full. Summarize with counts only. A caller that omits the optional fields has
788
+ // told us nothing, so absent fields print nothing rather than "none found".
789
+ const legacyCopyCount = initResult.legacyProcedureCopies?.length ?? 0;
790
+ const legacyWarningCount = initResult.legacyProcedureProbeWarnings?.length ?? 0;
791
+ if (legacyCopyCount > 0 || legacyWarningCount > 0) {
792
+ const parts = [];
793
+ if (legacyCopyCount > 0)
794
+ parts.push(`${legacyCopyCount} old fallback ${legacyCopyCount === 1 ? "copy" : "copies"} found`);
795
+ if (legacyWarningCount > 0)
796
+ parts.push(`${legacyWarningCount} old ${legacyWarningCount === 1 ? "path" : "paths"} could not be checked`);
797
+ stdout(`Old procedure copies: ${parts.join(", ")} (details above).`);
798
+ }
504
799
  if (verificationFailures.length > 0) {
505
800
  stderr("\nBridge API upgrade did NOT converge:");
506
801
  for (const failure of verificationFailures)
@@ -518,16 +813,26 @@ export async function runUpgradeCli(argv, injected) {
518
813
  // ---- Phase 8 (BAPI-1398): the project's /drive-epic-run permission rules ----
519
814
  // Only after every gate above passed. Advisory: a malformed settings file or a
520
815
  // refused launcher is reported and never changes the exit code.
816
+ // BAPI-1446: the same step carries the pinned SessionStart upgrade-notice hook,
817
+ // requested only for a Claude Code installation and pinned to the CONVERGED
818
+ // target version (never `VERSION`, `@latest`, or the fake-latest override).
521
819
  if (deps.applyBridgePermissions) {
522
820
  try {
523
- reportUpgradePermissions(await deps.applyBridgePermissions(deps.cwd, deps.env ?? {}), stdout, stderr);
821
+ const sessionStartHook = (await isClaudeCodeInstallation(deps))
822
+ ? { pinnedVersion: target.version }
823
+ : undefined;
824
+ reportUpgradePermissions(await deps.applyBridgePermissions(deps.cwd, deps.env ?? {}, sessionStartHook), stdout, stderr);
524
825
  }
525
826
  catch {
526
827
  stderr("Warning: could not write Bridge's permission rules (non-fatal).");
527
828
  }
528
829
  }
529
- const applicableStandard = verified.filter(isApplicableStandardLauncher);
530
- if (isEveryLauncherAlreadyCurrent(initResult.configActions)) {
830
+ const applicableStandard = [
831
+ ...verified.filter(isApplicableStandardLauncher),
832
+ ...(codexVerified && isApplicableStandardLauncher(codexVerified) ? [codexVerified] : []),
833
+ ];
834
+ const everyAction = [...initResult.configActions, ...(codexAction ? [codexAction] : [])];
835
+ if (isEveryLauncherAlreadyCurrent(everyAction)) {
531
836
  // The exact string `/upgrade-bridge` is contracted to relay verbatim.
532
837
  stdout("\nAlready up-to-date.");
533
838
  }
@@ -538,15 +843,50 @@ export async function runUpgradeCli(argv, injected) {
538
843
  }
539
844
  else {
540
845
  stdout(`\nUpgrade complete: launcher pins are at ${target.version}.`);
541
- // The running session still holds the OLD MCP connection — MCP config is only
542
- // read at session startup, so a launcher repin does not hot-reload it. Tell the
543
- // user the concrete action instead of spawning a fresh session for them.
544
- stdout("Reload your MCP now (run /mcp) or restart your session to pick up the upgraded server.");
846
+ // Running sessions still hold the OLD MCP connection — MCP config is only read
847
+ // at session startup, so a launcher repin does not hot-reload it. Name the
848
+ // concrete action for each host whose configuration actually changed, from the
849
+ // registry's own hints and in its order.
850
+ const changedHosts = new Set();
851
+ for (const action of initResult.configActions) {
852
+ if (!CHANGED_ACTION_KINDS.has(action.kind))
853
+ continue;
854
+ const id = hostIdForRelPath(action.relPath);
855
+ if (id)
856
+ changedHosts.add(id);
857
+ }
858
+ if (codexAction && CHANGED_ACTION_KINDS.has(codexAction.kind))
859
+ changedHosts.add(codex.hostId);
860
+ for (const line of reloadGuidanceLines(changedHosts))
861
+ stdout(line);
545
862
  }
546
863
  return 0;
547
864
  }
865
+ /** Planned actions that change a configuration and so warrant reload guidance. */
866
+ const PLANNED_CHANGE_ACTIONS = new Set([
867
+ "repin",
868
+ "normalize-unpinned",
869
+ "add",
870
+ "create",
871
+ ]);
872
+ /** Real-run action kinds that changed a configuration. */
873
+ const CHANGED_ACTION_KINDS = new Set([
874
+ "repinned",
875
+ "normalized",
876
+ "added",
877
+ "created",
878
+ ]);
548
879
  /** Render the project permission result in upgrade's own report style. */
549
880
  export function reportUpgradePermissions(result, stdout, stderr) {
881
+ if (result.hook) {
882
+ // Reported on its own, so a refused launcher or a malformed hooks structure
883
+ // never hides, or is hidden by, the permission-rule result.
884
+ const described = describeSessionStartHookOutcome(result.hook);
885
+ for (const line of described.lines)
886
+ stdout(line);
887
+ for (const warning of described.warnings)
888
+ stderr(`Warning: ${warning}.`);
889
+ }
550
890
  if (!result.launcher.ok) {
551
891
  stderr(`Warning: Bridge permission rules were not written: ${result.launcher.reason}.`);
552
892
  return;