@bridge_gpt/mcp-server 0.2.53 → 0.2.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -56
- package/build/agent-guidance.generated.js +3 -0
- package/build/agent-launchers/claude-executor-adapter.js +3 -0
- package/build/agent-launchers/claude.js +3 -3
- package/build/agent-launchers/prompt.js +8 -11
- package/build/agent-notes.js +178 -0
- package/build/agent-registry.js +5 -2
- package/build/agent-utils.js +58 -0
- package/build/agents.generated.js +1 -1
- package/build/base-ref.js +33 -9
- package/build/bounded-wait.js +174 -0
- package/build/codex-skill-adapter.js +55 -0
- package/build/commands.generated.js +6 -5
- package/build/conductor/bridge-api-client.js +235 -14
- package/build/conductor/bring-up-facts.js +187 -0
- package/build/conductor/claude-hook.js +7 -5
- package/build/conductor/cli.js +28 -0
- package/build/conductor/doctor.js +80 -9
- package/build/conductor/epic-implementer-cli.js +1298 -0
- package/build/conductor/epic-runtime.js +134 -98
- package/build/conductor/errors.js +2 -2
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-hooks.js +28 -14
- package/build/conductor/install-doctor.js +11 -5
- package/build/conductor/readiness-cli.js +10 -10
- package/build/conductor/readiness-sections.js +58 -9
- package/build/conductor/readiness.js +120 -4
- package/build/conductor/recovery-cli.js +289 -10
- package/build/conductor/recovery-operations.js +125 -2
- package/build/conductor/repair-contract.js +58 -0
- package/build/conductor/run-branch.js +137 -0
- package/build/conductor/store.js +2 -2
- package/build/conductor/supervisor-runtime.js +1 -1
- package/build/conductor/test-run-branch-vectors.js +165 -0
- package/build/conductor-bin.js +2 -139
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
- package/build/conductor-removed-stub-bin.js +30 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +145 -18
- package/build/drive-epic.js +752 -90
- package/build/epic-implementer-bin.js +145 -0
- package/build/epic-implementer-bundle-cli.js +264 -0
- package/build/epic-implementer-claude-hook-bin.js +3 -0
- package/build/epic-integration-pr.js +5 -3
- package/build/executor/claim-scope.js +104 -0
- package/build/executor/cli.js +14 -25
- package/build/executor/env-file-guard.js +82 -3
- package/build/executor/env.js +6 -0
- package/build/executor/interrupted-worktree.js +60 -0
- package/build/executor/job-errors.js +45 -0
- package/build/executor/job-runner.js +334 -9
- package/build/executor/job-types.js +25 -9
- package/build/executor/merge-tree-classifier.js +171 -0
- package/build/executor/reconcile-overlap-governance.js +129 -0
- package/build/executor/reconcile-overlap-job.js +989 -0
- package/build/executor/reconcile-overlap-types.js +14 -0
- package/build/executor/spawn-job-driver.js +1 -0
- package/build/executor/types.js +2 -0
- package/build/executor/worker-finalization.js +25 -2
- package/build/executor/worker-guard-hook.js +15 -7
- package/build/implement-epic/bridge-client.js +773 -0
- package/build/implement-epic/checkpoint-store.js +542 -0
- package/build/implement-epic/cli.js +3158 -0
- package/build/implement-epic/cut-protocol.js +392 -0
- package/build/implement-epic/lock.js +302 -0
- package/build/implement-epic/pr-state.js +286 -0
- package/build/implement-epic/spawn.js +113 -0
- package/build/index.js +657 -481
- package/build/init.js +72 -8
- package/build/install-bridge-conductor.js +5 -5
- package/build/install-bridge.js +403 -70
- package/build/local-artifact-storage.js +130 -0
- package/build/mcp-host-config.js +22 -60
- package/build/mcp-host-entry-adapter.js +18 -0
- package/build/mcp-host-targets.js +1 -21
- package/build/merge-pull-request.js +1 -1
- package/build/pipelines.generated.js +23 -16
- package/build/plan-epic-conductor-eligibility.js +1 -1
- package/build/plane/cli.js +321 -41
- package/build/plane/manifest.js +209 -1
- package/build/plane/member-roster.js +70 -0
- package/build/plane/preflight.js +128 -12
- package/build/plane/shutdown.js +18 -5
- package/build/plane/status.js +35 -1
- package/build/plane/supervisor.js +546 -164
- package/build/plane/types.js +25 -2
- package/build/polling-policy.js +72 -0
- package/build/readiness-check.js +3 -3
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +219 -0
- package/build/run-unit-tests-launcher.js +6 -1
- package/build/setup-epic.js +567 -38
- package/build/start-tickets-conductor.js +8 -7
- package/build/ticket-key-utils.js +4 -3
- package/build/ticket-review-artifact-gate.js +461 -0
- package/build/upgrade-cli.js +5 -26
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +1 -1
- package/docs/CONDUCTOR.md +8 -6
- package/docs/install/mcp-tool-integrations.md +23 -1
- package/package.json +5 -3
- package/pipelines/review-ticket.json +17 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import{readFileSync}from"node:fs";import path from"node:path";var WORKER_GUARD_REASON_PREFIX="bridge worker guard",WORKER_GUARD_MALFORMED_LINE=`${WORKER_GUARD_REASON_PREFIX}: payload could not be read; continuing fail-open`;function renderWorkerGuardReason(category,family){return`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). This command family is restricted to the worker's own branch, worktree, and read-only network access. Use a different approach and continue.`}var UNSAFE_UNQUOTED=new Set(["$","`",">","<"]),SEPARATORS=new Set([";","|","&"]);function tokenizeShellCommand(command){let commands=[],argv=[],current="",hasCurrent=!1,unsafe=!1,endToken=()=>{hasCurrent&&(argv.push(current),current="",hasCurrent=!1)},endCommand=()=>{endToken(),(argv.length>0||unsafe)&&commands.push({argv,unsafe}),argv=[],unsafe=!1};for(let i=0;i<command.length;i+=1){let ch=command[i];if(ch==="\\"){let next=command[i+1];next!==void 0&&(current+=next,hasCurrent=!0,i+=1);continue}if(ch==="'"){let close=command.indexOf("'",i+1);if(close===-1){unsafe=!0,current+=command.slice(i+1),hasCurrent=!0;break}current+=command.slice(i+1,close),hasCurrent=!0,i=close;continue}if(ch==='"'){let j=i+1,body="",closed=!1;for(;j<command.length;){let c=command[j];if(c==="\\"&&j+1<command.length){body+=command[j+1],j+=2;continue}if(c==='"'){closed=!0;break}(c==="$"||c==="`")&&(unsafe=!0),body+=c,j+=1}closed||(unsafe=!0),current+=body,hasCurrent=!0,i=j;continue}if(UNSAFE_UNQUOTED.has(ch)){unsafe=!0,current+=ch,hasCurrent=!0;continue}if(SEPARATORS.has(ch)){endCommand();continue}if(ch===`
|
|
3
|
-
`){endCommand();continue}if(ch===" "||ch===" "||ch==="\r"){endToken();continue}current+=ch,hasCurrent=!0}return endCommand(),commands}var WORKER_GUARD_MAX_PAYLOAD_BYTES=1e6;function asRecord(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)?value:void 0}function isInside(root,candidate){let rel=path.relative(root,candidate);return rel===""?!0:rel.startsWith("..")?!1:!path.isAbsolute(rel)}function resolveEffectiveCwd(ctx,payloadCwd){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root))return;let normalizedRoot=path.resolve(root);if(payloadCwd===void 0)return normalizedRoot;if(typeof payloadCwd!="string"||payloadCwd.length===0)return;let resolved=path.isAbsolute(payloadCwd)?path.resolve(payloadCwd):path.resolve(normalizedRoot,payloadCwd);return isInside(normalizedRoot,resolved)?resolved:void 0}function allTargetsContained(ctx,cwd,targets){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root)||cwd===void 0)return!1;let normalizedRoot=path.resolve(root);return targets.every(target=>{if(target.length===0||target==="~"||target.startsWith("~/"))return!1;let resolved=path.isAbsolute(target)?path.resolve(target):path.resolve(cwd,target);return isInside(normalizedRoot,resolved)})}function parseGitInvocation(argv,baseCwd){let cwd=baseCwd,unsafe=!1,i=1;for(;i<argv.length;){let token=argv[i];if(token==="-C"){let dir=argv[i+1];dir===void 0||cwd===void 0?(unsafe=!0,cwd=void 0):cwd=path.isAbsolute(dir)?path.resolve(dir):path.resolve(cwd,dir),i+=2;continue}if(token==="-c"||token==="--namespace"||token==="--git-dir"||token==="--work-tree"){unsafe=!0,i+=2;continue}if(token.startsWith("-")){i+=1;continue}break}return{subcommand:argv[i]??"",args:argv.slice(i+1),cwd,unsafe}}function isOwnBranchDestination(destination,workerBranch){let colon=destination.indexOf(":"),dst=colon===-1?destination:destination.slice(colon+1);if(dst.length===0)return!1;let bare=dst.startsWith("+")?dst.slice(1):dst;return bare===workerBranch||bare===`refs/heads/${workerBranch}`}function evaluateGitPush(args,ctx){let deny={kind:"deny",category:"git-push",family:"git"},positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}if(token.startsWith("-")){if(token==="--tags"||token==="--delete"||token==="-d"||token==="--mirror"||token==="--all"||token==="--follow-tags"||token.startsWith("--repo"))return deny;(token==="-o"||token==="--push-option"||token==="--receive-pack"||token==="--exec")&&(i+=1);continue}positional.push(token)}if(positional.length<=1)return{kind:"allow"};let workerBranch=ctx.workerBranch;return typeof workerBranch!="string"||workerBranch.length===0?deny:positional.slice(1).every(d=>isOwnBranchDestination(d,workerBranch))?{kind:"allow"}:deny}function evaluateGitReset(args,ctx){let deny={kind:"deny",category:"git-reset-hard",family:"git"};if(!args.includes("--hard"))return{kind:"allow"};let positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}token.startsWith("-")||positional.push(token)}if(positional.length===0)return{kind:"allow"};let workerBranch=ctx.workerBranch;if(typeof workerBranch!="string"||workerBranch.length===0)return deny;let allowed=new Set([workerBranch,`origin/${workerBranch}`,`refs/heads/${workerBranch}`,`refs/remotes/origin/${workerBranch}`]);return positional.every(ref=>allowed.has(ref))?{kind:"allow"}:deny}function evaluateGitClean(args,ctx,cwd){let deny={kind:"deny",category:"filesystem-clean",family:"git"};if(cwd===void 0||!allTargetsContained(ctx,cwd,[cwd]))return deny;let pathspecs=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){pathspecs.push(...args.slice(i+1));break}if(token==="-e"||token==="--exclude"){i+=1;continue}token.startsWith("-")||pathspecs.push(token)}return pathspecs.length===0?{kind:"allow"}:allTargetsContained(ctx,cwd,pathspecs)?{kind:"allow"}:deny}function isRecursiveForcedRemove(args){let recursive=!1,force=!1;for(let token of args){if(token==="--")break;if(token==="--recursive")recursive=!0;else if(token==="--force")force=!0;else{if(token.startsWith("--"))continue;if(token.startsWith("-")&&token.length>1)for(let flag of token.slice(1))(flag==="r"||flag==="R")&&(recursive=!0),flag==="f"&&(force=!0)}}return recursive&&force}function evaluateRemove(argv,ctx,cwd){let deny={kind:"deny",category:"filesystem-remove",family:"rm"},args=argv.slice(1);if(!isRecursiveForcedRemove(args))return{kind:"allow"};let targets=[],sawDoubleDash=!1;for(let token of args){if(!sawDoubleDash&&token==="--"){sawDoubleDash=!0;continue}!sawDoubleDash&&token.startsWith("-")&&token.length>1||targets.push(token)}return targets.length===0?deny:allTargetsContained(ctx,cwd,targets)?{kind:"allow"}:deny}function isDatabaseCommand(executable){let base=path.basename(executable);return base==="psql"||base.startsWith("pg_")}var DESTRUCTIVE_SQL=/\b(?:DROP|TRUNCATE)\b/i;function evaluateDatabase(argv){let deny={kind:"deny",category:"database-destructive",family:"psql"};return argv.slice(1).some(arg=>DESTRUCTIVE_SQL.test(arg))?deny:{kind:"allow"}}var MUTATING_METHODS=new Set(["POST","PUT","PATCH","DELETE"]);function evaluateNetwork(argv){let deny={kind:"deny",category:"network-mutating",family:"curl"},base=path.basename(argv[0]??""),args=argv.slice(1);for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="-X"||token==="--request"||token==="--method"){let value=args[i+1];if(value===void 0||MUTATING_METHODS.has(value.toUpperCase()))return deny;i+=1;continue}if(token.startsWith("-X")&&token.length>2){if(MUTATING_METHODS.has(token.slice(2).toUpperCase()))return deny;continue}if(token.startsWith("--request=")||token.startsWith("--method=")){let value=token.slice(token.indexOf("=")+1);if(MUTATING_METHODS.has(value.toUpperCase()))return deny;continue}if(token==="--data"||token.startsWith("--data=")||token.startsWith("--data-")||token==="--form"||token==="-F"||token==="--upload-file"||token==="-T"||token==="--post-data"||token.startsWith("--post-data=")||token==="--post-file"||token.startsWith("--post-file=")||token==="--body-data"||token.startsWith("--body-data=")||token==="--body-file"||token.startsWith("--body-file=")||base==="curl"&&token==="-d")return deny}return{kind:"allow"}}function classify(executable){let base=path.basename(executable);return base==="git"?"git":base==="rm"?"rm":isDatabaseCommand(base)?"database":base==="curl"||base==="wget"?"network":null}function unsafeSegmentDecision(family){switch(family){case"git":return{kind:"deny",category:"git-push",family:"git"};case"rm":return{kind:"deny",category:"filesystem-remove",family:"rm"};case"database":return{kind:"deny",category:"database-destructive",family:"psql"};case"network":return{kind:"deny",category:"network-mutating",family:"curl"}}}var TRANSPARENT_PREFIXES=new Set(["sudo","env","command","nohup","time","nice","exec"]),STDIN_ARGUMENT_PREFIXES=new Set(["xargs"]),SHELL_EXECUTABLES=new Set(["sh","bash","zsh","dash","ksh"]),EVAL_BUILTINS=new Set(["eval"]);function unwrapTransparentPrefixes(argv){let rest=argv;for(let depth=0;depth<8;depth+=1){let head=rest[0];if(head===void 0||!TRANSPARENT_PREFIXES.has(path.basename(head)))return rest;let i=1;for(;i<rest.length;){let token=rest[i];if(path.basename(head)==="env"&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)){i+=1;continue}if(token.startsWith("-")){i+=1;continue}break}rest=rest.slice(i)}return rest}function stripStdinPrefixOptions(argv){let VALUE_OPTS=new Set(["-I","-i","-L","-n","-P","-s","-d","-E","-e","-a","--replace","--max-lines","--max-args","--max-procs","--max-chars","--delimiter","--eof","--arg-file"]),i=1;for(;i<argv.length;){let token=argv[i];if(token==="--"){i+=1;break}if(!token.startsWith("-"))break;if(token.includes("=")){i+=1;continue}i+=VALUE_OPTS.has(token)?2:1}return argv.slice(i)}function evaluateSimpleCommand(segment,ctx,payloadCwd,depth=0,stdinFed=!1){let argv=unwrapTransparentPrefixes(segment.argv),executable=argv[0];if(executable===void 0)return{kind:"allow"};if(SHELL_EXECUTABLES.has(path.basename(executable))&&depth<4){let cIndex=argv.findIndex(t=>t==="-c"),inner=cIndex===-1?void 0:argv[cIndex+1];if(inner!==void 0){for(let nested of tokenizeShellCommand(inner)){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed);if(decision.kind==="deny")return decision}return{kind:"allow"}}}if(EVAL_BUILTINS.has(path.basename(executable))&&depth<4){for(let nested of tokenizeShellCommand(argv.slice(1).join(" "))){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed);if(decision.kind==="deny")return decision}return{kind:"allow"}}if(STDIN_ARGUMENT_PREFIXES.has(path.basename(executable))&&depth<4){let rest=stripStdinPrefixOptions(argv);return rest.length===0?{kind:"allow"}:evaluateSimpleCommand({argv:rest,unsafe:segment.unsafe},ctx,payloadCwd,depth+1,!0)}segment={argv,unsafe:segment.unsafe};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family);let cwd=resolveEffectiveCwd(ctx,payloadCwd);if(family==="rm")return evaluateRemove(segment.argv,ctx,cwd);if(family==="database")return evaluateDatabase(segment.argv);if(family==="network")return evaluateNetwork(segment.argv);let git=parseGitInvocation(segment.argv,cwd);return git.subcommand==="push"?git.unsafe?{kind:"deny",category:"git-push",family:"git"}:evaluateGitPush(git.args,ctx):git.subcommand==="reset"?git.unsafe?{kind:"deny",category:"git-reset-hard",family:"git"}:evaluateGitReset(git.args,ctx):git.subcommand==="clean"?git.unsafe?{kind:"deny",category:"filesystem-clean",family:"git"}:evaluateGitClean(git.args,ctx,git.cwd):{kind:"allow"}}function evaluateWorkerGuardPayload(payload,ctx){let root=asRecord(payload);if(root===void 0)return{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return{kind:"malformed"};if(toolName!=="Bash")return{kind:"allow"};let input=asRecord(root.tool_input??root.toolInput);if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return{kind:"malformed"};let rawCwd=root.cwd??input.cwd,payloadCwd=typeof rawCwd=="string"?rawCwd:void 0;for(let segment of tokenizeShellCommand(command)){let decision=evaluateSimpleCommand(segment,ctx,payloadCwd);if(decision.kind==="deny")return decision}return{kind:"allow"}}function evaluateWorkerGuardInput(raw,ctx){if(typeof raw!="string")return{kind:"malformed"};if(raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES)return{kind:"malformed"};if(raw.trim().length===0)return{kind:"malformed"};let parsed;try{parsed=JSON.parse(raw)}catch{return{kind:"malformed"}}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return{kind:"malformed"}}}var WORKER_GUARD_ROOT_FLAG="--worktree-root";function resolveWorkerGuardContext(env,cwd,argv=[]){let ctx={},root;for(let i=0;i<argv.length;i+=1){if(argv[i]===WORKER_GUARD_ROOT_FLAG){root=argv[i+1];break}if(argv[i].startsWith(`${WORKER_GUARD_ROOT_FLAG}=`)){root=argv[i].slice(WORKER_GUARD_ROOT_FLAG.length+1);break}}root===void 0&&typeof cwd=="string"&&(root=cwd),typeof root=="string"&&path.isAbsolute(root)&&(ctx.worktreePath=path.resolve(root));let branch=env.BAPI_WORKER_BRANCH;return typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim()),ctx}function readStdinSync(){return readFileSync(0,"utf-8")}var exitCode=0;try{let raw=readStdinSync(),ctx=resolveWorkerGuardContext(process.env,process.cwd(),process.argv.slice(2)),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(process.stderr.write(`${renderWorkerGuardReason(decision.category,decision.family)}
|
|
3
|
+
`){endCommand();continue}if(ch===" "||ch===" "||ch==="\r"){endToken();continue}current+=ch,hasCurrent=!0}return endCommand(),commands}var WORKER_GUARD_MAX_PAYLOAD_BYTES=1e6;function asRecord(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)?value:void 0}function isInside(root,candidate){let rel=path.relative(root,candidate);return rel===""?!0:rel.startsWith("..")?!1:!path.isAbsolute(rel)}function resolveEffectiveCwd(ctx,payloadCwd){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root))return;let normalizedRoot=path.resolve(root);if(payloadCwd===void 0)return normalizedRoot;if(typeof payloadCwd!="string"||payloadCwd.length===0)return;let resolved=path.isAbsolute(payloadCwd)?path.resolve(payloadCwd):path.resolve(normalizedRoot,payloadCwd);return isInside(normalizedRoot,resolved)?resolved:void 0}function allTargetsContained(ctx,cwd,targets){let root=ctx.worktreePath;if(typeof root!="string"||!path.isAbsolute(root)||cwd===void 0)return!1;let normalizedRoot=path.resolve(root);return targets.every(target=>{if(target.length===0||target==="~"||target.startsWith("~/"))return!1;let resolved=path.isAbsolute(target)?path.resolve(target):path.resolve(cwd,target);return isInside(normalizedRoot,resolved)})}function parseGitInvocation(argv,baseCwd){let cwd=baseCwd,unsafe=!1,i=1;for(;i<argv.length;){let token=argv[i];if(token==="-C"){let dir=argv[i+1];dir===void 0||cwd===void 0?(unsafe=!0,cwd=void 0):cwd=path.isAbsolute(dir)?path.resolve(dir):path.resolve(cwd,dir),i+=2;continue}if(token==="-c"||token==="--namespace"||token==="--git-dir"||token==="--work-tree"){unsafe=!0,i+=2;continue}if(token.startsWith("-")){i+=1;continue}break}return{subcommand:argv[i]??"",args:argv.slice(i+1),cwd,unsafe}}function isOwnBranchDestination(destination,workerBranch){let colon=destination.indexOf(":"),dst=colon===-1?destination:destination.slice(colon+1);if(dst.length===0)return!1;let bare=dst.startsWith("+")?dst.slice(1):dst;return bare===workerBranch||bare===`refs/heads/${workerBranch}`}function evaluateGitPush(args,ctx){let deny={kind:"deny",category:"git-push",family:"git"},positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}if(token.startsWith("-")){if(token==="--tags"||token==="--delete"||token==="-d"||token==="--mirror"||token==="--all"||token==="--follow-tags"||token.startsWith("--repo"))return deny;(token==="-o"||token==="--push-option"||token==="--receive-pack"||token==="--exec")&&(i+=1);continue}positional.push(token)}let workerBranch=ctx.workerBranch;return typeof workerBranch!="string"||workerBranch.length===0?deny:positional.length<=1?{kind:"allow"}:positional.slice(1).every(d=>isOwnBranchDestination(d,workerBranch))?{kind:"allow"}:deny}function evaluateGitReset(args,ctx){let deny={kind:"deny",category:"git-reset-hard",family:"git"};if(!args.includes("--hard"))return{kind:"allow"};let positional=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){positional.push(...args.slice(i+1));break}token.startsWith("-")||positional.push(token)}if(positional.length===0)return{kind:"allow"};let workerBranch=ctx.workerBranch;if(typeof workerBranch!="string"||workerBranch.length===0)return deny;let allowed=new Set([workerBranch,`origin/${workerBranch}`,`refs/heads/${workerBranch}`,`refs/remotes/origin/${workerBranch}`]);return positional.every(ref=>allowed.has(ref))?{kind:"allow"}:deny}function evaluateGitClean(args,ctx,cwd){let deny={kind:"deny",category:"filesystem-clean",family:"git"};if(cwd===void 0||!allTargetsContained(ctx,cwd,[cwd]))return deny;let pathspecs=[];for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="--"){pathspecs.push(...args.slice(i+1));break}if(token==="-e"||token==="--exclude"){i+=1;continue}token.startsWith("-")||pathspecs.push(token)}return pathspecs.length===0?{kind:"allow"}:allTargetsContained(ctx,cwd,pathspecs)?{kind:"allow"}:deny}function isRecursiveForcedRemove(args){let recursive=!1,force=!1;for(let token of args){if(token==="--")break;if(token==="--recursive")recursive=!0;else if(token==="--force")force=!0;else{if(token.startsWith("--"))continue;if(token.startsWith("-")&&token.length>1)for(let flag of token.slice(1))(flag==="r"||flag==="R")&&(recursive=!0),flag==="f"&&(force=!0)}}return recursive&&force}function evaluateRemove(argv,ctx,cwd){let deny={kind:"deny",category:"filesystem-remove",family:"rm"},args=argv.slice(1);if(!isRecursiveForcedRemove(args))return{kind:"allow"};let targets=[],sawDoubleDash=!1;for(let token of args){if(!sawDoubleDash&&token==="--"){sawDoubleDash=!0;continue}!sawDoubleDash&&token.startsWith("-")&&token.length>1||targets.push(token)}return targets.length===0?deny:allTargetsContained(ctx,cwd,targets)?{kind:"allow"}:deny}function isDatabaseCommand(executable){let base=path.basename(executable);return base==="psql"||base.startsWith("pg_")}var DESTRUCTIVE_SQL=/\b(?:DROP|TRUNCATE)\b/i;function evaluateDatabase(argv){let deny={kind:"deny",category:"database-destructive",family:"psql"};return argv.slice(1).some(arg=>DESTRUCTIVE_SQL.test(arg))?deny:{kind:"allow"}}var MUTATING_METHODS=new Set(["POST","PUT","PATCH","DELETE"]);function evaluateNetwork(argv){let deny={kind:"deny",category:"network-mutating",family:"curl"},base=path.basename(argv[0]??""),args=argv.slice(1);for(let i=0;i<args.length;i+=1){let token=args[i];if(token==="-X"||token==="--request"||token==="--method"){let value=args[i+1];if(value===void 0||MUTATING_METHODS.has(value.toUpperCase()))return deny;i+=1;continue}if(token.startsWith("-X")&&token.length>2){if(MUTATING_METHODS.has(token.slice(2).toUpperCase()))return deny;continue}if(token.startsWith("--request=")||token.startsWith("--method=")){let value=token.slice(token.indexOf("=")+1);if(MUTATING_METHODS.has(value.toUpperCase()))return deny;continue}if(token==="--data"||token.startsWith("--data=")||token.startsWith("--data-")||token==="--form"||token==="-F"||token==="--upload-file"||token==="-T"||token==="--post-data"||token.startsWith("--post-data=")||token==="--post-file"||token.startsWith("--post-file=")||token==="--body-data"||token.startsWith("--body-data=")||token==="--body-file"||token.startsWith("--body-file=")||base==="curl"&&token==="-d")return deny}return{kind:"allow"}}function classify(executable){let base=path.basename(executable);return base==="git"?"git":base==="rm"?"rm":isDatabaseCommand(base)?"database":base==="curl"||base==="wget"?"network":null}function unsafeSegmentDecision(family){switch(family){case"git":return{kind:"deny",category:"git-push",family:"git"};case"rm":return{kind:"deny",category:"filesystem-remove",family:"rm"};case"database":return{kind:"deny",category:"database-destructive",family:"psql"};case"network":return{kind:"deny",category:"network-mutating",family:"curl"}}}var TRANSPARENT_PREFIXES=new Set(["sudo","env","command","nohup","time","nice","exec"]),STDIN_ARGUMENT_PREFIXES=new Set(["xargs"]),SHELL_EXECUTABLES=new Set(["sh","bash","zsh","dash","ksh"]),EVAL_BUILTINS=new Set(["eval"]);function unwrapTransparentPrefixes(argv){let rest=argv;for(let depth=0;depth<8;depth+=1){let head=rest[0];if(head===void 0||!TRANSPARENT_PREFIXES.has(path.basename(head)))return rest;let i=1;for(;i<rest.length;){let token=rest[i];if(path.basename(head)==="env"&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)){i+=1;continue}if(token.startsWith("-")){i+=1;continue}break}rest=rest.slice(i)}return rest}function stripStdinPrefixOptions(argv){let VALUE_OPTS=new Set(["-I","-i","-L","-n","-P","-s","-d","-E","-e","-a","--replace","--max-lines","--max-args","--max-procs","--max-chars","--delimiter","--eof","--arg-file"]),i=1;for(;i<argv.length;){let token=argv[i];if(token==="--"){i+=1;break}if(!token.startsWith("-"))break;if(token.includes("=")){i+=1;continue}i+=VALUE_OPTS.has(token)?2:1}return argv.slice(i)}function evaluateSimpleCommand(segment,ctx,payloadCwd,depth=0,stdinFed=!1){let argv=unwrapTransparentPrefixes(segment.argv),executable=argv[0];if(executable===void 0)return{kind:"allow"};if(SHELL_EXECUTABLES.has(path.basename(executable))&&depth<4){let cIndex=argv.findIndex(t=>t==="-c"),inner=cIndex===-1?void 0:argv[cIndex+1];if(inner!==void 0){for(let nested of tokenizeShellCommand(inner)){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed);if(decision.kind==="deny")return decision}return{kind:"allow"}}}if(EVAL_BUILTINS.has(path.basename(executable))&&depth<4){for(let nested of tokenizeShellCommand(argv.slice(1).join(" "))){let decision=evaluateSimpleCommand(nested,ctx,payloadCwd,depth+1,stdinFed);if(decision.kind==="deny")return decision}return{kind:"allow"}}if(STDIN_ARGUMENT_PREFIXES.has(path.basename(executable))&&depth<4){let rest=stripStdinPrefixOptions(argv);return rest.length===0?{kind:"allow"}:evaluateSimpleCommand({argv:rest,unsafe:segment.unsafe},ctx,payloadCwd,depth+1,!0)}segment={argv,unsafe:segment.unsafe};let family=classify(executable);if(family===null)return{kind:"allow"};if(stdinFed||segment.unsafe)return unsafeSegmentDecision(family);let cwd=resolveEffectiveCwd(ctx,payloadCwd);if(family==="rm")return evaluateRemove(segment.argv,ctx,cwd);if(family==="database")return evaluateDatabase(segment.argv);if(family==="network")return evaluateNetwork(segment.argv);let git=parseGitInvocation(segment.argv,cwd);return git.subcommand==="push"?git.unsafe?{kind:"deny",category:"git-push",family:"git"}:evaluateGitPush(git.args,ctx):git.subcommand==="reset"?git.unsafe?{kind:"deny",category:"git-reset-hard",family:"git"}:evaluateGitReset(git.args,ctx):git.subcommand==="clean"?git.unsafe?{kind:"deny",category:"filesystem-clean",family:"git"}:evaluateGitClean(git.args,ctx,git.cwd):{kind:"allow"}}function evaluateWorkerGuardPayload(payload,ctx){let root=asRecord(payload);if(root===void 0)return{kind:"malformed"};let toolName=root.tool_name??root.toolName;if(typeof toolName!="string")return{kind:"malformed"};if(toolName!=="Bash")return{kind:"allow"};let input=asRecord(root.tool_input??root.toolInput);if(input===void 0)return{kind:"malformed"};let command=input.command;if(typeof command!="string")return{kind:"malformed"};let rawCwd=root.cwd??input.cwd,payloadCwd=typeof rawCwd=="string"?rawCwd:void 0;for(let segment of tokenizeShellCommand(command)){let decision=evaluateSimpleCommand(segment,ctx,payloadCwd);if(decision.kind==="deny")return decision}return{kind:"allow"}}function evaluateWorkerGuardInput(raw,ctx){if(typeof raw!="string")return{kind:"malformed"};if(raw.length>WORKER_GUARD_MAX_PAYLOAD_BYTES)return{kind:"malformed"};if(raw.trim().length===0)return{kind:"malformed"};let parsed;try{parsed=JSON.parse(raw)}catch{return{kind:"malformed"}}try{return evaluateWorkerGuardPayload(parsed,ctx)}catch{return{kind:"malformed"}}}var WORKER_GUARD_ROOT_FLAG="--worktree-root";function resolveWorkerGuardContext(env,cwd,argv=[]){let ctx={},root;for(let i=0;i<argv.length;i+=1){if(argv[i]===WORKER_GUARD_ROOT_FLAG){root=argv[i+1];break}if(argv[i].startsWith(`${WORKER_GUARD_ROOT_FLAG}=`)){root=argv[i].slice(WORKER_GUARD_ROOT_FLAG.length+1);break}}root===void 0&&typeof cwd=="string"&&(root=cwd),typeof root=="string"&&path.isAbsolute(root)&&(ctx.worktreePath=path.resolve(root));let branch=env.BAPI_WORKER_BRANCH;return typeof branch=="string"&&branch.trim().length>0&&(ctx.workerBranch=branch.trim()),ctx}function readStdinSync(){return readFileSync(0,"utf-8")}var exitCode=0;try{let raw=readStdinSync(),ctx=resolveWorkerGuardContext(process.env,process.cwd(),process.argv.slice(2)),decision=evaluateWorkerGuardInput(raw,ctx);decision.kind==="deny"?(process.stderr.write(`${renderWorkerGuardReason(decision.category,decision.family)}
|
|
4
4
|
`),exitCode=2):decision.kind==="malformed"&&(process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
|
|
5
5
|
`),exitCode=0)}catch{try{process.stderr.write(`${WORKER_GUARD_MALFORMED_LINE}
|
|
6
6
|
`)}catch{}exitCode=0}process.exit(exitCode);
|
package/docs/CONDUCTOR.md
CHANGED
|
@@ -12,11 +12,13 @@ config. For the everyday `start-tickets` flags and cross-platform behavior, see
|
|
|
12
12
|
|
|
13
13
|
## Epic Conductor v2 — how an epic is actually driven
|
|
14
14
|
|
|
15
|
-
**The v1 `
|
|
15
|
+
**The v1 `epic-implementer epic-tick` command is frozen.** It throws
|
|
16
16
|
`EPIC_TICK_V1_FROZEN` on every invocation and advances nothing. There is nothing
|
|
17
17
|
to schedule locally. If you have an epic-tick schedule registered from an earlier
|
|
18
18
|
release, remove it — it is a dead timer.
|
|
19
|
-
`
|
|
19
|
+
`epic-implementer doctor` flags one if it finds it. (S4/BAPI-1080 renamed the
|
|
20
|
+
event-ledger CLI from `conductor` to `epic-implementer`; the retained
|
|
21
|
+
`conductor` bin name is now a fixed migration pointer.)
|
|
20
22
|
|
|
21
23
|
v2 splits the old local tick into two halves:
|
|
22
24
|
|
|
@@ -227,7 +229,7 @@ existing hooks) so the spawned session streams local `run.started` / `run.stoppe
|
|
|
227
229
|
environment scoped to that one terminal/tab/session — no credentials are ever placed
|
|
228
230
|
in the env, hook command, or run metadata. Override the gate/supervisor labels with
|
|
229
231
|
`BAPI_CONDUCTOR_GATE_NAME` / `BAPI_CONDUCTOR_SUPERVISOR_MODE`. Inspect the stream
|
|
230
|
-
with `
|
|
232
|
+
with `epic-implementer doctor`. Observability is best-effort: a conductor failure never
|
|
231
233
|
blocks or aborts a spawn, and `--dry-run` performs no conductor side effects.
|
|
232
234
|
(Epic dispatch always enables conductor internally, independent of this flag.)
|
|
233
235
|
|
|
@@ -236,13 +238,13 @@ When `--conductor` is set, the spawn boundary also injects
|
|
|
236
238
|
supervisor MCP tools (a plain `start-tickets` run stays on the default `core`
|
|
237
239
|
profile). See [README → Environment Variables](../README.md#environment-variables).
|
|
238
240
|
|
|
239
|
-
## `
|
|
241
|
+
## `epic-implementer install-git-hooks` (BAPI-395)
|
|
240
242
|
|
|
241
243
|
Installs local git hooks that opportunistically emit conductor git/PR/CI events into
|
|
242
244
|
the local ledger:
|
|
243
245
|
|
|
244
246
|
```
|
|
245
|
-
|
|
247
|
+
epic-implementer install-git-hooks [--json]
|
|
246
248
|
```
|
|
247
249
|
|
|
248
250
|
The installed hooks are **local, unversioned, opportunistic, and bypassable**: they
|
|
@@ -256,7 +258,7 @@ never a fatal error. The hooks installed are `post-commit` (emits
|
|
|
256
258
|
`git.commit_created`) and `reference-transaction` (emits `worktree.changed` for
|
|
257
259
|
committed ref updates).
|
|
258
260
|
|
|
259
|
-
Missing hooks do **not** prevent PR/CI gate evaluation — `
|
|
261
|
+
Missing hooks do **not** prevent PR/CI gate evaluation — `epic-implementer doctor` reads
|
|
260
262
|
hook presence and managed-snippet status **read-only** (a new `git hooks` section /
|
|
261
263
|
`git_hooks` JSON object alongside the ledger report), and the reconciler drives CI
|
|
262
264
|
polling and gate evaluation regardless of whether hooks are installed.
|
|
@@ -173,8 +173,30 @@ by tier.
|
|
|
173
173
|
| `merge_pull_request` | VCS **[BLOCK]** (Tier-1; protected server-decided merge) + local `gh` login **[BLOCK]** (the merge executes here — see below) |
|
|
174
174
|
| `resolve_ci_checks` | VCS **[BLOCK]** (Tier-1) |
|
|
175
175
|
| `poll_ci_checks` | VCS **[BLOCK]** (Tier-1); also **hidden until `ci_check_config` is resolved** (needs a prior `resolve_ci_checks`) |
|
|
176
|
+
| `wait_for_ci_checks` | VCS **[BLOCK]** (Tier-1); also **hidden until `ci_check_config` is resolved** (needs a prior `resolve_ci_checks`) — it reads the same envelope as `poll_ci_checks` and shares its visibility gate exactly |
|
|
176
177
|
| `fresh_base` (`action: "materialize"`) | VCS / git base ref **[BLOCK]** (Tier-1; materializes a pinned base tree) |
|
|
177
178
|
|
|
179
|
+
#### The bounded wait tools — what `pending` and `ready` mean
|
|
180
|
+
|
|
181
|
+
`wait_for_ci_checks` and `wait_for_ticket_review` (BAPI-1104) exist so a worker can
|
|
182
|
+
wait without a foreground `sleep` and without binding a long server-side operation to
|
|
183
|
+
the 900-second MCP client deadline. Three properties matter to a caller:
|
|
184
|
+
|
|
185
|
+
- **`state: "pending"` is a normal, resumable result, not an error.** Each call is one
|
|
186
|
+
bounded window of at most 240 seconds. Waiting longer means calling again — never
|
|
187
|
+
sleeping, never polling in an unbounded loop, and never re-issuing the original
|
|
188
|
+
generation request.
|
|
189
|
+
- **Terminal is not the same as passing.** `wait_for_ci_checks` reports only that every
|
|
190
|
+
check has *finished*, along with each check's `required`, `status` and `conclusion`.
|
|
191
|
+
It never consults `all_passed` and never decides the gate; the recipe does.
|
|
192
|
+
- **Unavailable CI is actionable, not a wait.** When `ci_check_config` is unresolved the
|
|
193
|
+
tool returns `state: "unavailable"` immediately rather than burning the window. The
|
|
194
|
+
remedy is `resolve_ci_checks`, which also re-enables both CI tools together.
|
|
195
|
+
|
|
196
|
+
`wait_for_ticket_review` is read-only in the strict sense: it never starts a review,
|
|
197
|
+
takes no claim token, and writes no state. It requires *both* review documents to be
|
|
198
|
+
retrievable and newer than the submission before reporting ready.
|
|
199
|
+
|
|
178
200
|
#### `merge_pull_request` — what the caller can observe
|
|
179
201
|
|
|
180
202
|
The tool holds no VCS credential and makes no merge decision of its own; it asks
|
|
@@ -302,7 +324,7 @@ wanting both selects `sfcc,sfcc-write` (or `full`).
|
|
|
302
324
|
|---|---|---|
|
|
303
325
|
| **Ticket backend** (jira mode) | `create_ticket`, `get_ticket(s)`, `update_ticket_description`, `update_jira_status`, and every AI generator (they read the ticket) | — |
|
|
304
326
|
| **Jira (only)** | `get_comments`, `add_comment`, `attachment`, `estimate_epic` | — |
|
|
305
|
-
| **Version control (VCS)** | `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`, `fresh_base`, `parse_repository`, `regenerate_directory_map`; **Tier-3** `request_plan_generation`/`get_plan`, `request_reimplement_context`/`get_reimplement_context`, `create_doc`(tdd/architecture) | **Tier-4** `request_ticket_review`, `create_doc`(prd/fsd), `clarify_task` |
|
|
327
|
+
| **Version control (VCS)** | `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`, `wait_for_ci_checks`, `fresh_base`, `parse_repository`, `regenerate_directory_map`; **Tier-3** `request_plan_generation`/`get_plan`, `request_reimplement_context`/`get_reimplement_context`, `create_doc`(tdd/architecture) | **Tier-4** `request_ticket_review`, `create_doc`(prd/fsd), `clarify_task` |
|
|
306
328
|
| **Code index** (succeeded parse) | Tier-3 plan/reimplement/`create_doc`(tdd/architecture); `request_council` in `technical`/`discovery` modes | Tier-4 review/prd/fsd, `clarify_task` |
|
|
307
329
|
| **SFCC OCAPI** | `check_permissions` + every gated SFCC read and write tool | — |
|
|
308
330
|
| **SFCC WebDAV logs** | `sfcc_log_query` | — |
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge_gpt/mcp-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.55",
|
|
4
4
|
"description": "Bridge API MCP server — exposes Jira endpoints as MCP tools for Claude Code agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"mcp-server": "./build/index.js",
|
|
9
9
|
"bridge-api-mcp-server": "./build/index.js",
|
|
10
|
+
"epic-implementer": "./build/epic-implementer-bin.js",
|
|
11
|
+
"epic-implementer-claude-hook": "./build/epic-implementer-claude-hook-bin.js",
|
|
10
12
|
"conductor": "./build/conductor-bin.js",
|
|
11
13
|
"conductor-claude-hook": "./build/conductor-claude-hook-bin.js",
|
|
12
14
|
"bridge-worker-guard": "./build/worker-guard-hook-bin.js"
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
"//test": "The `test` script delegates to the bounded launcher (scripts/run-unit-tests.js → src/run-unit-tests-launcher.ts, BAPI-683), which discovers build/*.test.js at runtime and spawns them in size-bounded batches so the argv never exceeds Windows' ~8191-char command-line limit. Two determinism rules from the pre-launcher script are preserved INSIDE the launcher: (1) multi-file batches run WITHOUT --test-force-exit, because that flag makes node:test exit before aggregating per-subprocess summaries and the tally becomes nondeterministic (failures still surface and still set exit 1, but the count cannot be trusted as a completeness signal); (2) build/secret-safety.test.js leaks a handle that holds the event loop ~60s after its tests finish (they run in ~147ms), so the launcher quarantines it into its own single-file --test-force-exit invocation (FORCE_EXIT_QUARANTINE in src/run-unit-tests-launcher.ts) where the tally stays exact. Do NOT add --test-force-exit to batched invocations, and do NOT remove the quarantine, until the underlying handle leak is fixed.",
|
|
25
27
|
"//version": "BAPI-930: fired by `npm version <level>` AFTER npm rewrites package.json's version field, so the bumped version and the repinned README land in the SAME commit. A bump performed by hand-editing package.json instead of running `npm version` bypasses this hook entirely and must repin manually via `node scripts/sync-readme-host-examples.js` — the `--check` wired into `build` below is the backstop that catches that case.",
|
|
26
28
|
"scripts": {
|
|
27
|
-
"build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
|
|
29
|
+
"build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && node scripts/bundle-agent-guidance.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
|
|
28
30
|
"version": "node scripts/sync-readme-host-examples.js --write && git add -- README.md package.json package-lock.json && git commit -m \"chore(mcp-server): release $npm_package_version\" && git tag \"mcp-server/v$npm_package_version\"",
|
|
29
31
|
"check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
|
|
30
32
|
"assert:release-identity": "node scripts/assert-release-identity.js",
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
},
|
|
61
63
|
"overrides": {
|
|
62
64
|
"fast-uri": ">=4.1.3",
|
|
63
|
-
"hono": ">=4.13.
|
|
65
|
+
"hono": ">=4.13.7",
|
|
64
66
|
"@hono/node-server": "^1.19.13",
|
|
65
67
|
"ip-address": ">=10.1.1"
|
|
66
68
|
},
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "review-ticket",
|
|
3
|
-
"description": "Review a ticket, evaluate suggestions for accuracy, and produce a combined review-and-resolution document with decision trees. A single request_ticket_review step runs the backend's resolved difficulty-adaptive review policy, which internally runs any second-opinion rounds server-side. Pass --rounds=1|2 to forward an explicit round count that forces the review shape (1 = single pass, 2 = full second-opinion review); omit it to let the backend policy executor decide adaptively. The round count is injected into the request_ticket_review step as a real integer by get_pipeline_recipe, not substituted as a recipe variable.",
|
|
4
|
-
"variables": [
|
|
3
|
+
"description": "Review a ticket, evaluate suggestions for accuracy, and produce a combined review-and-resolution document with decision trees. A single request_ticket_review step runs the backend's resolved difficulty-adaptive review policy, which internally runs any second-opinion rounds server-side. Pass --rounds=1|2 to forward an explicit round count that forces the review shape (1 = single pass, 2 = full second-opinion review); omit it to let the backend policy executor decide adaptively. The round count is injected into the request_ticket_review step as a real integer by get_pipeline_recipe, not substituted as a recipe variable. BAPI-1121: this recipe has SEVEN steps, not nine — the former standalone retrieve-clarifying-questions and retrieve-ticket-critique MCP calls were removed because generation-aware persistence is performed by the shared artifact gate (ticket-review-artifact-gate.ts), which this recipe reaches through wait_for_ticket_review (see the wait step's instruction). BAPI-1129: that gate is the sole persistence implementation for the two review documents, shared by both calling conventions — blocking request_ticket_review(wait_for_result: true) reaches the same gate — so no step here may retrieve or save a review document on its own.",
|
|
4
|
+
"variables": [
|
|
5
|
+
"ticket_key",
|
|
6
|
+
"docs_dir",
|
|
7
|
+
"base_branch",
|
|
8
|
+
"base_sha",
|
|
9
|
+
"no_refresh_base"
|
|
10
|
+
],
|
|
5
11
|
"steps": [
|
|
6
12
|
{
|
|
7
13
|
"type": "mcp_call",
|
|
@@ -14,13 +20,20 @@
|
|
|
14
20
|
"tool": "request_ticket_review",
|
|
15
21
|
"params": {
|
|
16
22
|
"ticket_number": "{ticket_key}",
|
|
17
|
-
"wait_for_result":
|
|
23
|
+
"wait_for_result": false,
|
|
18
24
|
"save_locally": true,
|
|
19
25
|
"provider": "{provider}"
|
|
20
26
|
},
|
|
21
|
-
"description": "
|
|
27
|
+
"description": "Submit the combined clarify+critique review and RETURN IMMEDIATELY. The backend runs the resolved difficulty-adaptive policy (including any second-opinion rounds) server-side; an explicit rounds value (1|2) forces the shape. BAPI-1104: this step no longer blocks. Blocking here bound the whole review to the 900-second MCP client deadline, and a review that outran it killed the worker mid-wait \u2014 it exited without .conductor/result.json, produced MissingVerdictArtifact, and was requeued, spending a full attempt on work that had already been done. The wait moved to the following step, which is resumable.",
|
|
22
28
|
"on_error": "warn_and_continue"
|
|
23
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"type": "agent_task",
|
|
32
|
+
"id": "wait-for-ticket-review",
|
|
33
|
+
"instruction": "FIRST, check whether a review was actually submitted. The previous step runs with `on_error: warn_and_continue`, so a rejected submission (a 402, 403 or 500 from the generate endpoint, or a dropped connection) lets the pipeline continue into this step. If that step returned an error message rather than its submission confirmation or a `recovery_get` envelope, do NOT wait: nothing is running, and `wait_for_ticket_review` cannot tell \"never submitted\" from \"still running\" \u2014 it would read `pending` in every window until the caller's own timeout. Treat a failed submission as a terminal failure, report the error the previous step gave, and take the verdict contract's `revise` path.\n\nOtherwise, wait for the review submitted by the previous step to finish, using repeated BOUNDED windows.\n\nCall `wait_for_ticket_review` with `ticket_number` set to the ticket key. Each call occupies at most 240 seconds and returns a JSON body with a `state` field:\n\n- `state: \"pending\"` \u2014 NORMAL and expected. The review is still running. Call the tool again immediately. Do NOT sleep, do NOT poll in a shell loop, and do NOT re-run `request_ticket_review`: re-submitting starts a second billable multi-model review and abandons the one already in flight.\n- `state: \"ready\"` \u2014 the review is over. BAPI-1121: a ready result has ALREADY persisted exactly the accepted documents and reconciled the canonical source files \u2014 it writes every leg in `saved_legs` to its canonical path and removes the stale canonical file for every leg in `withheld_legs`. Do NOT call `get_clarifying_questions` or `get_ticket_critique` after a ready result; those retrieval calls were removed from this recipe because reading them again could reintroduce exactly the stale content the wait just withheld. Treat `saved_legs` as the authoritative source set for the next steps, not file presence.\n - `freshness_basis: \"generation\"` is the normal case: `advanced_legs`/`saved_legs` are current-review evidence, proven newer than the submission. Any leg in `withheld_legs` was delivered but NOT proven current \u2014 surface its `reason` (`generation_not_advanced` or `identity_unreadable`) as a visible warning; do not describe the result as an ordinary cost-saved one-leg review when a withholding occurred. A leg in `absent_legs` with `reason: \"not_delivered\"` is an ordinary policy omission (e.g. an Easy-ticket clarify-only review) unless its status also flags it as policy-expected, in which case call that out too.\n - `freshness_basis: \"terminality_only\"` means no submission baseline was available (e.g. a resumed session) \u2014 `advanced_legs` is empty by construction and `saved_legs` was accepted on terminal delivery alone. Surface this prominently as a DEGRADED-freshness warning: the saved artifact(s) may be indistinguishable from a prior attachment, and the downstream identity-footer agreement check is the remaining backstop. Retain this warning when reporting to the next steps rather than treating a `terminality_only` ready result as equivalent to a `generation`-backed one.\n - If the result instead carries `state: \"error\"` alongside a persistence failure (an `ARTIFACT_PERSISTENCE_FAILED` `error`/`status`/`message` envelope), HALT rather than proceeding with whatever files happen to exist on disk \u2014 report the failure; the accepted legs already on disk before the failure are named in the envelope and are not lost, but nothing new was persisted for this window.\n- `state: \"error\"` \u2014 terminal. Report the `reason` and stop; the review will not complete on its own. `UPSTREAM_TERMINAL` means the review failed or expired server-side \u2014 take the verdict contract's `revise` path; do NOT open another wait window and do NOT re-run `request_ticket_review`. `INVALID_RESPONSE` means the delivered document could not be read.\n\nKeep looping on `pending` for as long as this session's own budget allows. There is no iteration cap here: the bound is the caller's \u2014 for the conductor's spec-review worker that is the existing 120-minute job timeout, which produces the ordinary liveness outcome if the review never lands. Never fabricate a verdict or a review result from a pending state.",
|
|
34
|
+
"description": "Wait for the submitted review in repeated bounded windows; a ready result has already persisted the accepted documents and reconciled the canonical files (BAPI-1121). Resumable: a pending result is a normal observation, not a failure.",
|
|
35
|
+
"on_error": "halt"
|
|
36
|
+
},
|
|
24
37
|
{
|
|
25
38
|
"type": "mcp_call",
|
|
26
39
|
"id": "materialize-fresh-base",
|