@bridge_gpt/mcp-server 0.2.18 → 0.2.20

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/CONDUCTOR.md +75 -0
  2. package/README.md +8 -5
  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 +5 -3
  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/local-merge.js +458 -95
  20. package/build/conductor/merge-ledger.js +6 -4
  21. package/build/conductor/pr-ci-producer.js +17 -2
  22. package/build/conductor/producer-ledger.js +1 -1
  23. package/build/conductor/store.js +161 -18
  24. package/build/conductor/supervisor-merge.js +32 -5
  25. package/build/conductor/taxonomy.js +8 -0
  26. package/build/conductor/tools.js +28 -6
  27. package/build/conductor/worker-ledger-cli.js +244 -0
  28. package/build/conductor-bin.js +1884 -6917
  29. package/build/doctor.js +8 -0
  30. package/build/estimate-epic.js +84 -0
  31. package/build/executor/cli.js +229 -0
  32. package/build/executor/credentials.js +65 -0
  33. package/build/executor/deps.js +117 -0
  34. package/build/executor/env.js +79 -0
  35. package/build/executor/heartbeat.js +59 -0
  36. package/build/executor/http-client.js +131 -0
  37. package/build/executor/index.js +10 -0
  38. package/build/executor/job-errors.js +55 -0
  39. package/build/executor/job-log-registry.js +110 -0
  40. package/build/executor/job-runner.js +822 -0
  41. package/build/executor/job-types.js +60 -0
  42. package/build/executor/merge-job.js +229 -0
  43. package/build/executor/observation.js +123 -0
  44. package/build/executor/permissions.js +79 -0
  45. package/build/executor/preflight.js +144 -0
  46. package/build/executor/process.js +81 -0
  47. package/build/executor/prompt-spec.js +235 -0
  48. package/build/executor/results.js +134 -0
  49. package/build/executor/resume-pre-spawn.js +179 -0
  50. package/build/executor/runner.js +98 -0
  51. package/build/executor/terminal-mutation.js +34 -0
  52. package/build/executor/test-clock.js +109 -0
  53. package/build/executor/types.js +18 -0
  54. package/build/executor/verdict-artifact.js +53 -0
  55. package/build/executor/viewer-tabs.js +78 -0
  56. package/build/executor/watch-cli.js +113 -0
  57. package/build/executor/worker-command.js +106 -0
  58. package/build/executor/worker-finalization.js +177 -0
  59. package/build/executor/worker-log.js +92 -0
  60. package/build/executor/worktree-gc.js +134 -0
  61. package/build/executor/worktree-inspection.js +86 -0
  62. package/build/executor/worktree.js +103 -0
  63. package/build/index.js +14534 -10416
  64. package/build/mcp-invoke.js +19 -3
  65. package/build/mcp-provisioning.js +31 -25
  66. package/build/mcp-registration-doctor.js +27 -7
  67. package/build/mcp-server-invocation.js +152 -0
  68. package/build/pipelines.generated.js +16 -20
  69. package/build/readme.generated.js +1 -1
  70. package/build/sfcc/client.js +192 -50
  71. package/build/sfcc/ocapi-write-faults.js +94 -0
  72. package/build/sfcc/permissions.js +7 -22
  73. package/build/sfcc/reads-site-preference.js +52 -19
  74. package/build/sfcc/register.js +9 -0
  75. package/build/sfcc/write-grants.js +80 -0
  76. package/build/sfcc/write-guard.js +39 -0
  77. package/build/sfcc/write-result.js +47 -0
  78. package/build/sfcc/write-tool-common.js +85 -0
  79. package/build/sfcc/writes-custom-object-def.js +141 -0
  80. package/build/sfcc/writes-object-attribute-payloads.js +97 -0
  81. package/build/sfcc/writes-site-preference-payloads.js +59 -0
  82. package/build/sfcc/writes-site-preference.js +96 -0
  83. package/build/sfcc/writes-system-object-payloads.js +213 -0
  84. package/build/sfcc/writes-system-object.js +348 -0
  85. package/build/sfcc/writes.js +66 -0
  86. package/build/start-tickets-conductor.js +25 -93
  87. package/build/start-tickets-prereqs.js +152 -1
  88. package/build/start-tickets.js +96 -158
  89. package/build/version.generated.js +1 -1
  90. package/build/visual-diff-worker.js +313 -0
  91. package/build/visual-diff.js +632 -0
  92. package/build/worktree-core.js +202 -0
  93. package/package.json +8 -4
  94. package/pipelines/idea-to-ticket.json +7 -0
  95. package/pipelines/review-ticket.json +5 -18
  96. package/public/css/main.min.css +1612 -107
  97. package/public/css/main.min.css.map +1 -1
  98. package/public/js/main.min.js +10267 -1
  99. package/public/js/main.min.js.map +1 -1
  100. package/smoke-test/SMOKE-TEST.md +2 -1
