@bridge_gpt/mcp-server 0.2.66 → 0.2.67

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 (100) hide show
  1. package/README.md +252 -77
  2. package/build/agent-capabilities/probes.js +15 -5
  3. package/build/agent-launchers/claude-executor-adapter.js +43 -16
  4. package/build/agent-launchers/executor-adapter.js +7 -4
  5. package/build/agents.generated.js +1 -1
  6. package/build/bounded-wait.js +67 -10
  7. package/build/commands.generated.js +11 -11
  8. package/build/conductor/bridge-api-client.js +143 -1
  9. package/build/conductor/bring-up-facts.js +1 -1
  10. package/build/conductor/doctor.js +3 -3
  11. package/build/conductor/epic-implementer-cli.js +22 -6
  12. package/build/conductor/errors.js +4 -3
  13. package/build/conductor/guided-repair.js +6 -2
  14. package/build/conductor/hook-runtime-qualification.js +1 -1
  15. package/build/conductor/merge-execution-contract.js +27 -8
  16. package/build/conductor/merge-execution-eligibility.js +3 -2
  17. package/build/conductor/pr-ci-producer.js +1 -1
  18. package/build/conductor/readiness-cli.js +18 -2
  19. package/build/conductor/readiness-sections.js +41 -6
  20. package/build/conductor/readiness.js +181 -29
  21. package/build/conductor/recovery-cli.js +200 -8
  22. package/build/conductor/recovery-operations.js +69 -3
  23. package/build/conductor/repair-contract.js +54 -3
  24. package/build/conductor/repository-workflow-remedy.js +125 -0
  25. package/build/conductor/tools.js +2 -2
  26. package/build/conductor-bundle-artifacts.js +8 -0
  27. package/build/docs.generated.js +8 -3
  28. package/build/drive-epic-key.js +6 -5
  29. package/build/drive-epic.js +85 -54
  30. package/build/epic-coherence-input.js +2 -1
  31. package/build/epic-implementer-bin.js +22 -20
  32. package/build/epic-implementer-bundle-cli.js +2 -2
  33. package/build/executor/cli.js +3 -2
  34. package/build/executor/completion-contract.js +48 -5
  35. package/build/executor/completion-supervision.js +4 -0
  36. package/build/executor/env.js +19 -0
  37. package/build/executor/heartbeat.js +1 -1
  38. package/build/executor/http-client.js +20 -0
  39. package/build/executor/job-errors.js +25 -0
  40. package/build/executor/job-runner.js +225 -41
  41. package/build/executor/observation.js +6 -1
  42. package/build/executor/preflight.js +51 -14
  43. package/build/executor/required-work-ledger.js +6 -1
  44. package/build/executor/resume-pre-spawn.js +2 -0
  45. package/build/executor/runner.js +8 -2
  46. package/build/executor/service-unit.js +7 -1
  47. package/build/executor/worker-finalization.js +82 -29
  48. package/build/executor/worker-guard-hook.js +137 -33
  49. package/build/executor/worktree.js +82 -1
  50. package/build/index.js +403 -429
  51. package/build/install-bridge-conductor.js +8 -6
  52. package/build/install-bridge.js +3 -3
  53. package/build/phase-context-git-probe.js +68 -0
  54. package/build/phase-result-artifacts.js +173 -5
  55. package/build/pipelines.generated.js +18 -24
  56. package/build/plane/alembic-head.js +12 -3
  57. package/build/plane/base-worktree-guard.js +147 -0
  58. package/build/plane/cli.js +217 -17
  59. package/build/plane/code-snapshot.js +468 -0
  60. package/build/plane/defaults.js +48 -3
  61. package/build/plane/endpoint.js +41 -0
  62. package/build/plane/manifest.js +120 -1
  63. package/build/plane/member-roster.js +94 -19
  64. package/build/plane/preflight.js +30 -2
  65. package/build/plane/process-cwd.js +67 -0
  66. package/build/plane/restart.js +542 -0
  67. package/build/plane/shutdown.js +31 -0
  68. package/build/plane/status.js +143 -8
  69. package/build/plane/supervisor.js +586 -179
  70. package/build/plane/types.js +54 -2
  71. package/build/printed-remedy-commands.js +37 -0
  72. package/build/readiness-check.js +1 -1
  73. package/build/readme.generated.js +1 -1
  74. package/build/review-generation.js +22 -0
  75. package/build/setup-epic.js +44 -19
  76. package/build/sfcc/config.js +9 -0
  77. package/build/sfcc/credentials.js +100 -38
  78. package/build/sfcc/hostname.js +67 -0
  79. package/build/sfcc/permissions.js +17 -13
  80. package/build/sfcc/register.js +1 -1
  81. package/build/sfcc/tool-wrapper.js +39 -43
  82. package/build/sfcc/write-grants.js +17 -11
  83. package/build/sfcc/write-result.js +4 -3
  84. package/build/sfcc/writes-site-preference.js +20 -8
  85. package/build/start-tickets.js +179 -31
  86. package/build/tool-error-envelope.js +61 -0
  87. package/build/version.generated.js +3 -3
  88. package/build/visual-diff.js +22 -1
  89. package/build/worker-guard-hook-bin.js +3 -6
  90. package/build/worker-guard-hook-main.js +47 -0
  91. package/build/worktree-core.js +130 -4
  92. package/docs/EPIC-IMPLEMENTER-INSTALL.md +849 -0
  93. package/docs/EPIC-IMPLEMENTER.md +662 -0
  94. package/docs/install/mcp-tool-integrations.md +24 -15
  95. package/docs/install/sfcc-integration.md +12 -4
  96. package/package.json +1 -1
  97. package/pipelines/plan-epic.json +0 -5
  98. package/pipelines/review-ticket.json +1 -1
  99. package/docs/CONDUCTOR.md +0 -605
  100. package/pipelines/idea-to-pr.json +0 -49
