@bridge_gpt/mcp-server 0.2.36 → 0.2.37

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 (90) hide show
  1. package/README.md +44 -6
  2. package/build/base-url.js +79 -0
  3. package/build/bridge-api-urls.js +9 -0
  4. package/build/chain-orchestrator.js +93 -15
  5. package/build/claude-user-config-doctor.js +317 -0
  6. package/build/commands.generated.js +2 -1
  7. package/build/conductor/bridge-api-client.js +178 -4
  8. package/build/conductor-bin.js +1 -1
  9. package/build/conductor-bundle-artifacts.js +7 -6
  10. package/build/credential-store.js +205 -4
  11. package/build/direct-ticket-tools.js +70 -0
  12. package/build/doctor.js +239 -80
  13. package/build/executor/cli.js +51 -1
  14. package/build/executor/credentials.js +1 -7
  15. package/build/executor/deps.js +18 -1
  16. package/build/executor/env.js +51 -25
  17. package/build/executor/heartbeat.js +138 -17
  18. package/build/executor/http-client.js +49 -8
  19. package/build/executor/job-errors.js +4 -0
  20. package/build/executor/job-runner.js +422 -22
  21. package/build/executor/observation.js +130 -0
  22. package/build/executor/permissions.js +104 -8
  23. package/build/executor/preflight.js +32 -0
  24. package/build/executor/runner.js +8 -0
  25. package/build/executor/test-clock.js +67 -3
  26. package/build/executor/types.js +4 -1
  27. package/build/executor/worker-command.js +11 -3
  28. package/build/executor/worker-config-isolation.js +287 -0
  29. package/build/executor/worker-finalization.js +68 -14
  30. package/build/executor/worktree.js +46 -4
  31. package/build/index.js +614 -244
  32. package/build/init.js +363 -73
  33. package/build/install-bridge.js +568 -80
  34. package/build/launcher-config-inspection.js +351 -0
  35. package/build/mcp-invoke.js +49 -6
  36. package/build/mcp-provisioning.js +30 -7
  37. package/build/mcp-registration-doctor.js +14 -5
  38. package/build/notifications.js +553 -0
  39. package/build/pipeline-orchestrator.js +146 -4
  40. package/build/pipeline-utils.js +3 -0
  41. package/build/pipelines.generated.js +22 -9
  42. package/build/plan-execution-ledger.js +550 -0
  43. package/build/plan-phase-routing.js +272 -0
  44. package/build/plane/alembic-head.js +110 -0
  45. package/build/plane/build-freshness.js +167 -0
  46. package/build/plane/cli.js +480 -0
  47. package/build/plane/defaults.js +266 -0
  48. package/build/plane/manifest.js +377 -0
  49. package/build/plane/member-logs.js +147 -0
  50. package/build/plane/member-roster.js +147 -0
  51. package/build/plane/preflight.js +289 -0
  52. package/build/plane/shutdown.js +195 -0
  53. package/build/plane/status.js +125 -0
  54. package/build/plane/supervisor.js +569 -0
  55. package/build/plane/test-fakes.js +156 -0
  56. package/build/plane/types.js +75 -0
  57. package/build/readme.generated.js +1 -1
  58. package/build/run-unit-tests-launcher.js +2 -0
  59. package/build/setup-epic.js +662 -27
  60. package/build/sfcc/log-gate.js +38 -11
  61. package/build/sfcc/log-query.js +55 -15
  62. package/build/sfcc/ocapi-shape.js +51 -14
  63. package/build/sfcc/output.js +41 -11
  64. package/build/sfcc/permissions.js +24 -2
  65. package/build/sfcc/read-projection.js +181 -0
  66. package/build/sfcc/read-result.js +158 -0
  67. package/build/sfcc/reads-custom-object-def.js +29 -18
  68. package/build/sfcc/reads-site-preference.js +75 -29
  69. package/build/sfcc/reads-system-object.js +40 -34
  70. package/build/sfcc/sfcc-result.js +106 -0
  71. package/build/sfcc/tool-wrapper.js +56 -13
  72. package/build/sfcc/write-grants.js +45 -22
  73. package/build/sfcc/write-guard.js +21 -13
  74. package/build/sfcc/write-result.js +61 -14
  75. package/build/sfcc/write-tool-common.js +126 -32
  76. package/build/sfcc/writes-system-object.js +11 -50
  77. package/build/start-tickets-prereqs.js +129 -0
  78. package/build/start-tickets.js +17 -13
  79. package/build/ticket-backend-metadata.js +59 -0
  80. package/build/ticket-key-utils.js +92 -0
  81. package/build/tool-error-envelope.js +71 -0
  82. package/build/tool-surface-gating.js +72 -0
  83. package/build/update-status.js +102 -0
  84. package/build/upgrade-advice.js +47 -0
  85. package/build/upgrade-cli.js +417 -101
  86. package/build/version.generated.js +1 -1
  87. package/build/worktree-core.js +73 -0
  88. package/docs/CONDUCTOR.md +23 -8
  89. package/package.json +3 -3
  90. package/pipelines/implement-ticket.json +15 -5
