@bridge_gpt/mcp-server 0.2.38 → 0.2.39

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 (85) hide show
  1. package/README.md +189 -14
  2. package/build/agent-capabilities/probe-context.js +2 -1
  3. package/build/agent-launchers/claude-executor-adapter.js +392 -0
  4. package/build/agent-launchers/executor-adapter-inspection.js +163 -0
  5. package/build/agent-launchers/executor-adapter-registry.js +90 -0
  6. package/build/agent-launchers/executor-adapter.js +136 -0
  7. package/build/agent-registry.js +28 -0
  8. package/build/agents.generated.js +1 -1
  9. package/build/claude-login.js +85 -0
  10. package/build/claude-user-config-doctor.js +59 -33
  11. package/build/commands.generated.js +12 -11
  12. package/build/conduct-epic/bridge-client.js +345 -0
  13. package/build/conduct-epic/checkpoint-store.js +423 -0
  14. package/build/conduct-epic/cli.js +1732 -0
  15. package/build/conduct-epic/lock.js +302 -0
  16. package/build/conduct-epic/pr-state.js +197 -0
  17. package/build/conduct-epic/spawn.js +101 -0
  18. package/build/conductor/bridge-api-client.js +37 -2
  19. package/build/conductor/doctor.js +11 -1
  20. package/build/conductor/install-doctor.js +184 -10
  21. package/build/conductor-bin.js +7 -7
  22. package/build/credential-store.js +10 -4
  23. package/build/credentials-cli.js +34 -19
  24. package/build/docs.generated.js +1 -1
  25. package/build/doctor.js +579 -88
  26. package/build/executor/agent-identity.js +32 -0
  27. package/build/executor/cli.js +50 -39
  28. package/build/executor/deps.js +15 -1
  29. package/build/executor/env.js +56 -45
  30. package/build/executor/index.js +9 -1
  31. package/build/executor/install-preflight.js +138 -0
  32. package/build/executor/job-errors.js +200 -0
  33. package/build/executor/job-runner.js +619 -268
  34. package/build/executor/observation.js +165 -0
  35. package/build/executor/permissions.js +163 -36
  36. package/build/executor/platform.js +54 -0
  37. package/build/executor/preflight.js +175 -67
  38. package/build/executor/process.js +39 -7
  39. package/build/executor/runner.js +19 -0
  40. package/build/executor/service-lifecycle.js +269 -0
  41. package/build/executor/service-unit.js +121 -12
  42. package/build/executor/stale-artifacts.js +70 -0
  43. package/build/executor/test-clock.js +188 -24
  44. package/build/executor/worker-command.js +22 -58
  45. package/build/executor/worker-log.js +82 -0
  46. package/build/executor/worktree-lock.js +264 -0
  47. package/build/index.js +527 -357
  48. package/build/install-bridge-conductor.js +376 -38
  49. package/build/install-bridge.js +414 -114
  50. package/build/install-doctor.js +13 -0
  51. package/build/install-reexec.js +5 -3
  52. package/build/mcp-install-state.js +130 -0
  53. package/build/mcp-profile.js +11 -2
  54. package/build/mcp-provisioning.js +15 -0
  55. package/build/merge-pull-request.js +562 -0
  56. package/build/phase-result-artifacts.js +450 -0
  57. package/build/pipeline-orchestrator.js +4 -0
  58. package/build/pipeline-utils.js +16 -0
  59. package/build/pipelines.generated.js +7 -7
  60. package/build/plane/preflight.js +18 -14
  61. package/build/plane/supervisor.js +8 -1
  62. package/build/project-root.js +34 -0
  63. package/build/readme.generated.js +1 -1
  64. package/build/run-unit-tests-launcher.js +36 -9
  65. package/build/setup-epic.js +57 -4
  66. package/build/sfcc/permissions.js +25 -6
  67. package/build/sfcc/reads-site-preference.js +6 -0
  68. package/build/sfcc/register.js +61 -23
  69. package/build/sfcc/registration-inventory.js +89 -0
  70. package/build/sfcc/setup-status.js +18 -34
  71. package/build/sfcc/tool-wrapper.js +294 -17
  72. package/build/sfcc/write-grants.js +33 -1
  73. package/build/sfcc/write-guard.js +41 -12
  74. package/build/sfcc/writes-custom-object-def.js +6 -2
  75. package/build/sfcc/writes-site-preference.js +6 -1
  76. package/build/sfcc/writes-system-object.js +11 -2
  77. package/build/sfcc/writes.js +13 -8
  78. package/build/start-tickets-prereqs.js +25 -15
  79. package/build/start-tickets.js +123 -21
  80. package/build/version.generated.js +1 -1
  81. package/build/worktree-core.js +9 -3
  82. package/docs/install/mcp-tool-integrations.md +54 -9
  83. package/docs/install/sfcc-integration.md +71 -24
  84. package/package.json +3 -3
  85. package/build/executor/worker-config-isolation.js +0 -287
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge_gpt/mcp-server",
3
- "version": "0.2.38",
3
+ "version": "0.2.39",
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",
@@ -29,7 +29,7 @@
29
29
  "test": "npm run test:normal && npm run test:module-mocks",
