@bridge_gpt/mcp-server 0.2.41 → 0.2.42

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 (73) hide show
  1. package/README.md +10 -10
  2. package/build/agent-capabilities/cli.js +2 -1
  3. package/build/agent-launchers/claude-executor-adapter.js +17 -4
  4. package/build/claude-user-config-doctor.js +42 -11
  5. package/build/cli-release.js +2 -1
  6. package/build/commands.generated.js +4 -4
  7. package/build/conduct-epic/bridge-client.js +354 -113
  8. package/build/conduct-epic/checkpoint-store.js +17 -0
  9. package/build/conduct-epic/cli.js +752 -99
  10. package/build/conduct-epic/cut-protocol.js +327 -0
  11. package/build/conduct-epic/spawn.js +14 -2
  12. package/build/conductor/bridge-api-client.js +27 -1
  13. package/build/conductor/cli.js +46 -1
  14. package/build/conductor/doctor.js +101 -16
  15. package/build/conductor/epic-reconcile.js +72 -19
  16. package/build/conductor/epic-runtime.js +15 -3
  17. package/build/conductor/errors.js +47 -0
  18. package/build/conductor/git-hooks.js +205 -11
  19. package/build/conductor/install-doctor.js +230 -1
  20. package/build/conductor/local-merge.js +130 -28
  21. package/build/conductor/tools.js +32 -3
  22. package/build/conductor/worker-ledger-cli.js +27 -1
  23. package/build/conductor-bin.js +15 -15
  24. package/build/credentials-cli.js +3 -2
  25. package/build/doctor.js +107 -41
  26. package/build/executor/cli.js +48 -1
  27. package/build/executor/env.js +21 -0
  28. package/build/executor/index-scope.js +39 -0
  29. package/build/executor/job-log-registry.js +69 -0
  30. package/build/executor/job-runner.js +148 -26
  31. package/build/executor/live-worker-registry.js +83 -0
  32. package/build/executor/observation.js +167 -6
  33. package/build/executor/platform.js +147 -3
  34. package/build/executor/process.js +58 -14
  35. package/build/executor/runner.js +235 -48
  36. package/build/executor/test-clock.js +3 -2
  37. package/build/index-scope-contract.js +96 -0
  38. package/build/index.js +153 -204
  39. package/build/init.js +83 -22
  40. package/build/install-bridge-conductor.js +323 -14
  41. package/build/install-bridge.js +202 -38
  42. package/build/install-doctor.js +23 -9
  43. package/build/install-reexec.js +2 -1
  44. package/build/launcher-config-inspection.js +83 -22
  45. package/build/mcp-host-config.js +331 -67
  46. package/build/mcp-host-targets.js +45 -21
  47. package/build/mcp-identity.js +92 -0
  48. package/build/mcp-install-state.js +94 -1
  49. package/build/mcp-invoke.js +2 -1
  50. package/build/mcp-provisioning.js +45 -12
  51. package/build/mcp-registration-doctor.js +35 -13
  52. package/build/mcp-server-invocation.js +4 -2
  53. package/build/merge-pull-request.js +208 -9
  54. package/build/pipelines.generated.js +3 -3
  55. package/build/plane/defaults.js +4 -1
  56. package/build/plane/preflight.js +81 -10
  57. package/build/plane/test-fakes.js +9 -1
  58. package/build/readme.generated.js +1 -1
  59. package/build/regression-check.js +3 -2
  60. package/build/review-tickets.js +8 -7
  61. package/build/run-unit-tests-launcher.js +74 -1
  62. package/build/schedule-run.js +3 -2
  63. package/build/setup-epic.js +453 -78
  64. package/build/sfcc/tool-wrapper.js +15 -0
  65. package/build/start-tickets-prereqs.js +11 -6
  66. package/build/start-tickets.js +91 -85
  67. package/build/update-check.js +3 -2
  68. package/build/upgrade-advice.js +2 -1
  69. package/build/upgrade-cli.js +50 -18
  70. package/build/version.generated.js +1 -1
  71. package/docs/CONDUCTOR.md +22 -0
  72. package/docs/install/mcp-tool-integrations.md +19 -3
  73. package/package.json +2 -2
@@ -23,9 +23,13 @@
23
23
  * filesystem write, install-state write, and write-consent prompt. (It still
24
24
  * asks the base-URL confirmation above, because its doctor GET is a network
25
25
  * call that reveals the key to the resolved host.)
