@bridge_gpt/mcp-server 0.2.67 → 0.2.68
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 +178 -15
- package/build/agent-launchers/claude-executor-adapter.js +27 -0
- package/build/agents.generated.js +22 -2
- package/build/bitbucket-workspace-remote.js +136 -0
- package/build/bootstrap-project-identity.js +24 -0
- package/build/bridge-permissions.js +829 -0
- package/build/claude-settings.js +18 -0
- package/build/cli-help.js +21 -0
- package/build/command-assets-doctor.js +24 -70
- package/build/command-provisioning.js +29 -5
- package/build/commands.generated.js +8 -8
- package/build/conductor/bridge-api-client.js +714 -0
- package/build/conductor/command-drift-production.js +92 -0
- package/build/conductor/command-drift.js +89 -0
- package/build/conductor/doctor.js +69 -26
- package/build/conductor/epic-branch-ci-inspection.js +1375 -0
- package/build/conductor/epic-implementer-cli.js +76 -22
- package/build/conductor/gate-discovery.js +890 -0
- package/build/conductor/install-doctor.js +15 -3
- package/build/conductor/json-output.js +63 -0
- package/build/conductor/readiness-cli.js +95 -10
- package/build/conductor/readiness-sections.js +369 -3
- package/build/conductor/readiness.js +211 -8
- package/build/conductor/recovery-cli.js +110 -4
- package/build/conductor/recovery-operations.js +99 -1
- package/build/connect-bitbucket-api.js +41 -6
- package/build/connect-bitbucket.js +106 -33
- package/build/credential-store.js +207 -47
- package/build/docs.generated.js +57 -3
- package/build/doctor.js +92 -1
- package/build/drive-epic.js +45 -8
- package/build/epic-coherence-input.test-fixtures.js +4 -3
- package/build/epic-implementer-bin.js +1374 -25
- package/build/epic-workarounds-path.js +39 -0
- package/build/executor/cli.js +30 -0
- package/build/executor/completion-contract.js +18 -5
- package/build/executor/deps.js +4 -1
- package/build/executor/gate-qualification-job.js +499 -0
- package/build/executor/guarded-fast-forward-push.js +129 -0
- package/build/executor/integration-job.js +122 -8
- package/build/executor/job-errors.js +8 -1
- package/build/executor/job-runner.js +589 -47
- package/build/executor/likely-tests.js +290 -0
- package/build/executor/live-worker-registry.js +1 -1
- package/build/executor/observation.js +31 -2
- package/build/executor/packaged-recipe.js +106 -0
- package/build/executor/permissions.js +115 -28
- package/build/executor/platform.js +1 -1
- package/build/executor/preflight.js +61 -12
- package/build/executor/process-containment.js +36 -12
- package/build/executor/process.js +15 -7
- package/build/executor/prompt-spec.js +33 -1
- package/build/executor/recently-failed-tests-file.js +71 -0
- package/build/executor/reconcile-overlap-job.js +41 -23
- package/build/executor/required-verification.js +1 -1
- package/build/executor/required-work-ledger.js +105 -17
- package/build/executor/retirement.js +69 -0
- package/build/executor/runner.js +114 -11
- package/build/executor/test-clock.js +15 -0
- package/build/executor/types.js +32 -0
- package/build/executor/viewer-tabs.js +58 -8
- package/build/executor/worker-command.js +23 -7
- package/build/executor/worker-diagnostics.js +37 -0
- package/build/executor/worker-finalization.js +31 -5
- package/build/executor/worker-guard-hook.js +179 -25
- package/build/executor/worker-log.js +4 -3
- package/build/executor/worker-verification-guidance.js +37 -0
- package/build/executor/worktree.js +88 -11
- package/build/first-build-advisory.js +97 -0
- package/build/generated-assets-advisory.js +14 -0
- package/build/generated-assets-catalog.js +66 -0
- package/build/generated-assets-context.js +46 -0
- package/build/generated-assets-dependencies.js +26 -0
- package/build/generated-assets-doctor.js +50 -0
- package/build/generated-assets-health.js +65 -0
- package/build/generated-assets-host.js +17 -0
- package/build/generated-assets-inventory.js +17 -0
- package/build/generated-assets-paths.js +60 -0
- package/build/generated-assets-publisher.js +133 -0
- package/build/generated-assets-remedy.js +25 -0
- package/build/generated-assets-repair.js +72 -0
- package/build/generated-assets-session.js +71 -0
- package/build/generated-assets-test-support.js +35 -0
- package/build/index.js +964 -768
- package/build/init.js +54 -177
- package/build/install-bridge-conductor-gate.js +1372 -0
- package/build/install-bridge-conductor.js +289 -36
- package/build/install-bridge.js +544 -37
- package/build/mcp-install-state.js +50 -4
- package/build/merge-workflow-install.js +3 -4
- package/build/pipeline-utils.js +6 -2
- package/build/pipelines.generated.js +22 -142
- package/build/plan-execution-ledger.js +2 -32
- package/build/plane/manifest.js +16 -1
- package/build/plane/preflight.js +54 -1
- package/build/printed-remedy-commands.js +16 -1
- package/build/readme.generated.js +1 -1
- package/build/repair-assets.js +93 -0
- package/build/run-unit-tests-launcher.js +130 -49
- package/build/secret-redaction.js +33 -0
- package/build/setup-epic.js +108 -3
- package/build/start-tickets-prereqs.js +70 -16
- package/build/start-tickets.js +29 -5
- package/build/tool-surface-gating.js +36 -0
- package/build/upgrade-cli.js +55 -1
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +3 -2
- package/build/worker-guard-hook-main.js +1 -1
- package/build/worktree-core.js +15 -2
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +30 -12
- package/docs/EPIC-IMPLEMENTER.md +68 -2
- package/package.json +3 -3
- package/pipelines/greenfield-setup.json +4 -128
- package/pipelines/implement-ticket.json +3 -2
|
@@ -17,6 +17,7 @@ import path from "path";
|
|
|
17
17
|
import { resolveBapiCredentials, getPrimaryCredentialStorePath, } from "./credential-store.js";
|
|
18
18
|
import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
19
19
|
import { probeWorktreeMcpRegistration } from "./mcp-registration-doctor.js";
|
|
20
|
+
import { VERSION } from "./version.generated.js";
|
|
20
21
|
import { probeWorktreeCommandAssets } from "./command-assets-doctor.js";
|
|
21
22
|
import { MCP_SERVER_NAME, MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
22
23
|
// BAPI-941: both are LEAF modules (the pure classifier and its git-backed
|
|
@@ -159,7 +160,7 @@ const WORKTRUNK_INSTALL_HINTS = {
|
|
|
159
160
|
linux: "See the Worktrunk documentation for Linux install instructions: https://worktrunk.dev",
|
|
160
161
|
};
|
|
161
162
|
const GIT_INSTALL_HINTS = {
|
|
162
|
-
darwin: "xcode-select --install (or brew install git)",
|
|
163
|
+
darwin: "xcode-select --install (or brew install git). Installing another Git does not ensure PATH selects it; verify with which git and git --version.",
|
|
163
164
|
linux: "Install git with your distro package manager, e.g. apt install git",
|
|
164
165
|
win32: "Install Git for Windows: https://git-scm.com/download/win",
|
|
165
166
|
};
|
|
@@ -444,24 +445,19 @@ export function worktreeMcpReachabilityDescriptor() {
|
|
|
444
445
|
},
|
|
445
446
|
};
|
|
446
447
|
}
|
|
447
|
-
/**
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
* files, which are fill-only, never refreshed, and Git-excluded per worktree.
|
|
452
|
-
*/
|
|
453
|
-
const WORKTREE_COMMAND_ASSETS_HINT = "Re-run start-tickets to provision the packaged Claude command assets " +
|
|
454
|
-
"(.claude/commands/) into this worktree. Do not manually copy or commit the " +
|
|
455
|
-
"generated command files — they are bootstrapped per worktree and Git-excluded.";
|
|
448
|
+
/** Version-pinned fill-only repair; permissions and unsafe paths need their own fix. */
|
|
449
|
+
const WORKTREE_COMMAND_ASSETS_HINT = `Run npx -y ${MCP_PACKAGE_NAME}@${VERSION} repair-assets --project-root <established-root> --host claude ` +
|
|
450
|
+
"to fill missing packaged files. Resolve permission/path/platform problems separately; refresh/reconnect and run doctor.";
|
|
451
|
+
const WORKTREE_COMMAND_ASSETS_PROVISION_HINT = "In-place asset repair is not supported on this platform. Re-run start-tickets to provision the packaged Claude command assets (.claude/commands/) into this worktree. Existing files are preserved; refresh/reconnect and run doctor.";
|
|
456
452
|
const WORKTREE_COMMAND_ASSETS_INSTALL_HINTS = {
|
|
457
|
-
darwin:
|
|
453
|
+
darwin: WORKTREE_COMMAND_ASSETS_PROVISION_HINT,
|
|
458
454
|
linux: WORKTREE_COMMAND_ASSETS_HINT,
|
|
459
|
-
win32:
|
|
455
|
+
win32: WORKTREE_COMMAND_ASSETS_PROVISION_HINT,
|
|
460
456
|
};
|
|
461
457
|
/**
|
|
462
458
|
* Doctor-only, strictly read-only probe: does the current worktree contain every
|
|
463
459
|
* packaged Claude slash-command asset under `.claude/commands/`? Delegates to the
|
|
464
|
-
* read-only {@link probeWorktreeCommandAssets} using only `deps.
|
|
460
|
+
* read-only {@link probeWorktreeCommandAssets} using only `deps.commandAssetMetadata`,
|
|
465
461
|
* `deps.platform`, and `deps.cwd`. Reports an unavailable diagnostic (never a
|
|
466
462
|
* false "present") when the optional read-only filesystem boundary is absent. The
|
|
467
463
|
* probe performs NO writes, Git calls, or process spawns.
|
|
@@ -472,15 +468,14 @@ export function worktreeCommandAssetsDescriptor() {
|
|
|
472
468
|
label: "Worktree Claude command assets",
|
|
473
469
|
installHint: WORKTREE_COMMAND_ASSETS_INSTALL_HINTS,
|
|
474
470
|
probe: async (deps) => {
|
|
475
|
-
|
|
476
|
-
if (!readFile) {
|
|
471
|
+
if (!deps.commandAssetMetadata) {
|
|
477
472
|
return {
|
|
478
473
|
found: false,
|
|
479
474
|
detail: "command-asset probe unavailable (no read-only filesystem access)",
|
|
480
475
|
};
|
|
481
476
|
}
|
|
482
477
|
const result = await probeWorktreeCommandAssets(deps.cwd, {
|
|
483
|
-
|
|
478
|
+
metadata: deps.commandAssetMetadata,
|
|
484
479
|
platform: deps.platform,
|
|
485
480
|
});
|
|
486
481
|
return { found: result.found, detail: result.detail };
|
|
@@ -817,6 +812,7 @@ export function getDoctorOnlyPrereqDescriptors(_platform, _env, agent) {
|
|
|
817
812
|
return [
|
|
818
813
|
uvDescriptor(),
|
|
819
814
|
agentDescriptor(agent),
|
|
815
|
+
gitCompatibilityDescriptor(),
|
|
820
816
|
credentialResolutionDescriptor(),
|
|
821
817
|
worktreeMcpReachabilityDescriptor(),
|
|
822
818
|
worktreeCommandAssetsDescriptor(),
|
|
@@ -863,6 +859,7 @@ export async function probePrerequisite(deps, descriptor) {
|
|
|
863
859
|
label: descriptor.label,
|
|
864
860
|
found: outcome.found,
|
|
865
861
|
detail: outcome.detail,
|
|
862
|
+
gitCompatibility: outcome.gitCompatibility,
|
|
866
863
|
installHint: hintForPlatform(descriptor.installHint, deps.platform),
|
|
867
864
|
authNote: descriptor.authNote,
|
|
868
865
|
// Deliberately NOT carried through when the probe THREW: the catch above
|
|
@@ -872,6 +869,63 @@ export async function probePrerequisite(deps, descriptor) {
|
|
|
872
869
|
renderDetailWhenMissing: descriptor.renderDetailWhenMissing === true && !probeThrew ? true : undefined,
|
|
873
870
|
};
|
|
874
871
|
}
|
|
872
|
+
/** Release-specific advisory baseline; never a launcher admission requirement.
|
|
873
|
+
* Evidence: https://github.com/max-sixty/worktrunk/blob/v0.80.0/src/git/version.rs
|
|
874
|
+
*/
|
|
875
|
+
export const WORKTRUNK_GIT_BASELINE = "Worktrunk v0.80.0 (Git >= 2.43.0)";
|
|
876
|
+
/** Read-only, advisory compatibility with the documented release, not every release. */
|
|
877
|
+
export async function probeGitWorktrunkCompatibility(deps) {
|
|
878
|
+
const basis = WORKTRUNK_GIT_BASELINE;
|
|
879
|
+
let executable;
|
|
880
|
+
let version;
|
|
881
|
+
const unknown = (reason) => ({ status: "unknown", executable, version, basis, reason });
|
|
882
|
+
try {
|
|
883
|
+
const locator = getCommandProbe("git", deps.platform);
|
|
884
|
+
const located = await deps.runCommand(locator.file, locator.args, { timeoutMs: 3000 });
|
|
885
|
+
if (located.unavailable)
|
|
886
|
+
return unknown("locator-unavailable");
|
|
887
|
+
if (!commandSucceeded(located))
|
|
888
|
+
return unknown(located.exitCode === 1 ? "git-missing" : "locator-unavailable");
|
|
889
|
+
const candidate = located.stdout.trim().split(/\r?\n/)[0];
|
|
890
|
+
// Only absolute paths from a successful locator are diagnostic metadata.
|
|
891
|
+
if (!candidate || candidate.length > 512 || /[\x00-\x1f\x7f]/.test(candidate) ||
|
|
892
|
+
!(deps.platform === "win32" ? path.win32.isAbsolute(candidate) : path.posix.isAbsolute(candidate))) {
|
|
893
|
+
return unknown("locator-unavailable");
|
|
894
|
+
}
|
|
895
|
+
executable = candidate;
|
|
896
|
+
const result = await deps.runCommand(executable, ["--version"], { timeoutMs: 3000 });
|
|
897
|
+
if (!commandSucceeded(result))
|
|
898
|
+
return unknown("version-unavailable");
|
|
899
|
+
const match = /^git version (\d{1,5})\.(\d{1,5})(?:\.(\d{1,5}))?(?:\.windows\.\d+| \(Apple Git-[\w.-]+\))?$/.exec(result.stdout.trim());
|
|
900
|
+
if (!match)
|
|
901
|
+
return unknown("version-unparseable");
|
|
902
|
+
const parts = [Number(match[1]), Number(match[2]), Number(match[3] ?? 0)];
|
|
903
|
+
version = parts.join(".");
|
|
904
|
+
if (deps.env[WORKTRUNK_BINARY_OVERRIDE_ENV]?.trim())
|
|
905
|
+
return unknown("override-unconfirmed");
|
|
906
|
+
const minimum = [2, 43, 0];
|
|
907
|
+
const differing = parts.findIndex((n, i) => n !== minimum[i]);
|
|
908
|
+
const compatible = differing < 0 || parts[differing] > minimum[differing];
|
|
909
|
+
return { status: compatible ? "compatible" : "incompatible", executable, version, basis };
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
return unknown(executable ? "version-unavailable" : "locator-unavailable");
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/** Pure formatter shared by doctor and first-build output. No raw probe output. */
|
|
916
|
+
export function formatGitCompatibilityDiagnostic(result) {
|
|
917
|
+
const metadata = [result.executable, result.version && `Git ${result.version}`].filter(Boolean).join("; ");
|
|
918
|
+
return `Git compatibility advisory: ${result.status}${metadata ? ` — ${metadata}` : ""}; comparison: ${result.basis}` +
|
|
919
|
+
(result.reason ? `; ${result.reason}` : "") +
|
|
920
|
+
(result.status === "incompatible" ? ". Verify the Git executable/version selected on PATH and install a supported Git manually." : "") +
|
|
921
|
+
(result.reason === "override-unconfirmed" ? ". Custom Worktrunk release requirement is unconfirmed." : "");
|
|
922
|
+
}
|
|
923
|
+
export function gitCompatibilityDescriptor() {
|
|
924
|
+
return {
|
|
925
|
+
id: "git-compatibility", label: "Git compatibility advisory", installHint: GIT_INSTALL_HINTS,
|
|
926
|
+
probe: async (deps) => ({ found: true, gitCompatibility: await probeGitWorktrunkCompatibility(deps) }),
|
|
927
|
+
};
|
|
928
|
+
}
|
|
875
929
|
/**
|
|
876
930
|
* Enforce the preflight descriptors fail-fast (matching the existing
|
|
877
931
|
* `runPreflight` behaviour). Iterates the shared preflight descriptors in order
|
package/build/start-tickets.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordProvisionedAssets } from "./generated-assets-inventory.js";
|
|
1
2
|
/**
|
|
2
3
|
* start-tickets — packaged CLI subcommand of the bridge-api-mcp-server bin.
|
|
3
4
|
*
|
|
@@ -836,6 +837,7 @@ export function createDefaultStartTicketsDeps() {
|
|
|
836
837
|
encoding: "utf-8",
|
|
837
838
|
// Optional bounded timeout (e.g. cursor-agent --list-models probes).
|
|
838
839
|
timeout: options?.timeoutMs,
|
|
840
|
+
signal: options?.signal,
|
|
839
841
|
}, (error, stdout, stderr) => {
|
|
840
842
|
const exitCode = error && typeof error.code === "number"
|
|
841
843
|
? (error.code)
|
|
@@ -846,6 +848,7 @@ export function createDefaultStartTicketsDeps() {
|
|
|
846
848
|
stdout: stdout ?? "",
|
|
847
849
|
stderr: stderr ?? "",
|
|
848
850
|
exitCode,
|
|
851
|
+
...(error && typeof error.code === "string" ? { unavailable: true } : {}),
|
|
849
852
|
});
|
|
850
853
|
});
|
|
851
854
|
});
|
|
@@ -1643,7 +1646,7 @@ export async function spawnMacOSTerminalTab(deps, terminal, shellCommand, contex
|
|
|
1643
1646
|
: buildTerminalAppleScript(shellCommand, title);
|
|
1644
1647
|
const result = await deps.runCommand("osascript", ["-e", script]);
|
|
1645
1648
|
if (commandSucceeded(result))
|
|
1646
|
-
return { ok: true };
|
|
1649
|
+
return { ok: true, destination: terminal === "iterm" ? "iTerm tab" : "macOS Terminal tab" };
|
|
1647
1650
|
const reason = (result.stderr || result.stdout || "").trim();
|
|
1648
1651
|
return {
|
|
1649
1652
|
ok: false,
|
|
@@ -1731,7 +1734,7 @@ export async function spawnWindowsTerminalTab(deps, _terminal, shellCommand, con
|
|
|
1731
1734
|
const args = buildWindowsTerminalArgs(worktreePath, shellCommand, title);
|
|
1732
1735
|
const result = await deps.runCommand(WINDOWS_TERMINAL_COMMAND, args);
|
|
1733
1736
|
if (commandSucceeded(result))
|
|
1734
|
-
return { ok: true };
|
|
1737
|
+
return { ok: true, destination: "Windows Terminal tab" };
|
|
1735
1738
|
const reason = combineCommandOutput(result);
|
|
1736
1739
|
return {
|
|
1737
1740
|
ok: false,
|
|
@@ -1754,7 +1757,7 @@ export async function spawnWindowsTerminalTab(deps, _terminal, shellCommand, con
|
|
|
1754
1757
|
fallback,
|
|
1755
1758
|
]);
|
|
1756
1759
|
if (commandSucceeded(result))
|
|
1757
|
-
return { ok: true };
|
|
1760
|
+
return { ok: true, destination: "PowerShell fallback window" };
|
|
1758
1761
|
const reason = combineCommandOutput(result);
|
|
1759
1762
|
return {
|
|
1760
1763
|
ok: false,
|
|
@@ -1858,7 +1861,7 @@ export async function spawnLinuxTmuxTerminalTab(deps, _terminal, shellCommand, c
|
|
|
1858
1861
|
: buildTmuxNewSessionArgs(session, window, worktreePath, paneCommand);
|
|
1859
1862
|
const result = await deps.runCommand(TMUX_COMMAND, args);
|
|
1860
1863
|
if (commandSucceeded(result))
|
|
1861
|
-
return { ok: true };
|
|
1864
|
+
return { ok: true, destination: "detached tmux session" };
|
|
1862
1865
|
const reason = combineCommandOutput(result);
|
|
1863
1866
|
return {
|
|
1864
1867
|
ok: false,
|
|
@@ -2118,7 +2121,7 @@ export async function spawnTabsForCreatedWorktrees(deps, rows, terminal, buildSh
|
|
|
2118
2121
|
worktreePath: row.path,
|
|
2119
2122
|
});
|
|
2120
2123
|
if (result.ok) {
|
|
2121
|
-
out.push({ ...row, status: "spawned" });
|
|
2124
|
+
out.push({ ...row, status: "spawned", ...(result.destination ? { sessionDestination: result.destination } : {}) });
|
|
2122
2125
|
}
|
|
2123
2126
|
else {
|
|
2124
2127
|
out.push({ ...row, status: "spawn-failed", error: result.error });
|
|
@@ -2220,6 +2223,23 @@ export function buildDryRunDetailLines(agent, key, branch, platform = process.pl
|
|
|
2220
2223
|
* ` path=PATH` when available) followed by warning lines for any create/spawn
|
|
2221
2224
|
* failures.
|
|
2222
2225
|
*/
|
|
2226
|
+
/** Explain only actual successful sessions; summary serialization stays stable. */
|
|
2227
|
+
export function formatSpawnedSessionExplanation(rows, context) {
|
|
2228
|
+
const spawned = rows.filter((row) => row.status === "spawned");
|
|
2229
|
+
if (!spawned.length)
|
|
2230
|
+
return "";
|
|
2231
|
+
const lines = ["Spawned sessions:"];
|
|
2232
|
+
for (const row of spawned) {
|
|
2233
|
+
lines.push(` ${row.key}: ${context.agent} runs in ${row.path ?? "a worktree whose path was not reported"} on branch ${row.branch}; ${row.sessionDestination ?? "terminal destination not reported"}.`);
|
|
2234
|
+
}
|
|
2235
|
+
lines.push("Work on each branch from its reported folder: it is already checked out there and normally cannot also be checked out in the original folder.");
|
|
2236
|
+
lines.push("Monitor the reported terminal session and its agent output; use the attach commands below on Linux. Session startup is not review or implementation completion.");
|
|
2237
|
+
if (context.workflow === "review-and-implement")
|
|
2238
|
+
lines.push("Each session reviews independently, then opens a fresh implementation session only after its review halt gate permits it.");
|
|
2239
|
+
if (spawned.length > 1)
|
|
2240
|
+
lines.push("Coordinate ownership across these independent sessions; pause affected work if overlapping changes or conflicts appear.");
|
|
2241
|
+
return lines.join("\n");
|
|
2242
|
+
}
|
|
2223
2243
|
export function formatSummaryReport(rows) {
|
|
2224
2244
|
const lines = ["Summary:"];
|
|
2225
2245
|
// Surface the single conductor run id near the top (first line stays
|
|
@@ -2323,6 +2343,7 @@ export function buildCommandProvisioningDeps(deps) {
|
|
|
2323
2343
|
runCommand: deps.runCommand,
|
|
2324
2344
|
platform: deps.platform,
|
|
2325
2345
|
cwd: deps.cwd,
|
|
2346
|
+
...(!deps.readFile && !deps.writeFile && !deps.mkdir ? { recordAssets: recordProvisionedAssets } : {}),
|
|
2326
2347
|
};
|
|
2327
2348
|
}
|
|
2328
2349
|
/**
|
|
@@ -4213,6 +4234,9 @@ export async function runStartTicketsCli(argv, overrides = {}) {
|
|
|
4213
4234
|
return 1;
|
|
4214
4235
|
}
|
|
4215
4236
|
log(formatSummaryReport(result.rows));
|
|
4237
|
+
const explanation = formatSpawnedSessionExplanation(result.rows, { agent: options.agentName, workflow: options.workflow ?? "implement" });
|
|
4238
|
+
if (explanation)
|
|
4239
|
+
log(explanation);
|
|
4216
4240
|
// Linux: each spawned ticket runs in a detached tmux session — tell the user
|
|
4217
4241
|
// how to attach.
|
|
4218
4242
|
const spawned = result.rows.filter((r) => r.status === "spawned");
|
|
@@ -449,6 +449,42 @@ export function createUpdateAdvisoryListHandler(inner, getAdvisory, onListServed
|
|
|
449
449
|
}
|
|
450
450
|
};
|
|
451
451
|
}
|
|
452
|
+
/** Compose once over undecorated SDK results, preserving both metadata keys.
|
|
453
|
+
* Weak keys prevent retained results from accumulating session-wide state. */
|
|
454
|
+
const undecoratedResults = new WeakMap();
|
|
455
|
+
export function composeListAdvisories(original, update, recovery) {
|
|
456
|
+
const base = undecoratedResults.get(original) ?? original;
|
|
457
|
+
const updated = decorateListResultWithUpdateAdvisory(base, update);
|
|
458
|
+
if (!recovery)
|
|
459
|
+
return updated;
|
|
460
|
+
try {
|
|
461
|
+
const anchor = base.tools.find(t => t.name === "ping") ?? base.tools[0];
|
|
462
|
+
const result = { ...updated,
|
|
463
|
+
tools: updated.tools.map(tool => tool.name === anchor?.name
|
|
464
|
+
? { ...tool, description: `${tool.name === "ping" && update ? `${update}\n\n` : ""}${recovery.text}\n\n${anchor?.description ?? ""}`.trimEnd() } : tool),
|
|
465
|
+
_meta: { ...(updated._meta ?? {}), "bridge-api/asset-recovery": { host: recovery.host,
|
|
466
|
+
missingCount: recovery.missingCount, paths: recovery.paths, capabilities: recovery.capabilities, command: recovery.command } },
|
|
467
|
+
};
|
|
468
|
+
undecoratedResults.set(result, base);
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
return updated;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
export function createComposedAdvisoryListHandler(inner, getUpdate, getRecovery, onListServed) {
|
|
476
|
+
return async (request, extra) => {
|
|
477
|
+
const original = await inner(request, extra);
|
|
478
|
+
try {
|
|
479
|
+
const recovery = await getRecovery();
|
|
480
|
+
onListServed?.();
|
|
481
|
+
return composeListAdvisories(original, getUpdate(), recovery);
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
return decorateListResultWithUpdateAdvisory(original, getUpdate());
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
452
488
|
/** Fixed, sanitized boot error (never includes map contents or config). */
|
|
453
489
|
const COMPAT_ERROR = "tool-surface gating: incompatible MCP SDK — the tools/list handler could not be resolved for override.";
|
|
454
490
|
/**
|
package/build/upgrade-cli.js
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* The command never reports a transition it did not confirm on disk.
|
|
33
33
|
*/
|
|
34
34
|
import { spawn } from "child_process";
|
|
35
|
-
import { readFile, stat } from "fs/promises";
|
|
35
|
+
import { mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
36
36
|
import path from "path";
|
|
37
37
|
import { VERSION } from "./version.generated.js";
|
|
38
38
|
import { runInit } from "./init.js";
|
|
@@ -42,9 +42,28 @@ import { fetchLatestVersion } from "./cli-release.js";
|
|
|
42
42
|
import { CANONICAL_UPGRADE_COMMAND } from "./upgrade-advice.js";
|
|
43
43
|
import { describeLauncherReason, inspectLauncherConfigs, isApplicableStandardLauncher, isPrerequisiteFailure, LAUNCHER_CONFIG_TARGETS, } from "./launcher-config-inspection.js";
|
|
44
44
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
45
|
+
import { applyProjectPermissions, BRIDGE_PERMISSIONS_MANIFEST_RELPATH, BRIDGE_PROJECT_SETTINGS_RELPATH, } from "./bridge-permissions.js";
|
|
46
|
+
import { ensureGitignored } from "./git-ignore-utils.js";
|
|
45
47
|
import { defaultNpmEntryFileExists, resolveNpmEntry } from "./windows-npm-entry.js";
|
|
46
48
|
/** The sentinel this CLI passes to the child it re-execs from `@latest`. */
|
|
47
49
|
const INTERNAL_REEXEC_FLAG = "--internal-reexec";
|
|
50
|
+
/** Production binding of {@link UpgradeCliDeps.applyBridgePermissions}. */
|
|
51
|
+
export async function defaultApplyBridgePermissions(cwd, env) {
|
|
52
|
+
const fsDeps = {
|
|
53
|
+
readFile: (filePath) => readFile(filePath, "utf-8"),
|
|
54
|
+
writeFile: (filePath, data) => writeFile(filePath, data, "utf-8"),
|
|
55
|
+
mkdir: (dirPath, options) => mkdir(dirPath, options),
|
|
56
|
+
};
|
|
57
|
+
for (const relPath of [BRIDGE_PERMISSIONS_MANIFEST_RELPATH, BRIDGE_PROJECT_SETTINGS_RELPATH]) {
|
|
58
|
+
try {
|
|
59
|
+
await ensureGitignored(cwd, relPath, fsDeps);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Advisory: the files are still written, and doctor reads them either way.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return applyProjectPermissions({ cwd, env }, fsDeps);
|
|
66
|
+
}
|
|
48
67
|
/**
|
|
49
68
|
* The fixed, secret-free message printed when, on Windows, the latest CLI could
|
|
50
69
|
* not be STARTED. Upgrade stops here rather than continuing with the local copy.
|
|
@@ -186,6 +205,7 @@ export function createDefaultUpgradeCliDeps(cwd) {
|
|
|
186
205
|
stdout: (m) => console.log(m),
|
|
187
206
|
stderr: (m) => console.error(m),
|
|
188
207
|
env: process.env,
|
|
208
|
+
applyBridgePermissions: defaultApplyBridgePermissions,
|
|
189
209
|
};
|
|
190
210
|
}
|
|
191
211
|
// ---------------------------------------------------------------------------
|
|
@@ -495,6 +515,17 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
495
515
|
stderr("\nBridge API upgrade finished with an UNCONFIGURED MCP entry (see above).");
|
|
496
516
|
return 1;
|
|
497
517
|
}
|
|
518
|
+
// ---- Phase 8 (BAPI-1398): the project's /drive-epic-run permission rules ----
|
|
519
|
+
// Only after every gate above passed. Advisory: a malformed settings file or a
|
|
520
|
+
// refused launcher is reported and never changes the exit code.
|
|
521
|
+
if (deps.applyBridgePermissions) {
|
|
522
|
+
try {
|
|
523
|
+
reportUpgradePermissions(await deps.applyBridgePermissions(deps.cwd, deps.env ?? {}), stdout, stderr);
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
stderr("Warning: could not write Bridge's permission rules (non-fatal).");
|
|
527
|
+
}
|
|
528
|
+
}
|
|
498
529
|
const applicableStandard = verified.filter(isApplicableStandardLauncher);
|
|
499
530
|
if (isEveryLauncherAlreadyCurrent(initResult.configActions)) {
|
|
500
531
|
// The exact string `/upgrade-bridge` is contracted to relay verbatim.
|
|
@@ -514,6 +545,29 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
514
545
|
}
|
|
515
546
|
return 0;
|
|
516
547
|
}
|
|
548
|
+
/** Render the project permission result in upgrade's own report style. */
|
|
549
|
+
export function reportUpgradePermissions(result, stdout, stderr) {
|
|
550
|
+
if (!result.launcher.ok) {
|
|
551
|
+
stderr(`Warning: Bridge permission rules were not written: ${result.launcher.reason}.`);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const scope = result.project;
|
|
555
|
+
if (scope.status === "written" || scope.status === "unchanged") {
|
|
556
|
+
stdout(`Bridge permission rules (${scope.path}, launcher ${result.launcher.prefix}):`);
|
|
557
|
+
if (scope.inserted.length === 0 && scope.removedStale.length === 0) {
|
|
558
|
+
stdout(` all ${scope.alreadyPresent.length} rules already present.`);
|
|
559
|
+
}
|
|
560
|
+
for (const rule of scope.inserted)
|
|
561
|
+
stdout(` wrote ${rule}`);
|
|
562
|
+
for (const rule of scope.removedStale)
|
|
563
|
+
stdout(` removed stale ${rule}`);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (scope.status === "malformed" || scope.status === "io-error") {
|
|
567
|
+
stderr(`Warning: ${scope.path} ${scope.status === "malformed" ? "is not valid JSON settings and was left unchanged" : "could not be written"}; ` +
|
|
568
|
+
`${scope.skipped.length} Bridge permission rule(s) were not written.`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
517
571
|
/**
|
|
518
572
|
* Project pre-mutation inspections onto the action shape `isEveryLauncherAlreadyCurrent`
|
|
519
573
|
* consumes, so the dry run answers the currentness question exactly the way the
|
|
@@ -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.68";
|
|
3
|
+
export const BUILD_COMMIT = "2aef1c5656bc";
|
|
4
|
+
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.68", "serve"];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
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){
|
|
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 unsafeGitCategory(argv){let{subcommand}=parseGitInvocation(argv,void 0);return subcommand==="push"?"git-push":subcommand==="reset"?"git-reset-hard":subcommand==="clean"?"filesystem-clean":"git-ambiguous"}function unsafeSegmentDecision(family,argv){switch(family){case"git":return{kind:"deny",category:unsafeGitCategory(argv),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)}var WORKFLOW_NAMES="implement-ticket|plan-ticket|idea-to-pr|review-and-implement",WORKFLOW_SLASH_LAUNCH=new RegExp(`(?:^|\\s)/(?:bridge-)?(?:${WORKFLOW_NAMES})(?=\\s|$)`,"i"),WORKFLOW_NAME_EXACT=new RegExp(`^/?(?:[A-Za-z0-9_-]+:)?(?:bridge-)?(?:${WORKFLOW_NAMES})$`,"i"),CLAUDE_PACKAGE_TOKEN=/^(?:@anthropic-ai\/)?claude(?:-code)?(?:@\S+)?$/;function claudeLaunchArgs(argv){let base=path.basename(argv[0]??"");if(base==="claude")return argv.slice(1);if(base==="npx"){let index=argv.findIndex((token,i)=>i>0&&CLAUDE_PACKAGE_TOKEN.test(token));return index===-1?void 0:argv.slice(index+1)}}function isClaudeWorkflowPrintLaunch(segment){let args=claudeLaunchArgs(segment.argv);return args===void 0||!args.some(token=>token==="-p"||token==="--print"||token.startsWith("--print="))?!1:segment.unsafe?!0:args.some(token=>{let value=token.startsWith("--print=")?token.slice(8):token;return WORKFLOW_SLASH_LAUNCH.test(value)})}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)}if(segment={argv,unsafe:segment.unsafe},ctx.correctionEnforced&&isClaudeWorkflowPrintLaunch(segment))return{kind:"deny",category:"workflow-launch-shell",family:"workflow"};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family,segment.argv);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 workflowDeny(category){return{kind:"deny",category,family:"workflow"}}var FILE_TOOLS=new Set(["Read","Edit","Write","MultiEdit","NotebookEdit","Grep","Glob"]),INVOCATION_FIELDS={Skill:["skill","args"],Task:["prompt","description"],Agent:["prompt","description"],SlashCommand:["command"]},PLANNER_TOOL_NAME=/(?:request_plan_generation|get_pipeline_recipe|plan_ticket|implement_ticket)/i;function evaluateCorrectionToolRole(toolName,input){if(FILE_TOOLS.has(toolName))return{kind:"allow"};if(toolName==="Bash")return;let fields=INVOCATION_FIELDS[toolName];if(fields!==void 0){for(let field of fields){let value=input[field];if(value===void 0)continue;if(typeof value!="string")return workflowDeny("workflow-launch-skill");let nameField=toolName==="Skill"?field==="skill":toolName==="SlashCommand";if(WORKFLOW_SLASH_LAUNCH.test(value)||nameField&&WORKFLOW_NAME_EXACT.test(value.trim().split(/\s+/)[0]??""))return workflowDeny("workflow-launch-skill")}return{kind:"allow"}}if(/^mcp__/i.test(toolName)&&PLANNER_TOOL_NAME.test(toolName))return workflowDeny("workflow-planner-tool");let args=Object.values(input).filter(v=>typeof v=="string").join(" ");return WORKFLOW_SLASH_LAUNCH.test(args)?workflowDeny("workflow-entry"):void 0}function evaluateWorkerGuardPayload(payload,ctx){if(ctx.correctionContextInvalid)return workflowDeny("correction-context-invalid");let root=asRecord(payload);if(root===void 0)return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let input=asRecord(root.tool_input??root.toolInput);if(ctx.correctionEnforced){if(input===void 0)return workflowDeny("correction-payload-invalid");let decision=evaluateCorrectionToolRole(toolName,input);if(decision!==void 0)return decision}if(toolName!=="Bash")return{kind:"allow"};if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{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){let unreadable=()=>ctx.correctionContextInvalid?workflowDeny("correction-context-invalid"):ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};if(typeof raw!="string"||raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES||raw.trim().length===0)return unreadable();let parsed;try{parsed=JSON.parse(raw)}catch{return unreadable()}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return unreadable()}}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;typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim());let flagIndex=argv.indexOf("--correction-intent-version"),flagValue=flagIndex>=0?argv[flagIndex+1]:void 0,envValue=env.BAPI_CORRECTION_INTENT_VERSION;return ctx.correctionEnforced=flagValue==="1"||envValue==="1",ctx.correctionContextInvalid=flagIndex>=0&&flagValue!=="1"||envValue!==void 0&&envValue!=="1"||flagIndex>=0&&envValue!==void 0&&envValue!==flagValue,ctx}function runWorkerGuardHook(io){let exitCode=0,correctionRequested=io.argv.includes("--correction-intent-version")||io.env.BAPI_CORRECTION_INTENT_VERSION!==void 0;try{let raw=io.readStdin(),ctx=resolveWorkerGuardContext(io.env,io.cwd,io.argv),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(io.writeStderr(renderWorkerGuardReason(decision.category,decision.family)),exitCode=2):decision.kind==="malformed"&&(io.writeStderr(WORKER_GUARD_MALFORMED_LINE),exitCode=0)}catch{try{io.writeStderr(correctionRequested?renderWorkerGuardReason("correction-payload-invalid","workflow"):WORKER_GUARD_MALFORMED_LINE)}catch{}exitCode=correctionRequested?2:0}return exitCode}function readStdinSync(){return readFileSync(0,"utf-8")}process.exit(runWorkerGuardHook({readStdin:readStdinSync,env:process.env,cwd:process.cwd(),argv:process.argv.slice(2),writeStderr:text=>{process.stderr.write(`${text}
|
|
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,form){if(category==="git-ambiguous")return`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). This shell form cannot be safely resolved: substitution, redirection, or stdin-fed arguments hide what git will run. Use literal arguments instead; for a commit message, write it to a file and run git commit -F <file>.`;if(form==="shell_form"){let pattern=category==="git-push"?"If the destination is your own branch and no force is used, run git push origin HEAD:<your work branch> alone.":category==="git-reset-hard"?"If resetting your own branch, run git reset --hard origin/<your work branch> alone.":category==="filesystem-clean"?"If all pathspecs are inside your worktree, run git clean with literal worktree-contained pathspecs alone.":category==="filesystem-remove"?"If all paths are inside your worktree, run rm with literal worktree-contained paths alone.":"If arguments are read-only, run literal read-only arguments alone, without redirects or $VAR.";return`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). Shell form cannot be checked: redirects, pipes, substitution, or stdin-fed arguments. `+pattern}let restriction={"git-push":"Push only to your own branch, without force. Use HEAD:<your work branch> as the destination.","git-reset-hard":"Hard reset only against your own branch ref.","filesystem-clean":"Clean only literal paths inside your worktree.","filesystem-remove":"Remove only literal paths inside your worktree.","database-destructive":"Destructive SQL is prohibited. Use read-only database arguments.","network-mutating":"Mutating requests are prohibited. Use read-only network arguments."};return restriction[category]?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). ${restriction[category]}`:category==="workflow-entry"||category==="workflow-launch-skill"||category==="workflow-launch-shell"||category==="workflow-planner-tool"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). A correction worker must not start a full workflow. Reading or editing files that mention a workflow is allowed; make the focused correction directly.`:category==="correction-context-invalid"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). The correction context for this worker is not valid, so no tool call can be checked. Stop and report the launch problem.`:category==="correction-payload-invalid"?`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). The tool call could not be read as a tool name with a tool input, so it cannot be checked. Retry with a well-formed call.`:`${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}function diagnosticProjections(command){if(command.length>WORKER_GUARD_MAX_PAYLOAD_BYTES)return[];let result=[],start=0,masked="",quote="",uncertain=!1,finish=end=>{let original=tokenizeShellCommand(command.slice(start,end)),projected=tokenizeShellCommand(masked),value=!uncertain&&projected.length===1?{argv:projected[0].argv,complete:!projected[0].unsafe}:void 0;result.push(...original.map((_,i)=>i===0?value:void 0)),masked="",uncertain=!1,start=end+1};for(let i=0;i<command.length;i+=1){let ch=command[i];if(ch==="\\"&"e!=="'"){masked+=command.slice(i,i+2),i+=1;continue}if(quote){masked+=ch,ch===quote&&(quote="");continue}if(ch==="'"||ch==='"'){quote=ch,masked+=ch;continue}if(ch===">"||ch==="<"){masked=masked.replace(/(^|\s)\d+$/,"$1");let j=i+1;for(command[j]===ch&&(j+=1),command[j]==="&"&&(j+=1);/[ \t]/.test(command[j]??"")&&j<command.length;)j+=1;let operandStart=j,operandQuote="";for(;j<command.length;){let c=command[j];if(c==="\\"&&operandQuote!=="'"){j+=2;continue}if(operandQuote){c===operandQuote&&(operandQuote=""),j+=1;continue}if(c==="'"||c==='"'){operandQuote=c,j+=1;continue}if(/[\s;|&<>]/.test(c))break;j+=1}(j===operandStart||operandQuote||command.slice(i,j).startsWith("<<"))&&(uncertain=!0),masked+=" ",i=j-1;continue}if(SEPARATORS.has(ch)||ch===`
|
|
4
|
+
`){finish(i);continue}masked+=ch}return quote&&(uncertain=!0),finish(command.length),result}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 unsafeGitCategory(argv){let{subcommand}=parseGitInvocation(argv,void 0);return subcommand==="push"?"git-push":subcommand==="reset"?"git-reset-hard":subcommand==="clean"?"filesystem-clean":"git-ambiguous"}function diagnosticPushForce(args){return args.some(t=>t==="--force"||t==="-f"||t==="--force-with-lease"||t.startsWith("--force-with-lease=")||t.startsWith("+"))}function diagnosticViolation(family,diagnostic,ctx,cwd){let full=unwrapTransparentPrefixes(diagnostic.argv),unknown=diagnostic.complete?-1:full.findIndex(t=>/[$`]/.test(t)),argv=unknown<0?full:full.slice(0,unknown);if(family==="database")return evaluateDatabase(argv).kind==="deny";if(family==="network")return evaluateNetwork(argv).kind==="deny";let git=parseGitInvocation(argv,cwd);return family==="git"&&git.subcommand==="push"&&diagnosticPushForce(git.args)?!0:diagnostic.complete?family==="rm"?evaluateRemove(argv,ctx,cwd).kind==="deny":git.unsafe?!0:git.subcommand==="push"?evaluateGitPush(git.args,ctx).kind==="deny":git.subcommand==="reset"?evaluateGitReset(git.args,ctx).kind==="deny":git.subcommand==="clean"?evaluateGitClean(git.args,ctx,git.cwd).kind==="deny":!1:family==="rm"?argv.slice(1).some(t=>!t.startsWith("-"))&&evaluateRemove(argv,ctx,cwd).kind==="deny":git.subcommand==="push"?git.args.filter(t=>!t.startsWith("-")).length>=2&&evaluateGitPush(git.args,ctx).kind==="deny":git.subcommand==="reset"?git.args.some(t=>!t.startsWith("-"))&&evaluateGitReset(git.args,ctx).kind==="deny":git.subcommand==="clean"?evaluateGitClean(git.args,ctx,git.cwd).kind==="deny":!1}function unsafeSegmentDecision(family,argv,ctx,cwd,diagnostic){let category=family==="git"?unsafeGitCategory(argv):family==="rm"?"filesystem-remove":family==="database"?"database-destructive":"network-mutating",decision={kind:"deny",category,family:family==="database"?"psql":family==="network"?"curl":family};return diagnostic&&category!=="git-ambiguous"&&!diagnosticViolation(family,diagnostic,ctx,cwd)?{...decision,form:"shell_form"}:decision}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)}var WORKFLOW_NAMES="implement-ticket|plan-ticket|idea-to-pr|review-and-implement",WORKFLOW_SLASH_LAUNCH=new RegExp(`(?:^|\\s)/(?:bridge-)?(?:${WORKFLOW_NAMES})(?=\\s|$)`,"i"),WORKFLOW_NAME_EXACT=new RegExp(`^/?(?:[A-Za-z0-9_-]+:)?(?:bridge-)?(?:${WORKFLOW_NAMES})$`,"i"),CLAUDE_PACKAGE_TOKEN=/^(?:@anthropic-ai\/)?claude(?:-code)?(?:@\S+)?$/;function claudeLaunchArgs(argv){let base=path.basename(argv[0]??"");if(base==="claude")return argv.slice(1);if(base==="npx"){let index=argv.findIndex((token,i)=>i>0&&CLAUDE_PACKAGE_TOKEN.test(token));return index===-1?void 0:argv.slice(index+1)}}function isClaudeWorkflowPrintLaunch(segment){let args=claudeLaunchArgs(segment.argv);return args===void 0||!args.some(token=>token==="-p"||token==="--print"||token.startsWith("--print="))?!1:segment.unsafe?!0:args.some(token=>{let value=token.startsWith("--print=")?token.slice(8):token;return WORKFLOW_SLASH_LAUNCH.test(value)})}function evaluateSimpleCommand(segment,ctx,payloadCwd,depth=0,stdinFed=!1,diagnostic){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){let projections=diagnostic?.complete?diagnosticProjections(inner):[];for(let[i,nested]of tokenizeShellCommand(inner).entries()){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed,projections[i]);if(decision.kind==="deny")return decision}return{kind:"allow"}}}if(EVAL_BUILTINS.has(path.basename(executable))&&depth<4){let inner=argv.slice(1).join(" "),projections=diagnostic?.complete?diagnosticProjections(inner):[];for(let[i,nested]of tokenizeShellCommand(inner).entries()){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed,projections[i]);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,diagnostic?{argv:stripStdinPrefixOptions(unwrapTransparentPrefixes(diagnostic.argv)),complete:!1}:void 0)}if(segment={argv,unsafe:segment.unsafe},ctx.correctionEnforced&&isClaudeWorkflowPrintLaunch(segment))return{kind:"deny",category:"workflow-launch-shell",family:"workflow"};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family,segment.argv,ctx,resolveEffectiveCwd(ctx,payloadCwd),diagnostic);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?unsafeSegmentDecision("git",segment.argv,ctx,cwd,diagnostic):evaluateGitPush(git.args,ctx):git.subcommand==="reset"?git.unsafe?unsafeSegmentDecision("git",segment.argv,ctx,cwd,diagnostic):evaluateGitReset(git.args,ctx):git.subcommand==="clean"?git.unsafe?unsafeSegmentDecision("git",segment.argv,ctx,cwd,diagnostic):evaluateGitClean(git.args,ctx,git.cwd):{kind:"allow"}}function workflowDeny(category){return{kind:"deny",category,family:"workflow"}}var FILE_TOOLS=new Set(["Read","Edit","Write","MultiEdit","NotebookEdit","Grep","Glob"]),INVOCATION_FIELDS={Skill:["skill","args"],Task:["prompt","description"],Agent:["prompt","description"],SlashCommand:["command"]},PLANNER_TOOL_NAME=/(?:request_plan_generation|get_pipeline_recipe|plan_ticket|implement_ticket)/i;function evaluateCorrectionToolRole(toolName,input){if(FILE_TOOLS.has(toolName))return{kind:"allow"};if(toolName==="Bash")return;let fields=INVOCATION_FIELDS[toolName];if(fields!==void 0){for(let field of fields){let value=input[field];if(value===void 0)continue;if(typeof value!="string")return workflowDeny("workflow-launch-skill");let nameField=toolName==="Skill"?field==="skill":toolName==="SlashCommand";if(WORKFLOW_SLASH_LAUNCH.test(value)||nameField&&WORKFLOW_NAME_EXACT.test(value.trim().split(/\s+/)[0]??""))return workflowDeny("workflow-launch-skill")}return{kind:"allow"}}if(/^mcp__/i.test(toolName)&&PLANNER_TOOL_NAME.test(toolName))return workflowDeny("workflow-planner-tool");let args=Object.values(input).filter(v=>typeof v=="string").join(" ");return WORKFLOW_SLASH_LAUNCH.test(args)?workflowDeny("workflow-entry"):void 0}function evaluateWorkerGuardPayload(payload,ctx){if(ctx.correctionContextInvalid)return workflowDeny("correction-context-invalid");let root=asRecord(payload);if(root===void 0)return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let input=asRecord(root.tool_input??root.toolInput);if(ctx.correctionEnforced){if(input===void 0)return workflowDeny("correction-payload-invalid");let decision=evaluateCorrectionToolRole(toolName,input);if(decision!==void 0)return decision}if(toolName!=="Bash")return{kind:"allow"};if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};let rawCwd=root.cwd??input.cwd,payloadCwd=typeof rawCwd=="string"?rawCwd:void 0,projections=diagnosticProjections(command);for(let[i,segment]of tokenizeShellCommand(command).entries()){let decision=evaluateSimpleCommand(segment,ctx,payloadCwd,0,!1,projections[i]);if(decision.kind==="deny")return decision}return{kind:"allow"}}function evaluateWorkerGuardInput(raw,ctx){let unreadable=()=>ctx.correctionContextInvalid?workflowDeny("correction-context-invalid"):ctx.correctionEnforced?workflowDeny("correction-payload-invalid"):{kind:"malformed"};if(typeof raw!="string"||raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES||raw.trim().length===0)return unreadable();let parsed;try{parsed=JSON.parse(raw)}catch{return unreadable()}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return unreadable()}}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;typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim());let flagIndex=argv.indexOf("--correction-intent-version"),flagValue=flagIndex>=0?argv[flagIndex+1]:void 0,envValue=env.BAPI_CORRECTION_INTENT_VERSION;return ctx.correctionEnforced=flagValue==="1"||envValue==="1",ctx.correctionContextInvalid=flagIndex>=0&&flagValue!=="1"||envValue!==void 0&&envValue!=="1"||flagIndex>=0&&envValue!==void 0&&envValue!==flagValue,ctx}function runWorkerGuardHook(io){let exitCode=0,correctionRequested=io.argv.includes("--correction-intent-version")||io.env.BAPI_CORRECTION_INTENT_VERSION!==void 0;try{let raw=io.readStdin(),ctx=resolveWorkerGuardContext(io.env,io.cwd,io.argv),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(io.writeStderr(renderWorkerGuardReason(decision.category,decision.family,decision.form)),exitCode=2):decision.kind==="malformed"&&(io.writeStderr(WORKER_GUARD_MALFORMED_LINE),exitCode=0)}catch{try{io.writeStderr(correctionRequested?renderWorkerGuardReason("correction-payload-invalid","workflow"):WORKER_GUARD_MALFORMED_LINE)}catch{}exitCode=correctionRequested?2:0}return exitCode}function readStdinSync(){return readFileSync(0,"utf-8")}process.exit(runWorkerGuardHook({readStdin:readStdinSync,env:process.env,cwd:process.cwd(),argv:process.argv.slice(2),writeStderr:text=>{process.stderr.write(`${text}
|
|
4
5
|
`)}}));
|
|
@@ -20,7 +20,7 @@ export function runWorkerGuardHook(io) {
|
|
|
20
20
|
const ctx = resolveWorkerGuardContext(io.env, io.cwd, io.argv);
|
|
21
21
|
const decision = evaluateWorkerGuardInput(raw, ctx);
|
|
22
22
|
if (decision.kind === "deny") {
|
|
23
|
-
io.writeStderr(renderWorkerGuardReason(decision.category, decision.family));
|
|
23
|
+
io.writeStderr(renderWorkerGuardReason(decision.category, decision.family, decision.form));
|
|
24
24
|
exitCode = 2;
|
|
25
25
|
}
|
|
26
26
|
else if (decision.kind === "malformed") {
|
package/build/worktree-core.js
CHANGED
|
@@ -420,6 +420,13 @@ export async function createWorktreeForTicket(deps, key, branchOverrides, worktr
|
|
|
420
420
|
};
|
|
421
421
|
}
|
|
422
422
|
const worktreePath = extractWorktreePath(result.stdout, deps.cwd, deps.platform);
|
|
423
|
+
if (behavior.postSwitch) {
|
|
424
|
+
const error = await behavior.postSwitch(worktreePath, branch);
|
|
425
|
+
if (error)
|
|
426
|
+
return { key, branch, status: "create-failed", error };
|
|
427
|
+
}
|
|
428
|
+
const resetTarget = async (target) => behavior.preReset
|
|
429
|
+
? behavior.preReset(worktreePath, branch, target) : { ok: true, target };
|
|
423
430
|
// BAPI-1367: a lineage-approved resume stops here. Retained commits and
|
|
424
431
|
// uncommitted files ARE the work, so none of the destructive steps below
|
|
425
432
|
// (reset, clean, base verification) may run.
|
|
@@ -431,7 +438,10 @@ export async function createWorktreeForTicket(deps, key, branchOverrides, worktr
|
|
|
431
438
|
// `origin/<branch>`), so this only applies when the local branch pre-existed.
|
|
432
439
|
// Run inside the switched worktree (the branch is checked out there).
|
|
433
440
|
if (exists && behavior.freshenFromOrigin) {
|
|
434
|
-
const
|
|
441
|
+
const selected = await resetTarget(behavior.freshenFromOrigin);
|
|
442
|
+
if (!selected.ok)
|
|
443
|
+
return { key, branch, status: "create-failed", error: selected.error };
|
|
444
|
+
const resetError = await hardResetWorktree(deps, worktreePath, selected.target);
|
|
435
445
|
if (resetError) {
|
|
436
446
|
return { key, branch, status: "create-failed", error: resetError };
|
|
437
447
|
}
|
|
@@ -447,7 +457,10 @@ export async function createWorktreeForTicket(deps, key, branchOverrides, worktr
|
|
|
447
457
|
// branches) before this destructive reset. Absent branches were created
|
|
448
458
|
// straight from `baseStartPoint`, so no alignment is needed there.
|
|
449
459
|
if (exists && behavior.alignExistingBranchTo) {
|
|
450
|
-
const
|
|
460
|
+
const selected = await resetTarget(behavior.alignExistingBranchTo);
|
|
461
|
+
if (!selected.ok)
|
|
462
|
+
return { key, branch, status: "create-failed", error: selected.error };
|
|
463
|
+
const resetError = await hardResetWorktree(deps, worktreePath, selected.target);
|
|
451
464
|
if (resetError) {
|
|
452
465
|
return { key, branch, status: "create-failed", error: resetError };
|
|
453
466
|
}
|