@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.
- package/README.md +66 -15
- package/build/agent-launchers/claude-executor-adapter.js +145 -20
- package/build/agent-launchers/executor-adapter.js +81 -2
- package/build/agents.generated.js +10 -1
- package/build/bounded-wait.js +32 -1
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +7 -6
- package/build/conductor/bridge-api-client.js +565 -32
- package/build/conductor/bring-up-facts.js +3 -1
- package/build/conductor/doctor.js +23 -14
- package/build/conductor/epic-child-continuation.js +351 -0
- package/build/conductor/epic-implementer-cli.js +154 -17
- package/build/conductor/git-hooks.js +529 -118
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/hook-runtime-qualification.js +340 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness-sections.js +5 -4
- package/build/conductor/readiness.js +45 -12
- package/build/conductor/recovery-cli.js +859 -27
- package/build/conductor/recovery-operations.js +574 -5
- package/build/conductor/repair-contract.js +69 -3
- package/build/conductor-bundle-artifacts.js +1 -0
- package/build/credential-store.js +52 -13
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +3 -2
- package/build/doctor.js +147 -21
- package/build/drive-epic-key.js +12 -0
- package/build/drive-epic.js +1537 -125
- package/build/epic-coherence-input-cli.js +137 -0
- package/build/epic-coherence-input.js +920 -0
- package/build/epic-coherence-input.test-fixtures.js +175 -0
- package/build/epic-implementer-bin.js +41 -33
- package/build/epic-implementer-bundle-cli.js +14 -1
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +58 -19
- package/build/epic-run-status.js +5 -0
- package/build/epic-shared/bridge-client.js +46 -4
- package/build/executor/cli.js +39 -0
- package/build/executor/completion-contract.js +423 -0
- package/build/executor/completion-supervision.js +291 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/credentials.js +3 -1
- package/build/executor/deps.js +65 -1
- package/build/executor/env.js +12 -0
- package/build/executor/heartbeat.js +13 -3
- package/build/executor/http-client.js +91 -4
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +61 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +1550 -55
- package/build/executor/job-types.js +9 -6
- package/build/executor/preflight.js +189 -1
- package/build/executor/process-containment.js +756 -0
- package/build/executor/process.js +235 -5
- package/build/executor/prompt-spec.js +366 -1
- package/build/executor/provider-checks.js +69 -0
- package/build/executor/rebase-finalization.js +351 -0
- package/build/executor/reconcile-overlap-job.js +77 -84
- package/build/executor/required-verification.js +313 -0
- package/build/executor/required-work-ledger.js +1121 -0
- package/build/executor/results.js +142 -0
- package/build/executor/resume-pre-spawn.js +87 -4
- package/build/executor/runner.js +168 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/test-declaration.js +20 -0
- package/build/executor/types.js +13 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +70 -34
- package/build/executor/worker-finalization.js +168 -26
- package/build/executor/worker-guard-hook.js +75 -14
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/executor/worktree.js +13 -0
- package/build/executor-launch.js +658 -0
- package/build/index.js +594 -1152
- package/build/init.js +107 -4
- package/build/install-bridge-conductor.js +231 -41
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/merge-pull-request.js +1 -1
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +9 -8
- package/build/plan-discovery.js +137 -0
- package/build/plan-epic-implementer-eligibility.js +2 -2
- package/build/plane/cli.js +37 -3
- package/build/plane/member-logs.js +3 -0
- package/build/plane/member-roster.js +7 -1
- package/build/plane/shutdown.js +59 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +146 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/scripts/executor-protocol-contract-driver.js +13 -3
- package/build/setup-epic.js +208 -125
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +5 -4
- package/build/worktree-core.js +6 -5
- package/docs/CONDUCTOR.md +116 -2
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +2 -1
- package/pipelines/implement-ticket.json +1 -1
package/build/doctor.js
CHANGED
|
@@ -24,8 +24,9 @@ import { spawn } from "child_process";
|
|
|
24
24
|
import os from "os";
|
|
25
25
|
import path from "path";
|
|
26
26
|
import { createDefaultStartTicketsDeps } from "./start-tickets.js";
|
|
27
|
+
import { defaultNpmEntryFileExists, resolveNpmEntry } from "./windows-npm-entry.js";
|
|
27
28
|
import { VERSION, BUILD_COMMIT } from "./version.generated.js";
|
|
28
|
-
import { collectInstallStatusChecks, formatInstallStatusReport, formatInstallStatusFallbackReport, resolveInstallDoctorTarget, } from "./install-doctor.js";
|
|
29
|
+
import { collectInstallStatusChecks, collectProjectIdentityAndRoot, formatInstallStatusReport, formatInstallStatusFallbackReport, formatProjectIdentityAndRootFallbackReport, formatProjectIdentityAndRootReport, resolveInstallDoctorTarget, } from "./install-doctor.js";
|
|
29
30
|
import { codexHome, inspectBridgeCodexProfile } from "./codex-bridge-profile.js";
|
|
30
31
|
import { resolveRoutingHost } from "./model-routing-host.js";
|
|
31
32
|
import { modelRoutingPreferencesPath, readModelRoutingPreference } from "./model-routing-preferences.js";
|
|
@@ -34,9 +35,10 @@ import { resolveExecutorAgentAdapter } from "./agent-launchers/executor-adapter-
|
|
|
34
35
|
import { parseDefaultOnEnvFlag } from "./env-flags.js";
|
|
35
36
|
import { createBridgeApiUrls } from "./bridge-api-urls.js";
|
|
36
37
|
import { probeToolSurface } from "./tool-surface-gating.js";
|
|
37
|
-
import {
|
|
38
|
-
import { listEpicLaunchProfiles } from "./epic-launch-profile.js";
|
|
39
|
-
import { resolveConductorBridgeApiAccess } from "./conductor/bridge-api-client.js";
|
|
38
|
+
import { readStoredBindingState, resolveBapiCredentials } from "./credential-store.js";
|
|
39
|
+
import { EPIC_LAUNCH_PROFILE_NAME_PATTERN, listEpicLaunchProfiles } from "./epic-launch-profile.js";
|
|
40
|
+
import { CONDUCTOR_DEFAULT_BASE_URL, resolveConductorBridgeApiAccess } from "./conductor/bridge-api-client.js";
|
|
41
|
+
import { validateHttpBaseUrl } from "./base-url.js";
|
|
40
42
|
import { getLiveRepositoryConductors, getParseDispatcherHealth, } from "./epic-shared/bridge-client.js";
|
|
41
43
|
import { resolveRequiredStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
42
44
|
import { collectWorkerContainmentDiagnostic, formatWorkerContainmentDiagnosticReport, } from "./worker-containment-diagnostic.js";
|
|
@@ -87,6 +89,12 @@ export function getDoctorUsage() {
|
|
|
87
89
|
"bootstrap-field completeness, integration credentials, and repository-indexing",
|
|
88
90
|
"state. It performs read-only GETs only and never affects the exit code.",
|
|
89
91
|
"",
|
|
92
|
+
"Next comes an advisory 'Project identity and root' section (BAPI-1318): whether",
|
|
93
|
+
"each BAPI_PROJECT_ROOT (from the environment and the project MCP configs) names",
|
|
94
|
+
"a real project folder the server will use, and whether .bridge/config's",
|
|
95
|
+
"repo_name matches each project MCP config's BAPI_REPO_NAME. It only reads",
|
|
96
|
+
"files and never affects the exit code.",
|
|
97
|
+
"",
|
|
90
98
|
"After that come the start-tickets prerequisite checks (for the current OS):",
|
|
91
99
|
"the start-tickets preflight prerequisites plus",
|
|
92
100
|
"uv, the selected agent's command, Bridge API credential resolution, and",
|
|
@@ -540,10 +548,25 @@ export function parseLauncherPin(args) {
|
|
|
540
548
|
* spawn error, or timeout is classified `indeterminate` (never silently "warm").
|
|
541
549
|
* `--no-install` guarantees the probe never downloads/warms the bucket.
|
|
542
550
|
*/
|
|
543
|
-
function probeNpxNoInstallDefault(spec) {
|
|
551
|
+
export function probeNpxNoInstallDefault(spec, options = {}) {
|
|
552
|
+
const doSpawn = options.spawn ?? spawn;
|
|
544
553
|
return new Promise((resolve) => {
|
|
554
|
+
// WINDOWS (BAPI-1319): a bare `npx` is ENOENT without a shell, so the probe
|
|
555
|
+
// always read "indeterminate". Map it to npm's `npx-cli.js` through Node;
|
|
556
|
+
// `--no-install` is kept, so doctor still can never warm the cache. Other
|
|
557
|
+
// platforms pass through unchanged.
|
|
558
|
+
const resolved = resolveNpmEntry("npx", ["--no-install", spec, "--version"], {
|
|
559
|
+
platform: options.platform ?? process.platform,
|
|
560
|
+
env: options.env ?? process.env,
|
|
561
|
+
execPath: options.execPath ?? process.execPath,
|
|
562
|
+
fileExists: options.fileExists ?? defaultNpmEntryFileExists,
|
|
563
|
+
});
|
|
564
|
+
if (resolved.status !== "resolved") {
|
|
565
|
+
resolve({ warmed: false, indeterminate: true });
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
545
568
|
try {
|
|
546
|
-
const child =
|
|
569
|
+
const child = doSpawn(resolved.command, resolved.args, {
|
|
547
570
|
shell: false,
|
|
548
571
|
stdio: "ignore",
|
|
549
572
|
timeout: 30_000,
|
|
@@ -1559,6 +1582,47 @@ export async function collectOperationalAdvisories(deps) {
|
|
|
1559
1582
|
legacyDriveHelpers,
|
|
1560
1583
|
};
|
|
1561
1584
|
}
|
|
1585
|
+
/**
|
|
1586
|
+
* BAPI-1299 — the "Automation queue" advisory line, from the `automation_queue`
|
|
1587
|
+
* block of the same health GET. Queued reviews, plans, and docs wait for the
|
|
1588
|
+
* background worker; this line says whether they are being drained.
|
|
1589
|
+
*
|
|
1590
|
+
* Only an `undrained` verdict WARNs. An old queue with a fresh running-job
|
|
1591
|
+
* heartbeat is a busy worker, reported OK with its backlog. Anything the server
|
|
1592
|
+
* did not positively observe — no credential, a failed read, an older server
|
|
1593
|
+
* with no block, an unreadable block — is SKIP, never OK. Advisory only.
|
|
1594
|
+
*/
|
|
1595
|
+
export function formatAutomationQueueAdvisory(dispatcher) {
|
|
1596
|
+
if (dispatcher === null) {
|
|
1597
|
+
return " SKIP Automation queue: not checked (no Bridge credential resolved).";
|
|
1598
|
+
}
|
|
1599
|
+
const queue = dispatcher.automationQueue ?? null;
|
|
1600
|
+
if (queue === null) {
|
|
1601
|
+
return (" SKIP Automation queue: unavailable — the health read carried no queue block\n" +
|
|
1602
|
+
" (the read failed, or the server predates this check).");
|
|
1603
|
+
}
|
|
1604
|
+
if (queue.status === "unreadable" || queue.verdict === "unknown") {
|
|
1605
|
+
return (" SKIP Automation queue: unreadable — the server could not read the queue.\n" +
|
|
1606
|
+
" This is not evidence that the queue is stuck.");
|
|
1607
|
+
}
|
|
1608
|
+
const count = queue.queuedCount ?? 0;
|
|
1609
|
+
const age = queue.oldestQueuedAgeSeconds;
|
|
1610
|
+
const backlog = age === null ? `${count} queued` : `${count} queued, oldest waiting ${age}s`;
|
|
1611
|
+
if (queue.verdict === "undrained") {
|
|
1612
|
+
return (` WARN Automation queue: ${backlog}, and no running job shows a fresh heartbeat.\n` +
|
|
1613
|
+
" Queued reviews, plans, and docs wait for the background worker. Check that\n" +
|
|
1614
|
+
" `python worker.py` (the `worker:` dyno) is running and DISABLE_SCHEDULER is\n" +
|
|
1615
|
+
" not true on it. The jobs have not failed; they will run once it drains.");
|
|
1616
|
+
}
|
|
1617
|
+
if (queue.verdict === "working_backlog") {
|
|
1618
|
+
return (` OK Automation queue: ${backlog} — the worker is busy (running jobs are\n` +
|
|
1619
|
+
" heartbeating), so this is a working backlog, not a stopped worker.");
|
|
1620
|
+
}
|
|
1621
|
+
if (count === 0) {
|
|
1622
|
+
return " OK Automation queue: nothing queued.";
|
|
1623
|
+
}
|
|
1624
|
+
return ` OK Automation queue: ${backlog} — within the normal wait.`;
|
|
1625
|
+
}
|
|
1562
1626
|
/** Render the operational-advisories section. Advisory: never changes the exit code. */
|
|
1563
1627
|
export function formatOperationalAdvisoryReport(diagnostic) {
|
|
1564
1628
|
const lines = ["", "operational advisories (advisory)"];
|
|
@@ -1577,6 +1641,7 @@ export function formatOperationalAdvisoryReport(diagnostic) {
|
|
|
1577
1641
|
lines.push(" SKIP Parse dispatcher: unavailable — the liveness read produced no verdict.\n" +
|
|
1578
1642
|
" This is not evidence that no dispatcher is running.");
|
|
1579
1643
|
}
|
|
1644
|
+
lines.push(formatAutomationQueueAdvisory(dispatcher));
|
|
1580
1645
|
if (diagnostic.conductorObservation === "unavailable") {
|
|
1581
1646
|
lines.push(" SKIP Concurrent conductors: unavailable — the repository-scoped read produced\n" +
|
|
1582
1647
|
" no verdict.");
|
|
@@ -1620,12 +1685,20 @@ export function formatOperationalAdvisoryReport(diagnostic) {
|
|
|
1620
1685
|
* migrates anything.
|
|
1621
1686
|
*/
|
|
1622
1687
|
export async function collectEpicLaunchProfileDiagnostic(deps) {
|
|
1688
|
+
const selectedName = deps.env.BAPI_EPIC_LAUNCH_PROFILE?.trim();
|
|
1689
|
+
const selected = selectedName
|
|
1690
|
+
? EPIC_LAUNCH_PROFILE_NAME_PATTERN.test(selectedName)
|
|
1691
|
+
? { name: selectedName, status: "missing" }
|
|
1692
|
+
: { name: "<invalid>", status: "invalid" }
|
|
1693
|
+
: undefined;
|
|
1623
1694
|
const listing = await listEpicLaunchProfiles(deps);
|
|
1624
1695
|
if (!listing.ok)
|
|
1625
|
-
return { store: "unreadable", message: listing.message };
|
|
1696
|
+
return { store: "unreadable", message: listing.message, ...(selected ? { selected } : {}) };
|
|
1626
1697
|
if (!listing.present)
|
|
1627
|
-
return { store: "absent" };
|
|
1698
|
+
return { store: "absent", ...(selected ? { selected: { ...selected, status: selected.status === "invalid" ? "invalid" : "missing" } } : {}) };
|
|
1628
1699
|
const host = deps.hostname();
|
|
1700
|
+
const production = validateHttpBaseUrl(CONDUCTOR_DEFAULT_BASE_URL);
|
|
1701
|
+
const productionBaseUrl = production.ok ? production.baseUrl : null;
|
|
1629
1702
|
const profiles = [];
|
|
1630
1703
|
for (const entry of listing.entries) {
|
|
1631
1704
|
if (!entry.validation.ok) {
|
|
@@ -1633,22 +1706,32 @@ export async function collectEpicLaunchProfileDiagnostic(deps) {
|
|
|
1633
1706
|
continue;
|
|
1634
1707
|
}
|
|
1635
1708
|
const p = entry.validation.profile;
|
|
1636
|
-
const
|
|
1709
|
+
const binding = await readStoredBindingState(p.credentialRef.target, {
|
|
1637
1710
|
env: deps.env,
|
|
1638
1711
|
homedir: deps.homedir,
|
|
1639
1712
|
platform: process.platform,
|
|
1640
1713
|
readFile: deps.readFile,
|
|
1641
|
-
// Presence probe via read:
|
|
1714
|
+
// Presence probe via read: an unreadable store is reported as unbound.
|
|
1642
1715
|
stat: async (filePath) => {
|
|
1643
1716
|
await deps.readFile(filePath);
|
|
1644
1717
|
return { mode: 0o600 };
|
|
1645
1718
|
},
|
|
1646
1719
|
});
|
|
1647
1720
|
const hostMatches = p.host === host;
|
|
1648
|
-
|
|
1721
|
+
// Only a genuinely ABSENT binding field is accepted at production; a
|
|
1722
|
+
// present-but-empty field is a broken binding, reported as different.
|
|
1723
|
+
const credentialBinding = binding.state === "bound"
|
|
1724
|
+
? binding.baseUrl === p.baseUrl
|
|
1725
|
+
? "matches"
|
|
1726
|
+
: "different"
|
|
1727
|
+
: binding.state === "invalid"
|
|
1728
|
+
? "different"
|
|
1729
|
+
: binding.state === "absent" && productionBaseUrl !== null && p.baseUrl === productionBaseUrl
|
|
1730
|
+
? "production-default"
|
|
1731
|
+
: "unbound";
|
|
1649
1732
|
profiles.push({
|
|
1650
1733
|
name: p.name,
|
|
1651
|
-
usable: hostMatches && credentialBinding === "matches",
|
|
1734
|
+
usable: hostMatches && (credentialBinding === "matches" || credentialBinding === "production-default"),
|
|
1652
1735
|
target: p.baseUrl,
|
|
1653
1736
|
repo: p.repoName,
|
|
1654
1737
|
mode: p.executionMode,
|
|
@@ -1656,13 +1739,33 @@ export async function collectEpicLaunchProfileDiagnostic(deps) {
|
|
|
1656
1739
|
credentialBinding,
|
|
1657
1740
|
});
|
|
1658
1741
|
}
|
|
1659
|
-
|
|
1742
|
+
const selectedProfile = selected?.status !== "invalid" ? profiles.find((p) => p.name === selected?.name) : undefined;
|
|
1743
|
+
return {
|
|
1744
|
+
store: "present",
|
|
1745
|
+
profiles,
|
|
1746
|
+
...(selected ? { selected: { name: selected.name, status: selected.status === "invalid" ? "invalid" : !selectedProfile ? "missing" : selectedProfile.usable ? "usable" : "unusable" } } : {}),
|
|
1747
|
+
};
|
|
1660
1748
|
}
|
|
1661
|
-
/**
|
|
1749
|
+
/**
|
|
1750
|
+
* BAPI-1272 — a target-selection fact, shown whether or not a profile store
|
|
1751
|
+
* exists. It says nothing about credentials or readiness.
|
|
1752
|
+
*/
|
|
1753
|
+
export const EPIC_LAUNCH_DEFAULT_TARGET_LINE = ` [info] drive-epic with no profile, --base-url, or BAPI_BASE_URL targets production ` +
|
|
1754
|
+
`(${CONDUCTOR_DEFAULT_BASE_URL}); production needs no profile.`;
|
|
1755
|
+
/**
|
|
1756
|
+
* Render the section. With no profile store it holds only the default-target
|
|
1757
|
+
* line (BAPI-1272) — the common production case.
|
|
1758
|
+
*/
|
|
1662
1759
|
export function formatEpicLaunchProfileDiagnosticReport(diagnostic) {
|
|
1760
|
+
const lines = ["", "Epic launch profiles (read-only)", "", EPIC_LAUNCH_DEFAULT_TARGET_LINE];
|
|
1761
|
+
if (diagnostic.selected) {
|
|
1762
|
+
const { name, status } = diagnostic.selected;
|
|
1763
|
+
lines.push(` [${status === "usable" ? "ok" : "fail"}] BAPI_EPIC_LAUNCH_PROFILE selects ${name}: ${status}.`);
|
|
1764
|
+
if (status !== "usable")
|
|
1765
|
+
lines.push(" Epic launch will refuse this selection; inspect the local profile and credential binding.");
|
|
1766
|
+
}
|
|
1663
1767
|
if (diagnostic.store === "absent")
|
|
1664
|
-
return
|
|
1665
|
-
const lines = ["", "Epic launch profiles (read-only)", ""];
|
|
1768
|
+
return lines.join("\n");
|
|
1666
1769
|
if (diagnostic.store === "unreadable") {
|
|
1667
1770
|
lines.push(` [warn] ${diagnostic.message}`);
|
|
1668
1771
|
return lines.join("\n");
|
|
@@ -1684,6 +1787,9 @@ export function formatEpicLaunchProfileDiagnosticReport(diagnostic) {
|
|
|
1684
1787
|
else if (p.credentialBinding === "different") {
|
|
1685
1788
|
lines.push(" stored credential is approved for a DIFFERENT target — a launch will refuse to send it.");
|
|
1686
1789
|
}
|
|
1790
|
+
else if (p.credentialBinding === "production-default") {
|
|
1791
|
+
lines.push(" stored credential is unbound, which production accepts — no binding is needed.");
|
|
1792
|
+
}
|
|
1687
1793
|
}
|
|
1688
1794
|
return lines.join("\n");
|
|
1689
1795
|
}
|
|
@@ -1705,8 +1811,8 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
1705
1811
|
const agent = resolveAgentSpec(parsed.options.agentName) ?? resolveAgentSpec(DEFAULT_AGENT_NAME);
|
|
1706
1812
|
const collection = await collectDoctorResults(deps, parsed.options.agentName);
|
|
1707
1813
|
// ---- Section order (BAPI-669, U9c) ----
|
|
1708
|
-
// Broad header → Install status →
|
|
1709
|
-
// MCP tool surface. Install status renders FIRST and UNCONDITIONALLY: it answers
|
|
1814
|
+
// Broad header → Install status → Project identity and root (BAPI-1318) →
|
|
1815
|
+
// start-tickets prerequisites → launcher cache → MCP tool surface. Install status renders FIRST and UNCONDITIONALLY: it answers
|
|
1710
1816
|
// "is this project actually set up?", which is what someone running `doctor` after
|
|
1711
1817
|
// an install is asking. There is deliberately no "invocation context" flag to gate
|
|
1712
1818
|
// the ordering on — the report has a single caller and carries no context, so
|
|
@@ -1742,6 +1848,27 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
1742
1848
|
log(formatInstallStatusFallbackReport());
|
|
1743
1849
|
}
|
|
1744
1850
|
}
|
|
1851
|
+
// Advisory "Project identity and root" section (BAPI-1318). Read-only: it
|
|
1852
|
+
// validates each explicit BAPI_PROJECT_ROOT the way the server does and
|
|
1853
|
+
// compares .bridge/config's repo_name with each project MCP config. Placed
|
|
1854
|
+
// right after Install status without reordering any existing section; a throw
|
|
1855
|
+
// renders a sanitized line, and the exit code is never affected.
|
|
1856
|
+
if (overrides.projectIdentity !== false) {
|
|
1857
|
+
try {
|
|
1858
|
+
const injectedFs = deps;
|
|
1859
|
+
const identityDeps = {
|
|
1860
|
+
env: overrides.projectIdentity?.env ?? deps.env,
|
|
1861
|
+
cwd: overrides.projectIdentity?.cwd ?? deps.cwd,
|
|
1862
|
+
readFile: overrides.projectIdentity?.readFile ?? injectedFs.readFile ?? ((p) => readFile(p, "utf-8")),
|
|
1863
|
+
statDir: overrides.projectIdentity?.statDir ?? ((p) => stat(p)),
|
|
1864
|
+
};
|
|
1865
|
+
const collect = overrides.projectIdentity?.collect ?? collectProjectIdentityAndRoot;
|
|
1866
|
+
log(formatProjectIdentityAndRootReport(await collect(identityDeps)));
|
|
1867
|
+
}
|
|
1868
|
+
catch {
|
|
1869
|
+
log(formatProjectIdentityAndRootFallbackReport());
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1745
1872
|
if (parsed.options.agentName === "codex") {
|
|
1746
1873
|
const readCodexFile = deps.readFile ?? ((p) => readFile(p, "utf-8"));
|
|
1747
1874
|
const profile = await inspectBridgeCodexProfile(codexHome(deps.env), readCodexFile);
|
|
@@ -1816,7 +1943,7 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
1816
1943
|
const launcherDeps = {
|
|
1817
1944
|
cwd: overrides.launcherProbe?.cwd ?? deps.cwd,
|
|
1818
1945
|
readFile: overrides.launcherProbe?.readFile ?? ((p) => readFile(p, "utf-8")),
|
|
1819
|
-
probeNpxNoInstall: overrides.launcherProbe?.probeNpxNoInstall ?? probeNpxNoInstallDefault,
|
|
1946
|
+
probeNpxNoInstall: overrides.launcherProbe?.probeNpxNoInstall ?? ((spec) => probeNpxNoInstallDefault(spec)),
|
|
1820
1947
|
};
|
|
1821
1948
|
const launcherInspections = await inspectLauncherCache(launcherDeps);
|
|
1822
1949
|
log(formatLauncherCacheReport(launcherInspections));
|
|
@@ -2000,8 +2127,7 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
2000
2127
|
((p) => readFile(p, "utf-8")),
|
|
2001
2128
|
hostname: overrides.launchProfiles?.hostname ?? os.hostname,
|
|
2002
2129
|
}));
|
|
2003
|
-
|
|
2004
|
-
log(report);
|
|
2130
|
+
log(report);
|
|
2005
2131
|
}
|
|
2006
2132
|
catch {
|
|
2007
2133
|
/* advisory only: an unexpected failure drops the section, never the doctor */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BAPI-1285: the one epic-key pattern `drive-epic` and plan discovery share.
|
|
3
|
+
*
|
|
4
|
+
* It lives in its own module so `plan-discovery.ts` can import it without
|
|
5
|
+
* importing `drive-epic.ts`, which imports discovery — a cycle.
|
|
6
|
+
*
|
|
7
|
+
* This is deliberately the narrower CLI grammar. Bundle finalization accepts some
|
|
8
|
+
* underscore or digit project prefixes, but `drive-epic` already rejects them, so
|
|
9
|
+
* metadata naming such a key can never be discovered through this CLI.
|
|
10
|
+
*/
|
|
11
|
+
/** Jira epic key shape. Matches the epic-key positional rule exactly. */
|
|
12
|
+
export const EPIC_KEY_PATTERN = /^[A-Z]+-[0-9]+$/;
|