@bridge_gpt/mcp-server 0.2.16 → 0.2.18
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/build/agents.generated.js +2 -2
- package/build/commands.generated.js +6 -6
- package/build/conductor/bridge-api-client.js +191 -11
- package/build/conductor/claude-hook.js +22 -4
- package/build/conductor/cli.js +11 -13
- package/build/conductor/done-gate.js +5 -0
- package/build/conductor/epic-reconcile.js +62 -13
- package/build/conductor/epic-runtime.js +447 -35
- package/build/conductor/epic-state.js +517 -63
- package/build/conductor/errors.js +41 -0
- package/build/conductor/event-accessors.js +234 -0
- package/build/conductor/file-scope-guard.js +201 -0
- package/build/conductor/github-mergeability.js +85 -0
- package/build/conductor/local-merge.js +47 -1
- package/build/conductor/merge-identity.js +41 -0
- package/build/conductor/merge-ledger.js +13 -68
- package/build/conductor/plan.js +12 -2
- package/build/conductor/pr-discovery.js +11 -1
- package/build/conductor/supervisor-config.js +4 -39
- package/build/conductor/supervisor-escalation.js +10 -26
- package/build/conductor/supervisor-ledger.js +5 -12
- package/build/conductor/supervisor-message-relay.js +2 -5
- package/build/conductor/supervisor-notification.js +1 -1
- package/build/conductor/supervisor-runtime.js +12 -54
- package/build/conductor/supervisor-state.js +4 -18
- package/build/conductor/supervisor-types.js +2 -2
- package/build/conductor/taxonomy.js +4 -0
- package/build/conductor-bin.js +2333 -666
- package/build/conductor-claude-hook-bin.js +4 -2
- package/build/doctor.js +32 -0
- package/build/index.js +10125 -8522
- package/build/install-bridge.js +25 -8
- package/build/install-doctor.js +387 -0
- package/build/pipelines.generated.js +30 -5
- package/build/regression-check.js +53 -1
- package/build/review-tickets.js +175 -21
- package/build/start-tickets-conductor.js +22 -6
- package/build/start-tickets-prereqs.js +33 -3
- package/build/start-tickets.js +122 -22
- package/build/version.generated.js +1 -1
- package/package.json +5 -5
- package/pipelines/review-ticket.json +24 -2
- package/public/css/main.min.css +3272 -1
- package/public/css/main.min.css.map +1 -1
- package/smoke-test/SMOKE-TEST.md +4 -2
package/build/install-bridge.js
CHANGED
|
@@ -65,7 +65,14 @@ export function buildPrewarmCommandPreview() {
|
|
|
65
65
|
return `npx ${buildPrewarmArgs().join(" ")}`;
|
|
66
66
|
}
|
|
67
67
|
/** The natural-language sequential prompt handed to the spawned agent session. */
|
|
68
|
-
export const INSTALL_BRIDGE_AGENT_PROMPT = "Please execute the /install-bridge command. Once it completes successfully, execute the
|
|
68
|
+
export const INSTALL_BRIDGE_AGENT_PROMPT = "Please execute the /install-bridge command. Once it completes successfully, execute the " +
|
|
69
|
+
"/learn-repository command. After /learn-repository completes, ask the user for consent to start " +
|
|
70
|
+
"repository indexing; with consent, call the parse_repository MCP tool once and report that the " +
|
|
71
|
+
"job was queued (progress is checked with get_parse_status — do not poll it to completion). " +
|
|
72
|
+
"When you are done, end with an explicit summary line stating how " +
|
|
73
|
+
"many config fields the apply_install_manifest call applied (e.g. 'Applied 8 of 9 derived " +
|
|
74
|
+
"fields') and whether indexing was queued — if 0 fields were applied, say so loudly and explain " +
|
|
75
|
+
"what is still pending.";
|
|
69
76
|
/** Default base URL when `BAPI_BASE_URL` is unset (mirrors index.ts). */
|
|
70
77
|
export const DEFAULT_BAPI_BASE_URL = "https://bridgegpt-api.com";
|
|
71
78
|
/** Default docs dir when `BAPI_DOCS_DIR` is unset (mirrors index.ts). */
|
|
@@ -739,15 +746,19 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
739
746
|
}
|
|
740
747
|
else {
|
|
741
748
|
log(` warning: could not persist the routing credential (${result.kind}). ` +
|
|
742
|
-
`start-tickets model routing may not resolve the key for ${plan.credentialTarget}
|
|
743
|
-
"
|
|
749
|
+
`start-tickets model routing may not resolve the key for ${plan.credentialTarget} ` +
|
|
750
|
+
"and will fail open to the premium/Opus tier (the most expensive) — " +
|
|
751
|
+
"set BAPI_API_KEY in the shell or re-run install-bridge, then verify with " +
|
|
752
|
+
"'npx -y @bridge_gpt/mcp-server doctor'.");
|
|
744
753
|
}
|
|
745
754
|
}
|
|
746
755
|
catch {
|
|
747
756
|
// Fail-open: persistence is best-effort (mirrors Stage 6). The secret is never
|
|
748
757
|
// included in the warning.
|
|
749
758
|
log(" warning: could not persist the routing credential (unexpected error). " +
|
|
750
|
-
"start-tickets model routing may need BAPI_API_KEY in the shell
|
|
759
|
+
"start-tickets model routing may need BAPI_API_KEY in the shell and will fail open " +
|
|
760
|
+
"to the premium/Opus tier (the most expensive) until fixed — verify with " +
|
|
761
|
+
"'npx -y @bridge_gpt/mcp-server doctor'.");
|
|
751
762
|
}
|
|
752
763
|
// ---- Step 5 — spawn a fresh agent session for the agentic remainder ----
|
|
753
764
|
log(`Step 5/5 — opening a ${agent.name} session for /install-bridge + /learn-repository…`);
|
|
@@ -759,14 +770,20 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
759
770
|
if (!spawnResult.ok) {
|
|
760
771
|
// Steps 1–4 (scaffold, config, connectivity-verified, credential persist) all
|
|
761
772
|
// succeeded and are durable; only the best-effort Step 5 tab spawn failed. The
|
|
762
|
-
//
|
|
773
|
+
// deterministic setup is complete, so this is a non-fatal warning (exit 0) — the
|
|
763
774
|
// user can run the agentic remainder by hand. Mirrors start-tickets treating a
|
|
764
775
|
// spawn failure as non-fatal to the run.
|
|
765
|
-
errorLog(`Warning: setup completed, but the agent session could not be opened (${spawnResult.error}). ` +
|
|
766
|
-
"
|
|
776
|
+
errorLog(`Warning: setup steps completed, but the agent session could not be opened (${spawnResult.error}). ` +
|
|
777
|
+
"The project is NOT configured yet — run /install-bridge then /learn-repository manually " +
|
|
778
|
+
"in this project to derive and apply the config fields.");
|
|
767
779
|
return 0;
|
|
768
780
|
}
|
|
769
781
|
log("");
|
|
770
|
-
log(
|
|
782
|
+
log(`install-bridge setup steps complete. A fresh ${agent.name} session is now running ` +
|
|
783
|
+
"/install-bridge then /learn-repository.");
|
|
784
|
+
log("NOTE: the install is not finished until that session's apply reports applied fields — " +
|
|
785
|
+
"it will pause to ask you to approve the project description. Verify afterwards on the " +
|
|
786
|
+
"project's Get Started page (install status panel) or via the session's " +
|
|
787
|
+
"'Applied N of M' summary.");
|
|
771
788
|
return 0;
|
|
772
789
|
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install-doctor — an advisory, strictly read-only "Install status" section for
|
|
3
|
+
* the `doctor` subcommand, turning the install manifest's prose done-criteria
|
|
4
|
+
* into an executable checklist.
|
|
5
|
+
*
|
|
6
|
+
* Checks (all best-effort, all read-only GETs, none affect the doctor exit code):
|
|
7
|
+
* - identity — can a repo name / base URL be resolved (env, then the
|
|
8
|
+
* project-local MCP config env blocks)?
|
|
9
|
+
* - credential — does the standard resolver (env → user-scoped store) find
|
|
10
|
+
* a Bridge API key? (never reads or prints the key value)
|
|
11
|
+
* - connectivity — GET /jira/ping (distinguishes rejected key vs unknown repo)
|
|
12
|
+
* - bootstrap — GET /jira/config/install-manifest → N/M bootstrap fields set
|
|
13
|
+
* - integrations — the same manifest's presence-only integrations checklist
|
|
14
|
+
* (unconfigured credentials are configured by a human in the
|
|
15
|
+
* setup UI — never through an agent)
|
|
16
|
+
* - indexing — GET /jira/parse-status → parse job running / idle
|
|
17
|
+
*
|
|
18
|
+
* The section is advisory by contract: any failure inside it degrades to a
|
|
19
|
+
* WARN/SKIP line, never a throw, and the doctor exit code stays driven by the
|
|
20
|
+
* required prerequisites only.
|
|
21
|
+
*/
|
|
22
|
+
import path from "path";
|
|
23
|
+
import { resolveBapiCredentials } from "./credential-store.js";
|
|
24
|
+
/** Default production base URL (mirrors install-bridge's DEFAULT_BAPI_BASE_URL). */
|
|
25
|
+
const DEFAULT_BASE_URL = "https://bridgegpt-api.com";
|
|
26
|
+
/** Project-local MCP configs whose env blocks may carry repo/base-URL values. */
|
|
27
|
+
const MCP_CONFIG_ENV_TARGETS = [
|
|
28
|
+
{ relPath: ".mcp.json", topLevelKey: "mcpServers" },
|
|
29
|
+
{ relPath: ".cursor/mcp.json", topLevelKey: "mcpServers" },
|
|
30
|
+
{ relPath: ".vscode/mcp.json", topLevelKey: "servers" },
|
|
31
|
+
];
|
|
32
|
+
/** Timeout for each read-only probe request. */
|
|
33
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the repo name and base URL: environment first, then the NON-SECRET
|
|
36
|
+
* env-block values of the project-local MCP configs (`BAPI_REPO_NAME` /
|
|
37
|
+
* `BAPI_BASE_URL`). The API key is deliberately NEVER read from config files —
|
|
38
|
+
* secret resolution goes through the standard credential resolver only.
|
|
39
|
+
*/
|
|
40
|
+
export async function resolveInstallDoctorTarget(deps) {
|
|
41
|
+
const envRepo = deps.env.BAPI_REPO_NAME?.trim();
|
|
42
|
+
const envBase = deps.env.BAPI_BASE_URL?.trim();
|
|
43
|
+
let repoName = envRepo && envRepo.length > 0 ? envRepo : null;
|
|
44
|
+
let repoSource = repoName ? "env" : null;
|
|
45
|
+
let baseUrl = envBase && envBase.length > 0 ? envBase : null;
|
|
46
|
+
if (!repoName || !baseUrl) {
|
|
47
|
+
for (const { relPath, topLevelKey } of MCP_CONFIG_ENV_TARGETS) {
|
|
48
|
+
let raw;
|
|
49
|
+
try {
|
|
50
|
+
raw = await deps.readFile(path.join(deps.cwd, relPath));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue; // absent config is not a finding
|
|
54
|
+
}
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const envBlock = parsed && typeof parsed === "object"
|
|
63
|
+
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
parsed[topLevelKey]?.["bridge-api"]?.env
|
|
65
|
+
: undefined;
|
|
66
|
+
if (!envBlock)
|
|
67
|
+
continue;
|
|
68
|
+
if (!repoName && typeof envBlock.BAPI_REPO_NAME === "string" && envBlock.BAPI_REPO_NAME.trim()) {
|
|
69
|
+
repoName = envBlock.BAPI_REPO_NAME.trim();
|
|
70
|
+
repoSource = "config";
|
|
71
|
+
}
|
|
72
|
+
if (!baseUrl && typeof envBlock.BAPI_BASE_URL === "string" && envBlock.BAPI_BASE_URL.trim()) {
|
|
73
|
+
baseUrl = envBlock.BAPI_BASE_URL.trim();
|
|
74
|
+
}
|
|
75
|
+
if (repoName && baseUrl)
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { repoName, repoSource, baseUrl: baseUrl ?? DEFAULT_BASE_URL };
|
|
80
|
+
}
|
|
81
|
+
/** Read-only GET with a bounded timeout. Never throws. */
|
|
82
|
+
async function probeGet(deps, url, apiKey) {
|
|
83
|
+
try {
|
|
84
|
+
const resp = await deps.fetch(url, {
|
|
85
|
+
headers: { "X-API-Key": apiKey },
|
|
86
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
|
87
|
+
});
|
|
88
|
+
let body = null;
|
|
89
|
+
try {
|
|
90
|
+
body = await resp.json();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
/* non-JSON body — status alone is still useful */
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, status: resp.status, body };
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Count set/total bootstrap fields from an install-manifest response body. */
|
|
102
|
+
function summarizeManifestGroups(body) {
|
|
103
|
+
if (!body || typeof body !== "object")
|
|
104
|
+
return null;
|
|
105
|
+
const groups = body.groups;
|
|
106
|
+
if (!Array.isArray(groups))
|
|
107
|
+
return null;
|
|
108
|
+
let total = 0;
|
|
109
|
+
const unset = [];
|
|
110
|
+
for (const group of groups) {
|
|
111
|
+
const fields = group?.fields;
|
|
112
|
+
if (!Array.isArray(fields))
|
|
113
|
+
continue;
|
|
114
|
+
for (const field of fields) {
|
|
115
|
+
const name = field?.field_name;
|
|
116
|
+
if (typeof name !== "string")
|
|
117
|
+
continue;
|
|
118
|
+
total += 1;
|
|
119
|
+
if (!field.is_set)
|
|
120
|
+
unset.push(name);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { total, unset };
|
|
124
|
+
}
|
|
125
|
+
/** Summarize the manifest's integrations checklist (presence booleans only). */
|
|
126
|
+
function summarizeIntegrations(body) {
|
|
127
|
+
if (!body || typeof body !== "object")
|
|
128
|
+
return null;
|
|
129
|
+
const integrations = body.integrations;
|
|
130
|
+
if (!Array.isArray(integrations))
|
|
131
|
+
return null;
|
|
132
|
+
let total = 0;
|
|
133
|
+
const unconfigured = [];
|
|
134
|
+
for (const item of integrations) {
|
|
135
|
+
const label = item?.label;
|
|
136
|
+
if (typeof label !== "string")
|
|
137
|
+
continue;
|
|
138
|
+
total += 1;
|
|
139
|
+
if (!item.is_configured)
|
|
140
|
+
unconfigured.push(label);
|
|
141
|
+
}
|
|
142
|
+
return { total, unconfigured };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Collect the install-status checklist. Never throws; every failure path becomes
|
|
146
|
+
* a WARN or SKIP check line. Performs at most three read-only GETs, and only
|
|
147
|
+
* when both a repo name and a credential resolve.
|
|
148
|
+
*/
|
|
149
|
+
export async function collectInstallStatusChecks(deps) {
|
|
150
|
+
const checks = [];
|
|
151
|
+
const target = await resolveInstallDoctorTarget(deps);
|
|
152
|
+
if (!target.repoName) {
|
|
153
|
+
checks.push({
|
|
154
|
+
id: "identity",
|
|
155
|
+
label: "Repository identity",
|
|
156
|
+
status: "SKIP",
|
|
157
|
+
detail: "no BAPI_REPO_NAME in the environment or project-local MCP configs",
|
|
158
|
+
remediation: "run install-bridge (or set BAPI_REPO_NAME) to configure this project.",
|
|
159
|
+
});
|
|
160
|
+
return checks;
|
|
161
|
+
}
|
|
162
|
+
checks.push({
|
|
163
|
+
id: "identity",
|
|
164
|
+
label: "Repository identity",
|
|
165
|
+
status: "PASS",
|
|
166
|
+
detail: `${target.repoName} (from ${target.repoSource}), base URL ${target.baseUrl}`,
|
|
167
|
+
});
|
|
168
|
+
const credDeps = {
|
|
169
|
+
env: deps.env,
|
|
170
|
+
homedir: deps.homedir,
|
|
171
|
+
platform: deps.platform,
|
|
172
|
+
readFile: deps.readFile,
|
|
173
|
+
stat: deps.stat,
|
|
174
|
+
stderr: () => { },
|
|
175
|
+
};
|
|
176
|
+
const cred = await resolveBapiCredentials(target.repoName, credDeps);
|
|
177
|
+
if (!cred.ok) {
|
|
178
|
+
checks.push({
|
|
179
|
+
id: "credential",
|
|
180
|
+
label: "Bridge API credential",
|
|
181
|
+
status: "WARN",
|
|
182
|
+
detail: `not resolved (${cred.kind})`,
|
|
183
|
+
remediation: "set BAPI_API_KEY or persist it via /install-bridge / the credentials subcommand; " +
|
|
184
|
+
"until then the remaining install checks are skipped.",
|
|
185
|
+
});
|
|
186
|
+
for (const [id, label] of [
|
|
187
|
+
["connectivity", "Server connectivity"],
|
|
188
|
+
["bootstrap", "Bootstrap config fields"],
|
|
189
|
+
["indexing", "Repository indexing"],
|
|
190
|
+
]) {
|
|
191
|
+
checks.push({ id, label, status: "SKIP", detail: "no credential resolved" });
|
|
192
|
+
}
|
|
193
|
+
return checks;
|
|
194
|
+
}
|
|
195
|
+
checks.push({
|
|
196
|
+
id: "credential",
|
|
197
|
+
label: "Bridge API credential",
|
|
198
|
+
status: "PASS",
|
|
199
|
+
detail: `resolved from ${cred.credentials.source} (value never read into the report)`,
|
|
200
|
+
});
|
|
201
|
+
const apiKey = cred.credentials.apiKey;
|
|
202
|
+
const repoQuery = `repo_name=${encodeURIComponent(target.repoName)}`;
|
|
203
|
+
// --- connectivity ---
|
|
204
|
+
const ping = await probeGet(deps, `${target.baseUrl}/jira/ping?${repoQuery}`, apiKey);
|
|
205
|
+
if (!ping.ok) {
|
|
206
|
+
checks.push({
|
|
207
|
+
id: "connectivity",
|
|
208
|
+
label: "Server connectivity",
|
|
209
|
+
status: "WARN",
|
|
210
|
+
detail: `unreachable (${ping.error})`,
|
|
211
|
+
remediation: `check ${target.baseUrl} and your network, then re-run doctor.`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
else if (ping.status === 200) {
|
|
215
|
+
checks.push({ id: "connectivity", label: "Server connectivity", status: "PASS", detail: "ping OK" });
|
|
216
|
+
}
|
|
217
|
+
else if (ping.status === 401 || ping.status === 403) {
|
|
218
|
+
checks.push({
|
|
219
|
+
id: "connectivity",
|
|
220
|
+
label: "Server connectivity",
|
|
221
|
+
status: "WARN",
|
|
222
|
+
detail: `the server rejected the resolved key (HTTP ${ping.status})`,
|
|
223
|
+
remediation: "the key may have been rotated — re-run install-bridge with a current key.",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else if (ping.status === 404) {
|
|
227
|
+
checks.push({
|
|
228
|
+
id: "connectivity",
|
|
229
|
+
label: "Server connectivity",
|
|
230
|
+
status: "WARN",
|
|
231
|
+
detail: `the server does not know repo '${target.repoName}' (HTTP 404)`,
|
|
232
|
+
remediation: "create the project in the setup UI (or check BAPI_REPO_NAME spelling).",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
checks.push({
|
|
237
|
+
id: "connectivity",
|
|
238
|
+
label: "Server connectivity",
|
|
239
|
+
status: "WARN",
|
|
240
|
+
detail: `unexpected HTTP ${ping.status} from /jira/ping`,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// --- bootstrap fields ---
|
|
244
|
+
const manifest = await probeGet(deps, `${target.baseUrl}/jira/config/install-manifest?${repoQuery}`, apiKey);
|
|
245
|
+
if (!manifest.ok) {
|
|
246
|
+
checks.push({
|
|
247
|
+
id: "bootstrap",
|
|
248
|
+
label: "Bootstrap config fields",
|
|
249
|
+
status: "WARN",
|
|
250
|
+
detail: `manifest unreachable (${manifest.error})`,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else if (manifest.status === 404) {
|
|
254
|
+
checks.push({
|
|
255
|
+
id: "bootstrap",
|
|
256
|
+
label: "Bootstrap config fields",
|
|
257
|
+
status: "WARN",
|
|
258
|
+
detail: "no configuration row found for this repository",
|
|
259
|
+
remediation: "create the project in the setup UI, then run /install-bridge.",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
else if (manifest.status === 200) {
|
|
263
|
+
const summary = summarizeManifestGroups(manifest.body);
|
|
264
|
+
if (!summary) {
|
|
265
|
+
checks.push({
|
|
266
|
+
id: "bootstrap",
|
|
267
|
+
label: "Bootstrap config fields",
|
|
268
|
+
status: "WARN",
|
|
269
|
+
detail: "manifest response had an unexpected shape",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
else if (summary.unset.length === 0) {
|
|
273
|
+
checks.push({
|
|
274
|
+
id: "bootstrap",
|
|
275
|
+
label: "Bootstrap config fields",
|
|
276
|
+
status: "PASS",
|
|
277
|
+
detail: `${summary.total}/${summary.total} bootstrap fields set`,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
checks.push({
|
|
282
|
+
id: "bootstrap",
|
|
283
|
+
label: "Bootstrap config fields",
|
|
284
|
+
status: "WARN",
|
|
285
|
+
detail: `${summary.total - summary.unset.length}/${summary.total} set; unset: ${summary.unset.join(", ")}`,
|
|
286
|
+
remediation: "run /install-bridge to derive the unset fields (intentionally-unset fields are fine to leave).",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
checks.push({
|
|
292
|
+
id: "bootstrap",
|
|
293
|
+
label: "Bootstrap config fields",
|
|
294
|
+
status: "WARN",
|
|
295
|
+
detail: `unexpected HTTP ${manifest.status} from the install manifest`,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
// --- integrations (from the same manifest response; presence booleans only) ---
|
|
299
|
+
if (manifest.ok && manifest.status === 200) {
|
|
300
|
+
const integrations = summarizeIntegrations(manifest.body);
|
|
301
|
+
if (integrations === null) {
|
|
302
|
+
checks.push({
|
|
303
|
+
id: "integrations",
|
|
304
|
+
label: "Integration credentials",
|
|
305
|
+
status: "SKIP",
|
|
306
|
+
detail: "the manifest response carried no integrations checklist",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else if (integrations.unconfigured.length === 0) {
|
|
310
|
+
checks.push({
|
|
311
|
+
id: "integrations",
|
|
312
|
+
label: "Integration credentials",
|
|
313
|
+
status: "PASS",
|
|
314
|
+
detail: `${integrations.total}/${integrations.total} configured`,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
checks.push({
|
|
319
|
+
id: "integrations",
|
|
320
|
+
label: "Integration credentials",
|
|
321
|
+
status: "WARN",
|
|
322
|
+
detail: `not configured: ${integrations.unconfigured.join(", ")}`,
|
|
323
|
+
remediation: "a human configures these in the setup UI (project settings) — Bridge API never accepts " +
|
|
324
|
+
"integration secrets through an agent or MCP tool.",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
checks.push({
|
|
330
|
+
id: "integrations",
|
|
331
|
+
label: "Integration credentials",
|
|
332
|
+
status: "SKIP",
|
|
333
|
+
detail: "manifest unavailable",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// --- indexing ---
|
|
337
|
+
const parse = await probeGet(deps, `${target.baseUrl}/jira/parse-status?${repoQuery}`, apiKey);
|
|
338
|
+
if (!parse.ok || parse.status !== 200) {
|
|
339
|
+
checks.push({
|
|
340
|
+
id: "indexing",
|
|
341
|
+
label: "Repository indexing",
|
|
342
|
+
status: "SKIP",
|
|
343
|
+
detail: parse.ok ? `HTTP ${parse.status} from /jira/parse-status` : `unreachable (${parse.error})`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const status = parse.body?.status;
|
|
348
|
+
if (status === "in_progress") {
|
|
349
|
+
const startedAt = parse.body.started_at;
|
|
350
|
+
checks.push({
|
|
351
|
+
id: "indexing",
|
|
352
|
+
label: "Repository indexing",
|
|
353
|
+
status: "INFO",
|
|
354
|
+
detail: `parse job in progress${typeof startedAt === "string" ? ` (started ${startedAt})` : ""}`,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
checks.push({
|
|
359
|
+
id: "indexing",
|
|
360
|
+
label: "Repository indexing",
|
|
361
|
+
status: "INFO",
|
|
362
|
+
detail: "no parse job currently running — if this repository has never been indexed, " +
|
|
363
|
+
"queue one with /parse-repository (monitor with get_parse_status).",
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return checks;
|
|
368
|
+
}
|
|
369
|
+
/** Render the install-status section of the doctor report (pure formatting). */
|
|
370
|
+
export function formatInstallStatusReport(checks) {
|
|
371
|
+
const lines = ["", "Install status (easy-install done criteria — advisory)", ""];
|
|
372
|
+
const pad = {
|
|
373
|
+
PASS: "PASS ",
|
|
374
|
+
WARN: "WARN ",
|
|
375
|
+
INFO: "INFO ",
|
|
376
|
+
SKIP: "SKIPPED",
|
|
377
|
+
};
|
|
378
|
+
for (const check of checks) {
|
|
379
|
+
lines.push(`${pad[check.status]} ${check.label}${check.detail ? ` — ${check.detail}` : ""}`);
|
|
380
|
+
if (check.remediation) {
|
|
381
|
+
lines.push(` ${check.remediation}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
lines.push("");
|
|
385
|
+
lines.push("This section is advisory and never changes the doctor exit code. It performs read-only GETs only.");
|
|
386
|
+
return lines.join("\n");
|
|
387
|
+
}
|