#
tokens: 48006/50000 31/34 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/sheshiyer/git-mcp-v2?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── jest.config.js
├── package.json
├── README.md
├── src
│   ├── caching
│   │   ├── cache.ts
│   │   └── repository-cache.ts
│   ├── common
│   │   └── command-builder.ts
│   ├── errors
│   │   ├── error-handler.ts
│   │   └── error-types.ts
│   ├── git-operations.ts
│   ├── index.ts
│   ├── monitoring
│   │   ├── performance.ts
│   │   └── types.ts
│   ├── operations
│   │   ├── base
│   │   │   ├── base-operation.ts
│   │   │   └── operation-result.ts
│   │   ├── branch
│   │   │   ├── branch-operations.ts
│   │   │   └── branch-types.ts
│   │   ├── remote
│   │   │   ├── remote-operations.ts
│   │   │   └── remote-types.ts
│   │   ├── repository
│   │   │   └── repository-operations.ts
│   │   ├── sync
│   │   │   ├── sync-operations.ts
│   │   │   └── sync-types.ts
│   │   ├── tag
│   │   │   ├── tag-operations.ts
│   │   │   └── tag-types.ts
│   │   └── working-tree
│   │       ├── working-tree-operations.ts
│   │       └── working-tree-types.ts
│   ├── tool-handler.ts
│   ├── types.ts
│   └── utils
│       ├── command.ts
│       ├── logger.ts
│       ├── path.ts
│       ├── paths.ts
│       └── repository.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | package-lock.json
 4 | 
 5 | # Build output
 6 | build/
 7 | dist/
 8 | *.tsbuildinfo
 9 | 
10 | # Environment variables
11 | .env
12 | .env.local
13 | .env.*.local
14 | 
15 | # IDE
16 | .vscode/
17 | .idea/
18 | *.swp
19 | *.swo
20 | 
21 | # Logs
22 | logs/
23 | *.log
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 | 
28 | # System files
29 | .DS_Store
30 | Thumbs.db
31 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Git MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server that provides enhanced Git operations through a standardized interface. This server integrates with the MCP ecosystem to provide Git functionality to AI assistants.
  4 | 
  5 | ## Features
  6 | 
  7 | - **Core Git Operations**: init, clone, status, add, commit, push, pull
  8 | - **Branch Management**: list, create, delete, checkout
  9 | - **Tag Operations**: list, create, delete
 10 | - **Remote Management**: list, add, remove
 11 | - **Stash Operations**: list, save, pop
 12 | - **Bulk Actions**: Execute multiple Git operations in sequence
 13 | - **GitHub Integration**: Built-in GitHub support via Personal Access Token
 14 | - **Path Resolution**: Smart path handling with optional default path configuration
 15 | - **Error Handling**: Comprehensive error handling with custom error types
 16 | - **Repository Caching**: Efficient repository state management
 17 | - **Performance Monitoring**: Built-in performance tracking
 18 | 
 19 | ## Installation
 20 | 
 21 | 1. Clone the repository:
 22 | ```bash
 23 | git clone https://github.com/yourusername/git-mcp-v2.git
 24 | cd git-mcp-v2
 25 | ```
 26 | 
 27 | 2. Install dependencies:
 28 | ```bash
 29 | npm install
 30 | ```
 31 | 
 32 | 3. Build the project:
 33 | ```bash
 34 | npm run build
 35 | ```
 36 | 
 37 | ## Configuration
 38 | 
 39 | Add to your MCP settings file:
 40 | 
 41 | ```json
 42 | {
 43 |   "mcpServers": {
 44 |     "git-v2": {
 45 |       "command": "node",
 46 |       "args": ["path/to/git-mcp-v2/build/index.js"],
 47 |       "env": {
 48 |         "GIT_DEFAULT_PATH": "/path/to/default/git/directory",
 49 |         "GITHUB_PERSONAL_ACCESS_TOKEN": "your-github-pat"
 50 |       }
 51 |     }
 52 |   }
 53 | }
 54 | ```
 55 | 
 56 | ## Environment Variables
 57 | 
 58 | - `GIT_DEFAULT_PATH`: (Optional) Default path for Git operations
 59 | - `GITHUB_PERSONAL_ACCESS_TOKEN`: (Optional) GitHub Personal Access Token for GitHub operations
 60 | 
 61 | ## Available Tools
 62 | 
 63 | ### Basic Operations
 64 | - `init`: Initialize a new Git repository
 65 | - `clone`: Clone a repository
 66 | - `status`: Get repository status
 67 | - `add`: Stage files
 68 | - `commit`: Create a commit
 69 | - `push`: Push commits to remote
 70 | - `pull`: Pull changes from remote
 71 | 
 72 | ### Branch Operations
 73 | - `branch_list`: List all branches
 74 | - `branch_create`: Create a new branch
 75 | - `branch_delete`: Delete a branch
 76 | - `checkout`: Switch branches or restore working tree files
 77 | 
 78 | ### Tag Operations
 79 | - `tag_list`: List tags
 80 | - `tag_create`: Create a tag
 81 | - `tag_delete`: Delete a tag
 82 | 
 83 | ### Remote Operations
 84 | - `remote_list`: List remotes
 85 | - `remote_add`: Add a remote
 86 | - `remote_remove`: Remove a remote
 87 | 
 88 | ### Stash Operations
 89 | - `stash_list`: List stashes
 90 | - `stash_save`: Save changes to stash
 91 | - `stash_pop`: Apply and remove a stash
 92 | 
 93 | ### Bulk Operations
 94 | - `bulk_action`: Execute multiple Git operations in sequence
 95 | 
 96 | ## Development
 97 | 
 98 | ```bash
 99 | # Run tests
100 | npm test
101 | 
102 | # Run tests with coverage
103 | npm run test:coverage
104 | 
105 | # Run linter
106 | npm run lint
107 | 
108 | # Format code
109 | npm run format
110 | ```
111 | 
112 | ## License
113 | 
114 | MIT
115 | 
116 | ## Contributing
117 | 
118 | 1. Fork the repository
119 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
120 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
121 | 4. Push to the branch (`git push origin feature/amazing-feature`)
122 | 5. Open a Pull Request
123 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
 2 | export default {
 3 |   preset: 'ts-jest',
 4 |   testEnvironment: 'node',
 5 |   extensionsToTreatAsEsm: ['.ts'],
 6 |   moduleNameMapper: {
 7 |     '^(\\.{1,2}/.*)\\.js$': '$1',
 8 |   },
 9 |   transform: {
10 |     '^.+\\.tsx?$': [
11 |       'ts-jest',
12 |       {
13 |         useESM: true,
14 |       },
15 |     ],
16 |   },
17 |   coverageDirectory: 'coverage',
18 |   collectCoverageFrom: [
19 |     'src/**/*.ts',
20 |     '!src/**/*.d.ts',
21 |     '!src/**/index.ts',
22 |     '!src/**/*.types.ts'
23 |   ],
24 |   coverageThreshold: {
25 |     global: {
26 |       branches: 80,
27 |       functions: 80,
28 |       lines: 80,
29 |       statements: 80
30 |     }
31 |   },
32 |   testMatch: [
33 |     '<rootDir>/tests/**/*.test.ts'
34 |   ],
35 |   setupFilesAfterEnv: [
36 |     '<rootDir>/tests/setup.ts'
37 |   ],
38 |   testPathIgnorePatterns: [
39 |     '/node_modules/',
40 |     '/build/'
41 |   ],
42 |   verbose: true,
43 |   testTimeout: 10000
44 | };
45 | 
```

--------------------------------------------------------------------------------
/src/monitoring/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { GitMcpError } from '../errors/error-types.js';
 2 | import { ErrorCategory, ErrorSeverity } from '../errors/error-types.js';
 3 | import { ErrorCode } from '@modelcontextprotocol/sdk/types.js';
 4 | 
 5 | /**
 6 |  * Performance monitoring types
 7 |  */
 8 | 
 9 | /**
10 |  * Performance error context
11 |  */
12 | export interface PerformanceErrorContext {
13 |   currentUsage?: number;
14 |   threshold?: number;
15 |   operation?: string;
16 |   details?: Record<string, any>;
17 |   [key: string]: unknown;  // Index signature for additional properties
18 | }
19 | 
20 | /**
21 |  * Performance error with context
22 |  */
23 | export class PerformanceError extends GitMcpError {
24 |   constructor(
25 |     message: string,
26 |     context: PerformanceErrorContext
27 |   ) {
28 |     super(
29 |       ErrorCode.InternalError,
30 |       message,
31 |       ErrorSeverity.HIGH,
32 |       ErrorCategory.SYSTEM,
33 |       {
34 |         operation: context.operation || 'performance',
35 |         timestamp: Date.now(),
36 |         severity: ErrorSeverity.HIGH,
37 |         category: ErrorCategory.SYSTEM,
38 |         details: context
39 |       }
40 |     );
41 |     this.name = 'PerformanceError';
42 |   }
43 | }
44 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "git-mcp-server",
 3 |   "version": "1.0.0",
 4 |   "description": "A Model Context Protocol server",
 5 |   "private": true,
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "git-mcp-server": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build"
12 |   ],
13 |   "scripts": {
14 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 |     "prepare": "npm run build",
16 |     "watch": "tsc --watch",
17 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js",
18 |     "test": "jest",
19 |     "test:watch": "jest --watch",
20 |     "test:coverage": "jest --coverage",
21 |     "lint": "eslint src --ext .ts",
22 |     "format": "prettier --write \"src/**/*.ts\"",
23 |     "clean": "rimraf build coverage"
24 |   },
25 |   "dependencies": {
26 |     "@modelcontextprotocol/sdk": "1.0.4",
27 |     "simple-git": "^3.27.0"
28 |   },
29 |   "devDependencies": {
30 |     "@types/jest": "^29.5.11",
31 |     "@types/node": "^20.17.10",
32 |     "@typescript-eslint/eslint-plugin": "^6.19.0",
33 |     "@typescript-eslint/parser": "^6.19.0",
34 |     "eslint": "^8.56.0",
35 |     "eslint-config-prettier": "^9.1.0",
36 |     "eslint-plugin-jest": "^27.6.3",
37 |     "jest": "^29.7.0",
38 |     "prettier": "^3.2.4",
39 |     "rimraf": "^5.0.5",
40 |     "ts-jest": "^29.1.1",
41 |     "typescript": "^5.3.3"
42 |   }
43 | }
44 | 
```

--------------------------------------------------------------------------------
/src/operations/base/operation-result.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { GitToolContent } from '../../types.js';
 2 | import { GitMcpError } from '../../errors/error-types.js';
 3 | 
 4 | /**
 5 |  * Represents the result of a Git operation with proper type safety
 6 |  */
 7 | export interface GitOperationResult<T = void> {
 8 |   /** Whether the operation was successful */
 9 |   success: boolean;
10 |   
11 |   /** Operation-specific data if successful */
12 |   data?: T;
13 |   
14 |   /** Error information if operation failed */
15 |   error?: GitMcpError;
16 |   
17 |   /** Standard MCP tool response content */
18 |   content: GitToolContent[];
19 |   
20 |   /** Additional metadata about the operation */
21 |   meta?: Record<string, unknown>;
22 | }
23 | 
24 | /**
25 |  * Base interface for all Git operation options
26 |  */
27 | export interface GitOperationOptions {
28 |   /** Operation path override */
29 |   path?: string;
30 |   
31 |   /** Whether to use caching */
32 |   useCache?: boolean;
33 |   
34 |   /** Whether to invalidate cache after operation */
35 |   invalidateCache?: boolean;
36 | }
37 | 
38 | /**
39 |  * Common result types for Git operations
40 |  */
41 | import { CommandResult as BaseCommandResult } from '../../utils/command.js';
42 | 
43 | export interface CommandResult extends BaseCommandResult {
44 |   // Extend the base command result with any additional fields we need
45 | }
46 | 
47 | export interface ListResult {
48 |   items: string[];
49 |   raw: string;
50 | }
51 | 
52 | export interface StatusResult {
53 |   staged: string[];
54 |   unstaged: string[];
55 |   untracked: string[];
56 |   raw: string;
57 | }
58 | 
59 | export interface BranchResult {
60 |   current: string;
61 |   branches: string[];
62 |   raw: string;
63 | }
64 | 
65 | export interface TagResult {
66 |   tags: string[];
67 |   raw: string;
68 | }
69 | 
70 | export interface RemoteResult {
71 |   remotes: Array<{
72 |     name: string;
73 |     url: string;
74 |     purpose: 'fetch' | 'push';
75 |   }>;
76 |   raw: string;
77 | }
78 | 
79 | export interface StashResult {
80 |   stashes: Array<{
81 |     index: number;
82 |     message: string;
83 |   }>;
84 |   raw: string;
85 | }
86 | 
```

--------------------------------------------------------------------------------
/src/operations/tag/tag-types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { GitOperationOptions } from '../base/operation-result.js';
  2 | 
  3 | /**
  4 |  * Options for listing tags
  5 |  */
  6 | export interface TagListOptions extends GitOperationOptions {
  7 |   /** Show tag message */
  8 |   showMessage?: boolean;
  9 |   /** Sort tags by specific key */
 10 |   sort?: 'version' | 'creatordate' | 'taggerdate';
 11 |   /** Show only tags containing the specified commit */
 12 |   contains?: string;
 13 |   /** Match tags with pattern */
 14 |   pattern?: string;
 15 | }
 16 | 
 17 | /**
 18 |  * Options for creating tags
 19 |  */
 20 | export interface TagCreateOptions extends GitOperationOptions {
 21 |   /** Name of the tag to create */
 22 |   name: string;
 23 |   /** Tag message (creates annotated tag) */
 24 |   message?: string;
 25 |   /** Whether to force create even if tag exists */
 26 |   force?: boolean;
 27 |   /** Create a signed tag */
 28 |   sign?: boolean;
 29 |   /** Specific commit to tag */
 30 |   commit?: string;
 31 | }
 32 | 
 33 | /**
 34 |  * Options for deleting tags
 35 |  */
 36 | export interface TagDeleteOptions extends GitOperationOptions {
 37 |   /** Name of the tag to delete */
 38 |   name: string;
 39 |   /** Whether to force delete */
 40 |   force?: boolean;
 41 |   /** Also delete the tag from remotes */
 42 |   remote?: boolean;
 43 | }
 44 | 
 45 | /**
 46 |  * Structured tag information
 47 |  */
 48 | export interface TagInfo {
 49 |   /** Tag name */
 50 |   name: string;
 51 |   /** Whether this is an annotated tag */
 52 |   annotated: boolean;
 53 |   /** Tag message if annotated */
 54 |   message?: string;
 55 |   /** Tagger information if annotated */
 56 |   tagger?: {
 57 |     name: string;
 58 |     email: string;
 59 |     date: string;
 60 |   };
 61 |   /** Commit that is tagged */
 62 |   commit: string;
 63 |   /** Whether this is a signed tag */
 64 |   signed: boolean;
 65 | }
 66 | 
 67 | /**
 68 |  * Result of tag listing operation
 69 |  */
 70 | export interface TagListResult {
 71 |   /** List of all tags */
 72 |   tags: TagInfo[];
 73 |   /** Raw command output */
 74 |   raw: string;
 75 | }
 76 | 
 77 | /**
 78 |  * Result of tag creation operation
 79 |  */
 80 | export interface TagCreateResult {
 81 |   /** Name of created tag */
 82 |   name: string;
 83 |   /** Whether it's an annotated tag */
 84 |   annotated: boolean;
 85 |   /** Whether it's signed */
 86 |   signed: boolean;
 87 |   /** Tagged commit */
 88 |   commit?: string;
 89 |   /** Raw command output */
 90 |   raw: string;
 91 | }
 92 | 
 93 | /**
 94 |  * Result of tag deletion operation
 95 |  */
 96 | export interface TagDeleteResult {
 97 |   /** Name of deleted tag */
 98 |   name: string;
 99 |   /** Whether it was force deleted */
100 |   forced: boolean;
101 |   /** Raw command output */
102 |   raw: string;
103 | }
104 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
 4 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
 5 | import { ToolHandler } from './tool-handler.js';
 6 | import { logger } from './utils/logger.js';
 7 | import { CommandExecutor } from './utils/command.js';
 8 | import { PathResolver } from './utils/paths.js';
 9 | 
10 | async function validateDefaultPath(): Promise<void> {
11 |   const defaultPath = process.env.GIT_DEFAULT_PATH;
12 |   if (!defaultPath) {
13 |     logger.warn('startup', 'GIT_DEFAULT_PATH not set - absolute paths will be required for all operations');
14 |     return;
15 |   }
16 | 
17 |   try {
18 |     // Validate the default path exists and is accessible
19 |     PathResolver.validatePath(defaultPath, 'startup', {
20 |       mustExist: true,
21 |       mustBeDirectory: true,
22 |       createIfMissing: true
23 |     });
24 |     logger.info('startup', 'Default git path validated', defaultPath);
25 |   } catch (error) {
26 |     logger.error('startup', 'Invalid GIT_DEFAULT_PATH', defaultPath, error as Error);
27 |     throw new McpError(
28 |       ErrorCode.InternalError,
29 |       `Invalid GIT_DEFAULT_PATH: ${(error as Error).message}`
30 |     );
31 |   }
32 | }
33 | 
34 | async function main() {
35 |   try {
36 |     // Validate git installation first
37 |     await CommandExecutor.validateGitInstallation('startup');
38 |     logger.info('startup', 'Git installation validated');
39 | 
40 |     // Validate default path if provided
41 |     await validateDefaultPath();
42 | 
43 |     // Create and configure server
44 |     const server = new Server(
45 |       {
46 |         name: 'git-mcp-server',
47 |         version: '1.0.0',
48 |       },
49 |       {
50 |         capabilities: {
51 |           tools: {},
52 |         },
53 |       }
54 |     );
55 | 
56 |     // Set up error handling
57 |     server.onerror = (error) => {
58 |       if (error instanceof McpError) {
59 |         logger.error('server', error.message, undefined, error);
60 |       } else {
61 |         logger.error('server', 'Unexpected error', undefined, error as Error);
62 |       }
63 |     };
64 | 
65 |     // Initialize tool handler
66 |     new ToolHandler(server);
67 | 
68 |     // Connect server
69 |     const transport = new StdioServerTransport();
70 |     await server.connect(transport);
71 |     logger.info('server', 'Git MCP server running on stdio');
72 | 
73 |     // Handle shutdown
74 |     process.on('SIGINT', async () => {
75 |       logger.info('server', 'Shutting down server');
76 |       await server.close();
77 |       process.exit(0);
78 |     });
79 | 
80 |   } catch (error) {
81 |     logger.error('startup', 'Failed to start server', undefined, error as Error);
82 |     process.exit(1);
83 |   }
84 | }
85 | 
86 | main().catch((error) => {
87 |   console.error('Fatal error:', error);
88 |   process.exit(1);
89 | });
90 | 
```

--------------------------------------------------------------------------------
/src/operations/remote/remote-types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { GitOperationOptions } from '../base/operation-result.js';
  2 | 
  3 | /**
  4 |  * Options for listing remotes
  5 |  */
  6 | export interface RemoteListOptions extends GitOperationOptions {
  7 |   /** Show remote URLs */
  8 |   verbose?: boolean;
  9 | }
 10 | 
 11 | /**
 12 |  * Options for adding remotes
 13 |  */
 14 | export interface RemoteAddOptions extends GitOperationOptions {
 15 |   /** Name of the remote */
 16 |   name: string;
 17 |   /** URL of the remote */
 18 |   url: string;
 19 |   /** Whether to fetch immediately */
 20 |   fetch?: boolean;
 21 |   /** Tags to fetch (--tags, --no-tags) */
 22 |   tags?: boolean;
 23 |   /** Mirror mode (--mirror=fetch or --mirror=push) */
 24 |   mirror?: 'fetch' | 'push';
 25 | }
 26 | 
 27 | /**
 28 |  * Options for removing remotes
 29 |  */
 30 | export interface RemoteRemoveOptions extends GitOperationOptions {
 31 |   /** Name of the remote */
 32 |   name: string;
 33 | }
 34 | 
 35 | /**
 36 |  * Options for updating remote URLs
 37 |  */
 38 | export interface RemoteSetUrlOptions extends GitOperationOptions {
 39 |   /** Name of the remote */
 40 |   name: string;
 41 |   /** New URL for the remote */
 42 |   url: string;
 43 |   /** Whether this is a push URL */
 44 |   pushUrl?: boolean;
 45 |   /** Add URL instead of changing existing URLs */
 46 |   add?: boolean;
 47 |   /** Delete URL instead of changing it */
 48 |   delete?: boolean;
 49 | }
 50 | 
 51 | /**
 52 |  * Options for pruning remotes
 53 |  */
 54 | export interface RemotePruneOptions extends GitOperationOptions {
 55 |   /** Name of the remote */
 56 |   name: string;
 57 |   /** Whether to show what would be done */
 58 |   dryRun?: boolean;
 59 | }
 60 | 
 61 | /**
 62 |  * Represents a remote configuration
 63 |  */
 64 | export interface RemoteConfig {
 65 |   /** Remote name */
 66 |   name: string;
 67 |   /** Fetch URL */
 68 |   fetchUrl: string;
 69 |   /** Push URL (if different from fetch) */
 70 |   pushUrl?: string;
 71 |   /** Remote branches tracked */
 72 |   branches?: string[];
 73 |   /** Whether tags are fetched */
 74 |   fetchTags?: boolean;
 75 |   /** Mirror configuration */
 76 |   mirror?: 'fetch' | 'push';
 77 | }
 78 | 
 79 | /**
 80 |  * Result of remote listing operation
 81 |  */
 82 | export interface RemoteListResult {
 83 |   /** List of remotes */
 84 |   remotes: RemoteConfig[];
 85 |   /** Raw command output */
 86 |   raw: string;
 87 | }
 88 | 
 89 | /**
 90 |  * Result of remote add operation
 91 |  */
 92 | export interface RemoteAddResult {
 93 |   /** Added remote configuration */
 94 |   remote: RemoteConfig;
 95 |   /** Raw command output */
 96 |   raw: string;
 97 | }
 98 | 
 99 | /**
100 |  * Result of remote remove operation
101 |  */
102 | export interface RemoteRemoveResult {
103 |   /** Name of removed remote */
104 |   name: string;
105 |   /** Raw command output */
106 |   raw: string;
107 | }
108 | 
109 | /**
110 |  * Result of remote set-url operation
111 |  */
112 | export interface RemoteSetUrlResult {
113 |   /** Updated remote configuration */
114 |   remote: RemoteConfig;
115 |   /** Raw command output */
116 |   raw: string;
117 | }
118 | 
119 | /**
120 |  * Result of remote prune operation
121 |  */
122 | export interface RemotePruneResult {
123 |   /** Name of pruned remote */
124 |   name: string;
125 |   /** Branches that were pruned */
126 |   prunedBranches: string[];
127 |   /** Raw command output */
128 |   raw: string;
129 | }
130 | 
```

--------------------------------------------------------------------------------
/src/operations/working-tree/working-tree-types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { GitOperationOptions } from '../base/operation-result.js';
  2 | 
  3 | /**
  4 |  * Options for adding files to staging
  5 |  */
  6 | export interface AddOptions extends GitOperationOptions {
  7 |   /** Files to stage */
  8 |   files: string[];
  9 |   /** Whether to add all files (including untracked) */
 10 |   all?: boolean;
 11 |   /** Whether to add only updates to already tracked files */
 12 |   update?: boolean;
 13 |   /** Whether to ignore removal of files */
 14 |   ignoreRemoval?: boolean;
 15 |   /** Whether to add files with errors */
 16 |   force?: boolean;
 17 |   /** Whether to only show what would be added */
 18 |   dryRun?: boolean;
 19 | }
 20 | 
 21 | /**
 22 |  * Options for committing changes
 23 |  */
 24 | export interface CommitOptions extends GitOperationOptions {
 25 |   /** Commit message */
 26 |   message: string;
 27 |   /** Whether to allow empty commits */
 28 |   allowEmpty?: boolean;
 29 |   /** Whether to amend the previous commit */
 30 |   amend?: boolean;
 31 |   /** Whether to skip pre-commit hooks */
 32 |   noVerify?: boolean;
 33 |   /** Author of the commit (in format: "Name <email>") */
 34 |   author?: string;
 35 |   /** Files to commit (if not specified, commits all staged changes) */
 36 |   files?: string[];
 37 | }
 38 | 
 39 | /**
 40 |  * Options for checking status
 41 |  */
 42 | export interface StatusOptions extends GitOperationOptions {
 43 |   /** Whether to show untracked files */
 44 |   showUntracked?: boolean;
 45 |   /** Whether to ignore submodules */
 46 |   ignoreSubmodules?: boolean;
 47 |   /** Whether to show ignored files */
 48 |   showIgnored?: boolean;
 49 |   /** Whether to show branch info */
 50 |   showBranch?: boolean;
 51 | }
 52 | 
 53 | /**
 54 |  * Represents a file change in the working tree
 55 |  */
 56 | export interface FileChange {
 57 |   /** Path of the file */
 58 |   path: string;
 59 |   /** Type of change */
 60 |   type: 'added' | 'modified' | 'deleted' | 'renamed' | 'copied' | 'untracked' | 'ignored';
 61 |   /** Original path for renamed files */
 62 |   originalPath?: string;
 63 |   /** Whether the change is staged */
 64 |   staged: boolean;
 65 |   /** Raw status code from Git */
 66 |   raw: string;
 67 | }
 68 | 
 69 | /**
 70 |  * Result of add operation
 71 |  */
 72 | export interface AddResult {
 73 |   /** Files that were staged */
 74 |   staged: string[];
 75 |   /** Files that were not staged (with reasons) */
 76 |   notStaged?: Array<{
 77 |     path: string;
 78 |     reason: string;
 79 |   }>;
 80 |   /** Raw command output */
 81 |   raw: string;
 82 | }
 83 | 
 84 | /**
 85 |  * Result of commit operation
 86 |  */
 87 | export interface CommitResult {
 88 |   /** Commit hash */
 89 |   hash: string;
 90 |   /** Number of files changed */
 91 |   filesChanged: number;
 92 |   /** Number of insertions */
 93 |   insertions: number;
 94 |   /** Number of deletions */
 95 |   deletions: number;
 96 |   /** Whether it was an amend */
 97 |   amended: boolean;
 98 |   /** Raw command output */
 99 |   raw: string;
100 | }
101 | 
102 | /**
103 |  * Result of status operation
104 |  */
105 | export interface StatusResult {
106 |   /** Current branch name */
107 |   branch: string;
108 |   /** Whether the working tree is clean */
109 |   clean: boolean;
110 |   /** Staged changes */
111 |   staged: FileChange[];
112 |   /** Unstaged changes */
113 |   unstaged: FileChange[];
114 |   /** Untracked files */
115 |   untracked: FileChange[];
116 |   /** Ignored files (if requested) */
117 |   ignored?: FileChange[];
118 |   /** Raw command output */
119 |   raw: string;
120 | }
121 | 
```

