@bridge_gpt/mcp-server 0.2.62 → 0.2.66

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 (113) hide show
  1. package/README.md +66 -15
  2. package/build/agent-launchers/claude-executor-adapter.js +145 -20
  3. package/build/agent-launchers/executor-adapter.js +81 -2
  4. package/build/agents.generated.js +10 -1
  5. package/build/bounded-wait.js +32 -1
  6. package/build/bridge-config.js +104 -0
  7. package/build/codex-bridge-profile.js +10 -1
  8. package/build/commands.generated.js +7 -6
  9. package/build/conductor/bridge-api-client.js +565 -32
  10. package/build/conductor/bring-up-facts.js +3 -1
  11. package/build/conductor/doctor.js +23 -14
  12. package/build/conductor/epic-child-continuation.js +351 -0
  13. package/build/conductor/epic-implementer-cli.js +154 -17
  14. package/build/conductor/git-hooks.js +529 -118
  15. package/build/conductor/guided-repair.js +323 -0
  16. package/build/conductor/hook-runtime-qualification.js +340 -0
  17. package/build/conductor/readiness-cli.js +30 -9
  18. package/build/conductor/readiness-sections.js +5 -4
  19. package/build/conductor/readiness.js +45 -12
  20. package/build/conductor/recovery-cli.js +859 -27
  21. package/build/conductor/recovery-operations.js +574 -5
  22. package/build/conductor/repair-contract.js +69 -3
  23. package/build/conductor-bundle-artifacts.js +1 -0
  24. package/build/credential-store.js +52 -13
  25. package/build/diagnostic-text.js +36 -0
  26. package/build/docs.generated.js +3 -2
  27. package/build/doctor.js +147 -21
  28. package/build/drive-epic-key.js +12 -0
  29. package/build/drive-epic.js +1537 -125
  30. package/build/epic-coherence-input-cli.js +137 -0
  31. package/build/epic-coherence-input.js +920 -0
  32. package/build/epic-coherence-input.test-fixtures.js +175 -0
  33. package/build/epic-implementer-bin.js +41 -33
  34. package/build/epic-implementer-bundle-cli.js +14 -1
  35. package/build/epic-launch-diagnostic.js +31 -0
  36. package/build/epic-launch-profile.js +58 -19
  37. package/build/epic-run-status.js +5 -0
  38. package/build/epic-shared/bridge-client.js +46 -4
  39. package/build/executor/cli.js +39 -0
  40. package/build/executor/completion-contract.js +423 -0
  41. package/build/executor/completion-supervision.js +291 -0
  42. package/build/executor/correction-dispatch.js +60 -0
  43. package/build/executor/credentials.js +3 -1
  44. package/build/executor/deps.js +65 -1
  45. package/build/executor/env.js +12 -0
  46. package/build/executor/heartbeat.js +13 -3
  47. package/build/executor/http-client.js +91 -4
  48. package/build/executor/integration-job.js +6 -1
  49. package/build/executor/job-errors.js +61 -4
  50. package/build/executor/job-log-registry.js +340 -73
  51. package/build/executor/job-runner.js +1550 -55
  52. package/build/executor/job-types.js +9 -6
  53. package/build/executor/preflight.js +189 -1
  54. package/build/executor/process-containment.js +756 -0
  55. package/build/executor/process.js +235 -5
  56. package/build/executor/prompt-spec.js +366 -1
  57. package/build/executor/provider-checks.js +69 -0
  58. package/build/executor/rebase-finalization.js +351 -0
  59. package/build/executor/reconcile-overlap-job.js +77 -84
  60. package/build/executor/required-verification.js +313 -0
  61. package/build/executor/required-work-ledger.js +1121 -0
  62. package/build/executor/results.js +142 -0
  63. package/build/executor/resume-pre-spawn.js +87 -4
  64. package/build/executor/runner.js +168 -0
  65. package/build/executor/stale-artifacts.js +5 -4
  66. package/build/executor/test-declaration.js +20 -0
  67. package/build/executor/types.js +13 -0
  68. package/build/executor/watch-cli.js +35 -8
  69. package/build/executor/worker-command.js +70 -34
  70. package/build/executor/worker-finalization.js +168 -26
  71. package/build/executor/worker-guard-hook.js +75 -14
  72. package/build/executor/worker-log.js +137 -70
  73. package/build/executor/worktree-gc.js +30 -6
  74. package/build/executor/worktree.js +13 -0
  75. package/build/executor-launch.js +658 -0
  76. package/build/index.js +594 -1152
  77. package/build/init.js +107 -4
  78. package/build/install-bridge-conductor.js +231 -41
  79. package/build/install-bridge.js +284 -20
  80. package/build/install-doctor.js +172 -30
  81. package/build/install-reexec.js +69 -7
  82. package/build/macos-terminal-permission-guidance.js +16 -0
  83. package/build/merge-pull-request.js +1 -1
  84. package/build/model-thresholds-tool.js +185 -0
  85. package/build/pipelines.generated.js +9 -8
  86. package/build/plan-discovery.js +137 -0
  87. package/build/plan-epic-implementer-eligibility.js +2 -2
  88. package/build/plane/cli.js +37 -3
  89. package/build/plane/member-logs.js +3 -0
  90. package/build/plane/member-roster.js +7 -1
  91. package/build/plane/shutdown.js +59 -1
  92. package/build/plane/status.js +4 -0
  93. package/build/plane/supervisor.js +146 -0
  94. package/build/plane/worker-exit-code.js +2 -0
  95. package/build/project-root.js +99 -0
  96. package/build/readme.generated.js +1 -1
  97. package/build/review-tickets.js +156 -13
  98. package/build/scripts/executor-protocol-contract-driver.js +13 -3
  99. package/build/setup-epic.js +208 -125
  100. package/build/sfcc/setup-status.js +2 -0
  101. package/build/sfcc/tool-wrapper.js +2 -0
  102. package/build/sfcc/tools-configured.js +96 -0
  103. package/build/start-tickets.js +163 -58
  104. package/build/upgrade-cli.js +109 -24
  105. package/build/version.generated.js +3 -3
  106. package/build/windows-npm-entry.js +103 -0
  107. package/build/worker-guard-hook-bin.js +5 -4
  108. package/build/worktree-core.js +6 -5
  109. package/docs/CONDUCTOR.md +116 -2
  110. package/docs/install/mcp-tool-integrations.md +1 -0
  111. package/docs/install/sfcc-integration.md +12 -7
  112. package/package.json +2 -1
  113. package/pipelines/implement-ticket.json +1 -1