@@ -1,140 +1,456 @@
1
1
  /**
2
- * Bridge API MCP server one-command upgrade (BAPI-430).
2
+ * Bridge API MCP server one-command upgrade (BAPI-430, rebuilt for BAPI-728).
3
3
  *
4
4
  * Extracted from `index.ts` so the upgrade logic always runs from the latest
5
5
  * published version (`@bridge_gpt/mcp-server@latest`) rather than the
6
6
  * potentially-stale copy npx resolved at the call site.
7
7
  *
8
- * Flow:
9
- * 1. Fetch latest version from the npm registry.
10
- * 2. If newer and not already re-exec'd, re-spawn from `@latest` with
11
- * `--internal-reexec` so upgrade logic comes from new code.
12
- * 3. Remove any competing local `node_modules/@bridge_gpt/mcp-server` install
13
- * (the silent trap that makes npx always prefer the stale local copy).
14
- * 4. Rewrite the launcher pin across all per-host configs via `runInit`.
15
- * 5. Print `old -> new` and open a fresh reconnect session.
8
+ * ---------------------------------------------------------------------------
9
+ * BAPI-728 the defect this file was rebuilt to fix
10
+ * ---------------------------------------------------------------------------
11
+ * The old decision was `isNewerVersion(VERSION, latestVersion)`, where `VERSION`
12
+ * is the version of the CLI process npx just resolved. Invoked the documented
13
+ * way (`@latest`), `VERSION === latestVersion`, so that expression was ALWAYS
14
+ * false. Consequences:
15
+ *
16
+ * - `oldVersion` and `targetVersion` both collapsed to `VERSION`, so a run
17
+ * that genuinely repinned three configs from 0.2.16 to 0.2.36 announced
18
+ * itself as `Upgrade complete: 0.2.36 -> 0.2.36`.
19
+ * - The config pin was never an INPUT to anything. It moved only as a side
20
+ * effect of `runInit`, and nothing reported that it had.
21
+ * - A no-op and a twenty-release jump printed identical output.
22
+ *
23
+ * The fix separates two genuinely different questions that were conflated:
24
+ *
25
+ * 1. "Is the CLI I am running the newest CLI?" → `VERSION` vs the registry.
26
+ * This decides ONE thing: whether to re-exec from `@latest`.
27
+ * 2. "Is each project config pinned where it should be?" → the shared,
28
+ * read-only launcher inspection. This decides everything the user sees:
29
+ * per-config transitions, whether anything changed, and the exit status.
30
+ *
31
+ * Every launcher claim is verified by RE-READING the configs after the write.
32
+ * The command never reports a transition it did not confirm on disk.
16
33
  */
17
34
  import { spawn } from "child_process";
18
- import { stat } from "fs/promises";
35
+ import { readFile, stat } from "fs/promises";
19
36
  import path from "path";
20
37
  import { VERSION } from "./version.generated.js";
21
38
  import { runInit } from "./init.js";
39
+ import { InitPreflightError } from "./init.js";
22
40
  import { isNewerVersion } from "./update-check.js";
23
41
  import { fetchLatestVersion } from "./cli-release.js";
42
+ import { CANONICAL_UPGRADE_COMMAND } from "./upgrade-advice.js";
43
+ import { describeLauncherReason, inspectLauncherConfigs, isApplicableStandardLauncher, isPrerequisiteFailure, LAUNCHER_CONFIG_TARGETS, } from "./launcher-config-inspection.js";
24
44
  import { buildGenericAgentShellCommand, detectTerminal, getDefaultSpawnTerminalTabForPlatform, createDefaultStartTicketsDeps, } from "./start-tickets.js";