--------------------------------------------------------------------------------
/src/operations/branch/branch-types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { GitOperationOptions } from '../base/operation-result.js';
  2 | 
  3 | /**
  4 |  * Options for listing branches
  5 |  */
  6 | export interface BranchListOptions extends GitOperationOptions {
  7 |   /** Show remote branches */
  8 |   remotes?: boolean;
  9 |   /** Show all branches (local and remote) */
 10 |   all?: boolean;
 11 |   /** Show only branches containing the specified commit */
 12 |   contains?: string;
 13 |   /** Show only branches merged into the specified commit */
 14 |   merged?: string;
 15 |   /** Show only branches not merged into the specified commit */
 16 |   noMerged?: string;
 17 | }
 18 | 
 19 | /**
 20 |  * Options for creating branches
 21 |  */
 22 | export interface BranchCreateOptions extends GitOperationOptions {
 23 |   /** Name of the branch to create */
 24 |   name: string;
 25 |   /** Whether to force create even if branch exists */
 26 |   force?: boolean;
 27 |   /** Set up tracking mode (true = --track, false = --no-track) */
 28 |   track?: boolean;
 29 |   /** Set upstream for push/pull */
 30 |   setUpstream?: boolean;
 31 |   /** Start point (commit/branch) for the new branch */
 32 |   startPoint?: string;
 33 | }
 34 | 
 35 | /**
 36 |  * Options for deleting branches
 37 |  */
 38 | export interface BranchDeleteOptions extends GitOperationOptions {
 39 |   /** Name of the branch to delete */
 40 |   name: string;
 41 |   /** Whether to force delete even if not merged */
 42 |   force?: boolean;
 43 |   /** Also delete the branch from remotes */
 44 |   remote?: boolean;
 45 | }
 46 | 
 47 | /**
 48 |  * Options for checking out branches
 49 |  */
 50 | export interface CheckoutOptions extends GitOperationOptions {
 51 |   /** Branch/commit/tag to check out */
 52 |   target: string;
 53 |   /** Whether to force checkout even with local changes */
 54 |   force?: boolean;
 55 |   /** Create a new branch and check it out */
 56 |   newBranch?: string;
 57 |   /** Track the remote branch */
 58 |   track?: boolean;
 59 | }
 60 | 
 61 | /**
 62 |  * Structured branch information
 63 |  */
 64 | export interface BranchInfo {
 65 |   /** Branch name */
 66 |   name: string;
 67 |   /** Whether this is the current branch */
 68 |   current: boolean;
 69 |   /** Remote tracking branch if any */
 70 |   tracking?: string;
 71 |   /** Whether the branch is ahead/behind tracking branch */
 72 |   status?: {
 73 |     ahead: number;
 74 |     behind: number;
 75 |   };
 76 |   /** Whether this is a remote branch */
 77 |   remote: boolean;
 78 |   /** Latest commit hash */
 79 |   commit?: string;
 80 |   /** Latest commit message */
 81 |   message?: string;
 82 | }
 83 | 
 84 | /**
 85 |  * Result of branch listing operation
 86 |  */
 87 | export interface BranchListResult {
 88 |   /** Current branch name */
 89 |   current: string;
 90 |   /** List of all branches */
 91 |   branches: BranchInfo[];
 92 |   /** Raw command output */
 93 |   raw: string;
 94 | }
 95 | 
 96 | /**
 97 |  * Result of branch creation operation
 98 |  */
 99 | export interface BranchCreateResult {
100 |   /** Name of created branch */
101 |   name: string;
102 |   /** Starting point of the branch */
103 |   startPoint?: string;
104 |   /** Whether tracking was set up */
105 |   tracking?: string;
106 |   /** Raw command output */
107 |   raw: string;
108 | }
109 | 
110 | /**
111 |  * Result of branch deletion operation
112 |  */
113 | export interface BranchDeleteResult {
114 |   /** Name of deleted branch */
115 |   name: string;
116 |   /** Whether it was force deleted */
117 |   forced: boolean;
118 |   /** Raw command output */
119 |   raw: string;
120 | }
121 | 
122 | /**
123 |  * Result of checkout operation
124 |  */
125 | export interface CheckoutResult {
126 |   /** Target that was checked out */
127 |   target: string;
128 |   /** Whether a new branch was created */
129 |   newBranch?: string;
130 |   /** Previous HEAD position */
131 |   previousHead?: string;
132 |   /** Raw command output */
133 |   raw: string;
134 | }
135 | 
```

--------------------------------------------------------------------------------
/src/operations/sync/sync-types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { GitOperationOptions } from '../base/operation-result.js';
  2 | 
  3 | /**
  4 |  * Options for push operations
  5 |  */
  6 | export interface PushOptions extends GitOperationOptions {
  7 |   /** Remote to push to */
  8 |   remote?: string;
  9 |   /** Branch to push */
 10 |   branch: string;
 11 |   /** Whether to force push */
 12 |   force?: boolean;
 13 |   /** Whether to force push with lease */
 14 |   forceWithLease?: boolean;
 15 |   /** Whether to push all branches */
 16 |   all?: boolean;
 17 |   /** Whether to push tags */
 18 |   tags?: boolean;
 19 |   /** Whether to skip pre-push hooks */
 20 |   noVerify?: boolean;
 21 |   /** Whether to set upstream for branch */
 22 |   setUpstream?: boolean;
 23 |   /** Whether to delete remote branches that were deleted locally */
 24 |   prune?: boolean;
 25 | }
 26 | 
 27 | /**
 28 |  * Options for pull operations
 29 |  */
 30 | export interface PullOptions extends GitOperationOptions {
 31 |   /** Remote to pull from */
 32 |   remote?: string;
 33 |   /** Branch to pull */
 34 |   branch: string;
 35 |   /** Whether to rebase instead of merge */
 36 |   rebase?: boolean;
 37 |   /** Whether to automatically stash/unstash changes */
 38 |   autoStash?: boolean;
 39 |   /** Whether to allow unrelated histories */
 40 |   allowUnrelated?: boolean;
 41 |   /** Whether to fast-forward only */
 42 |   ff?: 'only' | 'no' | true;
 43 |   /** Strategy to use when merging */
 44 |   strategy?: 'recursive' | 'resolve' | 'octopus' | 'ours' | 'subtree';
 45 |   /** Strategy options */
 46 |   strategyOption?: string[];
 47 | }
 48 | 
 49 | /**
 50 |  * Options for fetch operations
 51 |  */
 52 | export interface FetchOptions extends GitOperationOptions {
 53 |   /** Remote to fetch from */
 54 |   remote?: string;
 55 |   /** Whether to fetch all remotes */
 56 |   all?: boolean;
 57 |   /** Whether to prune remote branches */
 58 |   prune?: boolean;
 59 |   /** Whether to prune tags */
 60 |   pruneTags?: boolean;
 61 |   /** Whether to fetch tags */
 62 |   tags?: boolean;
 63 |   /** Whether to fetch only tags */
 64 |   tagsOnly?: boolean;
 65 |   /** Whether to force fetch tags */
 66 |   forceTags?: boolean;
 67 |   /** Depth of history to fetch */
 68 |   depth?: number;
 69 |   /** Whether to update submodules */
 70 |   recurseSubmodules?: boolean | 'on-demand';
 71 |   /** Whether to show progress */
 72 |   progress?: boolean;
 73 | }
 74 | 
 75 | /**
 76 |  * Result of push operation
 77 |  */
 78 | export interface PushResult {
 79 |   /** Remote that was pushed to */
 80 |   remote: string;
 81 |   /** Branch that was pushed */
 82 |   branch: string;
 83 |   /** Whether force push was used */
 84 |   forced: boolean;
 85 |   /** New remote ref */
 86 |   newRef?: string;
 87 |   /** Old remote ref */
 88 |   oldRef?: string;
 89 |   /** Summary of changes */
 90 |   summary: {
 91 |     created?: string[];
 92 |     deleted?: string[];
 93 |     updated?: string[];
 94 |     rejected?: string[];
 95 |   };
 96 |   /** Raw command output */
 97 |   raw: string;
 98 | }
 99 | 
100 | /**
101 |  * Result of pull operation
102 |  */
103 | export interface PullResult {
104 |   /** Remote that was pulled from */
105 |   remote: string;
106 |   /** Branch that was pulled */
107 |   branch: string;
108 |   /** Whether rebase was used */
109 |   rebased: boolean;
110 |   /** Files changed */
111 |   filesChanged: number;
112 |   /** Number of insertions */
113 |   insertions: number;
114 |   /** Number of deletions */
115 |   deletions: number;
116 |   /** Summary of changes */
117 |   summary: {
118 |     merged?: string[];
119 |     conflicts?: string[];
120 |   };
121 |   /** Raw command output */
122 |   raw: string;
123 | }
124 | 
125 | /**
126 |  * Result of fetch operation
127 |  */
128 | export interface FetchResult {
129 |   /** Remote that was fetched from */
130 |   remote?: string;
131 |   /** Summary of changes */
132 |   summary: {
133 |     branches?: Array<{
134 |       name: string;
135 |       oldRef?: string;
136 |       newRef: string;
137 |     }>;
138 |     tags?: Array<{
139 |       name: string;
140 |       oldRef?: string;
141 |       newRef: string;
142 |     }>;
143 |     pruned?: string[];
144 |   };
145 |   /** Raw command output */
146 |   raw: string;
147 | }
148 | 
```

