@@ -26,7 +26,36 @@ function runGitCommand(repoPath: string, command: string): string {
2626 if ( ! existsSync ( fullPath ) ) {
2727 throw new Error ( `Repository path does not exist: ${ fullPath } ` ) ;
2828 }
29- return execSync ( command , { cwd : fullPath , encoding : "utf-8" } ) ;
29+ try {
30+ return execSync ( command , {
31+ cwd : fullPath ,
32+ encoding : "utf-8" ,
33+ timeout : 30000 ,
34+ maxBuffer : 10 * 1024 * 1024 // 10MB buffer for large repos
35+ } ) ;
36+ } catch ( error : any ) {
37+ throw new Error ( `Git command failed: ${ error . message } ` ) ;
38+ }
39+ }
40+
41+ function validateDate ( date : string , fieldName : string ) : void {
42+ if ( ! / ^ \d { 4 } - \d { 2 } - \d { 2 } $ / . test ( date ) ) {
43+ throw new Error ( `Invalid ${ fieldName } format. Use YYYY-MM-DD (e.g., 2025-11-21)` ) ;
44+ }
45+ }
46+
47+ function validateRepoPath ( repoPath : string ) : void {
48+ if ( ! repoPath || typeof repoPath !== 'string' ) {
49+ throw new Error ( 'repo_path is required and must be a string' ) ;
50+ }
51+ const fullPath = resolve ( repoPath ) ;
52+ if ( ! existsSync ( fullPath ) ) {
53+ throw new Error ( `Repository path does not exist: ${ fullPath } ` ) ;
54+ }
55+ const gitPath = resolve ( fullPath , '.git' ) ;
56+ if ( ! existsSync ( gitPath ) ) {
57+ throw new Error ( `Not a git repository: ${ fullPath } ` ) ;
58+ }
3059}
3160
3261function parseCommitData ( output : string ) {
@@ -190,13 +219,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
190219
191220 if ( request . params . name === "get_commit_stats" ) {
192221 const { repo_path, since, until, author } = args ;
222+
223+ validateRepoPath ( repo_path ) ;
224+ validateDate ( since , "since" ) ;
225+ if ( until ) validateDate ( until , "until" ) ;
226+
193227 let cmd = `git log --since="${ since } "` ;
194228 if ( until ) cmd += ` --until="${ until } "` ;
195229 if ( author ) cmd += ` --author="${ author } "` ;
196230 cmd += ` --pretty=format:"%H|%an|%ae|%ad|%s" --date=short --numstat` ;
197231
198232 const output = runGitCommand ( repo_path , cmd ) ;
199- const lines = output . trim ( ) . split ( "\n" ) ;
233+ const lines = output . trim ( ) . split ( "\n" ) . slice ( 0 , 10000 ) ; // Limit to 10k lines
200234
201235 let commits = 0 , additions = 0 , deletions = 0 , filesChanged = 0 ;
202236
@@ -226,12 +260,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
226260
227261 if ( request . params . name === "get_author_metrics" ) {
228262 const { repo_path, since, until } = args ;
263+
264+ validateRepoPath ( repo_path ) ;
265+ validateDate ( since , "since" ) ;
266+ if ( until ) validateDate ( until , "until" ) ;
267+
229268 let cmd = `git log --since="${ since } "` ;
230269 if ( until ) cmd += ` --until="${ until } "` ;
231270 cmd += ` --pretty=format:"%an|%ae" --numstat` ;
232271
233272 const output = runGitCommand ( repo_path , cmd ) ;
234- const lines = output . trim ( ) . split ( "\n" ) ;
273+ const lines = output . trim ( ) . split ( "\n" ) . slice ( 0 , 10000 ) ;
235274
236275 const authorStats : Record < string , { commits : number ; additions: number ; deletions: number ; files: number } > = { } ;
237276 let currentAuthor = "" ;
@@ -259,10 +298,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
259298
260299 if ( request . params . name === "get_file_churn" ) {
261300 const { repo_path , since , limit = 10 } = args ;
301+
302+ validateRepoPath ( repo_path ) ;
303+ validateDate ( since , "since" ) ;
304+
262305 const cmd = `git log --since="${ since } " --name-only --pretty=format:` ;
263306
264307 const output = runGitCommand ( repo_path , cmd ) ;
265- const files = output . trim ( ) . split ( "\n" ) . filter ( f => f ) ;
308+ const files = output . trim ( ) . split ( "\n" ) . filter ( f => f ) . slice ( 0 , 10000 ) ;
266309
267310 const fileCount : Record < string , number > = { } ;
268311 for ( const file of files ) {
@@ -271,7 +314,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
271314
272315 const sorted = Object . entries ( fileCount )
273316 . sort ( ( [ , a ] , [ , b ] ) => b - a )
274- . slice ( 0 , limit )
317+ . slice ( 0 , Math . min ( limit , 100 ) ) // Max 100 files
275318 . map ( ( [ file , changes ] ) => ( { file, changes } ) ) ;
276319
277320 return {
@@ -282,12 +325,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
282325 if ( request . params . name === "get_team_summary" ) {
283326 const { repo_path, since, until } = args ;
284327
328+ validateRepoPath ( repo_path ) ;
329+ validateDate ( since , "since" ) ;
330+ if ( until ) validateDate ( until , "until" ) ;
331+
285332 let statsCmd = `git log --since="${ since } "` ;
286333 if ( until ) statsCmd += ` --until="${ until } "` ;
287334 statsCmd += ` --pretty=format:"%an|%ae" --numstat` ;
288335
289336 const output = runGitCommand ( repo_path , statsCmd ) ;
290- const lines = output . trim ( ) . split ( "\n" ) ;
337+ const lines = output . trim ( ) . split ( "\n" ) . slice ( 0 , 10000 ) ;
291338
292339 const authorStats : Record < string , any > = { } ;
293340 let currentAuthor = "" ;
@@ -331,12 +378,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
331378
332379 if ( request . params . name === "get_commit_patterns" ) {
333380 const { repo_path, since, until } = args ;
381+
382+ validateRepoPath ( repo_path ) ;
383+ validateDate ( since , "since" ) ;
384+ if ( until ) validateDate ( until , "until" ) ;
385+
334386 let cmd = `git log --since="${ since } "` ;
335387 if ( until ) cmd += ` --until="${ until } "` ;
336388 cmd += ` --pretty=format:"%ad" --date=format:"%u|%H"` ;
337389
338390 const output = runGitCommand ( repo_path , cmd ) ;
339- const lines = output . trim ( ) . split ( "\n" ) ;
391+ const lines = output . trim ( ) . split ( "\n" ) . slice ( 0 , 10000 ) ;
340392
341393 const byDay : Record < string , number > = { "1" : 0 , "2" : 0 , "3" : 0 , "4" : 0 , "5" : 0 , "6" : 0 , "7" : 0 } ;
342394 const byHour : Record < string , number > = { } ;
@@ -374,10 +426,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
374426
375427 if ( request . params . name === "get_code_ownership" ) {
376428 const { repo_path, since } = args ;
429+
430+ validateRepoPath ( repo_path ) ;
431+ validateDate ( since , "since" ) ;
432+
377433 const cmd = `git log --since="${ since } " --pretty=format:"%an|%ae" --name-only` ;
378434
379435 const output = runGitCommand ( repo_path , cmd ) ;
380- const lines = output . trim ( ) . split ( "\n" ) ;
436+ const lines = output . trim ( ) . split ( "\n" ) . slice ( 0 , 10000 ) ;
381437
382438 const fileAuthors : Record < string , Set < string > > = { } ;
383439 let currentAuthor = "";
0 commit comments