@bridge_gpt/mcp-server 0.2.67 → 0.2.68
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 +178 -15
- 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 +829 -0
- package/build/claude-settings.js +18 -0
- package/build/cli-help.js +21 -0
- package/build/command-assets-doctor.js +24 -70
- package/build/command-provisioning.js +29 -5
- package/build/commands.generated.js +8 -8
- 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/credential-store.js +207 -47
- package/build/docs.generated.js +57 -3
- package/build/doctor.js +92 -1
- package/build/drive-epic.js +45 -8
- package/build/epic-coherence-input.test-fixtures.js +4 -3
- package/build/epic-implementer-bin.js +1374 -25
- package/build/epic-workarounds-path.js +39 -0
- package/build/executor/cli.js +30 -0
- package/build/executor/completion-contract.js +18 -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/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 +964 -768
- 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 +544 -37
- 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/setup-epic.js +108 -3
- package/build/start-tickets-prereqs.js +70 -16
- package/build/start-tickets.js +29 -5
- package/build/tool-surface-gating.js +36 -0
- package/build/upgrade-cli.js +55 -1
- 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
|
@@ -197,6 +197,355 @@ export function presentRequiredChecks(supervisor) {
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
// ---------------------------------------------------------------------------
|
|
200
|
+
// Gate-profile wording (BAPI-1390) — shared with the readiness gate's rows
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
/**
|
|
203
|
+
* Render a server-validated check or command name for a remedy. The name is
|
|
204
|
+
* the ONLY payload value any remedy interpolates; backticks are swapped so it
|
|
205
|
+
* cannot close its own quoting.
|
|
206
|
+
*/
|
|
207
|
+
export function quotedSubject(subject, fallback) {
|
|
208
|
+
if (subject === null)
|
|
209
|
+
return fallback;
|
|
210
|
+
const safe = subject.replace(/`/g, "'");
|
|
211
|
+
return `\`${safe}\``;
|
|
212
|
+
}
|
|
213
|
+
/** Human words for the profile kind. `null` is an unknown kind, never a held one. */
|
|
214
|
+
const GATE_PROFILE_KIND_LABELS = {
|
|
215
|
+
provider: "Provider",
|
|
216
|
+
local: "Local",
|
|
217
|
+
mixed: "Mixed",
|
|
218
|
+
none: "None",
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* One line describing a gate profile: kind, source, confirmation, revision,
|
|
222
|
+
* members, migration guard, and qualification state. Shared with the install
|
|
223
|
+
* doctor's section so the two surfaces cannot word the same profile apart.
|
|
224
|
+
* Only validated names are interpolated; everything else is a closed token.
|
|
225
|
+
*/
|
|
226
|
+
export function describeGateProfile(profile) {
|
|
227
|
+
const kind = profile.kind === null ? "Unknown" : GATE_PROFILE_KIND_LABELS[profile.kind] ?? "Unknown";
|
|
228
|
+
const source = profile.source === "legacy"
|
|
229
|
+
? "legacy: pre-profile done-gate checks treated as a confirmed provider profile"
|
|
230
|
+
: profile.source;
|
|
231
|
+
const parts = [
|
|
232
|
+
`${kind} gate profile (${source}, ${profile.confirmed ? "confirmed" : "unconfirmed"}, revision ${profile.revision})`,
|
|
233
|
+
];
|
|
234
|
+
if (profile.provider_checks.length > 0) {
|
|
235
|
+
parts.push(`provider checks: ${profile.provider_checks
|
|
236
|
+
.map((c) => `${quotedSubject(c.name, "")} ${c.outcome === "evidence_unavailable" ? "unavailable" : c.outcome ?? "unknown"}`)
|
|
237
|
+
.join(", ")}`);
|
|
238
|
+
}
|
|
239
|
+
if (profile.local_commands.length > 0) {
|
|
240
|
+
parts.push(`local commands: ${profile.local_commands.map((c) => quotedSubject(c.name, "")).join(", ")}`);
|
|
241
|
+
}
|
|
242
|
+
parts.push(profile.migration_guard === null
|
|
243
|
+
? profile.migration_guard_required === true
|
|
244
|
+
? "migration guard: unset (required)"
|
|
245
|
+
: "migration guard: none"
|
|
246
|
+
: `migration guard: ${profile.migration_guard.kind}${profile.migration_guard.name === null ? "" : ` ${quotedSubject(profile.migration_guard.name, "")}`}`);
|
|
247
|
+
if (profile.qualification_state !== null)
|
|
248
|
+
parts.push(`qualification: ${profile.qualification_state}`);
|
|
249
|
+
if (profile.kind === "none") {
|
|
250
|
+
parts.push("unattended runs proceed, but every child merge waits for approval (`merge-child`)");
|
|
251
|
+
}
|
|
252
|
+
return parts.join("; ");
|
|
253
|
+
}
|
|
254
|
+
/** Fixed copy for a known warning code. Only the validated name is interpolated. */
|
|
255
|
+
export function describeGateProfileWarning(code, name) {
|
|
256
|
+
switch (code) {
|
|
257
|
+
case "provider_check_not_seen_on_epic_branches":
|
|
258
|
+
return `the check ${quotedSubject(name, "named in the gate profile")} has not been seen on an epic branch yet — this does not hold a launch`;
|
|
259
|
+
case "epic_branch_search_unavailable":
|
|
260
|
+
return "Bridge could not search epic branches for the gate-profile checks — this does not hold a launch";
|
|
261
|
+
default:
|
|
262
|
+
return "the gate profile reported a warning this CLI does not recognize — this does not hold a launch";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** Section id for each known warn-only gate-profile code; anything else is generic. */
|
|
266
|
+
const GATE_PROFILE_WARNING_SECTION_IDS = {
|
|
267
|
+
provider_check_not_seen_on_epic_branches: "supervisor-gate-profile-epic-branch-unseen",
|
|
268
|
+
epic_branch_search_unavailable: "supervisor-gate-profile-epic-branch-search",
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* BAPI-1390 — the gate-profile section plus its warn-only rows. Emitted ONLY
|
|
272
|
+
* when the server reported a profile, so an older server's report is
|
|
273
|
+
* byte-identical. Never `fatal`: the server's admission evidence decides holds;
|
|
274
|
+
* these sections describe. Warnings are `degraded` with copy that says they do
|
|
275
|
+
* not hold a launch, one section per known code (names joined) plus one
|
|
276
|
+
* generic section for codes this client does not recognize.
|
|
277
|
+
*/
|
|
278
|
+
export function gateProfileSections(profile) {
|
|
279
|
+
if (profile === undefined)
|
|
280
|
+
return [];
|
|
281
|
+
if (profile === null) {
|
|
282
|
+
return [
|
|
283
|
+
{
|
|
284
|
+
id: "supervisor-gate-profile",
|
|
285
|
+
label: "Supervisor gate profile",
|
|
286
|
+
status: "degraded",
|
|
287
|
+
detail: "the server reported no gate profile, so the CI gate is unverified",
|
|
288
|
+
remediation: "upgrade the Bridge API deploy so it reports the gate profile.",
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
}
|
|
292
|
+
const detail = describeGateProfile(profile);
|
|
293
|
+
const held = profile.read_failed ||
|
|
294
|
+
profile.stored_malformed ||
|
|
295
|
+
profile.verdict === "denied" ||
|
|
296
|
+
profile.verdict === "unable_to_verify";
|
|
297
|
+
const sections = [];
|
|
298
|
+
if (held) {
|
|
299
|
+
sections.push({
|
|
300
|
+
id: "supervisor-gate-profile",
|
|
301
|
+
label: "Supervisor gate profile",
|
|
302
|
+
status: "degraded",
|
|
303
|
+
detail: profile.read_failed
|
|
304
|
+
? `the stored gate profile could not be read — ${detail}`
|
|
305
|
+
: profile.stored_malformed
|
|
306
|
+
? `the stored gate profile is malformed — ${detail}`
|
|
307
|
+
: `${profile.verdict === "denied" ? "denied" : "could not be verified"}${profile.reason ? ` (${profile.reason})` : ""} — ${detail}`,
|
|
308
|
+
remediation: "a repository administrator reviews the gate profile on the repository's Setup page; " +
|
|
309
|
+
"the server's launch evidence names the specific item.",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
else if (profile.verdict === "confirmed" && profile.kind !== "none") {
|
|
313
|
+
sections.push({ id: "supervisor-gate-profile", label: "Supervisor gate profile", status: "ok", detail });
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
sections.push({
|
|
317
|
+
id: "supervisor-gate-profile",
|
|
318
|
+
label: "Supervisor gate profile",
|
|
319
|
+
status: "degraded",
|
|
320
|
+
detail,
|
|
321
|
+
remediation: profile.kind === "none"
|
|
322
|
+
? "no action is needed to launch; to let child merges proceed without approval, add checks to the gate profile."
|
|
323
|
+
: "a repository administrator confirms the gate profile; the server's launch evidence decides whether a run is held.",
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const grouped = new Map();
|
|
327
|
+
for (const warning of profile.warnings) {
|
|
328
|
+
const id = GATE_PROFILE_WARNING_SECTION_IDS[warning.code] ?? "supervisor-gate-profile-warning";
|
|
329
|
+
const entry = grouped.get(id) ?? { code: warning.code, names: [] };
|
|
330
|
+
entry.names.push(warning.name);
|
|
331
|
+
grouped.set(id, entry);
|
|
332
|
+
}
|
|
333
|
+
for (const [id, { code, names }] of grouped) {
|
|
334
|
+
const named = names.filter((name) => name !== null);
|
|
335
|
+
sections.push({
|
|
336
|
+
id,
|
|
337
|
+
label: "Supervisor gate profile warning",
|
|
338
|
+
status: "degraded",
|
|
339
|
+
detail: code === "provider_check_not_seen_on_epic_branches" && named.length > 1
|
|
340
|
+
? `the checks ${named.map((name) => quotedSubject(name, "")).join(", ")} have not been seen on an epic branch yet — this does not hold a launch`
|
|
341
|
+
: describeGateProfileWarning(code, named[0] ?? null),
|
|
342
|
+
remediation: "no action is needed to launch; this is a warning only.",
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return sections;
|
|
346
|
+
}
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// BAPI-1405 — epic-branch CI rows (warn-only)
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
/** Stable section ids for the three epic-branch CI rows. */
|
|
351
|
+
export const EPIC_BRANCH_CI_SECTION_IDS = {
|
|
352
|
+
reviewMode: "effective-review-mode",
|
|
353
|
+
decision: "epic-branch-ci-decision",
|
|
354
|
+
coverage: "epic-branch-ci-coverage-warning",
|
|
355
|
+
};
|
|
356
|
+
/** Row labels, shared by readiness and the install doctor. */
|
|
357
|
+
export const EPIC_BRANCH_CI_SECTION_LABELS = {
|
|
358
|
+
reviewMode: "Effective review mode",
|
|
359
|
+
decision: "Epic-branch CI decision",
|
|
360
|
+
coverage: "Epic-branch CI coverage warning",
|
|
361
|
+
};
|
|
362
|
+
/** What every row says when the server did not report the block. Never "no warning". */
|
|
363
|
+
export const EPIC_BRANCH_CI_UNREPORTED_DETAIL = "Unreported — upgrade the server";
|
|
364
|
+
/** The one remediation an unreported block carries. */
|
|
365
|
+
export const EPIC_BRANCH_CI_UNREPORTED_REMEDIATION = "upgrade the Bridge API deploy so it reports epic-branch CI facts; this does not hold a launch.";
|
|
366
|
+
/** The single next step for evidence that may be out of date or uncertain. */
|
|
367
|
+
export const EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE = "Rerun install after workflow or ruleset changes.";
|
|
368
|
+
/** Plain words for each effective review mode. */
|
|
369
|
+
const EFFECTIVE_REVIEW_MODE_WORDS = {
|
|
370
|
+
repository_policy: "repository_policy — no review verdict is required for epic-branch merges",
|
|
371
|
+
verdict_protocol: "verdict_protocol — child merges wait for the claude-review verdict",
|
|
372
|
+
native_review_decision: "native_review_decision — child merges wait for GitHub's own review decision",
|
|
373
|
+
none: "none — no review signal gates child merges",
|
|
374
|
+
};
|
|
375
|
+
/** Plain words for each known no-CI reason. An unknown reason renders generically. */
|
|
376
|
+
function describeNoCiReason(reason) {
|
|
377
|
+
switch (reason) {
|
|
378
|
+
case "gate_profile_none":
|
|
379
|
+
return "the gate profile is `none`, so no CI check gates child merges";
|
|
380
|
+
case "gate_profile_local_only":
|
|
381
|
+
return "the gate is local-only, so on a feature-branch run no provider check runs on epic-branch PRs";
|
|
382
|
+
case "triggers_do_not_cover_epic":
|
|
383
|
+
return "the evidence recorded at install says no pull_request trigger covers epic/**";
|
|
384
|
+
default:
|
|
385
|
+
return `the server reported a reason this CLI does not recognize (${reason})`;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/** One next step per known no-CI reason. */
|
|
389
|
+
function noCiReasonNextStep(reason) {
|
|
390
|
+
switch (reason) {
|
|
391
|
+
case "gate_profile_none":
|
|
392
|
+
return "add provider checks to the gate profile";
|
|
393
|
+
case "gate_profile_local_only":
|
|
394
|
+
return "add a provider check to the gate profile if epic-branch PRs should run CI";
|
|
395
|
+
case "triggers_do_not_cover_epic":
|
|
396
|
+
return "add epic/** to your CI workflow's pull_request branches filter, then rerun install";
|
|
397
|
+
default:
|
|
398
|
+
return "upgrade this CLI to read the server's reason";
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function yesNoUnknown(value) {
|
|
402
|
+
return value === null ? "could not tell" : value ? "yes" : "no";
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* The compact "recorded at install" evidence summary. Trigger coverage is
|
|
406
|
+
* worded as coverage, never as proof that CI runs.
|
|
407
|
+
*/
|
|
408
|
+
export function describeEpicBranchCiEvidence(evidence) {
|
|
409
|
+
return ("recorded at install: " +
|
|
410
|
+
`pull_request trigger covers epic/** ${yesNoUnknown(evidence.workflow_triggers_cover_epic)}; ` +
|
|
411
|
+
`approvals apply on epic/** ${yesNoUnknown(evidence.required_approvals_apply_to_epic)}; ` +
|
|
412
|
+
`observation quality ${evidence.observation_quality === "could_not_tell" ? "could not tell" : evidence.observation_quality}; ` +
|
|
413
|
+
`exemption outcome ${evidence.exemption_outcome === "not_exempt" ? "not exempt" : evidence.exemption_outcome}`);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* One reason per "could not tell" in the evidence, each in plain words. Empty
|
|
417
|
+
* when nothing is uncertain. The shared next step is
|
|
418
|
+
* {@link EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE}.
|
|
419
|
+
*/
|
|
420
|
+
export function epicBranchCiEvidenceUnknowns(evidence) {
|
|
421
|
+
const unknowns = [];
|
|
422
|
+
if (evidence.workflow_triggers_cover_epic === null) {
|
|
423
|
+
unknowns.push("trigger coverage could not tell: a workflow was not read confidently");
|
|
424
|
+
}
|
|
425
|
+
if (evidence.required_approvals_apply_to_epic === null) {
|
|
426
|
+
unknowns.push("approvals could not tell: the epic-branch rules were not readable");
|
|
427
|
+
}
|
|
428
|
+
if (evidence.observation_quality === "could_not_tell" &&
|
|
429
|
+
evidence.workflow_triggers_cover_epic !== null &&
|
|
430
|
+
evidence.required_approvals_apply_to_epic !== null) {
|
|
431
|
+
unknowns.push("observation quality could not tell: a finding was not confirmed");
|
|
432
|
+
}
|
|
433
|
+
if (evidence.exemption_outcome === "unknown") {
|
|
434
|
+
unknowns.push("exemption could not tell: the approval exemption was not confirmed");
|
|
435
|
+
}
|
|
436
|
+
return unknowns;
|
|
437
|
+
}
|
|
438
|
+
function effectiveReviewModeSection(block) {
|
|
439
|
+
const mode = block.effective_review_mode;
|
|
440
|
+
if (mode === null) {
|
|
441
|
+
return {
|
|
442
|
+
id: EPIC_BRANCH_CI_SECTION_IDS.reviewMode,
|
|
443
|
+
label: EPIC_BRANCH_CI_SECTION_LABELS.reviewMode,
|
|
444
|
+
status: "degraded",
|
|
445
|
+
detail: "the server reported a review mode this CLI does not recognize",
|
|
446
|
+
remediation: "upgrade this CLI so it can read the review mode; this does not hold a launch.",
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
id: EPIC_BRANCH_CI_SECTION_IDS.reviewMode,
|
|
451
|
+
label: EPIC_BRANCH_CI_SECTION_LABELS.reviewMode,
|
|
452
|
+
status: "ok",
|
|
453
|
+
detail: EFFECTIVE_REVIEW_MODE_WORDS[mode],
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function epicBranchCiDecisionSection(block) {
|
|
457
|
+
const choice = block.decision === "include"
|
|
458
|
+
? "Include — child merges wait for the epic-branch CI checks"
|
|
459
|
+
: block.decision === "decline"
|
|
460
|
+
? "Decline — child merges do not wait for CI checks on the epic branch; problems surface at the integration PR"
|
|
461
|
+
: "Not recorded — behaves like include";
|
|
462
|
+
const evidence = block.evidence;
|
|
463
|
+
const detail = evidence === null
|
|
464
|
+
? `${choice}; no evidence was recorded at install`
|
|
465
|
+
: `${choice}; ${describeEpicBranchCiEvidence(evidence)}`;
|
|
466
|
+
const unknowns = evidence === null ? [] : epicBranchCiEvidenceUnknowns(evidence);
|
|
467
|
+
if (unknowns.length > 0) {
|
|
468
|
+
return {
|
|
469
|
+
id: EPIC_BRANCH_CI_SECTION_IDS.decision,
|
|
470
|
+
label: EPIC_BRANCH_CI_SECTION_LABELS.decision,
|
|
471
|
+
status: "degraded",
|
|
472
|
+
detail,
|
|
473
|
+
remediation: `${unknowns.join("; ")}. ${EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE} This does not hold a launch.`,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
id: EPIC_BRANCH_CI_SECTION_IDS.decision,
|
|
478
|
+
label: EPIC_BRANCH_CI_SECTION_LABELS.decision,
|
|
479
|
+
status: "ok",
|
|
480
|
+
detail: block.decision === null
|
|
481
|
+
? `${detail}; run install to record a decision`
|
|
482
|
+
: evidence === null
|
|
483
|
+
? detail
|
|
484
|
+
: `${detail}. ${EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE}`,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function epicBranchCiCoverageSection(block) {
|
|
488
|
+
const reasons = block.no_ci_reasons;
|
|
489
|
+
if (!block.no_ci_warning && reasons.length === 0) {
|
|
490
|
+
const triggers = block.evidence?.workflow_triggers_cover_epic ?? null;
|
|
491
|
+
return {
|
|
492
|
+
id: EPIC_BRANCH_CI_SECTION_IDS.coverage,
|
|
493
|
+
label: EPIC_BRANCH_CI_SECTION_LABELS.coverage,
|
|
494
|
+
status: "ok",
|
|
495
|
+
detail: triggers === null
|
|
496
|
+
? "no warning from the gate profile; trigger coverage of epic/** was not recorded or could not be told at install"
|
|
497
|
+
: "no warning: the gate profile and the evidence recorded at install give no reason to expect epic-branch PRs to skip CI (trigger coverage does not guarantee CI runs)",
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const described = reasons.map(describeNoCiReason);
|
|
501
|
+
return {
|
|
502
|
+
id: EPIC_BRANCH_CI_SECTION_IDS.coverage,
|
|
503
|
+
label: EPIC_BRANCH_CI_SECTION_LABELS.coverage,
|
|
504
|
+
status: "degraded",
|
|
505
|
+
detail: described.length === 0
|
|
506
|
+
? "Warning: epic-branch PRs may run no CI (the server gave no reason)"
|
|
507
|
+
: `Warning: epic-branch PRs may run no CI — ${described.join("; ")}`,
|
|
508
|
+
remediation: (reasons.length === 0
|
|
509
|
+
? "review the gate profile and your CI workflow triggers"
|
|
510
|
+
: reasons.map(noCiReasonNextStep).join("; ")) + ". This warning does not hold a launch.",
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* BAPI-1405 — the three warn-only epic-branch CI rows: **Effective review
|
|
515
|
+
* mode**, **Epic-branch CI decision**, and **Epic-branch CI coverage warning**.
|
|
516
|
+
*
|
|
517
|
+
* Always three rows, so an older server is visible: an absent or `null` block
|
|
518
|
+
* renders every row as {@link EPIC_BRANCH_CI_UNREPORTED_DETAIL}, never as "no
|
|
519
|
+
* warning". Never `fatal`: none of these rows may change an admission verdict,
|
|
520
|
+
* a readiness verdict, or an exit status. Status is carried by words in the
|
|
521
|
+
* detail as well as by the status field, never by color alone.
|
|
522
|
+
*/
|
|
523
|
+
export function epicBranchCiSections(block) {
|
|
524
|
+
if (block === undefined || block === null) {
|
|
525
|
+
return ["reviewMode", "decision", "coverage"].map((key) => ({
|
|
526
|
+
id: EPIC_BRANCH_CI_SECTION_IDS[key],
|
|
527
|
+
label: EPIC_BRANCH_CI_SECTION_LABELS[key],
|
|
528
|
+
status: "degraded",
|
|
529
|
+
detail: EPIC_BRANCH_CI_UNREPORTED_DETAIL,
|
|
530
|
+
remediation: EPIC_BRANCH_CI_UNREPORTED_REMEDIATION,
|
|
531
|
+
}));
|
|
532
|
+
}
|
|
533
|
+
return [
|
|
534
|
+
effectiveReviewModeSection(block),
|
|
535
|
+
epicBranchCiDecisionSection(block),
|
|
536
|
+
epicBranchCiCoverageSection(block),
|
|
537
|
+
];
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* True when the server says the effective review mode is `repository_policy`,
|
|
541
|
+
* which requires no review verdict, so the claude-review workflow and the
|
|
542
|
+
* review-policy alignment rows have nothing to nag about. Absent block → false
|
|
543
|
+
* (older behavior is kept exactly).
|
|
544
|
+
*/
|
|
545
|
+
export function effectiveReviewModeIsRepositoryPolicy(readiness) {
|
|
546
|
+
return readiness?.epic_branch_ci?.effective_review_mode === "repository_policy";
|
|
547
|
+
}
|
|
548
|
+
// ---------------------------------------------------------------------------
|
|
200
549
|
// Section builders
|
|
201
550
|
// ---------------------------------------------------------------------------
|
|
202
551
|
export function supervisorSection(readiness) {
|
|
@@ -253,7 +602,18 @@ export function supervisorSection(readiness) {
|
|
|
253
602
|
// manufacture a finding out of a server that never answered.
|
|
254
603
|
const alignment = s.review_policy_alignment;
|
|
255
604
|
if (alignment !== null) {
|
|
256
|
-
if (alignment.status === "divergent"
|
|
605
|
+
if (alignment.status === "divergent" && effectiveReviewModeIsRepositoryPolicy(readiness)) {
|
|
606
|
+
// BAPI-1405 — `repository_policy` requires no review verdict, so a
|
|
607
|
+
// divergent done-gate review signal is not a nag here. `invalid` still
|
|
608
|
+
// warns: an unreadable review configuration is its own problem.
|
|
609
|
+
sections.push({
|
|
610
|
+
id: "supervisor-review-alignment",
|
|
611
|
+
label: "Supervisor review-policy alignment",
|
|
612
|
+
status: "ok",
|
|
613
|
+
detail: "not applicable: the effective review mode is 'repository_policy', which requires no review verdict",
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
else if (alignment.status === "divergent" || alignment.status === "invalid") {
|
|
257
617
|
sections.push({
|
|
258
618
|
id: "supervisor-review-alignment",
|
|
259
619
|
label: "Supervisor review-policy alignment",
|
|
@@ -279,6 +639,8 @@ export function supervisorSection(readiness) {
|
|
|
279
639
|
});
|
|
280
640
|
}
|
|
281
641
|
}
|
|
642
|
+
// BAPI-1390 — nothing at all when the server did not report a profile.
|
|
643
|
+
sections.push(...gateProfileSections(readiness.gate_profile));
|
|
282
644
|
return sections;
|
|
283
645
|
}
|
|
284
646
|
export function githubCredentialsSection(readiness) {
|
|
@@ -526,7 +888,9 @@ export function workflowSection(presence, reviewPolicySource, driftDetail) {
|
|
|
526
888
|
// never `absent`, and the policy-applicability branch below does not apply
|
|
527
889
|
// (a stale workflow only matters to a policy that consumes its verdict, and
|
|
528
890
|
// the caller only classifies drift when it does).
|
|
529
|
-
|
|
891
|
+
// BAPI-1405: under `repository_policy` no verdict is consumed, so a drifted
|
|
892
|
+
// file falls through to the not-applicable branch below instead of nagging.
|
|
893
|
+
if (presence === "drifted" && reviewPolicySource !== "repository_policy") {
|
|
530
894
|
return {
|
|
531
895
|
id: "claude-review-workflow",
|
|
532
896
|
label: "claude-review workflow",
|
|
@@ -540,7 +904,9 @@ export function workflowSection(presence, reviewPolicySource, driftDetail) {
|
|
|
540
904
|
// review decision does not need the Claude workflow at all, so its absence is
|
|
541
905
|
// not a gap. Reporting it as degraded there would train operators to ignore
|
|
542
906
|
// this section.
|
|
543
|
-
if (reviewPolicySource === "native_review_decision" ||
|
|
907
|
+
if (reviewPolicySource === "native_review_decision" ||
|
|
908
|
+
reviewPolicySource === "none" ||
|
|
909
|
+
reviewPolicySource === "repository_policy") {
|
|
544
910
|
return {
|
|
545
911
|
id: "claude-review-workflow",
|
|
546
912
|
label: "claude-review workflow",
|