30
30
  "test:normal": "node scripts/run-unit-tests.js normal",
31
31
  "test:module-mocks": "node scripts/run-unit-tests.js module-mocks",
32
- "test:integration": "node --test build/integration/refresh-main.integration.test.js build/integration/command-provisioning.integration.test.js build/integration/start-tickets.integration.test.js build/integration/start-tickets-tier-handoff.integration.test.js build/integration/doctor.integration.test.js build/integration/agent-capabilities.integration.test.js build/integration/conductor-producer.integration.test.js build/integration/conductor-message-relay.integration.test.js build/integration/executor-http-runner.integration.test.js build/integration/executor-job-behaviors.integration.test.js build/integration/executor-recovery-jobs.integration.test.js build/integration/executor-spec-review-prompt.integration.test.js build/integration/resume-pre-spawn.git.integration.test.js build/integration/worker-finalization-origin.integration.test.js build/integration/post-remediation-merge-ci-wait.integration.test.js build/integration/executor-merge-supervision.integration.test.js build/integration/attachment-binary-roundtrip.integration.test.js build/integration/dependent-ticket-fresh-base.integration.test.js build/integration/execute-plan-instructions.integration.test.js build/integration/implement-ticket-finalization-order.integration.test.js build/integration/conductor-bundle-artifacts.integration.test.js build/integration/install-bridge-repo-resolution.integration.test.js build/integration/capability-report-contract.integration.test.js build/integration/request-brainstorm-general.integration.test.js build/integration/request-council-trigger-drop.integration.test.js build/integration/install-bridge-onboarding-launch.integration.test.js build/integration/install-bridge-conductor.integration.test.js build/integration/install-bridge-failure-guards.integration.test.js build/integration/learn-repository-pipeline.integration.test.js build/integration/visual-diff-mcp.integration.test.js build/integration/executor-mcp-provisioning.integration.test.js build/integration/executor-worktree-reentry.integration.test.js build/integration/sfcc-read-error-contract.integration.test.js build/integration/sfcc-read-surface.integration.test.js build/integration/sfcc-response-envelope.integration.test.js build/integration/sfcc-total-error-contract.integration.test.js build/integration/serve-stdio.integration.test.js build/integration/install-alias.integration.test.js build/integration/setup-epic-validation.integration.test.js build/integration/plane-cli.integration.test.js",
32
+ "test:integration": "node --test build/conductor/conductor-runtime.integration.test.js build/integration/refresh-main.integration.test.js build/integration/command-provisioning.integration.test.js build/integration/start-tickets.integration.test.js build/integration/start-tickets-tier-handoff.integration.test.js build/integration/doctor.integration.test.js build/integration/agent-capabilities.integration.test.js build/integration/conductor-producer.integration.test.js build/integration/conductor-message-relay.integration.test.js build/integration/executor-http-runner.integration.test.js build/integration/executor-job-behaviors.integration.test.js build/integration/executor-recovery-jobs.integration.test.js build/integration/executor-spec-review-prompt.integration.test.js build/integration/resume-pre-spawn.git.integration.test.js build/integration/executor-containment.git.integration.test.js build/integration/conduct-epic-cli.integration.test.js build/integration/conduct-epic-corrections.integration.test.js build/integration/worker-finalization-origin.integration.test.js build/integration/post-remediation-merge-ci-wait.integration.test.js build/integration/executor-merge-supervision.integration.test.js build/integration/attachment-binary-roundtrip.integration.test.js build/integration/dependent-ticket-fresh-base.integration.test.js build/integration/execute-plan-instructions.integration.test.js build/integration/implement-ticket-finalization-order.integration.test.js build/integration/conductor-bundle-artifacts.integration.test.js build/integration/install-bridge-repo-resolution.integration.test.js build/integration/capability-report-contract.integration.test.js build/integration/request-brainstorm-general.integration.test.js build/integration/request-council-trigger-drop.integration.test.js build/integration/install-bridge-onboarding-launch.integration.test.js build/integration/install-bridge-conductor.integration.test.js build/integration/install-bridge-conductor-executor.integration.test.js build/integration/install-bridge-failure-guards.integration.test.js build/integration/learn-repository-pipeline.integration.test.js build/integration/visual-diff-mcp.integration.test.js build/integration/executor-mcp-provisioning.integration.test.js build/integration/executor-worktree-reentry.integration.test.js build/integration/sfcc-read-error-contract.integration.test.js build/integration/sfcc-read-surface.integration.test.js build/integration/sfcc-response-envelope.integration.test.js build/integration/sfcc-total-error-contract.integration.test.js build/integration/sfcc-target-boundary.integration.test.js build/integration/serve-stdio.integration.test.js build/integration/install-alias.integration.test.js build/integration/setup-epic-validation.integration.test.js build/integration/plane-cli.integration.test.js build/integration/executor-auth-retirement.integration.test.js build/integration/decision-implications-command-scaffold.test.js build/integration/strict-mcp-worker-contract.test.js build/integration/executor-adapter-flow.test.js build/integration/install-cli-contract.integration.test.js build/integration/inline-phase-state-tools.test.js",
33
33
  "test:smoke": "node --test build/integration/packaged-cli-smoke.test.js",