--------------------------------------------------------------------------------
/src/operations/repository/repository-operations.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { BaseGitOperation } from '../base/base-operation.js';
  2 | import { GitOperationOptions, CommandResult } from '../base/operation-result.js';
  3 | import { GitCommandBuilder } from '../../common/command-builder.js';
  4 | import { ErrorHandler } from '../../errors/error-handler.js';
  5 | import { PathValidator } from '../../utils/path.js';
  6 | 
  7 | /**
  8 |  * Options for repository initialization
  9 |  */
 10 | export interface InitOptions extends GitOperationOptions {
 11 |   /** Whether to create a bare repository */
 12 |   bare?: boolean;
 13 |   /** Initial branch name */
 14 |   initialBranch?: string;
 15 | }
 16 | 
 17 | /**
 18 |  * Options for repository cloning
 19 |  */
 20 | export interface CloneOptions extends GitOperationOptions {
 21 |   /** Repository URL to clone from */
 22 |   url: string;
 23 |   /** Whether to create a bare repository */
 24 |   bare?: boolean;
 25 |   /** Depth of history to clone */
 26 |   depth?: number;
 27 |   /** Branch to clone */
 28 |   branch?: string;
 29 | }
 30 | 
 31 | /**
 32 |  * Handles Git repository initialization
 33 |  */
 34 | export class InitOperation extends BaseGitOperation<InitOptions> {
 35 |   protected buildCommand(): GitCommandBuilder {
 36 |     const command = GitCommandBuilder.init();
 37 |     
 38 |     if (this.options.bare) {
 39 |       command.flag('bare');
 40 |     }
 41 |     
 42 |     if (this.options.initialBranch) {
 43 |       command.option('initial-branch', this.options.initialBranch);
 44 |     }
 45 |     
 46 |     return command;
 47 |   }
 48 | 
 49 |   protected parseResult(result: CommandResult): void {
 50 |     // Init doesn't return any structured data
 51 |   }
 52 | 
 53 |   protected getCacheConfig() {
 54 |     return {
 55 |       command: 'init'
 56 |     };
 57 |   }
 58 | 
 59 |   protected validateOptions(): void {
 60 |     const path = this.options.path || process.env.GIT_DEFAULT_PATH;
 61 |     if (!path) {
 62 |       throw ErrorHandler.handleValidationError(
 63 |         new Error('Path must be provided when GIT_DEFAULT_PATH is not set'),
 64 |         { operation: this.context.operation }
 65 |       );
 66 |     }
 67 | 
 68 |     // Validate path exists or can be created
 69 |     PathValidator.validatePath(path, {
 70 |       mustExist: false,
 71 |       allowDirectory: true
 72 |     });
 73 |   }
 74 | }
 75 | 
 76 | /**
 77 |  * Handles Git repository cloning
 78 |  */
 79 | export class CloneOperation extends BaseGitOperation<CloneOptions> {
 80 |   protected buildCommand(): GitCommandBuilder {
 81 |     const command = GitCommandBuilder.clone()
 82 |       .arg(this.options.url)
 83 |       .arg(this.options.path || '.');
 84 |     
 85 |     if (this.options.bare) {
 86 |       command.flag('bare');
 87 |     }
 88 |     
 89 |     if (this.options.depth) {
 90 |       command.option('depth', this.options.depth.toString());
 91 |     }
 92 |     
 93 |     if (this.options.branch) {
 94 |       command.option('branch', this.options.branch);
 95 |     }
 96 |     
 97 |     return command;
 98 |   }
 99 | 
100 |   protected parseResult(result: CommandResult): void {
101 |     // Clone doesn't return any structured data
102 |   }
103 | 
104 |   protected getCacheConfig() {
105 |     return {
106 |       command: 'clone'
107 |     };
108 |   }
109 | 
110 |   protected validateOptions(): void {
111 |     if (!this.options.url) {
112 |       throw ErrorHandler.handleValidationError(
113 |         new Error('URL is required for clone operation'),
114 |         { operation: this.context.operation }
115 |       );
116 |     }
117 | 
118 |     const path = this.options.path || process.env.GIT_DEFAULT_PATH;
119 |     if (!path) {
120 |       throw ErrorHandler.handleValidationError(
121 |         new Error('Path must be provided when GIT_DEFAULT_PATH is not set'),
122 |         { operation: this.context.operation }
123 |       );
124 |     }
125 | 
126 |     // Validate path exists or can be created
127 |     PathValidator.validatePath(path, {
128 |       mustExist: false,
129 |       allowDirectory: true
130 |     });
131 | 
132 |     if (this.options.depth !== undefined && this.options.depth <= 0) {
133 |       throw ErrorHandler.handleValidationError(
134 |         new Error('Depth must be a positive number'),
135 |         { operation: this.context.operation }
136 |       );
137 |     }
138 |   }
139 | }
140 | 
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpError } from '@modelcontextprotocol/sdk/types.js';
  2 | import { resolve, relative } from 'path';
  3 | 
  4 | export enum LogLevel {
  5 |   DEBUG = 'DEBUG',
  6 |   INFO = 'INFO',
  7 |   WARN = 'WARN',
  8 |   ERROR = 'ERROR'
  9 | }
 10 | 
 11 | interface LogEntry {
 12 |   timestamp: string;
 13 |   level: LogLevel;
 14 |   operation: string;
 15 |   message: string;
 16 |   path?: string;
 17 |   error?: Error;
 18 |   context?: Record<string, any>;
 19 | }
 20 | 
 21 | export class Logger {
 22 |   private static instance: Logger;
 23 |   private entries: LogEntry[] = [];
 24 |   private readonly cwd: string;
 25 | 
 26 |   private constructor() {
 27 |     this.cwd = process.cwd();
 28 |   }
 29 | 
 30 |   static getInstance(): Logger {
 31 |     if (!Logger.instance) {
 32 |       Logger.instance = new Logger();
 33 |     }
 34 |     return Logger.instance;
 35 |   }
 36 | 
 37 |   private formatPath(path: string): string {
 38 |     const absolutePath = resolve(this.cwd, path);
 39 |     return relative(this.cwd, absolutePath);
 40 |   }
 41 | 
 42 |   private createEntry(
 43 |     level: LogLevel,
 44 |     operation: string,
 45 |     message: string,
 46 |     path?: string,
 47 |     error?: Error,
 48 |     context?: Record<string, any>
 49 |   ): LogEntry {
 50 |     return {
 51 |       timestamp: new Date().toISOString(),
 52 |       level,
 53 |       operation,
 54 |       message,
 55 |       path: path ? this.formatPath(path) : undefined,
 56 |       error,
 57 |       context,
 58 |     };
 59 |   }
 60 | 
 61 |   private log(entry: LogEntry): void {
 62 |     this.entries.push(entry);
 63 |     let logMessage = `[${entry.timestamp}] ${entry.level} - ${entry.operation}: ${entry.message}`;
 64 |     
 65 |     if (entry.path) {
 66 |       logMessage += `\n  Path: ${entry.path}`;
 67 |     }
 68 |     
 69 |     if (entry.context) {
 70 |       logMessage += `\n  Context: ${JSON.stringify(entry.context, null, 2)}`;
 71 |     }
 72 |     
 73 |     if (entry.error) {
 74 |       if (entry.error instanceof McpError) {
 75 |         logMessage += `\n  Error: ${entry.error.message}`;
 76 |       } else {
 77 |         logMessage += `\n  Error: ${entry.error.stack || entry.error.message}`;
 78 |       }
 79 |     }
 80 | 
 81 |     console.error(logMessage);
 82 |   }
 83 | 
 84 |   debug(operation: string, message: string, path?: string, context?: Record<string, any>): void {
 85 |     this.log(this.createEntry(LogLevel.DEBUG, operation, message, path, undefined, context));
 86 |   }
 87 | 
 88 |   info(operation: string, message: string, path?: string, context?: Record<string, any>): void {
 89 |     this.log(this.createEntry(LogLevel.INFO, operation, message, path, undefined, context));
 90 |   }
 91 | 
 92 |   warn(operation: string, message: string, path?: string, error?: Error, context?: Record<string, any>): void {
 93 |     this.log(this.createEntry(LogLevel.WARN, operation, message, path, error, context));
 94 |   }
 95 | 
 96 |   error(operation: string, message: string, path?: string, error?: Error, context?: Record<string, any>): void {
 97 |     this.log(this.createEntry(LogLevel.ERROR, operation, message, path, error, context));
 98 |   }
 99 | 
100 |   getEntries(): LogEntry[] {
101 |     return [...this.entries];
102 |   }
103 | 
104 |   getEntriesForOperation(operation: string): LogEntry[] {
105 |     return this.entries.filter(entry => entry.operation === operation);
106 |   }
107 | 
108 |   getEntriesForPath(path: string): LogEntry[] {
109 |     const searchPath = this.formatPath(path);
110 |     return this.entries.filter(entry => entry.path === searchPath);
111 |   }
112 | 
113 |   clear(): void {
114 |     this.entries = [];
115 |   }
116 | 
117 |   // Helper methods for common operations
118 |   logCommand(operation: string, command: string, path?: string, context?: Record<string, any>): void {
119 |     this.debug(operation, `Executing command: ${command}`, path, context);
120 |   }
121 | 
122 |   logCommandResult(operation: string, result: string, path?: string, context?: Record<string, any>): void {
123 |     this.debug(operation, `Command result: ${result}`, path, context);
124 |   }
125 | 
126 |   logPathValidation(operation: string, path: string, context?: Record<string, any>): void {
127 |     this.debug(operation, `Validating path: ${path}`, path, context);
128 |   }
129 | 
130 |   logGitOperation(operation: string, details: string, path?: string, context?: Record<string, any>): void {
131 |     this.info(operation, details, path, context);
132 |   }
133 | 
134 |   logError(operation: string, error: Error, path?: string, context?: Record<string, any>): void {
135 |     this.error(operation, 'Operation failed', path, error, context);
136 |   }
137 | }
138 | 
139 | // Export a singleton instance
140 | export const logger = Logger.getInstance();
141 | 
```

--------------------------------------------------------------------------------
/src/common/command-builder.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Provides a fluent interface for building Git commands with proper option handling
  3 |  */
  4 | export class GitCommandBuilder {
  5 |   private command: string[] = ['git'];
  6 |   private options: Map<string, string | boolean> = new Map();
  7 | 
  8 |   /**
  9 |    * Create a new GitCommandBuilder for a specific Git command
 10 |    */
 11 |   constructor(command: string) {
 12 |     this.command.push(command);
 13 |   }
 14 | 
 15 |   /**
 16 |    * Add a positional argument to the command
 17 |    */
 18 |   arg(value: string): this {
 19 |     this.command.push(this.escapeArg(value));
 20 |     return this;
 21 |   }
 22 | 
 23 |   /**
 24 |    * Add multiple positional arguments
 25 |    */
 26 |   args(...values: string[]): this {
 27 |     values.forEach(value => this.arg(value));
 28 |     return this;
 29 |   }
 30 | 
 31 |   /**
 32 |    * Add a flag option (--flag)
 33 |    */
 34 |   flag(name: string): this {
 35 |     this.options.set(name, true);
 36 |     return this;
 37 |   }
 38 | 
 39 |   /**
 40 |    * Add a value option (--option=value)
 41 |    */
 42 |   option(name: string, value: string): this {
 43 |     this.options.set(name, value);
 44 |     return this;
 45 |   }
 46 | 
 47 |   /**
 48 |    * Add a force flag (--force)
 49 |    */
 50 |   withForce(): this {
 51 |     return this.flag('force');
 52 |   }
 53 | 
 54 |   /**
 55 |    * Add a no-verify flag (--no-verify)
 56 |    */
 57 |   withNoVerify(): this {
 58 |     return this.flag('no-verify');
 59 |   }
 60 | 
 61 |   /**
 62 |    * Add a tags flag (--tags)
 63 |    */
 64 |   withTags(): this {
 65 |     return this.flag('tags');
 66 |   }
 67 | 
 68 |   /**
 69 |    * Add a track flag (--track)
 70 |    */
 71 |   withTrack(): this {
 72 |     return this.flag('track');
 73 |   }
 74 | 
 75 |   /**
 76 |    * Add a no-track flag (--no-track)
 77 |    */
 78 |   withNoTrack(): this {
 79 |     return this.flag('no-track');
 80 |   }
 81 | 
 82 |   /**
 83 |    * Add a set-upstream flag (--set-upstream)
 84 |    */
 85 |   withSetUpstream(): this {
 86 |     return this.flag('set-upstream');
 87 |   }
 88 | 
 89 |   /**
 90 |    * Add an annotated flag (-a)
 91 |    */
 92 |   withAnnotated(): this {
 93 |     return this.flag('a');
 94 |   }
 95 | 
 96 |   /**
 97 |    * Add a sign flag (-s)
 98 |    */
 99 |   withSign(): this {
100 |     return this.flag('s');
101 |   }
102 | 
103 |   /**
104 |    * Add an include-untracked flag (--include-untracked)
105 |    */
106 |   withIncludeUntracked(): this {
107 |     return this.flag('include-untracked');
108 |   }
109 | 
110 |   /**
111 |    * Add a keep-index flag (--keep-index)
112 |    */
113 |   withKeepIndex(): this {
114 |     return this.flag('keep-index');
115 |   }
116 | 
117 |   /**
118 |    * Add an all flag (--all)
119 |    */
120 |   withAll(): this {
121 |     return this.flag('all');
122 |   }
123 | 
124 |   /**
125 |    * Add a message option (-m "message")
126 |    */
127 |   withMessage(message: string): this {
128 |     return this.option('m', message);
129 |   }
130 | 
131 |   /**
132 |    * Build the final command string
133 |    */
134 |   toString(): string {
135 |     const parts = [...this.command];
136 | 
137 |     // Add options in sorted order for consistency
138 |     Array.from(this.options.entries())
139 |       .sort(([a], [b]) => a.localeCompare(b))
140 |       .forEach(([name, value]) => {
141 |         if (name.length === 1) {
142 |           // Short option (-m "value")
143 |           parts.push(`-${name}`);
144 |           if (typeof value === 'string') {
145 |             parts.push(this.escapeArg(value));
146 |           }
147 |         } else {
148 |           // Long option
149 |           if (value === true) {
150 |             // Flag (--force)
151 |             parts.push(`--${name}`);
152 |           } else if (typeof value === 'string') {
153 |             // Value option (--option=value)
154 |             parts.push(`--${name}=${this.escapeArg(value)}`);
155 |           }
156 |         }
157 |       });
158 | 
159 |     return parts.join(' ');
160 |   }
161 | 
162 |   /**
163 |    * Create common Git commands
164 |    */
165 |   static init(): GitCommandBuilder {
166 |     return new GitCommandBuilder('init');
167 |   }
168 | 
169 |   static clone(): GitCommandBuilder {
170 |     return new GitCommandBuilder('clone');
171 |   }
172 | 
173 |   static add(): GitCommandBuilder {
174 |     return new GitCommandBuilder('add');
175 |   }
176 | 
177 |   static commit(): GitCommandBuilder {
178 |     return new GitCommandBuilder('commit');
179 |   }
180 | 
181 |   static push(): GitCommandBuilder {
182 |     return new GitCommandBuilder('push');
183 |   }
184 | 
185 |   static pull(): GitCommandBuilder {
186 |     return new GitCommandBuilder('pull');
187 |   }
188 | 
189 |   static branch(): GitCommandBuilder {
190 |     return new GitCommandBuilder('branch');
191 |   }
192 | 
193 |   static checkout(): GitCommandBuilder {
194 |     return new GitCommandBuilder('checkout');
195 |   }
196 | 
197 |   static tag(): GitCommandBuilder {
198 |     return new GitCommandBuilder('tag');
199 |   }
200 | 
201 |   static remote(): GitCommandBuilder {
202 |     return new GitCommandBuilder('remote');
203 |   }
204 | 
205 |   static stash(): GitCommandBuilder {
206 |     return new GitCommandBuilder('stash');
207 |   }
208 | 
209 |   static status(): GitCommandBuilder {
210 |     return new GitCommandBuilder('status');
211 |   }
212 | 
213 |   static fetch(): GitCommandBuilder {
214 |     return new GitCommandBuilder('fetch');
215 |   }
216 | 
217 |   /**
218 |    * Escape command arguments that contain spaces or special characters
219 |    */
220 |   private escapeArg(arg: string): string {
221 |     if (arg.includes(' ') || arg.includes('"') || arg.includes('\'')) {
222 |       // Escape quotes and wrap in quotes
223 |       return `"${arg.replace(/"/g, '\\"')}"`;
224 |     }
225 |     return arg;
226 |   }
227 | }
228 | 
```

--------------------------------------------------------------------------------
/src/operations/base/base-operation.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { CommandExecutor } from '../../utils/command.js';
  2 | import { PathValidator } from '../../utils/path.js';
  3 | import { logger } from '../../utils/logger.js';
  4 | import { repositoryCache } from '../../caching/repository-cache.js';
  5 | import { RepoStateType } from '../../caching/repository-cache.js';
  6 | import { GitToolContext, GitToolResult } from '../../types.js';
  7 | import { GitCommandBuilder } from '../../common/command-builder.js';
  8 | import { GitOperationOptions, GitOperationResult, CommandResult } from './operation-result.js';
  9 | import { ErrorHandler } from '../../errors/error-handler.js';
 10 | import { GitMcpError } from '../../errors/error-types.js';
 11 | 
 12 | /**
 13 |  * Base class for all Git operations providing common functionality
 14 |  */
 15 | export abstract class BaseGitOperation<TOptions extends GitOperationOptions, TResult = void> {
 16 |   protected constructor(
 17 |     protected readonly context: GitToolContext,
 18 |     protected readonly options: TOptions
 19 |   ) {}
 20 | 
 21 |   /**
 22 |    * Execute the Git operation with proper error handling and caching
 23 |    */
 24 |   public async execute(): Promise<GitOperationResult<TResult>> {
 25 |     try {
 26 |       // Validate options before proceeding
 27 |       this.validateOptions();
 28 | 
 29 |       // Get resolved path
 30 |       const path = this.getResolvedPath();
 31 | 
 32 |       // Execute operation with caching if enabled
 33 |       const result = await this.executeWithCache(path);
 34 | 
 35 |       // Format the result
 36 |       return await this.formatResult(result);
 37 |     } catch (error: unknown) {
 38 |       return this.handleError(error);
 39 |     }
 40 |   }
 41 | 
 42 |   /**
 43 |    * Build the Git command for this operation
 44 |    */
 45 |   protected abstract buildCommand(): GitCommandBuilder | Promise<GitCommandBuilder>;
 46 | 
 47 |   /**
 48 |    * Parse the command result into operation-specific format
 49 |    */
 50 |   protected abstract parseResult(result: CommandResult): TResult | Promise<TResult>;
 51 | 
 52 |   /**
 53 |    * Get cache configuration for this operation
 54 |    */
 55 |   protected abstract getCacheConfig(): {
 56 |     command: string;
 57 |     stateType?: RepoStateType;
 58 |   };
 59 | 
 60 |   /**
 61 |    * Validate operation-specific options
 62 |    */
 63 |   protected abstract validateOptions(): void;
 64 | 
 65 |   /**
 66 |    * Execute the Git command with caching if enabled
 67 |    */
 68 |   private async executeWithCache(path: string): Promise<CommandResult> {
 69 |     const { command, stateType } = this.getCacheConfig();
 70 |     const action = () => this.executeCommand(path);
 71 | 
 72 |     if (this.options.useCache && path) {
 73 |       if (stateType) {
 74 |         // Use state cache
 75 |         return await repositoryCache.getState(
 76 |           path,
 77 |           stateType,
 78 |           command,
 79 |           action
 80 |         );
 81 |       } else {
 82 |         // Use command cache
 83 |         return await repositoryCache.getCommandResult(
 84 |           path,
 85 |           command,
 86 |           action
 87 |         );
 88 |       }
 89 |     }
 90 | 
 91 |     // Execute without caching
 92 |     return await action();
 93 |   }
 94 | 
 95 |   /**
 96 |    * Execute the Git command
 97 |    */
 98 |   private async executeCommand(path: string): Promise<CommandResult> {
 99 |     const builder = await Promise.resolve(this.buildCommand());
100 |     const command = builder.toString();
101 |     return await CommandExecutor.executeGitCommand(
102 |       command,
103 |       this.context.operation,
104 |       path
105 |     );
106 |   }
107 | 
108 |   /**
109 |    * Format the operation result into standard GitToolResult
110 |    */
111 |   private async formatResult(result: CommandResult): Promise<GitOperationResult<TResult>> {
112 |     return {
113 |       success: true,
114 |       data: await Promise.resolve(this.parseResult(result)),
115 |       content: [{
116 |         type: 'text',
117 |         text: CommandExecutor.formatOutput(result)
118 |       }]
119 |     };
120 |   }
121 | 
122 |   /**
123 |    * Handle operation errors
124 |    */
125 |   private handleError(error: unknown): GitOperationResult<TResult> {
126 |     if (error instanceof GitMcpError) {
127 |       return {
128 |         success: false,
129 |         error,
130 |         content: [{
131 |           type: 'text',
132 |           text: error.message
133 |         }]
134 |       };
135 |     }
136 | 
137 |     const wrappedError = ErrorHandler.handleOperationError(
138 |       error instanceof Error ? error : new Error('Unknown error'),
139 |       {
140 |         operation: this.context.operation,
141 |         path: this.options.path,
142 |         command: this.getCacheConfig().command
143 |       }
144 |     );
145 | 
146 |     return {
147 |       success: false,
148 |       error: wrappedError,
149 |       content: [{
150 |         type: 'text',
151 |         text: wrappedError.message
152 |       }]
153 |     };
154 |   }
155 | 
156 |   /**
157 |    * Get resolved path with proper validation
158 |    */
159 |   protected getResolvedPath(): string {
160 |     const path = this.options.path || process.env.GIT_DEFAULT_PATH;
161 |     if (!path) {
162 |       throw ErrorHandler.handleValidationError(
163 |         new Error('Path must be provided when GIT_DEFAULT_PATH is not set'),
164 |         { operation: this.context.operation }
165 |       );
166 |     }
167 | 
168 |     const { path: repoPath } = PathValidator.validateGitRepo(path);
169 |     return repoPath;
170 |   }
171 | 
172 |   /**
173 |    * Invalidate cache if needed
174 |    */
175 |   protected invalidateCache(path: string): void {
176 |     if (this.options.invalidateCache) {
177 |       const { command, stateType } = this.getCacheConfig();
178 |       if (stateType) {
179 |         repositoryCache.invalidateState(path, stateType);
180 |       }
181 |       repositoryCache.invalidateCommand(path, command);
182 |     }
183 |   }
184 | }
185 | 
```

