@bridge_gpt/mcp-server 0.2.18 → 0.2.19

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.
Files changed (81) hide show
  1. package/CONDUCTOR.md +75 -0
  2. package/README.md +2 -2
  3. package/build/agent-capabilities/probe-context.js +13 -3
  4. package/build/agent-capabilities/probes.js +262 -11
  5. package/build/agent-capabilities/reporter.js +1 -0
  6. package/build/agents.generated.js +1 -1
  7. package/build/backend-warnings.js +44 -0
  8. package/build/claude-settings.js +129 -0
  9. package/build/commands.generated.js +1 -0
  10. package/build/conductor/bridge-api-client.js +7 -7
  11. package/build/conductor/cli.js +65 -12
  12. package/build/conductor/deny-enforcement-preflight.js +96 -0
  13. package/build/conductor/doctor.js +183 -2
  14. package/build/conductor/epic-reconcile.js +9 -1
  15. package/build/conductor/epic-runtime.js +403 -43
  16. package/build/conductor/epic-state.js +7 -0
  17. package/build/conductor/errors.js +115 -3
  18. package/build/conductor/event-accessors.js +28 -10
  19. package/build/conductor/merge-ledger.js +6 -4
  20. package/build/conductor/pr-ci-producer.js +17 -2
  21. package/build/conductor/producer-ledger.js +1 -1
  22. package/build/conductor/store.js +161 -18
  23. package/build/conductor/supervisor-merge.js +32 -5
  24. package/build/conductor/taxonomy.js +8 -0
  25. package/build/conductor/tools.js +28 -6
  26. package/build/conductor/worker-ledger-cli.js +244 -0
  27. package/build/conductor-bin.js +1884 -6917
  28. package/build/doctor.js +8 -0
  29. package/build/executor/cli.js +229 -0
  30. package/build/executor/credentials.js +65 -0
  31. package/build/executor/deps.js +117 -0
  32. package/build/executor/env.js +79 -0
  33. package/build/executor/heartbeat.js +59 -0
  34. package/build/executor/http-client.js +131 -0
  35. package/build/executor/index.js +10 -0
  36. package/build/executor/job-errors.js +55 -0
  37. package/build/executor/job-log-registry.js +110 -0
  38. package/build/executor/job-runner.js +688 -0
  39. package/build/executor/job-types.js +60 -0
  40. package/build/executor/merge-job.js +155 -0
  41. package/build/executor/observation.js +123 -0
  42. package/build/executor/permissions.js +79 -0
  43. package/build/executor/preflight.js +144 -0
  44. package/build/executor/process.js +81 -0
  45. package/build/executor/prompt-spec.js +235 -0
  46. package/build/executor/results.js +134 -0
  47. package/build/executor/resume-pre-spawn.js +179 -0
  48. package/build/executor/runner.js +98 -0
  49. package/build/executor/terminal-mutation.js +34 -0
  50. package/build/executor/test-clock.js +109 -0
  51. package/build/executor/types.js +18 -0
  52. package/build/executor/verdict-artifact.js +53 -0
  53. package/build/executor/viewer-tabs.js +78 -0
  54. package/build/executor/watch-cli.js +113 -0
  55. package/build/executor/worker-command.js +106 -0
  56. package/build/executor/worker-finalization.js +97 -0
  57. package/build/executor/worker-log.js +92 -0
  58. package/build/executor/worktree-gc.js +134 -0
  59. package/build/executor/worktree-inspection.js +86 -0
  60. package/build/executor/worktree.js +103 -0
  61. package/build/index.js +11222 -8544
  62. package/build/mcp-invoke.js +19 -3
  63. package/build/mcp-provisioning.js +31 -25
  64. package/build/mcp-registration-doctor.js +27 -7
  65. package/build/mcp-server-invocation.js +152 -0
  66. package/build/pipelines.generated.js +1 -1
  67. package/build/readme.generated.js +1 -1
  68. package/build/sfcc/reads-site-preference.js +52 -19
  69. package/build/start-tickets-conductor.js +25 -93
  70. package/build/start-tickets-prereqs.js +152 -1
  71. package/build/start-tickets.js +96 -158
  72. package/build/version.generated.js +1 -1
  73. package/build/visual-diff-worker.js +313 -0
  74. package/build/visual-diff.js +632 -0
  75. package/build/worktree-core.js +202 -0
  76. package/package.json +8 -4
  77. package/public/css/main.min.css +39 -0
  78. package/public/css/main.min.css.map +1 -1
  79. package/public/js/main.min.js +7924 -1
  80. package/public/js/main.min.js.map +1 -1
  81. package/smoke-test/SMOKE-TEST.md +2 -1
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Conservative executor worktree GC sweep (BAPI-535, TDD §7 teardown).
3
+ *
4
+ * Removes ONLY executor/conductor-managed worktrees whose branch is merged into
5
+ * the base branch (`git merge-base --is-ancestor <branch> origin/<base>`) and
6
+ * idle for more than 24h. CONSERVATIVE by construction: a worktree that is the
7
+ * base branch, unmanaged, dirty, recently active, on an unknown/detached branch,
8
+ * unmerged, or whose merged/idle status cannot be PROVEN is always retained. Any
9
+ * inability to prove eligibility retains the worktree; per-worktree failures
10
+ * never abort the whole sweep. All I/O is injected for offline unit testing.
11
+ */
12
+ import { listGitWorktrees, pathApiForExecutorPlatform, } from "./worktree-inspection.js";
13
+ /** Idle cutoff: a worktree must be idle strictly longer than 24h to be eligible. */
14
+ export const EXECUTOR_WORKTREE_GC_IDLE_MS = 24 * 60 * 60 * 1000;
15
+ function conductorMarkerPaths(worktreePath, platform) {
16
+ const pathApi = pathApiForExecutorPlatform(platform);
17
+ const dir = pathApi.join(worktreePath, ".conductor");
18
+ return {
19
+ dir,
20
+ workerLog: pathApi.join(dir, "worker.log"),
21
+ resultJson: pathApi.join(dir, "result.json"),
22
+ };
23
+ }
24
+ /**
25
+ * Conservatively identify an executor/conductor-managed worktree by the presence
26
+ * of a `.conductor` directory, `.conductor/worker.log`, or `.conductor/result.json`.
27
+ */
28
+ export async function isExecutorManagedWorktree(worktreePath, deps) {
29
+ const markers = conductorMarkerPaths(worktreePath, deps.platform ?? process.platform);
30
+ for (const marker of [markers.workerLog, markers.resultJson, markers.dir]) {
31
+ if ((await deps.statMtimeMs(marker)) !== null)
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ /**
37
+ * Compute last activity as the NEWEST mtime among `.conductor/worker.log`,
38
+ * `.conductor/result.json`, and the worktree directory itself. Returns `null`
39
+ * when none can be stat'd (activity cannot be proven → the caller retains).
40
+ */
41
+ export async function getWorktreeLastActivityMs(worktreePath, deps) {
42
+ const markers = conductorMarkerPaths(worktreePath, deps.platform ?? process.platform);
43
+ const candidates = [markers.workerLog, markers.resultJson, worktreePath];
44
+ let newest = null;
45
+ for (const candidate of candidates) {
46
+ const mtime = await deps.statMtimeMs(candidate);
47
+ if (mtime !== null && (newest === null || mtime > newest))
48
+ newest = mtime;
49
+ }
50
+ return newest;
51
+ }
52
+ /** Idle when the last activity is STRICTLY more than 24h before `now`. */
53
+ export function isWorktreeIdle(lastActivityMs, nowMs) {
54
+ return nowMs - lastActivityMs > EXECUTOR_WORKTREE_GC_IDLE_MS;
55
+ }
56
+ /** True only when `git merge-base --is-ancestor <branch> origin/<base>` exits 0. */
57
+ export async function isBranchMergedIntoBase(runCommand, cwd, branch, baseBranch) {
58
+ const result = await runCommand("git", ["merge-base", "--is-ancestor", branch, `origin/${baseBranch}`], { cwd });
59
+ return result.exitCode === 0;
60
+ }
61
+ /** True when `git status --porcelain` inside the worktree is non-empty. */
62
+ export async function isWorktreeDirty(runCommand, worktreePath) {
63
+ const result = await runCommand("git", ["status", "--porcelain"], { cwd: worktreePath });
64
+ return result.stdout.trim().length > 0;
65
+ }
66
+ /**
67
+ * Sweep worktrees, removing ONLY managed, clean, idle, non-base, merged
68
+ * worktrees. Everything else — including any worktree whose eligibility cannot be
69
+ * proven — is retained with a structured reason. Per-worktree failures are
70
+ * recorded and the sweep continues; the sweep never throws globally.
71
+ */
72
+ export async function sweepExecutorWorktrees(deps) {
73
+ const summary = { removed: [], retained: [], errors: [] };
74
+ const listWorktrees = deps.listWorktrees ?? listGitWorktrees;
75
+ const nowMs = deps.now();
76
+ let entries;
77
+ try {
78
+ entries = await listWorktrees(deps.runCommand, deps.cwd);
79
+ }
80
+ catch (err) {
81
+ summary.errors.push({ path: deps.cwd, error: err instanceof Error ? err.message : String(err) });
82
+ return summary;
83
+ }
84
+ for (const entry of entries) {
85
+ try {
86
+ if (!entry.branch) {
87
+ summary.retained.push({ path: entry.path, reason: "detached/unknown branch" });
88
+ continue;
89
+ }
90
+ if (entry.branch === deps.baseBranch) {
91
+ summary.retained.push({ path: entry.path, reason: "base branch" });
92
+ continue;
93
+ }
94
+ if (!(await isExecutorManagedWorktree(entry.path, deps))) {
95
+ summary.retained.push({ path: entry.path, reason: "unmanaged (no .conductor marker)" });
96
+ continue;
97
+ }
98
+ if (await isWorktreeDirty(deps.runCommand, entry.path)) {
99
+ summary.retained.push({ path: entry.path, reason: "dirty" });
100
+ continue;
101
+ }
102
+ const lastActivity = await getWorktreeLastActivityMs(entry.path, deps);
103
+ if (lastActivity === null) {
104
+ summary.retained.push({ path: entry.path, reason: "activity unprovable" });
105
+ continue;
106
+ }
107
+ if (!isWorktreeIdle(lastActivity, nowMs)) {
108
+ summary.retained.push({ path: entry.path, reason: "recent activity" });
109
+ continue;
110
+ }
111
+ if (!(await isBranchMergedIntoBase(deps.runCommand, deps.cwd, entry.branch, deps.baseBranch))) {
112
+ summary.retained.push({ path: entry.path, reason: "not merged into base" });
113
+ continue;
114
+ }
115
+ const removeResult = await deps.runCommand("git", ["worktree", "remove", entry.path], {
116
+ cwd: deps.cwd,
117
+ });
118
+ if (removeResult.exitCode === 0) {
119
+ summary.removed.push(entry.path);
120
+ }
121
+ else {
122
+ summary.errors.push({
123
+ path: entry.path,
124
+ error: (removeResult.stderr || removeResult.stdout || "git worktree remove failed").trim(),
125
+ });
126
+ }
127
+ }
128
+ catch (err) {
129
+ // Any failure to prove eligibility retains the worktree.
130
+ summary.errors.push({ path: entry.path, error: err instanceof Error ? err.message : String(err) });
131
+ }
132
+ }
133
+ return summary;
134
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Local worktree inspection utilities for executor job behaviors (BAPI-535).
3
+ *
4
+ * Safe, read-only Git worktree lookup, branch verification, and remote-branch
5
+ * probes used by the `resume` pre-spawn protocol and the worktree GC sweep. All
6
+ * subprocess access is behind the injected `RunCommand` boundary so every helper
7
+ * is unit-testable on Linux CI with no real `git`. NONE of these helpers ever
8
+ * mutates repository state (no reset/checkout/clean/branch writes).
9
+ */
10
+ import path from "node:path";
11
+ import { BranchMismatch, ExecutorNamedError } from "./job-errors.js";
12
+ /** Return the Node `path` API matching a platform (`win32` vs POSIX). */
13
+ export function pathApiForExecutorPlatform(platform) {
14
+ return platform === "win32" ? path.win32 : path.posix;
15
+ }
16
+ /**
17
+ * Parse `git worktree list --porcelain` output into entries. Each record is a
18
+ * blank-line-separated block whose first line is `worktree <path>`; a `branch
19
+ * refs/heads/<name>` line carries the local branch, which is normalized to the
20
+ * short branch name. Detached/no-branch worktrees are represented without a
21
+ * `branch` (never an invented one).
22
+ */
23
+ export function parseGitWorktreePorcelain(porcelain) {
24
+ const entries = [];
25
+ let current = null;
26
+ for (const rawLine of porcelain.split("\n")) {
27
+ const line = rawLine.replace(/\r$/, "");
28
+ if (line.startsWith("worktree ")) {
29
+ if (current)
30
+ entries.push(current);
31
+ current = { path: line.slice("worktree ".length).trim() };
32
+ }
33
+ else if (line.startsWith("branch ") && current) {
34
+ const ref = line.slice("branch ".length).trim();
35
+ current.branch = ref.startsWith("refs/heads/")
36
+ ? ref.slice("refs/heads/".length)
37
+ : ref;
38
+ }
39
+ // `detached`, `bare`, `HEAD <sha>`, and blank lines carry no branch.
40
+ }
41
+ if (current)
42
+ entries.push(current);
43
+ return entries;
44
+ }
45
+ /** Run `git worktree list --porcelain` from the repo root and parse the result. */
46
+ export async function listGitWorktrees(runCommand, cwd) {
47
+ const result = await runCommand("git", ["worktree", "list", "--porcelain"], { cwd });
48
+ if (result.exitCode !== 0)
49
+ return [];
50
+ return parseGitWorktreePorcelain(result.stdout);
51
+ }
52
+ /**
53
+ * Return the worktree path whose local branch EXACTLY equals `branch` (not a
54
+ * substring / prefix match), or `undefined` when none matches.
55
+ */
56
+ export function findWorktreeByBranch(entries, branch) {
57
+ const match = entries.find((e) => e.branch === branch);
58
+ return match?.path;
59
+ }
60
+ /** Run `git rev-parse --abbrev-ref HEAD` inside a worktree and return the branch. */
61
+ export async function getCurrentBranch(runCommand, worktreePath) {
62
+ const result = await runCommand("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
63
+ cwd: worktreePath,
64
+ });
65
+ return result.stdout.trim();
66
+ }
67
+ /**
68
+ * Verify a worktree is on the expected branch; throw {@link ExecutorNamedError}
69
+ * with kind {@link BranchMismatch} otherwise. Working on the wrong branch would
70
+ * corrupt an unrelated branch, so this aborts rather than proceeding (TDD §7).
71
+ */
72
+ export async function assertWorktreeOnBranch(runCommand, worktreePath, expectedBranch) {
73
+ const actual = await getCurrentBranch(runCommand, worktreePath);
74
+ if (actual !== expectedBranch) {
75
+ throw new ExecutorNamedError(BranchMismatch, `worktree '${worktreePath}' is on '${actual}', expected '${expectedBranch}'`);
76
+ }
77
+ }
78
+ /**
79
+ * Non-mutating probe for whether `origin/<branch>` exists. Uses
80
+ * `git rev-parse --verify --quiet origin/<branch>` (exit 0 ⇒ exists). Never
81
+ * fetches, never writes a ref.
82
+ */
83
+ export async function remoteBranchExists(runCommand, cwd, branch) {
84
+ const result = await runCommand("git", ["rev-parse", "--verify", "--quiet", `origin/${branch}`], { cwd });
85
+ return result.exitCode === 0;
86
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Executor worktree ensure (BAPI-534, TDD §7, §12).
3
+ *
4
+ * Real worker jobs run in a Worktrunk-created worktree via the SHARED primitive
5
+ * (`worktree-core.createWorktreeForTicket`) — no duplicated `wt switch` argument
6
+ * construction or Worktrunk JSON path parsing here. Per-job create failures are
7
+ * returned as structured job failures (→ `/fail`), never thrown.
8
+ */
9
+ import { commandSucceeded } from "../start-tickets-prereqs.js";
10
+ import { createWorktreeForTicket } from "../worktree-core.js";
11
+ /**
12
+ * Resolve the branch for a real spawn job: `expected_branch` wins; else
13
+ * `feature/<ticket_key>`; else a structured contract failure.
14
+ */
15
+ export function resolveExecutorBranch(job) {
16
+ const expected = typeof job.expected_branch === "string" && job.expected_branch.trim().length > 0
17
+ ? job.expected_branch.trim()
18
+ : null;
19
+ if (expected)
20
+ return { ok: true, branch: expected };
21
+ const ticketKey = typeof job.ticket_key === "string" && job.ticket_key.trim().length > 0
22
+ ? job.ticket_key.trim()
23
+ : null;
24
+ if (ticketKey)
25
+ return { ok: true, branch: `feature/${ticketKey}` };
26
+ return { ok: false, error: "no branch could be resolved (no expected_branch and no ticket_key)" };
27
+ }
28
+ /** Build the lean shared-worktree deps from the executor deps. */
29
+ function toWorktreeCoreDeps(deps) {
30
+ return {
31
+ runCommand: deps.runCommand,
32
+ platform: deps.platform,
33
+ env: deps.env,
34
+ cwd: deps.cwd,
35
+ };
36
+ }
37
+ /**
38
+ * Ensure the worktree for a job exists, delegating to the shared Worktrunk
39
+ * primitive. A create failure is returned as a structured failure that the job
40
+ * runner can send to `/fail`.
41
+ *
42
+ * `policy.reuseExistingBranch` selects between the two supported modes (see
43
+ * {@link EnsureExecutorWorktreePolicy}); it defaults to the fresh-off-base,
44
+ * F7-guarded behavior so callers that omit it are unchanged.
45
+ */
46
+ export async function ensureExecutorWorktree(job, options, deps, policy = {}) {
47
+ const branchResult = resolveExecutorBranch(job);
48
+ if (!branchResult.ok)
49
+ return { ok: false, error: branchResult.error };
50
+ const branch = branchResult.branch;
51
+ // The shared primitive resolves the branch from `key` via a per-key override,
52
+ // so we pin the exact resolved branch through the override map.
53
+ const key = typeof job.ticket_key === "string" && job.ticket_key.trim().length > 0
54
+ ? job.ticket_key.trim()
55
+ : branch;
56
+ const reuseExistingBranch = policy.reuseExistingBranch === true;
57
+ // The F7 guard applies to FRESH implementation-style jobs — refuse to reuse a
58
+ // stale pre-existing branch carrying commits not on base (mirrors the v1
59
+ // conductor's `!isResume` guard). RECOVERY jobs intentionally continue from the
60
+ // ticket's bound branch (BAPI-542): they cut from `origin/<branch>` — the pushed
61
+ // PR branch, so a missing local branch is recreated from it rather than from
62
+ // base — with the guard OFF, because that branch legitimately carries the
63
+ // implement commit and later correction rounds.
64
+ const baseStartPoint = reuseExistingBranch ? `origin/${branch}` : options.baseBranch;
65
+ const guardStaleWorktree = !reuseExistingBranch;
66
+ // BAPI-528: recovery correction is ALWAYS post-push (the PR exists), so the
67
+ // pushed head is the source of truth. Two gaps this closes:
68
+ // 1. A multi-consumer executor that only cloned/fetched main has NEITHER the
69
+ // local branch NOR the `origin/<branch>` tracking ref, so cutting from
70
+ // `origin/<branch>` cannot resolve its start point → WorktreeError → the
71
+ // run strands at code_review. Fetch the pushed head first so it can.
72
+ // 2. When a local branch already exists but has DIVERGED from origin, reusing
73
+ // it as-is would address review comments on stale code and force-push over
74
+ // newer origin commits. Freshen it onto `origin/<branch>` (below).
75
+ // A failed fetch surfaces as a structured job failure — never a silent stale
76
+ // reuse. (§16's "no fetch && reset --hard" rule is scoped to the RESUME path,
77
+ // which must preserve unpushed local work; it does not apply here.)
78
+ if (reuseExistingBranch) {
79
+ const originRef = `origin/${branch}`;
80
+ const fetch = await deps.runCommand("git", ["fetch", "origin", branch], { cwd: deps.cwd });
81
+ if (!commandSucceeded(fetch)) {
82
+ const reason = (fetch.stderr || fetch.stdout || "").trim();
83
+ return {
84
+ ok: false,
85
+ error: `git fetch origin ${branch} failed${reason ? `: ${reason}` : ""}`,
86
+ };
87
+ }
88
+ const row = await createWorktreeForTicket(toWorktreeCoreDeps(deps), key, { [key]: branch }, options.worktrunkBinary, baseStartPoint, guardStaleWorktree,
89
+ // Hard-reset a reused existing local branch onto the freshly fetched
90
+ // pushed head; a missing local branch is instead cut from `baseStartPoint`
91
+ // (= `origin/<branch>`) inside the primitive, so both land at origin.
92
+ { freshenFromOrigin: originRef });
93
+ if (row.status === "created" && typeof row.path === "string") {
94
+ return { ok: true, worktreePath: row.path, branch };
95
+ }
96
+ return { ok: false, error: row.error ?? `worktree creation failed for branch '${branch}'` };
97
+ }
98
+ const row = await createWorktreeForTicket(toWorktreeCoreDeps(deps), key, { [key]: branch }, options.worktrunkBinary, baseStartPoint, guardStaleWorktree);
99
+ if (row.status === "created" && typeof row.path === "string") {
100
+ return { ok: true, worktreePath: row.path, branch };
101
+ }
102
+ return { ok: false, error: row.error ?? `worktree creation failed for branch '${branch}'` };
103
+ }