@codesherlock/codesherlock-alpha-mcp-server 0.4.0 → 0.4.2
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/cli/index.js +64 -0
- package/build/index.js +34 -35
- package/package.json +7 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{Command as ke,CommanderError as Ee}from"commander";var J={name:"@codesherlock/codesherlock-alpha-mcp-server",version:"0.4.2",description:"A TypeScript-based Model Context Protocol (MCP) server",mcpName:"io.github.FGC-Shreyansh-Chachaundiya/codesherlock-mcp-server",main:"build/index.js",bin:{"codesherlock-mcp-server":"./build/index.js",codesherlock:"./build/cli/index.js"},files:["build","README.md",".env"],type:"module",scripts:{clean:`node -e "const fs=require('fs');fs.rmSync('./build',{recursive:true,force:true});"`,build:"npm run clean && tsc -p tsconfig.prod.json --noEmit && node esbuild.config.mjs","build:dev":"tsc",dev:"tsx src/index.ts",start:"node build/index.js",watch:"tsc --watch",prepublishOnly:"npm run build",prepare:"npm run build",test:"jest","test:coverage":"jest --coverage","test:git-changes":"tsx test-analyzeGitChanges.ts"},keywords:["mcp","model-context-protocol","typescript","ai"],author:"",license:"MIT",dependencies:{"@modelcontextprotocol/sdk":"^1.0.4",applicationinsights:"^3.12.1",archiver:"^7.0.1",chalk:"^4.1.2",commander:"^14.0.3",dotenv:"^17.2.3","form-data":"^4.0.5",jszip:"^3.10.1",keytar:"^7.9.0","simple-git":"^3.30.0",ws:"^8.18.3",zod:"^3.24.1","zod-to-json-schema":"^3.25.0"},devDependencies:{"@types/archiver":"^7.0.0","@types/jest":"^30.0.0","@types/node":"^22.10.1","@types/ws":"^8.18.1",esbuild:"^0.27.4",jest:"^30.2.0","ts-jest":"^29.4.5",tsx:"^4.19.2",typescript:"^5.7.2"}};import we from"dotenv";import{resolve as ne,join as Se,dirname as ve}from"path";import{existsSync as be}from"fs";import{simpleGit as ae}from"simple-git";import*as P from"applicationinsights";var B=class f{static instance;client=null;isInitialized=!1;constructor(){}static getInstance(){return f.instance||(f.instance=new f),f.instance}async initialize(e){if(!this.isInitialized){if(!e){this.isInitialized=!1,this.client=null;return}try{let t=new P.TelemetryClient(e);P.setup(e).setAutoDependencyCorrelation(!1).setAutoCollectRequests(!1).setAutoCollectPerformance(!1,!1).setAutoCollectExceptions(!0).setAutoCollectDependencies(!1).setAutoCollectConsole(!1).setSendLiveMetrics(!1),P.start(),this.client=t,this.isInitialized=!0}catch{this.isInitialized=!1,this.client=null}}}logInfo(e,t){this.client&&this.client.trackTrace({message:e,severity:1,properties:t})}logWarning(e,t){this.client&&this.client.trackTrace({message:e,severity:2,properties:t})}logError(e,t,n){this.client&&(t?this.client.trackException({exception:t,properties:{...n,customMessage:e}}):this.client.trackTrace({message:e,severity:3,properties:n}))}logEvent(e,t,n){this.client&&this.client.trackEvent({name:e,properties:t,measurements:n})}trackMetric(e,t,n){this.client&&this.client.trackMetric({name:e,value:t,properties:n})}trackDependency(e,t,n,i,c){this.client&&this.client.trackDependency({name:e,data:t,duration:n,success:i,dependencyTypeName:c||"HTTP"})}async flush(){this.client&&this.client.flush()}isEnabled(){return this.isInitialized&&this.client!==null}},s=B.getInstance();var $=class{getGitInstance(e){return ae(e)}async getRepoStructure(e){try{let{readdir:t}=await import("fs/promises"),{join:n,relative:i}=await import("path"),c=new Set(["node_modules",".git",".venv",".idea",".vscode","__pycache__"]),o=[],r=async l=>{let a;try{a=await t(l,{withFileTypes:!0})}catch(g){s.logWarning("Failed to read directory while building repo structure",{directory:l,error:String(g)});return}for(let g of a){let d=n(l,g.name);if(g.isDirectory()){if(c.has(g.name))continue;await r(d)}else if(g.isFile()){let u=i(e,d).replace(/\\/g,"/");o.push(u)}}};return await r(e),s.logInfo("Repo structure built",{repoPath:e,fileCount:String(o.length)}),o}catch(t){throw s.logError("Error building repo structure",t,{repoPath:e}),t}}async getDiff(e,t){try{let n=this.getGitInstance(e);return!t||t==="HEAD"?(s.logInfo("Fetching diff for HEAD (unstaged changes)",{repoPath:e}),await n.diff()):t.includes("..")?(s.logInfo(`Fetching diff for range: ${t}`,{repoPath:e}),await n.diff([t])):(s.logInfo(`Fetching diff for commit: ${t}`,{repoPath:e}),await n.show([t]))}catch(n){throw s.logError("Error fetching diff",n,{repoPath:e,commitHash:t}),n}}async getUncommittedDiff(e){try{let t=this.getGitInstance(e),n=await t.diff(["--cached"]),i=await t.diff();return[n,i].filter(o=>o.trim()).join(`
|
|
3
|
+
`)}catch(t){throw s.logError("Error fetching uncommitted diff",t,{repoPath:e}),t}}async getFileContent(e,t,n){try{let i=this.getGitInstance(e);if(!n){let{readFileSync:o,existsSync:r}=await import("fs"),{join:l}=await import("path"),a=l(e,t);if(s.logInfo("Reading file from working tree",{filePath:t,fullPath:a,exists:String(r(a))}),!r(a))throw s.logWarning("File does not exist in working tree",{filePath:t,fullPath:a}),new Error(`File does not exist: ${a}`);let g=o(a,"utf-8");return s.logInfo("File read from working tree",{filePath:t,contentLength:String(g.length),lineCount:String(g.split(`
|
|
4
|
+
`).length)}),g}s.logInfo("Reading file from git commit",{filePath:t,ref:n,gitCommand:`${n}:${t}`});let c=await i.show([`${n}:${t}`]);return s.logInfo("File read from git commit",{filePath:t,ref:n,contentLength:String(c.length),lineCount:String(c.split(`
|
|
5
|
+
`).length)}),c}catch(i){throw s.logError("Error fetching file content",i,{repoPath:e,filePath:t,ref:n||"working tree",errorMessage:String(i)}),i}}async getChangedFiles(e,t){try{let n=this.getGitInstance(e);if(!t){let c=await n.status();return[...c.modified,...c.created,...c.not_added,...c.deleted,...c.renamed.map(o=>o.to)]}return(await n.diffSummary([`${t}^`,t])).files.map(c=>c.file)}catch(n){throw s.logError("Error fetching changed files",n,{repoPath:e,commitHash:t}),n}}async getCommitInfo(e,t){try{let i=await this.getGitInstance(e).log([t,"-1"]);if(!i.latest)throw new Error(`Commit ${t} not found`);let c=i.latest,o=await this.getChangedFiles(e,t);return{hash:c.hash,author:c.author_name,email:c.author_email,date:c.date,message:c.message,files:o}}catch(n){throw s.logError("Error fetching commit info",n,{repoPath:e,commitHash:t}),n}}async getRepoStatus(e){try{let n=await this.getGitInstance(e).status();return{branch:n.current||"HEAD",modified:n.modified,staged:[...n.created,...n.staged],untracked:n.not_added}}catch(t){throw s.logError("Error fetching repo status",t,{repoPath:e}),t}}async getCurrentCommitHash(e){try{return(await this.getGitInstance(e).revparse(["HEAD"])).trim()}catch(t){return s.logWarning("Unable to resolve current commit hash; using placeholder",{repoPath:e,error:String(t)}),"UNCOMMITTED"}}async getFileDiff(e,t,n,i){try{let c=this.getGitInstance(e);if(!n){if(i==="untracked"||i==="added"){s.logInfo(`Generating synthetic patch for ${i} file`,{filePath:t,repoPath:e,status:i});let l=await this.buildUntrackedPatch(e,t);return s.logInfo("Synthetic patch generated",{filePath:t,patchLength:String(l.length)}),l}s.logInfo("Getting diff for uncommitted file",{filePath:t,repoPath:e,step:"checking staged diff first"});try{let l=await c.diff(["--cached","--",t]);if(l.trim())return s.logInfo("Found staged diff",{filePath:t,diffLength:String(l.length)}),l;s.logInfo("Staged diff is empty",{filePath:t})}catch(l){s.logInfo(`Staged diff failed for ${t}, trying unstaged`,{error:String(l),filePath:t})}s.logInfo("Trying unstaged diff",{filePath:t});try{let l=await c.diff(["--",t]);if(l.trim())return s.logInfo("Found unstaged diff",{filePath:t,diffLength:String(l.length)}),l;s.logInfo("Unstaged diff is empty",{filePath:t})}catch(l){s.logInfo(`Unstaged diff failed for ${t}, checking if untracked`,{error:String(l),filePath:t})}s.logInfo(`No diff found for ${t}, generating synthetic patch for untracked/added file`,{filePath:t,repoPath:e});let r=await this.buildUntrackedPatch(e,t);return s.logInfo("Synthetic patch generated",{filePath:t,patchLength:String(r.length)}),r}s.logInfo("Getting diff for committed file",{filePath:t,commitHash:n,gitCommand:`${n}^..${n}`});let o=await c.diff([`${n}^..${n}`,"--",t]);return s.logInfo("Committed file diff retrieved",{filePath:t,commitHash:n,diffLength:String(o.length)}),o}catch(c){throw s.logError("Error fetching file diff",c,{repoPath:e,filePath:t,commitHash:n||"working tree",errorMessage:String(c)}),c}}async buildUntrackedPatch(e,t){try{s.logInfo("Building synthetic patch for untracked/added file",{filePath:t,repoPath:e});let{readFileSync:n,existsSync:i}=await import("fs"),{join:c}=await import("path"),o=c(e,t);if(s.logInfo("Checking file existence for patch",{filePath:t,fullPath:o,exists:String(i(o))}),!i(o))return s.logWarning(`File does not exist for untracked patch: ${t}`,{filePath:t,fullPath:o}),"";s.logInfo("Reading file content for synthetic patch",{filePath:t,fullPath:o});let r=n(o,"utf-8"),l=r.split(`
|
|
6
|
+
`),a=l.length;s.logInfo("File content read for patch generation",{filePath:t,contentLength:String(r.length),lineCount:String(a)});let g=[`diff --git a/${t} b/${t}`,"new file mode 100644","index 0000000..e69de29","--- /dev/null",`+++ b/${t}`];if(a>0){g.push(`@@ -0,0 +1,${a} @@`);for(let d of l)g.push(`+${d}`)}else g.push("@@ -0,0 +1,0 @@");return g.join(`
|
|
7
|
+
`)}catch(n){return s.logError("Error building untracked patch",n,{repoPath:e,filePath:t}),""}}async getChangedFilesWithStatus(e,t){var n,i,c,o,r,l,a;try{let g=this.getGitInstance(e);if(!t){s.logInfo("Getting git status for uncommitted changes",{repoPath:e});let u=await g.status();if(s.logInfo("Git status retrieved",{repoPath:e,totalFiles:String(((n=u.files)==null?void 0:n.length)||0),modifiedCount:String(((i=u.modified)==null?void 0:i.length)||0),createdCount:String(((c=u.created)==null?void 0:c.length)||0),notAddedCount:String(((o=u.not_added)==null?void 0:o.length)||0),deletedCount:String(((r=u.deleted)==null?void 0:r.length)||0),renamedCount:String(((l=u.renamed)==null?void 0:l.length)||0),stagedCount:String(((a=u.staged)==null?void 0:a.length)||0)}),!u.files||u.files.length===0)return s.logInfo("No changed files found in repository status",{repoPath:e}),[];s.logInfo("Raw git status files",{files:JSON.stringify(u.files.map(p=>({path:p.path,index:p.index,working_dir:p.working_dir})))});let h=u.files.map(p=>{if(!p.path)return s.logWarning("File entry missing path in status",{file:JSON.stringify(p)}),null;let S=this.determineFileStatus(p.index,p.working_dir);return s.logInfo("File status determined",{path:p.path,indexCode:p.index,workingDirCode:p.working_dir,determinedStatus:S}),{path:p.path,status:S}}).filter(p=>p!==null),m=h.reduce((p,S)=>(p[S.status]=(p[S.status]||0)+1,p),{});return s.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
|
+
`).filter(Boolean).map(u=>{let h=u.split(" "),m=h[0][0],p=h[1],S="modified";return m==="A"?S="added":m==="D"?S="deleted":m==="R"&&(S="renamed",p=h[2]||h[1]),{status:S,path:p}})}catch(g){throw s.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{s.logInfo("Analyzing git changes",{directory:t,uncommitted:String(e),targetHash:e?"working tree":"HEAD"});let n=e?void 0:"HEAD",i=await this.getChangedFilesWithStatus(t,n);s.logInfo(`Found ${i.length} changed files`,{directory:t,uncommitted:String(e),files:JSON.stringify(i.map(o=>`${o.path} (${o.status})`))});let c=[];for(let o of i){s.logInfo("Processing file in analyzeGitChanges",{filename:o.path,status:o.status,isAdded:String(o.status==="added"),targetHash:n||"working tree"});let r="";if(o.status!=="deleted")try{s.logInfo("Getting content for file",{filename:o.path,status:o.status,source:n?`commit ${n}`:"working tree"}),r=await this.getFileContent(t,o.path,n),s.logInfo("Content retrieved successfully",{filename:o.path,status:o.status,contentLength:String(r.length)})}catch(g){s.logWarning(`Failed to read content for ${o.path}`,{error:String(g),status:o.status,filename:o.path})}else s.logInfo("Skipping content for deleted file",{filename:o.path});let l="";try{s.logInfo("Getting diff for file",{filename:o.path,status:o.status,targetHash:n||"working tree"}),l=await this.getFileDiff(t,o.path,n,o.status),s.logInfo("Diff retrieved successfully",{filename:o.path,status:o.status,patchLength:String(l.length)})}catch(g){s.logWarning(`Failed to get diff for ${o.path}`,{error:String(g),status:o.status,filename:o.path})}let a={filename:o.path,status:o.status,new_content:r,patch:l};s.logInfo("File processing complete",{filename:o.path,status:o.status,hasContent:String(r.length>0),hasPatch:String(l.length>0),contentLength:String(r.length),patchLength:String(l.length)}),c.push(a)}return s.logInfo("Git changes analysis complete",{directory:t,totalFiles:String(c.length),filesProcessed:JSON.stringify(c.map(o=>({filename:o.filename,status:o.status,hasContent:o.new_content.length>0,hasPatch:o.patch.length>0})))}),c}catch(n){throw s.logError("Error analyzing git changes",n,{directory:t}),n}}async getOrganizationName(e){try{let n=await this.getGitInstance(e).getRemotes(!0),i=n.find(l=>l.name==="origin")||n[0];if(!i||!i.refs.fetch)return;let c=i.refs.fetch,o=c.match(/(?:github\.com|gitlab\.com|bitbucket\.org)[:/]([^/]+)\//);if(o&&o[1])return o[1];let r=c.replace(/\.git$/,"").split(/[:/]/);return r.length>=2?r[r.length-2]:void 0}catch(t){s.logWarning("Failed to get organization name from git remote",{repoPath:e,error:String(t)});return}}async getFileChanges(e,t,n){try{if(!e&&!t)throw new Error("commitHash is required when uncommitted is false");s.logInfo("Getting file changes",{directory:n,uncommitted:String(e),commitHash:t||"none"});let i=e?void 0:t,c=await this.getChangedFilesWithStatus(n,i);s.logInfo(`Found ${c.length} changed files`);let o=[];for(let r of c){s.logInfo("Processing file change",{filename:r.path,status:r.status,targetHash:i||"working tree",isAdded:String(r.status==="added")});let l="";if(r.status!=="deleted")try{s.logInfo("Getting file content",{filename:r.path,status:r.status,source:i?`commit ${i}`:"working tree",isAdded:String(r.status==="added")}),l=await this.getFileContent(n,r.path,i),s.logInfo("File content retrieved",{filename:r.path,status:r.status,contentLength:String(l.length),lineCount:String(l.split(`
|
|
9
|
+
`).length)})}catch(g){s.logWarning(`Failed to read content for ${r.path}`,{error:String(g),status:r.status,targetHash:i||"working tree"})}else s.logInfo("Skipping content retrieval for deleted file",{filename:r.path});let a="";try{s.logInfo("Getting file diff",{filename:r.path,status:r.status,targetHash:i||"working tree",isAdded:String(r.status==="added")}),a=await this.getFileDiff(n,r.path,i,r.status),s.logInfo("File diff retrieved",{filename:r.path,status:r.status,patchLength:String(a.length),hasPatch:String(a.length>0)})}catch(g){s.logWarning(`Failed to get diff for ${r.path}`,{error:String(g),status:r.status,targetHash:i||"working tree"})}o.push({filename:r.path,status:r.status,new_content:l,patch:a}),s.logInfo("File change processed",{filename:r.path,status:r.status,hasContent:String(l.length>0),hasPatch:String(a.length>0),contentLength:String(l.length),patchLength:String(a.length)})}return s.logInfo(`Successfully processed ${o.length} file changes`),o}catch(i){throw s.logError("Error in getFileChanges",i,{directory:n,uncommitted:String(e),commitHash:t||"none"}),i}}};import ce from"ws";import le from"jszip";import{File as ge,Blob as ue}from"node:buffer";import T from"ws";var L=class{connections=new Set;connectionsByUserId=new Map;isShuttingDown=!1;registerConnection(e,t){if(this.isShuttingDown){s.logWarning("Attempted to register WebSocket during shutdown",{userId:t||"unknown"});return}if(t){let i=this.connectionsByUserId.get(t);if(i&&i.readyState===T.OPEN){s.logWarning("Closing existing WebSocket connection for user before registering new one",{userId:t});try{i.close(1e3,"Replaced by new connection")}catch(c){s.logWarning("Error closing existing connection",{error:c instanceof Error?c.message:String(c),userId:t})}this.connections.delete(i),this.connectionsByUserId.delete(t)}this.connectionsByUserId.set(t,e)}this.connections.add(e),s.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),s.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){s.logInfo("No active WebSocket connections to close");return}s.logInfo("Closing all WebSocket connections due to MCP client disconnection",{connectionCount:String(e)});let t=[];for(let n of this.connections)if(n.readyState===T.OPEN||n.readyState===T.CONNECTING){let i=new Promise(c=>{let o=setTimeout(()=>{try{n.terminate()}catch(r){s.logWarning("Error terminating WebSocket",{error:r instanceof Error?r.message:String(r)})}c()},2e3);n.once("close",()=>{clearTimeout(o),c()});try{n.close(1e3,"MCP client disconnected")}catch(r){s.logWarning("Error closing WebSocket",{error:r instanceof Error?r.message:String(r)}),clearTimeout(o),c()}});t.push(i)}else this.connections.delete(n);try{await Promise.race([Promise.all(t),new Promise(n=>{setTimeout(()=>{s.logWarning("Timeout waiting for WebSocket connections to close"),n()},5e3)})])}catch(n){s.logError("Error closing WebSocket connections",n)}this.connections.clear(),this.connectionsByUserId.clear(),this.isShuttingDown=!1,s.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===T.OPEN)return t;t&&(s.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){s.logInfo("Closing WebSocket connection for user",{userId:e});try{(t.readyState===T.OPEN||t.readyState===T.CONNECTING)&&t.close(1e3,"User connection closed")}catch(n){s.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===T.CLOSED||t.readyState===T.CLOSING)&&e.push(t);for(let t of e){this.connections.delete(t);for(let[n,i]of this.connectionsByUserId.entries())if(i===t){this.connectionsByUserId.delete(n);break}}e.length>0&&s.logInfo("Cleaned up stale WebSocket connections",{count:String(e.length)})}},W=new L;var F=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"):(s.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||""),i=["ECONNREFUSED","ETIMEDOUT","ENOTFOUND","ECONNRESET","EAI_AGAIN","fetch failed","network","timeout"];return!!(i.some(c=>n.includes(c))||i.some(c=>t.toLowerCase().includes(c.toLowerCase())))}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithRetry(e,t,n=0){let i=new AbortController,c=setTimeout(()=>{i.abort()},this.fetchTimeout);try{let o=await fetch(e,{...t,signal:i.signal});return clearTimeout(c),o}catch(o){if(clearTimeout(c),this.isRetryableError(o)&&n<this.maxRetries){let d=n+1,u=this.retryDelay*Math.pow(2,n);return s.logWarning(`Fetch failed (attempt ${d}/${this.maxRetries+1}), retrying in ${u}ms`,{url:e,error:o.message||String(o),errorCode:o.code||"",attempt:String(d),maxRetries:String(this.maxRetries)}),await this.sleep(u),this.fetchWithRetry(e,t,n+1)}let r=o.message||String(o),l=o.code||"",a=r;o.name==="AbortError"||i.signal.aborted?a=`Request timeout after ${this.fetchTimeout}ms. The backend may be slow or unreachable.`:l==="ECONNREFUSED"?a=`Connection refused. Please verify the backend is running at ${e}`:l==="ETIMEDOUT"?a=`Connection timeout. The backend may be slow or unreachable at ${e}`:l==="ENOTFOUND"&&(a=`DNS resolution failed. Please check the backend URL: ${e}`);let g=new Error(a);throw g.code=l,g.originalError=o,s.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 i=new le;i.file("files.json",e.files_json);let c=await i.generateAsync({type:"nodebuffer"}),o=new ue([c],{type:"application/zip"});n.append("files_zip",new ge([o],"files.zip",{type:"application/zip"})),e.organization_name&&n.append("organization_name",e.organization_name);let r=await this.fetchWithRetry(t,{method:"POST",headers:{...this.apiKey?{"X-CS-MCP-API-Key":this.apiKey}:{}},body:n});if(!r.ok){let a="Unknown error",g,d;try{d=await r.json(),a=d.detail||d.error||d.message||JSON.stringify(d)}catch{let p=await r.text();g=p,a=p||`HTTP ${r.status}`}let u=d?JSON.stringify(d):"",h=g??"";s.logError("Backend commit-review HTTP error response",new Error(a),{url:t,status:String(r.status),parsedBody:u,rawBody:h});let m=new Error(`HTTP error! status: ${r.status}, detail: ${a}`);throw m.statusCode=r.status,m.errorDetail=a,m}if(r.status===202){let a={};try{let g=r.headers.get("content-type");g&&g.includes("application/json")?a=await r.json()??{}:a={message:await r.text()||"Commit review request received. Processing in the background."}}catch{a={message:"Commit review request received. Processing in the background."}}return{success:!0,data:a,analysisId:a.analysis_id?String(a.analysis_id):void 0,message:a.message||"Commit review submitted successfully"}}let l={};try{l=await r.json()??{}}catch{s.logWarning("Failed to parse JSON response, using default",{status:String(r.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 s.logError("Error submitting commit review",t),t}}async identifyMissingDependencies(e){try{let t=`${this.baseUrl}/v2/missing-dependencies`;if(!this.apiKey)throw new Error("API key is required to call the missing-dependencies endpoint");let n=await this.fetchWithRetry(t,{method:"POST",headers:{"Content-Type":"application/json","X-CS-MCP-API-Key":this.apiKey},body:JSON.stringify(e)});if(!n.ok){let o="Unknown error",r,l;try{r=await n.json(),o=r.detail||r.error||r.message||JSON.stringify(r)}catch{let g=await n.text();l=g,o=g||`HTTP ${n.status}`}s.logError("Backend missing-dependencies HTTP error response",new Error(o),{url:t,status:String(n.status),parsedBody:r?JSON.stringify(r):"",rawBody:l??""});let a=new Error(`HTTP error! status: ${n.status}, detail: ${o}`);throw a.statusCode=n.status,a.errorDetail=o,a}let i={};try{i=await n.json()}catch{s.logWarning("Failed to parse JSON response from missing-dependencies endpoint, using default",{status:String(n.status)})}return{status:typeof i.status=="number"?i.status:1,missing_files:Array.isArray(i.missing_files)?i.missing_files:[]}}catch(t){throw s.logError("Error calling missing-dependencies endpoint",t),t}}connectWebSocket(e,t,n,i){return new Promise((c,o)=>{try{W.cleanupStaleConnections();let r=W.getConnectionForUser(e);if(r){s.logInfo("Reusing existing WebSocket connection",{userId:e});let g=h=>{try{let m=JSON.parse(h.toString());t(m)}catch(m){s.logError("Error parsing WebSocket message",m)}},d=h=>{s.logError("WebSocket connection error",h,{userId:e}),n==null||n(h)},u=(h,m)=>{let p=m.toString();s.logInfo("WebSocket connection closed",{userId:e,code:String(h),reason:p}),i==null||i(h,p)};r.removeAllListeners("message"),r.removeAllListeners("error"),r.removeAllListeners("close"),r.on("message",g),r.on("error",d),r.on("close",u),c(r);return}let l=`${this.wsBaseUrl}/v2/ws/${e}`;if(s.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.`);s.logError("Invalid WebSocket URL format",g,{wsUrl:l,baseUrl:this.baseUrl,wsBaseUrl:this.wsBaseUrl}),o(g);return}let a=new ce(l);a.on("open",()=>{s.logInfo("WebSocket connection opened",{userId:e}),W.registerConnection(a,e),c(a)}),a.on("message",g=>{try{let d=JSON.parse(g.toString());t(d)}catch(d){s.logError("Error parsing WebSocket message",d)}}),a.on("error",g=>{let d=g instanceof Error?g.message:String(g),u=new Error(`WebSocket connection failed: ${d}. URL: ${l}. Please verify that the backend service is running and accessible.`);s.logError("WebSocket error",u,{userId:e,wsUrl:l}),n==null||n(u),o(u)}),a.on("close",(g,d)=>{let u=d.toString();g!==1e3&&s.logWarning("WebSocket closed unexpectedly",{userId:e,code:String(g),reason:u,wsUrl:l}),i==null||i(g,u)})}catch(r){let l=r instanceof Error?r.message:String(r),a=new Error(`Failed to create WebSocket connection: ${l}. Please check your network connection and backend service availability.`);s.logError("Error creating WebSocket connection",a,{userId:e,wsBaseUrl:this.wsBaseUrl}),o(a)}})}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 i="Unknown error";try{let c=await t.json();i=c.detail||JSON.stringify(c)}catch{i=await t.text()||`HTTP ${t.status}`}throw s.logError(`HTTP error! status: ${t.status}, detail: ${i}`),t.status===401?new Error(i||"Authentication required. Please provide a valid API key."):t.status===404?new Error(i||"User not found."):new Error(i||`HTTP error! status: ${t.status}`)}let n=await t.json();return s.logInfo("User retrieved from secure route",{user_id:n.userid}),n}catch(e){throw s.logError("Error fetching user from secure route",e),e}}};import K from"ws";var N=class{backendApiService;constructor(e){this.backendApiService=e}async submitAndWaitForResults(e,t){let n;try{s.logInfo("Step 1: Establishing WebSocket connection BEFORE submitting review",{user_id:e.user_id});let c=await new Promise((d,u)=>{this.backendApiService.connectWebSocket(e.user_id,()=>{},h=>{s.logError("WebSocket connection failed before submit",h,{user_id:e.user_id}),u(h)},()=>{}).then(h=>{if(h.readyState===K.OPEN)n=h,s.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}`);s.logError("\u274C WebSocket not in OPEN state",m,{user_id:e.user_id,readyState:String(h.readyState)}),u(m)}}).catch(u)});s.logInfo("\u2705 WebSocket connection confirmed OPEN, proceeding with HTTP call",{user_id:e.user_id});let o=e.file_changes.length,r=this.collectResultsViaEstablishedWebSocket(c,e.user_id,1200*1e3,t,o);r.catch(()=>{}),s.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});s.logInfo("Commit review submitted",{analysisId:l.analysisId||"none"});let a=await r;if(!a||a.length===0)return s.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=a.filter(d=>d&&d.file_name&&d.analysis!==void 0&&d.analysis!==null);return g.length===0?(s.logWarning("All analysis results were invalid or empty",{user_id:e.user_id,commit_id:e.commit_id,totalResults:String(a.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:a}):(s.logInfo("Analysis results received successfully",{user_id:e.user_id,commit_id:e.commit_id,resultCount:String(g.length),totalResults:String(a.length)}),{success:!0,analysisId:l.analysisId,message:l.message,results:g})}catch(i){return s.logError("Error in commit review flow",i),i instanceof Error&&i.message.includes("status: 401")&&n&&(s.logInfo("Closing WebSocket connection due to 401 Unauthorized error"),n.close()),{success:!1,error:i instanceof Error?i.message:String(i)}}}async collectResultsViaEstablishedWebSocket(e,t,n=1200*1e3,i,c){let o=[],r=null,l,a,g=!1,d;if(e.readyState!==K.OPEN)throw new Error(`WebSocket must be in OPEN state. Current state: ${e.readyState}`);let u=new Promise((h,m)=>{d=setTimeout(()=>{g||(r="Analysis timeout: The analysis took too long to complete. Please try again with fewer files or contact support if the issue persists.",s.logError("WebSocket timeout",new Error(r),{user_id:t,timeoutMs:String(n)}),e.close(),m(new Error(r)))},n)});return new Promise((h,m)=>{let p=I=>{var _;try{let v=JSON.parse(I.toString());if(d&&(clearTimeout(d),d=setTimeout(()=>{g||(r="Analysis timeout: The analysis is taking longer than expected. Please try again or contact support.",s.logError("WebSocket timeout after receiving initial results",new Error(r),{user_id:t,resultsReceived:String(o.length)}),e.close(),k(),m(new Error(r)))},n)),v.status_code!==200&&v.status_code!==void 0){l=v.status_code,a=v.error_message||"Unknown error from backend",r=a||"Unknown error from backend",g=!0,d&&clearTimeout(d),s.logError("WebSocket error message received",new Error(a),{status_code:String(l),user_id:t,raw_message:JSON.stringify(v)}),k(),m(new Error(r||"Unknown error from backend"));return}if(v.content&&v.content.analysis_type==="commit_review"){let w=((_=v.content.file_name)==null?void 0:_.replace(/\\/g,"/"))||"",C=v.content.analysis;w&&C!==void 0&&C!==null&&(o.push({analysis:C,language:v.content.language,file_name:w,analysisId:v.analysis_id??0}),s.logInfo("Analysis result received for file",{user_id:t,file_name:w,result_count:String(o.length)})),(v.content.is_complete||v.content.status==="complete")&&(g=!0,d&&clearTimeout(d),s.logInfo("Analysis marked as complete",{user_id:t,total_results:String(o.length)}),k(),h(o))}}catch(v){s.logError("Error parsing WebSocket message",v)}},S=I=>{d&&clearTimeout(d),r=I.message,s.logError("WebSocket connection error",I,{user_id:t}),k(),m(I)},E=(I,_)=>{d&&clearTimeout(d);let v=_.toString();I!==1e3&&!g?(r=`Connection closed unexpectedly: ${v}`,s.logWarning("WebSocket closed unexpectedly",{code:String(I),reason:v,user_id:t,isComplete:String(g),results_received:String(o.length)}),k(),m(new Error(r))):I===1e3&&!g?(s.logInfo("WebSocket closed normally, treating as completion",{user_id:t,results_received:String(o.length)}),g=!0,k(),h(o)):(k(),h(o))},k=()=>{e.removeListener("message",p),e.removeListener("error",S),e.removeListener("close",E)};e.on("message",p),e.on("error",S),e.on("close",E),u.catch(()=>{})})}async collectResultsViaWebSocket(e,t,n=1200*1e3){let i=[],c=null,o,r,l=!1,a,g,d=new Promise((u,h)=>{a=setTimeout(()=>{l||(c="Analysis timeout: The analysis took too long to complete. Please try again with fewer files or contact support if the issue persists.",s.logError("WebSocket timeout",new Error(c),{user_id:e,timeoutMs:String(n)}),g&&g.close(),h(new Error(c)))},n)});try{await Promise.race([new Promise((u,h)=>{this.backendApiService.connectWebSocket(e,m=>{var p;if(a&&(clearTimeout(a),a=setTimeout(()=>{l||(c="Analysis timeout: The analysis is taking longer than expected. Please try again or contact support.",s.logError("WebSocket timeout after receiving initial results",new Error(c),{user_id:e,resultsReceived:String(i.length)}),g&&g.close(),h(new Error(c)))},n)),m.status_code!==200&&m.status_code!==void 0){o=m.status_code,r=m.error_message||"Unknown error from backend",c=r,l=!0,a&&clearTimeout(a),s.logError("WebSocket error message received",new Error(r),{status_code:String(o),user_id:e,raw_message:JSON.stringify(m)}),u();return}if(m.content)if(m.content.analysis_type==="commit_review"){let E=((p=m.content.file_name)==null?void 0:p.replace(/\\/g,"/"))||"",k=m.content.analysis;E&&k!==void 0&&k!==null?(i.push({analysis:k,language:m.content.language,file_name:E,analysisId:m.analysis_id??0}),s.logInfo("Analysis result received for file",{user_id:e,file_name:E,result_count:String(i.length)})):s.logWarning("Received invalid analysis result (missing file_name or analysis)",{user_id:e,has_file_name:String(!!E),has_analysis:String(k!=null)}),(m.content.is_complete||m.content.status==="complete")&&(l=!0,a&&clearTimeout(a),s.logInfo("Analysis marked as complete",{user_id:e,total_results:String(i.length)}),u())}else s.logInfo("Received non-commit-review WebSocket message",{user_id:e,analysis_type:m.content.analysis_type||"unknown"});else s.logInfo("Received WebSocket message with null content",{user_id:e,results_so_far:String(i.length)})},m=>{a&&clearTimeout(a),c=m.message,s.logError("WebSocket connection error",m,{user_id:e}),h(m)},(m,p)=>{a&&clearTimeout(a),m!==1e3&&!l?(c=`Connection closed unexpectedly: ${p}`,s.logWarning("WebSocket closed unexpectedly",{code:String(m),reason:p.toString(),user_id:e,isComplete:String(l),results_received:String(i.length)})):m===1e3&&!l&&(s.logInfo("WebSocket closed normally, treating as completion",{user_id:e,results_received:String(i.length)}),l=!0),u()}).then(m=>{g=m,t&&t(m)}).catch(h)}),d])}catch(u){throw a&&clearTimeout(a),u}if(c){let u=new Error(c);throw o!==void 0&&(u.statusCode=o),r&&(u.errorDetail=r),u}return s.logInfo("WebSocket collection completed",{user_id:e,results_count:String(i.length),isComplete:String(l)}),i}};function x(f,e,t){let n=f instanceof Error?f.message:String(f),i=n,c=e,o=t;if(f&&typeof f=="object"&&("statusCode"in f&&!c&&(c=f.statusCode),"errorDetail"in f&&!o&&(o=f.errorDetail)),!c){let d=n.match(/status:\s*(\d+)/i);c=d?parseInt(d[1],10):void 0}let r=o||n,l="internal_error",a=r,g=!1;return c===401||r.includes("Authentication")||r.includes("API key")||r.includes("authentication required")?(l="authentication_error",a=`API key missing. Authentication is required.
|
|
10
|
+
|
|
11
|
+
To get an API key:
|
|
12
|
+
1. Visit https://codesherlock.ai/login and sign up or log in.
|
|
13
|
+
2. Navigate to the MCP API Keys page: https://codesherlock.ai/codesherlock-mcp-server/mcp/api/key.
|
|
14
|
+
3. Generate or copy your API key.
|
|
15
|
+
|
|
16
|
+
Usage instructions:
|
|
17
|
+
See the MCP integration guide at https://docs.codesherlock.ai/codesherlock-mcp-server/mcp/setup/guide.
|
|
18
|
+
|
|
19
|
+
If you followed these steps and still face issues, contact support at support@codesherlock.ai.`,g=!1):c===404||r.includes("not found")||r.includes("User not found")||r.includes("WebSocket not active")?(l="not_found_error",r.includes("User not found")?a="User not found. Please verify your authentication.":r.includes("WebSocket not active")||r.includes("WebSocket not active for user")?a="WebSocket connection not active. The backend service may be unavailable. Please try again.":a="Resource not found.",g=!1):c===422||r.includes("Invalid")||r.includes("validation")||r.includes("must contain")||r.includes("must be")?(l="validation_error",r.includes("files.json")||r.includes("Zip file must contain")?a="Invalid file format. The zip file must contain a valid 'files.json' file.":r.includes("not valid UTF-8")||r.includes("UTF-8")?a="Invalid file encoding. The files.json must be valid UTF-8 text.":r.includes("Invalid JSON format")||r.includes("JSONDecodeError")?a="Invalid JSON format in files.json. Please ensure the file contains valid JSON.":r.includes("must be a list")||r.includes("must be a dictionary")?a="Invalid file structure. The files.json must contain a list of file objects with 'filename', 'status', and 'new_content' fields.":r.includes("No valid code files found")||r.includes("No valid code files")?a="No valid code files found in the commit. Please ensure your changes include code files that can be analyzed.":r.includes("No analysis results received")||r.includes("no analysis results")?a="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.":r.includes("no valid results were generated")||r.includes("All analysis results were invalid")?a="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.":r.includes("Analysis timeout")||r.includes("took too long")?a=r:r.includes("Invalid factor")||r.includes("valid factor")?a="Invalid analysis factor. Please use one of: power_analysis, owasp, cwe_mitre, or cwe_kev.":r.includes("GitHub App is not installed")||r.includes("not installed in this repository")?a="CodeSherlock GitHub App is not installed in this repository. Commit review would be allowed once it is installed in this repository.":r.includes("No Git repository information")||r.includes("No Git repository information found")?a="No Git repository information found. To run commit review, please provide valid organization and repository names.":r.includes("organization details")||r.includes("retrieving your organization")?a="We ran into an issue while retrieving your organization details. Please try again later or contact support@codesherlock.ai.":r.includes("usage details")||r.includes("retrieving your usage")?a="We ran into an issue while retrieving your usage details. Please try again later or contact support@codesherlock.ai.":r.includes("Missing keys")||r.includes("required_keys")?a="Invalid file structure. Each file in files.json must have 'filename', 'status', and 'new_content' fields.":a=r,g=!1):r.includes("Git")||r.includes("git")||r.includes("repository")||r.includes("commit")?(l="git_error",a=`Git error: ${r}`,g=!0):r.includes("WebSocket")||r.includes("websocket")||r.includes("Connection")||r.includes("connection closed")?(l="websocket_error",r.includes("Connection closed unexpectedly")?a="WebSocket connection closed unexpectedly. Please try again.":a="WebSocket connection error. Please try again.",g=!0):c===500||r.includes("Internal server error")||r.includes("internal error")||r.includes("Internal error")||r.includes("An error occurred")||r.includes("Failed to generate")?(l="internal_error",r.includes("Failed to generate commit review analysis")||r.includes("commit review pipeline returned no result")?a="Failed to generate commit review analysis. The analysis pipeline encountered an error.":r.includes("An internal error occurred before running")||r.includes("error occurred during pre-checks")?a="An internal error occurred before running the analysis. Please try again later or contact support.":r.includes("error occurred during commit review")?a="An error occurred during commit review analysis. Please try again later or contact support.":a="An internal server error occurred. Please try again later or contact support@codesherlock.ai.",g=!0):r.includes("usage")||r.includes("token")||r.includes("limit")||r.includes("trial")||r.includes("quota")?(l="usage_limit_error",a=r,g=!1):c&&c>=400&&c<500?(l="api_error",a=r||`Client error (${c}). Please check your request and try again.`,g=!1):c&&c>=500&&(l="api_error",a="Backend service error. Please try again later or contact support.",g=!0),{statusCode:c,errorType:l,userMessage:a,technicalDetails:i,retryable:g}}import de from"fs";async function H(f,e,t){var g;let{uncommitted:n,directory:i,factor:c}=f,{gitService:o,commitReviewService:r,backendApiService:l}=e,a;try{await t.send("start",0,10);let d=c==="power"?"power_analysis":c;if(s.logInfo("=== runAnalysis invoked ===",{uncommitted:String(n),directory:i,factor:d}),!de.existsSync(i)){let y=x(new Error("Directory does not exist"));return{success:!1,error:y.userMessage,errorType:y.errorType,statusCode:404,errorDetails:{userMessage:y.userMessage,technicalDetails:`Directory not found: ${i}`,retryable:!1}}}await t.send("git_start",1,10);let u;try{u=await o.analyzeGitChanges(n,i),s.logInfo("File changes received",{fileCount:String(u.length)}),await t.send("git_complete",2,10,{files:u.length})}catch(y){let w=x(y);return s.logError("Failed to analyze git changes",y,{directory:i,uncommitted:String(n)}),{success:!1,error:w.userMessage,errorType:w.errorType,statusCode:w.statusCode,errorDetails:{userMessage:w.userMessage,technicalDetails:w.technicalDetails,retryable:w.retryable}}}if(!u||u.length===0)return{success:!1,error:"No file changes found.",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}};let h=20;if(u.length>h)return s.logWarning("File limit exceeded",{fileCount:String(u.length),maxLimit:String(h)}),{success:!1,error:`Too many files. Found ${u.length}, maximum is ${h}.`,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:`Too many files to analyze. Found ${u.length} files, but the maximum allowed is ${h}. Please reduce the number of files in your commit or uncommitted changes.`,technicalDetails:`File count ${u.length} exceeds limit of ${h}`,retryable:!1}};let m=[];for(let y=0;y<u.length;y++){let w=u[y];if(!w||typeof w!="object"){m.push(`File at index ${y} is not a valid object`);continue}(!w.filename||typeof w.filename!="string")&&m.push(`File at index ${y} is missing or has invalid 'filename' field`),(!w.status||typeof w.status!="string")&&m.push(`File at index ${y} is missing or has invalid 'status' field`),w.new_content===void 0&&m.push(`File at index ${y} is missing 'new_content' field`)}await t.send("deps_check",3,10);try{let y=await o.getRepoStructure(i),w=await l.identifyMissingDependencies({repo_structure:y,files:u.map(C=>({filename:C.filename,status:C.status,new_content:C.new_content}))});if(w.status===0&&Array.isArray(w.missing_files)&&w.missing_files.length>0){let C=n?void 0:"HEAD";for(let D of w.missing_files)try{let A=await o.getFileContent(i,D,C);u.push({filename:D,status:"context",new_content:A,patch:""})}catch(A){s.logWarning("Failed to load missing dependency file",{filename:D,error:String(A)})}s.logInfo("Missing dependencies appended",{totalFilesAfter:String(u.length)})}}catch(y){s.logWarning("Failed to check missing dependencies. Proceeding without additional context.",{error:String(y)})}if(m.length>0){let y=`Invalid file changes structure: ${m.join("; ")}`;return s.logError("Invalid file changes structure",new Error(y),{invalidFilesCount:String(m.length)}),{success:!1,error:"Invalid file structure.",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}}}let p;try{p=await o.getCurrentCommitHash(i)}catch{p="UNCOMMITTED",s.logWarning("Could not get commit hash, using fallback",{commitId:p})}let S;try{S=await l.getUserFromSecureRoute()}catch(y){let w=x(y);return s.logError("Failed to fetch user from secure route",y),{success:!1,error:w.userMessage,errorType:w.errorType,statusCode:w.statusCode,status:w.errorType,errorDetails:{userMessage:w.userMessage,technicalDetails:w.technicalDetails,retryable:w.retryable}}}a=S.userid;let E=S.username,k=i.split(/[/\\]/).pop()||"unknown-repo",I=await o.getOrganizationName(i);if(!I){let y=i.split(/[/\\]/),w=y.findIndex(C=>C.toLowerCase()==="github");I=w>=0&&y[w+1]?y[w+1]:void 0}let _;try{await t.send("smart_analysis_intro",4,10),t.startTipTimer(),_=await r.submitAndWaitForResults({factor:d,user_id:a,repo_name:k,commit_id:p,username:E,file_changes:u,organization_name:I},t)}finally{t.stopTipTimer()}if(!_.success){let y=x(new Error(_.error||"Unknown error"));return s.logError("Commit review failed",new Error(_.error||"Unknown error"),{user_id:a,commit_id:p}),{success:!1,error:_.error||y.userMessage,errorType:y.errorType,statusCode:y.statusCode,errorDetails:{userMessage:y.userMessage,technicalDetails:_.error||y.technicalDetails,retryable:y.retryable}}}let v=(_.results??[]).reduce((y,w)=>{if(!w||typeof w!="object")return y;let C=Array.isArray(w.analysis)?w.analysis:[];return y+C.reduce((D,A)=>{var j;return D+(((j=A==null?void 0:A.issue_items)==null?void 0:j.length)||0)},0)},0);return await t.send("complete",10,10,{issues:v}),s.logInfo("Analysis complete",{user_id:a,analysisId:_.analysisId||"none",resultCount:String(((g=_.results)==null?void 0:g.length)||0),issueCount:String(v)}),{success:!0,analysisId:_.analysisId,results:_.results,message:_.message}}catch(d){let u=d instanceof Error?d.message:String(d),h=u.includes("timeout")||u.includes("timed out"),m=u.includes("aborted")||u.includes("cancelled")||u.includes("Tool call was aborted");if(s.logError("Unexpected error in runAnalysis",d,{directory:i,uncommitted:String(n),factor:c,isAborted:String(m||h)}),(m||h)&&a){let S=a;await W.closeConnectionForUser(S).catch(E=>{s.logWarning("Error during WebSocket cleanup",{error:E instanceof Error?E.message:String(E),user_id:S})})}let p=x(d);return{success:!1,error:p.userMessage,errorType:p.errorType,statusCode:p.statusCode||500,errorDetails:{userMessage:p.userMessage,technicalDetails:p.technicalDetails,retryable:p.retryable}}}}function G(f,e){try{switch(f){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"deps_check":return"Checking for additional context files...";case"smart_analysis_intro":return"Performing smart analysis. In the meantime, enjoy these coding tips!";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`Coding tip:${(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 O from"fs";import*as q from"path";var M=class{filePath;constructor(e){this.filePath=e??q.join(process.cwd(),"src","utils","cs-dev-mongo.coding_tips.json")}getTips(){try{if(O.existsSync(this.filePath)){let e=O.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."]}}},U=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,i=e();if(n>=15e3&&i.length>0){let c;do c=Math.floor(Math.random()*i.length);while(this.seenTips.has(c)&&this.seenTips.size<i.length);this.seenTips.add(c),this.seenTips.size>=i.length&&this.seenTips.clear(),this.lastMessageTimestamp=Date.now(),Promise.resolve(t(i[c])).catch(o=>{s.logWarning("TipScheduler: onTip() failed",{error:o instanceof Error?o.message:String(o),tipIndex:String(c)})})}},1e3)}stop(){this.tipInterval&&(clearInterval(this.tipInterval),this.tipInterval=null)}resetTimestamp(){this.lastMessageTimestamp=Date.now()}};import fe from"chalk";var z=class{scheduler=new U;tipsProvider=new M;formatCliMessage(e,t){if(e==="coding_tip"){let n=(t==null?void 0:t.tip)||"Write clean, maintainable code.";return`${fe.green("Coding tip")}: ${n}`}return G(e,t)}async send(e,t,n,i){this.scheduler.resetTimestamp();let c=this.formatCliMessage(e,i);process.stderr.write(`${c}
|
|
20
|
+
`)}startTipTimer(){let e=this.tipsProvider.getTips();this.scheduler.start(()=>e,t=>{let n=this.formatCliMessage("coding_tip",{tip:t});process.stderr.write(`${n}
|
|
21
|
+
`)})}stopTipTimer(){this.scheduler.stop()}};import b from"chalk";function me(f){switch((f??"").toLowerCase()){case"critical":return b.redBright.bold;case"high":return b.red;case"medium":return b.yellow;case"low":return b.cyan;default:return b.white}}function pe(f){let e=String(f??"UNKNOWN").toUpperCase();return me(e)(e.padEnd(8))}var he=b.gray("\u2500".repeat(60)),R=b.gray("\u2550".repeat(60));function V(f){return(f==null?"":String(f)).split(`
|
|
22
|
+
`).map(t=>b.gray(" \u2502 ")+b.green(t)).join(`
|
|
23
|
+
`)}function ye(f,e){let t=[];return t.push(` ${pe(f.severity)} ${f.issue}`),f.start_line&&f.end_line&&t.push(` ${b.gray("Lines:")} ${f.start_line}\u2013${f.end_line}`),f.issue_code_snippet&&(t.push(b.gray(" Problem:")),t.push(V(f.issue_code_snippet))),t.push(` ${b.gray("Solution:")} ${f.solution}`),f.solution_code_snippet&&(t.push(b.gray(" Fix:")),t.push(V(f.solution_code_snippet))),t.join(`
|
|
24
|
+
`)}function X(f){process.stdout.write(R+`
|
|
25
|
+
`),process.stdout.write(b.bold.white(" CodeSherlock Analysis Results")+`
|
|
26
|
+
`),process.stdout.write(R+`
|
|
27
|
+
|
|
28
|
+
`);let e=0;for(let n of f)try{process.stdout.write(b.bold.blue(`File: ${n.file_name}`)+`
|
|
29
|
+
`),process.stdout.write(he+`
|
|
30
|
+
`);let i=Array.isArray(n.analysis)?n.analysis:[];for(let c of i){if(!c)continue;process.stdout.write(b.bold.white(`
|
|
31
|
+
${c.characteristic}`)+`
|
|
32
|
+
`),c.description_of_characteristic&&process.stdout.write(b.gray(` ${c.description_of_characteristic}`)+`
|
|
33
|
+
`);let o=c.issue_items??[];for(let r of o)process.stdout.write(`
|
|
34
|
+
`+ye(r,n.language??"")+`
|
|
35
|
+
`),e++}process.stdout.write(`
|
|
36
|
+
`)}catch(i){process.stderr.write(b.red(`Error rendering analysis for file ${n.file_name}: ${String(i)}
|
|
37
|
+
`))}process.stdout.write(R+`
|
|
38
|
+
`);let t=e===1?" 1 issue found.":` ${e} issues found.`;process.stdout.write(b.bold(t)+`
|
|
39
|
+
`),process.stdout.write(R+`
|
|
40
|
+
`)}function Y(){process.stdout.write(R+`
|
|
41
|
+
`),process.stdout.write(b.bold.white(" CodeSherlock Analysis Results")+`
|
|
42
|
+
`),process.stdout.write(R+`
|
|
43
|
+
|
|
44
|
+
`),process.stdout.write(b.green(" No issues found.")+`
|
|
45
|
+
|
|
46
|
+
`),process.stdout.write(R+`
|
|
47
|
+
`)}import Z from"keytar";var Q="codesherlock-cli",ee="default-api-key";async function te(f){await Z.setPassword(Q,ee,f)}async function re(){return await Z.getPassword(Q,ee)??void 0}var _e="InstrumentationKey=9a687b23-0384-4345-abaa-4e7065b0d103;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=9e40bff2-07af-4326-be55-f5dc98e9e9c3";async function se(f){try{await te(f),process.stdout.write(`API key saved securely in the OS keychain
|
|
48
|
+
`)}catch{process.stderr.write(`Error: Unable to securely save API key in the OS keychain.
|
|
49
|
+
Please try again after enabling OS keychain access on this machine.
|
|
50
|
+
`),process.exit(1)}}async function ie(f,e){var u;let t=ne(f.directory??process.cwd()),n=Se(ne(ve(e[1]??""),"../.."),".env");be(n)&&we.config({path:n,quiet:!0});let i;try{i=await re()}catch{i=void 0}let c=f.apiKey||i;if(!c){process.stderr.write(`Error: No API key found.
|
|
51
|
+
To get an API key:
|
|
52
|
+
1. Visit https://codesherlock.ai/login and sign up or log in.
|
|
53
|
+
2. Navigate to the MCP API Keys page: https://codesherlock.ai/codesherlock-mcp-server/mcp/api/key.
|
|
54
|
+
3. Generate or copy your API key.
|
|
55
|
+
|
|
56
|
+
Usage instructions:
|
|
57
|
+
See the MCP integration guide at https://docs.codesherlock.ai/codesherlock-mcp-server/mcp/setup/guide.
|
|
58
|
+
|
|
59
|
+
If you followed these steps and still face issues, contact support at support@codesherlock.ai.`),process.exit(1);return}let o=process.env.BACKEND_API_URL;if(!o){process.stderr.write(`Error: BACKEND_API_URL is not set.
|
|
60
|
+
Add BACKEND_API_URL=https://api.codesherlock.ai to the .env file.
|
|
61
|
+
`),process.exit(1);return}try{await s.initialize(_e)}catch{}let r=new $,l=new F(o,c),a=new N(l),g=new z,d=await H({uncommitted:f.uncommitted,directory:t,factor:f.factor},{gitService:r,commitReviewService:a,backendApiService:l},g);if(await s.flush().catch(()=>{}),!d.success){process.stderr.write(`Error: ${((u=d.errorDetails)==null?void 0:u.userMessage)??d.error}
|
|
62
|
+
`),process.exit(1);return}f.output==="json"?process.stdout.write(JSON.stringify(d.results??[],null,2)+`
|
|
63
|
+
`):d.results&&d.results.length>0?X(d.results):Y()}async function Ie(f){let e=new ke;e.name("codesherlock").description("AI-powered code analysis for security, quality, and compliance").version(J.version).exitOverride(),e.command("auth <api-key>").description("Authenticate with your CodeSherlock API key").action(async t=>{await se(t)}),e.command("analyze").description("Analyze code changes in a git repository").option("--uncommitted","Analyze staged/unstaged changes instead of last commit",!1).option("--directory <path>","Path to the git repository (default: current directory)").option("--factor <name>","Analysis focus: power_analysis, owasp, cwe_mitre, cwe_kev","power_analysis").option("--api-key <key>","API key for this run ").option("--output <format>","Output format: markdown or json","markdown").action(async t=>{await ie(t,f)}),await e.parseAsync(f)}process.env.NODE_ENV!=="test"&&Ie(process.argv).catch(f=>{if(f instanceof Ee){process.exit(f.exitCode??0);return}process.stderr.write(`Fatal error: ${f instanceof Error?f.message:String(f)}
|
|
64
|
+
`),process.exit(1)});export{Ie as run};
|
package/build/index.js
CHANGED
|
@@ -1,41 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{McpServer as
|
|
3
|
-
`)}catch(t){throw
|
|
4
|
-
`).length)}),
|
|
5
|
-
`).length)}),
|
|
6
|
-
`),c=
|
|
7
|
-
`)}catch(
|
|
8
|
-
`).filter(Boolean).map(
|
|
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 ce from"ws";import le from"jszip";import{File as ge,Blob as ue}from"node:buffer";import R from"ws";var j=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===R.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===R.OPEN||n.readyState===R.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===R.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===R.OPEN||t.readyState===R.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===R.CLOSED||t.readyState===R.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)})}},T=new j;var z=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 le;a.file("files.json",e.files_json);let o=await a.generateAsync({type:"nodebuffer"}),i=new ue([o],{type:"application/zip"});n.append("files_zip",new ge([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 w=await s.text();g=w,c=w||`HTTP ${s.status}`}let f=d?JSON.stringify(d):"",y=g??"";r.logError("Backend commit-review HTTP error response",new Error(c),{url:t,status:String(s.status),parsedBody:f,rawBody:y});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}}async identifyMissingDependencies(e){try{let t=`${this.baseUrl}/v2/missing-dependencies`;if(!this.apiKey)throw new Error("API key is required to call the missing-dependencies endpoint");let n=await this.fetchWithRetry(t,{method:"POST",headers:{"Content-Type":"application/json","X-CS-MCP-API-Key":this.apiKey},body:JSON.stringify(e)});if(!n.ok){let i="Unknown error",s,l;try{s=await n.json(),i=s.detail||s.error||s.message||JSON.stringify(s)}catch{let g=await n.text();l=g,i=g||`HTTP ${n.status}`}r.logError("Backend missing-dependencies HTTP error response",new Error(i),{url:t,status:String(n.status),parsedBody:s?JSON.stringify(s):"",rawBody:l??""});let c=new Error(`HTTP error! status: ${n.status}, detail: ${i}`);throw c.statusCode=n.status,c.errorDetail=i,c}let a={};try{a=await n.json()}catch{r.logWarning("Failed to parse JSON response from missing-dependencies endpoint, using default",{status:String(n.status)})}return{status:typeof a.status=="number"?a.status:1,missing_files:Array.isArray(a.missing_files)?a.missing_files:[]}}catch(t){throw r.logError("Error calling missing-dependencies endpoint",t),t}}connectWebSocket(e,t,n,a){return new Promise((o,i)=>{try{T.cleanupStaleConnections();let s=T.getConnectionForUser(e);if(s){r.logInfo("Reusing existing WebSocket connection",{userId:e});let g=y=>{try{let m=JSON.parse(y.toString());t(m)}catch(m){r.logError("Error parsing WebSocket message",m)}},d=y=>{r.logError("WebSocket connection error",y,{userId:e}),n==null||n(y)},f=(y,m)=>{let w=m.toString();r.logInfo("WebSocket connection closed",{userId:e,code:String(y),reason:w}),a==null||a(y,w)};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 ce(l);c.on("open",()=>{r.logInfo("WebSocket connection opened",{userId:e}),T.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 Z 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,()=>{},y=>{r.logError("WebSocket connection failed before submit",y,{user_id:e.user_id}),f(y)},()=>{}).then(y=>{if(y.readyState===Z.OPEN)n=y,r.logInfo("\u2705 WebSocket connection fully established and verified OPEN",{user_id:e.user_id,readyState:String(y.readyState)}),d(y);else{let m=new Error(`WebSocket connection not fully open. ReadyState: ${y.readyState}`);r.logError("\u274C WebSocket not in OPEN state",m,{user_id:e.user_id,readyState:String(y.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!==Z.OPEN)throw new Error(`WebSocket must be in OPEN state. Current state: ${e.readyState}`);let f=new Promise((y,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((y,m)=>{let w=_=>{var A;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 W=((A=v.content.file_name)==null?void 0:A.replace(/\\/g,"/"))||"",$=v.content.analysis;W&&$!==void 0&&$!==null&&(i.push({analysis:$,language:v.content.language,file_name:W,analysisId:v.analysis_id??0}),r.logInfo("Analysis result received for file",{user_id:t,file_name:W,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(),y(i))}}catch(v){r.logError("Error parsing WebSocket message",v)}},S=_=>{d&&clearTimeout(d),s=_.message,r.logError("WebSocket connection error",_,{user_id:t}),b(),m(_)},I=(_,A)=>{d&&clearTimeout(d);let v=A.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(),y(i)):(b(),y(i))},b=()=>{e.removeListener("message",w),e.removeListener("error",S),e.removeListener("close",I)};e.on("message",w),e.on("error",S),e.on("close",I),f.catch(()=>{})})}async collectResultsViaWebSocket(e,t,n=1200*1e3){let a=[],o=null,i,s,l=!1,c,g,d=new Promise((f,y)=>{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(),y(new Error(o)))},n)});try{await Promise.race([new Promise((f,y)=>{this.backendApiService.connectWebSocket(e,m=>{var w;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(),y(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 I=((w=m.content.file_name)==null?void 0:w.replace(/\\/g,"/"))||"",b=m.content.analysis;I&&b!==void 0&&b!==null?(a.push({analysis:b,language:m.content.language,file_name:I,analysisId:m.analysis_id??0}),r.logInfo("Analysis result received for file",{user_id:e,file_name:I,result_count:String(a.length)})):r.logWarning("Received invalid analysis result (missing file_name or analysis)",{user_id:e,has_file_name:String(!!I),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}),y(m)},(m,w)=>{c&&clearTimeout(c),m!==1e3&&!l?(o=`Connection closed unexpectedly: ${w}`,r.logWarning("WebSocket closed unexpectedly",{code:String(m),reason:w.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(y)}),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 Q={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")},ee={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 te(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}
|
|
2
|
+
import{McpServer as de}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as fe}from"@modelcontextprotocol/sdk/server/stdio.js";import{simpleGit as se}from"simple-git";import*as M from"applicationinsights";var O=class d{static instance;client=null;isInitialized=!1;constructor(){}static getInstance(){return d.instance||(d.instance=new d),d.instance}async initialize(e){if(!this.isInitialized){if(!e){this.isInitialized=!1,this.client=null;return}try{let t=new M.TelemetryClient(e);M.setup(e).setAutoDependencyCorrelation(!1).setAutoCollectRequests(!1).setAutoCollectPerformance(!1,!1).setAutoCollectExceptions(!0).setAutoCollectDependencies(!1).setAutoCollectConsole(!1).setSendLiveMetrics(!1),M.start(),this.client=t,this.isInitialized=!0}catch{this.isInitialized=!1,this.client=null}}}logInfo(e,t){this.client&&this.client.trackTrace({message:e,severity:1,properties:t})}logWarning(e,t){this.client&&this.client.trackTrace({message:e,severity:2,properties:t})}logError(e,t,r){this.client&&(t?this.client.trackException({exception:t,properties:{...r,customMessage:e}}):this.client.trackTrace({message:e,severity:3,properties:r}))}logEvent(e,t,r){this.client&&this.client.trackEvent({name:e,properties:t,measurements:r})}trackMetric(e,t,r){this.client&&this.client.trackMetric({name:e,value:t,properties:r})}trackDependency(e,t,r,i,a){this.client&&this.client.trackDependency({name:e,data:t,duration:r,success:i,dependencyTypeName:a||"HTTP"})}async flush(){this.client&&this.client.flush()}isEnabled(){return this.isInitialized&&this.client!==null}},n=O.getInstance();var F=class{getGitInstance(e){return se(e)}async getRepoStructure(e){try{let{readdir:t}=await import("fs/promises"),{join:r,relative:i}=await import("path"),a=new Set(["node_modules",".git",".venv",".idea",".vscode","__pycache__"]),o=[],s=async g=>{let c;try{c=await t(g,{withFileTypes:!0})}catch(l){n.logWarning("Failed to read directory while building repo structure",{directory:g,error:String(l)});return}for(let l of c){let f=r(g,l.name);if(l.isDirectory()){if(a.has(l.name))continue;await s(f)}else if(l.isFile()){let u=i(e,f).replace(/\\/g,"/");o.push(u)}}};return await s(e),n.logInfo("Repo structure built",{repoPath:e,fileCount:String(o.length)}),o}catch(t){throw n.logError("Error building repo structure",t,{repoPath:e}),t}}async getDiff(e,t){try{let r=this.getGitInstance(e);return!t||t==="HEAD"?(n.logInfo("Fetching diff for HEAD (unstaged changes)",{repoPath:e}),await r.diff()):t.includes("..")?(n.logInfo(`Fetching diff for range: ${t}`,{repoPath:e}),await r.diff([t])):(n.logInfo(`Fetching diff for commit: ${t}`,{repoPath:e}),await r.show([t]))}catch(r){throw n.logError("Error fetching diff",r,{repoPath:e,commitHash:t}),r}}async getUncommittedDiff(e){try{let t=this.getGitInstance(e),r=await t.diff(["--cached"]),i=await t.diff();return[r,i].filter(o=>o.trim()).join(`
|
|
3
|
+
`)}catch(t){throw n.logError("Error fetching uncommitted diff",t,{repoPath:e}),t}}async getFileContent(e,t,r){try{let i=this.getGitInstance(e);if(!r){let{readFileSync:o,existsSync:s}=await import("fs"),{join:g}=await import("path"),c=g(e,t);if(n.logInfo("Reading file from working tree",{filePath:t,fullPath:c,exists:String(s(c))}),!s(c))throw n.logWarning("File does not exist in working tree",{filePath:t,fullPath:c}),new Error(`File does not exist: ${c}`);let l=o(c,"utf-8");return n.logInfo("File read from working tree",{filePath:t,contentLength:String(l.length),lineCount:String(l.split(`
|
|
4
|
+
`).length)}),l}n.logInfo("Reading file from git commit",{filePath:t,ref:r,gitCommand:`${r}:${t}`});let a=await i.show([`${r}:${t}`]);return n.logInfo("File read from git commit",{filePath:t,ref:r,contentLength:String(a.length),lineCount:String(a.split(`
|
|
5
|
+
`).length)}),a}catch(i){throw n.logError("Error fetching file content",i,{repoPath:e,filePath:t,ref:r||"working tree",errorMessage:String(i)}),i}}async getChangedFiles(e,t){try{let r=this.getGitInstance(e);if(!t){let a=await r.status();return[...a.modified,...a.created,...a.not_added,...a.deleted,...a.renamed.map(o=>o.to)]}return(await r.diffSummary([`${t}^`,t])).files.map(a=>a.file)}catch(r){throw n.logError("Error fetching changed files",r,{repoPath:e,commitHash:t}),r}}async getCommitInfo(e,t){try{let i=await this.getGitInstance(e).log([t,"-1"]);if(!i.latest)throw new Error(`Commit ${t} not found`);let a=i.latest,o=await this.getChangedFiles(e,t);return{hash:a.hash,author:a.author_name,email:a.author_email,date:a.date,message:a.message,files:o}}catch(r){throw n.logError("Error fetching commit info",r,{repoPath:e,commitHash:t}),r}}async getRepoStatus(e){try{let r=await this.getGitInstance(e).status();return{branch:r.current||"HEAD",modified:r.modified,staged:[...r.created,...r.staged],untracked:r.not_added}}catch(t){throw n.logError("Error fetching repo status",t,{repoPath:e}),t}}async getCurrentCommitHash(e){try{return(await this.getGitInstance(e).revparse(["HEAD"])).trim()}catch(t){return n.logWarning("Unable to resolve current commit hash; using placeholder",{repoPath:e,error:String(t)}),"UNCOMMITTED"}}async getFileDiff(e,t,r,i){try{let a=this.getGitInstance(e);if(!r){if(i==="untracked"||i==="added"){n.logInfo(`Generating synthetic patch for ${i} file`,{filePath:t,repoPath:e,status:i});let g=await this.buildUntrackedPatch(e,t);return n.logInfo("Synthetic patch generated",{filePath:t,patchLength:String(g.length)}),g}n.logInfo("Getting diff for uncommitted file",{filePath:t,repoPath:e,step:"checking staged diff first"});try{let g=await a.diff(["--cached","--",t]);if(g.trim())return n.logInfo("Found staged diff",{filePath:t,diffLength:String(g.length)}),g;n.logInfo("Staged diff is empty",{filePath:t})}catch(g){n.logInfo(`Staged diff failed for ${t}, trying unstaged`,{error:String(g),filePath:t})}n.logInfo("Trying unstaged diff",{filePath:t});try{let g=await a.diff(["--",t]);if(g.trim())return n.logInfo("Found unstaged diff",{filePath:t,diffLength:String(g.length)}),g;n.logInfo("Unstaged diff is empty",{filePath:t})}catch(g){n.logInfo(`Unstaged diff failed for ${t}, checking if untracked`,{error:String(g),filePath:t})}n.logInfo(`No diff found for ${t}, generating synthetic patch for untracked/added file`,{filePath:t,repoPath:e});let s=await this.buildUntrackedPatch(e,t);return n.logInfo("Synthetic patch generated",{filePath:t,patchLength:String(s.length)}),s}n.logInfo("Getting diff for committed file",{filePath:t,commitHash:r,gitCommand:`${r}^..${r}`});let o=await a.diff([`${r}^..${r}`,"--",t]);return n.logInfo("Committed file diff retrieved",{filePath:t,commitHash:r,diffLength:String(o.length)}),o}catch(a){throw n.logError("Error fetching file diff",a,{repoPath:e,filePath:t,commitHash:r||"working tree",errorMessage:String(a)}),a}}async buildUntrackedPatch(e,t){try{n.logInfo("Building synthetic patch for untracked/added file",{filePath:t,repoPath:e});let{readFileSync:r,existsSync:i}=await import("fs"),{join:a}=await import("path"),o=a(e,t);if(n.logInfo("Checking file existence for patch",{filePath:t,fullPath:o,exists:String(i(o))}),!i(o))return n.logWarning(`File does not exist for untracked patch: ${t}`,{filePath:t,fullPath:o}),"";n.logInfo("Reading file content for synthetic patch",{filePath:t,fullPath:o});let s=r(o,"utf-8"),g=s.split(`
|
|
6
|
+
`),c=g.length;n.logInfo("File content read for patch generation",{filePath:t,contentLength:String(s.length),lineCount:String(c)});let l=[`diff --git a/${t} b/${t}`,"new file mode 100644","index 0000000..e69de29","--- /dev/null",`+++ b/${t}`];if(c>0){l.push(`@@ -0,0 +1,${c} @@`);for(let f of g)l.push(`+${f}`)}else l.push("@@ -0,0 +1,0 @@");return l.join(`
|
|
7
|
+
`)}catch(r){return n.logError("Error building untracked patch",r,{repoPath:e,filePath:t}),""}}async getChangedFilesWithStatus(e,t){var r,i,a,o,s,g,c;try{let l=this.getGitInstance(e);if(!t){n.logInfo("Getting git status for uncommitted changes",{repoPath:e});let u=await l.status();if(n.logInfo("Git status retrieved",{repoPath:e,totalFiles:String(((r=u.files)==null?void 0:r.length)||0),modifiedCount:String(((i=u.modified)==null?void 0:i.length)||0),createdCount:String(((a=u.created)==null?void 0:a.length)||0),notAddedCount:String(((o=u.not_added)==null?void 0:o.length)||0),deletedCount:String(((s=u.deleted)==null?void 0:s.length)||0),renamedCount:String(((g=u.renamed)==null?void 0:g.length)||0),stagedCount:String(((c=u.staged)==null?void 0:c.length)||0)}),!u.files||u.files.length===0)return n.logInfo("No changed files found in repository status",{repoPath:e}),[];n.logInfo("Raw git status files",{files:JSON.stringify(u.files.map(h=>({path:h.path,index:h.index,working_dir:h.working_dir})))});let p=u.files.map(h=>{if(!h.path)return n.logWarning("File entry missing path in status",{file:JSON.stringify(h)}),null;let S=this.determineFileStatus(h.index,h.working_dir);return n.logInfo("File status determined",{path:h.path,indexCode:h.index,workingDirCode:h.working_dir,determinedStatus:S}),{path:h.path,status:S}}).filter(h=>h!==null),m=p.reduce((h,S)=>(h[S.status]=(h[S.status]||0)+1,h),{});return n.logInfo("Files grouped by status",{repoPath:e,filesByStatus:JSON.stringify(m),totalFiles:String(p.length),fileList:JSON.stringify(p.map(h=>`${h.path} (${h.status})`))}),p}return(await l.raw(["show","--name-status","--format=",t])).trim().split(`
|
|
8
|
+
`).filter(Boolean).map(u=>{let p=u.split(" "),m=p[0][0],h=p[1],S="modified";return m==="A"?S="added":m==="D"?S="deleted":m==="R"&&(S="renamed",h=p[2]||p[1]),{status:S,path:h}})}catch(l){throw n.logError("Error fetching changed files with status",l,{repoPath:e,commitHash:t||"HEAD"}),l}}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{n.logInfo("Analyzing git changes",{directory:t,uncommitted:String(e),targetHash:e?"working tree":"HEAD"});let r=e?void 0:"HEAD",i=await this.getChangedFilesWithStatus(t,r);n.logInfo(`Found ${i.length} changed files`,{directory:t,uncommitted:String(e),files:JSON.stringify(i.map(o=>`${o.path} (${o.status})`))});let a=[];for(let o of i){n.logInfo("Processing file in analyzeGitChanges",{filename:o.path,status:o.status,isAdded:String(o.status==="added"),targetHash:r||"working tree"});let s="";if(o.status!=="deleted")try{n.logInfo("Getting content for file",{filename:o.path,status:o.status,source:r?`commit ${r}`:"working tree"}),s=await this.getFileContent(t,o.path,r),n.logInfo("Content retrieved successfully",{filename:o.path,status:o.status,contentLength:String(s.length)})}catch(l){n.logWarning(`Failed to read content for ${o.path}`,{error:String(l),status:o.status,filename:o.path})}else n.logInfo("Skipping content for deleted file",{filename:o.path});let g="";try{n.logInfo("Getting diff for file",{filename:o.path,status:o.status,targetHash:r||"working tree"}),g=await this.getFileDiff(t,o.path,r,o.status),n.logInfo("Diff retrieved successfully",{filename:o.path,status:o.status,patchLength:String(g.length)})}catch(l){n.logWarning(`Failed to get diff for ${o.path}`,{error:String(l),status:o.status,filename:o.path})}let c={filename:o.path,status:o.status,new_content:s,patch:g};n.logInfo("File processing complete",{filename:o.path,status:o.status,hasContent:String(s.length>0),hasPatch:String(g.length>0),contentLength:String(s.length),patchLength:String(g.length)}),a.push(c)}return n.logInfo("Git changes analysis complete",{directory:t,totalFiles:String(a.length),filesProcessed:JSON.stringify(a.map(o=>({filename:o.filename,status:o.status,hasContent:o.new_content.length>0,hasPatch:o.patch.length>0})))}),a}catch(r){throw n.logError("Error analyzing git changes",r,{directory:t}),r}}async getOrganizationName(e){try{let r=await this.getGitInstance(e).getRemotes(!0),i=r.find(g=>g.name==="origin")||r[0];if(!i||!i.refs.fetch)return;let a=i.refs.fetch,o=a.match(/(?:github\.com|gitlab\.com|bitbucket\.org)[:/]([^/]+)\//);if(o&&o[1])return o[1];let s=a.replace(/\.git$/,"").split(/[:/]/);return s.length>=2?s[s.length-2]:void 0}catch(t){n.logWarning("Failed to get organization name from git remote",{repoPath:e,error:String(t)});return}}async getFileChanges(e,t,r){try{if(!e&&!t)throw new Error("commitHash is required when uncommitted is false");n.logInfo("Getting file changes",{directory:r,uncommitted:String(e),commitHash:t||"none"});let i=e?void 0:t,a=await this.getChangedFilesWithStatus(r,i);n.logInfo(`Found ${a.length} changed files`);let o=[];for(let s of a){n.logInfo("Processing file change",{filename:s.path,status:s.status,targetHash:i||"working tree",isAdded:String(s.status==="added")});let g="";if(s.status!=="deleted")try{n.logInfo("Getting file content",{filename:s.path,status:s.status,source:i?`commit ${i}`:"working tree",isAdded:String(s.status==="added")}),g=await this.getFileContent(r,s.path,i),n.logInfo("File content retrieved",{filename:s.path,status:s.status,contentLength:String(g.length),lineCount:String(g.split(`
|
|
9
|
+
`).length)})}catch(l){n.logWarning(`Failed to read content for ${s.path}`,{error:String(l),status:s.status,targetHash:i||"working tree"})}else n.logInfo("Skipping content retrieval for deleted file",{filename:s.path});let c="";try{n.logInfo("Getting file diff",{filename:s.path,status:s.status,targetHash:i||"working tree",isAdded:String(s.status==="added")}),c=await this.getFileDiff(r,s.path,i,s.status),n.logInfo("File diff retrieved",{filename:s.path,status:s.status,patchLength:String(c.length),hasPatch:String(c.length>0)})}catch(l){n.logWarning(`Failed to get diff for ${s.path}`,{error:String(l),status:s.status,targetHash:i||"working tree"})}o.push({filename:s.path,status:s.status,new_content:g,patch:c}),n.logInfo("File change processed",{filename:s.path,status:s.status,hasContent:String(g.length>0),hasPatch:String(c.length>0),contentLength:String(g.length),patchLength:String(c.length)})}return n.logInfo(`Successfully processed ${o.length} file changes`),o}catch(i){throw n.logError("Error in getFileChanges",i,{directory:r,uncommitted:String(e),commitHash:t||"none"}),i}}};import ie from"ws";import oe from"jszip";import{File as ae,Blob as ce}from"node:buffer";import A from"ws";var B=class{connections=new Set;connectionsByUserId=new Map;isShuttingDown=!1;registerConnection(e,t){if(this.isShuttingDown){n.logWarning("Attempted to register WebSocket during shutdown",{userId:t||"unknown"});return}if(t){let i=this.connectionsByUserId.get(t);if(i&&i.readyState===A.OPEN){n.logWarning("Closing existing WebSocket connection for user before registering new one",{userId:t});try{i.close(1e3,"Replaced by new connection")}catch(a){n.logWarning("Error closing existing connection",{error:a instanceof Error?a.message:String(a),userId:t})}this.connections.delete(i),this.connectionsByUserId.delete(t)}this.connectionsByUserId.set(t,e)}this.connections.add(e),n.logInfo("WebSocket connection registered",{userId:t||"unknown",totalConnections:String(this.connections.size)});let r=()=>{this.unregisterConnection(e,t)};e.removeListener("close",r),e.on("close",r)}unregisterConnection(e,t){this.connections.delete(e)&&(t&&this.connectionsByUserId.get(t)===e&&this.connectionsByUserId.delete(t),n.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){n.logInfo("No active WebSocket connections to close");return}n.logInfo("Closing all WebSocket connections due to MCP client disconnection",{connectionCount:String(e)});let t=[];for(let r of this.connections)if(r.readyState===A.OPEN||r.readyState===A.CONNECTING){let i=new Promise(a=>{let o=setTimeout(()=>{try{r.terminate()}catch(s){n.logWarning("Error terminating WebSocket",{error:s instanceof Error?s.message:String(s)})}a()},2e3);r.once("close",()=>{clearTimeout(o),a()});try{r.close(1e3,"MCP client disconnected")}catch(s){n.logWarning("Error closing WebSocket",{error:s instanceof Error?s.message:String(s)}),clearTimeout(o),a()}});t.push(i)}else this.connections.delete(r);try{await Promise.race([Promise.all(t),new Promise(r=>{setTimeout(()=>{n.logWarning("Timeout waiting for WebSocket connections to close"),r()},5e3)})])}catch(r){n.logError("Error closing WebSocket connections",r)}this.connections.clear(),this.connectionsByUserId.clear(),this.isShuttingDown=!1,n.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===A.OPEN)return t;t&&(n.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){n.logInfo("Closing WebSocket connection for user",{userId:e});try{(t.readyState===A.OPEN||t.readyState===A.CONNECTING)&&t.close(1e3,"User connection closed")}catch(r){n.logWarning("Error closing connection for user",{error:r instanceof Error?r.message:String(r),userId:e})}this.connections.delete(t),this.connectionsByUserId.delete(e)}}cleanupStaleConnections(){let e=[];for(let t of this.connections)(t.readyState===A.CLOSED||t.readyState===A.CLOSING)&&e.push(t);for(let t of e){this.connections.delete(t);for(let[r,i]of this.connectionsByUserId.entries())if(i===t){this.connectionsByUserId.delete(r);break}}e.length>0&&n.logInfo("Cleaned up stale WebSocket connections",{count:String(e.length)})}},E=new B;var D=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"):(n.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),r=String(e.code||""),i=["ECONNREFUSED","ETIMEDOUT","ENOTFOUND","ECONNRESET","EAI_AGAIN","fetch failed","network","timeout"];return!!(i.some(a=>r.includes(a))||i.some(a=>t.toLowerCase().includes(a.toLowerCase())))}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithRetry(e,t,r=0){let i=new AbortController,a=setTimeout(()=>{i.abort()},this.fetchTimeout);try{let o=await fetch(e,{...t,signal:i.signal});return clearTimeout(a),o}catch(o){if(clearTimeout(a),this.isRetryableError(o)&&r<this.maxRetries){let f=r+1,u=this.retryDelay*Math.pow(2,r);return n.logWarning(`Fetch failed (attempt ${f}/${this.maxRetries+1}), retrying in ${u}ms`,{url:e,error:o.message||String(o),errorCode:o.code||"",attempt:String(f),maxRetries:String(this.maxRetries)}),await this.sleep(u),this.fetchWithRetry(e,t,r+1)}let s=o.message||String(o),g=o.code||"",c=s;o.name==="AbortError"||i.signal.aborted?c=`Request timeout after ${this.fetchTimeout}ms. The backend may be slow or unreachable.`:g==="ECONNREFUSED"?c=`Connection refused. Please verify the backend is running at ${e}`:g==="ETIMEDOUT"?c=`Connection timeout. The backend may be slow or unreachable at ${e}`:g==="ENOTFOUND"&&(c=`DNS resolution failed. Please check the backend URL: ${e}`);let l=new Error(c);throw l.code=g,l.originalError=o,n.logError("Fetch failed after retries",l,{url:e,retryCount:String(r),maxRetries:String(this.maxRetries),errorCode:g}),l}}async submitCommitReview(e){try{let t=`${this.baseUrl}/v2/commit-review/${e.factor}`,r=new FormData;r.append("user_id",e.user_id),r.append("repo_name",e.repo_name),r.append("commit_id",e.commit_id),r.append("username",e.username),r.append("factor",e.factor);let i=new oe;i.file("files.json",e.files_json);let a=await i.generateAsync({type:"nodebuffer"}),o=new ce([a],{type:"application/zip"});r.append("files_zip",new ae([o],"files.zip",{type:"application/zip"})),e.organization_name&&r.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:r});if(!s.ok){let c="Unknown error",l,f;try{f=await s.json(),c=f.detail||f.error||f.message||JSON.stringify(f)}catch{let h=await s.text();l=h,c=h||`HTTP ${s.status}`}let u=f?JSON.stringify(f):"",p=l??"";n.logError("Backend commit-review HTTP error response",new Error(c),{url:t,status:String(s.status),parsedBody:u,rawBody:p});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 l=s.headers.get("content-type");l&&l.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 g={};try{g=await s.json()??{}}catch{n.logWarning("Failed to parse JSON response, using default",{status:String(s.status)}),g={message:"Commit review submitted successfully"}}return{success:!0,data:g,analysisId:g.analysis_id?String(g.analysis_id):void 0,message:g.message||"Commit review submitted successfully"}}catch(t){throw n.logError("Error submitting commit review",t),t}}async identifyMissingDependencies(e){try{let t=`${this.baseUrl}/v2/missing-dependencies`;if(!this.apiKey)throw new Error("API key is required to call the missing-dependencies endpoint");let r=await this.fetchWithRetry(t,{method:"POST",headers:{"Content-Type":"application/json","X-CS-MCP-API-Key":this.apiKey},body:JSON.stringify(e)});if(!r.ok){let o="Unknown error",s,g;try{s=await r.json(),o=s.detail||s.error||s.message||JSON.stringify(s)}catch{let l=await r.text();g=l,o=l||`HTTP ${r.status}`}n.logError("Backend missing-dependencies HTTP error response",new Error(o),{url:t,status:String(r.status),parsedBody:s?JSON.stringify(s):"",rawBody:g??""});let c=new Error(`HTTP error! status: ${r.status}, detail: ${o}`);throw c.statusCode=r.status,c.errorDetail=o,c}let i={};try{i=await r.json()}catch{n.logWarning("Failed to parse JSON response from missing-dependencies endpoint, using default",{status:String(r.status)})}return{status:typeof i.status=="number"?i.status:1,missing_files:Array.isArray(i.missing_files)?i.missing_files:[]}}catch(t){throw n.logError("Error calling missing-dependencies endpoint",t),t}}connectWebSocket(e,t,r,i){return new Promise((a,o)=>{try{E.cleanupStaleConnections();let s=E.getConnectionForUser(e);if(s){n.logInfo("Reusing existing WebSocket connection",{userId:e});let l=p=>{try{let m=JSON.parse(p.toString());t(m)}catch(m){n.logError("Error parsing WebSocket message",m)}},f=p=>{n.logError("WebSocket connection error",p,{userId:e}),r==null||r(p)},u=(p,m)=>{let h=m.toString();n.logInfo("WebSocket connection closed",{userId:e,code:String(p),reason:h}),i==null||i(p,h)};s.removeAllListeners("message"),s.removeAllListeners("error"),s.removeAllListeners("close"),s.on("message",l),s.on("error",f),s.on("close",u),a(s);return}let g=`${this.wsBaseUrl}/v2/ws/${e}`;if(n.logInfo("Connecting to WebSocket",{wsUrl:g,userId:e}),!g.startsWith("ws://")&&!g.startsWith("wss://")){let l=new Error(`Invalid WebSocket URL format: ${g}. Expected URL to start with ws:// or wss://. Please check your BACKEND_API_URL configuration.`);n.logError("Invalid WebSocket URL format",l,{wsUrl:g,baseUrl:this.baseUrl,wsBaseUrl:this.wsBaseUrl}),o(l);return}let c=new ie(g);c.on("open",()=>{n.logInfo("WebSocket connection opened",{userId:e}),E.registerConnection(c,e),a(c)}),c.on("message",l=>{try{let f=JSON.parse(l.toString());t(f)}catch(f){n.logError("Error parsing WebSocket message",f)}}),c.on("error",l=>{let f=l instanceof Error?l.message:String(l),u=new Error(`WebSocket connection failed: ${f}. URL: ${g}. Please verify that the backend service is running and accessible.`);n.logError("WebSocket error",u,{userId:e,wsUrl:g}),r==null||r(u),o(u)}),c.on("close",(l,f)=>{let u=f.toString();l!==1e3&&n.logWarning("WebSocket closed unexpectedly",{userId:e,code:String(l),reason:u,wsUrl:g}),i==null||i(l,u)})}catch(s){let g=s instanceof Error?s.message:String(s),c=new Error(`Failed to create WebSocket connection: ${g}. Please check your network connection and backend service availability.`);n.logError("Error creating WebSocket connection",c,{userId:e,wsBaseUrl:this.wsBaseUrl}),o(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 i="Unknown error";try{let a=await t.json();i=a.detail||JSON.stringify(a)}catch{i=await t.text()||`HTTP ${t.status}`}throw n.logError(`HTTP error! status: ${t.status}, detail: ${i}`),t.status===401?new Error(i||"Authentication required. Please provide a valid API key."):t.status===404?new Error(i||"User not found."):new Error(i||`HTTP error! status: ${t.status}`)}let r=await t.json();return n.logInfo("User retrieved from secure route",{user_id:r.userid}),r}catch(e){throw n.logError("Error fetching user from secure route",e),e}}};import K from"ws";var $=class{backendApiService;constructor(e){this.backendApiService=e}async submitAndWaitForResults(e,t){let r;try{n.logInfo("Step 1: Establishing WebSocket connection BEFORE submitting review",{user_id:e.user_id});let a=await new Promise((f,u)=>{this.backendApiService.connectWebSocket(e.user_id,()=>{},p=>{n.logError("WebSocket connection failed before submit",p,{user_id:e.user_id}),u(p)},()=>{}).then(p=>{if(p.readyState===K.OPEN)r=p,n.logInfo("\u2705 WebSocket connection fully established and verified OPEN",{user_id:e.user_id,readyState:String(p.readyState)}),f(p);else{let m=new Error(`WebSocket connection not fully open. ReadyState: ${p.readyState}`);n.logError("\u274C WebSocket not in OPEN state",m,{user_id:e.user_id,readyState:String(p.readyState)}),u(m)}}).catch(u)});n.logInfo("\u2705 WebSocket connection confirmed OPEN, proceeding with HTTP call",{user_id:e.user_id});let o=e.file_changes.length,s=this.collectResultsViaEstablishedWebSocket(a,e.user_id,1200*1e3,t,o);s.catch(()=>{}),n.logInfo("Submitting commit review to backend",{user_id:e.user_id,factor:e.factor,fileCount:String(e.file_changes.length)});let g=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});n.logInfo("Commit review submitted",{analysisId:g.analysisId||"none"});let c=await s;if(!c||c.length===0)return n.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:g.analysisId,results:[]};let l=c.filter(f=>f&&f.file_name&&f.analysis!==void 0&&f.analysis!==null);return l.length===0?(n.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:g.analysisId,results:c}):(n.logInfo("Analysis results received successfully",{user_id:e.user_id,commit_id:e.commit_id,resultCount:String(l.length),totalResults:String(c.length)}),{success:!0,analysisId:g.analysisId,message:g.message,results:l})}catch(i){return n.logError("Error in commit review flow",i),i instanceof Error&&i.message.includes("status: 401")&&r&&(n.logInfo("Closing WebSocket connection due to 401 Unauthorized error"),r.close()),{success:!1,error:i instanceof Error?i.message:String(i)}}}async collectResultsViaEstablishedWebSocket(e,t,r=1200*1e3,i,a){let o=[],s=null,g,c,l=!1,f;if(e.readyState!==K.OPEN)throw new Error(`WebSocket must be in OPEN state. Current state: ${e.readyState}`);let u=new Promise((p,m)=>{f=setTimeout(()=>{l||(s="Analysis timeout: The analysis took too long to complete. Please try again with fewer files or contact support if the issue persists.",n.logError("WebSocket timeout",new Error(s),{user_id:t,timeoutMs:String(r)}),e.close(),m(new Error(s)))},r)});return new Promise((p,m)=>{let h=I=>{var _;try{let v=JSON.parse(I.toString());if(f&&(clearTimeout(f),f=setTimeout(()=>{l||(s="Analysis timeout: The analysis is taking longer than expected. Please try again or contact support.",n.logError("WebSocket timeout after receiving initial results",new Error(s),{user_id:t,resultsReceived:String(o.length)}),e.close(),k(),m(new Error(s)))},r)),v.status_code!==200&&v.status_code!==void 0){g=v.status_code,c=v.error_message||"Unknown error from backend",s=c||"Unknown error from backend",l=!0,f&&clearTimeout(f),n.logError("WebSocket error message received",new Error(c),{status_code:String(g),user_id:t,raw_message:JSON.stringify(v)}),k(),m(new Error(s||"Unknown error from backend"));return}if(v.content&&v.content.analysis_type==="commit_review"){let w=((_=v.content.file_name)==null?void 0:_.replace(/\\/g,"/"))||"",T=v.content.analysis;w&&T!==void 0&&T!==null&&(o.push({analysis:T,language:v.content.language,file_name:w,analysisId:v.analysis_id??0}),n.logInfo("Analysis result received for file",{user_id:t,file_name:w,result_count:String(o.length)})),(v.content.is_complete||v.content.status==="complete")&&(l=!0,f&&clearTimeout(f),n.logInfo("Analysis marked as complete",{user_id:t,total_results:String(o.length)}),k(),p(o))}}catch(v){n.logError("Error parsing WebSocket message",v)}},S=I=>{f&&clearTimeout(f),s=I.message,n.logError("WebSocket connection error",I,{user_id:t}),k(),m(I)},C=(I,_)=>{f&&clearTimeout(f);let v=_.toString();I!==1e3&&!l?(s=`Connection closed unexpectedly: ${v}`,n.logWarning("WebSocket closed unexpectedly",{code:String(I),reason:v,user_id:t,isComplete:String(l),results_received:String(o.length)}),k(),m(new Error(s))):I===1e3&&!l?(n.logInfo("WebSocket closed normally, treating as completion",{user_id:t,results_received:String(o.length)}),l=!0,k(),p(o)):(k(),p(o))},k=()=>{e.removeListener("message",h),e.removeListener("error",S),e.removeListener("close",C)};e.on("message",h),e.on("error",S),e.on("close",C),u.catch(()=>{})})}async collectResultsViaWebSocket(e,t,r=1200*1e3){let i=[],a=null,o,s,g=!1,c,l,f=new Promise((u,p)=>{c=setTimeout(()=>{g||(a="Analysis timeout: The analysis took too long to complete. Please try again with fewer files or contact support if the issue persists.",n.logError("WebSocket timeout",new Error(a),{user_id:e,timeoutMs:String(r)}),l&&l.close(),p(new Error(a)))},r)});try{await Promise.race([new Promise((u,p)=>{this.backendApiService.connectWebSocket(e,m=>{var h;if(c&&(clearTimeout(c),c=setTimeout(()=>{g||(a="Analysis timeout: The analysis is taking longer than expected. Please try again or contact support.",n.logError("WebSocket timeout after receiving initial results",new Error(a),{user_id:e,resultsReceived:String(i.length)}),l&&l.close(),p(new Error(a)))},r)),m.status_code!==200&&m.status_code!==void 0){o=m.status_code,s=m.error_message||"Unknown error from backend",a=s,g=!0,c&&clearTimeout(c),n.logError("WebSocket error message received",new Error(s),{status_code:String(o),user_id:e,raw_message:JSON.stringify(m)}),u();return}if(m.content)if(m.content.analysis_type==="commit_review"){let C=((h=m.content.file_name)==null?void 0:h.replace(/\\/g,"/"))||"",k=m.content.analysis;C&&k!==void 0&&k!==null?(i.push({analysis:k,language:m.content.language,file_name:C,analysisId:m.analysis_id??0}),n.logInfo("Analysis result received for file",{user_id:e,file_name:C,result_count:String(i.length)})):n.logWarning("Received invalid analysis result (missing file_name or analysis)",{user_id:e,has_file_name:String(!!C),has_analysis:String(k!=null)}),(m.content.is_complete||m.content.status==="complete")&&(g=!0,c&&clearTimeout(c),n.logInfo("Analysis marked as complete",{user_id:e,total_results:String(i.length)}),u())}else n.logInfo("Received non-commit-review WebSocket message",{user_id:e,analysis_type:m.content.analysis_type||"unknown"});else n.logInfo("Received WebSocket message with null content",{user_id:e,results_so_far:String(i.length)})},m=>{c&&clearTimeout(c),a=m.message,n.logError("WebSocket connection error",m,{user_id:e}),p(m)},(m,h)=>{c&&clearTimeout(c),m!==1e3&&!g?(a=`Connection closed unexpectedly: ${h}`,n.logWarning("WebSocket closed unexpectedly",{code:String(m),reason:h.toString(),user_id:e,isComplete:String(g),results_received:String(i.length)})):m===1e3&&!g&&(n.logInfo("WebSocket closed normally, treating as completion",{user_id:e,results_received:String(i.length)}),g=!0),u()}).then(m=>{l=m,t&&t(m)}).catch(p)}),f])}catch(u){throw c&&clearTimeout(c),u}if(a){let u=new Error(a);throw o!==void 0&&(u.statusCode=o),s&&(u.errorDetail=s),u}return n.logInfo("WebSocket collection completed",{user_id:e,results_count:String(i.length),isComplete:String(g)}),i}};import*as b from"zod";var q={uncommitted:b.boolean().describe("value will be false for commit analysis and true for unstaged changes"),directory:b.string().describe("Path to the Git repository root"),factor:b.enum(["power_analysis","owasp","cwe_mitre","cwe_kev"]).describe("Analysis factor: power_analysis, owasp, cwe_mitre, or cwe_kev")},Y={success:b.boolean().describe("Whether the commit analysis was successful"),analysisId:b.string().optional().describe("Analysis ID returned from the backend (if successful)"),status:b.string().optional().describe("Status string (e.g., 'complete', 'pending', 'failed')"),statusCode:b.number().optional().describe("HTTP status code for error scenarios. Common values: 401 (authentication), 404 (not found), 422 (validation), 500 (internal error)"),errorType:b.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:b.any().optional().describe("Analysis results array (present when success is true)"),message:b.string().optional().describe("Success or informational message"),error:b.string().optional().describe("Error message (present when success is false)"),errorDetails:b.object({userMessage:b.string().optional().describe("User-friendly error message that can be displayed to end users"),technicalDetails:b.string().optional().describe("Technical error details for debugging purposes"),retryable:b.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 V(d,e="",t=""){let r=e.toLowerCase(),i="",{issue:a,start_line:o,end_line:s,issue_code_snippet:g,severity:c,solution:l,solution_code_snippet:f}=d;return i+=`**Issue:** ${a}
|
|
10
10
|
|
|
11
|
-
`,t&&(
|
|
11
|
+
`,t&&(i+=`**Quality Aspect:** ${t}
|
|
12
12
|
|
|
13
|
-
`),
|
|
13
|
+
`),i+=`**Severity:** ${c}
|
|
14
14
|
|
|
15
|
-
`,
|
|
15
|
+
`,o&&s&&(i+=`**Lines:** ${o}-${s}
|
|
16
16
|
|
|
17
|
-
`),
|
|
18
|
-
${
|
|
17
|
+
`),g&&(i+=`\`\`\`${r}
|
|
18
|
+
${g}
|
|
19
19
|
\`\`\`
|
|
20
20
|
|
|
21
|
-
`),
|
|
21
|
+
`),i+=`**Solution:** ${l}
|
|
22
22
|
|
|
23
|
-
`,
|
|
24
|
-
${
|
|
23
|
+
`,f&&(i+=`\`\`\`${r}
|
|
24
|
+
${f}
|
|
25
25
|
\`\`\`
|
|
26
26
|
|
|
27
|
-
`),
|
|
28
|
-
${
|
|
27
|
+
`),i}function le(d,e=""){var i;let t=e.toLowerCase(),r="";for(let a of d)if(a){let o="",{characteristic:s,description_of_characteristic:g,issue_items:c=[]}=a;o+=`## ${s}
|
|
28
|
+
${g}
|
|
29
29
|
|
|
30
|
-
`;let
|
|
30
|
+
`;let l=[],f=[];for(let u of c){let p=((i=u.severity)==null?void 0:i.toLowerCase())||"";p==="critical"||p==="high"?l.push(u):(p==="medium"||p==="low")&&f.push(u)}if(l.length>0)for(let u of l)o+=V(u,t,s);if(f.length>0)for(let u of f)o+=V(u,t,s);r+=o}return r}function ge(d,e="",t=""){let r="";return t&&(r+=`# Analysis for ${t}
|
|
31
31
|
|
|
32
|
-
`),Array.isArray(
|
|
33
|
-
`,
|
|
32
|
+
`),Array.isArray(d)?r+=le(d,e):r+=`No analysis data available.
|
|
33
|
+
`,r}function X(d){let e=`# Code Analysis Results
|
|
34
34
|
|
|
35
|
-
`;for(let t of
|
|
35
|
+
`;for(let t of d)e+=`---
|
|
36
36
|
|
|
37
|
-
`,e+=
|
|
38
|
-
`;return e}function
|
|
37
|
+
`,e+=ge(t.analysis,t.language||"",t.file_name),e+=`
|
|
38
|
+
`;return e}function Z(d,e){try{switch(d){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"deps_check":return"Checking for additional context files...";case"smart_analysis_intro":return"Performing smart analysis. In the meantime, enjoy these coding tips!";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`Coding tip:${(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 z from"fs";import*as Q from"path";var G=class{filePath;constructor(e){this.filePath=e??Q.join(process.cwd(),"src","utils","cs-dev-mongo.coding_tips.json")}getTips(){try{if(z.existsSync(this.filePath)){let e=z.readFileSync(this.filePath,"utf-8");return JSON.parse(e).map(r=>r.message)}return["Write clean, maintainable code.","Test your code early and often."]}catch{return["Write clean, maintainable code."]}}},L=class{tipInterval=null;seenTips=new Set;lastMessageTimestamp=Date.now();start(e,t){this.stop(),this.lastMessageTimestamp=Date.now(),this.tipInterval=setInterval(()=>{let r=Date.now()-this.lastMessageTimestamp,i=e();if(r>=15e3&&i.length>0){let a;do a=Math.floor(Math.random()*i.length);while(this.seenTips.has(a)&&this.seenTips.size<i.length);this.seenTips.add(a),this.seenTips.size>=i.length&&this.seenTips.clear(),this.lastMessageTimestamp=Date.now(),Promise.resolve(t(i[a])).catch(o=>{n.logWarning("TipScheduler: onTip() failed",{error:o instanceof Error?o.message:String(o),tipIndex:String(a)})})}},1e3)}stop(){this.tipInterval&&(clearInterval(this.tipInterval),this.tipInterval=null)}resetTimestamp(){this.lastMessageTimestamp=Date.now()}},N=class{constructor(e,t,r=new G,i=n,a=new L){this.server=e;this.progressToken=t;this.tipsProvider=r;this.log=i;this.scheduler=a;this.tips=this.tipsProvider.getTips()}server;progressToken;tipsProvider;log;scheduler;tips=[];startTipTimer(){this.scheduler.start(()=>this.tips,e=>this.send("coding_tip",50,100,{tip:e}))}stopTipTimer(){this.scheduler.stop()}async send(e,t,r,i){try{this.scheduler.resetTimestamp();let a=Z(e,i);if(process.stderr.write(`[DEBUG:EMITTER] send() called. Step: ${e}, Token: ${this.progressToken?"PRESENT":"MISSING"}
|
|
39
|
+
`),this.progressToken){try{process.stderr.write(`[DEBUG:EMITTER] Attempting MCP $/progress notification: "${a}"
|
|
40
|
+
`),await this.server.server.notification({method:"$/progress",params:{progressToken:this.progressToken,progress:t,total:r,message:a}}),process.stderr.write(`[DEBUG:EMITTER] \u2705 $/progress notification DELIVERED to transport. Step: ${e}, Progress: ${t}/${r}
|
|
41
|
+
`)}catch(o){this.log.logWarning("MCP progress notification failed",{error:o instanceof Error?o.message:String(o),step:e,token:this.progressToken})}return}try{process.stderr.write(`[DEBUG:EMITTER] Attempting Fallback notification: "${a}"
|
|
42
|
+
`),await this.server.server.notification({method:"notifications/codesherlock/progress",params:{progress:t,total:r,message:a}})}catch(o){this.log.logInfo("Fallback progress notification failed (ignoring)",{error:o instanceof Error?o.message:String(o),step:e})}}catch(a){this.log.logWarning("Unexpected error in ProgressEmitter.send()",{error:a instanceof Error?a.message:String(a),step:e})}}};var U=class{emitter;constructor(e,t){this.emitter=new N(e,t)}async send(e,t,r,i){return this.emitter.send(e,t,r,i)}startTipTimer(){this.emitter.startTipTimer()}stopTipTimer(){this.emitter.stopTipTimer()}};function R(d,e,t){let r=d instanceof Error?d.message:String(d),i=r,a=e,o=t;if(d&&typeof d=="object"&&("statusCode"in d&&!a&&(a=d.statusCode),"errorDetail"in d&&!o&&(o=d.errorDetail)),!a){let f=r.match(/status:\s*(\d+)/i);a=f?parseInt(f[1],10):void 0}let s=o||r,g="internal_error",c=s,l=!1;return a===401||s.includes("Authentication")||s.includes("API key")||s.includes("authentication required")?(g="authentication_error",c=`API key missing. Authentication is required.
|
|
39
43
|
|
|
40
44
|
To get an API key:
|
|
41
45
|
1. Visit https://codesherlock.ai/login and sign up or log in.
|
|
@@ -45,24 +49,19 @@ To get an API key:
|
|
|
45
49
|
Usage instructions:
|
|
46
50
|
See the MCP integration guide at https://docs.codesherlock.ai/codesherlock-mcp-server/mcp/setup/guide.
|
|
47
51
|
|
|
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 me from"fs";function ne(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
|
-
${(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 G from"fs";import*as se from"path";var H=class{filePath;constructor(e){this.filePath=e??se.join(process.cwd(),"src","utils","cs-dev-mongo.coding_tips.json")}getTips(){try{if(G.existsSync(this.filePath)){let e=G.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."]}}},K=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 H,a=r,o=new K){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=ne(e,a);if(process.stderr.write(`[DEBUG:EMITTER] send() called. Step: ${e}, Token: ${this.progressToken?"PRESENT":"MISSING"}
|
|
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}}),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 P(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 ie=(u,e,t,n,a=(o,i)=>new O(o,i))=>async({uncommitted:o,directory:i,factor:s},l)=>{var f,y,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 w=s==="power"?"power_analysis":s;if(r.logInfo("=== TOOL INVOKED: codesherlock_analyze ===",{uncommitted:String(o),directory:i,originalFactor:s,normalizedFactor:w}),!me.existsSync(i)){let p=x(new Error("Directory does not exist")),h={success:!1,error:p.userMessage,errorType:p.errorType,statusCode:404,errorDetails:{userMessage:p.userMessage,technicalDetails:`Directory not found: ${i}`,retryable:!1}};return P("codesherlock_analyze: returning error (directory does not exist)",h),{content:[{type:"text",text:`Error: ${p.userMessage}`}],structuredContent:h,isError:!0}}r.logInfo("Fetching file changes",{uncommitted:String(o),directory:i,factor:s}),await g.send("git_start",1,10);let S;try{r.logInfo("Calling gitService.analyzeGitChanges",{uncommitted:String(o),directory:i}),S=await e.analyzeGitChanges(o,i),r.logInfo("File changes received from gitService",{fileCount:String(S.length),files:JSON.stringify(S.map(p=>{var h,C;return{filename:p.filename,status:p.status,contentLength:String(((h=p.new_content)==null?void 0:h.length)||0),patchLength:String(((C=p.patch)==null?void 0:C.length)||0)}}))})}catch(p){let h=x(p);r.logError("Failed to analyze git changes",p,{directory:i,uncommitted:String(o)});let C={success:!1,error:h.userMessage,errorType:h.errorType,statusCode:h.statusCode,errorDetails:{userMessage:h.userMessage,technicalDetails:h.technicalDetails,retryable:h.retryable}};return P("codesherlock_analyze: returning error (gitService.analyzeGitChanges failed)",C),{content:[{type:"text",text:`Error: ${h.userMessage}`}],structuredContent:C,isError:!0}}if(!S||S.length===0){let p=x(new Error("No file changes found in the commit")),h={success:!1,error:p.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 P("codesherlock_analyze: returning error (no file changes)",h),{content:[{type:"text",text:`Error: ${p.userMessage}`}],structuredContent:h,isError:!0}}let I=20;if(S.length>I){let p=x(new Error(`Too many files to analyze. Maximum ${I} files allowed.`));r.logWarning("File limit exceeded",{fileCount:String(S.length),maxLimit:String(I)});let h={success:!1,error:p.userMessage,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:`Too many files to analyze. Found ${S.length} files, but the maximum allowed is ${I} files. Please reduce the number of files in your commit or uncommitted changes.`,technicalDetails:`File count validation failed: ${S.length} files exceeds limit of ${I}`,retryable:!1}};return P("codesherlock_analyze: returning error (file limit exceeded)",h),{content:[{type:"text",text:`Error: Too many files to analyze. Found ${S.length} files, but the maximum allowed is ${I} files. Please reduce the number of files in your commit or uncommitted changes.`}],structuredContent:h,isError:!0}}let b=[];for(let p=0;p<S.length;p++){let h=S[p];if(!h||typeof h!="object"){b.push(`File at index ${p} is not a valid object`);continue}(!h.filename||typeof h.filename!="string")&&b.push(`File at index ${p} is missing or has invalid 'filename' field`),(!h.status||typeof h.status!="string")&&b.push(`File at index ${p} is missing or has invalid 'status' field`),h.new_content===void 0&&b.push(`File at index ${p} is missing 'new_content' field`)}try{r.logInfo("Checking for missing dependencies for commit analysis",{directory:i,uncommitted:String(o),fileCount:String(S.length)});let p=await e.getRepoStructure(i);r.logInfo("Repo structure retrieved for missing dependency analysis",{directory:i,repoFileCount:String(p.length)});let h=await n.identifyMissingDependencies({repo_structure:p,files:S.map(C=>({filename:C.filename,status:C.status,new_content:C.new_content}))});if(h.status===0&&Array.isArray(h.missing_files)&&h.missing_files.length>0){r.logInfo("Missing dependencies identified for commit analysis",{missingCount:String(h.missing_files.length)});let C=o?void 0:"HEAD";for(let L of h.missing_files)try{let B=await e.getFileContent(i,L,C);S.push({filename:L,status:"context",new_content:B,patch:""})}catch(B){r.logWarning("Failed to load content for missing dependency file",{filename:L,error:String(B)})}r.logInfo("Missing dependency context files appended",{totalFilesAfter:String(S.length)})}else r.logInfo("No missing dependencies identified for commit analysis",{status:String(h.status)})}catch(p){r.logWarning("Failed to check/fetch missing dependencies. Proceeding without additional context.",{directory:i,error:String(p)})}if(b.length>0){let p=`Invalid file changes structure: ${b.join("; ")}`,h=x(new Error(p));r.logError("Invalid file changes structure",new Error(p),{invalidFilesCount:String(b.length)});let C={success:!1,error:h.userMessage,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:"Invalid file changes structure. Each file must have 'filename', 'status', and 'new_content' fields.",technicalDetails:p,retryable:!1}};return P("codesherlock_analyze: returning error (invalid file changes structure)",C),{content:[{type:"text",text:`Error: ${h.userMessage}`}],structuredContent:C,isError:!0}}let _;try{_=await e.getCurrentCommitHash(i)}catch(p){let h=x(p);r.logError("Failed to get commit hash",p,{directory:i}),_="UNCOMMITTED",r.logWarning("Using fallback commit ID",{commitId:_})}r.logInfo("Fetching user information from secure route");let A;try{A=await n.getUserFromSecureRoute()}catch(p){let h=x(p);r.logError("Failed to fetch user from secure route",p);let C={success:!1,error:h.userMessage,errorType:h.errorType,statusCode:h.statusCode,status:h.errorType,errorDetails:{userMessage:h.userMessage,technicalDetails:h.technicalDetails,retryable:h.retryable}};return P("codesherlock_analyze: returning error (getUserFromSecureRoute failed)",C),{content:[{type:"text",text:`Error: ${h.userMessage}`}],structuredContent:C,isError:!0}}d=A.userid;let v=A.username;r.logInfo("User information retrieved",{user_id:d,username:v});let M=await e.getRepositoryName(i);M?r.logInfo("Repository name extracted from git remote",{repoName:M}):(M=i.split(/[/\\]/).pop()||"unknown-repo",r.logInfo("Repository name extracted from directory path fallback",{repoName:M}));let W=await e.getOrganizationName(i);if(W)r.logInfo("Organization name extracted from git remote",{organizationName:W});else{let p=i.split(/[/\\]/),h=p.findIndex(C=>C.toLowerCase()==="github");W=h>=0&&p[h+1]?p[h+1]:void 0,r.logInfo("Organization name extracted from directory path fallback",{organizationName:W||"undefined"})}let $=!1,E;try{await g.send("smart_analysis_intro",4,10),g.startTipTimer(),E=await t.submitAndWaitForResults({factor:w,user_id:d,repo_name:M,commit_id:_,username:v,file_changes:S,organization_name:W},g)}finally{g.stopTipTimer()}if(!E.success){let p=x(new Error(E.error||"Unknown error"));r.logError("Commit review failed",new Error(E.error||"Unknown error"),{user_id:d,commit_id:_});let h={success:!1,error:E.error||p.userMessage,errorType:p.errorType,statusCode:p.statusCode,errorDetails:{userMessage:p.userMessage,technicalDetails:E.error||p.technicalDetails,retryable:p.retryable}};return P("codesherlock_analyze: returning error (result.success === false)",h),{content:[{type:"text",text:`Error: ${p.userMessage}`}],structuredContent:h,isError:!0}}r.logInfo("Processing analysis results",{success:String(E.success),hasResults:String(!!E.results),resultsLength:String(((y=E.results)==null?void 0:y.length)||0)});let V="";E.success&&E.results&&E.results.length>0?V=re(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 X={success:E.success,analysisId:E.analysisId,status:E.status,results:E.results,message:E.message};return P("codesherlock_analyze: returning success structuredContent",X),{content:[{type:"text",text:V||JSON.stringify(E,null,2)}],structuredContent:X}}catch(w){let S=w instanceof Error?w.message:String(w),I=S.includes("timeout")||S.includes("timed out"),b=S.includes("aborted")||S.includes("cancelled")||S.includes("Tool call was aborted");if(r.logError("Error in codesherlock_analyze handler",w,{directory:i,uncommitted:String(o),factor:s,isAborted:String(b||I),hasUserId:String(!!d)}),(b||I)&&d){let v=d;r.logInfo("Cleaning up WebSocket connection for user after tool call abortion/timeout",{user_id:v}),await T.closeConnectionForUser(v).catch(M=>{r.logWarning("Error during WebSocket cleanup",{error:M instanceof Error?M.message:String(M),user_id:v})})}let _=x(w),A={success:!1,error:_.userMessage,errorType:_.errorType,statusCode:_.statusCode||500,errorDetails:{userMessage:_.userMessage,technicalDetails:_.technicalDetails,retryable:_.retryable}};return P("codesherlock_analyze: returning error (catch block)",A),{content:[{type:"text",text:`Error analyzing commit: ${_.userMessage}`}],structuredContent:A,isError:!0}}};function oe(u){try{let e=new N,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 z(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:Q,outputSchema:ee},ie(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 ye from"dotenv";import{fileURLToPath as we}from"url";import{dirname as Se,join as ve,resolve as be}from"path";var D=()=>{};console.log=D;console.error=D;console.warn=D;console.info=D;console.debug=D;console.trace=D;var _e=we(import.meta.url),Ee=Se(_e),ke=be(Ee,".."),Ce=ve(ke,".env");ye.config({path:Ce});var Ie="InstrumentationKey=c2a3ecc2-842d-42e7-812b-fb533dc8be5b;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=a15f4e29-d8f7-4cc4-a103-764e7371ffa0",Te=Ie;async function Ae(){await r.initialize(Te)}await Ae();async function Me(){process.on("uncaughtException",async u=>{r.logError("Uncaught exception - performing cleanup",u,{errorType:"uncaught_exception"});try{T.hasActiveConnections()&&(r.logInfo("Closing WebSocket connections due to uncaught exception",{connectionCount:String(T.getConnectionCount())}),await T.closeAllConnections())}catch(t){r.logError("Error during cleanup in uncaughtException handler",t)}await r.flush(),process.stderr.write(`
|
|
52
|
+
If you followed these steps and still face issues, contact support at support@codesherlock.ai.`,l=!1):a===404||s.includes("not found")||s.includes("User not found")||s.includes("WebSocket not active")?(g="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.",l=!1):a===422||s.includes("Invalid")||s.includes("validation")||s.includes("must contain")||s.includes("must be")?(g="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,l=!1):s.includes("Git")||s.includes("git")||s.includes("repository")||s.includes("commit")?(g="git_error",c=`Git error: ${s}`,l=!0):s.includes("WebSocket")||s.includes("websocket")||s.includes("Connection")||s.includes("connection closed")?(g="websocket_error",s.includes("Connection closed unexpectedly")?c="WebSocket connection closed unexpectedly. Please try again.":c="WebSocket connection error. Please try again.",l=!0):a===500||s.includes("Internal server error")||s.includes("internal error")||s.includes("Internal error")||s.includes("An error occurred")||s.includes("Failed to generate")?(g="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.",l=!0):s.includes("usage")||s.includes("token")||s.includes("limit")||s.includes("trial")||s.includes("quota")?(g="usage_limit_error",c=s,l=!1):a&&a>=400&&a<500?(g="api_error",c=s||`Client error (${a}). Please check your request and try again.`,l=!1):a&&a>=500&&(g="api_error",c="Backend service error. Please try again later or contact support.",l=!0),{statusCode:a,errorType:g,userMessage:c,technicalDetails:i,retryable:l}}import ue from"fs";async function ee(d,e,t){var l;let{uncommitted:r,directory:i,factor:a}=d,{gitService:o,commitReviewService:s,backendApiService:g}=e,c;try{await t.send("start",0,10);let f=a==="power"?"power_analysis":a;if(n.logInfo("=== runAnalysis invoked ===",{uncommitted:String(r),directory:i,factor:f}),!ue.existsSync(i)){let y=R(new Error("Directory does not exist"));return{success:!1,error:y.userMessage,errorType:y.errorType,statusCode:404,errorDetails:{userMessage:y.userMessage,technicalDetails:`Directory not found: ${i}`,retryable:!1}}}await t.send("git_start",1,10);let u;try{u=await o.analyzeGitChanges(r,i),n.logInfo("File changes received",{fileCount:String(u.length)}),await t.send("git_complete",2,10,{files:u.length})}catch(y){let w=R(y);return n.logError("Failed to analyze git changes",y,{directory:i,uncommitted:String(r)}),{success:!1,error:w.userMessage,errorType:w.errorType,statusCode:w.statusCode,errorDetails:{userMessage:w.userMessage,technicalDetails:w.technicalDetails,retryable:w.retryable}}}if(!u||u.length===0)return{success:!1,error:"No file changes found.",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}};let p=20;if(u.length>p)return n.logWarning("File limit exceeded",{fileCount:String(u.length),maxLimit:String(p)}),{success:!1,error:`Too many files. Found ${u.length}, maximum is ${p}.`,errorType:"validation_error",statusCode:422,errorDetails:{userMessage:`Too many files to analyze. Found ${u.length} files, but the maximum allowed is ${p}. Please reduce the number of files in your commit or uncommitted changes.`,technicalDetails:`File count ${u.length} exceeds limit of ${p}`,retryable:!1}};let m=[];for(let y=0;y<u.length;y++){let w=u[y];if(!w||typeof w!="object"){m.push(`File at index ${y} is not a valid object`);continue}(!w.filename||typeof w.filename!="string")&&m.push(`File at index ${y} is missing or has invalid 'filename' field`),(!w.status||typeof w.status!="string")&&m.push(`File at index ${y} is missing or has invalid 'status' field`),w.new_content===void 0&&m.push(`File at index ${y} is missing 'new_content' field`)}await t.send("deps_check",3,10);try{let y=await o.getRepoStructure(i),w=await g.identifyMissingDependencies({repo_structure:y,files:u.map(T=>({filename:T.filename,status:T.status,new_content:T.new_content}))});if(w.status===0&&Array.isArray(w.missing_files)&&w.missing_files.length>0){let T=r?void 0:"HEAD";for(let x of w.missing_files)try{let P=await o.getFileContent(i,x,T);u.push({filename:x,status:"context",new_content:P,patch:""})}catch(P){n.logWarning("Failed to load missing dependency file",{filename:x,error:String(P)})}n.logInfo("Missing dependencies appended",{totalFilesAfter:String(u.length)})}}catch(y){n.logWarning("Failed to check missing dependencies. Proceeding without additional context.",{error:String(y)})}if(m.length>0){let y=`Invalid file changes structure: ${m.join("; ")}`;return n.logError("Invalid file changes structure",new Error(y),{invalidFilesCount:String(m.length)}),{success:!1,error:"Invalid file structure.",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}}}let h;try{h=await o.getCurrentCommitHash(i)}catch{h="UNCOMMITTED",n.logWarning("Could not get commit hash, using fallback",{commitId:h})}let S;try{S=await g.getUserFromSecureRoute()}catch(y){let w=R(y);return n.logError("Failed to fetch user from secure route",y),{success:!1,error:w.userMessage,errorType:w.errorType,statusCode:w.statusCode,status:w.errorType,errorDetails:{userMessage:w.userMessage,technicalDetails:w.technicalDetails,retryable:w.retryable}}}c=S.userid;let C=S.username,k=i.split(/[/\\]/).pop()||"unknown-repo",I=await o.getOrganizationName(i);if(!I){let y=i.split(/[/\\]/),w=y.findIndex(T=>T.toLowerCase()==="github");I=w>=0&&y[w+1]?y[w+1]:void 0}let _;try{await t.send("smart_analysis_intro",4,10),t.startTipTimer(),_=await s.submitAndWaitForResults({factor:f,user_id:c,repo_name:k,commit_id:h,username:C,file_changes:u,organization_name:I},t)}finally{t.stopTipTimer()}if(!_.success){let y=R(new Error(_.error||"Unknown error"));return n.logError("Commit review failed",new Error(_.error||"Unknown error"),{user_id:c,commit_id:h}),{success:!1,error:_.error||y.userMessage,errorType:y.errorType,statusCode:y.statusCode,errorDetails:{userMessage:y.userMessage,technicalDetails:_.error||y.technicalDetails,retryable:y.retryable}}}let v=(_.results??[]).reduce((y,w)=>{if(!w||typeof w!="object")return y;let T=Array.isArray(w.analysis)?w.analysis:[];return y+T.reduce((x,P)=>{var H;return x+(((H=P==null?void 0:P.issue_items)==null?void 0:H.length)||0)},0)},0);return await t.send("complete",10,10,{issues:v}),n.logInfo("Analysis complete",{user_id:c,analysisId:_.analysisId||"none",resultCount:String(((l=_.results)==null?void 0:l.length)||0),issueCount:String(v)}),{success:!0,analysisId:_.analysisId,results:_.results,message:_.message}}catch(f){let u=f instanceof Error?f.message:String(f),p=u.includes("timeout")||u.includes("timed out"),m=u.includes("aborted")||u.includes("cancelled")||u.includes("Tool call was aborted");if(n.logError("Unexpected error in runAnalysis",f,{directory:i,uncommitted:String(r),factor:a,isAborted:String(m||p)}),(m||p)&&c){let S=c;await E.closeConnectionForUser(S).catch(C=>{n.logWarning("Error during WebSocket cleanup",{error:C instanceof Error?C.message:String(C),user_id:S})})}let h=R(f);return{success:!1,error:h.userMessage,errorType:h.errorType,statusCode:h.statusCode||500,errorDetails:{userMessage:h.userMessage,technicalDetails:h.technicalDetails,retryable:h.retryable}}}}function te(d,e){try{let t=e;n.logInfo(d,{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){n.logError("Failed to log structuredContent debug payload",t)}}var re=(d,e,t,r)=>async({uncommitted:i,directory:a,factor:o},s)=>{var p,m;let g=((p=s==null?void 0:s._meta)==null?void 0:p.progressToken)??(s==null?void 0:s.progressToken),c=new U(d,g),l=await ee({uncommitted:i,directory:a,factor:o},{gitService:e,commitReviewService:t,backendApiService:r},c);if(!l.success){let h={success:!1,error:l.error,errorType:l.errorType,statusCode:l.statusCode,status:l.status,errorDetails:l.errorDetails};return te("codesherlock_analyze: returning error",h),{content:[{type:"text",text:`Error: ${((m=l.errorDetails)==null?void 0:m.userMessage)??l.error}`}],structuredContent:h,isError:!0}}let f=l.results&&l.results.length>0?X(l.results):"";n.logEvent("tool_completed",{toolName:"codesherlock_analyze",success:String(l.success),analysisId:l.analysisId||"none",factor:o});let u={success:l.success,analysisId:l.analysisId,status:l.status,results:l.results,message:l.message};return te("codesherlock_analyze: returning success",u),{content:[{type:"text",text:f||JSON.stringify(l,null,2)}],structuredContent:u}};function ne(d){try{let e=new F,t=process.env.BACKEND_API_URL;n.logInfo("Backend URL: "+t),n.logInfo("MCP API Key: "+process.env.MCP_API_KEY);let r,i;try{r=new D(t,process.env.MCP_API_KEY),i=new $(r)}catch(a){let o=a instanceof Error?a.message:String(a);throw n.logError("Failed to create backend services",a instanceof Error?a:new Error(o),{errorType:"service_creation_failure"}),new Error(`Failed to initialize backend services: ${o}`)}try{d.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:q,outputSchema:Y},re(d,e,i,r)),n.logInfo("Tool 'codesherlock_analyze' registered successfully")}catch(a){let o=a instanceof Error?a.message:String(a);throw n.logError("Failed to register tool 'codesherlock_analyze'",a instanceof Error?a:new Error(o),{toolName:"codesherlock_analyze",errorType:"tool_registration_failure"}),new Error(`Failed to register tool 'codesherlock_analyze': ${o}`)}}catch(e){throw e}}import me from"dotenv";import{fileURLToPath as pe}from"url";import{dirname as he,join as ye,resolve as we}from"path";var W=()=>{};console.log=W;console.error=W;console.warn=W;console.info=W;console.debug=W;console.trace=W;var Se=pe(import.meta.url),ve=he(Se),be=we(ve,".."),_e=ye(be,".env");me.config({path:_e});var Ee="InstrumentationKey=9a687b23-0384-4345-abaa-4e7065b0d103;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=9e40bff2-07af-4326-be55-f5dc98e9e9c3",ke=Ee;async function Ce(){await n.initialize(ke)}await Ce();async function Ie(){process.on("uncaughtException",async d=>{n.logError("Uncaught exception - performing cleanup",d,{errorType:"uncaught_exception"});try{E.hasActiveConnections()&&(n.logInfo("Closing WebSocket connections due to uncaught exception",{connectionCount:String(E.getConnectionCount())}),await E.closeAllConnections())}catch(t){n.logError("Error during cleanup in uncaughtException handler",t)}await n.flush(),process.stderr.write(`
|
|
54
53
|
\u274C Unhandled Exception
|
|
55
54
|
|
|
56
55
|
An unexpected error occurred. The server will shut down.
|
|
57
56
|
Please check the logs for details or contact support@codesherlock.ai for assistance.
|
|
58
57
|
|
|
59
|
-
`),process.exit(1)}),process.on("unhandledRejection",async(
|
|
58
|
+
`),process.exit(1)}),process.on("unhandledRejection",async(d,e)=>{let t=d instanceof Error?d:new Error(String(d));n.logError("Unhandled promise rejection - performing cleanup",t,{errorType:"unhandled_rejection",promiseString:String(e)});try{E.hasActiveConnections()&&(n.logInfo("Closing WebSocket connections due to unhandled rejection",{connectionCount:String(E.getConnectionCount())}),await E.closeAllConnections())}catch(i){n.logError("Error during cleanup in unhandledRejection handler",i)}await n.flush(),process.stderr.write(`
|
|
60
59
|
\u274C Unhandled Promise Rejection
|
|
61
60
|
|
|
62
61
|
An unexpected error occurred. The server will shut down.
|
|
63
62
|
Please check the logs for details or contact support@codesherlock.ai for assistance.
|
|
64
63
|
|
|
65
|
-
`),process.exit(1)})}await
|
|
64
|
+
`),process.exit(1)})}await Ie();var j;try{j=new de({name:"codesherlock-beta-mcp-server",version:"0.0.1"}),n.logInfo("MCP Server instance created",{serverName:"codesherlock-beta-mcp-server",version:"0.0.1"})}catch(d){let e=d instanceof Error?d.message:String(d),t=d instanceof Error?d.stack:void 0;n.logError("Failed to create MCP server instance",d instanceof Error?d:new Error(e),{serverName:"codesherlock-beta-mcp-server",version:"0.0.1",errorType:"server_creation_failure"}),process.stderr.write(`
|
|
66
65
|
\u274C MCP Server Creation Failed
|
|
67
66
|
|
|
68
67
|
The MCP server failed to initialize. This prevents the server from starting.
|
|
@@ -70,7 +69,7 @@ The MCP server failed to initialize. This prevents the server from starting.
|
|
|
70
69
|
Please ensure your MCP JSON configuration is correctly set up.
|
|
71
70
|
You can try again or contact support@codesherlock.ai for assistance.
|
|
72
71
|
|
|
73
|
-
`),await
|
|
72
|
+
`),await n.flush(),process.exit(1)}try{ne(j),n.logInfo("All handlers registered successfully")}catch(d){let e=d instanceof Error?d.message:String(d),t=d instanceof Error?d.stack:void 0;n.logError("Failed to register tools",d instanceof Error?d:new Error(e),{errorType:"tool_registration_failure"}),process.stderr.write(`
|
|
74
73
|
\u274C Tool Registration Failed
|
|
75
74
|
|
|
76
75
|
The MCP server failed to register tools. This prevents the server from starting.
|
|
@@ -78,7 +77,7 @@ The MCP server failed to register tools. This prevents the server from starting.
|
|
|
78
77
|
Please ensure your MCP JSON configuration is correctly set up.
|
|
79
78
|
You can try again or contact support@codesherlock.ai for assistance.
|
|
80
79
|
|
|
81
|
-
`),await
|
|
80
|
+
`),await n.flush(),process.exit(1)}async function J(d){n.logInfo("MCP client disconnected",{reason:d}),E.hasActiveConnections()&&(n.logInfo("Closing WebSocket connections due to MCP client disconnection",{connectionCount:String(E.getConnectionCount())}),await E.closeAllConnections()),await n.flush()}async function Te(){let d=new fe;try{await j.connect(d),n.logInfo("Server started successfully",{transport:"stdio",timestamp:new Date().toISOString()})}catch(r){let i=r instanceof Error?r.message:String(r),a=r instanceof Error?r.stack:void 0;n.logError("Failed to connect transport",r instanceof Error?r:new Error(i),{transport:"stdio",errorType:"transport_connection_failure"}),process.stderr.write(`
|
|
82
81
|
\u274C Transport Connection Failed
|
|
83
82
|
|
|
84
83
|
The MCP server failed to connect to the transport layer. This prevents the server from starting.
|
|
@@ -86,4 +85,4 @@ The MCP server failed to connect to the transport layer. This prevents the serve
|
|
|
86
85
|
Please ensure your MCP JSON configuration is correctly set up.
|
|
87
86
|
You can try again or contact support@codesherlock.ai for assistance.
|
|
88
87
|
|
|
89
|
-
`),await
|
|
88
|
+
`),await n.flush(),process.exit(1)}let e=!1,t=async r=>{e||(e=!0,await J(r))};if(process.stdin.on("end",async()=>{await t("stdin ended (EOF) - MCP client disconnected"),process.exit(0)}),process.stdin.on("error",async r=>{n.logError("stdin error - MCP client may have disconnected",r),await t(`stdin error: ${r.message}`),process.exit(1)}),process.stdout.on("error",async r=>{n.logError("stdout error - MCP client may have disconnected",r),await t(`stdout error: ${r.message}`),process.exit(1)}),process.stderr.on("error",async r=>{n.logError("stderr error - MCP client may have disconnected",r),await t(`stderr error: ${r.message}`),process.exit(1)}),process.on("disconnect",async()=>{await t("parent process disconnected (IPC channel closed)"),process.exit(0)}),process.stdin.isTTY===!1){let r=setInterval(()=>{(process.stdin.readableEnded||!process.stdin.readable)&&(clearInterval(r),e||(t("stdin became unreadable - client may have disconnected"),process.exit(0)))},3e4);process.on("exit",()=>{clearInterval(r)})}}process.on("SIGINT",async()=>{n.logInfo("Shutting down server (SIGINT)..."),await J("SIGINT signal received"),process.exit(0)});process.on("SIGTERM",async()=>{n.logInfo("Shutting down server (SIGTERM)..."),await J("SIGTERM signal received"),process.exit(0)});Te().catch(async d=>{n.logError("Fatal error in main()",d),await n.flush(),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codesherlock/codesherlock-alpha-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
|
-
"codesherlock-mcp-server": "./build/index.js"
|
|
8
|
+
"codesherlock-mcp-server": "./build/index.js",
|
|
9
|
+
"codesherlock": "./build/cli/index.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"build",
|
|
@@ -38,9 +39,12 @@
|
|
|
38
39
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
39
40
|
"applicationinsights": "^3.12.1",
|
|
40
41
|
"archiver": "^7.0.1",
|
|
42
|
+
"chalk": "^4.1.2",
|
|
43
|
+
"commander": "^14.0.3",
|
|
41
44
|
"dotenv": "^17.2.3",
|
|
42
45
|
"form-data": "^4.0.5",
|
|
43
46
|
"jszip": "^3.10.1",
|
|
47
|
+
"keytar": "^7.9.0",
|
|
44
48
|
"simple-git": "^3.30.0",
|
|
45
49
|
"ws": "^8.18.3",
|
|
46
50
|
"zod": "^3.24.1",
|
|
@@ -57,4 +61,4 @@
|
|
|
57
61
|
"tsx": "^4.19.2",
|
|
58
62
|
"typescript": "^5.7.2"
|
|
59
63
|
}
|
|
60
|
-
}
|
|
64
|
+
}
|