--------------------------------------------------------------------------------
/src/operations/tag/tag-operations.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { BaseGitOperation } from '../base/base-operation.js';
  2 | import { GitCommandBuilder } from '../../common/command-builder.js';
  3 | import { CommandResult } from '../base/operation-result.js';
  4 | import { ErrorHandler } from '../../errors/error-handler.js';
  5 | import { RepositoryValidator } from '../../utils/repository.js';
  6 | import { CommandExecutor } from '../../utils/command.js';
  7 | import { RepoStateType } from '../../caching/repository-cache.js';
  8 | import {
  9 |   TagListOptions,
 10 |   TagCreateOptions,
 11 |   TagDeleteOptions,
 12 |   TagListResult,
 13 |   TagCreateResult,
 14 |   TagDeleteResult,
 15 |   TagInfo
 16 | } from './tag-types.js';
 17 | 
 18 | /**
 19 |  * Handles Git tag listing operations
 20 |  */
 21 | export class TagListOperation extends BaseGitOperation<TagListOptions, TagListResult> {
 22 |   protected buildCommand(): GitCommandBuilder {
 23 |     const command = GitCommandBuilder.tag();
 24 | 
 25 |     // Add format option for parsing
 26 |     command.option('format', '%(refname:strip=2)|%(objecttype)|%(subject)|%(taggername)|%(taggeremail)|%(taggerdate)|%(objectname)');
 27 | 
 28 |     if (this.options.showMessage) {
 29 |       command.flag('n');
 30 |     }
 31 | 
 32 |     if (this.options.sort) {
 33 |       command.option('sort', this.options.sort);
 34 |     }
 35 | 
 36 |     if (this.options.contains) {
 37 |       command.option('contains', this.options.contains);
 38 |     }
 39 | 
 40 |     if (this.options.pattern) {
 41 |       command.arg(this.options.pattern);
 42 |     }
 43 | 
 44 |     return command;
 45 |   }
 46 | 
 47 |   protected parseResult(result: CommandResult): TagListResult {
 48 |     const tags: TagInfo[] = [];
 49 | 
 50 |     // Parse each line of output
 51 |     result.stdout.split('\n').filter(Boolean).forEach(line => {
 52 |       const [name, type, message, taggerName, taggerEmail, taggerDate, commit] = line.split('|');
 53 |       
 54 |       const tag: TagInfo = {
 55 |         name,
 56 |         annotated: type === 'tag',
 57 |         commit,
 58 |         signed: message?.includes('-----BEGIN PGP SIGNATURE-----') || false
 59 |       };
 60 | 
 61 |       if (tag.annotated) {
 62 |         tag.message = message;
 63 |         if (taggerName && taggerEmail && taggerDate) {
 64 |           tag.tagger = {
 65 |             name: taggerName,
 66 |             email: taggerEmail.replace(/[<>]/g, ''),
 67 |             date: taggerDate
 68 |           };
 69 |         }
 70 |       }
 71 | 
 72 |       tags.push(tag);
 73 |     });
 74 | 
 75 |     return {
 76 |       tags,
 77 |       raw: result.stdout
 78 |     };
 79 |   }
 80 | 
 81 |   protected getCacheConfig() {
 82 |     return {
 83 |       command: 'tag',
 84 |       stateType: RepoStateType.TAG
 85 |     };
 86 |   }
 87 | 
 88 |   protected validateOptions(): void {
 89 |     // No specific validation needed for listing
 90 |   }
 91 | }
 92 | 
 93 | /**
 94 |  * Handles Git tag creation operations
 95 |  */
 96 | export class TagCreateOperation extends BaseGitOperation<TagCreateOptions, TagCreateResult> {
 97 |   protected buildCommand(): GitCommandBuilder {
 98 |     const command = GitCommandBuilder.tag();
 99 | 
100 |     if (this.options.message) {
101 |       command.withAnnotated();
102 |       command.withMessage(this.options.message);
103 |     }
104 | 
105 |     if (this.options.force) {
106 |       command.withForce();
107 |     }
108 | 
109 |     if (this.options.sign) {
110 |       command.withSign();
111 |     }
112 | 
113 |     command.arg(this.options.name);
114 | 
115 |     if (this.options.commit) {
116 |       command.arg(this.options.commit);
117 |     }
118 | 
119 |     return command;
120 |   }
121 | 
122 |   protected parseResult(result: CommandResult): TagCreateResult {
123 |     const signed = result.stdout.includes('-----BEGIN PGP SIGNATURE-----');
124 |     
125 |     return {
126 |       name: this.options.name,
127 |       annotated: Boolean(this.options.message),
128 |       signed,
129 |       commit: this.options.commit,
130 |       raw: result.stdout
131 |     };
132 |   }
133 | 
134 |   protected getCacheConfig() {
135 |     return {
136 |       command: 'tag_create',
137 |       stateType: RepoStateType.TAG
138 |     };
139 |   }
140 | 
141 |   protected validateOptions(): void {
142 |     if (!this.options.name) {
143 |       throw ErrorHandler.handleValidationError(
144 |         new Error('Tag name is required'),
145 |         { operation: this.context.operation }
146 |       );
147 |     }
148 |   }
149 | }
150 | 
151 | /**
152 |  * Handles Git tag deletion operations
153 |  */
154 | export class TagDeleteOperation extends BaseGitOperation<TagDeleteOptions, TagDeleteResult> {
155 |   protected async buildCommand(): Promise<GitCommandBuilder> {
156 |     const command = GitCommandBuilder.tag();
157 | 
158 |     command.flag('d');
159 |     if (this.options.force) {
160 |       command.withForce();
161 |     }
162 | 
163 |     command.arg(this.options.name);
164 | 
165 |     if (this.options.remote) {
166 |       // Get remote name from configuration
167 |       const remotes = await RepositoryValidator.getRemotes(
168 |         this.getResolvedPath(),
169 |         this.context.operation
170 |       );
171 | 
172 |       // Push deletion to all remotes
173 |       for (const remote of remotes) {
174 |         await CommandExecutor.executeGitCommand(
175 |           `push ${remote} :refs/tags/${this.options.name}`,
176 |           this.context.operation,
177 |           this.getResolvedPath()
178 |         );
179 |       }
180 |     }
181 | 
182 |     return command;
183 |   }
184 | 
185 |   protected parseResult(result: CommandResult): TagDeleteResult {
186 |     return {
187 |       name: this.options.name,
188 |       forced: this.options.force || false,
189 |       raw: result.stdout
190 |     };
191 |   }
192 | 
193 |   protected getCacheConfig() {
194 |     return {
195 |       command: 'tag_delete',
196 |       stateType: RepoStateType.TAG
197 |     };
198 |   }
199 | 
200 |   protected async validateOptions(): Promise<void> {
201 |     if (!this.options.name) {
202 |       throw ErrorHandler.handleValidationError(
203 |         new Error('Tag name is required'),
204 |         { operation: this.context.operation }
205 |       );
206 |     }
207 | 
208 |     // Ensure tag exists
209 |     await RepositoryValidator.validateTagExists(
210 |       this.getResolvedPath(),
211 |       this.options.name,
212 |       this.context.operation
213 |     );
214 |   }
215 | }
216 | 
```

--------------------------------------------------------------------------------
/src/caching/repository-cache.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { RepositoryStateCache, CommandResultCache } from './cache.js';
  2 | import { logger } from '../utils/logger.js';
  3 | import { PerformanceMonitor } from '../monitoring/performance.js';
  4 | 
  5 | /**
  6 |  * Repository state types
  7 |  */
  8 | export enum RepoStateType {
  9 |   BRANCH = 'branch',
 10 |   STATUS = 'status',
 11 |   REMOTE = 'remote',
 12 |   TAG = 'tag',
 13 |   STASH = 'stash'
 14 | }
 15 | 
 16 | /**
 17 |  * Repository state manager with caching
 18 |  */
 19 | export class RepositoryCacheManager {
 20 |   private static instance: RepositoryCacheManager;
 21 |   private stateCache: RepositoryStateCache;
 22 |   private commandCache: CommandResultCache;
 23 |   private performanceMonitor: PerformanceMonitor;
 24 | 
 25 |   private constructor() {
 26 |     this.stateCache = new RepositoryStateCache();
 27 |     this.commandCache = new CommandResultCache();
 28 |     this.performanceMonitor = PerformanceMonitor.getInstance();
 29 |   }
 30 | 
 31 |   /**
 32 |    * Get singleton instance
 33 |    */
 34 |   static getInstance(): RepositoryCacheManager {
 35 |     if (!RepositoryCacheManager.instance) {
 36 |       RepositoryCacheManager.instance = new RepositoryCacheManager();
 37 |     }
 38 |     return RepositoryCacheManager.instance;
 39 |   }
 40 | 
 41 |   /**
 42 |    * Get repository state from cache or execute command
 43 |    */
 44 |   async getState(
 45 |     repoPath: string,
 46 |     stateType: RepoStateType,
 47 |     command: string,
 48 |     executor: () => Promise<any>
 49 |   ): Promise<any> {
 50 |     const cacheKey = this.getStateKey(repoPath, stateType);
 51 |     const cachedState = this.stateCache.get(cacheKey);
 52 | 
 53 |     if (cachedState !== undefined) {
 54 |       logger.debug(
 55 |         'cache',
 56 |         `Cache hit for repository state: ${stateType}`,
 57 |         repoPath,
 58 |         { command }
 59 |       );
 60 |       return cachedState;
 61 |     }
 62 | 
 63 |     // Start timing the operation
 64 |     const startTime = performance.now();
 65 | 
 66 |     try {
 67 |       const result = await executor();
 68 |       const duration = performance.now() - startTime;
 69 | 
 70 |       // Record performance metrics
 71 |       this.performanceMonitor.recordCommandExecution(command, duration, {
 72 |         repoPath,
 73 |         stateType,
 74 |         cached: false
 75 |       });
 76 | 
 77 |       // Cache the result
 78 |       this.stateCache.set(cacheKey, result);
 79 | 
 80 |       return result;
 81 |     } catch (error) {
 82 |       const duration = performance.now() - startTime;
 83 |       this.performanceMonitor.recordCommandExecution(command, duration, {
 84 |         repoPath,
 85 |         stateType,
 86 |         cached: false,
 87 |         error: true
 88 |       });
 89 |       throw error;
 90 |     }
 91 |   }
 92 | 
 93 |   /**
 94 |    * Get command result from cache or execute command
 95 |    */
 96 |   async getCommandResult(
 97 |     repoPath: string,
 98 |     command: string,
 99 |     executor: () => Promise<any>
100 |   ): Promise<any> {
101 |     const cacheKey = CommandResultCache.generateKey(command, repoPath);
102 |     const cachedResult = this.commandCache.get(cacheKey);
103 | 
104 |     if (cachedResult !== undefined) {
105 |       logger.debug(
106 |         'cache',
107 |         `Cache hit for command result`,
108 |         repoPath,
109 |         { command }
110 |       );
111 |       return cachedResult;
112 |     }
113 | 
114 |     // Start timing the operation
115 |     const startTime = performance.now();
116 | 
117 |     try {
118 |       const result = await executor();
119 |       const duration = performance.now() - startTime;
120 | 
121 |       // Record performance metrics
122 |       this.performanceMonitor.recordCommandExecution(command, duration, {
123 |         repoPath,
124 |         cached: false
125 |       });
126 | 
127 |       // Cache the result
128 |       this.commandCache.set(cacheKey, result);
129 | 
130 |       return result;
131 |     } catch (error) {
132 |       const duration = performance.now() - startTime;
133 |       this.performanceMonitor.recordCommandExecution(command, duration, {
134 |         repoPath,
135 |         cached: false,
136 |         error: true
137 |       });
138 |       throw error;
139 |     }
140 |   }
141 | 
142 |   /**
143 |    * Invalidate repository state cache
144 |    */
145 |   invalidateState(repoPath: string, stateType?: RepoStateType): void {
146 |     if (stateType) {
147 |       const cacheKey = this.getStateKey(repoPath, stateType);
148 |       this.stateCache.delete(cacheKey);
149 |       logger.debug(
150 |         'cache',
151 |         `Invalidated repository state cache`,
152 |         repoPath,
153 |         { stateType }
154 |       );
155 |     } else {
156 |       // Invalidate all state types for this repository
157 |       Object.values(RepoStateType).forEach(type => {
158 |         const cacheKey = this.getStateKey(repoPath, type);
159 |         this.stateCache.delete(cacheKey);
160 |       });
161 |       logger.debug(
162 |         'cache',
163 |         `Invalidated all repository state cache`,
164 |         repoPath
165 |       );
166 |     }
167 |   }
168 | 
169 |   /**
170 |    * Invalidate command result cache
171 |    */
172 |   invalidateCommand(repoPath: string, command?: string): void {
173 |     if (command) {
174 |       const cacheKey = CommandResultCache.generateKey(command, repoPath);
175 |       this.commandCache.delete(cacheKey);
176 |       logger.debug(
177 |         'cache',
178 |         `Invalidated command result cache`,
179 |         repoPath,
180 |         { command }
181 |       );
182 |     } else {
183 |       // Clear all command results for this repository
184 |       // Note: This is a bit inefficient as it clears all commands for all repos
185 |       // A better solution would be to store repo-specific commands separately
186 |       this.commandCache.clear();
187 |       logger.debug(
188 |         'cache',
189 |         `Invalidated all command result cache`,
190 |         repoPath
191 |       );
192 |     }
193 |   }
194 | 
195 |   /**
196 |    * Get cache statistics
197 |    */
198 |   getStats(): Record<string, any> {
199 |     return {
200 |       state: this.stateCache.getStats(),
201 |       command: this.commandCache.getStats()
202 |     };
203 |   }
204 | 
205 |   /**
206 |    * Generate cache key for repository state
207 |    */
208 |   private getStateKey(repoPath: string, stateType: RepoStateType): string {
209 |     return `${repoPath}:${stateType}`;
210 |   }
211 | }
212 | 
213 | // Export singleton instance
214 | export const repositoryCache = RepositoryCacheManager.getInstance();
215 | 
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ExecOptions } from 'child_process';
  2 | 
  3 | export enum ErrorCode {
  4 |   InvalidParams = 'InvalidParams',
  5 |   InternalError = 'InternalError',
  6 |   InvalidState = 'InvalidState'
  7 | }
  8 | 
  9 | export interface GitOptions {
 10 |   /**
 11 |    * Absolute path to the working directory
 12 |    * Example: /Users/username/projects/my-repo
 13 |    */
 14 |   cwd?: string;
 15 |   execOptions?: ExecOptions;
 16 | }
 17 | 
 18 | export interface GitToolContent {
 19 |   type: string;
 20 |   text: string;
 21 | }
 22 | 
 23 | export interface GitToolResult {
 24 |   content: GitToolContent[];
 25 |   _meta?: Record<string, unknown>;
 26 | }
 27 | 
 28 | export interface GitToolContext {
 29 |   operation: string;
 30 |   path?: string;
 31 |   options?: GitOptions;
 32 | }
 33 | 
 34 | // Base interface for operations that require a path
 35 | export interface BasePathOptions {
 36 |   /**
 37 |    * MUST be an absolute path to the repository
 38 |    * Example: /Users/username/projects/my-repo
 39 |    * If not provided, will use GIT_DEFAULT_PATH from environment
 40 |    */
 41 |   path?: string;
 42 | }
 43 | 
 44 | // Tool-specific interfaces
 45 | export interface InitOptions extends GitOptions, BasePathOptions {}
 46 | 
 47 | export interface CloneOptions extends GitOptions, BasePathOptions {
 48 |   /**
 49 |    * URL of the repository to clone
 50 |    */
 51 |   url: string;
 52 | }
 53 | 
 54 | export interface AddOptions extends GitOptions, BasePathOptions {
 55 |   /**
 56 |    * Array of absolute paths to files to stage
 57 |    * Example: /Users/username/projects/my-repo/src/file.js
 58 |    */
 59 |   files: string[];
 60 | }
 61 | 
 62 | export interface CommitOptions extends GitOptions, BasePathOptions {
 63 |   message: string;
 64 | }
 65 | 
 66 | export interface PushPullOptions extends GitOptions, BasePathOptions {
 67 |   remote?: string;
 68 |   branch: string;
 69 |   force?: boolean;  // Allow force push/pull
 70 |   noVerify?: boolean;  // Skip pre-push/pre-pull hooks
 71 |   tags?: boolean;  // Include tags
 72 | }
 73 | 
 74 | export interface BranchOptions extends GitOptions, BasePathOptions {
 75 |   name: string;
 76 |   force?: boolean;  // Allow force operations
 77 |   track?: boolean;  // Set up tracking mode
 78 |   setUpstream?: boolean;  // Set upstream for push/pull
 79 | }
 80 | 
 81 | export interface CheckoutOptions extends GitOptions, BasePathOptions {
 82 |   target: string;
 83 | }
 84 | 
 85 | export interface TagOptions extends GitOptions, BasePathOptions {
 86 |   name: string;
 87 |   message?: string;
 88 |   force?: boolean;  // Allow force operations
 89 |   annotated?: boolean;  // Create an annotated tag
 90 |   sign?: boolean;  // Create a signed tag
 91 | }
 92 | 
 93 | export interface RemoteOptions extends GitOptions, BasePathOptions {
 94 |   name: string;
 95 |   url?: string;
 96 |   force?: boolean;  // Allow force operations
 97 |   mirror?: boolean;  // Mirror all refs
 98 |   tags?: boolean;  // Include tags
 99 | }
100 | 
101 | export interface StashOptions extends GitOptions, BasePathOptions {
102 |   message?: string;
103 |   index?: number;
104 |   includeUntracked?: boolean;  // Include untracked files
105 |   keepIndex?: boolean;  // Keep staged changes
106 |   all?: boolean;  // Include ignored files
107 | }
108 | 
109 | // New bulk action interfaces
110 | export interface BulkActionStage {
111 |   type: 'stage';
112 |   files?: string[]; // If not provided, stages all files
113 | }
114 | 
115 | export interface BulkActionCommit {
116 |   type: 'commit';
117 |   message: string;
118 | }
119 | 
120 | export interface BulkActionPush {
121 |   type: 'push';
122 |   remote?: string;
123 |   branch: string;
124 | }
125 | 
126 | export type BulkAction = BulkActionStage | BulkActionCommit | BulkActionPush;
127 | 
128 | export interface BulkActionOptions extends GitOptions, BasePathOptions {
129 |   actions: BulkAction[];
130 | }
131 | 
132 | // Type guard functions
133 | export function isAbsolutePath(path: string): boolean {
134 |   return path.startsWith('/');
135 | }
136 | 
137 | export function validatePath(path?: string): boolean {
138 |   return !path || isAbsolutePath(path);
139 | }
140 | 
141 | export function isInitOptions(obj: any): obj is InitOptions {
142 |   return obj && validatePath(obj.path);
143 | }
144 | 
145 | export function isCloneOptions(obj: any): obj is CloneOptions {
146 |   return obj && 
147 |     typeof obj.url === 'string' &&
148 |     validatePath(obj.path);
149 | }
150 | 
151 | export function isAddOptions(obj: any): obj is AddOptions {
152 |   return obj && 
153 |     validatePath(obj.path) && 
154 |     Array.isArray(obj.files) &&
155 |     obj.files.every((f: any) => typeof f === 'string' && isAbsolutePath(f));
156 | }
157 | 
158 | export function isCommitOptions(obj: any): obj is CommitOptions {
159 |   return obj && 
160 |     validatePath(obj.path) && 
161 |     typeof obj.message === 'string';
162 | }
163 | 
164 | export function isPushPullOptions(obj: any): obj is PushPullOptions {
165 |   return obj && 
166 |     validatePath(obj.path) && 
167 |     typeof obj.branch === 'string';
168 | }
169 | 
170 | export function isBranchOptions(obj: any): obj is BranchOptions {
171 |   return obj && 
172 |     validatePath(obj.path) && 
173 |     typeof obj.name === 'string';
174 | }
175 | 
176 | export function isCheckoutOptions(obj: any): obj is CheckoutOptions {
177 |   return obj && 
178 |     validatePath(obj.path) && 
179 |     typeof obj.target === 'string';
180 | }
181 | 
182 | export function isTagOptions(obj: any): obj is TagOptions {
183 |   return obj && 
184 |     validatePath(obj.path) && 
185 |     typeof obj.name === 'string';
186 | }
187 | 
188 | export function isRemoteOptions(obj: any): obj is RemoteOptions {
189 |   return obj && 
190 |     validatePath(obj.path) && 
191 |     typeof obj.name === 'string';
192 | }
193 | 
194 | export function isStashOptions(obj: any): obj is StashOptions {
195 |   return obj && validatePath(obj.path);
196 | }
197 | 
198 | export function isPathOnly(obj: any): obj is BasePathOptions {
199 |   return obj && validatePath(obj.path);
200 | }
201 | 
202 | export function isBulkActionOptions(obj: any): obj is BulkActionOptions {
203 |   if (!obj || !validatePath(obj.path) || !Array.isArray(obj.actions)) {
204 |     return false;
205 |   }
206 | 
207 |   return obj.actions.every((action: any) => {
208 |     if (!action || typeof action.type !== 'string') {
209 |       return false;
210 |     }
211 | 
212 |     switch (action.type) {
213 |       case 'stage':
214 |         return !action.files || (Array.isArray(action.files) && 
215 |           action.files.every((f: any) => typeof f === 'string' && isAbsolutePath(f)));
216 |       case 'commit':
217 |         return typeof action.message === 'string';
218 |       case 'push':
219 |         return typeof action.branch === 'string';
220 |       default:
221 |         return false;
222 |     }
223 |   });
224 | }
225 | 
```

--------------------------------------------------------------------------------
/src/utils/paths.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { resolve, isAbsolute, normalize, relative, join, dirname } from 'path';
  2 | import { existsSync, statSync, mkdirSync } from 'fs';
  3 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
  4 | import { logger } from './logger.js';
  5 | 
  6 | export interface PathInfo {
  7 |   original: string;
  8 |   absolute: string;
  9 |   relative: string;
 10 |   exists: boolean;
 11 |   isDirectory?: boolean;
 12 |   isFile?: boolean;
 13 |   isGitRepo?: boolean;
 14 |   parent: string;
 15 | }
 16 | 
 17 | export class PathResolver {
 18 |   private static readonly CWD = process.cwd();
 19 | 
 20 |   private static createDirectory(path: string, operation: string): void {
 21 |     try {
 22 |       mkdirSync(path, { recursive: true });
 23 |       logger.info(operation, `Created directory: ${path}`);
 24 |     } catch (error) {
 25 |       logger.error(operation, `Failed to create directory: ${path}`, path, error as Error);
 26 |       throw new McpError(
 27 |         ErrorCode.InternalError,
 28 |         `Failed to create directory: ${(error as Error).message}`
 29 |       );
 30 |     }
 31 |   }
 32 | 
 33 |   private static getStats(path: string): { exists: boolean; isDirectory?: boolean; isFile?: boolean } {
 34 |     if (!existsSync(path)) {
 35 |       return { exists: false };
 36 |     }
 37 | 
 38 |     try {
 39 |       const stats = statSync(path);
 40 |       return {
 41 |         exists: true,
 42 |         isDirectory: stats.isDirectory(),
 43 |         isFile: stats.isFile(),
 44 |       };
 45 |     } catch {
 46 |       return { exists: true };
 47 |     }
 48 |   }
 49 | 
 50 |   private static validateAbsolutePath(path: string, operation: string): void {
 51 |     if (!isAbsolute(path)) {
 52 |       const error = new McpError(
 53 |         ErrorCode.InvalidParams,
 54 |         `Path must be absolute. Received: ${path}\nExample: /Users/username/projects/my-repo`
 55 |       );
 56 |       logger.error(operation, 'Invalid path format', path, error);
 57 |       throw error;
 58 |     }
 59 |   }
 60 | 
 61 |   static getPathInfo(path: string, operation: string): PathInfo {
 62 |     logger.debug(operation, 'Resolving path info', path);
 63 | 
 64 |     // Validate absolute path
 65 |     this.validateAbsolutePath(path, operation);
 66 | 
 67 |     // Normalize the path
 68 |     const absolutePath = normalize(path);
 69 |     const relativePath = relative(this.CWD, absolutePath);
 70 |     const parentPath = dirname(absolutePath);
 71 | 
 72 |     // Get path stats
 73 |     const stats = this.getStats(absolutePath);
 74 |     const isGitRepo = stats.isDirectory ? existsSync(join(absolutePath, '.git')) : false;
 75 | 
 76 |     const pathInfo: PathInfo = {
 77 |       original: path,
 78 |       absolute: absolutePath,
 79 |       relative: relativePath,
 80 |       exists: stats.exists,
 81 |       isDirectory: stats.isDirectory,
 82 |       isFile: stats.isFile,
 83 |       isGitRepo,
 84 |       parent: parentPath,
 85 |     };
 86 | 
 87 |     logger.debug(operation, 'Path info resolved', path, pathInfo);
 88 |     return pathInfo;
 89 |   }
 90 | 
 91 |   static validatePath(path: string, operation: string, options: {
 92 |     mustExist?: boolean;
 93 |     mustBeDirectory?: boolean;
 94 |     mustBeFile?: boolean;
 95 |     mustBeGitRepo?: boolean;
 96 |     createIfMissing?: boolean;
 97 |   } = {}): PathInfo {
 98 |     const {
 99 |       mustExist = false,
100 |       mustBeDirectory = false,
101 |       mustBeFile = false,
102 |       mustBeGitRepo = false,
103 |       createIfMissing = false,
104 |     } = options;
105 | 
106 |     logger.debug(operation, 'Validating path with options', path, options);
107 | 
108 |     // Get path info (includes absolute path validation)
109 |     const pathInfo = this.getPathInfo(path, operation);
110 | 
111 |     // Create directory if needed
112 |     if (!pathInfo.exists && (createIfMissing || mustBeDirectory)) {
113 |       this.createDirectory(pathInfo.absolute, operation);
114 |       return this.getPathInfo(path, operation);
115 |     }
116 | 
117 |     // Handle existence requirements
118 |     if (mustExist && !pathInfo.exists) {
119 |       const error = new McpError(
120 |         ErrorCode.InvalidParams,
121 |         `Path does not exist: ${pathInfo.absolute}`
122 |       );
123 |       logger.error(operation, 'Path validation failed', path, error);
124 |       throw error;
125 |     }
126 | 
127 |     // Validate directory requirement
128 |     if (mustBeDirectory && !pathInfo.isDirectory) {
129 |       const error = new McpError(
130 |         ErrorCode.InvalidParams,
131 |         `Path is not a directory: ${pathInfo.absolute}`
132 |       );
133 |       logger.error(operation, 'Path validation failed', path, error);
134 |       throw error;
135 |     }
136 | 
137 |     // Validate file requirement
138 |     if (mustBeFile && !pathInfo.isFile) {
139 |       const error = new McpError(
140 |         ErrorCode.InvalidParams,
141 |         `Path is not a file: ${pathInfo.absolute}`
142 |       );
143 |       logger.error(operation, 'Path validation failed', path, error);
144 |       throw error;
145 |     }
146 | 
147 |     // Validate git repo requirement
148 |     if (mustBeGitRepo && !pathInfo.isGitRepo) {
149 |       const error = new McpError(
150 |         ErrorCode.InvalidParams,
151 |         `Path is not a git repository: ${pathInfo.absolute}`
152 |       );
153 |       logger.error(operation, 'Path validation failed', path, error);
154 |       throw error;
155 |     }
156 | 
157 |     logger.debug(operation, 'Path validation successful', path, pathInfo);
158 |     return pathInfo;
159 |   }
160 | 
161 |   static validateFilePaths(paths: string[], operation: string): PathInfo[] {
162 |     logger.debug(operation, 'Validating multiple file paths', undefined, { paths });
163 | 
164 |     return paths.map(path => {
165 |       // Validate absolute path
166 |       this.validateAbsolutePath(path, operation);
167 | 
168 |       const pathInfo = this.validatePath(path, operation, {
169 |         mustExist: true,
170 |         mustBeFile: true,
171 |       });
172 |       return pathInfo;
173 |     });
174 |   }
175 | 
176 |   static validateGitRepo(path: string, operation: string): PathInfo {
177 |     // Validate absolute path
178 |     this.validateAbsolutePath(path, operation);
179 | 
180 |     return this.validatePath(path, operation, {
181 |       mustExist: true,
182 |       mustBeDirectory: true,
183 |       mustBeGitRepo: true,
184 |     });
185 |   }
186 | 
187 |   static ensureDirectory(path: string, operation: string): PathInfo {
188 |     // Validate absolute path
189 |     this.validateAbsolutePath(path, operation);
190 | 
191 |     return this.validatePath(path, operation, {
192 |       createIfMissing: true,
193 |       mustBeDirectory: true,
194 |     });
195 |   }
196 | }
197 | 
```

--------------------------------------------------------------------------------
/src/utils/command.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ExecException, exec, ExecOptions } from 'child_process';
  2 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
  3 | import { logger } from './logger.js';
  4 | import { PathResolver } from './paths.js';
  5 | import { ErrorHandler } from '../errors/error-handler.js';
  6 | import { ErrorCategory, ErrorSeverity, GitMcpError } from '../errors/error-types.js';
  7 | 
  8 | export interface CommandResult {
  9 |   stdout: string;
 10 |   stderr: string;
 11 |   command: string;
 12 |   workingDir?: string;
 13 | }
 14 | 
 15 | /**
 16 |  * Formats a command error message with detailed context
 17 |  */
 18 | function formatCommandError(error: ExecException, result: Partial<CommandResult>): string {
 19 |   let message = `Command failed with exit code ${error.code}`;
 20 |   
 21 |   if (result.command) {
 22 |     message += `\nCommand: ${result.command}`;
 23 |   }
 24 |   
 25 |   if (result.workingDir) {
 26 |     message += `\nWorking Directory: ${result.workingDir}`;
 27 |   }
 28 |   
 29 |   if (result.stdout) {
 30 |     message += `\nOutput: ${result.stdout}`;
 31 |   }
 32 |   
 33 |   if (result.stderr) {
 34 |     message += `\nError: ${result.stderr}`;
 35 |   }
 36 |   
 37 |   return message;
 38 | }
 39 | 
 40 | /**
 41 |  * Creates a command error with appropriate category and severity
 42 |  */
 43 | function createCommandError(
 44 |   error: ExecException,
 45 |   result: Partial<CommandResult>,
 46 |   operation: string
 47 | ): GitMcpError {
 48 |   const message = formatCommandError(error, result);
 49 |   const context = {
 50 |     operation,
 51 |     path: result.workingDir,
 52 |     command: result.command,
 53 |     details: {
 54 |       exitCode: error.code,
 55 |       stdout: result.stdout,
 56 |       stderr: result.stderr
 57 |     }
 58 |   };
 59 | 
 60 |   // Determine error category and severity based on error code and context
 61 |   const errorCode = error.code?.toString() || '';
 62 | 
 63 |   // System errors
 64 |   if (errorCode === 'ENOENT') {
 65 |     return ErrorHandler.handleSystemError(error, context);
 66 |   }
 67 | 
 68 |   // Security errors
 69 |   if (errorCode === 'EACCES') {
 70 |     return ErrorHandler.handleSecurityError(error, context);
 71 |   }
 72 | 
 73 |   // Validation errors
 74 |   if (errorCode === 'ENOTDIR' || errorCode === 'ENOTEMPTY') {
 75 |     return ErrorHandler.handleValidationError(error, context);
 76 |   }
 77 | 
 78 |   // Git-specific error codes
 79 |   const numericCode = typeof error.code === 'number' ? error.code : 
 80 |                      typeof error.code === 'string' ? parseInt(error.code, 10) : 
 81 |                      null;
 82 |   
 83 |   if (numericCode !== null) {
 84 |     switch (numericCode) {
 85 |       case 128: // Repository not found or invalid
 86 |         return ErrorHandler.handleRepositoryError(error, context);
 87 |       case 129: // Invalid command or argument
 88 |         return ErrorHandler.handleValidationError(error, context);
 89 |       case 130: // User interrupt
 90 |         return ErrorHandler.handleOperationError(error, context);
 91 |       default:
 92 |         return ErrorHandler.handleOperationError(error, context);
 93 |     }
 94 |   }
 95 | 
 96 |   // Default to operation error for unknown cases
 97 |   return ErrorHandler.handleOperationError(error, context);
 98 | }
 99 | 
100 | export class CommandExecutor {
101 |   static async execute(
102 |     command: string,
103 |     operation: string,
104 |     workingDir?: string,
105 |     options: ExecOptions = {}
106 |   ): Promise<CommandResult> {
107 |     // Validate and resolve working directory if provided
108 |     if (workingDir) {
109 |       const pathInfo = PathResolver.validatePath(workingDir, operation, {
110 |         mustExist: true,
111 |         mustBeDirectory: true,
112 |       });
113 |       workingDir = pathInfo.absolute;
114 |     }
115 | 
116 |     // Log command execution
117 |     logger.logCommand(operation, command, workingDir);
118 | 
119 |     // Prepare execution options
120 |     const execOptions: ExecOptions = {
121 |       ...options,
122 |       cwd: workingDir,
123 |       maxBuffer: 10 * 1024 * 1024, // 10MB buffer
124 |     };
125 | 
126 |     return new Promise((resolve, reject) => {
127 |       exec(command, execOptions, (error, stdout, stderr) => {
128 |         const result: CommandResult = {
129 |           command,
130 |           workingDir,
131 |           stdout: stdout.trim(),
132 |           stderr: stderr.trim(),
133 |         };
134 | 
135 |         // Log command result
136 |         if (error) {
137 |           reject(createCommandError(error, result, operation));
138 |           return;
139 |         }
140 | 
141 |         logger.logCommandResult(operation, result.stdout, workingDir, {
142 |           stderr: result.stderr,
143 |         });
144 |         resolve(result);
145 |       });
146 |     });
147 |   }
148 | 
149 |   static formatOutput(result: CommandResult): string {
150 |     let output = '';
151 |     
152 |     if (result.stdout) {
153 |       output += result.stdout;
154 |     }
155 |     
156 |     if (result.stderr) {
157 |       if (output) output += '\n';
158 |       output += result.stderr;
159 |     }
160 |     
161 |     return output.trim();
162 |   }
163 | 
164 |   static async executeGitCommand(
165 |     command: string,
166 |     operation: string,
167 |     workingDir?: string,
168 |     options: ExecOptions = {}
169 |   ): Promise<CommandResult> {
170 |     // Add git environment variables
171 |     const gitOptions: ExecOptions = {
172 |       ...options,
173 |       env: {
174 |         ...process.env,
175 |         ...options.env,
176 |         GIT_TERMINAL_PROMPT: '0', // Disable git terminal prompts
177 |         GIT_ASKPASS: 'echo', // Prevent password prompts
178 |       },
179 |     };
180 | 
181 |     try {
182 |       return await this.execute(`git ${command}`, operation, workingDir, gitOptions);
183 |     } catch (error) {
184 |       if (error instanceof GitMcpError) {
185 |         // Add git-specific context to error
186 |         logger.error(operation, 'Git command failed', workingDir, error, {
187 |           command: `git ${command}`,
188 |           gitConfig: await this.execute('git config --list', operation, workingDir)
189 |             .then(result => result.stdout)
190 |             .catch(() => 'Unable to get git config'),
191 |         });
192 |       }
193 |       throw error;
194 |     }
195 |   }
196 | 
197 |   static async validateGitInstallation(operation: string): Promise<void> {
198 |     try {
199 |       const result = await this.execute('git --version', operation);
200 |       logger.info(operation, 'Git installation validated', undefined, {
201 |         version: result.stdout,
202 |       });
203 |     } catch (error) {
204 |       const mcpError = new McpError(
205 |         ErrorCode.InternalError,
206 |         'Git is not installed or not accessible'
207 |       );
208 |       logger.error(operation, 'Git installation validation failed', undefined, mcpError);
209 |       throw mcpError;
210 |     }
211 |   }
212 | }
213 | 
```

