alchemylab 1.4.12 → 1.4.14

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/dist/index.js CHANGED
@@ -3,9 +3,9 @@ if(void 0!==r)return r.exports
3
3
  var module=s[t]={exports:{}}
4
4
  return o[t].call(module.exports,module,module.exports,e),module.exports}var t,r,n,o={184(e,exports,t){"use strict"
5
5
  function r(e,t,r,n,o={}){const s=d.default.resolve(e)
6
- if(R.has(s))return t.child({component:"mcp-server-registry"}).debug("Using existing server for path",{path:s}),R.get(s)
6
+ if(j.has(s))return t.child({component:"mcp-server-registry"}).debug("Using existing server for path",{path:s}),j.get(s)
7
7
  const i=new x(s,t,r,n,o)
8
- return R.set(s,i),i}var n,o=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
8
+ return j.set(s,i),i}var n,o=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
9
9
  var o=Object.getOwnPropertyDescriptor(t,r)
10
10
  o&&!("get"in o?!t.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get(){return t[r]}}),Object.defineProperty(e,n,o)}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]}),s=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=this&&this.__importStar||(n=function(e){return n=Object.getOwnPropertyNames||function(e){var t=[]
11
11
  for(var r in e)({}).hasOwnProperty.call(e,r)&&(t[t.length]=r)
@@ -14,8 +14,8 @@ var t={}
14
14
  if(null!=e)for(var r=n(e),i=0;i<r.length;i++)"default"!==r[i]&&o(t,e,r[i])
15
15
  return s(t,e),t}),a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
16
16
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.McpServer=void 0,exports.resetStdoutWrite=function(){process.stdout.write=P},exports.getMcpServer=r,exports.createServer=function(e,t,n,o,s={}){return r(e,t,n,o,s)}
17
- const c=t(3807),l=t(8909),d=a(t(6928)),u=a(t(9896)),h=a(t(1943)),p=t(9567),g=t(3444),m=t(7660),f=t(1253),y=i(t(7478)),w=t(4139),b=new Set,I=t(9860),k=t(1472),v=t(3328),C=t(8955),_=t(3217),E=t(5278),S=t(9060),T=t(8824),q=(0,g.createLogger)({serviceName:"stdout-capture",console:!1,logToFile:!1}),P=process.stdout.write,j=process.argv.includes("--stdio")||"true"===process.env.MCP_DIRECT_MODE
18
- !(0,g.isDebugToolsEnabled)()||j&&"true"!==process.env.DEBUG_STDOUT_CAPTURE||(process.stdout.write=function(e){try{return(0,g.isDebugToolsEnabled)()&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()&&"string"==typeof e&&!e.trim().startsWith('{"jsonrpc":"2.0"')&&e.trim().length>0&&q.debug("Stdout output",{content:e.substring(0,100),length:e.length}),P.call(process.stdout,e)}catch(e){return q.error("Error in safeStdoutWrite",{error:e+""}),!0}})
17
+ const c=t(3807),l=t(8909),d=a(t(6928)),u=a(t(9896)),h=a(t(1943)),p=t(9567),g=t(3444),m=t(7660),f=t(1253),y=i(t(7478)),w=t(4139),b=new Set,I=t(9860),k=t(1472),v=t(3328),C=t(8955),_=t(3217),E=t(5278),S=t(9060),T=t(8824),q=(0,g.createLogger)({serviceName:"stdout-capture",console:!1,logToFile:!1}),P=process.stdout.write,R=process.argv.includes("--stdio")||"true"===process.env.MCP_DIRECT_MODE
18
+ !(0,g.isDebugToolsEnabled)()||R&&"true"!==process.env.DEBUG_STDOUT_CAPTURE||(process.stdout.write=function(e){try{return(0,g.isDebugToolsEnabled)()&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()&&"string"==typeof e&&!e.trim().startsWith('{"jsonrpc":"2.0"')&&e.trim().length>0&&q.debug("Stdout output",{content:e.substring(0,100),length:e.length}),P.call(process.stdout,e)}catch(e){return q.error("Error in safeStdoutWrite",{error:e+""}),!0}})
19
19
  class x{constructor(e,t,r,n,o={}){this.git=null,this.currentSSEConnection=null,this.stdioTransport=null,this.isInitializing=!1,this.isInitialized=!1,this.logger=t.child({component:"mcp-server"}),this.logger.info("Creating McpServer instance",{basePath:e,remoteServerUrl:n,hasExistingProjectId:!!o.existingProjectId}),this.basePath=e,this.alchemyPath=d.default.join(e,w.ALCHEMY_DIR_NAME),this.metrics=r,this.remoteServerUrl=n,this.existingProjectId=o.existingProjectId,this.gitBinary=(0,l.detectGitBinary)(),this.logger.info("Detected git binary",{gitBinary:this.gitBinary,version:(0,l.getGitVersion)()}),this.projectContext=new m.ProjectContext(e,t),this.tokenManager=new f.TokenManager(t,e,{remoteServerUrl:n}),this.configManager=new S.ConfigManager(t),this.logger.debug("Server paths initialized",{basePath:this.basePath,alchemyPath:this.alchemyPath}),this.server=new p.Server({name:"alchemy",version:"1.0.0"},{capabilities:{tools:{listChanged:!0}}}),this.metrics.incrementCounter("mcp_server_init_started",1),this.logger.info("Starting initialization for server instance"),this.initializationPromise=this.initialize()}get branchState(){if(!this.workspaceManager)return{currentBranch:null,activeRequestId:null,isOnMainBranch:!0}
20
20
  const e=this.workspaceManager.getCurrentContext("__server__")
21
21
  return{currentBranch:e.branchName,activeRequestId:e.requestId,isOnMainBranch:e.isMainBranch}}async updateBranchState(e,t){this.logger.debug("updateBranchState called - managed by WorkspaceManager",{branch:e,requestId:t})}async getCurrentBranchContext(){if(!this.workspaceManager)return{branch:null,requestId:null,isMain:!0}
@@ -94,7 +94,7 @@ r.success?this.logger.info("Successfully switched workspace to correct branch",{
94
94
  return this.logger.error("Failed to switch to request context",{requestId:e,error:r}),{success:!1,message:"Failed to switch context: "+r}}}async getEffectiveBasePath(e){if(!this.workspaceManager||!e)return this.basePath
95
95
  const t=await this.workspaceManager.getWorkspaceInfo(e)
96
96
  return t?.path||this.basePath}}exports.McpServer=x
97
- const R=new Map},213(e,exports,t){"use strict"
97
+ const j=new Map},213(e,exports,t){"use strict"
98
98
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.def=void 0
99
99
  const r=t(6183),n=t(3707)
100
100
  exports.def={name:"quick_write",description:"WHEN TO USE: trivial single-file edits only. For multi-file or feature work, spawn a coder via subagent_operations.\n\nCreate or update a file or document. Wraps the change in a request/task workflow that auto-completes.\n\nUse 'path' for filesystem files or 'documentId' for tracked documents.\nSet append:true to add content to existing content.\n\n`content` accepts a single string OR an array of lines (joined with \\n). PREFER the array form for files containing brackets, quotes, or code-like syntax (TOML, JSON, Python, YAML) \u2014 sidesteps the JSON-escaping foot-gun where multi-line content with `[` or `{` confuses the parser.\n\nRequires: title, description, path/documentId, content.",schema:r.QuickWriteSchema,annotations:{title:"Quick Write",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1},handler(e,t){return(0,n.handleQuickChange)(e,"write",t.context,t.apiClient,t.logger,t.metrics,t.mcpServer)}}},233(e,exports,t){"use strict"
@@ -327,22 +327,22 @@ return t},r(e)},function(e){if(e&&e.__esModule)return e
327
327
  var t={}
328
328
  if(null!=e)for(var s=r(e),i=0;i<s.length;i++)"default"!==s[i]&&n(t,e,s[i])
329
329
  return o(t,e),t}),i=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
330
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.registerApiRoutes=async function(e,r,n,o){const s=n.child({component:"api"}),i=r.getTokenManager(),q=process.env.REMOTE_SERVER_URL||"https://api.alchemylabcode.com",P=new f.AuthenticationManager(i,n.child({component:"auth-manager"})),j=P.getTokenSynchronizer(),x=await P.getTokenString()||void 0
330
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.registerApiRoutes=async function(e,r,n,o){const s=n.child({component:"api"}),i=r.getTokenManager(),q=process.env.REMOTE_SERVER_URL||"https://api.alchemylabcode.com",P=new f.AuthenticationManager(i,n.child({component:"auth-manager"})),R=P.getTokenSynchronizer(),x=await P.getTokenString()||void 0
331
331
  n.debug("Token retrieved from authentication manager")
332
- const R=new b.ConnectionStatusManager(q,n.child({component:"connection-status-manager"}),o,{checkIntervalMs:3e4,timeoutMs:5e3}),M=new w.ProjectClient(q,n.child({component:"project-client"}),o,R,x)
333
- j.registerClient(M)
334
- const O=new k.TaskClient(q,n.child({component:"task-client"}),o,M,R,x)
335
- j.registerClient(O)
336
- const $=new v.FileOperationsClient(q,n.child({component:"file-client"}),o,M,x,R)
337
- j.registerClient($)
338
- const D=new C.ProxyClient(q,n.child({component:"proxy-client"}),o,M,x,R)
339
- j.registerClient(D)
340
- const A=new _.RequestClient(q,n.child({component:"request-client"}),o,M,x,R)
341
- A.setAuthToken(x),(0,d.isDebugToolsEnabled)()&&n.debug("Explicitly set token on RequestClient",{tokenPresent:!!x}),j.registerClient(A)
342
- const F=new E.DocumentClient(q,n.child({component:"document-client"}),o,M,x,R)
343
- j.registerClient(F)
344
- const N=new S.ToolRecordClient(q,n.child({component:"tool-record-client"}),o,M,x,R)
345
- j.registerClient(N)
332
+ const j=new b.ConnectionStatusManager(q,n.child({component:"connection-status-manager"}),o,{checkIntervalMs:3e4,timeoutMs:5e3}),M=new w.ProjectClient(q,n.child({component:"project-client"}),o,j,x)
333
+ R.registerClient(M)
334
+ const O=new k.TaskClient(q,n.child({component:"task-client"}),o,M,j,x)
335
+ R.registerClient(O)
336
+ const $=new v.FileOperationsClient(q,n.child({component:"file-client"}),o,M,x,j)
337
+ R.registerClient($)
338
+ const D=new C.ProxyClient(q,n.child({component:"proxy-client"}),o,M,x,j)
339
+ R.registerClient(D)
340
+ const A=new _.RequestClient(q,n.child({component:"request-client"}),o,M,x,j)
341
+ A.setAuthToken(x),(0,d.isDebugToolsEnabled)()&&n.debug("Explicitly set token on RequestClient",{tokenPresent:!!x}),R.registerClient(A)
342
+ const F=new E.DocumentClient(q,n.child({component:"document-client"}),o,M,x,j)
343
+ R.registerClient(F)
344
+ const N=new S.ToolRecordClient(q,n.child({component:"tool-record-client"}),o,M,x,j)
345
+ R.registerClient(N)
346
346
  const z=new T.ApiClient(P,M,O,$,A,F,N,D,n.child({component:"api-client"})),L=a.default.Router();(0,h.registerAuthRoutes)(L,{projectClient:M,taskClient:O,fileClient:$,authManager:P,apiClient:z},i,n)
347
347
  const H=r.getContext();(0,p.registerConfigRoutes)(L,n,H.basePath),e.use("/api",L)
348
348
  const B=e=>{const t=o.startTimer(e)
@@ -546,18 +546,18 @@ _=r}else _=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.basePath||
546
546
  v.resolvedPath=_
547
547
  let E=!1
548
548
  try{E=(await p.default.stat(_)).isFile()}catch(e){const t=`Cannot access path ${_}: ${(0,I.getErrorMessage)(e)}`
549
- throw v.errors.push(t),Error(t)}const S=C.maxResults||50,T=C.caseSensitive||!1,q=C.useRegex||!1,P=C.fileExtension,j=C.searchIn||"content"
549
+ throw v.errors.push(t),Error(t)}const S=C.maxResults||50,T=C.caseSensitive||!1,q=C.useRegex||!1,P=C.fileExtension,R=C.searchIn||"content"
550
550
  let x=void 0!==C.contextLines?C.contextLines:0
551
551
  if(0>x?x=0:x>10&&(l.warn(`contextLines ${x} exceeds maximum of 10, clamping to 10`),x=10),!E&&!P)throw v.errors.push('File extension is required for directory search (use "*" for any extension, or formats like "ts", ".ts", "*.ts")'),(0,m.createValidationError)('File extension is required for directory search (use "*" for any extension, or formats like "ts", ".ts", "*.ts")')
552
- let R,M=""
552
+ let j,M=""
553
553
  if(E)M="*"
554
554
  else if("*"===P)M="*"
555
- else if(P){let e=P;(e.startsWith("*.")||e.startsWith("*"))&&(e=e.substring(1)),M=e.startsWith(".")?e:"."+e}try{R=q?T?RegExp(n):RegExp(n,"i"):T?RegExp(o(n)):RegExp(o(n),"i")}catch(e){const t=`Invalid ${q?"regex":"search"} pattern: ${(0,I.getErrorMessage)(e)}`
556
- throw v.errors.push(t),(0,m.createValidationError)(t)}if(v.patternInfo={needle:n,regex:R.toString(),caseSensitive:T,fileExtension:P,normalizedExtension:M},!E)try{const e=Date.now(),t=await p.default.readdir(_,{withFileTypes:!0}),r=[],n=[]
555
+ else if(P){let e=P;(e.startsWith("*.")||e.startsWith("*"))&&(e=e.substring(1)),M=e.startsWith(".")?e:"."+e}try{j=q?T?RegExp(n):RegExp(n,"i"):T?RegExp(o(n)):RegExp(o(n),"i")}catch(e){const t=`Invalid ${q?"regex":"search"} pattern: ${(0,I.getErrorMessage)(e)}`
556
+ throw v.errors.push(t),(0,m.createValidationError)(t)}if(v.patternInfo={needle:n,regex:j.toString(),caseSensitive:T,fileExtension:P,normalizedExtension:M},!E)try{const e=Date.now(),t=await p.default.readdir(_,{withFileTypes:!0}),r=[],n=[]
557
557
  for(const e of t)e.isDirectory()?r.push(e.name):e.isFile()&&n.push(e.name)
558
558
  v.initialScan={totalEntries:t.length,directories:r,files:n,firstFewFiles:n.slice(0,10),scanTime:Date.now()-e},v.phases.directoryListingTime=v.initialScan.scanTime}catch(e){const t="Failed to scan directory contents: "+(0,I.getErrorMessage)(e)
559
559
  v.errors.push(t),l.error(t)}const O=E?"file":"directory"
560
- l.info(`Searching for "${n}" in ${O} ${_} (mode: ${j})`,{needle:n,searchTarget:O,path:_,options:{maxResults:S,caseSensitive:T,fileExtension:M,searchIn:j}}),k=Date.now(),v.phases.initializationTime=k-u
560
+ l.info(`Searching for "${n}" in ${O} ${_} (mode: ${R})`,{needle:n,searchTarget:O,path:_,options:{maxResults:S,caseSensitive:T,fileExtension:M,searchIn:R}}),k=Date.now(),v.phases.initializationTime=k-u
561
561
  const $=Date.now()
562
562
  let D=[],A=0,F=0
563
563
  try{const e=await async function(e,t,n,o,s,i){const{maxResults:a,fileExtension:c,searchIn:l,isFile:d,contextLines:u}=n,g=[]
@@ -575,7 +575,7 @@ o.errors.push(r),n.error(r)}return{matches:i,totalFiles:1}}(e,t,l,s,i,u)
575
575
  const m={path:e,filesProcessed:0,filesMatched:0,subdirectories:0,errors:[]}
576
576
  try{const{filesCount:n,fileMatchesCount:c,subdirCount:d,subdirStats:h}=await r(e,t,g,a,o,s,0,m,l,u)
577
577
  return m.filesProcessed=n,m.filesMatched=c,m.subdirectories=d,i.directoryStats.push(m),h&&h.length>0&&i.directoryStats.push(...h),s.info("performLocalSearch completed",{totalFiles:n,matchesFound:g.length,directories:d+1}),{matches:g,totalFiles:n}}catch(t){const r="Error in performLocalSearch: "+(0,I.getErrorMessage)(t)
578
- return m.errors.push(r),i.directoryStats.push(m),i.errors.push(r),s.error(r,{error:t instanceof Error?t.stack:t+"",searchPath:e}),{matches:g,totalFiles:m.filesProcessed}}}(_,R,{maxResults:S,fileExtension:M,searchIn:j,isFile:E,contextLines:x},M,l,v)
578
+ return m.errors.push(r),i.directoryStats.push(m),i.errors.push(r),s.error(r,{error:t instanceof Error?t.stack:t+"",searchPath:e}),{matches:g,totalFiles:m.filesProcessed}}}(_,j,{maxResults:S,fileExtension:M,searchIn:R,isFile:E,contextLines:x},M,l,v)
579
579
  D=e.matches,A=e.totalFiles
580
580
  const t=1e3
581
581
  D.length>0&&D.length<=t?(D.sort(((e,t)=>void 0!==e.modifiedTime&&void 0!==t.modifiedTime?t.modifiedTime-e.modifiedTime:void 0!==e.modifiedTime&&void 0===t.modifiedTime?-1:void 0===e.modifiedTime&&void 0!==t.modifiedTime?1:0)),l.info(`Sorted ${D.length} matches by modification time (newest first)`)):D.length>t&&l.info(`Skipped sorting ${D.length} matches (exceeds hard cap of ${t} for performance)`),F=D.length,D.length>S&&(D=D.slice(0,S),l.info(`Limited results to ${S} (from ${F} total matches)`)),D.forEach((e=>{e.relativePath=function(e,t){const r=h.default.relative(e,t)
@@ -584,7 +584,7 @@ v.errors.push(t),l.error(t),v.phases.searchTime=Date.now()-$}v.totalFiles=A,v.to
584
584
  let N,z=""
585
585
  try{if(t.workspaceManager){const e=t.workspaceManager.getCurrentContext(t.currentSessionId)
586
586
  e.branchName&&!e.permanentBranch&&(z=e.isMainBranch?` (searched on main branch: ${e.branchName})`:` (searched on request branch: ${e.branchName}${e.requestId?" for "+e.requestId:""})`)}}catch(e){l.debug("Could not get branch context",{error:e}),z=" (branch context unavailable)"}l.debug("Search diagnostic information",{diagnostic:v})
587
- const L="content"===j?"content":"filename"===j?"filenames":"content and filenames"
587
+ const L="content"===R?"content":"filename"===R?"filenames":"content and filenames"
588
588
  if(F>D.length?(N=`Found ${F} results for "${n}" (searching ${L})`,g&&(N+=" in "+g),P&&(N+=" with extension "+P),N+=`${z}. Showing first ${D.length}:\n\n`):(N=`Found ${D.length} results for "${n}" (searching ${L})`,g&&(N+=" in "+g),P&&(N+=" with extension "+P),N+=z+":\n\n"),D.length>0)if(x>0){const{filenameMatches:e,mergedGroups:t}=function(e,t){const r=e.filter((e=>"filename"===e.matchType)),n=e.filter((e=>"content"===e.matchType))
589
589
  if(0===n.length)return{filenameMatches:r,mergedGroups:[]}
590
590
  const o=new Map
@@ -718,21 +718,21 @@ Date.now()
718
718
  let q=Date.now()
719
719
  if(l=r(b),Date.now(),q=Date.now(),C){const e=await _()
720
720
  if(Date.now(),!e)return k.warn("Unauthenticated tool call attempt",{tool:n}),{content:[{type:"text",text:"Authentication required. Please run 'alchemylab setup' to authenticate and configure your project."}],isError:!0}}const P=(0,f.getToolByName)(n)
721
- let j
721
+ let R
722
722
  if(q=Date.now(),s?(s.setOptions({createRecords:P?.createsRecords??!1}),k.info(`Setting record creation for tool ${n}: ${P?.createsRecords??!1}`)):k.debug(`No API client available for tool ${n} (minimal mode)`),Date.now(),Date.now(),q=Date.now(),S?.sessionId){const e=S.sessionId,t=S.messageId,r=h.sessionIdentity.getIdentity()
723
723
  if(r)r.sessionId!==e&&((0,m.logSessionIdentity)(n,"_meta-session-switch",{prevSessionId:r.sessionId,prevAgentType:r.agentType,newSessionId:e}),h.sessionIdentity.setIdentity({providerId:"opencode",sessionId:e,serverUrl:r.serverUrl}),o?.workspaceManager?.resetCurrentContext?.(e))
724
724
  else{const t=await(0,u.getServerUrl)(o,k)
725
- h.sessionIdentity.setIdentity({providerId:"opencode",sessionId:e,serverUrl:t||""})}j={providerId:"opencode",sessionId:e,messageId:t,createdAt:(new Date).toISOString()},(0,m.logSessionIdentity)(n,"_meta",{sessionId:e,messageId:t})}else if(g=n,E=S,v||(v=!0,k.warn("No _meta.sessionId on tool call \u2014 caller not on alchemylab-opencode fork.",{tool:g,metaKeys:E?Object.keys(E):null})),!1!==P?.requiresSession)throw(0,p.createMcpProtocolError)(`_meta.sessionId required for tool '${n}'`,{tool:n})
726
- h.sessionIdentity.setCurrentCallOrigin(j)
727
- const x=j?.sessionId||S?.sessionId
725
+ h.sessionIdentity.setIdentity({providerId:"opencode",sessionId:e,serverUrl:t||""})}R={providerId:"opencode",sessionId:e,messageId:t,createdAt:(new Date).toISOString()},(0,m.logSessionIdentity)(n,"_meta",{sessionId:e,messageId:t})}else if(g=n,E=S,v||(v=!0,k.warn("No _meta.sessionId on tool call \u2014 caller not on alchemylab-opencode fork.",{tool:g,metaKeys:E?Object.keys(E):null})),!1!==P?.requiresSession)throw(0,p.createMcpProtocolError)(`_meta.sessionId required for tool '${n}'`,{tool:n})
726
+ h.sessionIdentity.setCurrentCallOrigin(R)
727
+ const x=R?.sessionId||S?.sessionId
728
728
  if(o?.workspaceManager){q=Date.now()
729
729
  try{const e=await(0,u.getServerUrl)(o,k)
730
- e&&s&&await o.workspaceManager.ensureReadonlyBranchIfNeeded(e,n,s,x)}catch(e){k.debug("Error during workspace check",{error:(0,d.getErrorMessage)(e)})}Date.now()}const R=o?{...o,currentSessionId:x??"__server__"}:null,M=`${Date.now()}-${Math.random().toString(36).slice(2,10)}`,O=e=>{const{getActionServerBaseUrl:r}=t(9414)
730
+ e&&s&&await o.workspaceManager.ensureReadonlyBranchIfNeeded(e,n,s,x)}catch(e){k.debug("Error during workspace check",{error:(0,d.getErrorMessage)(e)})}Date.now()}const j=o?{...o,currentSessionId:x??"__server__"}:null,M=`${Date.now()}-${Math.random().toString(36).slice(2,10)}`,O=e=>{const{getActionServerBaseUrl:r}=t(9414)
731
731
  fetch(r()+"/api/mcp/tool-call-event",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:e,callId:M}),signal:AbortSignal.timeout(500)}).catch((()=>{}))}
732
732
  let $
733
733
  O("start")
734
- try{if(P){(0,y.preNormalize)(n,l),await(0,w.expandTaskRefsInArgs)(l,s,j?.sessionId,k),l=(0,f.resolveSchema)(P).parse(l)
735
- const t=e.params?._meta?.progressToken,r=c?.sendNotification,o={context:R,apiClient:s,logger:i,metrics:a,mcpServer:I,cliOrigin:j,progressToken:t,sendNotification:r}
734
+ try{if(P){(0,y.preNormalize)(n,l),await(0,w.expandTaskRefsInArgs)(l,s,R?.sessionId,k),l=(0,f.resolveSchema)(P).parse(l)
735
+ const t=e.params?._meta?.progressToken,r=c?.sendNotification,o={context:j,apiClient:s,logger:i,metrics:a,mcpServer:I,cliOrigin:R,progressToken:t,sendNotification:r}
736
736
  $=await P.handler(l,o)}else k.info(`Tool ${n} not found, returning negative response`),$={content:[{type:"text",text:`The tool '${n}' does not exist.`}]}}finally{O("end")}return Date.now(),Date.now(),T({tool:n,success:!0}),$}catch(t){k.error("Error executing tool:",{error:t instanceof Error?t.stack:t+"",tool:e.params.name}),a.incrementCounter("tool_errors_total",1,{tool:e.params.name,error:t instanceof Error?t.name:"unknown"})
737
737
  let r="Unknown error"
738
738
  return r=t instanceof b.ZodError?function(e,t){return 1===e.issues.length?n(e.issues[0],t):e.issues.map((e=>n(e,t))).join("; ")}(t,l):(0,d.getStructuredErrorMessage)(t),{content:[{type:"text",text:"Error: "+r}],isError:!0}}var g,E})),k.info("Tools registration complete")}
@@ -1098,14 +1098,14 @@ if(r.length>0){const e=r.map((e=>e.opencodeSessionId)).join(", ")
1098
1098
  if(!u)return s.warn("Blocked spawn: request already has active workers",{requestId:d,existingWorkerCount:r.length,existingSessionIds:r.map((e=>e.opencodeSessionId))}),{content:[{type:"text",text:`\u274c Cannot spawn: Request ${d} already has ${r.length} active worker(s): ${e}\n\nOptions:\n\u2022 Use \`useExistingSession: "${r[0].opencodeSessionId}"\` to send work to the existing session\n\u2022 Use \`allowParallel: true\` if you really need multiple workers on the same request`}],isError:!0}
1099
1099
  E=`\u26a0\ufe0f Warning: Request ${d} already has ${r.length} active worker(s): ${e}. Proceeding anyway (allowParallel=true).\n\n`,s.warn("Spawning parallel subagent for request with existing active workers",{requestId:d,existingWorkerCount:r.length,existingSessionIds:r.map((e=>e.opencodeSessionId)),allowParallel:!0})}}}catch(e){s.debug("Failed to check for existing workers",{error:(0,I.getErrorMessage)(e)})}let T,q,P=l
1100
1100
  d&&(T="requests/"+d,r(t,"Setting initialBranch for agent",{agent:c,requestId:d,initialBranch:T,agentRequestContextMode:p||"undefined"}),P="readonly"===p?`Working on request ${d} (READONLY mode - you cannot modify files, only explore and report).\n\n${l}`:`Working on request ${d}.\n\n${l}`)
1101
- let j,x=!1
1101
+ let R,x=!1
1102
1102
  if(h){if(q=h,s.info("Using existing session for spawn",{sessionId:q,requestId:d}),!g)throw new m.ValidationError("Could not determine OpenCode server URL")
1103
1103
  try{let e
1104
1104
  try{const t=await fetch(`http://localhost:${S}/api/project-settings`)
1105
1105
  if(t.ok){const r=await t.json()
1106
1106
  if(r.data?.defaultModel){const[t,...n]=r.data.defaultModel.split("/")
1107
- e={providerID:t,modelID:n.join("/")}}}}catch{}j=await o({apiClient:i,fromSessionId:a?.sessionId,toSessionId:q,body:P,requestId:d,logger:s})
1108
- const t=n(P,j),r=`${g.replace(/\/$/,"")}/session/${q}/prompt_async`,l={alchemy:{kind:"agent-delegation",payload:{body:P,fromSessionId:a?.sessionId,fromAgent:w.sessionIdentity.getIdentity()?.agentType,...d?{requestId:d}:{},...j?{instructionCode:j}:{}}}},u=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:t,metadata:l}],...c?{agent:c}:{},...e?{model:e}:{}}),signal:AbortSignal.timeout(1e4)})
1107
+ e={providerID:t,modelID:n.join("/")}}}}catch{}R=await o({apiClient:i,fromSessionId:a?.sessionId,toSessionId:q,body:P,requestId:d,logger:s})
1108
+ const t=n(P,R),r=`${g.replace(/\/$/,"")}/session/${q}/prompt_async`,l={alchemy:{kind:"agent-delegation",payload:{body:P,fromSessionId:a?.sessionId,fromAgent:w.sessionIdentity.getIdentity()?.agentType,...d?{requestId:d}:{},...R?{instructionCode:R}:{}}}},u=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:t,metadata:l}],...c?{agent:c}:{},...e?{model:e}:{}}),signal:AbortSignal.timeout(1e4)})
1109
1109
  if(!u.ok){const e=await u.text().catch((()=>"Unknown error"))
1110
1110
  throw Error(`Failed to send to existing session: ${u.status} ${e}`)}s.info("Prompt sent to existing session",{sessionId:q,agent:c})}catch(e){throw Error("Failed to use existing session: "+(0,I.getErrorMessage)(e))}}else{x=!0,r(t,"Creating session via spawn-session endpoint")
1111
1111
  const h=v;(0,b.logSessionIdentity)("subagent_operations.spawn","_meta",{parentSessionId:h})
@@ -1114,10 +1114,10 @@ if(!g.ok){const e=await g.text().catch((()=>"Unknown error"))
1114
1114
  throw Error(`Spawn session endpoint failed: ${g.status} ${e}`)}const m=await g.json()
1115
1115
  if(!m.success||!m.data)throw Error(m.error||"Spawn session returned unsuccessful")
1116
1116
  const{sessionId:y,model:b,serverUrl:I,initialBranch:v}=m.data
1117
- T=v,j=await o({apiClient:i,fromSessionId:a?.sessionId,toSessionId:y,body:P,requestId:d,logger:s})
1118
- const C=n(P,j),_=(0,f.spawnDetachedSubagent)({agent:c,prompt:C,cwd:k,sessionId:y,model:b,initialBranch:T,allowParallel:u,serverUrl:I})
1117
+ T=v,R=await o({apiClient:i,fromSessionId:a?.sessionId,toSessionId:y,body:P,requestId:d,logger:s})
1118
+ const C=n(P,R),_=(0,f.spawnDetachedSubagent)({agent:c,prompt:C,cwd:k,sessionId:y,model:b,initialBranch:T,allowParallel:u,serverUrl:I})
1119
1119
  if(!_.success)throw Error("Failed to spawn subagent: "+_.error)
1120
- q=y,r(t,"Spawn successful via unified endpoint",{sessionId:y,pid:_.pid,parentSessionId:h,model:b,instructionCode:j}),s.info("Background subagent spawned via unified endpoint",{agent:c,requestId:d,sessionId:y,pid:_.pid,parentSessionId:h,instructionCode:j}),async function(e){const{serverUrl:t,sessionId:r,payload:n,logger:o}=e,s=t.replace(/\/$/,""),i=Date.now()+3e4
1120
+ q=y,r(t,"Spawn successful via unified endpoint",{sessionId:y,pid:_.pid,parentSessionId:h,model:b,instructionCode:R}),s.info("Background subagent spawned via unified endpoint",{agent:c,requestId:d,sessionId:y,pid:_.pid,parentSessionId:h,instructionCode:R}),async function(e){const{serverUrl:t,sessionId:r,payload:n,logger:o}=e,s=t.replace(/\/$/,""),i=Date.now()+3e4
1121
1121
  let a=0
1122
1122
  for(;Date.now()<i;){a++
1123
1123
  try{const e=await fetch(`${s}/session/${r}/message`,{signal:AbortSignal.timeout(5e3)})
@@ -1125,10 +1125,10 @@ if(e.ok){const t=(await e.json()).find((e=>"user"===e.info?.role)),i=t?.parts.fi
1125
1125
  if(t&&i){const e={...i,metadata:{...i.metadata||{},alchemy:{kind:"agent-delegation",payload:n}}},c=`${s}/session/${r}/message/${t.info.id}/part/${i.id}`,l=await fetch(c,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(5e3)})
1126
1126
  if(l.ok)return void o.info("Tagged initial delegation message",{sessionId:r,messageId:t.info.id,partId:i.id,attempt:a})
1127
1127
  const d=await l.text().catch((()=>"unknown"))
1128
- o.warn("PATCH initial delegation failed",{sessionId:r,status:l.status,body:d.slice(0,200)})}}}catch{}await new Promise((e=>setTimeout(e,500)))}o.warn("Timed out tagging initial delegation; message will appear as plain user bubble",{sessionId:r,attempts:a})}({serverUrl:I,sessionId:y,payload:{body:P,fromSessionId:a?.sessionId,fromAgent:w.sessionIdentity.getIdentity()?.agentType,...d?{requestId:d}:{},...j?{instructionCode:j}:{}},logger:s})}catch(e){throw Error("Failed to spawn subagent: "+(0,I.getErrorMessage)(e))}}let R=E
1129
- R+=h?`Prompt sent to existing session ${q}.`:`Background ${c} subagent spawned successfully.`,d&&(R+="\nWorking on: "+d),j&&(R+=`\nInstruction code: ${j} (the specialist will cite this on their reports/questions via relatesTo)`),R+="\n\nThe subagent is now working independently. You can continue with other tasks.","coder"===c&&(R+="\n\nIf this work has shifted or expanded your area of expertise, update your specialization summary with `update_specialization`.")
1128
+ o.warn("PATCH initial delegation failed",{sessionId:r,status:l.status,body:d.slice(0,200)})}}}catch{}await new Promise((e=>setTimeout(e,500)))}o.warn("Timed out tagging initial delegation; message will appear as plain user bubble",{sessionId:r,attempts:a})}({serverUrl:I,sessionId:y,payload:{body:P,fromSessionId:a?.sessionId,fromAgent:w.sessionIdentity.getIdentity()?.agentType,...d?{requestId:d}:{},...R?{instructionCode:R}:{}},logger:s})}catch(e){throw Error("Failed to spawn subagent: "+(0,I.getErrorMessage)(e))}}let j=E
1129
+ j+=h?`Prompt sent to existing session ${q}.`:`Background ${c} subagent spawned successfully.`,d&&(j+="\nWorking on: "+d),R&&(j+=`\nInstruction code: ${R} (the specialist will cite this on their reports/questions via relatesTo)`),j+="\n\nThe subagent is now working independently. You can continue with other tasks.","coder"===c&&(j+="\n\nIf this work has shifted or expanded your area of expertise, update your specialization summary with `update_specialization`.")
1130
1130
  const M=!!q&&"unknown"!==q&&"spawning"!==q