@@ -143,10 +143,11 @@ import { randomBytes as cryptoRandomBytes, createHash } from "crypto";
143
143
  import os from "os";
144
144
  import path from "path";
145
145
  import readline from "readline";
146
- import { runInit, InitPreflightError, buildBridgeApiEntry, mergeBridgeApiProfileToken, resolveInitScaffoldAssets, refreshBridgeApiPackageSpec, currentBridgePackageSpec, } from "./init.js";
146
+ import { runInit, InitPreflightError, buildBridgeApiEntry, buildBridgeConfigManifest, mergeBridgeApiProfileToken, resolveInitScaffoldAssets, refreshBridgeApiPackageSpec, currentBridgePackageSpec, classifyTrackedProbeExitCode, decideExistingProjectRoot, formatUndeterminedRootReplacedWarning, } from "./init.js";
147
147
  import { hasProjectRootMarker as sharedHasProjectRootMarker } from "./project-root.js";
148
148
  import { VERSION } from "./version.generated.js";
149
- import { validateRepoName } from "./bridge-config.js";
149
+ import { bridgeConfigPath, parseBridgeConfigToml, rewriteBridgeConfigRepoName, validateBridgeConfigRepoNameRepresentability, validateRepoName, } from "./bridge-config.js";
150
+ import { renderDiagnosticValue } from "./diagnostic-text.js";
150
151
  import { MCP_HOST_TARGETS, HOST_PLATFORM_ORDER, allHostTargets, agentForPlatform, isHostPlatformId, detectDefaultPlatforms, hostAdapterForTarget, } from "./mcp-host-targets.js";
151
152
  import { provisionHostTarget, createDefaultVendorProcessDeps, inspectBridgeApiProfileToken, } from "./mcp-host-config.js";
152
153
  import { adaptBridgeEntryForHostTarget, } from "./mcp-host-entry-adapter.js";
@@ -190,6 +191,8 @@ import { fetchBitbucketConfigurationState } from "./connect-bitbucket-api.js";
190
191
  import { createDefaultConnectBitbucketDeps, runBitbucketConnectionFlow } from "./connect-bitbucket.js";
191
192
  import { resolveAgentSpec, isAgentName, formatValidAgentNames, resolveModelAlias, } from "./agent-registry.js";
192
193
  import { buildGenericAgentShellCommand, getDefaultSpawnTerminalTabForPlatform, detectTerminal, createDefaultStartTicketsDeps, materializeWorkerLaunchCommand, MAX_TERMINAL_COMMAND_BYTES, validateResolvedModelAliasForAgent, } from "./start-tickets.js";
