@bridge_gpt/mcp-server 0.2.49 → 0.2.50
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 +24 -7
- package/build/base-ref.js +28 -3
- package/build/claude-review-workflow-drift-probe.js +130 -0
- package/build/claude-review-workflow-drift.js +173 -0
- package/build/claude-review-workflow.js +81 -16
- package/build/commands.generated.js +5 -5
- package/build/conductor/done-gate.js +25 -3
- package/build/conductor/install-doctor.js +65 -5
- package/build/conductor/latest-check-selector.js +170 -0
- package/build/conductor/local-merge.js +8 -6
- package/build/conductor-bin.js +1 -1
- package/build/{brainstorm-files.js → council-files.js} +15 -15
- package/build/decision-page-schema.js +1 -1
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +162 -4
- package/build/executor/worktree.js +46 -1
- package/build/index.js +92 -51
- package/build/init.js +9 -2
- package/build/install-bridge.js +60 -2
- package/build/install-reexec.js +47 -9
- package/build/pipelines.generated.js +1 -1
- package/build/plane/cli.js +12 -2
- package/build/plane/manifest.js +25 -1
- package/build/plane/member-roster.js +61 -7
- package/build/plane/preflight.js +24 -9
- package/build/plane/supervisor.js +77 -5
- package/build/plane/types.js +23 -3
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +2 -1
- package/build/stale-worktree-doctor.js +120 -0
- package/build/start-tickets-prereqs.js +70 -0
- package/build/start-tickets.js +91 -3
- package/build/version.generated.js +3 -2
- package/package.json +4 -2
- package/build/chain-orchestrator.js +0 -1457
- package/build/chain-utils.js +0 -68
- package/build/command-catalog.js +0 -376
- package/build/schedule-run.js +0 -1300
- package/build/schedule-store.js +0 -172
- package/build/scheduled-prompt.js +0 -115
- package/build/scheduler-backends/at-fallback.js +0 -139
- package/build/scheduler-backends/escaping.js +0 -143
- package/build/scheduler-backends/index.js +0 -72
- package/build/scheduler-backends/launchd.js +0 -225
- package/build/scheduler-backends/systemd-user.js +0 -250
- package/build/scheduler-backends/task-scheduler.js +0 -214
- package/build/scheduler-backends/types.js +0 -23
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only stale-branch diagnostic (BAPI-948).
|
|
3
|
+
*
|
|
4
|
+
* `start-tickets --guard-stale-branch` (and `--conductor`, which implies it) refuses
|
|
5
|
+
* to reuse a pre-existing `feature/<KEY>` branch whose tip is not an ancestor of the
|
|
6
|
+
* resolved base. That refusal is correct but it only tells you AFTER you tried to cut
|
|
7
|
+
* the worktree. This module answers the same question BEFORE the cut, and without
|
|
8
|
+
* touching a single ref.
|
|
9
|
+
*
|
|
10
|
+
* It owns no ancestry logic of its own. `branchExists` and
|
|
11
|
+
* `isExistingBranchSafeToReuse` from `./worktree-core.js` are the exact predicates the
|
|
12
|
+
* guard itself runs, so a `would-refuse` finding here and a `create-failed` row there
|
|
13
|
+
* can never disagree — and the operator sentence is the same sentence in both places.
|
|
14
|
+
* Deliberately NOT a second ahead-count / diff / merge-base implementation: a
|
|
15
|
+
* diagnostic that re-derives the condition is a diagnostic that can be wrong about it.
|
|
16
|
+
*
|
|
17
|
+
* Strictly read-only. The only commands it can reach are the two the predicates issue
|
|
18
|
+
* — `git show-ref --verify --quiet`, `git rev-parse --verify --quiet`, and
|
|
19
|
+
* `git merge-base --is-ancestor` — none of which mutate anything. There is no fetch,
|
|
20
|
+
* reset, switch, checkout, branch deletion, or worktree creation on any path here, and
|
|
21
|
+
* nothing in this module writes a file.
|
|
22
|
+
*/
|
|
23
|
+
import { branchExists, isExistingBranchSafeToReuse } from "./worktree-core.js";
|
|
24
|
+
/**
|
|
25
|
+
* The one sentence an `indeterminate` finding renders. Fixed text: the underlying
|
|
26
|
+
* throw is swallowed rather than formatted, so no probe failure can smuggle command
|
|
27
|
+
* output into a report.
|
|
28
|
+
*/
|
|
29
|
+
export const STALE_WORKTREE_INDETERMINATE_REASON = "the read-only branch check could not complete (git was unavailable or failed to run); " +
|
|
30
|
+
"re-run the diagnostic, or inspect the branch by hand with " +
|
|
31
|
+
"`git merge-base --is-ancestor <branch> <base>`.";
|
|
32
|
+
/**
|
|
33
|
+
* Diagnose each target independently, read-only.
|
|
34
|
+
*
|
|
35
|
+
* Independence is the point: one unreadable branch must not blank out its siblings'
|
|
36
|
+
* findings, which is why every target is wrapped on its own rather than the loop as a
|
|
37
|
+
* whole. Targets are evaluated in the order given so the rendered report is stable.
|
|
38
|
+
*/
|
|
39
|
+
export async function diagnoseStaleWorktreeBranches(deps, targets) {
|
|
40
|
+
const findings = [];
|
|
41
|
+
for (const target of targets) {
|
|
42
|
+
const { key, branch, baseStartPoint } = target;
|
|
43
|
+
try {
|
|
44
|
+
// No local branch → nothing pre-existing for the guard to act on. Return before
|
|
45
|
+
// the ancestry probe: running it would be a wasted command AND would produce a
|
|
46
|
+
// "not an ancestor" reason about a branch that is not there.
|
|
47
|
+
if (!(await branchExists(deps, branch))) {
|
|
48
|
+
findings.push({ key, branch, baseStartPoint, status: "branch-absent" });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// The SAME predicate `createWorktreeForTicket` runs, with the same branch and
|
|
52
|
+
// the same base start point, so the verdict and its remedy are identical to the
|
|
53
|
+
// refusal the guarded cut would emit.
|
|
54
|
+
const safety = await isExistingBranchSafeToReuse(deps, branch, baseStartPoint);
|
|
55
|
+
if (safety.safe) {
|
|
56
|
+
findings.push({ key, branch, baseStartPoint, status: "safe" });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
findings.push({
|
|
60
|
+
key,
|
|
61
|
+
branch,
|
|
62
|
+
baseStartPoint,
|
|
63
|
+
status: "would-refuse",
|
|
64
|
+
reason: safety.reason,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
findings.push({
|
|
69
|
+
key,
|
|
70
|
+
branch,
|
|
71
|
+
baseStartPoint,
|
|
72
|
+
status: "indeterminate",
|
|
73
|
+
reason: STALE_WORKTREE_INDETERMINATE_REASON,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return findings;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Render the advisory section. Advisory in the strict doctor sense: the caller never
|
|
81
|
+
* derives an exit code from it.
|
|
82
|
+
*
|
|
83
|
+
* One row per target, each naming the ticket, the resolved branch, and the base it was
|
|
84
|
+
* measured against — an operator diagnosing an epic cut needs all three to act. A
|
|
85
|
+
* `WOULD REFUSE` row is followed by the predicate's remedy on its own indented line
|
|
86
|
+
* rather than being folded into the row, because that sentence is the thing the
|
|
87
|
+
* operator has to read.
|
|
88
|
+
*/
|
|
89
|
+
export function formatStaleWorktreeDiagnosticReport(findings) {
|
|
90
|
+
const lines = ["", "Stale ticket branches (advisory)"];
|
|
91
|
+
if (findings.length === 0) {
|
|
92
|
+
lines.push(" SKIP No --stale-branch targets supplied; nothing was checked.");
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
95
|
+
for (const finding of findings) {
|
|
96
|
+
const where = `${finding.key}: ${finding.branch} vs ${finding.baseStartPoint}`;
|
|
97
|
+
switch (finding.status) {
|
|
98
|
+
case "branch-absent":
|
|
99
|
+
lines.push(` OK ${where} — no local branch; a fresh one is cut from the base.`);
|
|
100
|
+
break;
|
|
101
|
+
case "safe":
|
|
102
|
+
lines.push(` OK ${where} — branch is an ancestor of the base; safe to reuse.`);
|
|
103
|
+
break;
|
|
104
|
+
case "would-refuse":
|
|
105
|
+
lines.push(` WARN ${where} — WOULD REFUSE.`);
|
|
106
|
+
if (finding.reason)
|
|
107
|
+
lines.push(` ${finding.reason}`);
|
|
108
|
+
break;
|
|
109
|
+
case "indeterminate":
|
|
110
|
+
lines.push(` WARN ${where} — indeterminate.`);
|
|
111
|
+
if (finding.reason)
|
|
112
|
+
lines.push(` ${finding.reason}`);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
lines.push(" Read-only: this section reads current local refs only. It never fetches,");
|
|
117
|
+
lines.push(" resets, switches, deletes, or repairs any git state, and never changes the");
|
|
118
|
+
lines.push(" exit code.");
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
@@ -19,6 +19,11 @@ import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
|
19
19
|
import { probeWorktreeMcpRegistration } from "./mcp-registration-doctor.js";
|
|
20
20
|
import { probeWorktreeCommandAssets } from "./command-assets-doctor.js";
|
|
21
21
|
import { MCP_SERVER_NAME, MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
22
|
+
// BAPI-941: both are LEAF modules (the pure classifier and its git-backed
|
|
23
|
+
// acquisition), so importing them here cannot close a cycle with `base-ref.ts`,
|
|
24
|
+
// which imports `commandSucceeded` from this module.
|
|
25
|
+
import { CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION, CLAUDE_REVIEW_WORKFLOW_RELPATH, summarizeClaudeReviewWorkflowDrift, } from "./claude-review-workflow-drift.js";
|
|
26
|
+
import { probeClaudeReviewWorkflowDrift, resolveCurrentBranch, resolveRepositoryDefaultBranch, } from "./claude-review-workflow-drift-probe.js";
|
|
22
27
|
import { CLAUDE_MCP_SHADOWING_REMEDIATION_COMMAND, claudeMcpShadowingRemediationCommand, formatClaudeMcpShadowFinding, formatClaudeUserConfigDiagnostic, inspectClaudeUserConfigForMcpShadowing, resolveClaudeUserConfigPath, } from "./claude-user-config-doctor.js";
|
|
23
28
|
// ---------------------------------------------------------------------------
|
|
24
29
|
// Constants (moved here from start-tickets.ts so both consumers share them)
|
|
@@ -717,6 +722,67 @@ export function liveSourceGuardDescriptor() {
|
|
|
717
722
|
},
|
|
718
723
|
};
|
|
719
724
|
}
|
|
725
|
+
/** Secret-free remediation hint for the workflow-drift diagnostic. */
|
|
726
|
+
const CLAUDE_REVIEW_WORKFLOW_DRIFT_INSTALL_HINTS = {
|
|
727
|
+
darwin: CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION,
|
|
728
|
+
linux: CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION,
|
|
729
|
+
win32: CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION,
|
|
730
|
+
};
|
|
731
|
+
/**
|
|
732
|
+
* BAPI-941 doctor-only, strictly read-only probe: does the current branch's copy
|
|
733
|
+
* of the reviewed workflow match the repository default branch's copy?
|
|
734
|
+
*
|
|
735
|
+
* Answers, without spawning a worker or creating a worktree, the question that
|
|
736
|
+
* otherwise only surfaces an hour into a conductor run: has this branch gone
|
|
737
|
+
* blind to automated code review? `anthropics/claude-code-action` refuses to run
|
|
738
|
+
* when the invoking workflow differs from the default branch's copy, and a
|
|
739
|
+
* long-lived `epic/**` branch inherits that staleness into every pull request
|
|
740
|
+
* based on it — while the pull requests themselves change no workflow file at
|
|
741
|
+
* all, so no path-based guard fires.
|
|
742
|
+
*
|
|
743
|
+
* **Read-only, and network-free.** It runs `git symbolic-ref`, `git rev-parse`,
|
|
744
|
+
* and two `git show` invocations against the local object database. It never
|
|
745
|
+
* fetches, checks out, installs, migrates, creates a worktree, or writes a
|
|
746
|
+
* credential.
|
|
747
|
+
*
|
|
748
|
+
* **Never blocking.** `doctor`'s exit code is
|
|
749
|
+
* `results.some((r) => !r.found) ? 1 : 0`, so this probe reports `found: true`
|
|
750
|
+
* for ALL THREE outcomes and carries the state in `detail`. Confirmed drift is an
|
|
751
|
+
* advisory an operator should act on, not a missing prerequisite that fails the
|
|
752
|
+
* command — and an unavailable comparison is uncertainty, which is even less of
|
|
753
|
+
* a fault. Registered in `getDoctorOnlyPrereqDescriptors`, never in
|
|
754
|
+
* `getPreflightPrereqDescriptors`.
|
|
755
|
+
*
|
|
756
|
+
* **Bounded output.** Every rendered string comes from the shared classifier's
|
|
757
|
+
* closed vocabulary plus refs and the workflow path. Subprocess stderr, GitHub
|
|
758
|
+
* exception text, and file content never reach the report.
|
|
759
|
+
*/
|
|
760
|
+
export function claudeReviewWorkflowDriftDescriptor() {
|
|
761
|
+
return {
|
|
762
|
+
id: "claude-review-workflow-drift",
|
|
763
|
+
label: "claude-review workflow lineage",
|
|
764
|
+
installHint: CLAUDE_REVIEW_WORKFLOW_DRIFT_INSTALL_HINTS,
|
|
765
|
+
probe: async (deps) => {
|
|
766
|
+
const probeDeps = { runCommand: deps.runCommand, cwd: deps.cwd };
|
|
767
|
+
const baseRef = await resolveCurrentBranch(probeDeps);
|
|
768
|
+
if (!baseRef) {
|
|
769
|
+
// A detached HEAD has no branch whose lineage to report. Inconclusive,
|
|
770
|
+
// not a fault.
|
|
771
|
+
return {
|
|
772
|
+
found: true,
|
|
773
|
+
detail: `comparison unavailable (base_ref_unresolved) — advisory check failed open, ` +
|
|
774
|
+
`${CLAUDE_REVIEW_WORKFLOW_RELPATH} lineage unknown`,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
const defaultRef = await resolveRepositoryDefaultBranch(probeDeps);
|
|
778
|
+
const classification = await probeClaudeReviewWorkflowDrift(probeDeps, {
|
|
779
|
+
baseRef,
|
|
780
|
+
defaultRef,
|
|
781
|
+
});
|
|
782
|
+
return { found: true, detail: summarizeClaudeReviewWorkflowDrift(classification) };
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
}
|
|
720
786
|
/**
|
|
721
787
|
* The exact existing live-preflight requirement set per platform, plus the git
|
|
722
788
|
* work-tree custom check appended after the command checks. This is the ONLY
|
|
@@ -765,6 +831,10 @@ export function getDoctorOnlyPrereqDescriptors(_platform, _env, agent) {
|
|
|
765
831
|
reviewTicketsGitDescriptor(),
|
|
766
832
|
liveSourceGuardDescriptor(),
|
|
767
833
|
claudeMcpShadowingDescriptor(),
|
|
834
|
+
// BAPI-941: advisory workflow-lineage diagnostic. Doctor-only by design —
|
|
835
|
+
// adding it to `getPreflightPrereqDescriptors` would make a stale base
|
|
836
|
+
// branch block worktree creation, which is the opposite of fail-open.
|
|
837
|
+
claudeReviewWorkflowDriftDescriptor(),
|
|
768
838
|
];
|
|
769
839
|
}
|
|
770
840
|
/**
|
package/build/start-tickets.js
CHANGED
|
@@ -60,6 +60,9 @@ import path from "path";
|
|
|
60
60
|
import { VERSION } from "./version.generated.js";
|
|
61
61
|
import { resolveBapiCredentials, getPrimaryCredentialStorePath, } from "./credential-store.js";
|
|
62
62
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
63
|
+
// BAPI-941: the shared drift diagnostic formatter. The leaf drift module imports
|
|
64
|
+
// nothing from here, so this direction stays acyclic.
|
|
65
|
+
import { formatClaudeReviewWorkflowDriftDiagnostic } from "./claude-review-workflow-drift.js";
|
|
63
66
|
import { resolveStartTicketsRepoName as resolveSharedStartTicketsRepoName, resolveRequiredStartTicketsRepoName, } from "./start-tickets-repo.js";
|
|
64
67
|
import { provisionMcpRegistrationsForCreatedWorktrees, } from "./mcp-provisioning.js";
|
|
65
68
|
import { provisionCommandsForCreatedWorktrees, } from "./command-provisioning.js";
|
|
@@ -132,7 +135,8 @@ export function getStartTicketsUsage() {
|
|
|
132
135
|
" --base-branch BRANCH Cut new worktrees from BRANCH and refresh origin/BRANCH (default: main)",
|
|
133
136
|
" --no-refresh-main Skip refresh of the configured base branch (default main); historical name retained for backward compatibility",
|
|
134
137
|
" --max-parallel N Max worktrees to create concurrently (default: 3)",
|
|
135
|
-
" --conductor Enable the Conductor system: per-worker hook injection + BAPI_CONDUCTOR_* env, lifecycle ledger events, and a supervisor peer tab (default: off — a plain run just spawns cd <worktree> && <agent> '/implement-ticket <KEY>')",
|
|
138
|
+
" --conductor Enable the Conductor system: per-worker hook injection + BAPI_CONDUCTOR_* env, lifecycle ledger events, and a supervisor peer tab (default: off — a plain run just spawns cd <worktree> && <agent> '/implement-ticket <KEY>'). Implies --guard-stale-branch: a pre-existing ticket branch carrying commits beyond the resolved base is refused, not silently reused.",
|
|
139
|
+
" --guard-stale-branch Refuse to reuse a pre-existing feature/KEY branch whose tip is NOT an ancestor of the resolved base (it carries commits the base does not). The ticket is reported create-failed with the stale-worktree remedy instead of spawning a worker on leftover code. Implied by --conductor; pass it explicitly on the LLM-conductor pilot cut path. Off by default, so a plain interactive run keeps reusing a same-named branch.",
|
|
136
140
|
" -h, --help Show this help",
|
|
137
141
|
"",
|
|
138
142
|
"Environment:",
|
|
@@ -176,6 +180,11 @@ export function parseStartTicketsArgs(argv) {
|
|
|
176
180
|
let agentName = DEFAULT_AGENT_NAME;
|
|
177
181
|
let baseBranch = "main";
|
|
178
182
|
let conductorEnabled = false;
|
|
183
|
+
// BAPI-948: `--guard-stale-branch` (and `--conductor`, which implies it) opt the
|
|
184
|
+
// packaged CLI into the F7 stale-branch guard. Tracked separately from
|
|
185
|
+
// `conductorEnabled` so the pilot can take the safety check WITHOUT taking the
|
|
186
|
+
// v2 conductor context that `--conductor` also switches on.
|
|
187
|
+
let guardStaleBranch = false;
|
|
179
188
|
let workflow = "implement";
|
|
180
189
|
let reviewRoundsRaw;
|
|
181
190
|
// BAPI-642 `--tier` override. `undefined` = flag absent (property omitted from
|
|
@@ -358,6 +367,10 @@ export function parseStartTicketsArgs(argv) {
|
|
|
358
367
|
conductorEnabled = true;
|
|
359
368
|
continue;
|
|
360
369
|
}
|
|
370
|
+
if (arg === "--guard-stale-branch") {
|
|
371
|
+
guardStaleBranch = true;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
361
374
|
if (arg === "--no-refresh-main") {
|
|
362
375
|
refreshMain = false;
|
|
363
376
|
continue;
|
|
@@ -454,6 +467,11 @@ export function parseStartTicketsArgs(argv) {
|
|
|
454
467
|
conductorEnabled,
|
|
455
468
|
workflow,
|
|
456
469
|
reviewRounds,
|
|
470
|
+
// BAPI-948: include `guardStaleWorktree` ONLY when the run asked for the F7
|
|
471
|
+
// guard — `--guard-stale-branch`, or `--conductor` which implies it. A plain
|
|
472
|
+
// interactive invocation leaves the property entirely absent (not `false`),
|
|
473
|
+
// so the reuse-friendly human option shape is byte-identical to before.
|
|
474
|
+
...(guardStaleBranch || conductorEnabled ? { guardStaleWorktree: true } : {}),
|
|
457
475
|
// BAPI-642: include the injected-tier property ONLY when `--tier` was
|
|
458
476
|
// supplied, so an omitted flag leaves the legacy option shape untouched
|
|
459
477
|
// (no own `injectedTier` key, not even `undefined`).
|
|
@@ -834,9 +852,23 @@ export async function refreshBaseBranch(deps, options) {
|
|
|
834
852
|
return { ok: true };
|
|
835
853
|
const baseBranch = options.baseBranch;
|
|
836
854
|
const originRef = `origin/${baseBranch}`;
|
|
837
|
-
|
|
855
|
+
// BAPI-941: refresh the DEFAULT branch alongside the base in ONE invocation so
|
|
856
|
+
// the caller's workflow-drift advisory reads an up-to-date `origin/<default>`
|
|
857
|
+
// without a second network round trip. Deduplicated when they are the same
|
|
858
|
+
// branch, and retried without the extra refspec on failure so this enrichment
|
|
859
|
+
// can never convert a working refresh into an error.
|
|
860
|
+
const defaultBranch = await resolveRepositoryDefaultBranch(deps);
|
|
861
|
+
const extraRefs = defaultBranch && defaultBranch !== baseBranch && validateBranchName(defaultBranch) === null
|
|
862
|
+
? [defaultBranch]
|
|
863
|
+
: [];
|
|
864
|
+
let fetch = await deps.runCommand("git", ["fetch", "origin", baseBranch, ...extraRefs], {
|
|
838
865
|
cwd: deps.cwd,
|
|
839
866
|
});
|
|
867
|
+
if (!commandSucceeded(fetch) && extraRefs.length > 0) {
|
|
868
|
+
fetch = await deps.runCommand("git", ["fetch", "origin", baseBranch], {
|
|
869
|
+
cwd: deps.cwd,
|
|
870
|
+
});
|
|
871
|
+
}
|
|
840
872
|
if (!commandSucceeded(fetch)) {
|
|
841
873
|
return {
|
|
842
874
|
ok: false,
|
|
@@ -934,12 +966,49 @@ export async function runWithConcurrency(items, limit, worker) {
|
|
|
934
966
|
// same function references.
|
|
935
967
|
import { resolveBranchForTicket, branchExists, buildWtSwitchArgs, pathApiForPlatform, extractWorktreePath, isExistingBranchSafeToReuse, createWorktreeForTicket, } from "./worktree-core.js";
|
|
936
968
|
export { resolveBranchForTicket, branchExists, buildWtSwitchArgs, pathApiForPlatform, extractWorktreePath, isExistingBranchSafeToReuse, createWorktreeForTicket, };
|
|
969
|
+
/**
|
|
970
|
+
* BAPI-941: emit the per-spawn `claude-review` workflow-drift advisory.
|
|
971
|
+
*
|
|
972
|
+
* Called once at the shared post-base-resolution, pre-worktree choke point. It
|
|
973
|
+
* returns `void` and swallows everything: this diagnostic exists to tell an
|
|
974
|
+
* operator that their epic branch has gone blind to code review, and a
|
|
975
|
+
* diagnostic that can abort the run it is describing would be strictly worse
|
|
976
|
+
* than no diagnostic at all.
|
|
977
|
+
*
|
|
978
|
+
* All three outcomes continue to worktree creation:
|
|
979
|
+
*
|
|
980
|
+
* - `drifted` — a WARNING naming the stale base and the remedy, and saying
|
|
981
|
+
* plainly that work continues.
|
|
982
|
+
* - `unverified` — an INFO line naming which comparison was unavailable
|
|
983
|
+
* (missing ref, unreadable workflow, unresolved default branch).
|
|
984
|
+
* - `aligned` — silence. A clean spawn prints nothing and performs no
|
|
985
|
+
* additional network work beyond the fetch that already happened.
|
|
986
|
+
*/
|
|
987
|
+
async function emitClaudeReviewWorkflowDriftAdvisory(deps, baseBranch, overrides) {
|
|
988
|
+
const warn = overrides.workflowDriftWarningLog ?? ((m) => console.error(m));
|
|
989
|
+
try {
|
|
990
|
+
const probe = overrides.probeWorkflowDrift ?? probeClaudeReviewWorkflowDrift;
|
|
991
|
+
const defaultRef = await resolveRepositoryDefaultBranch(deps);
|
|
992
|
+
const classification = await probe(deps, { baseRef: baseBranch, defaultRef });
|
|
993
|
+
if (classification.state === "aligned")
|
|
994
|
+
return;
|
|
995
|
+
warn(formatClaudeReviewWorkflowDriftDiagnostic(classification));
|
|
996
|
+
}
|
|
997
|
+
catch {
|
|
998
|
+
// A thrown probe is itself an unavailable comparison. Stay silent about the
|
|
999
|
+
// exception (it can echo absolute paths and provider text) and never let it
|
|
1000
|
+
// reach the caller.
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
937
1003
|
// BAPI-586: base-ref resolution now lives in the focused `base-ref.ts` module so
|
|
938
1004
|
// the executor can depend on it without pulling in this large CLI module. We
|
|
939
1005
|
// import the VALUES here (so internal uses keep working) and re-export the same
|
|
940
1006
|
// references so existing importers of `./start-tickets.js` (index.ts,
|
|
941
1007
|
// review-tickets.ts) and the pinned start-tickets tests are unaffected.
|
|
942
|
-
import { validateBranchName, fetchAndResolveBaseSha
|
|
1008
|
+
import { validateBranchName, fetchAndResolveBaseSha,
|
|
1009
|
+
// BAPI-941: both live in the same leaf module so the interactive refresh path
|
|
1010
|
+
// and the non-mutating conductor path share one drift implementation.
|
|
1011
|
+
resolveRepositoryDefaultBranch, probeClaudeReviewWorkflowDrift, } from "./base-ref.js";
|
|
943
1012
|
export { validateBranchName, fetchAndResolveBaseSha };
|
|
944
1013
|
/**
|
|
945
1014
|
* Create / switch worktrees for every ticket, throttled to `maxParallel`, using
|
|
@@ -2898,6 +2967,22 @@ export async function orchestrateStartTickets(deps, options, overrides = {}) {
|
|
|
2898
2967
|
if (!refresh.ok)
|
|
2899
2968
|
return { ok: false, error: refresh.error };
|
|
2900
2969
|
}
|
|
2970
|
+
// BAPI-941: the `claude-review` workflow-drift advisory, emitted ONCE per
|
|
2971
|
+
// worker spawn at the one choke point both dispatch paths pass through, after
|
|
2972
|
+
// the base is resolved and before any worktree exists.
|
|
2973
|
+
//
|
|
2974
|
+
// Per-spawn timing is the whole point. `epic/BAPI-902` was cut clean and was
|
|
2975
|
+
// blinded 24 hours later by an unrelated commit on `main`, so a check that ran
|
|
2976
|
+
// only at `init`/`setup-epic` time would have passed and still let every
|
|
2977
|
+
// subsequent PR go unreviewed. Both refs were just refreshed by the fetch
|
|
2978
|
+
// above, so the comparison is local and costs no extra round trip.
|
|
2979
|
+
//
|
|
2980
|
+
// ADVISORY ONLY, and fail-open in every degraded case (unreachable remote,
|
|
2981
|
+
// missing ref, unresolvable default branch): it prints to stderr and the run
|
|
2982
|
+
// continues to worktree creation. It is not a `RunPolicy` key and not a
|
|
2983
|
+
// worker-emitted control signal — R14 rules 2 and 3 are about the conductor's
|
|
2984
|
+
// decision surface, and a spawner's warning is neither.
|
|
2985
|
+
await emitClaudeReviewWorkflowDriftAdvisory(deps, options.baseBranch, overrides);
|
|
2901
2986
|
const createWorktreesFn = overrides.createWorktrees ?? createWorktrees;
|
|
2902
2987
|
const provisionCommandsFn = overrides.provisionCommands ??
|
|
2903
2988
|
((rows, d) => provisionCommandsForCreatedWorktrees(rows, buildCommandProvisioningDeps(d)));
|
|
@@ -3113,6 +3198,9 @@ export async function runStartTicketsCli(argv, overrides = {}) {
|
|
|
3113
3198
|
modelRoutingLog: log,
|
|
3114
3199
|
modelRoutingWarningLog: errorLog,
|
|
3115
3200
|
repoNameWarningLog: errorLog,
|
|
3201
|
+
// BAPI-941: the workflow-drift advisory goes to stderr like every other
|
|
3202
|
+
// spawner diagnostic, so it never contaminates the summary on stdout.
|
|
3203
|
+
workflowDriftWarningLog: errorLog,
|
|
3116
3204
|
// Conductor is OPT-IN (BAPI-394): the three seams are supplied ONLY when the
|
|
3117
3205
|
// user passes `--conductor`. Supplying `createConductorContext` activates the
|
|
3118
3206
|
// conductor stage inside orchestrate (env injection + supervisor tab); the
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
|
|
2
|
-
export const VERSION = "0.2.
|
|
3
|
-
export const BUILD_COMMIT = "
|
|
2
|
+
export const VERSION = "0.2.50";
|
|
3
|
+
export const BUILD_COMMIT = "4b5d3e65b2a3";
|
|
4
|
+
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.50", "serve"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge_gpt/mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.50",
|
|
4
4
|
"description": "Bridge API MCP server — exposes Jira endpoints as MCP tools for Claude Code agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,8 +21,10 @@
|
|
|
21
21
|
"LICENSE"
|
|
22
22
|
],
|
|
23
23
|
"//test": "The `test` script delegates to the bounded launcher (scripts/run-unit-tests.js → src/run-unit-tests-launcher.ts, BAPI-683), which discovers build/*.test.js at runtime and spawns them in size-bounded batches so the argv never exceeds Windows' ~8191-char command-line limit. Two determinism rules from the pre-launcher script are preserved INSIDE the launcher: (1) multi-file batches run WITHOUT --test-force-exit, because that flag makes node:test exit before aggregating per-subprocess summaries and the tally becomes nondeterministic (failures still surface and still set exit 1, but the count cannot be trusted as a completeness signal); (2) build/secret-safety.test.js leaks a handle that holds the event loop ~60s after its tests finish (they run in ~147ms), so the launcher quarantines it into its own single-file --test-force-exit invocation (FORCE_EXIT_QUARANTINE in src/run-unit-tests-launcher.ts) where the tally stays exact. Do NOT add --test-force-exit to batched invocations, and do NOT remove the quarantine, until the underlying handle leak is fixed.",
|
|
24
|
+
"//version": "BAPI-930: fired by `npm version <level>` AFTER npm rewrites package.json's version field, so the bumped version and the repinned README land in the SAME commit. A bump performed by hand-editing package.json instead of running `npm version` bypasses this hook entirely and must repin manually via `node scripts/sync-readme-host-examples.js` — the `--check` wired into `build` below is the backstop that catches that case.",
|
|
24
25
|
"scripts": {
|
|
25
|
-
"build": "node scripts/bundle-version.js && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
|
|
26
|
+
"build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
|
|
27
|
+
"version": "node scripts/sync-readme-host-examples.js --write && git add -- README.md package.json package-lock.json && git commit -m \"chore(mcp-server): release $npm_package_version\" && git tag \"mcp-server/v$npm_package_version\"",
|
|
26
28
|
"check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
|
|
27
29
|
"postbuild": "node scripts/prepend-shebang.cjs",
|
|
28
30
|
"start": "node build/index.js",
|