@bridge_gpt/mcp-server 0.2.54 → 0.2.55
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 +38 -54
- package/build/agent-guidance.generated.js +3 -0
- package/build/agent-launchers/claude-executor-adapter.js +3 -0
- package/build/agent-notes.js +178 -0
- package/build/agent-registry.js +5 -2
- package/build/agent-utils.js +58 -0
- package/build/agents.generated.js +1 -1
- package/build/codex-skill-adapter.js +55 -0
- package/build/commands.generated.js +5 -4
- package/build/conductor/bridge-api-client.js +199 -6
- package/build/conductor/bring-up-facts.js +187 -0
- package/build/conductor/claude-hook.js +7 -5
- package/build/conductor/cli.js +28 -0
- package/build/conductor/doctor.js +80 -9
- package/build/conductor/epic-implementer-cli.js +1298 -0
- package/build/conductor/epic-runtime.js +1 -1
- package/build/conductor/errors.js +2 -2
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-hooks.js +28 -14
- package/build/conductor/install-doctor.js +11 -5
- package/build/conductor/readiness-cli.js +10 -10
- package/build/conductor/readiness-sections.js +58 -9
- package/build/conductor/readiness.js +37 -6
- package/build/conductor/recovery-cli.js +289 -10
- package/build/conductor/recovery-operations.js +125 -2
- package/build/conductor/repair-contract.js +58 -0
- package/build/conductor/store.js +2 -2
- package/build/conductor/supervisor-runtime.js +1 -1
- package/build/conductor-bin.js +2 -139
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
- package/build/conductor-removed-stub-bin.js +30 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +77 -17
- package/build/drive-epic.js +541 -115
- package/build/epic-implementer-bin.js +145 -0
- package/build/epic-implementer-bundle-cli.js +264 -0
- package/build/epic-implementer-claude-hook-bin.js +3 -0
- package/build/epic-integration-pr.js +5 -3
- package/build/executor/env.js +6 -0
- package/build/executor/interrupted-worktree.js +60 -0
- package/build/executor/job-errors.js +45 -0
- package/build/executor/job-runner.js +274 -9
- package/build/executor/job-types.js +25 -9
- package/build/executor/merge-tree-classifier.js +171 -0
- package/build/executor/reconcile-overlap-governance.js +129 -0
- package/build/executor/reconcile-overlap-job.js +989 -0
- package/build/executor/reconcile-overlap-types.js +14 -0
- package/build/executor/spawn-job-driver.js +1 -0
- package/build/executor/types.js +2 -0
- package/build/executor/worker-finalization.js +25 -2
- package/build/executor/worker-guard-hook.js +15 -7
- package/build/implement-epic/bridge-client.js +773 -0
- package/build/implement-epic/checkpoint-store.js +542 -0
- package/build/implement-epic/cli.js +3158 -0
- package/build/implement-epic/cut-protocol.js +392 -0
- package/build/implement-epic/lock.js +302 -0
- package/build/implement-epic/pr-state.js +286 -0
- package/build/implement-epic/spawn.js +113 -0
- package/build/index.js +586 -138
- package/build/init.js +72 -8
- package/build/install-bridge-conductor.js +5 -5
- package/build/install-bridge.js +403 -70
- package/build/mcp-host-config.js +22 -60
- package/build/mcp-host-entry-adapter.js +18 -0
- package/build/mcp-host-targets.js +1 -21
- package/build/merge-pull-request.js +1 -1
- package/build/pipelines.generated.js +7 -7
- package/build/plan-epic-conductor-eligibility.js +1 -1
- package/build/plane/cli.js +36 -5
- package/build/plane/preflight.js +128 -12
- package/build/plane/shutdown.js +4 -4
- package/build/readiness-check.js +3 -3
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +1 -1
- package/build/setup-epic.js +69 -31
- package/build/start-tickets-conductor.js +8 -7
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +1 -1
- package/docs/CONDUCTOR.md +8 -6
- package/package.json +5 -3
package/build/setup-epic.js
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* association is immutable server-side and can never be attached later.
|
|
48
48
|
* c. Cut or validate `origin/<feature>` at EXACTLY the canonical indexed SHA
|
|
49
49
|
* with the operator's LOCAL `git`, through the shared cut protocol
|
|
50
|
-
* `
|
|
50
|
+
* `implement-epic init` also drives, with the run id so the scope is minted
|
|
51
51
|
* owned by the run. An existing branch at a different commit fails closed.
|
|
52
52
|
* The GitHub App never pushes — it is `contents: read`.
|
|
53
53
|
* d. Store and approve (unchanged requests). The server now READ-validates the
|
|
@@ -81,7 +81,7 @@ import { createProductionEpicIntegrationGhRunner, ensureEpicIntegrationPullReque
|
|
|
81
81
|
import { approveEpicPlan, createEpicRunWithDisposition, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
82
82
|
import { resolveTrackedPaths } from "./git-reachability.js";
|
|
83
83
|
import { validateBranchName } from "./base-ref.js";
|
|
84
|
-
// Reused from the
|
|
84
|
+
// Reused from the implement-epic client on purpose: adding a second wrapper under
|
|
85
85
|
// `conductor/` would give the v2 path its own copy to drift from.
|
|
86
86
|
//
|
|
87
87
|
// BAPI-850 extends that reuse to the canonical parse read, the repository base
|
|
@@ -89,9 +89,10 @@ import { validateBranchName } from "./base-ref.js";
|
|
|
89
89
|
// typed clients the pilot already has are the v2 path's clients too, so neither
|
|
90
90
|
// conductor can drift on a field name or an endpoint shape. No MCP tool, no
|
|
91
91
|
// input-schema field, and no description changes.
|
|
92
|
-
import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./
|
|
93
|
-
import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./
|
|
92
|
+
import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./implement-epic/bridge-client.js";
|
|
93
|
+
import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./implement-epic/cut-protocol.js";
|
|
94
94
|
import { hashPlan } from "./conductor/plan.js";
|
|
95
|
+
import { BRING_UP_REFUSAL_MESSAGES, firstBringUpRefusal, } from "./conductor/bring-up-facts.js";
|
|
95
96
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
96
97
|
// BAPI-872: local plane-manifest binding, so `plane down` can later stop the
|
|
97
98
|
// run this setup resolved WITHOUT guessing from repository-wide active-run
|
|
@@ -176,7 +177,7 @@ export function getSetupEpicUsage() {
|
|
|
176
177
|
"single-node plan) is unchanged and composes no posture at all.",
|
|
177
178
|
"",
|
|
178
179
|
"Unattended runs have TWO one-time repository prerequisites, refused by name",
|
|
179
|
-
"before any run is created: repository consent (
|
|
180
|
+
"before any run is created: repository consent (unattended_epic_implementer_allowed) and",
|
|
180
181
|
"a verified notify webhook default. `drive-epic` readiness lists both with their",
|
|
181
182
|
"current state.",
|
|
182
183
|
"",
|
|
@@ -1180,7 +1181,7 @@ export function evaluateSetupEpicUnattendedPrerequisites(readiness, requestedPol
|
|
|
1180
1181
|
return {
|
|
1181
1182
|
reason: "target_not_allowed",
|
|
1182
1183
|
message: "this repository has not consented to unattended conductor runs. Set " +
|
|
1183
|
-
"`
|
|
1184
|
+
"`unattended_epic_implementer_allowed` for it (see " +
|
|
1184
1185
|
"docs/claude/account-settings-operator-runbook.md), or pass --attended to " +
|
|
1185
1186
|
"create an attended run. No run was created.",
|
|
1186
1187
|
};
|
|
@@ -1218,6 +1219,31 @@ export function evaluateSetupEpicUnattendedPrerequisites(readiness, requestedPol
|
|
|
1218
1219
|
}
|
|
1219
1220
|
return null;
|
|
1220
1221
|
}
|
|
1222
|
+
/** The closing sentence every pre-create refusal ends with. */
|
|
1223
|
+
const NO_RUN_WAS_CREATED = "No run was created.";
|
|
1224
|
+
/**
|
|
1225
|
+
* Evaluate the bring-up facts against a readiness report.
|
|
1226
|
+
*
|
|
1227
|
+
* Pure and total: `null` is a readiness read that failed, which has proven
|
|
1228
|
+
* nothing and refuses as `readiness_unreadable`. Otherwise at most ONE refusal,
|
|
1229
|
+
* in the fixed {@link BRING_UP_REFUSAL_REASONS} order — each fact's
|
|
1230
|
+
* "unreadable" before its "bad", so an operator is never told to repair a
|
|
1231
|
+
* condition that could not be read.
|
|
1232
|
+
*/
|
|
1233
|
+
export function evaluateSetupEpicBringUpPrerequisites(readiness) {
|
|
1234
|
+
if (readiness === null) {
|
|
1235
|
+
return {
|
|
1236
|
+
reason: "readiness_unreadable",
|
|
1237
|
+
message: "the Bridge API conductor readiness report could not be read, so the database " +
|
|
1238
|
+
"migration, worker presence, and single-reconciler facts could not be confirmed. " +
|
|
1239
|
+
`Retry once the server is reachable. ${NO_RUN_WAS_CREATED}`,
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
const reason = firstBringUpRefusal(readiness.bring_up ?? null);
|
|
1243
|
+
if (reason === null)
|
|
1244
|
+
return null;
|
|
1245
|
+
return { reason, message: `${BRING_UP_REFUSAL_MESSAGES[reason]} ${NO_RUN_WAS_CREATED}` };
|
|
1246
|
+
}
|
|
1221
1247
|
/**
|
|
1222
1248
|
* Does the policy this invocation will actually send request unattended operation?
|
|
1223
1249
|
*
|
|
@@ -1619,13 +1645,13 @@ function scopeFailureGuidance(reason, featureBranch, epicRunId) {
|
|
|
1619
1645
|
if (reason === "canonical_index_advanced") {
|
|
1620
1646
|
return (`${SETUP_EPIC_SCOPE_REDRIVE_RECOVERY} The canonical index advanced before the ` +
|
|
1621
1647
|
`scope could be seeded: delete origin/${featureBranch}, abandon this run ` +
|
|
1622
|
-
`(
|
|
1648
|
+
`(epic-implementer abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
|
|
1623
1649
|
}
|
|
1624
1650
|
if (reason === "head_mismatch") {
|
|
1625
1651
|
return (`${SETUP_EPIC_SCOPE_REDRIVE_RECOVERY} origin/${featureBranch} does not sit at the ` +
|
|
1626
1652
|
`commit this scope was cut at, and setup-epic never force-updates an existing ` +
|
|
1627
1653
|
`ref: delete origin/${featureBranch}, abandon this run ` +
|
|
1628
|
-
`(
|
|
1654
|
+
`(epic-implementer abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
|
|
1629
1655
|
}
|
|
1630
1656
|
return `Recovery: ${SETUP_EPIC_SCOPE_RECOVERY_COMMAND}.`;
|
|
1631
1657
|
}
|
|
@@ -1924,7 +1950,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1924
1950
|
else if (err instanceof ConductorBridgeApiError && err.status === 409) {
|
|
1925
1951
|
deps.errorLog(`Epic ${opts.epicKey} has MULTIPLE active runs — it is wedged, and every ` +
|
|
1926
1952
|
`plan call will keep failing. Abandon the duplicate before retrying:\n` +
|
|
1927
|
-
`
|
|
1953
|
+
` epic-implementer abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
|
|
1928
1954
|
`Detail: ${errorDetail(err)}`);
|
|
1929
1955
|
return 1;
|
|
1930
1956
|
}
|
|
@@ -1982,7 +2008,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1982
2008
|
`'${existingBaseBranch}', not the repository base '${repositoryBase}', and ` +
|
|
1983
2009
|
`--into-base asks for the repository base branch. setup-epic will not retarget ` +
|
|
1984
2010
|
`an existing run. Re-run without --into-base to reuse it unchanged, or abandon ` +
|
|
1985
|
-
`the run (
|
|
2011
|
+
`the run (epic-implementer abandon-run --epic-run-id ${existingRunId}) and recreate ` +
|
|
1986
2012
|
`it on base.`);
|
|
1987
2013
|
return 1;
|
|
1988
2014
|
}
|
|
@@ -2112,7 +2138,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2112
2138
|
`keep failing. This is TERMINAL: it will not clear on retry, and it is ` +
|
|
2113
2139
|
`NOT a problem with your plan. Adopt the run you want to keep, or ` +
|
|
2114
2140
|
`abandon the duplicate:\n` +
|
|
2115
|
-
`
|
|
2141
|
+
` epic-implementer abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
|
|
2116
2142
|
`No run was created and no automation-start charge occurred.\n` +
|
|
2117
2143
|
`Detail: ${errorDetail(err)}`);
|
|
2118
2144
|
return 1;
|
|
@@ -2186,7 +2212,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2186
2212
|
// used for status instead). A no-feature run performs none of this.
|
|
2187
2213
|
//
|
|
2188
2214
|
// The scope's BASE is the repository's configured base branch (the same
|
|
2189
|
-
// resolution `
|
|
2215
|
+
// resolution `implement-epic init` uses), NOT `policy_json.base_branch` — that
|
|
2190
2216
|
// key names the FEATURE branch. A feature branch equal to the configured base
|
|
2191
2217
|
// needs no scope (dispatch already cuts from it) and matches the server's
|
|
2192
2218
|
// long-standing no-op for that case.
|
|
@@ -2371,9 +2397,9 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2371
2397
|
// run that is already going.
|
|
2372
2398
|
//
|
|
2373
2399
|
// Evaluated against the COMPOSED policy, so it covers the default profile and
|
|
2374
|
-
// a `--policy-file` alike;
|
|
2375
|
-
// policy that is not unattended
|
|
2376
|
-
//
|
|
2400
|
+
// a `--policy-file` alike; the UNATTENDED evaluation is skipped for
|
|
2401
|
+
// `--attended` and for any policy that is not unattended. (The readiness
|
|
2402
|
+
// read itself is no longer skipped — see BAPI-1153 below.)
|
|
2377
2403
|
//
|
|
2378
2404
|
// The file branch runs through `applySetupEpicUnattendedDefault` rather than
|
|
2379
2405
|
// taking the file verbatim: a file bypasses the no-file composer entirely, so
|
|
@@ -2381,18 +2407,30 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2381
2407
|
// which made the default profile unreachable by any documented invocation.
|
|
2382
2408
|
// The composed object is what gets gated AND what gets sent, so the create
|
|
2383
2409
|
// request carries exactly what was validated.
|
|
2410
|
+
//
|
|
2411
|
+
// BAPI-1153: ONE readiness read now serves both gates, and it happens for
|
|
2412
|
+
// EVERY create — attended included — because the bring-up gate below applies
|
|
2413
|
+
// to every run. Before any create request, manifest binding, or lane.
|
|
2384
2414
|
{
|
|
2415
|
+
let readiness = null;
|
|
2416
|
+
try {
|
|
2417
|
+
readiness = await fetchConductorReadiness(access, deps.fetch);
|
|
2418
|
+
}
|
|
2419
|
+
catch {
|
|
2420
|
+
// Fail CLOSED, and silently as to WHY: the thrown value can carry a URL
|
|
2421
|
+
// or a response excerpt, and this refusal is rendered to a terminal. The
|
|
2422
|
+
// generic `readiness_unreadable` message says what to do about it.
|
|
2423
|
+
readiness = null;
|
|
2424
|
+
}
|
|
2425
|
+
// Bring-up first: a behind database or a missing worker makes the
|
|
2426
|
+
// unattended questions moot, and refusing on the cheaper, universal fact
|
|
2427
|
+
// keeps the first-named blocker the same for attended and unattended runs.
|
|
2428
|
+
const bringUpRefusal = evaluateSetupEpicBringUpPrerequisites(readiness);
|
|
2429
|
+
if (bringUpRefusal !== null) {
|
|
2430
|
+
deps.errorLog(`setup-epic REFUSED (${bringUpRefusal.reason}): ${bringUpRefusal.message}`);
|
|
2431
|
+
return 1;
|
|
2432
|
+
}
|
|
2385
2433
|
if (requestedPolicyIsUnattended(composedForCreate, opts.attended)) {
|
|
2386
|
-
let readiness = null;
|
|
2387
|
-
try {
|
|
2388
|
-
readiness = await fetchConductorReadiness(access, deps.fetch);
|
|
2389
|
-
}
|
|
2390
|
-
catch {
|
|
2391
|
-
// Fail CLOSED, and silently as to WHY: the thrown value can carry a URL
|
|
2392
|
-
// or a response excerpt, and this refusal is rendered to a terminal. The
|
|
2393
|
-
// generic `readiness_unreadable` message says what to do about it.
|
|
2394
|
-
readiness = null;
|
|
2395
|
-
}
|
|
2396
2434
|
// The COMPOSED policy, not the flags: a --policy-file declaring
|
|
2397
2435
|
// notify.webhook_url satisfies the escalation-channel prerequisite, and
|
|
2398
2436
|
// it is the same object the create request carries.
|
|
@@ -2456,7 +2494,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2456
2494
|
say(`Run: adopting existing active run ${run.epic_run_id} (status: ${run.status})`);
|
|
2457
2495
|
say(` No second run was created — one epic has at most one active ` +
|
|
2458
2496
|
`run. Continue on this one, or abandon it first with ` +
|
|
2459
|
-
`\`
|
|
2497
|
+
`\`epic-implementer abandon-run --epic-run-id ${run.epic_run_id}\` to start over.`);
|
|
2460
2498
|
if (requestedPolicy !== undefined) {
|
|
2461
2499
|
const outcome = await applyReusedRunPolicy({
|
|
2462
2500
|
access,
|
|
@@ -2522,7 +2560,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2522
2560
|
else {
|
|
2523
2561
|
const msg = `Could not bind the local plane to run ${result.epic_run_id} (${bound.message}). ` +
|
|
2524
2562
|
"A later `plane down` cannot be guaranteed to stop it automatically — if you need " +
|
|
2525
|
-
`to stop this run, run \`
|
|
2563
|
+
`to stop this run, run \`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`.`;
|
|
2526
2564
|
warnings.push(msg);
|
|
2527
2565
|
say(`Plane: [warn] ${msg}`);
|
|
2528
2566
|
}
|
|
@@ -2542,7 +2580,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2542
2580
|
const msg = `A local plane manifest exists but could not be validated (${manifestRead.error}), so ` +
|
|
2543
2581
|
`run ${result.epic_run_id} was NOT bound to it. If a plane is running, \`plane down\` ` +
|
|
2544
2582
|
"will not stop this run automatically — use " +
|
|
2545
|
-
`\`
|
|
2583
|
+
`\`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`. A manifest written by a ` +
|
|
2546
2584
|
"NEWER build must be wound down with that build's `plane down`.";
|
|
2547
2585
|
warnings.push(msg);
|
|
2548
2586
|
say(`Plane: [warn] ${msg}`);
|
|
@@ -2553,7 +2591,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2553
2591
|
// an unexpected error here is reported as a warning, never thrown.
|
|
2554
2592
|
const msg = `Could not check for a local plane to bind run ${result.epic_run_id} to (${errorDetail(err)}). ` +
|
|
2555
2593
|
"A later `plane down` cannot be guaranteed to stop it automatically — if you need to stop " +
|
|
2556
|
-
`this run, run \`
|
|
2594
|
+
`this run, run \`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`.`;
|
|
2557
2595
|
warnings.push(msg);
|
|
2558
2596
|
say(`Plane: [warn] ${msg}`);
|
|
2559
2597
|
}
|
|
@@ -2562,7 +2600,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2562
2600
|
// AFTER the run exists and BEFORE the plan is stored/approved: the scope's run
|
|
2563
2601
|
// association is immutable server-side, so the cut is driven with this run's
|
|
2564
2602
|
// id and the scope is minted owned by it. Everything from `cut/begin` to the
|
|
2565
|
-
// hold's release is the SHARED protocol `
|
|
2603
|
+
// hold's release is the SHARED protocol `implement-epic init` drives — local git
|
|
2566
2604
|
// only, an exact-SHA create-only push, never the GitHub App, never a force
|
|
2567
2605
|
// update. A failure leaves the run non-active (never discoverable by the
|
|
2568
2606
|
// reconciler) and re-running setup-epic reuses it and re-drives the cut.
|
|
@@ -2683,7 +2721,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2683
2721
|
deps.errorLog(`The plan was NOT approved: this repository is no longer authorized for ` +
|
|
2684
2722
|
`unattended runs. The run WAS created and is left in 'planning' with no ` +
|
|
2685
2723
|
`activation and no executor dispatch — nothing was half-applied.\n` +
|
|
2686
|
-
`Restore \`
|
|
2724
|
+
`Restore \`unattended_epic_implementer_allowed\` for this repository (see ` +
|
|
2687
2725
|
`docs/claude/account-settings-operator-runbook.md) and re-run setup-epic, ` +
|
|
2688
2726
|
`or deliberately convert this run to an attended posture through the ` +
|
|
2689
2727
|
`supported operator flow.\nDetail: ${errorDetail(err)}`);
|
|
@@ -83,9 +83,10 @@ export const DEFAULT_CONDUCTOR_GATE_NAME = "implement-ticket";
|
|
|
83
83
|
* Exported since BAPI-1020 so the executor resolves the worker-guard bin through
|
|
84
84
|
* the SAME resolver rather than growing a second one. Correct in both build
|
|
85
85
|
* shapes because this module compiles to the build ROOT: the esbuild bundle puts
|
|
86
|
-
* `import.meta.url` at `build/index.js` / `build/
|
|
87
|
-
* it at `build/start-tickets-conductor.js`. A resolver living in a
|
|
88
|
-
* module would resolve to `build/<subdir>/<filename>` under `tsc`
|
|
86
|
+
* `import.meta.url` at `build/index.js` / `build/epic-implementer-bin.js`, and
|
|
87
|
+
* `tsc` puts it at `build/start-tickets-conductor.js`. A resolver living in a
|
|
88
|
+
* subdirectory module would resolve to `build/<subdir>/<filename>` under `tsc`
|
|
89
|
+
* and be wrong.
|
|
89
90
|
*/
|
|
90
91
|
export function defaultResolveBinPath(filename) {
|
|
91
92
|
return fileURLToPath(new URL(`./${filename}`, import.meta.url));
|
|
@@ -124,12 +125,12 @@ export async function createStartTicketsConductorContext(options, agent, deps) {
|
|
|
124
125
|
gateName,
|
|
125
126
|
supervisorMode,
|
|
126
127
|
agentName: agent.name,
|
|
127
|
-
cliFile: resolveBinPath("
|
|
128
|
+
cliFile: resolveBinPath("epic-implementer-bin.js"),
|
|
128
129
|
// Capture the conductor process's Node executable so dispatched workers run
|
|
129
|
-
//
|
|
130
|
-
// worker's own Node (BAPI-527).
|
|
130
|
+
// epic-implementer-bin.js under THIS runtime (which owns the ledger), never
|
|
131
|
+
// the worker's own Node (BAPI-527).
|
|
131
132
|
conductorNodePath: deps.execPath ?? process.execPath,
|
|
132
|
-
hookBinPath: resolveBinPath("
|
|
133
|
+
hookBinPath: resolveBinPath("epic-implementer-claude-hook-bin.js"),
|
|
133
134
|
};
|
|
134
135
|
if (options.epic) {
|
|
135
136
|
context.epic = options.epic;
|
|
@@ -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.55";
|
|
3
|
+
export const BUILD_COMMIT = "b96c1451f24e";
|
|
4
|
+
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.55", "serve"];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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)}
|
|
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)}let workerBranch=ctx.workerBranch;return typeof workerBranch!="string"||workerBranch.length===0?deny:positional.length<=1?{kind:"allow"}: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
4
|
`),exitCode=2):decision.kind==="malformed"&&(process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
|
|
5
5
|
`),exitCode=0)}catch{try{process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
|
|
6
6
|
`)}catch{}exitCode=0}process.exit(exitCode);
|
package/docs/CONDUCTOR.md
CHANGED
|
@@ -12,11 +12,13 @@ config. For the everyday `start-tickets` flags and cross-platform behavior, see
|
|
|
12
12
|
|
|
13
13
|
## Epic Conductor v2 — how an epic is actually driven
|
|
14
14
|
|
|
15
|
-
**The v1 `
|
|
15
|
+
**The v1 `epic-implementer epic-tick` command is frozen.** It throws
|
|
16
16
|
`EPIC_TICK_V1_FROZEN` on every invocation and advances nothing. There is nothing
|
|
17
17
|
to schedule locally. If you have an epic-tick schedule registered from an earlier
|
|
18
18
|
release, remove it — it is a dead timer.
|
|
19
|
-
`
|
|
19
|
+
`epic-implementer doctor` flags one if it finds it. (S4/BAPI-1080 renamed the
|
|
20
|
+
event-ledger CLI from `conductor` to `epic-implementer`; the retained
|
|
21
|
+
`conductor` bin name is now a fixed migration pointer.)
|
|
20
22
|
|
|
21
23
|
v2 splits the old local tick into two halves:
|
|
22
24
|
|
|
@@ -227,7 +229,7 @@ existing hooks) so the spawned session streams local `run.started` / `run.stoppe
|
|
|
227
229
|
environment scoped to that one terminal/tab/session — no credentials are ever placed
|
|
228
230
|
in the env, hook command, or run metadata. Override the gate/supervisor labels with
|
|
229
231
|
`BAPI_CONDUCTOR_GATE_NAME` / `BAPI_CONDUCTOR_SUPERVISOR_MODE`. Inspect the stream
|
|
230
|
-
with `
|
|
232
|
+
with `epic-implementer doctor`. Observability is best-effort: a conductor failure never
|
|
231
233
|
blocks or aborts a spawn, and `--dry-run` performs no conductor side effects.
|
|
232
234
|
(Epic dispatch always enables conductor internally, independent of this flag.)
|
|
233
235
|
|
|
@@ -236,13 +238,13 @@ When `--conductor` is set, the spawn boundary also injects
|
|
|
236
238
|
supervisor MCP tools (a plain `start-tickets` run stays on the default `core`
|
|
237
239
|
profile). See [README → Environment Variables](../README.md#environment-variables).
|
|
238
240
|
|
|
239
|
-
## `
|
|
241
|
+
## `epic-implementer install-git-hooks` (BAPI-395)
|
|
240
242
|
|
|
241
243
|
Installs local git hooks that opportunistically emit conductor git/PR/CI events into
|
|
242
244
|
the local ledger:
|
|
243
245
|
|
|
244
246
|
```
|
|
245
|
-
|
|
247
|
+
epic-implementer install-git-hooks [--json]
|
|
246
248
|
```
|
|
247
249
|
|
|
248
250
|
The installed hooks are **local, unversioned, opportunistic, and bypassable**: they
|
|
@@ -256,7 +258,7 @@ never a fatal error. The hooks installed are `post-commit` (emits
|
|
|
256
258
|
`git.commit_created`) and `reference-transaction` (emits `worktree.changed` for
|
|
257
259
|
committed ref updates).
|
|
258
260
|
|
|
259
|
-
Missing hooks do **not** prevent PR/CI gate evaluation — `
|
|
261
|
+
Missing hooks do **not** prevent PR/CI gate evaluation — `epic-implementer doctor` reads
|
|
260
262
|
hook presence and managed-snippet status **read-only** (a new `git hooks` section /
|
|
261
263
|
`git_hooks` JSON object alongside the ledger report), and the reconciler drives CI
|
|
262
264
|
polling and gate evaluation regardless of whether hooks are installed.
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge_gpt/mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.55",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"mcp-server": "./build/index.js",
|
|
9
9
|
"bridge-api-mcp-server": "./build/index.js",
|
|
10
|
+
"epic-implementer": "./build/epic-implementer-bin.js",
|
|
11
|
+
"epic-implementer-claude-hook": "./build/epic-implementer-claude-hook-bin.js",
|
|
10
12
|
"conductor": "./build/conductor-bin.js",
|
|
11
13
|
"conductor-claude-hook": "./build/conductor-claude-hook-bin.js",
|
|
12
14
|
"bridge-worker-guard": "./build/worker-guard-hook-bin.js"
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
"//test": "The `test` script delegates to the bounded launcher (scripts/run-unit-tests.js → src/run-unit-tests-launcher.ts, BAPI-683), which discovers build/*.test.js at runtime and spawns them in size-bounded batches so the argv never exceeds Windows' ~8191-char command-line limit. Two determinism rules from the pre-launcher script are preserved INSIDE the launcher: (1) multi-file batches run WITHOUT --test-force-exit, because that flag makes node:test exit before aggregating per-subprocess summaries and the tally becomes nondeterministic (failures still surface and still set exit 1, but the count cannot be trusted as a completeness signal); (2) build/secret-safety.test.js leaks a handle that holds the event loop ~60s after its tests finish (they run in ~147ms), so the launcher quarantines it into its own single-file --test-force-exit invocation (FORCE_EXIT_QUARANTINE in src/run-unit-tests-launcher.ts) where the tally stays exact. Do NOT add --test-force-exit to batched invocations, and do NOT remove the quarantine, until the underlying handle leak is fixed.",
|
|
25
27
|
"//version": "BAPI-930: fired by `npm version <level>` AFTER npm rewrites package.json's version field, so the bumped version and the repinned README land in the SAME commit. A bump performed by hand-editing package.json instead of running `npm version` bypasses this hook entirely and must repin manually via `node scripts/sync-readme-host-examples.js` — the `--check` wired into `build` below is the backstop that catches that case.",
|
|
26
28
|
"scripts": {
|
|
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",
|
|
29
|
+
"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 && node scripts/bundle-agent-guidance.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
|
|
28
30
|
"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\"",
|
|
29
31
|
"check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
|
|
30
32
|
"assert:release-identity": "node scripts/assert-release-identity.js",
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
},
|
|
61
63
|
"overrides": {
|
|
62
64
|
"fast-uri": ">=4.1.3",
|
|
63
|
-
"hono": ">=4.13.
|
|
65
|
+
"hono": ">=4.13.7",
|
|
64
66
|
"@hono/node-server": "^1.19.13",
|
|
65
67
|
"ip-address": ">=10.1.1"
|
|
66
68
|
},
|