@@ -0,0 +1,348 @@
1
+ /**
2
+ * SFCC system-object metadata write tools (BAPI-583).
3
+ *
4
+ * Live OCAPI write surface mirroring `reads-system-object.ts`. Implements six
5
+ * sandbox-only write operations:
6
+ * system_object_attribute_definition_create — PUT /system_object_definitions/{type}/attribute_definitions/{id}
7
+ * system_object_attribute_definition_update — PATCH /system_object_definitions/{type}/attribute_definitions/{id}
8
+ * system_object_attribute_group_create — PUT /system_object_definitions/{type}/attribute_groups/{id}
9
+ * system_object_attribute_group_update — PATCH /system_object_definitions/{type}/attribute_groups/{id}
10
+ * system_object_attribute_assign_to_group — PUT /system_object_definitions/{type}/attribute_groups/{group}/attribute_definitions/{def}
11
+ * custom_preference_definition_create — PUT /system_object_definitions/{SitePreferences|OrganizationPreferences}/attribute_definitions/{id}
12
+ *
13
+ * Every handler follows the BAPI-582 foundation write recipe: parse inputs →
14
+ * `rejectIfNotSandboxForWrite` → build a decoupled payload → `ocapiPut`/`ocapiPatch`
15
+ * → `formatOcapiWriteToolResult` (which echoes the OCAPI write-grant JSON on 403).
16
+ */
17
+ import { z } from "zod";
18
+ import { ocapiPut, ocapiPatch } from "./client.js";
19
+ import { withSfccGate } from "./tool-wrapper.js";
20
+ import { rejectIfNotSandboxForWrite } from "./write-guard.js";
21
+ import { formatOcapiWriteToolResult } from "./write-result.js";
22
+ import { objectAttributeDefinitionCreateBodySchema, objectAttributeDefinitionPatchBodySchema, attributeGroupPatchBodySchema, localizedStringSchema, SfccWritePayloadFault, buildObjectAttributeDefinitionCreatePayload, buildObjectAttributeDefinitionPatchPayload, buildAttributeGroupPutPayload, buildAttributeGroupPatchPayload, buildEmptyRelationPayload, } from "./writes-system-object-payloads.js";
23
+ // ---------------------------------------------------------------------------
24
+ // Annotations (destructive OCAPI writes against a developer sandbox)
25
+ // ---------------------------------------------------------------------------
26
+ const WRITE_ANNOTATIONS = {
27
+ readOnlyHint: false,
28
+ destructiveHint: true,
29
+ idempotentHint: false,
30
+ openWorldHint: true,
31
+ };
32
+ function textResult(text) {
33
+ return { content: [{ type: "text", text }] };
34
+ }
35
+ /** Safe 400 envelope for a Zod validation failure — no stacks, no raw thrown strings. */
36
+ function zodValidationEnvelope(err) {
37
+ return textResult(JSON.stringify({
38
+ error: "VALIDATION_ERROR",
39
+ status: 400,
40
+ message: "Invalid SFCC write tool input.",
41
+ issues: err.issues,
42
+ }));
43
+ }
44
+ /** Safe 400 envelope for a local payload-shape fault, in OCAPI-fault vocabulary. */
45
+ function payloadFaultEnvelope(fault) {
46
+ return textResult(JSON.stringify({
47
+ error: "OCAPI_WRITE_ERROR",
48
+ status: fault.status,
49
+ fault: { type: fault.faultType, message: fault.message },
50
+ }));
51
+ }
52
+ /** Safe 500 envelope — never includes stacks, secrets, headers, or raw error text. */
53
+ function unexpectedEnvelope() {
54
+ return textResult(JSON.stringify({
55
+ error: "INTERNAL_ERROR",
56
+ status: 500,
57
+ message: "Unexpected SFCC write tool failure",
58
+ }));
59
+ }
60
+ /** Map a pre-transport error (parse or payload-build) to its safe envelope. */
61
+ function preTransportErrorEnvelope(err) {
62
+ if (err instanceof z.ZodError)
63
+ return zodValidationEnvelope(err);
64
+ if (err instanceof SfccWritePayloadFault)
65
+ return payloadFaultEnvelope(err);
66
+ return unexpectedEnvelope();
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // OCAPI path builders (every dynamic segment URL-encoded)
70
+ // ---------------------------------------------------------------------------
71
+ function encodedSegment(segment) {
72
+ return encodeURIComponent(segment);
73
+ }
74
+ function attributeDefinitionPath(objectType, attributeId) {
75
+ return `/system_object_definitions/${encodedSegment(objectType)}/attribute_definitions/${encodedSegment(attributeId)}`;
76
+ }
77
+ function attributeGroupPath(objectType, groupId) {
78
+ return `/system_object_definitions/${encodedSegment(objectType)}/attribute_groups/${encodedSegment(groupId)}`;
79
+ }
80
+ function attributeGroupAssignmentPath(objectType, groupId, attributeId) {
81
+ return (`/system_object_definitions/${encodedSegment(objectType)}` +
82
+ `/attribute_groups/${encodedSegment(groupId)}` +
83
+ `/attribute_definitions/${encodedSegment(attributeId)}`);
84
+ }
85
+ /** Map a preference scope to the OCAPI system object that backs preference attributes. */
86
+ function preferenceObjectTypeForScope(scope) {
87
+ return scope === "site" ? "SitePreferences" : "OrganizationPreferences";
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // Input schemas
91
+ // ---------------------------------------------------------------------------
92
+ /** Optional sandbox instance context. Writes are sandbox-only (see write-guard). */
93
+ const instanceSchema = z
94
+ .string()
95
+ .optional()
96
+ .describe("Sandbox-only: omit (defaults to sandbox) or pass \"sandbox\".");
97
+ const createAttributeDefinitionInput = z.object({
98
+ object_type: z.string().describe("System object type, e.g. \"Product\"."),
99
+ attribute_id: z.string().describe("Attribute id (URL id); the body id must match it."),
100
+ definition: objectAttributeDefinitionCreateBodySchema.describe("OCAPI ObjectAttributeDefinition create body (requires value_type)."),
101
+ instance: instanceSchema,
102
+ });
103
+ const updateAttributeDefinitionInput = z.object({
104
+ object_type: z.string().describe("System object type."),
105
+ attribute_id: z.string().describe("Attribute id (URL id)."),
106
+ patch: objectAttributeDefinitionPatchBodySchema.describe("Partial ObjectAttributeDefinition body (>=1 field)."),
107
+ instance: instanceSchema,
108
+ });
109
+ const createAttributeGroupInput = z.object({
110
+ object_type: z.string().describe("System object type."),
111
+ group_id: z.string().describe("Attribute group id (URL id)."),
112
+ display_name: localizedStringSchema.describe("Localized group display name."),
113
+ internal: z.boolean().describe("Whether the group is internal (BM-only)."),
114
+ instance: instanceSchema,
115
+ });
116
+ const updateAttributeGroupInput = z.object({
117
+ object_type: z.string().describe("System object type."),
118
+ group_id: z.string().describe("Attribute group id (URL id)."),
119
+ patch: attributeGroupPatchBodySchema.describe("Partial group body (display_name and/or internal; >=1 field)."),
120
+ instance: instanceSchema,
121
+ });
122
+ const assignAttributeToGroupInput = z.object({
123
+ object_type: z.string().describe("System object type."),
124
+ group_id: z.string().describe("Attribute group id."),
125
+ attribute_id: z.string().describe("Attribute definition id to assign."),
126
+ instance: instanceSchema,
127
+ });
128
+ const createCustomPreferenceDefinitionInput = z.object({
129
+ preference_scope: z
130
+ .enum(["site", "organization"])
131
+ .describe("site → SitePreferences; organization → OrganizationPreferences."),
132
+ preference_id: z.string().describe("Preference (attribute) id."),
133
+ definition: objectAttributeDefinitionCreateBodySchema.describe("OCAPI ObjectAttributeDefinition create body (requires value_type). " +
134
+ "Set default_value for booleans (else they resolve to null)."),
135
+ instance: instanceSchema,
136
+ });
137
+ // ---------------------------------------------------------------------------
138
+ // Handlers
139
+ // ---------------------------------------------------------------------------
140
+ function buildCreateAttributeDefinitionHandler(gateDeps) {
141
+ return withSfccGate(gateDeps, async (args, credentials) => {
142
+ let path;
143
+ let body;
144
+ try {
145
+ const parsed = createAttributeDefinitionInput.parse(args);
146
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
147
+ if (guard)
148
+ return guard;
149
+ path = attributeDefinitionPath(parsed.object_type, parsed.attribute_id);
150
+ body = buildObjectAttributeDefinitionCreatePayload(parsed.attribute_id, parsed.definition);
151
+ }
152
+ catch (err) {
153
+ return preTransportErrorEnvelope(err);
154
+ }
155
+ try {
156
+ const result = await ocapiPut(path, body, credentials);
157
+ return formatOcapiWriteToolResult(result, "PUT", path);
158
+ }
159
+ catch {
160
+ return unexpectedEnvelope();
161
+ }
162
+ });
163
+ }
164
+ function buildUpdateAttributeDefinitionHandler(gateDeps) {
165
+ return withSfccGate(gateDeps, async (args, credentials) => {
166
+ let path;
167
+ let body;
168
+ try {
169
+ const parsed = updateAttributeDefinitionInput.parse(args);
170
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
171
+ if (guard)
172
+ return guard;
173
+ path = attributeDefinitionPath(parsed.object_type, parsed.attribute_id);
174
+ body = buildObjectAttributeDefinitionPatchPayload(parsed.attribute_id, parsed.patch);
175
+ }
176
+ catch (err) {
177
+ return preTransportErrorEnvelope(err);
178
+ }
179
+ try {
180
+ // ocapiPatch performs the GET-then-If-Match round trip; 409/412 flow
181
+ // back through formatOcapiWriteToolResult rather than becoming generic errors.
182
+ const result = await ocapiPatch(path, body, credentials);
183
+ return formatOcapiWriteToolResult(result, "PATCH", path);
184
+ }
185
+ catch {
186
+ return unexpectedEnvelope();
187
+ }
188
+ });
189
+ }
190
+ function buildCreateAttributeGroupHandler(gateDeps) {
191
+ return withSfccGate(gateDeps, async (args, credentials) => {
192
+ let path;
193
+ let body;
194
+ try {
195
+ const parsed = createAttributeGroupInput.parse(args);
196
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
197
+ if (guard)
198
+ return guard;
199
+ path = attributeGroupPath(parsed.object_type, parsed.group_id);
200
+ body = buildAttributeGroupPutPayload({
201
+ display_name: parsed.display_name,
202
+ internal: parsed.internal,
203
+ });
204
+ }
205
+ catch (err) {
206
+ return preTransportErrorEnvelope(err);
207
+ }
208
+ try {
209
+ const result = await ocapiPut(path, body, credentials);
210
+ return formatOcapiWriteToolResult(result, "PUT", path);
211
+ }
212
+ catch {
213
+ return unexpectedEnvelope();
214
+ }
215
+ });
216
+ }
217
+ function buildUpdateAttributeGroupHandler(gateDeps) {
218
+ return withSfccGate(gateDeps, async (args, credentials) => {
219
+ let path;
220
+ let body;
221
+ try {
222
+ const parsed = updateAttributeGroupInput.parse(args);
223
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
224
+ if (guard)
225
+ return guard;
226
+ path = attributeGroupPath(parsed.object_type, parsed.group_id);
227
+ body = buildAttributeGroupPatchPayload(parsed.patch);
228
+ }
229
+ catch (err) {
230
+ return preTransportErrorEnvelope(err);
231
+ }
232
+ try {
233
+ const result = await ocapiPatch(path, body, credentials);
234
+ return formatOcapiWriteToolResult(result, "PATCH", path);
235
+ }
236
+ catch {
237
+ return unexpectedEnvelope();
238
+ }
239
+ });
240
+ }
241
+ function buildAssignAttributeToGroupHandler(gateDeps) {
242
+ return withSfccGate(gateDeps, async (args, credentials) => {
243
+ let path;
244
+ try {
245
+ const parsed = assignAttributeToGroupInput.parse(args);
246
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
247
+ if (guard)
248
+ return guard;
249
+ path = attributeGroupAssignmentPath(parsed.object_type, parsed.group_id, parsed.attribute_id);
250
+ }
251
+ catch (err) {
252
+ return preTransportErrorEnvelope(err);
253
+ }
254
+ try {
255
+ const result = await ocapiPut(path, buildEmptyRelationPayload(), credentials);
256
+ return formatOcapiWriteToolResult(result, "PUT", path);
257
+ }
258
+ catch {
259
+ return unexpectedEnvelope();
260
+ }
261
+ });
262
+ }
263
+ function buildCreateCustomPreferenceDefinitionHandler(gateDeps) {
264
+ return withSfccGate(gateDeps, async (args, credentials) => {
265
+ let path;
266
+ let body;
267
+ try {
268
+ const parsed = createCustomPreferenceDefinitionInput.parse(args);
269
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
270
+ if (guard)
271
+ return guard;
272
+ const objectType = preferenceObjectTypeForScope(parsed.preference_scope);
273
+ path = attributeDefinitionPath(objectType, parsed.preference_id);
274
+ body = buildObjectAttributeDefinitionCreatePayload(parsed.preference_id, parsed.definition);
275
+ }
276
+ catch (err) {
277
+ return preTransportErrorEnvelope(err);
278
+ }
279
+ try {
280
+ const result = await ocapiPut(path, body, credentials);
281
+ return formatOcapiWriteToolResult(result, "PUT", path);
282
+ }
283
+ catch {
284
+ return unexpectedEnvelope();
285
+ }
286
+ });
287
+ }
288
+ // ---------------------------------------------------------------------------
289
+ // Registration
290
+ // ---------------------------------------------------------------------------
291
+ /** Concrete write tool names registered by this module. */
292
+ export const SYSTEM_OBJECT_WRITE_TOOL_NAMES = [
293
+ "system_object_attribute_definition_create",
294
+ "system_object_attribute_definition_update",
295
+ "system_object_attribute_group_create",
296
+ "system_object_attribute_group_update",
297
+ "system_object_attribute_assign_to_group",
298
+ "custom_preference_definition_create",
299
+ ];
300
+ /**
301
+ * Register the six SFCC system-object write tools. Called from the write seam
302
+ * (`registerSfccWriteTools` in writes.ts) — no direct `register.ts`/`index.ts` edits.
303
+ */
304
+ export function registerSystemObjectWriteTools(registerTool, deps) {
305
+ const { gateDeps } = deps;
306
+ registerTool("system_object_attribute_definition_create", {
307
+ description: "Create a system-object attribute definition (sandbox-only, destructive). " +
308
+ "PUT .../attribute_definitions/{id}; body id must equal URL id and system must be false. " +
309
+ "Returns 201. HTTP 403 echoes the OCAPI write-grant JSON.",
310
+ inputSchema: createAttributeDefinitionInput,
311
+ annotations: WRITE_ANNOTATIONS,
312
+ }, buildCreateAttributeDefinitionHandler(gateDeps));
313
+ registerTool("system_object_attribute_definition_update", {
314
+ description: "Update a system-object attribute definition (sandbox-only, destructive). " +
315
+ "PATCH .../attribute_definitions/{id} via the ETag GET-then-If-Match round trip. " +
316
+ "Returns 200; surfaces 409/412 on ETag conflicts. HTTP 403 echoes the write-grant JSON.",
317
+ inputSchema: updateAttributeDefinitionInput,
318
+ annotations: WRITE_ANNOTATIONS,
319
+ }, buildUpdateAttributeDefinitionHandler(gateDeps));
320
+ registerTool("system_object_attribute_group_create", {
321
+ description: "Create a system-object attribute group (sandbox-only, destructive). " +
322
+ "PUT .../attribute_groups/{id} with a minimal body (display_name + internal). " +
323
+ "HTTP 403 echoes the OCAPI write-grant JSON.",
324
+ inputSchema: createAttributeGroupInput,
325
+ annotations: WRITE_ANNOTATIONS,
326
+ }, buildCreateAttributeGroupHandler(gateDeps));
327
+ registerTool("system_object_attribute_group_update", {
328
+ description: "Update a system-object attribute group (sandbox-only, destructive). " +
329
+ "PATCH .../attribute_groups/{id} via the ETag round trip (display_name and/or internal). " +
330
+ "HTTP 403 echoes the OCAPI write-grant JSON.",
331
+ inputSchema: updateAttributeGroupInput,
332
+ annotations: WRITE_ANNOTATIONS,
333
+ }, buildUpdateAttributeGroupHandler(gateDeps));
334
+ registerTool("system_object_attribute_assign_to_group", {
335
+ description: "Assign an attribute definition to an attribute group (sandbox-only, destructive). " +
336
+ "PUT .../attribute_groups/{group}/attribute_definitions/{def} with an empty body. " +
337
+ "HTTP 403 echoes the OCAPI write-grant JSON.",
338
+ inputSchema: assignAttributeToGroupInput,
339
+ annotations: WRITE_ANNOTATIONS,
340
+ }, buildAssignAttributeToGroupHandler(gateDeps));
341
+ registerTool("custom_preference_definition_create", {
342
+ description: "Create a custom preference definition (sandbox-only, destructive). " +
343
+ "PUT .../{SitePreferences|OrganizationPreferences}/attribute_definitions/{id}. " +
344
+ "Booleans resolve to null unless default_value is set. HTTP 403 echoes the write-grant JSON.",
345
+ inputSchema: createCustomPreferenceDefinitionInput,
346
+ annotations: WRITE_ANNOTATIONS,
347
+ }, buildCreateCustomPreferenceDefinitionHandler(gateDeps));
348
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * SFCC write-tool registration aggregator (BAPI-581 / BAPI-582 foundation,
3
+ * populated by BAPI-584).
4
+ *
5
+ * This is the registration seam that write-surface tickets register through.
6
+ * `register.ts` wires this behind the existing `includeReadTools` SFCC gate, so
7
+ * the write tools become default-on with the reads without touching
8
+ * `register.ts` or `index.ts`.
9
+ *
10
+ * Each write handler follows the foundation recipe:
11
+ * 1. Parse inputs.
12
+ * 2. Call `rejectIfNotSandboxForWrite` (write-guard.ts) BEFORE payload
13
+ * construction or any OCAPI mutation.
14
+ * 3. Build the SFCC payload in a tool-specific payload helper.
15
+ * 4. Call `ocapiPut` / `ocapiPatch` / `ocapiPatchDirect` (client.ts).
16
+ * 5. Route the result through `formatOcapiWriteToolResult` (write-result.ts).
17
+ */
18
+ import { registerSystemObjectWriteTools, SYSTEM_OBJECT_WRITE_TOOL_NAMES, } from "./writes-system-object.js";
19
+ import { registerSfccCustomObjectDefWriteTools, CUSTOM_OBJECT_DEF_WRITE_TOOL_NAMES, } from "./writes-custom-object-def.js";
20
+ import { registerSitePreferenceWriteTools, SITE_PREFERENCE_WRITE_TOOL_NAMES, } from "./writes-site-preference.js";
21
+ // ---------------------------------------------------------------------------
22
+ // Tool name registry
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Write tool names registered by this seam (centralized inventory). Extended by
26
+ * each write-surface ticket — BAPI-583 adds the six system-object write tools,
27
+ * BAPI-584 adds the custom-object attribute-definition and site-preference value
28
+ * write tools.
29
+ */
30
+ export const SFCC_WRITE_TOOL_NAMES = [
31
+ ...SYSTEM_OBJECT_WRITE_TOOL_NAMES,
32
+ ...CUSTOM_OBJECT_DEF_WRITE_TOOL_NAMES,
33
+ ...SITE_PREFERENCE_WRITE_TOOL_NAMES,
34
+ ];
35
+ // ---------------------------------------------------------------------------
36
+ // Registration
37
+ // ---------------------------------------------------------------------------
38
+ /**
39
+ * Register all SFCC write tools through the host's registerTool wrapper.
40
+ *
41
+ * Registration is delegated to per-surface write modules. Each write handler
42
+ * MUST:
43
+ * 1. Parse inputs.
44
+ * 2. Call `rejectIfNotSandboxForWrite` (write-guard.ts) BEFORE payload
45
+ * construction or any OCAPI mutation.
46
+ * 3. Build the SFCC payload in a tool-specific payload helper.
47
+ * 4. Call `ocapiPut` or `ocapiPatch` (client.ts) for transport.
48
+ * 5. Route the result through `formatOcapiWriteToolResult` (write-result.ts).
49
+ *
50
+ * Each handler is additionally call-time gated by `withSfccGate` and
51
+ * sandbox-guarded, so registering them here (default-on with the reads) does
52
+ * not weaken the per-call safety model.
53
+ *
54
+ * BAPI-583 registers the system-object write surface here; BAPI-584 adds the
55
+ * custom-object attribute-definition and site-preference value write surfaces
56
+ * alongside it. Later write-surface tickets add their modules the same way,
57
+ * without touching `register.ts`/`index.ts`.
58
+ */
59
+ export function registerSfccWriteTools(registerTool, deps) {
60
+ // System-object attribute/group definition writes (BAPI-583).
61
+ registerSystemObjectWriteTools(registerTool, { gateDeps: deps.gateDeps });
62
+ // Custom-object attribute-definition create/update (PUT / ETag-PATCH).
63
+ registerSfccCustomObjectDefWriteTools(registerTool, { gateDeps: deps.gateDeps });
64
+ // Site-preference value set (direct PATCH of a flat c_ map).
65
+ registerSitePreferenceWriteTools(registerTool, { gateDeps: deps.gateDeps });
66
+ }
@@ -20,8 +20,8 @@
20
20
  * metadata, or the shell command string.
21
21
  */
22
22
  import { randomBytes } from "node:crypto";
23
- import path from "node:path";
24
23
  import { fileURLToPath } from "node:url";
24
+ import { mergeClaudeSettingsWithCommandHook, detectExistingPreToolUseMatcher, provisionClaudeSettingsForWorktree, DEFAULT_PRE_TOOL_USE_MATCHER, } from "./claude-settings.js";
25
25
  import { normalizeDeclaredTouchedFiles } from "./conductor/file-scope-guard.js";
26
26
  import { resolveProfiles } from "./mcp-profile.js";
27
27
  import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
@@ -116,6 +116,10 @@ export async function createStartTicketsConductorContext(options, agent, deps) {
116
116
  supervisorMode,
117
117
  agentName: agent.name,
118
118
  cliFile: resolveBinPath("conductor-bin.js"),
119
+ // Capture the conductor process's Node executable so dispatched workers run
120
+ // conductor-bin.js under THIS runtime (which owns the ledger), never the
121
+ // worker's own Node (BAPI-527).
122
+ conductorNodePath: deps.execPath ?? process.execPath,
119
123
  hookBinPath: resolveBinPath("conductor-claude-hook-bin.js"),
120
124
  };
121
125
  if (options.epic) {
@@ -167,6 +171,10 @@ export function buildConductorWorkerEnv(context, worker, parentEnv) {
167
171
  BAPI_CONDUCTOR_GATE_NAME: context.gateName,
168
172
  BAPI_CONDUCTOR_SUPERVISOR_MODE: context.supervisorMode,
169
173
  BAPI_CONDUCTOR_CLI_FILE: context.cliFile,
174
+ // BAPI-527: the CLI file + Node runtime are a PAIR. The worker runs
175
+ // conductor-bin.js under this captured conductor Node for every worker-side
176
+ // ledger op, so the worker Node never loads better-sqlite3. Non-secret.
177
+ CONDUCTOR_NODE_PATH: context.conductorNodePath,
170
178
  };
171
179
  if (context.repoName) {
172
180
  env.BAPI_CONDUCTOR_REPO_NAME = context.repoName;
@@ -205,8 +213,10 @@ export const CONDUCTOR_HOOK_LIFECYCLE_EVENTS = [
205
213
  "SessionEnd",
206
214
  "Notification",
207
215
  ];
208
- /** Broad matcher used for `PreToolUse` when no existing matcher is present. */
209
- export const DEFAULT_PRE_TOOL_USE_MATCHER = "*";
216
+ // BAPI-534: the broad PreToolUse matcher and generic hook-merge machinery now
217
+ // live in `claude-settings.ts` (shared with the executor deny-layer path).
218
+ // Re-exported so existing importers of this name keep resolving.
219
+ export { DEFAULT_PRE_TOOL_USE_MATCHER };
210
220
  /** Shell-quote a path for embedding in a hook command string. */
211
221
  function shellQuotePath(value) {
212
222
  // Single-quote for POSIX safety; embedded single quotes are escaped. Hook
@@ -224,55 +234,15 @@ export function resolveConductorHookCommand(env, hookBinPath, execPath = process
224
234
  }
225
235
  return `${shellQuotePath(execPath)} ${shellQuotePath(hookBinPath)}`;
226
236
  }
227
- function asHookEntries(value) {
228
- return Array.isArray(value) ? value : [];
229
- }
230
- /** Does this event already register `command` as a command hook? */
231
- function entriesContainCommand(entries, command) {
232
- return entries.some((entry) => Array.isArray(entry?.hooks) &&
233
- entry.hooks.some((h) => h && h.type === "command" && h.command === command));
234
- }
235
237
  /**
236
- * Merge conductor lifecycle hooks into an existing Claude settings object
237
- * WITHOUT removing any existing settings or hooks. Idempotent: re-applying the
238
- * same command never duplicates an entry. `PreToolUse` is registered only when
239
- * `enablePreToolUse` is true, mirroring any existing matcher style.
238
+ * Merge conductor lifecycle hooks into an existing Claude settings object.
239
+ * BAPI-534: a thin wrapper over the generic {@link mergeClaudeSettingsWithCommandHook}
240
+ * bound to the conductor lifecycle events preserving the exported signature and
241
+ * behavior (idempotent; `PreToolUse` only when `enablePreToolUse`; mirrors any
242
+ * existing matcher style; never removes existing settings/hooks).
240
243
  */
241
244
  export function mergeClaudeSettingsWithConductorHook(settings, command, options = {}) {
242
- const existingHooks = settings.hooks !== null && typeof settings.hooks === "object" && !Array.isArray(settings.hooks)
243
- ? settings.hooks
244
- : {};
245
- const hooks = { ...existingHooks };
246
- const events = [...CONDUCTOR_HOOK_LIFECYCLE_EVENTS];
247
- if (options.enablePreToolUse) {
248
- events.push("PreToolUse");
249
- }
250
- for (const event of events) {
251
- const entries = asHookEntries(hooks[event]);
252
- if (entriesContainCommand(entries, command)) {
253
- // Idempotent: already registered for this event.
254
- hooks[event] = entries;
255
- continue;
256
- }
257
- const newEntry = { hooks: [{ type: "command", command }] };
258
- if (event === "PreToolUse") {
259
- newEntry.matcher = options.preToolUseMatcher ?? DEFAULT_PRE_TOOL_USE_MATCHER;
260
- }
261
- hooks[event] = [...entries, newEntry];
262
- }
263
- return { ...settings, hooks };
264
- }
265
- /** Detect an existing PreToolUse matcher to mirror, if any. */
266
- function detectExistingPreToolUseMatcher(settings) {
267
- const hooks = settings.hooks;
268
- if (hooks === null || typeof hooks !== "object" || Array.isArray(hooks))
269
- return undefined;
270
- const entries = asHookEntries(hooks.PreToolUse);
271
- for (const entry of entries) {
272
- if (typeof entry?.matcher === "string")
273
- return entry.matcher;
274
- }
275
- return undefined;
245
+ return mergeClaudeSettingsWithCommandHook(settings, command, CONDUCTOR_HOOK_LIFECYCLE_EVENTS, options);
276
246
  }
277
247
  /**
278
248
  * Provision the conductor Claude hook into ONE worktree's
@@ -281,52 +251,14 @@ function detectExistingPreToolUseMatcher(settings) {
281
251
  * pretty-printed JSON, mirroring any existing PreToolUse matcher.
282
252
  */
283
253
  export async function provisionConductorHookForWorktree(worktreePath, command, options, deps) {
284
- const claudeDir = path.join(worktreePath, ".claude");
285
- const settingsPath = path.join(claudeDir, "settings.local.json");
286
- let existing = {};
287
- let raw = null;
288
- try {
289
- raw = await deps.readFile(settingsPath);
290
- }
291
- catch {
292
- // Missing file -> treat as empty settings.
293
- raw = null;
294
- }
295
- if (raw !== null) {
296
- try {
297
- const parsed = JSON.parse(raw);
298
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
299
- return {
300
- ok: false,
301
- reason: "malformed",
302
- error: "existing .claude/settings.local.json is not a JSON object",
303
- };
304
- }
305
- existing = parsed;
306
- }
307
- catch {
308
- // Malformed JSON: fail the row rather than clobber the user's file. The
309
- // error is generic — it never echoes the file contents.
310
- return {
311
- ok: false,
312
- reason: "malformed",
313
- error: "existing .claude/settings.local.json contains invalid JSON",
314
- };
315
- }
316
- }
317
- const merged = mergeClaudeSettingsWithConductorHook(existing, command, {
254
+ // BAPI-534: delegate the malformed-safe read/merge/write to the shared
255
+ // `provisionClaudeSettingsForWorktree`, supplying a conductor-hook merge
256
+ // closure. The returned `{ ok, reason, error }` shape is identical to
257
+ // ConductorHookProvisionResult, so the mapping is a pass-through.
258
+ return provisionClaudeSettingsForWorktree(worktreePath, (existing) => mergeClaudeSettingsWithConductorHook(existing, command, {
318
259
  enablePreToolUse: options.enablePreToolUse,
319
260
  preToolUseMatcher: options.preToolUseMatcher ?? detectExistingPreToolUseMatcher(existing),
320
- });
321
- try {
322
- await deps.mkdir(claudeDir, { recursive: true });
323
- await deps.writeFile(settingsPath, `${JSON.stringify(merged, null, 2)}\n`);
324
- }
325
- catch {
326
- // Best-effort: a write/mkdir failure must not block the actual work.
327
- return { ok: false, reason: "io", error: "failed to write .claude/settings.local.json" };
328
- }
329
- return { ok: true };
261
+ }), deps);
330
262
  }
331
263
  /**
332
264
  * Provision conductor identity across rows. Attaches `runId` to EVERY row (so