@bridge_gpt/mcp-server 0.2.65 → 0.2.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +25 -13
  2. package/build/agent-launchers/claude-executor-adapter.js +12 -18
  3. package/build/agent-launchers/executor-adapter.js +27 -0
  4. package/build/bridge-config.js +104 -0
  5. package/build/codex-bridge-profile.js +10 -1
  6. package/build/commands.generated.js +4 -4
  7. package/build/conductor/bridge-api-client.js +230 -10
  8. package/build/conductor/epic-child-continuation.js +61 -34
  9. package/build/conductor/epic-implementer-cli.js +12 -0
  10. package/build/conductor/guided-repair.js +323 -0
  11. package/build/conductor/readiness-cli.js +30 -9
  12. package/build/conductor/readiness.js +23 -1
  13. package/build/conductor/recovery-cli.js +507 -16
  14. package/build/conductor/recovery-operations.js +325 -2
  15. package/build/conductor/repair-contract.js +61 -3
  16. package/build/credential-store.js +5 -4
  17. package/build/diagnostic-text.js +36 -0
  18. package/build/docs.generated.js +1 -1
  19. package/build/doctor.js +70 -10
  20. package/build/drive-epic.js +395 -78
  21. package/build/epic-implementer-bin.js +24 -22
  22. package/build/epic-launch-diagnostic.js +31 -0
  23. package/build/epic-launch-profile.js +44 -14
  24. package/build/executor/completion-supervision.js +5 -0
  25. package/build/executor/correction-dispatch.js +60 -0
  26. package/build/executor/env.js +6 -0
  27. package/build/executor/http-client.js +15 -0
  28. package/build/executor/integration-job.js +6 -1
  29. package/build/executor/job-errors.js +13 -4
  30. package/build/executor/job-log-registry.js +340 -73
  31. package/build/executor/job-runner.js +260 -33
  32. package/build/executor/job-types.js +8 -13
  33. package/build/executor/preflight.js +91 -2
  34. package/build/executor/prompt-spec.js +113 -9
  35. package/build/executor/runner.js +23 -0
  36. package/build/executor/stale-artifacts.js +5 -4
  37. package/build/executor/types.js +1 -0
  38. package/build/executor/watch-cli.js +35 -8
  39. package/build/executor/worker-command.js +43 -43
  40. package/build/executor/worker-finalization.js +80 -15
  41. package/build/executor/worker-guard-hook.js +46 -10
  42. package/build/executor/worker-log.js +137 -70
  43. package/build/executor/worktree-gc.js +30 -6
  44. package/build/index.js +177 -329
  45. package/build/init.js +107 -4
  46. package/build/install-bridge.js +284 -20
  47. package/build/install-doctor.js +172 -30
  48. package/build/install-reexec.js +69 -7
  49. package/build/macos-terminal-permission-guidance.js +16 -0
  50. package/build/model-thresholds-tool.js +185 -0
  51. package/build/pipelines.generated.js +1 -1
  52. package/build/plane/status.js +4 -0
  53. package/build/plane/supervisor.js +4 -0
  54. package/build/plane/worker-exit-code.js +2 -0
  55. package/build/project-root.js +99 -0
  56. package/build/readme.generated.js +1 -1
  57. package/build/review-tickets.js +156 -13
  58. package/build/setup-epic.js +180 -108
  59. package/build/sfcc/setup-status.js +2 -0
  60. package/build/sfcc/tool-wrapper.js +2 -0
  61. package/build/sfcc/tools-configured.js +96 -0
  62. package/build/start-tickets.js +163 -58
  63. package/build/upgrade-cli.js +109 -24
  64. package/build/version.generated.js +3 -3
  65. package/build/windows-npm-entry.js +103 -0
  66. package/build/worker-guard-hook-bin.js +4 -3
  67. package/build/worktree-core.js +6 -5
  68. package/docs/install/mcp-tool-integrations.md +1 -0
  69. package/docs/install/sfcc-integration.md +12 -7
  70. package/package.json +1 -1
  71. package/pipelines/implement-ticket.json +1 -1
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Windows `npx` / `npm` entry resolution (BAPI-1319).
3
+ *
4
+ * WHY THIS EXISTS. Since Node's April 2024 security releases (CVE-2024-27980),
5
+ * `child_process.spawn` throws `EINVAL` when handed a `.cmd` or `.bat` file
6
+ * without the `shell` option, and a bare `"npx"` is `ENOENT` on Windows because
7
+ * the real launcher is `npx.cmd`. `shell: true` is not an option: the install
8
+ * re-exec argv carries user input. So on `win32` this module skips the shim and
9
+ * runs npm's own JavaScript entry point (`npx-cli.js` / `npm-cli.js`) through
10
+ * the running Node executable, with no shell at all.
11
+ *
12
+ * LOOKUP ORDER MIRRORS THE SHELL. The first `npx.cmd` (or `npm.cmd`) on PATH
13
+ * wins, and its sibling `node_modules/npm/bin/<cli>.js` is the entry point. If
14
+ * that first shim has no sibling CLI the result is `unresolved` — a later PATH
15
+ * candidate is never tried, because that is not the npx the user's shell would
16
+ * run, and the test harnesses that put a fake `npx.cmd` first on PATH must still
17
+ * catch the call. Only when no shim is on PATH at all does the resolver fall
18
+ * back to the npm bundled beside `execPath`.
19
+ *
20
+ * On every platform other than `win32` the input is returned unchanged, so
21
+ * macOS and Linux spawn exactly what they spawned before this module existed.
22
+ *
23
+ * The resolver never throws and never carries exception text: every failure is
24
+ * the typed `unresolved` result.
25
+ */
26
+ import { statSync } from "fs";
27
+ import path from "path";
28
+ const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
29
+ const CLI_FILE = {
30
+ npx: "npx-cli.js",
31
+ npm: "npm-cli.js",
32
+ };
33
+ /** Production `fileExists`: a regular file, never a throw. */
34
+ export function defaultNpmEntryFileExists(filePath) {
35
+ try {
36
+ return statSync(filePath).isFile();
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ /** Case-insensitive env read, as Windows environments are. */
43
+ function _readEnvCaseInsensitive(env, name) {
44
+ const target = name.toUpperCase();
45
+ for (const key of Object.keys(env)) {
46
+ if (key.toUpperCase() === target) {
47
+ const value = env[key];
48
+ if (value === undefined)
49
+ continue;
50
+ if (typeof value !== "string")
51
+ throw new TypeError("malformed environment value");
52
+ return value;
53
+ }
54
+ }
55
+ return undefined;
56
+ }
57
+ /** Find the first shim on PATH, or null when none exists. */
58
+ function _findFirstShim(command, env, fileExists) {
59
+ const rawPath = _readEnvCaseInsensitive(env, "PATH") ?? "";
60
+ const rawPathExt = _readEnvCaseInsensitive(env, "PATHEXT");
61
+ const pathExt = rawPathExt && rawPathExt.trim().length > 0 ? rawPathExt : DEFAULT_PATHEXT;
62
+ // Only the `.cmd` shim is the launcher npm installs beside its CLI. PATHEXT is
63
+ // matched case-insensitively and its own spelling is used for the lookup.
64
+ const cmdExt = pathExt
65
+ .split(";")
66
+ .map((ext) => ext.trim())
67
+ .find((ext) => ext.toLowerCase() === ".cmd");
68
+ if (!cmdExt)
69
+ return null;
70
+ for (const segment of rawPath.split(";")) {
71
+ const dir = segment.trim();
72
+ if (dir.length === 0)
73
+ continue;
74
+ const candidate = path.win32.join(dir, `${command}${cmdExt}`);
75
+ if (fileExists(candidate))
76
+ return candidate;
77
+ }
78
+ return null;
79
+ }
80
+ /**
81
+ * Map `npx`/`npm` plus args to a spawnable `{ command, args }`.
82
+ *
83
+ * Non-`win32`: `{ command, args }` unchanged. `win32`: `{ command: execPath,
84
+ * args: [<cliPath>, ...args] }`, or `unresolved`.
85
+ */
86
+ export function resolveNpmEntry(command, args, options) {
87
+ if (options.platform !== "win32") {
88
+ return { status: "resolved", command, args };
89
+ }
90
+ try {
91
+ const cliFile = CLI_FILE[command];
92
+ const shim = _findFirstShim(command, options.env, options.fileExists);
93
+ const cliPath = shim
94
+ ? path.win32.join(path.win32.dirname(shim), "node_modules", "npm", "bin", cliFile)
95
+ : path.win32.join(path.win32.dirname(options.execPath), "node_modules", "npm", "bin", cliFile);
96
+ if (!options.fileExists(cliPath))
97
+ return { status: "unresolved" };
98
+ return { status: "resolved", command: options.execPath, args: [cliPath, ...args] };
99
+ }
100
+ catch {
101
+ return { status: "unresolved" };
102
+ }
103
+ }
@@ -1,6 +1,7 @@
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 category==="git-ambiguous"?`${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>.`:`${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)}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)}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,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 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 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)}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,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 evaluateWorkerGuardPayload(payload,ctx){let root=asRecord(payload);if(ctx.correctionContextInvalid)return{kind:"deny",category:"workflow-entry",family:"workflow"};if(root===void 0)return ctx.correctionEnforced?{kind:"deny",category:"workflow-entry",family:"workflow"}:{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return ctx.correctionEnforced?{kind:"deny",category:"workflow-entry",family:"workflow"}:{kind:"malformed"};let input=asRecord(root.tool_input??root.toolInput);if(ctx.correctionEnforced){if(input===void 0)return{kind:"deny",category:"workflow-entry",family:"workflow"};let args=Object.values(input).filter(v=>typeof v=="string").join(" "),fullEntry=/(?:^|\s|\/)\/?(?:bridge-)?(?:implement-ticket|plan-ticket|idea-to-pr|review-and-implement)(?:\s|$|\.)/i,recipePath=/(?:commands\/src|\.claude\/commands|\.cursor\/commands|pipelines)\/(?:implement-ticket|plan-ticket)(?:\.md|\.json)?/i,plannerTool=/(?:request_plan_generation|get_pipeline_recipe|plan_ticket|implement_ticket)/i;if(recipePath.test(args)||fullEntry.test(args)||plannerTool.test(toolName)&&toolName!=="Bash")return{kind:"deny",category:"workflow-entry",family:"workflow"};if(/^(?:Task|Agent|Skill|SlashCommand|mcp__)/i.test(toolName)&&(fullEntry.test(args)||plannerTool.test(args)))return{kind:"deny",category:"workflow-entry",family:"workflow"}}if(toolName!=="Bash")return{kind:"allow"};if(input===void 0)return ctx.correctionEnforced?{kind:"deny",category:"workflow-entry",family:"workflow"}:{kind:"malformed"};let command=input.command;if(typeof command!="string")return ctx.correctionEnforced?{kind:"deny",category:"workflow-entry",family:"workflow"}:{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.correctionEnforced||ctx.correctionContextInvalid?{kind:"deny",category:"workflow-entry",family:"workflow"}:{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 readStdinSync(){return readFileSync(0,"utf-8")}var exitCode=0,correctionRequested=process.argv.includes("--correction-intent-version")||process.env.BAPI_CORRECTION_INTENT_VERSION!==void 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
- `),exitCode=0)}catch{try{process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
6
- `)}catch{}exitCode=0}process.exit(exitCode);
5
+ `),exitCode=0)}catch{try{process.stderr.write(correctionRequested?`${renderWorkerGuardReason("workflow-entry","workflow")}
6
+ `:`${WORKER_GUARD_MALFORMED_LINE}
7
+ `)}catch{}exitCode=correctionRequested?2:0}process.exit(exitCode);
@@ -111,16 +111,17 @@ function staleLeftoverRemedy(branch, baseRef) {
111
111
  *
112
112
  * Names the `pr_not_attached` gate observation so the executor refusal and the
113
113
  * reconciler's own state are one searchable vocabulary, points at attachment, and
114
- * states plainly that the two remedies above destroy completed work. It must not
114
+ * states plainly that the two remedies above destroy pushed work. It must not
115
115
  * contain the words "delete" or "rebase" at all: an operator scanning a wall of
116
116
  * job output for the next command is exactly who lost work last time.
117
117
  */
118
118
  function unattachedSuccessRemedy(branch) {
119
- return (`it carries the commits of a SUCCEEDED implement whose pull request was never attached ` +
119
+ return (`it carries the pushed commits of an implement whose pull request was never attached ` +
120
120
  `(implement gate observation: pr_not_attached). Refusing to reuse the worktree — but this ` +
121
- `branch is finished work, not a leftover. Open or attach a pull request from '${branch}' and ` +
122
- `let the reconciler bind it. Do NOT remove or re-write the branch: either would destroy a ` +
123
- `completed implementation.`);
121
+ `branch is pushed work, not a leftover; whether its required work is finished or its checks ` +
122
+ `passed is not established by this observation. Open or attach a pull request from ` +
123
+ `'${branch}' and let the reconciler bind it. Do NOT remove or re-write the branch: either ` +
124
+ `would destroy pushed work.`);
124
125
  }
125
126
  export async function isExistingBranchSafeToReuse(deps, branch, baseStartPoint, classification = "unknown") {
126
127
  let baseRef = baseStartPoint;
@@ -85,6 +85,7 @@ These are never gated (`NEVER_GATED_ROUTE_KEYS`) or run entirely locally.
85
85
  | `get_project_standards` | — none |
86
86
  | `invite_member` | — none (mints a scoped key in the Bridge DB; admin-only) |
87
87
  | `config_field` (get/update/list) | — none |
88
+ | `model_thresholds` (get/set/clear) | — none (project and member scopes are admin-only) |
88
89
  | `get_install_manifest` | — none |
89
90
  | `apply_install_manifest` | — none |
90
91
  | `persist_routing_credential` | — none (`LOCAL`) |
@@ -127,29 +127,34 @@ Create a `dw.json` in your project root (auto-added to git exclude — never com
127
127
  ```
128
128
 
129
129
  Accepted key spellings: `hostname`/`host`, `client-id`/`clientId`/`client_id`,
130
- `client-secret`/`clientSecret`/`client_secret`. Prefer a single config — a multi-entry
131
- `configs[]` array forces an explicit `instance` on every call. Alternatively, export
130
+ `client-secret`/`clientSecret`/`client_secret`. Use a single config — a multi-entry
131
+ `configs[]` array is rejected outright, not worked around with an explicit `instance`
132
+ (see the README's SFCC section for why). Alternatively, export
132
133
  `SFCC_HOSTNAME` / `SFCC_CLIENT_ID` / `SFCC_CLIENT_SECRET` in the MCP server environment.
133
134
 
134
135
  ## 3. Set the repo `version` config field
135
136
 
136
137
  Set the repo's `version` config to your SFCC project type — one of
137
138
  `sfra | pwakit | sitegenesis | storefrontnext | hybrid`. The call-time gate reads this;
138
- a non-SFCC value blocks every SFCC tool except `sfcc_setup_status`. Set it via your
139
+ a non-SFCC value blocks the OCAPI tools (the eight reads, the nine writes, and
140
+ `check_permissions`). It does not block `sfcc_setup_status` or `sfcc_log_query` — log
141
+ query has its own backend gate. Set it via your
139
142
  normal config path, the `config_field` MCP tool (operation `update`, field `version`),
140
143
  or the `/teach-bridge` skill.
141
144
 
142
145
  ## 4. Enable the SFCC tools
143
146
 
144
- The two diagnostic tools (`sfcc_setup_status`, `check_permissions`) are always
145
- registered. Everything else is gated, behind **two independent profile groups**:
147
+ No SFCC tool is registered by default — a session on the default `core` profile has
148
+ none. Every SFCC tool sits behind **two independent profile groups**:
146
149
 
147
150
  | Group | Registers |
148
151
  |---|---|
149
- | `sfcc` | the 8 OCAPI read tools + `sfcc_log_query` — read-only |
152
+ | `sfcc` | the two diagnostics (`sfcc_setup_status`, `check_permissions`), the 8 OCAPI read tools, and `sfcc_log_query` — read-only |
150
153
  | `sfcc-write` | the 9 destructive write tools |
151
154
 
152
- Neither implies the other. Add what you need to `BRIDGE_MCP_PROFILE` in the MCP server
155
+ Neither implies the other. An `sfcc-write`-only session has no diagnostics, so it
156
+ cannot run `check_permissions` to print the grant JSON; use `sfcc,sfcc-write` when
157
+ you need writes. Add what you need to `BRIDGE_MCP_PROFILE` in the MCP server
153
158
  `env` block (it is comma-separated), then **restart the MCP client**:
154
159
 
155
160
  ```json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge_gpt/mcp-server",
3
- "version": "0.2.65",
3
+ "version": "0.2.66",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "implement-ticket",
3
- "description": "Generate plan, produce artifacts, checkpoint them to origin, run the plan's remaining verification faithfully, commit, open PR, close post-PR gaps, and monitor CI. Re-entrant: recovery jobs (ci_fix/remediate/rebase) safely re-run this whole recipe from step 1 — the plan-generation step reuses a fresh existing plan (reused: true, no new run) and the produce phase recognizes work already recorded in the durable ledger instead of redoing it.",
3
+ "description": "Initial feature workflow: generate a plan, produce artifacts, checkpoint to origin, verify, commit, open a PR, close post-PR gaps, and monitor CI. Re-entry of this initial workflow may reuse a fresh plan, but remaining-work corrections and continuations use versioned focused intent on the retained branch and never re-enter this recipe.",
4
4
  "variables": ["ticket_key", "docs_dir", "provider"],
5
5
  "steps": [
6
6
  {