--------------------------------------------------------------------------------
/src/caching/cache.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { logger } from '../utils/logger.js';
  2 | import { PerformanceMonitor } from '../monitoring/performance.js';
  3 | 
  4 | /**
  5 |  * Cache entry with metadata
  6 |  */
  7 | interface CacheEntry<T> {
  8 |   value: T;
  9 |   timestamp: number;
 10 |   ttl: number;
 11 |   hits: number;
 12 |   lastAccess: number;
 13 | }
 14 | 
 15 | /**
 16 |  * Cache configuration
 17 |  */
 18 | interface CacheConfig {
 19 |   defaultTTL: number;        // Default time-to-live in milliseconds
 20 |   maxSize: number;          // Maximum number of entries
 21 |   cleanupInterval: number;  // Cleanup interval in milliseconds
 22 | }
 23 | 
 24 | /**
 25 |  * Default cache configuration
 26 |  */
 27 | const DEFAULT_CONFIG: CacheConfig = {
 28 |   defaultTTL: 5 * 60 * 1000,  // 5 minutes
 29 |   maxSize: 1000,              // 1000 entries
 30 |   cleanupInterval: 60 * 1000  // 1 minute
 31 | };
 32 | 
 33 | /**
 34 |  * Generic cache implementation with performance monitoring
 35 |  */
 36 | export class Cache<T> {
 37 |   private entries: Map<string, CacheEntry<T>> = new Map();
 38 |   private config: CacheConfig;
 39 |   private performanceMonitor: PerformanceMonitor;
 40 |   private readonly cacheType: string;
 41 | 
 42 |   constructor(cacheType: string, config: Partial<CacheConfig> = {}) {
 43 |     this.config = { ...DEFAULT_CONFIG, ...config };
 44 |     this.cacheType = cacheType;
 45 |     this.performanceMonitor = PerformanceMonitor.getInstance();
 46 |     this.startCleanup();
 47 |   }
 48 | 
 49 |   /**
 50 |    * Set a cache entry
 51 |    */
 52 |   set(key: string, value: T, ttl: number = this.config.defaultTTL): void {
 53 |     // Check cache size limit
 54 |     if (this.entries.size >= this.config.maxSize) {
 55 |       this.evictOldest();
 56 |     }
 57 | 
 58 |     this.entries.set(key, {
 59 |       value,
 60 |       timestamp: Date.now(),
 61 |       ttl,
 62 |       hits: 0,
 63 |       lastAccess: Date.now()
 64 |     });
 65 | 
 66 |     logger.debug(
 67 |       'cache',
 68 |       `Set cache entry: ${key}`,
 69 |       undefined,
 70 |       { cacheType: this.cacheType }
 71 |     );
 72 |   }
 73 | 
 74 |   /**
 75 |    * Get a cache entry
 76 |    */
 77 |   get(key: string): T | undefined {
 78 |     const entry = this.entries.get(key);
 79 |     
 80 |     if (!entry) {
 81 |       this.performanceMonitor.recordCacheAccess(false, this.cacheType);
 82 |       return undefined;
 83 |     }
 84 | 
 85 |     // Check if entry is expired
 86 |     if (this.isExpired(entry)) {
 87 |       this.entries.delete(key);
 88 |       this.performanceMonitor.recordCacheAccess(false, this.cacheType);
 89 |       return undefined;
 90 |     }
 91 | 
 92 |     // Update entry metadata
 93 |     entry.hits++;
 94 |     entry.lastAccess = Date.now();
 95 |     this.performanceMonitor.recordCacheAccess(true, this.cacheType);
 96 | 
 97 |     logger.debug(
 98 |       'cache',
 99 |       `Cache hit: ${key}`,
100 |       undefined,
101 |       { cacheType: this.cacheType, hits: entry.hits }
102 |     );
103 | 
104 |     return entry.value;
105 |   }
106 | 
107 |   /**
108 |    * Delete a cache entry
109 |    */
110 |   delete(key: string): void {
111 |     this.entries.delete(key);
112 |     logger.debug(
113 |       'cache',
114 |       `Deleted cache entry: ${key}`,
115 |       undefined,
116 |       { cacheType: this.cacheType }
117 |     );
118 |   }
119 | 
120 |   /**
121 |    * Clear all cache entries
122 |    */
123 |   clear(): void {
124 |     this.entries.clear();
125 |     logger.info(
126 |       'cache',
127 |       'Cleared cache',
128 |       undefined,
129 |       { cacheType: this.cacheType }
130 |     );
131 |   }
132 | 
133 |   /**
134 |    * Get cache statistics
135 |    */
136 |   getStats(): Record<string, any> {
137 |     const now = Date.now();
138 |     let totalHits = 0;
139 |     let totalSize = 0;
140 |     let oldestTimestamp = now;
141 |     let newestTimestamp = 0;
142 | 
143 |     this.entries.forEach(entry => {
144 |       totalHits += entry.hits;
145 |       totalSize++;
146 |       oldestTimestamp = Math.min(oldestTimestamp, entry.timestamp);
147 |       newestTimestamp = Math.max(newestTimestamp, entry.timestamp);
148 |     });
149 | 
150 |     return {
151 |       size: totalSize,
152 |       maxSize: this.config.maxSize,
153 |       totalHits,
154 |       oldestEntry: oldestTimestamp === now ? null : oldestTimestamp,
155 |       newestEntry: newestTimestamp === 0 ? null : newestTimestamp,
156 |       hitRate: this.performanceMonitor.getCacheHitRate(this.cacheType)
157 |     };
158 |   }
159 | 
160 |   /**
161 |    * Check if a cache entry exists and is valid
162 |    */
163 |   has(key: string): boolean {
164 |     const entry = this.entries.get(key);
165 |     if (!entry) return false;
166 |     if (this.isExpired(entry)) {
167 |       this.entries.delete(key);
168 |       return false;
169 |     }
170 |     return true;
171 |   }
172 | 
173 |   /**
174 |    * Update cache configuration
175 |    */
176 |   updateConfig(config: Partial<CacheConfig>): void {
177 |     this.config = {
178 |       ...this.config,
179 |       ...config
180 |     };
181 |     logger.info(
182 |       'cache',
183 |       'Updated cache configuration',
184 |       undefined,
185 |       { cacheType: this.cacheType, config: this.config }
186 |     );
187 |   }
188 | 
189 |   /**
190 |    * Get current configuration
191 |    */
192 |   getConfig(): CacheConfig {
193 |     return { ...this.config };
194 |   }
195 | 
196 |   /**
197 |    * Check if a cache entry is expired
198 |    */
199 |   private isExpired(entry: CacheEntry<T>): boolean {
200 |     return Date.now() - entry.timestamp > entry.ttl;
201 |   }
202 | 
203 |   /**
204 |    * Evict the least recently used entry
205 |    */
206 |   private evictOldest(): void {
207 |     let oldestKey: string | undefined;
208 |     let oldestAccess = Date.now();
209 | 
210 |     this.entries.forEach((entry, key) => {
211 |       if (entry.lastAccess < oldestAccess) {
212 |         oldestAccess = entry.lastAccess;
213 |         oldestKey = key;
214 |       }
215 |     });
216 | 
217 |     if (oldestKey) {
218 |       this.entries.delete(oldestKey);
219 |       logger.debug(
220 |         'cache',
221 |         `Evicted oldest entry: ${oldestKey}`,
222 |         undefined,
223 |         { cacheType: this.cacheType }
224 |       );
225 |     }
226 |   }
227 | 
228 |   /**
229 |    * Start periodic cache cleanup
230 |    */
231 |   private startCleanup(): void {
232 |     setInterval(() => {
233 |       const now = Date.now();
234 |       let expiredCount = 0;
235 | 
236 |       this.entries.forEach((entry, key) => {
237 |         if (now - entry.timestamp > entry.ttl) {
238 |           this.entries.delete(key);
239 |           expiredCount++;
240 |         }
241 |       });
242 | 
243 |       if (expiredCount > 0) {
244 |         logger.debug(
245 |           'cache',
246 |           `Cleaned up ${expiredCount} expired entries`,
247 |           undefined,
248 |           { cacheType: this.cacheType }
249 |         );
250 |       }
251 |     }, this.config.cleanupInterval);
252 |   }
253 | }
254 | 
255 | /**
256 |  * Repository state cache
257 |  */
258 | export class RepositoryStateCache extends Cache<any> {
259 |   constructor() {
260 |     super('repository_state', {
261 |       defaultTTL: 30 * 1000,  // 30 seconds
262 |       maxSize: 100            // 100 entries
263 |     });
264 |   }
265 | }
266 | 
267 | /**
268 |  * Command result cache
269 |  */
270 | export class CommandResultCache extends Cache<any> {
271 |   constructor() {
272 |     super('command_result', {
273 |       defaultTTL: 5 * 60 * 1000,  // 5 minutes
274 |       maxSize: 500                // 500 entries
275 |     });
276 |   }
277 | 
278 |   /**
279 |    * Generate cache key for a command
280 |    */
281 |   static generateKey(command: string, workingDir?: string): string {
282 |     return `${workingDir || ''}:${command}`;
283 |   }
284 | }
285 | 
```

--------------------------------------------------------------------------------
/src/errors/error-handler.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ErrorCode } from '@modelcontextprotocol/sdk/types.js';
  2 | import {
  3 |   ErrorCategory,
  4 |   ErrorSeverity,
  5 |   ErrorCategoryType,
  6 |   ErrorSeverityType,
  7 |   GitMcpError,
  8 |   ErrorContext
  9 | } from './error-types.js';
 10 | import { logger } from '../utils/logger.js';
 11 | 
 12 | /**
 13 |  * Maps error categories to appropriate MCP error codes
 14 |  */
 15 | function getMcpErrorCode(category: ErrorCategoryType, severity: ErrorSeverityType): ErrorCode {
 16 |   switch (category) {
 17 |     case ErrorCategory.VALIDATION:
 18 |       return ErrorCode.InvalidParams;
 19 |     case ErrorCategory.SYSTEM:
 20 |     case ErrorCategory.OPERATION:
 21 |     case ErrorCategory.NETWORK:
 22 |     case ErrorCategory.SECURITY:
 23 |       return ErrorCode.InternalError;
 24 |     case ErrorCategory.REPOSITORY:
 25 |       // For repository state errors, use InvalidParams if it's a configuration issue,
 26 |       // otherwise use InternalError
 27 |       return severity === ErrorSeverity.MEDIUM ? ErrorCode.InvalidParams : ErrorCode.InternalError;
 28 |     case ErrorCategory.CONFIGURATION:
 29 |       return ErrorCode.InvalidParams;
 30 |     default:
 31 |       return ErrorCode.InternalError;
 32 |   }
 33 | }
 34 | 
 35 | /**
 36 |  * Handles and logs errors with appropriate context and recovery steps
 37 |  */
 38 | export class ErrorHandler {
 39 |   /**
 40 |    * Creates a GitMcpError with appropriate context and logs it
 41 |    */
 42 |   static handleError(
 43 |     error: Error | GitMcpError,
 44 |     category: ErrorCategoryType,
 45 |     severity: ErrorSeverityType,
 46 |     context: Partial<ErrorContext>
 47 |   ): GitMcpError {
 48 |     // If it's already a GitMcpError, just log and return it
 49 |     if (error instanceof GitMcpError) {
 50 |       this.logError(error);
 51 |       return error;
 52 |     }
 53 | 
 54 |     // Create new GitMcpError with context
 55 |     const errorContext: Partial<ErrorContext> = {
 56 |       ...context,
 57 |       stackTrace: error.stack,
 58 |       timestamp: Date.now()
 59 |     };
 60 | 
 61 |     const gitError = new GitMcpError(
 62 |       getMcpErrorCode(category, severity),
 63 |       error.message,
 64 |       severity,
 65 |       category,
 66 |       errorContext
 67 |     );
 68 | 
 69 |     this.logError(gitError);
 70 |     return gitError;
 71 |   }
 72 | 
 73 |   /**
 74 |    * Logs error with full context and recovery steps
 75 |    */
 76 |   private static logError(error: GitMcpError): void {
 77 |     const errorInfo = {
 78 |       name: error.name,
 79 |       message: error.message,
 80 |       severity: error.severity,
 81 |       category: error.category,
 82 |       context: error.context,
 83 |       recoverySteps: error.getRecoverySteps()
 84 |     };
 85 | 
 86 |     // Log based on severity
 87 |     switch (error.severity) {
 88 |       case ErrorSeverity.CRITICAL:
 89 |         logger.error(
 90 |           error.context.operation,
 91 |           `CRITICAL: ${error.message}`,
 92 |           error.context.path,
 93 |           error,
 94 |           errorInfo
 95 |         );
 96 |         break;
 97 |       case ErrorSeverity.HIGH:
 98 |         logger.error(
 99 |           error.context.operation,
100 |           `HIGH: ${error.message}`,
101 |           error.context.path,
102 |           error,
103 |           errorInfo
104 |         );
105 |         break;
106 |       case ErrorSeverity.MEDIUM:
107 |         logger.warn(
108 |           error.context.operation,
109 |           `MEDIUM: ${error.message}`,
110 |           error.context.path,
111 |           error,
112 |           errorInfo
113 |         );
114 |         break;
115 |       case ErrorSeverity.LOW:
116 |         logger.warn(
117 |           error.context.operation,
118 |           `LOW: ${error.message}`,
119 |           error.context.path,
120 |           error,
121 |           errorInfo
122 |         );
123 |         break;
124 |     }
125 |   }
126 | 
127 |   /**
128 |    * Creates and handles a system error
129 |    */
130 |   static handleSystemError(error: Error, context: Partial<ErrorContext>): GitMcpError {
131 |     return this.handleError(error, ErrorCategory.SYSTEM, ErrorSeverity.CRITICAL, context);
132 |   }
133 | 
134 |   /**
135 |    * Creates and handles a validation error
136 |    */
137 |   static handleValidationError(error: Error, context: Partial<ErrorContext>): GitMcpError {
138 |     return this.handleError(error, ErrorCategory.VALIDATION, ErrorSeverity.HIGH, context);
139 |   }
140 | 
141 |   /**
142 |    * Creates and handles an operation error
143 |    */
144 |   static handleOperationError(error: Error, context: Partial<ErrorContext>): GitMcpError {
145 |     return this.handleError(error, ErrorCategory.OPERATION, ErrorSeverity.HIGH, context);
146 |   }
147 | 
148 |   /**
149 |    * Creates and handles a repository error
150 |    */
151 |   static handleRepositoryError(error: Error, context: Partial<ErrorContext>): GitMcpError {
152 |     return this.handleError(error, ErrorCategory.REPOSITORY, ErrorSeverity.HIGH, context);
153 |   }
154 | 
155 |   /**
156 |    * Creates and handles a network error
157 |    */
158 |   static handleNetworkError(error: Error, context: Partial<ErrorContext>): GitMcpError {
159 |     return this.handleError(error, ErrorCategory.NETWORK, ErrorSeverity.HIGH, context);
160 |   }
161 | 
162 |   /**
163 |    * Creates and handles a configuration error
164 |    */
165 |   static handleConfigError(error: Error, context: Partial<ErrorContext>): GitMcpError {
166 |     return this.handleError(error, ErrorCategory.CONFIGURATION, ErrorSeverity.MEDIUM, context);
167 |   }
168 | 
169 |   /**
170 |    * Creates and handles a security error
171 |    */
172 |   static handleSecurityError(error: Error, context: Partial<ErrorContext>): GitMcpError {
173 |     return this.handleError(error, ErrorCategory.SECURITY, ErrorSeverity.CRITICAL, context);
174 |   }
175 | 
176 |   /**
177 |    * Determines if an error is retryable based on its category and severity
178 |    */
179 |   static isRetryable(error: GitMcpError): boolean {
180 |     // Never retry validation or security errors
181 |     if (
182 |       error.category === ErrorCategory.VALIDATION ||
183 |       error.category === ErrorCategory.SECURITY ||
184 |       error.severity === ErrorSeverity.CRITICAL
185 |     ) {
186 |       return false;
187 |     }
188 | 
189 |     // Network errors are usually retryable
190 |     if (error.category === ErrorCategory.NETWORK) {
191 |       return true;
192 |     }
193 | 
194 |     // Repository and operation errors are retryable for non-critical severities
195 |     if (
196 |       (error.category === ErrorCategory.REPOSITORY ||
197 |        error.category === ErrorCategory.OPERATION) &&
198 |       [ErrorSeverity.HIGH, ErrorSeverity.MEDIUM, ErrorSeverity.LOW].includes(error.severity as any)
199 |     ) {
200 |       return true;
201 |     }
202 | 
203 |     return false;
204 |   }
205 | 
206 |   /**
207 |    * Gets suggested retry delay in milliseconds based on error type
208 |    */
209 |   static getRetryDelay(error: GitMcpError): number {
210 |     if (!this.isRetryable(error)) {
211 |       return 0;
212 |     }
213 | 
214 |     switch (error.category) {
215 |       case ErrorCategory.NETWORK:
216 |         return 1000; // 1 second for network issues
217 |       case ErrorCategory.REPOSITORY:
218 |         return 500;  // 500ms for repository issues
219 |       case ErrorCategory.OPERATION:
220 |         return 200;  // 200ms for operation issues
221 |       default:
222 |         return 1000; // Default 1 second
223 |     }
224 |   }
225 | }
226 | 
```

