diff --git a/commands/security/analyze.toml b/commands/security/analyze.toml index cde5ded..d8f1eab 100644 --- a/commands/security/analyze.toml +++ b/commands/security/analyze.toml @@ -108,7 +108,8 @@ Your first action is to create a `SECURITY_ANALYSIS_TODO.md` file with the follo You will now begin executing the plan. The following are your precise instructions to start with. 1. **To complete the 'Define the audit scope' task:** - * You **MUST** use the `get_audit_scope` tool and nothing else to get a list of changed files to perform a security scan on. + * Identify if the user specified specific branches to compare (e.g. "compare main and dev"). + * You **MUST** use the `get_audit_scope` tool and nothing else to get a list of changed files to perform a security scan on. Pass the branch names as arguments if the user provided them; otherwise call it with no arguments to scan the current changes. * After using the tool, provide the user a list of changed files. If the list of files is empty, ask the user to provide files to be scanned. 2. **Immediately after defining the scope, you must refine your plan:** diff --git a/mcp-server/src/filesystem.test.ts b/mcp-server/src/filesystem.test.ts index 7af19d5..c3cd1e1 100644 --- a/mcp-server/src/filesystem.test.ts +++ b/mcp-server/src/filesystem.test.ts @@ -12,24 +12,58 @@ import * as fs from 'fs'; describe('filesystem', () => { beforeAll(() => { execSync('git init'); - execSync('git remote add origin https://github.com/gemini-testing/gemini-test-repo.git'); fs.writeFileSync('test.txt', 'hello'); execSync('git add .'); execSync('git commit -m "initial commit"'); }); afterAll(() => { - fs.unlinkSync('test.txt'); + // Cleanup created files and git repository if they exist for all tests + if (fs.existsSync('test.txt')) fs.unlinkSync('test.txt'); + if (fs.existsSync('branch-test.txt')) fs.unlinkSync('branch-test.txt'); execSync('rm -rf .git'); }); it('should return true if the directory is a github repository', () => { + // Setup: Add remote specifically for this test + execSync('git remote add origin https://github.com/gemini-testing/gemini-test-repo.git'); + expect(isGitHubRepository()).toBe(true); + + // Cleanup: Remove remote so it doesn't affect other tests + execSync('git remote remove origin'); }); - it('should return a diff of the current changes', () => { + it('should return a diff of the current changes when no branches or commits are specified', () => { fs.writeFileSync('test.txt', 'hello world'); + // Defaults to 'git diff' with remote removed const diff = getAuditScope(); expect(diff).toContain('hello world'); }); + + it('should return a diff between two specific branches', () => { + // 1. Base branch with specific content + execSync('git checkout -b pre'); + fs.writeFileSync('branch-test.txt', 'pre content'); + execSync('git add .'); + execSync('git commit -m "pre branch commit"'); + + // 2. Head branch with the content modified + execSync('git checkout -b post'); + fs.writeFileSync('branch-test.txt', 'post content'); + execSync('git add .'); + execSync('git commit -m "post branch commit"'); + + // 3. Compare them using the new arguments + const diff = getAuditScope('pre', 'post'); + + // 4. Verify the diff output + expect(diff).toContain('diff --git a/branch-test.txt b/branch-test.txt'); + // FIXED: Updated expectations to match the actual file content + expect(diff).toContain('-pre content'); + expect(diff).toContain('+post content'); + + // Cleanup by switching back to the main, so other tests aren't affected + execSync('git checkout master || git checkout main'); + }); }); diff --git a/mcp-server/src/filesystem.ts b/mcp-server/src/filesystem.ts index d3850a1..af51c3f 100644 --- a/mcp-server/src/filesystem.ts +++ b/mcp-server/src/filesystem.ts @@ -27,13 +27,28 @@ export const isGitHubRepository = (): boolean => { }; /** - * Gets a changelist of the repository + * Gets a changelist of the repository between two commits. + * Can compare between two commits, or get the diff of the working directory. + * If no commits are provided, it gets the changelist of the working directory. + * @param base The base commit branch or hash. + * @param head The head commit branch or hash. + * @returns The changelist as a string. */ -export function getAuditScope(): string { - let command = isGitHubRepository() ? 'git diff --merge-base origin/HEAD' : 'git diff'; +export function getAuditScope(base?: string, head?: string): string { + // Default to working directory diff if no commits are provided + const args: string[] = ["diff"]; + + // Add commit range if both base and head are provided + if (base !== undefined && head !== undefined) { + args.push(base, head); + } + // Otherwise, if this is a GitHub repository, use origin/HEAD as the base + else if (isGitHubRepository()) { + args.push('--merge-base', 'origin/HEAD'); + } try { const diff = ( - spawnSync('git', command.split(' ').slice(1), { + spawnSync('git', args, { encoding: 'utf-8', }).stdout || '' ).trim(); diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts index 4e85832..2ebd816 100644 --- a/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -37,10 +37,13 @@ server.tool( server.tool( 'get_audit_scope', - 'Checks if the current directory is a GitHub repository.', - {}, - () => { - const diff = getAuditScope(); + 'Gets the git diff of the current changes. Can optionally compare two specific branches.', + { + base: z.string().optional().describe('The base branch or commit hash (e.g., "main").'), + head: z.string().optional().describe('The head branch or commit hash (e.g., "feature-branch").'), + } as any, + ((args: { base?: string; head?: string }) => { + const diff = getAuditScope(args.base, args.head); return { content: [ { @@ -49,7 +52,7 @@ server.tool( }, ], }; - } + }) as any ); server.tool(