1131
- return{content:[{type:"text",text:R}],...(0,_.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:M?q:void 0,agentType:c,requestId:d||void 0,description:e.title||l.slice(0,50)||c,...j?{instructionCode:j}:{}}),...j?{instructionCode:j}:{}}}async function i(e,t,r,s,i,a,c){const l=await(0,y.getServerUrl)(r,s)
1131
+ return{content:[{type:"text",text:j}],...(0,_.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:M?q:void 0,agentType:c,requestId:d||void 0,description:e.title||l.slice(0,50)||c,...R?{instructionCode:R}:{}}),...R?{instructionCode:R}:{}}}async function i(e,t,r,s,i,a,c){const l=await(0,y.getServerUrl)(r,s)
1132
1132
  if(!l)throw new m.ValidationError("Could not determine OpenCode server URL")
1133
1133
  if(s.info("Sending message to subagent session",{sessionId:e,promptLength:t.length}),i)try{const t=c?.sessionId
1134
1134
  if(t){const r=i.getCLISessionClient()
@@ -1344,7 +1344,8 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleMergeReques
1344
1344
  try{if(!h)throw(0,n.createValidationError)("requestId is required")
1345
1345
  d.info("Starting merge request",{requestId:h,verificationCount:p.length})
1346
1346
  const e=a.getRequestClient(),s=await e.getRequest(h)
1347
- if("ready_for_review"===s?.status)return u({success:!1,reason:"testing_in_progress"}),{content:[{type:"text",text:`STOP: Cannot merge request ${h} \u2014 testing is in progress. Wait for tests to complete. The request will be moved to ready_for_merge when tests pass.`}],isError:!0}
1347
+ if("running"===s?.testStatus)return u({success:!1,reason:"testing_in_progress"}),{content:[{type:"text",text:`STOP: Cannot merge request ${h} \u2014 tests are still running. Wait for the test report; you'll be cleared to merge when they pass.`}],isError:!0}
1348
+ if("ready_for_review"===s?.status)return u({success:!1,reason:"awaiting_human"}),{content:[{type:"text",text:`STOP: Cannot merge request ${h} \u2014 it is awaiting human action (sign-off or review). A maintainer finalizes it from the UI.`}],isError:!0}
1348
1349
  if(p.length>0)try{await a.proxyPost(`/api/projects/${t.projectId}/requests/${h}/verify-criteria`,{verifications:p}),d.info("Criteria verified",{requestId:h,count:p.length})}catch(e){const t=(0,i.getErrorMessage)(e)
1349
1350
  d.warn("Criteria verification failed, continuing with merge",{requestId:h,error:t})}let c,l,g
1350
1351
  try{const e=await a.proxyPost(`/api/projects/${t.projectId}/requests/${h}/validate-completion`,{})
@@ -1387,23 +1388,24 @@ const o=process.config||{},s=o.commands||[]
1387
1388
  let a=`## ${process.name}\n`
1388
1389
  process.description&&(a+=process.description+"\n"),process.scope&&(a+=`**Scope:** \`${process.scope}\`\n`),a+=`**Status:** ${process.isActive?"Active":"Inactive"}\n\n`,o.setup?.length&&(a+=`### Setup\n${o.setup.map((e=>`\`\`\`\n${e}\n\`\`\``)).join("\n")}\n\n`),a+=`### Test Commands (${s.length})\n`
1389
1390
  for(let e=0;e<s.length;e++){const t=s[e]
1390
- a+=`\n**${e+1}. ${t.name}**${t.required?" \u2014 REQUIRED":" \u2014 optional"}\n`,a+=` Command: \`${t.command}\`\n`,t.timeout&&(a+=` Timeout: ${t.timeout}s\n`)}return Array.isArray(o.fingerprint)&&o.fingerprint.length>0&&(a+=`\n### Fingerprint Files\n${o.fingerprint.map((e=>`- \`${e}\``)).join("\n")}\n`),"number"==typeof o.poolSize&&(a+=`\n**Pool size:** ${o.poolSize}\n`),i({success:!0}),{content:[{type:"text",text:a}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to get test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessCreate=async function(e,t,n,o,s){const i=s.startTimer("tool_execution_time")
1391
- try{const r={setup:e.setup||[],commands:e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300})))}
1392
- void 0!==e.fingerprint&&(r.fingerprint=e.fingerprint),void 0!==e.poolSize&&(r.poolSize=e.poolSize)
1393
- const o=await n.proxyPost(`/api/projects/${t.projectId}/test-processes`,{name:e.name,description:e.description,scope:e.scope,config:r,createdBy:"test-architect"}),process=o?.data||o
1394
- return i({success:!0}),{content:[{type:"text",text:`\u2705 Created test process **${process.name}** (${process.id}) with ${r.commands.length} test commands.${e.scope?`\nScope: \`${e.scope}\``:""}\n\nThe process is now active and will run when requests are completed.`}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to create test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessUpdate=async function(e,t,n,o,s){const i=s.startTimer("tool_execution_time")
1391
+ a+=`\n**${e+1}. ${t.name}**${t.required?" \u2014 REQUIRED":" \u2014 optional"}\n`,a+=` Command: \`${t.command}\`\n`,t.timeout&&(a+=` Timeout: ${t.timeout}s\n`)}return Array.isArray(o.fingerprint)&&o.fingerprint.length>0&&(a+=`\n### Fingerprint Files\n${o.fingerprint.map((e=>`- \`${e}\``)).join("\n")}\n`),"number"==typeof o.poolSize&&(a+=`\n**Pool size:** ${o.poolSize}\n`),i({success:!0}),{content:[{type:"text",text:a}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to get test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessCreate=async function(e,n,o,s,i){const a=i.startTimer("tool_execution_time")
1392
+ try{const i={setup:e.setup||[],commands:e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300})))}
1393
+ void 0!==e.fingerprint&&(i.fingerprint=e.fingerprint),void 0!==e.poolSize&&(i.poolSize=e.poolSize)
1394
+ const c=await o.proxyPost(`/api/projects/${n.projectId}/test-processes`,{name:e.name,description:e.description,scope:e.scope,config:i,createdBy:"test-architect"}),process=c?.data||c
1395
+ try{const{sessionIdentity:e}=await Promise.resolve().then(t.bind(t,6688)),r=e.getIdentity()
1396
+ r?.sessionId&&process?.id&&await o.proxyPost(`/api/projects/${n.projectId}/session-test-process-associations`,{opencodeSessionId:r.sessionId,testProcessId:process.id})}catch(e){s.warn("Failed to write session-test-process association",{error:(0,r.getErrorMessage)(e)})}return a({success:!0}),{content:[{type:"text",text:`\u2705 Created test process **${process.name}** (${process.id}) with ${i.commands.length} test commands.${e.scope?`\nScope: \`${e.scope}\``:""}\n\nThe process is now active and will run when requests are completed.`}]}}catch(e){return a({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to create test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessUpdate=async function(e,t,n,o,s){const i=s.startTimer("tool_execution_time")
1395
1397
  try{const r={}
1396
1398
  if(void 0!==e.name&&(r.name=e.name),void 0!==e.description&&(r.description=e.description),void 0!==e.scope&&(r.scope=e.scope),void 0!==e.isActive&&(r.isActive=e.isActive),void 0!==e.commands||void 0!==e.setup||void 0!==e.fingerprint||void 0!==e.poolSize){const o=await n.proxyGet(`/api/projects/${t.projectId}/test-processes/${e.processId}`),s=o?.data||o,i=s?.config||{},a={setup:e.setup??i.setup??[],commands:e.commands?e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300}))):i.commands??[]}
1397
1399
  void 0!==e.fingerprint?a.fingerprint=e.fingerprint:void 0!==i.fingerprint&&(a.fingerprint=i.fingerprint),void 0!==e.poolSize?a.poolSize=e.poolSize:void 0!==i.poolSize&&(a.poolSize=i.poolSize),void 0!==i.verifyAcceptanceCriteria&&(a.verifyAcceptanceCriteria=i.verifyAcceptanceCriteria),r.config=a}const o=await n.proxyPatch(`/api/projects/${t.projectId}/test-processes/${e.processId}`,r),process=o?.data||o
1398
- return i({success:!0}),{content:[{type:"text",text:`\u2705 Updated test process **${process.name}** (${process.id}).`}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to update test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleExecuteTestCommand=async function(e,n,o,s,a,c,l){const d=a.startTimer("tool_execution_time"),u=s.child({tool:"execute_test_command"})
1399
- try{const a=await o.proxyGet(`/api/projects/${n.projectId}/test-processes/${e.processId}`),process=a?.data||a
1400
+ return i({success:!0}),{content:[{type:"text",text:`\u2705 Updated test process **${process.name}** (${process.id}).`}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to update test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleExecuteTestCommand=async function(e,n,o,s,i,a,l){const d=i.startTimer("tool_execution_time"),u=s.child({tool:"execute_test_command"})
1401
+ try{const i=await o.proxyGet(`/api/projects/${n.projectId}/test-processes/${e.processId}`),process=i?.data||i
1400
1402
  if(!process?.config)return d({success:!1,error:"not_found"}),{content:[{type:"text",text:`Test process ${e.processId} not found or has no config.`}],isError:!0}
1401
- let c
1403
+ let a
1402
1404
  if("setup"===e.commandName){const e=process.config.setup||[]
1403
1405
  if(0===e.length)return d({success:!0}),{content:[{type:"text",text:"No setup commands configured for this test process."}]}
1404
- c=e.map(((e,t)=>({name:"setup-"+(t+1),command:e,timeout:120})))}else{const t=process.config.commands||[],r=t.find((t=>t.name===e.commandName))
1406
+ a=e.map(((e,t)=>({name:"setup-"+(t+1),command:e,timeout:120})))}else{const t=process.config.commands||[],r=t.find((t=>t.name===e.commandName))
1405
1407
  if(!r){const r=t.map((e=>e.name)).join(", ")
1406
- return d({success:!1,error:"command_not_found"}),{content:[{type:"text",text:`Command "${e.commandName}" not found in test process "${process.name}".\nAvailable commands: ${r}\nUse "setup" to run setup commands.`}],isError:!0}}c=[{name:r.name,command:r.command,timeout:r.timeout||300}]}const{runShellCommand:h}=await Promise.resolve().then(t.bind(t,4600)),p=globalThis.process
1408
+ return d({success:!1,error:"command_not_found"}),{content:[{type:"text",text:`Command "${e.commandName}" not found in test process "${process.name}".\nAvailable commands: ${r}\nUse "setup" to run setup commands.`}],isError:!0}}a=[{name:r.name,command:r.command,timeout:r.timeout||300}]}const{runShellCommand:h}=await Promise.resolve().then(t.bind(t,4600)),p=globalThis.process
1407
1409
  let g=null
1408
1410
  const m=[]
1409
1411
  try{const{sessionIdentity:e}=await Promise.resolve().then(t.bind(t,6688)),r=e.getIdentity()
@@ -1412,53 +1414,60 @@ if(m.push("reqId: "+(t||"NONE")),t){const e=await o.proxyGet(`/api/projects/${n.
1412
1414
  m.push(`runs: ${Array.isArray(r)?r.length:0}, withWorktree: ${s?.id||"NONE"}, path: ${s?.worktreePath||s?.worktree_path||"NONE"}`),s&&(g=s.worktreePath||s.worktree_path,u.info("Using test worktree from request test run",{worktreePath:g,reqId:t}))}}}catch(e){u.warn("Failed to resolve test worktree",{error:(0,r.getErrorMessage)(e)})}if(!g)return d({success:!1,error:"no_worktree"}),{content:[{type:"text",text:`No test worktree found.\n\nDiagnostics:\n${m.length>0?m.map((e=>"\u2022 "+e)).join("\n"):"\u2022 No session identity found"}\n\nEnsure this session is linked to a test process and a test run has been created.`}],isError:!0}
1413
1415
  const f=[],{default:y}=await Promise.resolve().then(t.t.bind(t,6928,23)),{default:w}=await Promise.resolve().then(t.t.bind(t,9896,23)),{ALCHEMY_DIR_NAME:b}=await Promise.resolve().then(t.bind(t,4139)),I=y.join(g,b,"command-logs")
1414
1416
  try{w.mkdirSync(I,{recursive:!0})}catch{}const{runTrackedShellCommand:k}=await Promise.resolve().then(t.bind(t,4600)),{formatCombinedOutput:v}=await Promise.resolve().then(t.bind(t,1816)),{sessionIdentity:C}=await Promise.resolve().then(t.bind(t,6688)),_=C.getIdentity()?.sessionId