194
+ import { defaultNpmEntryFileExists, resolveNpmEntry } from "./windows-npm-entry.js";
195
+ import { macosTerminalPermissionGuidance } from "./macos-terminal-permission-guidance.js";
193
196
  /** Redaction sentinel — the API-key value is NEVER printed; this stands in. */
194
197
  export const REDACTED_API_KEY = "<REDACTED>";
195
198
  /**
@@ -1261,11 +1264,32 @@ export function sanitizePrewarmEnv(env) {
1261
1264
  delete sanitized[INSTALL_REEXEC_KEY_SOURCE_ENV];
1262
1265
  return sanitized;
1263
1266
  }
1264
- function spawnPrewarmDefault(command, args, env) {
1267
+ export function spawnPrewarmDefault(command, args, env, options = {}) {
1268
+ const doSpawn = options.spawn ?? spawn;
1265
1269
  return new Promise((resolve) => {
1266
1270
  const sanitizedEnv = sanitizePrewarmEnv(env);
1271
+ // WINDOWS (BAPI-1319): a bare `npx` is ENOENT without a shell, so the
1272
+ // pre-warm never ran. Map it to npm's `npx-cli.js` through Node, resolved
1273
+ // against the SANITIZED env the child actually receives. Other platforms
1274
+ // pass through unchanged.
1275
+ let spawnCommand = command;
1276
+ let spawnArgs = args;
1277
+ if (command === "npx") {
1278
+ const resolved = resolveNpmEntry("npx", args, {
1279
+ platform: options.platform ?? process.platform,
1280
+ env: sanitizedEnv,
1281
+ execPath: options.execPath ?? process.execPath,
1282
+ fileExists: options.fileExists ?? defaultNpmEntryFileExists,
1283
+ });
1284
+ if (resolved.status !== "resolved") {
1285
+ resolve({ ok: false, warning: "the pre-warm process could not be started" });
1286
+ return;
1287
+ }
1288
+ spawnCommand = resolved.command;
1289
+ spawnArgs = resolved.args;
1290
+ }
1267
1291
  try {
1268
- const child = spawn(command, args, {
1292
+ const child = doSpawn(spawnCommand, spawnArgs, {
1269
1293
  shell: false,
1270
1294
  stdio: "ignore",
1271
1295
  timeout: 60_000,
@@ -1929,7 +1953,10 @@ export async function resolveInviteToken(options, deps) {
1929
1953
  const INSTALL_BRIDGE_SIGNUP_EMAIL_SHAPE = /^[^@\s]+@[^@\s]+$/;
1930
1954
  /** The one hint line printed after a blank/malformed email, before the single re-prompt. */
1931
1955
  export const INSTALL_BRIDGE_SIGNUP_EMAIL_HINT = "Enter a valid email address (e.g. you@example.com).";