34
34
  "canary:agent-capabilities": "npm run build && node scripts/agent-capabilities-canary.mjs",
35
35
  "prepublishOnly": "node scripts/bundle-assets.js && npm run build && node scripts/verify-shebang.cjs"
@@ -55,7 +55,7 @@
55
55
  "node": ">=18.0.0"
56
56
  },
57
57
  "overrides": {
58
- "fast-uri": ">=4.1.1",
58
+ "fast-uri": ">=4.1.2",
59
59
  "hono": ">=4.13.1",
60
60
  "@hono/node-server": "^1.19.13",
61
61
  "ip-address": ">=10.1.1"
@@ -1,287 +0,0 @@
1
- /** Restrictive mode for the isolation directory itself (owner-only). */
2
- export const ISOLATION_DIR_MODE = 0o700;
3
- /** Owner-only mode for the seeded Bridge credential file. */
4
- export const ISOLATION_FILE_MODE = 0o600;
5
- /**
6
- * Fixed name prefix for every directory this module creates. The startup sweep
7
- * recognizes ONLY this prefix, which is what keeps it from deleting unrelated
8
- * temporary directories that happen to share the parent.
9
- */
10
- export const ISOLATION_DIR_PREFIX = "bapi-conductor-claude-cfg-";
11
- /**
12
- * Age past which the startup sweep may remove an orphan, deliberately far above
13
- * any supported worker lifetime (the longest executor job timeout is measured in
14
- * hours, not days). A directory younger than this may still belong to a LIVE
15
- * worker owned by a concurrent executor process, so the threshold is the only
16
- * thing preventing the sweep from deleting an in-use configuration.
17
- */
18
- export const ISOLATION_ORPHAN_MAX_AGE_MS = 24 * 60 * 60 * 1000;
19
- /** Platforms whose POSIX mode model supports the `0700` containment above. */
20
- const SUPPORTED_PLATFORMS = ["darwin", "linux"];
21
- /** Fixed remediation text per reason. Never includes a path or a value. */
22
- const REASON_MESSAGES = {
23
- "unsupported-platform": "worker config isolation is not supported on this platform (POSIX 0700/0600 containment is required)",
24
- "unsupported-auth-layout": "no ANTHROPIC_API_KEY is available to the executor; an OAuth/subscription-only login does not " +
25
- "follow an isolated CLAUDE_CONFIG_DIR, and copying operator credentials is not permitted",
26
- "missing-filesystem-dependency": "the executor was constructed without the filesystem boundaries worker config isolation requires",
27
- "unsafe-isolation-parent": "the isolation parent directory is a symbolic link or not a directory; refusing to create an " +
28
- "isolated config directory beneath it",
29
- "directory-creation-failed": "the isolated config directory could not be created or permissioned",
30
- };
31
- /**
32
- * Render a capability failure as a bounded, secret-free sentence naming only the
33
- * platform and the fixed reason — the exact text posted as an `error_message`.
34
- */
35
- export function formatWorkerConfigIsolationReason(platform, reason) {
36
- return `worker config isolation unavailable on ${platform}: ${REASON_MESSAGES[reason]}`;
37
- }
38
- /**
39
- * Decide whether isolation CAN be established here, without creating anything.
40
- *
41
- * Read-only by construction — this is what lets `doctor` reuse it (BAPI-731
42
- * requirement 4) while keeping doctor's no-writes contract. It never reads the
43
- * operator's `.claude.json`, never touches the credential store, and never
44
- * returns a credential value: the auth check is presence-only.
45
- */
46
- export function evaluateWorkerConfigIsolationCapability(deps) {
47
- const platform = String(deps.platform);
48
- if (!SUPPORTED_PLATFORMS.includes(deps.platform)) {
49
- return { supported: false, platform, reason: "unsupported-platform" };
50
- }
51
- // Presence-only. The VALUE is never read into any result, log, or diagnostic.
52
- const apiKey = deps.env?.ANTHROPIC_API_KEY;
53
- if (typeof apiKey !== "string" || apiKey.trim().length === 0) {
54
- return { supported: false, platform, reason: "unsupported-auth-layout" };
55
- }
56
- return { supported: true, platform, strategy: "api-key-env" };
57
- }
58
- function failure(platform, reason) {
59
- return {
60
- ok: false,
61
- platform,
62
- reason,
63
- message: formatWorkerConfigIsolationReason(platform, reason),
64
- };
65
- }
66
- /**
67
- * Create a private, owner-only configuration directory for ONE job attempt.
68
- *
69
- * The directory lives under the executor's temporary root — never inside the
70
- * worktree, which would put executor-owned state into the diff the worker is
71
- * about to commit and would be destroyed by the worktree's own reset/clean paths.
72
- *
73
- * Seeds NOTHING of the operator's Claude configuration. Inventory finding 3
74
- * established that no trust entry is needed to keep a headless worker from
75
- * prompting, and finding 4 established that the ANTHROPIC credential travels in
76
- * the environment. No projected fragment of the operator's configuration is
77
- * written, so none can get stale or over-grant.
78
- *
79
- * The single exception is {@link SeededBridgeCredential} — see that type for why
80
- * an empty directory is NOT sufficient for the Bridge credential specifically:
81
- * finding 4 does not extend to `BAPI_API_KEY`, which is denied from the worker
82
- * environment on purpose, leaving the redirected store as its only route in.
83
- */
84
- export async function establishWorkerConfigIsolation(deps, options = {}) {
85
- const capability = evaluateWorkerConfigIsolationCapability(deps);
86
- if (!capability.supported) {
87
- return failure(capability.platform, capability.reason);
88
- }
89
- const platform = capability.platform;
90
- let parent;
91
- try {
92
- parent = deps.tmpdir();
93
- }
94
- catch {
95
- return failure(platform, "unsafe-isolation-parent");
96
- }
97
- if (typeof parent !== "string" || parent.length === 0) {
98
- return failure(platform, "unsafe-isolation-parent");
99
- }
100
- // Reject a symlinked or non-directory parent BEFORE creating anything beneath
101
- // it: `mkdtemp` under a symlink would silently place a credential-adjacent
102
- // directory wherever the link points.
103
- try {
104
- const parentMeta = await deps.lstatPath(parent);
105
- if (!parentMeta || parentMeta.isSymbolicLink || !parentMeta.isDirectory) {
106
- return failure(platform, "unsafe-isolation-parent");
107
- }
108
- }
109
- catch {
110
- return failure(platform, "unsafe-isolation-parent");
111
- }
112
- // `mkdtemp` is atomic and collision-free, so two concurrent jobs on the same
113
- // executor can never be handed the same directory.
114
- let created;
115
- try {
116
- created = await deps.mkdtemp(deps.join(parent, ISOLATION_DIR_PREFIX));
117
- if (typeof created !== "string" || created.length === 0) {
118
- return failure(platform, "directory-creation-failed");
119
- }
120
- }
121
- catch {
122
- // The raw error may carry a host path; only the fixed reason escapes.
123
- return failure(platform, "directory-creation-failed");
124
- }
125
- // Verify what we actually got is a real directory and not a link, then narrow
126
- // it to owner-only. `mkdtemp` already creates at 0700 on POSIX; the explicit
127
- // chmod makes the guarantee independent of the platform's umask behavior.
128
- try {
129
- const meta = await deps.lstatPath(created);
130
- if (!meta || meta.isSymbolicLink || !meta.isDirectory) {
131
- await safeDispose(deps, created);
132
- return failure(platform, "unsafe-isolation-parent");
133
- }
134
- await deps.chmod(created, ISOLATION_DIR_MODE);
135
- }
136
- catch {
137
- await safeDispose(deps, created);
138
- return failure(platform, "directory-creation-failed");
139
- }
140
- // The isolated XDG root lives INSIDE the disposable directory, so a single
141
- // recursive removal reclaims both and no second lifetime needs tracking.
142
- const xdgConfigHome = deps.join(created, "xdg");
143
- try {
144
- await deps.mkdir(xdgConfigHome, { recursive: true });
145
- await deps.chmod(xdgConfigHome, ISOLATION_DIR_MODE);
146
- }
147
- catch {
148
- await safeDispose(deps, created);
149
- return failure(platform, "directory-creation-failed");
150
- }
151
- // Seed the one credential the redirect would otherwise strip. Fail-CLOSED,
152
- // matching this module's posture: a worker that cannot reach its Bridge MCP
153
- // tools cannot do the job it was dispatched for, and the failure mode is a
154
- // clean exit that looks like success. Refusing to spawn is strictly better
155
- // than spawning a worker guaranteed to accomplish nothing.
156
- const credential = options.bridgeCredential;
157
- if (credential) {
158
- if (!deps.writeFile) {
159
- await safeDispose(deps, created);
160
- return failure(platform, "missing-filesystem-dependency");
161
- }
162
- try {
163
- const bridgeDir = deps.join(xdgConfigHome, "bridge");
164
- await deps.mkdir(bridgeDir, { recursive: true });
165
- await deps.chmod(bridgeDir, ISOLATION_DIR_MODE);
166
- const storePath = deps.join(bridgeDir, "credentials.json");
167
- await deps.writeFile(storePath,
168
- // Exactly the shape resolveCredentialBundle reads: a logical target
169
- // mapping to its named secret values.
170
- `${JSON.stringify({ [`bapi:${credential.repoName}`]: { BAPI_API_KEY: credential.apiKey } }, null, 2)}\n`);
171
- // Narrow AFTER writing: the content is secret, and a permissive window
172
- // between create and chmod is the whole risk being closed here.
173
- await deps.chmod(storePath, ISOLATION_FILE_MODE);
174
- }
175
- catch {
176
- // The raw error may carry a host path or the secret; only the fixed
177
- // reason escapes.
178
- await safeDispose(deps, created);
179
- return failure(platform, "directory-creation-failed");
180
- }
181
- }
182
- let disposed = false;
183
- return {
184
- ok: true,
185
- isolation: {
186
- claudeConfigDir: created,
187
- xdgConfigHome,
188
- async dispose() {
189
- if (disposed)
190
- return null;
191
- disposed = true;
192
- try {
193
- await deps.rmRecursive(created);
194
- return null;
195
- }
196
- catch {
197
- // Best-effort by contract: cleanup failure must never replace an
198
- // already-determined job outcome, and the raw error never escapes.
199
- return "worker config isolation directory could not be removed";
200
- }
201
- },
202
- },
203
- };
204
- }
205
- /** Remove a partially-created directory, swallowing every failure. */
206
- async function safeDispose(deps, path) {
207
- try {
208
- await deps.rmRecursive(path);
209
- }
210
- catch {
211
- /* best-effort */
212
- }
213
- }
214
- /**
215
- * Remove stale isolation directories left behind by an ungraceful executor exit
216
- * (SIGKILL, OOM, host reboot) where per-job disposal never ran.
217
- *
218
- * Deliberately conservative, because this function deletes directories: it looks
219
- * only inside the executor's own temporary root, recognizes only entries
220
- * carrying {@link ISOLATION_DIR_PREFIX}, requires NON-following metadata to say
221
- * the entry is a real directory (so a symlink is skipped rather than followed),
222
- * and requires an age beyond {@link ISOLATION_ORPHAN_MAX_AGE_MS} so a live
223
- * concurrent executor's active job directory is never removed.
224
- *
225
- * Never throws: startup must proceed even if the sweep cannot run at all.
226
- */
227
- export async function sweepOrphanedWorkerConfigDirectories(deps) {
228
- const result = { removed: 0, skipped: 0, diagnostics: [] };
229
- const maxAgeMs = typeof deps.maxAgeMs === "number" ? deps.maxAgeMs : ISOLATION_ORPHAN_MAX_AGE_MS;
230
- let parent;
231
- try {
232
- parent = deps.tmpdir();
233
- if (typeof parent !== "string" || parent.length === 0) {
234
- result.diagnostics.push("worker config isolation sweep skipped: no temporary root");
235
- return result;
236
- }
237
- }
238
- catch {
239
- result.diagnostics.push("worker config isolation sweep skipped: no temporary root");
240
- return result;
241
- }
242
- let entries;
243
- try {
244
- entries = await deps.readdir(parent);
245
- }
246
- catch {
247
- result.diagnostics.push("worker config isolation sweep skipped: temporary root unreadable");
248
- return result;
249
- }
250
- const cutoff = deps.now() - maxAgeMs;
251
- for (const entry of entries) {
252
- // Prefix match FIRST: anything the module did not create is not ours to
253
- // delete, whatever its age.
254
- if (typeof entry !== "string" || !entry.startsWith(ISOLATION_DIR_PREFIX)) {
255
- result.skipped += 1;
256
- continue;
257
- }
258
- const candidate = deps.join(parent, entry);
259
- let meta;
260
- try {
261
- meta = await deps.lstatPath(candidate);
262
- }
263
- catch {
264
- result.skipped += 1;
265
- continue;
266
- }
267
- // A symlink is skipped, never followed — removing it recursively could
268
- // delete whatever it points at.
269
- if (!meta || meta.isSymbolicLink || !meta.isDirectory) {
270
- result.skipped += 1;
271
- continue;
272
- }
273
- if (!(meta.mtimeMs < cutoff)) {
274
- result.skipped += 1;
275
- continue;
276
- }
277
- try {
278
- await deps.rmRecursive(candidate);
279
- result.removed += 1;
280
- }
281
- catch {
282
- result.skipped += 1;
283
- result.diagnostics.push("worker config isolation sweep could not remove a stale directory");
284
- }
285
- }
286
- return result;
287
- }