@bridge_gpt/mcp-server 0.2.67 → 0.2.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +272 -51
- package/build/agent-launchers/claude-executor-adapter.js +27 -0
- package/build/agents.generated.js +22 -2
- package/build/bitbucket-workspace-remote.js +136 -0
- package/build/bootstrap-project-identity.js +24 -0
- package/build/bridge-permissions.js +1362 -0
- package/build/check-update-cli.js +74 -0
- package/build/claude-settings.js +102 -2
- package/build/cli-help.js +27 -0
- package/build/codex-home.js +9 -1
- package/build/codex-launcher-pin.js +598 -0
- package/build/command-assets-doctor.js +24 -70
- package/build/command-provisioning.js +29 -5
- package/build/commands.generated.js +9 -9
- package/build/conductor/bridge-api-client.js +714 -0
- package/build/conductor/command-drift-production.js +92 -0
- package/build/conductor/command-drift.js +89 -0
- package/build/conductor/doctor.js +69 -26
- package/build/conductor/epic-branch-ci-inspection.js +1375 -0
- package/build/conductor/epic-implementer-cli.js +76 -22
- package/build/conductor/gate-discovery.js +890 -0
- package/build/conductor/install-doctor.js +15 -3
- package/build/conductor/json-output.js +63 -0
- package/build/conductor/readiness-cli.js +95 -10
- package/build/conductor/readiness-sections.js +369 -3
- package/build/conductor/readiness.js +211 -8
- package/build/conductor/recovery-cli.js +110 -4
- package/build/conductor/recovery-operations.js +99 -1
- package/build/connect-bitbucket-api.js +41 -6
- package/build/connect-bitbucket.js +106 -33
- package/build/connect-github-api.js +33 -14
- package/build/credential-store.js +207 -47
- package/build/docs.generated.js +57 -3
- package/build/doctor.js +198 -4
- package/build/drive-epic.js +45 -8
- package/build/epic-coherence-input.test-fixtures.js +4 -3
- package/build/epic-implementer-bin.js +1369 -25
- package/build/epic-workarounds-path.js +39 -0
- package/build/executor/cli.js +30 -0
- package/build/executor/completion-contract.js +20 -5
- package/build/executor/deps.js +4 -1
- package/build/executor/gate-qualification-job.js +499 -0
- package/build/executor/guarded-fast-forward-push.js +129 -0
- package/build/executor/http-client.js +43 -4
- package/build/executor/integration-job.js +122 -8
- package/build/executor/job-errors.js +8 -1
- package/build/executor/job-runner.js +589 -47
- package/build/executor/likely-tests.js +290 -0
- package/build/executor/live-worker-registry.js +1 -1
- package/build/executor/observation.js +31 -2
- package/build/executor/packaged-recipe.js +106 -0
- package/build/executor/permissions.js +115 -28
- package/build/executor/platform.js +1 -1
- package/build/executor/preflight.js +61 -12
- package/build/executor/process-containment.js +36 -12
- package/build/executor/process.js +15 -7
- package/build/executor/prompt-spec.js +33 -1
- package/build/executor/recently-failed-tests-file.js +71 -0
- package/build/executor/reconcile-overlap-job.js +41 -23
- package/build/executor/required-verification.js +1 -1
- package/build/executor/required-work-ledger.js +105 -17
- package/build/executor/retirement.js +69 -0
- package/build/executor/runner.js +114 -11
- package/build/executor/test-clock.js +15 -0
- package/build/executor/types.js +32 -0
- package/build/executor/viewer-tabs.js +58 -8
- package/build/executor/worker-command.js +23 -7
- package/build/executor/worker-diagnostics.js +37 -0
- package/build/executor/worker-finalization.js +31 -5
- package/build/executor/worker-guard-hook.js +179 -25
- package/build/executor/worker-log.js +4 -3
- package/build/executor/worker-verification-guidance.js +37 -0
- package/build/executor/worktree.js +88 -11
- package/build/first-build-advisory.js +97 -0
- package/build/generated-assets-advisory.js +14 -0
- package/build/generated-assets-catalog.js +66 -0
- package/build/generated-assets-context.js +46 -0
- package/build/generated-assets-dependencies.js +26 -0
- package/build/generated-assets-doctor.js +50 -0
- package/build/generated-assets-health.js +65 -0
- package/build/generated-assets-host.js +17 -0
- package/build/generated-assets-inventory.js +17 -0
- package/build/generated-assets-paths.js +60 -0
- package/build/generated-assets-publisher.js +133 -0
- package/build/generated-assets-remedy.js +25 -0
- package/build/generated-assets-repair.js +72 -0
- package/build/generated-assets-session.js +71 -0
- package/build/generated-assets-test-support.js +35 -0
- package/build/index.js +1109 -695
- package/build/init.js +54 -177
- package/build/install-bridge-conductor-gate.js +1372 -0
- package/build/install-bridge-conductor.js +289 -36
- package/build/install-bridge.js +722 -252
- package/build/launcher-config-inspection.js +184 -19
- package/build/mcp-host-targets.js +4 -0
- package/build/mcp-identity.js +7 -0
- package/build/mcp-install-state.js +50 -4
- package/build/merge-workflow-install.js +3 -4
- package/build/pipeline-utils.js +6 -2
- package/build/pipelines.generated.js +22 -142
- package/build/plan-execution-ledger.js +2 -32
- package/build/plane/manifest.js +16 -1
- package/build/plane/preflight.js +54 -1
- package/build/printed-remedy-commands.js +16 -1
- package/build/readme.generated.js +1 -1
- package/build/repair-assets.js +93 -0
- package/build/run-unit-tests-launcher.js +130 -49
- package/build/secret-redaction.js +33 -0
- package/build/session-upgrade-notice.js +86 -0
- package/build/setup-epic.js +108 -3
- package/build/start-tickets-prereqs.js +70 -16
- package/build/start-tickets.js +44 -5
- package/build/tool-call-advisory.js +162 -0
- package/build/tool-surface-gating.js +32 -57
- package/build/update-check.js +97 -16
- package/build/update-status.js +61 -71
- package/build/upgrade-advice.js +15 -11
- package/build/upgrade-cli.js +398 -17
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +3 -2
- package/build/worker-guard-hook-main.js +1 -1
- package/build/worktree-core.js +15 -2
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +30 -12
- package/docs/EPIC-IMPLEMENTER.md +68 -2
- package/package.json +3 -3
- package/pipelines/greenfield-setup.json +4 -128
- package/pipelines/implement-ticket.json +3 -2
package/build/doctor.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { formatGitCompatibilityDiagnostic } from "./start-tickets-prereqs.js";
|
|
2
|
+
import { constants as assetFsConstants } from "node:fs";
|
|
3
|
+
import { collectPackagedAssetDoctor } from "./generated-assets-doctor.js";
|
|
1
4
|
/**
|
|
2
5
|
* doctor — a strictly read-only, detect-and-instruct diagnostics subcommand for
|
|
3
6
|
* the packaged `start-tickets` automation (BAPI-305).
|
|
@@ -19,7 +22,7 @@
|
|
|
19
22
|
* reach a mutating lifecycle verb — `enable-linger`, `bootstrap`, `start` and
|
|
20
23
|
* their kin exist in this file only as remediation TEXT the operator runs.
|
|
21
24
|
*/
|
|
22
|
-
import { readdir, readFile, stat } from "fs/promises";
|
|
25
|
+
import { readdir, readFile, stat, lstat, access, realpath } from "fs/promises";
|
|
23
26
|
import { spawn } from "child_process";
|
|
24
27
|
import os from "os";
|
|
25
28
|
import path from "path";
|
|
@@ -33,6 +36,9 @@ import { modelRoutingPreferencesPath, readModelRoutingPreference } from "./model
|
|
|
33
36
|
import { inspectExecutorAgentAdapter, inspectionForFailedResolution, } from "./agent-launchers/executor-adapter-inspection.js";
|
|
34
37
|
import { resolveExecutorAgentAdapter } from "./agent-launchers/executor-adapter-registry.js";
|
|
35
38
|
import { parseDefaultOnEnvFlag } from "./env-flags.js";
|
|
39
|
+
import { checkForUpdate, isPlainReleaseVersion } from "./update-check.js";
|
|
40
|
+
import { getNoticeDecision, isUnattendedSession, UPDATE_CHECK_ENABLED_ENV, } from "./session-upgrade-notice.js";
|
|
41
|
+
import { formatUpdateAdvice } from "./upgrade-advice.js";
|
|
36
42
|
import { createBridgeApiUrls } from "./bridge-api-urls.js";
|
|
37
43
|
import { probeToolSurface } from "./tool-surface-gating.js";
|
|
38
44
|
import { readStoredBindingState, resolveBapiCredentials } from "./credential-store.js";
|
|
@@ -42,7 +48,7 @@ import { validateHttpBaseUrl } from "./base-url.js";
|
|
|
42
48
|
import { getLiveRepositoryConductors, getParseDispatcherHealth, } from "./epic-shared/bridge-client.js";
|
|
43
49
|
import { resolveRequiredStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
44
50
|
import { collectWorkerContainmentDiagnostic, formatWorkerContainmentDiagnosticReport, } from "./worker-containment-diagnostic.js";
|
|
45
|
-
import { BRIDGE_PACKAGE_NAME, describeLauncherReason, DUPLICATE_REGISTRATION_GUIDANCE, inspectLauncherConfigs, parseLauncherPin as parseSharedLauncherPin, } from "./launcher-config-inspection.js";
|
|
51
|
+
import { BRIDGE_PACKAGE_NAME, describeLauncherReason, DUPLICATE_REGISTRATION_GUIDANCE, inspectLauncherConfigs, isExactVersion, isPrerequisiteFailure, parseLauncherPin as parseSharedLauncherPin, } from "./launcher-config-inspection.js";
|
|
46
52
|
import { MCP_SERVER_NAME, MCP_PACKAGE_NAME, } from "./mcp-identity.js";
|
|
47
53
|
import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, } from "./agent-registry.js";
|
|
48
54
|
import { getDoctorPrereqDescriptors, probePrerequisite, } from "./start-tickets-prereqs.js";
|
|
@@ -54,6 +60,7 @@ import { validateBranchName } from "./base-ref.js";
|
|
|
54
60
|
import { readPlaneManifest } from "./plane/manifest.js";
|
|
55
61
|
import { validateTicketKey } from "./ticket-key-utils.js";
|
|
56
62
|
import { resolveBranchForTicket } from "./worktree-core.js";
|
|
63
|
+
import { BRIDGE_PERMISSIONS_MANIFEST_RELPATH, inspectBridgePermissions, } from "./bridge-permissions.js";
|
|
57
64
|
import { diagnoseStaleWorktreeBranches, formatStaleWorktreeDiagnosticReport, } from "./stale-worktree-doctor.js";
|
|
58
65
|
/**
|
|
59
66
|
* The report/usage title (BAPI-669, U9b). `doctor` diagnoses the whole Bridge
|
|
@@ -76,6 +83,7 @@ export function getDoctorUsage() {
|
|
|
76
83
|
`${DOCTOR_REPORT_TITLE}. It only checks your environment and prints manual`,
|
|
77
84
|
"install instructions — it does not install anything, modify your system, or",
|
|
78
85
|
"start the MCP server.",
|
|
86
|
+
"Git presence remains a prerequisite; Git-version compatibility is advisory-only.",
|
|
79
87
|
"",
|
|
80
88
|
"Flags:",
|
|
81
89
|
" --agent claude|cursor-agent|codex Agent to include in the prerequisite check (default: claude)",
|
|
@@ -159,6 +167,14 @@ export function getDoctorUsage() {
|
|
|
159
167
|
"It reads no install-state file, writes nothing, runs no launchctl/systemctl, and",
|
|
160
168
|
"is advisory: missing, malformed, or credential-less units never change the exit code.",
|
|
161
169
|
"",
|
|
170
|
+
"It also includes an advisory '/drive-epic-run permission rules' section",
|
|
171
|
+
"(BAPI-1398): whether the project permissions.allow rules and the optional user",
|
|
172
|
+
"autoMode entries (added through /drive-epic-run or the bridge-permissions",
|
|
173
|
+
"subcommand, never by install) are PRESENT, MISSING, or DRIFTED (for",
|
|
174
|
+
"example after the BAPI_MCP_CLI launcher changed). It reads .claude/settings.local.json,",
|
|
175
|
+
"~/.claude/settings.json, and .bridge/bridge-permissions.json, writes nothing,",
|
|
176
|
+
"and never changes the exit code.",
|
|
177
|
+
"",
|
|
162
178
|
"Conductor ledger / native-module diagnostics (the SQLite ledger's native",
|
|
163
179
|
"binding load status and Node-version skew) live under a separate command:",
|
|
164
180
|
" epic-implementer doctor",
|
|
@@ -363,6 +379,7 @@ export async function collectDoctorResults(deps, agentName) {
|
|
|
363
379
|
const injected = deps;
|
|
364
380
|
const probeDeps = {
|
|
365
381
|
...deps,
|
|
382
|
+
commandAssetMetadata: injected.commandAssetMetadata ?? { lstat, access: p => access(p, assetFsConstants.R_OK) },
|
|
366
383
|
readFile: injected.readFile ?? ((p) => readFile(p, "utf-8")),
|
|
367
384
|
stat: injected.stat ?? ((p) => stat(p)),
|
|
368
385
|
homedir: injected.homedir ?? os.homedir,
|
|
@@ -373,6 +390,82 @@ export async function collectDoctorResults(deps, agentName) {
|
|
|
373
390
|
}
|
|
374
391
|
return { ok: true, results };
|
|
375
392
|
}
|
|
393
|
+
/** The production checker, bound so it can neither read nor populate the cache. */
|
|
394
|
+
export function createDoctorVersionChecker(env) {
|
|
395
|
+
return () => checkForUpdate({ env, readCache: false, writeCache: false });
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Collect the version diagnostic. Never throws. The lookup is skipped when update
|
|
399
|
+
* checks are disabled or the session is unattended; a null, rejected, or malformed
|
|
400
|
+
* result is `unavailable`. Only a reliable fresh (or override) result reaches the
|
|
401
|
+
* shared notice decision, and only then are project pins evaluated.
|
|
402
|
+
*/
|
|
403
|
+
export async function collectVersionDiagnostic(deps) {
|
|
404
|
+
try {
|
|
405
|
+
if (!parseDefaultOnEnvFlag(deps.env[UPDATE_CHECK_ENABLED_ENV]) || isUnattendedSession(deps.env)) {
|
|
406
|
+
return { status: "not-checked" };
|
|
407
|
+
}
|
|
408
|
+
let result = null;
|
|
409
|
+
try {
|
|
410
|
+
result = await deps.checkLatest();
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
result = null;
|
|
414
|
+
}
|
|
415
|
+
if (!result || typeof result !== "object" || !isPlainReleaseVersion(result.latestVersion)) {
|
|
416
|
+
return { status: "unavailable" };
|
|
417
|
+
}
|
|
418
|
+
const latest = result.latestVersion;
|
|
419
|
+
const authoritative = { known: true, latest };
|
|
420
|
+
const upgrade = getNoticeDecision(deps.env, VERSION, authoritative).show;
|
|
421
|
+
const stalePins = [];
|
|
422
|
+
try {
|
|
423
|
+
const shared = await inspectLauncherConfigs({
|
|
424
|
+
cwd: deps.cwd,
|
|
425
|
+
targetVersion: VERSION,
|
|
426
|
+
readFile: deps.readFile,
|
|
427
|
+
});
|
|
428
|
+
for (const found of shared) {
|
|
429
|
+
if (!found.filePresent || !found.bridgeEntryPresent)
|
|
430
|
+
continue;
|
|
431
|
+
if (found.shape !== "standard" || isPrerequisiteFailure(found))
|
|
432
|
+
continue;
|
|
433
|
+
if (!isExactVersion(found.currentVersion))
|
|
434
|
+
continue;
|
|
435
|
+
const pin = found.currentVersion;
|
|
436
|
+
if (getNoticeDecision(deps.env, pin, authoritative).show) {
|
|
437
|
+
stalePins.push({ relPath: found.relPath, pin });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
// Pin diagnostics are advisory; the installed-runtime line still renders.
|
|
443
|
+
}
|
|
444
|
+
return { status: "known", latest, upgrade, stalePins };
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return { status: "unavailable" };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/** The `Bridge MCP version` lines, or none when no diagnostic was collected. */
|
|
451
|
+
export function formatDoctorVersionLines(diagnostic) {
|
|
452
|
+
if (!diagnostic)
|
|
453
|
+
return [];
|
|
454
|
+
if (diagnostic.status === "not-checked") {
|
|
455
|
+
return [`Bridge MCP version: installed ${VERSION}, latest: not checked`];
|
|
456
|
+
}
|
|
457
|
+
if (diagnostic.status === "unavailable") {
|
|
458
|
+
return [`Bridge MCP version: installed ${VERSION}, latest: unavailable`];
|
|
459
|
+
}
|
|
460
|
+
const lines = [`Bridge MCP version: installed ${VERSION}, latest ${diagnostic.latest}`];
|
|
461
|
+
if (diagnostic.upgrade)
|
|
462
|
+
lines.push(` ${formatUpdateAdvice(VERSION, diagnostic.latest)}`);
|
|
463
|
+
// Stale project pins are subordinate to the runtime line above.
|
|
464
|
+
for (const { relPath, pin } of diagnostic.stalePins) {
|
|
465
|
+
lines.push(` ${relPath} pin: ${formatUpdateAdvice(pin, diagnostic.latest)}`);
|
|
466
|
+
}
|
|
467
|
+
return lines;
|
|
468
|
+
}
|
|
376
469
|
/**
|
|
377
470
|
* The BROAD report header: title plus the run's platform / agent / profile
|
|
378
471
|
* metadata (BAPI-669, U9b). Split out from the prerequisite body so `runDoctorCli`
|
|
@@ -380,7 +473,10 @@ export async function collectDoctorResults(deps, agentName) {
|
|
|
380
473
|
* ordering change is the whole point, and it must not require rewriting the
|
|
381
474
|
* prerequisite formatting below.
|
|
382
475
|
*/
|
|
383
|
-
export function buildDoctorReportHeader(platform, agent
|
|
476
|
+
export function buildDoctorReportHeader(platform, agent,
|
|
477
|
+
// A DEFAULT value, not a plain optional marker, so the function's declared arity
|
|
478
|
+
// stays two: the header takes no dependency seam and does no I/O.
|
|
479
|
+
versionDiagnostic = undefined) {
|
|
384
480
|
const activeGroups = Array.from(resolveProfiles(process.env.BRIDGE_MCP_PROFILE)).join(", ");
|
|
385
481
|
return [
|
|
386
482
|
DOCTOR_REPORT_TITLE,
|
|
@@ -391,6 +487,7 @@ export function buildDoctorReportHeader(platform, agent) {
|
|
|
391
487
|
// never blank and never omitted. Neither line feeds launcher-pin drift,
|
|
392
488
|
// which stays a `VERSION`-only comparison.
|
|
393
489
|
`Package version: ${VERSION}`,
|
|
490
|
+
...formatDoctorVersionLines(versionDiagnostic),
|
|
394
491
|
`Build commit: ${BUILD_COMMIT}`,
|
|
395
492
|
`Platform: ${platform}`,
|
|
396
493
|
`Selected agent: ${agent.name} (command: ${agent.command})`,
|
|
@@ -412,6 +509,10 @@ export function formatDoctorPrereqSection(platform, collection) {
|
|
|
412
509
|
return lines.join("\n");
|
|
413
510
|
}
|
|
414
511
|
for (const result of collection.results) {
|
|
512
|
+
if (result.gitCompatibility) {
|
|
513
|
+
lines.push(formatGitCompatibilityDiagnostic(result.gitCompatibility));
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
415
516
|
const status = result.found ? "FOUND " : "MISSING";
|
|
416
517
|
// Missing-detail stays suppressed for every prerequisite EXCEPT one that has
|
|
417
518
|
// been explicitly audited (BAPI-727, `renderDetailWhenMissing`). The general
|
|
@@ -1679,6 +1780,55 @@ export function formatOperationalAdvisoryReport(diagnostic) {
|
|
|
1679
1780
|
lines.push(" runs NO command probe, repairs no scheduler, and never changes the exit code.");
|
|
1680
1781
|
return lines.join("\n");
|
|
1681
1782
|
}
|
|
1783
|
+
/** Collect the permission-rules inspection. Reads only; never writes. */
|
|
1784
|
+
export async function collectBridgePermissionsDiagnostic(deps) {
|
|
1785
|
+
return inspectBridgePermissions({ cwd: deps.cwd, homeDir: deps.homeDir, env: deps.env }, { readFile: deps.readFile });
|
|
1786
|
+
}
|
|
1787
|
+
function formatPermissionScope(label, scope, optionalNote) {
|
|
1788
|
+
switch (scope.state) {
|
|
1789
|
+
case "present":
|
|
1790
|
+
return [`PRESENT ${label} — all ${scope.count} Bridge entries in ${scope.path}`];
|
|
1791
|
+
case "missing":
|
|
1792
|
+
return [
|
|
1793
|
+
`MISSING ${label} — ${scope.missing.length} Bridge entr${scope.missing.length === 1 ? "y is" : "ies are"} absent from ${scope.path}`,
|
|
1794
|
+
...(optionalNote ? [` ${optionalNote}`] : []),
|
|
1795
|
+
];
|
|
1796
|
+
case "drifted":
|
|
1797
|
+
return [
|
|
1798
|
+
`DRIFTED ${label} — ${scope.reason} (${scope.path})`,
|
|
1799
|
+
...(scope.missing.length > 0 ? [` ${scope.missing.length} current rule(s) missing.`] : []),
|
|
1800
|
+
...(scope.stale.length > 0 ? [` ${scope.stale.length} rule(s) Bridge wrote earlier no longer match.`] : []),
|
|
1801
|
+
];
|
|
1802
|
+
case "malformed":
|
|
1803
|
+
return [`MALFORMED ${label} — ${scope.path} is not valid JSON settings; doctor did not change it.`];
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
/** Render the permission-rules section (pure formatting, secret-free). */
|
|
1807
|
+
export function formatBridgePermissionsDiagnosticReport(inspection) {
|
|
1808
|
+
const lines = ["", "/drive-epic-run permission rules (auto mode)", ""];
|
|
1809
|
+
if (inspection.launcher.ok) {
|
|
1810
|
+
lines.push(`Launcher prefix: ${inspection.launcher.prefix}`);
|
|
1811
|
+
if (inspection.recordedLauncher && inspection.recordedLauncher !== inspection.launcher.prefix) {
|
|
1812
|
+
lines.push(` Rules were written for a different launcher: ${inspection.recordedLauncher}`);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
if (inspection.project.state === "launcher-refused") {
|
|
1816
|
+
lines.push("REFUSED project permissions.allow — the launcher override cannot be matched by an exact rule.");
|
|
1817
|
+
if (!inspection.launcher.ok)
|
|
1818
|
+
lines.push(` ${inspection.launcher.reason}.`);
|
|
1819
|
+
}
|
|
1820
|
+
else {
|
|
1821
|
+
lines.push(...formatPermissionScope("project permissions.allow", inspection.project));
|
|
1822
|
+
}
|
|
1823
|
+
lines.push(...formatPermissionScope("user autoMode entries", inspection.user, `Optional: /drive-epic-run offers to add them, or run \`npx -y ${MCP_PACKAGE_NAME} bridge-permissions\`.`));
|
|
1824
|
+
const healthy = inspection.project.state === "present" && inspection.user.state === "present";
|
|
1825
|
+
if (!healthy) {
|
|
1826
|
+
lines.push(` To repair the project rules: re-run \`npx -y ${MCP_PACKAGE_NAME} install\` in a terminal (it lists every rule it writes).`);
|
|
1827
|
+
lines.push(` To remove Bridge's entries: \`npx -y ${MCP_PACKAGE_NAME} install --remove-bridge-permissions\`.`);
|
|
1828
|
+
}
|
|
1829
|
+
lines.push(` Bridge's ownership record: ${BRIDGE_PERMISSIONS_MANIFEST_RELPATH}. Advisory only; never changes the exit code.`);
|
|
1830
|
+
return lines.join("\n");
|
|
1831
|
+
}
|
|
1682
1832
|
/**
|
|
1683
1833
|
* Read-only inventory of this host's epic-launch profiles and whether each
|
|
1684
1834
|
* one's credential binding matches its target. It reports; it never binds or
|
|
@@ -1819,7 +1969,26 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
1819
1969
|
// always-first is both simpler and equivalent. The `installStatus: false` override
|
|
1820
1970
|
// remains a dependency-injection-only seam for hermetic tests; a normal CLI
|
|
1821
1971
|
// invocation never takes it.
|
|
1822
|
-
|
|
1972
|
+
// BAPI-1446: the version diagnostic is advisory and read-only (fresh lookup, no
|
|
1973
|
+
// cache read or write, no spawn). A failure leaves the header exactly as it was.
|
|
1974
|
+
let versionDiagnostic;
|
|
1975
|
+
if (overrides.version !== false && (overrides.deps === undefined || overrides.version?.checkLatest)) {
|
|
1976
|
+
try {
|
|
1977
|
+
const versionEnv = overrides.version?.env ?? deps.env;
|
|
1978
|
+
versionDiagnostic = await collectVersionDiagnostic({
|
|
1979
|
+
env: versionEnv,
|
|
1980
|
+
cwd: overrides.version?.cwd ?? overrides.launcherProbe?.cwd ?? deps.cwd,
|
|
1981
|
+
readFile: overrides.version?.readFile ??
|
|
1982
|
+
overrides.launcherProbe?.readFile ??
|
|
1983
|
+
((p) => readFile(p, "utf-8")),
|
|
1984
|
+
checkLatest: overrides.version?.checkLatest ?? createDoctorVersionChecker(versionEnv),
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
catch {
|
|
1988
|
+
versionDiagnostic = undefined;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
log(buildDoctorReportHeader(deps.platform, agent, versionDiagnostic).join("\n"));
|
|
1823
1992
|
// Advisory install-status section (easy-install done criteria). Read-only GETs
|
|
1824
1993
|
// only; any failure degrades to WARN/SKIP lines inside the section, and any
|
|
1825
1994
|
// unexpected throw renders the sanitized fallback rather than dropping the
|
|
@@ -1886,6 +2055,13 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
1886
2055
|
const preference = await readModelRoutingPreference(deps.env.BAPI_REPO_NAME ?? "_doctor_unscoped_", "review_ticket", preferencePath);
|
|
1887
2056
|
log(`Personal model routing preference: ${preference.warning ?? preference.source}.`);
|
|
1888
2057
|
log(formatDoctorPrereqSection(deps.platform, collection));
|
|
2058
|
+
if (overrides.packagedAssets !== false) {
|
|
2059
|
+
const injected = overrides.packagedAssets ?? {};
|
|
2060
|
+
log(await collectPackagedAssetDoctor({ cwd: injected.cwd ?? deps.cwd,
|
|
2061
|
+
readFile: injected.readFile ?? deps.readFile ?? (p => readFile(p, "utf-8")),
|
|
2062
|
+
lstat: injected.lstat ?? lstat, access: injected.access ?? (p => access(p, assetFsConstants.R_OK)), realpath: injected.realpath ?? realpath,
|
|
2063
|
+
platform: deps.platform }));
|
|
2064
|
+
}
|
|
1889
2065
|
// Executor-adapter section (BAPI-781). Advisory with respect to the EXIT CODE
|
|
1890
2066
|
// — an unusable adapter is reported loudly but does not change doctor's
|
|
1891
2067
|
// pass/fail, which stays driven by the start-tickets prerequisites this
|
|
@@ -1972,6 +2148,24 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
1972
2148
|
/* local-launcher diagnostics are advisory; never block the doctor report */
|
|
1973
2149
|
}
|
|
1974
2150
|
}
|
|
2151
|
+
// BAPI-1398 /drive-epic-run permission-rules section. Strictly read-only (two
|
|
2152
|
+
// settings files and Bridge's manifest; no command, no network), and advisory:
|
|
2153
|
+
// it never changes the exit code.
|
|
2154
|
+
if (overrides.bridgePermissions !== false) {
|
|
2155
|
+
try {
|
|
2156
|
+
const injected = deps;
|
|
2157
|
+
const permissionDeps = {
|
|
2158
|
+
cwd: overrides.bridgePermissions?.cwd ?? deps.cwd,
|
|
2159
|
+
homeDir: overrides.bridgePermissions?.homeDir ?? (injected.homedir ?? os.homedir)(),
|
|
2160
|
+
env: overrides.bridgePermissions?.env ?? deps.env,
|
|
2161
|
+
readFile: overrides.bridgePermissions?.readFile ?? injected.readFile ?? ((p) => readFile(p, "utf-8")),
|
|
2162
|
+
};
|
|
2163
|
+
log(formatBridgePermissionsDiagnosticReport(await collectBridgePermissionsDiagnostic(permissionDeps)));
|
|
2164
|
+
}
|
|
2165
|
+
catch {
|
|
2166
|
+
/* permission-rule diagnostics are advisory; never block the doctor report */
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
1975
2169
|
// Worker containment advisory section (BAPI-1019/A.7). Placed beside the other
|
|
1976
2170
|
// advisory sections and, uniquely, issuing NO probe of any kind — no command,
|
|
1977
2171
|
// no file read, no network call. That is what keeps doctor's "an unsupported
|
package/build/drive-epic.js
CHANGED
|
@@ -55,13 +55,14 @@
|
|
|
55
55
|
*/
|
|
56
56
|
import { readdir, readFile } from "node:fs/promises";
|
|
57
57
|
import path, { dirname, join } from "node:path";
|
|
58
|
-
import { CONDUCTOR_DEFAULT_BASE_URL, ConductorBridgeApiError, resolveEpicLaunchBridgeApiAccess, fetchConductorReadiness, fetchEpicRunState, safeDiagnosticMessage, } from "./conductor/bridge-api-client.js";
|
|
58
|
+
import { CONDUCTOR_DEFAULT_BASE_URL, ConductorBridgeApiError, resolveEpicLaunchBridgeApiAccess, fetchConductorReadiness, fetchEpicRunState, gateProfileWaivesCiWorkflow, safeDiagnosticMessage, } from "./conductor/bridge-api-client.js";
|
|
59
59
|
import { EPIC_LAUNCH_PROFILE_NAME_PATTERN, epicLaunchPolicyDigest, pinEpicLaunchPolicy, renderEpicLaunchCommand, resolveEpicLaunchSelection, shellQuote, } from "./epic-launch-profile.js";
|
|
60
60
|
import { validateHttpBaseUrl, describeBaseUrlRejection } from "./base-url.js";
|
|
61
61
|
import { BRING_UP_REFUSAL_MESSAGES, BRING_UP_RUNBOOK, firstBringUpRefusal, } from "./conductor/bring-up-facts.js";
|
|
62
62
|
import { repositoryWorkflowRemedy } from "./conductor/repository-workflow-remedy.js";
|
|
63
|
-
import { policyDeclaresNotifyWebhook, effectivePolicyFacts, readSetupEpicPolicyFile, runSetupEpicWorkflow, } from "./setup-epic.js";
|
|
63
|
+
import { policyDeclaresNotifyWebhook, policyNotifyDestinationsConflict, notifyDestinationsConflictMessage, effectivePolicyFacts, readSetupEpicPolicyFile, runSetupEpicWorkflow, } from "./setup-epic.js";
|
|
64
64
|
import { EPIC_KEY_PATTERN } from "./drive-epic-key.js";
|
|
65
|
+
import { flushProcessStdout, writeJsonDocument } from "./conductor/json-output.js";
|
|
65
66
|
import { discoverEpicPlan, } from "./plan-discovery.js";
|
|
66
67
|
import { formatPlaneDiagnostic, launchPlaneControlPlane, requestPlaneScopedLanes, runDefaultPlanePreflight, runPlaneCli, } from "./plane/cli.js";
|
|
67
68
|
import { PLANE_PORT_PROBE_TIMEOUT_MS, describePlanePortOccupant, } from "./plane/preflight.js";
|
|
@@ -183,7 +184,14 @@ export const V2_READINESS_REQUIREMENTS = [
|
|
|
183
184
|
// fixed install-command sentence that stood here could never clear a missing
|
|
184
185
|
// migration-guard job — no install step creates one — so it is gone from this
|
|
185
186
|
// refusal.
|
|
186
|
-
|
|
187
|
+
//
|
|
188
|
+
// BAPI-1390: the server decides which files are required (the review file
|
|
189
|
+
// only for a `verdict_protocol` run; the CI lane file only for a repository
|
|
190
|
+
// with no STORED gate profile and no `no_ci_gate`), and `satisfied` reads that
|
|
191
|
+
// decision as given. The remedy waives the CI file the same way, so a stored
|
|
192
|
+
// profile is never told to add a file it does not need. Gate-profile WARNINGS
|
|
193
|
+
// (epic-branch rows) are not requirements and are never read here.
|
|
194
|
+
remedyFor: (r) => repositoryWorkflowRemedy(gateProfileWaivesCiWorkflow(r) ? { ...r, conductor_ci_workflow: null } : r),
|
|
187
195
|
satisfied: (r) => r.unattended?.repository_readiness_confirmed === true,
|
|
188
196
|
},
|
|
189
197
|
// BAPI-1135 — merge-execution eligibility is deliberately NOT a requirement here.
|
|
@@ -1242,6 +1250,7 @@ export function createDefaultDriveEpicDeps() {
|
|
|
1242
1250
|
// construction, so nothing has claimed stdout for the protocol transport.
|
|
1243
1251
|
log: (message) => console.log(message),
|
|
1244
1252
|
errorLog: (message) => console.error(message),
|
|
1253
|
+
flushStdout: flushProcessStdout,
|
|
1245
1254
|
};
|
|
1246
1255
|
}
|
|
1247
1256
|
/**
|
|
@@ -1787,6 +1796,21 @@ async function checkPlaneAdmission(ctx, readiness, manifest) {
|
|
|
1787
1796
|
*/
|
|
1788
1797
|
async function coldStartPlane(ctx) {
|
|
1789
1798
|
const { deps, epicKey } = ctx;
|
|
1799
|
+
// BAPI-1397 (F1): a policy the server would reject for its notify destinations
|
|
1800
|
+
// is refused BEFORE the plane preflight and `plane up`, naming its file. The
|
|
1801
|
+
// same predicate setup-epic applies; a cold start has no live run, so the
|
|
1802
|
+
// selected file is the explicit one or the profile's, as in
|
|
1803
|
+
// `prepareSetupSelection`. An unreadable file is left to that function.
|
|
1804
|
+
const coldPolicyPath = ctx.options.policyFile ?? ctx.profile?.policyRef?.path;
|
|
1805
|
+
if (coldPolicyPath !== undefined) {
|
|
1806
|
+
const read = await readSetupEpicPolicyFile(coldPolicyPath, (filePath) => deps.readPolicyFile(filePath));
|
|
1807
|
+
if (read.ok && policyNotifyDestinationsConflict(read.policy)) {
|
|
1808
|
+
return refuse(deps, [
|
|
1809
|
+
`${epicKey}: REFUSED (policy-notify-conflict) — ` +
|
|
1810
|
+
notifyDestinationsConflictMessage(coldPolicyPath, ctx.options.policyFile !== undefined ? "explicit" : "profile"),
|
|
1811
|
+
]);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1790
1814
|
// The cold start leaves the readiness `catch` before the ordinary advisory
|
|
1791
1815
|
// read is reached, so it surfaces the BAPI-1022 containment advisory itself —
|
|
1792
1816
|
// before anything is started, exactly as every other plan-file route does.
|
|
@@ -2656,7 +2680,11 @@ async function runDriveEpicPreview(deps, options) {
|
|
|
2656
2680
|
});
|
|
2657
2681
|
}
|
|
2658
2682
|
ensureRefusalExplained(preview);
|
|
2659
|
-
emitPreview(deps, options, preview);
|
|
2683
|
+
const emitted = await emitPreview(deps, options, preview);
|
|
2684
|
+
// BAPI-1397 (F3): a `--json` document that could not reach stdout whole is a
|
|
2685
|
+
// failed preview, never a silently truncated one.
|
|
2686
|
+
if (!emitted)
|
|
2687
|
+
return 1;
|
|
2660
2688
|
return preview.decision === "create" || preview.decision === "attach" ? 0 : 1;
|
|
2661
2689
|
}
|
|
2662
2690
|
/** The fixed explanation used when a refusal reached the end with none of its own. */
|
|
@@ -2986,11 +3014,19 @@ async function previewExecutorFacts(ctx, readiness, planeTarget, live, outcome)
|
|
|
2986
3014
|
? null
|
|
2987
3015
|
: executorPreconditionRefusal(epicKey, inspected.message, outcome === "No run was created" ? "No run was created." : "No run was changed.");
|
|
2988
3016
|
}
|
|
2989
|
-
/**
|
|
2990
|
-
|
|
3017
|
+
/**
|
|
3018
|
+
* Print the preview: one JSON line with `--json`, short readable lines otherwise.
|
|
3019
|
+
*
|
|
3020
|
+
* BAPI-1397 (F3): the `--json` document goes through the shared CLI writer, which
|
|
3021
|
+
* waits for a piped stdout to drain before the exit code is returned. Resolves
|
|
3022
|
+
* `false` only when that document could not be written whole.
|
|
3023
|
+
*/
|
|
3024
|
+
async function emitPreview(deps, options, preview) {
|
|
2991
3025
|
if (options.json === true) {
|
|
2992
|
-
|
|
2993
|
-
|
|
3026
|
+
return writeJsonDocument(JSON.stringify(preview), {
|
|
3027
|
+
log: deps.log,
|
|
3028
|
+
flush: deps.flushStdout ?? (async () => true),
|
|
3029
|
+
});
|
|
2994
3030
|
}
|
|
2995
3031
|
const lines = [
|
|
2996
3032
|
`${preview.epic_key}: launch preview (read-only; nothing was created or started)`,
|
|
@@ -3018,4 +3054,5 @@ function emitPreview(deps, options, preview) {
|
|
|
3018
3054
|
lines.push(` Refusal: ${preview.refusal.reason_id} — ${preview.refusal.remedy}`);
|
|
3019
3055
|
}
|
|
3020
3056
|
deps.log(lines.join("\n"));
|
|
3057
|
+
return true;
|
|
3021
3058
|
}
|
|
@@ -14,7 +14,7 @@ export function memoryRepo(files, options = {}) {
|
|
|
14
14
|
const contents = new Map();
|
|
15
15
|
for (const [rel, body] of Object.entries(files))
|
|
16
16
|
contents.set(path.join(ROOT, rel), body);
|
|
17
|
-
const dirs = new Set([path.
|
|
17
|
+
const dirs = new Set([path.parse(ROOT).root, ROOT]);
|
|
18
18
|
const addDirs = (abs) => {
|
|
19
19
|
let dir = path.dirname(abs);
|
|
20
20
|
while (!dirs.has(dir)) {
|
|
@@ -33,8 +33,9 @@ export function memoryRepo(files, options = {}) {
|
|
|
33
33
|
const repo = { fs: undefined, files: contents, writes: [], reads: [], realpaths: [] };
|
|
34
34
|
const missing = (p) => Object.assign(new Error(`ENOENT: ${p}`), { code: "ENOENT" });
|
|
35
35
|
const resolveLinks = (p) => {
|
|
36
|
-
const
|
|
37
|
-
|
|
36
|
+
const root = path.parse(p).root;
|
|
37
|
+
const parts = p.slice(root.length).split(path.sep).filter(Boolean);
|
|
38
|
+
let current = root;
|
|
38
39
|
for (const part of parts) {
|
|
39
40
|
current = path.join(current, part);
|
|
40
41
|
const target = symlinks.get(current);
|