1932
- export async function resolveSignupEmail(options, deps) {
1956
+ export function buildInstallBridgeSignupPrivacyNotice(baseUrl) {
1957
+ return `Privacy Policy: ${baseUrl.replace(/\/+$/, "")}/privacy`;
1958
+ }
1959
+ export async function resolveSignupEmail(options, deps, baseUrl = DEFAULT_BAPI_BASE_URL) {
1933
1960
  // BAPI-757 (R-3 / AC-9): self-serve signup now REQUIRES an interactive terminal,
1934
1961
  // and the check runs FIRST — before `--email` and before `BAPI_SIGNUP_EMAIL` —
1935
1962
  // so an unattended run fails before any request is made, no verification email
@@ -1940,6 +1967,7 @@ export async function resolveSignupEmail(options, deps) {
1940
1967
  if (!deps.isTTY || !deps.promptLine) {
1941
1968
  return { ok: false, error: INSTALL_BRIDGE_SIGNUP_REQUIRES_TTY };
1942
1969
  }
1970
+ deps.log(buildInstallBridgeSignupPrivacyNotice(baseUrl));
1943
1971
  if (typeof options.email === "string" && options.email.trim().length > 0) {
1944
1972
  return { ok: true, value: options.email.trim() };
1945
1973
  }
@@ -2987,23 +3015,25 @@ function trackedConfigWarning(relPath) {
2987
3015
  * Read-only probe: is `relPath` already tracked by git in the project (BAPI-666)?
2988
3016
  * Runs `git ls-files --error-unmatch -- <relPath>` through the injected, list-based
2989
3017
  * command seam (never a shell string) with `cwd = deps.cwd`. ONLY exit code 0 means
2990
- * tracked; every other outcome — a non-zero exit (untracked / gitignored), a non-git
2991
- * directory, or a thrown command failure — is treated as NOT tracked, preserving the
2992
- * required fail-open behavior (a probe failure must never block the install). Emits at
2993
- * most a fixed debug-level line (safe relative path + exit classification) and never
2994
- * the command's stdout/stderr, exception text, env, or config content.
3018
+ * tracked; exit 1 is untracked, and every other outcome — another exit code (a
3019
+ * non-git directory), or a thrown command failure — is `undetermined` (BAPI-1318).
3020
+ * Every caller treats `undetermined` as NOT tracked, preserving the required
3021
+ * fail-open behavior (a probe failure must never block the install). Emits at most a
3022
+ * fixed debug-level line (safe relative path + exit classification) and never the
3023
+ * command's stdout/stderr, exception text, env, or config content.
2995
3024
  */
2996
3025
  async function isTrackedProjectConfig(deps, relPath) {
2997
3026
  try {
2998
3027
  const result = await deps.startTicketsDeps.runCommand("git", ["ls-files", "--error-unmatch", "--", relPath], { cwd: deps.cwd });
2999
- if (result.exitCode === 0)
3000
- return true;
3028
+ const verdict = classifyTrackedProbeExitCode(result.exitCode);
3029
+ if (verdict === "tracked")
3030
+ return verdict;
3001
3031
  deps.debugLog(`install-bridge: git-tracked probe for ${relPath} → not tracked (exit ${result.exitCode})`);
3002
- return false;
3032
+ return verdict;
3003
3033
  }
3004
3034
  catch {
3005
3035
  deps.debugLog(`install-bridge: git-tracked probe for ${relPath} → not tracked (probe failed)`);
3006
- return false;
3036
+ return "undetermined";
3007
3037
  }
3008
3038
  }
3009
3039
  /**
@@ -3038,12 +3068,24 @@ async function requestTrackedConfigConsent(deps, relPath) {
3038
3068
  * cannot recognize — the new template args (which DO include `serve`) are used
3039
3069
  * verbatim. `command` and `env` always come from the freshly built entry: this
3040
3070
  * helper governs launcher-token migration only, never credentials.
3071
+ *
3072
+ * BAPI-1318: the one exception is `BAPI_PROJECT_ROOT`. With `preserveProjectRoot`
3073
+ * set (the config is git-tracked), an existing non-blank value is kept exactly
3074
+ * rather than replaced by this machine's `cwd`; a blank or absent value still
3075
+ * takes the fresh entry's root.
3041
3076
  */
3042
- export function preserveExistingLauncherArgs(existing, entry) {
3077
+ export function preserveExistingLauncherArgs(existing, entry, options = {}) {
3043
3078
  const existingEntry = asRecord(existing) ?? {};
3044
3079
  const existingArgs = asRecord(existing)?.args;
3045
3080
  const preserved = refreshBridgeApiPackageSpec(existingArgs, currentBridgePackageSpec());
3046
- return { ...existingEntry, ...entry, ...(preserved ? { args: preserved } : {}) };
3081
+ const merged = { ...existingEntry, ...entry, ...(preserved ? { args: preserved } : {}) };
3082
+ if (options.preserveProjectRoot) {
3083
+ const existingRoot = asRecord(existingEntry.env)?.BAPI_PROJECT_ROOT;
3084
+ if (typeof existingRoot === "string" && existingRoot.trim().length > 0) {
3085
+ merged.env = { ...merged.env, BAPI_PROJECT_ROOT: existingRoot };
3086
+ }
3087
+ }
3088
+ return merged;
3047
3089
  }
3048
3090
  /**
3049
3091
  * Read-only pre-scan for both-key configs across EVERY project target (BAPI-807).
@@ -3107,7 +3149,8 @@ async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
3107
3149
  }
3108
3150
  // Choose the entry variant. A real key only lands in an untracked target, or a
3109
3151
  // tracked target the user EXPLICITLY consented to on a TTY.
3110
- const tracked = trackedState.get(target.relPath) ?? false;
3152
+ const trackedVerdict = trackedState.get(target.relPath) ?? "untracked";
3153
+ const tracked = trackedVerdict === "tracked";
3111
3154
  let entry = entries.real;
3112
3155
  let mode = "real-key";
3113
3156
  if (tracked) {
@@ -3154,7 +3197,18 @@ async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
3154
3197
  // real-key and secret-free variants: credential safety must not decide whether
3155
3198
  // a launcher is migrated.
3156
3199
  const physicalEntry = adaptBridgeEntryForHostTarget(entry, target.target);
3157
- topLevel[registrationKey] = preserveExistingLauncherArgs(topLevel[registrationKey], physicalEntry);
3200
+ // BAPI-1318: the tracked-root rule matches runInit's, for BOTH variants: a
3201
+ // tracked config keeps its committed non-blank BAPI_PROJECT_ROOT. An
3202
+ // undetermined probe fails open (cwd is written) and warns once when that
3203
+ // replaces a different existing value.
3204
+ const existingRoot = asRecord(asRecord(topLevel[registrationKey])?.env)?.BAPI_PROJECT_ROOT;
3205
+ const rootDecision = decideExistingProjectRoot(existingRoot, deps.cwd, trackedVerdict);
3206
+ if (asRecord(topLevel[registrationKey]) && rootDecision.warnReplaced) {
3207
+ deps.errorLog(formatUndeterminedRootReplacedWarning(target.relPath));
3208
+ }
3209
+ topLevel[registrationKey] = preserveExistingLauncherArgs(topLevel[registrationKey], physicalEntry, {
3210
+ preserveProjectRoot: tracked,
3211
+ });
3158
3212
  config[target.topLevelKey] = topLevel;
3159
3213
  await deps.mkdir(path.dirname(fullPath), { recursive: true });
3160
3214
  await deps.writeFile(fullPath, JSON.stringify(config, null, 2) + "\n", {
@@ -3164,6 +3218,160 @@ async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
3164
3218
  }
3165
3219
  return { written, skipped, duplicateRegistrations: [] };
3166
3220
  }
3221
+ // ---------------------------------------------------------------------------
3222
+ // BAPI-1318: `.bridge/config` final-name reconciliation and consented repair
3223
+ // ---------------------------------------------------------------------------
3224
+ /** The doctor command every `.bridge/config` repair warning points at. */
3225
+ const BRIDGE_CONFIG_DOCTOR_HINT = `Then run "npx -y ${MCP_PACKAGE_NAME} doctor" to confirm.`;
3226
+ /** Mismatch warning with the exact fix line (non-TTY or declined repair). */
3227
+ export function formatBridgeConfigMismatchWarning(fileName, provenName) {
3228
+ const file = renderDiagnosticValue(fileName);
3229
+ const proven = renderDiagnosticValue(provenName);
3230
+ return (`.bridge/config names '${file}' but this project is '${proven}'. Worktrees and shell tools ` +
3231
+ `will use the wrong project. Fix: set repo_name = "${proven}" in .bridge/config. ` +
3232
+ BRIDGE_CONFIG_DOCTOR_HINT);
3233
+ }
3234
+ /** Warning for a proven name the manifest's TOML subset cannot hold: no fix line. */
3235
+ export function formatBridgeConfigUnrepresentableWarning(fileName, provenName) {
3236
+ const proven = renderDiagnosticValue(provenName);
3237
+ const lead = fileName === null
3238
+ ? `.bridge/config could not be updated to '${proven}'`
3239
+ : `.bridge/config names '${renderDiagnosticValue(fileName)}' but this project is '${proven}'`;
3240
+ return (`${lead}, and that name cannot be written in .bridge/config (quotes and backslashes are not ` +
3241
+ `supported). Automatic repair is unavailable. ${BRIDGE_CONFIG_DOCTOR_HINT}`);
3242
+ }
3243
+ /** Warning for a `.bridge/config` that does not parse. */
3244
+ export function formatBridgeConfigMalformedWarning() {
3245
+ return (".bridge/config could not be parsed, so its project name was not checked or changed. " +
3246
+ `Fix the file by hand. ${BRIDGE_CONFIG_DOCTOR_HINT}`);
3247
+ }
3248
+ /** Warning for a read, prompt, or write failure during the advisory repair. */
3249
+ export function formatBridgeConfigRepairFailedWarning() {
3250
+ return (".bridge/config could not be checked or updated (a read or write failed); it was left " +
3251
+ `unchanged. Check its repo_name by hand. ${BRIDGE_CONFIG_DOCTOR_HINT}`);
3252
+ }
3253
+ /**
3254
+ * Replace `.bridge/config` atomically: write a sibling temp file with the
3255
+ * original file mode, then rename it over the target. A failure removes the temp
3256
+ * file (best effort) and rethrows so the advisory caller can warn.
3257
+ */
3258
+ async function writeBridgeConfigAtomically(deps, configPath, text) {
3259
+ let mode;
3260
+ try {
3261
+ const info = (await deps.stat(configPath));
3262
+ if (typeof info?.mode === "number")
3263
+ mode = info.mode & 0o777;
3264
+ }
3265
+ catch {
3266
+ mode = undefined;
3267
+ }
3268
+ const tempPath = `${configPath}.${deps.randomBytes(6).toString("hex")}.tmp`;
3269
+ try {
3270
+ await deps.writeFile(tempPath, text, mode !== undefined ? { encoding: "utf-8", mode } : { encoding: "utf-8" });
3271
+ if (mode !== undefined)
3272
+ await deps.chmod(tempPath, mode);
3273
+ await deps.rename(tempPath, configPath);
3274
+ }
3275
+ catch (error) {
3276
+ try {
3277
+ await deps.unlink(tempPath);
3278
+ }
3279
+ catch {
3280
+ /* best-effort temp cleanup only; the original file is untouched */
3281
+ }
3282
+ throw error;
3283
+ }
3284
+ }
3285
+ /**
3286
+ * After the ping has PROVEN `provenName`, make `<cwd>/.bridge/config` agree with
3287
+ * it (BAPI-1318). Only the install folder's own file is read — never a parent's,
3288
+ * so a monorepo subfolder that is its own Bridge project stays safe.
3289
+ *
3290
+ * - `createdText` is the exact text THIS run scaffolded (null when it did not
3291
+ * create the file). If the file still holds exactly that text, it is this
3292
+ * run's own output: a later rename (409 loop or invite exchange) is written
3293
+ * through without a prompt, with one line saying so.
3294
+ * - Otherwise the file is user-owned. A differing name is repaired only after a
3295
+ * default-No TTY consent; a non-TTY run or a "no" leaves it byte-identical and
3296
+ * warns with the exact fix.
3297
+ *
3298
+ * Advisory by contract: every failure warns and returns; nothing here can fail
3299
+ * the install. Warnings name only the two project names, never file content.
3300
+ */
3301
+ export async function reconcileBridgeConfigAfterPing(deps, provenName, createdText) {
3302
+ const configPath = bridgeConfigPath(deps.cwd);
3303
+ try {
3304
+ let text;
3305
+ try {
3306
+ text = await deps.readFile(configPath);
3307
+ }
3308
+ catch (error) {
3309
+ if (error?.code === "ENOENT")
3310
+ return;
3311
+ deps.errorLog(formatBridgeConfigRepairFailedWarning());
3312
+ return;
3313
+ }
3314
+ const parsed = parseBridgeConfigToml(text);
3315
+ if (!parsed.ok) {
3316
+ deps.errorLog(formatBridgeConfigMalformedWarning());
3317
+ return;
3318
+ }
3319
+ const fileName = parsed.manifest.repoName;
3320
+ if (fileName === provenName)
3321
+ return;
3322
+ const representable = validateBridgeConfigRepoNameRepresentability(provenName);
3323
+ if (!representable.ok) {
3324
+ deps.errorLog(formatBridgeConfigUnrepresentableWarning(fileName, provenName));
3325
+ return;
3326
+ }
3327
+ const ownFile = createdText !== null && text === createdText;
3328
+ if (!ownFile) {
3329
+ if (!deps.isTTY || !deps.promptLine) {
3330
+ deps.errorLog(formatBridgeConfigMismatchWarning(fileName, provenName));
3331
+ return;
3332
+ }
3333
+ const answer = (await deps.promptLine(`.bridge/config names '${renderDiagnosticValue(fileName)}' but this project is ` +
3334
+ `'${renderDiagnosticValue(provenName)}'. Update repo_name in .bridge/config? (y/N) `)).trim().toLowerCase();
3335
+ if (answer !== "y" && answer !== "yes") {
3336
+ deps.errorLog(formatBridgeConfigMismatchWarning(fileName, provenName));
3337
+ return;
3338
+ }
3339
+ }
3340
+ // This run's own file already carries the notice from the scaffold, so a
3341
+ // rewrite adds it only to an older user-owned file the user consented to.
3342
+ const rewritten = rewriteBridgeConfigRepoName(text, provenName);
3343
+ if (!rewritten.ok) {
3344
+ deps.errorLog(rewritten.kind === "unrepresentable-name"
3345
+ ? formatBridgeConfigUnrepresentableWarning(fileName, provenName)
3346
+ : formatBridgeConfigMalformedWarning());
3347
+ return;
3348
+ }
3349
+ await writeBridgeConfigAtomically(deps, configPath, rewritten.text);
3350
+ deps.log(` .bridge/config: repo_name updated from '${renderDiagnosticValue(fileName)}' to ` +
3351
+ `'${renderDiagnosticValue(provenName)}'`);
3352
+ }
3353
+ catch {
3354
+ deps.errorLog(formatBridgeConfigRepairFailedWarning());
3355
+ }
3356
+ }
3357
+ /**
3358
+ * Best-effort cleanup for a run that ends in failure before the ping proved the
3359
+ * name (BAPI-1318): remove the `.bridge/config` this run created, but ONLY when
3360
+ * its bytes still equal what this run wrote. A retry then scaffolds it fresh
3361
+ * with the right name. A pre-existing or since-edited file is never removed, and
3362
+ * a cleanup failure never masks the original failure.
3363
+ */
3364
+ async function removeUnprovenBridgeConfig(deps, createdText) {
3365
+ const configPath = bridgeConfigPath(deps.cwd);
3366
+ try {
3367
+ if ((await deps.readFile(configPath)) !== createdText)
3368
+ return;
3369
+ await deps.unlink(configPath);
3370
+ }
3371
+ catch {
3372
+ /* advisory cleanup only */
3373
+ }
3374
+ }
3167
3375
  /**
3168
3376
  * Provision the selected GLOBAL (Codex) and MANUAL (Windsurf)
3169
3377
  * targets through the registry-driven emitter (BAPI-635). Project JSON targets
@@ -4949,6 +5157,24 @@ export async function resolveValidatedPremiumModelAlias(deps, agent) {
4949
5157
  * parser/resolution errors return 1; a ping failure halts (return 1) BEFORE the
4950
5158
  * credential persist and agent spawn. The API key is never logged on any path.
4951
5159
  */
5160
+ /**
5161
+ * The fixed message a bare install prints under Git Bash / mintty (BAPI-1319).
5162
+ * mintty gives Node no TTY, so no prompt can be shown there; this replaces the
5163
+ * confusing "no interactive terminal" credential error with the actual remedy.
5164
+ */
5165
+ export const INSTALL_BRIDGE_MINTTY_MESSAGE = "This terminal (Git Bash / mintty) can't show Bridge's interactive prompts. " +
5166
+ "Run the install from PowerShell or Windows Terminal, or pass --api-key <key> --repo <name>.";
5167
+ /**
5168
+ * True for a native-Windows, non-TTY Git Bash / mintty session (BAPI-1319).
5169
+ * Pure. Prompts are deliberately NOT enabled here: GitHub's Windows runners use
5170
+ * bash with `MSYSTEM` set, and a prompt there would hang CI.
5171
+ */
5172
+ export function isMinttyNonInteractiveInstall(platform, env, isTTY) {
5173
+ if (platform !== "win32" || isTTY)
5174
+ return false;
5175
+ const msystem = env.MSYSTEM;
5176
+ return (typeof msystem === "string" && msystem.trim().length > 0) || env.TERM_PROGRAM === "mintty";
5177
+ }
4952
5178
  export async function runInstallBridgeCli(argv, overrides = {}) {
4953
5179
  const deps = { ...createDefaultInstallBridgeDeps(), ...overrides };
4954
5180
  // When the caller overrode only `fetch` (the common test seam), bind the
@@ -5120,6 +5346,16 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5120
5346
  // every downstream `deps.env` consumer observes it. The caller-owned object (and
5121
5347
  // `process.env`) is never mutated.
5122
5348
  deps.env = { ...deps.env, BAPI_BASE_URL: baseUrl };
5349
+ // ---- BAPI-1319: bare install under Git Bash / mintty ----
5350
+ // A bare run there has no TTY and no credential, so it would fall through to
5351
+ // the "no interactive terminal" credential error. Say what actually helps
5352
+ // instead, before anything is prompted for or written. Runs with arguments or
5353
+ // an env credential are untouched.
5354
+ const hasEnvCredential = (deps.env.BAPI_API_KEY ?? "").trim().length > 0 || (deps.env.BAPI_INVITE ?? "").trim().length > 0;
5355
+ if (argv.length === 0 && !hasEnvCredential && isMinttyNonInteractiveInstall(deps.platform, deps.env, deps.isTTY)) {
5356
+ errorLog(INSTALL_BRIDGE_MINTTY_MESSAGE);
5357
+ return 1;
5358
+ }
5123
5359
  // ---- BAPI-818 (R2): --dry-run short-circuits HERE — genuinely inert ----
5124
5360
  // Fires immediately after argument parsing and the pure base-URL validation
5125
5361
  // above, and strictly BEFORE the project-root confirmation prompt, onboarding
@@ -5296,7 +5532,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5296
5532
  // key-authentication ping, so there is no source to attribute.
5297
5533
  let apiKeySource;
5298
5534
  if (selfServeSignupMode) {
5299
- const emailResult = await resolveSignupEmail(options, deps);
5535
+ const emailResult = await resolveSignupEmail(options, deps, baseUrl);
5300
5536
  if (!emailResult.ok) {
5301
5537
  errorLog(`Error: ${emailResult.error}`);
5302
5538
  return 1;
@@ -5674,6 +5910,10 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5674
5910
  // an exception that ESCAPED all of them.
5675
5911
  let currentStep = INSTALL_BRIDGE_STEP_LABELS.scaffold;
5676
5912
  let bootstrapExchangeSucceeded = false;
5913
+ // BAPI-1318: the `.bridge/config` text Step 1 created (null when it created
5914
+ // none), and whether it still awaits the ping that proves its name.
5915
+ let scaffoldedBridgeConfigText = null;
5916
+ let unprovenBridgeConfigPending = false;
5677
5917
  try {
5678
5918
  // ---- Step 1 — scaffold (secret-free placeholders only) ----
5679
5919
  currentStep = INSTALL_BRIDGE_STEP_LABELS.scaffold;
@@ -5683,9 +5923,17 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5683
5923
  // Step 2 mints/verifies the key and Step 4 persists it, so a placeholder
5684
5924
  // warning here would be a false alarm about a config the installer is
5685
5925
  // actively configuring. The key value itself never crosses this boundary.
5686
- await deps.runInit(deps.cwd, {
5926
+ const initResult = await deps.runInit(deps.cwd, {
5687
5927
  creationContext: { repoName, baseUrl, credentialAvailable: true },
5928
+ isTrackedConfig: (relPath) => isTrackedProjectConfig(deps, relPath),
5688
5929
  });
5930
+ // BAPI-1318: remember the exact `.bridge/config` text this run scaffolded. The
5931
+ // name can still change (409 rename, server-returned invite name) before the
5932
+ // ping proves it; until then a failed run removes its own unchanged file.
5933
+ if (initResult && initResult.bridgeConfigCreated) {
5934
+ scaffoldedBridgeConfigText = buildBridgeConfigManifest(initResult.bridgeConfigRepoName ?? repoName);
5935
+ unprovenBridgeConfigPending = true;
5936
+ }
5689
5937
  // ---- Step 2 — mint (invite mode) or verify (normal mode) the credential ----
5690
5938
  let inviteFingerprint = "";
5691
5939
  if (bootstrapInviteMode) {
@@ -6229,6 +6477,11 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
6229
6477
  : `Error: ${ping.message}`);
6230
6478
  }
6231
6479
  log(" connectivity OK");
6480
+ // BAPI-1318: the name is proven now. Make `.bridge/config` agree with it —
6481
+ // a rename of this run's own scaffold, or a consented repair of an older
6482
+ // file. Advisory: it warns on any problem and never fails the install.
6483
+ unprovenBridgeConfigPending = false;
6484
+ await reconcileBridgeConfigAfterPing(deps, repoName, scaffoldedBridgeConfigText);
6232
6485
  // ---- BAPI-1221 (S2): persist the captured posture, now that connectivity
6233
6486
  // is validated and `repoName`/`apiKey` are both final. Overwrites any
6234
6487
  // existing value unconditionally — the old value is never read first. A
@@ -6567,6 +6820,10 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
6567
6820
  // deterministic setup is complete, so this is a non-fatal warning (exit 0), with
6568
6821
  // the same shared continuation so the remediation is consistent.
6569
6822
  errorLog(`Warning: setup steps completed, but the agent session could not be opened (${spawnResult.error}).`);
6823
+ // BAPI-1319: on macOS the usual cause is a missing Automation or
6824
+ // Accessibility grant — the same guidance start-tickets prints.
6825
+ if (deps.platform === "darwin")
6826
+ errorLog(macosTerminalPermissionGuidance());
6570
6827
  log(buildManualInstallBridgeContinuation("configured", labelsForPlatforms(selectedPlatforms)));
6571
6828
  return 0;
6572
6829
  }
@@ -6643,4 +6900,11 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
6643
6900
  errorLog(line);
6644
6901
  return 1;
6645
6902
  }
6903
+ finally {
6904
+ // BAPI-1318: every exit before the ping proved the name is a failure, so a
6905
+ // still-pending scaffold is removed when it is unchanged (see helper).
6906
+ if (unprovenBridgeConfigPending && scaffoldedBridgeConfigText !== null) {
6907
+ await removeUnprovenBridgeConfig(deps, scaffoldedBridgeConfigText);
6908
+ }
6909
+ }
6646
6910
  }