25
45
  import { AGENT_REGISTRY } from "./agent-registry.js";
26
- export async function runUpgradeCli(argv) {
27
- const cwd = process.cwd();
28
- const isDryRun = argv.includes("--dry-run");
29
- const isInternalReexec = argv.includes("--internal-reexec");
30
- let latestVersion = VERSION;
31
- const fetched = await fetchLatestVersion();
46
+ /** The sentinel this CLI passes to the child it re-execs from `@latest`. */
47
+ const INTERNAL_REEXEC_FLAG = "--internal-reexec";
48
+ // ---------------------------------------------------------------------------
49
+ // Default dependency construction
50
+ // ---------------------------------------------------------------------------
51
+ function defaultReexec(cwd, oldVersion) {
52
+ const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
53
+ return new Promise((resolve) => {
54
+ const child = spawn(npxCmd, [
55
+ "-y",
56
+ "@bridge_gpt/mcp-server@latest",
57
+ "upgrade",
58
+ INTERNAL_REEXEC_FLAG,
59
+ "--old-version",
60
+ oldVersion,
61
+ ], { stdio: "inherit", cwd });
62
+ child.on("close", (code) => resolve(code ?? 0));
63
+ child.on("error", (err) => {
64
+ console.error(`Bridge API upgrade failed: could not re-exec npx: ${err.message}`);
65
+ resolve(1);
66
+ });
67
+ });
68
+ }
69
+ function localInstallPath(cwd) {
70
+ return path.join(cwd, "node_modules", "@bridge_gpt", "mcp-server");
71
+ }
72
+ async function defaultHasCompetingLocalInstall(cwd) {
73
+ try {
74
+ await stat(localInstallPath(cwd));
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ function defaultRemoveCompetingLocalInstall(cwd) {
82
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
83
+ return new Promise((resolve) => {
84
+ const child = spawn(npmCmd, ["uninstall", "@bridge_gpt/mcp-server"], {
85
+ stdio: "inherit",
86
+ cwd,
87
+ });
88
+ child.on("close", (code) => resolve(code === 0));
89
+ child.on("error", () => resolve(false));
90
+ });
91
+ }
92
+ async function defaultSpawnReconnect(cwd, command) {
93
+ try {
94
+ const terminal = detectTerminal(undefined, process.env);
95
+ const deps = createDefaultStartTicketsDeps();
96
+ const spawnTerminalTab = getDefaultSpawnTerminalTabForPlatform(process.platform);
97
+ const result = await spawnTerminalTab(deps, terminal, command, {
98
+ key: "reconnect",
99
+ worktreePath: cwd,
100
+ });
101
+ return result.ok;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ /** Build the production dependency set. */
108
+ export function createDefaultUpgradeCliDeps(cwd) {
109
+ return {
110
+ cwd,
111
+ fetchLatestVersion,
112
+ readFile: (filePath) => readFile(filePath, "utf-8"),
113
+ hasCompetingLocalInstall: defaultHasCompetingLocalInstall,
114
+ removeCompetingLocalInstall: defaultRemoveCompetingLocalInstall,
115
+ runInit,
116
+ reexecFromLatest: defaultReexec,
117
+ spawnReconnect: defaultSpawnReconnect,
118
+ stdout: (m) => console.log(m),
119
+ stderr: (m) => console.error(m),
120
+ env: process.env,
121
+ };
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // Target resolution
125
+ // ---------------------------------------------------------------------------
126
+ /**
127
+ * Decide the target version and how much we trust it.
128
+ *
129
+ * Note what is NOT here: the project's config pins. The target is "what the
130
+ * newest published version is"; the pins are what we compare AGAINST it. Mixing
131
+ * the two is precisely the bug this rewrite removes.
132
+ */
133
+ export async function resolveUpgradeTarget(argv, deps) {
134
+ const fetched = await deps.fetchLatestVersion();
32
135
  if (fetched) {
33
- latestVersion = fetched;
136
+ return { version: fetched, confidence: "registry-confirmed" };
34
137
  }
35
- const isNewer = isNewerVersion(VERSION, latestVersion);
36
- const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
37
- // Re-exec from @latest to ensure upgrade logic runs from latest code, not
38
- // the stale local copy that npx may have resolved.
39
- if (isNewer && !isInternalReexec) {
40
- if (!isDryRun) {
41
- console.log(`Update available: ${VERSION} -> ${latestVersion}. Re-executing from @latest...`);
42
- return new Promise((resolve) => {
43
- const childArgs = [
44
- "-y",
45
- "@bridge_gpt/mcp-server@latest",
46
- "upgrade",
47
- "--internal-reexec",
48
- "--old-version",
49
- VERSION,
50
- ];
51
- const child = spawn(npxCmd, childArgs, { stdio: "inherit", cwd });
52
- child.on("close", (code) => resolve(code ?? 0));
53
- child.on("error", (err) => {
54
- console.error(`Bridge API upgrade failed: could not re-exec npx: ${err.message}`);
55
- resolve(1);
56
- });
57
- });
138
+ if (argv.includes(INTERNAL_REEXEC_FLAG)) {
139
+ // We ARE the `@latest` child this CLI spawned, so `VERSION` is latest by
140
+ // construction the parent resolved it through npx moments ago.
141
+ return { version: VERSION, confidence: "internal-reexec" };
142
+ }
143
+ // No reliable way exists to observe from inside the process whether npx
144
+ // resolved us via `@latest`, a bare spec, or a local node_modules copy — npm's
145
+ // exported environment does not carry the requested spec. So a canonical
146
+ // invocation must be asserted explicitly rather than guessed.
147
+ const asserted = deps.env?.BAPI_UPGRADE_ASSUME_LATEST;
148
+ if (typeof asserted === "string" && asserted.trim().toLowerCase() === "true") {
149
+ return { version: VERSION, confidence: "latest-invocation-confirmed" };
150
+ }
151
+ return { version: VERSION, confidence: "running-version-only" };
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // Reporting helpers (pure)
155
+ // ---------------------------------------------------------------------------
156
+ /** Render the dry-run preview line for one inspected target. */
157
+ export function formatDryRunLine(found) {
158
+ const { relPath, targetVersion } = found;
159
+ switch (found.action) {
160
+ case "repin":
161
+ return ` ${relPath}: ${found.currentVersion} -> ${targetVersion}`;
162
+ case "already-current":
163
+ return ` ${relPath}: already ${targetVersion}`;
164
+ case "normalize-unpinned":
165
+ return ` ${relPath}: would normalize ${found.spec} -> @bridge_gpt/mcp-server@${targetVersion}`;
166
+ case "add":
167
+ return ` ${relPath}: would add @bridge_gpt/mcp-server@${targetVersion}`;
168
+ case "create":
169
+ return ` ${relPath}: would create with @bridge_gpt/mcp-server@${targetVersion}`;
170
+ case "skip-worktree-shim":
171
+ return ` ${relPath}: skipped — worktree mcp-invoke shim (no published-package pin applies)`;
172
+ case "skip-inactive":
173
+ return null; // Absent config for an inactive IDE — not worth a line.
174
+ default:
175
+ return null;
176
+ }
177
+ }
178
+ /** Render the real-run result line for one verified config action. */
179
+ export function formatResultLine(action) {
180
+ const { relPath } = action;
181
+ switch (action.kind) {
182
+ case "repinned":
183
+ return ` ${relPath}: ${action.fromVersion} -> ${action.toVersion}`;
184
+ case "already-current":
185
+ return ` ${relPath}: already ${action.toVersion}`;
186
+ case "normalized":
187
+ return ` ${relPath}: normalized ${action.fromSpec} -> @bridge_gpt/mcp-server@${action.toVersion}`;
188
+ case "added":
189
+ return ` ${relPath}: added @bridge_gpt/mcp-server@${action.toVersion}`;
190
+ case "created":
191
+ return ` ${relPath}: created with @bridge_gpt/mcp-server@${action.toVersion}`;
192
+ case "skipped-worktree-shim":
193
+ return ` ${relPath}: skipped — worktree mcp-invoke shim preserved`;
194
+ case "skipped-ahead":
195
+ // Defensive: upgrade blocks on an ahead pin during preflight, so this is
196
+ // only reachable if a config changed underneath the run.
197
+ return ` ${relPath}: skipped — already pinned ahead at ${action.fromVersion}`;
198
+ case "skipped-inactive":
199
+ return null;
200
+ default:
201
+ return null;
202
+ }
203
+ }
204
+ /**
205
+ * True when the run changed no launcher pin: every applicable standard launcher
206
+ * was already exactly at the target, and nothing was created, added, or
207
+ * normalized. This — and ONLY this — is what `Already up-to-date.` means.
208
+ */
209
+ export function isEveryLauncherAlreadyCurrent(actions) {
210
+ const applicable = actions.filter((a) => a.kind !== "skipped-worktree-shim" &&
211
+ a.kind !== "skipped-inactive" &&
212
+ a.kind !== "skipped-ahead");
213
+ if (applicable.length === 0)
214
+ return false; // Nothing applicable ⇒ not "current".
215
+ return applicable.every((a) => a.kind === "already-current");
216
+ }
217
+ // ---------------------------------------------------------------------------
218
+ // The command
219
+ // ---------------------------------------------------------------------------
220
+ export async function runUpgradeCli(argv, injected) {
221
+ const deps = {
222
+ ...createDefaultUpgradeCliDeps(injected?.cwd ?? process.cwd()),
223
+ ...injected,
224
+ };
225
+ const { stdout, stderr } = deps;
226
+ const isDryRun = argv.includes("--dry-run");
227
+ const isInternalReexec = argv.includes(INTERNAL_REEXEC_FLAG);
228
+ // ---- Phase 1: CLI freshness — the ONLY thing `VERSION` decides ----
229
+ const target = await resolveUpgradeTarget(argv, deps);
230
+ if (target.confidence === "registry-confirmed" &&
231
+ isNewerVersion(VERSION, target.version) &&
232
+ !isInternalReexec &&
233
+ !isDryRun) {
234
+ stdout(`Update available: ${VERSION} -> ${target.version}. Re-executing from @latest...`);
235
+ return deps.reexecFromLatest(deps.cwd, VERSION);
236
+ }
237
+ if (target.confidence === "running-version-only") {
238
+ // Fail closed. Writing the running version into every project config when we
239
+ // could not confirm it IS the latest would replace an honest stale pin with a
240
+ // confidently wrong one.
241
+ stderr("Bridge API upgrade aborted: could not confirm the latest published version " +
242
+ "(the npm registry lookup failed) and this process was not started from @latest.");
243
+ stderr(` Nothing was changed. Re-run the canonical command: ${CANONICAL_UPGRADE_COMMAND}`);
244
+ return 1;
245
+ }
246
+ // ---- Phase 2: read-only launcher preflight, before ANY side effect ----
247
+ const isCreatable = deps.isCreatable ?? ((relPath) => relPath === ".mcp.json");
248
+ let inspections;
249
+ try {
250
+ inspections = await inspectLauncherConfigs({
251
+ cwd: deps.cwd,
252
+ targetVersion: target.version,
253
+ readFile: deps.readFile,
254
+ isCreatable,
255
+ targets: LAUNCHER_CONFIG_TARGETS,
256
+ });
257
+ }
258
+ catch (err) {
259
+ const msg = err instanceof Error ? err.message : String(err);
260
+ stderr(`Bridge API upgrade failed: could not inspect launcher configs: ${msg}`);
261
+ return 1;
262
+ }
263
+ const blocking = inspections.filter(isPrerequisiteFailure);
264
+ const ahead = inspections.filter((i) => i.action === "ahead");
265
+ if (blocking.length > 0 || ahead.length > 0) {
266
+ stderr("Bridge API upgrade aborted — these configs cannot be safely reconciled:");
267
+ for (const found of [...blocking, ...ahead]) {
268
+ stderr(` ${found.relPath}: ${describeLauncherReason(found.reason)}`);
58
269
  }
270
+ stderr(" Nothing was changed. Resolve these by hand, then re-run the upgrade.");
271
+ return 1;
59
272
  }
60
- const oldVersionIdx = argv.indexOf("--old-version");
61
- const oldVersion = oldVersionIdx !== -1 && oldVersionIdx + 1 < argv.length ? argv[oldVersionIdx + 1] : VERSION;
62
- const targetVersion = isDryRun && isNewer ? latestVersion : VERSION;
273
+ // ---- Phase 3: dry run reports the REAL pre-mutation state and stops ----
63
274
  if (isDryRun) {
64
- const configTargets = [".mcp.json", ".vscode/mcp.json", ".cursor/mcp.json"];
65
- console.log(`\n[Dry Run] Upgrade preview:`);
66
- if (isNewer && !isInternalReexec) {
67
- console.log(`- Would re-exec from @latest to upgrade ${VERSION} -> ${latestVersion}`);
275
+ stdout(`\n[Dry Run] Upgrade preview:`);
276
+ if (target.confidence === "registry-confirmed" &&
277
+ isNewerVersion(VERSION, target.version) &&
278
+ !isInternalReexec) {
279
+ stdout(`- Would re-exec from @latest to upgrade the CLI ${VERSION} -> ${target.version}`);
68
280
  }
69
- console.log(`- Would detect and remove competing local install in node_modules/@bridge_gpt/mcp-server if present.`);
70
- console.log(`- Would rewrite launcher pin to @bridge_gpt/mcp-server@${targetVersion} across active per-host configs:`);
71
- for (const target of configTargets) {
72
- try {
73
- await stat(path.join(cwd, target));
74
- console.log(` - ${target}`);
75
- }
76
- catch { }
281
+ stdout(`- Would detect and remove competing local install in node_modules/@bridge_gpt/mcp-server if present.`);
282
+ stdout(`- Launcher pins (target @bridge_gpt/mcp-server@${target.version}):`);
283
+ const lines = inspections
284
+ .map(formatDryRunLine)
285
+ .filter((l) => l !== null);
286
+ if (lines.length === 0) {
287
+ stdout(" (no applicable launcher configs found)");
288
+ }
289
+ else {
290
+ for (const line of lines)
291
+ stdout(line);
292
+ }
293
+ if (isEveryLauncherAlreadyCurrent(toPlannedActions(inspections))) {
294
+ stdout(`- Would print: Already up-to-date.`);
77
295
  }
78
- console.log(`- Would refresh scaffolded artifacts (commands, agents, pipelines).`);
79
- const spawnCommand = buildGenericAgentShellCommand(AGENT_REGISTRY.claude, "Bridge API MCP server has been upgraded. Please reload/reconnect the MCP server in your environment.", cwd, process.platform);
80
- console.log(`- Would print: ${oldVersion} -> ${targetVersion} and spawn a fresh reconnect session with command:\n ${spawnCommand}`);
296
+ stdout(`- Would refresh scaffolded artifacts (commands, agents, pipelines).`);
297
+ stdout(`- Would open a fresh reconnect session.`);
81
298
  return 0;
82
299
  }
83
- // Detect and resolve the competing-local-install trap: npx silently prefers
84
- // a local node_modules copy over the registry, so the stale version wins even
85
- // after an upgrade unless we remove it.
300
+ // ---- Phase 4: competing local install a reconciliation, not a transition ----
301
+ if (await deps.hasCompetingLocalInstall(deps.cwd)) {
302
+ stdout("Found a competing local installation in node_modules. Removing so npx resolves the pinned version...");
303
+ const removed = await deps.removeCompetingLocalInstall(deps.cwd);
304
+ if (!removed) {
305
+ // Leaving it in place means npx keeps preferring the stale local copy, so
306
+ // the repin below would be cosmetic. That is not a completed upgrade.
307
+ stderr("Bridge API upgrade failed: could not remove node_modules/@bridge_gpt/mcp-server.");
308
+ stderr(" Remove it manually (rm -rf node_modules/@bridge_gpt/mcp-server), then re-run the upgrade.");
309
+ return 1;
310
+ }
311
+ stdout(" Removed competing local install.");
312
+ }
313
+ // ---- Phase 5: reconcile ----
314
+ let initResult;
86
315
  try {
87
- const localModulePath = path.join(cwd, "node_modules", "@bridge_gpt", "mcp-server");
88
- await stat(localModulePath);
89
- console.log("Found stale local installation in node_modules. Removing to converge on pinned-npx...");
90
- const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
91
- await new Promise((resolve) => {
92
- const child = spawn(npmCmd, ["uninstall", "@bridge_gpt/mcp-server"], {
93
- stdio: "inherit",
94
- cwd,
95
- });
96
- child.on("close", (code) => {
97
- if (code !== 0)
98
- console.warn("Warning: npm uninstall failed; you may need to remove node_modules/@bridge_gpt/mcp-server manually.");
99
- resolve();
100
- });
101
- child.on("error", () => resolve());
316
+ stdout(`\nReconciling launcher pins to @bridge_gpt/mcp-server@${target.version}...\n`);
317
+ initResult = await deps.runInit(deps.cwd, {
318
+ targetVersion: target.version,
319
+ // Upgrade renders its own truthful per-config report below; the generic
320
+ // "updated entry and pinned version" summary would only contradict it.
321
+ suppressConfigSummary: true,
102
322
  });
103
323
  }
104
- catch {
105
- // Not installed locally — nothing to clean up.
324
+ catch (err) {
325
+ if (err instanceof InitPreflightError) {
326
+ stderr("Bridge API upgrade aborted — these configs cannot be safely reconciled:");
327
+ for (const failure of err.failures) {
328
+ stderr(` ${failure.relPath}: ${failure.reason}`);
329
+ }
330
+ return 1;
331
+ }
332
+ const msg = err instanceof Error ? err.message : String(err);
333
+ stderr(`Bridge API upgrade failed: ${msg}`);
334
+ return 1;
106
335
  }
336
+ // ---- Phase 6: verify every claim by re-reading the configs ----
337
+ let verified;
107
338
  try {
108
- console.log(`\nUpgrading @bridge_gpt/mcp-server to ${targetVersion}...\n`);
109
- // Scaffold & rewrite the launcher pin on upgrade.
110
- console.log("Refreshing scaffolded artifacts and updating launcher pins...\n");
111
- await runInit(cwd);
112
- console.log(`\nUpgrade complete: ${oldVersion} -> ${targetVersion}`);
113
- console.log("Please reconnect your MCP now.");
339
+ verified = await inspectLauncherConfigs({
340
+ cwd: deps.cwd,
341
+ targetVersion: target.version,
342
+ readFile: deps.readFile,
343
+ isCreatable,
344
+ targets: LAUNCHER_CONFIG_TARGETS,
345
+ });
114
346
  }
115
347
  catch (err) {
116
348
  const msg = err instanceof Error ? err.message : String(err);
117
- console.error(`Bridge API upgrade failed: ${msg}`);
349
+ stderr(`Bridge API upgrade failed: could not verify launcher configs after writing: ${msg}`);
118
350
  return 1;
119
351
  }
120
- // Open a fresh agent session so the user lands in a reconnected environment
121
- // rather than having to manually restart.
122
- try {
123
- const terminal = detectTerminal(undefined, process.env);
124
- const deps = createDefaultStartTicketsDeps();
125
- const spawnTerminalTab = getDefaultSpawnTerminalTabForPlatform(process.platform);
126
- const spawnCommand = buildGenericAgentShellCommand(AGENT_REGISTRY.claude, "Bridge API MCP server has been upgraded. Please reload/reconnect the MCP server in your environment.", cwd, process.platform);
127
- console.log("\nOpening a fresh agent session to reconnect...");
128
- const spawnResult = await spawnTerminalTab(deps, terminal, spawnCommand, {
129
- key: "reconnect",
130
- worktreePath: cwd,
131
- });
132
- if (!spawnResult.ok) {
133
- console.log(`\nTo continue, manually run:\n${spawnCommand}\n`);
352
+ const verifiedByPath = new Map(verified.map((v) => [v.relPath, v]));
353
+ const verificationFailures = [];
354
+ for (const action of initResult.configActions) {
355
+ if (action.kind === "skipped-worktree-shim" ||
356
+ action.kind === "skipped-inactive" ||
357
+ action.kind === "skipped-ahead") {
358
+ // Assert the shim really was left alone rather than quietly rewritten.
359
+ const after = verifiedByPath.get(action.relPath);
360
+ if (action.kind === "skipped-worktree-shim" && after?.action !== "skip-worktree-shim") {
361
+ verificationFailures.push(`${action.relPath}: a worktree shim was expected to be preserved but no longer looks like one`);
362
+ }
363
+ continue;
364
+ }
365
+ const after = verifiedByPath.get(action.relPath);
366
+ if (!after) {
367
+ verificationFailures.push(`${action.relPath}: config disappeared after the write`);
368
+ continue;
369
+ }
370
+ if (after.action !== "already-current" || after.currentVersion !== target.version) {
371
+ verificationFailures.push(`${action.relPath}: expected @bridge_gpt/mcp-server@${target.version} after the write, ` +
372
+ `found ${after.spec ?? "no recognizable package token"}`);
134
373
  }
135
374
  }
136
- catch {
137
- console.log("To continue, please reload your agent session manually.");
375
+ // ---- Phase 7: report what actually happened ----
376
+ const resultLines = initResult.configActions
377
+ .map(formatResultLine)
378
+ .filter((l) => l !== null);
379
+ if (resultLines.length > 0) {
380
+ stdout("Launcher pins:");
381
+ for (const line of resultLines)
382
+ stdout(line);
383
+ }
384
+ stdout("Refreshed scaffolded artifacts (commands, agents, pipelines).");
385
+ if (verificationFailures.length > 0) {
386
+ stderr("\nBridge API upgrade did NOT converge:");
387
+ for (const failure of verificationFailures)
388
+ stderr(` ${failure}`);
389
+ stderr(" Re-run the upgrade, or repair the listed configs by hand.");
390
+ return 1;
391
+ }
392
+ if (!initResult.ok) {
393
+ // `runInit` already printed the path-specific readiness warning. Do not add a
394
+ // completion line on top of it — a config that authenticates as nobody is not
395
+ // a finished upgrade.
396
+ stderr("\nBridge API upgrade finished with an UNCONFIGURED MCP entry (see above).");
397
+ return 1;
398
+ }
399
+ const applicableStandard = verified.filter(isApplicableStandardLauncher);
400
+ if (isEveryLauncherAlreadyCurrent(initResult.configActions)) {
401
+ // The exact string `/upgrade-bridge` is contracted to relay verbatim.
402
+ stdout("\nAlready up-to-date.");
403
+ }
404
+ else if (applicableStandard.length === 0 && resultLines.length > 0) {
405
+ // Only worktree shims (or nothing applicable) — reporting "current" here
406
+ // would claim something about pins that were never evaluated.
407
+ stdout("\nNo standard launcher pin was changed.");
408
+ }
409
+ else {
410
+ stdout(`\nUpgrade complete: launcher pins are at ${target.version}.`);
411
+ stdout("Please reconnect your MCP now.");
412
+ }
413
+ // ---- Phase 8: reconnect — convenience only, never changes the verdict ----
414
+ const spawnCommand = buildGenericAgentShellCommand(AGENT_REGISTRY.claude, "Bridge API MCP server has been upgraded. Please reload/reconnect the MCP server in your environment.", deps.cwd, process.platform);
415
+ const spawned = await deps.spawnReconnect(deps.cwd, spawnCommand);
416
+ if (!spawned) {
417
+ stdout(`\nTo continue, manually run:\n${spawnCommand}\n`);
138
418
  }
139
419
  return 0;
140
420
  }
421
+ /**
422
+ * Project pre-mutation inspections onto the action shape `isEveryLauncherAlreadyCurrent`
423
+ * consumes, so the dry run answers the currentness question exactly the way the
424
+ * real run will rather than with a second, drifting rule.
425
+ */
426
+ function toPlannedActions(inspections) {
427
+ const actions = [];
428
+ for (const found of inspections) {
429
+ switch (found.action) {
430
+ case "repin":
431
+ actions.push({ relPath: found.relPath, kind: "repinned" });
432
+ break;
433
+ case "already-current":
434
+ actions.push({ relPath: found.relPath, kind: "already-current" });
435
+ break;
436
+ case "normalize-unpinned":
437
+ actions.push({ relPath: found.relPath, kind: "normalized" });
438
+ break;
439
+ case "add":
440
+ actions.push({ relPath: found.relPath, kind: "added" });
441
+ break;
442
+ case "create":
443
+ actions.push({ relPath: found.relPath, kind: "created" });
444
+ break;
445
+ case "skip-worktree-shim":
446
+ actions.push({ relPath: found.relPath, kind: "skipped-worktree-shim" });
447
+ break;
448
+ case "skip-inactive":
449
+ actions.push({ relPath: found.relPath, kind: "skipped-inactive" });
450
+ break;
451
+ default:
452
+ break;
453
+ }
454
+ }
455
+ return actions;
456
+ }
@@ -1,2 +1,2 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.2.36";
2
+ export const VERSION = "0.2.37";