1415
- for(const t of c){u.info("Executing test command",{name:t.name,command:t.command,timeout:t.timeout,cwd:g})
1417
+ for(const t of a){u.info("Executing test command",{name:t.name,command:t.command,timeout:t.timeout,cwd:g})
1416
1418
  const r=`${t.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,n=y.join(I,r+".log")
1417
1419
  let o=null
1418
- try{o=w.createWriteStream(n,{flags:"w"}),o.write(`# ${(new Date).toISOString()} starting: ${t.command}\n# cwd: ${g}\n# timeout: ${t.timeout}s\n\n`)}catch(e){u.warn("Failed to open command log file",{logPath:n,error:e+""})}const s=await(0,i.runTrackedShellWithHeartbeat)(t.command,{cwd:g,timeoutMs:1e3*t.timeout,env:{...p.env,FORCE_COLOR:"0"},onChunk:o?(e,t)=>{o?.write(t)}:void 0},{id:r,name:t.name,sessionId:_,logPath:n},l)
1420
+ try{o=w.createWriteStream(n,{flags:"w"}),o.write(`# ${(new Date).toISOString()} starting: ${t.command}\n# cwd: ${g}\n# timeout: ${t.timeout}s\n\n`)}catch(e){u.warn("Failed to open command log file",{logPath:n,error:e+""})}const s=await(0,c.runTrackedShellWithHeartbeat)(t.command,{cwd:g,timeoutMs:1e3*t.timeout,env:{...p.env,FORCE_COLOR:"0"},onChunk:o?(e,t)=>{o?.write(t)}:void 0},{id:r,name:t.name,sessionId:_,logPath:n},l)
1419
1421
  o&&(o.write(`\n# exit: ${s.exitCode}${s.timedOut?" (timed out)":""}\n`),o.end())
1420
- const a=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim(),{output:c,truncatedNote:d,logFooter:h}=v(a,{invocationId:r})
1421
- if(f.push({name:t.name,command:t.command,exitCode:s.exitCode,output:c,timedOut:s.timedOut,invocationId:r,truncatedNote:d,logFooter:h}),"setup"===e.commandName&&0!==s.exitCode)break}const E=f.every((e=>0===e.exitCode)),S=f.map((e=>{const t=e.timedOut?"\u23f1 TIMEOUT":0===e.exitCode?"\u2705 PASS":`\u274c FAIL (exit ${e.exitCode})`
1422
+ const i=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim(),{output:a,truncatedNote:d,logFooter:h}=v(i,{invocationId:r})
1423
+ if(f.push({name:t.name,command:t.command,exitCode:s.exitCode,output:a,timedOut:s.timedOut,invocationId:r,truncatedNote:d,logFooter:h}),"setup"===e.commandName&&0!==s.exitCode)break}const E=f.every((e=>0===e.exitCode)),S=f.map((e=>{const t=e.timedOut?"\u23f1 TIMEOUT":0===e.exitCode?"\u2705 PASS":`\u274c FAIL (exit ${e.exitCode})`
1422
1424
  return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n\`\`\`${e.truncatedNote}\n${e.logFooter}`})).join("\n\n")
1423
- return d({success:!0}),{content:[{type:"text",text:`## ${"setup"===e.commandName?"Setup":e.commandName} \u2014 ${E?"PASSED":"FAILED"}\n\n${S}`}]}}catch(e){return d({success:!1,error:"execution_error"}),{content:[{type:"text",text:"Failed to execute test command: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleReportTestResults=async function(e,s,i,a,c,l){const d=c.startTimer("tool_execution_time")
1424
- try{const{resolveRequestIdOrError:u}=await Promise.resolve().then(t.bind(t,9032)),h=await u(i,a,l?.sessionId,"Cannot report test results: this session is not associated with a request.")
1425
- if(!h.ok)return d({success:!1,error:"no_request_association"}),h.errorResult
1426
- const p=h.requestId,g=await i.proxyGet(`/api/projects/${s.projectId}/requests/${p}/test-runs`),m=g?.data||g||[],f=Array.isArray(m)?m:[],y=f.find((e=>"pending"===e.status||"running"===e.status))||f[0]
1427
- if(!y)return d({success:!1,error:"no_test_run"}),{content:[{type:"text",text:`No test run found for request ${p}.`}],isError:!0}
1428
- const w=y.id
1429
- await i.proxyPatch(`/api/projects/${s.projectId}/test-runs/${w}`,{status:e.status,results:e.results}),y.worktreePath&&fetch((0,o.getActionServerBaseUrl)()+"/api/release-pool-slot",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({worktreePath:y.worktreePath}),signal:AbortSignal.timeout(5e3)}).catch((e=>a.debug("release-pool-slot fire-and-forget failed (non-fatal)",{error:(0,r.getErrorMessage)(e)})))
1430
- const b="passed"===e.status?"\u2705":"failed"===e.status?"\u274c":"\u26a0\ufe0f"
1431
- try{const t=i.getRequestClient()
1432
- "passed"===e.status?(await t.updateRequest(p,{status:"ready_for_merge"}),a.info("Tests passed, set request to ready_for_merge",{requestId:p})):(await t.updateRequest(p,{status:"in_progress"}),a.info("Tests failed, set request back to in_progress",{requestId:p}))}catch(e){a.warn("Failed to update request status after test results",{requestId:p,error:(0,r.getErrorMessage)(e)})}const I="passed"===e.status?"\n\nAll tests passed \u2014 the coder can now call `merge` to merge this request.":"\n\nTests failed \u2014 the coder should fix the issues and call `complete` again.",k=`${b} Test run ${e.status} for request ${p}\n\n${e.results.summary}${I}`
1433
- try{const t=await(0,n.handleSessionMessageOperations)({operation:"send",type:"report",status:"passed"===e.status?"success":"failure",summary:k,details:e.results.commands?.map((e=>`${"passed"===e.status?"\u2705":"\u274c"} ${e.name}: ${e.status}${e.diagnosis?" - "+e.diagnosis:""}`)).join("\n"),finish:!0},s,a,c,i,l),r=t?.content?.[0]?.text||"unknown",o=t?.isError
1434
- return d({success:!0}),{content:[{type:"text",text:`Test run for request ${p} reported as **${e.status}**.${o?" Parent notification FAILED: "+r:" Parent session notified."}\n\n${e.results.summary}`}]}}catch(t){return d({success:!0}),{content:[{type:"text",text:`Test run for request ${p} reported as **${e.status}** but failed to notify parent: ${(0,r.getErrorMessage)(t)}\n\n${e.results.summary}`}]}}}catch(e){return d({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to report test results: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingReference=async function(e,t,r,n,o){return o.startTimer("tool_execution_time")({success:!0}),{content:[{type:"text",text:'# Test Process Design Guide\n\n## Overview\n\nA test process defines how to verify code changes for a project. It consists of:\n- **Setup**: Commands to prepare the environment (install deps, link node_modules, etc.) \u2014 runs once per worktree\n- **Commands**: The actual test commands (build, lint, test, etc.) \u2014 the verification steps\n\n## Command Properties\n\n- **name**: Human-readable name (e.g., "Build", "Unit Tests")\n- **command**: Shell command to run (e.g., `npm test`)\n- **required** (default: true): If a required command fails, the request won\'t be merged\n- **timeout** (default: 300s): Maximum execution time. Exit code 0 = pass, non-zero = fail.\n\n## Scope (Monorepo Support)\n\nSet `scope` to a path prefix to limit when a process runs:\n- `scope: "packages/api/"` \u2192 only runs when files under packages/api/ change\n- omit `scope` \u2192 runs for all changes (global process)\n\nMultiple active processes per project are supported.\n\n## Common Patterns\n\n### Node.js / TypeScript\n```json\n{\n "setup": ["ln -s ../../node_modules node_modules"],\n "commands": [\n { "name": "Build", "command": "npm run build" },\n { "name": "Lint", "command": "npm run lint", "required": false },\n { "name": "Unit Tests", "command": "npm test" }\n ]\n}\n```\n\n### Python\n```json\n{\n "setup": ["pip install -e \'.[dev]\'"],\n "commands": [\n { "name": "Type Check", "command": "mypy src/" },\n { "name": "Lint", "command": "ruff check .", "required": false },\n { "name": "Tests", "command": "pytest" }\n ]\n}\n```\n\n### Go\n```json\n{\n "setup": ["go mod download"],\n "commands": [\n { "name": "Build", "command": "go build ./..." },\n { "name": "Vet", "command": "go vet ./...", "required": false },\n { "name": "Tests", "command": "go test ./..." }\n ]\n}\n```\n\n### Rust\n```json\n{\n "setup": ["cargo fetch"],\n "commands": [\n { "name": "Build", "command": "cargo build" },\n { "name": "Clippy", "command": "cargo clippy -- -D warnings", "required": false },\n { "name": "Tests", "command": "cargo test" }\n ]\n}\n```\n\n## Project Analysis Checklist\n\nWhen setting up testing for a new project, check for:\n1. **package.json** \u2192 Node.js/TypeScript (check "scripts" for test/build/lint commands)\n2. **Cargo.toml** \u2192 Rust\n3. **go.mod** \u2192 Go\n4. **pyproject.toml** / **requirements.txt** \u2192 Python\n5. **Makefile** \u2192 Custom build system (check for test/build targets)\n6. **.github/workflows/** \u2192 Existing CI configuration (great source of test commands)\n7. **.gitlab-ci.yml** \u2192 GitLab CI\n8. **tsconfig.json** \u2192 TypeScript type checking\n9. **.eslintrc** / **biome.json** \u2192 Linting configuration\n10. **vitest.config.ts** / **jest.config.ts** / **pytest.ini** \u2192 Test framework configuration'}]}},exports.handleTestingProcessDelete=async function(e,t,n,o,s){const i=s.startTimer("tool_execution_time")
1435
- try{return await n.proxyDelete(`/api/projects/${t.projectId}/test-processes/${e.processId}`),o.info("Deleted test process",{processId:e.processId}),i({success:!0}),{content:[{type:"text",text:`Deleted test process ${e.processId}.`}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to delete test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTests=async function(e,n,i,a,c,l){const d=c.startTimer("tool_execution_time")
1436
- try{if("list"===e.operation){const e=await i.proxyGet(`/api/projects/${n.projectId}/test-processes`),t=e?.data||e||[],r=Array.isArray(t)?t:[]
1425
+ return d({success:!0}),{content:[{type:"text",text:`## ${"setup"===e.commandName?"Setup":e.commandName} \u2014 ${E?"PASSED":"FAILED"}\n\n${S}`}]}}catch(e){return d({success:!1,error:"execution_error"}),{content:[{type:"text",text:"Failed to execute test command: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleReportTestResults=async function(e,a,c,l,d,u){const h=d.startTimer("tool_execution_time")
1426
+ try{const{resolveRequestIdOrError:p}=await Promise.resolve().then(t.bind(t,9032)),g=await p(c,l,u?.sessionId,"Cannot report test results: this session is not associated with a request.")
1427
+ if(!g.ok)return h({success:!1,error:"no_request_association"}),g.errorResult
1428
+ const m=g.requestId,f=await c.proxyGet(`/api/projects/${a.projectId}/requests/${m}/test-runs`),y=f?.data||f||[],w=Array.isArray(y)?y:[],b=w.find((e=>"pending"===e.status||"running"===e.status))||w[0]
1429
+ if(!b)return h({success:!1,error:"no_test_run"}),{content:[{type:"text",text:`No test run found for request ${m}.`}],isError:!0}
1430
+ const I=b.id
1431
+ await c.proxyPatch(`/api/projects/${a.projectId}/test-runs/${I}`,{status:e.status,results:e.results})
1432
+ const k=e.results.acceptanceCriteria
1433
+ if(k&&k.length>0)try{const e=c.getRequestClient(),t=await e.getRequest(m),r=t?.acceptanceCriteria||[]
1434
+ if(r.length>0){const e=(0,n.buildCriteriaKeyMap)(r),t=k.filter((t=>"verified"===t.status&&!!t.key&&e.has(t.key))).map((t=>({criterionId:e.get(t.key).id,evidence:t.evidence,testRunId:I})))
1435
+ t.length>0&&await c.proxyPost(`/api/projects/${a.projectId}/requests/${m}/verify-criteria`,{verifications:t,dimension:"test"})}}catch(e){l.warn("Failed to persist test-dimension criteria verdicts",{error:(0,r.getErrorMessage)(e)})}b.worktreePath&&fetch((0,i.getActionServerBaseUrl)()+"/api/release-pool-slot",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({worktreePath:b.worktreePath}),signal:AbortSignal.timeout(5e3)}).catch((e=>l.debug("release-pool-slot fire-and-forget failed (non-fatal)",{error:(0,r.getErrorMessage)(e)})))
1436
+ const v="passed"===e.status?"\u2705":"failed"===e.status?"\u274c":"\u26a0\ufe0f"
1437
+ let C=!1,_=!1
1438
+ try{const t=c.getRequestClient()
1439
+ if("passed"===e.status){const e=await t.updateRequest(m,{status:"ready_for_merge"})
1440
+ _="ready_for_review"===e?.status,l.info("Tests passed",{requestId:m,status:e?.status})}else{const{escalate:e,failedRounds:r}=(0,o.decideTestRoundCap)(w,I,o.TEST_MAX_ROUNDS)
1441
+ e?(await t.updateRequest(m,{status:"ready_for_review"}),C=!0,l.warn("Test gate cap reached; escalating to human review",{requestId:m,failedRounds:r,cap:o.TEST_MAX_ROUNDS})):(await t.updateRequest(m,{status:"in_progress"}),l.info("Tests failed, set request back to in_progress",{requestId:m,failedRounds:r}))}}catch(e){l.warn("Failed to update request status after test results",{requestId:m,error:(0,r.getErrorMessage)(e)})}const E="passed"===e.status?"\n\nAll tests passed \u2014 you can now call `merge` to finalize this request.":"\n\nTests failed \u2014 fix the issues and call `complete` again.",S=`${v} Test run ${e.status} for request ${m}\n\n${e.results.summary}${C?"\n\nTests failed repeatedly; escalated for human review. Stop here; do not call complete again.":_?"\n\nAll tests passed. Human sign-off is required, so the request now awaits a maintainer to finalize in the UI. Stop here; do not call merge.":E}`
1442
+ try{const t=await(0,s.handleSessionMessageOperations)({operation:"send",type:"report",status:"passed"===e.status?"success":"failure",summary:S,details:e.results.commands?.map((e=>`${"passed"===e.status?"\u2705":"\u274c"} ${e.name}: ${e.status}${e.diagnosis?" - "+e.diagnosis:""}`)).join("\n"),finish:!0},a,l,d,c,u),r=t?.content?.[0]?.text||"unknown",n=t?.isError
1443
+ return h({success:!0}),{content:[{type:"text",text:`Test run for request ${m} reported as **${e.status}**.${n?" Parent notification FAILED: "+r:" Parent session notified."}\n\n${e.results.summary}`}]}}catch(t){return h({success:!0}),{content:[{type:"text",text:`Test run for request ${m} reported as **${e.status}** but failed to notify parent: ${(0,r.getErrorMessage)(t)}\n\n${e.results.summary}`}]}}}catch(e){return h({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to report test results: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingReference=async function(e,t,r,n,o){return o.startTimer("tool_execution_time")({success:!0}),{content:[{type:"text",text:'# Test Process Design Guide\n\n## Overview\n\nA test process defines how to verify code changes for a project. It consists of:\n- **Setup**: Commands to prepare the environment (install deps, link node_modules, etc.) \u2014 runs once per worktree\n- **Commands**: The actual test commands (build, lint, test, etc.) \u2014 the verification steps\n\n## Command Properties\n\n- **name**: Human-readable name (e.g., "Build", "Unit Tests")\n- **command**: Shell command to run (e.g., `npm test`)\n- **required** (default: true): If a required command fails, the request won\'t be merged\n- **timeout** (default: 300s): Maximum execution time. Exit code 0 = pass, non-zero = fail.\n\n## Scope (Monorepo Support)\n\nSet `scope` to a path prefix to limit when a process runs:\n- `scope: "packages/api/"` \u2192 only runs when files under packages/api/ change\n- omit `scope` \u2192 runs for all changes (global process)\n\nMultiple active processes per project are supported.\n\n## Common Patterns\n\n### Node.js / TypeScript\n```json\n{\n "setup": ["ln -s ../../node_modules node_modules"],\n "commands": [\n { "name": "Build", "command": "npm run build" },\n { "name": "Lint", "command": "npm run lint", "required": false },\n { "name": "Unit Tests", "command": "npm test" }\n ]\n}\n```\n\n### Python\n```json\n{\n "setup": ["pip install -e \'.[dev]\'"],\n "commands": [\n { "name": "Type Check", "command": "mypy src/" },\n { "name": "Lint", "command": "ruff check .", "required": false },\n { "name": "Tests", "command": "pytest" }\n ]\n}\n```\n\n### Go\n```json\n{\n "setup": ["go mod download"],\n "commands": [\n { "name": "Build", "command": "go build ./..." },\n { "name": "Vet", "command": "go vet ./...", "required": false },\n { "name": "Tests", "command": "go test ./..." }\n ]\n}\n```\n\n### Rust\n```json\n{\n "setup": ["cargo fetch"],\n "commands": [\n { "name": "Build", "command": "cargo build" },\n { "name": "Clippy", "command": "cargo clippy -- -D warnings", "required": false },\n { "name": "Tests", "command": "cargo test" }\n ]\n}\n```\n\n## Project Analysis Checklist\n\nWhen setting up testing for a new project, check for:\n1. **package.json** \u2192 Node.js/TypeScript (check "scripts" for test/build/lint commands)\n2. **Cargo.toml** \u2192 Rust\n3. **go.mod** \u2192 Go\n4. **pyproject.toml** / **requirements.txt** \u2192 Python\n5. **Makefile** \u2192 Custom build system (check for test/build targets)\n6. **.github/workflows/** \u2192 Existing CI configuration (great source of test commands)\n7. **.gitlab-ci.yml** \u2192 GitLab CI\n8. **tsconfig.json** \u2192 TypeScript type checking\n9. **.eslintrc** / **biome.json** \u2192 Linting configuration\n10. **vitest.config.ts** / **jest.config.ts** / **pytest.ini** \u2192 Test framework configuration'}]}},exports.handleTestingProcessDelete=async function(e,t,n,o,s){const i=s.startTimer("tool_execution_time")
1444
+ try{return await n.proxyDelete(`/api/projects/${t.projectId}/test-processes/${e.processId}`),o.info("Deleted test process",{processId:e.processId}),i({success:!0}),{content:[{type:"text",text:`Deleted test process ${e.processId}.`}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to delete test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTests=async function(e,n,o,s,c,l){const d=c.startTimer("tool_execution_time")
1445
+ try{if("list"===e.operation){const e=await o.proxyGet(`/api/projects/${n.projectId}/test-processes`),t=e?.data||e||[],r=Array.isArray(t)?t:[]
1437
1446
  if(0===r.length)return d({success:!0}),{content:[{type:"text",text:'No test processes registered for this project. Use `tests` with `operation: "setup"` to spawn an interactive setup session that helps configure one.'}]}
1438
- const o=r.map((e=>`**${e.name}** (${e.id})${e.scope?` [scope: ${e.scope}]`:""}\n ${e.isActive?"\u2705 Active":"\u23f8 Inactive"} \u2014 ${e.config?.commands?.length||0} commands${e.description?"\n "+e.description:""}`)).join("\n\n")
1439
- return d({success:!0}),{content:[{type:"text",text:`## Test Processes (${r.length})\n\n${o}`}]}}if("run"===e.operation){let c,u=[]
1440
- try{const e=await i.proxyGet(`/api/projects/${n.projectId}/test-processes`),t=e?.data||e||[]
1441
- u=Array.isArray(t)?t:[]}catch(e){a.warn("tests run: existence check failed; proceeding optimistically",{error:(0,r.getErrorMessage)(e)})}if(0===u.length)return d({success:!1,error:"no_process"}),{content:[{type:"text",text:'No test process registered for this project. Run `tests` with `operation: "setup"` first to bootstrap one.'}],isError:!0}
1447
+ const s=r.map((e=>`**${e.name}** (${e.id})${e.scope?` [scope: ${e.scope}]`:""}\n ${e.isActive?"\u2705 Active":"\u23f8 Inactive"} \u2014 ${e.config?.commands?.length||0} commands${e.description?"\n "+e.description:""}`)).join("\n\n")
1448
+ return d({success:!0}),{content:[{type:"text",text:`## Test Processes (${r.length})\n\n${s}`}]}}if("run"===e.operation){let c,u=[]
1449
+ try{const e=await o.proxyGet(`/api/projects/${n.projectId}/test-processes`),t=e?.data||e||[]
1450
+ u=Array.isArray(t)?t:[]}catch(e){s.warn("tests run: existence check failed; proceeding optimistically",{error:(0,r.getErrorMessage)(e)})}if(0===u.length)return d({success:!1,error:"no_process"}),{content:[{type:"text",text:'No test process registered for this project. Run `tests` with `operation: "setup"` first to bootstrap one.'}],isError:!0}
1442
1451
  const h=e.process
1443
1452
  if(h){const e=h.toLowerCase(),t=u.find((t=>t.id===h||"string"==typeof t.name&&t.name.toLowerCase()===e))
1444
1453
  if(!t){const e=u.map((e=>`- ${e.name} (${e.id})`)).join("\n")
1445
- return d({success:!1,error:"process_not_found"}),{content:[{type:"text",text:`No test process matched "${h}". Available:\n${e}`}],isError:!0}}c=[t.id]}const p=l?.sessionId,{resolveRequestIdOrError:g}=await Promise.resolve().then(t.bind(t,9032)),m=await g(i,a,p,"tests run requires an associated request \u2014 your session is not currently linked to one. Open or create a request, then try again.")
1454
+ return d({success:!1,error:"process_not_found"}),{content:[{type:"text",text:`No test process matched "${h}". Available:\n${e}`}],isError:!0}}c=[t.id]}const p=l?.sessionId,{resolveRequestIdOrError:g}=await Promise.resolve().then(t.bind(t,9032)),m=await g(o,s,p,"tests run requires an associated request \u2014 your session is not currently linked to one. Open or create a request, then try again.")
1446
1455
  if(!m.ok)return d({success:!1,error:"no_request"}),m.errorResult
1447
1456
  const f=m.requestId,y=e.name?[e.name]:void 0
1448
- try{const e=await fetch((0,o.getActionServerBaseUrl)()+"/api/run-tests-now",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:f,parentSessionId:p,testProcessIds:c,commandNames:y}),signal:AbortSignal.timeout(15e3)})
1457
+ try{const e=await fetch((0,i.getActionServerBaseUrl)()+"/api/run-tests-now",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:f,parentSessionId:p,testProcessIds:c,commandNames:y}),signal:AbortSignal.timeout(15e3)})
1449
1458
  if(!e.ok){const t=await e.text().catch((()=>""))
1450
1459
  return d({success:!1,error:"run_failed"}),{content:[{type:"text",text:`tests run failed: action server returned ${e.status}: ${t.slice(0,300)}`}],isError:!0}}const t=await e.json()
1451
1460
  if(!t.success||!t.data)return d({success:!1,error:"run_failed"}),{content:[{type:"text",text:"tests run failed: "+(t.error||"unknown error")}],isError:!0}
1452
1461
  const r=t.data.spawnedSessionIds?.[0],n=y?` (filtered to: ${y.join(", ")})`:""
1453
- return d({success:!0}),{content:[{type:"text",text:`Test run started${n}.\nSpawned ${t.data.agentsSpawned} runner(s) for ${t.data.processCount} process(es). Test runs: ${t.data.testRunIds.join(", ")}.\n\nThe auto-tester is executing the registered commands. You will receive a notification on your next turn when results are reported \u2014 continue with other work in the meantime.`}],...(0,s.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:r,agentType:"auto-tester",requestId:f,description:y?"Test run: "+y.join(", "):"Test run"})}}catch(e){return d({success:!1,error:"fetch_error"}),{content:[{type:"text",text:"tests run failed: could not reach action server: "+(0,r.getErrorMessage)(e)}],isError:!0}}}if("setup"===e.operation){let e
1454
- try{const t=await i.proxyGet(`/api/projects/${n.projectId}/test-processes`),r=t?.data||t||[],o=Array.isArray(r)?r:[]
1455
- if(o.length>0){const t=o.map((e=>`- ${e.name}${e.scope?` (scope: ${e.scope})`:""} \u2014 ${e.config?.commands?.length||0} command(s), ${e.isActive?"active":"inactive"}`)).join("\n")
1456
- e=`${o.length} test process(es) already registered for this project:\n${t}\n\nPick a distinct name AND a non-overlapping scope for the new process. Confirm with the user what they want this new process to cover before registering.`}}catch(e){a.warn("tests setup: existing-processes lookup failed; proceeding without hint",{error:(0,r.getErrorMessage)(e)})}const{sessionIdentity:c}=await Promise.resolve().then(t.bind(t,6688)),u=l?.sessionId,h=u?await c.getSessionRequestId(i,a,u):void 0
1457
- try{const t=await fetch((0,o.getActionServerBaseUrl)()+"/api/setup-tests",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h,parentSessionId:u,context:e}),signal:AbortSignal.timeout(15e3)})
1462
+ return d({success:!0}),{content:[{type:"text",text:`Test run started${n}.\nSpawned ${t.data.agentsSpawned} runner(s) for ${t.data.processCount} process(es). Test runs: ${t.data.testRunIds.join(", ")}.\n\nThe auto-tester is executing the registered commands. You will receive a notification on your next turn when results are reported \u2014 continue with other work in the meantime.`}],...(0,a.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:r,agentType:"auto-tester",requestId:f,description:y?"Test run: "+y.join(", "):"Test run"})}}catch(e){return d({success:!1,error:"fetch_error"}),{content:[{type:"text",text:"tests run failed: could not reach action server: "+(0,r.getErrorMessage)(e)}],isError:!0}}}if("setup"===e.operation){let e
1463
+ try{const t=await o.proxyGet(`/api/projects/${n.projectId}/test-processes`),r=t?.data||t||[],s=Array.isArray(r)?r:[]
1464
+ if(s.length>0){const t=s.map((e=>`- ${e.name}${e.scope?` (scope: ${e.scope})`:""} \u2014 ${e.config?.commands?.length||0} command(s), ${e.isActive?"active":"inactive"}`)).join("\n")
1465
+ e=`${s.length} test process(es) already registered for this project:\n${t}\n\nPick a distinct name AND a non-overlapping scope for the new process. Confirm with the user what they want this new process to cover before registering.`}}catch(e){s.warn("tests setup: existing-processes lookup failed; proceeding without hint",{error:(0,r.getErrorMessage)(e)})}const{sessionIdentity:c}=await Promise.resolve().then(t.bind(t,6688)),u=l?.sessionId,h=u?await c.getSessionRequestId(o,s,u):void 0
1466
+ try{const t=await fetch((0,i.getActionServerBaseUrl)()+"/api/setup-tests",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h,parentSessionId:u,context:e}),signal:AbortSignal.timeout(15e3)})
1458
1467
  if(!t.ok){const e=await t.text().catch((()=>""))
1459
1468
  return d({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:`tests setup failed: action server returned ${t.status}: ${e.slice(0,300)}`}],isError:!0}}const r=await t.json()
1460
- return r.success&&r.data?(d({success:!0}),{content:[{type:"text",text:`Test setup session started.\nWorktree: ${r.data.worktreePath} (branch: ${r.data.branch})\n\nThe test-architect agent is now interviewing the user. You will receive a notification on your next turn when configuration completes \u2014 continue with other work in the meantime.`}],...(0,s.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:r.data.sessionId,agentType:"test-architect",requestId:h??void 0,description:"Test setup"})}):(d({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"tests setup failed: "+(r.error||"unknown error")}],isError:!0})}catch(e){return d({success:!1,error:"fetch_error"}),{content:[{type:"text",text:"tests setup failed: could not reach action server: "+(0,r.getErrorMessage)(e)}],isError:!0}}}return d({success:!1,error:"unknown_operation"}),{content:[{type:"text",text:"Unknown operation: "+e.operation}],isError:!0}}catch(e){return d({success:!1,error:"api_error"}),{content:[{type:"text",text:"tests failed: "+(0,r.getErrorMessage)(e)}],isError:!0}}}
1461
- const r=t(8824),n=t(9072),o=t(9414),s=t(2272),i=t(1375)},2085(e,exports,t){"use strict"
1469
+ return r.success&&r.data?(d({success:!0}),{content:[{type:"text",text:`Test setup session started.\nWorktree: ${r.data.worktreePath} (branch: ${r.data.branch})\n\nThe test-architect agent is now interviewing the user. You will receive a notification on your next turn when configuration completes \u2014 continue with other work in the meantime.`}],...(0,a.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:r.data.sessionId,agentType:"test-architect",requestId:h??void 0,description:"Test setup"})}):(d({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"tests setup failed: "+(r.error||"unknown error")}],isError:!0})}catch(e){return d({success:!1,error:"fetch_error"}),{content:[{type:"text",text:"tests setup failed: could not reach action server: "+(0,r.getErrorMessage)(e)}],isError:!0}}}return d({success:!1,error:"unknown_operation"}),{content:[{type:"text",text:"Unknown operation: "+e.operation}],isError:!0}}catch(e){return d({success:!1,error:"api_error"}),{content:[{type:"text",text:"tests failed: "+(0,r.getErrorMessage)(e)}],isError:!0}}}
1470
+ const r=t(8824),n=t(5454),o=t(7499),s=t(9072),i=t(9414),a=t(2272),c=t(1375)},2085(e,exports,t){"use strict"
1462
1471
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.getRequestTimelineForFile=async function(e,t,s,i){let a=`# File History: ${t}\n\n`
1463
1472
  a+="**Context**: Main branch (request-level view)\n\n"
1464
1473
  try{const o=await e.log({file:t,n:100})
@@ -1612,10 +1621,10 @@ f.info("Initial branch context",{initialBranch:S.branch,initialRequestId:S.reque
1612
1621
  const T=[]
1613
1622
  for(const e of u){const{normalizedPath:t,fullPath:n}=l.validateAndNormalizePath(e.source,r),{normalizedPath:o,fullPath:s}=l.validateAndNormalizePath(e.destination,r)
1614
1623
  T.push({source:t,destination:o,fullSource:n,fullDestination:s})}I=l.FileOperationPhase.TASK_VALIDATION,g.incrementCounter("move_files_phase",1,{phase:I})
1615
- const q=await l.validateTaskId(E,T[0].source,n,f,g,k.metricsPrefix),P=q?.requestId,j=await l.validateWorkItemStates(E,T[0].source,n,s,r,f,g,k.metricsPrefix),x=await l.autoPromoteTaskStatus(E,q.status,P,j.request?.status,T[0].source,n,s,f,g,k.metricsPrefix)
1616
- x.taskPromoted&&f.info(`Auto-promoted task ${E} (${q.title||"Untitled"}) to in_progress for file operations`,{taskId:E,taskTitle:q.title,previousStatus:q.status,newStatus:x.newTaskStatus,operationCount:u.length}),x.requestPromoted&&f.info(`Auto-promoted request ${P} to in_progress for file operations`,{requestId:P,previousStatus:j.request?.status,newStatus:x.newRequestStatus,triggeringTaskId:E})
1617
- const R=await l.ensureCorrectWorkspaceContext({taskPromoted:x.taskPromoted,requestId:P,context:r,operation:"move-files",logger:f,metrics:g,metricsPrefix:k.metricsPrefix})
1618
- if(!R.success)throw Error(R.error)
1624
+ const q=await l.validateTaskId(E,T[0].source,n,f,g,k.metricsPrefix),P=q?.requestId,R=await l.validateWorkItemStates(E,T[0].source,n,s,r,f,g,k.metricsPrefix),x=await l.autoPromoteTaskStatus(E,q.status,P,R.request?.status,T[0].source,n,s,f,g,k.metricsPrefix)
1625
+ x.taskPromoted&&f.info(`Auto-promoted task ${E} (${q.title||"Untitled"}) to in_progress for file operations`,{taskId:E,taskTitle:q.title,previousStatus:q.status,newStatus:x.newTaskStatus,operationCount:u.length}),x.requestPromoted&&f.info(`Auto-promoted request ${P} to in_progress for file operations`,{requestId:P,previousStatus:R.request?.status,newStatus:x.newRequestStatus,triggeringTaskId:E})
1626
+ const j=await l.ensureCorrectWorkspaceContext({taskPromoted:x.taskPromoted,requestId:P,context:r,operation:"move-files",logger:f,metrics:g,metricsPrefix:k.metricsPrefix})
1627
+ if(!j.success)throw Error(j.error)
1619
1628
  let M={hadUserMods:!1,userModsCommitHash:void 0}
1620
1629
  if(r.git)try{f.debug("[MOVE-FILES] Checking for user changes before move operations")
1621
1630
  const e=await(0,h.commitUserChangesBeforeToolOperation)(r.git,"move-files",{requestId:P,taskId:E,workspaceManager:r.workspaceManager},f,g)
@@ -1653,7 +1662,7 @@ exports.def={name:"quick_manage",description:"WHEN TO USE: trivial single-file e
1653
1662
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.buildTaskEndpoint=function(e,t,r){const n=r?"/"+r.replace(/^\//,""):""
1654
1663
  return`/api/projects/${e}/tasks/${encodeURIComponent(t)}${n}`}},2462(e,exports,t){"use strict"
1655
1664
  Object.defineProperty(exports,"__esModule",{value:!0})
1656
- const r=t(2245),n=t(9080),o=t(5692),s=t(9030),i=t(2903),a=t(4607),c=t(9499),l=t(6770),d=t(8045),u=t(9697),h=t(9196),p=t(5459),g=t(4805),m=t(260),f=t(2283),y=t(4165),w=t(7834),b=t(9373),I=t(6111),k=t(700),v=t(1535),C=t(7257),_=t(9230),E=t(4150),S=t(6473),T=t(6309),q=t(4921),P=t(8850),j=t(3556),x=t(1391),R=t(6315),M=t(7713),O=t(6252),$=t(577),D=t(4206),A=t(213),F=t(7250),N=t(2425),z=t(1540),L=t(5767),H=t(4973),B=t(509),U=t(7613),G=t(8456),W=t(3322),V=t(5247),Q=t(8849),K=t(542);(0,r.registerTool)(n.def),(0,r.registerTool)(o.def),(0,r.registerTool)(s.def),(0,r.registerTool)(i.def),(0,r.registerTool)(a.def),(0,r.registerTool)(c.def),(0,r.registerTool)(l.def),(0,r.registerTool)(d.def),(0,r.registerTool)(u.def),(0,r.registerTool)(h.def),(0,r.registerTool)(p.def),(0,r.registerTool)(g.def),(0,r.registerTool)(m.def),(0,r.registerTool)(f.def),(0,r.registerTool)(y.def),(0,r.registerTool)(w.def),(0,r.registerTool)(b.def),(0,r.registerTool)(I.def),(0,r.registerTool)(k.def),(0,r.registerTool)(v.def),(0,r.registerTool)(E.def),(0,r.registerTool)(C.def),(0,r.registerTool)(_.def),(0,r.registerTool)(S.def),(0,r.registerTool)(T.def),(0,r.registerTool)(q.def),(0,r.registerTool)(P.def),(0,r.registerTool)(j.def),(0,r.registerTool)(x.def),(0,r.registerTool)(R.def),(0,r.registerTool)(M.def),(0,r.registerTool)(O.def),(0,r.registerTool)($.def),(0,r.registerTool)(D.def),(0,r.registerTool)(A.def),(0,r.registerTool)(F.def),(0,r.registerTool)(N.def),(0,r.registerTool)(z.def),(0,r.registerTool)(L.def),(0,r.registerTool)(H.def),(0,r.registerTool)(B.def),(0,r.registerTool)(U.def),(0,r.registerTool)(G.def),(0,r.registerTools)(W.metaDefs),(0,r.registerTools)(V.testingDefs),(0,r.registerTools)(Q.checkingDefs),(0,r.registerTools)(K.debugDefs),(0,t(2245).freezeRegistry)()},2478(e,exports){"use strict"
1665
+ const r=t(2245),n=t(9080),o=t(5692),s=t(9030),i=t(2903),a=t(4607),c=t(9499),l=t(6770),d=t(8045),u=t(9697),h=t(9196),p=t(5459),g=t(4805),m=t(260),f=t(2283),y=t(4165),w=t(7834),b=t(9373),I=t(6111),k=t(700),v=t(5837),C=t(1535),_=t(7257),E=t(9230),S=t(4150),T=t(6473),q=t(6309),P=t(4921),R=t(8850),x=t(3556),j=t(1391),M=t(6315),O=t(7713),$=t(6252),D=t(577),A=t(4206),F=t(213),N=t(7250),z=t(2425),L=t(1540),H=t(5767),B=t(4973),U=t(509),G=t(7613),W=t(8456),V=t(3322),Q=t(5247),K=t(8849),J=t(542);(0,r.registerTool)(n.def),(0,r.registerTool)(o.def),(0,r.registerTool)(s.def),(0,r.registerTool)(i.def),(0,r.registerTool)(a.def),(0,r.registerTool)(c.def),(0,r.registerTool)(l.def),(0,r.registerTool)(d.def),(0,r.registerTool)(u.def),(0,r.registerTool)(h.def),(0,r.registerTool)(p.def),(0,r.registerTool)(g.def),(0,r.registerTool)(m.def),(0,r.registerTool)(f.def),(0,r.registerTool)(y.def),(0,r.registerTool)(w.def),(0,r.registerTool)(b.def),(0,r.registerTool)(I.def),(0,r.registerTool)(k.def),(0,r.registerTool)(v.def),(0,r.registerTool)(C.def),(0,r.registerTool)(S.def),(0,r.registerTool)(_.def),(0,r.registerTool)(E.def),(0,r.registerTool)(T.def),(0,r.registerTool)(q.def),(0,r.registerTool)(P.def),(0,r.registerTool)(R.def),(0,r.registerTool)(x.def),(0,r.registerTool)(j.def),(0,r.registerTool)(M.def),(0,r.registerTool)(O.def),(0,r.registerTool)($.def),(0,r.registerTool)(D.def),(0,r.registerTool)(A.def),(0,r.registerTool)(F.def),(0,r.registerTool)(N.def),(0,r.registerTool)(z.def),(0,r.registerTool)(L.def),(0,r.registerTool)(H.def),(0,r.registerTool)(B.def),(0,r.registerTool)(U.def),(0,r.registerTool)(G.def),(0,r.registerTool)(W.def),(0,r.registerTools)(V.metaDefs),(0,r.registerTools)(Q.testingDefs),(0,r.registerTools)(K.checkingDefs),(0,r.registerTools)(J.debugDefs),(0,t(2245).freezeRegistry)()},2478(e,exports){"use strict"
1657
1666
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.requestConfig=void 0,exports.areTestsRequiredForCompletion=function(){return exports.requestConfig.requireTestsForCompletion},exports.setTestsRequiredForCompletion=function(e){exports.requestConfig.requireTestsForCompletion=e},exports.getCompletionMode=function(e){return e&&["auto-merge","ask-me","test-by-default","always-test"].includes(e)?e:"test-by-default"},exports.getReviewMode=function(e){return e&&["auto-finalize","manual-review"].includes(e)?e:"auto-finalize"},exports.getApprovalMode=function(e){return e&&["none","require-approval"].includes(e)?e:"none"},exports.requestConfig={requireTestsForCompletion:!1}},2537(e,exports,t){"use strict"
1658
1667
  async function r(e,t,r){if(v.has(r)&&C>=t){if(0===t)return 0
1659
1668
  try{return(await y.default.readFile(e,"utf-8")).split("\n").length}catch{return}}}function n(e,t,r){const n="depth"in e,o=n?e:r,s=n?e.isDirectory:e.isDirectory(),a=e.name,c=s?"[\u{1f4c1} DIR]":"[\u{1f4c4} FILE]"
@@ -2205,14 +2214,14 @@ try{const C=e,{operations:_,comment:E}=C,{enforceActiveTask:S}=await Promise.res
2205
2214
  if("errorResult"in T)return T.errorResult
2206
2215
  const{taskId:q}=T
2207
2216
  if(y.info("Replacing strings across files and documents",{taskId:q,operationCount:_.length,comment:E}),!_||0===_.length)throw new u.ValidationError("At least one operation is required")
2208
- const P=_.filter((e=>e.path)),j=_.filter((e=>e.documentId))
2209
- y.info("Operation breakdown",{fileOperations:P.length,documentOperations:j.length})
2217
+ const P=_.filter((e=>e.path)),R=_.filter((e=>e.documentId))
2218
+ y.info("Operation breakdown",{fileOperations:P.length,documentOperations:R.length})
2210
2219
  const x=[]
2211
2220
  for(const e of P){const{normalizedPath:t,fullPath:n}=d.validateAndNormalizePath(e.path,r)
2212
- x.push({type:"file",path:t,fullPath:n,replacements:e.replacements,mode:e.mode||"unique"})}const R=[]
2213
- if(j.length>0&&!r.documentFileManager)throw Error("Document file manager not available - cannot process document operations")
2214
- for(const e of j)R.push({type:"document",documentId:e.documentId,replacements:e.replacements,mode:e.mode||"unique"})
2215
- if(R.length>0&&r.apiClient){const e=R.map((e=>e.documentId))
2221
+ x.push({type:"file",path:t,fullPath:n,replacements:e.replacements,mode:e.mode||"unique"})}const j=[]
2222
+ if(R.length>0&&!r.documentFileManager)throw Error("Document file manager not available - cannot process document operations")
2223
+ for(const e of R)j.push({type:"document",documentId:e.documentId,replacements:e.replacements,mode:e.mode||"unique"})
2224
+ if(j.length>0&&r.apiClient){const e=j.map((e=>e.documentId))
2216
2225
  try{await(0,g.ensureDocumentsNotLocked)(e,r.apiClient,y,c,"replace-strings-documents")}catch(e){if(e instanceof u.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
2217
2226
  throw e}}k=d.FileOperationPhase.TASK_VALIDATION,c.incrementCounter("replace_strings_phase",1,{phase:k})
2218
2227
  const M=x.length>0?x[0].path:"document-operation",O=await d.validateTaskId(q,M,o,y,c,v.metricsPrefix),$=O?.requestId
@@ -2224,9 +2233,9 @@ const F=await d.ensureCorrectWorkspaceContext({taskPromoted:A.taskPromoted,reque
2224
2233
  if(!F.success)throw Error(F.error)
2225
2234
  if(x.length>0){y.debug("Re-validating file paths with correct workspace context",{operationCount:x.length,workspaceBasePath:r.workspaceManager?.getCurrentContext(r.currentSessionId).basePath})
2226
2235
  for(let e=0;e<x.length;e++){const t=x[e],n=P[e],{normalizedPath:o,fullPath:s}=d.validateAndNormalizePath(n.path,r)
2227
- x[e]={...t,path:o,fullPath:s}}}if(R.length>0&&$)try{await(0,h.validateRequestStatusForDocumentOperation)($,i,y,c,"replace-strings-document")}catch(e){if(e instanceof u.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
2236
+ x[e]={...t,path:o,fullPath:s}}}if(j.length>0&&$)try{await(0,h.validateRequestStatusForDocumentOperation)($,i,y,c,"replace-strings-document")}catch(e){if(e instanceof u.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
2228
2237
  throw e}k=d.FileOperationPhase.RECORD_CREATION,c.incrementCounter("replace_strings_phase",1,{phase:k})
2229
- const N=[...x.map((e=>({type:"file",path:e.path,replacements:e.replacements,mode:e.mode}))),...R.map((e=>({type:"document",documentId:e.documentId,replacements:e.replacements,mode:e.mode})))],{toolRecordId:z}=await d.createFileOperationRecords(q,$,r.projectId,v.recordType,M,{operationCount:_.length,fileOperations:x.length,documentOperations:R.length,operations:N,userComment:E},o,s,y,c,v.metricsPrefix,f)
2238
+ const N=[...x.map((e=>({type:"file",path:e.path,replacements:e.replacements,mode:e.mode}))),...j.map((e=>({type:"document",documentId:e.documentId,replacements:e.replacements,mode:e.mode})))],{toolRecordId:z}=await d.createFileOperationRecords(q,$,r.projectId,v.recordType,M,{operationCount:_.length,fileOperations:x.length,documentOperations:j.length,operations:N,userComment:E},o,s,y,c,v.metricsPrefix,f)
2230
2239
  if(b=z,I=z,k=v.fileOperationPhase,c.incrementCounter("replace_strings_phase",1,{phase:k}),x.length>0&&r.git)try{y.debug("[REPLACE-STRINGS] Checking for user changes before replace operations")
2231
2240
  const e=await(0,p.commitUserChangesBeforeToolOperation)(r.git,"replace-strings",{requestId:$,taskId:q,workspaceManager:r.workspaceManager},y,c)
2232
2241
  e.userChangesCommitted&&(y.debug("[REPLACE-STRINGS] User changes committed before replace operations",{commitHash:e.commitHash?.substring(0,8),filesCommitted:e.filesCommitted}),y.info("User changes committed before replace-strings operation",{taskId:q,fileOperationsCount:x.length,userCommitHash:e.commitHash?.substring(0,8),userFilesCommitted:e.filesCommitted}))}catch(e){y.debug("[REPLACE-STRINGS] Pre-tool commit failed, continuing with replacements",{error:e instanceof Error?e.message:e+""}),y.warn("Pre-tool user changes commit failed, continuing with replace operation",{taskId:q,fileOperationsCount:x.length,error:e instanceof Error?e.message:e+""})}y.debug("[REPLACE-STRINGS] Proceeding with file operations",{fileCount:x.length})
@@ -2235,20 +2244,20 @@ let H=0,B=0
2235
2244
  for(const e of x){const t={type:"file",path:e.path,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0}
2236
2245
  try{if(await l.default.access(e.fullPath),!(await l.default.stat(e.fullPath)).isFile())throw Error("Path is not a file: "+e.path)
2237
2246
  const r=await l.default.readFile(e.fullPath,"utf8"),{newContent:o,results:s}=await n(r,e.replacements,e.mode)
2238
- t.replacements=s,t.successfulReplacements=s.filter((e=>e.success)).length,t.failedReplacements=s.filter((e=>!e.success)).length,o!==r?(await l.default.writeFile(e.fullPath,o,"utf8"),t.success=!0,B++,y.info("File updated successfully",{path:e.path,successfulReplacements:t.successfulReplacements,failedReplacements:t.failedReplacements})):0===t.failedReplacements&&(t.success=!0),H+=t.failedReplacements}catch(r){t.error=(0,m.getErrorMessage)(r),t.failedReplacements=e.replacements.length,H+=t.failedReplacements,y.error("File operation failed",{path:e.path,error:t.error})}L.push(t)}for(const e of R){const t={type:"document",documentId:e.documentId,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0}
2247
+ t.replacements=s,t.successfulReplacements=s.filter((e=>e.success)).length,t.failedReplacements=s.filter((e=>!e.success)).length,o!==r?(await l.default.writeFile(e.fullPath,o,"utf8"),t.success=!0,B++,y.info("File updated successfully",{path:e.path,successfulReplacements:t.successfulReplacements,failedReplacements:t.failedReplacements})):0===t.failedReplacements&&(t.success=!0),H+=t.failedReplacements}catch(r){t.error=(0,m.getErrorMessage)(r),t.failedReplacements=e.replacements.length,H+=t.failedReplacements,y.error("File operation failed",{path:e.path,error:t.error})}L.push(t)}for(const e of j){const t={type:"document",documentId:e.documentId,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0}
2239
2248
  try{const o=await r.documentFileManager.getDocumentAsFile(e.documentId),s=r.documentFileManager.getCachedDocumentInfo(e.documentId)
2240
2249
  t.filename=s?.filename
2241
2250
  const i=await l.default.readFile(o,"utf8"),{newContent:a,results:c}=await n(i,e.replacements,e.mode)
2242
- t.replacements=c,t.successfulReplacements=c.filter((e=>e.success)).length,t.failedReplacements=c.filter((e=>!e.success)).length,a!==i?(await l.default.writeFile(o,a,"utf8"),r.documentFileManager.markForSync(e.documentId),t.success=!0,B++,y.info("Document updated successfully",{documentId:e.documentId,filename:t.filename,successfulReplacements:t.successfulReplacements,failedReplacements:t.failedReplacements})):0===t.failedReplacements&&(t.success=!0),H+=t.failedReplacements}catch(r){t.error=(0,m.getErrorMessage)(r),t.failedReplacements=e.replacements.length,H+=t.failedReplacements,y.error("Document operation failed",{documentId:e.documentId,error:t.error})}L.push(t)}if(R.length>0)try{for(const e of R){const t=L.find((t=>t.documentId===e.documentId))
2251
+ t.replacements=c,t.successfulReplacements=c.filter((e=>e.success)).length,t.failedReplacements=c.filter((e=>!e.success)).length,a!==i?(await l.default.writeFile(o,a,"utf8"),r.documentFileManager.markForSync(e.documentId),t.success=!0,B++,y.info("Document updated successfully",{documentId:e.documentId,filename:t.filename,successfulReplacements:t.successfulReplacements,failedReplacements:t.failedReplacements})):0===t.failedReplacements&&(t.success=!0),H+=t.failedReplacements}catch(r){t.error=(0,m.getErrorMessage)(r),t.failedReplacements=e.replacements.length,H+=t.failedReplacements,y.error("Document operation failed",{documentId:e.documentId,error:t.error})}L.push(t)}if(j.length>0)try{for(const e of j){const t=L.find((t=>t.documentId===e.documentId))
2243
2252
  t?.success&&await r.documentFileManager.syncDocument(e.documentId)}}catch(e){y.warn("Some documents may not have synced properly",{error:e})}const U=await d.handleMultiFileGitCommit(q,x,L,B,{operationType:"Replace strings",toolRecordId:I,successField:"successfulReplacements",logger:y,metrics:c,metricsPrefix:"replace_strings"},r),G=d.processMultiFileOperationResults(L,"successfulReplacements"),{successfulOperations:W,failedOperations:V,totalSuccessfulChanges:Q,overallSuccess:K,successfulFiles:J,failedFiles:Y}=G
2244
- try{await s.updateToolRecord(I,{metadata:{path:x.length>0?x[0].path:"document:"+R[0]?.documentId,status:K?"completed":"failed",error:K?void 0:V+" operations failed",totalOperations:L.length,successfulFiles:J,failedFiles:Y,operations:N,totalSuccessfulReplacements:Q,totalFailedReplacements:H,operationsWithChanges:B,processingTime:Date.now()-w,userComment:E,...x.length>0&&U.success&&U.commitHash&&{commitHash:U.commitHash,previousCommitHash:U.previousCommitHash||void 0}},note:K?`String replacement completed: ${W}/${L.length} operations successful${x.length>0?" (committed to git)":""}`:`String replacement partially failed: ${V}/${L.length} operations failed`})}catch(e){y.warn("Failed to update tool record",{recordError:e})}d.formatMultiFileToolResponse("String Replacement",{successfulOperations:W,failedOperations:V,totalSuccessfulChanges:Q,overallSuccess:K,successfulFiles:J,failedFiles:Y},U,F)
2253
+ try{await s.updateToolRecord(I,{metadata:{path:x.length>0?x[0].path:"document:"+j[0]?.documentId,status:K?"completed":"failed",error:K?void 0:V+" operations failed",totalOperations:L.length,successfulFiles:J,failedFiles:Y,operations:N,totalSuccessfulReplacements:Q,totalFailedReplacements:H,operationsWithChanges:B,processingTime:Date.now()-w,userComment:E,...x.length>0&&U.success&&U.commitHash&&{commitHash:U.commitHash,previousCommitHash:U.previousCommitHash||void 0}},note:K?`String replacement completed: ${W}/${L.length} operations successful${x.length>0?" (committed to git)":""}`:`String replacement partially failed: ${V}/${L.length} operations failed`})}catch(e){y.warn("Failed to update tool record",{recordError:e})}d.formatMultiFileToolResponse("String Replacement",{successfulOperations:W,failedOperations:V,totalSuccessfulChanges:Q,overallSuccess:K,successfulFiles:J,failedFiles:Y},U,F)
2245
2254
  let Z="## String Replacement Results\n\n"
2246
2255
  if(r.workspaceManager){const e=r.workspaceManager.getCurrentContext(r.currentSessionId)
2247
2256
  e.permanentBranch||(Z+=`**Branch:** \`${e.branchName}\`\n\n`)}if(Z+="**Summary:**\n",Z+=`- **Files processed:** ${W+V}\n`,Z+=`- **Successful operations:** ${W}\n`,Z+=`- **Failed operations:** ${V}\n`,Z+=`- **Total replacements:** ${Q} successful, ${H} failed\n\n`,U.success&&U.commitHash&&(Z+="**Git Commit:**\n",Z+=`- **Commit Hash:** ${U.commitHash.substring(0,8)}\n`,Z+=`- **Files Committed:** ${U.filesCommitted.length}\n`,U.previousCommitHash&&(Z+=`- **Previous Commit:** ${U.previousCommitHash.substring(0,8)}\n`),Z+="\n"),1===L.length){const e=L[0]
2248
2257
  if(Z+=`**Operation:** ${e.success?"\u2705 Success":"\u274c Failed"}\n`,"file"===e.type?Z+=`**File:** ${e.path}\n`:(Z+="**Document:** "+e.documentId,e.filename&&(Z+=` (${e.filename})`),Z+="\n"),e.error?Z+=`**Error:** ${e.error}\n`:(Z+=`**Replacements:** ${e.successfulReplacements} successful`,e.failedReplacements>0&&(Z+=`, ${e.failedReplacements} failed`),Z+="\n"),e.replacements.some((e=>!e.success))){Z+="**Failed Replacement Details:**\n"
2249
2258
  for(const t of e.replacements.filter((e=>!e.success)))Z+=`- "${t.old_str}" \u2192 "${t.new_str}": ${t.error}\n`}}else{Z+="**Summary:**\n",Z+=`- **Total Operations:** ${L.length}\n`,Z+=`- **Successful:** ${W}\n`,Z+=`- **Failed:** ${V}\n`,Z+=`- **Total Replacements:** ${Q} successful, ${H} failed\n\n`,Z+="**Detailed Results:**\n\n"
2250
2259
  for(const e of L){if("file"===e.type?Z+=`### File: ${e.path}\n`:(Z+="### Document: "+e.documentId,e.filename&&(Z+=` (${e.filename})`),Z+="\n"),Z+=`- **Status:** ${e.success?"\u2705 Success":"\u274c Failed"}\n`,e.error?Z+=`- **Error:** ${e.error}\n`:(Z+=`- **Replacements:** ${e.successfulReplacements} successful`,e.failedReplacements>0&&(Z+=`, ${e.failedReplacements} failed`),Z+="\n"),e.replacements.some((e=>!e.success))){Z+="- **Failed Replacement Details:**\n"
2251
- for(const t of e.replacements.filter((e=>!e.success)))Z+=` - "${t.old_str}" \u2192 "${t.new_str}": ${t.error}\n`}Z+="\n"}}R.length>0&&(Z+="\u{1f4c4} **Document Changes:** Modified documents have been automatically synced back to the tracking system.\n\n")
2260
+ for(const t of e.replacements.filter((e=>!e.success)))Z+=` - "${t.old_str}" \u2192 "${t.new_str}": ${t.error}\n`}Z+="\n"}}j.length>0&&(Z+="\u{1f4c4} **Document Changes:** Modified documents have been automatically synced back to the tracking system.\n\n")
2252
2261
  const X=Date.now()-w
2253
2262
  return y.info("Replace strings operation completed",{duration:X,success:K,totalOperations:L.length,successfulOperations:W,failedOperations:V}),c.incrementCounter("replace_strings_total",1,{success:K}),{content:[{type:"text",text:Z}],isError:!K}}catch(t){const r=Date.now()-w
2254
2263
  if(y.error("Replace strings operation failed",{error:t instanceof Error?t.stack:t+"",duration:r,phase:k}),I)try{await s.updateToolRecord(I,{metadata:{path:e?.operations?.[0]?.path||e?.operations?.[0]?.documentId,status:"failed",error:(0,m.getErrorMessage)(t),phase:k,duration:r,userComment:e?.comment},note:"String replacement failed: "+(0,m.getErrorMessage)(t)})}catch(e){}return c.incrementCounter("replace_strings_errors",1,{phase:k}),{content:[{type:"text",text:"\u274c **Replace Strings Failed:** "+(0,m.getErrorMessage)(t)}],isError:!0}}}
@@ -2357,7 +2366,8 @@ let o=null,s=null},3545(e,exports){"use strict"
2357
2366
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.asInternal=exports.asExternal=void 0,exports.asExternal=e=>e,exports.asInternal=e=>e},3556(e,exports,t){"use strict"
2358
2367
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.def=void 0
2359
2368
  const r=t(6183),n=t(8317)
2360
- exports.def={name:"request_file_history",description:"Get file change history for an entire request. Supports summary mode (net diffs) or detailed mode (chronological operations). Can be filtered to specific task. Use query_history tool instead.",schema:r.QueryHistorySchema,annotations:{title:"Request File History",readOnlyHint:!0},hidden:!0,readOnly:!0,handler(e,t){return(0,n.handleRequestFileHistory)(e,t.context,t.apiClient,t.logger,t.metrics)}}},3611(e,exports,t){"use strict"
2369
+ exports.def={name:"request_file_history",description:"Get file change history for an entire request. Supports summary mode (net diffs) or detailed mode (chronological operations). Can be filtered to specific task. Use query_history tool instead.",schema:r.QueryHistorySchema,annotations:{title:"Request File History",readOnlyHint:!0},hidden:!0,readOnly:!0,handler(e,t){return(0,n.handleRequestFileHistory)(e,t.context,t.apiClient,t.logger,t.metrics)}}},3580(e,exports){"use strict"
2370
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.isAgentReviewMode=function(e){return"agent-review"===e||"agent-review-then-signoff"===e}},3611(e,exports,t){"use strict"
2361
2371
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
2362
2372
  Object.defineProperty(exports,"__esModule",{value:!0})
2363
2373
  const n=r(t(6928)),o=r(t(7252)),s=r(t(8577)),i=t(184),a=t(1213),c=t(3444),l=t(9955),d=t(2568),u=t(3102),h=t(1152),p=t(3884),g=t(7888),m=t(4139),f=t(6800),y=t(6539),w=t(8824),b=t(6975),I=t(1253),k=process.argv.slice(2),v=k[0]||process.cwd(),C=n.default.resolve(v),_=parseInt(process.env.LOCAL_SERVER_PORT||k[1]||"3002",10),E=(0,c.createEnvLogger)({serviceName:"mcp-http-server"});(0,c.isDebugToolsEnabled)()?E.info("HTTP server initializing",{baseDir:C,preferredPort:_,debugLogging:process.env.DEBUG_LOGGING,debugLevel:process.env.DEBUG_LEVEL}):E.info("HTTP server starting",{port:_})
@@ -2369,17 +2379,17 @@ const t=new l.ProjectRegistry(E),r=new u.DefaultProjectFileSystem(E),c=n.default
2369
2379
  E.info(`Checking ${m.ALCHEMY_DIR_NAME} directory`,{alchemyPath:c}),await r.ensureDirectoryExists(c)
2370
2380
  const h=new I.TokenManager(E,n.default.join(c,"auth-token.json")),k=(await y.ClientFactory.createClient(T,E,S,{tokenManager:h,workingDir:C,alchemyPath:c})).projectClient,v=new b.ProjectClientAdapter(k,E),q=new d.ProjectSynchronizer(E,t,v,r),P=await q.getOrCreateProject(C,{name:n.default.basename(C)||"Local Project",description:"Project for "+n.default.basename(C),status:"active",metadata:{basePath:C,alchemyPath:c,bootstrapped:!0}})
2371
2381
  E.info("Using project",{projectId:P.id,projectName:P.name})
2372
- const j=(0,o.default)()
2373
- j.use((0,s.default)()),j.use(o.default.json({limit:"50mb"})),j.use(o.default.urlencoded({extended:!0})),j.use((0,p.requestLogger)(E))
2382
+ const R=(0,o.default)()
2383
+ R.use((0,s.default)()),R.use(o.default.json({limit:"50mb"})),R.use(o.default.urlencoded({extended:!0})),R.use((0,p.requestLogger)(E))
2374
2384
  try{E.info("Creating MCP server instance")
2375
2385
  const t=await(0,i.createServer)(C,E,S,T,{existingProjectId:P.id})
2376
2386
  E.info("MCP server instance created successfully")
2377
2387
  const r=t.getContext()
2378
- E.info("Got server context",{projectId:r.projectId}),t.getContext()&&t.connectionManager?(E.info("Adding connection status middleware"),j.use((0,g.addConnectionStatusInfo)(t.connectionManager))):E.warn("Unable to add connection status middleware - connection manager not available")
2388
+ E.info("Got server context",{projectId:r.projectId}),t.getContext()&&t.connectionManager?(E.info("Adding connection status middleware"),R.use((0,g.addConnectionStatusInfo)(t.connectionManager))):E.warn("Unable to add connection status middleware - connection manager not available")
2379
2389
  try{const e=P.id
2380
- E.info("Setting up project context middleware",{projectId:e}),j.use("/api",((t,r,n)=>{"GET"===t.method&&(t.query.projectId=e),["POST","PATCH","PUT"].includes(t.method)&&t.body&&(t.body.projectId=e),n()}))}catch(e){E.warn("Failed to set up project context middleware",{error:(0,w.getErrorMessage)(e)})}(0,a.registerApiRoutes)(j,t,E,S),j.use((0,p.errorHandler)(E,S)),j.listen(e,(()=>{E.info("HTTP API server listening on port "+e,{baseDir:C,port:e,nodeVersion:process.version})}))}catch(e){E.error("Failed to create or initialize MCP server",{error:e instanceof Error?e.stack:e+""})
2390
+ E.info("Setting up project context middleware",{projectId:e}),R.use("/api",((t,r,n)=>{"GET"===t.method&&(t.query.projectId=e),["POST","PATCH","PUT"].includes(t.method)&&t.body&&(t.body.projectId=e),n()}))}catch(e){E.warn("Failed to set up project context middleware",{error:(0,w.getErrorMessage)(e)})}(0,a.registerApiRoutes)(R,t,E,S),R.use((0,p.errorHandler)(E,S)),R.listen(e,(()=>{E.info("HTTP API server listening on port "+e,{baseDir:C,port:e,nodeVersion:process.version})}))}catch(e){E.error("Failed to create or initialize MCP server",{error:e instanceof Error?e.stack:e+""})
2381
2391
  let t=!1
2382
- e instanceof Error&&(t=e.message.includes("connection")||e.message.includes("timeout")||e.message.includes("network")||"ConnectionError"===e.name),S.incrementCounter("server_initialization_failure",1,{isConnectionError:t?"true":"false"}),j.use(((e,t,r)=>{t.set("X-Connection-Status","offline"),t.set("X-Server-Initialization-Failed","true"),r()})),j.use("/api",((t,r)=>{r.status(503).json({success:!1,error:{code:"CONNECTION_ERROR",message:"Server failed to initialize due to connection issues. Please check connectivity and try again.",details:{serverError:e instanceof Error?e.message:e+"",suggestions:["Check your network connection","Verify that the remote server is running","Try restarting the local server"],isConnectionIssue:!0},retryable:!0,statusCode:503}})})),E.warn("Continuing with limited functionality due to initialization error")}process.on("SIGINT",(()=>{E.info("Received SIGINT signal, shutting down..."),process.exit(0)})),process.on("SIGTERM",(()=>{E.info("Received SIGTERM signal, shutting down..."),process.exit(0)})),process.on("uncaughtException",(e=>{E.error("Uncaught exception",{error:e.stack}),process.exit(1)})),process.on("unhandledRejection",(e=>{E.error("Unhandled promise rejection",{reason:e}),process.exit(1)}))}catch(e){E.error("Failed to start HTTP API server",{error:e instanceof Error?e.stack:e+""}),process.exit(1)}}()},3626(e,exports,t){"use strict"
2392
+ e instanceof Error&&(t=e.message.includes("connection")||e.message.includes("timeout")||e.message.includes("network")||"ConnectionError"===e.name),S.incrementCounter("server_initialization_failure",1,{isConnectionError:t?"true":"false"}),R.use(((e,t,r)=>{t.set("X-Connection-Status","offline"),t.set("X-Server-Initialization-Failed","true"),r()})),R.use("/api",((t,r)=>{r.status(503).json({success:!1,error:{code:"CONNECTION_ERROR",message:"Server failed to initialize due to connection issues. Please check connectivity and try again.",details:{serverError:e instanceof Error?e.message:e+"",suggestions:["Check your network connection","Verify that the remote server is running","Try restarting the local server"],isConnectionIssue:!0},retryable:!0,statusCode:503}})})),E.warn("Continuing with limited functionality due to initialization error")}process.on("SIGINT",(()=>{E.info("Received SIGINT signal, shutting down..."),process.exit(0)})),process.on("SIGTERM",(()=>{E.info("Received SIGTERM signal, shutting down..."),process.exit(0)})),process.on("uncaughtException",(e=>{E.error("Uncaught exception",{error:e.stack}),process.exit(1)})),process.on("unhandledRejection",(e=>{E.error("Unhandled promise rejection",{reason:e}),process.exit(1)}))}catch(e){E.error("Failed to start HTTP API server",{error:e instanceof Error?e.stack:e+""}),process.exit(1)}}()},3626(e,exports,t){"use strict"
2383
2393
  function r(e){if(!e)return!1
2384
2394
  const t=e.toLowerCase().replace(/^\./,"")
2385
2395
  return u.has(t)}function n(e,t){if(r(t)){const t=e.match(/^([ \t]*)(.*?)\s*$/)
@@ -2499,19 +2509,19 @@ if(P=e.id,f.info("Created task",{taskId:P}),f.debug("Executing file operation",{
2499
2509
  else if("replace"===t)C=await(0,s.handleReplaceInFiles)({taskId:P,..._,comment:E},u,b,k,w,p,g,m)
2500
2510
  else{if("manage"!==t)throw new r.ValidationError("Unsupported operation type: "+t)
2501
2511
  C=await(0,i.handleManageFiles)({taskId:P,..._,comment:E||v},u,b,k,w,h,p,g,m)}if(C.isError){f.error("File operation failed, cleaning up request and task",{requestId:q,taskId:P})
2502
- try{await b.updateTaskStatus(P,"canceled"),f.info("Canceled task after operation failure",{taskId:P})}catch(e){f.warn("Failed to cancel task during cleanup",{taskId:P,error:e instanceof Error?e.message:e+""})}try{await w.updateRequest(q,{status:"canceled"}),f.info("Canceled request after operation failure",{requestId:q}),u.workspaceManager&&(await u.workspaceManager.cancelRequest(q),f.info("Cleaned up branch/worktree after operation failure",{requestId:q}))}catch(e){f.warn("Failed to cancel request during cleanup",{requestId:q,error:e instanceof Error?e.message:e+""})}return{content:[{type:"text",text:`\u274c Quick change failed during ${t} operation:\n\n${C.content[0].text}\n\nThe request (${q}) and task have been canceled and cleaned up.`}],isError:!0}}const T=C.content[0].text,j=T.includes("\u26a0\ufe0f Partial Success")||T.includes("\u274c Failed")||T.includes("\u274c 0 successful")
2503
- j?f.warn("File operation completed with partial failures",{requestId:q}):f.info("File operation completed successfully"),f.debug("Completing task")
2504
- try{await(0,a.handleCompleteTask)({taskId:P,summary:`Executed ${t} operation for quick fix: ${v}${j?" (partial success)":""}`},u,h,p,g),f.info("Task completed successfully",{taskId:P})}catch(e){f.error("Task completion via handleCompleteTask failed, attempting direct status update",{taskId:P,error:e instanceof Error?e.message:e+"",errorStack:e instanceof Error?e.stack:void 0})
2512
+ try{await b.updateTaskStatus(P,"canceled"),f.info("Canceled task after operation failure",{taskId:P})}catch(e){f.warn("Failed to cancel task during cleanup",{taskId:P,error:e instanceof Error?e.message:e+""})}try{await w.updateRequest(q,{status:"canceled"}),f.info("Canceled request after operation failure",{requestId:q}),u.workspaceManager&&(await u.workspaceManager.cancelRequest(q),f.info("Cleaned up branch/worktree after operation failure",{requestId:q}))}catch(e){f.warn("Failed to cancel request during cleanup",{requestId:q,error:e instanceof Error?e.message:e+""})}return{content:[{type:"text",text:`\u274c Quick change failed during ${t} operation:\n\n${C.content[0].text}\n\nThe request (${q}) and task have been canceled and cleaned up.`}],isError:!0}}const T=C.content[0].text,R=T.includes("\u26a0\ufe0f Partial Success")||T.includes("\u274c Failed")||T.includes("\u274c 0 successful")
2513
+ R?f.warn("File operation completed with partial failures",{requestId:q}):f.info("File operation completed successfully"),f.debug("Completing task")
2514
+ try{await(0,a.handleCompleteTask)({taskId:P,summary:`Executed ${t} operation for quick fix: ${v}${R?" (partial success)":""}`},u,h,p,g),f.info("Task completed successfully",{taskId:P})}catch(e){f.error("Task completion via handleCompleteTask failed, attempting direct status update",{taskId:P,error:e instanceof Error?e.message:e+"",errorStack:e instanceof Error?e.stack:void 0})
2505
2515
  try{await b.updateTaskStatus(P,"completed"),f.info("Task completed via direct updateTaskStatus call",{taskId:P})}catch(e){return f.error("Direct task completion ALSO failed - task may be stuck in in_progress",{taskId:P,error:e instanceof Error?e.message:e+"",errorStack:e instanceof Error?e.stack:void 0,operationType:t,isDocumentOperation:!!_.documentId}),{content:[{type:"text",text:`\u26a0\ufe0f Quick change operation succeeded but task completion failed.\n\n**Request:** ${q}\n**Task:** ${P}\n**Issue:** Could not mark task as completed - it remains in 'in_progress' state\n\nThe ${t} operation was successful, but we couldn't automatically complete the task. Please use the complete-task tool manually with taskId: ${P}\n\nError: `+(e instanceof Error?e.message:e+"")}],isError:!0}}}f.debug("Completing request")
2506
- const x=j?`Quick change completed with partial success: ${v}. Some replacements failed - see details below.`:"Quick change completed: "+v,R=j?t+" operation partially completed - some operations succeeded, some failed":t+" operation executed successfully"
2516
+ const x=R?`Quick change completed with partial success: ${v}. Some replacements failed - see details below.`:"Quick change completed: "+v,j=R?t+" operation partially completed - some operations succeeded, some failed":t+" operation executed successfully"
2507
2517
  let M=!1
2508
- try{const e=await(0,c.handleCompleteRequest)({requestId:q,summary:x,acceptanceEvidence:[{evidence:R}],testVerification:void 0},u,h,p,g,m)
2518
+ try{const e=await(0,c.handleCompleteRequest)({requestId:q,summary:x,acceptanceEvidence:[{evidence:j}],testVerification:void 0},u,h,p,g,m)
2509
2519
  if(e.isError)f.error("Request completion failed, attempting direct completion",{requestId:q,error:e.content[0].text})
2510
2520
  else{try{await w.updateRequest(q,{status:"completed",completionSummary:x}),f.info("Request finalized to completed",{requestId:q})}catch(e){f.warn("Failed to finalize request to completed, may be stuck at ready_for_merge",{requestId:q,error:(0,d.getErrorMessage)(e)})}M=!0,f.info("Request completed successfully",{requestId:q})}}catch(e){f.error("Request completion threw exception, attempting direct completion",{requestId:q,error:e instanceof Error?e.message:e+""})}if(!M)try{f.info("Attempting direct request completion via API",{requestId:q}),await w.updateRequest(q,{status:"completed",completionSummary:x}),M=!0,f.info("Request completed successfully via direct API call",{requestId:q})}catch(e){return f.error("Direct request completion also failed",{requestId:q,error:e instanceof Error?e.message:e+""}),{content:[{type:"text",text:`\u26a0\ufe0f Quick change operation succeeded but request completion failed.\n\n**Request:** ${q}\n**Status:** Still in 'in_progress' - please complete manually\n\nThe file operation was successful, but we couldn't automatically complete the request. Please use the complete-request tool manually.`}],isError:!0}}const O=Date.now()-y
2511
- f.info("Quick fix completed",{requestId:q,duration:O,hasPartialFailure:j})
2521
+ f.info("Quick fix completed",{requestId:q,duration:O,hasPartialFailure:R})
2512
2522
  const $=(0,n.readAlchemyMeta)(C)?.toolRecordId
2513
2523
  let D
2514
- return D=j?`\u26a0\ufe0f Quick change completed with partial success\n\n**Request:** ${q}\n**Title:** ${v}\n**Operation:** ${t}\n**Duration:** ${O}ms\n**Status:** Some operations succeeded, some failed\n\nThe successful changes have been applied and automatically merged to main.\n**Active branch:** main\n\n**\u26a0\ufe0f Note:** This quick-fix had partial failures. See operation details below:\n\n`+T:`\u2705 Quick change completed successfully!\n\n**Request:** ${q}\n**Title:** ${v}\n**Operation:** ${t}\n**Duration:** ${O}ms\n\nThe change has been applied and automatically merged to main.\n**Active branch:** main`+($?`\n[RECORD:${$}]`:""),S&&(D+="\n\n\u{1f4a1} **Recommendation:** You've created several quick-edits in a row. For better traceability and organization, consider creating a proper feature or bugfix request with multiple tasks for your next change."),{content:[{type:"text",text:D}],...(0,n.alchemyMeta)({kind:"quick-"+t,toolRecordId:$})}}catch(e){if(f.error("Quick change workflow failed after request creation, cleaning up",{requestId:q,error:(0,d.getErrorMessage)(e)}),P)try{await b.updateTaskStatus(P,"canceled"),f.info("Canceled task after workflow failure",{taskId:P})}catch(e){f.warn("Failed to cancel task during cleanup",{taskId:P,error:e instanceof Error?e.message:e+""})}try{await w.updateRequest(q,{status:"canceled"}),f.info("Canceled request after workflow failure",{requestId:q}),u.workspaceManager&&(await u.workspaceManager.cancelRequest(q),f.info("Cleaned up branch/worktree after workflow failure",{requestId:q}))}catch(e){f.warn("Failed to cancel request during cleanup",{requestId:q,error:e instanceof Error?e.message:e+""})}throw e}}catch(e){const t=Date.now()-y
2524
+ return D=R?`\u26a0\ufe0f Quick change completed with partial success\n\n**Request:** ${q}\n**Title:** ${v}\n**Operation:** ${t}\n**Duration:** ${O}ms\n**Status:** Some operations succeeded, some failed\n\nThe successful changes have been applied and automatically merged to main.\n**Active branch:** main\n\n**\u26a0\ufe0f Note:** This quick-fix had partial failures. See operation details below:\n\n`+T:`\u2705 Quick change completed successfully!\n\n**Request:** ${q}\n**Title:** ${v}\n**Operation:** ${t}\n**Duration:** ${O}ms\n\nThe change has been applied and automatically merged to main.\n**Active branch:** main`+($?`\n[RECORD:${$}]`:""),S&&(D+="\n\n\u{1f4a1} **Recommendation:** You've created several quick-edits in a row. For better traceability and organization, consider creating a proper feature or bugfix request with multiple tasks for your next change."),{content:[{type:"text",text:D}],...(0,n.alchemyMeta)({kind:"quick-"+t,toolRecordId:$})}}catch(e){if(f.error("Quick change workflow failed after request creation, cleaning up",{requestId:q,error:(0,d.getErrorMessage)(e)}),P)try{await b.updateTaskStatus(P,"canceled"),f.info("Canceled task after workflow failure",{taskId:P})}catch(e){f.warn("Failed to cancel task during cleanup",{taskId:P,error:e instanceof Error?e.message:e+""})}try{await w.updateRequest(q,{status:"canceled"}),f.info("Canceled request after workflow failure",{requestId:q}),u.workspaceManager&&(await u.workspaceManager.cancelRequest(q),f.info("Cleaned up branch/worktree after workflow failure",{requestId:q}))}catch(e){f.warn("Failed to cancel request during cleanup",{requestId:q,error:e instanceof Error?e.message:e+""})}throw e}}catch(e){const t=Date.now()-y
2515
2525
  return f.error("Quick change failed",{error:(0,d.getErrorMessage)(e),duration:t}),e instanceof r.ValidationError?{content:[{type:"text",text:"\u274c "+e.message}],isError:!0}:{content:[{type:"text",text:"\u274c Quick change failed: "+(0,d.getErrorMessage)(e)}],isError:!0}}}
2516
2526
  const r=t(9860),n=t(2272),o=t(4079),s=t(9442),i=t(7684),a=t(2736),c=t(5715),l=t(7478),d=t(8824)},3807(module){"use strict"
2517
2527
  module.exports=require("simple-git")},3884(e,exports,t){"use strict"
@@ -2599,12 +2609,12 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleWriteFile=a
2599
2609
  let v,C,_=d.FileOperationPhase.VALIDATION
2600
2610
  const E={toolName:"write_file",operationName:"write",fileOperationPhase:d.FileOperationPhase.FILE_WRITE,recordType:"write-file",metricsPrefix:"write_file",successMessage:"Successfully wrote content",failureMessage:"Failed to write content"}
2601
2611
  try{const n=e
2602
- let{path:m,documentId:S,content:T,format:q,append:P,comment:j}=n
2612
+ let{path:m,documentId:S,content:T,format:q,append:P,comment:R}=n
2603
2613
  Array.isArray(T)&&(T=T.join("\n"))
2604
- const{enforceActiveTask:x}=await Promise.resolve().then(t.bind(t,3451)),R=await x(r,o,f,e.taskId)
2605
- if("errorResult"in R)return R.errorResult
2606
- const{taskId:M}=R,O=!!S,$=!!m
2607
- b.info("Writing content to "+(O?"document":"file"),{taskId:M,filePath:$?m:void 0,documentId:O?S:void 0,contentLength:"string"==typeof T?T.length:JSON.stringify(T).length,format:q,append:P,comment:j})
2614
+ const{enforceActiveTask:x}=await Promise.resolve().then(t.bind(t,3451)),j=await x(r,o,f,e.taskId)
2615
+ if("errorResult"in j)return j.errorResult
2616
+ const{taskId:M}=j,O=!!S,$=!!m
2617
+ b.info("Writing content to "+(O?"document":"file"),{taskId:M,filePath:$?m:void 0,documentId:O?S:void 0,contentLength:"string"==typeof T?T.length:JSON.stringify(T).length,format:q,append:P,comment:R})
2608
2618
  let D,A,F,N="",z=!1,L=!1
2609
2619
  const H=$?m:void 0
2610
2620
  if(O){if(r.apiClient)try{await(0,g.ensureDocumentNotLocked)(S,r.apiClient,b,y,"write_file-document")}catch(e){if(e instanceof u.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
@@ -2623,7 +2633,7 @@ if(Q.switched&&b.info("Workspace context successfully established",{requestId:G,
2623
2633
  D=e.normalizedPath,A=e.fullPath,N=A
2624
2634
  try{await c.default.access(A),L=!0}catch{L=!1,z=!0}b.debug("Path validated with correct workspace context",{rawPath:H,normalizedPath:D,fullPath:A,workspaceBasePath:r.workspaceManager?.getCurrentContext(r.currentSessionId).basePath})}if(O&&G)try{await(0,h.validateRequestStatusForDocumentOperation)(G,i,b,y,"write_file-document")}catch(e){if(e instanceof u.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
2625
2635
  throw e}_=d.FileOperationPhase.RECORD_CREATION,y.incrementCounter("write_file_phase",1,{phase:_})
2626
- const{toolRecordId:K}=await d.createFileOperationRecords(M,G,r.projectId,E.recordType,B,{operationType:O?"document":"file",documentId:O?S:void 0,documentFilename:F,filePath:$?D:void 0,contentLength:"string"==typeof T?T.length:JSON.stringify(T).length,format:q||"text",append:P||!1,wasFileCreated:!L,userComment:j},o,s,b,y,E.metricsPrefix,w)
2636
+ const{toolRecordId:K}=await d.createFileOperationRecords(M,G,r.projectId,E.recordType,B,{operationType:O?"document":"file",documentId:O?S:void 0,documentFilename:F,filePath:$?D:void 0,contentLength:"string"==typeof T?T.length:JSON.stringify(T).length,format:q||"text",append:P||!1,wasFileCreated:!L,userComment:R},o,s,b,y,E.metricsPrefix,w)
2627
2637
  let J
2628
2638
  if(v=K,C=K,_=E.fileOperationPhase,y.incrementCounter("write_file_phase",1,{phase:_}),"json"===q)if("string"==typeof T)try{JSON.parse(T),J=T}catch(e){throw new u.ValidationError("Content is not valid JSON")}else J=JSON.stringify(T,null,2)
2629
2639
  else J="string"==typeof T?T:JSON.stringify(T,null,2)
@@ -2636,7 +2646,7 @@ if(await c.default.mkdir(Z,{recursive:!0}),await c.default.writeFile(N,Y,"utf8")
2636
2646
  try{await r.documentFileManager.syncDocument(S),b.info("Document synced successfully",{documentId:S})}catch(e){b.warn("Failed to sync document immediately",{documentId:S,error:e})}}let X,ee,te=null,re=!1
2637
2647
  if($){if(_=d.FileOperationPhase.GIT_COMMIT,y.incrementCounter("write_file_phase",1,{phase:_}),r.workspaceManager){const e=r.workspaceManager.getCurrentContext(r.currentSessionId)
2638
2648
  ee=e.branchName,re=e.permanentBranch,b.debug("Retrieved branch name from workspace manager",{branchName:ee,requestId:e.requestId,mode:e.mode,isMainBranch:e.isMainBranch})}try{const e=`${z?"Create":P?"Append to":"Update"} ${D} (${C})`,t=await d.commitToGit(M,D,C,C,e,r,b,y,E.metricsPrefix)
2639
- X=t.commitHash,te=t.previousCommitHash,b.info("Changes committed to git via shared utility",{path:D,commitMessage:e,commitHash:X?.substring(0,8),previousCommitHash:te?.substring(0,8),branchName:ee,toolRecordId:C})}catch(e){b.warn("Git operations failed via shared utility",{error:e})}}try{await s.updateToolRecord(C,{metadata:{path:$?D:"document:"+S,status:"completed",finalContentLength:Y.length,wasFileCreated:z,wasAppended:P&&L,operationType:O?"document":"file",processingTime:Date.now()-k,userComment:j,...$&&X&&{commitHash:X,previousCommitHash:te||void 0}},note:"Write operation completed successfully"+(O?" (document synced)":" (committed to git)")})}catch(e){b.warn("Failed to update tool record",{recordError:e})}let ne=`**${z?"Created":P?"Appended to":"Updated"} ${O?`document ${S}${F?` (${F})`:""}`:"file "+D}**\n`
2649
+ X=t.commitHash,te=t.previousCommitHash,b.info("Changes committed to git via shared utility",{path:D,commitMessage:e,commitHash:X?.substring(0,8),previousCommitHash:te?.substring(0,8),branchName:ee,toolRecordId:C})}catch(e){b.warn("Git operations failed via shared utility",{error:e})}}try{await s.updateToolRecord(C,{metadata:{path:$?D:"document:"+S,status:"completed",finalContentLength:Y.length,wasFileCreated:z,wasAppended:P&&L,operationType:O?"document":"file",processingTime:Date.now()-k,userComment:R,...$&&X&&{commitHash:X,previousCommitHash:te||void 0}},note:"Write operation completed successfully"+(O?" (document synced)":" (committed to git)")})}catch(e){b.warn("Failed to update tool record",{recordError:e})}let ne=`**${z?"Created":P?"Appended to":"Updated"} ${O?`document ${S}${F?` (${F})`:""}`:"file "+D}**\n`
2640
2650
  ne+=`**Content Length:** ${Y.length} characters\n`,$&&ee&&(re||(ne+=`**Branch:** ${ee}\n`)),$&&X&&(ne+=`**Commit Hash:** ${X.substring(0,8)}\n`,te&&(ne+=`**Previous Commit:** ${te.substring(0,8)}\n`)),O&&(ne+=`\n**Document Update:** The document content has been updated and synced back to the tracking system. You can continue editing using other file modification tools with documentId \`${S}\`.`)
2641
2651
  const oe=Date.now()
2642
2652
  b.info("Write operation completed successfully",{duration:oe-k,path:$?D:"document:"+S,contentLength:Y.length,wasCreated:z}),I({tool:"write_file",success:!0}),y.incrementCounter("write_file_total",1,{success:!0})
@@ -2770,9 +2780,9 @@ e.grep&&(C=`${C} 2>&1 | grep -i ${JSON.stringify(e.grep)}`)
2770
2780
  const _=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.basePath||t.basePath,E=v.cwd?s.default.resolve(_,v.cwd):_,S=s.default.join(_,a.ALCHEMY_DIR_NAME,"command-logs"),T=`${v.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,q=s.default.join(S,T+".log")
2771
2781
  let P=null
2772
2782
  try{i.default.mkdirSync(S,{recursive:!0}),P=i.default.createWriteStream(q,{flags:"w"}),P.write(`# ${(new Date).toISOString()} starting: ${C}\n# cwd: ${E}\n# timeout: ${v.timeout||60}s\n\n`)}catch(e){h.warn("Failed to open command log file",{logPath:q,error:(0,l.getErrorMessage)(e)})}h.info("Running command",{name:v.name,command:C,cwd:E,configuredCwd:v.cwd,logPath:q})
2773
- const j=await(0,n.runTrackedShellWithHeartbeat)(C,{cwd:E,timeoutMs:1e3*(v.timeout||60),env:{...process.env,FORCE_COLOR:"0"},onChunk:P?(e,t)=>{P?.write(t)}:void 0},{id:T,name:v.name,sessionId:m?.sessionId,logPath:q},f)
2774
- P&&(P.write(`\n# exit: ${j.exitCode}${j.timedOut?" (timed out)":""}\n`),P.end())
2775
- const{stdout:x,stderr:R,exitCode:M,timedOut:O}=j,{output:$,truncatedNote:D,logFooter:A}=(0,o.formatCommandOutput)(x,R,{invocationId:T}),F=O?"TIMEOUT":null
2783
+ const R=await(0,n.runTrackedShellWithHeartbeat)(C,{cwd:E,timeoutMs:1e3*(v.timeout||60),env:{...process.env,FORCE_COLOR:"0"},onChunk:P?(e,t)=>{P?.write(t)}:void 0},{id:T,name:v.name,sessionId:m?.sessionId,logPath:q},f)
2784
+ P&&(P.write(`\n# exit: ${R.exitCode}${R.timedOut?" (timed out)":""}\n`),P.end())
2785
+ const{stdout:x,stderr:j,exitCode:M,timedOut:O}=R,{output:$,truncatedNote:D,logFooter:A}=(0,o.formatCommandOutput)(x,j,{invocationId:T}),F=O?"TIMEOUT":null
2776
2786
  let N
2777
2787
  if(g)try{const e=await g.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:v.name,command:C,exitCode:M,timedOut:O,status:O?"timeout":0===M?"success":"failed",timestamp:(new Date).toISOString()}})
2778
2788
  N=e?.id}catch(e){h.debug("Failed to create tool record for command run",{error:(0,l.getErrorMessage)(e)})}return y({success:!0}),{content:[{type:"text",text:`## ${v.name}${F?` \u2014 ${F} (killed after ${v.timeout}s)`:""}\n\`\`\`\n$ ${C}\n${$}\n\`\`\`${D}\n\nExit code: ${M}\n${A} (pass to read_files or search)`}],...(0,c.alchemyMeta)({kind:"run-command",toolRecordId:N})}}catch(e){return y({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to run command: "+(0,l.getErrorMessage)(e)}],isError:!0}}}
@@ -2883,7 +2893,7 @@ const{operation:I}=w
2883
2893
  if("list"!==I&&"search"!==I&&!b){const e=await(0,a.resolveRequestIdOrError)(p,y,t.currentSessionId,`Error: requestId is required for '${I}' and could not be resolved from session`)
2884
2894
  if(!e.ok)return e.errorResult
2885
2895
  b=e.requestId}switch(y.info("Processing request operation",{requestId:b,operation:I}),I){case"complete":{y.info("Routing to complete-request handler")
2886
- const e={requestId:b,summary:w.complete.summary,skipTestsReason:w.complete.skipTestsReason,skipTests:w.complete.skipTests}
2896
+ const e={requestId:b,summary:w.complete.summary,skipTestsReason:w.complete.skipTestsReason,skipTests:w.complete.skipTests,criteriaComments:w.complete.criteriaComments}
2887
2897
  return(0,u.withAlchemyKind)(await(0,r.handleCompleteRequest)(e,t,p,g,m,f),h.complete)}case"merge":return y.info("Routing to merge handler"),(0,u.withAlchemyKind)(await(0,l.handleMergeRequest)({requestId:b,verifications:w.merge?.verifications||[]},t,p,g,m,f),h.merge)
2888
2898
  case"approve":{y.info("Routing to approve-request handler")
2889
2899
  const e={requestId:b}
@@ -3321,48 +3331,59 @@ return{isOffline:this.isInOfflineMode(),consecutiveFailures:e.consecutiveFailure
3321
3331
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.def=void 0
3322
3332
  const r=t(6183),n=t(2965)
3323
3333
  exports.def={name:"read_files",description:'Read the contents of multiple files and documents with flexible line selection.\n\nLINE SELECTION (string format):\n- Single line: "lines": "5"\n- Multiple lines: "lines": "1,3,7,10"\n- Range: "lines": "5-15"\n- Entire file: omit "lines" parameter\n\nEXAMPLES:\n- Read first 10 lines: {"path": "file.txt", "lines": "1-10"}\n- Read specific lines: {"path": "file.txt", "lines": "1,5,25"}\n- Read line 42 only: {"path": "file.txt", "lines": "42"}\n- Read entire file: {"path": "file.txt"}\n\nSupports both filesystem files (using \'path\') and tracked documents (using \'documentId\'). More efficient than reading files one by one. Failed reads won\'t stop the entire operation.',schema:r.ReadFilesSchema,annotations:{title:"Read Files/Documents",readOnlyHint:!0},readOnly:!0,handler(e,t){return(0,n.handleReadFiles)(e,t.context,t.logger,t.metrics)}}},5715(e,exports,t){"use strict"
3324
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCompleteRequest=async function(e,t,c,l,d,u,h){const p=l.child({tool:"complete_request"}),g=d.startTimer("tool_execution_time")
3325
- try{const{requestId:n,summary:o,skipTestsReason:l,skipTests:d}=e
3334
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCompleteRequest=async function(e,t,l,d,u,h,p){const g=d.child({tool:"complete_request"}),m=u.startTimer("tool_execution_time")
3335
+ try{const{requestId:n,summary:o,skipTestsReason:d,skipTests:u,criteriaComments:h}=e
3326
3336
  if(!n)throw(0,r.createValidationError)("Request ID is required")
3327
3337
  if(!o)throw(0,r.createValidationError)("Summary is required")
3328
- if(l&&d&&d.length>0)throw(0,r.createValidationError)("Provide either skipTestsReason (skip all) or skipTests (per-command), not both.")
3329
- p.info("Starting request completion",{requestId:n,skipAllRequested:!!l,perCommandSkipCount:d?.length||0})
3330
- const u=h?.sessionId
3331
- let m
3332
- try{const e=await c.proxyPost(`/api/projects/${t.projectId}/requests/${n}/validate-completion`,{sessionId:u,skipTestsReason:l,skipTests:d})
3333
- m=e?.data||e}catch(e){const t=(0,s.getErrorMessage)(e)
3334
- return p.error("Server validation failed",{requestId:n,error:t}),g({success:!1,error:"validation_request_failed"}),{content:[{type:"text",text:"Failed to validate completion: "+t}],isError:!0}}if(!m.canComplete){let e=`Cannot complete request ${n}: ${m.reason}\n`
3335
- return m.blockedByTasks&&m.blockedByTasks.length>0&&(e+="\n**Tasks that must be completed or canceled first:**\n",m.blockedByTasks.forEach(((t,r)=>{e+=`${r+1}. **${t.taskId}** - ${t.title} (Status: ${t.status})\n`}))),m.lifoViolation&&(e+="\n**Requests that must be re-completed first (LIFO order):**\n",m.lifoViolation.blockingRequests.forEach(((t,r)=>{e+=`${r+1}. **${t.requestId}** - ${t.title} (Status: ${t.status})\n`}))),g({success:!1,reason:"server_blocked"}),{content:[{type:"text",text:e}],isError:!0}}if(m.testsSkipped&&(l||d&&d.length>0))try{const e=c.getToolRecordClient()
3336
- e&&await e.createToolRecord({projectId:t.projectId,requestId:n,type:"tests-skipped",metadata:{...l?{reason:l}:{},...d&&d.length>0?{skipTests:d}:{},completionMode:m.completionMode,timestamp:(new Date).toISOString()}})}catch(e){p.warn("Failed to persist tests-skipped tool_record",{error:(0,s.getErrorMessage)(e)})}const f=c.getRequestClient(),y="auto-merge"!==m.completionMode&&!m.testsSkipped&&!1===m.hasActiveTestProcesses&&("test-by-default"===m.completionMode||"always-test"===m.completionMode)
3337
- let w="auto-merge"!==m.completionMode&&!m.testsSkipped&&!y
3338
- const b="manual-review"===m.reviewMode
3339
- let I,k=null
3340
- if(w)try{const[e,r]=await Promise.all([c.proxyGet(`/api/projects/${t.projectId}/requests/${n}/test-runs`),fetch(`${(0,a.getActionServerBaseUrl)()}/api/branch-head?requestId=${encodeURIComponent(n)}`,{signal:AbortSignal.timeout(5e3)}).catch((e=>(p.debug("branch-head fetch failed; proceeding with ready_for_review",{error:(0,s.getErrorMessage)(e)}),null)))]),o=e?.data||e||[],i=Array.isArray(o)&&o.length>0?o[0]:null
3341
- if(i&&"passed"===i.status&&i.commitSha){let e=null
3342
- if(r&&r.ok){const t=await r.json()
3343
- e=t?.data?.sha??null}e&&e===i.commitSha&&(w=!1,k=`latest test run ${i.id} passed against current branch HEAD ${e.slice(0,7)}`,I=i.id,p.info("Skip-if-fresh: SHA match, bypassing ready_for_review",{requestId:n,runId:i.id,sha:e}))}}catch(e){p.debug("Skip-if-fresh lookup failed; proceeding with ready_for_review",{error:(0,s.getErrorMessage)(e)})}const v=w||b?"ready_for_review":"ready_for_merge"
3344
- try{await f.updateRequest(n,{status:v,completionSummary:o,...I?{skippedTestRunId:I}:{}})}catch(e){const t=(0,s.getErrorMessage)(e)
3345
- return g({success:!1,error:"status_transition_failed"}),{content:[{type:"text",text:"Failed to transition request: "+t}],isError:!0}}let C
3346
- try{C=await f.getRequest(n)}catch(e){const t=(0,s.getErrorMessage)(e)
3347
- return g({success:!1,error:"fetch_request_failed"}),{content:[{type:"text",text:"Failed to fetch request criteria: "+t}],isError:!0}}const _=C?.acceptanceCriteria||[]
3348
- if(g({success:!0}),w){const e=m.completionMode
3349
- return{content:[{type:"text",text:`Request ${n} moved to ready_for_review. ${"ask-me"===e?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip).":`Tests are running automatically in the background (${e} mode). When they pass and the project is set to auto-finalize, the request will be merged without further input; otherwise the user will click Finalize after reviewing.`} Do NOT call merge or any other tool. Your work is done. Stop here and wait.`}]}}if(b)return{content:[{type:"text",text:`Request ${n} submitted for human review.${m.testsSkipped&&l?`\n\n_Tests skipped: ${l}_`:""}\n\nThe project is set to manual-review \u2014 a person will approve and merge from the UI. Do NOT call merge yourself.`}]}
3350
- const E=k?`\n\n_Skipped re-running tests: ${k}._\n`:"",S=y?`\n\n_No test processes are configured for this project; ${m.completionMode} mode is being treated as auto-merge._\n`:""
3351
- return 0===_.length?{content:[{type:"text",text:`Request ${n} is ready. ${E}${S}No acceptance criteria defined \u2014 call merge directly.`}]}:{content:[{type:"text",text:`Request ${n} completion started (${v}).${E}${S}\n\n## Next Step: Verify & Merge\n\n${(0,i.formatVerifyInstructions)(_)}\n\nUse the merge operation with your verification evidence to merge to main.`}]}}catch(r){let s="An unexpected error occurred",i=""
3352
- r instanceof n.ZodError?(s="Validation error",i=r.errors.map((e=>`${e.path.join(".")}: ${e.message}`)).join(", ")):r instanceof Error?(s=r.message,i=r.stack||""):i=r+"",p.error("Complete request failed",{error:s,details:i})
3353
- try{await(0,o.createFailureToolRecord)("complete-request",r,{error:s,details:i,requestId:e?.requestId},c.getToolRecordClient(),p,d,{requestId:e?.requestId,projectId:t.projectId,note:"Failed to complete request"})}catch{}return d.startTimer("tool_execution_time")({success:!1,error:r instanceof Error?r.name:"unknown"}),{content:[{type:"text",text:"Error: "+s}],isError:!0}}}
3354
- const r=t(9860),n=t(1569),o=t(8616),s=t(8824),i=t(5454),a=t(9414)},5767(e,exports,t){"use strict"
3338
+ if(d&&u&&u.length>0)throw(0,r.createValidationError)("Provide either skipTestsReason (skip all) or skipTests (per-command), not both.")
3339
+ g.info("Starting request completion",{requestId:n,skipAllRequested:!!d,perCommandSkipCount:u?.length||0})
3340
+ const f=p?.sessionId??t.currentSessionId,y=p??(f?{providerId:"unknown",sessionId:f,createdAt:(new Date).toISOString()}:void 0)
3341
+ let w,b=null
3342
+ try{const e=await fetch(`${(0,a.getActionServerBaseUrl)()}/api/branch-head?requestId=${encodeURIComponent(n)}`,{signal:AbortSignal.timeout(5e3)})
3343
+ if(e.ok){const t=await e.json()
3344
+ b=t?.data?.sha??null}}catch(e){g.debug("branch-head fetch failed; server will fall back",{error:(0,s.getErrorMessage)(e)})}try{const e=await l.proxyPost(`/api/projects/${t.projectId}/requests/${n}/validate-completion`,{sessionId:f,skipTestsReason:d,skipTests:u,headSha:b})
3345
+ w=e?.data||e}catch(e){const t=(0,s.getErrorMessage)(e)
3346
+ return g.error("Server validation failed",{requestId:n,error:t}),m({success:!1,error:"validation_request_failed"}),{content:[{type:"text",text:"Failed to validate completion: "+t}],isError:!0}}if(!w.canComplete){let e=`Cannot complete request ${n}: ${w.reason}\n`
3347
+ return w.blockedByTasks&&w.blockedByTasks.length>0&&(e+="\n**Tasks that must be completed or canceled first:**\n",w.blockedByTasks.forEach(((t,r)=>{e+=`${r+1}. **${t.taskId}** - ${t.title} (Status: ${t.status})\n`}))),w.lifoViolation&&(e+="\n**Requests that must be re-completed first (LIFO order):**\n",w.lifoViolation.blockingRequests.forEach(((t,r)=>{e+=`${r+1}. **${t.requestId}** - ${t.title} (Status: ${t.status})\n`}))),m({success:!1,reason:"server_blocked"}),{content:[{type:"text",text:e}],isError:!0}}if(h&&h.length>0)try{const e=l.getRequestClient(),r=await e.getRequest(n),o=r?.acceptanceCriteria||[]
3348
+ if(o.length>0){const e=(0,i.buildCriteriaKeyMap)(o),r=[]
3349
+ for(const t of h){const n=e.get(t.key)
3350
+ n&&r.push({criterionId:n.id,evidence:t.comment})}r.length>0&&await l.proxyPost(`/api/projects/${t.projectId}/requests/${n}/verify-criteria`,{verifications:r,dimension:"coder"})}}catch(e){g.warn("Failed to persist coder criteria comments",{error:(0,s.getErrorMessage)(e)})}if((0,c.isAgentReviewMode)(w.reviewMode)&&"escalated"===w.reviewStatus)return m({success:!0}),{content:[{type:"text",text:`Request ${n}: agent review hit its pass cap and was escalated for human sign-off. No further action from you; a human finalizes or sends back must_fix items.`}]}
3351
+ if((0,c.isAgentReviewMode)(w.reviewMode)&&w.reviewFresh&&"pending"!==w.reviewStatus&&"completed"!==w.reviewStatus&&"escalated"!==w.reviewStatus)try{await l.getRequestClient().updateRequest(n,{reviewStatus:"completed",completionSummary:o})}catch(e){return m({success:!1,error:"review_mark_failed"}),{content:[{type:"text",text:"Failed to record review: "+(0,s.getErrorMessage)(e)}],isError:!0}}else if((0,c.isAgentReviewMode)(w.reviewMode)&&"pending"!==w.reviewStatus&&"completed"!==w.reviewStatus){const e=l.getRequestClient()
3352
+ try{await e.updateRequest(n,{reviewStatus:"pending",completionSummary:o,cliOrigin:y})}catch(e){return m({success:!1,error:"review_start_failed"}),{content:[{type:"text",text:"Failed to start review: "+(0,s.getErrorMessage)(e)}],isError:!0}}return m({success:!0}),{content:[{type:"text",text:`Request ${n}: review started. Read-only reviewers are reading your diff. STOP here and wait \u2014 do NOT poll, sleep, or call tools to pass the time; each reviewer's report is delivered to you automatically and wakes you, so just end your turn between reports. When their reports arrive (a detailed handoff follows with the specifics), act on the findings, finish your tasks, and call complete again. After review passes, the server runs any remaining gates and messages you the exact next step (run tests, await human sign-off, or merge) -- wait for that and don't merge until it tells you to.`}]}}if((0,c.isAgentReviewMode)(w.reviewMode)&&"pending"===w.reviewStatus){const e=l.getRequestClient()
3353
+ try{await e.updateRequest(n,{reviewStatus:"completed",completionSummary:o,cliOrigin:y})}catch(e){return m({success:!1,error:"review_finish_failed"}),{content:[{type:"text",text:"Failed to finish review: "+(0,s.getErrorMessage)(e)}],isError:!0}}return m({success:!0}),{content:[{type:"text",text:`Request ${n}: review submitted. The server is evaluating it and will message you the exact next step (run tests, await human sign-off, or merge) -- wait for that before acting. If a criterion is still unmet, the criteria reviewer takes another pass and reports back; address any must_fix and call complete again.`}]}}if((0,c.isAgentReviewMode)(w.reviewMode)&&"completed"===w.reviewStatus&&("auto-merge"===w.completionMode||w.testsSkipped||!0!==w.hasActiveTestProcesses))return m({success:!0}),{content:[{type:"text",text:`Request ${n}: review is complete and all gates are clear. Call merge to finalize (do not call complete again).`}]}
3354
+ if(w.testsSkipped&&(d||u&&u.length>0))try{const e=l.getToolRecordClient()
3355
+ e&&await e.createToolRecord({projectId:t.projectId,requestId:n,type:"tests-skipped",metadata:{...d?{reason:d}:{},...u&&u.length>0?{skipTests:u}:{},completionMode:w.completionMode,timestamp:(new Date).toISOString()}})}catch(e){g.warn("Failed to persist tests-skipped tool_record",{error:(0,s.getErrorMessage)(e)})}const I=l.getRequestClient(),k=!w.testsSkipped&&!1===w.hasActiveTestProcesses&&("test-by-default"===w.completionMode||"always-test"===w.completionMode)
3356
+ let v="auto-merge"!==w.completionMode&&!w.testsSkipped&&!k
3357
+ const C="manual-review"===w.reviewMode
3358
+ let _,E=null
3359
+ if(v)try{const e=await l.proxyGet(`/api/projects/${t.projectId}/requests/${n}/test-runs`),r=e?.data||e||[],o=Array.isArray(r)&&r.length>0?r[0]:null
3360
+ if(o&&"passed"===o.status&&o.commitSha){const e=b
3361
+ e&&e===o.commitSha&&(v=!1,E=`latest test run ${o.id} passed against current branch HEAD ${e.slice(0,7)}`,_=o.id,g.info("Skip-if-fresh: SHA match, bypassing ready_for_review",{requestId:n,runId:o.id,sha:e}))}}catch(e){g.debug("Skip-if-fresh lookup failed; proceeding with ready_for_review",{error:(0,s.getErrorMessage)(e)})}const S=v?"in_progress":C?"ready_for_review":"ready_for_merge",T=v?"running":void 0
3362
+ try{await I.updateRequest(n,{status:S,...T?{testStatus:T}:{},completionSummary:o,cliOrigin:y,..._?{skippedTestRunId:_}:{}})}catch(e){const t=(0,s.getErrorMessage)(e)
3363
+ return m({success:!1,error:"status_transition_failed"}),{content:[{type:"text",text:"Failed to transition request: "+t}],isError:!0}}let q
3364
+ try{q=await I.getRequest(n)}catch(e){const t=(0,s.getErrorMessage)(e)
3365
+ return m({success:!1,error:"fetch_request_failed"}),{content:[{type:"text",text:"Failed to fetch request criteria: "+t}],isError:!0}}const P=q?.acceptanceCriteria||[]
3366
+ if(m({success:!0}),v){const e=w.completionMode
3367
+ return{content:[{type:"text",text:`Request ${n}: tests are now running. ${"ask-me"===e?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip). Stop here; you will receive a report when testing finishes.":`Tests are running automatically in the background (${e} mode). Stop here; a tester subagent will run the test commands and post a report back to you when done.`}\n\n## When the report arrives\n- **Passed** \u2014 call \`merge\` with verification evidence for each acceptance criterion to finalize the request.\n- **Failed** \u2014 fix the issues and call \`complete\` again.\n\nDo NOT call merge, finalize, or any other tool BEFORE the report arrives \u2014 stop here and wait.`}]}}if(C)return{content:[{type:"text",text:`Request ${n} submitted for human review.${w.testsSkipped&&d?`\n\n_Tests skipped: ${d}_`:""}\n\nThe project is set to manual-review \u2014 a person will approve and merge from the UI. Do NOT call merge yourself.`}]}
3368
+ const R=E?`\n\n_Skipped re-running tests: ${E}._\n`:"",x=k?`\n\n_No test processes are configured for this project; ${w.completionMode} mode is being treated as auto-merge._\n`:""
3369
+ return 0===P.length?{content:[{type:"text",text:`Request ${n} is ready. ${R}${x}No acceptance criteria defined \u2014 call merge directly.`}]}:{content:[{type:"text",text:`Request ${n}: all gates are clear.${R}${x}\n\n## Next Step: Verify & Merge\n\n${(0,i.formatVerifyInstructions)(P)}\n\nUse the merge operation with your verification evidence to merge to main.`}]}}catch(r){let s="An unexpected error occurred",i=""
3370
+ r instanceof n.ZodError?(s="Validation error",i=r.errors.map((e=>`${e.path.join(".")}: ${e.message}`)).join(", ")):r instanceof Error?(s=r.message,i=r.stack||""):i=r+"",g.error("Complete request failed",{error:s,details:i})
3371
+ try{await(0,o.createFailureToolRecord)("complete-request",r,{error:s,details:i,requestId:e?.requestId},l.getToolRecordClient(),g,u,{requestId:e?.requestId,projectId:t.projectId,note:"Failed to complete request"})}catch{}return u.startTimer("tool_execution_time")({success:!1,error:r instanceof Error?r.name:"unknown"}),{content:[{type:"text",text:"Error: "+s}],isError:!0}}}
3372
+ const r=t(9860),n=t(1569),o=t(8616),s=t(8824),i=t(5454),a=t(9414),c=t(3580)},5767(e,exports,t){"use strict"
3355
3373
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.def=void 0
3356
3374
  const r=t(6183),n=t(6285)
3357
3375
  exports.def={name:"specialist_ops",description:"Manage your pool of up to 10 specialists. Each has a specialization describing their domain. Reuse specialists via follow-up before spawning new ones.\n\nAll params are flat (no nested objects). The operation field determines which params apply.\n\nOperations:\n\u2022 delegate \u2014 prompt + specialization required. Optional: description, specializationHeadline, requestId.\n\u2022 follow-up \u2014 prompt + sessionId required\n\u2022 check-status \u2014 sessionId required\n\u2022 list \u2014 all params optional: activeOnly, requestId (filter), limit\n\u2022 abort \u2014 sessionId required",schema:r.SpecialistOpsSchema,annotations:{title:"Specialist Operations",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1},handler(e,t){return(0,n.handleSpecialistOps)(e,t.context,t.logger,t.metrics,t.apiClient??void 0,t.cliOrigin)}}},5802(module){"use strict"
3358
3376
  module.exports=require("@modelcontextprotocol/sdk/server/stdio.js")},5806(e,exports,t){"use strict"
3359
3377
  function r(){const e=(0,o.areTestsRequiredForCompletion)(),t=e?s:i
3360
3378
  if(t)return t
3361
- const r=n.z.object({requestId:n.z.string(),summary:n.z.string(),skipTestsReason:n.z.string().min(20).optional(),skipTests:n.z.array(n.z.object({commandName:n.z.string().min(1),reason:n.z.string().min(20)})).optional(),acceptanceEvidence:n.z.array(n.z.object({evidence:n.z.string()}))}),a=n.z.object({testRecords:n.z.array(n.z.string()),coverage:n.z.object({criteriaTestedCount:n.z.number(),totalTestCases:n.z.number()}),criterionCoverage:n.z.array(n.z.object({coverage:n.z.enum(["none","partial","full"]),testRecords:n.z.array(n.z.string())}))}),c=e?r.extend({testVerification:a}):r
3379
+ const r=n.z.object({requestId:n.z.string(),summary:n.z.string(),skipTestsReason:n.z.string().min(20).optional(),skipTests:n.z.array(n.z.object({commandName:n.z.string().min(1),reason:n.z.string().min(20)})).optional(),criteriaComments:n.z.array(n.z.object({key:n.z.string().min(1),comment:n.z.string().min(1)})).optional()}),a=n.z.object({testRecords:n.z.array(n.z.string()),coverage:n.z.object({criteriaTestedCount:n.z.number(),totalTestCases:n.z.number()}),criterionCoverage:n.z.array(n.z.object({coverage:n.z.enum(["none","partial","full"]),testRecords:n.z.array(n.z.string())}))}),c=e?r.extend({testVerification:a}):r
3362
3380
  return e?s=c:i=c,c}Object.defineProperty(exports,"__esModule",{value:!0}),exports.DynamicCompleteRequestSchema=void 0,exports.getDynamicCompleteRequestSchema=r
3363
3381
  const n=t(1569),o=t(2478)
3364
3382
  let s=null,i=null
3365
- exports.DynamicCompleteRequestSchema={get schema(){return r()}}},5964(e,exports){"use strict"
3383
+ exports.DynamicCompleteRequestSchema={get schema(){return r()}}},5837(e,exports,t){"use strict"
3384
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.def=void 0
3385
+ const r=t(1569),n=t(7648),o=r.z.object({requestId:r.z.string().describe("The request whose acceptance criteria are being recorded."),verifications:r.z.array(r.z.object({key:r.z.string().min(1).describe('Short criterion key (e.g. "1a") as shown next to the criterion.'),status:r.z.enum(["met","unmet"]).default("met").describe('Verdict for this criterion: "met" if the diff delivers it, "unmet" if missing or only partial.'),evidence:r.z.string().min(1).describe("For met: how the diff satisfies it (cite file:line). For unmet: what is missing and how to fix it.")})).min(1).describe("One entry per acceptance criterion you reviewed: record a verdict (met or unmet) for EVERY criterion, not only the satisfied ones.")})
3386
+ exports.def={name:"record_criteria_verification",description:"Record the per-criterion acceptance-criteria verdict (met or unmet + reason) for every criterion; record-only, does NOT merge. For the criteria reviewer to persist its full verdict.",schema:o,annotations:{title:"Record Criteria Verification",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0},handler(e,t){return(0,n.handleRecordCriteriaVerification)(e,t.context,t.apiClient,t.logger,t.metrics)}}},5964(e,exports){"use strict"
3366
3387
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ConflictDetectionError=void 0,exports.createConflictDetectionError=function(e,r){return new t(e,r)}
3367
3388
  class t extends Error{constructor(e,t){super(e),this.isConflictDetection=!0,this.preserveWorktree=!0,this.name="ConflictDetectionError",this.conflictInfo=t}static isConflictDetectionError(e){return e instanceof t||e instanceof Error&&(!0===e.isConflictDetection||void 0!==e.conflictInfo)}static shouldPreserveWorktree(e){return t.isConflictDetectionError(e)||e instanceof Error&&!0===e.preserveWorktree}}exports.ConflictDetectionError=t},5967(e,exports,t){"use strict"
3368
3389
  var r=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
@@ -3383,49 +3404,49 @@ const I={toolName:"delete-files",operationName:"delete",fileOperationPhase:c.Fil
3383
3404
  try{const d=n.getTaskClient(),k=n.getToolRecordClient(),v=n.getRequestClient(),C=e,{paths:_,operations:E,comment:S}=C,{enforceActiveTask:T}=await Promise.resolve().then(t.bind(t,3451)),q=await T(r,n,o,e.taskId)
3384
3405
  if("errorResult"in q)return q.errorResult
3385
3406
  const{taskId:P}=q
3386
- let j=[]
3387
- if(_)j=_.map((e=>({type:"file",target:e})))
3407
+ let R=[]
3408
+ if(_)R=_.map((e=>({type:"file",target:e})))
3388
3409
  else{if(!E)throw Error("Either paths or operations must be specified")
3389
- j=E.map((e=>{if(e.path)return{type:"file",target:e.path}
3410
+ R=E.map((e=>{if(e.path)return{type:"file",target:e.path}
3390
3411
  if(e.documentId)return{type:"document",target:e.documentId}
3391
- throw Error("Invalid operation: neither path nor documentId specified")}))}if(0===j.length)throw Error("At least one file or document must be specified for deletion")
3392
- const x=j.filter((e=>"file"===e.type)),R=j.filter((e=>"document"===e.type)),M=j.length
3393
- m.info("Starting mixed file and document deletion operation",{taskId:P,totalOperations:j.length,fileOperations:x.length,documentOperations:R.length,comment:S}),c.validateTaskIdFormat(P,m,s,I.metricsPrefix)
3412
+ throw Error("Invalid operation: neither path nor documentId specified")}))}if(0===R.length)throw Error("At least one file or document must be specified for deletion")
3413
+ const x=R.filter((e=>"file"===e.type)),j=R.filter((e=>"document"===e.type)),M=R.length
3414
+ m.info("Starting mixed file and document deletion operation",{taskId:P,totalOperations:R.length,fileOperations:x.length,documentOperations:j.length,comment:S}),c.validateTaskIdFormat(P,m,s,I.metricsPrefix)
3394
3415
  const O=r.getCurrentBranchContext?await r.getCurrentBranchContext():{branch:null,requestId:null,isMain:!0}
3395
3416
  m.info("Initial branch context",{initialBranch:O.branch,initialRequestId:O.requestId,isMain:O.isMain})
3396
3417
  const $=[]
3397
3418
  for(const e of x){const{normalizedPath:t,fullPath:n}=c.validateAndNormalizePath(e.target,r)
3398
3419
  $.push({normalized:t,full:n})}b=c.FileOperationPhase.TASK_VALIDATION,s.incrementCounter("delete_files_phase",1,{phase:b})
3399
3420
  const D=$.length>0?$[0].normalized:"document_operations",A=await c.validateTaskId(P,D,d,m,s,I.metricsPrefix),F=A?.requestId,N=await c.validateWorkItemStates(P,D,d,v,r,m,s,I.metricsPrefix),z=await c.autoPromoteTaskStatus(P,A.status,F,N.request?.status,D,d,v,m,s,I.metricsPrefix)
3400
- z.taskPromoted&&m.info(`Auto-promoted task ${P} (${A.title||"Untitled"}) to in_progress for mixed operations`,{taskId:P,taskTitle:A.title,previousStatus:A.status,newStatus:z.newTaskStatus,totalOperations:j.length,fileOperations:x.length,documentOperations:R.length}),z.requestPromoted&&m.info(`Auto-promoted request ${F} to in_progress for file operations`,{requestId:F,previousStatus:N.request?.status,newStatus:z.newRequestStatus,triggeringTaskId:P})
3421
+ z.taskPromoted&&m.info(`Auto-promoted task ${P} (${A.title||"Untitled"}) to in_progress for mixed operations`,{taskId:P,taskTitle:A.title,previousStatus:A.status,newStatus:z.newTaskStatus,totalOperations:R.length,fileOperations:x.length,documentOperations:j.length}),z.requestPromoted&&m.info(`Auto-promoted request ${F} to in_progress for file operations`,{requestId:F,previousStatus:N.request?.status,newStatus:z.newRequestStatus,triggeringTaskId:P})
3401
3422
  const L=await c.ensureCorrectWorkspaceContext({taskPromoted:z.taskPromoted,requestId:F,context:r,operation:"delete-files",logger:m,metrics:s,metricsPrefix:I.metricsPrefix})
3402
3423
  if(!L.success)throw Error(L.error)
3403
3424
  let H={hadUserMods:!1,userModsCommitHash:void 0}
3404
3425
  if(r.git)try{m.debug("[DELETE-FILES] Checking for user changes before mixed delete operations")
3405
3426
  const e=await(0,u.commitUserChangesBeforeToolOperation)(r.git,"delete-files",{requestId:F,taskId:P,workspaceManager:r.workspaceManager},m,s)
3406
- e.userChangesCommitted&&(m.debug("[DELETE-FILES] User changes committed before mixed delete operations",{commitHash:e.commitHash?.substring(0,8),filesCommitted:e.filesCommitted}),m.info("User changes committed before mixed delete-files operation",{taskId:P,totalOperations:j.length,fileOperations:x.length,documentOperations:R.length,userCommitHash:e.commitHash?.substring(0,8),userFilesCommitted:e.filesCommitted}),H={hadUserMods:!0,userModsCommitHash:e.commitHash})}catch(e){m.debug("[DELETE-FILES] Pre-tool commit failed, continuing with mixed delete operations",{error:e instanceof Error?e.message:e+""}),m.warn("Pre-tool user changes commit failed, continuing with mixed delete operation",{taskId:P,totalOperations:j.length,fileOperations:x.length,documentOperations:R.length,error:e instanceof Error?e.message:e+""})}m.debug("[DELETE-FILES] Proceeding with mixed delete operations",{totalOperations:j.length,fileDeletes:x.length,documentDeletes:R.length}),b=c.FileOperationPhase.RECORD_CREATION,s.incrementCounter("delete_files_phase",1,{phase:b})
3407
- const{toolRecordId:B,localToolRecordId:U}=await c.createFileOperationRecords(P,F,r.projectId,I.recordType,D,{totalOperations:j.length,fileOperations:x.length,documentOperations:R.length,filePaths:$.map((e=>e.normalized)),documentIds:R.map((e=>e.target)),userComment:S,hadUserMods:H.hadUserMods,userModsCommitHash:H.userModsCommitHash},d,k,m,s,I.metricsPrefix,i)
3427
+ e.userChangesCommitted&&(m.debug("[DELETE-FILES] User changes committed before mixed delete operations",{commitHash:e.commitHash?.substring(0,8),filesCommitted:e.filesCommitted}),m.info("User changes committed before mixed delete-files operation",{taskId:P,totalOperations:R.length,fileOperations:x.length,documentOperations:j.length,userCommitHash:e.commitHash?.substring(0,8),userFilesCommitted:e.filesCommitted}),H={hadUserMods:!0,userModsCommitHash:e.commitHash})}catch(e){m.debug("[DELETE-FILES] Pre-tool commit failed, continuing with mixed delete operations",{error:e instanceof Error?e.message:e+""}),m.warn("Pre-tool user changes commit failed, continuing with mixed delete operation",{taskId:P,totalOperations:R.length,fileOperations:x.length,documentOperations:j.length,error:e instanceof Error?e.message:e+""})}m.debug("[DELETE-FILES] Proceeding with mixed delete operations",{totalOperations:R.length,fileDeletes:x.length,documentDeletes:j.length}),b=c.FileOperationPhase.RECORD_CREATION,s.incrementCounter("delete_files_phase",1,{phase:b})
3428
+ const{toolRecordId:B,localToolRecordId:U}=await c.createFileOperationRecords(P,F,r.projectId,I.recordType,D,{totalOperations:R.length,fileOperations:x.length,documentOperations:j.length,filePaths:$.map((e=>e.normalized)),documentIds:j.map((e=>e.target)),userComment:S,hadUserMods:H.hadUserMods,userModsCommitHash:H.userModsCommitHash},d,k,m,s,I.metricsPrefix,i)
3408
3429
  y=B,w=B,b=I.fileOperationPhase,s.incrementCounter("delete_files_phase",1,{phase:b})
3409
3430
  const G=[]
3410
3431
  let W=0
3411
- R.length>0&&n.setProjectId(r.projectId)
3432
+ j.length>0&&n.setProjectId(r.projectId)
3412
3433
  for(let e=0;e<$.length;e++){const t=$[e],r={type:"file",path:t.normalized,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0}
3413
3434
  try{try{await a.default.access(t.full)}catch(e){throw Error("File not found: "+t.normalized)}if((await a.default.stat(t.full)).isDirectory())throw Error("Cannot 'delete' a directory: "+t.normalized)
3414
- await a.default.unlink(t.full),r.success=!0,W++,m.info("Successfully deleted file",{path:t.normalized})}catch(e){r.error=(0,g.getErrorMessage)(e),m.warn("Failed to delete file "+t.normalized,{error:r.error})}G.push(r)}const V=R.length>0?n.getDocumentClient():null
3415
- for(const e of R){const t=e.target,r={type:"document",documentId:t,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0}
3435
+ await a.default.unlink(t.full),r.success=!0,W++,m.info("Successfully deleted file",{path:t.normalized})}catch(e){r.error=(0,g.getErrorMessage)(e),m.warn("Failed to delete file "+t.normalized,{error:r.error})}G.push(r)}const V=j.length>0?n.getDocumentClient():null
3436
+ for(const e of j){const t=e.target,r={type:"document",documentId:t,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0}
3416
3437
  try{let e
3417
3438
  try{if(e=await V.getDocument(t),r.filename=e.filename,m.info("Document found for archival",{documentId:e.id,filename:e.filename,type:e.type,requestId:e.requestId}),e.requestId)try{await(0,h.validateRequestStatusForDocumentOperation)(e.requestId,v,m,s,"delete-document")}catch(e){if(e instanceof l.ValidationError)throw Error(e.message)
3418
3439
  throw e}try{await(0,p.ensureDocumentNotLocked)(t,n,m,s,"delete-document-archive")}catch(e){if(e instanceof l.ValidationError)throw Error(e.message)
3419
3440
  throw e}}catch(e){throw Error(`Document not found or validation failed: ${t}. ${(0,g.getErrorMessage)(e)}`)}const o=await V.archiveDocument(t)
3420
3441
  r.success=!0,r.filename=o.filename,W++,m.info("Document archived successfully",{documentId:o.id,filename:o.filename,archivedAt:o.archived_at})}catch(e){r.error=(0,g.getErrorMessage)(e),m.warn("Failed to delete document "+t,{error:r.error})}G.push(r)}const Q=Date.now()-f
3421
- s.recordValue("delete_files_time",Q),s.incrementCounter("delete_files_operations_total",M),s.incrementCounter("delete_files_operations_success",W),s.incrementCounter("delete_files_operations_failed",M-W),s.incrementCounter("delete_files_file_operations",x.length),s.incrementCounter("delete_files_document_operations",R.length),W===M?s.incrementCounter("delete_files_success",1):W>0?s.incrementCounter("delete_files_partial_success",1):s.incrementCounter("delete_files_total_failure",1)
3442
+ s.recordValue("delete_files_time",Q),s.incrementCounter("delete_files_operations_total",M),s.incrementCounter("delete_files_operations_success",W),s.incrementCounter("delete_files_operations_failed",M-W),s.incrementCounter("delete_files_file_operations",x.length),s.incrementCounter("delete_files_document_operations",j.length),W===M?s.incrementCounter("delete_files_success",1):W>0?s.incrementCounter("delete_files_partial_success",1):s.incrementCounter("delete_files_total_failure",1)
3422
3443
  let K=null
3423
3444
  if(W>0){b=c.FileOperationPhase.GIT_COMMIT,s.incrementCounter("delete_files_phase",1,{phase:b})
3424
3445
  const e=G.filter((e=>e.success)),t=e.filter((e=>"file"===e.type)).length,n=e.filter((e=>"document"===e.type)).length
3425
3446
  let o
3426
3447
  o=t>0&&n>0?`Delete ${t} files and ${n} documents - ${S}`:t>0?1===t?"Delete file - "+S:`Delete ${t} files - ${S}`:1===n?"Delete document - "+S:`Delete ${n} documents - ${S}`,K=await c.commitToGit(P,D,B,U,o,r,m,s,I.metricsPrefix)}b=c.FileOperationPhase.RECORD_UPDATE_SUCCESS,s.incrementCounter("delete_files_phase",1,{phase:b})
3427
3448
  const J=G.filter((e=>e.success)),Y=G.filter((e=>!e.success))
3428
- await c.updateFileOperationRecords(P,y,w,D,I.recordType,W===M?`Successfully deleted all ${M} operations`:`Deleted ${W}/${M} operations successfully`,{status:W===M?"completed":"partial_success",commitHash:K?.commitHash,previousCommitHash:K?.previousCommitHash,totalOperations:M,fileOperations:x.length,documentOperations:R.length,successCount:W,failureCount:M-W,successfulOperations:J.map((e=>({type:e.type,path:e.path,documentId:e.documentId,filename:e.filename}))),failedOperations:Y.map((e=>({type:e.type,path:e.path,documentId:e.documentId,filename:e.filename,error:e.error}))),userComment:S,hadUserMods:H.hadUserMods,userModsCommitHash:H.userModsCommitHash},W===M?`Successfully deleted all ${M} operations`:`Deleted ${W}/${M} operations successfully`,d,k,m,s,I.metricsPrefix)
3449
+ await c.updateFileOperationRecords(P,y,w,D,I.recordType,W===M?`Successfully deleted all ${M} operations`:`Deleted ${W}/${M} operations successfully`,{status:W===M?"completed":"partial_success",commitHash:K?.commitHash,previousCommitHash:K?.previousCommitHash,totalOperations:M,fileOperations:x.length,documentOperations:j.length,successCount:W,failureCount:M-W,successfulOperations:J.map((e=>({type:e.type,path:e.path,documentId:e.documentId,filename:e.filename}))),failedOperations:Y.map((e=>({type:e.type,path:e.path,documentId:e.documentId,filename:e.filename,error:e.error}))),userComment:S,hadUserMods:H.hadUserMods,userModsCommitHash:H.userModsCommitHash},W===M?`Successfully deleted all ${M} operations`:`Deleted ${W}/${M} operations successfully`,d,k,m,s,I.metricsPrefix)
3429
3450
  const Z=r.getCurrentBranchContext?await r.getCurrentBranchContext():{branch:null,requestId:null,isMain:!0}
3430
3451
  let X=""
3431
3452
  if(W===M){const e=J.filter((e=>"file"===e.type)).length,t=J.filter((e=>"document"===e.type)).length
@@ -3510,7 +3531,7 @@ if("create"===r){e.name||t.addIssue({code:"custom",message:"name is required for
3510
3531
  const r=s.QUEST_METADATA_KEYS.filter((t=>void 0!==e[t]))
3511
3532
  r.length>0&&t.addIssue({code:"custom",message:`Quest metadata fields (${r.join(", ")}) are not allowed on plan_write create. Quests are created via the UI flow at /quests/new. plan_write update still works on existing Quest documents.`})}else e.planId||t.addIssue({code:"custom",message:`planId is required for '${r}'.`})
3512
3533
  if("append"!==r||e.content||t.addIssue({code:"custom",message:'content is required for \'append\'. Example: { "operation": "append", "planId": "plan000001", "content": "..." }'}),"update"===r){const r=void 0!==e.content,n=void 0!==e.criteria||void 0!==e.tickIntervalSeconds||void 0!==e.maxInFlight||void 0!==e.maxPerDay||void 0!==e.minIterations||void 0!==e.status||void 0!==e.completionMode
3513
- r||n||t.addIssue({code:"custom",message:"update requires either 'content' (plan or quest body) or at least one metadata field (criteria, tickIntervalSeconds, maxInFlight, maxPerDay, minIterations, status, completionMode \u2014 quest only)."})}"replace"===r&&(e.old||t.addIssue({code:"custom",message:'old is required for replace. Example: { "operation": "replace", "planId": "plan000001", "old": "find this", "new": "replace with" }'}),void 0===e.new&&t.addIssue({code:"custom",message:"new is required for replace"})),"restore"!==r||e.versionNumber||t.addIssue({code:"custom",message:'versionNumber is required for restore. Example: { "operation": "restore", "planId": "plan000001", "versionNumber": 1 }'})})),exports.QuestSetCriteriaStatusesSchema=r.z.object({questId:r.z.string().min(1).describe("The quest documentId whose criteria to update."),updates:r.z.array(r.z.object({id:r.z.string().min(1).describe("Criterion id (must match an existing item in metadata.criteria)."),status:r.z.enum(["unverified","verified"]).describe("New status for this criterion."),evidence:r.z.string().optional().describe("Optional evidence note (file path, request id, test name, command output, etc.) describing why the status changed.")})).min(1).describe("One or more per-criterion updates. Each id must exist in the quest's metadata.criteria; unknown ids return an error listing the valid ids. Updates merge into the existing item (other fields preserved); only status and evidence change.")}),exports.QuestAuditCompleteSchema=r.z.object({questId:r.z.string().min(1).describe("The quest documentId to audit (the long uuid form returned by plan_write create, or the project-scoped doc000NN id)."),summary:r.z.string().min(1).describe("Short prose summary of what evidence the audit found. Stored on the quest if this call reaches the threshold and flips status \u2014 visible to the user when they review."),evidence:r.z.array(r.z.string()).optional().describe("Optional list of evidence references (file paths, request IDs, test names, command outputs) that support the audit conclusion. Stored on the quest alongside the summary if status flips.")}),exports.SwitchToMainBranchSchema=r.z.object({}),exports.SwitchToBranchSchema=r.z.object({branch:r.z.string().optional()}),exports.RevertRequestSchema=r.z.object({requestId:c,reason:r.z.string(),confirmDestructive:r.z.boolean().optional()}),exports.ModifyFileSchema=r.z.object({operations:r.z.array(r.z.object({path:r.z.string().optional(),documentId:l.optional(),operationType:r.z.enum(["replace-string","insert-lines","replace-lines","delete-string","delete-lines"]),replaceString:r.z.object({replacements:r.z.array(r.z.object({old_str:r.z.string(),new_str:r.z.string()})),mode:r.z.enum(["unique","all"]).optional()}).optional(),insertLines:r.z.object({insertions:r.z.array(r.z.object({type:r.z.enum(["before","after","between"]),line:r.z.number().optional(),afterLine:r.z.number().optional(),beforeLine:r.z.number().optional(),content:r.z.union([r.z.string(),r.z.array(r.z.string())])}).refine((e=>"between"===e.type?void 0!==e.afterLine&&void 0!==e.beforeLine&&void 0===e.line:void 0!==e.line&&void 0===e.afterLine&&void 0===e.beforeLine),{message:"Invalid line parameters"}))}).optional(),replaceLines:r.z.object({replacements:r.z.array(r.z.object({lines:r.z.string(),content:r.z.union([r.z.string(),r.z.array(r.z.string())])}))}).optional(),deleteString:r.z.object({patterns:r.z.array(r.z.string()),mode:r.z.enum(["all","unique"]).optional()}).optional(),deleteLines:r.z.object({lines:r.z.string()}).optional()}).transform(d).refine(u,{message:"path or documentId required"}).refine((e=>"replace-string"===e.operationType?e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteString&&!e.deleteLines:"insert-lines"===e.operationType?e.insertLines&&!e.replaceString&&!e.replaceLines&&!e.deleteString&&!e.deleteLines:"replace-lines"===e.operationType?e.replaceLines&&!e.replaceString&&!e.insertLines&&!e.deleteString&&!e.deleteLines:"delete-string"===e.operationType?e.deleteString&&!e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteLines:"delete-lines"===e.operationType&&e.deleteLines&&!e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteString),{message:"Invalid operation data"})),comment:r.z.string()}),exports.ManageFilesSchema=r.z.object({operations:r.z.array(r.z.object({operationType:r.z.enum(["create-directory","move-files","delete-files","get-file-info"]),createDirectory:r.z.object({path:r.z.string()}).optional(),moveFiles:r.z.object({moves:r.z.array(r.z.object({source:r.z.string(),destination:r.z.string()}))}).optional(),deleteFiles:r.z.object({targets:r.z.array(r.z.object({path:r.z.string().optional(),documentId:l.optional()}).transform(d).refine(u,{message:"path or documentId required"}))}).optional(),getFileInfo:r.z.object({path:r.z.string()}).optional()}).refine((e=>"create-directory"===e.operationType?e.createDirectory&&!e.moveFiles&&!e.deleteFiles&&!e.getFileInfo:"move-files"===e.operationType?e.moveFiles&&!e.createDirectory&&!e.deleteFiles&&!e.getFileInfo:"delete-files"===e.operationType?e.deleteFiles&&!e.createDirectory&&!e.moveFiles&&!e.getFileInfo:"get-file-info"===e.operationType&&e.getFileInfo&&!e.createDirectory&&!e.moveFiles&&!e.deleteFiles),{message:"Invalid operation data"})),comment:r.z.string()}),exports.RequestOperationsSchema=r.z.object({requestId:c.optional(),operation:r.z.enum(["complete","merge","approve","revert","get-info","list","search","delete-empty"]),complete:r.z.preprocess((e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e),r.z.object({summary:r.z.string(),skipTestsReason:exports.skipTestsReasonSchema,skipTests:exports.skipTestsSchema})).optional(),merge:r.z.preprocess((e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e),r.z.object({verifications:r.z.array(r.z.object({key:r.z.string().min(2).max(4),evidence:r.z.string().min(1)})).min(1)})).optional(),revert:r.z.object({reason:r.z.string(),confirmDestructive:r.z.boolean().optional()}).optional(),list:r.z.object({filters:r.z.object({type:r.z.enum(["feature","bugfix"]).optional(),status:r.z.enum(["pending_approval","open","in_progress","ready_for_review","ready_for_merge","resolving_conflicts","completed","canceled"]).optional(),planDocumentId:r.z.string().optional().describe("Narrow to requests linked to this Plan document (use the plan's id or local id)."),questDocumentId:r.z.string().optional().describe("Narrow to requests spawned under this Quest document (use the quest's id or local id)."),dateFrom:r.z.string().optional(),dateTo:r.z.string().optional(),limit:r.z.number().min(1).max(100).default(20),offset:r.z.number().min(0).default(0),sortBy:r.z.enum(["created","updated"]).default("updated"),sortDirection:r.z.enum(["asc","desc"]).default("desc")}).optional()}).optional(),search:r.z.object({query:r.z.string().min(1),searchIn:r.z.enum(["title","description","criteria","all"]).default("all"),filters:r.z.object({type:r.z.enum(["feature","bugfix"]).optional(),status:r.z.enum(["pending_approval","open","in_progress","ready_for_review","ready_for_merge","resolving_conflicts","completed","canceled"]).optional(),planDocumentId:r.z.string().optional().describe("Narrow to requests linked to this Plan document."),questDocumentId:r.z.string().optional().describe("Narrow to requests spawned under this Quest document."),dateFrom:r.z.string().optional(),dateTo:r.z.string().optional(),limit:r.z.number().min(1).max(100).default(20),offset:r.z.number().min(0).default(0)}).optional()}).optional(),approve:r.z.object({}).optional()}).superRefine(((e,t)=>{e.requestId
3534
+ r||n||t.addIssue({code:"custom",message:"update requires either 'content' (plan or quest body) or at least one metadata field (criteria, tickIntervalSeconds, maxInFlight, maxPerDay, minIterations, status, completionMode \u2014 quest only)."})}"replace"===r&&(e.old||t.addIssue({code:"custom",message:'old is required for replace. Example: { "operation": "replace", "planId": "plan000001", "old": "find this", "new": "replace with" }'}),void 0===e.new&&t.addIssue({code:"custom",message:"new is required for replace"})),"restore"!==r||e.versionNumber||t.addIssue({code:"custom",message:'versionNumber is required for restore. Example: { "operation": "restore", "planId": "plan000001", "versionNumber": 1 }'})})),exports.QuestSetCriteriaStatusesSchema=r.z.object({questId:r.z.string().min(1).describe("The quest documentId whose criteria to update."),updates:r.z.array(r.z.object({id:r.z.string().min(1).describe("Criterion id (must match an existing item in metadata.criteria)."),status:r.z.enum(["unverified","verified"]).describe("New status for this criterion."),evidence:r.z.string().optional().describe("Optional evidence note (file path, request id, test name, command output, etc.) describing why the status changed.")})).min(1).describe("One or more per-criterion updates. Each id must exist in the quest's metadata.criteria; unknown ids return an error listing the valid ids. Updates merge into the existing item (other fields preserved); only status and evidence change.")}),exports.QuestAuditCompleteSchema=r.z.object({questId:r.z.string().min(1).describe("The quest documentId to audit (the long uuid form returned by plan_write create, or the project-scoped doc000NN id)."),summary:r.z.string().min(1).describe("Short prose summary of what evidence the audit found. Stored on the quest if this call reaches the threshold and flips status \u2014 visible to the user when they review."),evidence:r.z.array(r.z.string()).optional().describe("Optional list of evidence references (file paths, request IDs, test names, command outputs) that support the audit conclusion. Stored on the quest alongside the summary if status flips.")}),exports.SwitchToMainBranchSchema=r.z.object({}),exports.SwitchToBranchSchema=r.z.object({branch:r.z.string().optional()}),exports.RevertRequestSchema=r.z.object({requestId:c,reason:r.z.string(),confirmDestructive:r.z.boolean().optional()}),exports.ModifyFileSchema=r.z.object({operations:r.z.array(r.z.object({path:r.z.string().optional(),documentId:l.optional(),operationType:r.z.enum(["replace-string","insert-lines","replace-lines","delete-string","delete-lines"]),replaceString:r.z.object({replacements:r.z.array(r.z.object({old_str:r.z.string(),new_str:r.z.string()})),mode:r.z.enum(["unique","all"]).optional()}).optional(),insertLines:r.z.object({insertions:r.z.array(r.z.object({type:r.z.enum(["before","after","between"]),line:r.z.number().optional(),afterLine:r.z.number().optional(),beforeLine:r.z.number().optional(),content:r.z.union([r.z.string(),r.z.array(r.z.string())])}).refine((e=>"between"===e.type?void 0!==e.afterLine&&void 0!==e.beforeLine&&void 0===e.line:void 0!==e.line&&void 0===e.afterLine&&void 0===e.beforeLine),{message:"Invalid line parameters"}))}).optional(),replaceLines:r.z.object({replacements:r.z.array(r.z.object({lines:r.z.string(),content:r.z.union([r.z.string(),r.z.array(r.z.string())])}))}).optional(),deleteString:r.z.object({patterns:r.z.array(r.z.string()),mode:r.z.enum(["all","unique"]).optional()}).optional(),deleteLines:r.z.object({lines:r.z.string()}).optional()}).transform(d).refine(u,{message:"path or documentId required"}).refine((e=>"replace-string"===e.operationType?e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteString&&!e.deleteLines:"insert-lines"===e.operationType?e.insertLines&&!e.replaceString&&!e.replaceLines&&!e.deleteString&&!e.deleteLines:"replace-lines"===e.operationType?e.replaceLines&&!e.replaceString&&!e.insertLines&&!e.deleteString&&!e.deleteLines:"delete-string"===e.operationType?e.deleteString&&!e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteLines:"delete-lines"===e.operationType&&e.deleteLines&&!e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteString),{message:"Invalid operation data"})),comment:r.z.string()}),exports.ManageFilesSchema=r.z.object({operations:r.z.array(r.z.object({operationType:r.z.enum(["create-directory","move-files","delete-files","get-file-info"]),createDirectory:r.z.object({path:r.z.string()}).optional(),moveFiles:r.z.object({moves:r.z.array(r.z.object({source:r.z.string(),destination:r.z.string()}))}).optional(),deleteFiles:r.z.object({targets:r.z.array(r.z.object({path:r.z.string().optional(),documentId:l.optional()}).transform(d).refine(u,{message:"path or documentId required"}))}).optional(),getFileInfo:r.z.object({path:r.z.string()}).optional()}).refine((e=>"create-directory"===e.operationType?e.createDirectory&&!e.moveFiles&&!e.deleteFiles&&!e.getFileInfo:"move-files"===e.operationType?e.moveFiles&&!e.createDirectory&&!e.deleteFiles&&!e.getFileInfo:"delete-files"===e.operationType?e.deleteFiles&&!e.createDirectory&&!e.moveFiles&&!e.getFileInfo:"get-file-info"===e.operationType&&e.getFileInfo&&!e.createDirectory&&!e.moveFiles&&!e.deleteFiles),{message:"Invalid operation data"})),comment:r.z.string()}),exports.RequestOperationsSchema=r.z.object({requestId:c.optional(),operation:r.z.enum(["complete","merge","approve","revert","get-info","list","search","delete-empty"]),complete:r.z.preprocess((e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e),r.z.object({summary:r.z.string(),skipTestsReason:exports.skipTestsReasonSchema,skipTests:exports.skipTestsSchema,criteriaComments:r.z.array(r.z.object({key:r.z.string().min(1),comment:r.z.string().min(1)})).optional()})).optional(),merge:r.z.preprocess((e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e),r.z.object({verifications:r.z.array(r.z.object({key:r.z.string().min(2).max(4),evidence:r.z.string().min(1)})).min(1)})).optional(),revert:r.z.object({reason:r.z.string(),confirmDestructive:r.z.boolean().optional()}).optional(),list:r.z.object({filters:r.z.object({type:r.z.enum(["feature","bugfix"]).optional(),status:r.z.enum(["pending_approval","open","in_progress","ready_for_review","ready_for_merge","resolving_conflicts","completed","canceled"]).optional(),planDocumentId:r.z.string().optional().describe("Narrow to requests linked to this Plan document (use the plan's id or local id)."),questDocumentId:r.z.string().optional().describe("Narrow to requests spawned under this Quest document (use the quest's id or local id)."),dateFrom:r.z.string().optional(),dateTo:r.z.string().optional(),limit:r.z.number().min(1).max(100).default(20),offset:r.z.number().min(0).default(0),sortBy:r.z.enum(["created","updated"]).default("updated"),sortDirection:r.z.enum(["asc","desc"]).default("desc")}).optional()}).optional(),search:r.z.object({query:r.z.string().min(1),searchIn:r.z.enum(["title","description","criteria","all"]).default("all"),filters:r.z.object({type:r.z.enum(["feature","bugfix"]).optional(),status:r.z.enum(["pending_approval","open","in_progress","ready_for_review","ready_for_merge","resolving_conflicts","completed","canceled"]).optional(),planDocumentId:r.z.string().optional().describe("Narrow to requests linked to this Plan document."),questDocumentId:r.z.string().optional().describe("Narrow to requests spawned under this Quest document."),dateFrom:r.z.string().optional(),dateTo:r.z.string().optional(),limit:r.z.number().min(1).max(100).default(20),offset:r.z.number().min(0).default(0)}).optional()}).optional(),approve:r.z.object({}).optional()}).superRefine(((e,t)=>{e.requestId
3514
3535
  const r=e.operation,n={complete:{field:"complete",hint:'Provide: { complete: { summary: "..." } }'},merge:{field:"merge",hint:'Provide: { merge: { verifications: [{key: "1a", evidence: "..."}] } }'},revert:{field:"revert",hint:'Provide: { revert: { reason: "..." } }'},search:{field:"search",hint:'Provide: { search: { query: "..." } }'}}[r]
3515
3536
  n&&!e[n.field]&&t.addIssue({code:"custom",message:`'${r}' operation requires '${n.field}' data. ${n.hint}`})})),exports.QuickWriteSchema=r.z.object({title:r.z.string().min(1,"Title is required"),description:r.z.string().min(1,"Description is required"),path:r.z.string().optional(),documentId:l.optional(),content:r.z.union([r.z.string(),r.z.array(r.z.string())]),format:r.z.enum(["text","json"]).optional(),append:r.z.boolean().optional()}).transform(d).refine(u,{message:"path or documentId required"}),exports.QuickReplaceSchema=r.z.object({title:r.z.string().min(1,"Title is required"),description:r.z.string().min(1,"Description is required"),operations:r.z.array(r.z.object({path:r.z.string().optional(),documentId:l.optional(),replacements:r.z.array(r.z.object({old:r.z.string().min(1),new:r.z.string(),match_mode:r.z.enum(["unique","all"]).optional()})).min(1),fuzzy_match:r.z.boolean().optional()}).transform(d).refine(u,{message:"path or documentId required"})).min(1),fuzzy_match:r.z.boolean().optional(),comment:r.z.string().min(1,"Comment describing the change is required")}),exports.QuickManageSchema=r.z.object({title:r.z.string().min(1,"Title is required"),description:r.z.string().min(1,"Description is required"),operations:r.z.array(r.z.object({operationType:r.z.enum(["create-directory","move-files","delete-files"]),createDirectory:r.z.object({path:r.z.string()}).optional(),moveFiles:r.z.object({moves:r.z.array(r.z.object({source:r.z.string(),destination:r.z.string()}))}).optional(),deleteFiles:r.z.object({targets:r.z.array(r.z.object({path:r.z.string().optional(),documentId:l.optional()}).transform(d).refine(u,{message:"path or documentId required"}))}).optional()})).min(1)}),exports.GenerateRequestJournalSchema=r.z.object({requestId:r.z.string().regex(/^req[_\d].+$/,"Invalid request ID format. Expected format: req_[26 chars] or req######")}),exports.MetaWorkflowListSchema=r.z.object({}),exports.MetaWorkflowGetSchema=r.z.object({workflowId:r.z.string().describe("Workflow ID to retrieve")}),exports.MetaWorkflowCreateSchema=r.z.object({name:r.z.string().min(1).describe("Unique name for the workflow"),shortName:r.z.string().min(1).max(10).describe("Short display name (required, max 10 chars)"),description:r.z.string().optional().describe("Description of what the workflow does"),documentation:r.z.string().optional().describe("Detailed documentation"),defaultAgent:r.z.string().optional().describe("Default agent template to use"),completionMode:r.z.enum(["auto-merge","ask-me","test-by-default","always-test"]).optional().describe("Testing axis: auto-merge (skip tests), ask-me (notify human on completion), test-by-default (tests run; opt-out allowed), always-test (tests required)"),reviewMode:r.z.enum(["auto-finalize","manual-review"]).optional().describe("Review axis: auto-finalize (server merges on pass/skip) or manual-review (human approves via UI)"),approvalMode:r.z.enum(["none","require-approval"]).optional().describe("Pre-work approval: none (default) or require-approval")}),exports.MetaWorkflowUpdateSchema=r.z.object({workflowId:r.z.string().describe("Workflow ID to update"),name:r.z.string().min(1).optional().describe("New name"),shortName:r.z.string().max(10).optional().describe("New short name (max 10 chars)"),description:r.z.string().optional().describe("New description"),documentation:r.z.string().optional().describe("New documentation"),defaultAgent:r.z.string().optional().describe("New default agent"),completionMode:r.z.enum(["auto-merge","ask-me","test-by-default","always-test"]).optional().describe("Testing axis"),reviewMode:r.z.enum(["auto-finalize","manual-review"]).optional().describe("Review axis"),approvalMode:r.z.enum(["none","require-approval"]).optional().describe("Pre-work approval: none (default) or require-approval")}),exports.MetaRecipeListSchema=r.z.object({}),exports.MetaRecipeGetSchema=r.z.object({recipeId:r.z.string().describe("Recipe ID to retrieve")}),exports.MetaRecipeCreateSchema=r.z.object({name:r.z.string().min(1).describe("Unique name for the recipe"),description:r.z.string().optional().describe("Description of what the recipe does"),documentation:r.z.string().optional().describe("Detailed documentation")}),exports.MetaRecipeAddHookSchema=r.z.object({recipeId:r.z.string().describe("Recipe ID to add the hook to"),hookId:r.z.string().describe("Hook template ID to add"),position:r.z.number().int().min(0).optional().describe("Position in the recipe (0-based, appended if omitted)")}),exports.MetaRecipeRemoveHookSchema=r.z.object({recipeId:r.z.string().describe("Recipe ID to remove the hook from"),hookId:r.z.string().describe("Hook template ID to remove")}),exports.MetaHookListSchema=r.z.object({}),exports.MetaHookCreateSchema=r.z.object({name:r.z.string().min(1).describe("Unique name for the hook"),description:r.z.string().optional().describe("Description of what the hook does"),eventType:r.z.string().describe("Event type that triggers this hook (e.g., session_idle, request_completed, custom:plan_ready)"),category:r.z.enum(["alchemy","session","message","editing","reading","file_management","documents","history","tool"]).default("session").describe("Hook category"),agent:r.z.string().describe("Agent template to execute when hook fires"),promptTemplate:r.z.string().describe("Prompt template with {{variable}} substitution"),condition:r.z.record(r.z.any()).optional().describe("Condition object for filtering when hook fires"),executionMode:r.z.enum(["same_session","sub_agent","background","notification","direct_report"]).default("background").describe("How to execute the hook"),timing:r.z.enum(["before","after"]).default("after").describe("When to fire relative to the event"),maxIterations:r.z.number().int().min(1).optional().describe("Maximum times to fire per session"),emitEvent:r.z.string().optional().describe("Custom event to emit on completion"),emitEventOnCondition:r.z.union([r.z.object({condition:r.z.object({field:r.z.string(),operator:r.z.enum(["contains","not_contains","equals","not_equals","matches","not_matches"]),value:r.z.string()}),event:r.z.string()}),r.z.array(r.z.object({condition:r.z.object({field:r.z.string(),operator:r.z.enum(["contains","not_contains","equals","not_equals","matches","not_matches"]),value:r.z.string()}),event:r.z.string()}))]).optional().describe("Conditional event emission based on output")}),exports.MetaWorkflowAddRecipeSchema=r.z.object({workflowId:r.z.string().describe("Workflow ID to add the recipe to"),recipeId:r.z.string().describe("Recipe ID to add"),position:r.z.number().int().min(0).optional().describe("Position in the workflow (0-based, appended if omitted)")}),exports.MetaWorkflowRemoveRecipeSchema=r.z.object({workflowId:r.z.string().describe("Workflow ID to remove the recipe from"),recipeId:r.z.string().describe("Recipe ID to remove")}),exports.MetaAgentListSchema=r.z.object({}),exports.MetaReferenceSchema=r.z.object({}),exports.TestingProcessListSchema=r.z.object({}),exports.TestingProcessGetSchema=r.z.object({processId:r.z.string().describe("Test process ID to retrieve")})
3516
3537
  const g=r.z.object({name:r.z.string().min(1).describe('Command name (e.g., "Build", "Unit Tests")'),command:r.z.string().min(1).describe('Shell command to run (e.g., "npm test")'),required:r.z.boolean().default(!0).describe("If true, failure blocks the request (default: true)"),timeout:r.z.number().int().min(10).max(3600).default(300).describe("Max execution time in seconds (default: 300)")})
@@ -3652,14 +3673,14 @@ try{const C=e,{operations:_,comment:E}=C,{enforceActiveTask:S}=await Promise.res
3652
3673
  if("errorResult"in T)return T.errorResult
3653
3674
  const{taskId:q}=T
3654
3675
  if(y.info("Inserting lines into files and documents",{taskId:q,operationCount:_.length,comment:E}),!_||0===_.length)throw new d.ValidationError("At least one operation is required")
3655
- const P=_.filter((e=>e.path)),j=_.filter((e=>e.documentId))
3656
- y.info("Operation breakdown",{fileOperations:P.length,documentOperations:j.length})
3676
+ const P=_.filter((e=>e.path)),R=_.filter((e=>e.documentId))
3677
+ y.info("Operation breakdown",{fileOperations:P.length,documentOperations:R.length})
3657
3678
  const x=[]
3658
3679
  for(const e of P){const{normalizedPath:t,fullPath:r}=l.validateAndNormalizePath(e.path,n)
3659
- x.push({type:"file",path:t,fullPath:r,insertions:e.insertions})}const R=[]
3660
- if(j.length>0&&!n.documentFileManager)throw Error("Document file manager not available - cannot process document operations")
3661
- for(const e of j)R.push({type:"document",documentId:e.documentId,insertions:e.insertions})
3662
- if(R.length>0&&n.apiClient){const e=R.map((e=>e.documentId))
3680
+ x.push({type:"file",path:t,fullPath:r,insertions:e.insertions})}const j=[]
3681
+ if(R.length>0&&!n.documentFileManager)throw Error("Document file manager not available - cannot process document operations")
3682
+ for(const e of R)j.push({type:"document",documentId:e.documentId,insertions:e.insertions})
3683
+ if(j.length>0&&n.apiClient){const e=j.map((e=>e.documentId))
3663
3684
  try{await(0,p.ensureDocumentsNotLocked)(e,n.apiClient,y,m,"insert-lines-documents")}catch(e){if(e instanceof d.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
3664
3685
  throw e}}k=l.FileOperationPhase.TASK_VALIDATION,m.incrementCounter("insert_lines_phase",1,{phase:k})
3665
3686
  const M=x.length>0?x[0].path:"document-operation",O=await l.validateTaskId(q,M,o,y,m,v.metricsPrefix),$=O?.requestId
@@ -3671,9 +3692,9 @@ const F=await l.ensureCorrectWorkspaceContext({taskPromoted:A.taskPromoted,reque
3671
3692
  if(!F.success)throw Error(F.error)
3672
3693
  if(x.length>0){y.debug("Re-validating file paths with correct workspace context",{operationCount:x.length,workspaceBasePath:n.workspaceManager?.getCurrentContext(n.currentSessionId).basePath})
3673
3694
  for(let e=0;e<x.length;e++){const t=x[e],r=P[e],{normalizedPath:o,fullPath:s}=l.validateAndNormalizePath(r.path,n)
3674
- x[e]={...t,path:o,fullPath:s}}}if(R.length>0&&$)try{await(0,u.validateRequestStatusForDocumentOperation)($,i,y,m,"insert-lines-document")}catch(e){if(e instanceof d.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
3695
+ x[e]={...t,path:o,fullPath:s}}}if(j.length>0&&$)try{await(0,u.validateRequestStatusForDocumentOperation)($,i,y,m,"insert-lines-document")}catch(e){if(e instanceof d.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
3675
3696
  throw e}k=l.FileOperationPhase.RECORD_CREATION,m.incrementCounter("insert_lines_phase",1,{phase:k})
3676
- const N=[...x.map((e=>({type:"file",path:e.path,insertions:e.insertions}))),...R.map((e=>({type:"document",documentId:e.documentId,insertions:e.insertions})))],{toolRecordId:z}=await l.createFileOperationRecords(q,$,n.projectId,v.recordType,M,{operationCount:_.length,fileOperations:x.length,documentOperations:R.length,operations:N,userComment:E},o,s,y,m,v.metricsPrefix,f)
3697
+ const N=[...x.map((e=>({type:"file",path:e.path,insertions:e.insertions}))),...j.map((e=>({type:"document",documentId:e.documentId,insertions:e.insertions})))],{toolRecordId:z}=await l.createFileOperationRecords(q,$,n.projectId,v.recordType,M,{operationCount:_.length,fileOperations:x.length,documentOperations:j.length,operations:N,userComment:E},o,s,y,m,v.metricsPrefix,f)
3677
3698
  if(b=z,I=z,k=v.fileOperationPhase,m.incrementCounter("insert_lines_phase",1,{phase:k}),x.length>0&&n.git)try{y.debug("[INSERT-LINES] Checking for user changes before insert operations")
3678
3699
  const e=await(0,h.commitUserChangesBeforeToolOperation)(n.git,"insert-lines",{requestId:$,taskId:q,workspaceManager:n.workspaceManager},y,m)
3679
3700
  e.userChangesCommitted&&(y.debug("[INSERT-LINES] User changes committed before insert operations",{commitHash:e.commitHash?.substring(0,8),filesCommitted:e.filesCommitted}),y.info("User changes committed before insert-lines operation",{taskId:q,fileOperationsCount:x.length,userCommitHash:e.commitHash?.substring(0,8),userFilesCommitted:e.filesCommitted}))}catch(e){y.debug("[INSERT-LINES] Pre-tool commit failed, continuing with insertions",{error:e instanceof Error?e.message:e+""}),y.warn("Pre-tool user changes commit failed, continuing with insert operation",{taskId:q,fileOperationsCount:x.length,error:e instanceof Error?e.message:e+""})}y.debug("[INSERT-LINES] Proceeding with file operations",{fileCount:x.length})
@@ -3682,13 +3703,13 @@ let H=0,B=0,U=0
3682
3703
  for(const e of x){const t={type:"file",path:e.path,success:!1,insertions:[],successfulInsertions:0,failedInsertions:0,totalLinesAdded:0}
3683
3704
  try{if(await c.default.access(e.fullPath),!(await c.default.stat(e.fullPath)).isFile())throw Error("Path is not a file: "+e.path)
3684
3705
  const n=await c.default.readFile(e.fullPath,"utf8"),{newContent:o,results:s}=await r(n,e.insertions)
3685
- t.insertions=s,t.successfulInsertions=s.filter((e=>e.success)).length,t.failedInsertions=s.filter((e=>!e.success)).length,t.totalLinesAdded=s.filter((e=>e.success)).reduce(((e,t)=>e+(t.linesAdded||0)),0),o!==n&&t.successfulInsertions>0?(await c.default.writeFile(e.fullPath,o,"utf8"),t.success=!0,U++,y.info("File updated successfully",{path:e.path,successfulInsertions:t.successfulInsertions,failedInsertions:t.failedInsertions,linesAdded:t.totalLinesAdded})):0===t.failedInsertions&&(t.success=!0),H+=t.failedInsertions,B+=t.totalLinesAdded}catch(r){t.error=(0,g.getErrorMessage)(r),t.failedInsertions=e.insertions.length,H+=t.failedInsertions,y.error("File operation failed",{path:e.path,error:t.error})}L.push(t)}for(const e of R){const t={type:"document",documentId:e.documentId,success:!1,insertions:[],successfulInsertions:0,failedInsertions:0,totalLinesAdded:0}
3706
+ t.insertions=s,t.successfulInsertions=s.filter((e=>e.success)).length,t.failedInsertions=s.filter((e=>!e.success)).length,t.totalLinesAdded=s.filter((e=>e.success)).reduce(((e,t)=>e+(t.linesAdded||0)),0),o!==n&&t.successfulInsertions>0?(await c.default.writeFile(e.fullPath,o,"utf8"),t.success=!0,U++,y.info("File updated successfully",{path:e.path,successfulInsertions:t.successfulInsertions,failedInsertions:t.failedInsertions,linesAdded:t.totalLinesAdded})):0===t.failedInsertions&&(t.success=!0),H+=t.failedInsertions,B+=t.totalLinesAdded}catch(r){t.error=(0,g.getErrorMessage)(r),t.failedInsertions=e.insertions.length,H+=t.failedInsertions,y.error("File operation failed",{path:e.path,error:t.error})}L.push(t)}for(const e of j){const t={type:"document",documentId:e.documentId,success:!1,insertions:[],successfulInsertions:0,failedInsertions:0,totalLinesAdded:0}
3686
3707
  try{const o=await n.documentFileManager.getDocumentAsFile(e.documentId),s=n.documentFileManager.getCachedDocumentInfo(e.documentId)
3687
3708
  t.filename=s?.filename
3688
3709
  const i=await c.default.readFile(o,"utf8"),{newContent:a,results:l}=await r(i,e.insertions)
3689
- t.insertions=l,t.successfulInsertions=l.filter((e=>e.success)).length,t.failedInsertions=l.filter((e=>!e.success)).length,t.totalLinesAdded=l.filter((e=>e.success)).reduce(((e,t)=>e+(t.linesAdded||0)),0),a!==i&&t.successfulInsertions>0?(await c.default.writeFile(o,a,"utf8"),n.documentFileManager.markForSync(e.documentId),t.success=!0,U++,y.info("Document updated successfully",{documentId:e.documentId,filename:t.filename,successfulInsertions:t.successfulInsertions,failedInsertions:t.failedInsertions,linesAdded:t.totalLinesAdded})):0===t.failedInsertions&&(t.success=!0),H+=t.failedInsertions,B+=t.totalLinesAdded}catch(r){t.error=(0,g.getErrorMessage)(r),t.failedInsertions=e.insertions.length,H+=t.failedInsertions,y.error("Document operation failed",{documentId:e.documentId,error:t.error})}L.push(t)}if(R.length>0)try{for(const e of R){const t=L.find((t=>t.documentId===e.documentId))
3710
+ t.insertions=l,t.successfulInsertions=l.filter((e=>e.success)).length,t.failedInsertions=l.filter((e=>!e.success)).length,t.totalLinesAdded=l.filter((e=>e.success)).reduce(((e,t)=>e+(t.linesAdded||0)),0),a!==i&&t.successfulInsertions>0?(await c.default.writeFile(o,a,"utf8"),n.documentFileManager.markForSync(e.documentId),t.success=!0,U++,y.info("Document updated successfully",{documentId:e.documentId,filename:t.filename,successfulInsertions:t.successfulInsertions,failedInsertions:t.failedInsertions,linesAdded:t.totalLinesAdded})):0===t.failedInsertions&&(t.success=!0),H+=t.failedInsertions,B+=t.totalLinesAdded}catch(r){t.error=(0,g.getErrorMessage)(r),t.failedInsertions=e.insertions.length,H+=t.failedInsertions,y.error("Document operation failed",{documentId:e.documentId,error:t.error})}L.push(t)}if(j.length>0)try{for(const e of j){const t=L.find((t=>t.documentId===e.documentId))
3690
3711
  t?.success&&await n.documentFileManager.syncDocument(e.documentId)}}catch(e){y.warn("Some documents may not have synced properly",{error:e})}const G=await l.handleMultiFileGitCommit(q,x,L,U,{operationType:"Insert lines",toolRecordId:I,successField:"successfulInsertions",logger:y,metrics:m,metricsPrefix:"insert_lines"},n),W=l.processMultiFileOperationResults(L,"successfulInsertions"),{successfulOperations:V,failedOperations:Q,totalSuccessfulChanges:K,overallSuccess:J,successfulFiles:Y,failedFiles:Z}=W
3691
- try{await s.updateToolRecord(I,{metadata:{path:x.length>0?x[0].path:"document:"+R[0]?.documentId,status:J?"completed":"failed",error:J?void 0:Q+" operations failed",totalOperations:L.length,successfulFiles:Y,failedFiles:Z,operations:N,totalSuccessfulInsertions:K,totalFailedInsertions:H,totalLinesAdded:B,operationsWithChanges:U,processingTime:Date.now()-w,userComment:E,...x.length>0&&G.success&&G.commitHash&&{commitHash:G.commitHash,previousCommitHash:G.previousCommitHash||void 0}},note:J?`Line insertion completed: ${V}/${L.length} operations successful${x.length>0?" (committed to git)":""}`:`Line insertion partially failed: ${Q}/${L.length} operations failed`})}catch(e){y.warn("Failed to update tool record",{recordError:e})}let X="## Line Insertion Results\n\n"
3712
+ try{await s.updateToolRecord(I,{metadata:{path:x.length>0?x[0].path:"document:"+j[0]?.documentId,status:J?"completed":"failed",error:J?void 0:Q+" operations failed",totalOperations:L.length,successfulFiles:Y,failedFiles:Z,operations:N,totalSuccessfulInsertions:K,totalFailedInsertions:H,totalLinesAdded:B,operationsWithChanges:U,processingTime:Date.now()-w,userComment:E,...x.length>0&&G.success&&G.commitHash&&{commitHash:G.commitHash,previousCommitHash:G.previousCommitHash||void 0}},note:J?`Line insertion completed: ${V}/${L.length} operations successful${x.length>0?" (committed to git)":""}`:`Line insertion partially failed: ${Q}/${L.length} operations failed`})}catch(e){y.warn("Failed to update tool record",{recordError:e})}let X="## Line Insertion Results\n\n"
3692
3713
  if(n.workspaceManager){const e=n.workspaceManager.getCurrentContext(n.currentSessionId)
3693
3714
  e.permanentBranch||(X+=`**Branch:** \`${e.branchName}\`\n\n`)}if(X+="**Summary:**\n",X+=`- **Files processed:** ${V+Q}\n`,X+=`- **Successful operations:** ${V}\n`,X+=`- **Failed operations:** ${Q}\n`,X+=`- **Total insertions:** ${K} successful, ${H} failed\n`,X+=`- **Total Lines Added:** ${B}\n`,X+="\n",G.success&&G.commitHash&&(X+="**Git Commit:**\n",X+=`- **Commit Hash:** ${G.commitHash.substring(0,8)}\n`,X+=`- **Files Committed:** ${G.filesCommitted.length}\n`,G.previousCommitHash&&(X+=`- **Previous Commit:** ${G.previousCommitHash.substring(0,8)}\n`),X+="\n"),1===L.length){const e=L[0]
3694
3715
  if(X+=`**Operation:** ${e.success?"\u2705 Success":"\u274c Failed"}\n`,"file"===e.type?X+=`**File:** ${e.path}\n`:(X+="**Document:** "+e.documentId,e.filename&&(X+=` (${e.filename})`),X+="\n"),e.error?X+=`**Error:** ${e.error}\n`:(X+=`**Insertions:** ${e.successfulInsertions} successful`,e.failedInsertions>0&&(X+=`, ${e.failedInsertions} failed`),X+="\n",X+=`**Lines Added:** ${e.totalLinesAdded}\n`),e.insertions.some((e=>!e.success))){X+="**Failed Insertion Details:**\n"
@@ -3696,7 +3717,7 @@ for(const t of e.insertions.filter((e=>!e.success))){const e="between"===t.type?
3696
3717
  X+=`- ${t.type} insertion ${e}: ${t.error}\n`}}}else{X+="**Summary:**\n",X+=`- **Total Operations:** ${L.length}\n`,X+=`- **Successful:** ${V}\n`,X+=`- **Failed:** ${Q}\n`,X+=`- **Total Insertions:** ${K} successful, ${H} failed\n`,X+=`- **Total Lines Added:** ${B}\n\n`,X+="**Detailed Results:**\n\n"
3697
3718
  for(const e of L){if("file"===e.type?X+=`### File: ${e.path}\n`:(X+="### Document: "+e.documentId,e.filename&&(X+=` (${e.filename})`),X+="\n"),X+=`- **Status:** ${e.success?"\u2705 Success":"\u274c Failed"}\n`,e.error?X+=`- **Error:** ${e.error}\n`:(X+=`- **Insertions:** ${e.successfulInsertions} successful`,e.failedInsertions>0&&(X+=`, ${e.failedInsertions} failed`),X+="\n",X+=`- **Lines Added:** ${e.totalLinesAdded}\n`),e.insertions.some((e=>!e.success))){X+="- **Failed Insertion Details:**\n"
3698
3719
  for(const t of e.insertions.filter((e=>!e.success))){const e="between"===t.type?`between lines ${t.afterLine} and ${t.beforeLine}`:`${t.type} line ${t.line}`
3699
- X+=` - ${t.type} insertion ${e}: ${t.error}\n`}}X+="\n"}}R.length>0&&(X+="\u{1f4c4} **Document Changes:** Modified documents have been automatically synced back to the tracking system.\n\n")
3720
+ X+=` - ${t.type} insertion ${e}: ${t.error}\n`}}X+="\n"}}j.length>0&&(X+="\u{1f4c4} **Document Changes:** Modified documents have been automatically synced back to the tracking system.\n\n")
3700
3721
  const ee=Date.now()-w
3701
3722
  return y.info("Insert lines operation completed",{duration:ee,success:J,totalOperations:L.length,successfulOperations:V,failedOperations:Q}),m.incrementCounter("insert_lines_total",1,{success:J}),{content:[{type:"text",text:X}],isError:!J}}catch(t){const r=Date.now()-w
3702
3723
  if(y.error("Insert lines operation failed",{error:t instanceof Error?t.stack:t+"",duration:r,phase:k}),I)try{await s.updateToolRecord(I,{metadata:{path:e?.operations?.[0]?.path||e?.operations?.[0]?.documentId,status:"failed",error:(0,g.getErrorMessage)(t),phase:k,duration:r,userComment:e?.comment},note:"Line insertion failed: "+(0,g.getErrorMessage)(t)})}catch(e){}return m.incrementCounter("insert_lines_errors",1,{phase:k}),{content:[{type:"text",text:"\u274c **Insert Lines Failed:** "+(0,g.getErrorMessage)(t)}],isError:!0}}}
@@ -3718,15 +3739,15 @@ const q=new p.DocumentClient(e,f,y,_,v||void 0,C,{timeout:w.timeout,retryAttempt
3718
3739
  I.registerClient(q)
3719
3740
  const P=new m.ToolRecordClient(e,f,y,_,v||void 0,C,{timeout:w.timeout,retryAttempts:w.retryAttempts,retryDelay:w.retryDelay,circuitBreakerThreshold:w.circuitBreakerThreshold,circuitBreakerResetTime:w.circuitBreakerResetTime})
3720
3741
  I.registerClient(P)
3721
- const j=new d.ProxyClient(e,f,y,_,v||void 0,C,{timeout:w.timeout,retryAttempts:w.retryAttempts,retryDelay:w.retryDelay,circuitBreakerThreshold:w.circuitBreakerThreshold,circuitBreakerResetTime:w.circuitBreakerResetTime})
3722
- if(I.registerClient(j),!w.alchemyPath)throw Error("alchemyPath is required for ProjectSynchronizer creation. Cannot create API client without project synchronization capabilities.")
3742
+ const R=new d.ProxyClient(e,f,y,_,v||void 0,C,{timeout:w.timeout,retryAttempts:w.retryAttempts,retryDelay:w.retryDelay,circuitBreakerThreshold:w.circuitBreakerThreshold,circuitBreakerResetTime:w.circuitBreakerResetTime})
3743
+ if(I.registerClient(R),!w.alchemyPath)throw Error("alchemyPath is required for ProjectSynchronizer creation. Cannot create API client without project synchronization capabilities.")
3723
3744
  try{f.info("Creating ProjectSynchronizer components")
3724
3745
  const{ProjectRegistry,ProjectFileSystem,ProjectSynchronizer}=await t.e(478).then(t.bind(t,5478)),e=new ProjectRegistry(f),r=new ProjectFileSystem(f),n=new ProjectSynchronizer(f,e,new l.ProjectClientAdapter(_,f),r,{createRemoteProjects:!0})
3725
3746
  _.setProjectSynchronizer(n),f.info("ProjectSynchronizer created and connected to ProjectClient")}catch(e){const t="Failed to create required ProjectSynchronizer components: "+(0,h.getErrorMessage)(e)
3726
- throw f.error(t,{error:e instanceof Error?e.stack:e+""}),Error(t)}const x=new o.ApiClient(k,_,E,S,T,q,P,j,f)
3747
+ throw f.error(t,{error:e instanceof Error?e.stack:e+""}),Error(t)}const x=new o.ApiClient(k,_,E,S,T,q,P,R,f)
3727
3748
  f.info("API client initialized with specialized clients"),v?(x.setAuthToken(v),f.info("Auth token set on ApiClient during creation",{hasToken:!0,tokenLength:v.length})):f.warn("No auth token available during ApiClient creation",{info:"Client will need to authenticate before making requests"})
3728
- const R=I.verifyAllClients()
3729
- R.success?f.info("Token consistency verified across all clients"):f.error(`Token inconsistency detected in ${R.failedClients.length} clients: ${R.failedClients.join(", ")}`)
3749
+ const j=I.verifyAllClients()
3750
+ j.success?f.info("Token consistency verified across all clients"):f.error(`Token inconsistency detected in ${j.failedClients.length} clients: ${j.failedClients.join(", ")}`)
3730
3751
  const M=x.getAuthToken()
3731
3752
  return v&&!M?(f.error("CRITICAL: ApiClient token was not set properly!"),x.setAuthToken(v)):M&&f.info("ApiClient token verified",{tokenLength:M.length,matches:M===v}),f.info("API client creation completed successfully"),x}}},6540(e,exports){"use strict"
3732
3753
  var t
@@ -3806,34 +3827,34 @@ try{const _=e,{operations:E,comment:S}=_,{enforceActiveTask:T}=await Promise.res
3806
3827
  if("errorResult"in q)return q.errorResult
3807
3828
  const{taskId:P}=q
3808
3829
  if(w.info("Replacing lines in files and documents",{taskId:P,operationCount:E.length,comment:S}),!E||0===E.length)throw new d.ValidationError("At least one operation is required")
3809
- const j=E.filter((e=>e.path)),x=E.filter((e=>e.documentId))
3810
- w.info("Operation breakdown",{fileOperations:j.length,documentOperations:x.length})
3811
- const R=[]
3812
- for(const e of j){const{normalizedPath:t,fullPath:r}=l.validateAndNormalizePath(e.path,n)
3813
- R.push({type:"file",path:t,fullPath:r,replacements:e.replacements.map((e=>({lines:(0,f.parseLineSpec)(e.lines),content:e.content})))})}const M=[]
3830
+ const R=E.filter((e=>e.path)),x=E.filter((e=>e.documentId))
3831
+ w.info("Operation breakdown",{fileOperations:R.length,documentOperations:x.length})
3832
+ const j=[]
3833
+ for(const e of R){const{normalizedPath:t,fullPath:r}=l.validateAndNormalizePath(e.path,n)
3834
+ j.push({type:"file",path:t,fullPath:r,replacements:e.replacements.map((e=>({lines:(0,f.parseLineSpec)(e.lines),content:e.content})))})}const M=[]
3814
3835
  if(x.length>0&&!n.documentFileManager)throw Error("Document file manager not available - cannot process document operations")
3815
3836
  for(const e of x)M.push({type:"document",documentId:e.documentId,replacements:e.replacements.map((e=>({lines:(0,f.parseLineSpec)(e.lines),content:e.content})))})
3816
3837
  if(M.length>0&&n.apiClient){const e=M.map((e=>e.documentId))
3817
3838
  try{await(0,p.ensureDocumentsNotLocked)(e,n.apiClient,w,m,"replace-lines-documents")}catch(e){if(e instanceof d.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
3818
3839
  throw e}}v=l.FileOperationPhase.TASK_VALIDATION,m.incrementCounter("replace_lines_phase",1,{phase:v})
3819
- const O=R.length>0?R[0].path:"document-operation",$=await l.validateTaskId(P,O,o,w,m,C.metricsPrefix),D=$?.requestId
3840
+ const O=j.length>0?j[0].path:"document-operation",$=await l.validateTaskId(P,O,o,w,m,C.metricsPrefix),D=$?.requestId
3820
3841
  let A=null
3821
- R.length>0&&D&&(A=await l.validateWorkItemStates(P,R[0].path,o,i,n,w,m,C.metricsPrefix))
3842
+ j.length>0&&D&&(A=await l.validateWorkItemStates(P,j[0].path,o,i,n,w,m,C.metricsPrefix))
3822
3843
  let F={taskPromoted:!1,requestPromoted:!1}
3823
- R.length>0&&D&&A&&(F=await l.autoPromoteTaskStatus(P,$.status,D,A.request?.status,R[0].path,o,i,w,m,C.metricsPrefix),F.taskPromoted&&w.info("Auto-promoted task to in_progress for replace-lines operation",{taskId:P,requestId:D,previousStatus:$.status,newStatus:F.newTaskStatus,triggeringOperation:"replace-lines"}))
3844
+ j.length>0&&D&&A&&(F=await l.autoPromoteTaskStatus(P,$.status,D,A.request?.status,j[0].path,o,i,w,m,C.metricsPrefix),F.taskPromoted&&w.info("Auto-promoted task to in_progress for replace-lines operation",{taskId:P,requestId:D,previousStatus:$.status,newStatus:F.newTaskStatus,triggeringOperation:"replace-lines"}))
3824
3845
  const N=await l.ensureCorrectWorkspaceContext({taskPromoted:F.taskPromoted,requestId:D,context:n,operation:"replace-lines",logger:w,metrics:m,metricsPrefix:C.metricsPrefix})
3825
3846
  if(!N.success)throw Error(N.error)
3826
- if(R.length>0){w.debug("Re-validating file paths with correct workspace context",{operationCount:R.length,workspaceBasePath:n.workspaceManager?.getCurrentContext(n.currentSessionId).basePath})
3827
- for(let e=0;e<R.length;e++){const t=R[e],r=j[e],{normalizedPath:o,fullPath:s}=l.validateAndNormalizePath(r.path,n)
3828
- R[e]={...t,path:o,fullPath:s}}}if(M.length>0&&D)try{await(0,u.validateRequestStatusForDocumentOperation)(D,i,w,m,"replace-lines-document")}catch(e){if(e instanceof d.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
3847
+ if(j.length>0){w.debug("Re-validating file paths with correct workspace context",{operationCount:j.length,workspaceBasePath:n.workspaceManager?.getCurrentContext(n.currentSessionId).basePath})
3848
+ for(let e=0;e<j.length;e++){const t=j[e],r=R[e],{normalizedPath:o,fullPath:s}=l.validateAndNormalizePath(r.path,n)
3849
+ j[e]={...t,path:o,fullPath:s}}}if(M.length>0&&D)try{await(0,u.validateRequestStatusForDocumentOperation)(D,i,w,m,"replace-lines-document")}catch(e){if(e instanceof d.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
3829
3850
  throw e}v=l.FileOperationPhase.RECORD_CREATION,m.incrementCounter("replace_lines_phase",1,{phase:v})
3830
- const z=[...R.map((e=>({type:"file",path:e.path,replacements:e.replacements}))),...M.map((e=>({type:"document",documentId:e.documentId,replacements:e.replacements})))],{toolRecordId:L}=await l.createFileOperationRecords(P,D,n.projectId,C.recordType,O,{operationCount:E.length,fileOperations:R.length,documentOperations:M.length,operations:z,userComment:S},o,s,w,m,C.metricsPrefix,y)
3831
- if(I=L,k=L,v=C.fileOperationPhase,m.incrementCounter("replace_lines_phase",1,{phase:v}),R.length>0&&n.git)try{w.debug("[REPLACE-LINES] Checking for user changes before replace operations")
3851
+ const z=[...j.map((e=>({type:"file",path:e.path,replacements:e.replacements}))),...M.map((e=>({type:"document",documentId:e.documentId,replacements:e.replacements})))],{toolRecordId:L}=await l.createFileOperationRecords(P,D,n.projectId,C.recordType,O,{operationCount:E.length,fileOperations:j.length,documentOperations:M.length,operations:z,userComment:S},o,s,w,m,C.metricsPrefix,y)
3852
+ if(I=L,k=L,v=C.fileOperationPhase,m.incrementCounter("replace_lines_phase",1,{phase:v}),j.length>0&&n.git)try{w.debug("[REPLACE-LINES] Checking for user changes before replace operations")
3832
3853
  const e=await(0,h.commitUserChangesBeforeToolOperation)(n.git,"replace-lines",{requestId:D,taskId:P,workspaceManager:n.workspaceManager},w,m)
3833
- e.userChangesCommitted&&(w.debug("[REPLACE-LINES] User changes committed before replace operations",{commitHash:e.commitHash?.substring(0,8),filesCommitted:e.filesCommitted}),w.info("User changes committed before replace-lines operation",{taskId:P,fileOperationsCount:R.length,userCommitHash:e.commitHash?.substring(0,8),userFilesCommitted:e.filesCommitted}))}catch(e){w.debug("[REPLACE-LINES] Pre-tool commit failed, continuing with replacements",{error:e instanceof Error?e.message:e+""}),w.warn("Pre-tool user changes commit failed, continuing with replace operation",{taskId:P,fileOperationsCount:R.length,error:e instanceof Error?e.message:e+""})}w.debug("[REPLACE-LINES] Proceeding with file operations",{fileCount:R.length})
3854
+ e.userChangesCommitted&&(w.debug("[REPLACE-LINES] User changes committed before replace operations",{commitHash:e.commitHash?.substring(0,8),filesCommitted:e.filesCommitted}),w.info("User changes committed before replace-lines operation",{taskId:P,fileOperationsCount:j.length,userCommitHash:e.commitHash?.substring(0,8),userFilesCommitted:e.filesCommitted}))}catch(e){w.debug("[REPLACE-LINES] Pre-tool commit failed, continuing with replacements",{error:e instanceof Error?e.message:e+""}),w.warn("Pre-tool user changes commit failed, continuing with replace operation",{taskId:P,fileOperationsCount:j.length,error:e instanceof Error?e.message:e+""})}w.debug("[REPLACE-LINES] Proceeding with file operations",{fileCount:j.length})
3834
3855
  const H=[]
3835
3856
  let B=0,U=0,G=0,W=0
3836
- for(const e of R){const t={type:"file",path:e.path,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0,totalLinesReplaced:0,totalLinesAdded:0}
3857
+ for(const e of j){const t={type:"file",path:e.path,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0,totalLinesReplaced:0,totalLinesAdded:0}
3837
3858
  try{if(await c.default.access(e.fullPath),!(await c.default.stat(e.fullPath)).isFile())throw Error("Path is not a file: "+e.path)
3838
3859
  const n=await c.default.readFile(e.fullPath,"utf8"),{newContent:o,results:s}=await r(n,e.replacements)
3839
3860
  t.replacements=s,t.successfulReplacements=s.filter((e=>e.success)).length,t.failedReplacements=s.filter((e=>!e.success)).length,t.totalLinesReplaced=s.filter((e=>e.success)).reduce(((e,t)=>e+(t.linesReplaced||0)),0),t.totalLinesAdded=s.filter((e=>e.success)).reduce(((e,t)=>e+(t.linesAdded||0)),0),o!==n&&t.successfulReplacements>0?(await c.default.writeFile(e.fullPath,o,"utf8"),t.success=!0,W++,w.info("File updated successfully",{path:e.path,successfulReplacements:t.successfulReplacements,failedReplacements:t.failedReplacements,linesReplaced:t.totalLinesReplaced,linesAdded:t.totalLinesAdded})):0===t.failedReplacements&&(t.success=!0),B+=t.failedReplacements,U+=t.totalLinesReplaced,G+=t.totalLinesAdded}catch(r){t.error=(0,g.getErrorMessage)(r),t.failedReplacements=e.replacements.length,B+=t.failedReplacements,w.error("File operation failed",{path:e.path,error:t.error})}H.push(t)}for(const e of M){const t={type:"document",documentId:e.documentId,success:!1,replacements:[],successfulReplacements:0,failedReplacements:0,totalLinesReplaced:0,totalLinesAdded:0}
@@ -3841,8 +3862,8 @@ try{const o=await n.documentFileManager.getDocumentAsFile(e.documentId),s=n.docu
3841
3862
  t.filename=s?.filename
3842
3863
  const i=await c.default.readFile(o,"utf8"),{newContent:a,results:l}=await r(i,e.replacements)
3843
3864
  t.replacements=l,t.successfulReplacements=l.filter((e=>e.success)).length,t.failedReplacements=l.filter((e=>!e.success)).length,t.totalLinesReplaced=l.filter((e=>e.success)).reduce(((e,t)=>e+(t.linesReplaced||0)),0),t.totalLinesAdded=l.filter((e=>e.success)).reduce(((e,t)=>e+(t.linesAdded||0)),0),a!==i&&t.successfulReplacements>0?(await c.default.writeFile(o,a,"utf8"),n.documentFileManager.markForSync(e.documentId),t.success=!0,W++,w.info("Document updated successfully",{documentId:e.documentId,filename:t.filename,successfulReplacements:t.successfulReplacements,failedReplacements:t.failedReplacements,linesReplaced:t.totalLinesReplaced,linesAdded:t.totalLinesAdded})):0===t.failedReplacements&&(t.success=!0),B+=t.failedReplacements,U+=t.totalLinesReplaced,G+=t.totalLinesAdded}catch(r){t.error=(0,g.getErrorMessage)(r),t.failedReplacements=e.replacements.length,B+=t.failedReplacements,w.error("Document operation failed",{documentId:e.documentId,error:t.error})}H.push(t)}if(M.length>0)try{for(const e of M){const t=H.find((t=>t.documentId===e.documentId))
3844
- t?.success&&await n.documentFileManager.syncDocument(e.documentId)}}catch(e){w.warn("Some documents may not have synced properly",{error:e})}const V=await l.handleMultiFileGitCommit(P,R,H,W,{operationType:"Replace lines",toolRecordId:k,successField:"successfulReplacements",logger:w,metrics:m,metricsPrefix:"replace_lines"},n),Q=l.processMultiFileOperationResults(H,"successfulReplacements"),{successfulOperations:K,failedOperations:J,totalSuccessfulChanges:Y,overallSuccess:Z,successfulFiles:X,failedFiles:ee}=Q
3845
- try{await s.updateToolRecord(k,{metadata:{path:R.length>0?R[0].path:"document:"+M[0]?.documentId,status:Z?"completed":"failed",error:Z?void 0:J+" operations failed",totalOperations:H.length,successfulFiles:X,failedFiles:ee,operations:z,totalSuccessfulReplacements:Y,totalFailedReplacements:B,totalLinesReplaced:U,totalLinesAdded:G,operationsWithChanges:W,processingTime:Date.now()-b,userComment:S,...R.length>0&&V.success&&V.commitHash&&{commitHash:V.commitHash,previousCommitHash:V.previousCommitHash||void 0}},note:Z?`Line replacement completed: ${K}/${H.length} operations successful${R.length>0?" (committed to git)":""}`:`Line replacement partially failed: ${J}/${H.length} operations failed`})}catch(e){w.warn("Failed to update tool record",{recordError:e})}let te="## Line Replacement Results\n\n"
3865
+ t?.success&&await n.documentFileManager.syncDocument(e.documentId)}}catch(e){w.warn("Some documents may not have synced properly",{error:e})}const V=await l.handleMultiFileGitCommit(P,j,H,W,{operationType:"Replace lines",toolRecordId:k,successField:"successfulReplacements",logger:w,metrics:m,metricsPrefix:"replace_lines"},n),Q=l.processMultiFileOperationResults(H,"successfulReplacements"),{successfulOperations:K,failedOperations:J,totalSuccessfulChanges:Y,overallSuccess:Z,successfulFiles:X,failedFiles:ee}=Q
3866
+ try{await s.updateToolRecord(k,{metadata:{path:j.length>0?j[0].path:"document:"+M[0]?.documentId,status:Z?"completed":"failed",error:Z?void 0:J+" operations failed",totalOperations:H.length,successfulFiles:X,failedFiles:ee,operations:z,totalSuccessfulReplacements:Y,totalFailedReplacements:B,totalLinesReplaced:U,totalLinesAdded:G,operationsWithChanges:W,processingTime:Date.now()-b,userComment:S,...j.length>0&&V.success&&V.commitHash&&{commitHash:V.commitHash,previousCommitHash:V.previousCommitHash||void 0}},note:Z?`Line replacement completed: ${K}/${H.length} operations successful${j.length>0?" (committed to git)":""}`:`Line replacement partially failed: ${J}/${H.length} operations failed`})}catch(e){w.warn("Failed to update tool record",{recordError:e})}let te="## Line Replacement Results\n\n"
3846
3867
  if(n.workspaceManager){const e=n.workspaceManager.getCurrentContext(n.currentSessionId)
3847
3868
  e.permanentBranch||(te+=`**Branch:** \`${e.branchName}\`\n\n`)}if(te+="**Summary:**\n",te+=`- **Files processed:** ${K+J}\n`,te+=`- **Successful operations:** ${K}\n`,te+=`- **Failed operations:** ${J}\n`,te+=`- **Total replacements:** ${Y} successful, ${B} failed\n`,te+=`- **Total Lines Replaced:** ${U}\n`,G>0&&(te+=`- **Total Lines Added:** ${G}\n`),te+="\n",V.success&&V.commitHash&&(te+="**Git Commit:**\n",te+=`- **Commit Hash:** ${V.commitHash.substring(0,8)}\n`,te+=`- **Files Committed:** ${V.filesCommitted.length}\n`,V.previousCommitHash&&(te+=`- **Previous Commit:** ${V.previousCommitHash.substring(0,8)}\n`),te+="\n"),1===H.length){const e=H[0]
3848
3869
  if(te+=`**Operation:** ${e.success?"\u2705 Success":"\u274c Failed"}\n`,"file"===e.type?te+=`**File:** ${e.path}\n`:(te+="**Document:** "+e.documentId,e.filename&&(te+=` (${e.filename})`),te+="\n"),e.error?te+=`**Error:** ${e.error}\n`:(te+=`**Replacements:** ${e.successfulReplacements} successful`,e.failedReplacements>0&&(te+=`, ${e.failedReplacements} failed`),te+="\n",te+=`**Lines Replaced:** ${e.totalLinesReplaced}\n`,e.totalLinesAdded>0&&(te+=`**Lines Added:** ${e.totalLinesAdded}\n`)),e.replacements.some((e=>!e.success))){te+="**Failed Replacement Details:**\n"
@@ -3946,10 +3967,10 @@ s.info("Getting file changes for task "+g,{showDiff:m})
3946
3967
  const f=r.getTaskClient(),y=r.getToolRecordClient(),w=t.projectId
3947
3968
  let b
3948
3969
  r.setProjectId(w)
3949
- try{const j=await f.getTask(g)
3950
- s.info("Raw task response data",{responseDataType:typeof j,isArray:Array.isArray(j),hasData:null!=j,properties:j&&"object"==typeof j?Object.keys(j):[]}),b=(0,l.extractDataFromResponse)(j,{logger:s,operationName:"getTask",defaultValue:{id:g,title:"Unknown Task",status:"unknown",description:"No description available"}}),s.info("Found task: "+b.title)}catch(x){s.warn("Could not fetch task information: "+(0,u.getErrorMessage)(x)),b={id:g,title:"Unknown Task",status:"unknown",description:"No description available"}}let I=[]
3951
- try{const R=await y.getToolRecordsByTaskId(g)
3952
- if(s.info("Raw records response data",{responseDataType:typeof R,isArray:Array.isArray(R),hasData:null!=R,properties:R&&"object"==typeof R?Object.keys(R):[]}),I=(0,l.extractArrayFromResponse)(R,{logger:s,operationName:"searchRecords",defaultValue:[]}),I.length>0&&s.info("First record summary:",{id:I[0].id,type:I[0].type,hasMetadata:!!I[0].metadata,hasCommitHash:!!I[0].metadata?.commitHash}),s.info(`Extracted ${I.length} records for task ${g}`),I.length>0){const M={}
3970
+ try{const R=await f.getTask(g)
3971
+ s.info("Raw task response data",{responseDataType:typeof R,isArray:Array.isArray(R),hasData:null!=R,properties:R&&"object"==typeof R?Object.keys(R):[]}),b=(0,l.extractDataFromResponse)(R,{logger:s,operationName:"getTask",defaultValue:{id:g,title:"Unknown Task",status:"unknown",description:"No description available"}}),s.info("Found task: "+b.title)}catch(x){s.warn("Could not fetch task information: "+(0,u.getErrorMessage)(x)),b={id:g,title:"Unknown Task",status:"unknown",description:"No description available"}}let I=[]
3972
+ try{const j=await y.getToolRecordsByTaskId(g)
3973
+ if(s.info("Raw records response data",{responseDataType:typeof j,isArray:Array.isArray(j),hasData:null!=j,properties:j&&"object"==typeof j?Object.keys(j):[]}),I=(0,l.extractArrayFromResponse)(j,{logger:s,operationName:"searchRecords",defaultValue:[]}),I.length>0&&s.info("First record summary:",{id:I[0].id,type:I[0].type,hasMetadata:!!I[0].metadata,hasCommitHash:!!I[0].metadata?.commitHash}),s.info(`Extracted ${I.length} records for task ${g}`),I.length>0){const M={}
3953
3974
  I.forEach((e=>{M[e.type]=(M[e.type]||0)+1})),s.info("Record type distribution:",M)}}catch(O){s.warn("Error fetching records for task "+g,{error:(0,u.getErrorMessage)(O)}),I=[]}if(0===I.length)return p({success:!0,recordCount:0}),{content:[{type:"text",text:`No records found for task ${g}.`}]}
3954
3975
  function k(e){const t=new Map,r=new Map,n=[]
3955
3976
  for(const o of e)if("move-file"===o.type&&o.metadata){const e=o.metadata.source||o.metadata.oldPath,s=o.metadata.destination||o.metadata.newPath
@@ -4046,14 +4067,14 @@ const T=3e3
4046
4067
  let q=0,P=!1
4047
4068
  if(m){for(const Pe of E.values())Pe.hasDiff&&Pe.diffText&&(q+=Pe.diffText.split("\n").length)
4048
4069
  P=q>T,s.info(`Total diff size: ${q} lines (limit: ${T} lines)`),s.info(`Diffs ${P?"will be limited":"will be shown"} based on size`)}if(S+="## Summary\n",S+=`- **Files Modified**: ${_.size}\n`,S+=`- **Total File Operations**: ${C.length}\n`,S+="- **What's Shown**: For existing files, comparing state before task started with final state.\n",S+=" For files created during task, showing the entire file as created.\n",P?(S+=`- **Diff Display**: Limited (total diff size of ${q} lines exceeds limit of ${T} lines)\n`,S+=" Use file-history tool with individual files to see detailed changes\n\n"):S+=`- **Diff Display**: ${m?"Enabled":"Disabled"} (use showDiff=${!m} to ${m?"hide":"show"} diffs)\n\n`,v.allMoves.length>0){S+="## Move Operations\n\n",S+=`Detected ${v.allMoves.length} file move operation(s):\n\n`
4049
- for(const je of v.allMoves)S+=`- **${je.source}** \u2192 **${je.destination}**\n`,S+=` - Operation: ${je.type}\n`,S+=` - Timestamp: ${new Date(je.timestamp).toLocaleString()}\n`,S+=` - Record ID: ${je.recordId}\n\n`}S+="## Modified Files\n\n"
4050
- for(const[xe,Re]of E.entries()){const{normalizedPath:Me,fullPath:Oe}=d.validateAndNormalizePath(xe,t)
4070
+ for(const Re of v.allMoves)S+=`- **${Re.source}** \u2192 **${Re.destination}**\n`,S+=` - Operation: ${Re.type}\n`,S+=` - Timestamp: ${new Date(Re.timestamp).toLocaleString()}\n`,S+=` - Record ID: ${Re.recordId}\n\n`}S+="## Modified Files\n\n"
4071
+ for(const[xe,je]of E.entries()){const{normalizedPath:Me,fullPath:Oe}=d.validateAndNormalizePath(xe,t)
4051
4072
  let $e=!1
4052
4073
  try{await a.access(Oe),$e=!0}catch{}S+=`### ${xe} ${$e?"":"(Deleted)"}\n\n`
4053
- const De=new Date(Re.firstChange.timestamp).toLocaleString(),Ae=new Date(Re.lastChange.timestamp).toLocaleString()
4054
- S+=`**First Change**: ${De}\n`,S+=`**Last Change**: ${Ae}\n`,"pre-task"===Re.firstChange.status&&Re.firstChange.commitHash?S+=`**Pre-Task Commit**: ${Re.firstChange.commitHash.substring(0,8)}\n`:Re.firstChange.commitHash&&(S+=`**File Created During Task**: ${Re.firstChange.commitHash.substring(0,8)}\n`),Re.lastChange.commitHash&&(S+=`**Final Commit**: ${Re.lastChange.commitHash.substring(0,8)}\n`)
4074
+ const De=new Date(je.firstChange.timestamp).toLocaleString(),Ae=new Date(je.lastChange.timestamp).toLocaleString()
4075
+ S+=`**First Change**: ${De}\n`,S+=`**Last Change**: ${Ae}\n`,"pre-task"===je.firstChange.status&&je.firstChange.commitHash?S+=`**Pre-Task Commit**: ${je.firstChange.commitHash.substring(0,8)}\n`:je.firstChange.commitHash&&(S+=`**File Created During Task**: ${je.firstChange.commitHash.substring(0,8)}\n`),je.lastChange.commitHash&&(S+=`**Final Commit**: ${je.lastChange.commitHash.substring(0,8)}\n`)
4055
4076
  const Fe=v.sourceToMove.get(xe),Ne=v.destToMove.get(xe)
4056
- Fe?S+=`**\u2139\ufe0f Move Operation**: This file was moved TO **${Fe.destination}**\n`:Ne&&(S+=`**\u2139\ufe0f Move Operation**: This file was moved FROM **${Ne.source}**\n`),m&&!P?Re.hasDiff?(S+="\n**Changes**:\n",S+="```diff\n",S+=Re.diffText,S+="\n```\n\n"):S+=`\n**Note**: ${Re.diffText}\n\n`:P&&Re.hasDiff?(S+=`\n**Note**: File has changes (${Re.diffText?Re.diffText.split("\n").length:0} lines). `,S+=`Compare ${Re.firstChange.commitHash?Re.firstChange.commitHash.substring(0,8):"unknown"}..${Re.lastChange.commitHash?Re.lastChange.commitHash.substring(0,8):"unknown"} to see changes.\n\n`):Re.hasDiff?S+="\n**Note**: File has changes. Use showDiff=true to see the diff.\n\n":S+=`\n**Note**: ${Re.diffText}\n\n`,S+="---\n\n"}return p({success:!0,fileCount:_.size}),{content:[{type:"text",text:S}]}}catch(ze){return s.error("Error in task file history tool",{error:ze instanceof Error?ze.stack:ze+""}),p({success:!1,error:ze instanceof Error?ze.name:"unknown"}),{content:[{type:"text",text:"Error: "+(0,u.getErrorMessage)(ze)}],isError:!0}}}
4077
+ Fe?S+=`**\u2139\ufe0f Move Operation**: This file was moved TO **${Fe.destination}**\n`:Ne&&(S+=`**\u2139\ufe0f Move Operation**: This file was moved FROM **${Ne.source}**\n`),m&&!P?je.hasDiff?(S+="\n**Changes**:\n",S+="```diff\n",S+=je.diffText,S+="\n```\n\n"):S+=`\n**Note**: ${je.diffText}\n\n`:P&&je.hasDiff?(S+=`\n**Note**: File has changes (${je.diffText?je.diffText.split("\n").length:0} lines). `,S+=`Compare ${je.firstChange.commitHash?je.firstChange.commitHash.substring(0,8):"unknown"}..${je.lastChange.commitHash?je.lastChange.commitHash.substring(0,8):"unknown"} to see changes.\n\n`):je.hasDiff?S+="\n**Note**: File has changes. Use showDiff=true to see the diff.\n\n":S+=`\n**Note**: ${je.diffText}\n\n`,S+="---\n\n"}return p({success:!0,fileCount:_.size}),{content:[{type:"text",text:S}]}}catch(ze){return s.error("Error in task file history tool",{error:ze instanceof Error?ze.stack:ze+""}),p({success:!1,error:ze instanceof Error?ze.name:"unknown"}),{content:[{type:"text",text:"Error: "+(0,u.getErrorMessage)(ze)}],isError:!0}}}
4057
4078
  const i=t(9860),a=s(t(1943)),c=t(9671),l=t(2960),d=s(t(5967)),u=t(8824),h=t(4466)},6928(module){"use strict"
4058
4079
  module.exports=require("path")},6975(e,exports,t){"use strict"
4059
4080
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProjectClientAdapter=void 0
@@ -4410,7 +4431,9 @@ let r=`\n\n\u26a0\ufe0f **Performance Warning**: Large untracked files detected
4410
4431
  for(const t of e.slice(0,5))r+=`- \`${t.path}\` (${t.sizeMB} MB)\n`
4411
4432
  return e.length>5&&(r+=`- ... and ${e.length-5} more large files\n`),r+="\nConsider adding these to `.gitignore` or moving them outside the repository.",r}
4412
4433
  const h=t(3444),p=t(9860),g=t(3922),m=t(8824),f=u(t(9896)),y=u(t(6928))
4413
- t(857)},7575(e,exports){"use strict"
4434
+ t(857)},7499(e,exports){"use strict"
4435
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.TEST_MAX_ROUNDS=void 0,exports.decideTestRoundCap=function(e,t,r=exports.TEST_MAX_ROUNDS){const n=e.filter((e=>e.id!==t&&("failed"===e.status||"error"===e.status))).length+1
4436
+ return{escalate:n>=r,failedRounds:n}},exports.TEST_MAX_ROUNDS=3},7575(e,exports){"use strict"
4414
4437
  var t
4415
4438
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.FileOperationPhase=void 0,function(e){e.VALIDATION="validation",e.TASK_VALIDATION="task_validation",e.RECORD_CREATION="record_creation",e.FILE_WRITE="file_write",e.STRING_REPLACEMENT="string_replacement",e.GIT_COMMIT="git_commit",e.RECORD_UPDATE_SUCCESS="record_update_success",e.RECORD_UPDATE_FAILURE="record_update_failure"}(t||(exports.FileOperationPhase=t={}))},7609(e,exports,t){"use strict"
4416
4439
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleUpdatePlan=async function(e,t,o,s){const i=s.child({tool:"plan_operations",operation:"update"})
@@ -4611,7 +4634,26 @@ return!1}async cleanup(){this.logger.info("Cleaning up multi-agent workspace man
4611
4634
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ConnectionAwareClient=void 0
4612
4635
  const r=t(4192),n=t(7163)
4613
4636
  class o extends r.BaseApiClient{constructor(e,t,r,o,s,i){t.debug("ConnectionAwareClient constructor parameters:",{hasAuthToken:!!o,hasOptions:!!s,optionsType:s?typeof s:"undefined",options:s?JSON.stringify(s):"undefined",hasConnectionManager:!!i}),super(e,t,r,o,s),this.isGettingAuthToken=!1,this.connectionStatusManager=i||{isInOfflineMode(){return!1},isOffline(){return!1},setOfflineMode(){},async checkConnectivity(){return!0},getHealthCheckStats(){return{isOffline:!1,consecutiveFailures:0,currentBackoffMs:0,lastCheckTime:0}},cleanup(){}},o&&this.logger.debug("Client initialized with auth token",{clientType:this.constructor.name,tokenPrefix:o.substring(0,10)+"..."})
4614
- try{(0,n.debugTokenAccess)(this,"ConnectionAwareClient constructor")}catch(e){this.logger.debug("Error accessing token details during init",{error:e instanceof Error?e.message:e+""})}}setConnectionStatusManager(e){this.logger.debug("Setting connection status manager in ConnectionAwareClient"),this.connectionStatusManager=e}}exports.ConnectionAwareClient=o},7660(e,exports,t){"use strict"
4637
+ try{(0,n.debugTokenAccess)(this,"ConnectionAwareClient constructor")}catch(e){this.logger.debug("Error accessing token details during init",{error:e instanceof Error?e.message:e+""})}}setConnectionStatusManager(e){this.logger.debug("Setting connection status manager in ConnectionAwareClient"),this.connectionStatusManager=e}}exports.ConnectionAwareClient=o},7648(e,exports,t){"use strict"
4638
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRecordCriteriaVerification=async function(e,t,i,a,c){const l=a.child({tool:"record_criteria_verification"}),d=c.startTimer("tool_execution_time")
4639
+ try{const{requestId:a,verifications:c}=e
4640
+ if(!a)throw(0,r.createValidationError)("requestId is required")
4641
+ if(!Array.isArray(c)||0===c.length)throw(0,r.createValidationError)("verifications array is required and must be non-empty")
4642
+ const u=i.getRequestClient()
4643
+ let h
4644
+ try{h=await u.getRequest(a)}catch(e){throw(0,r.createValidationError)(`Failed to fetch request ${a}: ${(0,n.getErrorMessage)(e)}`)}const p=h?.acceptanceCriteria||[]
4645
+ if(0===p.length)return d({success:!1,reason:"no_criteria"}),{content:[{type:"text",text:`Request ${a} has no acceptance criteria to record.`}],isError:!0}
4646
+ const g=(0,s.buildCriteriaKeyMap)(p),m=new Set(g.keys()),f=c.filter((e=>!m.has(e.key))).map((e=>e.key))
4647
+ if(f.length>0)return d({success:!1,reason:"unknown_keys"}),{content:[{type:"text",text:`Unknown criterion key(s): ${f.join(", ")}\n\nValid criteria:\n${(0,s.formatCriteriaWithKeys)(p)}`}],isError:!0}
4648
+ const y=c.map((e=>({criterionId:g.get(e.key).id,evidence:e.evidence,status:"unmet"===e.status?"unmet":"met"})))
4649
+ let w,b=null
4650
+ try{const e=await fetch(`${(0,o.getActionServerBaseUrl)()}/api/branch-head?requestId=${encodeURIComponent(a)}`,{signal:AbortSignal.timeout(5e3)})
4651
+ e.ok&&(b=(await e.json())?.data?.sha??null)}catch(e){l.debug("branch-head fetch failed; verdicts recorded without sha",{error:(0,n.getErrorMessage)(e)})}try{const e=await i.proxyPost(`/api/projects/${t.projectId}/requests/${a}/verify-criteria`,{verifications:y,dimension:"review",headSha:b})
4652
+ w=e?.data||e}catch(e){return d({success:!1,error:"record_failed"}),{content:[{type:"text",text:"Failed to record criteria verification: "+(0,n.getErrorMessage)(e)}],isError:!0}}l.info("Recorded criteria verification",{requestId:a,verified:w.verified,remaining:w.remainingUnverified})
4653
+ const I=y.filter((e=>"met"===e.status)).length,k=y.length-I
4654
+ return d({success:!0}),{content:[{type:"text",text:`Recorded verdicts for ${y.length} criterion/criteria on ${a} (${I} met, ${k} unmet). ${w.remainingUnverified} remain unmet overall; the server opens a rework task for the coder when any are unmet. This does NOT merge; the coder finalizes after review.`}]}}catch(e){const t=(0,n.getErrorMessage)(e)
4655
+ return l.error("record_criteria_verification failed",{error:t}),d({success:!1,error:"unexpected"}),{content:[{type:"text",text:"Error: "+t}],isError:!0}}}
4656
+ const r=t(9860),n=t(8824),o=t(9414),s=t(5454)},7660(e,exports,t){"use strict"
4615
4657
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.projectContextMiddleware=exports.ProjectContext=exports.ProjectIDFactory=exports.ProjectID=void 0
4616
4658
  const r=t(4055)
4617
4659
  Object.defineProperty(exports,"ProjectID",{enumerable:!0,get(){return r.ProjectID}}),Object.defineProperty(exports,"ProjectIDFactory",{enumerable:!0,get(){return r.ProjectIDFactory}})
@@ -4639,10 +4681,10 @@ const e=await(0,i.handleDeleteFiles)({taskId:k,operations:_,comment:w+" [via man
4639
4681
  S.push({type:"delete-files",result:e})}for(const e of E){f.info("Routing to get-file-info handler",{path:e.path})
4640
4682
  const t=await(0,o.handleGetFileInfo)({path:e.path},c,f,g)
4641
4683
  S.push({type:"get-file-info",result:t})}const T=S.some((e=>e.result.isError)),q=S.filter((e=>!e.result.isError)).length,P=S.filter((e=>e.result.isError)).length
4642
- let j="## Manage Files Results\n\n"
4643
- j+="**Summary:**\n",j+=`- **Total operation types:** ${S.length}\n`,j+=`- **Successful:** ${q}\n`,j+=`- **Failed:** ${P}\n\n`
4644
- for(const{type:e,result:t}of S)j+=`### ${e} operations\n`,j+=t.content[0].text+"\n\n"
4645
- return f.info("Manage-files operations completed",{totalOperationTypes:S.length,successCount:q,errorCount:P}),g.incrementCounter("manage_files_total",1,{success:!T}),{content:[{type:"text",text:j}],...(0,s.alchemyMeta)({kind:"manage-files"}),isError:T}}catch(e){return f.error("Manage-files operation failed",{error:e instanceof Error?e.stack:e+""}),g.incrementCounter("manage_files_errors",1),{content:[{type:"text",text:"\u274c **Manage Files Failed:** "+(0,a.getErrorMessage)(e)}],isError:!0}}}
4684
+ let R="## Manage Files Results\n\n"
4685
+ R+="**Summary:**\n",R+=`- **Total operation types:** ${S.length}\n`,R+=`- **Successful:** ${q}\n`,R+=`- **Failed:** ${P}\n\n`
4686
+ for(const{type:e,result:t}of S)R+=`### ${e} operations\n`,R+=t.content[0].text+"\n\n"
4687
+ return f.info("Manage-files operations completed",{totalOperationTypes:S.length,successCount:q,errorCount:P}),g.incrementCounter("manage_files_total",1,{success:!T}),{content:[{type:"text",text:R}],...(0,s.alchemyMeta)({kind:"manage-files"}),isError:T}}catch(e){return f.error("Manage-files operation failed",{error:e instanceof Error?e.stack:e+""}),g.incrementCounter("manage_files_errors",1),{content:[{type:"text",text:"\u274c **Manage Files Failed:** "+(0,a.getErrorMessage)(e)}],isError:!0}}}
4646
4688
  const r=t(8309),n=t(2414),o=t(3628),s=t(2272),i=t(5992),a=t(8824)},7713(e,exports,t){"use strict"
4647
4689
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.def=void 0
4648
4690
  const r=t(6183),n=t(5303)
@@ -4846,18 +4888,18 @@ const{normalizedPath:_,fullPath:E}=l.validateAndNormalizePath(u,r)
4846
4888
  b=l.FileOperationPhase.TASK_VALIDATION,p.incrementCounter("create_directory_phase",1,{phase:b})
4847
4889
  const S=await l.validateTaskId(C,_,n,m,p,I.metricsPrefix),T=S?.requestId,q=await l.validateWorkItemStates(C,_,n,s,r,m,p,I.metricsPrefix),P=await l.autoPromoteTaskStatus(C,S.status,T,q.request?.status,_,n,s,m,p,I.metricsPrefix)
4848
4890
  P.taskPromoted&&m.info(`Auto-promoted task ${C} (${S.title||"Untitled"}) to in_progress for file operation`,{taskId:C,taskTitle:S.title,previousStatus:S.status,newStatus:P.newTaskStatus,path:_}),P.requestPromoted&&m.info(`Auto-promoted request ${T} to in_progress for file operation`,{requestId:T,previousStatus:q.request?.status,newStatus:P.newRequestStatus,triggeringTaskId:C})
4849
- const j=await l.ensureCorrectWorkspaceContext({taskPromoted:P.taskPromoted,requestId:T,context:r,operation:"create-directory",logger:m,metrics:p,metricsPrefix:I.metricsPrefix})
4850
- if(!j.success)throw Error(j.error)
4891
+ const R=await l.ensureCorrectWorkspaceContext({taskPromoted:P.taskPromoted,requestId:T,context:r,operation:"create-directory",logger:m,metrics:p,metricsPrefix:I.metricsPrefix})
4892
+ if(!R.success)throw Error(R.error)
4851
4893
  let x=!1
4852
4894
  try{x=(await a.default.stat(E)).isDirectory()}catch{x=!1}b=l.FileOperationPhase.RECORD_CREATION,p.incrementCounter("create_directory_phase",1,{phase:b})
4853
- const{toolRecordId:R,localToolRecordId:M}=await l.createFileOperationRecords(C,T,r.projectId,I.recordType,_,{directoryExists:x,userComment:h},n,o,m,p,I.metricsPrefix,g)
4854
- y=R,w=R,b=I.fileOperationPhase,p.incrementCounter("create_directory_phase",1,{phase:b})
4895
+ const{toolRecordId:j,localToolRecordId:M}=await l.createFileOperationRecords(C,T,r.projectId,I.recordType,_,{directoryExists:x,userComment:h},n,o,m,p,I.metricsPrefix,g)
4896
+ y=j,w=j,b=I.fileOperationPhase,p.incrementCounter("create_directory_phase",1,{phase:b})
4855
4897
  let O=x
4856
4898
  if(!x){await a.default.mkdir(E,{recursive:!0}),m.info("Successfully created directory "+_)
4857
4899
  try{const e=c.default.join(E,".gitkeep")
4858
4900
  await a.default.writeFile(e,"# This file makes the directory trackable by Git\n"),O=!0,m.info("Created .gitkeep file for Git tracking")}catch(e){m.warn("Failed to create .gitkeep file",{error:e instanceof Error?e.message:e+""}),O=!1}}const $=Date.now()-f
4859
4901
  p.recordValue("create_directory_time",$),p.incrementCounter("create_directory_success",1),b=l.FileOperationPhase.GIT_COMMIT,p.incrementCounter("create_directory_phase",1,{phase:b})
4860
- const D=`Create directory: ${_} - ${h||"Create directory"}`,A=!x&&O?_+"/.gitkeep":_,F=await l.commitToGit(C,A,R,M,D,r,m,p,I.metricsPrefix)
4902
+ const D=`Create directory: ${_} - ${h||"Create directory"}`,A=!x&&O?_+"/.gitkeep":_,F=await l.commitToGit(C,A,j,M,D,r,m,p,I.metricsPrefix)
4861
4903
  b=l.FileOperationPhase.RECORD_UPDATE_SUCCESS,p.incrementCounter("create_directory_phase",1,{phase:b}),await l.updateFileOperationRecords(C,y,w,_,I.recordType,`Successfully ${x?"verified":"created"} directory: ${_}`,{status:"completed",commitHash:F.commitHash,previousCommitHash:F.previousCommitHash,directoryExists:x,isDirectory:!0,userComment:h},`Successfully ${x?"verified":"created"} directory: ${_}`,n,o,m,p,I.metricsPrefix)
4862
4904
  const N=r.getCurrentBranchContext?await r.getCurrentBranchContext():{branch:null,requestId:null,isMain:!0}
4863
4905
  return{content:[{type:"text",text:x?"Directory already exists":`Successfully created directory${F.commitHash?"\nCommit: "+F.commitHash.substring(0,8):""}${N.requestId&&!N.permanentBranch?"\nBranch: "+N.requestId:""}`}]}}catch(t){if(e.taskId&&o)try{const n=t instanceof d.NotFoundError||t instanceof d.ValidationError||t instanceof u.ZodError
@@ -5473,10 +5515,10 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleReplaceInFi
5473
5515
  let C,_=u.FileOperationPhase.VALIDATION
5474
5516
  try{const E=e,{enforceActiveTask:S}=await Promise.resolve().then(t.bind(t,3451)),T=await S(r,o,a,e.taskId)
5475
5517
  if("errorResult"in T)return T.errorResult
5476
- const{taskId:q}=T,{operations:P,fuzzy_match:j=!0,comment:x}=E
5477
- k.info("Replace in files batch operation",{taskId:q,operationCount:P.length,totalReplacements:P.reduce(((e,t)=>e+t.replacements.length),0),globalFuzzyMatch:j,comment:x})
5478
- const R=P.filter((e=>e.documentId)).map((e=>e.documentId))
5479
- if(R.length>0&&r.apiClient)try{await(0,f.ensureDocumentsNotLocked)(R,r.apiClient,k,c,"replace_in_files-documents")}catch(e){if(e instanceof p.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
5518
+ const{taskId:q}=T,{operations:P,fuzzy_match:R=!0,comment:x}=E
5519
+ k.info("Replace in files batch operation",{taskId:q,operationCount:P.length,totalReplacements:P.reduce(((e,t)=>e+t.replacements.length),0),globalFuzzyMatch:R,comment:x})
5520
+ const j=P.filter((e=>e.documentId)).map((e=>e.documentId))
5521
+ if(j.length>0&&r.apiClient)try{await(0,f.ensureDocumentsNotLocked)(j,r.apiClient,k,c,"replace_in_files-documents")}catch(e){if(e instanceof p.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
5480
5522
  throw e}_=u.FileOperationPhase.TASK_VALIDATION,c.incrementCounter("replace_in_files_phase",1,{phase:_})
5481
5523
  const M=P[0],O=M.documentId?"document:"+M.documentId:M.path
5482
5524
  let $=Date.now()
@@ -5491,10 +5533,10 @@ const t=await u.autoPromoteTaskStatus(q,D.status,A,e.request?.status,O,o,i,k,c,"
5491
5533
  t.taskPromoted&&k.info("Auto-promoted task to in_progress",{taskId:q,requestId:A,previousStatus:D.status,newStatus:t.newTaskStatus}),Date.now(),$=Date.now()
5492
5534
  const n=await u.ensureCorrectWorkspaceContext({taskPromoted:t.taskPromoted,requestId:A,context:r,operation:"replace_in_files",logger:k,metrics:c,metricsPrefix:"replace_in_files"})
5493
5535
  if(!n.success)throw Error(n.error)
5494
- Date.now(),N=!0}if(R.length>0&&A)try{await(0,g.validateRequestStatusForDocumentOperation)(A,i,k,c,"replace_in_files-documents")}catch(e){if(e instanceof p.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
5536
+ Date.now(),N=!0}if(j.length>0&&A)try{await(0,g.validateRequestStatusForDocumentOperation)(A,i,k,c,"replace_in_files-documents")}catch(e){if(e instanceof p.ValidationError)return{content:[{type:"text",text:e.message}],isError:!0}
5495
5537
  throw e}$=Date.now()
5496
5538
  const z=new Map
5497
- if(k.info("\u{1f50d} Starting external edit detection",{hasFileOperations:F,hasGit:!!r.git,operationCount:P.length}),F&&r.git)for(const e of P)if(e.path&&!e.documentId)try{const t=u.validateAndNormalizePath(e.path,r),n=await l.default.readFile(t.fullPath,"utf8"),o=void 0!==e.fuzzy_match?e.fuzzy_match:j,s=[]
5539
+ if(k.info("\u{1f50d} Starting external edit detection",{hasFileOperations:F,hasGit:!!r.git,operationCount:P.length}),F&&r.git)for(const e of P)if(e.path&&!e.documentId)try{const t=u.validateAndNormalizePath(e.path,r),n=await l.default.readFile(t.fullPath,"utf8"),o=void 0!==e.fuzzy_match?e.fuzzy_match:R,s=[]
5498
5540
  for(const t of e.replacements){const r=(0,h.findMatches)(n,t.old,o,100,e.path)
5499
5541
  s.push(...r)}if(s.length>0){const o=(0,y.calculateReplacementRegion)(n,s,t.fullPath)
5500
5542
  k.info("\u{1f50d} Checking for external edits",{path:t.normalizedPath,affectedLines:o.lineRanges.map((e=>`${e.start}-${e.end}`)).join(", "),matchCount:s.length})
@@ -5502,10 +5544,10 @@ const i=await(0,w.detectExternalEdits)(r.git,o,k)
5502
5544
  k.info("\u{1f50d} External edit detection result",{path:t.normalizedPath,hasExternalEdits:i.hasExternalEdits,conflictingLines:i.conflictingLines,hasMessage:!!i.message}),i.hasExternalEdits&&i.message?(z.set(e.path,{message:i.message,debugInfo:i.debugInfo}),k.warn("\u26a0\ufe0f External edits detected before auto-commit",{path:t.normalizedPath,conflictingLines:i.conflictingLines})):(i.debugInfo&&z.set(e.path,{message:"",debugInfo:i.debugInfo}),k.info("\u2705 No external edit conflicts detected",{path:t.normalizedPath}))}else k.info("\u2139\ufe0f No matches found for external edit detection",{path:t.normalizedPath}),z.set(e.path,{message:"",debugInfo:{detectionRan:!1,reason:"No matches found - patterns not found in file",affectedRegion:"none",uncommittedChanges:"not checked",overlapDetected:!1}})}catch(t){k.warn("Failed to check for external edits before auto-commit",{path:e.path,error:(0,b.getErrorMessage)(t)})}if(Date.now(),$=Date.now(),F&&r.git)try{k.debug("[REPLACE-IN-FILES] Checking for user changes before batch operation")
5503
5545
  const e=await(0,m.commitUserChangesBeforeToolOperation)(r.git,"replace_in_files",{requestId:A,taskId:q,workspaceManager:r.workspaceManager},k,c)
5504
5546
  e.userChangesCommitted&&(k.debug("[REPLACE-IN-FILES] User changes committed",{commitHash:e.commitHash?.substring(0,8),filesCommitted:e.filesCommitted}),k.info("User changes committed before batch operation",{taskId:q,userCommitHash:e.commitHash?.substring(0,8),userFilesCommitted:e.filesCommitted}))}catch(e){k.warn("Pre-tool user changes commit failed",{taskId:q,error:e instanceof Error?e.message:e+""})}Date.now(),$=Date.now(),_=u.FileOperationPhase.RECORD_CREATION,c.incrementCounter("replace_in_files_phase",1,{phase:_})
5505
- const{toolRecordId:L}=await u.createFileOperationRecords(q,A,r.projectId,"replace-in-files",O,{operationCount:P.length,totalReplacements:P.reduce(((e,t)=>e+t.replacements.length),0),globalFuzzyMatch:j,userComment:x},o,s,k,c,"replace_in_files",I)
5547
+ const{toolRecordId:L}=await u.createFileOperationRecords(q,A,r.projectId,"replace-in-files",O,{operationCount:P.length,totalReplacements:P.reduce(((e,t)=>e+t.replacements.length),0),globalFuzzyMatch:R,userComment:x},o,s,k,c,"replace_in_files",I)
5506
5548
  C=L,Date.now(),$=Date.now(),_=u.FileOperationPhase.STRING_REPLACEMENT,c.incrementCounter("replace_in_files_phase",1,{phase:_})
5507
5549
  const H=[],B=[]
5508
- for(const e of P){const t=e.path?z.get(e.path):void 0,o=await n(e,j,r,0,0,t?.message,t?.debugInfo)
5550
+ for(const e of P){const t=e.path?z.get(e.path):void 0,o=await n(e,R,r,0,0,t?.message,t?.debugInfo)
5509
5551
  H.push(o),o.successfulReplacements>0&&"file"===o.type&&o.path&&B.push(o.path)}Date.now(),$=Date.now()
5510
5552
  const U=P.filter((e=>e.path&&!e.documentId)).map((e=>({path:e.path}))),G=await u.handleMultiFileGitCommit(q,U,H,B.length,{operationType:"Replace in files",toolRecordId:L||C||"unknown",successField:"successfulReplacements",logger:k,metrics:c,metricsPrefix:"replace_in_files"},r),W=G.commitHash
5511
5553
  Date.now(),Date.now()