26
- * 3. **Two independent consents.** Confirming the resolved base URL is NOT
27
- * consent to write, and neither is consent to overwrite a workflow file. Each
28
- * write surface asks for itself.
26
+ * 3. **One consent per write surface.** Confirming the resolved base URL is NOT
27
+ * consent to write; consent to overwrite a workflow file is not consent to
28
+ * edit host MCP configuration; and neither is consent to install Git hooks.
29
+ * The rule is per-SURFACE, not a fixed count — BAPI-775 added two more write
30
+ * surfaces (host MCP configs, managed Git hooks) and each asks for itself, so
31
+ * counting consents was never the invariant. A missing prompt seam (no TTY)
32
+ * is a decline everywhere, never a default-yes.
29
33
  * 4. **The executor leg is never claimed as provisioned by local success.**
30
34
  * AMENDED BY BAPI-779 (R-1): this command can now write the service unit
31
35
  * *and* start it. What did not change — and must not — is where readiness
@@ -36,6 +40,20 @@
36
40
  * otherwise. Local lifecycle success and server observation are reported as
37
41
  * two separate facts, and the second is never derived from the first.
38
42
  *
43
+ * The two local out-of-the-box phases (BAPI-775) run between the workflow
44
+ * scaffold and setup-epic verification, each behind its own consent:
45
+ *
46
+ * tool visibility — merge the `conductor` token into EXISTING supported host
47
+ * MCP entries (`.mcp.json`, `.vscode/mcp.json`, `.cursor/mcp.json` only,
48
+ * never Codex or Copilot), then print the restart advisory, because
49
+ * `ACTIVE_GROUPS` is frozen at MCP server startup.
50
+ * local observability — install the managed conductor Git hooks, then report
51
+ * native ledger loadability. The ledger probe is READ-ONLY, so it needs no
52
+ * consent and runs whether hook consent was accepted or declined.
53
+ *
54
+ * Neither can produce `complete`: they provision LOCAL capability, and the
55
+ * terminal outcome still turns only on the live server-observed executor claim.
56
+ *
39
57
  * The executor phase (BAPI-779) is opt-in via `--executor-id` and runs behind
40
58
  * TWO independent consents, per property 3: writing the unit is not consent to
41
59
  * start a persistent daemon. Its shape is:
@@ -57,6 +75,9 @@
57
75
  */
58
76
  import { bootstrapConductorSupervisorDefaults, ConductorBridgeApiError, fetchConductorReadiness, } from "./conductor/bridge-api-client.js";
59
77
  import { CONDUCTOR_OPERATOR_RUNBOOK_POINTER, EXECUTOR_PROVISIONING_GUIDANCE, conductorInstallDoctorExitCode, conductorInstallDoctorHasFatal, formatConductorInstallDoctorReport, } from "./conductor/install-doctor.js";
78
+ import { describeNativeLedgerAvailability, } from "./conductor/doctor.js";
79
+ import { CONDUCTOR_BIN_UNRESOLVED_ERROR, MANAGED_HOOK_NAMES, } from "./conductor/git-hooks.js";
80
+ import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
60
81
  import { CLAUDE_REVIEW_WORKFLOW_RELPATH, renderClaudeReviewWorkflow, } from "./claude-review-workflow.js";
61
82
  import { formatExecutorInstallPreflight, } from "./executor/install-preflight.js";
62
83
  import { buildExecutorServiceStartPlan, formatExecutorServiceStartFailure, } from "./executor/service-lifecycle.js";
