@codesherlock/codesherlock-alpha-mcp-server 0.4.1 → 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 +23 -23
- package/package.json +3 -3
package/build/cli/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as
|
|
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
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
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
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
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
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
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 oe from"ws";import ae from"jszip";import{File as ce,Blob as le}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)})}},P=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 ae;i.file("files.json",e.files_json);let c=await i.generateAsync({type:"nodebuffer"}),o=new le([c],{type:"application/zip"});n.append("files_zip",new ce([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{P.cleanupStaleConnections();let r=P.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 oe(l);a.on("open",()=>{s.logInfo("WebSocket connection opened",{userId:e}),P.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 J 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===J.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!==J.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 b;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=((b=v.content.file_name)==null?void 0:b.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,b)=>{d&&clearTimeout(d);let v=b.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.
|
|
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
10
|
|
|
11
11
|
To get an API key:
|
|
12
12
|
1. Visit https://codesherlock.ai/login and sign up or log in.
|
|
@@ -16,38 +16,38 @@ To get an API key:
|
|
|
16
16
|
Usage instructions:
|
|
17
17
|
See the MCP integration guide at https://docs.codesherlock.ai/codesherlock-mcp-server/mcp/setup/guide.
|
|
18
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 ge from"fs";async function K(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}),!ge.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 b;try{await t.send("smart_analysis_intro",4,10),t.startTipTimer(),b=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(!b.success){let y=x(new Error(b.error||"Unknown error"));return s.logError("Commit review failed",new Error(b.error||"Unknown error"),{user_id:a,commit_id:p}),{success:!1,error:b.error||y.userMessage,errorType:y.errorType,statusCode:y.statusCode,errorDetails:{userMessage:y.userMessage,technicalDetails:b.error||y.technicalDetails,retryable:y.retryable}}}let v=(b.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:b.analysisId||"none",resultCount:String(((g=b.results)==null?void 0:g.length)||0),issueCount:String(v)}),{success:!0,analysisId:b.analysisId,results:b.results,message:b.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 P.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 H from"path";var M=class{filePath;constructor(e){this.filePath=e??H.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 ue from"chalk";var B=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`${ue.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}
|
|
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
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
|
|
22
|
-
`).map(t=>
|
|
23
|
-
`)}function
|
|
24
|
-
`)}function
|
|
25
|
-
`),process.stdout.write(
|
|
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
26
|
`),process.stdout.write(R+`
|
|
27
27
|
|
|
28
|
-
`);let e=0;for(let n of f)try{process.stdout.write(
|
|
29
|
-
`),process.stdout.write(
|
|
30
|
-
`);let i=Array.isArray(n.analysis)?n.analysis:[];for(let c of i){if(!c)continue;process.stdout.write(
|
|
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
31
|
${c.characteristic}`)+`
|
|
32
|
-
`),c.description_of_characteristic&&process.stdout.write(
|
|
32
|
+
`),c.description_of_characteristic&&process.stdout.write(b.gray(` ${c.description_of_characteristic}`)+`
|
|
33
33
|
`);let o=c.issue_items??[];for(let r of o)process.stdout.write(`
|
|
34
|
-
`+
|
|
34
|
+
`+ye(r,n.language??"")+`
|
|
35
35
|
`),e++}process.stdout.write(`
|
|
36
|
-
`)}catch(i){process.stderr.write(
|
|
36
|
+
`)}catch(i){process.stderr.write(b.red(`Error rendering analysis for file ${n.file_name}: ${String(i)}
|
|
37
37
|
`))}process.stdout.write(R+`
|
|
38
|
-
`);let t=e===1?" 1 issue found.":` ${e} issues found.`;process.stdout.write(
|
|
38
|
+
`);let t=e===1?" 1 issue found.":` ${e} issues found.`;process.stdout.write(b.bold(t)+`
|
|
39
39
|
`),process.stdout.write(R+`
|
|
40
|
-
`)}function
|
|
41
|
-
`),process.stdout.write(
|
|
40
|
+
`)}function Y(){process.stdout.write(R+`
|
|
41
|
+
`),process.stdout.write(b.bold.white(" CodeSherlock Analysis Results")+`
|
|
42
42
|
`),process.stdout.write(R+`
|
|
43
43
|
|
|
44
|
-
`),process.stdout.write(
|
|
44
|
+
`),process.stdout.write(b.green(" No issues found.")+`
|
|
45
45
|
|
|
46
46
|
`),process.stdout.write(R+`
|
|
47
|
-
`)}import
|
|
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
48
|
`)}catch{process.stderr.write(`Error: Unable to securely save API key in the OS keychain.
|
|
49
49
|
Please try again after enabling OS keychain access on this machine.
|
|
50
|
-
`),process.exit(1)}}async function
|
|
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
51
|
To get an API key:
|
|
52
52
|
1. Visit https://codesherlock.ai/login and sign up or log in.
|
|
53
53
|
2. Navigate to the MCP API Keys page: https://codesherlock.ai/codesherlock-mcp-server/mcp/api/key.
|
|
@@ -58,7 +58,7 @@ See the MCP integration guide at https://docs.codesherlock.ai/codesherlock-mcp-s
|
|
|
58
58
|
|
|
59
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
60
|
Add BACKEND_API_URL=https://api.codesherlock.ai to the .env file.
|
|
61
|
-
`),process.exit(1);return}try{await s.initialize(
|
|
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
62
|
`),process.exit(1);return}f.output==="json"?process.stdout.write(JSON.stringify(d.results??[],null,2)+`
|
|
63
|
-
`):d.results&&d.results.length>0?
|
|
64
|
-
`),process.exit(1)});export{
|
|
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/package.json
CHANGED
|
@@ -1,12 +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",
|
|
9
|
-
"codesherlock": "build/cli/index.js"
|
|
8
|
+
"codesherlock-mcp-server": "./build/index.js",
|
|
9
|
+
"codesherlock": "./build/cli/index.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"build",
|