@@ -65,6 +65,10 @@ import { SFCC_VERSIONS, getSfccVersionConfig } from "./config.js";
65
65
  import { publishSfccVersion } from "./tools-configured.js";
66
66
  import { resolveSfccCredentials } from "./credentials.js";
67
67
  import { formatSfccFailure } from "./sfcc-result.js";
68
+ import { canonicalizeSfccHostname, formatSafeHost } from "./hostname.js";
69
+ // `canonicalizeSfccHostname` moved to hostname.ts; re-exported so existing
70
+ // importers of this module are unaffected.
71
+ export { canonicalizeSfccHostname };
68
72
  // ---------------------------------------------------------------------------
69
73
  // Hostname policy
70
74
  // ---------------------------------------------------------------------------
@@ -108,47 +112,8 @@ export const APPROVED_SANDBOX_HOST_FORMS = [
108
112
  "<realm>-<nnn>.sandbox.dx.commercecloud.salesforce.com",
109
113
  "<realm>-<nnn>.dx.commercecloud.salesforce.com",
110
114
  ];
111
- /** One DNS label: 1–63 chars, alphanumeric, internal hyphens only. */
112
- const DNS_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
113
115
  /** Form B's leading label: an SFCC realm id followed by a numeric instance. */
114
116
  const REALM_INSTANCE_LABEL = /^[a-z0-9]+-\d{3}$/;