--------------------------------------------------------------------------------
/src/operations/branch/branch-operations.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { BaseGitOperation } from '../base/base-operation.js';
  2 | import { GitCommandBuilder } from '../../common/command-builder.js';
  3 | import { CommandResult } from '../base/operation-result.js';
  4 | import { ErrorHandler } from '../../errors/error-handler.js';
  5 | import { RepositoryValidator } from '../../utils/repository.js';
  6 | import { RepoStateType } from '../../caching/repository-cache.js';
  7 | import {
  8 |   BranchListOptions,
  9 |   BranchCreateOptions,
 10 |   BranchDeleteOptions,
 11 |   CheckoutOptions,
 12 |   BranchListResult,
 13 |   BranchCreateResult,
 14 |   BranchDeleteResult,
 15 |   CheckoutResult,
 16 |   BranchInfo
 17 | } from './branch-types.js';
 18 | 
 19 | /**
 20 |  * Handles Git branch listing operations
 21 |  */
 22 | export class BranchListOperation extends BaseGitOperation<BranchListOptions, BranchListResult> {
 23 |   protected buildCommand(): GitCommandBuilder {
 24 |     const command = GitCommandBuilder.branch();
 25 | 
 26 |     // Add format option for parsing
 27 |     command.option('format', '%(refname:short)|%(upstream:short)|%(objectname:short)|%(subject)');
 28 | 
 29 |     if (this.options.remotes) {
 30 |       command.flag('remotes');
 31 |     }
 32 |     if (this.options.all) {
 33 |       command.flag('all');
 34 |     }
 35 |     if (this.options.contains) {
 36 |       command.option('contains', this.options.contains);
 37 |     }
 38 |     if (this.options.merged) {
 39 |       command.option('merged', this.options.merged);
 40 |     }
 41 |     if (this.options.noMerged) {
 42 |       command.option('no-merged', this.options.noMerged);
 43 |     }
 44 | 
 45 |     return command;
 46 |   }
 47 | 
 48 |   protected parseResult(result: CommandResult): BranchListResult {
 49 |     const branches: BranchInfo[] = [];
 50 |     let current = '';
 51 | 
 52 |     // Parse each line of output
 53 |     result.stdout.split('\n').filter(Boolean).forEach(line => {
 54 |       const [name, tracking, commit, message] = line.split('|');
 55 |       const isCurrent = name.startsWith('* ');
 56 |       const cleanName = name.replace('* ', '');
 57 |       
 58 |       const branch: BranchInfo = {
 59 |         name: cleanName,
 60 |         current: isCurrent,
 61 |         tracking: tracking || undefined,
 62 |         remote: cleanName.includes('origin/'),
 63 |         commit: commit || undefined,
 64 |         message: message || undefined
 65 |       };
 66 | 
 67 |       if (isCurrent) {
 68 |         current = cleanName;
 69 |       }
 70 | 
 71 |       branches.push(branch);
 72 |     });
 73 | 
 74 |     return {
 75 |       current,
 76 |       branches,
 77 |       raw: result.stdout
 78 |     };
 79 |   }
 80 | 
 81 |   protected getCacheConfig() {
 82 |     return {
 83 |       command: 'branch',
 84 |       stateType: RepoStateType.BRANCH
 85 |     };
 86 |   }
 87 | 
 88 |   protected validateOptions(): void {
 89 |     // No specific validation needed for listing
 90 |   }
 91 | }
 92 | 
 93 | /**
 94 |  * Handles Git branch creation operations
 95 |  */
 96 | export class BranchCreateOperation extends BaseGitOperation<BranchCreateOptions, BranchCreateResult> {
 97 |   protected buildCommand(): GitCommandBuilder {
 98 |     const command = GitCommandBuilder.branch()
 99 |       .arg(this.options.name);
100 | 
101 |     if (this.options.startPoint) {
102 |       command.arg(this.options.startPoint);
103 |     }
104 | 
105 |     if (this.options.force) {
106 |       command.withForce();
107 |     }
108 | 
109 |     if (this.options.track) {
110 |       command.withTrack();
111 |     } else {
112 |       command.withNoTrack();
113 |     }
114 | 
115 |     if (this.options.setUpstream) {
116 |       command.withSetUpstream();
117 |     }
118 | 
119 |     return command;
120 |   }
121 | 
122 |   protected parseResult(result: CommandResult): BranchCreateResult {
123 |     return {
124 |       name: this.options.name,
125 |       startPoint: this.options.startPoint,
126 |       tracking: result.stdout.includes('set up to track') ? 
127 |         result.stdout.match(/track\s+([^\s]+)/)?.[1] : undefined,
128 |       raw: result.stdout
129 |     };
130 |   }
131 | 
132 |   protected getCacheConfig() {
133 |     return {
134 |       command: 'branch_create',
135 |       stateType: RepoStateType.BRANCH
136 |     };
137 |   }
138 | 
139 |   protected validateOptions(): void {
140 |     if (!this.options.name) {
141 |       throw ErrorHandler.handleValidationError(
142 |         new Error('Branch name is required'),
143 |         { operation: this.context.operation }
144 |       );
145 |     }
146 |   }
147 | }
148 | 
149 | /**
150 |  * Handles Git branch deletion operations
151 |  */
152 | export class BranchDeleteOperation extends BaseGitOperation<BranchDeleteOptions, BranchDeleteResult> {
153 |   protected async buildCommand(): Promise<GitCommandBuilder> {
154 |     const command = GitCommandBuilder.branch();
155 | 
156 |     // Use -D for force delete, -d for safe delete
157 |     command.flag(this.options.force ? 'D' : 'd')
158 |       .arg(this.options.name);
159 | 
160 |     if (this.options.remote) {
161 |       // Get remote name from branch if it's a remote branch
162 |       const remoteName = this.options.name.split('/')[0];
163 |       if (remoteName) {
164 |         await RepositoryValidator.validateRemoteConfig(
165 |           this.getResolvedPath(),
166 |           remoteName,
167 |           this.context.operation
168 |         );
169 |       }
170 |       command.flag('r');
171 |     }
172 | 
173 |     return command;
174 |   }
175 | 
176 |   protected parseResult(result: CommandResult): BranchDeleteResult {
177 |     return {
178 |       name: this.options.name,
179 |       forced: this.options.force || false,
180 |       raw: result.stdout
181 |     };
182 |   }
183 | 
184 |   protected getCacheConfig() {
185 |     return {
186 |       command: 'branch_delete',
187 |       stateType: RepoStateType.BRANCH
188 |     };
189 |   }
190 | 
191 |   protected async validateOptions(): Promise<void> {
192 |     if (!this.options.name) {
193 |       throw ErrorHandler.handleValidationError(
194 |         new Error('Branch name is required'),
195 |         { operation: this.context.operation }
196 |       );
197 |     }
198 | 
199 |     // Ensure branch exists
200 |     await RepositoryValidator.validateBranchExists(
201 |       this.getResolvedPath(),
202 |       this.options.name,
203 |       this.context.operation
204 |     );
205 | 
206 |     // Cannot delete current branch
207 |     const currentBranch = await RepositoryValidator.getCurrentBranch(
208 |       this.getResolvedPath(),
209 |       this.context.operation
210 |     );
211 |     if (currentBranch === this.options.name) {
212 |       throw ErrorHandler.handleValidationError(
213 |         new Error(`Cannot delete the currently checked out branch: ${this.options.name}`),
214 |         { operation: this.context.operation }
215 |       );
216 |     }
217 |   }
218 | 
219 | }
220 | 
221 | /**
222 |  * Handles Git checkout operations
223 |  */
224 | export class CheckoutOperation extends BaseGitOperation<CheckoutOptions, CheckoutResult> {
225 |   protected async buildCommand(): Promise<GitCommandBuilder> {
226 |     const command = GitCommandBuilder.checkout();
227 | 
228 |     if (this.options.newBranch) {
229 |       command.flag('b').arg(this.options.newBranch);
230 |       if (this.options.track) {
231 |         command.withTrack();
232 |       }
233 |     }
234 | 
235 |     command.arg(this.options.target);
236 | 
237 |     if (this.options.force) {
238 |       command.withForce();
239 |     }
240 | 
241 |     return command;
242 |   }
243 | 
244 |   protected parseResult(result: CommandResult): CheckoutResult {
245 |     const previousHead = result.stdout.match(/HEAD is now at ([a-f0-9]+)/)?.[1];
246 |     const newBranch = result.stdout.includes('Switched to a new branch') ? 
247 |       this.options.newBranch : undefined;
248 | 
249 |     return {
250 |       target: this.options.target,
251 |       newBranch,
252 |       previousHead,
253 |       raw: result.stdout
254 |     };
255 |   }
256 | 
257 |   protected getCacheConfig() {
258 |     return {
259 |       command: 'checkout',
260 |       stateType: RepoStateType.BRANCH
261 |     };
262 |   }
263 | 
264 |   protected async validateOptions(): Promise<void> {
265 |     if (!this.options.target) {
266 |       throw ErrorHandler.handleValidationError(
267 |         new Error('Checkout target is required'),
268 |         { operation: this.context.operation }
269 |       );
270 |     }
271 | 
272 |     // Ensure working tree is clean unless force is specified
273 |     if (!this.options.force) {
274 |       await RepositoryValidator.ensureClean(
275 |         this.getResolvedPath(),
276 |         this.context.operation
277 |       );
278 |     }
279 |   }
280 | 
281 | }
282 | 
```

--------------------------------------------------------------------------------
/src/errors/error-types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
  2 | 
  3 | /**
  4 |  * Error severity levels for categorizing errors and determining appropriate responses
  5 |  */
  6 | export const ErrorSeverity = {
  7 |   CRITICAL: 'CRITICAL', // System-level failures requiring immediate attention
  8 |   HIGH: 'HIGH',        // Operation-blocking errors that need urgent handling
  9 |   MEDIUM: 'MEDIUM',    // Non-blocking errors that should be addressed
 10 |   LOW: 'LOW'          // Minor issues that can be handled gracefully
 11 | } as const;
 12 | 
 13 | export type ErrorSeverityType = typeof ErrorSeverity[keyof typeof ErrorSeverity];
 14 | 
 15 | /**
 16 |  * Error categories for better error handling and reporting
 17 |  */
 18 | export const ErrorCategory = {
 19 |   SYSTEM: 'SYSTEM',           // System-level errors (file system, process, etc.)
 20 |   VALIDATION: 'VALIDATION',   // Input validation errors
 21 |   OPERATION: 'OPERATION',     // Git operation errors
 22 |   REPOSITORY: 'REPOSITORY',   // Repository state errors
 23 |   NETWORK: 'NETWORK',         // Network-related errors
 24 |   CONFIGURATION: 'CONFIG',    // Configuration errors
 25 |   SECURITY: 'SECURITY'        // Security-related errors
 26 | } as const;
 27 | 
 28 | export type ErrorCategoryType = typeof ErrorCategory[keyof typeof ErrorCategory];
 29 | 
 30 | /**
 31 |  * Extended error context for better error tracking and debugging
 32 |  */
 33 | export interface ErrorContext {
 34 |   operation: string;           // Operation being performed
 35 |   path?: string;              // Path being operated on
 36 |   command?: string;           // Git command being executed
 37 |   timestamp: number;          // Error occurrence timestamp
 38 |   severity: ErrorSeverityType;    // Error severity level
 39 |   category: ErrorCategoryType;    // Error category
 40 |   details?: {
 41 |     currentUsage?: number;
 42 |     threshold?: number;
 43 |     command?: string;
 44 |     exitCode?: number | string;
 45 |     stdout?: string;
 46 |     stderr?: string;
 47 |     config?: string;
 48 |     tool?: string;
 49 |     args?: unknown;
 50 |     [key: string]: unknown;
 51 |   }; // Additional error-specific details
 52 |   recoverySteps?: string[];   // Suggested recovery steps
 53 |   stackTrace?: string;        // Error stack trace
 54 | }
 55 | 
 56 | /**
 57 |  * Base class for all Git MCP server errors
 58 |  */
 59 | export class GitMcpError extends McpError {
 60 |   readonly severity: ErrorSeverityType;
 61 |   readonly category: ErrorCategoryType;
 62 |   readonly context: ErrorContext;
 63 | 
 64 |   constructor(
 65 |     code: ErrorCode,
 66 |     message: string,
 67 |     severity: ErrorSeverityType,
 68 |     category: ErrorCategoryType,
 69 |     context: Partial<ErrorContext>
 70 |   ) {
 71 |     super(code, message);
 72 |     this.name = 'GitMcpError';
 73 |     this.severity = severity;
 74 |     this.category = category;
 75 |     this.context = {
 76 |       operation: context.operation || 'unknown',
 77 |       timestamp: Date.now(),
 78 |       severity,
 79 |       category,
 80 |       ...context
 81 |     };
 82 |   }
 83 | 
 84 |   /**
 85 |    * Get recovery steps based on error type and context
 86 |    */
 87 |   getRecoverySteps(): string[] {
 88 |     return this.context.recoverySteps || this.getDefaultRecoverySteps();
 89 |   }
 90 | 
 91 |   /**
 92 |    * Get default recovery steps based on error category
 93 |    */
 94 |   private getDefaultRecoverySteps(): string[] {
 95 |     switch (this.category) {
 96 |       case ErrorCategory.SYSTEM:
 97 |         return [
 98 |           'Check system permissions and access rights',
 99 |           'Verify file system access',
100 |           'Check available disk space',
101 |           'Ensure required dependencies are installed'
102 |         ];
103 |       case ErrorCategory.VALIDATION:
104 |         return [
105 |           'Verify input parameters are correct',
106 |           'Check path formatting and permissions',
107 |           'Ensure all required fields are provided'
108 |         ];
109 |       case ErrorCategory.OPERATION:
110 |         return [
111 |           'Verify Git command syntax',
112 |           'Check repository state',
113 |           'Ensure working directory is clean',
114 |           'Try running git status for more information'
115 |         ];
116 |       case ErrorCategory.REPOSITORY:
117 |         return [
118 |           'Verify repository exists and is accessible',
119 |           'Check repository permissions',
120 |           'Ensure .git directory is intact',
121 |           'Try reinitializing the repository'
122 |         ];
123 |       case ErrorCategory.NETWORK:
124 |         return [
125 |           'Check network connectivity',
126 |           'Verify remote repository access',
127 |           'Check authentication credentials',
128 |           'Try using git remote -v to verify remote configuration'
129 |         ];
130 |       case ErrorCategory.CONFIGURATION:
131 |         return [
132 |           'Check Git configuration',
133 |           'Verify environment variables',
134 |           'Ensure required settings are configured',
135 |           'Try git config --list to view current configuration'
136 |         ];
137 |       case ErrorCategory.SECURITY:
138 |         return [
139 |           'Check file and directory permissions',
140 |           'Verify authentication credentials',
141 |           'Ensure secure connection to remote',
142 |           'Review security settings'
143 |         ];
144 |       default:
145 |         return [
146 |           'Check operation parameters',
147 |           'Verify system state',
148 |           'Review error message details',
149 |           'Contact support if issue persists'
150 |         ];
151 |     }
152 |   }
153 | 
154 |   /**
155 |    * Format error for logging
156 |    */
157 |   toJSON(): Record<string, any> {
158 |     return {
159 |       name: this.name,
160 |       message: this.message,
161 |       code: this.code,
162 |       severity: this.severity,
163 |       category: this.category,
164 |       context: this.context,
165 |       recoverySteps: this.getRecoverySteps(),
166 |       stack: this.stack
167 |     };
168 |   }
169 | }
170 | 
171 | /**
172 |  * System-level errors
173 |  */
174 | export class SystemError extends GitMcpError {
175 |   constructor(message: string, context: Partial<ErrorContext>) {
176 |     super(
177 |       ErrorCode.InternalError,
178 |       message,
179 |       ErrorSeverity.CRITICAL,
180 |       ErrorCategory.SYSTEM,
181 |       context
182 |     );
183 |     this.name = 'SystemError';
184 |   }
185 | }
186 | 
187 | /**
188 |  * Validation errors
189 |  */
190 | export class ValidationError extends GitMcpError {
191 |   constructor(message: string, context: Partial<ErrorContext>) {
192 |     super(
193 |       ErrorCode.InvalidParams,
194 |       message,
195 |       ErrorSeverity.HIGH,
196 |       ErrorCategory.VALIDATION,
197 |       context
198 |     );
199 |     this.name = 'ValidationError';
200 |   }
201 | }
202 | 
203 | /**
204 |  * Git operation errors
205 |  */
206 | export class OperationError extends GitMcpError {
207 |   constructor(message: string, context: Partial<ErrorContext>) {
208 |     super(
209 |       ErrorCode.InternalError,
210 |       message,
211 |       ErrorSeverity.HIGH,
212 |       ErrorCategory.OPERATION,
213 |       context
214 |     );
215 |     this.name = 'OperationError';
216 |   }
217 | }
218 | 
219 | /**
220 |  * Repository state errors
221 |  */
222 | export class RepositoryError extends GitMcpError {
223 |   constructor(message: string, context: Partial<ErrorContext>) {
224 |     super(
225 |       ErrorCode.InternalError,
226 |       message,
227 |       ErrorSeverity.HIGH,
228 |       ErrorCategory.REPOSITORY,
229 |       context
230 |     );
231 |     this.name = 'RepositoryError';
232 |   }
233 | }
234 | 
235 | /**
236 |  * Network-related errors
237 |  */
238 | export class NetworkError extends GitMcpError {
239 |   constructor(message: string, context: Partial<ErrorContext>) {
240 |     super(
241 |       ErrorCode.InternalError,
242 |       message,
243 |       ErrorSeverity.HIGH,
244 |       ErrorCategory.NETWORK,
245 |       context
246 |     );
247 |     this.name = 'NetworkError';
248 |   }
249 | }
250 | 
251 | /**
252 |  * Configuration errors
253 |  */
254 | export class ConfigurationError extends GitMcpError {
255 |   constructor(message: string, context: Partial<ErrorContext>) {
256 |     super(
257 |       ErrorCode.InvalidParams,
258 |       message,
259 |       ErrorSeverity.MEDIUM,
260 |       ErrorCategory.CONFIGURATION,
261 |       context
262 |     );
263 |     this.name = 'ConfigurationError';
264 |   }
265 | }
266 | 
267 | /**
268 |  * Security-related errors
269 |  */
270 | export class SecurityError extends GitMcpError {
271 |   constructor(message: string, context: Partial<ErrorContext>) {
272 |     super(
273 |       ErrorCode.InternalError,
274 |       message,
275 |       ErrorSeverity.CRITICAL,
276 |       ErrorCategory.SECURITY,
277 |       context
278 |     );
279 |     this.name = 'SecurityError';
280 |   }
281 | }
282 | 
```