@@ -101,9 +122,11 @@ export function getInstallBridgeConductorUsage() {
101
122
  " --branches a,b claude-review workflow: PR branch triggers",
102
123
  " --skip-workflow Never offer the claude-review workflow",
103
124
  " --dry-run Diagnose and preview only. Performs NO writes:",
104
- " no POST, no file write, no install-state write,",
105
- " no service-unit write, no launchctl/systemctl",
106
- " command, and no write- or start-consent prompt.",
125
+ " no POST, no file write, no host MCP config write,",
126
+ " no Git hook write, no install-state or capability",
127
+ " write, no service-unit write, no launchctl/systemctl",
128
+ " or other lifecycle command, and no write- or",
129
+ " start-consent prompt.",
107
130
  " Still asks the base-URL confirmation below first —",
108
131
  " its doctor readiness GET carries the repo API key.",
109
132
  " -h, --help Show this help",
@@ -113,6 +136,22 @@ export function getInstallBridgeConductorUsage() {
113
136
  "production when unset). The bootstrap POST then asks its own, separate write",
114
137
  "consent. Without a TTY there is no consent, so the command aborts.",
115
138
  "",
139
+ "Tool visibility (its own consent): adds the `conductor` token to the",
140
+ "BRIDGE_MCP_PROFILE of the Bridge entry in host MCP configurations that",
141
+ "ALREADY have one. Automatic merging covers .mcp.json, .vscode/mcp.json, and",
142
+ ".cursor/mcp.json only — never Codex or Copilot, which are neither inspected",
143
+ "nor changed. No file is ever created: when no supported config carries a",
144
+ "a Bridge entry, the run says so and points at the base `install` command.",
145
+ "The MCP server captures its active groups at startup, so a merge activates on",
146
+ "the next launch — restart your MCP client to see the conductor tools.",
147
+ "",
148
+ "Local observability (a SEPARATE consent): installs or updates this",
149
+ "repository's managed conductor Git hooks (post-commit,",
150
+ "reference-transaction) and reports each hook's action. It then probes native",
151
+ "ledger loadability, which is READ-ONLY and therefore runs with or without",
152
+ "hook consent; a degraded ledger reports the module, NODE_MODULE_VERSION, and",
153
+ "failure kind, and never triggers an install, rebuild, or migration.",
154
+ "",
116
155
  "Executor provisioning: `executor install-service` still generates a unit and",
117
156
  "nothing else, and remains available for operators who want the file only.",
118
157
  "",
@@ -503,6 +542,216 @@ async function runExecutorProvisioningPhase(deps, executor, plans, access) {
503
542
  deps.log(" executor_ready is NOT claimed — only a live server observation can produce it.");
504
543
  return null;
505
544
  }
545
+ // ---------------------------------------------------------------------------
546
+ // Local out-of-the-box phases (BAPI-775)
547
+ // ---------------------------------------------------------------------------
548
+ /**
549
+ * The exact restart advisory the SFCC profile merge prints (`index.ts`), reused
550
+ * verbatim in shape for the conductor token.
551
+ *
552
+ * `ACTIVE_GROUPS` is frozen when the MCP server process starts, so a merged
553
+ * profile changes nothing about the session that is already running. Printing
554
+ * this after EVERY non-empty merge — including an idempotent one where the token
555
+ * was already present — is deliberate: an operator whose second run silently
556
+ * said nothing would reasonably conclude the tools were already visible.
557
+ */
558
+ export function formatConductorRestartAdvisory(mergedProfile) {
559
+ return (`⚠️ Conductor profile updated: BRIDGE_MCP_PROFILE is now set to \`${mergedProfile}\` ` +
560
+ "in your local MCP config file(s). The running MCP server captured its active groups " +
561
+ "at startup, so this activates on the next MCP server launch — restart your MCP " +
562
+ "client to gain access to the conductor tools.");
563
+ }
564
+ /** Remediation printed when no supported host config carries a Bridge entry. */
565
+ export const CONDUCTOR_PROFILE_EMPTY_MERGE_REMEDIATION = `run \`npx ${MCP_PACKAGE_NAME} install\` to create and register the host MCP entry, ` +
566
+ "then re-run `install conductor`.";
567
+ /** Remediation printed whenever the managed conductor hooks were not installed. */
568
+ export const CONDUCTOR_HOOKS_MANUAL_REMEDIATION = "re-run `install conductor` and accept the local-observability consent, or run " +
569
+ "`conductor install-git-hooks` directly.";
570
+ /**
571
+ * Phase: tool visibility.
572
+ *
573
+ * Adds the `conductor` token to EXISTING supported host MCP entries behind its
574
+ * own consent, then prints the restart advisory. It never creates a host config
575
+ * — the merge helper's never-create rule is what keeps a declined or
576
+ * unconfigured repository from acquiring files it did not ask for.
577
+ */
578
+ async function runConductorToolVisibilityPhase(deps, local) {
579
+ const consented = await confirm(deps, "Update existing supported host MCP configurations (.mcp.json, .vscode/mcp.json, " +
580
+ ".cursor/mcp.json) to add the `conductor` profile? [y/N]: ");
581
+ if (!consented) {
582
+ deps.log(" declined — no host MCP configuration and no install state were written.");
583
+ return {
584
+ manualActions: [
585
+ "tool visibility: re-run `install conductor` and accept the profile consent to " +
586
+ "make the conductor MCP tools visible.",
587
+ ],
588
+ };
589
+ }
590
+ let mergedProfile;
591
+ try {
592
+ mergedProfile = await local.mergeProfileToken();
593
+ }
594
+ catch {
595
+ // The thrown value is discarded: it can carry an absolute config path.
596
+ deps.errorLog(" the host MCP profile could not be updated.");
597
+ return { manualActions: [], fatal: true };
598
+ }
599
+ if (mergedProfile === "") {
600
+ deps.log(" no supported host MCP configuration carries a Bridge entry, so nothing " +
601
+ "was written (this command never creates one).");
602
+ return {
603
+ manualActions: [
604
+ `tool visibility: ${CONDUCTOR_PROFILE_EMPTY_MERGE_REMEDIATION}`,
605
+ ],
606
+ };
607
+ }
608
+ // Which configs actually carry the token now. Derived from the READ-ONLY
609
+ // inspector rather than guessed from the merged profile string, which says
610
+ // nothing about which files it came from.
611
+ let carrying = [];
612
+ try {
613
+ const inspection = await local.inspectProfileToken();
614
+ carrying = inspection.configs
615
+ .filter((c) => c.state === "token-present")
616
+ .map((c) => c.path);
617
+ }
618
+ catch {
619
+ // A failed read-back is not a failed merge. The advisory below still prints.
620
+ }
621
+ deps.log(carrying.length > 0
622
+ ? ` updated: ${carrying.join(", ")}`
623
+ : " the conductor profile was merged into your supported host MCP config(s).");
624
+ // Coverage honesty: the merge path writes these three files and no others.
625
+ deps.log(" Codex and Copilot host configuration was NOT inspected or changed.");
626
+ deps.log(` ${formatConductorRestartAdvisory(mergedProfile)}`);
627
+ const recorded = await local.recordCapability("conductor_tool_visibility");
628
+ if (!recorded.ok) {
629
+ // Advisory: install state is a diagnostic aid. A recording failure does not
630
+ // invalidate the host-config change that just landed.
631
+ deps.errorLog(` note: install-state not updated (${recorded.error ?? "unknown"})`);
632
+ }
633
+ return { manualActions: [] };
634
+ }
635
+ /**
636
+ * Phase: local observability.
637
+ *
638
+ * Installs the managed conductor Git hooks behind its OWN consent — never
639
+ * inferred from the base-URL, bootstrap, workflow, profile-token, or executor
640
+ * consents — and then always reports native ledger loadability, which is
641
+ * read-only and therefore needs no consent at all.
642
+ */
643
+ async function runConductorLocalObservabilityPhase(deps, local) {
644
+ const manualActions = [];
645
+ let fatal = false;
646
+ const consented = await confirm(deps, "Install or update this repository's managed conductor Git hooks " +
647
+ "(post-commit, reference-transaction)? [y/N]: ");
648
+ if (!consented) {
649
+ deps.log(" declined — no Git hook and no install state were written.");
650
+ manualActions.push(`local observability: ${CONDUCTOR_HOOKS_MANUAL_REMEDIATION}`);
651
+ }
652
+ else {
653
+ let result = null;
654
+ try {
655
+ result = await local.installGitHooks();
656
+ }
657
+ catch {
658
+ // Generic by design: the caught value can carry a hooks-directory path.
659
+ // The phase is marked fatal but does NOT return here — the read-only
660
+ // ledger report below still owes the operator its answer.
661
+ deps.errorLog(" the conductor Git hooks could not be installed.");
662
+ manualActions.push(`local observability: ${CONDUCTOR_HOOKS_MANUAL_REMEDIATION}`);
663
+ fatal = true;
664
+ }
665
+ if (result !== null && !result.ok) {
666
+ // BAPI-772's fail-loud case: nothing was created, merged, or chmodded, and
667
+ // `reason` is already a sanitized sentence. This one IS a command failure —
668
+ // a hook that cannot resolve its binary is a dead hook, and the whole point
669
+ // of that ticket is that it no longer gets written silently.
670
+ deps.errorLog(result.error === CONDUCTOR_BIN_UNRESOLVED_ERROR
671
+ ? ` hook installation refused — ${result.reason}`
672
+ : " hook installation refused.");
673
+ manualActions.push(`local observability: ${CONDUCTOR_HOOKS_MANUAL_REMEDIATION}`);
674
+ fatal = true;
675
+ }
676
+ else if (result !== null && result.ok) {
677
+ if (!result.is_worktree) {
678
+ deps.log(" not a git worktree, so no managed hook was installed (degraded optional capability).");
679
+ manualActions.push("local observability: run `install conductor` from a git worktree to install the " +
680
+ "managed conductor hooks.");
681
+ }
682
+ else {
683
+ for (const hook of result.installed) {
684
+ // The per-hook warning is rendered beside its action, not merely
685
+ // counted: a hook reported as `created` while carrying "could not
686
+ // chmod +x" reads as provisioned unless the warning is visible, and
687
+ // that warning is exactly what makes it unusable.
688
+ deps.log(hook.warning === undefined
689
+ ? ` ${hook.name}: ${hook.action}`
690
+ : ` ${hook.name}: ${hook.action} — warning: ${hook.warning}`);
691
+ }
692
+ // Sanitized: the installer's own warnings are secret-free sentences, and
693
+ // hook CONTENT, env, and caught errors are never rendered.
694
+ for (const warning of result.warnings)
695
+ deps.log(` warning: ${warning}`);
696
+ // Completeness is checked against the managed-hook ROSTER, not against
697
+ // "at least one hook was installed": a partial install that wrote only
698
+ // post-commit and warned about nothing would otherwise be recorded as
699
+ // provisioned observability, and the missing reference-transaction hook
700
+ // is precisely the one that stops half the events from ever being
701
+ // emitted.
702
+ const usable = (name) => {
703
+ const hook = result.installed.find((h) => h.name === name);
704
+ return hook !== undefined && hook.action !== "skipped" && hook.warning === undefined;
705
+ };
706
+ const complete = MANAGED_HOOK_NAMES.every((name) => usable(name)) && result.warnings.length === 0;
707
+ if (complete) {
708
+ const recorded = await local.recordCapability("conductor_local_observability");
709
+ if (!recorded.ok) {
710
+ deps.errorLog(` note: install-state not updated (${recorded.error ?? "unknown"})`);
711
+ }
712
+ }
713
+ else {
714
+ // A skipped or warned hook is NOT provisioned local observability, and
715
+ // recording the capability here would make install state claim
716
+ // something a `conductor doctor` run would immediately contradict.
717
+ deps.log(" the managed hooks are not fully usable, so local observability was not recorded.");
718
+ manualActions.push(`local observability: ${CONDUCTOR_HOOKS_MANUAL_REMEDIATION}`);
719
+ }
720
+ }
721
+ }
722
+ }
723
+ // The ledger probe runs whether hook consent was accepted or DECLINED: it is
724
+ // strictly read-only, so it needs no consent, and its result is exactly what
725
+ // an operator needs to know before a hook can produce an event. It also runs
726
+ // before any fail-loud return below, so a hook-binary refusal never costs the
727
+ // operator this diagnostic.
728
+ let inspection = null;
729
+ try {
730
+ inspection = await local.probeNativeLedger();
731
+ }
732
+ catch {
733
+ inspection = null;
734
+ }
735
+ if (inspection === null) {
736
+ deps.log(" ledger loadability could not be probed on this host.");
737
+ }
738
+ else {
739
+ const availability = describeNativeLedgerAvailability(inspection);
740
+ if (availability.status === "available") {
741
+ deps.log(" ledger available — the native binding loads.");
742
+ }
743
+ else {
744
+ // Never fatal, and never an install/rebuild/migration attempt: the module,
745
+ // ABI, and sanitized failure kind are what make an npm optional-dependency
746
+ // build skip diagnosable.
747
+ deps.log(` ledger degraded — ${availability.module} did not load ` +
748
+ `(NODE_MODULE_VERSION ${availability.nodeModulesAbi}, failure: ${availability.failureKind}).`);
749
+ manualActions.push(`local observability: reinstall ${availability.module} for this Node runtime to ` +
750
+ "restore the local conductor event ledger.");
751
+ }
752
+ }
753
+ return { manualActions, fatal };
754
+ }
506
755
  /**
507
756
  * Run the nested conductor installer. Returns the process exit code.
508
757
  */
@@ -542,7 +791,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
542
791
  const access = accessResult.ok ? accessResult.access : null;
543
792
  const accessError = accessResult.ok ? undefined : accessResult.error;
544
793
  // ---- Phase 1: unified doctor, BEFORE every write -----------------------
545
- deps.log("Phase 1/6 — running the unified conductor doctor (read-only)…");
794
+ deps.log("Phase 1/8 — running the unified conductor doctor (read-only)…");
546
795
  const firstReport = await deps.runDoctor({
547
796
  access,
548
797
  accessError,
@@ -571,6 +820,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
571
820
  // effect — provisioning a service that cannot run is worse than refusing —
572
821
  // and before the dry-run return, so `--dry-run` reports the same refusal.
573
822
  const executor = deps.executorProvisioning;
823
+ const local = deps.localProvisioning;
574
824
  const executorRequested = Boolean(executor && options.executorId);
575
825
  let executorPlans = null;
576
826
  if (executor && executorRequested) {
@@ -638,6 +888,18 @@ export async function runInstallBridgeConductorCli(argv, deps) {
638
888
  // would actually happen. Still zero writes: no unit writer, no
639
889
  // install-state recorder, no lifecycle runner, and no consent prompt has
640
890
  // been reached on this branch.
891
+ // BAPI-775: named as SEPARATE consents so the preview cannot read as one
892
+ // blanket approval. Nothing below this line invokes the profile merger, the
893
+ // hook installer, the capability recorder, or the phase's ledger probe —
894
+ // this branch returns before every one of them.
895
+ deps.log(local
896
+ ? " Tool visibility: would ask its OWN consent before adding the `conductor` " +
897
+ "profile to existing .mcp.json / .vscode/mcp.json / .cursor/mcp.json entries."
898
+ : " Tool visibility: skipped (this caller supplied no local-provisioning seams)");
899
+ deps.log(local
900
+ ? " Local observability: would ask a SEPARATE consent before installing the " +
901
+ "managed conductor Git hooks; the ledger probe is read-only."
902
+ : " Local observability: skipped (this caller supplied no local-provisioning seams)");
641
903
  if (executorPlans) {
642
904
  deps.log(` Would write executor unit: ${executorPlans.install.unitPath}`);
643
905
  deps.log(` Would start it with: ${executorPlans.start.manualCommand}`);
@@ -668,7 +930,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
668
930
  }
669
931
  // ---- Phase 2: bootstrap ------------------------------------------------
670
932
  deps.log("");
671
- deps.log("Phase 2/6 — bootstrapping supervisor project defaults…");
933
+ deps.log("Phase 2/8 — bootstrapping supervisor project defaults…");
672
934
  let bootstrapResponse;
673
935
  try {
674
936
  bootstrapResponse = await bootstrapConductorSupervisorDefaults(access, {
@@ -692,7 +954,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
692
954
  deps.log(` wrote supervisor setup + config (fields: ${bootstrapResponse.audited_field_names.join(", ")})`);
693
955
  // ---- Phase 3: optional workflow scaffold -------------------------------
694
956
  deps.log("");
695
- deps.log("Phase 3/6 — claude-review workflow…");
957
+ deps.log("Phase 3/8 — claude-review workflow…");
696
958
  if (options.skipWorkflow || options.reviewPolicy !== "verdict_protocol") {
697
959
  deps.log(` skipped — the '${options.reviewPolicy}' review policy does not consume the ` +
698
960
  "sticky verdict this workflow emits.");
@@ -749,12 +1011,47 @@ export async function runInstallBridgeConductorCli(argv, deps) {
749
1011
  }
750
1012
  }
751
1013
  }
752
- // ---- Phase 4: setup-epic verification (conditional) --------------------
1014
+ // ---- Phases 4 + 5: local out-of-the-box provisioning (BAPI-775) --------
1015
+ // Placed after the workflow scaffold and BEFORE setup-epic verification,
1016
+ // executor provisioning, and the capability report, so a run that stops at a
1017
+ // later phase has still closed the local gap it consented to close.
1018
+ const manualActions = [];
1019
+ deps.log("");
1020
+ deps.log("Phase 4/8 — conductor tool visibility…");
1021
+ if (local) {
1022
+ const outcome = await runConductorToolVisibilityPhase(deps, local);
1023
+ manualActions.push(...outcome.manualActions);
1024
+ if (outcome.fatal)
1025
+ return 1;
1026
+ }
1027
+ else {
1028
+ deps.log(" skipped — this caller supplied no local-provisioning seams.");
1029
+ manualActions.push("tool visibility: run the packaged `install conductor` command to add the " +
1030
+ "`conductor` profile to your host MCP configuration.");
1031
+ }
1032
+ deps.log("");
1033
+ deps.log("Phase 5/8 — conductor local observability…");
1034
+ let observabilityFatal = false;
1035
+ if (local) {
1036
+ const outcome = await runConductorLocalObservabilityPhase(deps, local);
1037
+ manualActions.push(...outcome.manualActions);
1038
+ observabilityFatal = outcome.fatal === true;
1039
+ }
1040
+ else {
1041
+ deps.log(" skipped — this caller supplied no local-provisioning seams.");
1042
+ manualActions.push("local observability: run the packaged `install conductor` command, or " +
1043
+ "`conductor install-git-hooks`, to install the managed conductor Git hooks.");
1044
+ }
1045
+ // The fail-loud hook-binary refusal exits 1 — but only AFTER the phase's
1046
+ // read-only ledger report has been printed, which the phase guarantees.
1047
+ if (observabilityFatal)
1048
+ return 1;
1049
+ // ---- Phase 6: setup-epic verification (conditional) --------------------
753
1050
  // `parseSetupEpicArgs` hard-requires --epic-key AND --plan-file before any
754
1051
  // dry-run branch, so this phase can only run when both are supplied. Fresh
755
1052
  // repositories skip it with an explicit message rather than hard-failing.
756
1053
  deps.log("");
757
- deps.log("Phase 4/6 — setup-epic verification…");
1054
+ deps.log("Phase 6/8 — setup-epic verification…");
758
1055
  if (options.epicKey && options.planFile) {
759
1056
  deps.log(" verifying with `setup-epic --dry-run`…");
760
1057
  const verification = await deps.runSetupEpicDryRun([
@@ -783,7 +1080,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
783
1080
  // started daemon behind, and BEFORE the capability report so the matrix can
784
1081
  // use the readiness this phase actually observed.
785
1082
  deps.log("");
786
- deps.log("Phase 5/6 — executor provisioning…");
1083
+ deps.log("Phase 7/8 — executor provisioning…");
787
1084
  let observedReadiness = null;
788
1085
  if (executor && executorPlans) {
789
1086
  observedReadiness = await runExecutorProvisioningPhase(deps, executor, executorPlans, access);
@@ -796,7 +1093,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
796
1093
  }
797
1094
  // ---- Phase 6: capability matrix + final doctor -------------------------
798
1095
  deps.log("");
799
- deps.log("Phase 6/6 — capability report…");
1096
+ deps.log("Phase 8/8 — capability report…");
800
1097
  // The observed payload WINS when the executor phase actually saw the server
801
1098
  // report readiness. Otherwise the matrix re-reads readiness itself — it is
802
1099
  // never handed a synthesized response, so `executor_ready` still originates
@@ -820,9 +1117,21 @@ export async function runInstallBridgeConductorCli(argv, deps) {
820
1117
  reviewPolicySource: options.reviewPolicy,
821
1118
  });
822
1119
  deps.log(formatConductorInstallDoctorReport(finalReport));
1120
+ // Manual actions (BAPI-775): every declined, unavailable, empty, or incomplete
1121
+ // local phase leaves a pointer here. Collected in one place because an
1122
+ // operator who scrolled past a phase still needs to know what is outstanding.
1123
+ if (manualActions.length > 0) {
1124
+ deps.log("");
1125
+ deps.log("Manual actions");
1126
+ deps.log("──────────────");
1127
+ for (const action of manualActions)
1128
+ deps.log(` → ${action}`);
1129
+ }
823
1130
  deps.log("");
824
1131
  // The executor leg can only be true when the server observed a recent claim,
825
- // so a bootstrap alone can never print `complete`.
1132
+ // so a bootstrap alone can never print `complete`. The two local phases above
1133
+ // deliberately do NOT feed this predicate: local provisioning cannot
1134
+ // manufacture `complete`, and the terminal outcome is unchanged by BAPI-775.
826
1135
  const allReady = matrix.apiReady && matrix.configurationReady && matrix.executorReady;
827
1136
  deps.log(allReady ? "complete" : "complete-with-manual-steps");
828
1137
  return conductorInstallDoctorExitCode(finalReport);