115
- /**
116
- * Reduce a raw hostname to a canonical comparable form, or `null` if it is not
117
- * a bare hostname at all.
118
- *
119
- * Only DNS-equivalent normalization is applied — lowercasing and stripping a
120
- * single trailing root dot. Everything else is a rejection rather than a
121
- * repair: a value carrying a scheme, port, path, query, credentials, or
122
- * whitespace is not a hostname, and "cleaning it up" would mean guessing which
123
- * part the operator meant. Guessing is how a boundary gets bypassed, so this
124
- * returns `null` and the caller fails closed.
125
- */
126
- export function canonicalizeSfccHostname(raw) {
127
- if (typeof raw !== "string")
128
- return null;
129
- let value = raw.trim().toLowerCase();
130
- if (value.endsWith("."))
131
- value = value.slice(0, -1);
132
- if (value === "" || value.length > 253)
133
- return null;
134
- // Anything that is not strictly `label(.label)*` — scheme, port, path, query,
135
- // userinfo, whitespace, IPv6 brackets, wildcards.
136
- if (!/^[a-z0-9.-]+$/.test(value))
137
- return null;
138
- if (value.includes(".."))
139
- return null;
140
- const labels = value.split(".");
141
- if (labels.length < 2)
142
- return null;
143
- if (!labels.every((label) => DNS_LABEL.test(label)))
144
- return null;
145
- // A bare IPv4 address is not a sandbox hostname and must not be canonicalized
146
- // into one; the suffix check below would reject it anyway, but rejecting here
147
- // keeps "is this a hostname?" and "is this host approved?" separate concerns.
148
- if (/^\d+(\.\d+)*$/.test(value))
149
- return null;
150
- return value;
151
- }
152
117
  /**
153
118
  * Decide whether a canonical hostname is an approved developer sandbox.
154
119
  *
@@ -216,9 +181,9 @@ export function isApprovedSfccSandboxHost(canonical) {
216
181
  * raw `dw.json` contents never enter this envelope.
217
182
  */
