@bridge_gpt/mcp-server 0.2.16 → 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 (103) 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 +3 -3
  7. package/build/backend-warnings.js +44 -0
  8. package/build/claude-settings.js +129 -0
  9. package/build/commands.generated.js +7 -6
  10. package/build/conductor/bridge-api-client.js +198 -18
  11. package/build/conductor/claude-hook.js +22 -4
  12. package/build/conductor/cli.js +76 -25
  13. package/build/conductor/deny-enforcement-preflight.js +96 -0
  14. package/build/conductor/doctor.js +183 -2
  15. package/build/conductor/done-gate.js +5 -0
  16. package/build/conductor/epic-reconcile.js +71 -14
  17. package/build/conductor/epic-runtime.js +839 -67
  18. package/build/conductor/epic-state.js +524 -63
  19. package/build/conductor/errors.js +156 -3
  20. package/build/conductor/event-accessors.js +252 -0
  21. package/build/conductor/file-scope-guard.js +201 -0
  22. package/build/conductor/github-mergeability.js +85 -0
  23. package/build/conductor/local-merge.js +47 -1
  24. package/build/conductor/merge-identity.js +41 -0
  25. package/build/conductor/merge-ledger.js +19 -72
  26. package/build/conductor/plan.js +12 -2
  27. package/build/conductor/pr-ci-producer.js +17 -2
  28. package/build/conductor/pr-discovery.js +11 -1
  29. package/build/conductor/producer-ledger.js +1 -1
  30. package/build/conductor/store.js +161 -18
  31. package/build/conductor/supervisor-config.js +4 -39
  32. package/build/conductor/supervisor-escalation.js +10 -26
  33. package/build/conductor/supervisor-ledger.js +5 -12
  34. package/build/conductor/supervisor-merge.js +32 -5
  35. package/build/conductor/supervisor-message-relay.js +2 -5
  36. package/build/conductor/supervisor-notification.js +1 -1
  37. package/build/conductor/supervisor-runtime.js +12 -54
  38. package/build/conductor/supervisor-state.js +4 -18
  39. package/build/conductor/supervisor-types.js +2 -2
  40. package/build/conductor/taxonomy.js +12 -0
  41. package/build/conductor/tools.js +28 -6
  42. package/build/conductor/worker-ledger-cli.js +244 -0
  43. package/build/conductor-bin.js +1800 -5166
  44. package/build/conductor-claude-hook-bin.js +4 -2
  45. package/build/doctor.js +40 -0
  46. package/build/executor/cli.js +229 -0
  47. package/build/executor/credentials.js +65 -0
  48. package/build/executor/deps.js +117 -0
  49. package/build/executor/env.js +79 -0
  50. package/build/executor/heartbeat.js +59 -0
  51. package/build/executor/http-client.js +131 -0
  52. package/build/executor/index.js +10 -0
  53. package/build/executor/job-errors.js +55 -0
  54. package/build/executor/job-log-registry.js +110 -0
  55. package/build/executor/job-runner.js +688 -0
  56. package/build/executor/job-types.js +60 -0
  57. package/build/executor/merge-job.js +155 -0
  58. package/build/executor/observation.js +123 -0
  59. package/build/executor/permissions.js +79 -0
  60. package/build/executor/preflight.js +144 -0
  61. package/build/executor/process.js +81 -0
  62. package/build/executor/prompt-spec.js +235 -0
  63. package/build/executor/results.js +134 -0
  64. package/build/executor/resume-pre-spawn.js +179 -0
  65. package/build/executor/runner.js +98 -0
  66. package/build/executor/terminal-mutation.js +34 -0
  67. package/build/executor/test-clock.js +109 -0
  68. package/build/executor/types.js +18 -0
  69. package/build/executor/verdict-artifact.js +53 -0
  70. package/build/executor/viewer-tabs.js +78 -0
  71. package/build/executor/watch-cli.js +113 -0
  72. package/build/executor/worker-command.js +106 -0
  73. package/build/executor/worker-finalization.js +97 -0
  74. package/build/executor/worker-log.js +92 -0
  75. package/build/executor/worktree-gc.js +134 -0
  76. package/build/executor/worktree-inspection.js +86 -0
  77. package/build/executor/worktree.js +103 -0
  78. package/build/index.js +13950 -9669
  79. package/build/install-bridge.js +25 -8
  80. package/build/install-doctor.js +387 -0
  81. package/build/mcp-invoke.js +19 -3
  82. package/build/mcp-provisioning.js +31 -25
  83. package/build/mcp-registration-doctor.js +27 -7
  84. package/build/mcp-server-invocation.js +152 -0
  85. package/build/pipelines.generated.js +31 -6
  86. package/build/readme.generated.js +1 -1
  87. package/build/regression-check.js +53 -1
  88. package/build/review-tickets.js +175 -21
  89. package/build/sfcc/reads-site-preference.js +52 -19
  90. package/build/start-tickets-conductor.js +47 -99
  91. package/build/start-tickets-prereqs.js +185 -4
  92. package/build/start-tickets.js +218 -180
  93. package/build/version.generated.js +1 -1
  94. package/build/visual-diff-worker.js +313 -0
  95. package/build/visual-diff.js +632 -0
  96. package/build/worktree-core.js +202 -0
  97. package/package.json +10 -6
  98. package/pipelines/review-ticket.json +24 -2
  99. package/public/css/main.min.css +3311 -1
  100. package/public/css/main.min.css.map +1 -1
  101. package/public/js/main.min.js +7924 -1
  102. package/public/js/main.min.js.map +1 -1
  103. package/smoke-test/SMOKE-TEST.md +5 -2
