@codesherlock/codesherlock-alpha-mcp-server 0.1.3 → 0.1.5
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/build/index.js +4 -3
- package/package.json +3 -2
package/build/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import{McpServer as me}from"@modelcontextprotocol/sdk/server/mcp.js";import{Stdi
|
|
|
6
6
|
`),c=l.length;r.logInfo("File content read for patch generation",{filePath:t,contentLength:String(s.length),lineCount:String(c)});let g=[`diff --git a/${t} b/${t}`,"new file mode 100644","index 0000000..e69de29","--- /dev/null",`+++ b/${t}`];if(c>0){g.push(`@@ -0,0 +1,${c} @@`);for(let d of l)g.push(`+${d}`)}else g.push("@@ -0,0 +1,0 @@");return g.join(`
|
|
7
7
|
`)}catch(n){return r.logError("Error building untracked patch",n,{repoPath:e,filePath:t}),""}}async getChangedFilesWithStatus(e,t){var n,a,o,i,s,l,c;try{let g=this.getGitInstance(e);if(!t){r.logInfo("Getting git status for uncommitted changes",{repoPath:e});let f=await g.status();if(r.logInfo("Git status retrieved",{repoPath:e,totalFiles:String(((n=f.files)==null?void 0:n.length)||0),modifiedCount:String(((a=f.modified)==null?void 0:a.length)||0),createdCount:String(((o=f.created)==null?void 0:o.length)||0),notAddedCount:String(((i=f.not_added)==null?void 0:i.length)||0),deletedCount:String(((s=f.deleted)==null?void 0:s.length)||0),renamedCount:String(((l=f.renamed)==null?void 0:l.length)||0),stagedCount:String(((c=f.staged)==null?void 0:c.length)||0)}),!f.files||f.files.length===0)return r.logInfo("No changed files found in repository status",{repoPath:e}),[];r.logInfo("Raw git status files",{files:JSON.stringify(f.files.map(p=>({path:p.path,index:p.index,working_dir:p.working_dir})))});let h=f.files.map(p=>{if(!p.path)return r.logWarning("File entry missing path in status",{file:JSON.stringify(p)}),null;let w=this.determineFileStatus(p.index,p.working_dir);return r.logInfo("File status determined",{path:p.path,indexCode:p.index,workingDirCode:p.working_dir,determinedStatus:w}),{path:p.path,status:w}}).filter(p=>p!==null),m=h.reduce((p,w)=>(p[w.status]=(p[w.status]||0)+1,p),{});return r.logInfo("Files grouped by status",{repoPath:e,filesByStatus:JSON.stringify(m),totalFiles:String(h.length),fileList:JSON.stringify(h.map(p=>`${p.path} (${p.status})`))}),h}return(await g.raw(["show","--name-status","--format=",t])).trim().split(`
|
|
8
8
|
`).filter(Boolean).map(f=>{let h=f.split(" "),m=h[0][0],p=h[1],w="modified";return m==="A"?w="added":m==="D"?w="deleted":m==="R"&&(w="renamed",p=h[2]||h[1]),{status:w,path:p}})}catch(g){throw r.logError("Error fetching changed files with status",g,{repoPath:e,commitHash:t||"HEAD"}),g}}determineFileStatus(e,t){return e==="?"&&t==="?"?"untracked":e==="A"?"added":e==="D"||t==="D"?"deleted":e==="R"||t==="R"?"renamed":"modified"}async analyzeGitChanges(e,t){try{r.logInfo("Analyzing git changes",{directory:t,uncommitted:String(e),targetHash:e?"working tree":"HEAD"});let n=e?void 0:"HEAD",a=await this.getChangedFilesWithStatus(t,n);r.logInfo(`Found ${a.length} changed files`,{directory:t,uncommitted:String(e),files:JSON.stringify(a.map(i=>`${i.path} (${i.status})`))});let o=[];for(let i of a){r.logInfo("Processing file in analyzeGitChanges",{filename:i.path,status:i.status,isAdded:String(i.status==="added"),targetHash:n||"working tree"});let s="";if(i.status!=="deleted")try{r.logInfo("Getting content for file",{filename:i.path,status:i.status,source:n?`commit ${n}`:"working tree"}),s=await this.getFileContent(t,i.path,n),r.logInfo("Content retrieved successfully",{filename:i.path,status:i.status,contentLength:String(s.length)})}catch(g){r.logWarning(`Failed to read content for ${i.path}`,{error:String(g),status:i.status,filename:i.path})}else r.logInfo("Skipping content for deleted file",{filename:i.path});let l="";try{r.logInfo("Getting diff for file",{filename:i.path,status:i.status,targetHash:n||"working tree"}),l=await this.getFileDiff(t,i.path,n,i.status),r.logInfo("Diff retrieved successfully",{filename:i.path,status:i.status,patchLength:String(l.length)})}catch(g){r.logWarning(`Failed to get diff for ${i.path}`,{error:String(g),status:i.status,filename:i.path})}let c={filename:i.path,status:i.status,new_content:s,patch:l};r.logInfo("File processing complete",{filename:i.path,status:i.status,hasContent:String(s.length>0),hasPatch:String(l.length>0),contentLength:String(s.length),patchLength:String(l.length)}),o.push(c)}return r.logInfo("Git changes analysis complete",{directory:t,totalFiles:String(o.length),filesProcessed:JSON.stringify(o.map(i=>({filename:i.filename,status:i.status,hasContent:i.new_content.length>0,hasPatch:i.patch.length>0})))}),o}catch(n){throw r.logError("Error analyzing git changes",n,{directory:t}),n}}async getFileChanges(e,t,n){try{if(!e&&!t)throw new Error("commitHash is required when uncommitted is false");r.logInfo("Getting file changes",{directory:n,uncommitted:String(e),commitHash:t||"none"});let a=e?void 0:t,o=await this.getChangedFilesWithStatus(n,a);r.logInfo(`Found ${o.length} changed files`);let i=[];for(let s of o){r.logInfo("Processing file change",{filename:s.path,status:s.status,targetHash:a||"working tree",isAdded:String(s.status==="added")});let l="";if(s.status!=="deleted")try{r.logInfo("Getting file content",{filename:s.path,status:s.status,source:a?`commit ${a}`:"working tree",isAdded:String(s.status==="added")}),l=await this.getFileContent(n,s.path,a),r.logInfo("File content retrieved",{filename:s.path,status:s.status,contentLength:String(l.length),lineCount:String(l.split(`
|
|
9
|
-
`).length)})}catch(g){r.logWarning(`Failed to read content for ${s.path}`,{error:String(g),status:s.status,targetHash:a||"working tree"})}else r.logInfo("Skipping content retrieval for deleted file",{filename:s.path});let c="";try{r.logInfo("Getting file diff",{filename:s.path,status:s.status,targetHash:a||"working tree",isAdded:String(s.status==="added")}),c=await this.getFileDiff(n,s.path,a,s.status),r.logInfo("File diff retrieved",{filename:s.path,status:s.status,patchLength:String(c.length),hasPatch:String(c.length>0)})}catch(g){r.logWarning(`Failed to get diff for ${s.path}`,{error:String(g),status:s.status,targetHash:a||"working tree"})}i.push({filename:s.path,status:s.status,new_content:l,patch:c}),r.logInfo("File change processed",{filename:s.path,status:s.status,hasContent:String(l.length>0),hasPatch:String(c.length>0),contentLength:String(l.length),patchLength:String(c.length)})}return r.logInfo(`Successfully processed ${i.length} file changes`),i}catch(a){throw r.logError("Error in getFileChanges",a,{directory:n,uncommitted:String(e),commitHash:t||"none"}),a}}};import ae from"ws";import ce from"jszip";import{File as le,Blob as ge}from"node:buffer";import x from"ws";var B=class{connections=new Set;connectionsByUserId=new Map;isShuttingDown=!1;registerConnection(e,t){if(this.isShuttingDown){r.logWarning("Attempted to register WebSocket during shutdown",{userId:t||"unknown"});return}if(t){let a=this.connectionsByUserId.get(t);if(a&&a.readyState===x.OPEN){r.logWarning("Closing existing WebSocket connection for user before registering new one",{userId:t});try{a.close(1e3,"Replaced by new connection")}catch(o){r.logWarning("Error closing existing connection",{error:o instanceof Error?o.message:String(o),userId:t})}this.connections.delete(a),this.connectionsByUserId.delete(t)}this.connectionsByUserId.set(t,e)}this.connections.add(e),r.logInfo("WebSocket connection registered",{userId:t||"unknown",totalConnections:String(this.connections.size)});let n=()=>{this.unregisterConnection(e,t)};e.removeListener("close",n),e.on("close",n)}unregisterConnection(e,t){this.connections.delete(e)&&(t&&this.connectionsByUserId.get(t)===e&&this.connectionsByUserId.delete(t),r.logInfo("WebSocket connection unregistered",{userId:t||"unknown",totalConnections:String(this.connections.size)}))}async closeAllConnections(){if(this.isShuttingDown)return;this.isShuttingDown=!0;let e=this.connections.size;if(e===0){r.logInfo("No active WebSocket connections to close");return}r.logInfo("Closing all WebSocket connections due to MCP client disconnection",{connectionCount:String(e)});let t=[];for(let n of this.connections)if(n.readyState===x.OPEN||n.readyState===x.CONNECTING){let a=new Promise(o=>{let i=setTimeout(()=>{try{n.terminate()}catch(s){r.logWarning("Error terminating WebSocket",{error:s instanceof Error?s.message:String(s)})}o()},2e3);n.once("close",()=>{clearTimeout(i),o()});try{n.close(1e3,"MCP client disconnected")}catch(s){r.logWarning("Error closing WebSocket",{error:s instanceof Error?s.message:String(s)}),clearTimeout(i),o()}});t.push(a)}else this.connections.delete(n);try{await Promise.race([Promise.all(t),new Promise(n=>{setTimeout(()=>{r.logWarning("Timeout waiting for WebSocket connections to close"),n()},5e3)})])}catch(n){r.logError("Error closing WebSocket connections",n)}this.connections.clear(),this.connectionsByUserId.clear(),this.isShuttingDown=!1,r.logInfo("All WebSocket connections closed",{closedCount:String(e)})}getConnectionCount(){return this.connections.size}hasActiveConnections(){return this.connections.size>0}getConnectionForUser(e){let t=this.connectionsByUserId.get(e);if(t&&t.readyState===x.OPEN)return t;t&&(r.logInfo("Removing stale WebSocket connection for user",{userId:e}),this.connections.delete(t),this.connectionsByUserId.delete(e))}async closeConnectionForUser(e){let t=this.connectionsByUserId.get(e);if(t){r.logInfo("Closing WebSocket connection for user",{userId:e});try{(t.readyState===x.OPEN||t.readyState===x.CONNECTING)&&t.close(1e3,"User connection closed")}catch(n){r.logWarning("Error closing connection for user",{error:n instanceof Error?n.message:String(n),userId:e})}this.connections.delete(t),this.connectionsByUserId.delete(e)}}cleanupStaleConnections(){let e=[];for(let t of this.connections)(t.readyState===x.CLOSED||t.readyState===x.CLOSING)&&e.push(t);for(let t of e){this.connections.delete(t);for(let[n,a]of this.connectionsByUserId.entries())if(a===t){this.connectionsByUserId.delete(n);break}}e.length>0&&r.logInfo("Cleaned up stale WebSocket connections",{count:String(e.length)})}},I=new B;var N=class{baseUrl;wsBaseUrl;apiKey;fetchTimeout;maxRetries;retryDelay;constructor(e,t){if(!e||e.trim()==="")throw new Error("BACKEND_API_URL environment variable is required but not set. Please configure it in your .env file or environment variables.");this.baseUrl=e,e.startsWith("https://")?this.wsBaseUrl=e.replace(/^https/,"wss"):e.startsWith("http://")?this.wsBaseUrl=e.replace(/^http/,"ws"):(r.logWarning("Backend URL doesn't start with http/https, assuming WebSocket protocol",{baseUrl:e}),this.wsBaseUrl=e),this.apiKey=t,this.fetchTimeout=parseInt(process.env.FETCH_TIMEOUT_MS||"30000",10),this.maxRetries=parseInt(process.env.FETCH_MAX_RETRIES||"3",10),this.retryDelay=parseInt(process.env.FETCH_RETRY_DELAY_MS||"1000",10)}isRetryableError(e){if(!e)return!1;let t=e.message||String(e),n=String(e.code||""),a=["ECONNREFUSED","ETIMEDOUT","ENOTFOUND","ECONNRESET","EAI_AGAIN","fetch failed","network","timeout"];return!!(a.some(o=>n.includes(o))||a.some(o=>t.toLowerCase().includes(o.toLowerCase())))}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithRetry(e,t,n=0){let a=new AbortController,o=setTimeout(()=>{a.abort()},this.fetchTimeout);try{let i=await fetch(e,{...t,signal:a.signal});return clearTimeout(o),i}catch(i){if(clearTimeout(o),this.isRetryableError(i)&&n<this.maxRetries){let d=n+1,f=this.retryDelay*Math.pow(2,n);return r.logWarning(`Fetch failed (attempt ${d}/${this.maxRetries+1}), retrying in ${f}ms`,{url:e,error:i.message||String(i),errorCode:i.code||"",attempt:String(d),maxRetries:String(this.maxRetries)}),await this.sleep(f),this.fetchWithRetry(e,t,n+1)}let s=i.message||String(i),l=i.code||"",c=s;i.name==="AbortError"||a.signal.aborted?c=`Request timeout after ${this.fetchTimeout}ms. The backend may be slow or unreachable.`:l==="ECONNREFUSED"?c=`Connection refused. Please verify the backend is running at ${e}`:l==="ETIMEDOUT"?c=`Connection timeout. The backend may be slow or unreachable at ${e}`:l==="ENOTFOUND"&&(c=`DNS resolution failed. Please check the backend URL: ${e}`);let g=new Error(c);throw g.code=l,g.originalError=i,r.logError("Fetch failed after retries",g,{url:e,retryCount:String(n),maxRetries:String(this.maxRetries),errorCode:l}),g}}async submitCommitReview(e){try{let t=`${this.baseUrl}/v2/commit-review/${e.factor}`,n=new FormData;n.append("user_id",e.user_id),n.append("repo_name",e.repo_name),n.append("commit_id",e.commit_id),n.append("username",e.username),n.append("factor",e.factor);let a=new ce;a.file("files.json",e.files_json);let o=await a.generateAsync({type:"nodebuffer"}),i=new ge([o],{type:"application/zip"});n.append("files_zip",new le([i],"files.zip",{type:"application/zip"})),e.organization_name&&n.append("organization_name",e.organization_name);let s=await this.fetchWithRetry(t,{method:"POST",headers:{...this.apiKey?{"X-CS-MCP-API-Key":this.apiKey}:{}},body:n});if(!s.ok){let c="Unknown error",g,d;try{d=await s.json(),c=d.detail||d.error||d.message||JSON.stringify(d)}catch{let p=await s.text();g=p,c=p||`HTTP ${s.status}`}let f=d?JSON.stringify(d):"",h=g??"";r.logError("Backend commit-review HTTP error response",new Error(c),{url:t,status:String(s.status),parsedBody:f,rawBody:h});let m=new Error(`HTTP error! status: ${s.status}, detail: ${c}`);throw m.statusCode=s.status,m.errorDetail=c,m}if(s.status===202){let c={};try{let g=s.headers.get("content-type");g&&g.includes("application/json")?c=await s.json()??{}:c={message:await s.text()||"Commit review request received. Processing in the background."}}catch{c={message:"Commit review request received. Processing in the background."}}return{success:!0,data:c,analysisId:c.analysis_id?String(c.analysis_id):void 0,message:c.message||"Commit review submitted successfully"}}let l={};try{l=await s.json()??{}}catch{r.logWarning("Failed to parse JSON response, using default",{status:String(s.status)}),l={message:"Commit review submitted successfully"}}return{success:!0,data:l,analysisId:l.analysis_id?String(l.analysis_id):void 0,message:l.message||"Commit review submitted successfully"}}catch(t){throw r.logError("Error submitting commit review",t),t}}connectWebSocket(e,t,n,a){return new Promise((o,i)=>{try{I.cleanupStaleConnections();let s=I.getConnectionForUser(e);if(s){r.logInfo("Reusing existing WebSocket connection",{userId:e});let g=h=>{try{let m=JSON.parse(h.toString());t(m)}catch(m){r.logError("Error parsing WebSocket message",m)}},d=h=>{r.logError("WebSocket connection error",h,{userId:e}),n==null||n(h)},f=(h,m)=>{let p=m.toString();r.logInfo("WebSocket connection closed",{userId:e,code:String(h),reason:p}),a==null||a(h,p)};s.removeAllListeners("message"),s.removeAllListeners("error"),s.removeAllListeners("close"),s.on("message",g),s.on("error",d),s.on("close",f),o(s);return}let l=`${this.wsBaseUrl}/v2/ws/${e}`;if(r.logInfo("Connecting to WebSocket",{wsUrl:l,userId:e}),!l.startsWith("ws://")&&!l.startsWith("wss://")){let g=new Error(`Invalid WebSocket URL format: ${l}. Expected URL to start with ws:// or wss://. Please check your BACKEND_API_URL configuration.`);r.logError("Invalid WebSocket URL format",g,{wsUrl:l,baseUrl:this.baseUrl,wsBaseUrl:this.wsBaseUrl}),i(g);return}let c=new ae(l);c.on("open",()=>{r.logInfo("WebSocket connection opened",{userId:e}),I.registerConnection(c,e),o(c)}),c.on("message",g=>{try{let d=JSON.parse(g.toString());t(d)}catch(d){r.logError("Error parsing WebSocket message",d)}}),c.on("error",g=>{let d=g instanceof Error?g.message:String(g),f=new Error(`WebSocket connection failed: ${d}. URL: ${l}. Please verify that the backend service is running and accessible.`);r.logError("WebSocket error",f,{userId:e,wsUrl:l}),n==null||n(f),i(f)}),c.on("close",(g,d)=>{let f=d.toString();g!==1e3&&r.logWarning("WebSocket closed unexpectedly",{userId:e,code:String(g),reason:f,wsUrl:l}),a==null||a(g,f)})}catch(s){let l=s instanceof Error?s.message:String(s),c=new Error(`Failed to create WebSocket connection: ${l}. Please check your network connection and backend service availability.`);r.logError("Error creating WebSocket connection",c,{userId:e,wsBaseUrl:this.wsBaseUrl}),i(c)}})}async sendDiff(e,t){return{success:!0,message:"Diff received (legacy method)"}}async getAnalysisResults(e){throw new Error("Not implemented")}async getUserFromSecureRoute(){try{let e=`${this.baseUrl}/v2/secure_route`;if(!this.apiKey)throw new Error("API key is required for secure route authentication");let t=await this.fetchWithRetry(e,{method:"POST",headers:{"X-CS-MCP-API-Key":this.apiKey,"Content-Type":"application/json"}});if(!t.ok){let a="Unknown error";try{let o=await t.json();a=o.detail||JSON.stringify(o)}catch{a=await t.text()||`HTTP ${t.status}`}throw r.logError(`HTTP error! status: ${t.status}, detail: ${a}`),t.status===401?new Error(a||"Authentication required. Please provide a valid API key."):t.status===404?new Error(a||"User not found."):new Error(a||`HTTP error! status: ${t.status}`)}let n=await t.json();return r.logInfo("User retrieved from secure route",{user_id:n.userid}),n}catch(e){throw r.logError("Error fetching user from secure route",e),e}}};import V from"ws";var U=class{backendApiService;constructor(e){this.backendApiService=e}async submitAndWaitForResults(e,t){let n;try{r.logInfo("Step 1: Establishing WebSocket connection BEFORE submitting review",{user_id:e.user_id});let o=await new Promise((d,f)=>{this.backendApiService.connectWebSocket(e.user_id,()=>{},h=>{r.logError("WebSocket connection failed before submit",h,{user_id:e.user_id}),f(h)},()=>{}).then(h=>{if(h.readyState===V.OPEN)n=h,r.logInfo("\u2705 WebSocket connection fully established and verified OPEN",{user_id:e.user_id,readyState:String(h.readyState)}),d(h);else{let m=new Error(`WebSocket connection not fully open. ReadyState: ${h.readyState}`);r.logError("\u274C WebSocket not in OPEN state",m,{user_id:e.user_id,readyState:String(h.readyState)}),f(m)}}).catch(f)});r.logInfo("\u2705 WebSocket connection confirmed OPEN, proceeding with HTTP call",{user_id:e.user_id});let i=e.file_changes.length,s=this.collectResultsViaEstablishedWebSocket(o,e.user_id,1200*1e3,t,i);s.catch(()=>{}),r.logInfo("Submitting commit review to backend",{user_id:e.user_id,factor:e.factor,fileCount:String(e.file_changes.length)});let l=await this.backendApiService.submitCommitReview({factor:e.factor,user_id:e.user_id,repo_name:e.repo_name,commit_id:e.commit_id,username:e.username,files_json:JSON.stringify(e.file_changes),organization_name:e.organization_name});r.logInfo("Commit review submitted",{analysisId:l.analysisId||"none"});let c=await s;if(!c||c.length===0)return r.logWarning("WebSocket completed but no analysis results received",{user_id:e.user_id,commit_id:e.commit_id,fileCount:String(e.file_changes.length)}),{success:!1,error:"No analysis results received. You can try again with fewer files or contact support if the issue persists.",analysisId:l.analysisId,results:[]};let g=c.filter(d=>d&&d.file_name&&d.analysis!==void 0&&d.analysis!==null);return g.length===0?(r.logWarning("All analysis results were invalid or empty",{user_id:e.user_id,commit_id:e.commit_id,totalResults:String(c.length)}),{success:!1,error:"Analysis completed but no valid results were generated. You can try again with fewer files or contact support if the issue persists.",analysisId:l.analysisId,results:c}):(r.logInfo("Analysis results received successfully",{user_id:e.user_id,commit_id:e.commit_id,resultCount:String(g.length),totalResults:String(c.length)}),{success:!0,analysisId:l.analysisId,message:l.message,results:g})}catch(a){return r.logError("Error in commit review flow",a),a instanceof Error&&a.message.includes("status: 401")&&n&&(r.logInfo("Closing WebSocket connection due to 401 Unauthorized error"),n.close()),{success:!1,error:a instanceof Error?a.message:String(a)}}}async collectResultsViaEstablishedWebSocket(e,t,n=1200*1e3,a,o){let i=[],s=null,l,c,g=!1,d;if(e.readyState!==V.OPEN)throw new Error(`WebSocket must be in OPEN state. Current state: ${e.readyState}`);let f=new Promise((h,m)=>{d=setTimeout(()=>{g||(s="Analysis timeout: The analysis took too long to complete. Please try again with fewer files or contact support if the issue persists.",r.logError("WebSocket timeout",new Error(s),{user_id:t,timeoutMs:String(n)}),e.close(),m(new Error(s)))},n)});return new Promise((h,m)=>{let p=_=>{var T;try{let v=JSON.parse(_.toString());if(d&&(clearTimeout(d),d=setTimeout(()=>{g||(s="Analysis timeout: The analysis is taking longer than expected. Please try again or contact support.",r.logError("WebSocket timeout after receiving initial results",new Error(s),{user_id:t,resultsReceived:String(i.length)}),e.close(),b(),m(new Error(s)))},n)),v.status_code!==200&&v.status_code!==void 0){l=v.status_code,c=v.error_message||"Unknown error from backend",s=c||"Unknown error from backend",g=!0,d&&clearTimeout(d),r.logError("WebSocket error message received",new Error(c),{status_code:String(l),user_id:t,raw_message:JSON.stringify(v)}),b(),m(new Error(s||"Unknown error from backend"));return}if(v.content&&v.content.analysis_type==="commit_review"){let P=((T=v.content.file_name)==null?void 0:T.replace(/\\/g,"/"))||"",F=v.content.analysis;P&&F!==void 0&&F!==null&&(i.push({analysis:F,language:v.content.language,file_name:P,analysisId:v.analysis_id??0}),r.logInfo("Analysis result received for file",{user_id:t,file_name:P,result_count:String(i.length)})),(v.content.is_complete||v.content.status==="complete")&&(g=!0,d&&clearTimeout(d),r.logInfo("Analysis marked as complete",{user_id:t,total_results:String(i.length)}),b(),h(i))}}catch(v){r.logError("Error parsing WebSocket message",v)}},w=_=>{d&&clearTimeout(d),s=_.message,r.logError("WebSocket connection error",_,{user_id:t}),b(),m(_)},C=(_,T)=>{d&&clearTimeout(d);let v=T.toString();_!==1e3&&!g?(s=`Connection closed unexpectedly: ${v}`,r.logWarning("WebSocket closed unexpectedly",{code:String(_),reason:v,user_id:t,isComplete:String(g),results_received:String(i.length)}),b(),m(new Error(s))):_===1e3&&!g?(r.logInfo("WebSocket closed normally, treating as completion",{user_id:t,results_received:String(i.length)}),g=!0,b(),h(i)):(b(),h(i))},b=()=>{e.removeListener("message",p),e.removeListener("error",w),e.removeListener("close",C)};e.on("message",p),e.on("error",w),e.on("close",C),f.catch(()=>{})})}async collectResultsViaWebSocket(e,t,n=1200*1e3){let a=[],o=null,i,s,l=!1,c,g,d=new Promise((f,h)=>{c=setTimeout(()=>{l||(o="Analysis timeout: The analysis took too long to complete. Please try again with fewer files or contact support if the issue persists.",r.logError("WebSocket timeout",new Error(o),{user_id:e,timeoutMs:String(n)}),g&&g.close(),h(new Error(o)))},n)});try{await Promise.race([new Promise((f,h)=>{this.backendApiService.connectWebSocket(e,m=>{var p;if(c&&(clearTimeout(c),c=setTimeout(()=>{l||(o="Analysis timeout: The analysis is taking longer than expected. Please try again or contact support.",r.logError("WebSocket timeout after receiving initial results",new Error(o),{user_id:e,resultsReceived:String(a.length)}),g&&g.close(),h(new Error(o)))},n)),m.status_code!==200&&m.status_code!==void 0){i=m.status_code,s=m.error_message||"Unknown error from backend",o=s,l=!0,c&&clearTimeout(c),r.logError("WebSocket error message received",new Error(s),{status_code:String(i),user_id:e,raw_message:JSON.stringify(m)}),f();return}if(m.content)if(m.content.analysis_type==="commit_review"){let C=((p=m.content.file_name)==null?void 0:p.replace(/\\/g,"/"))||"",b=m.content.analysis;C&&b!==void 0&&b!==null?(a.push({analysis:b,language:m.content.language,file_name:C,analysisId:m.analysis_id??0}),r.logInfo("Analysis result received for file",{user_id:e,file_name:C,result_count:String(a.length)})):r.logWarning("Received invalid analysis result (missing file_name or analysis)",{user_id:e,has_file_name:String(!!C),has_analysis:String(b!=null)}),(m.content.is_complete||m.content.status==="complete")&&(l=!0,c&&clearTimeout(c),r.logInfo("Analysis marked as complete",{user_id:e,total_results:String(a.length)}),f())}else r.logInfo("Received non-commit-review WebSocket message",{user_id:e,analysis_type:m.content.analysis_type||"unknown"});else r.logInfo("Received WebSocket message with null content",{user_id:e,results_so_far:String(a.length)})},m=>{c&&clearTimeout(c),o=m.message,r.logError("WebSocket connection error",m,{user_id:e}),h(m)},(m,p)=>{c&&clearTimeout(c),m!==1e3&&!l?(o=`Connection closed unexpectedly: ${p}`,r.logWarning("WebSocket closed unexpectedly",{code:String(m),reason:p.toString(),user_id:e,isComplete:String(l),results_received:String(a.length)})):m===1e3&&!l&&(r.logInfo("WebSocket closed normally, treating as completion",{user_id:e,results_received:String(a.length)}),l=!0),f()}).then(m=>{g=m,t&&t(m)}).catch(h)}),d])}catch(f){throw c&&clearTimeout(c),f}if(o){let f=new Error(o);throw i!==void 0&&(f.statusCode=i),s&&(f.errorDetail=s),f}return r.logInfo("WebSocket collection completed",{user_id:e,results_count:String(a.length),isComplete:String(l)}),a}};import*as k from"zod";var X={uncommitted:k.boolean().describe("value will be false for commit analysis and true for unstaged changes"),directory:k.string().describe("Path to the Git repository root"),factor:k.enum(["power_analysis","owasp","cwe_mitre","cwe_kev"]).describe("Analysis factor: power_analysis, owasp, cwe_mitre, or cwe_kev")},Z={success:k.boolean().describe("Whether the commit analysis was successful"),analysisId:k.string().optional().describe("Analysis ID returned from the backend (if successful)"),status:k.string().optional().describe("Status string (e.g., 'complete', 'pending', 'failed')"),statusCode:k.number().optional().describe("HTTP status code for error scenarios. Common values: 401 (authentication), 404 (not found), 422 (validation), 500 (internal error)"),errorType:k.string().optional().describe("Categorized error type for better error handling and user experience. Common values include: authentication_error, validation_error, not_found_error, git_error, websocket_error, api_error, internal_error, usage_limit_error, repository_error. Additional values may be introduced by the backend over time."),results:k.any().optional().describe("Analysis results array (present when success is true)"),message:k.string().optional().describe("Success or informational message"),error:k.string().optional().describe("Error message (present when success is false)"),errorDetails:k.object({userMessage:k.string().optional().describe("User-friendly error message that can be displayed to end users"),technicalDetails:k.string().optional().describe("Technical error details for debugging purposes"),retryable:k.boolean().optional().describe("Whether the error is retryable (true for transient errors like network issues, false for validation/authentication errors)")}).optional().describe("Detailed error information including user-friendly message and retry guidance")};function Q(u,e="",t=""){let n=e.toLowerCase(),a="",{issue:o,start_line:i,end_line:s,issue_code_snippet:l,severity:c,solution:g,solution_code_snippet:d}=u;return a+=`**Issue:** ${o}
|
|
9
|
+
`).length)})}catch(g){r.logWarning(`Failed to read content for ${s.path}`,{error:String(g),status:s.status,targetHash:a||"working tree"})}else r.logInfo("Skipping content retrieval for deleted file",{filename:s.path});let c="";try{r.logInfo("Getting file diff",{filename:s.path,status:s.status,targetHash:a||"working tree",isAdded:String(s.status==="added")}),c=await this.getFileDiff(n,s.path,a,s.status),r.logInfo("File diff retrieved",{filename:s.path,status:s.status,patchLength:String(c.length),hasPatch:String(c.length>0)})}catch(g){r.logWarning(`Failed to get diff for ${s.path}`,{error:String(g),status:s.status,targetHash:a||"working tree"})}i.push({filename:s.path,status:s.status,new_content:l,patch:c}),r.logInfo("File change processed",{filename:s.path,status:s.status,hasContent:String(l.length>0),hasPatch:String(c.length>0),contentLength:String(l.length),patchLength:String(c.length)})}return r.logInfo(`Successfully processed ${i.length} file changes`),i}catch(a){throw r.logError("Error in getFileChanges",a,{directory:n,uncommitted:String(e),commitHash:t||"none"}),a}}};import ae from"ws";import ce from"jszip";import{File as le,Blob as ge}from"node:buffer";import x from"ws";var B=class{connections=new Set;connectionsByUserId=new Map;isShuttingDown=!1;registerConnection(e,t){if(this.isShuttingDown){r.logWarning("Attempted to register WebSocket during shutdown",{userId:t||"unknown"});return}if(t){let a=this.connectionsByUserId.get(t);if(a&&a.readyState===x.OPEN){r.logWarning("Closing existing WebSocket connection for user before registering new one",{userId:t});try{a.close(1e3,"Replaced by new connection")}catch(o){r.logWarning("Error closing existing connection",{error:o instanceof Error?o.message:String(o),userId:t})}this.connections.delete(a),this.connectionsByUserId.delete(t)}this.connectionsByUserId.set(t,e)}this.connections.add(e),r.logInfo("WebSocket connection registered",{userId:t||"unknown",totalConnections:String(this.connections.size)});let n=()=>{this.unregisterConnection(e,t)};e.removeListener("close",n),e.on("close",n)}unregisterConnection(e,t){this.connections.delete(e)&&(t&&this.connectionsByUserId.get(t)===e&&this.connectionsByUserId.delete(t),r.logInfo("WebSocket connection unregistered",{userId:t||"unknown",totalConnections:String(this.connections.size)}))}async closeAllConnections(){if(this.isShuttingDown)return;this.isShuttingDown=!0;let e=this.connections.size;if(e===0){r.logInfo("No active WebSocket connections to close");return}r.logInfo("Closing all WebSocket connections due to MCP client disconnection",{connectionCount:String(e)});let t=[];for(let n of this.connections)if(n.readyState===x.OPEN||n.readyState===x.CONNECTING){let a=new Promise(o=>{let i=setTimeout(()=>{try{n.terminate()}catch(s){r.logWarning("Error terminating WebSocket",{error:s instanceof Error?s.message:String(s)})}o()},2e3);n.once("close",()=>{clearTimeout(i),o()});try{n.close(1e3,"MCP client disconnected")}catch(s){r.logWarning("Error closing WebSocket",{error:s instanceof Error?s.message:String(s)}),clearTimeout(i),o()}});t.push(a)}else this.connections.delete(n);try{await Promise.race([Promise.all(t),new Promise(n=>{setTimeout(()=>{r.logWarning("Timeout waiting for WebSocket connections to close"),n()},5e3)})])}catch(n){r.logError("Error closing WebSocket connections",n)}this.connections.clear(),this.connectionsByUserId.clear(),this.isShuttingDown=!1,r.logInfo("All WebSocket connections closed",{closedCount:String(e)})}getConnectionCount(){return this.connections.size}hasActiveConnections(){return this.connections.size>0}getConnectionForUser(e){let t=this.connectionsByUserId.get(e);if(t&&t.readyState===x.OPEN)return t;t&&(r.logInfo("Removing stale WebSocket connection for user",{userId:e}),this.connections.delete(t),this.connectionsByUserId.delete(e))}async closeConnectionForUser(e){let t=this.connectionsByUserId.get(e);if(t){r.logInfo("Closing WebSocket connection for user",{userId:e});try{(t.readyState===x.OPEN||t.readyState===x.CONNECTING)&&t.close(1e3,"User connection closed")}catch(n){r.logWarning("Error closing connection for user",{error:n instanceof Error?n.message:String(n),userId:e})}this.connections.delete(t),this.connectionsByUserId.delete(e)}}cleanupStaleConnections(){let e=[];for(let t of this.connections)(t.readyState===x.CLOSED||t.readyState===x.CLOSING)&&e.push(t);for(let t of e){this.connections.delete(t);for(let[n,a]of this.connectionsByUserId.entries())if(a===t){this.connectionsByUserId.delete(n);break}}e.length>0&&r.logInfo("Cleaned up stale WebSocket connections",{count:String(e.length)})}},I=new B;var N=class{baseUrl;wsBaseUrl;apiKey;fetchTimeout;maxRetries;retryDelay;constructor(e,t){if(!e||e.trim()==="")throw new Error("BACKEND_API_URL environment variable is required but not set. Please configure it in your .env file or environment variables.");this.baseUrl=e,e.startsWith("https://")?this.wsBaseUrl=e.replace(/^https/,"wss"):e.startsWith("http://")?this.wsBaseUrl=e.replace(/^http/,"ws"):(r.logWarning("Backend URL doesn't start with http/https, assuming WebSocket protocol",{baseUrl:e}),this.wsBaseUrl=e),this.apiKey=t,this.fetchTimeout=parseInt(process.env.FETCH_TIMEOUT_MS||"30000",10),this.maxRetries=parseInt(process.env.FETCH_MAX_RETRIES||"3",10),this.retryDelay=parseInt(process.env.FETCH_RETRY_DELAY_MS||"1000",10)}isRetryableError(e){if(!e)return!1;let t=e.message||String(e),n=String(e.code||""),a=["ECONNREFUSED","ETIMEDOUT","ENOTFOUND","ECONNRESET","EAI_AGAIN","fetch failed","network","timeout"];return!!(a.some(o=>n.includes(o))||a.some(o=>t.toLowerCase().includes(o.toLowerCase())))}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithRetry(e,t,n=0){let a=new AbortController,o=setTimeout(()=>{a.abort()},this.fetchTimeout);try{let i=await fetch(e,{...t,signal:a.signal});return clearTimeout(o),i}catch(i){if(clearTimeout(o),this.isRetryableError(i)&&n<this.maxRetries){let d=n+1,f=this.retryDelay*Math.pow(2,n);return r.logWarning(`Fetch failed (attempt ${d}/${this.maxRetries+1}), retrying in ${f}ms`,{url:e,error:i.message||String(i),errorCode:i.code||"",attempt:String(d),maxRetries:String(this.maxRetries)}),await this.sleep(f),this.fetchWithRetry(e,t,n+1)}let s=i.message||String(i),l=i.code||"",c=s;i.name==="AbortError"||a.signal.aborted?c=`Request timeout after ${this.fetchTimeout}ms. The backend may be slow or unreachable.`:l==="ECONNREFUSED"?c=`Connection refused. Please verify the backend is running at ${e}`:l==="ETIMEDOUT"?c=`Connection timeout. The backend may be slow or unreachable at ${e}`:l==="ENOTFOUND"&&(c=`DNS resolution failed. Please check the backend URL: ${e}`);let g=new Error(c);throw g.code=l,g.originalError=i,r.logError("Fetch failed after retries",g,{url:e,retryCount:String(n),maxRetries:String(this.maxRetries),errorCode:l}),g}}async submitCommitReview(e){try{let t=`${this.baseUrl}/v2/commit-review/${e.factor}`,n=new FormData;n.append("user_id",e.user_id),n.append("repo_name",e.repo_name),n.append("commit_id",e.commit_id),n.append("username",e.username),n.append("factor",e.factor);let a=new ce;a.file("files.json",e.files_json);let o=await a.generateAsync({type:"nodebuffer"}),i=new ge([o],{type:"application/zip"});n.append("files_zip",new le([i],"files.zip",{type:"application/zip"})),e.organization_name&&n.append("organization_name",e.organization_name);let s=await this.fetchWithRetry(t,{method:"POST",headers:{...this.apiKey?{"X-CS-MCP-API-Key":this.apiKey}:{}},body:n});if(!s.ok){let c="Unknown error",g,d;try{d=await s.json(),c=d.detail||d.error||d.message||JSON.stringify(d)}catch{let p=await s.text();g=p,c=p||`HTTP ${s.status}`}let f=d?JSON.stringify(d):"",h=g??"";r.logError("Backend commit-review HTTP error response",new Error(c),{url:t,status:String(s.status),parsedBody:f,rawBody:h});let m=new Error(`HTTP error! status: ${s.status}, detail: ${c}`);throw m.statusCode=s.status,m.errorDetail=c,m}if(s.status===202){let c={};try{let g=s.headers.get("content-type");g&&g.includes("application/json")?c=await s.json()??{}:c={message:await s.text()||"Commit review request received. Processing in the background."}}catch{c={message:"Commit review request received. Processing in the background."}}return{success:!0,data:c,analysisId:c.analysis_id?String(c.analysis_id):void 0,message:c.message||"Commit review submitted successfully"}}let l={};try{l=await s.json()??{}}catch{r.logWarning("Failed to parse JSON response, using default",{status:String(s.status)}),l={message:"Commit review submitted successfully"}}return{success:!0,data:l,analysisId:l.analysis_id?String(l.analysis_id):void 0,message:l.message||"Commit review submitted successfully"}}catch(t){throw r.logError("Error submitting commit review",t),t}}connectWebSocket(e,t,n,a){return new Promise((o,i)=>{try{I.cleanupStaleConnections();let s=I.getConnectionForUser(e);if(s){r.logInfo("Reusing existing WebSocket connection",{userId:e});let g=h=>{try{let m=JSON.parse(h.toString());t(m)}catch(m){r.logError("Error parsing WebSocket message",m)}},d=h=>{r.logError("WebSocket connection error",h,{userId:e}),n==null||n(h)},f=(h,m)=>{let p=m.toString();r.logInfo("WebSocket connection closed",{userId:e,code:String(h),reason:p}),a==null||a(h,p)};s.removeAllListeners("message"),s.removeAllListeners("error"),s.removeAllListeners("close"),s.on("message",g),s.on("error",d),s.on("close",f),o(s);return}let l=`${this.wsBaseUrl}/v2/ws/${e}`;if(r.logInfo("Connecting to WebSocket",{wsUrl:l,userId:e}),!l.startsWith("ws://")&&!l.startsWith("wss://")){let g=new Error(`Invalid WebSocket URL format: ${l}. Expected URL to start with ws:// or wss://. Please check your BACKEND_API_URL configuration.`);r.logError("Invalid WebSocket URL format",g,{wsUrl:l,baseUrl:this.baseUrl,wsBaseUrl:this.wsBaseUrl}),i(g);return}let c=new ae(l);c.on("open",()=>{r.logInfo("WebSocket connection opened",{userId:e}),I.registerConnection(c,e),o(c)}),c.on("message",g=>{try{let d=JSON.parse(g.toString());t(d)}catch(d){r.logError("Error parsing WebSocket message",d)}}),c.on("error",g=>{let d=g instanceof Error?g.message:String(g),f=new Error(`WebSocket connection failed: ${d}. URL: ${l}. Please verify that the backend service is running and accessible.`);r.logError("WebSocket error",f,{userId:e,wsUrl:l}),n==null||n(f),i(f)}),c.on("close",(g,d)=>{let f=d.toString();g!==1e3&&r.logWarning("WebSocket closed unexpectedly",{userId:e,code:String(g),reason:f,wsUrl:l}),a==null||a(g,f)})}catch(s){let l=s instanceof Error?s.message:String(s),c=new Error(`Failed to create WebSocket connection: ${l}. Please check your network connection and backend service availability.`);r.logError("Error creating WebSocket connection",c,{userId:e,wsBaseUrl:this.wsBaseUrl}),i(c)}})}async sendDiff(e,t){return{success:!0,message:"Diff received (legacy method)"}}async getAnalysisResults(e){throw new Error("Not implemented")}async getUserFromSecureRoute(){try{let e=`${this.baseUrl}/v2/secure_route`;if(!this.apiKey)throw new Error("API key is required for secure route authentication");let t=await this.fetchWithRetry(e,{method:"POST",headers:{"X-CS-MCP-API-Key":this.apiKey,"Content-Type":"application/json"}});if(!t.ok){let a="Unknown error";try{let o=await t.json();a=o.detail||JSON.stringify(o)}catch{a=await t.text()||`HTTP ${t.status}`}throw r.logError(`HTTP error! status: ${t.status}, detail: ${a}`),t.status===401?new Error(a||"Authentication required. Please provide a valid API key."):t.status===404?new Error(a||"User not found."):new Error(a||`HTTP error! status: ${t.status}`)}let n=await t.json();return r.logInfo("User retrieved from secure route",{user_id:n.userid}),n}catch(e){throw r.logError("Error fetching user from secure route",e),e}}};import V from"ws";var U=class{backendApiService;constructor(e){this.backendApiService=e}async submitAndWaitForResults(e,t){let n;try{r.logInfo("Step 1: Establishing WebSocket connection BEFORE submitting review",{user_id:e.user_id});let o=await new Promise((d,f)=>{this.backendApiService.connectWebSocket(e.user_id,()=>{},h=>{r.logError("WebSocket connection failed before submit",h,{user_id:e.user_id}),f(h)},()=>{}).then(h=>{if(h.readyState===V.OPEN)n=h,r.logInfo("\u2705 WebSocket connection fully established and verified OPEN",{user_id:e.user_id,readyState:String(h.readyState)}),d(h);else{let m=new Error(`WebSocket connection not fully open. ReadyState: ${h.readyState}`);r.logError("\u274C WebSocket not in OPEN state",m,{user_id:e.user_id,readyState:String(h.readyState)}),f(m)}}).catch(f)});r.logInfo("\u2705 WebSocket connection confirmed OPEN, proceeding with HTTP call",{user_id:e.user_id});let i=e.file_changes.length,s=this.collectResultsViaEstablishedWebSocket(o,e.user_id,1200*1e3,t,i);s.catch(()=>{}),r.logInfo("Submitting commit review to backend",{user_id:e.user_id,factor:e.factor,fileCount:String(e.file_changes.length)});let l=await this.backendApiService.submitCommitReview({factor:e.factor,user_id:e.user_id,repo_name:e.repo_name,commit_id:e.commit_id,username:e.username,files_json:JSON.stringify(e.file_changes),organization_name:e.organization_name});r.logInfo("Commit review submitted",{analysisId:l.analysisId||"none"});let c=await s;if(!c||c.length===0)return r.logWarning("WebSocket completed but no analysis results received",{user_id:e.user_id,commit_id:e.commit_id,fileCount:String(e.file_changes.length)}),{success:!1,error:"No analysis results received. You can try again with fewer files or contact support if the issue persists.",analysisId:l.analysisId,results:[]};let g=c.filter(d=>d&&d.file_name&&d.analysis!==void 0&&d.analysis!==null);return g.length===0?(r.logWarning("All analysis results were invalid or empty",{user_id:e.user_id,commit_id:e.commit_id,totalResults:String(c.length)}),{success:!1,error:"Analysis completed but no valid results were generated. You can try again with fewer files or contact support if the issue persists.",analysisId:l.analysisId,results:c}):(r.logInfo("Analysis results received successfully",{user_id:e.user_id,commit_id:e.commit_id,resultCount:String(g.length),totalResults:String(c.length)}),{success:!0,analysisId:l.analysisId,message:l.message,results:g})}catch(a){return r.logError("Error in commit review flow",a),a instanceof Error&&a.message.includes("status: 401")&&n&&(r.logInfo("Closing WebSocket connection due to 401 Unauthorized error"),n.close()),{success:!1,error:a instanceof Error?a.message:String(a)}}}async collectResultsViaEstablishedWebSocket(e,t,n=1200*1e3,a,o){let i=[],s=null,l,c,g=!1,d;if(e.readyState!==V.OPEN)throw new Error(`WebSocket must be in OPEN state. Current state: ${e.readyState}`);let f=new Promise((h,m)=>{d=setTimeout(()=>{g||(s="Analysis timeout: The analysis took too long to complete. Please try again with fewer files or contact support if the issue persists.",r.logError("WebSocket timeout",new Error(s),{user_id:t,timeoutMs:String(n)}),e.close(),m(new Error(s)))},n)});return new Promise((h,m)=>{let p=b=>{var T;try{let v=JSON.parse(b.toString());if(d&&(clearTimeout(d),d=setTimeout(()=>{g||(s="Analysis timeout: The analysis is taking longer than expected. Please try again or contact support.",r.logError("WebSocket timeout after receiving initial results",new Error(s),{user_id:t,resultsReceived:String(i.length)}),e.close(),E(),m(new Error(s)))},n)),v.status_code!==200&&v.status_code!==void 0){l=v.status_code,c=v.error_message||"Unknown error from backend",s=c||"Unknown error from backend",g=!0,d&&clearTimeout(d),r.logError("WebSocket error message received",new Error(c),{status_code:String(l),user_id:t,raw_message:JSON.stringify(v)}),E(),m(new Error(s||"Unknown error from backend"));return}if(v.content&&v.content.analysis_type==="commit_review"){let P=((T=v.content.file_name)==null?void 0:T.replace(/\\/g,"/"))||"",F=v.content.analysis;P&&F!==void 0&&F!==null&&(i.push({analysis:F,language:v.content.language,file_name:P,analysisId:v.analysis_id??0}),r.logInfo("Analysis result received for file",{user_id:t,file_name:P,result_count:String(i.length)})),(v.content.is_complete||v.content.status==="complete")&&(g=!0,d&&clearTimeout(d),r.logInfo("Analysis marked as complete",{user_id:t,total_results:String(i.length)}),E(),h(i))}}catch(v){r.logError("Error parsing WebSocket message",v)}},w=b=>{d&&clearTimeout(d),s=b.message,r.logError("WebSocket connection error",b,{user_id:t}),E(),m(b)},C=(b,T)=>{d&&clearTimeout(d);let v=T.toString();b!==1e3&&!g?(s=`Connection closed unexpectedly: ${v}`,r.logWarning("WebSocket closed unexpectedly",{code:String(b),reason:v,user_id:t,isComplete:String(g),results_received:String(i.length)}),E(),m(new Error(s))):b===1e3&&!g?(r.logInfo("WebSocket closed normally, treating as completion",{user_id:t,results_received:String(i.length)}),g=!0,E(),h(i)):(E(),h(i))},E=()=>{e.removeListener("message",p),e.removeListener("error",w),e.removeListener("close",C)};e.on("message",p),e.on("error",w),e.on("close",C),f.catch(()=>{})})}async collectResultsViaWebSocket(e,t,n=1200*1e3){let a=[],o=null,i,s,l=!1,c,g,d=new Promise((f,h)=>{c=setTimeout(()=>{l||(o="Analysis timeout: The analysis took too long to complete. Please try again with fewer files or contact support if the issue persists.",r.logError("WebSocket timeout",new Error(o),{user_id:e,timeoutMs:String(n)}),g&&g.close(),h(new Error(o)))},n)});try{await Promise.race([new Promise((f,h)=>{this.backendApiService.connectWebSocket(e,m=>{var p;if(c&&(clearTimeout(c),c=setTimeout(()=>{l||(o="Analysis timeout: The analysis is taking longer than expected. Please try again or contact support.",r.logError("WebSocket timeout after receiving initial results",new Error(o),{user_id:e,resultsReceived:String(a.length)}),g&&g.close(),h(new Error(o)))},n)),m.status_code!==200&&m.status_code!==void 0){i=m.status_code,s=m.error_message||"Unknown error from backend",o=s,l=!0,c&&clearTimeout(c),r.logError("WebSocket error message received",new Error(s),{status_code:String(i),user_id:e,raw_message:JSON.stringify(m)}),f();return}if(m.content)if(m.content.analysis_type==="commit_review"){let C=((p=m.content.file_name)==null?void 0:p.replace(/\\/g,"/"))||"",E=m.content.analysis;C&&E!==void 0&&E!==null?(a.push({analysis:E,language:m.content.language,file_name:C,analysisId:m.analysis_id??0}),r.logInfo("Analysis result received for file",{user_id:e,file_name:C,result_count:String(a.length)})):r.logWarning("Received invalid analysis result (missing file_name or analysis)",{user_id:e,has_file_name:String(!!C),has_analysis:String(E!=null)}),(m.content.is_complete||m.content.status==="complete")&&(l=!0,c&&clearTimeout(c),r.logInfo("Analysis marked as complete",{user_id:e,total_results:String(a.length)}),f())}else r.logInfo("Received non-commit-review WebSocket message",{user_id:e,analysis_type:m.content.analysis_type||"unknown"});else r.logInfo("Received WebSocket message with null content",{user_id:e,results_so_far:String(a.length)})},m=>{c&&clearTimeout(c),o=m.message,r.logError("WebSocket connection error",m,{user_id:e}),h(m)},(m,p)=>{c&&clearTimeout(c),m!==1e3&&!l?(o=`Connection closed unexpectedly: ${p}`,r.logWarning("WebSocket closed unexpectedly",{code:String(m),reason:p.toString(),user_id:e,isComplete:String(l),results_received:String(a.length)})):m===1e3&&!l&&(r.logInfo("WebSocket closed normally, treating as completion",{user_id:e,results_received:String(a.length)}),l=!0),f()}).then(m=>{g=m,t&&t(m)}).catch(h)}),d])}catch(f){throw c&&clearTimeout(c),f}if(o){let f=new Error(o);throw i!==void 0&&(f.statusCode=i),s&&(f.errorDetail=s),f}return r.logInfo("WebSocket collection completed",{user_id:e,results_count:String(a.length),isComplete:String(l)}),a}};import*as k from"zod";var X={uncommitted:k.boolean().describe("value will be false for commit analysis and true for unstaged changes"),directory:k.string().describe("Path to the Git repository root"),factor:k.enum(["power_analysis","owasp","cwe_mitre","cwe_kev"]).describe("Analysis factor: power_analysis, owasp, cwe_mitre, or cwe_kev")},Z={success:k.boolean().describe("Whether the commit analysis was successful"),analysisId:k.string().optional().describe("Analysis ID returned from the backend (if successful)"),status:k.string().optional().describe("Status string (e.g., 'complete', 'pending', 'failed')"),statusCode:k.number().optional().describe("HTTP status code for error scenarios. Common values: 401 (authentication), 404 (not found), 422 (validation), 500 (internal error)"),errorType:k.string().optional().describe("Categorized error type for better error handling and user experience. Common values include: authentication_error, validation_error, not_found_error, git_error, websocket_error, api_error, internal_error, usage_limit_error, repository_error. Additional values may be introduced by the backend over time."),results:k.any().optional().describe("Analysis results array (present when success is true)"),message:k.string().optional().describe("Success or informational message"),error:k.string().optional().describe("Error message (present when success is false)"),errorDetails:k.object({userMessage:k.string().optional().describe("User-friendly error message that can be displayed to end users"),technicalDetails:k.string().optional().describe("Technical error details for debugging purposes"),retryable:k.boolean().optional().describe("Whether the error is retryable (true for transient errors like network issues, false for validation/authentication errors)")}).optional().describe("Detailed error information including user-friendly message and retry guidance")};function Q(u,e="",t=""){let n=e.toLowerCase(),a="",{issue:o,start_line:i,end_line:s,issue_code_snippet:l,severity:c,solution:g,solution_code_snippet:d}=u;return a+=`**Issue:** ${o}
|
|
10
10
|
|
|
11
11
|
`,t&&(a+=`**Quality Aspect:** ${t}
|
|
12
12
|
|
|
@@ -48,8 +48,9 @@ See the MCP integration guide at https://docs.codesherlock.ai/codesherlock-mcp-s
|
|
|
48
48
|
If you followed these steps and still face issues, contact support at support@codesherlock.ai.`,g=!1):o===404||s.includes("not found")||s.includes("User not found")||s.includes("WebSocket not active")?(l="not_found_error",s.includes("User not found")?c="User not found. Please verify your authentication.":s.includes("WebSocket not active")||s.includes("WebSocket not active for user")?c="WebSocket connection not active. The backend service may be unavailable. Please try again.":c="Resource not found.",g=!1):o===422||s.includes("Invalid")||s.includes("validation")||s.includes("must contain")||s.includes("must be")?(l="validation_error",s.includes("files.json")||s.includes("Zip file must contain")?c="Invalid file format. The zip file must contain a valid 'files.json' file.":s.includes("not valid UTF-8")||s.includes("UTF-8")?c="Invalid file encoding. The files.json must be valid UTF-8 text.":s.includes("Invalid JSON format")||s.includes("JSONDecodeError")?c="Invalid JSON format in files.json. Please ensure the file contains valid JSON.":s.includes("must be a list")||s.includes("must be a dictionary")?c="Invalid file structure. The files.json must contain a list of file objects with 'filename', 'status', and 'new_content' fields.":s.includes("No valid code files found")||s.includes("No valid code files")?c="No valid code files found in the commit. Please ensure your changes include code files that can be analyzed.":s.includes("No analysis results received")||s.includes("no analysis results")?c="No analysis results were generated. This may occur if no valid code files were found in the commit, or if all file processing failed. Please ensure your commit contains code files that can be analyzed.":s.includes("no valid results were generated")||s.includes("All analysis results were invalid")?c="Analysis completed but no valid results were generated. This may occur if the files could not be analyzed or if the analysis pipeline encountered errors. Please try again or contact support.":s.includes("Analysis timeout")||s.includes("took too long")?c=s:s.includes("Invalid factor")||s.includes("valid factor")?c="Invalid analysis factor. Please use one of: power_analysis, owasp, cwe_mitre, or cwe_kev.":s.includes("GitHub App is not installed")||s.includes("not installed in this repository")?c="CodeSherlock GitHub App is not installed in this repository. Commit review would be allowed once it is installed in this repository.":s.includes("No Git repository information")||s.includes("No Git repository information found")?c="No Git repository information found. To run commit review, please provide valid organization and repository names.":s.includes("organization details")||s.includes("retrieving your organization")?c="We ran into an issue while retrieving your organization details. Please try again later or contact support@codesherlock.ai.":s.includes("usage details")||s.includes("retrieving your usage")?c="We ran into an issue while retrieving your usage details. Please try again later or contact support@codesherlock.ai.":s.includes("Missing keys")||s.includes("required_keys")?c="Invalid file structure. Each file in files.json must have 'filename', 'status', and 'new_content' fields.":c=s,g=!1):s.includes("Git")||s.includes("git")||s.includes("repository")||s.includes("commit")?(l="git_error",c=`Git error: ${s}`,g=!0):s.includes("WebSocket")||s.includes("websocket")||s.includes("Connection")||s.includes("connection closed")?(l="websocket_error",s.includes("Connection closed unexpectedly")?c="WebSocket connection closed unexpectedly. Please try again.":c="WebSocket connection error. Please try again.",g=!0):o===500||s.includes("Internal server error")||s.includes("internal error")||s.includes("Internal error")||s.includes("An error occurred")||s.includes("Failed to generate")?(l="internal_error",s.includes("Failed to generate commit review analysis")||s.includes("commit review pipeline returned no result")?c="Failed to generate commit review analysis. The analysis pipeline encountered an error.":s.includes("An internal error occurred before running")||s.includes("error occurred during pre-checks")?c="An internal error occurred before running the analysis. Please try again later or contact support.":s.includes("error occurred during commit review")?c="An error occurred during commit review analysis. Please try again later or contact support.":c="An internal server error occurred. Please try again later or contact support@codesherlock.ai.",g=!0):s.includes("usage")||s.includes("token")||s.includes("limit")||s.includes("trial")||s.includes("quota")?(l="usage_limit_error",c=s,g=!1):o&&o>=400&&o<500?(l="api_error",c=s||`Client error (${o}). Please check your request and try again.`,g=!1):o&&o>=500&&(l="api_error",c="Backend service error. Please try again later or contact support.",g=!0),{statusCode:o,errorType:l,userMessage:c,technicalDetails:a,retryable:g}}import fe from"fs";function te(u,e){try{switch(u){case"start":return"Starting CodeSherlock analysis...";case"git_start":return"Reviewing your Git changes...";case"git_complete":return`Found ${(e==null?void 0:e.files)??0} files to analyze.`;case"smart_analysis_intro":return"This will take around 5-7 minutes to perform a smart analysis. Will give you updates and coding tips on the way.";case"file_start":return`Analysis of file ${(e==null?void 0:e.index)??1} started...`;case"file_done":return`Reviewed ${(e==null?void 0:e.current)??0}/${(e==null?void 0:e.total)??0}: ${(e==null?void 0:e.filename)??""}`;case"coding_tip":return`CodeSherlock is analyzing your file, until then you can enjoy this coding tip:
|
|
49
49
|
${(e==null?void 0:e.tip)||"Write clean, maintainable code."}`;case"complete":return`Analysis complete! Found ${(e==null?void 0:e.issues)??0} issues.`;case"timeout":return"Still analyzing\u2026 this may take a little longer.";default:return"Processing..."}}catch{return"Processing..."}}import*as L from"fs";import*as re from"path";var J=class{filePath;constructor(e){this.filePath=e??re.join(process.cwd(),"src","utils","cs-dev-mongo.coding_tips.json")}getTips(){try{if(L.existsSync(this.filePath)){let e=L.readFileSync(this.filePath,"utf-8");return JSON.parse(e).map(n=>n.message)}return["Write clean, maintainable code.","Test your code early and often."]}catch{return["Write clean, maintainable code."]}}},j=class{tipInterval=null;seenTips=new Set;lastMessageTimestamp=Date.now();start(e,t){this.stop(),this.lastMessageTimestamp=Date.now(),this.tipInterval=setInterval(()=>{let n=Date.now()-this.lastMessageTimestamp,a=e();if(n>=15e3&&a.length>0){let o;do o=Math.floor(Math.random()*a.length);while(this.seenTips.has(o)&&this.seenTips.size<a.length);this.seenTips.add(o),this.seenTips.size>=a.length&&this.seenTips.clear(),Promise.resolve(t(a[o])).catch(()=>{})}},1e3)}stop(){this.tipInterval&&(clearInterval(this.tipInterval),this.tipInterval=null)}resetTimestamp(){this.lastMessageTimestamp=Date.now()}},O=class{constructor(e,t,n=new J,a=r,o=new j){this.server=e;this.progressToken=t;this.tipsProvider=n;this.log=a;this.scheduler=o;this.tips=this.tipsProvider.getTips()}tips=[];startTipTimer(){this.scheduler.start(()=>this.tips,e=>this.send("coding_tip",50,100,{tip:e}))}stopTipTimer(){this.scheduler.stop()}async send(e,t,n,a){try{this.scheduler.resetTimestamp();let o=te(e,a);if(process.stderr.write(`[DEBUG:EMITTER] send() called. Step: ${e}, Token: ${this.progressToken?"PRESENT":"MISSING"}
|
|
50
50
|
`),this.progressToken){try{process.stderr.write(`[DEBUG:EMITTER] Attempting MCP $/progress notification: "${o}"
|
|
51
|
-
`),await this.server.server.notification({method:"$/progress",params:{progressToken:this.progressToken,progress:t,total:n,message:o}})
|
|
52
|
-
`),await this.server.server.notification({method:"notifications/codesherlock/progress",params:{progress:t,total:n,message:o}})}catch(i){this.log.logInfo("Fallback progress notification failed (ignoring)",{error:i instanceof Error?i.message:String(i),step:e})}}catch(o){this.log.logWarning("Unexpected error in ProgressEmitter.send()",{error:o instanceof Error?o.message:String(o),step:e})}}};function W(u,e){try{let t=e;r.logInfo(u,{success:String(t==null?void 0:t.success),errorType:t!=null&&t.errorType?String(t.errorType):"",hasErrorType:t&&"errorType"in t?"true":"false",statusCode:(t==null?void 0:t.statusCode)!==void 0?String(t.statusCode):"",structuredContentPreview:JSON.stringify(t).slice(0,2e3)})}catch(t){r.logError("Failed to log structuredContent debug payload",t)}}var ne=(u,e,t,n,a=(o,i)=>new O(o,i))=>async({uncommitted:o,directory:i,factor:s},l)=>{var f,h,m;let c=((f=l==null?void 0:l._meta)==null?void 0:f.progressToken)??(l==null?void 0:l.progressToken),g=a(u,c),d;try{await g.send("start",0,10);let p=s==="power"?"power_analysis":s;if(r.logInfo("=== TOOL INVOKED: codesherlock_analyze ===",{uncommitted:String(o),directory:i,originalFactor:s,normalizedFactor:p}),!fe.existsSync(i)){let y=M(new Error("Directory does not exist")),S={success:!1,error:y.userMessage,errorType:y.errorType,statusCode:404,errorDetails:{userMessage:y.userMessage,technicalDetails:`Directory not found: ${i}`,retryable:!1}};return W("codesherlock_analyze: returning error (directory does not exist)",S),{content:[{type:"text",text:`Error: ${y.userMessage}`}],structuredContent:S,isError:!0}}r.logInfo("Fetching file changes",{uncommitted:String(o),directory:i,factor:s}),await g.send("git_start",1,10);let w;try{r.logInfo("Calling gitService.analyzeGitChanges",{uncommitted:String(o),directory:i}),w=await e.analyzeGitChanges(o,i),r.logInfo("File changes received from gitService",{fileCount:String(w.length),files:JSON.stringify(w.map(y=>{var S,A;return{filename:y.filename,status:y.status,contentLength:String(((S=y.new_content)==null?void 0:S.length)||0),patchLength:String(((A=y.patch)==null?void 0:A.length)||0)}}))})}catch(y){let S=M(y);r.logError("Failed to analyze git changes",y,{directory:i,uncommitted:String(o)});let A={success:!1,error:S.userMessage,errorType:S.errorType,statusCode:S.statusCode,errorDetails:{userMessage:S.userMessage,technicalDetails:S.technicalDetails,retryable:S.retryable}};return W("codesherlock_analyze: returning error (gitService.analyzeGitChanges failed)",A),{content:[{type:"text",text:`Error: ${S.userMessage}`}],structuredContent:A,isError:!0}}if(!w||w.length===0){let y=M(new Error("No file changes found in the commit")),S={success:!1,error:y.userMessage,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:"No file changes found. Please ensure you have committed changes or uncommitted changes to analyze.",technicalDetails:"Git analysis returned empty file changes array",retryable:!1}};return W("codesherlock_analyze: returning error (no file changes)",S),{content:[{type:"text",text:`Error: ${y.userMessage}`}],structuredContent:S,isError:!0}}let C=20;if(w.length>C){let y=M(new Error(`Too many files to analyze. Maximum ${C} files allowed.`));r.logWarning("File limit exceeded",{fileCount:String(w.length),maxLimit:String(C)});let S={success:!1,error:y.userMessage,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:`Too many files to analyze. Found ${w.length} files, but the maximum allowed is ${C} files. Please reduce the number of files in your commit or uncommitted changes.`,technicalDetails:`File count validation failed: ${w.length} files exceeds limit of ${C}`,retryable:!1}};return W("codesherlock_analyze: returning error (file limit exceeded)",S),{content:[{type:"text",text:`Error: Too many files to analyze. Found ${w.length} files, but the maximum allowed is ${C} files. Please reduce the number of files in your commit or uncommitted changes.`}],structuredContent:S,isError:!0}}let b=[];for(let y=0;y<w.length;y++){let S=w[y];if(!S||typeof S!="object"){b.push(`File at index ${y} is not a valid object`);continue}(!S.filename||typeof S.filename!="string")&&b.push(`File at index ${y} is missing or has invalid 'filename' field`),(!S.status||typeof S.status!="string")&&b.push(`File at index ${y} is missing or has invalid 'status' field`),S.new_content===void 0&&b.push(`File at index ${y} is missing 'new_content' field`)}if(b.length>0){let y=`Invalid file changes structure: ${b.join("; ")}`,S=M(new Error(y));r.logError("Invalid file changes structure",new Error(y),{invalidFilesCount:String(b.length)});let A={success:!1,error:S.userMessage,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:"Invalid file changes structure. Each file must have 'filename', 'status', and 'new_content' fields.",technicalDetails:y,retryable:!1}};return W("codesherlock_analyze: returning error (invalid file changes structure)",A),{content:[{type:"text",text:`Error: ${S.userMessage}`}],structuredContent:A,isError:!0}}let _;try{_=await e.getCurrentCommitHash(i)}catch(y){let S=M(y);r.logError("Failed to get commit hash",y,{directory:i}),_="UNCOMMITTED",r.logWarning("Using fallback commit ID",{commitId:_})}r.logInfo("Fetching user information from secure route");let T;try{T=await n.getUserFromSecureRoute()}catch(y){let S=M(y);r.logError("Failed to fetch user from secure route",y);let A={success:!1,error:S.userMessage,errorType:S.errorType,statusCode:S.statusCode,status:S.errorType,errorDetails:{userMessage:S.userMessage,technicalDetails:S.technicalDetails,retryable:S.retryable}};return W("codesherlock_analyze: returning error (getUserFromSecureRoute failed)",A),{content:[{type:"text",text:`Error: ${S.userMessage}`}],structuredContent:A,isError:!0}}d=T.userid;let v=T.username;r.logInfo("User information retrieved",{user_id:d,username:v});let R=i.split(/[/\\]/).pop()||"unknown-repo",P=i.split(/[/\\]/),F=P.findIndex(y=>y.toLowerCase()==="github"),ie=F>=0&&P[F+1]?P[F+1]:void 0,Me=!1,E;try{await g.send("smart_analysis_intro",4,10),g.startTipTimer(),E=await t.submitAndWaitForResults({factor:p,user_id:d,repo_name:R,commit_id:_,username:v,file_changes:w,organization_name:ie},g)}finally{g.stopTipTimer()}if(!E.success){let y=M(new Error(E.error||"Unknown error"));r.logError("Commit review failed",new Error(E.error||"Unknown error"),{user_id:d,commit_id:_});let S={success:!1,error:E.error||y.userMessage,errorType:y.errorType,statusCode:y.statusCode,errorDetails:{userMessage:y.userMessage,technicalDetails:E.error||y.technicalDetails,retryable:y.retryable}};return W("codesherlock_analyze: returning error (result.success === false)",S),{content:[{type:"text",text:`Error: ${y.userMessage}`}],structuredContent:S,isError:!0}}r.logInfo("Processing analysis results",{success:String(E.success),hasResults:String(!!E.results),resultsLength:String(((h=E.results)==null?void 0:h.length)||0)});let q="";E.success&&E.results&&E.results.length>0?q=ee(E.results):r.logInfo("Skipping markdown conversion - no results to convert"),r.logEvent("tool_completed",{toolName:"codesherlock_analyze",success:String(E.success),analysisId:E.analysisId||"none",factor:s}),await g.send("complete",10,10,{issues:((m=E.results)==null?void 0:m.length)||0});let Y={success:E.success,analysisId:E.analysisId,status:E.status,results:E.results,message:E.message};return W("codesherlock_analyze: returning success structuredContent",Y),{content:[{type:"text",text:q||JSON.stringify(E,null,2)}],structuredContent:Y}}catch(p){let w=p instanceof Error?p.message:String(p),C=w.includes("timeout")||w.includes("timed out"),b=w.includes("aborted")||w.includes("cancelled")||w.includes("Tool call was aborted");if(r.logError("Error in codesherlock_analyze handler",p,{directory:i,uncommitted:String(o),factor:s,isAborted:String(b||C),hasUserId:String(!!d)}),(b||C)&&d){let v=d;r.logInfo("Cleaning up WebSocket connection for user after tool call abortion/timeout",{user_id:v}),await I.closeConnectionForUser(v).catch(R=>{r.logWarning("Error during WebSocket cleanup",{error:R instanceof Error?R.message:String(R),user_id:v})})}let _=M(p),T={success:!1,error:_.userMessage,errorType:_.errorType,statusCode:_.statusCode||500,errorDetails:{userMessage:_.userMessage,technicalDetails:_.technicalDetails,retryable:_.retryable}};return W("codesherlock_analyze: returning error (catch block)",T),{content:[{type:"text",text:`Error analyzing commit: ${_.userMessage}`}],structuredContent:T,isError:!0}}};function se(u){try{let e=new z,t=process.env.BACKEND_API_URL;r.logInfo("Backend URL: "+t),r.logInfo("MCP API Key: "+process.env.MCP_API_KEY);let n,a;try{n=new N(t,process.env.MCP_API_KEY),a=new U(n)}catch(o){let i=o instanceof Error?o.message:String(o);throw r.logError("Failed to create backend services",o instanceof Error?o:new Error(i),{errorType:"service_creation_failure"}),new Error(`Failed to initialize backend services: ${i}`)}try{u.registerTool("codesherlock_analyze",{title:"CodeSherlock Analyze",description:"CodeSherlock is an AI- based code analysis tool that validates unstaged changes and commits directly inside IDEs and AI Agents. It helps developers catch security, quality, and design issues early by combining deep analysis with compliance-aware checks OWASP, CWE, SOC-2 at the moment code is written. CodeSherlock also performs other security vulnerability reviews along with Maintainability, Reliability and Scalability checks. Use CodeSherlock to review and validate code especially generated via AI.",inputSchema:X,outputSchema:Z},ne(u,e,a,n)),r.logInfo("Tool 'codesherlock_analyze' registered successfully")}catch(o){let i=o instanceof Error?o.message:String(o);throw r.logError("Failed to register tool 'codesherlock_analyze'",o instanceof Error?o:new Error(i),{toolName:"codesherlock_analyze",errorType:"tool_registration_failure"}),new Error(`Failed to register tool 'codesherlock_analyze': ${i}`)}}catch(e){throw e}}import pe from"dotenv";import{fileURLToPath as ye}from"url";import{dirname as Se,join as we,resolve as ve}from"path";var D=()=>{};console.log=D;console.error=D;console.warn=D;console.info=D;console.debug=D;console.trace=D;var be=ye(import.meta.url),_e=Se(be),Ee=ve(_e,".."),ke=we(Ee,".env");pe.config({path:ke});var Ce=process.env.APPLICATIONINSIGHTS_CONNECTION_STRING;async function Ie(){await r.initialize(Ce)}await Ie();async function Te(){process.on("uncaughtException",async u=>{r.logError("Uncaught exception - performing cleanup",u,{errorType:"uncaught_exception"});try{I.hasActiveConnections()&&(r.logInfo("Closing WebSocket connections due to uncaught exception",{connectionCount:String(I.getConnectionCount())}),await I.closeAllConnections())}catch(t){r.logError("Error during cleanup in uncaughtException handler",t)}await r.flush(),process.stderr.write(`
|
|
51
|
+
`),await this.server.server.notification({method:"$/progress",params:{progressToken:this.progressToken,progress:t,total:n,message:o}}),process.stderr.write(`[DEBUG:EMITTER] \u2705 $/progress notification DELIVERED to transport. Step: ${e}, Progress: ${t}/${n}
|
|
52
|
+
`)}catch(i){this.log.logWarning("MCP progress notification failed",{error:i instanceof Error?i.message:String(i),step:e,token:this.progressToken})}return}try{process.stderr.write(`[DEBUG:EMITTER] Attempting Fallback notification: "${o}"
|
|
53
|
+
`),await this.server.server.notification({method:"notifications/codesherlock/progress",params:{progress:t,total:n,message:o}})}catch(i){this.log.logInfo("Fallback progress notification failed (ignoring)",{error:i instanceof Error?i.message:String(i),step:e})}}catch(o){this.log.logWarning("Unexpected error in ProgressEmitter.send()",{error:o instanceof Error?o.message:String(o),step:e})}}};function W(u,e){try{let t=e;r.logInfo(u,{success:String(t==null?void 0:t.success),errorType:t!=null&&t.errorType?String(t.errorType):"",hasErrorType:t&&"errorType"in t?"true":"false",statusCode:(t==null?void 0:t.statusCode)!==void 0?String(t.statusCode):"",structuredContentPreview:JSON.stringify(t).slice(0,2e3)})}catch(t){r.logError("Failed to log structuredContent debug payload",t)}}var ne=(u,e,t,n,a=(o,i)=>new O(o,i))=>async({uncommitted:o,directory:i,factor:s},l)=>{var f,h,m;let c=((f=l==null?void 0:l._meta)==null?void 0:f.progressToken)??(l==null?void 0:l.progressToken),g=a(u,c),d;try{await g.send("start",0,10);let p=s==="power"?"power_analysis":s;if(r.logInfo("=== TOOL INVOKED: codesherlock_analyze ===",{uncommitted:String(o),directory:i,originalFactor:s,normalizedFactor:p}),!fe.existsSync(i)){let y=M(new Error("Directory does not exist")),S={success:!1,error:y.userMessage,errorType:y.errorType,statusCode:404,errorDetails:{userMessage:y.userMessage,technicalDetails:`Directory not found: ${i}`,retryable:!1}};return W("codesherlock_analyze: returning error (directory does not exist)",S),{content:[{type:"text",text:`Error: ${y.userMessage}`}],structuredContent:S,isError:!0}}r.logInfo("Fetching file changes",{uncommitted:String(o),directory:i,factor:s}),await g.send("git_start",1,10);let w;try{r.logInfo("Calling gitService.analyzeGitChanges",{uncommitted:String(o),directory:i}),w=await e.analyzeGitChanges(o,i),r.logInfo("File changes received from gitService",{fileCount:String(w.length),files:JSON.stringify(w.map(y=>{var S,A;return{filename:y.filename,status:y.status,contentLength:String(((S=y.new_content)==null?void 0:S.length)||0),patchLength:String(((A=y.patch)==null?void 0:A.length)||0)}}))})}catch(y){let S=M(y);r.logError("Failed to analyze git changes",y,{directory:i,uncommitted:String(o)});let A={success:!1,error:S.userMessage,errorType:S.errorType,statusCode:S.statusCode,errorDetails:{userMessage:S.userMessage,technicalDetails:S.technicalDetails,retryable:S.retryable}};return W("codesherlock_analyze: returning error (gitService.analyzeGitChanges failed)",A),{content:[{type:"text",text:`Error: ${S.userMessage}`}],structuredContent:A,isError:!0}}if(!w||w.length===0){let y=M(new Error("No file changes found in the commit")),S={success:!1,error:y.userMessage,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:"No file changes found. Please ensure you have committed changes or uncommitted changes to analyze.",technicalDetails:"Git analysis returned empty file changes array",retryable:!1}};return W("codesherlock_analyze: returning error (no file changes)",S),{content:[{type:"text",text:`Error: ${y.userMessage}`}],structuredContent:S,isError:!0}}let C=20;if(w.length>C){let y=M(new Error(`Too many files to analyze. Maximum ${C} files allowed.`));r.logWarning("File limit exceeded",{fileCount:String(w.length),maxLimit:String(C)});let S={success:!1,error:y.userMessage,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:`Too many files to analyze. Found ${w.length} files, but the maximum allowed is ${C} files. Please reduce the number of files in your commit or uncommitted changes.`,technicalDetails:`File count validation failed: ${w.length} files exceeds limit of ${C}`,retryable:!1}};return W("codesherlock_analyze: returning error (file limit exceeded)",S),{content:[{type:"text",text:`Error: Too many files to analyze. Found ${w.length} files, but the maximum allowed is ${C} files. Please reduce the number of files in your commit or uncommitted changes.`}],structuredContent:S,isError:!0}}let E=[];for(let y=0;y<w.length;y++){let S=w[y];if(!S||typeof S!="object"){E.push(`File at index ${y} is not a valid object`);continue}(!S.filename||typeof S.filename!="string")&&E.push(`File at index ${y} is missing or has invalid 'filename' field`),(!S.status||typeof S.status!="string")&&E.push(`File at index ${y} is missing or has invalid 'status' field`),S.new_content===void 0&&E.push(`File at index ${y} is missing 'new_content' field`)}if(E.length>0){let y=`Invalid file changes structure: ${E.join("; ")}`,S=M(new Error(y));r.logError("Invalid file changes structure",new Error(y),{invalidFilesCount:String(E.length)});let A={success:!1,error:S.userMessage,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:"Invalid file changes structure. Each file must have 'filename', 'status', and 'new_content' fields.",technicalDetails:y,retryable:!1}};return W("codesherlock_analyze: returning error (invalid file changes structure)",A),{content:[{type:"text",text:`Error: ${S.userMessage}`}],structuredContent:A,isError:!0}}let b;try{b=await e.getCurrentCommitHash(i)}catch(y){let S=M(y);r.logError("Failed to get commit hash",y,{directory:i}),b="UNCOMMITTED",r.logWarning("Using fallback commit ID",{commitId:b})}r.logInfo("Fetching user information from secure route");let T;try{T=await n.getUserFromSecureRoute()}catch(y){let S=M(y);r.logError("Failed to fetch user from secure route",y);let A={success:!1,error:S.userMessage,errorType:S.errorType,statusCode:S.statusCode,status:S.errorType,errorDetails:{userMessage:S.userMessage,technicalDetails:S.technicalDetails,retryable:S.retryable}};return W("codesherlock_analyze: returning error (getUserFromSecureRoute failed)",A),{content:[{type:"text",text:`Error: ${S.userMessage}`}],structuredContent:A,isError:!0}}d=T.userid;let v=T.username;r.logInfo("User information retrieved",{user_id:d,username:v});let R=i.split(/[/\\]/).pop()||"unknown-repo",P=i.split(/[/\\]/),F=P.findIndex(y=>y.toLowerCase()==="github"),ie=F>=0&&P[F+1]?P[F+1]:void 0,Me=!1,_;try{await g.send("smart_analysis_intro",4,10),g.startTipTimer(),_=await t.submitAndWaitForResults({factor:p,user_id:d,repo_name:R,commit_id:b,username:v,file_changes:w,organization_name:ie},g)}finally{g.stopTipTimer()}if(!_.success){let y=M(new Error(_.error||"Unknown error"));r.logError("Commit review failed",new Error(_.error||"Unknown error"),{user_id:d,commit_id:b});let S={success:!1,error:_.error||y.userMessage,errorType:y.errorType,statusCode:y.statusCode,errorDetails:{userMessage:y.userMessage,technicalDetails:_.error||y.technicalDetails,retryable:y.retryable}};return W("codesherlock_analyze: returning error (result.success === false)",S),{content:[{type:"text",text:`Error: ${y.userMessage}`}],structuredContent:S,isError:!0}}r.logInfo("Processing analysis results",{success:String(_.success),hasResults:String(!!_.results),resultsLength:String(((h=_.results)==null?void 0:h.length)||0)});let q="";_.success&&_.results&&_.results.length>0?q=ee(_.results):r.logInfo("Skipping markdown conversion - no results to convert"),r.logEvent("tool_completed",{toolName:"codesherlock_analyze",success:String(_.success),analysisId:_.analysisId||"none",factor:s}),await g.send("complete",10,10,{issues:((m=_.results)==null?void 0:m.length)||0});let Y={success:_.success,analysisId:_.analysisId,status:_.status,results:_.results,message:_.message};return W("codesherlock_analyze: returning success structuredContent",Y),{content:[{type:"text",text:q||JSON.stringify(_,null,2)}],structuredContent:Y}}catch(p){let w=p instanceof Error?p.message:String(p),C=w.includes("timeout")||w.includes("timed out"),E=w.includes("aborted")||w.includes("cancelled")||w.includes("Tool call was aborted");if(r.logError("Error in codesherlock_analyze handler",p,{directory:i,uncommitted:String(o),factor:s,isAborted:String(E||C),hasUserId:String(!!d)}),(E||C)&&d){let v=d;r.logInfo("Cleaning up WebSocket connection for user after tool call abortion/timeout",{user_id:v}),await I.closeConnectionForUser(v).catch(R=>{r.logWarning("Error during WebSocket cleanup",{error:R instanceof Error?R.message:String(R),user_id:v})})}let b=M(p),T={success:!1,error:b.userMessage,errorType:b.errorType,statusCode:b.statusCode||500,errorDetails:{userMessage:b.userMessage,technicalDetails:b.technicalDetails,retryable:b.retryable}};return W("codesherlock_analyze: returning error (catch block)",T),{content:[{type:"text",text:`Error analyzing commit: ${b.userMessage}`}],structuredContent:T,isError:!0}}};function se(u){try{let e=new z,t=process.env.BACKEND_API_URL;r.logInfo("Backend URL: "+t),r.logInfo("MCP API Key: "+process.env.MCP_API_KEY);let n,a;try{n=new N(t,process.env.MCP_API_KEY),a=new U(n)}catch(o){let i=o instanceof Error?o.message:String(o);throw r.logError("Failed to create backend services",o instanceof Error?o:new Error(i),{errorType:"service_creation_failure"}),new Error(`Failed to initialize backend services: ${i}`)}try{u.registerTool("codesherlock_analyze",{title:"CodeSherlock Analyze",description:"CodeSherlock is an AI- based code analysis tool that validates unstaged changes and commits directly inside IDEs and AI Agents. It helps developers catch security, quality, and design issues early by combining deep analysis with compliance-aware checks OWASP, CWE, SOC-2 at the moment code is written. CodeSherlock also performs other security vulnerability reviews along with Maintainability, Reliability and Scalability checks. Use CodeSherlock to review and validate code especially generated via AI.",inputSchema:X,outputSchema:Z},ne(u,e,a,n)),r.logInfo("Tool 'codesherlock_analyze' registered successfully")}catch(o){let i=o instanceof Error?o.message:String(o);throw r.logError("Failed to register tool 'codesherlock_analyze'",o instanceof Error?o:new Error(i),{toolName:"codesherlock_analyze",errorType:"tool_registration_failure"}),new Error(`Failed to register tool 'codesherlock_analyze': ${i}`)}}catch(e){throw e}}import pe from"dotenv";import{fileURLToPath as ye}from"url";import{dirname as Se,join as we,resolve as ve}from"path";var D=()=>{};console.log=D;console.error=D;console.warn=D;console.info=D;console.debug=D;console.trace=D;var Ee=ye(import.meta.url),be=Se(Ee),_e=ve(be,".."),ke=we(_e,".env");pe.config({path:ke});var Ce=process.env.APPLICATIONINSIGHTS_CONNECTION_STRING;async function Ie(){await r.initialize(Ce)}await Ie();async function Te(){process.on("uncaughtException",async u=>{r.logError("Uncaught exception - performing cleanup",u,{errorType:"uncaught_exception"});try{I.hasActiveConnections()&&(r.logInfo("Closing WebSocket connections due to uncaught exception",{connectionCount:String(I.getConnectionCount())}),await I.closeAllConnections())}catch(t){r.logError("Error during cleanup in uncaughtException handler",t)}await r.flush(),process.stderr.write(`
|
|
53
54
|
\u274C Unhandled Exception
|
|
54
55
|
|
|
55
56
|
An unexpected error occurred. The server will shut down.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codesherlock/codesherlock-alpha-mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A TypeScript-based Model Context Protocol (MCP) server",
|
|
5
5
|
"mcpName": "io.github.FGC-Shreyansh-Chachaundiya/codesherlock-mcp-server",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"author": "",
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@codesherlock/codesherlock-alpha-mcp-server": "^0.1.4",
|
|
38
39
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
39
40
|
"applicationinsights": "^3.12.1",
|
|
40
41
|
"archiver": "^7.0.1",
|
|
@@ -57,4 +58,4 @@
|
|
|
57
58
|
"tsx": "^4.19.2",
|
|
58
59
|
"typescript": "^5.7.2"
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
+
}
|