218
183
  function targetNotSandbox(rejectedHostname) {
219
- // A malformed value is echoed back bounded, so a pathological configuration
220
- // value cannot inflate the response.
221
- const safeRejected = rejectedHostname.length > 120 ? `${rejectedHostname.slice(0, 120)}…` : rejectedHostname;
184
+ // A valid host is echoed in full but bounded; anything that is not a bare
185
+ // hostname is replaced by a fixed label so arbitrary input is never echoed.
186
+ const safeRejected = formatSafeHost(rejectedHostname);
222
187
  return formatSfccFailure({
223
188
  code: "TARGET_NOT_SANDBOX",
224
189
  status: 403,
@@ -267,9 +232,19 @@ export function withSfccTargetBoundary(handler, deps = {}) {
267
232
  // actually returned.
268
233
  const rawInstance = typeof safeArgs.instance === "string" ? safeArgs.instance : undefined;
269
234
  const explicitHostname = rawInstance && rawInstance.includes(".") ? rawInstance : undefined;
235
+ // Refuse an unsafe explicit host BEFORE any credential lookup. The check
236
+ // below on the resolved host stays: this one only adds a refusal earlier, so
237
+ // an unknown host never reaches the resolver, and a host that resolution
238
+ // selects is still validated after it.
239
+ if (explicitHostname !== undefined) {
240
+ const explicitCanonical = canonicalizeSfccHostname(explicitHostname);
241
+ if (explicitCanonical === null || !isApprovedSfccSandboxHost(explicitCanonical)) {
242
+ return targetNotSandbox(explicitHostname);
243
+ }
244
+ }
270
245
  const credResult = await resolve(explicitHostname);
271
246
  if (!credResult.ok) {
272
- return handler(safeArgs, { approved: false, error: credResult.error });
247
+ return handler(safeArgs, { approved: false, error: credResult.error, kind: credResult.kind });
273
248
  }
274
249
  const rawHostname = credResult.credentials.hostname;
275
250
  const canonical = canonicalizeSfccHostname(rawHostname);
@@ -334,6 +309,22 @@ function notConfigured(failureClass, message) {
334
309
  details: { failure_class: failureClass },
335
310
  });
336
311
  }
312
+ /**
313
+ * Build the failure for a credential problem on a repository that IS set up.
314
+ *
315
+ * The repo has a `dw.json` (or the caller named an explicit instance), so the
316
+ * caller can fix the input. That is why this carries no `NO_RETRY_INSTRUCTION`
317
+ * and is a 400, not the 503 "not configured" envelope.
318
+ */
319
+ function credentialResolutionFailed(message) {
320
+ return formatSfccFailure({
321
+ code: "CREDENTIAL_RESOLUTION_FAILED",
322
+ status: 400,
323
+ message,
324
+ source: "gate",
325
+ details: { failure_class: "credential-resolution" },
326
+ });
327
+ }
337
328
  // ---------------------------------------------------------------------------
338
329
  // Gate
339
330
  // ---------------------------------------------------------------------------
@@ -402,6 +393,11 @@ export function withSfccGate(deps, handler) {
402
393
  // reaches here — the boundary already refused it — so the only remaining
403
394
  // case is credential resolution having failed outright.
404
395
  if (!target.approved) {
396
+ // Only a genuinely absent dw.json means "this repo is not set up for
397
+ // SFCC". Every other resolver failure is fixable by the caller.
398
+ if (target.kind !== undefined && target.kind !== "no-dw-json") {
399
+ return credentialResolutionFailed(target.error);
400
+ }
405
401
  return notConfigured("missing-dw-json", `SFCC credential resolution failed: ${target.error} ` +
406
402
  "Ensure a dw.json file exists in your project root with " +
407
403
  "hostname, client-id, and client-secret fields.");
@@ -13,6 +13,7 @@
13
13
  * where a caller can read it without parsing text and `result.isError === true`
14
14
  * still detects the failure.
15
15
  */
16
+ import { OCAPI_SETTINGS_STRUCTURE_VERSION } from "./config.js";
16
17
  import { formatSfccWriteFailure } from "./sfcc-result.js";
17
18
  // ---------------------------------------------------------------------------
18
19
  // Write resource families
@@ -25,7 +26,7 @@ export const OCAPI_WRITE_RESOURCE_IDS = [
25
26
  "/system_object_definitions",
26
27
  "/system_object_definitions/**",
27
28
  "/custom_object_definitions/**",
28
- "/site_preferences/**",
29
+ "/sites/*/site_preferences/**",
29
30
  ];
30
31
  /**
31
32
  * The exact HTTP methods each resource family needs, derived from the methods
@@ -50,27 +51,32 @@ export const OCAPI_WRITE_RESOURCE_IDS = [
50
51
  * custom preference create.
51
52
  * /custom_object_definitions/** put, patch (+get for ETag) — attribute
52
53
  * definition create/update.
53
- * /site_preferences/** patch (+get) — site_preference_values_set,
54
- * a direct PATCH with no ETag round trip.
54
+ * site-scoped site_preferences patch (+get) — site_preference_values_set,
55
+ * a direct PATCH with no ETag round trip on
56
+ * the site-scoped resource (the org-level
57
+ * /site_preferences/** is read/search only).
55
58
  */
56
59
  export const OCAPI_WRITE_RESOURCE_METHODS = {
57
60
  "/system_object_definitions": ["get"],
58
61
  "/system_object_definitions/**": ["get", "put", "patch"],
59
62
  "/custom_object_definitions/**": ["get", "put", "patch"],
60
- "/site_preferences/**": ["get", "patch"],
63
+ "/sites/*/site_preferences/**": ["get", "patch"],
61
64
  };
62
65
  // ---------------------------------------------------------------------------
63
66
  // Grant JSON builders
64
67
  // ---------------------------------------------------------------------------
65
68
  /**
66
- * Build the OCAPI Settings write-grant object for the given version.
69
+ * Build the OCAPI Settings write-grant object.
70
+ *
71
+ * `_v` is the settings-structure version, never an OCAPI API version, so no
72
+ * caller can override it.
67
73
  *
68
74
  * The `clientIdPlaceholder` stays as `<YOUR_CLIENT_ID>` by default so the output
69
75
  * is safe to print and paste — the real resolved client_id is never substituted.
70
76
  */
71
- export function buildOcapiWriteGrantSettings(ocapiVersion, clientIdPlaceholder = "<YOUR_CLIENT_ID>") {
77
+ export function buildOcapiWriteGrantSettings(clientIdPlaceholder = "<YOUR_CLIENT_ID>") {
72
78
  return {
73
- _v: ocapiVersion,
79
+ _v: OCAPI_SETTINGS_STRUCTURE_VERSION,
74
80
  clients: [
75
81
  {
76
82
  client_id: clientIdPlaceholder,
@@ -85,8 +91,8 @@ export function buildOcapiWriteGrantSettings(ocapiVersion, clientIdPlaceholder =
85
91
  };
86
92
  }
87
93
  /** Pretty-printed JSON string of the write-grant settings. */
88
- export function formatOcapiWriteGrantJson(ocapiVersion, clientIdPlaceholder = "<YOUR_CLIENT_ID>") {
89
- return JSON.stringify(buildOcapiWriteGrantSettings(ocapiVersion, clientIdPlaceholder), null, 2);
94
+ export function formatOcapiWriteGrantJson(clientIdPlaceholder = "<YOUR_CLIENT_ID>") {
95
+ return JSON.stringify(buildOcapiWriteGrantSettings(clientIdPlaceholder), null, 2);
90
96
  }
91
97
  // ---------------------------------------------------------------------------
92
98
  // 403 write-grant UX
@@ -115,7 +121,7 @@ export function ocapiWriteForbiddenMessage(operation, path) {
115
121
  * same way an OCAPI read fault body is preserved.
116
122
  */
117
123
  export function writeGrantForbiddenResult(params) {
118
- const { operation, path, ocapiVersion, body } = params;
124
+ const { operation, path, body } = params;
119
125
  return formatSfccWriteFailure({
120
126
  code: "OCAPI_WRITE_ERROR",
121
127
  status: 403,
@@ -127,7 +133,7 @@ export function writeGrantForbiddenResult(params) {
127
133
  remediation: {
128
134
  business_manager_path: OCAPI_WRITE_GRANT_BUSINESS_MANAGER_PATH,
129
135
  instruction: OCAPI_WRITE_GRANT_CLIENT_ID_INSTRUCTION,
130
- write_grant: buildOcapiWriteGrantSettings(ocapiVersion),
136
+ write_grant: buildOcapiWriteGrantSettings(),
131
137
  },
132
138
  ...(body === undefined ? {} : { body }),
133
139
  },
@@ -16,7 +16,6 @@
16
16
  * pre-transport ones.
17
17
  */
18
18
  import { writeGrantForbiddenResult } from "./write-grants.js";
19
- import { DEFAULT_OCAPI_VERSION } from "./config.js";
20
19
  import { formatSfccWriteFailure } from "./sfcc-result.js";
21
20
  import { redactSfccBody } from "./read-body.js";
22
21
  /** Wrap text in the standard MCP text-result shape (success path only). */
@@ -72,12 +71,14 @@ function ocapiWriteFaultMessage(body, status) {
72
71
  * `Response.status` verbatim, so the reported status *is* the upstream status
73
72
  * and the shared formatter would rightly omit a duplicate.
74
73
  */
75
- export function formatOcapiWriteToolResult(result, operation, path, ocapiVersion = DEFAULT_OCAPI_VERSION) {
74
+ export function formatOcapiWriteToolResult(result, operation, path,
75
+ // Kept for call compatibility. It no longer reaches the grant JSON: `_v` there
76
+ // is the settings-structure version, which an API version must not override.
77
+ _ocapiVersion) {
76
78
  if (result.status === 403) {
77
79
  return writeGrantForbiddenResult({
78
80
  operation,
79
81
  path,
80
- ocapiVersion,
81
82
  body: result.body,
82
83
  });
83
84
  }
@@ -2,9 +2,11 @@
2
2
  * SFCC site-preference value write tool (BAPI-584).
3
3
  *
4
4
  * Implements:
5
- * site_preference_values_set — PATCH /site_preferences/preference_groups/{group}/sandbox
5
+ * site_preference_values_set — PATCH /sites/{site_id}/site_preferences/preference_groups/{group}/sandbox
6
6
  *
7
- * Sets custom site-preference VALUES through the OCAPI Data API. Sandbox-only:
7
+ * Sets custom site-preference VALUES through the OCAPI Data API for ONE named
8
+ * site. `site_id` is required and is never derived from `dw.json`: a destructive
9
+ * write must not pick its target silently. Sandbox-only:
8
10
  * the value endpoint would otherwise accept `production`, so the `{instance}`
9
11
  * segment is forced to the `sandbox` literal AFTER the shared sandbox guard has
10
12
  * validated the requested instance. The body is a FLAT map of `c_`-prefixed
@@ -35,7 +37,17 @@ const INSTANCE_ENUM = z.enum(["staging", "development", "sandbox", "production"]
35
37
  // safeguard on the OCAPI route.
36
38
  const INSTANCE_DESCRIBE = "OCAPI instance context. v1 supports the 'sandbox' context only; any other " +
37
39
  "value is rejected before OCAPI is called.";
40
+ // A site id is one URL path segment. This is a conservative single-segment
41
+ // pattern (no separators, whitespace, or dots), not a claim about the full set
42
+ // OCAPI allows; the exact constraint is confirmed by owner check OR-1.
43
+ const SITE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
44
+ const SITE_ID_DESCRIBE = "Site id to write to; required, never defaulted. Not 'instance'.";
38
45
  const sitePreferenceValuesSetInput = z.object({
46
+ site_id: z
47
+ .string()
48
+ .min(1)
49
+ .regex(SITE_ID_PATTERN, "site_id must contain only letters, digits, '_' or '-'.")
50
+ .describe(SITE_ID_DESCRIBE),
39
51
  group: z.string().describe("Custom site preference group id, e.g. 'LLMIntegration'."),
40
52
  instance: INSTANCE_ENUM.optional().default("sandbox").describe(INSTANCE_DESCRIBE),
41
53
  values: sitePreferenceValuesPatchBodySchema.describe("Flat map of c_-prefixed preference ids to values (string, number, boolean, or string[])."),
@@ -44,12 +56,12 @@ const sitePreferenceValuesSetInput = z.object({
44
56
  // Path builder
45
57
  // ---------------------------------------------------------------------------
46
58
  /**
47
- * Build the site-preference group PATCH path. The `{instance}` segment is the
59
+ * Build the site-scoped site-preference group PATCH path. The `{instance}` segment is the
48
60
  * `sandbox` literal — a non-sandbox instance is rejected by the guard before
49
61
  * this is called, so a non-sandbox value can never leak into transport.
50
62
  */
51
- export function sitePreferenceGroupPath(group) {
52
- return `/site_preferences/preference_groups/${encodedSegment(group)}/sandbox`;
63
+ export function sitePreferenceGroupPath(siteId, group) {
64
+ return `/sites/${encodedSegment(siteId)}/site_preferences/preference_groups/${encodedSegment(group)}/sandbox`;
53
65
  }
54
66
  // ---------------------------------------------------------------------------
55
67
  // Handler
@@ -66,7 +78,7 @@ export function buildSitePreferenceValuesSetHandler(gateDeps) {
66
78
  const guard = rejectIfNotSandboxForWrite(parsed.instance);
67
79
  if (guard)
68
80
  return guard;
69
- const path = sitePreferenceGroupPath(parsed.group);
81
+ const path = sitePreferenceGroupPath(parsed.site_id, parsed.group);
70
82
  const body = buildSitePreferenceValuesPatchPayload(parsed.values);
71
83
  try {
72
84
  // Direct PATCH (no ETag round trip): the value endpoint does not gate on
@@ -92,10 +104,10 @@ export const SITE_PREFERENCE_WRITE_TOOL_NAMES = ["site_preference_values_set"];
92
104
  export function registerSitePreferenceWriteTools(registerTool, deps) {
93
105
  const { gateDeps } = deps;
94
106
  registerTool("site_preference_values_set", {
95
- description: "Set site-preference VALUES via PATCH /site_preferences/preference_groups/{group}/sandbox. " +
107
+ description: "Set site-preference VALUES via PATCH /sites/{site_id}/site_preferences/preference_groups/{group}/sandbox. " +
96
108
  "Preference must already be defined via custom_preference_definition_create. Sandbox-only, " +
97
109
  "destructive; flat c_-prefixed id→value map (string/number/boolean/string[]). " +
98
- "Bad group → 404 CustomPreferenceGroupNotFoundException. 403 → paste-ready grant JSON.",
110
+ "Bad group → 404. 403 → paste-ready grant JSON.",
99
111
  inputSchema: sitePreferenceValuesSetInput,
100
112
  annotations: WRITE_ANNOTATIONS,
101
113
  }, buildSitePreferenceValuesSetHandler(gateDeps));