@@ -0,0 +1,131 @@
1
+ /** Bound a server-provided diagnostic snippet included in error strings. */
2
+ function boundedDetail(text) {
3
+ const trimmed = (text ?? "").trim();
4
+ return trimmed.length > 200 ? `${trimmed.slice(0, 200)}…` : trimmed;
5
+ }
6
+ function isOkBody(bodyText) {
7
+ try {
8
+ const parsed = JSON.parse(bodyText);
9
+ return !!parsed && typeof parsed === "object" && parsed.ok === true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ /**
16
+ * Map a mutation (heartbeat/complete/fail) HTTP response onto the fencing
17
+ * outcome. `allowInvalidResult` maps 422 to `invalid_job_result` (complete only).
18
+ */
19
+ function mapMutationResponse(status, bodyText, allowInvalidResult) {
20
+ if (status === 409)
21
+ return "stale_claim";
22
+ if (status === 503)
23
+ return "retry_later";
24
+ if (status === 422 && allowInvalidResult)
25
+ return "invalid_job_result";
26
+ if (status >= 200 && status < 300 && isOkBody(bodyText))
27
+ return "updated";
28
+ return "fatal_http_error";
29
+ }
30
+ export function createExecutorHttpClient(config) {
31
+ const base = config.baseUrl.replace(/\/+$/, "") + "/executor/jobs";
32
+ function keyForRepo(repoName) {
33
+ return config.apiKeyByRepo?.[repoName] ?? config.apiKey;
34
+ }
35
+ function headersForRepo(repoName) {
36
+ return {
37
+ "Content-Type": "application/json",
38
+ "X-API-Key": keyForRepo(repoName),
39
+ "X-Bridge-MCP-Version": config.mcpVersion,
40
+ };
41
+ }
42
+ async function post(pathSuffix, body, repoName) {
43
+ const res = await config.fetch(`${base}${pathSuffix}`, {
44
+ method: "POST",
45
+ headers: headersForRepo(repoName),
46
+ body: JSON.stringify(body),
47
+ });
48
+ const text = await res.text();
49
+ return { status: res.status, text };
50
+ }
51
+ async function mutate(pathSuffix, body, allowInvalidResult, repoName) {
52
+ try {
53
+ const { status, text } = await post(pathSuffix, body, repoName);
54
+ return mapMutationResponse(status, text, allowInvalidResult);
55
+ }
56
+ catch {
57
+ // Transient network error — treat as fatal for a single mutation attempt;
58
+ // the caller's retry/dead-man logic decides whether to keep the worker alive.
59
+ return "fatal_http_error";
60
+ }
61
+ }
62
+ return {
63
+ async claim(manifest) {
64
+ let status;
65
+ let text;
66
+ try {
67
+ const res = await post("/claim", manifest, manifest.repo_name);
68
+ status = res.status;
69
+ text = res.text;
70
+ }
71
+ catch (err) {
72
+ const message = err instanceof Error ? err.message : String(err);
73
+ return { kind: "retryable", error: `claim network error: ${boundedDetail(message)}` };
74
+ }
75
+ if (status === 204)
76
+ return { kind: "none" };
77
+ if (status === 200) {
78
+ try {
79
+ const job = JSON.parse(text);
80
+ if (!job || typeof job !== "object" || typeof job.claim_token !== "string") {
81
+ return { kind: "fatal", error: "claim returned a malformed job payload" };
82
+ }
83
+ return { kind: "claimed", job };
84
+ }
85
+ catch {
86
+ return { kind: "fatal", error: "claim returned invalid JSON" };
87
+ }
88
+ }
89
+ if (status === 401 || status === 403 || status === 422) {
90
+ return { kind: "fatal", error: `claim rejected (HTTP ${status}): ${boundedDetail(text)}` };
91
+ }
92
+ // 5xx and any other unexpected status are retryable.
93
+ return { kind: "retryable", error: `claim failed (HTTP ${status}): ${boundedDetail(text)}` };
94
+ },
95
+ heartbeat(job, payload) {
96
+ return mutate(`/${job.id}/heartbeat`, {
97
+ repo_name: job.repo_name,
98
+ claim_token: job.claim_token,
99
+ local_commit_count: payload.local_commit_count,
100
+ last_commit_sha: payload.last_commit_sha,
101
+ telemetry: payload.telemetry,
102
+ }, false, job.repo_name);
103
+ },
104
+ complete(job, completion) {
105
+ return mutate(`/${job.id}/complete`, {
106
+ repo_name: job.repo_name,
107
+ claim_token: job.claim_token,
108
+ job_type: completion.job_type,
109
+ exit_code: completion.exit_code,
110
+ classification: completion.classification,
111
+ result: completion.result,
112
+ artifacts: completion.artifacts,
113
+ local_commit_count: completion.local_commit_count,
114
+ last_commit_sha: completion.last_commit_sha,
115
+ telemetry: completion.telemetry,
116
+ }, true, job.repo_name);
117
+ },
118
+ fail(job, failure) {
119
+ return mutate(`/${job.id}/fail`, {
120
+ repo_name: job.repo_name,
121
+ claim_token: job.claim_token,
122
+ error_kind: failure.error_kind,
123
+ error_message: failure.error_message,
124
+ classification: failure.classification,
125
+ local_commit_count: failure.local_commit_count,
126
+ last_commit_sha: failure.last_commit_sha,
127
+ telemetry: failure.telemetry,
128
+ }, false, job.repo_name);
129
+ },
130
+ };
131
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Epic Conductor v2 executor module entrypoint (BAPI-534).
3
+ *
4
+ * Public surface only — internal helpers stay module-private unless another
5
+ * production module needs them. Imports NOTHING from the v1 conductor
6
+ * event/ledger graph (see `types.ts` for the boundary contract).
7
+ */
8
+ export { runExecutorCli, getExecutorUsage, parseExecutorArgs } from "./cli.js";
9
+ export { runExecutor } from "./runner.js";
10
+ export { runExecutorWatchCli, getExecutorWatchUsage, parseExecutorWatchArgs, } from "./watch-cli.js";
@@ -0,0 +1,55 @@
1
+ /** Stable executor failure-kind constants (posted verbatim as `error_kind`). */
2
+ export const MissingVerdictArtifact = "MissingVerdictArtifact";
3
+ export const WorktreeLostBeforePush = "WorktreeLostBeforePush";
4
+ export const BranchMismatch = "BranchMismatch";
5
+ export const WorkerFinalizationMissingRemoteBranchAndPr = "WorkerFinalizationMissingRemoteBranchAndPr";
6
+ /** Bound a failure message so no unbounded/secret-bearing text is posted. */
7
+ const ERROR_MESSAGE_MAX_CHARS = 300;
8
+ /**
9
+ * An `Error` subclass carrying a stable `errorKind` plus a bounded
10
+ * `errorMessage`. Named executor failures default to the `crashed` wire
11
+ * classification (they are deterministic local aborts, not worker timeouts/kills).
12
+ */
13
+ export class ExecutorNamedError extends Error {
14
+ errorKind;
15
+ errorMessage;
16
+ classification;
17
+ constructor(errorKind, errorMessage, classification = "crashed") {
18
+ const bounded = String(errorMessage ?? "").slice(0, ERROR_MESSAGE_MAX_CHARS);
19
+ super(`${errorKind}: ${bounded}`);
20
+ this.name = "ExecutorNamedError";
21
+ this.errorKind = errorKind;
22
+ this.errorMessage = bounded;
23
+ this.classification = classification;
24
+ }
25
+ }
26
+ /** Type-guard for a named executor error. */
27
+ export function isExecutorNamedError(value) {
28
+ return value instanceof ExecutorNamedError;
29
+ }
30
+ /** Map a named executor error into the `/fail` payload shape. */
31
+ export function toExecutorFailure(error) {
32
+ return {
33
+ error_kind: error.errorKind,
34
+ error_message: error.errorMessage,
35
+ classification: error.classification,
36
+ };
37
+ }
38
+ /**
39
+ * Produce a bounded, secret-free fallback message for an UNEXPECTED (non-named)
40
+ * local failure. Token-shaped and credential-shaped substrings are stripped so a
41
+ * raw exception message can never carry a secret into a `/fail` payload or log.
42
+ */
43
+ export function secretFreeErrorMessage(error) {
44
+ const raw = error instanceof Error ? error.message : String(error);
45
+ const redacted = raw
46
+ // `key=value` / `token: value` credential pairs → drop the value.
47
+ .replace(/\b(token|password|passwd|pwd|secret|api[_-]?key|apikey|authorization|auth|bearer)\b\s*[:=]\s*\S+/gi, "$1=[REDACTED]")
48
+ // Standalone secret-shaped tokens (GitHub PATs, long opaque strings).
49
+ .replace(/\bgh[posru]_[A-Za-z0-9]{16,}\b/g, "[REDACTED_TOKEN]")
50
+ .replace(/\b[A-Za-z0-9_-]{40,}\b/g, "[REDACTED_TOKEN]");
51
+ const bounded = redacted.slice(0, ERROR_MESSAGE_MAX_CHARS).trim();
52
+ return bounded.length > 0
53
+ ? `unexpected executor failure: ${bounded}`
54
+ : "unexpected executor failure";
55
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Local executor job-log registry (BAPI-535, TDD §8).
3
+ *
4
+ * Persists a small, SECRET-FREE mapping from an executor job id to its worker-log
5
+ * path so a SEPARATE CLI process (`executor watch <job>`) can attach read-only to
6
+ * the log. Records carry only routing metadata (ids, repo, job type, paths,
7
+ * timestamps) — never job payloads, API tokens, or auth material. All filesystem
8
+ * / env access is injected so the registry is unit-testable with no real I/O.
9
+ */
10
+ import { pathApiForExecutorPlatform } from "./worktree-inspection.js";
11
+ /** Default state-dir basename under the system temp dir. */
12
+ const DEFAULT_STATE_DIR_NAME = "bridge-executor-job-logs";
13
+ /** Env var that overrides the executor state directory. */
14
+ export const EXECUTOR_STATE_DIR_ENV = "BAPI_EXECUTOR_STATE_DIR";
15
+ /**
16
+ * Resolve the executor state directory: the `BAPI_EXECUTOR_STATE_DIR` override
17
+ * when set, else a stable directory under the system temp dir (independent of
18
+ * cwd).
19
+ */
20
+ export function resolveExecutorStateDir(deps) {
21
+ const override = deps.env[EXECUTOR_STATE_DIR_ENV];
22
+ if (typeof override === "string" && override.trim().length > 0) {
23
+ return override.trim();
24
+ }
25
+ const pathApi = pathApiForExecutorPlatform(deps.platform ?? process.platform);
26
+ return pathApi.join(deps.tmpdir(), DEFAULT_STATE_DIR_NAME);
27
+ }
28
+ /**
29
+ * Derive the record file path for a numeric job id. The id is coerced to digits
30
+ * only so a malformed id can never escape the state directory (path traversal).
31
+ */
32
+ export function jobLogRecordPath(stateDir, jobId, platform = process.platform) {
33
+ const pathApi = pathApiForExecutorPlatform(platform);
34
+ const safeId = String(jobId).replace(/[^0-9]/g, "") || "0";
35
+ return pathApi.join(stateDir, `job-${safeId}.json`);
36
+ }
37
+ /** Whitelist the persisted fields so no extra (possibly secret) field leaks in. */
38
+ function normalizeRecord(record) {
39
+ const out = {
40
+ job_id: record.job_id,
41
+ repo_name: record.repo_name,
42
+ job_type: record.job_type,
43
+ log_path: record.log_path,
44
+ started_at: record.started_at,
45
+ };
46
+ if (record.ticket_key)
47
+ out.ticket_key = record.ticket_key;
48
+ if (record.worktree_path)
49
+ out.worktree_path = record.worktree_path;
50
+ if (record.finished_at)
51
+ out.finished_at = record.finished_at;
52
+ return out;
53
+ }
54
+ function isValidRecord(value) {
55
+ if (!value || typeof value !== "object")
56
+ return false;
57
+ const v = value;
58
+ return (typeof v.job_id === "number" &&
59
+ typeof v.repo_name === "string" &&
60
+ typeof v.job_type === "string" &&
61
+ typeof v.log_path === "string" &&
62
+ typeof v.started_at === "string");
63
+ }
64
+ /** Write the secret-free registry record when a worker log is initialized. */
65
+ export async function registerExecutorJobLog(record, deps) {
66
+ const stateDir = resolveExecutorStateDir(deps);
67
+ await deps.mkdir(stateDir, { recursive: true });
68
+ const filePath = jobLogRecordPath(stateDir, record.job_id, deps.platform ?? process.platform);
69
+ await deps.writeFile(filePath, JSON.stringify(normalizeRecord(record), null, 2));
70
+ }
71
+ /**
72
+ * Stamp `finished_at` on an existing record after terminal completion/failure,
73
+ * preserving all existing fields. A missing/invalid record is a no-op (the watch
74
+ * registry is a visibility nicety, not load-bearing).
75
+ */
76
+ export async function markExecutorJobLogFinished(jobId, finishedAt, deps) {
77
+ const existing = await readExecutorJobLogRecord(jobId, deps);
78
+ if (!existing.ok)
79
+ return;
80
+ const updated = { ...existing.record, finished_at: finishedAt };
81
+ const stateDir = resolveExecutorStateDir(deps);
82
+ const filePath = jobLogRecordPath(stateDir, jobId, deps.platform ?? process.platform);
83
+ await deps.writeFile(filePath, JSON.stringify(normalizeRecord(updated), null, 2));
84
+ }
85
+ /**
86
+ * Load and validate a record for `executor watch <job>`. Returns a structured
87
+ * missing/invalid result rather than throwing raw filesystem/JSON errors.
88
+ */
89
+ export async function readExecutorJobLogRecord(jobId, deps) {
90
+ const stateDir = resolveExecutorStateDir(deps);
91
+ const filePath = jobLogRecordPath(stateDir, jobId, deps.platform ?? process.platform);
92
+ let raw;
93
+ try {
94
+ raw = await deps.readFile(filePath);
95
+ }
96
+ catch {
97
+ return { ok: false, reason: `no registry record for job ${jobId} (expected ${filePath})` };
98
+ }
99
+ let parsed;
100
+ try {
101
+ parsed = JSON.parse(raw);
102
+ }
103
+ catch {
104
+ return { ok: false, reason: `registry record for job ${jobId} is not valid JSON` };
105
+ }
106
+ if (!isValidRecord(parsed)) {
107
+ return { ok: false, reason: `registry record for job ${jobId} is missing required fields` };
108
+ }
109
+ return { ok: true, record: parsed };
110
+ }