@bridge_gpt/mcp-server 0.2.50 → 0.2.52
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.
- package/README.md +24 -8
- package/build/agent-capabilities/probe-context.js +15 -7
- package/build/agent-capabilities/probes.js +42 -6
- package/build/agent-launchers/claude-executor-adapter.js +98 -14
- package/build/commands.generated.js +1 -1
- package/build/conduct-epic/bridge-client.js +115 -1
- package/build/conduct-epic/cli.js +351 -33
- package/build/conduct-epic/cut-protocol.js +65 -0
- package/build/conductor/bridge-api-client.js +171 -5
- package/build/conductor/deny-enforcement-preflight.js +107 -10
- package/build/conductor/local-merge.js +170 -11
- package/build/conductor-bin.js +2 -2
- package/build/connect-bitbucket-api.js +370 -0
- package/build/connect-bitbucket.js +437 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +230 -1
- package/build/drive-epic.js +423 -11
- package/build/env-file-link.js +164 -0
- package/build/epic-integration-pr.js +290 -0
- package/build/executor/cli.js +41 -6
- package/build/executor/deps.js +5 -1
- package/build/executor/env-file-guard.js +113 -0
- package/build/executor/env.js +78 -1
- package/build/executor/heartbeat.js +9 -0
- package/build/executor/http-client.js +90 -22
- package/build/executor/job-errors.js +43 -2
- package/build/executor/job-runner.js +137 -29
- package/build/executor/merge-job.js +102 -6
- package/build/executor/permissions.js +106 -0
- package/build/executor/preflight.js +38 -13
- package/build/executor/resume-pre-spawn.js +2 -1
- package/build/executor/runner.js +175 -4
- package/build/executor/service-unit.js +15 -0
- package/build/executor/terminal-mutation.js +22 -1
- package/build/executor/types.js +86 -0
- package/build/executor/worker-command.js +21 -5
- package/build/executor/worker-guard-hook.js +939 -0
- package/build/executor/worker-log.js +56 -0
- package/build/executor/worktree.js +11 -0
- package/build/git-reachability.js +147 -0
- package/build/index.js +535 -95
- package/build/install-bridge.js +95 -0
- package/build/pipelines.generated.js +10 -2
- package/build/plan-epic-conductor-eligibility.js +213 -0
- package/build/plane/cli.js +78 -15
- package/build/plane/defaults.js +165 -0
- package/build/plane/manifest.js +63 -8
- package/build/plane/member-logs.js +6 -0
- package/build/plane/member-roster.js +195 -11
- package/build/plane/preflight.js +43 -0
- package/build/plane/shutdown.js +25 -3
- package/build/plane/status.js +11 -0
- package/build/plane/supervisor.js +343 -14
- package/build/plane/test-fakes.js +43 -0
- package/build/plane/types.js +82 -11
- package/build/pr-base-contract.js +20 -0
- package/build/readme.generated.js +1 -1
- package/build/review-synthesis-config.js +60 -0
- package/build/scripts/executor-protocol-contract-driver.js +311 -0
- package/build/setup-epic.js +592 -139
- package/build/sfcc/log-query.js +2 -1
- package/build/sfcc/reads-custom-object-def.js +10 -13
- package/build/sfcc/reads-site-preference.js +5 -5
- package/build/sfcc/reads-system-object.js +4 -4
- package/build/sfcc/writes-custom-object-def.js +7 -7
- package/build/sfcc/writes-site-preference.js +4 -3
- package/build/sfcc/writes-system-object.js +7 -6
- package/build/start-tickets-conductor.js +11 -2
- package/build/start-tickets.js +69 -2
- package/build/version.generated.js +3 -3
- package/build/worker-containment-diagnostic.js +97 -0
- package/build/worker-guard-hook-bin.js +6 -0
- package/docs/CONDUCTOR.md +27 -0
- package/docs/install/mcp-tool-integrations.md +3 -2
- package/package.json +5 -3
- package/pipelines/plan-epic.json +5 -0
package/build/sfcc/log-query.js
CHANGED
|
@@ -110,7 +110,8 @@ function semanticCheck(args) {
|
|
|
110
110
|
const start = Date.parse(args.time_range.start);
|
|
111
111
|
const end = Date.parse(args.time_range.end);
|
|
112
112
|
if (Number.isNaN(start) || Number.isNaN(end)) {
|
|
113
|
-
return "time_range.start and time_range.end must be
|
|
113
|
+
return ("time_range.start and time_range.end must be ISO-8601 timestamps that " +
|
|
114
|
+
"include a seconds component and a UTC offset (e.g. 2026-07-15T00:00:00Z).");
|
|
114
115
|
}
|
|
115
116
|
if (!(start < end)) {
|
|
116
117
|
return "time_range.start must be strictly before time_range.end.";
|
|
@@ -50,11 +50,10 @@ const READ_ANNOTATIONS = {
|
|
|
50
50
|
// ---------------------------------------------------------------------------
|
|
51
51
|
// Input schemas
|
|
52
52
|
// ---------------------------------------------------------------------------
|
|
53
|
-
const OBJECT_TYPE_DISCOVERY_HINT = 'Known custom object type id (e.g. starting with "c_").
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"count matches that row's attribute_definition_count.";
|
|
53
|
+
const OBJECT_TYPE_DISCOVERY_HINT = 'Known custom object type id (e.g. starting with "c_"). ' +
|
|
54
|
+
"Type ids aren't enumerable; find via system_object_list projection=full. " +
|
|
55
|
+
"Match by display_name + attribute_definition_count per custom type, then " +
|
|
56
|
+
"confirm this tool's returned attribute count matches that row's count.";
|
|
58
57
|
const customObjectAttributesGetInput = z.object({
|
|
59
58
|
object_type: z.string().describe(OBJECT_TYPE_DISCOVERY_HINT),
|
|
60
59
|
projection: projectionInputFor("attribute_collection"),
|
|
@@ -139,20 +138,18 @@ function buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir) {
|
|
|
139
138
|
export function registerSfccCustomObjectDefReadTools(registerTool, deps) {
|
|
140
139
|
const { gateDeps, getDocsDir } = deps;
|
|
141
140
|
registerTool("custom_object_definition_attributes_get", {
|
|
142
|
-
description: "Retrieve attribute
|
|
141
|
+
description: "Retrieve the FULL attribute-definition set for a KNOWN custom object type via " +
|
|
143
142
|
"GET /custom_object_definitions/{type}/attribute_definitions " +
|
|
144
|
-
"(default_value withheld).
|
|
145
|
-
"
|
|
146
|
-
"confirm here. Type creation is v2 metadata-import only.",
|
|
143
|
+
"(default_value withheld). Type creation is v2 metadata-import only. " +
|
|
144
|
+
"Oversized output saved locally.",
|
|
147
145
|
inputSchema: customObjectAttributesGetInput,
|
|
148
146
|
annotations: READ_ANNOTATIONS,
|
|
149
147
|
}, buildCustomObjectAttributesGetHandler(gateDeps, getDocsDir));
|
|
150
148
|
registerTool("custom_object_definition_attribute_search", {
|
|
151
|
-
description: "
|
|
149
|
+
description: "Filter the attribute-definition set within a KNOWN custom object type via " +
|
|
152
150
|
"POST /custom_object_definitions/{type}/attribute_definition_search " +
|
|
153
|
-
"(default_value withheld).
|
|
154
|
-
"
|
|
155
|
-
"confirm here. Type creation is v2 metadata-import only.",
|
|
151
|
+
"(default_value withheld). Type creation is v2 metadata-import only. " +
|
|
152
|
+
"Oversized output saved locally.",
|
|
156
153
|
inputSchema: customObjectAttributeSearchInput,
|
|
157
154
|
annotations: READ_ANNOTATIONS,
|
|
158
155
|
}, buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir));
|
|
@@ -226,15 +226,15 @@ export function registerSitePreferenceTools(registerTool, deps) {
|
|
|
226
226
|
description: "List custom site preference group ids from the developer sandbox via " +
|
|
227
227
|
"GET /system_object_definitions/SitePreferences/attribute_groups. Read-only; " +
|
|
228
228
|
"use this to discover group ids before site_preference_get or site_preference_search. " +
|
|
229
|
-
"Accepts optional `count` and `start` for paging. Oversized
|
|
229
|
+
"Accepts optional `count` and `start` for paging. Oversized output saved locally.",
|
|
230
230
|
inputSchema: sitePreferenceGroupListInput,
|
|
231
231
|
annotations: READ_ANNOTATIONS,
|
|
232
232
|
}, buildSitePreferenceGroupListHandler(gateDeps, getDocsDir));
|
|
233
233
|
registerTool("site_preference_get", {
|
|
234
|
-
description: "List preference
|
|
234
|
+
description: "List ALL preference ids in a group (match-all) from the developer sandbox via " +
|
|
235
235
|
"POST /site_preferences/preference_groups/{group}/sandbox/preference_search. " +
|
|
236
|
-
"Returns
|
|
237
|
-
"Read-only, sandbox-only. Optional start/count paging. Oversized
|
|
236
|
+
"Returns identifiers only, NOT values — 'unset' and 'empty string' are indistinguishable. " +
|
|
237
|
+
"Read-only, sandbox-only. Optional start/count paging. Oversized output saved locally.",
|
|
238
238
|
inputSchema: sitePreferenceGetInput,
|
|
239
239
|
annotations: READ_ANNOTATIONS,
|
|
240
240
|
}, buildSitePreferenceGetHandler(gateDeps, getDocsDir));
|
|
@@ -243,7 +243,7 @@ export function registerSitePreferenceTools(registerTool, deps) {
|
|
|
243
243
|
"POST /site_preferences/preference_groups/{group}/sandbox/preference_search. " +
|
|
244
244
|
"Returns ids only — this endpoint does not expose values. Read-only; v1 sandbox only. " +
|
|
245
245
|
"Pass a plain string for id-only text search or a structured OCAPI query. " +
|
|
246
|
-
"Oversized
|
|
246
|
+
"Oversized output saved locally.",
|
|
247
247
|
inputSchema: sitePreferenceSearchInput,
|
|
248
248
|
annotations: READ_ANNOTATIONS,
|
|
249
249
|
}, buildSitePreferenceSearchHandler(gateDeps, getDocsDir));
|
|
@@ -149,7 +149,7 @@ export function registerSystemObjectReadTools(registerTool, deps) {
|
|
|
149
149
|
"GET /system_object_definitions. Read-only. " +
|
|
150
150
|
"Accepts optional `count` and `start` for paging (OCAPI default pagination applies when omitted). " +
|
|
151
151
|
"Returns ids only by default; pass projection=full for all fields. " +
|
|
152
|
-
"Oversized
|
|
152
|
+
"Oversized output saved locally.",
|
|
153
153
|
inputSchema: systemObjectListInput,
|
|
154
154
|
annotations: READ_ANNOTATIONS,
|
|
155
155
|
}, buildSystemObjectListHandler(gateDeps, getDocsDir));
|
|
@@ -157,17 +157,17 @@ export function registerSystemObjectReadTools(registerTool, deps) {
|
|
|
157
157
|
description: "Retrieve a system object type definition from the developer sandbox. Read-only. " +
|
|
158
158
|
"GET /system_object_definitions/{type}. " +
|
|
159
159
|
"Prefer system_object_attribute_search for targeted attribute lookups. " +
|
|
160
|
-
"Oversized
|
|
160
|
+
"Oversized output saved locally.",
|
|
161
161
|
inputSchema: systemObjectGetInput,
|
|
162
162
|
annotations: READ_ANNOTATIONS,
|
|
163
163
|
}, buildSystemObjectGetHandler(gateDeps, getDocsDir));
|
|
164
164
|
registerTool("system_object_attribute_search", {
|
|
165
165
|
description: "Search attribute definitions for a system object type. Read-only. " +
|
|
166
166
|
"POST /system_object_definitions/{type}/attribute_definition_search. " +
|
|
167
|
-
"Use for targeted
|
|
167
|
+
"Use for targeted attribute lookups; returns each attribute's schema " +
|
|
168
168
|
"by default (default_value withheld). " +
|
|
169
169
|
"Pass a plain string for text search or a structured OCAPI query. " +
|
|
170
|
-
"Oversized
|
|
170
|
+
"Oversized output saved locally.",
|
|
171
171
|
inputSchema: systemObjectAttributeSearchInput,
|
|
172
172
|
annotations: READ_ANNOTATIONS,
|
|
173
173
|
}, buildSystemObjectAttributeSearchHandler(gateDeps, getDocsDir));
|
|
@@ -37,9 +37,8 @@ const INSTANCE_DESCRIBE = "OCAPI instance context. Omit or pass \"sandbox\"; any
|
|
|
37
37
|
const createCustomObjectAttributeDefinitionInput = z.object({
|
|
38
38
|
object_type: z
|
|
39
39
|
.string()
|
|
40
|
-
.describe('Known custom object type identifier (must already exist
|
|
41
|
-
"
|
|
42
|
-
"definitions on a known type."),
|
|
40
|
+
.describe('Known custom object type identifier (must already exist; OCAPI cannot create ' +
|
|
41
|
+
"types). Type ids aren't enumerable; find via system_object_list projection=full."),
|
|
43
42
|
attribute_id: z
|
|
44
43
|
.string()
|
|
45
44
|
.describe("URL attribute-definition id. If the body also carries `id`, it must match."),
|
|
@@ -49,7 +48,8 @@ const createCustomObjectAttributeDefinitionInput = z.object({
|
|
|
49
48
|
const updateCustomObjectAttributeDefinitionInput = z.object({
|
|
50
49
|
object_type: z
|
|
51
50
|
.string()
|
|
52
|
-
.describe("Known custom object type identifier (must already exist
|
|
51
|
+
.describe("Known custom object type identifier (must already exist; OCAPI cannot create " +
|
|
52
|
+
"types). Type ids aren't enumerable; find via system_object_list projection=full."),
|
|
53
53
|
attribute_id: z.string().describe("URL attribute-definition id to update."),
|
|
54
54
|
patch: objectAttributeDefinitionPatchBodySchema.describe("Partial ObjectAttributeDefinition body; must change at least one field."),
|
|
55
55
|
instance: z.string().optional().describe(INSTANCE_DESCRIBE),
|
|
@@ -134,15 +134,15 @@ export function registerSfccCustomObjectDefWriteTools(registerTool, deps) {
|
|
|
134
134
|
registerTool("custom_object_definition_attribute_create", {
|
|
135
135
|
description: "Create an attribute definition on a KNOWN custom object type via " +
|
|
136
136
|
"PUT /custom_object_definitions/{type}/attribute_definitions/{id}. Sandbox-only, " +
|
|
137
|
-
"destructive; the type must pre-exist (OCAPI cannot create types).
|
|
138
|
-
"grant JSON
|
|
137
|
+
"destructive; the type must pre-exist (OCAPI cannot create types). " +
|
|
138
|
+
"403 → paste-ready grant JSON.",
|
|
139
139
|
inputSchema: createCustomObjectAttributeDefinitionInput,
|
|
140
140
|
annotations: WRITE_ANNOTATIONS,
|
|
141
141
|
}, buildCreateCustomObjectAttributeDefinitionHandler(gateDeps));
|
|
142
142
|
registerTool("custom_object_definition_attribute_update", {
|
|
143
143
|
description: "Update an attribute definition on a KNOWN custom object type via an ETag-conditional " +
|
|
144
144
|
"PATCH /custom_object_definitions/{type}/attribute_definitions/{id}. Sandbox-only, " +
|
|
145
|
-
"destructive; surfaces 409/412 conflicts
|
|
145
|
+
"destructive; surfaces 409/412 conflicts. 403 → paste-ready grant JSON.",
|
|
146
146
|
inputSchema: updateCustomObjectAttributeDefinitionInput,
|
|
147
147
|
annotations: WRITE_ANNOTATIONS,
|
|
148
148
|
}, buildUpdateCustomObjectAttributeDefinitionHandler(gateDeps));
|
|
@@ -92,9 +92,10 @@ export const SITE_PREFERENCE_WRITE_TOOL_NAMES = ["site_preference_values_set"];
|
|
|
92
92
|
export function registerSitePreferenceWriteTools(registerTool, deps) {
|
|
93
93
|
const { gateDeps } = deps;
|
|
94
94
|
registerTool("site_preference_values_set", {
|
|
95
|
-
description: "Set
|
|
96
|
-
"
|
|
97
|
-
"
|
|
95
|
+
description: "Set site-preference VALUES via PATCH /site_preferences/preference_groups/{group}/sandbox. " +
|
|
96
|
+
"Preference must already be defined via custom_preference_definition_create. Sandbox-only, " +
|
|
97
|
+
"destructive; flat c_-prefixed id→value map (string/number/boolean/string[]). " +
|
|
98
|
+
"Bad group → 404 CustomPreferenceGroupNotFoundException. 403 → paste-ready grant JSON.",
|
|
98
99
|
inputSchema: sitePreferenceValuesSetInput,
|
|
99
100
|
annotations: WRITE_ANNOTATIONS,
|
|
100
101
|
}, buildSitePreferenceValuesSetHandler(gateDeps));
|
|
@@ -276,42 +276,43 @@ export function registerSystemObjectWriteTools(registerTool, deps) {
|
|
|
276
276
|
registerTool("system_object_attribute_definition_create", {
|
|
277
277
|
description: "Create a system-object attribute definition (sandbox-only, destructive). " +
|
|
278
278
|
"PUT .../attribute_definitions/{id}; body id must equal URL id and system must be false. " +
|
|
279
|
-
"Returns 201.
|
|
279
|
+
"Returns 201. 403 → paste-ready grant JSON.",
|
|
280
280
|
inputSchema: createAttributeDefinitionInput,
|
|
281
281
|
annotations: WRITE_ANNOTATIONS,
|
|
282
282
|
}, buildCreateAttributeDefinitionHandler(gateDeps));
|
|
283
283
|
registerTool("system_object_attribute_definition_update", {
|
|
284
284
|
description: "Update a system-object attribute definition (sandbox-only, destructive). " +
|
|
285
285
|
"PATCH .../attribute_definitions/{id} via the ETag GET-then-If-Match round trip. " +
|
|
286
|
-
"Returns 200; surfaces 409/412 on ETag conflicts.
|
|
286
|
+
"Returns 200; surfaces 409/412 on ETag conflicts. 403 → paste-ready grant JSON.",
|
|
287
287
|
inputSchema: updateAttributeDefinitionInput,
|
|
288
288
|
annotations: WRITE_ANNOTATIONS,
|
|
289
289
|
}, buildUpdateAttributeDefinitionHandler(gateDeps));
|
|
290
290
|
registerTool("system_object_attribute_group_create", {
|
|
291
291
|
description: "Create a system-object attribute group (sandbox-only, destructive). " +
|
|
292
292
|
"PUT .../attribute_groups/{id} with a minimal body (display_name + internal). " +
|
|
293
|
-
"
|
|
293
|
+
"403 → paste-ready grant JSON.",
|
|
294
294
|
inputSchema: createAttributeGroupInput,
|
|
295
295
|
annotations: WRITE_ANNOTATIONS,
|
|
296
296
|
}, buildCreateAttributeGroupHandler(gateDeps));
|
|
297
297
|
registerTool("system_object_attribute_group_update", {
|
|
298
298
|
description: "Update a system-object attribute group (sandbox-only, destructive). " +
|
|
299
299
|
"PATCH .../attribute_groups/{id} via the ETag round trip (display_name and/or internal). " +
|
|
300
|
-
"
|
|
300
|
+
"403 → paste-ready grant JSON.",
|
|
301
301
|
inputSchema: updateAttributeGroupInput,
|
|
302
302
|
annotations: WRITE_ANNOTATIONS,
|
|
303
303
|
}, buildUpdateAttributeGroupHandler(gateDeps));
|
|
304
304
|
registerTool("system_object_attribute_assign_to_group", {
|
|
305
305
|
description: "Assign an attribute definition to an attribute group (sandbox-only, destructive). " +
|
|
306
306
|
"PUT .../attribute_groups/{group}/attribute_definitions/{def} with an empty body. " +
|
|
307
|
-
"
|
|
307
|
+
"403 → paste-ready grant JSON.",
|
|
308
308
|
inputSchema: assignAttributeToGroupInput,
|
|
309
309
|
annotations: WRITE_ANNOTATIONS,
|
|
310
310
|
}, buildAssignAttributeToGroupHandler(gateDeps));
|
|
311
311
|
registerTool("custom_preference_definition_create", {
|
|
312
312
|
description: "Create a custom preference definition (sandbox-only, destructive). " +
|
|
313
313
|
"PUT .../{SitePreferences|OrganizationPreferences}/attribute_definitions/{id}. " +
|
|
314
|
-
"Booleans resolve to null unless default_value is set.
|
|
314
|
+
"Booleans resolve to null unless default_value is set. After defining the schema, " +
|
|
315
|
+
"set its value with site_preference_values_set. 403 → paste-ready grant JSON.",
|
|
315
316
|
inputSchema: createCustomPreferenceDefinitionInput,
|
|
316
317
|
annotations: WRITE_ANNOTATIONS,
|
|
317
318
|
}, buildCreateCustomPreferenceDefinitionHandler(gateDeps));
|
|
@@ -77,8 +77,17 @@ export function buildEpicIdentityEnv(epic) {
|
|
|
77
77
|
// ---------------------------------------------------------------------------
|
|
78
78
|
/** Default gate name when no `BAPI_CONDUCTOR_GATE_NAME` override is present. */
|
|
79
79
|
export const DEFAULT_CONDUCTOR_GATE_NAME = "implement-ticket";
|
|
80
|
-
/**
|
|
81
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a packaged build artifact path relative to this compiled module.
|
|
82
|
+
*
|
|
83
|
+
* Exported since BAPI-1020 so the executor resolves the worker-guard bin through
|
|
84
|
+
* the SAME resolver rather than growing a second one. Correct in both build
|
|
85
|
+
* shapes because this module compiles to the build ROOT: the esbuild bundle puts
|
|
86
|
+
* `import.meta.url` at `build/index.js` / `build/conductor-bin.js`, and `tsc` puts
|
|
87
|
+
* it at `build/start-tickets-conductor.js`. A resolver living in a subdirectory
|
|
88
|
+
* module would resolve to `build/<subdir>/<filename>` under `tsc` and be wrong.
|
|
89
|
+
*/
|
|
90
|
+
export function defaultResolveBinPath(filename) {
|
|
82
91
|
return fileURLToPath(new URL(`./${filename}`, import.meta.url));
|
|
83
92
|
}
|
|
84
93
|
function nonEmpty(value) {
|
package/build/start-tickets.js
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
* unit-testable on Linux CI without spawning real commands or terminals.
|
|
55
55
|
*/
|
|
56
56
|
import { execFile } from "child_process";
|
|
57
|
-
import { readFile, writeFile, mkdir, mkdtemp, stat, readdir, rm } from "fs/promises";
|
|
57
|
+
import { readFile, writeFile, mkdir, mkdtemp, stat, lstat, symlink, unlink, copyFile, readdir, rm, } from "fs/promises";
|
|
58
58
|
import os from "node:os";
|
|
59
59
|
import path from "path";
|
|
60
60
|
import { VERSION } from "./version.generated.js";
|
|
@@ -965,6 +965,10 @@ export async function runWithConcurrency(items, limit, worker) {
|
|
|
965
965
|
// imports of these names from `./start-tickets.js` keep resolving to the exact
|
|
966
966
|
// same function references.
|
|
967
967
|
import { resolveBranchForTicket, branchExists, buildWtSwitchArgs, pathApiForPlatform, extractWorktreePath, isExistingBranchSafeToReuse, createWorktreeForTicket, } from "./worktree-core.js";
|
|
968
|
+
// BAPI-1019: interactive worktrees get the operator's `.env`/`.env.test` back as
|
|
969
|
+
// SYMLINKS after `.config/wt.toml` stopped copying them. Imported HERE, in the
|
|
970
|
+
// interactive orchestration module, and deliberately nowhere under `executor/`.
|
|
971
|
+
import { linkOperatorEnvFiles } from "./env-file-link.js";
|
|
968
972
|
export { resolveBranchForTicket, branchExists, buildWtSwitchArgs, pathApiForPlatform, extractWorktreePath, isExistingBranchSafeToReuse, createWorktreeForTicket, };
|
|
969
973
|
/**
|
|
970
974
|
* BAPI-941: emit the per-spawn `claude-review` workflow-drift advisory.
|
|
@@ -1033,7 +1037,70 @@ export async function createWorktrees(deps, options, worktrunkBinary, baseStartP
|
|
|
1033
1037
|
const behavior = exactBase
|
|
1034
1038
|
? { alignExistingBranchTo: baseStartPoint, verifyHeadMatches: baseStartPoint }
|
|
1035
1039
|
: {};
|
|
1036
|
-
|
|
1040
|
+
const link = deps.linkOperatorEnvFiles ?? linkOperatorEnvFiles;
|
|
1041
|
+
const linkDeps = buildEnvFileLinkDeps(deps);
|
|
1042
|
+
return runWithConcurrency(options.keys, options.maxParallel, async (key) => {
|
|
1043
|
+
const row = await createWorktreeForTicket(deps, key, options.branchOverrides, worktrunkBinary, baseStartPoint, options.guardStaleWorktree === true, behavior);
|
|
1044
|
+
// BAPI-1019 — re-provide the operator's env files by LINK, and only here.
|
|
1045
|
+
//
|
|
1046
|
+
// `.config/wt.toml` no longer copies `.env*` into any worktree, which is what
|
|
1047
|
+
// keeps a conductor worker from ever holding a real database target. An
|
|
1048
|
+
// interactive worktree still needs that configuration, so it gets a symlink to
|
|
1049
|
+
// the main checkout's file instead of a second copy at rest.
|
|
1050
|
+
//
|
|
1051
|
+
// `deps.cwd` is the canonical main checkout — the same value every other
|
|
1052
|
+
// Worktrunk/git call in this module resolves against — rather than a path
|
|
1053
|
+
// inferred from the new worktree, which would be a guess.
|
|
1054
|
+
//
|
|
1055
|
+
// Deliberately NOT in `worktree-core.ts` or command provisioning: the executor
|
|
1056
|
+
// shares those seams, and an executor worktree must receive neither a copy nor
|
|
1057
|
+
// a link. Keeping the call in this interactive orchestration function is what
|
|
1058
|
+
// makes that separation structural instead of a comment.
|
|
1059
|
+
if (row.status !== "created" || typeof row.path !== "string" || row.path.length === 0) {
|
|
1060
|
+
return row;
|
|
1061
|
+
}
|
|
1062
|
+
try {
|
|
1063
|
+
const linked = await link(deps.cwd, row.path, linkDeps);
|
|
1064
|
+
return linked.warnings.reduce((acc, warning) => appendSummaryRowWarning(acc, warning.message), row);
|
|
1065
|
+
}
|
|
1066
|
+
catch {
|
|
1067
|
+
// The linker already bounds its own failures; this catch covers a seam that
|
|
1068
|
+
// throws anyway (an injected fake, a future refactor). The worktree itself
|
|
1069
|
+
// was created successfully, so the row stays `created` and carries a fixed
|
|
1070
|
+
// warning — never the exception text, which can embed an absolute path.
|
|
1071
|
+
return appendSummaryRowWarning(row, "worktree environment files: could not be provided from the main checkout, so this " +
|
|
1072
|
+
"worktree has none. Commands that need them will fail until they are linked by hand.");
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Build the interactive env-file linker's filesystem bag from this module's real
|
|
1078
|
+
* `fs/promises` boundary (BAPI-1019), mirroring
|
|
1079
|
+
* {@link buildCommandProvisioningDeps}.
|
|
1080
|
+
*
|
|
1081
|
+
* `stat`/`lstat` are adapted to resolve `null` for a missing path rather than
|
|
1082
|
+
* rejecting, because "absent" is the ordinary case the linker branches on — most
|
|
1083
|
+
* checkouts have `.env` and no `.env.test`. There is no `readFile` here and there
|
|
1084
|
+
* must never be one: the linker's contract is that an environment file's contents
|
|
1085
|
+
* are never read.
|
|
1086
|
+
*/
|
|
1087
|
+
export function buildEnvFileLinkDeps(deps) {
|
|
1088
|
+
const metadata = (probe) => async (target) => {
|
|
1089
|
+
try {
|
|
1090
|
+
return await probe(target);
|
|
1091
|
+
}
|
|
1092
|
+
catch {
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
return {
|
|
1097
|
+
stat: metadata((target) => stat(target)),
|
|
1098
|
+
lstat: metadata((target) => lstat(target)),
|
|
1099
|
+
symlink: (target, linkPath) => symlink(target, linkPath),
|
|
1100
|
+
unlink: (target) => unlink(target),
|
|
1101
|
+
copyFile: (source, destination) => copyFile(source, destination),
|
|
1102
|
+
platform: deps.platform,
|
|
1103
|
+
};
|
|
1037
1104
|
}
|
|
1038
1105
|
/**
|
|
1039
1106
|
* Resume-mode worktree resolution (BAPI-441). Instead of creating worktrees,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
|
|
2
|
-
export const VERSION = "0.2.
|
|
3
|
-
export const BUILD_COMMIT = "
|
|
4
|
-
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
2
|
+
export const VERSION = "0.2.52";
|
|
3
|
+
export const BUILD_COMMIT = "82534a279ae4";
|
|
4
|
+
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.52", "serve"];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `doctor` "Worker containment" advisory section (BAPI-1019, A.7).
|
|
3
|
+
*
|
|
4
|
+
* Reports the DECLARED containment posture a conductor worker runs under: that
|
|
5
|
+
* the executor strips operator environment files from a worker's worktree before
|
|
6
|
+
* it spawns, that a worker's database environment names a target that does not
|
|
7
|
+
* exist, and which permission posture the run policy defaults to.
|
|
8
|
+
*
|
|
9
|
+
* ZERO PROBES, and that is a hard constraint rather than an efficiency choice.
|
|
10
|
+
* `doctor` pins that an unsupported platform issues zero command probes (see the
|
|
11
|
+
* note on `collectLocalCliOverrideDiagnostic` in `doctor.ts`), and a section that
|
|
12
|
+
* probed unconditionally would break that guarantee for every platform. So this
|
|
13
|
+
* module runs no command, opens no file, makes no network call, and reads no
|
|
14
|
+
* environment variable. It has no dependency bag at all — there is nothing to
|
|
15
|
+
* inject because there is nothing to do.
|
|
16
|
+
*
|
|
17
|
+
* WHAT THAT MEANS FOR THE READER, stated plainly in the rendered copy: these are
|
|
18
|
+
* DECLARED constants, not measurements. The section says what this build does,
|
|
19
|
+
* not what it just observed on this host. Rendering a static policy as though a
|
|
20
|
+
* live verification had run would be worse than printing nothing — an operator
|
|
21
|
+
* would read "Yes" as evidence about their machine. The real per-spawn proof is
|
|
22
|
+
* the executor's own fail-closed refusal (`ContractError.WorkerEnvFilePresent`),
|
|
23
|
+
* which happens at spawn time on the host that actually runs workers.
|
|
24
|
+
*
|
|
25
|
+
* It also prints no secret and no identity: no sentinel URL (it carries
|
|
26
|
+
* `postgresql://` credential syntax and would teach a reader to paste it
|
|
27
|
+
* somewhere), no path, no username, no discovered filename beyond the generic
|
|
28
|
+
* `.env*`.
|
|
29
|
+
*/
|
|
30
|
+
import { DEFAULT_WORKER_PERMISSION_POSTURE } from "./executor/worker-command.js";
|
|
31
|
+
/**
|
|
32
|
+
* Return the declared containment posture.
|
|
33
|
+
*
|
|
34
|
+
* Takes no dependencies and performs no I/O. The two booleans are `true` because
|
|
35
|
+
* this build unconditionally does both things — they are not feature flags, and
|
|
36
|
+
* there is deliberately no configuration that can turn either off. The posture
|
|
37
|
+
* comes from the executor's own exported default so this report and the runtime
|
|
38
|
+
* cannot drift.
|
|
39
|
+
*/
|
|
40
|
+
export function collectWorkerContainmentDiagnostic() {
|
|
41
|
+
return {
|
|
42
|
+
envFilesStripped: true,
|
|
43
|
+
sentinelDatabaseEnv: true,
|
|
44
|
+
permissionPosture: DEFAULT_WORKER_PERMISSION_POSTURE,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Rendered when a value is not a usable declaration. */
|
|
48
|
+
const UNAVAILABLE = "Not confirmed";
|
|
49
|
+
/**
|
|
50
|
+
* Column width for the aligned label/value rows. Wide enough that the longest
|
|
51
|
+
* label (`Sentinel DB environment:`) still leaves a gap before its value.
|
|
52
|
+
*/
|
|
53
|
+
const LABEL_WIDTH = 26;
|
|
54
|
+
function row(label, value) {
|
|
55
|
+
return `${`${label}:`.padEnd(LABEL_WIDTH)}${value}`;
|
|
56
|
+
}
|
|
57
|
+
function yesNo(value) {
|
|
58
|
+
if (value === true)
|
|
59
|
+
return "Yes";
|
|
60
|
+
if (value === false)
|
|
61
|
+
return "No";
|
|
62
|
+
return UNAVAILABLE;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Render the section (pure formatting), mirroring
|
|
66
|
+
* `formatLocalCliOverrideDiagnosticReport`.
|
|
67
|
+
*
|
|
68
|
+
* Accepts `unknown` on purpose. The caller wraps this in the advisory `try` that
|
|
69
|
+
* never changes doctor's exit code, and a formatter that threw on a malformed
|
|
70
|
+
* input would make the section's own failure mode louder than the thing it
|
|
71
|
+
* reports. A value it cannot read renders as "Not confirmed" — never as "Yes",
|
|
72
|
+
* and never as a claim that a probe ran and came back empty.
|
|
73
|
+
*/
|
|
74
|
+
export function formatWorkerContainmentDiagnosticReport(diagnostic) {
|
|
75
|
+
const d = diagnostic && typeof diagnostic === "object"
|
|
76
|
+
? diagnostic
|
|
77
|
+
: {};
|
|
78
|
+
const posture = typeof d.permissionPosture === "string" ? d.permissionPosture : UNAVAILABLE;
|
|
79
|
+
return [
|
|
80
|
+
"",
|
|
81
|
+
"Worker containment (conductor executor)",
|
|
82
|
+
"",
|
|
83
|
+
row("Env files stripped", yesNo(d.envFilesStripped)),
|
|
84
|
+
row("Sentinel DB environment", yesNo(d.sentinelDatabaseEnv)),
|
|
85
|
+
row("Permission posture", posture),
|
|
86
|
+
"",
|
|
87
|
+
" A conductor worker never inherits this checkout's .env* files: the executor",
|
|
88
|
+
" removes them from the worker's worktree as the last step before it starts,",
|
|
89
|
+
" and refuses to start at all if it cannot confirm they are gone. The worker's",
|
|
90
|
+
" database settings name a target that does not exist, so code that resolves a",
|
|
91
|
+
" database from the environment reaches nothing rather than something real.",
|
|
92
|
+
" Permission posture is the run policy's default and is informational here.",
|
|
93
|
+
" Declared posture, not a live check: this section runs no command, reads no",
|
|
94
|
+
" file, and makes no network call. The per-spawn proof is the executor's own",
|
|
95
|
+
" refusal on the host that runs workers, not this line.",
|
|
96
|
+
].join("\n");
|
|
97
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{readFileSync}from"node:fs";import path from"node:path";var WORKER_GUARD_REASON_PREFIX="bridge worker guard",WORKER_GUARD_MALFORMED_LINE=`${WORKER_GUARD_REASON_PREFIX}: payload could not be read; continuing fail-open`;function renderWorkerGuardReason(category,family){return`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). This command family is restricted to the worker's own branch, worktree, and read-only network access. Use a different approach and continue.`}var UNSAFE_UNQUOTED=new Set(["$","`",">","<"]),SEPARATORS=new Set([";","|","&"]);function tokenizeShellCommand(command){let commands=[],argv=[],current="",hasCurrent=!1,unsafe=!1,endToken=()=>{hasCurrent&&(argv.push(current),current="",hasCurrent=!1)},endCommand=()=>{endToken(),(argv.length>0||unsafe)&&commands.push({argv,unsafe}),argv=[],unsafe=!1};for(let i=0;i<command.length;i+=1){let ch=command[i];if(ch==="\\"){let next=command[i+1];next!==void 0&&(current+=next,hasCurrent=!0,i+=1);continue}if(ch==="'"){let close=command.indexOf("'",i+1);if(close===-1){unsafe=!0,current+=command.slice(i+1),hasCurrent=!0;break}current+=command.slice(i+1,close),hasCurrent=!0,i=close;continue}if(ch==='"'){let j=i+1,body="",closed=!1;for(;j<command.length;){let c=command[j];if(c==="\\"&&j+1<command.length){body+=command[j+1],j+=2;continue}if(c==='"'){closed=!0;break}(c==="$"||c==="`")&&(unsafe=!0),body+=c,j+=1}closed||(unsafe=!0),current+=body,hasCurrent=!0,i=j;continue}if(UNSAFE_UNQUOTED.has(ch)){unsafe=!0,current+=ch,hasCurrent=!0;continue}if(SEPARATORS.has(ch)){endCommand();continue}if(ch===`
|
|
3
|
+
`){endCommand();continue}if(ch===" "||ch===" "||ch==="\r"){endToken();continue}current+=ch,hasCurrent=!0}return endCommand(),commands}var WORKER_GUARD_MAX_PAYLOAD_BYTES=1e6;function asRecord(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)?value:void 0}function isInside(root,candidate){let rel=path.relative(root,candidate);return rel===""?!0:rel.startsWith("..")?!1:!path.isAbsolute(rel)}function resolveEffectiveCwd(ctx,payloadCwd){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root))return;let normalizedRoot=path.resolve(root);if(payloadCwd===void 0)return normalizedRoot;if(typeof payloadCwd!="string"||payloadCwd.length===0)return;let resolved=path.isAbsolute(payloadCwd)?path.resolve(payloadCwd):path.resolve(normalizedRoot,payloadCwd);return isInside(normalizedRoot,resolved)?resolved:void 0}function allTargetsContained(ctx,cwd,targets){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root)||cwd===void 0)return!1;let normalizedRoot=path.resolve(root);return targets.every(target=>{if(target.length===0||target==="~"||target.startsWith("~/"))return!1;let resolved=path.isAbsolute(target)?path.resolve(target):path.resolve(cwd,target);return isInside(normalizedRoot,resolved)})}function parseGitInvocation(argv,baseCwd){let cwd=baseCwd,unsafe=!1,i=1;for(;i<argv.length;){let token=argv[i];if(token==="-C"){let dir=argv[i+1];dir===void 0||cwd===void 0?(unsafe=!0,cwd=void 0):cwd=path.isAbsolute(dir)?path.resolve(dir):path.resolve(cwd,dir),i+=2;continue}if(token==="-c"||token==="--namespace"||token==="--git-dir"||token==="--work-tree"){unsafe=!0,i+=2;continue}if(token.startsWith("-")){i+=1;continue}break}return{subcommand:argv[i]??"",args:argv.slice(i+1),cwd,unsafe}}function isOwnBranchDestination(destination,workerBranch){let colon=destination.indexOf(":"),dst=colon===-1?destination:destination.slice(colon+1);if(dst.length===0)return!1;let bare=dst.startsWith("+")?dst.slice(1):dst;return bare===workerBranch||bare===`refs/heads/${workerBranch}`}function evaluateGitPush(args,ctx){let deny={kind:"deny",category:"git-push",family:"git"},positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}if(token.startsWith("-")){if(token==="--tags"||token==="--delete"||token==="-d"||token==="--mirror"||token==="--all"||token==="--follow-tags"||token.startsWith("--repo"))return deny;(token==="-o"||token==="--push-option"||token==="--receive-pack"||token==="--exec")&&(i+=1);continue}positional.push(token)}if(positional.length<=1)return{kind:"allow"};let workerBranch=ctx.workerBranch;return typeof workerBranch!="string"||workerBranch.length===0?deny:positional.slice(1).every(d=>isOwnBranchDestination(d,workerBranch))?{kind:"allow"}:deny}function evaluateGitReset(args,ctx){let deny={kind:"deny",category:"git-reset-hard",family:"git"};if(!args.includes("--hard"))return{kind:"allow"};let positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}token.startsWith("-")||positional.push(token)}if(positional.length===0)return{kind:"allow"};let workerBranch=ctx.workerBranch;if(typeof workerBranch!="string"||workerBranch.length===0)return deny;let allowed=new Set([workerBranch,`origin/${workerBranch}`,`refs/heads/${workerBranch}`,`refs/remotes/origin/${workerBranch}`]);return positional.every(ref=>allowed.has(ref))?{kind:"allow"}:deny}function evaluateGitClean(args,ctx,cwd){let deny={kind:"deny",category:"filesystem-clean",family:"git"};if(cwd===void 0||!allTargetsContained(ctx,cwd,[cwd]))return deny;let pathspecs=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){pathspecs.push(...args.slice(i+1));break}if(token==="-e"||token==="--exclude"){i+=1;continue}token.startsWith("-")||pathspecs.push(token)}return pathspecs.length===0?{kind:"allow"}:allTargetsContained(ctx,cwd,pathspecs)?{kind:"allow"}:deny}function isRecursiveForcedRemove(args){let recursive=!1,force=!1;for(let token of args){if(token==="--")break;if(token==="--recursive")recursive=!0;else if(token==="--force")force=!0;else{if(token.startsWith("--"))continue;if(token.startsWith("-")&&token.length>1)for(let flag of token.slice(1))(flag==="r"||flag==="R")&&(recursive=!0),flag==="f"&&(force=!0)}}return recursive&&force}function evaluateRemove(argv,ctx,cwd){let deny={kind:"deny",category:"filesystem-remove",family:"rm"},args=argv.slice(1);if(!isRecursiveForcedRemove(args))return{kind:"allow"};let targets=[],sawDoubleDash=!1;for(let token of args){if(!sawDoubleDash&&token==="--"){sawDoubleDash=!0;continue}!sawDoubleDash&&token.startsWith("-")&&token.length>1||targets.push(token)}return targets.length===0?deny:allTargetsContained(ctx,cwd,targets)?{kind:"allow"}:deny}function isDatabaseCommand(executable){let base=path.basename(executable);return base==="psql"||base.startsWith("pg_")}var DESTRUCTIVE_SQL=/\b(?:DROP|TRUNCATE)\b/i;function evaluateDatabase(argv){let deny={kind:"deny",category:"database-destructive",family:"psql"};return argv.slice(1).some(arg=>DESTRUCTIVE_SQL.test(arg))?deny:{kind:"allow"}}var MUTATING_METHODS=new Set(["POST","PUT","PATCH","DELETE"]);function evaluateNetwork(argv){let deny={kind:"deny",category:"network-mutating",family:"curl"},base=path.basename(argv[0]??""),args=argv.slice(1);for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="-X"||token==="--request"||token==="--method"){let value=args[i+1];if(value===void 0||MUTATING_METHODS.has(value.toUpperCase()))return deny;i+=1;continue}if(token.startsWith("-X")&&token.length>2){if(MUTATING_METHODS.has(token.slice(2).toUpperCase()))return deny;continue}if(token.startsWith("--request=")||token.startsWith("--method=")){let value=token.slice(token.indexOf("=")+1);if(MUTATING_METHODS.has(value.toUpperCase()))return deny;continue}if(token==="--data"||token.startsWith("--data=")||token.startsWith("--data-")||token==="--form"||token==="-F"||token==="--upload-file"||token==="-T"||token==="--post-data"||token.startsWith("--post-data=")||token==="--post-file"||token.startsWith("--post-file=")||token==="--body-data"||token.startsWith("--body-data=")||token==="--body-file"||token.startsWith("--body-file=")||base==="curl"&&token==="-d")return deny}return{kind:"allow"}}function classify(executable){let base=path.basename(executable);return base==="git"?"git":base==="rm"?"rm":isDatabaseCommand(base)?"database":base==="curl"||base==="wget"?"network":null}function unsafeSegmentDecision(family){switch(family){case"git":return{kind:"deny",category:"git-push",family:"git"};case"rm":return{kind:"deny",category:"filesystem-remove",family:"rm"};case"database":return{kind:"deny",category:"database-destructive",family:"psql"};case"network":return{kind:"deny",category:"network-mutating",family:"curl"}}}var TRANSPARENT_PREFIXES=new Set(["sudo","env","command","nohup","time","nice","exec"]),STDIN_ARGUMENT_PREFIXES=new Set(["xargs"]),SHELL_EXECUTABLES=new Set(["sh","bash","zsh","dash","ksh"]),EVAL_BUILTINS=new Set(["eval"]);function unwrapTransparentPrefixes(argv){let rest=argv;for(let depth=0;depth<8;depth+=1){let head=rest[0];if(head===void 0||!TRANSPARENT_PREFIXES.has(path.basename(head)))return rest;let i=1;for(;i<rest.length;){let token=rest[i];if(path.basename(head)==="env"&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)){i+=1;continue}if(token.startsWith("-")){i+=1;continue}break}rest=rest.slice(i)}return rest}function stripStdinPrefixOptions(argv){let VALUE_OPTS=new Set(["-I","-i","-L","-n","-P","-s","-d","-E","-e","-a","--replace","--max-lines","--max-args","--max-procs","--max-chars","--delimiter","--eof","--arg-file"]),i=1;for(;i<argv.length;){let token=argv[i];if(token==="--"){i+=1;break}if(!token.startsWith("-"))break;if(token.includes("=")){i+=1;continue}i+=VALUE_OPTS.has(token)?2:1}return argv.slice(i)}function evaluateSimpleCommand(segment,ctx,payloadCwd,depth=0,stdinFed=!1){let argv=unwrapTransparentPrefixes(segment.argv),executable=argv[0];if(executable===void 0)return{kind:"allow"};if(SHELL_EXECUTABLES.has(path.basename(executable))&&depth<4){let cIndex=argv.findIndex(t=>t==="-c"),inner=cIndex===-1?void 0:argv[cIndex+1];if(inner!==void 0){for(let nested of tokenizeShellCommand(inner)){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed);if(decision.kind==="deny")return decision}return{kind:"allow"}}}if(EVAL_BUILTINS.has(path.basename(executable))&&depth<4){for(let nested of tokenizeShellCommand(argv.slice(1).join(" "))){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed);if(decision.kind==="deny")return decision}return{kind:"allow"}}if(STDIN_ARGUMENT_PREFIXES.has(path.basename(executable))&&depth<4){let rest=stripStdinPrefixOptions(argv);return rest.length===0?{kind:"allow"}:evaluateSimpleCommand({argv:rest,unsafe:segment.unsafe},ctx,payloadCwd,depth+1,!0)}segment={argv,unsafe:segment.unsafe};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family);let cwd=resolveEffectiveCwd(ctx,payloadCwd);if(family==="rm")return evaluateRemove(segment.argv,ctx,cwd);if(family==="database")return evaluateDatabase(segment.argv);if(family==="network")return evaluateNetwork(segment.argv);let git=parseGitInvocation(segment.argv,cwd);return git.subcommand==="push"?git.unsafe?{kind:"deny",category:"git-push",family:"git"}:evaluateGitPush(git.args,ctx):git.subcommand==="reset"?git.unsafe?{kind:"deny",category:"git-reset-hard",family:"git"}:evaluateGitReset(git.args,ctx):git.subcommand==="clean"?git.unsafe?{kind:"deny",category:"filesystem-clean",family:"git"}:evaluateGitClean(git.args,ctx,git.cwd):{kind:"allow"}}function evaluateWorkerGuardPayload(payload,ctx){let root=asRecord(payload);if(root===void 0)return{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return{kind:"malformed"};if(toolName!=="Bash")return{kind:"allow"};let input=asRecord(root.tool_input??root.toolInput);if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return{kind:"malformed"};let rawCwd=root.cwd??input.cwd,payloadCwd=typeof rawCwd=="string"?rawCwd:void 0;for(let segment of tokenizeShellCommand(command)){let decision=evaluateSimpleCommand(segment,ctx,payloadCwd);if(decision.kind==="deny")return decision}return{kind:"allow"}}function evaluateWorkerGuardInput(raw,ctx){if(typeof raw!="string")return{kind:"malformed"};if(raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES)return{kind:"malformed"};if(raw.trim().length===0)return{kind:"malformed"};let parsed;try{parsed=JSON.parse(raw)}catch{return{kind:"malformed"}}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return{kind:"malformed"}}}var WORKER_GUARD_ROOT_FLAG="--worktree-root";function resolveWorkerGuardContext(env,cwd,argv=[]){let ctx={},root;for(let i=0;i<argv.length;i+=1){if(argv[i]===WORKER_GUARD_ROOT_FLAG){root=argv[i+1];break}if(argv[i].startsWith(`${WORKER_GUARD_ROOT_FLAG}=`)){root=argv[i].slice(WORKER_GUARD_ROOT_FLAG.length+1);break}}root===void 0&&typeof cwd=="string"&&(root=cwd),typeof root=="string"&&path.isAbsolute(root)&&(ctx.worktreePath=path.resolve(root));let branch=env.BAPI_WORKER_BRANCH;return typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim()),ctx}function readStdinSync(){return readFileSync(0,"utf-8")}var exitCode=0;try{let raw=readStdinSync(),ctx=resolveWorkerGuardContext(process.env,process.cwd(),process.argv.slice(2)),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(process.stderr.write(`${renderWorkerGuardReason(decision.category,decision.family)}
|
|
4
|
+
`),exitCode=2):decision.kind==="malformed"&&(process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
|
|
5
|
+
`),exitCode=0)}catch{try{process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
|
|
6
|
+
`)}catch{}exitCode=0}process.exit(exitCode);
|
package/docs/CONDUCTOR.md
CHANGED
|
@@ -64,6 +64,33 @@ pipeline step. `setup-epic` validates it locally (unique ticket keys, resolvable
|
|
|
64
64
|
`depends_on`/edge references, acyclicity) before sending anything, so a malformed
|
|
65
65
|
plan fails legibly instead of as a bare HTTP 400.
|
|
66
66
|
|
|
67
|
+
### Branch strategy: the epic branch is the default (BAPI-1009)
|
|
68
|
+
|
|
69
|
+
The command above — no branch flag — now runs a **multi-ticket** epic on a
|
|
70
|
+
dedicated `epic/<KEY>` branch. Every child-ticket PR targets that branch, and one
|
|
71
|
+
**draft** integration PR targets the repository base branch. This applies on every
|
|
72
|
+
path, CI and `--json` included. `drive-epic` forwards both flags below to
|
|
73
|
+
`setup-epic`, so the universal entry point can select either strategy.
|
|
74
|
+
|
|
75
|
+
| You want | Pass |
|
|
76
|
+
|---|---|
|
|
77
|
+
| The epic branch on a plan with **2 or more** nodes | *nothing* — it is the default |
|
|
78
|
+
| Your own branch name, or a branch for a **single-node** plan | `--feature-branch <name>` |
|
|
79
|
+
| The old direct-to-base behavior | `--into-base` |
|
|
80
|
+
|
|
81
|
+
> **Behavior change.** Before BAPI-1009 a run with no branch flag went straight to
|
|
82
|
+
> the repository base branch. Automation that relied on that must now pass
|
|
83
|
+
> `--into-base`. A single-node plan still keeps the base branch by default, matching
|
|
84
|
+
> plain `start-tickets`. `--feature-branch` and `--into-base` together are a parse
|
|
85
|
+
> error, refused before any file read, credential resolution, or network call.
|
|
86
|
+
|
|
87
|
+
On an epic-branch run, a shadow-index verification that cannot be proven
|
|
88
|
+
immediately resolves as bounded **hold-then-park** rather than blocking the run:
|
|
89
|
+
independent tickets proceed, a dependent holds, and past the deadline it parks
|
|
90
|
+
through the ordinary `needs_human` path. The epic run's gate snapshot carries a
|
|
91
|
+
run-level `shadow_hold` object naming the reason and the recovery while that is
|
|
92
|
+
live. This addresses BAPI-993 run issue A1.
|
|
93
|
+
|
|
67
94
|
Since BAPI-754 it then calls the server's **real** validator
|
|
68
95
|
(`POST /jira/epic-runs/plan/validate`) before creating anything, so a plan the
|
|
69
96
|
server would reject — a node missing `status`, say, which the local checks do not
|
|
@@ -159,6 +159,7 @@ by tier.
|
|
|
159
159
|
| Tool | Dependencies (class) |
|
|
160
160
|
|---|---|
|
|
161
161
|
| `second_opinion` | — none (pure LLM; repo access only) |
|
|
162
|
+
| `clarify_task` | No Jira ticket, no repository connection required · VCS **[DEGRADE]** (a connected repository adds code-aware questions) |
|
|
162
163
|
| `generate_image` | — none beyond an image provider |
|
|
163
164
|
| `visual_diff` | — none (`LOCAL` render + pixel diff; needs a reachable `target_url`) |
|
|
164
165
|
| `request_deep_research` / `get_deep_research` | Deep-research flag **[BLOCK]** (403 if `deep_research_enabled` off) |
|
|
@@ -301,8 +302,8 @@ wanting both selects `sfcc,sfcc-write` (or `full`).
|
|
|
301
302
|
|---|---|---|
|
|
302
303
|
| **Ticket backend** (jira mode) | `create_ticket`, `get_ticket(s)`, `update_ticket_description`, `update_jira_status`, and every AI generator (they read the ticket) | — |
|
|
303
304
|
| **Jira (only)** | `get_comments`, `add_comment`, `attachment`, `estimate_epic` | — |
|
|
304
|
-
| **Version control (VCS)** | `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`, `fresh_base`, `parse_repository`, `regenerate_directory_map`; **Tier-3** `request_plan_generation`/`get_plan`, `request_reimplement_context`/`get_reimplement_context`, `create_doc`(tdd/architecture) | **Tier-4** `request_ticket_review`, `create_doc`(prd/fsd) |
|
|
305
|
-
| **Code index** (succeeded parse) | Tier-3 plan/reimplement/`create_doc`(tdd/architecture); `request_council` in `technical`/`discovery` modes | Tier-4 review/prd/fsd |
|
|
305
|
+
| **Version control (VCS)** | `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`, `fresh_base`, `parse_repository`, `regenerate_directory_map`; **Tier-3** `request_plan_generation`/`get_plan`, `request_reimplement_context`/`get_reimplement_context`, `create_doc`(tdd/architecture) | **Tier-4** `request_ticket_review`, `create_doc`(prd/fsd), `clarify_task` |
|
|
306
|
+
| **Code index** (succeeded parse) | Tier-3 plan/reimplement/`create_doc`(tdd/architecture); `request_council` in `technical`/`discovery` modes | Tier-4 review/prd/fsd, `clarify_task` |
|
|
306
307
|
| **SFCC OCAPI** | `check_permissions` + every gated SFCC read and write tool | — |
|
|
307
308
|
| **SFCC WebDAV logs** | `sfcc_log_query` | — |
|
|
308
309
|
| **Deep-research flag** | `request_deep_research`, `get_deep_research` | — |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge_gpt/mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.52",
|
|
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",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"mcp-server": "./build/index.js",
|
|
9
9
|
"bridge-api-mcp-server": "./build/index.js",
|
|
10
10
|
"conductor": "./build/conductor-bin.js",
|
|
11
|
-
"conductor-claude-hook": "./build/conductor-claude-hook-bin.js"
|
|
11
|
+
"conductor-claude-hook": "./build/conductor-claude-hook-bin.js",
|
|
12
|
+
"bridge-worker-guard": "./build/worker-guard-hook-bin.js"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
15
|
"build/",
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
|
|
27
28
|
"version": "node scripts/sync-readme-host-examples.js --write && git add -- README.md package.json package-lock.json && git commit -m \"chore(mcp-server): release $npm_package_version\" && git tag \"mcp-server/v$npm_package_version\"",
|
|
28
29
|
"check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
|
|
30
|
+
"assert:release-identity": "node scripts/assert-release-identity.js",
|
|
29
31
|
"postbuild": "node scripts/prepend-shebang.cjs",
|
|
30
32
|
"start": "node build/index.js",
|
|
31
33
|
"test": "npm run test:normal && npm run test:module-mocks",
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
"test:integration": "node scripts/run-integration-tests.js",
|
|
35
37
|
"test:smoke": "node --test build/integration/packaged-cli-smoke.test.js",
|
|
36
38
|
"canary:agent-capabilities": "npm run build && node scripts/agent-capabilities-canary.mjs",
|
|
37
|
-
"prepublishOnly": "node scripts/bundle-assets.js && npm run build && node scripts/verify-shebang.cjs"
|
|
39
|
+
"prepublishOnly": "node scripts/bundle-assets.js && npm run build && node scripts/verify-shebang.cjs && npm run assert:release-identity"
|
|
38
40
|
},
|
|
39
41
|
"dependencies": {
|
|
40
42
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
package/pipelines/plan-epic.json
CHANGED
|
@@ -45,6 +45,11 @@
|
|
|
45
45
|
"instruction_file": "explore-epic-subtasks.md",
|
|
46
46
|
"description": "Perform focused exploration for each sub-task"
|
|
47
47
|
},
|
|
48
|
+
{
|
|
49
|
+
"type": "agent_task",
|
|
50
|
+
"instruction_file": "assess-conductor-eligibility.md",
|
|
51
|
+
"description": "Assessing conductor eligibility"
|
|
52
|
+
},
|
|
48
53
|
{
|
|
49
54
|
"type": "agent_task",
|
|
50
55
|
"instruction_file": "write-epic-summary.md",
|