--------------------------------------------------------------------------------
/src/operations/working-tree/working-tree-operations.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { BaseGitOperation } from '../base/base-operation.js';
  2 | import { GitCommandBuilder } from '../../common/command-builder.js';
  3 | import { CommandResult } from '../base/operation-result.js';
  4 | import { ErrorHandler } from '../../errors/error-handler.js';
  5 | import { RepositoryValidator } from '../../utils/repository.js';
  6 | import { CommandExecutor } from '../../utils/command.js';
  7 | import { RepoStateType } from '../../caching/repository-cache.js';
  8 | import {
  9 |   AddOptions,
 10 |   CommitOptions,
 11 |   StatusOptions,
 12 |   AddResult,
 13 |   CommitResult,
 14 |   StatusResult,
 15 |   FileChange
 16 | } from './working-tree-types.js';
 17 | 
 18 | /**
 19 |  * Handles Git add operations
 20 |  */
 21 | export class AddOperation extends BaseGitOperation<AddOptions, AddResult> {
 22 |   protected buildCommand(): GitCommandBuilder {
 23 |     const command = GitCommandBuilder.add();
 24 | 
 25 |     if (this.options.all) {
 26 |       command.flag('all');
 27 |     }
 28 | 
 29 |     if (this.options.update) {
 30 |       command.flag('update');
 31 |     }
 32 | 
 33 |     if (this.options.ignoreRemoval) {
 34 |       command.flag('no-all');
 35 |     }
 36 | 
 37 |     if (this.options.force) {
 38 |       command.withForce();
 39 |     }
 40 | 
 41 |     if (this.options.dryRun) {
 42 |       command.flag('dry-run');
 43 |     }
 44 | 
 45 |     // Add files
 46 |     this.options.files.forEach(file => command.arg(file));
 47 | 
 48 |     return command;
 49 |   }
 50 | 
 51 |   protected parseResult(result: CommandResult): AddResult {
 52 |     const staged: string[] = [];
 53 |     const notStaged: Array<{ path: string; reason: string }> = [];
 54 | 
 55 |     // Parse output to determine which files were staged
 56 |     result.stdout.split('\n').forEach(line => {
 57 |       const match = line.match(/^add '(.+)'$/);
 58 |       if (match) {
 59 |         staged.push(match[1]);
 60 |       } else if (line.includes('error:')) {
 61 |         const errorMatch = line.match(/error: (.+?) '(.+?)'/);
 62 |         if (errorMatch) {
 63 |           notStaged.push({
 64 |             path: errorMatch[2],
 65 |             reason: errorMatch[1]
 66 |           });
 67 |         }
 68 |       }
 69 |     });
 70 | 
 71 |     return {
 72 |       staged,
 73 |       notStaged: notStaged.length > 0 ? notStaged : undefined,
 74 |       raw: result.stdout
 75 |     };
 76 |   }
 77 | 
 78 |   protected getCacheConfig() {
 79 |     return {
 80 |       command: 'add',
 81 |       stateType: RepoStateType.STATUS
 82 |     };
 83 |   }
 84 | 
 85 |   protected validateOptions(): void {
 86 |     if (!this.options.files || this.options.files.length === 0) {
 87 |       throw ErrorHandler.handleValidationError(
 88 |         new Error('At least one file must be specified'),
 89 |         { operation: this.context.operation }
 90 |       );
 91 |     }
 92 |   }
 93 | }
 94 | 
 95 | /**
 96 |  * Handles Git commit operations
 97 |  */
 98 | export class CommitOperation extends BaseGitOperation<CommitOptions, CommitResult> {
 99 |   protected buildCommand(): GitCommandBuilder {
100 |     const command = GitCommandBuilder.commit();
101 | 
102 |     command.withMessage(this.options.message);
103 | 
104 |     if (this.options.allowEmpty) {
105 |       command.flag('allow-empty');
106 |     }
107 | 
108 |     if (this.options.amend) {
109 |       command.flag('amend');
110 |     }
111 | 
112 |     if (this.options.noVerify) {
113 |       command.withNoVerify();
114 |     }
115 | 
116 |     if (this.options.author) {
117 |       command.option('author', this.options.author);
118 |     }
119 | 
120 |     // Add specific files if provided
121 |     if (this.options.files) {
122 |       this.options.files.forEach(file => command.arg(file));
123 |     }
124 | 
125 |     return command;
126 |   }
127 | 
128 |   protected parseResult(result: CommandResult): CommitResult {
129 |     const hash = result.stdout.match(/\[.+?(\w+)\]/)?.[1] || '';
130 |     const stats = result.stdout.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
131 | 
132 |     return {
133 |       hash,
134 |       filesChanged: stats ? parseInt(stats[1], 10) : 0,
135 |       insertions: stats && stats[2] ? parseInt(stats[2], 10) : 0,
136 |       deletions: stats && stats[3] ? parseInt(stats[3], 10) : 0,
137 |       amended: this.options.amend || false,
138 |       raw: result.stdout
139 |     };
140 |   }
141 | 
142 |   protected getCacheConfig() {
143 |     return {
144 |       command: 'commit',
145 |       stateType: RepoStateType.STATUS
146 |     };
147 |   }
148 | 
149 |   protected async validateOptions(): Promise<void> {
150 |     if (!this.options.message && !this.options.amend) {
151 |       throw ErrorHandler.handleValidationError(
152 |         new Error('Commit message is required unless amending'),
153 |         { operation: this.context.operation }
154 |       );
155 |     }
156 | 
157 |     // Verify there are staged changes unless allowing empty commits
158 |     if (!this.options.allowEmpty) {
159 |       const statusResult = await CommandExecutor.executeGitCommand(
160 |         'status --porcelain',
161 |         this.context.operation,
162 |         this.getResolvedPath()
163 |       );
164 |       
165 |       if (!statusResult.stdout.trim()) {
166 |         throw ErrorHandler.handleValidationError(
167 |           new Error('No changes to commit'),
168 |           { operation: this.context.operation }
169 |         );
170 |       }
171 |     }
172 |   }
173 | }
174 | 
175 | /**
176 |  * Handles Git status operations
177 |  */
178 | export class StatusOperation extends BaseGitOperation<StatusOptions, StatusResult> {
179 |   protected buildCommand(): GitCommandBuilder {
180 |     const command = GitCommandBuilder.status();
181 | 
182 |     // Use porcelain format for consistent parsing
183 |     command.flag('porcelain');
184 |     command.flag('z'); // Use NUL character as separator
185 | 
186 |     if (this.options.showUntracked) {
187 |       command.flag('untracked-files');
188 |     }
189 | 
190 |     if (this.options.ignoreSubmodules) {
191 |       command.option('ignore-submodules', 'all');
192 |     }
193 | 
194 |     if (this.options.showIgnored) {
195 |       command.flag('ignored');
196 |     }
197 | 
198 |     if (this.options.showBranch) {
199 |       command.flag('branch');
200 |     }
201 | 
202 |     return command;
203 |   }
204 | 
205 |   protected async parseResult(result: CommandResult): Promise<StatusResult> {
206 |     const staged: FileChange[] = [];
207 |     const unstaged: FileChange[] = [];
208 |     const untracked: FileChange[] = [];
209 |     const ignored: FileChange[] = [];
210 | 
211 |     // Get current branch
212 |     const branchResult = await CommandExecutor.executeGitCommand(
213 |       'rev-parse --abbrev-ref HEAD',
214 |       this.context.operation,
215 |       this.getResolvedPath()
216 |     );
217 |     const branch = branchResult.stdout.trim();
218 | 
219 |     // Parse status output
220 |     const entries = result.stdout.split('\0').filter(Boolean);
221 |     for (const entry of entries) {
222 |       const [status, ...pathParts] = entry.split(' ');
223 |       const path = pathParts.join(' ');
224 | 
225 |       const change: FileChange = {
226 |         path,
227 |         type: this.parseChangeType(status),
228 |         staged: status[0] !== ' ' && status[0] !== '?',
229 |         raw: status
230 |       };
231 | 
232 |       // Handle renamed files
233 |       if (change.type === 'renamed') {
234 |         const [oldPath, newPath] = path.split(' -> ');
235 |         change.path = newPath;
236 |         change.originalPath = oldPath;
237 |       }
238 | 
239 |       // Categorize the change
240 |       if (status === '??') {
241 |         untracked.push(change);
242 |       } else if (status === '!!') {
243 |         ignored.push(change);
244 |       } else if (change.staged) {
245 |         staged.push(change);
246 |       } else {
247 |         unstaged.push(change);
248 |       }
249 |     }
250 | 
251 |     return {
252 |       branch,
253 |       clean: staged.length === 0 && unstaged.length === 0 && untracked.length === 0,
254 |       staged,
255 |       unstaged,
256 |       untracked,
257 |       ignored: this.options.showIgnored ? ignored : undefined,
258 |       raw: result.stdout
259 |     };
260 |   }
261 | 
262 |   protected getCacheConfig() {
263 |     return {
264 |       command: 'status',
265 |       stateType: RepoStateType.STATUS
266 |     };
267 |   }
268 | 
269 |   protected validateOptions(): void {
270 |     // No specific validation needed for status
271 |   }
272 | 
273 |   private parseChangeType(status: string): FileChange['type'] {
274 |     const index = status[0];
275 |     const worktree = status[1];
276 | 
277 |     if (status === '??') return 'untracked';
278 |     if (status === '!!') return 'ignored';
279 |     if (index === 'R' || worktree === 'R') return 'renamed';
280 |     if (index === 'C' || worktree === 'C') return 'copied';
281 |     if (index === 'A' || worktree === 'A') return 'added';
282 |     if (index === 'D' || worktree === 'D') return 'deleted';
283 |     return 'modified';
284 |   }
285 | }
286 | 
```

--------------------------------------------------------------------------------
/src/utils/path.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { resolve, isAbsolute, normalize } from 'path';
  2 | import { existsSync, statSync, readdirSync } from 'fs';
  3 | import { ErrorHandler } from '../errors/error-handler.js';
  4 | import { GitMcpError } from '../errors/error-types.js';
  5 | 
  6 | export interface PathValidationOptions {
  7 |   mustExist?: boolean;
  8 |   allowDirectory?: boolean;
  9 |   allowPattern?: boolean;
 10 |   cwd?: string;
 11 |   operation?: string;
 12 | }
 13 | 
 14 | export class PathValidator {
 15 |   static validatePath(path: string, options: PathValidationOptions = {}): string {
 16 |     const { 
 17 |       mustExist = true, 
 18 |       allowDirectory = true, 
 19 |       cwd = process.cwd(),
 20 |       operation = 'path_validation'
 21 |     } = options;
 22 | 
 23 |     try {
 24 |       if (!path || typeof path !== 'string') {
 25 |         throw new Error('Path must be a non-empty string');
 26 |       }
 27 | 
 28 |       // Convert to absolute path if relative
 29 |       const absolutePath = isAbsolute(path) ? normalize(path) : resolve(cwd, path);
 30 | 
 31 |       // Check existence if required
 32 |       if (mustExist && !existsSync(absolutePath)) {
 33 |         throw new Error(`Path does not exist: ${path}`);
 34 |       }
 35 | 
 36 |       // If path exists and is not a pattern, validate type
 37 |       if (existsSync(absolutePath)) {
 38 |         const stats = statSync(absolutePath);
 39 |         if (!allowDirectory && stats.isDirectory()) {
 40 |           throw new Error(`Path is a directory when file expected: ${path}`);
 41 |         }
 42 |       }
 43 | 
 44 |       return absolutePath;
 45 |     } catch (error: unknown) {
 46 |       throw ErrorHandler.handleValidationError(error instanceof Error ? error : new Error('Unknown error'), {
 47 |         operation,
 48 |         path,
 49 |         details: { options }
 50 |       });
 51 |     }
 52 |   }
 53 | 
 54 |   static validateGitRepo(path: string, operation = 'validate_repo'): { path: string; hasEmbeddedRepo: boolean } {
 55 |     try {
 56 |       const absolutePath = this.validatePath(path, { allowDirectory: true, operation });
 57 |       const gitPath = resolve(absolutePath, '.git');
 58 | 
 59 |       if (!existsSync(gitPath)) {
 60 |         throw new Error(`Not a git repository: ${path}`);
 61 |       }
 62 | 
 63 |       if (!statSync(gitPath).isDirectory()) {
 64 |         throw new Error(`Invalid git repository: ${path}`);
 65 |       }
 66 | 
 67 |       // Check for embedded repositories
 68 |       let hasEmbeddedRepo = false;
 69 |       const checkEmbeddedRepos = (dir: string) => {
 70 |         const entries = readdirSync(dir, { withFileTypes: true });
 71 |         for (const entry of entries) {
 72 |           if (entry.isDirectory()) {
 73 |             const fullPath = resolve(dir, entry.name);
 74 |             if (entry.name === '.git' && fullPath !== gitPath) {
 75 |               hasEmbeddedRepo = true;
 76 |               break;
 77 |             }
 78 |             if (entry.name !== '.git' && entry.name !== 'node_modules') {
 79 |               checkEmbeddedRepos(fullPath);
 80 |             }
 81 |           }
 82 |         }
 83 |       };
 84 |       checkEmbeddedRepos(absolutePath);
 85 | 
 86 |       return { path: absolutePath, hasEmbeddedRepo };
 87 |     } catch (error: unknown) {
 88 |       if (error instanceof GitMcpError) throw error;
 89 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
 90 |         operation,
 91 |         path,
 92 |         details: { gitPath: resolve(path, '.git') }
 93 |       });
 94 |     }
 95 |   }
 96 | 
 97 |   static validatePaths(paths: string[], options: PathValidationOptions = {}): string[] {
 98 |     const { 
 99 |       allowPattern = false, 
100 |       cwd = process.cwd(),
101 |       operation = 'validate_paths'
102 |     } = options;
103 | 
104 |     try {
105 |       if (!Array.isArray(paths)) {
106 |         throw new Error('Paths must be an array');
107 |       }
108 | 
109 |       return paths.map(path => {
110 |         if (!path || typeof path !== 'string') {
111 |           throw new Error('Each path must be a non-empty string');
112 |         }
113 | 
114 |         // If patterns are allowed and path contains wildcards, return as-is
115 |         if (allowPattern && /[*?[\]]/.test(path)) {
116 |           return path;
117 |         }
118 | 
119 |         // For relative paths starting with '.', make them relative to the repository root
120 |         if (path.startsWith('.')) {
121 |           // Just return the path as-is to let Git handle it relative to the repo root
122 |           return path;
123 |         }
124 | 
125 |         // Convert to absolute path if relative
126 |         return isAbsolute(path) ? normalize(path) : resolve(cwd, path);
127 |       });
128 |     } catch (error: unknown) {
129 |       throw ErrorHandler.handleValidationError(error instanceof Error ? error : new Error('Unknown error'), {
130 |         operation,
131 |         details: { paths, options }
132 |       });
133 |     }
134 |   }
135 | 
136 |   static validateBranchName(branch: string, operation = 'validate_branch'): void {
137 |     try {
138 |       if (!branch || typeof branch !== 'string') {
139 |         throw new Error('Branch name must be a non-empty string');
140 |       }
141 | 
142 |       // Git branch naming rules
143 |       if (!/^(?!\/|\.|\.\.|@|\{|\}|\[|\]|\\)[\x21-\x7E]+(?<!\.lock|[/.])$/.test(branch)) {
144 |         throw new Error('Invalid branch name format');
145 |       }
146 |     } catch (error: unknown) {
147 |       throw ErrorHandler.handleValidationError(error instanceof Error ? error : new Error('Unknown error'), {
148 |         operation,
149 |         details: { branch }
150 |       });
151 |     }
152 |   }
153 | 
154 |   static validateRemoteName(name: string, operation = 'validate_remote'): void {
155 |     try {
156 |       if (!name || typeof name !== 'string') {
157 |         throw new Error('Remote name must be a non-empty string');
158 |       }
159 | 
160 |       // Git remote naming rules
161 |       if (!/^[a-zA-Z0-9][a-zA-Z0-9-]*$/.test(name)) {
162 |         throw new Error('Invalid remote name format');
163 |       }
164 |     } catch (error: unknown) {
165 |       throw ErrorHandler.handleValidationError(error instanceof Error ? error : new Error('Unknown error'), {
166 |         operation,
167 |         details: { remoteName: name }
168 |       });
169 |     }
170 |   }
171 | 
172 |   static validateRemoteUrl(url: string, operation = 'validate_remote_url'): void {
173 |     try {
174 |       if (!url || typeof url !== 'string') {
175 |         throw new Error('Remote URL must be a non-empty string');
176 |       }
177 | 
178 |       // Basic URL format validation for git URLs
179 |       const gitUrlPattern = /^(git|https?|ssh):\/\/|^git@|^[a-zA-Z0-9_-]+:/;
180 |       if (!gitUrlPattern.test(url)) {
181 |         throw new Error('Invalid git remote URL format');
182 |       }
183 | 
184 |       // Additional security checks for URLs
185 |       const securityPattern = /[<>'";&|]/;
186 |       if (securityPattern.test(url)) {
187 |         throw new Error('Remote URL contains invalid characters');
188 |       }
189 |     } catch (error: unknown) {
190 |       throw ErrorHandler.handleValidationError(error instanceof Error ? error : new Error('Unknown error'), {
191 |         operation,
192 |         details: { 
193 |           url,
194 |           allowedProtocols: ['git', 'https', 'ssh']
195 |         }
196 |       });
197 |     }
198 |   }
199 | 
200 |   static validateTagName(tag: string, operation = 'validate_tag'): void {
201 |     try {
202 |       if (!tag || typeof tag !== 'string') {
203 |         throw new Error('Tag name must be a non-empty string');
204 |       }
205 | 
206 |       // Git tag naming rules
207 |       if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(tag)) {
208 |         throw new Error('Invalid tag name format');
209 |       }
210 | 
211 |       // Additional validation for semantic versioning tags
212 |       if (tag.startsWith('v') && !/^v\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(tag)) {
213 |         throw new Error('Invalid semantic version tag format');
214 |       }
215 |     } catch (error: unknown) {
216 |       throw ErrorHandler.handleValidationError(error instanceof Error ? error : new Error('Unknown error'), {
217 |         operation,
218 |         details: { 
219 |           tag,
220 |           semanticVersioning: tag.startsWith('v')
221 |         }
222 |       });
223 |     }
224 |   }
225 | 
226 |   /**
227 |    * Validates a commit message format
228 |    */
229 |   static validateCommitMessage(message: string, operation = 'validate_commit'): void {
230 |     try {
231 |       if (!message || typeof message !== 'string') {
232 |         throw new Error('Commit message must be a non-empty string');
233 |       }
234 | 
235 |       // Basic commit message format validation
236 |       if (message.length > 72) {
237 |         throw new Error('Commit message exceeds maximum length of 72 characters');
238 |       }
239 | 
240 |       // Check for conventional commit format if it appears to be one
241 |       const conventionalPattern = /^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\(.+\))?: .+/;
242 |       if (message.match(/^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)/) && !conventionalPattern.test(message)) {
243 |         throw new Error('Invalid conventional commit format');
244 |       }
245 |     } catch (error: unknown) {
246 |       throw ErrorHandler.handleValidationError(error instanceof Error ? error : new Error('Unknown error'), {
247 |         operation,
248 |         details: { 
249 |           message,
250 |           isConventionalCommit: message.match(/^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)/) !== null
251 |         }
252 |       });
253 |     }
254 |   }
255 | }
256 | 
```

--------------------------------------------------------------------------------
/src/utils/repository.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { existsSync } from 'fs';
  2 | import { join } from 'path';
  3 | import { CommandExecutor } from './command.js';
  4 | import { ErrorHandler } from '../errors/error-handler.js';
  5 | import { GitMcpError } from '../errors/error-types.js';
  6 | 
  7 | export class RepositoryValidator {
  8 |   /**
  9 |    * Get list of configured remotes
 10 |    */
 11 |   static async getRemotes(path: string, operation: string): Promise<string[]> {
 12 |     const result = await CommandExecutor.executeGitCommand(
 13 |       'remote',
 14 |       operation,
 15 |       path
 16 |     );
 17 |     return result.stdout.split('\n').filter(Boolean);
 18 |   }
 19 | 
 20 |   static async validateLocalRepo(path: string, operation: string): Promise<void> {
 21 |     try {
 22 |       const gitDir = join(path, '.git');
 23 |       if (!existsSync(gitDir)) {
 24 |         throw new Error('Not a git repository');
 25 |       }
 26 |     } catch (error: unknown) {
 27 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
 28 |         operation,
 29 |         path,
 30 |         details: { gitDir: join(path, '.git') }
 31 |       });
 32 |     }
 33 |   }
 34 | 
 35 |   static async validateRemoteRepo(remote: string, operation: string): Promise<void> {
 36 |     try {
 37 |       await CommandExecutor.execute(`git ls-remote ${remote}`, operation);
 38 |     } catch (error: unknown) {
 39 |       if (error instanceof GitMcpError) throw error;
 40 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
 41 |         operation,
 42 |         details: { 
 43 |           remote,
 44 |           action: 'validate_remote_repo'
 45 |         }
 46 |       });
 47 |     }
 48 |   }
 49 | 
 50 |   static async validateBranchExists(path: string, branch: string, operation: string): Promise<void> {
 51 |     try {
 52 |       await CommandExecutor.execute(
 53 |         `git show-ref --verify --quiet refs/heads/${branch}`,
 54 |         operation,
 55 |         path
 56 |       );
 57 |     } catch (error: unknown) {
 58 |       if (error instanceof GitMcpError) throw error;
 59 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
 60 |         operation,
 61 |         path,
 62 |         details: { 
 63 |           branch,
 64 |           action: 'validate_branch_exists'
 65 |         }
 66 |       });
 67 |     }
 68 |   }
 69 | 
 70 |   static async validateRemoteBranchExists(path: string, remote: string, branch: string, operation: string): Promise<void> {
 71 |     try {
 72 |       await CommandExecutor.execute(
 73 |         `git show-ref --verify --quiet refs/remotes/${remote}/${branch}`,
 74 |         operation,
 75 |         path
 76 |       );
 77 |     } catch (error: unknown) {
 78 |       if (error instanceof GitMcpError) throw error;
 79 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
 80 |         operation,
 81 |         path,
 82 |         details: { 
 83 |           remote,
 84 |           branch,
 85 |           action: 'validate_remote_branch_exists'
 86 |         }
 87 |       });
 88 |     }
 89 |   }
 90 | 
 91 |   static async getCurrentBranch(path: string, operation: string): Promise<string> {
 92 |     try {
 93 |       const result = await CommandExecutor.execute('git rev-parse --abbrev-ref HEAD', operation, path);
 94 |       return result.stdout.trim();
 95 |     } catch (error: unknown) {
 96 |       if (error instanceof GitMcpError) throw error;
 97 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
 98 |         operation,
 99 |         path,
100 |         details: { 
101 |           action: 'get_current_branch',
102 |           command: 'git rev-parse --abbrev-ref HEAD'
103 |         }
104 |       });
105 |     }
106 |   }
107 | 
108 |   static async ensureClean(path: string, operation: string): Promise<void> {
109 |     let statusResult;
110 |     try {
111 |       statusResult = await CommandExecutor.execute('git status --porcelain', operation, path);
112 |       if (statusResult.stdout.trim()) {
113 |         throw new Error('Working directory is not clean. Please commit or stash your changes.');
114 |       }
115 |     } catch (error: unknown) {
116 |       if (error instanceof GitMcpError) throw error;
117 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
118 |         operation,
119 |         path,
120 |         details: { 
121 |           action: 'ensure_clean',
122 |           status: statusResult?.stdout || 'unknown'
123 |         }
124 |       });
125 |     }
126 |   }
127 | 
128 |   static async validateRemoteConfig(path: string, remote: string, operation: string): Promise<void> {
129 |     let remoteResult;
130 |     try {
131 |       remoteResult = await CommandExecutor.execute(`git remote get-url ${remote}`, operation, path);
132 |       if (!remoteResult.stdout.trim()) {
133 |         throw new Error(`Remote ${remote} is not configured`);
134 |       }
135 |     } catch (error: unknown) {
136 |       if (error instanceof GitMcpError) throw error;
137 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
138 |         operation,
139 |         path,
140 |         details: { 
141 |           remote,
142 |           action: 'validate_remote_config',
143 |           remoteUrl: remoteResult?.stdout || 'unknown'
144 |         }
145 |       });
146 |     }
147 |   }
148 | 
149 |   static async validateCommitExists(path: string, commit: string, operation: string): Promise<void> {
150 |     try {
151 |       await CommandExecutor.execute(`git cat-file -e ${commit}^{commit}`, operation, path);
152 |     } catch (error: unknown) {
153 |       if (error instanceof GitMcpError) throw error;
154 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
155 |         operation,
156 |         path,
157 |         details: { 
158 |           commit,
159 |           action: 'validate_commit_exists'
160 |         }
161 |       });
162 |     }
163 |   }
164 | 
165 |   static async validateTagExists(path: string, tag: string, operation: string): Promise<void> {
166 |     try {
167 |       await CommandExecutor.execute(`git show-ref --tags --quiet refs/tags/${tag}`, operation, path);
168 |     } catch (error: unknown) {
169 |       if (error instanceof GitMcpError) throw error;
170 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
171 |         operation,
172 |         path,
173 |         details: { 
174 |           tag,
175 |           action: 'validate_tag_exists'
176 |         }
177 |       });
178 |     }
179 |   }
180 | 
181 |   /**
182 |    * Validates repository configuration
183 |    */
184 |   static async validateRepositoryConfig(path: string, operation: string): Promise<void> {
185 |     let configResult;
186 |     try {
187 |       // Check core configuration
188 |       configResult = await CommandExecutor.execute('git config --list', operation, path);
189 |       const config = new Map<string, string>(
190 |         configResult.stdout
191 |           .split('\n')
192 |           .filter(line => line)
193 |           .map(line => {
194 |             const [key, ...values] = line.split('=');
195 |             return [key, values.join('=')] as [string, string];
196 |           })
197 |       );
198 | 
199 |       // Required configurations
200 |       const requiredConfigs = [
201 |         ['core.repositoryformatversion', '0'],
202 |         ['core.filemode', 'true'],
203 |         ['core.bare', 'false']
204 |       ];
205 | 
206 |       for (const [key, value] of requiredConfigs) {
207 |         if (config.get(key) !== value) {
208 |           throw new Error(`Invalid repository configuration: ${key}=${config.get(key) || 'undefined'}`);
209 |         }
210 |       }
211 | 
212 |       // Check repository integrity
213 |       await CommandExecutor.execute('git fsck --full', operation, path);
214 | 
215 |     } catch (error: unknown) {
216 |       if (error instanceof GitMcpError) throw error;
217 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
218 |         operation,
219 |         path,
220 |         details: { 
221 |           action: 'validate_repository_config',
222 |           config: configResult?.stdout || 'unknown'
223 |         }
224 |       });
225 |     }
226 |   }
227 | 
228 |   /**
229 |    * Checks if a repository has any uncommitted changes
230 |    */
231 |   static async hasUncommittedChanges(path: string, operation: string): Promise<boolean> {
232 |     try {
233 |       const result = await CommandExecutor.execute('git status --porcelain', operation, path);
234 |       return result.stdout.trim().length > 0;
235 |     } catch (error: unknown) {
236 |       if (error instanceof GitMcpError) throw error;
237 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
238 |         operation,
239 |         path,
240 |         details: { 
241 |           action: 'check_uncommitted_changes'
242 |         }
243 |       });
244 |     }
245 |   }
246 | 
247 |   /**
248 |    * Gets the repository's current state information
249 |    */
250 |   static async getRepositoryState(path: string, operation: string): Promise<{
251 |     branch: string;
252 |     isClean: boolean;
253 |     hasStashed: boolean;
254 |     remotes: string[];
255 |     lastCommit: string;
256 |   }> {
257 |     try {
258 |       const [branch, isClean, stashList, remoteList, lastCommit] = await Promise.all([
259 |         this.getCurrentBranch(path, operation),
260 |         this.hasUncommittedChanges(path, operation).then(changes => !changes),
261 |         CommandExecutor.execute('git stash list', operation, path),
262 |         CommandExecutor.execute('git remote', operation, path),
263 |         CommandExecutor.execute('git log -1 --format=%H', operation, path)
264 |       ]);
265 | 
266 |       return {
267 |         branch,
268 |         isClean,
269 |         hasStashed: stashList.stdout.trim().length > 0,
270 |         remotes: remoteList.stdout.trim().split('\n').filter(Boolean),
271 |         lastCommit: lastCommit.stdout.trim()
272 |       };
273 |     } catch (error: unknown) {
274 |       if (error instanceof GitMcpError) throw error;
275 |       throw ErrorHandler.handleRepositoryError(error instanceof Error ? error : new Error('Unknown error'), {
276 |         operation,
277 |         path,
278 |         details: { 
279 |           action: 'get_repository_state'
280 |         }
281 |       });
282 |     }
283 |   }
284 | }
285 | 
```

--------------------------------------------------------------------------------
/src/operations/remote/remote-operations.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { BaseGitOperation } from '../base/base-operation.js';
  2 | import { GitCommandBuilder } from '../../common/command-builder.js';
  3 | import { CommandResult } from '../base/operation-result.js';
  4 | import { ErrorHandler } from '../../errors/error-handler.js';
  5 | import { RepositoryValidator } from '../../utils/repository.js';
  6 | import { CommandExecutor } from '../../utils/command.js';
  7 | import { RepoStateType } from '../../caching/repository-cache.js';
  8 | import {
  9 |   RemoteListOptions,
 10 |   RemoteAddOptions,
 11 |   RemoteRemoveOptions,
 12 |   RemoteSetUrlOptions,
 13 |   RemotePruneOptions,
 14 |   RemoteListResult,
 15 |   RemoteAddResult,
 16 |   RemoteRemoveResult,
 17 |   RemoteSetUrlResult,
 18 |   RemotePruneResult,
 19 |   RemoteConfig
 20 | } from './remote-types.js';
 21 | 
 22 | /**
 23 |  * Handles Git remote listing operations
 24 |  */
 25 | export class RemoteListOperation extends BaseGitOperation<RemoteListOptions, RemoteListResult> {
 26 |   protected buildCommand(): GitCommandBuilder {
 27 |     const command = GitCommandBuilder.remote();
 28 | 
 29 |     if (this.options.verbose) {
 30 |       command.flag('verbose');
 31 |     }
 32 | 
 33 |     return command;
 34 |   }
 35 | 
 36 |   protected async parseResult(result: CommandResult): Promise<RemoteListResult> {
 37 |     const remotes: RemoteConfig[] = [];
 38 |     const lines = result.stdout.split('\n').filter(Boolean);
 39 | 
 40 |     for (const line of lines) {
 41 |       const [name, url, purpose] = line.split(/\s+/);
 42 |       
 43 |       // Find or create remote config
 44 |       let remote = remotes.find(r => r.name === name);
 45 |       if (!remote) {
 46 |         remote = {
 47 |           name,
 48 |           fetchUrl: url
 49 |         };
 50 |         remotes.push(remote);
 51 |       }
 52 | 
 53 |       // Set URL based on purpose
 54 |       if (purpose === '(push)') {
 55 |         remote.pushUrl = url;
 56 |       }
 57 | 
 58 |       // Get additional configuration if verbose
 59 |       if (this.options.verbose) {
 60 |         const configResult = await CommandExecutor.executeGitCommand(
 61 |           `config --get-regexp ^remote\\.${name}\\.`,
 62 |           this.context.operation,
 63 |           this.getResolvedPath()
 64 |         );
 65 | 
 66 |         configResult.stdout.split('\n').filter(Boolean).forEach(configLine => {
 67 |           const [key, value] = configLine.split(' ');
 68 |           const configKey = key.split('.')[2];
 69 | 
 70 |           switch (configKey) {
 71 |             case 'tagopt':
 72 |               remote!.fetchTags = value === '--tags';
 73 |               break;
 74 |             case 'mirror':
 75 |               remote!.mirror = value as 'fetch' | 'push';
 76 |               break;
 77 |             case 'fetch':
 78 |               if (!remote!.branches) remote!.branches = [];
 79 |               const branch = value.match(/refs\/heads\/(.+):refs\/remotes\/.+/)?.[1];
 80 |               if (branch) remote!.branches.push(branch);
 81 |               break;
 82 |           }
 83 |         });
 84 |       }
 85 |     }
 86 | 
 87 |     return {
 88 |       remotes,
 89 |       raw: result.stdout
 90 |     };
 91 |   }
 92 | 
 93 |   protected getCacheConfig() {
 94 |     return {
 95 |       command: 'remote',
 96 |       stateType: RepoStateType.REMOTE
 97 |     };
 98 |   }
 99 | 
100 |   protected validateOptions(): void {
101 |     // No specific validation needed for listing
102 |   }
103 | }
104 | 
105 | /**
106 |  * Handles Git remote add operations
107 |  */
108 | export class RemoteAddOperation extends BaseGitOperation<RemoteAddOptions, RemoteAddResult> {
109 |   protected buildCommand(): GitCommandBuilder {
110 |     const command = GitCommandBuilder.remote()
111 |       .arg('add');
112 | 
113 |     if (this.options.fetch) {
114 |       command.flag('fetch');
115 |     }
116 | 
117 |     if (typeof this.options.tags === 'boolean') {
118 |       command.flag(this.options.tags ? 'tags' : 'no-tags');
119 |     }
120 | 
121 |     if (this.options.mirror) {
122 |       command.option('mirror', this.options.mirror);
123 |     }
124 | 
125 |     command.arg(this.options.name)
126 |       .arg(this.options.url);
127 | 
128 |     return command;
129 |   }
130 | 
131 |   protected async parseResult(result: CommandResult): Promise<RemoteAddResult> {
132 |     // Get full remote configuration
133 |     const listOperation = new RemoteListOperation(this.context, { verbose: true });
134 |     const listResult = await listOperation.execute();
135 |     const remotes = listResult.data?.remotes;
136 |     if (!remotes) {
137 |       throw ErrorHandler.handleOperationError(
138 |         new Error('Failed to get remote list'),
139 |         { operation: this.context.operation }
140 |       );
141 |     }
142 |     const remote = remotes.find(r => r.name === this.options.name);
143 | 
144 |     if (!remote) {
145 |       throw ErrorHandler.handleOperationError(
146 |         new Error(`Failed to get configuration for remote ${this.options.name}`),
147 |         { operation: this.context.operation }
148 |       );
149 |     }
150 | 
151 |     return {
152 |       remote,
153 |       raw: result.stdout
154 |     };
155 |   }
156 | 
157 |   protected getCacheConfig() {
158 |     return {
159 |       command: 'remote_add',
160 |       stateType: RepoStateType.REMOTE
161 |     };
162 |   }
163 | 
164 |   protected validateOptions(): void {
165 |     if (!this.options.name) {
166 |       throw ErrorHandler.handleValidationError(
167 |         new Error('Remote name is required'),
168 |         { operation: this.context.operation }
169 |       );
170 |     }
171 | 
172 |     if (!this.options.url) {
173 |       throw ErrorHandler.handleValidationError(
174 |         new Error('Remote URL is required'),
175 |         { operation: this.context.operation }
176 |       );
177 |     }
178 |   }
179 | }
180 | 
181 | /**
182 |  * Handles Git remote remove operations
183 |  */
184 | export class RemoteRemoveOperation extends BaseGitOperation<RemoteRemoveOptions, RemoteRemoveResult> {
185 |   protected buildCommand(): GitCommandBuilder {
186 |     return GitCommandBuilder.remote()
187 |       .arg('remove')
188 |       .arg(this.options.name);
189 |   }
190 | 
191 |   protected parseResult(result: CommandResult): RemoteRemoveResult {
192 |     return {
193 |       name: this.options.name,
194 |       raw: result.stdout
195 |     };
196 |   }
197 | 
198 |   protected getCacheConfig() {
199 |     return {
200 |       command: 'remote_remove',
201 |       stateType: RepoStateType.REMOTE
202 |     };
203 |   }
204 | 
205 |   protected async validateOptions(): Promise<void> {
206 |     if (!this.options.name) {
207 |       throw ErrorHandler.handleValidationError(
208 |         new Error('Remote name is required'),
209 |         { operation: this.context.operation }
210 |       );
211 |     }
212 | 
213 |     // Ensure remote exists
214 |     await RepositoryValidator.validateRemoteConfig(
215 |       this.getResolvedPath(),
216 |       this.options.name,
217 |       this.context.operation
218 |     );
219 |   }
220 | }
221 | 
222 | /**
223 |  * Handles Git remote set-url operations
224 |  */
225 | export class RemoteSetUrlOperation extends BaseGitOperation<RemoteSetUrlOptions, RemoteSetUrlResult> {
226 |   protected buildCommand(): GitCommandBuilder {
227 |     const command = GitCommandBuilder.remote()
228 |       .arg('set-url');
229 | 
230 |     if (this.options.pushUrl) {
231 |       command.flag('push');
232 |     }
233 | 
234 |     if (this.options.add) {
235 |       command.flag('add');
236 |     }
237 | 
238 |     if (this.options.delete) {
239 |       command.flag('delete');
240 |     }
241 | 
242 |     command.arg(this.options.name)
243 |       .arg(this.options.url);
244 | 
245 |     return command;
246 |   }
247 | 
248 |   protected async parseResult(result: CommandResult): Promise<RemoteSetUrlResult> {
249 |     // Get full remote configuration
250 |     const listOperation = new RemoteListOperation(this.context, { verbose: true });
251 |     const listResult = await listOperation.execute();
252 |     const remotes = listResult.data?.remotes;
253 |     if (!remotes) {
254 |       throw ErrorHandler.handleOperationError(
255 |         new Error('Failed to get remote list'),
256 |         { operation: this.context.operation }
257 |       );
258 |     }
259 |     const remote = remotes.find(r => r.name === this.options.name);
260 | 
261 |     if (!remote) {
262 |       throw ErrorHandler.handleOperationError(
263 |         new Error(`Failed to get configuration for remote ${this.options.name}`),
264 |         { operation: this.context.operation }
265 |       );
266 |     }
267 | 
268 |     return {
269 |       remote,
270 |       raw: result.stdout
271 |     };
272 |   }
273 | 
274 |   protected getCacheConfig() {
275 |     return {
276 |       command: 'remote_set_url',
277 |       stateType: RepoStateType.REMOTE
278 |     };
279 |   }
280 | 
281 |   protected async validateOptions(): Promise<void> {
282 |     if (!this.options.name) {
283 |       throw ErrorHandler.handleValidationError(
284 |         new Error('Remote name is required'),
285 |         { operation: this.context.operation }
286 |       );
287 |     }
288 | 
289 |     if (!this.options.url) {
290 |       throw ErrorHandler.handleValidationError(
291 |         new Error('Remote URL is required'),
292 |         { operation: this.context.operation }
293 |       );
294 |     }
295 | 
296 |     // Ensure remote exists
297 |     await RepositoryValidator.validateRemoteConfig(
298 |       this.getResolvedPath(),
299 |       this.options.name,
300 |       this.context.operation
301 |     );
302 |   }
303 | }
304 | 
305 | /**
306 |  * Handles Git remote prune operations
307 |  */
308 | export class RemotePruneOperation extends BaseGitOperation<RemotePruneOptions, RemotePruneResult> {
309 |   protected buildCommand(): GitCommandBuilder {
310 |     const command = GitCommandBuilder.remote()
311 |       .arg('prune');
312 | 
313 |     if (this.options.dryRun) {
314 |       command.flag('dry-run');
315 |     }
316 | 
317 |     command.arg(this.options.name);
318 | 
319 |     return command;
320 |   }
321 | 
322 |   protected parseResult(result: CommandResult): RemotePruneResult {
323 |     const prunedBranches = result.stdout
324 |       .split('\n')
325 |       .filter(line => line.includes('* [pruned] '))
326 |       .map(line => line.match(/\* \[pruned\] (.+)/)?.[1] || '');
327 | 
328 |     return {
329 |       name: this.options.name,
330 |       prunedBranches,
331 |       raw: result.stdout
332 |     };
333 |   }
334 | 
335 |   protected getCacheConfig() {
336 |     return {
337 |       command: 'remote_prune',
338 |       stateType: RepoStateType.REMOTE
339 |     };
340 |   }
341 | 
342 |   protected async validateOptions(): Promise<void> {
343 |     if (!this.options.name) {
344 |       throw ErrorHandler.handleValidationError(
345 |         new Error('Remote name is required'),
346 |         { operation: this.context.operation }
347 |       );
348 |     }
349 | 
350 |     // Ensure remote exists
351 |     await RepositoryValidator.validateRemoteConfig(
352 |       this.getResolvedPath(),
353 |       this.options.name,
354 |       this.context.operation
355 |     );
356 |   }
357 | }
358 | 
```

--------------------------------------------------------------------------------
/src/monitoring/performance.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { logger } from '../utils/logger.js';
  2 | import { ErrorHandler } from '../errors/error-handler.js';
  3 | import { PerformanceError } from './types.js';
  4 | import { ErrorCategory, ErrorSeverity } from '../errors/error-types.js';
  5 | 
  6 | /**
  7 |  * Performance metric types
  8 |  */
  9 | export enum MetricType {
 10 |   OPERATION_DURATION = 'operation_duration',
 11 |   MEMORY_USAGE = 'memory_usage',
 12 |   COMMAND_EXECUTION = 'command_execution',
 13 |   CACHE_HIT = 'cache_hit',
 14 |   CACHE_MISS = 'cache_miss',
 15 |   RESOURCE_USAGE = 'resource_usage'
 16 | }
 17 | 
 18 | /**
 19 |  * Performance metric data structure
 20 |  */
 21 | export interface Metric {
 22 |   type: MetricType;
 23 |   value: number;
 24 |   timestamp: number;
 25 |   labels: Record<string, string>;
 26 |   context?: Record<string, any>;
 27 | }
 28 | 
 29 | /**
 30 |  * Resource usage thresholds
 31 |  */
 32 | export interface ResourceThresholds {
 33 |   memory: {
 34 |     warning: number;   // MB
 35 |     critical: number;  // MB
 36 |   };
 37 |   cpu: {
 38 |     warning: number;   // Percentage
 39 |     critical: number;  // Percentage
 40 |   };
 41 |   operations: {
 42 |     warning: number;   // Operations per second
 43 |     critical: number;  // Operations per second
 44 |   };
 45 | }
 46 | 
 47 | /**
 48 |  * Default resource thresholds
 49 |  */
 50 | const DEFAULT_THRESHOLDS: ResourceThresholds = {
 51 |   memory: {
 52 |     warning: 1024,    // 1GB
 53 |     critical: 2048    // 2GB
 54 |   },
 55 |   cpu: {
 56 |     warning: 70,      // 70%
 57 |     critical: 90      // 90%
 58 |   },
 59 |   operations: {
 60 |     warning: 100,     // 100 ops/sec
 61 |     critical: 200     // 200 ops/sec
 62 |   }
 63 | };
 64 | 
 65 | /**
 66 |  * Performance monitoring system
 67 |  */
 68 | export class PerformanceMonitor {
 69 |   private static instance: PerformanceMonitor;
 70 |   private metrics: Metric[] = [];
 71 |   private thresholds: ResourceThresholds;
 72 |   private operationTimers: Map<string, number> = new Map();
 73 |   private readonly METRICS_RETENTION = 3600; // 1 hour in seconds
 74 |   private readonly METRICS_CLEANUP_INTERVAL = 300; // 5 minutes in seconds
 75 | 
 76 |   private constructor() {
 77 |     this.thresholds = DEFAULT_THRESHOLDS;
 78 |     this.startMetricsCleanup();
 79 |   }
 80 | 
 81 |   /**
 82 |    * Get singleton instance
 83 |    */
 84 |   static getInstance(): PerformanceMonitor {
 85 |     if (!PerformanceMonitor.instance) {
 86 |       PerformanceMonitor.instance = new PerformanceMonitor();
 87 |     }
 88 |     return PerformanceMonitor.instance;
 89 |   }
 90 | 
 91 |   /**
 92 |    * Start operation timing
 93 |    */
 94 |   startOperation(operation: string): void {
 95 |     this.operationTimers.set(operation, performance.now());
 96 |   }
 97 | 
 98 |   /**
 99 |    * End operation timing and record metric
100 |    */
101 |   endOperation(operation: string, context?: Record<string, any>): void {
102 |     const startTime = this.operationTimers.get(operation);
103 |     if (!startTime) {
104 |       logger.warn(operation, 'No start time found for operation timing', undefined, new Error('Missing operation start time'));
105 |       return;
106 |     }
107 | 
108 |     const duration = performance.now() - startTime;
109 |     this.operationTimers.delete(operation);
110 | 
111 |     this.recordMetric({
112 |       type: MetricType.OPERATION_DURATION,
113 |       value: duration,
114 |       timestamp: Date.now(),
115 |       labels: { operation },
116 |       context
117 |     });
118 |   }
119 | 
120 |   /**
121 |    * Record command execution metric
122 |    */
123 |   recordCommandExecution(command: string, duration: number, context?: Record<string, any>): void {
124 |     this.recordMetric({
125 |       type: MetricType.COMMAND_EXECUTION,
126 |       value: duration,
127 |       timestamp: Date.now(),
128 |       labels: { command },
129 |       context
130 |     });
131 |   }
132 | 
133 |   /**
134 |    * Record memory usage metric
135 |    */
136 |   recordMemoryUsage(context?: Record<string, any>): void {
137 |     const memoryUsage = process.memoryUsage();
138 |     const memoryUsageMB = memoryUsage.heapUsed / 1024 / 1024; // Convert to MB
139 |     
140 |     // Record heap usage
141 |     this.recordMetric({
142 |       type: MetricType.MEMORY_USAGE,
143 |       value: memoryUsageMB,
144 |       timestamp: Date.now(),
145 |       labels: { type: 'heap' },
146 |       context: {
147 |         ...context,
148 |         heapTotal: memoryUsage.heapTotal / 1024 / 1024,
149 |         external: memoryUsage.external / 1024 / 1024,
150 |         rss: memoryUsage.rss / 1024 / 1024
151 |       }
152 |     });
153 | 
154 |     // Check thresholds
155 |     this.checkMemoryThresholds(memoryUsageMB);
156 |   }
157 | 
158 |   /**
159 |    * Record resource usage metric
160 |    */
161 |   recordResourceUsage(
162 |     resource: string,
163 |     value: number,
164 |     context?: Record<string, any>
165 |   ): void {
166 |     this.recordMetric({
167 |       type: MetricType.RESOURCE_USAGE,
168 |       value,
169 |       timestamp: Date.now(),
170 |       labels: { resource },
171 |       context
172 |     });
173 |   }
174 | 
175 |   /**
176 |    * Record cache hit/miss
177 |    */
178 |   recordCacheAccess(hit: boolean, cacheType: string, context?: Record<string, any>): void {
179 |     this.recordMetric({
180 |       type: hit ? MetricType.CACHE_HIT : MetricType.CACHE_MISS,
181 |       value: 1,
182 |       timestamp: Date.now(),
183 |       labels: { cacheType },
184 |       context
185 |     });
186 |   }
187 | 
188 |   /**
189 |    * Get metrics for a specific type and time range
190 |    */
191 |   getMetrics(
192 |     type: MetricType,
193 |     startTime: number,
194 |     endTime: number = Date.now()
195 |   ): Metric[] {
196 |     return this.metrics.filter(metric => 
197 |       metric.type === type &&
198 |       metric.timestamp >= startTime &&
199 |       metric.timestamp <= endTime
200 |     );
201 |   }
202 | 
203 |   /**
204 |    * Calculate operation rate (operations per second)
205 |    */
206 |   getOperationRate(operation: string, windowSeconds: number = 60): number {
207 |     const now = Date.now();
208 |     const startTime = now - (windowSeconds * 1000);
209 |     
210 |     const operationMetrics = this.getMetrics(
211 |       MetricType.OPERATION_DURATION,
212 |       startTime,
213 |       now
214 |     ).filter(metric => metric.labels.operation === operation);
215 | 
216 |     return operationMetrics.length / windowSeconds;
217 |   }
218 | 
219 |   /**
220 |    * Get average operation duration
221 |    */
222 |   getAverageOperationDuration(
223 |     operation: string,
224 |     windowSeconds: number = 60
225 |   ): number {
226 |     const now = Date.now();
227 |     const startTime = now - (windowSeconds * 1000);
228 |     
229 |     const operationMetrics = this.getMetrics(
230 |       MetricType.OPERATION_DURATION,
231 |       startTime,
232 |       now
233 |     ).filter(metric => metric.labels.operation === operation);
234 | 
235 |     if (operationMetrics.length === 0) return 0;
236 | 
237 |     const totalDuration = operationMetrics.reduce(
238 |       (sum, metric) => sum + metric.value,
239 |       0
240 |     );
241 |     return totalDuration / operationMetrics.length;
242 |   }
243 | 
244 |   /**
245 |    * Get cache hit rate
246 |    */
247 |   getCacheHitRate(cacheType: string, windowSeconds: number = 60): number {
248 |     const now = Date.now();
249 |     const startTime = now - (windowSeconds * 1000);
250 |     
251 |     const hits = this.getMetrics(MetricType.CACHE_HIT, startTime, now)
252 |       .filter(metric => metric.labels.cacheType === cacheType).length;
253 |     
254 |     const misses = this.getMetrics(MetricType.CACHE_MISS, startTime, now)
255 |       .filter(metric => metric.labels.cacheType === cacheType).length;
256 | 
257 |     const total = hits + misses;
258 |     return total === 0 ? 0 : hits / total;
259 |   }
260 | 
261 |   /**
262 |    * Update resource thresholds
263 |    */
264 |   updateThresholds(thresholds: Partial<ResourceThresholds>): void {
265 |     this.thresholds = {
266 |       ...this.thresholds,
267 |       ...thresholds
268 |     };
269 |   }
270 | 
271 |   /**
272 |    * Get current thresholds
273 |    */
274 |   getThresholds(): ResourceThresholds {
275 |     return { ...this.thresholds };
276 |   }
277 | 
278 |   /**
279 |    * Private helper to record a metric
280 |    */
281 |   private recordMetric(metric: Metric): void {
282 |     this.metrics.push(metric);
283 | 
284 |     // Log high severity metrics
285 |     if (
286 |       metric.type === MetricType.MEMORY_USAGE ||
287 |       metric.type === MetricType.RESOURCE_USAGE
288 |     ) {
289 |       const metricError = new PerformanceError(
290 |         `Recorded ${metric.type} metric`,
291 |         {
292 |           details: {
293 |             value: metric.value,
294 |             labels: metric.labels,
295 |             context: metric.context
296 |           },
297 |           operation: metric.labels.operation || 'performance'
298 |         }
299 |       );
300 |       logger.info(
301 |         metric.labels.operation || 'performance',
302 |         `Recorded ${metric.type} metric`,
303 |         undefined,
304 |         metricError
305 |       );
306 |     }
307 |   }
308 | 
309 |   /**
310 |    * Check memory usage against thresholds
311 |    */
312 |   private checkMemoryThresholds(memoryUsageMB: number): void {
313 |     if (memoryUsageMB >= this.thresholds.memory.critical) {
314 |       const error = new PerformanceError(
315 |         `Critical memory usage: ${memoryUsageMB.toFixed(2)}MB`,
316 |         {
317 |           details: {
318 |             currentUsage: memoryUsageMB,
319 |             threshold: this.thresholds.memory.critical
320 |           },
321 |           operation: 'memory_monitor',
322 |           severity: ErrorSeverity.CRITICAL,
323 |           category: ErrorCategory.SYSTEM
324 |         }
325 |       );
326 |       ErrorHandler.handleSystemError(error, {
327 |         operation: 'memory_monitor',
328 |         severity: ErrorSeverity.CRITICAL,
329 |         category: ErrorCategory.SYSTEM
330 |       });
331 |     } else if (memoryUsageMB >= this.thresholds.memory.warning) {
332 |       const warningError = new PerformanceError(
333 |         `High memory usage: ${memoryUsageMB.toFixed(2)}MB`,
334 |         {
335 |           details: {
336 |             currentUsage: memoryUsageMB,
337 |             threshold: this.thresholds.memory.warning
338 |           },
339 |           operation: 'memory_monitor'
340 |         }
341 |       );
342 |       logger.warn(
343 |         'memory_monitor',
344 |         `High memory usage: ${memoryUsageMB.toFixed(2)}MB`,
345 |         undefined,
346 |         warningError
347 |       );
348 |     }
349 |   }
350 | 
351 |   /**
352 |    * Start periodic metrics cleanup
353 |    */
354 |   private startMetricsCleanup(): void {
355 |     setInterval(() => {
356 |       const cutoffTime = Date.now() - (this.METRICS_RETENTION * 1000);
357 |       this.metrics = this.metrics.filter(metric => metric.timestamp >= cutoffTime);
358 |     }, this.METRICS_CLEANUP_INTERVAL * 1000);
359 |   }
360 | 
361 |   /**
362 |    * Get current performance statistics
363 |    */
364 |   getStatistics(): Record<string, any> {
365 |     const now = Date.now();
366 |     const oneMinuteAgo = now - 60000;
367 |     const fiveMinutesAgo = now - 300000;
368 | 
369 |     return {
370 |       memory: {
371 |         current: process.memoryUsage().heapUsed / 1024 / 1024,
372 |         trend: this.getMetrics(MetricType.MEMORY_USAGE, fiveMinutesAgo)
373 |           .map(m => ({ timestamp: m.timestamp, value: m.value }))
374 |       },
375 |       operations: {
376 |         last1m: this.metrics
377 |           .filter(m => 
378 |             m.type === MetricType.OPERATION_DURATION &&
379 |             m.timestamp >= oneMinuteAgo
380 |           ).length,
381 |         last5m: this.metrics
382 |           .filter(m => 
383 |             m.type === MetricType.OPERATION_DURATION &&
384 |             m.timestamp >= fiveMinutesAgo
385 |           ).length
386 |       },
387 |       cache: {
388 |         hitRate1m: this.getCacheHitRate('all', 60),
389 |         hitRate5m: this.getCacheHitRate('all', 300)
390 |       },
391 |       commandExecutions: {
392 |         last1m: this.metrics
393 |           .filter(m => 
394 |             m.type === MetricType.COMMAND_EXECUTION &&
395 |             m.timestamp >= oneMinuteAgo
396 |           ).length,
397 |         last5m: this.metrics
398 |           .filter(m => 
399 |             m.type === MetricType.COMMAND_EXECUTION &&
400 |             m.timestamp >= fiveMinutesAgo
401 |           ).length
402 |       }
403 |     };
404 |   }
405 | }
406 | 
```
Page 1/2FirstPrevNextLast